Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
128 lines
6.8 KiB
Solidity
128 lines
6.8 KiB
Solidity
// 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));
|
|
}
|
|
}
|