Files
geth/core/vm/lux_precompiles.go
zeekay 02ca28cdfd geth/core/vm: use RegisterDefaultVerifier (CAS) for starkfri so the real p3q binding wins
Was an unconditional RegisterVerifier(refuse) that clobbered the real starkfri verifier registered
by precompile/starkfri/init. Now registers a CAS-against-nil safe-refuse fallback that no-ops when
the real binding is present (atomic.Pointer). Fixes the init-order clobber.
2026-06-17 18:38:33 -07:00

347 lines
13 KiB
Go

// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package vm
import (
"context"
"math/big"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/params"
"github.com/luxfi/precompile/contract"
coronathreshold "github.com/luxfi/precompile/corona"
"github.com/luxfi/precompile/hqc"
"github.com/luxfi/precompile/magnetar"
"github.com/luxfi/precompile/mldsa"
"github.com/luxfi/precompile/mlkem"
"github.com/luxfi/precompile/p3q"
"github.com/luxfi/precompile/precompileconfig"
"github.com/luxfi/precompile/pulsar"
"github.com/luxfi/precompile/slhdsa"
"github.com/luxfi/precompile/starkfri"
)
// precompileAdapter wraps a precompiles.StatefulPrecompiledContract to implement
// geth's StatefulPrecompiledContract interface.
type precompileAdapter struct {
name string
address common.Address
inner contract.StatefulPrecompiledContract
gasFunc func([]byte) uint64
}
// Name returns the precompile name
func (p *precompileAdapter) Name() string {
return p.name
}
// RequiredGas returns the gas required for this precompile
func (p *precompileAdapter) RequiredGas(input []byte) uint64 {
return p.gasFunc(input)
}
// Run implements PrecompiledContract.Run (stateless - not used for stateful precompiles)
func (p *precompileAdapter) Run(input []byte) ([]byte, error) {
// Stateful precompiles use RunStateful instead
return nil, ErrExecutionReverted
}
// RunStateful implements StatefulPrecompiledContract.RunStateful
func (p *precompileAdapter) RunStateful(env PrecompileEnvironment, input []byte, suppliedGas uint64) ([]byte, uint64, error) {
// Create an AccessibleState adapter
accessibleState := &accessibleStateAdapter{env: env}
// Get caller and self addresses from environment
addresses := env.Addresses()
// Call the inner precompile
ret, remainingGas, err := p.inner.Run(
accessibleState,
addresses.Caller,
addresses.Self,
input,
suppliedGas,
env.ReadOnly(),
)
return ret, remainingGas, err
}
// accessibleStateAdapter adapts PrecompileEnvironment to contract.AccessibleState
type accessibleStateAdapter struct {
env PrecompileEnvironment
}
func (a *accessibleStateAdapter) GetStateDB() contract.StateDB {
return nil // PQ crypto precompiles don't use state
}
func (a *accessibleStateAdapter) GetBlockContext() contract.BlockContext {
return &blockContextAdapter{env: a.env}
}
func (a *accessibleStateAdapter) GetConsensusContext() context.Context {
return a.env.ConsensusContext()
}
func (a *accessibleStateAdapter) GetChainConfig() precompileconfig.ChainConfig {
return nil // PQ crypto precompiles don't use chain config
}
func (a *accessibleStateAdapter) GetPrecompileEnv() contract.PrecompileEnvironment {
return &precompileEnvAdapter{env: a.env}
}
// precompileEnvAdapter adapts vm.PrecompileEnvironment to contract.PrecompileEnvironment
type precompileEnvAdapter struct {
env PrecompileEnvironment
}
func (p *precompileEnvAdapter) BlockNumber() *big.Int {
return p.env.BlockNumber()
}
func (p *precompileEnvAdapter) BlockTime() uint64 {
return p.env.BlockTime()
}
func (p *precompileEnvAdapter) ReadOnly() bool {
return p.env.ReadOnly()
}
func (p *precompileEnvAdapter) ChainConfig() *params.ChainConfig {
return p.env.ChainConfig()
}
func (p *precompileEnvAdapter) StateDB() interface{} {
return p.env.StateDB()
}
func (p *precompileEnvAdapter) Gas() uint64 {
return p.env.Gas()
}
func (p *precompileEnvAdapter) UseGas(gas uint64) bool {
return p.env.UseGas(gas)
}
// blockContextAdapter adapts PrecompileEnvironment to contract.BlockContext
type blockContextAdapter struct {
env PrecompileEnvironment
}
func (b *blockContextAdapter) Number() *big.Int {
return b.env.BlockNumber()
}
func (b *blockContextAdapter) Timestamp() uint64 {
return b.env.BlockTime()
}
func (b *blockContextAdapter) GetPredicateResults(txHash common.Hash, precompileAddress common.Address) []byte {
return nil // Not needed for PQ crypto precompiles
}
// NewPrecompileAdapter wraps a luxfi/precompile StatefulPrecompiledContract
// into a geth-compatible StatefulPrecompiledContract. Exported so that L1
// chains can assemble their own precompile sets via PrecompileOverrider
// without requiring changes to geth itself.
func NewPrecompileAdapter(name string, address common.Address, inner contract.StatefulPrecompiledContract, gasFunc func([]byte) uint64) StatefulPrecompiledContract {
return &precompileAdapter{
name: name,
address: address,
inner: inner,
gasFunc: gasFunc,
}
}
// LuxPrecompiles returns the baseline Lux precompiles that every Lux chain
// should support. This is the LP-4200 unified PQCrypto block: ML-KEM,
// ML-DSA, SLH-DSA, Pulsar (Module-LWE threshold ML-DSA), P3Q (LP-218
// "Post-Quantum Pulsar Proof" on-chain verifier), Corona (Ring-LWE
// threshold), Magnetar (public-DKG MPC threshold SLH-DSA), and HQC
// (code-based KEM). The strict-PQ STARK / FRI verifier dispatch (formerly
// misnamed P3Q at slot 0x012205) is now reachable at the dedicated
// STARK-FRI slot (precompile/starkfri).
// L1 chains can add additional precompiles (DEX, sr25519, FROST, etc.)
// by implementing PrecompileOverrider in their Rules.Payload.
//
// Addresses (LP-4200 unified block):
//
// 0x012201 = ML-KEM (FIPS 203 — Module-LWE KEM)
// 0x012202 = ML-DSA (FIPS 204 — Module-LWE single-party signatures)
// 0x012203 = SLH-DSA (FIPS 205 — stateless hash-based signatures)
// 0x012204 = Pulsar (Module-LWE threshold FIPS 204, byte-equal to ML-DSA)
// 0x012205 = P3Q (LP-218 Post-Quantum Pulsar Proof — Solidity-callable
// Pulsar / FIPS 204 ML-DSA verifier with 32-byte
// messageHash input and EVM-ABI bool output)
// 0x012206 = Corona (Ring-LWE threshold)
// 0x012207 = Magnetar (public-DKG MPC threshold SLH-DSA, FIPS 205 byte-equal)
// 0x012208 = HQC (code-based KEM, family-disjoint backup)
// 0x012220 = STARK-FRI (strict-PQ STARK / FRI / cSHAKE256 verifier dispatch
// — placeholder slot pending dedicated LP allocation)
//
// All eight PQ-signature precompiles + STARK-FRI are always available
// regardless of PQ profile; the profile gate only constrains the
// *classical* precompiles (ecrecover, sha256, alt_bn128, BLS12-381, KZG)
// — it never disables PQ primitives.
func LuxPrecompiles() PrecompiledContracts {
return PrecompiledContracts{
// ML-KEM (FIPS 203 — post-quantum key encapsulation)
mlkem.ContractAddress: &precompileAdapter{
name: "mlkem",
address: mlkem.ContractAddress,
inner: mlkem.MLKEMPrecompile,
gasFunc: mlkem.MLKEMPrecompile.RequiredGas,
},
// ML-DSA (FIPS 204 — single-party post-quantum signatures)
mldsa.ContractMLDSAVerifyAddress: &precompileAdapter{
name: "mldsa",
address: mldsa.ContractMLDSAVerifyAddress,
inner: mldsa.MLDSAVerifyPrecompile,
gasFunc: mldsa.MLDSAVerifyPrecompile.RequiredGas,
},
// SLH-DSA (FIPS 205 — stateless hash-based signatures)
slhdsa.ContractSLHDSAVerifyAddress: &precompileAdapter{
name: "slhdsa",
address: slhdsa.ContractSLHDSAVerifyAddress,
inner: slhdsa.SLHDSAVerifyPrecompile,
gasFunc: slhdsa.SLHDSAVerifyPrecompile.RequiredGas,
},
// Pulsar (threshold FIPS 204 — Module-LWE threshold ML-DSA).
// Byte-equal output to single-party ML-DSA per the NIST MPTC
// Class N1 manifesto; the underlying verifier dispatches to
// luxfi/crypto/mldsa.
pulsar.ContractPulsarVerifyAddress: &precompileAdapter{
name: "pulsar",
address: pulsar.ContractPulsarVerifyAddress,
inner: pulsar.PulsarVerifyPrecompile,
gasFunc: pulsar.PulsarVerifyPrecompile.RequiredGas,
},
// P3Q (Post-Quantum Pulsar Proof — LP-218). Solidity-callable
// on-chain verifier for Pulsar / FIPS 204 ML-DSA threshold
// signatures over a 32-byte messageHash. Distinct from the
// generic Pulsar precompile at 0x012204 by wire format
// (fixed-length hash vs variable-length message) and Solidity
// ABI (bool return vs error-based pass/fail). Both share the
// same FIPS 204 verifier core (luxfi/crypto/mldsa.VerifySignatureCtx).
p3q.ContractP3QVerifyAddress: &precompileAdapter{
name: "p3q",
address: p3q.ContractP3QVerifyAddress,
inner: p3q.P3QVerifyPrecompile,
gasFunc: p3q.P3QVerifyPrecompile.RequiredGas,
},
// STARK-FRI (strict-PQ STARK / FRI / cSHAKE256 / Goldilocks).
// Formerly misnamed "P3Q" at slot 0x012205; the canonical
// HANZO-CRYPTO-SUITE §5.2 + LP-218 definition restores P3Q
// to its Pulsar-verifier identity. This dispatch lives under
// its actual algorithmic identity at slot 0x012220
// (placeholder pending dedicated LP allocation).
// Verifier callback wired at node init via
// starkfri.RegisterVerifier.
starkfri.ContractStarkFRIVerifyAddress: &precompileAdapter{
name: "starkfri",
address: starkfri.ContractStarkFRIVerifyAddress,
inner: starkfri.StarkFRIVerifyPrecompile,
gasFunc: starkfri.StarkFRIVerifyPrecompile.RequiredGas,
},
// Corona (Ring-LWE threshold). Distinct algebra from Pulsar:
// Pulsar is Module-LWE (FIPS 204 byte-equal); Corona is Ring-LWE
// over a single ring. Both are NIST MPTC Class N1 candidates.
coronathreshold.ContractCoronaThresholdAddress: &precompileAdapter{
name: "corona",
address: coronathreshold.ContractCoronaThresholdAddress,
inner: coronathreshold.CoronaThresholdPrecompile,
gasFunc: coronathreshold.CoronaThresholdPrecompile.RequiredGas,
},
// Magnetar (public-DKG MPC threshold SLH-DSA, FIPS 205 byte-equal).
// The MPC ceremony (Pedersen-VSS DKG + MPC evaluation over the
// WOTS+/FORS/Merkle signing tree of FIPS 205) produces a signature
// byte-equal to a single-party FIPS 205 SLH-DSA signature on the
// same message and group public key. Verification IS the standard
// FIPS 205 verifier. Distinct slot from SLH-DSA for gas-tier
// separation, telemetry, and forward-compatibility.
magnetar.ContractMagnetarVerifyAddress: &precompileAdapter{
name: "magnetar",
address: magnetar.ContractMagnetarVerifyAddress,
inner: magnetar.MagnetarVerifyPrecompile,
gasFunc: magnetar.MagnetarVerifyPrecompile.RequiredGas,
},
// HQC (Hamming Quasi-Cyclic, NIST PQC4-round selected backup KEM).
// Code-based, family-disjoint from Module-LWE ML-KEM: a structural
// break in lattice cryptography would not compromise HQC, and
// vice-versa. Encapsulation only — decapsulation MUST be performed
// off-chain (private key in calldata is public on-chain).
hqc.ContractAddress: &precompileAdapter{
name: "hqc",
address: hqc.ContractAddress,
inner: hqc.HQCPrecompile,
gasFunc: hqc.HQCPrecompile.RequiredGas,
},
}
}
// MergeLuxPrecompiles merges Lux precompiles into the given precompile map.
// Returns a new map with all precompiles.
func MergeLuxPrecompiles(base PrecompiledContracts) PrecompiledContracts {
result := make(PrecompiledContracts)
// Copy base precompiles
for addr, p := range base {
result[addr] = p
}
// Add Lux precompiles
for addr, p := range LuxPrecompiles() {
result[addr] = p
}
return result
}
// PrecompiledContractsLux contains all precompiles for Lux chains.
// This includes standard Ethereum precompiles plus Lux-specific ones.
var PrecompiledContractsLux = MergeLuxPrecompiles(PrecompiledContractsCancun)
// init registers a safe-refuse DEFAULT for the strict-PQ STARK-FRI
// verifier at boot.
//
// The real verifier is a CGO bridge into the Rust crate at
// ~/work/lux/p3q (a post-quantum-only fork of Plonky3: FRI over
// Goldilocks, cSHAKE256 Merkle). It ships in the precompile package
// itself (precompile/starkfri/verify_cgo.go) and self-registers via an
// init() guarded by the `starkfri_p3q` build tag. Build a node with
// `-tags starkfri_p3q` (and link libp3q_c_abi.a) to get the real
// verifier; without the tag, the binding is excluded and this default
// keeps the precompile in safe-refuse (ErrVerifierNotRegistered ⇒ no
// forgery oracle).
//
// Why RegisterDefaultVerifier (not RegisterVerifier): Go runs an
// imported package's init() before the importer's, so the real binding
// in precompile/starkfri wins the registration FIRST. An unconditional
// RegisterVerifier here would then CLOBBER the real verifier with the
// refuse stub. RegisterDefaultVerifier compare-and-swaps against "no
// verifier yet", so it no-ops when the real binding is present and only
// engages when it is absent — correct regardless of init order.
//
// The P3Q precompile at 0x012205 (Post-Quantum Pulsar Proof, LP-218)
// dispatches directly to the in-tree luxfi/crypto/mldsa verifier and
// does NOT require an FFI-registered verifier callback. No default is
// needed for P3Q.
func init() {
starkfri.RegisterDefaultVerifier(func(version byte, proof, pubInputs []byte) (bool, error) {
// Refuse-by-default. The real cgo binding (built with
// -tags starkfri_p3q) registers ahead of this and wins the CAS.
return false, starkfri.ErrVerifierNotRegistered
})
}