Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
26 KiB
AERE Network: Full-EVM Validity Rollup (revm-in-SP1)
Chain: AERE Network mainnet, chain ID 2800 (Hyperledger Besu QBFT, ~0.5s blocks).
Status date: 2026-07-11.
Source of truth for addresses: sdk-js/src/addresses.ts. Contract source: contracts/contracts/. Guest/host prover: rollup-evm-validity/.
Scope: the REAL full-EVM validity path: a live chain-2800 block re-executed with revm inside the SP1 zkVM, its post-state root proven, and that proof verified on-chain through the SP1 gateway into the AereEVMValidity anchor. This document also states, honestly, what is and is not proven, and how this work supersedes the earlier bounded-VM proof of concept.
0. Honesty preamble (read this first)
Four points frame everything that follows.
-
This is a proof-of-approach, not a production rollup. What has been proven is that a REAL AERE mainnet block, executed with a real EVM (
revm) inside a zkVM, reproduces the canonical post-state root, and that the resulting proof verifies on chain 2800. The proven block is small (one EIP-1559 transaction). It is not a large batch, and the anchor records isolated proven blocks rather than a continuous sequenced chain. No claim built on this should imply a full-throughput validity rollup exists today. -
The realism is the point. The guest runs the actual
revmexecution stack (SP1-patchedreth1.9.3 /revm), not a hand-written subset. Full opcode set, call frames,SLOAD/SSTORE,LOG, EIP-1559 base-fee burn plus priority-to-coinbase, and the Cancun/Prague/Osaka system calls all execute and all affect the proven state root. This is the difference from the prior bounded-VM PoC (section 9). -
The anchors are now in the canonical registry.
AereEVMValidity(single block) at0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26and its multi-block siblingAereEVMValidityBatchat0x49D30a5999eA5b7f6eFf12196AB006316683Ff3Care both live on chain 2800, have real proofs recorded, and are now promoted intosdk-js/src/addresses.ts(verbatim there). Both are immutable, admin-free, and permissionless to record. -
The crypto is classical, a known hedge. SP1 Groth16 proves over BN254 and QBFT signs with classical keys, so this validity path is not itself post-quantum. AERE's post-quantum work (the verifier suite and the
AerePQChard-fork that also carries the native EIP-2935 lookback of section 8) is tracked separately.
1. What is proven (the theorem)
For a target chain-2800 block B with parent P, the SP1 Groth16 proof establishes:
Executing block
B's transactions withrevm(Cancun/Prague/Osaka rules per the chain-2800 genesis schedule) against the account and storage state committed byP.stateRoot, then re-rooting the resulting sparse Merkle-Patricia trie, yields a post-state root exactly equal toB.header.stateRoot.
The public values committed by the guest are exactly 224 bytes (7 x 32), ABI-encode compatible:
abi.encode(
uint256 chainId, // == block.chainid (2800)
uint256 blockNumber, // proven block height
bytes32 prevStateRoot, // parent block state root (pre-state anchor)
bytes32 postStateRoot, // proven post-execution state root == header.stateRoot
bytes32 txRoot, // header.transactionsRoot (binds the executed txs)
uint256 gasUsed, // total gas used by the block
uint256 numTxns // number of transactions executed
)
txRoot binds the proof to the canonical header's exact transaction set, prevStateRoot and postStateRoot bind it to the concrete pre-state and the post-state the real chain published, and chainId prevents cross-chain replay.
2. Architecture: host witness, guest re-execution, on-chain anchor
The pipeline has three stages, each with a distinct trust role.
chain-2800 node SP1 zkVM (guest) chain 2800 (on-chain)
--------------- ---------------- ---------------------
eth_getProof / block / code revm re-execute block SP1VerifierGateway
-> EthClientExecutorInput -> re-root sparse MPT -> 0x9ca479...70628
(host pre-flight revm) commit 224-byte pv -> AereEVMValidity anchor
- Host (
rollup-evm-validity/host). Gathers the pre-state witness for blockBfrom the live node usingrsp'sEthHostExecutor, which driveseth_getProofplus block and code RPC. It runsrevmonce, off chain, against a proof-recording database and asserts the recomputed post-state root equalsheader.stateRoot. This pre-flight produces no proof, only the serialized witness (EthClientExecutorInput). - Guest (
rollup-evm-validity/guest, ELFaere-client). Re-executes the block withrevminside the zkVM viarsp_client_executor::EthClientExecutor, re-roots the trie, and commits the 224-byte public values. Execution only succeeds when the computed post-state root matchesheader.stateRoot; a mismatch panics and no valid proof exists. - Anchor (
AereEVMValidity). Verifies the Groth16 proof through the SP1 gateway and, on success, records the block. Verification is delegated entirely to the gateway; the anchor adds no trust of its own beyond binding the exact program.
The gateway SP1VerifierGateway at 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 is the same canonical SP1 verifier gateway used by the rest of AERE's zk stack, routed by the leading 4-byte selector 0x4388a21c (SP1 v6 Groth16).
3. The guest: revm inside the zkVM (rsp / sp1-reth)
The guest is a thin driver around the rsp (sp1-reth) client executor. Its whole body is: read the bincode witness, capture the parent state root, transaction count and block number, build the chain-2800 ChainSpec from the embedded genesis, then:
let executor = EthClientExecutor::eth(chain_spec, input.custom_beneficiary);
let header = executor.execute(input).expect("EVM execution / state-root check failed");
EthClientExecutor::execute is real revm. It applies each transaction against the witnessed pre-state, charges gas, splits EIP-1559 fees (base fee burned, priority tip to the beneficiary), runs the pre/post-execution system calls, and folds the modified accounts and storage back into the sparse trie to recompute the root. The guest then commits chainId, blockNumber, prevStateRoot, postStateRoot, txRoot, gasUsed, numTxns.
Version pinning. The guest builds against reth tag v1.9.3 and sp1-zkvm =6.1.0 (this is the rsp / sp1-reth line tagged reth-1.9.3-sp1-6.1.0, not the old bounded toy VM). The precompile patches match rsp's reference client so that in-guest hashing and signature recovery are zk-accelerated: sha2, sha3 (Keccak for trie and keccak256), k256 (ecrecover), p256, and substrate-bn, all from the sp1-patches forks. This is what keeps a full block's Keccak-heavy trie walk inside a tractable cycle budget.
Program binding. The anchor's immutable PROGRAM_VKEY is the SP1 verification key of exactly this guest ELF: 0x00d79882d2f72013ede3b1be9d44edcad08107858e00dded883fe3b02dbbb808. Binding the vkey binds the exact revm-in-zkVM program; a proof from any other program is rejected by the gateway.
4. The witness path (eth_getProof, sparse MPT, re-root)
The guest never sees full chain state. It sees a witness: only the accounts and storage slots the block touches, each with the Merkle proof that ties it to P.stateRoot. The witness type ClientExecutorInput (patched in rollup-evm-validity/rsp-patches/io.rs) carries:
current_block(the blockBto execute),ancestor_headers(at least the parent, to supplyP.stateRootand theBLOCKHASHmap),parent_state(anEthereumState: the state trie plus the per-account storage tries, populated frometh_getProof),bytecodes(the contract code the block executes),genesis,custom_beneficiary,opcode_tracking.
witness_db verifies the witness before execution: the provided parent_state must hash to P.stateRoot (state_anchor() == state.state_root()), and each account's storage_root in the state trie must equal the hash of its supplied storage trie. Reads during execution go through a TrieDB DatabaseRef that resolves accounts and storage by walking these witnessed tries (state_trie.get_rlp, storage_tries[...].get_rlp), so any slot the block reads must have been included in the witness with a valid proof, or execution fails. After execution the same trie machinery folds the writes back and recomputes the root. This is what makes the proof a validity statement over the real MPT rather than over an opaque snapshot.
eth_getProof is therefore the sole bridge between the live node and the proven computation. The witness is trustless in the sense that a wrong or incomplete witness cannot produce a passing proof: the pre-state root check, the per-account storage-root check, and the final post-state root equality each independently reject it.
5. QBFT header adaptations
AERE runs Besu QBFT, not proof-of-work or a beacon-chain layer, and this required exactly one honest adaptation to the stock rsp witness logic, isolated in is_qbft_relaxed().
Under QBFT, the canonical block hash is not keccak256(rlp(header)), because the validator commit seals are excluded from the hashed header. Stock rsp links ancestor headers by asserting keccak(rlp(parent)) == child.parent_hash, which never holds on chain 2800. The patch relaxes only that ancestor parent-hash linkage assertion, and only for chain 2800:
fn is_qbft_relaxed(&self) -> bool {
matches!(&self.genesis, Genesis::Custom(c) if c.chain_id == 2800)
}
Everything that actually secures the state transition stays enforced:
- the
BLOCKHASHmap is still built from the canonicalchild.parent_hash()values, so theBLOCKHASHopcode returns real hashes; - the parent state root is still checked against
parent_header.state_root(); - the post-state root is still asserted equal to
header.stateRoot.
The relaxation removes a hash-shape assumption that is false under QBFT; it does not remove any check on the proven state transition. Other L1 fields QBFT lacks (parent beacon block root and blob base fee, both zero on chain 2800) are handled by the chain-2800 ChainSpec built from genesis, consistent with the live node.
6. The on-chain anchor: AereEVMValidity
AereEVMValidity (source contracts/contracts/parallel/AereEVMValidity.sol) is deployed at 0x1f2CB0ebDBb21500e99868DbF1dB3abCcDd7DF26 (canonical in sdk-js/src/addresses.ts; recorded in contracts/deployments/evm-validity.json, deploy tx 0x22284d1d...15e51b, deploy block 9074452, deployer 0xbeB33D20dFBBD49eC7AC1F617667f1f02dfd6465). A multi-block sibling, AereEVMValidityBatch at 0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C (also canonical), records a contiguous batch of real blocks in one Groth16 and is documented in Section 7.1.
Design:
- Two immutables, no admin.
SP1_VERIFIER(the gateway) andPROGRAM_VKEYare fixed at construction. There is no owner and no upgrade path. Recording is permissionless. Soundness rests entirely on the Groth16 proof plus the vkey binding. - Verify-then-record.
recordBlock(publicValues, proof)requirespublicValues.length == 224, calls the gateway'sverifyProof(PROGRAM_VKEY, publicValues, proof)inside atry/catchthat translates a revert into a typedInvalidProof(), decodes the seven fields, checkschainId == block.chainid, enforces one record per block number, then stores theProvenBlockand updateslatestPostRoot. Averify(...)view offers the same gateway check statelessly for callers who only want to validate without recording. - What it does not do. It does not chain records (there is no
prevRoot == latestRootcontinuity check, unlike the bounded-VM anchor of section 9), and it does not settle withdrawals or sequence a rollup. It is an attestation log of individually proven real blocks.
The trust boundary is stated in the contract's own header comment: this proves REAL EVM execution of a small live block as a proof-of-approach, and records isolated proven blocks rather than a continuous sequencer chain.
7. Measured results
Two real chain-2800 blocks were proven end-to-end with the server's SP1 v6.1.0 CPU prover (SP1_PROVER=cpu, RAYON_NUM_THREADS=16, native gnark Groth16). Both blocks carry one real EIP-1559 transaction performing an SSTORE plus LOG contract call at roughly 33.6k gas, roughly 1.18M RISC-V cycles in the guest, a fresh Groth16 produced in about 533s of CPU time, with peak resident memory about 27.7GB and no out-of-memory.
The block committed on-chain and captured in the repo artifact (contracts/deployments/evm-validity-proof.json) is block 9073073:
| Field | Value |
|---|---|
| chainId | 2800 |
| blockNumber | 9073073 |
| prevStateRoot | 0x31921701d25cb1791d47c0af23b0d84e6f6cdfdd3234a8643638854e588ae5eb |
| postStateRoot | 0x0403c73b02f82a4e25ddde929995995b624ceb9384a50e0c31755099b5346636 |
| txRoot | 0x00e9301708e05258def41ed6a8ec9523ba98b6ffc5945316e9180c841f5b4864 |
| gasUsed | 33601 |
| numTxns | 1 |
| guest cycles | 1,181,363 |
| proof selector | 0x4388a21c |
| proof bytes | 356 |
| public values bytes | 224 |
It was recorded on-chain by recordBlock in tx 0xb28a39507f8c03e6ffcfed307f9848814dd9d35268f01983825b658cbc56415c (submit block 9074454, status 1), gas used 460,015 against an estimate of 464,833, comfortably under the EIP-7825 per-transaction cap of 16,777,216. That record cost is dominated by the fixed Groth16 pairing check in the gateway and is essentially independent of the block's own gas or transaction count. A second, freshly produced block, 9075035, was proven with the identical profile during the same session, demonstrating repeatability on a block mined after the anchor was deployed. Every proof is also tamper-tested: flipping one byte inside postStateRoot in the public values makes the on-chain-equivalent sp1_verifier::Groth16Verifier reject it, confirming the proof binds the committed root.
7.1 Batch proofs and the dense-workload result (density caveat removed at demonstrated scale)
Two extensions build directly on Section 7 and are documented here rather than left implicit.
The multi-block batch anchor, AereEVMValidityBatch (0x49D30a5999eA5b7f6eFf12196AB006316683Ff3C, canonical). A sibling anchor records a contiguous batch of real chain-2800 blocks in one Groth16 proof. The batch guest threads each block's recomputed post-state root into the next block's pre-state and re-roots the sparse trie, so the proof verifies only when the final recomputed post-state root equals the canonical header.stateRoot of the last block. Its PROGRAM_VKEY binds the exact batch guest ELF (0x00418c4f40178103cc75ae57b63703f6075cabdce23582fed6043ceae819192e) and differs from the single-block vkey; the 256-byte public values carry (chainId, firstBlock, lastBlock, numBlocks, prevStateRoot, postStateRoot, totalGas, numTxns). A real 16-block batch, chain-2800 blocks 9111008..9111023, was recorded on-chain via recordBatch in tx 0x074a4e6c17855b78db4c56808e253d4c00a7b392cf0d141e4aa0e6e299ded9ec (submit block 9114536, status 1), gasUsed 460,817, under the cap. Like the single-block anchor it is immutable and admin-free.
The dense-workload proof (removes the "low tx density" caveat at demonstrated scale). Historical mainnet blocks are low-density and the live node prunes state (Bonsai), which capped earlier witnessable batches at a handful of transactions (the prior batch was 2 txs across 16 blocks). To show that a genuinely dense real-EVM block is provable and gateway-acceptable, a dense workload was generated on a throwaway single-validator node running the AERE fork profile (chain-config 2800) in archive mode so all touched state is witnessable via eth_getProof, and proven with the same real revm-in-SP1 batch prover against the same batch-program vkey:
- A single ultra-dense block of 155 real transactions and 460 SSTOREs (52 freshly created EOAs, 52 fresh ERC-20 holder slots), 29,572,987 guest cycles. Local SP1 Groth16 verify PASS, tampered
postStateRootREJECTED. Verified read-only against the live mainnet SP1 gateway0x9ca479C8c52C0EbB4599319a36a5a017BCC70628: the real proof was accepted and both a tampered-public-values and a tampered-proof call reverted. - A 2-block contiguous, state-continuity-chained dense batch (310 txs, 920 SSTOREs, 27,050,310 gas, 61,366,308 cycles; the guest enforces
post_root(N) == header.stateRootandpre_root(N+1) == post_root(N)), same live-gateway acceptance read-only with both tampers reverted.
Honest label: this dense chain is an isolated, engineered workload, NOT a replay of historical mainnet blocks; verification against the live gateway was eth_call only, no mainnet transaction was sent. Compatibility was confirmed by the guest reproducing Besu 26.4.0's exact header.stateRoot for every dense block, the same agreement already established on real chain-2800 blocks (Section 7). What it establishes is that the approach scales to dense, fully-witnessable blocks; the practical ceiling on the box used was about two full-density blocks (~61M cycles) before proving went swap-bound, so a longer dense batch is a prover-resource matter, not an approach question.
Canonical-binding V2 is STAGED, not deployed. A V2 layer (AereEVMValidityV2, AereEVMValidityBatchV2, and an updated guest that commits blockHash + parentHash and enforces prevStateRoot == parent.stateRoot inside the circuit) closes the canonicity gap of Section 8 by requiring the committed blockHash to equal the on-chain canonical hash of that block number. It exists in the tree and is self-consistent but MUST NOT be deployed yet, for one remaining reason: the changed public-values layout changes the SP1 vkey, so the guest must be re-proved end to end and the new vkey wired into the V2 contracts before any deploy. The second dependency has since been satisfied: practical canonical binding needs EIP-2935 history storage live on chain 2800 (the 256-block blockhash() window is far shorter than proving time), and EIP-2935 is now live on mainnet since block 9,189,161 (the AerePQC hard fork). V2 still requires its own re-prove and a supervised deploy; until it ships, recording continues against the honestly de-canonicalized V1 anchors, which never claim a record is the canonical chain-2800 block. See rollup-evm-validity/NOTE.md and dense-batch-2026-07-11/NOTE.md.
8. Canonicity of the pre-state root (honest gap and the fix)
The proof establishes postStateRoot given prevStateRoot. It does not, by itself, establish that prevStateRoot is the canonical parent state root of the real chain. For the two blocks above, both roots were cross-checked by the host against the live node's headers, so the records are genuine, but a permissionless caller could in principle record a proof over a self-chosen pre-state. Closing that gap trustlessly is the job of the anchoring layer, which has two live-adjacent pieces:
AereStorageProofVerifierat0xF9a1A183bEb3147D88dA5927301683fEbFb9362Eproves a value at an account slot against a given state root, with the same caveat that canonicity of that root is the caller's responsibility.AereHistoryStateRootAnchor(sourcecontracts/contracts/anchor/AereHistoryStateRootAnchor.sol, scratch-only so far, bundles into theAerePQChard-fork for mainnet) recovers a canonical block hash from the native EIP-2935 history-storage system contract at0x0000F90827F1C53a10cb7A02335B175320002935viaSTATICCALL, re-derives the header state root from the RLP header, and anchors it. On the AERE Besu fork the ring buffer is written by QBFT consensus itself at the start of every block (a system call, no keeper, no Foundation key), so the value is produced by consensus exactly likeBLOCKHASHbut with an 8191-block reach instead of 256, roughly 68 minutes of trustless lookback at ~0.5s blocks. This was proven on scratch chain 28099 by anchoring a header more than 258 blocks back, beyond theBLOCKHASHwindow, recovering the state root purely from the system contract.
The two are complementary: the validity proof says "this pre-state deterministically yields this post-state," the anchor says "this state root is canonical for this height." A production system would require the recorded prevStateRoot to be an anchored canonical root before accepting the transition. That wiring is future work, called out here rather than hidden.
9. How it supersedes the bounded-VM proof of concept
The prior anchor, AereRollupValidity at 0x38772063572DF94E90351e44ccbBEefD5F497fbd (source contracts/contracts/parallel/AereRollupValidity.sol), is a real validity proof, but of a bounded virtual machine. Its guest implements only four transaction kinds (Transfer, Sweep, Increment, AmmSwap) over a balance-and-storage map, with a hand-ported execute_txn and Keccak state-root folding taken verbatim from AERE's parallel-executor. It commits 160-byte public values (chainId, prevRoot, postRoot, batchHash, numTxns) and, notably, it does chain: it enforces prevRoot == latestRoot and advances a canonical tip, recording epochs. Epoch 0 is on record from a real proof.
AereEVMValidity supersedes it on the axis that matters most, execution fidelity:
| Dimension | AereRollupValidity (bounded PoC) |
AereEVMValidity (full-EVM) |
|---|---|---|
| VM | 4 fixed tx kinds over a map | real revm, full opcode set |
| Input | synthetic batch encoding | REAL chain-2800 block + header |
| State model | custom balance/storage map | real account and storage MPT via eth_getProof |
| Fees / system calls | none | EIP-1559 split, Cancun/Prague/Osaka system calls |
| Public values | 160 bytes | 224 bytes (adds blockNumber, txRoot, gasUsed) |
| Continuity | chains via latestRoot |
isolated records (deliberately, for now) |
The bounded VM could never prove that real EVM bytecode on the real chain reproduces the canonical root, because it does not run the EVM. The full-EVM anchor does exactly that. The trade made along the way is that the full-EVM anchor currently drops the continuity chaining the bounded anchor had, because at proof-of-approach stage you prove individual real blocks, not a sequenced batch chain. The continuity and finalization machinery in AereRollupValidity is the template to re-add when moving to a production sequencer, this time over real EVM blocks.
10. Honest scope versus a production sequencer or rollup
What exists today: a real revm-in-zkVM guest that re-executes a real chain-2800 block and proves the canonical post-state root; a host that gathers the eth_getProof witness and pre-flights execution; an immutable, admin-free on-chain anchor that verifies the Groth16 proof through the canonical SP1 gateway and records the block; and two real blocks proven end-to-end (9073073 recorded on-chain, 9075035 proven fresh) with measured cycles, gas, proving time and memory, plus a passing tamper test.
What does not exist yet, stated plainly:
- Batch scale. The low-tx-density caveat is now removed at demonstrated scale: dense blocks of 155 txs / 460 SSTOREs each are provable and live-gateway-acceptable, single and 2-block-chained (Section 7.1), and a real 16-block batch is recorded on-chain via
AereEVMValidityBatch. What still does not exist is a continuous production cadence: proving is single-box CPU and swap-bound past about two full-density blocks (~61M cycles), so a long dense batch or blocks packed to the gas limit motivates GPU proving and proof partitioning, and the anchors still record isolated proven blocks and ranges rather than a sequenced rollup chain. - A sequencer. There is no mempool, ordering service, or forced-inclusion path. The system proves blocks the QBFT chain already produced; it does not sequence a separate rollup.
- Continuity and settlement. The anchor records isolated blocks with no
prevRoot == latestRootchaining and no withdrawal or bridge settlement. There is no fraud/validity dispute lifecycle beyond "the proof verified." - Canonical pre-state binding. As in section 8, the recorded
prevStateRootis not yet required to be an anchored canonical root on-chain. - Data availability. The witness is gathered from a trusted node; there is no DA commitment or sampling for third-party reconstruction.
- Audit and decentralization. The contracts are unaudited by an external party, and chain 2800 runs three QBFT validators under a single operator. The prover is a single CPU box.
None of these gaps is hidden in the contract, the artifacts, or this spec. The correct one-line summary is: AERE can prove a real EVM block of its own mainnet in zero knowledge and verify that proof on-chain, as a proof-of-approach; it is not yet a throughput-grade validity rollup.
11. Path to production
The credible sequence from here, each step building on a demonstrated capability:
- Fuller blocks. Prove blocks with many transactions and near-limit gas, measuring the cycle and memory curve. This is the first honest scaling test.
- Continuity. Re-introduce the
prevRoot == latestRootchaining fromAereRollupValidity, over real EVM blocks, so records form a verified chain rather than a set. - Canonical pre-state binding. Require the recorded
prevStateRootto be an anchored canonical root, wiringAereEVMValiditytoAereHistoryStateRootAnchoronce that anchor ships in theAerePQCfork on mainnet. - Aggregation. Fold many per-block validity proofs into one Groth16 via
AereProofAggregatorat0x6a260238890E740dB12b371E0C5d17a2470F84C5, whose recursive aggregation is already demonstrated at n=10 (record tx0xd1fd4d60...9344d978, recorded gas roughly 394k, essentially flat in N), letting one on-chain verification stand for many proven blocks. - DA and sequencing. Add a data-availability commitment and, only if a distinct rollup is warranted, a sequencer with forced inclusion.
- Proving cost. Move from single-box CPU proving toward GPU and partitioned proving as block size grows.
The engineering already on chain (the SP1 gateway, the aggregator at scale, the EIP-2935 native lookback, the storage-proof verifier) makes these integration steps over demonstrated parts, not research from zero. The honest limit is that the integration has not been done yet.
Every address here is printed verbatim from sdk-js/src/addresses.ts (the SP1 gateway, AereStorageProofVerifier, AereRollupValidity, AereProofAggregator, AereEVMValidity, and AereEVMValidityBatch, all now canonical). The EIP-2935 system-contract address is a protocol constant. All measured figures come from the repo artifacts and the session prover run above; none are invented.