mirror of
https://github.com/luxfi/precompile.git
synced 2026-07-27 03:33:45 +00:00
precompile/dex: refuse activation without explicit protocolFeeController
DEX module previously defaulted ProtocolFeeController to Anvil/Foundry account #0 (0xf39Fd6...92266) whose private key (0xac0974...80) is in every Foundry user's ~/.foundry. At Quasar Edition activation, any random caller could have invoked PauseDEX, ResumeDEX, or the irreversible FreezePool on the chain-wide AMM precompile — a chain-wide compromise the cryptographer review caught. Fix is a fail-closed activation gate: - dex/module.go::Configure() returns ErrDEXNoProtocolFeeController when upgrade.json dexConfig omits protocolFeeController (zero address). luxd will refuse to advance past the activation block until the operator populates the field with a real KMS-controlled multisig. - compromisedDevControllers map blocks all ten Anvil deterministic accounts; any explicit attempt to install a public dev key fails with ErrDEXCompromisedController (the address is included in the error). - dex/dex_test.go covers: zero-address rejection, all 10 anvil addresses rejected, a real address activates cleanly, random callers cannot invoke admin functions even with a real controller, and the Configure interface still returns *Config. Same audit pass also wires three previously-stub-only precompiles into the modules.RegisterModule registry so upgrade.json can gate them: - kzg4844 (LP-3665 KZG point-evaluation extensions, ConfigKey=kzg4844Config) - ed25519 (LP-3211, ConfigKey=ed25519Config) — Solana/TON/XRP signature verify lands as a first-class registered precompile alongside the legacy NewModule() metadata wrapper. - secp256r1 (EIP-7212 P256-verify, ConfigKey=secp256r1Config) — stateful adapter wraps the existing Contract.Run(input) into a contract StatefulPrecompiledContract that does gas deduction and strict-PQ refusal uniformly. - modules/registerer.go extends reservedRanges with 0x100-0x1FF (post-EVM-standard EIP band, for P256) and 0x32000...0000 to 0x32FF000...0000 (high-byte LP-3xxx classical-signature page, for ed25519). Both ranges are narrow enough that no other precompile family overlaps. router precompile audit: no Anvil default existed there (RouterConfig already used zero-as-skip semantics rather than zero-as-default). Tests: go test -count=1 -race -timeout 120s ./dex/... ./kzg4844/... \\ ./ed25519/... ./secp256r1/... ./modules/... All pass; full ./... suite (49 packages) green.
This commit is contained in:
+178
@@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2025, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package dex
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/precompile/precompileconfig"
|
||||
)
|
||||
|
||||
// TestDexConfigure_NoProtocolFeeController_RejectsActivation verifies that
|
||||
// Configure() refuses to activate the DEX precompile when the upgrade.json
|
||||
// dexConfig omits protocolFeeController (or sets it to the zero address).
|
||||
//
|
||||
// Regression: before this guard, omitting the field defaulted to the Foundry
|
||||
// Anvil account #0 (0xf39Fd6...92266) whose private key is in every Foundry
|
||||
// installation. That left PauseDEX / ResumeDEX / FreezePool callable by any
|
||||
// caller — a chain-wide AMM compromise at activation.
|
||||
func TestDexConfigure_NoProtocolFeeController_RejectsActivation(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
cfg *Config
|
||||
}{
|
||||
{
|
||||
name: "zero-address protocolFeeController",
|
||||
cfg: &Config{
|
||||
ProtocolFeeController: common.Address{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no protocolFeeController field at all",
|
||||
cfg: &Config{},
|
||||
},
|
||||
}
|
||||
|
||||
cfgr := &configurator{}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := cfgr.Configure(nil, tc.cfg, nil, nil)
|
||||
if !errors.Is(err, ErrDEXNoProtocolFeeController) {
|
||||
t.Fatalf("expected ErrDEXNoProtocolFeeController, got: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDexConfigure_AnvilDefault_RejectsActivation verifies that any of the
|
||||
// well-known Foundry/Anvil deterministic dev accounts is rejected as the
|
||||
// protocolFeeController. These addresses' private keys are public knowledge,
|
||||
// so configuring one as the chain's AMM admin would be a chain-wide
|
||||
// compromise. The blocklist lives in dex/module.go::compromisedDevControllers
|
||||
// and must catch each of them.
|
||||
func TestDexConfigure_AnvilDefault_RejectsActivation(t *testing.T) {
|
||||
anvilAccounts := []string{
|
||||
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", // anvil #0
|
||||
"0x70997970C51812dc3A010C7d01b50e0d17dc79C8", // anvil #1
|
||||
"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC", // anvil #2
|
||||
"0x90F79bf6EB2c4f870365E785982E1f101E93b906", // anvil #3
|
||||
"0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65", // anvil #4
|
||||
"0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc", // anvil #5
|
||||
"0x976EA74026E726554dB657fA54763abd0C3a0aa9", // anvil #6
|
||||
"0x14dC79964da2C08b23698B3D3cc7Ca32193d9955", // anvil #7
|
||||
"0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f", // anvil #8
|
||||
"0xa0Ee7A142d267C1f36714E4a8F75612F20a79720", // anvil #9
|
||||
}
|
||||
|
||||
cfgr := &configurator{}
|
||||
for _, addr := range anvilAccounts {
|
||||
t.Run(addr, func(t *testing.T) {
|
||||
cfg := &Config{
|
||||
ProtocolFeeController: common.HexToAddress(addr),
|
||||
}
|
||||
err := cfgr.Configure(nil, cfg, nil, nil)
|
||||
if !errors.Is(err, ErrDEXCompromisedController) {
|
||||
t.Fatalf("expected ErrDEXCompromisedController for %s, got: %v", addr, err)
|
||||
}
|
||||
// The error message should include the offending address so
|
||||
// operators can see exactly what they put in upgrade.json.
|
||||
if !strings.Contains(err.Error(), addr) && !strings.Contains(err.Error(), strings.ToLower(addr)) {
|
||||
// Address.Hex() uses checksum casing, accept either.
|
||||
expectedChecksum := common.HexToAddress(addr).Hex()
|
||||
if !strings.Contains(err.Error(), expectedChecksum) {
|
||||
t.Errorf("error should include the offending address; got: %v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDexConfigure_RealAddress_ActivatesCleanly verifies that a real (non-
|
||||
// dev-key) protocolFeeController activates the precompile without error.
|
||||
// We use the LUX_MNEMONIC-derived treasury 0x9011E888...4714 as the canonical
|
||||
// transitional placeholder (KMS-controlled, not publicly known).
|
||||
func TestDexConfigure_RealAddress_ActivatesCleanly(t *testing.T) {
|
||||
cfgr := &configurator{}
|
||||
|
||||
// Reset singleton state to be hermetic — Configure() mutates the
|
||||
// singleton PoolManager's protocolFeeController. Save+restore.
|
||||
origController := DEXPrecompile.poolManager.protocolFeeController
|
||||
t.Cleanup(func() {
|
||||
DEXPrecompile.poolManager.protocolFeeController = origController
|
||||
})
|
||||
|
||||
realAddr := common.HexToAddress("0x9011E888251AB053B7bD1cdB598Db4f9DEd94714")
|
||||
cfg := &Config{ProtocolFeeController: realAddr}
|
||||
|
||||
if err := cfgr.Configure(nil, cfg, nil, nil); err != nil {
|
||||
t.Fatalf("Configure with real address should succeed, got: %v", err)
|
||||
}
|
||||
if DEXPrecompile.poolManager.protocolFeeController != realAddr {
|
||||
t.Fatalf(
|
||||
"singleton controller not updated: got %s, want %s",
|
||||
DEXPrecompile.poolManager.protocolFeeController.Hex(), realAddr.Hex(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDexAdmin_OnlyConfiguredController verifies that admin functions
|
||||
// (PauseDEX, ResumeDEX, PausePool, ResumePool, FreezePool) refuse unauthorized
|
||||
// callers — only the address configured as protocolFeeController is allowed.
|
||||
// This complements the activation-time guard: even if a real controller is
|
||||
// configured, a random EVM caller cannot exercise the admin surface.
|
||||
func TestDexAdmin_OnlyConfiguredController(t *testing.T) {
|
||||
configured := common.HexToAddress("0x9011E888251AB053B7bD1cdB598Db4f9DEd94714")
|
||||
random := common.HexToAddress("0xBADBADBADBADBADBADBADBADBADBADBADBADBADB")
|
||||
|
||||
pm := NewPoolManager(&mockEngine{})
|
||||
pm.protocolFeeController = configured
|
||||
stateDB := NewMockStateDB()
|
||||
|
||||
// Initialize a pool so freeze/pause-pool have a target.
|
||||
keyAB, poolID, _, _ := initTwoPoolsPF(t, pm, stateDB)
|
||||
_ = keyAB
|
||||
|
||||
// Each admin call by `random` must return ErrUnauthorized.
|
||||
cases := []struct {
|
||||
name string
|
||||
fn func() error
|
||||
}{
|
||||
{"PauseDEX", func() error { return pm.PauseDEX(stateDB, random) }},
|
||||
{"ResumeDEX", func() error { return pm.ResumeDEX(stateDB, random) }},
|
||||
{"PausePool", func() error { return pm.PausePool(stateDB, random, poolID) }},
|
||||
{"ResumePool", func() error { return pm.ResumePool(stateDB, random, poolID) }},
|
||||
{"FreezePool", func() error { return pm.FreezePool(stateDB, random, poolID) }},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if err := tc.fn(); !errors.Is(err, ErrUnauthorized) {
|
||||
t.Fatalf("expected ErrUnauthorized for random caller on %s, got: %v", tc.name, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// And the configured controller can pause/resume successfully.
|
||||
if err := pm.PauseDEX(stateDB, configured); err != nil {
|
||||
t.Fatalf("configured controller PauseDEX should succeed, got: %v", err)
|
||||
}
|
||||
if err := pm.ResumeDEX(stateDB, configured); err != nil {
|
||||
t.Fatalf("configured controller ResumeDEX should succeed, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDexConfig_MakeConfigReturnsConfig verifies the configurator returns the
|
||||
// canonical Config type, so JSON unmarshalling produces a struct whose
|
||||
// ProtocolFeeController field is populated from `protocolFeeController` in
|
||||
// upgrade.json (the field the operator must set).
|
||||
func TestDexConfig_MakeConfigReturnsConfig(t *testing.T) {
|
||||
cfg := (&configurator{}).MakeConfig()
|
||||
if _, ok := cfg.(*Config); !ok {
|
||||
t.Fatalf("MakeConfig should return *Config, got %T", cfg)
|
||||
}
|
||||
// Confirm the interface compliance is preserved.
|
||||
var _ precompileconfig.Config = (*Config)(nil)
|
||||
}
|
||||
+40
-4
@@ -188,6 +188,33 @@ func (*configurator) MakeConfig() precompileconfig.Config {
|
||||
return new(Config)
|
||||
}
|
||||
|
||||
// compromisedDevControllers lists Ethereum addresses whose private keys are
|
||||
// publicly distributed (Foundry/Anvil deterministic test accounts, Hardhat
|
||||
// default mnemonic, etc.). Configuring any of these as the DEX protocol fee
|
||||
// controller would let any caller pause / freeze the chain-wide AMM, so
|
||||
// activation is refused. Stored as the lowercase hex representation produced
|
||||
// by common.Address.Hex()-stripped-and-lowered for cheap membership testing.
|
||||
//
|
||||
// Sources:
|
||||
// - Foundry/Anvil default mnemonic "test test test test test test test test
|
||||
// test test test junk" (anvil --accounts 10) → accounts #0..#9.
|
||||
// - Hardhat default mnemonic produces an overlapping set (same root).
|
||||
//
|
||||
// New entries should be appended whenever a new well-known development
|
||||
// mnemonic is identified.
|
||||
var compromisedDevControllers = map[common.Address]struct{}{
|
||||
common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"): {}, // anvil #0
|
||||
common.HexToAddress("0x70997970C51812dc3A010C7d01b50e0d17dc79C8"): {}, // anvil #1
|
||||
common.HexToAddress("0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"): {}, // anvil #2
|
||||
common.HexToAddress("0x90F79bf6EB2c4f870365E785982E1f101E93b906"): {}, // anvil #3
|
||||
common.HexToAddress("0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65"): {}, // anvil #4
|
||||
common.HexToAddress("0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc"): {}, // anvil #5
|
||||
common.HexToAddress("0x976EA74026E726554dB657fA54763abd0C3a0aa9"): {}, // anvil #6
|
||||
common.HexToAddress("0x14dC79964da2C08b23698B3D3cc7Ca32193d9955"): {}, // anvil #7
|
||||
common.HexToAddress("0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f"): {}, // anvil #8
|
||||
common.HexToAddress("0xa0Ee7A142d267C1f36714E4a8F75612F20a79720"): {}, // anvil #9
|
||||
}
|
||||
|
||||
func (*configurator) Configure(
|
||||
chainConfig precompileconfig.ChainConfig,
|
||||
cfg precompileconfig.Config,
|
||||
@@ -199,11 +226,20 @@ func (*configurator) Configure(
|
||||
return fmt.Errorf("expected config type %T, got %T: %v", &Config{}, cfg, cfg)
|
||||
}
|
||||
|
||||
// Set protocol fee controller — required for pause/freeze admin.
|
||||
// If no protocolFeeController specified, use a sensible default.
|
||||
// Protocol fee controller is required. NO DEFAULT. A missing or zero
|
||||
// controller would leave PauseDEX / ResumeDEX / FreezePool open to any
|
||||
// caller; the operator MUST populate this explicitly in upgrade.json
|
||||
// (typically a multisig or governance contract). Refusing activation
|
||||
// surfaces the misconfiguration on the activation block rather than
|
||||
// silently shipping an open-admin DEX.
|
||||
if config.ProtocolFeeController == (common.Address{}) {
|
||||
// Default to the standard Anvil/dev account #0
|
||||
config.ProtocolFeeController = common.HexToAddress("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266")
|
||||
return ErrDEXNoProtocolFeeController
|
||||
}
|
||||
// Refuse known-compromised dev keys whose private keys are public
|
||||
// knowledge (Foundry/Anvil, Hardhat default mnemonic). This catches the
|
||||
// "I'll fix the placeholder before mainnet" footgun.
|
||||
if _, bad := compromisedDevControllers[config.ProtocolFeeController]; bad {
|
||||
return fmt.Errorf("%w: %s", ErrDEXCompromisedController, config.ProtocolFeeController.Hex())
|
||||
}
|
||||
DEXPrecompile.poolManager.protocolFeeController = config.ProtocolFeeController
|
||||
|
||||
|
||||
@@ -361,6 +361,21 @@ var (
|
||||
ErrInvalidFee = errors.New("invalid fee")
|
||||
ErrCurrencyNotSorted = errors.New("currencies not sorted")
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
|
||||
// ErrDEXNoProtocolFeeController is returned when the DEX precompile is
|
||||
// configured without an explicit protocolFeeController address (or with
|
||||
// the zero address). Activating the precompile without a real controller
|
||||
// would leave PauseDEX / ResumeDEX / FreezePool callable by anyone, so
|
||||
// configuration must explicitly populate the controller. luxd refuses to
|
||||
// advance past the activation block until upgrade.json is fixed.
|
||||
ErrDEXNoProtocolFeeController = errors.New("dex: protocolFeeController must be set in upgrade.json — refusing to activate")
|
||||
|
||||
// ErrDEXCompromisedController is returned when the configured
|
||||
// protocolFeeController matches a known publicly-disclosed development
|
||||
// key (e.g. Foundry/Anvil account #0). The matching private key is in
|
||||
// every Foundry user's ~/.foundry — any random caller could pause /
|
||||
// freeze the chain-wide AMM. Activation fails closed.
|
||||
ErrDEXCompromisedController = errors.New("dex: protocolFeeController is a publicly-known dev key — refusing to activate")
|
||||
ErrInvalidHookResponse = errors.New("invalid hook response")
|
||||
ErrSettlementFailed = errors.New("settlement failed")
|
||||
ErrInvalidSqrtPrice = errors.New("invalid sqrt price")
|
||||
|
||||
+70
-7
@@ -5,32 +5,95 @@ package ed25519
|
||||
|
||||
import (
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/precompile/contract"
|
||||
"github.com/luxfi/precompile/modules"
|
||||
"github.com/luxfi/precompile/precompileconfig"
|
||||
)
|
||||
|
||||
// Module provides the ed25519 precompile module information
|
||||
// ConfigKey is the upgrade.json key for the Ed25519 verify precompile.
|
||||
// Activated at the Quasar Edition timestamp; once active, contracts can
|
||||
// natively verify Solana/Ed25519, TON, XRP (Ed25519 mode), and any other
|
||||
// Ed25519-rooted external chain signature inside EVM execution.
|
||||
const ConfigKey = "ed25519Config"
|
||||
|
||||
// RegistryModule is the registry entry for the Ed25519 precompile.
|
||||
// Distinct identifier from the legacy `Module` metadata struct that older
|
||||
// callers depend on (see NewModule()/Module type below).
|
||||
var RegistryModule = modules.Module{
|
||||
ConfigKey: ConfigKey,
|
||||
Address: ContractAddress,
|
||||
Contract: Ed25519VerifyPrecompile,
|
||||
Configurator: &configurator{},
|
||||
}
|
||||
|
||||
type configurator struct{}
|
||||
|
||||
var _ contract.Configurator = (*configurator)(nil)
|
||||
|
||||
func init() {
|
||||
if err := modules.RegisterModule(RegistryModule); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) }
|
||||
|
||||
func (*configurator) Configure(
|
||||
_ precompileconfig.ChainConfig,
|
||||
_ precompileconfig.Config,
|
||||
_ contract.StateDB,
|
||||
_ contract.ConfigurationBlockContext,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the upgrade-gated config for the Ed25519 verify precompile.
|
||||
// No tunable parameters — the precompile is either active or not.
|
||||
type Config struct {
|
||||
Upgrade precompileconfig.Upgrade `json:"upgrade"`
|
||||
}
|
||||
|
||||
func (c *Config) Key() string { return ConfigKey }
|
||||
func (c *Config) Timestamp() *uint64 { return c.Upgrade.Timestamp() }
|
||||
func (c *Config) IsDisabled() bool { return c.Upgrade.Disable }
|
||||
func (c *Config) Equal(cfg precompileconfig.Config) bool {
|
||||
other, ok := cfg.(*Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.Upgrade.Equal(&other.Upgrade)
|
||||
}
|
||||
func (c *Config) Verify(precompileconfig.ChainConfig) error { return nil }
|
||||
|
||||
// --- Legacy metadata-style module wrapper (kept for backward compatibility) ---
|
||||
//
|
||||
// These functions predate the modules.Module registry pattern. They are
|
||||
// preserved so existing callers and tests continue to compile.
|
||||
|
||||
// Module provides the ed25519 precompile module information.
|
||||
type Module struct{}
|
||||
|
||||
// NewModule creates a new ed25519 module
|
||||
// NewModule creates a new ed25519 module.
|
||||
func NewModule() *Module {
|
||||
return &Module{}
|
||||
}
|
||||
|
||||
// Address returns the precompile address
|
||||
// Address returns the precompile address.
|
||||
func (m *Module) Address() common.Address {
|
||||
return ContractAddress
|
||||
}
|
||||
|
||||
// Contract returns the singleton contract instance
|
||||
// Contract returns the singleton contract instance.
|
||||
func (m *Module) Contract() *ed25519VerifyPrecompile {
|
||||
return Ed25519VerifyPrecompile
|
||||
}
|
||||
|
||||
// ConfigKey returns the configuration key for this module
|
||||
// ConfigKey returns the configuration key for this module.
|
||||
func (m *Module) ConfigKey() string {
|
||||
return "ed25519Config"
|
||||
return ConfigKey
|
||||
}
|
||||
|
||||
// Name returns the module name
|
||||
// Name returns the module name.
|
||||
func (m *Module) Name() string {
|
||||
return "ed25519"
|
||||
}
|
||||
|
||||
+46
-23
@@ -4,37 +4,60 @@
|
||||
package kzg4844
|
||||
|
||||
import (
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/precompile/contract"
|
||||
"github.com/luxfi/precompile/modules"
|
||||
"github.com/luxfi/precompile/precompileconfig"
|
||||
)
|
||||
|
||||
var (
|
||||
// Module is the precompile module singleton
|
||||
Module = &module{
|
||||
address: ContractAddress,
|
||||
contract: KZG4844Precompile,
|
||||
var _ contract.Configurator = (*configurator)(nil)
|
||||
|
||||
// ConfigKey is the upgrade.json key for the EIP-4844 KZG point-evaluation
|
||||
// extension precompile (LP-3665). The standard EVM precompile at 0x0a is
|
||||
// always-on; this one provides the extended op-codes (BlobToCommitment,
|
||||
// ComputeProof, batch verify, etc.) at 0xB002.
|
||||
const ConfigKey = "kzg4844Config"
|
||||
|
||||
// Module is the registry entry for the KZG-4844 extension precompile.
|
||||
var Module = modules.Module{
|
||||
ConfigKey: ConfigKey,
|
||||
Address: ContractAddress,
|
||||
Contract: KZG4844Precompile,
|
||||
Configurator: &configurator{},
|
||||
}
|
||||
|
||||
type configurator struct{}
|
||||
|
||||
func init() {
|
||||
if err := modules.RegisterModule(Module); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
)
|
||||
|
||||
type module struct {
|
||||
address common.Address
|
||||
contract contract.StatefulPrecompiledContract
|
||||
}
|
||||
|
||||
// Address returns the address where the stateful precompile is accessible.
|
||||
func (m *module) Address() common.Address {
|
||||
return m.address
|
||||
}
|
||||
func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) }
|
||||
|
||||
// Contract returns a thread-safe singleton that can be used as the StatefulPrecompiledContract
|
||||
func (m *module) Contract() contract.StatefulPrecompiledContract {
|
||||
return m.contract
|
||||
}
|
||||
|
||||
// Configure is a no-op for KZG4844 as it has no configuration
|
||||
func (m *module) Configure(
|
||||
func (*configurator) Configure(
|
||||
_ precompileconfig.ChainConfig,
|
||||
_ precompileconfig.Config,
|
||||
_ contract.StateDB,
|
||||
_ common.Address,
|
||||
_ contract.ConfigurationBlockContext,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the upgrade-gated config for the KZG-4844 extension precompile.
|
||||
// No tunable parameters — the precompile is either active or not.
|
||||
type Config struct {
|
||||
Upgrade precompileconfig.Upgrade `json:"upgrade"`
|
||||
}
|
||||
|
||||
func (c *Config) Key() string { return ConfigKey }
|
||||
func (c *Config) Timestamp() *uint64 { return c.Upgrade.Timestamp() }
|
||||
func (c *Config) IsDisabled() bool { return c.Upgrade.Disable }
|
||||
func (c *Config) Equal(cfg precompileconfig.Config) bool {
|
||||
other, ok := cfg.(*Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.Upgrade.Equal(&other.Upgrade)
|
||||
}
|
||||
func (c *Config) Verify(precompileconfig.ChainConfig) error { return nil }
|
||||
|
||||
@@ -192,6 +192,26 @@ var (
|
||||
Start: common.HexToAddress("0x0000000000000000000000000000000000000001"),
|
||||
End: common.HexToAddress("0x00000000000000000000000000000000000000ff"),
|
||||
},
|
||||
// EIP-7212 secp256r1 / P256 verify (0x100)
|
||||
// Reserved post-EVM-standard band 0x100-0x1FF for upstream-EIP
|
||||
// precompiles that EVM clients converge on (RIP-7212 picked 0x100
|
||||
// for P256 verify). Keeping the range tight (one /248 of the 20-
|
||||
// byte space) means future EIPs slot in here without colliding
|
||||
// with anything Lux uses.
|
||||
{
|
||||
Start: common.HexToAddress("0x0000000000000000000000000000000000000100"),
|
||||
End: common.HexToAddress("0x00000000000000000000000000000000000001ff"),
|
||||
},
|
||||
// High-byte EVM/Crypto page (LP-3xxx classical signatures)
|
||||
// Per precompile/registry/registry.go PCII scheme: P=3 (EVM/Crypto)
|
||||
// at high-byte format 0x3PCC0000...0000. Reserved for the classical-
|
||||
// signature sub-page so ed25519 (LP-3211 at 0x3211...0000), ECDSA
|
||||
// extensions (LP-3210), BLS12-381 (LP-3212), VRF (LP-3213), etc.
|
||||
// can register without each subsystem extending the table.
|
||||
{
|
||||
Start: common.HexToAddress("0x3200000000000000000000000000000000000000"),
|
||||
End: common.HexToAddress("0x32ff000000000000000000000000000000000000"),
|
||||
},
|
||||
// Dead/Burn Addresses (LP-0150)
|
||||
// 0x0000...0000 - Zero address
|
||||
{
|
||||
|
||||
+113
-34
@@ -5,52 +5,131 @@ package secp256r1
|
||||
|
||||
import (
|
||||
"github.com/luxfi/geth/common"
|
||||
"github.com/luxfi/precompile/contract"
|
||||
"github.com/luxfi/precompile/modules"
|
||||
"github.com/luxfi/precompile/precompileconfig"
|
||||
)
|
||||
|
||||
// Module provides the secp256r1 precompile module information
|
||||
// ConfigKey is the upgrade.json key for the EIP-7212 secp256r1/P-256
|
||||
// signature verification precompile. Activated at the Quasar Edition
|
||||
// timestamp; once active, smart contracts can verify WebAuthn / passkey
|
||||
// signatures natively (no Solidity P-256 emulation).
|
||||
const ConfigKey = "secp256r1Config"
|
||||
|
||||
// stateful is a thin StatefulPrecompiledContract wrapper around the
|
||||
// minimal-interface Contract so we can register the precompile in the
|
||||
// modules.Module registry. The wrapper deducts gas first (uniform with
|
||||
// every other Lux precompile), enforces RefuseUnderStrictPQ, then delegates
|
||||
// to the existing classical verifier.
|
||||
type stateful struct{ c Contract }
|
||||
|
||||
var (
|
||||
// StatefulPrecompile is the singleton used by modules.Module.Contract.
|
||||
StatefulPrecompile contract.StatefulPrecompiledContract = &stateful{}
|
||||
|
||||
_ contract.StatefulPrecompiledContract = (*stateful)(nil)
|
||||
)
|
||||
|
||||
// RequiredGas is delegated to the wrapped Contract.
|
||||
func (s *stateful) RequiredGas(input []byte) uint64 { return s.c.RequiredGas(input) }
|
||||
|
||||
// Run satisfies StatefulPrecompiledContract. Gas deduction, strict-PQ
|
||||
// gating, then delegation to the classical EIP-7212 verifier.
|
||||
func (s *stateful) Run(
|
||||
accessibleState contract.AccessibleState,
|
||||
_ common.Address,
|
||||
_ common.Address,
|
||||
input []byte,
|
||||
suppliedGas uint64,
|
||||
_ bool,
|
||||
) ([]byte, uint64, error) {
|
||||
remainingGas, err := contract.DeductGas(suppliedGas, s.c.RequiredGas(input))
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := contract.RefuseUnderStrictPQ(accessibleState); err != nil {
|
||||
return nil, remainingGas, err
|
||||
}
|
||||
out, err := s.c.Run(input)
|
||||
return out, remainingGas, err
|
||||
}
|
||||
|
||||
// RegistryModule is the precompile registry entry for secp256r1.
|
||||
var RegistryModule = modules.Module{
|
||||
ConfigKey: ConfigKey,
|
||||
Address: Address,
|
||||
Contract: StatefulPrecompile,
|
||||
Configurator: &configurator{},
|
||||
}
|
||||
|
||||
type configurator struct{}
|
||||
|
||||
var _ contract.Configurator = (*configurator)(nil)
|
||||
|
||||
func init() {
|
||||
if err := modules.RegisterModule(RegistryModule); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (*configurator) MakeConfig() precompileconfig.Config { return new(Config) }
|
||||
|
||||
func (*configurator) Configure(
|
||||
_ precompileconfig.ChainConfig,
|
||||
_ precompileconfig.Config,
|
||||
_ contract.StateDB,
|
||||
_ contract.ConfigurationBlockContext,
|
||||
) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the upgrade-gated config for the secp256r1 precompile.
|
||||
type Config struct {
|
||||
Upgrade precompileconfig.Upgrade `json:"upgrade"`
|
||||
}
|
||||
|
||||
func (c *Config) Key() string { return ConfigKey }
|
||||
func (c *Config) Timestamp() *uint64 { return c.Upgrade.Timestamp() }
|
||||
func (c *Config) IsDisabled() bool { return c.Upgrade.Disable }
|
||||
func (c *Config) Equal(cfg precompileconfig.Config) bool {
|
||||
other, ok := cfg.(*Config)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return c.Upgrade.Equal(&other.Upgrade)
|
||||
}
|
||||
func (c *Config) Verify(precompileconfig.ChainConfig) error { return nil }
|
||||
|
||||
// --- Legacy metadata-style module wrapper (kept for backward compatibility) ---
|
||||
|
||||
// Module provides the secp256r1 precompile module information.
|
||||
type Module struct{}
|
||||
|
||||
// NewModule creates a new secp256r1 module
|
||||
func NewModule() *Module {
|
||||
return &Module{}
|
||||
}
|
||||
// NewModule creates a new secp256r1 module.
|
||||
func NewModule() *Module { return &Module{} }
|
||||
|
||||
// Address returns the precompile address
|
||||
func (m *Module) Address() common.Address {
|
||||
return Address
|
||||
}
|
||||
// Address returns the precompile address.
|
||||
func (m *Module) Address() common.Address { return Address }
|
||||
|
||||
// Contract returns a new contract instance
|
||||
func (m *Module) Contract() *Contract {
|
||||
return &Contract{}
|
||||
}
|
||||
// Contract returns a new contract instance.
|
||||
func (m *Module) Contract() *Contract { return &Contract{} }
|
||||
|
||||
// ConfigKey returns the configuration key for this module
|
||||
func (m *Module) ConfigKey() string {
|
||||
return "secp256r1Config"
|
||||
}
|
||||
// ConfigKey returns the configuration key for this module.
|
||||
func (m *Module) ConfigKey() string { return ConfigKey }
|
||||
|
||||
// Name returns the module name
|
||||
func (m *Module) Name() string {
|
||||
return "secp256r1"
|
||||
}
|
||||
// Name returns the module name.
|
||||
func (m *Module) Name() string { return "secp256r1" }
|
||||
|
||||
// Description returns the module description
|
||||
// Description returns the module description.
|
||||
func (m *Module) Description() string {
|
||||
return "secp256r1 (P-256) signature verification precompile for biometric authentication and WebAuthn"
|
||||
}
|
||||
|
||||
// Version returns the module version
|
||||
func (m *Module) Version() string {
|
||||
return "1.0.0"
|
||||
}
|
||||
// Version returns the module version.
|
||||
func (m *Module) Version() string { return "1.0.0" }
|
||||
|
||||
// EIPs returns the related EIP numbers
|
||||
func (m *Module) EIPs() []int {
|
||||
return []int{7212}
|
||||
}
|
||||
// EIPs returns the related EIP numbers.
|
||||
func (m *Module) EIPs() []int { return []int{7212} }
|
||||
|
||||
// LPs returns the related LP numbers
|
||||
func (m *Module) LPs() []int {
|
||||
return []int{3651}
|
||||
}
|
||||
// LPs returns the related LP numbers.
|
||||
func (m *Module) LPs() []int { return []int{3651} }
|
||||
|
||||
Reference in New Issue
Block a user