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.
293 lines
12 KiB
Solidity
293 lines
12 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 AereEVMValidity uses it.
|
|
interface ISp1Verifier {
|
|
function verifyProof(
|
|
bytes32 programVKey,
|
|
bytes calldata publicValues,
|
|
bytes calldata proof
|
|
) external view;
|
|
}
|
|
|
|
/**
|
|
* @title AereEVMValidityBatch - on-chain anchor for REAL FULL-EVM validity proofs
|
|
* of a CONTIGUOUS BATCH of live chain-2800 blocks.
|
|
*
|
|
* @notice Sibling of AereEVMValidity. Where AereEVMValidity anchors the proof of a
|
|
* single re-executed chain-2800 (AERE mainnet, QBFT / Besu) block, this
|
|
* contract anchors the proof of a CONTIGUOUS RANGE of blocks proven as one
|
|
* SP1 zkVM Groth16 batch. The guest re-executes every block in
|
|
* [firstBlock, lastBlock] with revm (the SP1-patched reth 1.9.3 / revm
|
|
* stack) INSIDE the zkVM, threading each block's recomputed post-state root
|
|
* into the next block's pre-state, and only produces a valid proof when the
|
|
* final recomputed state root equals the `header.stateRoot` of the last
|
|
* block carried in the public values. This is REAL EVM execution (full
|
|
* opcode set, call frames, SLOAD/SSTORE, LOG, EIP-1559 base-fee burn +
|
|
* priority-to-coinbase, and the Cancun/Prague/Osaka system calls), not a
|
|
* bounded toy VM.
|
|
*
|
|
* BINDING CAVEAT (READ THIS): this V1 batch anchor attests a SELF-CONSISTENT
|
|
* execution of a range, NOT that the range is the CANONICAL chain-2800
|
|
* history. The public values carry no blockHash and this contract performs
|
|
* no blockhash() check and NO inter-record continuity, so a genuine SP1
|
|
* proof of a FABRICATED range can be recorded and overlapping/forked ranges
|
|
* can coexist. Do NOT treat a V1 record as canonical. Canonical tip binding
|
|
* plus gap-free/fork-free continuity lands in AereEVMValidityBatchV2.
|
|
*
|
|
* PROGRAM BINDING: PROGRAM_VKEY is the SP1 verification key of the exact
|
|
* BATCH guest ELF. It differs from the single-block guest's vkey. Binding
|
|
* the vkey binds the exact prover program, so a proof for the single-block
|
|
* program cannot be recorded here and vice-versa.
|
|
*
|
|
* PUBLIC-VALUES CONVENTION (exactly 256 bytes = 8 x 32), matching what the
|
|
* batch guest commits via abi.encode:
|
|
* abi.encode(
|
|
* uint256 chainId, // must equal block.chainid (2800)
|
|
* uint256 firstBlock, // first proven chain-2800 block height
|
|
* uint256 lastBlock, // last proven chain-2800 block height
|
|
* uint256 numBlocks, // count of blocks in the batch
|
|
* bytes32 prevStateRoot, // parent state root of firstBlock (pre-state,
|
|
* // NOT cross-checked on-chain in V1)
|
|
* bytes32 postStateRoot, // self-consistent post-state root of lastBlock
|
|
* uint256 totalGas, // total gas used across the batch
|
|
* uint256 numTxns // total transactions executed across the batch
|
|
* )
|
|
*
|
|
* 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 (firstBlock, lastBlock, prevStateRoot, postStateRoot) tuple that the zk
|
|
* proof establishes for PROGRAM_VKEY.
|
|
*
|
|
* ===========================================================================
|
|
* HONEST SCOPE - READ THIS.
|
|
* ===========================================================================
|
|
* This proves REAL EVM execution of a SMALL contiguous batch of live chain-2800
|
|
* blocks (a handful of transactions across the range). It is a legitimate
|
|
* real-EVM validity PROOF-OF-APPROACH for batched blocks. It is NOT yet a
|
|
* full-throughput production rollup: batch size is small and it records isolated
|
|
* proven ranges rather than a continuous sequencer chain. No code, UI or claim
|
|
* built on it should imply otherwise.
|
|
* ===========================================================================
|
|
*/
|
|
contract AereEVMValidityBatch {
|
|
|
|
/// @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 BATCH guest ELF.
|
|
bytes32 public immutable PROGRAM_VKEY;
|
|
|
|
/// @notice Number of proven batches recorded.
|
|
uint256 public provenCount;
|
|
|
|
struct ProvenBatch {
|
|
uint64 firstBlock;
|
|
uint64 lastBlock;
|
|
uint32 numBlocks;
|
|
bytes32 prevStateRoot;
|
|
bytes32 postStateRoot;
|
|
uint64 totalGas;
|
|
uint32 numTxns;
|
|
uint64 at; // block timestamp when the proof verified
|
|
address prover; // who submitted it
|
|
}
|
|
|
|
/// @notice recordId (0-based) => proven batch record.
|
|
mapping(uint256 => ProvenBatch) public records;
|
|
|
|
/// @notice keccak256(firstBlock, lastBlock) => 1-based recordId (0 == unseen).
|
|
mapping(bytes32 => uint256) public batchToRecord;
|
|
|
|
/// @notice The most recently proven post-state root (informational tip).
|
|
bytes32 public latestPostRoot;
|
|
|
|
/// @notice The last block of the most recently proven batch (informational tip).
|
|
uint64 public latestLastBlock;
|
|
|
|
/* --------------------------------- events ------------------------------- */
|
|
|
|
event BatchProven(
|
|
uint256 indexed recordId,
|
|
uint256 indexed firstBlock,
|
|
uint256 indexed lastBlock,
|
|
uint256 numBlocks,
|
|
bytes32 prevStateRoot,
|
|
bytes32 postStateRoot,
|
|
uint256 totalGas,
|
|
uint256 numTxns,
|
|
address prover,
|
|
uint256 at
|
|
);
|
|
|
|
/* --------------------------------- errors ------------------------------- */
|
|
|
|
error InvalidProof();
|
|
error BadPublicValuesLength(uint256 got);
|
|
error WrongChain(uint256 inProof, uint256 onChain);
|
|
error BadRange(uint256 firstBlock, uint256 lastBlock, uint256 numBlocks);
|
|
error AlreadyProven(uint256 firstBlock, uint256 lastBlock, 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;
|
|
}
|
|
|
|
/* ------------------------------ batch key ------------------------------- */
|
|
|
|
/// @notice Deterministic dedupe key for a contiguous batch range.
|
|
function batchKey(uint256 firstBlock, uint256 lastBlock) public pure returns (bytes32) {
|
|
return keccak256(abi.encodePacked(uint64(firstBlock), uint64(lastBlock)));
|
|
}
|
|
|
|
/* ----------------------------- record batch ----------------------------- */
|
|
|
|
/// @notice Verify a full-EVM validity proof for a contiguous batch of chain-2800
|
|
/// blocks and, on success, record it. Permissionless. Reverts if the
|
|
/// proof is not valid for PROGRAM_VKEY, if the proof is for a different
|
|
/// chain, if the range is malformed, or if that exact range was already
|
|
/// recorded.
|
|
/// @param publicValues abi.encode(chainId, firstBlock, lastBlock, numBlocks,
|
|
/// prevStateRoot, postStateRoot, totalGas, numTxns), 256 bytes.
|
|
/// @param proof SP1 Groth16 proof bytes (leading selector 0x4388a21c).
|
|
/// @return recordId the 0-based id of the recorded proven batch.
|
|
function recordBatch(
|
|
bytes calldata publicValues,
|
|
bytes calldata proof
|
|
) external returns (uint256 recordId) {
|
|
// 8 x 32 = 256 bytes.
|
|
if (publicValues.length != 256) 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 firstBlock,
|
|
uint256 lastBlock,
|
|
uint256 numBlocks,
|
|
bytes32 prevStateRoot,
|
|
bytes32 postStateRoot,
|
|
uint256 totalGas,
|
|
uint256 numTxns
|
|
) = abi.decode(
|
|
publicValues,
|
|
(uint256, uint256, uint256, uint256, bytes32, bytes32, uint256, uint256)
|
|
);
|
|
|
|
// 2) bind to this chain.
|
|
if (chainId != block.chainid) revert WrongChain(chainId, block.chainid);
|
|
|
|
// 3) contiguity invariant guaranteed by the batch guest.
|
|
if (lastBlock < firstBlock || numBlocks != lastBlock - firstBlock + 1) {
|
|
revert BadRange(firstBlock, lastBlock, numBlocks);
|
|
}
|
|
|
|
// 4) one record per exact range.
|
|
bytes32 key = batchKey(firstBlock, lastBlock);
|
|
uint256 existing = batchToRecord[key];
|
|
if (existing != 0) revert AlreadyProven(firstBlock, lastBlock, existing - 1);
|
|
|
|
// 5) record the proven batch.
|
|
recordId = provenCount;
|
|
records[recordId] = ProvenBatch({
|
|
firstBlock: uint64(firstBlock),
|
|
lastBlock: uint64(lastBlock),
|
|
numBlocks: uint32(numBlocks),
|
|
prevStateRoot: prevStateRoot,
|
|
postStateRoot: postStateRoot,
|
|
totalGas: uint64(totalGas),
|
|
numTxns: uint32(numTxns),
|
|
at: uint64(block.timestamp),
|
|
prover: msg.sender
|
|
});
|
|
batchToRecord[key] = recordId + 1; // 1-based so 0 means "unseen"
|
|
latestPostRoot = postStateRoot;
|
|
latestLastBlock = uint64(lastBlock);
|
|
provenCount = recordId + 1;
|
|
|
|
emit BatchProven(
|
|
recordId, firstBlock, lastBlock, numBlocks, prevStateRoot,
|
|
postStateRoot, totalGas, 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 firstBlock,
|
|
uint256 lastBlock,
|
|
uint256 numBlocks,
|
|
bytes32 prevStateRoot,
|
|
bytes32 postStateRoot,
|
|
uint256 totalGas,
|
|
uint256 numTxns
|
|
)
|
|
{
|
|
if (publicValues.length != 256) revert BadPublicValuesLength(publicValues.length);
|
|
ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof);
|
|
(, firstBlock, lastBlock, numBlocks, prevStateRoot, postStateRoot, totalGas, numTxns) =
|
|
abi.decode(
|
|
publicValues,
|
|
(uint256, uint256, uint256, uint256, bytes32, bytes32, uint256, uint256)
|
|
);
|
|
}
|
|
|
|
/// @notice Convenience accessor for a recorded proven batch.
|
|
function getRecord(uint256 recordId)
|
|
external
|
|
view
|
|
returns (
|
|
uint64 firstBlock,
|
|
uint64 lastBlock,
|
|
uint32 numBlocks,
|
|
bytes32 prevStateRoot,
|
|
bytes32 postStateRoot,
|
|
uint64 totalGas,
|
|
uint32 numTxns,
|
|
uint64 at,
|
|
address prover
|
|
)
|
|
{
|
|
ProvenBatch memory r = records[recordId];
|
|
return (
|
|
r.firstBlock, r.lastBlock, r.numBlocks, r.prevStateRoot,
|
|
r.postStateRoot, r.totalGas, r.numTxns, r.at, r.prover
|
|
);
|
|
}
|
|
|
|
/// @notice recordId for an exact range, or reverts if that range is unseen.
|
|
function recordIdForRange(uint256 firstBlock, uint256 lastBlock)
|
|
external
|
|
view
|
|
returns (uint256 recordId)
|
|
{
|
|
uint256 existing = batchToRecord[batchKey(firstBlock, lastBlock)];
|
|
require(existing != 0, "range not proven");
|
|
return existing - 1;
|
|
}
|
|
}
|