mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
evm/plugin: rename LuxStrictPQ -> PQ; install AllForbidden() projection
Config field LuxStrictPQ -> PQ (JSON key "pq", was "lux-strict-pq"). VM.Initialize now calls gethvm.SetPQProfile(gethvm.AllForbidden()) when config.PQ is true, refusing every classical precompile family (ecrecover, sha256/ripemd/blake2F, alt_bn128, BLS12-381, KZG) at the EVM gate — not just ecrecover. Test file renamed lux_strict_pq_test.go -> pq_test.go. Covers the strict-PQ path, classical-compat path, and a cross-family refusal matrix asserting AllForbidden() turns on every Forbid* flag.
This commit is contained in:
@@ -271,19 +271,24 @@ 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
|
||||
// PQ activates the chain-wide strict-PQ posture inside the EVM
|
||||
// precompile layer. PQ mode is binary: a chain is strict-PQ or it
|
||||
// isn't — there is no relaxed-PQ. 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"`
|
||||
// consensusconfig.ChainSecurityProfile (F102 close-out) so the EVM
|
||||
// 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.
|
||||
//
|
||||
// JSON key "pq" with legacy aliases "lux-strict-pq" and "annulus"
|
||||
// accepted by ConfigOverrides.UnmarshalJSON for chains that pinned
|
||||
// the older flag names.
|
||||
PQ bool `json:"pq,omitempty"`
|
||||
}
|
||||
|
||||
// TxPoolConfig contains the transaction pool config to be passed
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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 mirrors the one in luxfi/geth/core/vm/pq_profile_test.go.
|
||||
// The recovered address does not matter — what matters is that under PQ
|
||||
// the precompile returns ErrEcrecoverForbidden 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"
|
||||
|
||||
// resetProfile restores the package-level state so a PQ test in this
|
||||
// package does not leak across cases.
|
||||
func resetProfile(t *testing.T) {
|
||||
t.Helper()
|
||||
prev := gethvm.ActivePQProfile()
|
||||
t.Cleanup(func() {
|
||||
gethvm.SetPQProfile(prev)
|
||||
})
|
||||
}
|
||||
|
||||
// TestEcrecover_StrictPQ asserts the evm-side wire-up: when Config.PQ is
|
||||
// true, VM.Initialize installs the PQ posture into the geth precompile
|
||||
// layer, and the ecrecover precompile at 0x01 returns
|
||||
// ErrEcrecoverForbidden.
|
||||
func TestEcrecover_StrictPQ(t *testing.T) {
|
||||
resetProfile(t)
|
||||
gethvm.SetPQProfile(gethvm.AllForbidden())
|
||||
|
||||
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.ErrEcrecoverForbidden) {
|
||||
t.Fatalf("ecrecover.Run: got out=%x err=%v; want ErrEcrecoverForbidden",
|
||||
out, err)
|
||||
}
|
||||
if len(out) != 0 {
|
||||
t.Fatalf("PQ ecrecover must return zero bytes, got %d", len(out))
|
||||
}
|
||||
}
|
||||
|
||||
// TestEcrecover_ClassicalCompat asserts the classical-compat path is
|
||||
// preserved under evm: when no profile is installed, ecrecover runs
|
||||
// upstream go-ethereum semantics and does NOT return any PQ refusal
|
||||
// error.
|
||||
func TestEcrecover_ClassicalCompat(t *testing.T) {
|
||||
resetProfile(t)
|
||||
gethvm.SetPQProfile(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.ErrEcrecoverForbidden) {
|
||||
t.Fatalf("classical-compat path must not return ErrEcrecoverForbidden, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestPQ_AllFamiliesRefused asserts AllForbidden() turns on every Forbid
|
||||
// flag, so each precompile family (ecrecover, sha256, ripemd, blake2F,
|
||||
// bn256, BLS12-381, KZG) refuses under the canonical strict-PQ profile.
|
||||
// The cross-family coverage matrix lives in geth's pq_gate_test.go;
|
||||
// this test is a load-bearing sanity check at the evm-plugin layer that
|
||||
// the projection actually wires the gate on every Op.
|
||||
func TestPQ_AllFamiliesRefused(t *testing.T) {
|
||||
resetProfile(t)
|
||||
gethvm.SetPQProfile(gethvm.AllForbidden())
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
addr byte
|
||||
input []byte
|
||||
wantErr error
|
||||
}{
|
||||
{"ecrecover", 0x01, make([]byte, 128), gethvm.ErrEcrecoverForbidden},
|
||||
{"sha256", 0x02, []byte("benign"), gethvm.ErrSHA256Forbidden},
|
||||
{"ripemd160", 0x03, []byte("benign"), gethvm.ErrRIPEMD160Forbidden},
|
||||
}
|
||||
contracts := gethvm.PrecompiledContractsByzantium
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
p, ok := contracts[common.BytesToAddress([]byte{c.addr})]
|
||||
if !ok {
|
||||
t.Fatalf("precompile 0x%02x not registered", c.addr)
|
||||
}
|
||||
_, err := p.Run(c.input)
|
||||
if !errors.Is(err, c.wantErr) {
|
||||
t.Fatalf("%s: got %v, want %v", c.name, err, c.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -345,17 +345,21 @@ 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")
|
||||
// F102 close-out — install the chain-wide PQ posture into the EVM
|
||||
// precompile layer. PQ mode is binary: a chain is PQ or it isn't.
|
||||
// Strict-PQ refuses every classical primitive at the precompile
|
||||
// boundary: ecrecover/p256verify, sha256/ripemd160/blake2F,
|
||||
// alt_bn128/BLS12-381 pairings, and EIP-4844 KZG point evaluation.
|
||||
// Gas is still charged per EIP-150 so the refusal isn't observable
|
||||
// via gas timing.
|
||||
//
|
||||
// One concept, one way: SetPQProfile installs the projection; refuse()
|
||||
// is the gate every classical precompile calls. nil projection means
|
||||
// classical EVM semantics. config.PQ=false (default) preserves classical
|
||||
// semantics for legacy chains and the permissive profile.
|
||||
if vm.config.PQ {
|
||||
gethvm.SetPQProfile(gethvm.AllForbidden())
|
||||
log.Info("EVM PQ mode active: classical precompiles (ecrecover, sha256/ripemd/blake2F, alt_bn128, BLS12-381, KZG) refuse")
|
||||
}
|
||||
|
||||
if deprecateMsg != "" {
|
||||
|
||||
Reference in New Issue
Block a user