// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import {ISP1Verifier, ISP1VerifierWithHash} from "./ISP1Verifier.sol"; /// @title AerePQStarkVerifier - ISP1Verifier-compatible adapter for the native POST-QUANTUM /// STARK-verification precompile at 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify, no /// BN254 Groth16 wrap). /// /// @notice This adapter lets every existing SP1 consumer on AERE (AereEVMValidityV2, the compliance /// privacy pool, AereProofRegistry, AereOutboundVerifierV2, the zkML verifier, the zk light /// clients, ...) switch from the Shor-breakable BN254 Groth16 gateway /// (0x9ca479C8c52C0EbB4599319a36a5a017BCC70628, route 0x4388a21c) to the hash-based PQ /// precompile by a ONE-LINE address swap: point the consumer's `SP1_VERIFIER` immutable at /// an instance of this contract instead of at the gateway. The `verifyProof` ABI is /// byte-identical to Succinct's ISP1Verifier (reverts on an invalid proof, returns nothing /// on success), so no consumer code changes - only the constructor argument. /// /// @dev WIRE. It re-frames the SP1 call `(programVKey, publicValues, proofBytes)` into the /// precompile's self-describing v1 envelope (see /// docs/PQ-STARK-VERIFIER-PRECOMPILE-2026-07-18.md section 3): /// /// magic("AS1\0") || version(1) || configId(1) || reserved(2) /// || vkeyDigest(32) || publicValuesLen(4 BE) || publicValues /// || proofLen(4 BE) || proofBytes /// /// `vkeyDigest` here is passed through as the 32-byte `programVKey` (the precompile treats /// it as the Poseidon2 digest of the recursion vk / program vk; the exact digest domain is /// pinned in the spec). `configId` selects the frozen FRI config (1 = SP1 inner/shrink, /// 2 = SP1 wrap). The precompile returns the 32-byte word `0x..01` iff the inner STARK /// verifies, and EMPTY otherwise; this adapter reverts on anything but that success word. /// /// @dev ============================ HONEST SCOPE - READ THIS ============================ /// The precompile at 0x0AE8 is a REFERENCE SKELETON as of 2026-07-18 (NOT AUDITED, NOT a /// working verifier, and NOT activated on any AERE network - mainnet chain 2800 has exactly /// the FIVE PQC precompiles 0x0AE1..0x0AE5). Its BabyBear field layer, wire parser, and /// verifier structure are real and testable, but the Poseidon2 permutation, FRI folding /// arithmetic, and SP1 recursion-AIR constraints are DELEGATED and not yet ported, so the /// precompile FAIL-CLOSES: it returns EMPTY for every input and this adapter therefore /// REVERTS on every proof. That is the safe direction: a consumer swapped to this adapter /// before the core is completed + externally audited + founder-activated simply DENIES all /// proofs; it can never accept a forged one. Do NOT deploy a real consumer against this /// adapter until 0x0AE8 is activated per /// docs/PQ-STARK-VERIFIER-ACTIVATION-2026-07-18.md. Nothing here changes AERE consensus, /// which remains classical secp256k1 ECDSA QBFT. contract AerePQStarkVerifier is ISP1VerifierWithHash { /// @notice The native PQ STARK-verify precompile. address internal constant PQ_STARK_PRECOMPILE = address(0x0AE8); /// @notice v1 envelope constants. bytes4 internal constant MAGIC = 0x41533100; // "AS1\0" uint8 internal constant WIRE_VERSION = 1; /// @notice Which frozen FRI config the proofs use. 1 = SP1 inner/shrink, 2 = SP1 wrap. /// Immutable so an instance is bound to exactly one proof family. uint8 public immutable CONFIG_ID; error WrongVerifierSelector(bytes4 received, bytes4 expected); error PQProofInvalid(); /// @param configId 1 for the SP1 inner/shrink STARK, 2 for the SP1 wrap STARK. constructor(uint8 configId) { require(configId == 1 || configId == 2, "AerePQStarkVerifier: bad configId"); CONFIG_ID = configId; } /// @inheritdoc ISP1VerifierWithHash /// @dev A domain-separated tag so a consumer can tell a PQ verifier apart from the BN254 one. /// This is NOT the SP1 Groth16 VERIFIER_HASH; PQ proofs carry no BN254 selector. function VERIFIER_HASH() external pure returns (bytes32) { return keccak256("AerePQStarkVerifier.v1"); } /// @notice The circuit/proof family this instance verifies. function VERSION() external pure returns (string memory) { return "pq-stark-v1(reference-skeleton)"; } /// @notice Verify an SP1 inner FRI/STARK proof directly against the PQ precompile. /// @dev Reverts (PQProofInvalid) unless the precompile returns the 32-byte success word. /// Signature and semantics match Succinct's ISP1Verifier exactly, so this is a drop-in. /// @param programVKey 32-byte program/recursion vkey digest (Poseidon2 digest domain per spec). /// @param publicValues The public values the SP1 program committed, ABI-encoded as usual. /// @param proofBytes The serialized Plonky3 shard (FRI/STARK) proof for this config. function verifyProof( bytes32 programVKey, bytes calldata publicValues, bytes calldata proofBytes ) external view { bytes memory envelope = abi.encodePacked( MAGIC, WIRE_VERSION, CONFIG_ID, bytes2(0), // reserved programVKey, // vkeyDigest(32) uint32(publicValues.length), // publicValuesLen (BE) publicValues, uint32(proofBytes.length), // proofLen (BE) proofBytes ); (bool ok, bytes memory ret) = PQ_STARK_PRECOMPILE.staticcall(envelope); // Success == precompile returned exactly the 32-byte word 0x..01. EMPTY (unactivated // precompile, malformed input, or the skeleton's fail-closed path) -> revert. Fail-closed. if (!ok || ret.length != 32 || bytes32(ret) != bytes32(uint256(1))) { revert PQProofInvalid(); } } /// @notice Non-reverting variant for callers that already staticcall-wrap (e.g. the compliance /// pool adapter and AereProofRegistry.verifySP1Only). Returns false instead of reverting. function isValidProof( bytes32 programVKey, bytes calldata publicValues, bytes calldata proofBytes ) external view returns (bool) { bytes memory envelope = abi.encodePacked( MAGIC, WIRE_VERSION, CONFIG_ID, bytes2(0), programVKey, uint32(publicValues.length), publicValues, uint32(proofBytes.length), proofBytes ); (bool ok, bytes memory ret) = PQ_STARK_PRECOMPILE.staticcall(envelope); return ok && ret.length == 32 && bytes32(ret) == bytes32(uint256(1)); } }