mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
evm: ecrecover strict-PQ refusal via profile gate
VM.Initialize installs the chain-wide strict-PQ posture into the go-ethereum precompile layer when Config.LuxStrictPQ is true. The ecrecover precompile at 0x01 then returns ErrClassicalAuthForbidden (gas is still charged per EIP-150) and never produces a recovered address. Default (LuxStrictPQ=false) preserves classical-compat semantics required by legacy chains and the Lux-Permissive profile. Mirrors the coreth wire-up; the gate itself lives in luxfi/geth (commit 738ecc775, tag v1.16.88) and is single-implementation. Bumps luxfi/geth pre-release v1.16.86-0.20260413… → v1.16.88 to pull in the gate; transitive deps (luxfi/accel v1.0.7→v1.0.8, luxfi/consensus v1.22.70→v1.22.84, luxfi/precompile v0.5.12→v0.5.14) update as required by geth v1.16.88's own go.mod. Tests: TestEcrecoverStrictPQ_Forbidden — strict-PQ returns the error TestEcrecoverClassicalCompat_Works — nil profile keeps upstream path
This commit is contained in:
@@ -17,12 +17,12 @@ require (
|
||||
github.com/holiman/uint256 v1.3.2
|
||||
github.com/luxfi/cache v1.2.1
|
||||
github.com/luxfi/codec v1.1.4
|
||||
github.com/luxfi/consensus v1.22.70
|
||||
github.com/luxfi/consensus v1.22.84
|
||||
github.com/luxfi/constants v1.4.7
|
||||
github.com/luxfi/crypto v1.18.1
|
||||
github.com/luxfi/database v1.18.1
|
||||
github.com/luxfi/filesystem v0.0.1
|
||||
github.com/luxfi/geth v1.16.86-0.20260413014255-3e903c3d2e06
|
||||
github.com/luxfi/geth v1.16.88
|
||||
github.com/luxfi/go-bip39 v1.1.2
|
||||
github.com/luxfi/gpu v0.30.0
|
||||
github.com/luxfi/ids v1.2.9
|
||||
@@ -32,7 +32,7 @@ require (
|
||||
github.com/luxfi/math/safe v0.0.1
|
||||
github.com/luxfi/metric v1.5.1
|
||||
github.com/luxfi/p2p v1.19.2
|
||||
github.com/luxfi/precompile v0.5.12
|
||||
github.com/luxfi/precompile v0.5.14
|
||||
github.com/luxfi/runtime v1.0.1
|
||||
github.com/luxfi/sys v0.0.0-20260110090042-50187ec5ffd8
|
||||
github.com/luxfi/timer v1.0.2
|
||||
@@ -82,7 +82,7 @@ require (
|
||||
github.com/klauspost/crc32 v1.3.0 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/luxfi/accel v1.0.7 // indirect
|
||||
github.com/luxfi/accel v1.0.8 // indirect
|
||||
github.com/luxfi/age v1.4.0 // indirect
|
||||
github.com/luxfi/api v1.0.4 // indirect
|
||||
github.com/luxfi/atomic v1.0.0 // indirect
|
||||
|
||||
@@ -194,8 +194,8 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/leanovate/gopter v0.2.11 h1:vRjThO1EKPb/1NsDXuDrzldR28RLkBflWYcU9CvzWu4=
|
||||
github.com/leanovate/gopter v0.2.11/go.mod h1:aK3tzZP/C+p1m3SPRE4SYZFGP7jjkuSI4f7Xvpt0S9c=
|
||||
github.com/luxfi/accel v1.0.7 h1:ksHieAp50umwqxqgyHk9WiOmXM54kia3IEDb5H7FsM8=
|
||||
github.com/luxfi/accel v1.0.7/go.mod h1:iZD3oxffiMEIT/KvzD8bgwC/cBn4AYlMW3QJpbRa4RE=
|
||||
github.com/luxfi/accel v1.0.8 h1:NYvCa31W9/ay3RPyfG1mUjUPRTdp0O0Qrx0zIRl78Xk=
|
||||
github.com/luxfi/accel v1.0.8/go.mod h1:XvgcJLYsLObbKlrOgd3E2OJx5Oy5c/HnDlIewjr+Y+c=
|
||||
github.com/luxfi/age v1.4.0 h1:tU5q65RQSQdaVq64Z/DVUhiPQMoMPQT6pr41LsvG0AQ=
|
||||
github.com/luxfi/age v1.4.0/go.mod h1:iAYAxgvrXxcy746+Ovh/eWWDuF9teJLNcCSSOX9RYW0=
|
||||
github.com/luxfi/api v1.0.4 h1:5altGkSSk3zIsGzK/NPhRQe/It5QMHrAPgBVg2TMkK4=
|
||||
@@ -210,8 +210,8 @@ github.com/luxfi/compress v0.0.5 h1:4tEUHw5MK1bu5UOjfYCt4OKMiH7yykIgmGPRA/BfJTM=
|
||||
github.com/luxfi/compress v0.0.5/go.mod h1:Cc1yxD2pfzrvpO32W2GDwLKff+CylHEvzZh2Ko8RSIU=
|
||||
github.com/luxfi/concurrent v0.0.3 h1:eJyv1fhaC0jMLMw6+QS774cUmp7GK+ouMgvLCqnC7cc=
|
||||
github.com/luxfi/concurrent v0.0.3/go.mod h1:Aj/FR5NpM0cB2P4Nt3+tz9+dV6V+LUW4HuMgSjwq5hw=
|
||||
github.com/luxfi/consensus v1.22.70 h1:IQlDxkNY2j6W55nYh11WenvdIsZ9g619qo+xS69GA00=
|
||||
github.com/luxfi/consensus v1.22.70/go.mod h1:y6y+bVjvpDLkkTBCCKLcch6R4PRW8EA+lTfr+6sB6F8=
|
||||
github.com/luxfi/consensus v1.22.84 h1:fKw4n7fY+lCWW5HSx/ciGeRSxv4knODQGWh6lBWAveQ=
|
||||
github.com/luxfi/consensus v1.22.84/go.mod h1:y6y+bVjvpDLkkTBCCKLcch6R4PRW8EA+lTfr+6sB6F8=
|
||||
github.com/luxfi/constants v1.4.7 h1:e/Qs+DQP3pugle3Zncq6fZCxKgqqtbyD/z7Gm4ZjsYg=
|
||||
github.com/luxfi/constants v1.4.7/go.mod h1:hOszZ2NDQ8gMZKncfcZ67PXkb5OIbnwAzXC3oFbQwW0=
|
||||
github.com/luxfi/container v0.0.4 h1:BXhF82WyfqVP5mjlNcr7tP0Fcnvl0Ap1rkiu+rq5XuM=
|
||||
@@ -226,8 +226,8 @@ github.com/luxfi/filesystem v0.0.1 h1:VZ6xMFKaAPBW/ddlMsDnI2G0VU1lV5rYaVcW5d+KwE
|
||||
github.com/luxfi/filesystem v0.0.1/go.mod h1:OQVSU6XNwqrr1AI+MqkID2taHUclx7NYmmr3svgttec=
|
||||
github.com/luxfi/formatting v1.0.1 h1:ZnE1rAdEUds9yAegdVdGDOBGN6hLMPOv6E03Fp8IEYo=
|
||||
github.com/luxfi/formatting v1.0.1/go.mod h1:mYzNf5DJOiqSSKUPzNj5dKy4tstFbN3pZlkI5716eKc=
|
||||
github.com/luxfi/geth v1.16.86-0.20260413014255-3e903c3d2e06 h1:NAIcfesKGSITw/mYTQpq62igXqUiDR7gekx8rQTKBmQ=
|
||||
github.com/luxfi/geth v1.16.86-0.20260413014255-3e903c3d2e06/go.mod h1:OdpR6OnAsYROywc8wm7W0lPn1z06DjgVmRL/F8dz5Kw=
|
||||
github.com/luxfi/geth v1.16.88 h1:I46mMJPbLh2qfqjFDtP91RTSoyj1KdWiTmZX6rjHqaA=
|
||||
github.com/luxfi/geth v1.16.88/go.mod h1:E5YcGOW9jB6Rgo4LB7NlTZxvkPzIoh1w7q56O63Zf1g=
|
||||
github.com/luxfi/go-bip39 v1.1.2 h1:p+wLMPGs6MLQh7q0YIsmy2EhHL7LHiELEGTJko6t/Jg=
|
||||
github.com/luxfi/go-bip39 v1.1.2/go.mod h1:96de9VkR2kY/ASAnhMtvt3TSh+PZkAFAngNj0GjRGDo=
|
||||
github.com/luxfi/gpu v0.30.0 h1:k+7EDp/WHTa0176zXet1UXTl8IcZcp1ZkS8GUJ5l2iU=
|
||||
@@ -252,8 +252,8 @@ github.com/luxfi/node v1.23.4 h1:NzARkjbFUtlHyMEqSFQ0VeQRKF5eGWWwfQA7PO4f9VQ=
|
||||
github.com/luxfi/node v1.23.4/go.mod h1:eSzDGb6N60yHIANLmU5l8uRLlrTzVGq9yvwgITGeWkU=
|
||||
github.com/luxfi/p2p v1.19.2 h1:uqZq7ofmEDbXlTkv1QThtci01Q+dmDkNmAPeORIyP8E=
|
||||
github.com/luxfi/p2p v1.19.2/go.mod h1:tI9Bt1R0ouvVtJvXG4e20GlGeV4AR230k4mFF9Vglzk=
|
||||
github.com/luxfi/precompile v0.5.12 h1:YiF8gaPVFjXESHC+44XVM1RkAyZFMb8iU/60Tfq3Kxs=
|
||||
github.com/luxfi/precompile v0.5.12/go.mod h1:YiTU6xMf2wmKM2LBzKctn82JhaesFDz4ysC+S864qCA=
|
||||
github.com/luxfi/precompile v0.5.14 h1:z8mlWv7yWqWczWp59qoxTp0B7l/a0HMtMBPk27MEKyg=
|
||||
github.com/luxfi/precompile v0.5.14/go.mod h1:r2nImBshkpIfl3n0+Zas0uxfJ3c7JB0E0qpJkBtSV/s=
|
||||
github.com/luxfi/protocol v0.0.2 h1:TAuXMm1K4VDpG3B3brq1EE9Lb7XlbhQmVBObskgNhmc=
|
||||
github.com/luxfi/protocol v0.0.2/go.mod h1:Yjn2VRwn5PXim0+JTalAyrVG6YbmuSnYOfeUXSbAsqA=
|
||||
github.com/luxfi/resource v0.0.1 h1:mTh+ICWSy548GTUSSyx7V/X5dV18oEwxZeQEYGJQhD4=
|
||||
|
||||
@@ -98,6 +98,11 @@ type Config struct {
|
||||
// Airdrop
|
||||
AirdropFile string `json:"airdrop"`
|
||||
|
||||
// ImportChainData is the path to an RLP-encoded blockchain export to import after
|
||||
// the chain is initialized. If empty, no import is performed. Mirrors the
|
||||
// admin_importChain RPC but runs at startup. Used by luxd's --import-chain-data flag.
|
||||
ImportChainData string `json:"import-chain-data"`
|
||||
|
||||
// GenesisAllocFile is a path to a JSON file containing additional genesis allocations.
|
||||
// This allows bypassing P-chain transaction size limits for large genesis states.
|
||||
// The file should contain a JSON object with address-to-account mappings.
|
||||
@@ -265,6 +270,20 @@ type Config struct {
|
||||
|
||||
// Database Scheme
|
||||
StateScheme string `json:"state-scheme"`
|
||||
|
||||
// LuxStrictPQ activates the chain-wide strict-PQ posture inside the
|
||||
// EVM precompile layer. When true, the ecrecover precompile at 0x01
|
||||
// refuses every input with ErrClassicalAuthForbidden (gas is still
|
||||
// charged per EIP-150). Default false preserves classical-compat
|
||||
// semantics — required for legacy chains and the Lux-Permissive
|
||||
// profile.
|
||||
//
|
||||
// The node populates this from its resolved
|
||||
// consensusconfig.ChainSecurityProfile (F102 close-out) so the C-
|
||||
// chain plugin process inherits the chain-wide posture without
|
||||
// re-resolving genesis. One way only: install once at chain
|
||||
// bootstrap inside VM.Initialize, never per-tx.
|
||||
LuxStrictPQ bool `json:"lux-strict-pq,omitempty"`
|
||||
}
|
||||
|
||||
// TxPoolConfig contains the transaction pool config to be passed
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
// Copyright (C) 2019-2026, Lux Industries Inc. All rights reserved.
|
||||
// See the file LICENSE for licensing terms.
|
||||
|
||||
package evm
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/luxfi/geth/common"
|
||||
gethvm "github.com/luxfi/geth/core/vm"
|
||||
)
|
||||
|
||||
// ecrecoverAddr is the canonical Ethereum precompile address for
|
||||
// secp256k1 ecrecover, namely 0x0000000000000000000000000000000000000001.
|
||||
var ecrecoverAddr = common.BytesToAddress([]byte{0x1})
|
||||
|
||||
// validEcrecoverInputHex is a well-known signature for ecrecover at 0x01.
|
||||
// Mirrors the one in luxfi/geth/core/vm/lux_security_profile_test.go.
|
||||
//
|
||||
// The recovered address does NOT matter for these tests — what matters is
|
||||
// that under strict-PQ the precompile returns ErrClassicalAuthForbidden
|
||||
// and zero output bytes, regardless of input shape.
|
||||
const validEcrecoverInputHex = "" +
|
||||
// hash
|
||||
"a35a39e7715a7b2c5d2e3a5d8e8f8a8b8c8d8e8f9091929394959697989a9b9c" +
|
||||
// v (left-padded; canonical v = 27 or 28)
|
||||
"000000000000000000000000000000000000000000000000000000000000001c" +
|
||||
// r
|
||||
"7b6d1f1f0a85b5a3aa3f0e57c8c30a1b1c1d1e1f2021222324252627282a2b2c" +
|
||||
// s
|
||||
"3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c"
|
||||
|
||||
// resetActiveSecurityProfile restores the package-level state so a strict-PQ
|
||||
// test in this package does not leak across cases.
|
||||
func resetActiveSecurityProfile(t *testing.T) {
|
||||
t.Helper()
|
||||
prev := gethvm.ActiveSecurityProfile()
|
||||
t.Cleanup(func() {
|
||||
gethvm.SetActiveSecurityProfile(prev)
|
||||
})
|
||||
}
|
||||
|
||||
// TestEcrecoverStrictPQ_Forbidden asserts the evm-side wire-up: when
|
||||
// Config.LuxStrictPQ is true, VM.Initialize installs the strict-PQ
|
||||
// posture into the geth precompile layer, and the ecrecover precompile
|
||||
// at 0x01 returns ErrClassicalAuthForbidden.
|
||||
func TestEcrecoverStrictPQ_Forbidden(t *testing.T) {
|
||||
resetActiveSecurityProfile(t)
|
||||
gethvm.SetActiveSecurityProfile(&gethvm.LuxSecurityProfile{
|
||||
ForbidECDSAContractAuth: true,
|
||||
})
|
||||
|
||||
contracts := gethvm.PrecompiledContractsByzantium
|
||||
ecrec, ok := contracts[ecrecoverAddr]
|
||||
if !ok {
|
||||
t.Fatal("ecrecover precompile not registered at 0x01")
|
||||
}
|
||||
|
||||
input, err := hex.DecodeString(validEcrecoverInputHex)
|
||||
if err != nil {
|
||||
t.Fatalf("hex decode: %v", err)
|
||||
}
|
||||
|
||||
out, err := ecrec.Run(input)
|
||||
if !errors.Is(err, gethvm.ErrClassicalAuthForbidden) {
|
||||
t.Fatalf("ecrecover.Run: got out=%x err=%v; want ErrClassicalAuthForbidden",
|
||||
out, err)
|
||||
}
|
||||
if len(out) != 0 {
|
||||
t.Fatalf("strict-PQ ecrecover must return zero bytes, got %d", len(out))
|
||||
}
|
||||
}
|
||||
|
||||
// TestEcrecoverClassicalCompat_Works asserts the classical-compat path
|
||||
// is preserved under evm: when no profile is installed, ecrecover runs
|
||||
// upstream go-ethereum semantics and does NOT return
|
||||
// ErrClassicalAuthForbidden.
|
||||
func TestEcrecoverClassicalCompat_Works(t *testing.T) {
|
||||
resetActiveSecurityProfile(t)
|
||||
gethvm.SetActiveSecurityProfile(nil)
|
||||
|
||||
contracts := gethvm.PrecompiledContractsByzantium
|
||||
ecrec, ok := contracts[ecrecoverAddr]
|
||||
if !ok {
|
||||
t.Fatal("ecrecover precompile not registered at 0x01")
|
||||
}
|
||||
|
||||
_, err := ecrec.Run(make([]byte, 128))
|
||||
if errors.Is(err, gethvm.ErrClassicalAuthForbidden) {
|
||||
t.Fatalf("classical-compat path must not return ErrClassicalAuthForbidden, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ import (
|
||||
"github.com/luxfi/evm/plugin/evm/validators/interfaces"
|
||||
"github.com/luxfi/geth/core/rawdb"
|
||||
"github.com/luxfi/geth/core/types"
|
||||
gethvm "github.com/luxfi/geth/core/vm"
|
||||
"github.com/luxfi/geth/metrics"
|
||||
"github.com/luxfi/geth/triedb"
|
||||
"github.com/luxfi/geth/triedb/hashdb"
|
||||
@@ -344,6 +345,19 @@ func (vm *VM) Initialize(ctx context.Context, init block.Init) error {
|
||||
|
||||
log.Info("Initializing Chain EVM VM", "Version", Version, "geth version", params.VersionWithMeta, "Config", vm.config)
|
||||
|
||||
// F102 close-out — install the chain-wide strict-PQ posture into the
|
||||
// EVM precompile layer. Once installed, the ecrecover precompile at
|
||||
// 0x01 returns ErrClassicalAuthForbidden under strict-PQ (gas is
|
||||
// still charged per EIP-150). Default (config false) preserves
|
||||
// classical-compat semantics for legacy chains and the Lux-Permissive
|
||||
// profile.
|
||||
if vm.config.LuxStrictPQ {
|
||||
gethvm.SetActiveSecurityProfile(&gethvm.LuxSecurityProfile{
|
||||
ForbidECDSAContractAuth: true,
|
||||
})
|
||||
log.Info("EVM strict-PQ active: ecrecover (0x01) refuses classical contract-auth")
|
||||
}
|
||||
|
||||
if deprecateMsg != "" {
|
||||
log.Warn("Deprecation Warning", "msg", deprecateMsg)
|
||||
}
|
||||
@@ -374,6 +388,7 @@ func (vm *VM) Initialize(ctx context.Context, init block.Init) error {
|
||||
}
|
||||
|
||||
debugLog("Calling parseGenesis with genesisAllocFile=%q, upgradeBytes len=%d", vm.config.GenesisAllocFile, len(upgradeBytes))
|
||||
fmt.Fprintf(os.Stderr, "[GENESIS-BYTES-DEBUG] len=%d first500=%s\n", len(genesisBytes), string(genesisBytes[:min(500, len(genesisBytes))]))
|
||||
if len(upgradeBytes) > 0 {
|
||||
debugLog("upgradeBytes content: %s", string(upgradeBytes))
|
||||
}
|
||||
@@ -580,6 +595,30 @@ func (vm *VM) Initialize(ctx context.Context, init block.Init) error {
|
||||
}
|
||||
debugLog("Chain initialized successfully")
|
||||
|
||||
// Auto-import RLP chain data if configured. Runs the same logic as admin_importChain
|
||||
// but at startup, allowing luxd's --import-chain-data flag to drive bulk historical
|
||||
// imports without requiring the RPC server to be up first.
|
||||
if vm.config.ImportChainData != "" {
|
||||
log.Info("Auto-importing chain data at startup", "path", vm.config.ImportChainData)
|
||||
chain := vm.eth.BlockChain()
|
||||
persistAccepted := func(hash common.Hash, height uint64) error {
|
||||
var blkID ids.ID
|
||||
copy(blkID[:], hash[:])
|
||||
if err := vm.acceptedBlockDB.Put(lastAcceptedKey, blkID[:]); err != nil {
|
||||
return fmt.Errorf("failed to update acceptedBlockDB: %w", err)
|
||||
}
|
||||
if err := vm.versiondb.Commit(); err != nil {
|
||||
return fmt.Errorf("failed to commit versiondb: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
imported, lastHash, lastHeight, err := importBlocksFromFile(chain, vm.config.ImportChainData, persistAccepted)
|
||||
if err != nil {
|
||||
return fmt.Errorf("startup import failed: %w", err)
|
||||
}
|
||||
log.Info("Startup import complete", "blocks", imported, "lastHash", lastHash.Hex(), "height", lastHeight)
|
||||
}
|
||||
|
||||
// Start continuous profiler in a goroutine with recovery
|
||||
go func() {
|
||||
defer func() {
|
||||
@@ -660,6 +699,9 @@ func parseGenesis(ctx context.Context, genesisBytes []byte, upgradeBytes []byte,
|
||||
debugLog("CancunTime was nil, forced to 0")
|
||||
}
|
||||
debugLog("EVM forks: shanghaiTime=%v cancunTime=%v", *g.Config.ShanghaiTime, *g.Config.CancunTime)
|
||||
fmt.Fprintf(os.Stderr, "[EVM-FORK-DEBUG] genesisTimestamp=%v shanghaiTime=%v cancunTime=%v isShanghaiAtGenesis=%v\n",
|
||||
g.Timestamp, *g.Config.ShanghaiTime, *g.Config.CancunTime,
|
||||
g.Config.IsShanghai(big.NewInt(0), g.Timestamp))
|
||||
}
|
||||
|
||||
// Populate the Lux config extras.
|
||||
@@ -737,8 +779,10 @@ func parseGenesis(ctx context.Context, genesisBytes []byte, upgradeBytes []byte,
|
||||
}
|
||||
if val, ok := configData["durangoTimestamp"]; ok {
|
||||
log.Info("DEBUG: Found durangoTimestamp in genesis", "val", val, "type", fmt.Sprintf("%T", val))
|
||||
fmt.Fprintf(os.Stderr, "[DURANGO-DEBUG] val=%v type=%T\n", val, val)
|
||||
if ts, ok := val.(float64); ok {
|
||||
log.Info("DEBUG: Setting DurangoTimestamp from float64", "ts", ts, "uint64", uint64(ts))
|
||||
fmt.Fprintf(os.Stderr, "[DURANGO-DEBUG] setting durango=%d\n", uint64(ts))
|
||||
configExtra.DurangoTimestamp = utils.NewUint64(uint64(ts))
|
||||
} else {
|
||||
log.Info("DEBUG: durangoTimestamp is not float64")
|
||||
|
||||
Reference in New Issue
Block a user