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.
345 lines
47 KiB
Markdown
345 lines
47 KiB
Markdown
# 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.
|
|
|
|
1. **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.
|
|
|
|
2. **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 `AereProofRegistry` reports `programCount() = 1` and `proofCount() = 1` at head. None of this is hidden; it is the baseline against which the engineering below should be judged.
|
|
|
|
3. **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.
|
|
|
|
4. **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.
|
|
|
|
5. **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 account `0x0243A4f47D44b40b65D33f20329dE20D00c6f3C3`. 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.
|
|
|
|
6. **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):
|
|
|
|
- `SP1VerifierGateway` routes an SP1 proof to the correct SP1 verifier version by its leading selector, refusing frozen routes.
|
|
- `RiscZeroVerifierRouter` routes 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`: `0x9ca479C8c52C0EbB4599319a36a5a017BCC70628`
|
|
- `SP1VerifierGroth16_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: `0x4388a21c` to `0xb5456d48…`, `0x5a093a2f` to `0x24a7a85E…`, 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`: `0x62b96F7211F832f47d8Fc0D8317D5867B0Af43E5`
|
|
- `AereProofRegistry_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)` returns `0xb6fD00D88Bf8B08d6371d2D98E0e239B89Ab5B9D`
|
|
- the `verifiers(0xef6cb709)` mapping returns the same address
|
|
- `AereProofRegistry(0x0A9b0967…).risc0Router()` returns `0x3f7015BC3290e63F7EC68ecF769b00aB296a249C`
|
|
- 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.** `submitSP1Proof` calls `ISP1Verifier(sp1Gateway).verifyProof(...)` (reverts on invalid), then writes a `ProofRecord` and emits `ProofVerified` with the program key indexed. `submitRiscZeroProof` does the same through `IRiscZeroVerifier(risc0Router).verify(...)`. Off-chain consumers subscribe to `ProofVerified` filtered 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 can `disableProgram` a malicious program but cannot disable a legitimate one, and disabling never retroactively invalidates already-recorded proofs.
|
|
- **Free verdicts.** `verifySP1Only` / `verifyRiscZeroOnly` are `view` functions 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)`:
|
|
1. requires `aggVKey` is a Foundation-registered aggregation program;
|
|
2. recomputes `composite` from the supplied inner arrays **byte-for-byte with the guest** and requires it equals the 32-byte `publicValues`;
|
|
3. verifies the Groth16 proof through the SP1 gateway (selector `0x4388a21c`);
|
|
4. 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` / `verifyAndRecord` check that the model is `registered`, and bind `chainId`, `user` and `modelHash` as described below. Use this path if you need "a **registered** model produced this classification".
|
|
- the public `verify(bytes32, bytes, bytes)` twin checks **only** that `publicValues` is 128 bytes long and that the Groth16 proof is valid, then decodes the class. It does not consult the model registry, does not check `chainId`, does not check `user`, does not check `modelHash`, 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 `staticcall`s `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_getCode` at `0x0000F90827F1C53a10cb7A02335B175320002935` returns **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_getBlockByNumber` for 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 `AereHalo2ProofAnchor` in `sdk-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 deprecated `0xF9a1A183…` and onto `AereStorageProofVerifierV2 0x487021f9…`, so the extended-lookback path is not wired to a poisonable record.
|
|
- **Freeze or govern the verifier routes.** While `addRoute` / `addVerifier` remain 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()` on `0x9ca479C8…`, `0x3f7015BC…` and `0x0A9b0967…` 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 unsigned~~ **DONE.** `getVerifier(0xef6cb709)` on the canonical router returns `0xb6fD00D8…`; `AereProofRegistry.risc0Router()` returns the canonical router. Section 3.
|
|
- ~~Promote KZG / Halo2 verifier / aggregator / zkML / state-root anchor into `addresses.ts`~~ **DONE.** All five are in the registry; their addresses are printed in section 12.
|
|
- ~~Historical-state canonicity blocked because EIP-2935 is not deployed~~ **The premise was false.** EIP-2935 is live at `0x0000F908…2935` with 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 zkVM~~ **DONE.** `AereEVMValidity` `0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26` (2,095 bytes) and `AereEVMValidityBatch` `0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C` (2,776 bytes) are live with `provenCount() = 1` each, and the QBFT-canonical-bound successors `AereEVMValidityV2` `0x4884ad6617e320735b65D31915d1aBC884768D9B` (2,852 bytes) and `AereEVMValidityBatchV2` `0xe154B8993FB54dB4418e03ef4a04894f29E690aE` (3,425 bytes) are live as well. See `spec-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.*
|