mirror of
https://github.com/luxfi/precompile.git
synced 2026-09-24 00:26:21 +00:00
main
6
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
9ababce758 |
A precompile's config key names the precompile
Forty-five of the fifty-six suite modules keyed off <name>Config and eleven did not, in three other spellings: Verify, Threshold, Encapsulate. The LP-4200 unified PQCrypto block carried four of those conventions across its eight addresses — two KEMs named differently (mlkemConfig beside hqcEncapsulate) and two threshold schemes named differently (pulsarVerify beside coronaThreshold) — so the block that exists to be one thing did not read as one thing. The deviations also named an OPERATION where a precompile belongs. HQC decapsulates as well as encapsulates and the Verify precompiles do more than verify, so hqcEncapsulate was the name of one entry point standing in for the whole module. A config key selects a precompile; it is not a method selector. All fifty-six are <name>Config now. There is no compatibility shim: an upgrade.json naming an old key is naming something that does not exist, which luxd already refuses loudly, and every chain config in the estate moves with this. Co-authored-by: Hanzo Dev <[email protected]> |
||
|
|
414aa8fa29 |
precompiles: parse calldata through one cursor, answer with one verdict
Class 1. Every parser walked its wire format with hand-written slice expressions, and the reason that is dangerous is not the one written in the old fixtures' comments. A precompile's input is the two-index slice Memory.GetPtr returns; nothing on the path to Run copies it. len is what the caller declared and paid for, cap is the rest of EVM memory, and the caller MSTOREd it. So s[a:b] past len does not panic and does not halt a validator -- it returns bytes the attacker chose and the verifier answers over them. Reverting one cursor read in frost and declaring the input one byte short returns verdict word 1 over a byte nobody sent: an accept, not a crash. contract.Cursor bounds every read against len, caps what it returns so a field cannot be re-sliced forward, and refuses a declared count before it sizes anything -- Fields divides rather than multiplying, because n*w wraps and a wrapped product compares as small. STARKVerifyPrecompile did make([]uint64, n) for an attacker-chosen uint32, up to 34 GB, and only then checked. Class 2. (bool, error) has four states and three meanings: (false, nil) is both "verified and rejected" and "nothing ran". That collision is why one verifier could fold its rounds, discard the result, return true, and read as ordinary code. contract.Verdict carries the missing bit, so its zero value denies and a refusal is distinguishable from a rejection. A positive can only come from Checked, and Checked(true) fails the build -- reintroduced in fflonkVerify, the compiler and go vet both pass it, the gate names the line. Nine parse functions across eight packages carry zero hand-written bounds now. The class rests on two checks in Cursor; deleting either fails every suite, including with fixtures degraded back to plain append -- which is what the class used to depend on. |
||
|
|
a466c21334 |
crypto precompiles: a halt from calldata, two forgeries, and free calls
ring's lsagVerify halted the chain from calldata. A scalar of 0 or >= N makes ScalarMult return a nil point and curve.Add dereferences it; geth's dispatch has no recover, so one transaction stops every validator. Its parser also left scalars unvalidated, and the two builds disagree about them -- cgo refuses an out-of-range scalar, pure-Go reduces it silently -- so the same calldata produced different verdicts depending on how the node was compiled. frost's Schnorr check was a total forgery. An unliftable x-only key fell through to the identity, collapsing z*G == R + c*Y to z*G == R, so choosing any z and publishing R = z*G verifies under every off-curve x. cggmp21 forges the same way if either coordinate comparison is dropped: one coordinate does not identify a secp256k1 point, since (P, -P) share x and (P, lambda*P, lambda^2*P) share y through the GLV endomorphism. pulsar returned the full supplied gas on four exits -- work done, nothing billed -- and ring had three arms returning zero. Corona quoted base + n*10000 with n an unbounded uint32 off the wire, up to 4.29e13 gas. Capping the charger alone would have left the exported EstimateGas disagreeing with the fee, so quote and charge are now one function. corona's deserializePoly ignored the count from bytes.Reader.Read, which returns a short read with a nil error, so a truncated blob was zero-filled and fabricated coefficients were accepted as signed material. Its exported ExpectedSignatureSize said 4096 against a real 163840 -- it omitted the A matrix and bTilde that its own parser reads -- so any caller sizing calldata by it built a call that could only be rejected. Per-party gas prices nothing: corona's verifier never referenced t or n (dead parameters, now gone), and frost and cggmp21 use totalSigners only for a structural check. All three charge a variable fee for constant work. The cap contains it; the schedule is consensus-visible and left alone. Vectors are frozen hex with the generators deleted, because a fresh-key round trip passes when signer and verifier are uniformly wrong together. Each carries a genuine signature by the same key over the same message under a sibling slot's context, which must be refused -- domain separation tested against a real signature rather than corrupted bytes. 189 mutations, one at a time with restore-and-reverify. Five survivors, each proven equivalent by a downstream check reaching the identical verdict. |
||
|
|
fcd89aa0cc |
pulsar: fix mlen uint64 overflow → chain-halt (CRITICAL launch blocker)
verify parsed mlen from attacker's low-8-bytes, then guarded with len(body) < mlen+sigSize — which OVERFLOWS uint64 when mlen is near MaxUint64, slipping past the guard so body[:mlen] panics. A panic in geth's precompile dispatch has no recover() → every validator halts on the tx. Fixed with a non-overflowing subtraction guard (mlen > len(body) || len(body)-mlen < sigSize). Regression test TestVerify_MlenOverflowRejected proves reject-not-panic. Red-team finding #5. |
||
|
|
d3f1886f05 |
CRIT: register Pulsar (0x012204) + P3Q (0x012205) precompile modules
Neither pulsar/ nor p3q/ had a module.go. ML-DSA / ML-KEM / SLH-DSA / Corona each had an init() that calls modules.RegisterModule(...) at import time — that's how the registry binds the precompile's Configurator + Contract to its Address. Without a module.go for pulsar + p3q, neither was reachable from chain transactions; calling the address fell through to 'no code' (silently returned empty, caller saw 'verification = 0'). The Pulsar slot is the canonical Module-LWE threshold signature verifier in the LP-4200 block — the StrictPQ profile's primary threshold-signing path. P3Q is the strict-PQ STARK proof verifier for the family canonical Plonky3-fork backend. Add module.go for both, mirroring the mldsa/corona pattern: configurator + Config + init() registering against the canonical address. Tests pass for both packages. Also update modules/registerer.go to document the full 0x012200 block (was only listing through Corona-Threshold=0x12204; now explicit through 0x12206 Corona). |
||
|
|
f69b524211 |
precompile: decomplect strict-PQ gate across every classical Run() (#2)
* precompile: pulsar threshold ML-DSA at LP-4200 0x012204 Adds the Pulsar precompile slot for threshold ML-DSA verification. Pulsar's wire format is byte-equal to FIPS 204 ML-DSA (Class N1 manifesto), so the verification op dispatches to luxfi/crypto/mldsa under a Pulsar-specific domain-separation context. - Address: 0x0000000000000000000000000000000000012204 (LP-4200 unified PQCrypto block; companion to ML-KEM 0x012201, ML-DSA 0x012202, SLH-DSA 0x012203). - Three modes: Pulsar-44 / Pulsar-65 / Pulsar-87 mirroring ML-DSA Categories 2 / 3 / 5. Production target is Pulsar-65. - Gas: per-mode base + 10/byte (matches ML-DSA precompile tier). - Context: lux-evm-precompile-pulsar-v1 to keep on-chain transcripts unambiguous vs. single-party ML-DSA at 0x012202. Tests: 5 passing (address pin, gas table, round-trip across 3 modes, tampered-signature rejection, invalid-mode rejection). GOWORK=off go test -count=1 ./pulsar/... is green. Imports github.com/luxfi/pulsar-m (pre-rename); will be retargeted to github.com/luxfi/pulsar when the cross-repo move lands. * precompile/LLM.md: mark Pulsar 0x012204 as IMPLEMENTED Pulsar precompile now lives in precompile/pulsar/ and dispatches to luxfi/crypto/mldsa under a Pulsar-specific context. Update the LP-4200 canonical note to reflect deployment status alongside the other three PQCrypto slots. * precompile/zk: rip classical pairing-based opcodes (PQ-only) Shor breaks Groth16/PLONK/fflonk/Halo2/KZG/IPA/Bulletproofs. The 0x0900 EVM precompile now exposes only the PQ-safe ops: hash-based nullifiers (0x21) and Merkle commitment inclusion (0x22). PQ STARK verification is dispatched via the Z-Chain envelope path (consensus/protocol/zchain/verify.go), not from this precompile. * precompile/kzg4844: retire BLS12-381 KZG blob-opening precompile KZG point-evaluation verifies via the BLS12-381 pairing e(C - [y]G1, G2) == e(W, [tau - z]G2). The discrete-log assumption that anchors BLS12-381's security is broken by Shor's algorithm, so EIP-4844-style blob commitments give zero soundness against a future quantum adversary. The Lux EVM is PQ-only. Strip the implementation (gokzg4844, accel GPU MSM, all CPU opcodes). The module still registers the address 0xB002 so the precompile registry numbering stays stable, but every call to the precompile returns the new contract.ErrClassicalForbiddenInPQ sentinel. The minimum 21000 gas is deducted on every call to prevent the retired address being used as a zero-cost probe. Blob availability on the Lux EVM is now provided by hash-based commitments (Blake3 / Poseidon2) and STARK-validated state roots. * precompile/zk: strip Go-level classical pairing SNARK verifiers The Run() path of the 0x0900 ZK precompile only exposes hash-based nullifier checks and Merkle commitment inclusion, both PQ-safe. The remaining Go-level methods on ZKVerifier (VerifyGroth16, VerifyPlonk, VerifyFflonk, VerifyKZG, VerifyRangeProof, VerifyRollupBatch and their helpers) all dispatched through a BN254 pairing check or a BLS12-381 KZG opening, both broken by Shor's algorithm. Strip those methods, their bn256 / kzg4844 / accel-MSM dependencies, and the rollup-batch types (RollupBatch, RollupConfig, StateTransition, KZGSetup, BN254P) that only existed to feed the pairing verifier. Update verifier_test.go to cover only the PQ-safe surface that remains: NewZKVerifier, RegisterVerifyingKey (Go-side bookkeeping), nullifier check/spend, confidential pool create/add-commitment, hash-based Merkle inclusion. PQ STARK verification continues to be handled by the P3Q backend on the Z-chain envelope path (consensus/protocol/zchain/verify.go); it was never wired through this Go struct. * precompile/babyjubjub: retire BN254-companion twisted Edwards curve Baby Jubjub is the ZK-friendly twisted Edwards curve aligned to BN254's scalar field (used by circom / iden3 / Polygon zkEVM / Semaphore / RLN / EdDSA-on-BN254). Its security reduces to discrete-log on a curve of BN254 scalar order; Shor breaks that in poly time. Every consumer of Baby Jubjub is a classical pairing-based SNARK gadget with no PQ analogue, so the Lux EVM retires the precompile rather than offer a quantum-broken primitive. Strip the gnark-crypto twisted-edwards implementation. The module still registers address 0x0500..0007 so the precompile registry numbering stays stable, but every call returns contract.ErrClassicalForbiddenInPQ with 21000 minimum gas deducted. * precompile/pedersen: retire BN254 Pedersen commitment precompile The implementation built homomorphic Pedersen commitments over BN254 G1 (C = v*G + r*H, plus a 32-slot vector commitment). Both binding and hiding reduce to the discrete-log assumption on BN254, which Shor's algorithm breaks. The Lux EVM is PQ-only and retires the precompile rather than offer a quantum-broken commitment. Strip the gnark-crypto BN254 implementation and the 34 deterministic generators. The module still registers address 0x0500..0006 so the precompile registry numbering stays stable, but every call returns contract.ErrClassicalForbiddenInPQ with the 21000 minimum gas deducted. PQ-safe commitment schemes on the Lux EVM use hash-based primitives (Blake3 0x3202, Poseidon2 0x3201) and STARK gadgets on Z-chain. * Revert "precompile/pedersen: retire BN254 Pedersen commitment precompile" This reverts commit |