mirror of
https://github.com/luxfi/chains.git
synced 2026-09-24 00:26:00 +00:00
H1 — shielded-tx ProofVerifier was classical-only (groth16/plonk) and fail-closed only via all-zero dummy VKs; real bn254 VKs would re-enable a CRQC-forgeable shield/unshield mint/steal path. Add ZConfig.StrictPQ and a single gate refuseClassicalUnderStrictPQ(proofType) called at the top of VerifyTransactionProof AND per-tx in the GPU batch path (which verifies groth16 inline, bypassing VerifyTransactionProof). Under strict-PQ only "stark" is accepted — wired to precompile/starkfri (cSHAKE256/Goldilocks FRI, fail-closed until the prover binds), public inputs bound to the tx's nullifiers+commitments. loadVerifyingKeys now ERRORS on a non-dummy bn254 VK under strict-PQ (explicit fail-closed, not implicit dummy-keys). The remaining prover work (p3q_prove C ABI + shielded/ML-DSA AIR) is tracked as a code comment; shielded value is effectively disabled on strict-PQ chains until it lands — the correct no-classical-fallback posture. H2 — Groth16/PLONK precompiles (0x80/0x81) were registered unconditionally and Run([]byte) has no AccessibleState to call RefuseUnderStrictPQ. RegisterZKPrecompiles(registry, strictPQ) now omits Groth16/PLONK on strict-PQ chains (registering STARK 0x82 + fail-closed Halo2/Nova stubs only) so 0x80/0x81 fail closed by absence; non-strict chains keep them as an optional building block (kept, not deleted). CrossChainZKVerifier gains StrictPQ that refuses routing Groth16/PLONK while still routing STARK. H4 — verifyPLONK computed a self-cancelling pairing, discarded the result, ignored public inputs, and returned valid for ANY >=544-byte blob (total bypass). Now fails closed (errPLONKVerifierIncomplete) after structural parsing — NEVER universal-accepts. Groth16/bn254/PLONK remain present-but-gated; STARK/FRI (P3Q) is the only accepted proof path on strict-PQ chains. Tests: strict-PQ refuses groth16/plonk/bulletproofs (direct + GPU-batch); real-VK-load errors; STARK fails-closed unbound and accepts bound with nullifier+commitment-bound public inputs; non-strict path unchanged; strict registry omits classical + keeps STARK; cross-chain strict refuses classical; PLONK never universal-accepts. H-02 regression updated to the new wired-STARK fail-closed behavior.
68 lines
2.5 KiB
Go
68 lines
2.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
package precompiles
|
|
|
|
import "fmt"
|
|
|
|
// PrecompileRegistry allows registering precompiled contracts at fixed addresses.
|
|
type PrecompileRegistry interface {
|
|
Register(addr byte, contract PrecompiledContract)
|
|
}
|
|
|
|
// RegisterZKPrecompiles registers the Z-Chain ZK verifier precompiles.
|
|
//
|
|
// strictPQ gates the classical (quantum-breakable) verifiers. The
|
|
// PrecompiledContract.Run([]byte) interface carries no AccessibleState,
|
|
// so a strict-PQ chain cannot refuse at call time the way the
|
|
// AccessibleState-aware precompile/zk path does (RefuseUnderStrictPQ).
|
|
// Instead the decision is made HERE, at genesis registration: on a
|
|
// strict-PQ chain the classical Groth16 (0x80) and PLONK (0x81)
|
|
// verifiers are simply NOT REGISTERED, so any call to those addresses
|
|
// hits "no precompile at address" — fail-closed by absence. The
|
|
// post-quantum STARK/FRI verifier (0x82) is the only accepted proof
|
|
// system. Halo2 (0x83) and Nova (0x84) are stubs that already
|
|
// fail-closed (errNotImplemented) on every chain.
|
|
//
|
|
// On a non-strict chain every verifier is registered: Groth16/PLONK are
|
|
// kept as an OPTIONAL building block for cross-chain verification of
|
|
// classical proofs, never deleted.
|
|
func RegisterZKPrecompiles(registry PrecompileRegistry, strictPQ bool) {
|
|
// Post-quantum + always-fail-closed stubs: registered on every chain.
|
|
registry.Register(STARKVerifierAddr, &STARKVerifier{})
|
|
registry.Register(Halo2VerifierAddr, &Halo2Verifier{})
|
|
registry.Register(NovaVerifierAddr, &NovaVerifier{})
|
|
|
|
if strictPQ {
|
|
// Classical pairing-based verifiers are forbidden on strict-PQ
|
|
// chains: omit them so 0x80/0x81 resolve to "no precompile".
|
|
return
|
|
}
|
|
|
|
// Non-strict chain: classical verifiers available as an optional
|
|
// (quantum-breakable) building block.
|
|
registry.Register(Groth16VerifierAddr, &Groth16Verifier{})
|
|
registry.Register(PLONKVerifierAddr, &PLONKVerifier{})
|
|
}
|
|
|
|
// MapRegistry is a simple map-based precompile registry for testing.
|
|
type MapRegistry struct {
|
|
contracts map[byte]PrecompiledContract
|
|
}
|
|
|
|
func NewMapRegistry() *MapRegistry {
|
|
return &MapRegistry{contracts: make(map[byte]PrecompiledContract)}
|
|
}
|
|
|
|
func (r *MapRegistry) Register(addr byte, contract PrecompiledContract) {
|
|
r.contracts[addr] = contract
|
|
}
|
|
|
|
func (r *MapRegistry) Get(addr byte) (PrecompiledContract, error) {
|
|
c, ok := r.contracts[addr]
|
|
if !ok {
|
|
return nil, fmt.Errorf("no precompile at address 0x%02x", addr)
|
|
}
|
|
return c, nil
|
|
}
|