mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
Under the enable-everything precompile policy these verify-only precompiles are no longer refused under strict-PQ, so any divergence between the luxfi/accel GPU/SIMD path and the pure-CPU path is a LIVE consensus split: GPU-equipped and CPU-only validators would return different verify verdicts on the same input and the chain forks. Several accel paths were also outright wrong-primitive forgery surfaces. LAW applied: the GPU verdict must be byte-identical to the CPU verdict on every input, or the accel path is a consensus hazard and is removed. CPU (pure-Go, CGO_ENABLED=0) is now the single source of truth for every on-chain verdict. None of these GPU kernels can be proven byte-identical here, and several are provably NOT, so every verdict-trusting accel branch is deleted and routed to the canonical CPU verifier. Determinism over speed. Active forgery/wrong-output in the DEFAULT build (accelcrypto CPU fallback leaked non-CPU semantics, err==nil): - sr25519: verified via the accel Ed25519 kernel -> a valid Ed25519 signature was ACCEPTED as a valid sr25519/Schnorrkel signature (wrong-curve forgery). - blake3: accelcrypto.Hash(HashBlake3) returns BLAKE2b, not BLAKE3; the "real blake3" fallback was dead code. OpHash256 + MerkleRoot emitted BLAKE2b. Latent splits (reachable only under -tags accel + a GPU backend): - frost: FROST-Schnorr verified through the SigECDSA kernel (wrong primitive + 32B x-only key mis-framed as a 33B ECDSA key). - cggmp21: SigECDSA kernel truncated the 65B key to 33B and skipped the CPU path's recover-and-compare binding (ignored the v byte). - ed25519: GPU Ed25519 kernel not proven byte-identical to the canonical verifier on cofactor / non-canonical-S / small-order edge cases. - secp256r1: P-256 inputs sent to the fixed-curve secp256k1 ECDSA kernel. - mldsa: GPU verified with EMPTY context; precompile binds the FIPS-204 ctx "lux-evm-precompile-mldsa-v1" -> different mu, different verdict. - bls12381 / zk(groth16): GPU MSM result trusted without re-verification and with no curve binding carried to the kernel. - kzg4844: GPU commitment used the backend's OWN embedded trusted setup (empty SRS) instead of the canonical EIP-4844 SRS -> breaks KZG binding. - ai_mining (BatchVerifyMLDSA/BatchAccumulator): accel batch kernel truncates messages to 32 bytes and is mode-fixed -> diverges from VerifyMLDSA. - fhe: GPU computed BFV arithmetic while CPU computes TFHE (different scheme, incompatible ciphertext, nil relin key) -> corrupt + divergent ciphertext. Cleanups (no split, vestigial): removed dead mlkem encapsulateGPU; removed the hpke GPU pre-warm that discarded its result and returned the CPU output anyway. slhdsa was already CPU-only (prior identical fix). Tests (differential / forgery proofs, all green CGO_ENABLED=0 and CGO -race): - sr25519: a valid Ed25519 sig is now REJECTED; real sr25519 still verifies. - blake3: OpHash256/MerkleRoot output == BLAKE3 and != BLAKE2b. - cggmp21: flipped recovery byte is rejected (recover-compare authoritative). - frost: a valid ECDSA sig is rejected as FROST-Schnorr. - ed25519: verdict matches the canonical ed25519.Verify oracle over 64x4 vectors. - mldsa: empty-ctx sig (the accel framing) rejected; precompileCtx sig accepted. Net -593 LOC. No consensus code touched.
120 lines
3.4 KiB
Go
120 lines
3.4 KiB
Go
// SPDX-License-Identifier: MIT
|
|
// Copyright (C) 2025, Lux Industries, Inc. All rights reserved.
|
|
|
|
package secp256r1
|
|
|
|
import (
|
|
"crypto/elliptic"
|
|
"math/big"
|
|
|
|
luxsecp256r1 "github.com/luxfi/crypto/secp256r1"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/precompile/contract"
|
|
)
|
|
|
|
const (
|
|
// P256VerifyAddress is the precompile address for secp256r1 verification
|
|
// Matches RIP-7212/EIP-7212 for cross-ecosystem compatibility
|
|
P256VerifyAddress = "0x0000000000000000000000000000000000000100"
|
|
|
|
// GasP256Verify is the gas cost for signature verification
|
|
// Based on EIP-7212 benchmarking - 100x cheaper than Solidity
|
|
GasP256Verify = 3450
|
|
|
|
// InputLength is the required input length (160 bytes)
|
|
// 32 (hash) + 32 (r) + 32 (s) + 32 (x) + 32 (y)
|
|
InputLength = 160
|
|
)
|
|
|
|
var (
|
|
// Address is the precompile address as common.Address
|
|
Address = common.HexToAddress(P256VerifyAddress)
|
|
|
|
// Success return value (32 bytes, value 1)
|
|
successResult = common.LeftPadBytes([]byte{1}, 32)
|
|
|
|
// Errors
|
|
ErrInvalidInputLength = contract.ErrInvalidInput
|
|
)
|
|
|
|
// Contract implements the secp256r1 signature verification precompile
|
|
type Contract struct{}
|
|
|
|
// Address returns the precompile address
|
|
func (c *Contract) Address() common.Address {
|
|
return Address
|
|
}
|
|
|
|
// RequiredGas returns the gas required to execute the precompile
|
|
func (c *Contract) RequiredGas(input []byte) uint64 {
|
|
return GasP256Verify
|
|
}
|
|
|
|
// Run executes the secp256r1 signature verification
|
|
//
|
|
// Input format (160 bytes):
|
|
// - bytes 0-31: message hash
|
|
// - bytes 32-63: r (signature component)
|
|
// - bytes 64-95: s (signature component)
|
|
// - bytes 96-127: x (public key x-coordinate)
|
|
// - bytes 128-159: y (public key y-coordinate)
|
|
//
|
|
// Output:
|
|
// - Success: 32 bytes with value 1
|
|
// - Failure: empty bytes (invalid signature or point not on curve)
|
|
func (c *Contract) Run(input []byte) ([]byte, error) {
|
|
if len(input) != InputLength {
|
|
// Invalid input length returns empty (not error)
|
|
return nil, nil
|
|
}
|
|
|
|
// Extract components
|
|
hash := input[0:32]
|
|
r := new(big.Int).SetBytes(input[32:64])
|
|
s := new(big.Int).SetBytes(input[64:96])
|
|
x := new(big.Int).SetBytes(input[96:128])
|
|
y := new(big.Int).SetBytes(input[128:160])
|
|
|
|
// Get P-256 curve
|
|
curve := elliptic.P256()
|
|
|
|
// Validate point is on curve
|
|
if !curve.IsOnCurve(x, y) {
|
|
return nil, nil
|
|
}
|
|
|
|
// Validate r and s are in valid range [1, n-1]
|
|
n := curve.Params().N
|
|
if r.Sign() <= 0 || r.Cmp(n) >= 0 {
|
|
return nil, nil
|
|
}
|
|
if s.Sign() <= 0 || s.Cmp(n) >= 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
// CPU is the single source of truth, via the canonical luxfi/crypto/
|
|
// secp256r1 (NIST P-256) verifier. The luxfi/accel ECDSAVerifyBatch kernel
|
|
// is fixed to secp256k1 (it has no curve selector); feeding it P-256 points
|
|
// makes it evaluate the verify equation under the WRONG curve parameters,
|
|
// so its verdict was unrelated to this P-256 verifier -- a CPU/GPU
|
|
// consensus split. Re-introducing accel requires a P-256 kernel proven
|
|
// byte-identical to luxsecp256r1.Verify.
|
|
if luxsecp256r1.Verify(hash, r, s, x, y) {
|
|
return successResult, nil
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
// Name returns the precompile name
|
|
func (c *Contract) Name() string {
|
|
return "P256VERIFY"
|
|
}
|
|
|
|
// Verify is a convenience function for direct verification.
|
|
// Delegates to canonical luxfi/crypto/secp256r1, which performs
|
|
// curve membership and (r,s) range validation internally.
|
|
func Verify(hash []byte, r, s, x, y *big.Int) bool {
|
|
return luxsecp256r1.Verify(hash, r, s, x, y)
|
|
}
|