The public history carried kat/__pycache__/mlkem768_reference.cpython-314.pyc, a compiled Python artifact embedding the operator's absolute local path. Text secret scanners do not read compiled binaries, which is exactly how it slipped through, and removing it from the tip would have left it reachable through the old root commits. So this repository is republished from a single clean root. This root also carries, from the previously unpublished line of work: - corrected LICENSE year, LICENSING.md, VERIFY-POLICY.md, and CITATIONS-UNRESOLVED.md remeasured 2026-08-11 (101 paths, README aligned) - O-018: run_consensus_verification.py ran 19 of 29 models and reported PASS; it now runs all 29, and computemarket_smt.py gains resolveByTimeout / reclaimUnsettled cases plus a negative control - O-006: the word 'audited' removed from next to Bouncy Castle, twice, after a concurrent edit resurrected it - O-014: prior art named and dated - Algorand's native falcon_verify shipped about ten months before AERE's precompiles; the primacy claim is withdrawn where it was implied - bench/ scripts parametrized so they actually run for an outsider (the earlier textual sanitization left $STAGING unexpanded inside Python strings) - AIP-2/AIP-3 errata with measured figures, spec remeasurements at 2026-08-01, and the spec-zk-stack retractions (owner is an operational key, not the Foundation; 'maximally sound' withdrawn; aggregator V1 deprecated) The redacted bench-host environment files from the sanitized line are kept exactly as published; the unredacted local variants are not carried.
47 KiB
AERE Network: Zero-Knowledge Verification Stack
Chain: AERE Network mainnet, chain ID 2800 (Hyperledger Besu QBFT).
Status date: 2026-08-01. Every on-chain figure below was re-read from https://rpc.aere.network at head block 11,810,041 on that date.
Source of truth for addresses: sdk-js/src/addresses.ts. Contract source: contracts/contracts/.
Scope: the on-chain proof-verification layer. This covers SP1 (Groth16 / Plonk), RISC Zero, KZG / EIP-4844, Halo2, recursive proof aggregation, zkML, zk-KYC / zk-compliance, and the storage-proof coprocessor.
This document describes what each verifier proves, the exact on-chain path a proof takes to be accepted, and the honest trust boundary of each. It is deliberately conservative about claims: every capability is marked either live (deployed and demonstrated with an on-chain transaction recorded in the repo) or roadmap / in development.
0. Honesty preamble (read this first)
Six points frame everything below.
-
This is an application / account-layer proof stack, not a consensus feature. AERE's validators sign classical QBFT; none of the verifiers here change how blocks are produced or finalized. The verifiers are ordinary EVM contracts that any account can call. They add verifiable computation on top of the chain, not inside consensus.
-
The network is small and centralized today. AERE runs seven QBFT validators operated by a single operator, on a single client (Besu), at a measured mean block interval of 0.5175 s over the last 2,000 blocks. There is no external security audit of the AERE-authored contracts. On-chain usage of these verifiers is thin: the canonical
AereProofRegistryreportsprogramCount() = 1andproofCount() = 1at head. None of this is hidden; it is the baseline against which the engineering below should be judged. -
Address-citation discipline, corrected 2026-08-01. Addresses are printed verbatim only when they appear in the canonical registry
sdk-js/src/addresses.ts. An earlier revision of this spec withheld the hex for five contracts (KZG, Halo2, the recursive aggregator, the zkML verifier, the state-root anchor) on the grounds that they were "pending canonical registration". That is no longer true and the omission is corrected here: all five are in the registry today, and their addresses are printed below. Withholding them stopped protecting anything and only kept integrators away from the canonical source. One contract in that group,AereHalo2ProofAnchor, really is still absent from the registry, and is marked as such. -
Deprecation is stated, not hidden. Several contracts in this stack have been superseded by corrected redeployments. Where that is so, this spec names the deprecated address, says what is wrong with it, and prints the replacement next to it. A reader who learns from us that something is superseded is better served than one who finds out from the chain.
-
Ownership language. Where a contract is described as under an "operational deployment account", that is the literal measured state:
owner()returns a deployment or operations account rather than the Foundation account0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3. These contracts are on the path to governance ownership. Nothing about which accounts those are, or what they could reach, belongs in a public spec. -
We do not claim uniqueness beyond what is demonstrable. Where a superiority framing is tempting, the honest statement is narrow: AERE hosts a multi-prover verification surface (SP1 plus RISC Zero plus a raw KZG precompile path plus a generated Halo2 verifier) behind a route-by-selector gateway, with a permissionless attestation registry on top. That is unusual to have all in one place; it is not a claim that no other chain can verify any one of these.
1. Architecture: route-by-selector, verify-then-record
Every proof system in the stack follows the same two-layer shape.
Layer 1, the verifier. A concrete verifier (SP1 Groth16, SP1 Plonk, a RISC Zero Groth16 verifier, a generated Halo2 verifier, or the EIP-4844 precompile) takes proof bytes plus public inputs and either returns cleanly or reverts. The canonical SP1 and RISC Zero verifiers follow the Succinct / RISC Zero convention: verifyProof / verify returns nothing and reverts on an invalid proof. AERE's wrapper contracts consistently try/catch that revert and translate it into a typed InvalidProof() error, so a caller never mistakes empty return-data for a failure.
Layer 2, routing and attestation. Concrete verifiers sit behind routers keyed on the first 4 bytes of the proof (a version selector):
SP1VerifierGatewayroutes an SP1 proof to the correct SP1 verifier version by its leading selector, refusing frozen routes.RiscZeroVerifierRouterroutes a RISC Zero seal by its leading selector to the matching zkVM-version verifier.
Because routing is by selector, a new prover version is added with a single addRoute / addVerifier call on the router. Downstream application contracts that hold only the router address need no migration when a new prover version ships. AERE's application verifiers (storage proof, zk-KYC, zkML, rollup validity, aggregator) all hold the SP1 gateway address, not a concrete verifier, exactly for this reason.
On top of the routers, AereProofRegistry records each successful verification as a permanent, event-indexed attestation, with permissionless program registration.
2. SP1 (Succinct Labs): Groth16 and Plonk
Contracts (canonical, in addresses.ts):
SP1VerifierGateway:0x9ca479C8c52C0EbB4599319a36a5a017BCC70628SP1VerifierGroth16_v6_1_0:0xb5456d48bFdA70635c13b6CBE1Ad0310Dc0171aD(current production)SP1VerifierGroth16_v6_0_0:0xa9BD3020bC9a9614F9e2BC1618153c9fB1890ca6(prior production, retained)SP1VerifierPlonk_v6_1_0:0x24a7a85E6D9A2b120F2730880bE7283dFB14d29B
What it proves. SP1 is a RISC-V zkVM. A guest Rust program is compiled to an ELF; its verification key (programVKey, a bytes32) binds that exact ELF. A proof attests that the guest ran to completion on some private witness and committed a specific publicValues byte-string. Groth16 gives constant-size (roughly 260 to 356-byte) proofs at about 300k gas to verify; Plonk avoids a per-circuit trusted setup at a somewhat higher verification cost. Both are routed by the same gateway.
Verification path. SP1VerifierGateway.verifyProof(programVKey, publicValues, proofBytes) reads bytes4(proofBytes[:4]) as the selector, looks up routes[selector], reverts RouteNotFound / RouteIsFrozen on a bad route, and otherwise delegates to the concrete verifier's verifyProof. The Groth16 route selector on chain 2800 is 0x4388a21c (the leading 4 bytes of the v6.1.0 Groth16 verifier's VERIFIER_HASH 0x4388a21c687fdd5f...); the Plonk verifier hash is 0x5a093a2f.... Ownership and trust boundary, measured 2026-08-01. The gateway is Ownable, and owner() returns an operational deployment account, not the Foundation account 0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3. An earlier revision of this spec said "Ownable (Foundation)"; that was wrong, and the contract is on the path to governance ownership.
The same earlier revision drew a stronger conclusion than the code supports: it said the route owner "cannot forge a verification, because the actual cryptographic check happens in the immutable concrete verifier the route points at". Only the first half of that is true. Each concrete verifier is indeed immutable, so no one can change what a given verifier checks. But addRoute(bytes4,address) (selector 0x8c95ff1e in the live 1,746-byte gateway bytecode) and freezeRoute(bytes4) (0x814856f4) mean the route owner chooses which contract performs the check for a given selector. The honest statement is therefore:
A proof accepted through the gateway is exactly as sound as the concrete verifier the route currently points at. Immutability protects the verifier; it does not by itself constrain the route owner. An integrator who needs the stronger guarantee should hold the concrete verifier address (
0xb5456d48…,0x24a7a85E…) directly, or wait for the route to be frozen, or wait for the gateway to sit behind governance. Live routes measured at head:0x4388a21cto0xb5456d48…,0x5a093a2fto0x24a7a85E…, neither frozen.
This is a real limitation of the router pattern, not of this deployment alone, and the same reasoning applies to RiscZeroVerifierRouter in section 3.
Notes. A legacy v4.0.0-rc.3 SP1 verifier and registry were deployed on 2026-05-31 (recorded in contracts/deployments/zkverify-stack.json) and retain a historical Fibonacci fixture proof (registry proof id 0). Production traffic uses the v6.1.0 gateway above. Every AERE application verifier in this spec that says "SP1" routes through this one gateway.
3. RISC Zero zkVM: Groth16 receipts
Contracts (canonical, in addresses.ts):
RiscZeroVerifierRouter:0x3f7015BC3290e63F7EC68ecF769b00aB296a249C(2,042 bytes;owner()measured at head returns an operational deployment account, not the Foundation account; moving to governance)RiscZeroGroth16Verifier_DEPRECATED:0x95cB30f3bdb3187f39203A9907bf707Aef07a1FD(do not use)RiscZeroGroth16Verifier:0xb6fD00D88Bf8B08d6371d2D98E0e239B89Ab5B9D(corrected)RiscZeroVerifierRouter_Corrected:0x62b96F7211F832f47d8Fc0D8317D5867B0Af43E5AereProofRegistry_Corrected:0x174F616E2048A71408E2491791ef77cd6913bbEf
What it proves. RISC Zero is a second RISC-V zkVM. A receipt attests that a guest with a given imageId produced a journal whose digest is journalDigest. RiscZeroVerifierRouter.verify(seal, imageId, journalDigest) routes by the seal's leading selector to the concrete Groth16 verifier, which checks the seal against RISC Zero's CONTROL_ROOT / BN254_CONTROL_ID circuit-binding constants (ControlID.sol) for a specific zkVM release (VERSION = "5.0.0-rc.1").
A real bug, honestly recorded. The originally deployed RISC Zero verifier (0x95cB...a1FD) was bootstrapped with a non-canonical control root (0xb1f640...488167) that matches no released risc0 version. That wrong root computes selector 0xc27d1bc0, so the router could never route a genuine risc0 5.0.0-rc.1 receipt (which carries selector 0xef6cb709): every real seal reverted SelectorUnknown. The fix, recorded in contracts/deployments/risc0-verifier-fix.json, deployed a corrected verifier (0xb6fD...5B9D) with the genuine control root 0x4ec911...bde5b04 and selector 0xef6cb709.
Why there is a "corrected" side stack, and the premise that turned out to be wrong. A parallel corrected router (0x62b9...43E5) and registry (0x174F...bbEf) were deployed to record a real RISC Zero proof: a risc0 5.0.0-rc.1 Groth16 factorization proof (guest proves knowledge of a nontrivial factorization of n = 11,917,497,200) was registered and recorded (submit tx 0xf9269dde...396e629, record id 0), with independent public-RPC read-back confirming accept-on-valid and VerificationFailed() on tampered journal or seal. That proof and that read-back are real and stand.
The reason given for building that side stack does not stand. An earlier revision of this spec said the account that built the fix "cannot addVerifier on the Foundation router". Measured at head, owner() on the canonical router 0x3f7015BC… returns that same operational deployment account, so it always could, and it since has. The side stack is therefore historical, not necessary. New integrations should use the canonical router and registry, not 0x62b9…43E5 / 0x174F…bbEf.
Canonical RISC Zero recording is LIVE, not roadmap (corrected 2026-08-01). An earlier revision listed one addVerifier(0xef6cb709, 0xb6fD…5B9D) as "staged but not yet executed", and called canonical R0 recording "roadmap". That transaction has been executed. Measured at head on https://rpc.aere.network:
RiscZeroVerifierRouter(0x3f7015BC…).getVerifier(0xef6cb709)returns0xb6fD00D88Bf8B08d6371d2D98E0e239B89Ab5B9D- the
verifiers(0xef6cb709)mapping returns the same address AereProofRegistry(0x0A9b0967…).risc0Router()returns0x3f7015BC3290e63F7EC68ecF769b00aB296a249C- the corrected verifier
0xb6fD00D8…carries 4,664 bytes of live code
So the canonical path from a genuine risc0 5.0.0-rc.1 seal to a recorded attestation in the canonical registry is connected end to end. What remains open is usage, not capability: programCount() and proofCount() on the canonical registry are both 1 at head, and that single record is an SP1 Fibonacci fixture, not a RISC Zero proof. Capability live, traffic zero.
4. Multi-prover attestation registry
Contract (canonical): AereProofRegistry: 0x0A9b09677DbE995ACfC0A28F0033e68F068517Ee (7,196 bytes; owner() measured at head returns an operational deployment account, not the Foundation account; moving to governance).
Measured usage, stated up front. The mechanism described below is real and the code is live. The traffic is not. At head, programCount() = 1 and proofCount() = 1. The single registered program is "SP1 Fibonacci (canonical)" and the single recorded proof is the fib(20) = 6765 fixture from block 2,151,318. Every other statement in this section describes a capability, not a volume.
Purpose. A single log that verifies and records proofs from both SP1 and RISC Zero. Its design goals:
- Verify then record.
submitSP1ProofcallsISP1Verifier(sp1Gateway).verifyProof(...)(reverts on invalid), then writes aProofRecordand emitsProofVerifiedwith the program key indexed.submitRiscZeroProofdoes the same throughIRiscZeroVerifier(risc0Router).verify(...). Off-chain consumers subscribe toProofVerifiedfiltered by their program of interest. - Permissionless program registration. Anyone registers their own SP1 vkey or R0 imageId with a human-readable name plus URL (
registerSP1Program/registerRiscZeroProgram). The Foundation candisablePrograma malicious program but cannot disable a legitimate one, and disabling never retroactively invalidates already-recorded proofs. - Free verdicts.
verifySP1Only/verifyRiscZeroOnlyareviewfunctions that give the verdict (revert-on-invalid) without writing a record, for dApps that need the answer but not a permanent attestation.
Both immutables sp1Gateway and risc0Router are set at construction. Because the registry holds the gateway/router (not a concrete verifier), SP1 v7 or a new R0 verifier plug in upstream with no registry migration. The AereProofRegistryV2.sol source in the repo is an equivalent multi-prover registry design; the canonical live registry is the address above.
5. KZG / EIP-4844 point-evaluation verifier
Contract (canonical, in addresses.ts): AereKZGVerifier: 0x6596307BD8f54d9A91FE364EBC3e594F200AC862, 1,988 bytes of live code measured at head. KAT plus on-chain verify tx recorded in contracts/deployments/kzg-verifier.json. An earlier revision of this spec called this contract "deployed, not yet in addresses.ts" and withheld the hex; it has been in the canonical registry (line 386) and the omission is corrected here.
What it proves. That a blob polynomial P committed to by a 48-byte KZG commitment evaluates to y at point z, that is P(z) == y, using the EIP-4844 point-evaluation precompile at address 0x0A. This is the primitive underlying blob-data availability and any KZG-commitment scheme.
Verification path. verifyPointEvaluation(bytes input) forwards the raw 192-byte precompile input (versioned_hash(32) | z(32) | y(32) | commitment(48) | proof(48)) to 0x0A via staticcall. On a valid proof the precompile returns exactly 64 bytes: uint256(FIELD_ELEMENTS_PER_BLOB = 4096) | uint256(BLS_MODULUS). The contract enforces both the success flag and that canonical 64-byte return (rejecting any other length or constants), then records (sender, versionedHash, z, y, block, time) append-only. checkPointEvaluation is the gas-cheap view twin. Because precompiles have no code, the contract deliberately does no extcodesize check; correctness is enforced by the strict canonical-constant return, which no empty account or stray contract can satisfy by chance.
Live evidence. EIP-4844 blob support is active on chain 2800: the precompile was confirmed against the official go-ethereum known-answer vector (pointEvaluation1), and an on-chain verify transaction is recorded (gasUsed about 255,719 per the artifact). No owner, no admin, append-only.
Honest scope. This verifies a KZG opening; it does not by itself prove that a particular blob was posted for a particular transaction. QBFT produces no blobs (BLOBBASEFEE returns 0), so this is a verification primitive available to applications, not a data-availability layer the chain itself uses.
6. Halo2 (bn254 / KZG) verifier plus anchor
Contracts: AereHalo2CubicVerifier (generated verifier) is canonical, in addresses.ts line 387: 0x414Cfe640B2770856d3D7262a7a3729f5c07dC55, 4,804 bytes of live code measured at head. Its companion AereHalo2ProofAnchor (record) is genuinely still absent from addresses.ts; its address, the proof hash and the on-chain verify tx are recorded in contracts/deployments/halo2-cubic.json. An earlier revision of this spec described both as pending registration; that was correct for the anchor and wrong for the verifier.
What it proves. The bound circuit is a real 4-row standard-PLONK circuit proving knowledge of a private x with x^3 + x + 5 == out, where out is the single public instance (demonstrated with out = 35). The verifier is generated by privacy-scaling-explorations/halo2-solidity-verifier (bn254 / KZG commitments, Bdfg21 / SHPLONK batch opening) with the verifying key embedded in bytecode.
Verification path. AereHalo2ProofAnchor.verifyAndRecord(proof, instances) calls IHalo2Verifier(VERIFIER).verifyProof(proof, instances); the generated verifier returns true and reverts on an invalid proof. On success the anchor records (sender, keccak256(proof), keccak256(instances), block, time) append-only. The verifier address is immutable; no owner, no admin. Per the artifact, on-chain verify gas is about 449,517 (proof 1152 bytes, revm gas about 300,144), with valid-accept and tampered-reject both demonstrated.
Honest scope: the SRS is a dev setup, not a ceremony. The KZG structured reference string was generated locally on the AERE build server (ParamsKZG::setup), not from a public trusted-setup ceremony. That is fine for demonstrating that AERE can host and verify a real Halo2 proof on-chain; it is not production-grade for a value-bearing circuit, where a multi-party ceremony SRS would be required. This is the single most important caveat on the Halo2 path and is stated plainly in the artifact.
7. Recursive proof aggregation
Contracts, and which one to integrate against. Both are in addresses.ts and both are live; they are not interchangeable.
| Contract | Address | Live code | Status |
|---|---|---|---|
AereProofAggregator_DEPRECATED_UNENFORCED_INNER_VKEYS |
0x6a260238890E740dB12b371E0C5d17a2470F84C5 |
4,531 bytes | DEPRECATED. Do not integrate. |
AereProofAggregatorV2 |
0x944dE720D6C9696Bdf10b6E2F00dd21ABB1A2b4C |
4,663 bytes | CANONICAL |
Why V1 is deprecated, stated plainly. V1 counted how many folded inner programs were recognized and then recorded the aggregation regardless. It never enforced that every inner program was a registered one. The registry entry names it for exactly that: unenforced inner vkeys. V2 rejects an aggregation containing an unregistered inner program.
owner() on both returns the Foundation account 0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3 (measured at head). Aggregation counts at head: V1 = 2, V2 = 1. Aggregation vkey, inner-program mappings and both V1 on-chain records are in contracts/deployments/proof-aggregator.json and proof-aggregator-scale.json.
What it proves. One SP1 Groth16 "proof of proofs." An aggregation guest recursively verified N inner SP1 proofs inside the zkVM via verify_sp1_proof, and committed a single composite digest binding all N (innerVKeyDigest, innerPublicValuesHash) pairs:
composite = sha256( u32_be(n) || (innerVKeyDigest_0 || innerPvHash_0) || ... || (innerVKeyDigest_{n-1} || innerPvHash_{n-1}) )
innerVKeyDigest_i is the KoalaBear (SP1Field) verifying-key digest the recursion verifier actually checked. Binding that, rather than the BN254 on-chain vkey, is the right thing to bind, because it is the digest the recursion actually verified against.
An earlier revision of this spec called the resulting on-chain check "maximally sound". Withdrawn. That phrase described V2's behaviour and was attached to V1's address. On V1 the composite binding above is enforced, so an aggregation cannot claim children it did not fold; but membership of each child in the registered-program set was only counted, never required. "Maximally sound" is not a property V1 has. It is the property V2 was built to add.
Verification path. recordAggregation(aggVKey, innerVKeyDigests[], innerPvHashes[], publicValues, proof):
- requires
aggVKeyis a Foundation-registered aggregation program; - recomputes
compositefrom the supplied inner arrays byte-for-byte with the guest and requires it equals the 32-bytepublicValues; - verifies the Groth16 proof through the SP1 gateway (selector
0x4388a21c); - counts how many folded inner programs are recognized (registered via
registerInnerProgram, which cross-maps each KoalaBear digest to its published on-chain BN254 vkey and a label) and records the aggregation.
Live evidence. Two real aggregations are recorded on chain 2800: a 3-proof fold (inner programs zkscreen, over18, zkml-mnist; record tx 0xde4c102a..., gasUsed about 387,858) and a 10-proof scale fold of distinct statements (record tx 0xd1fd4d60..., block 8930005, gasUsed about 393,844, recognized = 10/10). Note the gas to verify the aggregate on-chain is roughly flat (about 390k) whether folding 3 or 10 inner proofs; that constant-verification-cost property is the point of recursion. The proving side is not free: the scale artifact records about 526s to produce the aggregation Groth16 and about 988s of inner proving (measured, off-chain). Tampering (mutated proof or a dropped child) reverts.
8. zkML: private machine-learning inference
Contract (canonical, in addresses.ts line 392): AereZKMLVerifier: 0xf1BF15d5018a35D21FBB4Ec868f062DF7C06783c, 3,658 bytes of live code measured at head. Model vkey/hash, accuracy and the on-chain verify tx are in contracts/deployments/zkml-mnist-verifier.json. An earlier revision called this "deployed, not yet in addresses.ts" and withheld the hex; that is corrected here.
What it proves. That a registered ML model classified a private input without revealing that input. The reference model is a genuinely trained, quantized MNIST digit classifier (784 to 256 to 10 integer MLP, ReLU, argmax). The forward pass runs inside the SP1 zkVM on a private 28x28 image; only the predicted digit is made public.
Model binding, and which call path actually enforces it. Each model is registered by the Foundation with programVKey (binds the exact guest ELF, which embeds the quantized weights), modelHash (keccak256 of the weight blob, recomputed inside the zkVM), numClasses, and a measured accuracyBps.
Only the stateful path enforces registration. The free view path does not. This is the single most important integration caveat on this contract and an earlier revision of this spec did not state it. In the live source:
submitInference/verifyAndRecordcheck that the model isregistered, and bindchainId,userandmodelHashas described below. Use this path if you need "a registered model produced this classification".- the public
verify(bytes32, bytes, bytes)twin checks only thatpublicValuesis 128 bytes long and that the Groth16 proof is valid, then decodes the class. It does not consult the model registry, does not checkchainId, does not checkuser, does not checkmodelHash, and records nothing. The contract's own source comment says as much.
So verify answers "some SP1 program with this vkey produced this output", not "the registered model classified this input". An integrator who calls verify and reports "registered model" to a user is over-claiming. If you only have the free path available, treat the vkey you pass in as the thing you are trusting, and pin it yourself. The reference model's measured accuracy is 97.98% (9798 of 10000 correct on the MNIST test set; float baseline 98.01%), recorded on-chain for transparency.
Verification path. submitInference(programVKey, publicValues, proof) requires the model is registered, requires publicValues is exactly 128 bytes = abi.encode(chainId, user, modelHash, predictedClass), verifies the SP1 proof through the gateway, then binds chainId == block.chainid, user == msg.sender, modelHash == registered model, and predictedClass < numClasses, and records the user's latest verified prediction. verify(...) is a stateless view twin. Per the artifact, the reference inference verified on-chain at gasUsed about 326,492; the guest runs about 5.22M cycles using the SP1 keccak precompile to hash the 407,592-byte weight blob (24.5M cycles without it).
Honest scope. This proves inference integrity for one small, quantized model on one dataset. It is a real, measured classifier, not a toy with hand-picked weights (it supersedes an earlier 4-to-8-to-3 toy). It is not a general zkML framework and makes no claim about large models.
9. zk-KYC / zk-compliance
Three related contracts implement "prove a compliance property without putting PII on-chain."
9.1 AereZKScreen (canonical)
Contract: AereZKScreen: 0x3A097A459FD26aC79573aCB5adB51430e473C2f1 (v3; 3,625 bytes live). owner() measured at head returns the Foundation account 0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3, so "Foundation-owned" is accurate here and was re-verified rather than assumed.
The over-18 attribute program is REGISTERED and live (measured 2026-08-01). Other AERE documents have described on-chain registration of the over-18 program as "not yet done, pending a single Foundation signature", and said that submitting an over-18 proof would revert as an unknown program. Measured, that is no longer the case. Reading programs(0x005aa94ac711bc65d0755b3a94f3622a6e8c76e2cdd708c29869587f633343c5) on the live contract returns registered = true with a non-zero authorizedRoot (0xb3b4d506…40b304) and a description recorded on-chain that reads, in part, "SP1 Groth16 zero-knowledge proof that an issuer-attested birth year implies age 18+ (born <= 2008), without revealing the date of birth. No PII on-chain." Because the authorized root is set as well as the registration, the root-binding check described below is armed, not bypassed. The capability is shipped. Usage of it is a separate question and remains thin.
What it proves. That a user passed an off-chain compliance screen (sanctions check, residency / accredited-investor / MiCA-jurisdiction attestation) inside an SP1 zkVM, anchoring only a boolean-style clearance. dApps gate access with isCleared(user, programVKey, minTimestamp); no PII touches the chain.
Verification path plus the critical soundness fix. submitProof(programVKey, publicValues, proof) requires the program is registered, requires publicValues is exactly 128 bytes = abi.encode(chainId, user, attestedAt, extraDataHash), verifies the SP1 proof, then enforces: chainId matches, user == msg.sender, attestedAt <= now, and the anti-downgrade rule attestedAt > clearedAt[user][program]. The root-binding check is the soundness core: each program binds a Foundation-set authorizedRoot, and the proof's extraDataHash must equal that root. Without it, a prover could prove membership in an allowlist of their own making (an "allowlist" containing only themselves), a total break of the compliance claim. The version history in addresses.ts records that earlier deployments had exactly this class of hole (v1 0xE9da9c... dead; v2 0x140572... had a self-clear hole); v3 (the address above) is the root-binding fix, and adds owner setAuthorizedRoot rotation and per-user revoke.
Consumer obligations (documented in-source). dApps MUST pass a real minTimestamp (for example now - freshnessWindow), never 0, and MUST treat clearance as necessary-not-sufficient, AND-ing it with a live sanctions check for sanctions-sensitive flows.
9.2 AereCompliancePool SP1 verifier (canonical)
Contracts: AereCompliancePoolSP1Verifier: 0xE2D3fa91b680E835c971761ba75Fde0204AEF95E; consumed by AereCompliancePoolV2: 0xB144c923572E5Ac1B6B961C4ccfec36917173465.
What it proves. A Privacy-Pools-style compliant withdrawal: that a note is a member of a deposit Merkle root and an approved association root, with a spent-nullifier, recipient, fee, and refund, all without linking the deposit to the withdrawal. The SP1 guest lives at zk-circuits/compliance-pool/program; its vkey is the verifier's immutable PROGRAM_VKEY (0x00aa183a... per addresses.ts).
Verification path. verify(bytes proof, uint256[8] pubs) re-abi-encodes the 8 public inputs [poolId, depositRoot, associationRoot, nullifierHash, recipient, feeRecipient, refund, fee] into the exact struct the guest commits, then staticcalls ISP1Verifier.verifyProof(PROGRAM_VKEY, publicValues, proof) and returns the success bool to fit the pool's IPrivacyPoolVerifier interface. It is a drop-in production replacement for the test MockPrivacyPoolVerifier. The pool itself (AereCompliancePoolV2) is sanctions-gated plus Travel-Rule-bound and, per addresses.ts, currently holds 0 deposits (fresh).
9.3 AereAIProof (provenance, not zk)
Contract: AereAIProof: 0xFf92c669AbF4C1DAE31eBFCC017764036d9D97e6.
Included here for completeness because it is often grouped with the compliance/attestation set, but it is signature-based, not zero-knowledge. Providers register a modelId with a signing key and anchor EIP-712-signed (modelId, inputHash, outputHash, requesterHash, timestamp, nonce) statements. Only hashes are stored (no raw prompts or completions). It gives tamper-evident AI-inference provenance (useful for agentic AERE402 flows and regulated AI audit logs) with a per-model monotonic nonce, a rotate-invalidates-old-nonce-space watermark, and ECDSA.recover against the current signer. It proves who signed what, not a hidden computation.
10. Storage-proof coprocessor
Contracts, and which one to integrate against. All four are in addresses.ts; two of them are deprecated.
| Contract | Address | Live code | Status |
|---|---|---|---|
AereStorageProofVerifier_DEPRECATED_POISONABLE |
0xF9a1A183bEb3147D88dA5927301683fEbFb9362E |
1,710 bytes | DEPRECATED. Do not read getProven() as canonical. |
AereStorageProofVerifierV2 |
0x487021f9aE018657B7335ADC6Fc254717751a1Dd |
3,160 bytes | CANONICAL |
AereStateRootAnchor_DEPRECATED_WIRED_TO_POISONABLE_V1 |
0x78b40a983E89c91Aefd8A62Be709bDF25ABB57cb |
see registry | DEPRECATED |
AereStateRootAnchorV2 |
0x0b959402283c0D2B9db4FfD7823f99782462e488 |
2,652 bytes | CANONICAL |
An earlier revision of this spec called 0xF9a1A183… "canonical, immutable, no owner". Two of those three words are still true and the important one is not. It is immutable and it has no owner. It is not canonical: the canonical registry has named it _DEPRECATED_POISONABLE and says literally "DO NOT read getProven() as canonical; repoint to V2".
Why V1 is deprecated, stated plainly. V1's stored record is keyed on (blockNumber, account, slot) but the record it writes carries whatever stateRoot the proof asserted. Because blockNumber is only metadata to the verifier (see the canonicity gap below), anyone can submit a genuine proof against a non-canonical root and thereby overwrite that key with a value that was never in the canonical chain. The record is poisonable. V2 closes this by binding to the canonicity anchor: AereStorageProofVerifierV2.CANONICAL_ANCHOR() returns 0x0b959402283c0D2B9db4FfD7823f99782462e488, verified in both directions at head.
Everything below describes the shared proof mechanism, which is the same in both versions. Integrate it through V2.
What it proves. That, at a given state root R, an account A's storage slot S held value V. The SP1 guest (aere-storage-proof-program) RLP-decodes and hash-chains the real eth_getProof account-proof and storage-proof nodes of AERE's Merkle-Patricia state trie: it walks stateRoot to storageRoot, then storageRoot to value, panicking on any node whose keccak256 does not match its parent reference. No Foundation-seeded side tree is involved.
Verification path. Public values are exactly 192 bytes = abi.encode(chainId, blockNumber, stateRoot, account, slot, value). submitProof verifies through the SP1 gateway (PROGRAM_VKEY = 0x0015e599...b659), enforces chainId == block.chainid, and records keccak256(blockNumber, account, slot) to {stateRoot, value, at}. submitProofWithExpectedRoot additionally reverts unless the proof's stateRoot == expectedStateRoot, moving the canonicity check to the call site. verify is a stateless view twin.
Live evidence. A first real proof is recorded: AereTreasury (0x687933...6119) slot 0 (its owner) equals the Foundation (0x0243A4...f3C3) at block 8915939, against canonical state root 0x515fce04...4a92f3 (confirmed equal to eth_getBlockByNumber(8915939).stateRoot); submit tx 0xe541c52b...6c54, gasUsed 348,345. Tampered proof or public values revert.
(An earlier revision added "under the EIP-7825 per-transaction cap of 16,777,216" here. That comparison is withdrawn as NOT MEASURED: we have not confirmed the per-transaction cap by reading the live chain. An eth_call carrying gas 0x2000000, which is 33,554,432 and therefore above the stated cap, succeeded without error, and eth_estimateGas does not discriminate. The only conclusive probe would be a transaction above the cap, which we do not send. What we did measure is that the header gasLimit at head is 0x1fffffffffffff = 9,007,199,254,740,991, so a 16,777,216 cap does not come from the block limit and cannot be inferred from it. The 348,345 figure itself is measured and stands on its own.)
Honest scope: the canonicity gap, and how the anchor closes it. The verifier alone proves "V is slot S of A in the trie whose root is stateRoot"; it does not prove that stateRoot is the canonical root of blockNumber (blockNumber is metadata). The consumer must establish canonicity independently. AereStateRootAnchorV2 (0x0b959402…) is the building block for that: it recovers a canonical block hash strictly from the BLOCKHASH opcode (never a caller argument), checks a caller-supplied RLP header against it, and records the header's state root as an anchored, trust-minimized value. BLOCKHASH is live but serves only the last 256 blocks, about 132 s at the measured 0.5175 s block interval.
EIP-2935 IS deployed on chain 2800. Correction of 2026-08-01. An earlier revision of this spec stated twice that "the EIP-2935 history contract is not deployed on chain 2800, so the extended roughly 8191-block window is unavailable". That is false, and it is false in the direction that under-sells the chain: we said we lacked a capability we have. Measured at head on https://rpc.aere.network:
eth_getCodeat0x0000F90827F1C53a10cb7A02335B175320002935returns 83 bytes. The contract is there. It went live with the AerePQC fork at block 9,189,161.- the serve window was measured end to end by reading the contract directly with a 32-byte block number. Offsets of 1, 255, 256 and 8,190 blocks behind head each returned exactly the canonical block hash reported by
eth_getBlockByNumberfor that height. An offset of 8,191 and anything beyond it reverts. That is the 8,191-entry history ring behaving exactly as specified, with one slot held by the in-flight block.
The consequence for this section is that the extended lookback is available, not roadmap. AereHistoryStateRootAnchorV2 (0x4C1F9daD515c7990b65F9833960CA4315216330C, 2,915 bytes, addresses.ts line 391) is the deployed consumer of it, and its HISTORY_STORAGE_ADDRESS() returns 0x0000F90827F1C53a10cb7A02335B175320002935, read from chain.
One open item on that anchor, stated because we found it. AereHistoryStateRootAnchorV2.VERIFIER() returns 0xF9a1A183bEb3147D88dA5927301683fEbFb9362E, which is the storage-proof verifier this same section marks deprecated. The extended-lookback anchor is therefore wired to the deprecated verifier. Do not treat that combination as a canonical record path until it is repointed. This is a real loose end and it is ours.
11. Rollup validity anchor
Contract: AereRollupValidity: 0x38772063572DF94E90351e44ccbBEefD5F497fbd (immutable, no owner).
What it proves. An SP1 Groth16 validity proof that AERE's deterministic rollup executor transitioned prevRoot to postRoot by applying a batch committed as batchHash. Unlike an optimistic rollup with a challenge window, an epoch recorded here is final the instant the proof verifies.
Verification path. Public values are exactly 160 bytes = abi.encode(chainId, prevRoot, postRoot, batchHash, numTxns). submitEpoch(publicValues, proof) verifies through the SP1 gateway (PROGRAM_VKEY = 0x00c39737...84ad, selector 0x4388a21c), binds chainId, enforces canonical-chain continuity prevRoot == latestRoot, then records the epoch and advances latestRoot. Immutables SP1_VERIFIER, PROGRAM_VKEY, GENESIS_ROOT are fixed at deploy; submission is permissionless and gated only by the proof plus continuity, so anyone can advance the tip and no one can fork it. Epoch 0 is recorded from a real proof (submit tx 0xd4f8d858...12a4b0, block 8880188; latestRoot = 0x256fa277...25e3ac, epochCount = 1), verified against the production aere-block-stm executor's state root for the same 5-tx batch.
Honest scope. This proves the state transition of AERE's current bounded-VM executor: a fixed transaction set (Transfer / Sweep / Increment / AmmSwap over a balance/storage map), not arbitrary EVM bytecode. It is a real validity proof of that executor. Nothing built on this contract should imply it proves a full EVM.
Update, 2026-08-01. An earlier revision described replacing the guest VM with a real EVM (revm) inside the zkVM as "a separate multi-quarter effort" still ahead of us. That work shipped. AereEVMValidity 0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26 (2,095 bytes live) and AereEVMValidityBatch 0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C (2,776 bytes live) each report provenCount() = 1 at head, and the canonical-bound successors AereEVMValidityV2 0x4884ad66… (2,852 bytes) and AereEVMValidityBatchV2 0xe154B899… (3,425 bytes) are live. They are the subject of spec-full-evm-validity.md. The bounded-VM anchor described in this section remains what it is; the full-EVM path is a different, live contract, and anyone reaching for one should know the other exists.
12. Consolidated status and address table
Canonical (verbatim from sdk-js/src/addresses.ts):
| Subsystem | Contract | Address | Status |
|---|---|---|---|
| SP1 routing | SP1VerifierGateway |
0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 |
Live |
| SP1 Groth16 (prod) | SP1VerifierGroth16_v6_1_0 |
0xb5456d48bFdA70635c13b6CBE1Ad0310Dc0171aD |
Live |
| SP1 Groth16 (prior) | SP1VerifierGroth16_v6_0_0 |
0xa9BD3020bC9a9614F9e2BC1618153c9fB1890ca6 |
Live, retained |
| SP1 Plonk | SP1VerifierPlonk_v6_1_0 |
0x24a7a85E6D9A2b120F2730880bE7283dFB14d29B |
Live |
| RISC Zero routing | RiscZeroVerifierRouter |
0x3f7015BC3290e63F7EC68ecF769b00aB296a249C |
Live; route 0xef6cb709 is set to 0xb6fD00D8… (measured) |
| RISC Zero Groth16 (corrected) | RiscZeroGroth16Verifier |
0xb6fD00D88Bf8B08d6371d2D98E0e239B89Ab5B9D |
Live |
| RISC Zero Groth16 (bad root) | RiscZeroGroth16Verifier_DEPRECATED |
0x95cB30f3bdb3187f39203A9907bf707Aef07a1FD |
Deprecated, do not use |
| RISC Zero corrected router | RiscZeroVerifierRouter_Corrected |
0x62b96F7211F832f47d8Fc0D8317D5867B0Af43E5 |
Live |
| Multi-prover registry | AereProofRegistry |
0x0A9b09677DbE995ACfC0A28F0033e68F068517Ee |
Live, R0 path connected; programCount 1, proofCount 1 |
| Historical side registry | AereProofRegistry_Corrected |
0x174F616E2048A71408E2491791ef77cd6913bbEf |
Live (recorded R0 proof id 0). Historical; use the canonical registry |
| Storage-proof coprocessor | AereStorageProofVerifierV2 |
0x487021f9aE018657B7335ADC6Fc254717751a1Dd |
Live, CANONICAL |
| Storage-proof coprocessor V1 | AereStorageProofVerifier_DEPRECATED_POISONABLE |
0xF9a1A183bEb3147D88dA5927301683fEbFb9362E |
Deprecated, record is poisonable. Do not use |
| Storage-proof canonicity anchor | AereStateRootAnchorV2 |
0x0b959402283c0D2B9db4FfD7823f99782462e488 |
Live, CANONICAL |
| Extended-lookback anchor (EIP-2935) | AereHistoryStateRootAnchorV2 |
0x4C1F9daD515c7990b65F9833960CA4315216330C |
Live; VERIFIER() still points at the deprecated V1 |
| zk-KYC screen | AereZKScreen |
0x3A097A459FD26aC79573aCB5adB51430e473C2f1 |
Live (v3, root-binding) |
| Compliance-pool SP1 verifier | AereCompliancePoolSP1Verifier |
0xE2D3fa91b680E835c971761ba75Fde0204AEF95E |
Live |
| Compliance pool | AereCompliancePoolV2 |
0xB144c923572E5Ac1B6B961C4ccfec36917173465 |
Live (0 deposits) |
| AI-inference provenance (signed, not zk) | AereAIProof |
0xFf92c669AbF4C1DAE31eBFCC017764036d9D97e6 |
Live |
| Rollup validity anchor | AereRollupValidity |
0x38772063572DF94E90351e44ccbBEefD5F497fbd |
Live (bounded VM) |
Formerly withheld, now printed. An earlier revision of this spec listed the six rows below as "deployed but NOT yet in the canonical registry" and deliberately omitted their hex. Five of the six have been in addresses.ts; the policy of omission was protecting nothing and hiding the canonical source from integrators. Code sizes measured at head.
| Subsystem | Contract | Address | Registry | Live code | Status |
|---|---|---|---|---|---|
| KZG / EIP-4844 | AereKZGVerifier |
0x6596307BD8f54d9A91FE364EBC3e594F200AC862 |
addresses.ts:386 |
1,988 B | Live |
| Halo2 verifier | AereHalo2CubicVerifier |
0x414Cfe640B2770856d3D7262a7a3729f5c07dC55 |
addresses.ts:387 |
4,804 B | Live, dev SRS, not a ceremony |
| Halo2 anchor | AereHalo2ProofAnchor |
see contracts/deployments/halo2-cubic.json |
absent | not measured | Deployed, genuinely still unregistered |
| Recursive aggregation | AereProofAggregatorV2 |
0x944dE720D6C9696Bdf10b6E2F00dd21ABB1A2b4C |
addresses.ts:396 |
4,663 B | Live, CANONICAL |
| Recursive aggregation V1 | AereProofAggregator_DEPRECATED_UNENFORCED_INNER_VKEYS |
0x6a260238890E740dB12b371E0C5d17a2470F84C5 |
addresses.ts:395 |
4,531 B | Deprecated. Do not use |
| zkML | AereZKMLVerifier |
0xf1BF15d5018a35D21FBB4Ec868f062DF7C06783c |
addresses.ts:392 |
3,658 B | Live, 97.98% MNIST; free verify path checks no registration |
| State-root anchor | AereStateRootAnchorV2 |
0x0b959402283c0D2B9db4FfD7823f99782462e488 |
addresses.ts:390 |
2,652 B | Live, 256-block BLOCKHASH window |
| Extended-lookback anchor | AereHistoryStateRootAnchorV2 |
0x4C1F9daD515c7990b65F9833960CA4315216330C |
addresses.ts:391 |
2,915 B | Live, 8,191-block EIP-2935 ring |
13. Roadmap summary (explicit "not done yet")
Four items that this list carried on 2026-07-11 have since been done, and are moved out of "not done yet" into the closed list below rather than quietly deleted.
Still not done:
- Halo2 production SRS: replace the local dev SRS with a multi-party ceremony SRS before any value-bearing Halo2 circuit. This remains the single most important caveat in the stack.
- Register
AereHalo2ProofAnchorinsdk-js/src/addresses.ts. It is the one contract in this spec that is genuinely deployed and genuinely unregistered. - Repoint
AereHistoryStateRootAnchorV2.VERIFIER()away from the deprecated0xF9a1A183…and ontoAereStorageProofVerifierV2 0x487021f9…, so the extended-lookback path is not wired to a poisonable record. - Freeze or govern the verifier routes. While
addRoute/addVerifierremain available to a route owner, "verified through the gateway" is only as strong as the current route (section 2). Freezing the production routes, or moving the routers behind governance, converts that from a trust assumption into a guarantee. - Move the routers and the canonical proof registry off the operational deployment account and onto governance. Measured today,
owner()on0x9ca479C8…,0x3f7015BC…and0x0A9b0967…is not the Foundation account. - Usage. The canonical registry has one program and one proof. Everything in this spec is capability; almost none of it is traffic. We would rather say that than let a mechanism description read as a volume claim.
Closed since the previous revision, with the measurement that closed it:
Canonical RISC Zero recording, staged and unsignedDONE.getVerifier(0xef6cb709)on the canonical router returns0xb6fD00D8…;AereProofRegistry.risc0Router()returns the canonical router. Section 3.Promote KZG / Halo2 verifier / aggregator / zkML / state-root anchor intoDONE. All five are in the registry; their addresses are printed in section 12.addresses.tsHistorical-state canonicity blocked because EIP-2935 is not deployedThe premise was false. EIP-2935 is live at0x0000F908…2935with 83 bytes of code, and the 8,191-block ring was measured end to end. Section 10.Full validity rollup: replace the bounded-VM executor guest with revm inside the zkVMDONE.AereEVMValidity0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26(2,095 bytes) andAereEVMValidityBatch0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C(2,776 bytes) are live withprovenCount() = 1each, and the QBFT-canonical-bound successorsAereEVMValidityV20x4884ad6617e320735b65D31915d1aBC884768D9B(2,852 bytes) andAereEVMValidityBatchV20xe154B8993FB54dB4418e03ef4a04894f29E690aE(3,425 bytes) are live as well. Seespec-full-evm-validity.md. Section 11's "honest next step" paragraph is updated accordingly.
Decentralization baseline, unchanged: seven validators, one operator, one client, no external audit, thin usage. That is what we are, it is published, and it is the frame for everything above.
Every address printed above is verbatim from sdk-js/src/addresses.ts, with one exception that is marked as absent from it (AereHalo2ProofAnchor). Copying the hex correctly is not enough: where the registry marks an entry deprecated, this spec now carries that label too, because an earlier revision copied addresses faithfully and left their status behind. Performance figures (gas, cycles, accuracy, proving times) are quoted from repo deployment artifacts and are measured single-run values, not benchmarked averages. Code sizes, owners, route targets, counters and the EIP-2935 window were re-read from https://rpc.aere.network on 2026-08-01 at head block 11,810,041.