aere-contracts/contracts/parallel/AereEVMValidity.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

256 lines
10 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/// @dev Canonical SP1 verifier / SP1VerifierGateway ABI: verifyProof RETURNS
/// NOTHING and REVERTS on an invalid proof. On chain 2800 the deployed
/// SP1VerifierGateway is 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 and the
/// SP1 Groth16 route is selected by the proof's leading 4-byte selector
/// 0x4388a21c (SP1 v6 groth16), exactly as AereRollupValidity uses it.
interface ISp1Verifier {
function verifyProof(
bytes32 programVKey,
bytes calldata publicValues,
bytes calldata proof
) external view;
}
/**
* @title AereEVMValidity - on-chain anchor for REAL FULL-EVM validity proofs of
* live chain-2800 blocks.
*
* @notice Verifies SP1 zkVM Groth16 proofs that a REAL AERE mainnet (chain 2800,
* QBFT / Besu) block was re-executed with revm INSIDE the zkVM and that
* the recomputed post-state root equals the `header.stateRoot` carried in
* the proof's public values. The guest (bin/aere-client) runs the
* SP1-patched reth 1.9.3 / revm stack over the block's transactions against
* the parent-block pre-state (account + storage MPT proofs gathered by the
* host from `eth_getProof`), re-roots the sparse trie, and only produces a
* valid proof when computed stateRoot == header.stateRoot.
*
* BINDING CAVEAT (READ THIS): this V1 anchor attests a SELF-CONSISTENT
* execution, NOT that the recorded block is the CANONICAL chain-2800 block
* at that height. The public values carry no blockHash/parentHash and this
* contract performs no blockhash() check, so a genuine SP1 proof of a
* FABRICATED execution can be recorded here, and first-write-wins dedup lets
* a slot be squatted. Do NOT treat a V1 record as canonical. Canonical
* binding (committed blockHash + on-chain blockhash()/EIP-2935 check + batch
* continuity) lands in AereEVMValidityV2 / AereEVMValidityBatchV2.
*
* Unlike AereRollupValidity (which proves AERE's BOUNDED toy-VM executor:
* Transfer/Sweep/Increment/AmmSwap), this contract anchors proofs of
* ARBITRARY EVM bytecode execution: full opcode set, call frames,
* SLOAD/SSTORE, LOG, EIP-1559 base-fee burn + priority-to-coinbase, and
* the Cancun/Prague/Osaka system calls, all of which affect the proven
* state root.
*
* PROGRAM BINDING: PROGRAM_VKEY is the SP1 verification key of the exact
* guest ELF that performs revm-in-zkVM execution + witness re-root.
* Binding the vkey binds the exact prover program.
*
* PUBLIC-VALUES CONVENTION (exactly 224 bytes = 7 x 32):
* abi.encode(
* uint256 chainId, // must equal block.chainid (2800)
* uint256 blockNumber, // the proven chain-2800 block height
* bytes32 prevStateRoot, // parent block state root (pre-state anchor,
* // NOT cross-checked on-chain in V1)
* bytes32 postStateRoot, // self-consistent post-execution state root
* bytes32 txRoot, // header.transactionsRoot (binds the txs)
* uint256 gasUsed, // total gas used by the block
* uint256 numTxns // number of transactions executed
* )
*
* IMMUTABILITY / TRUST: SP1_VERIFIER and PROGRAM_VKEY are fixed at deploy.
* There is NO owner and NO admin: recording is permissionless. Soundness
* rests entirely on the SP1 Groth16 proof: a record can only be written
* for a (blockNumber, prevStateRoot, postStateRoot) triple that the zk
* proof establishes for PROGRAM_VKEY.
*
* ===========================================================================
* HONEST SCOPE - READ THIS.
* ===========================================================================
* This proves REAL EVM execution of a SMALL live chain-2800 block (a handful
* of transactions). It is a legitimate real-EVM validity PROOF-OF-APPROACH.
* It is NOT yet a full-throughput production rollup: it does not prove a large
* batch, and it records isolated proven blocks rather than a continuous
* sequencer chain. No code, UI or claim built on it should imply otherwise.
* ===========================================================================
*/
contract AereEVMValidity {
/// @notice SP1 verifier gateway (routes to the Groth16 verifier by selector).
address public immutable SP1_VERIFIER;
/// @notice SP1 vkey binding the exact revm-in-zkVM guest ELF.
bytes32 public immutable PROGRAM_VKEY;
/// @notice Number of proven blocks recorded.
uint256 public provenCount;
struct ProvenBlock {
uint64 blockNumber;
bytes32 prevStateRoot;
bytes32 postStateRoot;
bytes32 txRoot;
uint64 gasUsed;
uint32 numTxns;
uint64 at; // block timestamp when the proof verified
address prover; // who submitted it
}
/// @notice recordId (0-based) => proven block record.
mapping(uint256 => ProvenBlock) public records;
/// @notice chain-2800 blockNumber => 1-based recordId (0 == not proven).
mapping(uint256 => uint256) public blockToRecord;
/// @notice The most recently proven post-state root (informational tip).
bytes32 public latestPostRoot;
/* --------------------------------- events ------------------------------- */
event BlockProven(
uint256 indexed recordId,
uint256 indexed blockNumber,
bytes32 prevStateRoot,
bytes32 postStateRoot,
bytes32 txRoot,
uint256 gasUsed,
uint256 numTxns,
address prover,
uint256 at
);
/* --------------------------------- errors ------------------------------- */
error InvalidProof();
error BadPublicValuesLength(uint256 got);
error WrongChain(uint256 inProof, uint256 onChain);
error AlreadyProven(uint256 blockNumber, uint256 recordId);
error ZeroAddress();
error ZeroVKey();
/* ----------------------------- constructor ------------------------------ */
constructor(address sp1Verifier, bytes32 programVKey) {
if (sp1Verifier == address(0)) revert ZeroAddress();
if (programVKey == bytes32(0)) revert ZeroVKey();
SP1_VERIFIER = sp1Verifier;
PROGRAM_VKEY = programVKey;
}
/* ------------------------------ record block ---------------------------- */
/// @notice Verify a full-EVM validity proof for a chain-2800 block and, on
/// success, record it. Permissionless. Reverts if the proof is not
/// valid for PROGRAM_VKEY, if the proof is for a different chain, or
/// if that block number was already recorded.
/// @param publicValues abi.encode(chainId, blockNumber, prevStateRoot,
/// postStateRoot, txRoot, gasUsed, numTxns), 224 bytes.
/// @param proof SP1 Groth16 proof bytes (leading selector 0x4388a21c).
/// @return recordId the 0-based id of the recorded proven block.
function recordBlock(
bytes calldata publicValues,
bytes calldata proof
) external returns (uint256 recordId) {
// 7 x 32 = 224 bytes.
if (publicValues.length != 224) revert BadPublicValuesLength(publicValues.length);
// 1) zk proof: reverts if the proof is not valid for PROGRAM_VKEY.
try ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof) {
// verified - did not revert
} catch {
revert InvalidProof();
}
(
uint256 chainId,
uint256 blockNumber,
bytes32 prevStateRoot,
bytes32 postStateRoot,
bytes32 txRoot,
uint256 gasUsed,
uint256 numTxns
) = abi.decode(
publicValues,
(uint256, uint256, bytes32, bytes32, bytes32, uint256, uint256)
);
// 2) bind to this chain.
if (chainId != block.chainid) revert WrongChain(chainId, block.chainid);
// 3) one record per block number.
uint256 existing = blockToRecord[blockNumber];
if (existing != 0) revert AlreadyProven(blockNumber, existing - 1);
// 4) record the proven block.
recordId = provenCount;
records[recordId] = ProvenBlock({
blockNumber: uint64(blockNumber),
prevStateRoot: prevStateRoot,
postStateRoot: postStateRoot,
txRoot: txRoot,
gasUsed: uint64(gasUsed),
numTxns: uint32(numTxns),
at: uint64(block.timestamp),
prover: msg.sender
});
blockToRecord[blockNumber] = recordId + 1; // 1-based so 0 means "unseen"
latestPostRoot = postStateRoot;
provenCount = recordId + 1;
emit BlockProven(
recordId, blockNumber, prevStateRoot, postStateRoot,
txRoot, gasUsed, numTxns, msg.sender, block.timestamp
);
}
/* ----------------------------------- views ------------------------------ */
/// @notice Stateless verification: reverts if the proof is invalid for
/// PROGRAM_VKEY, otherwise returns the decoded fields. Records nothing.
function verify(
bytes calldata publicValues,
bytes calldata proof
)
external
view
returns (
uint256 blockNumber,
bytes32 prevStateRoot,
bytes32 postStateRoot,
bytes32 txRoot,
uint256 gasUsed,
uint256 numTxns
)
{
if (publicValues.length != 224) revert BadPublicValuesLength(publicValues.length);
ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof);
(, blockNumber, prevStateRoot, postStateRoot, txRoot, gasUsed, numTxns) = abi.decode(
publicValues,
(uint256, uint256, bytes32, bytes32, bytes32, uint256, uint256)
);
}
/// @notice Convenience accessor for a recorded proven block.
function getRecord(uint256 recordId)
external
view
returns (
uint64 blockNumber,
bytes32 prevStateRoot,
bytes32 postStateRoot,
bytes32 txRoot,
uint64 gasUsed,
uint32 numTxns,
uint64 at,
address prover
)
{
ProvenBlock memory r = records[recordId];
return (
r.blockNumber, r.prevStateRoot, r.postStateRoot, r.txRoot,
r.gasUsed, r.numTxns, r.at, r.prover
);
}
}