aere-contracts/contracts/zkverify/AereProofRegistryV2.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

230 lines
8.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ISP1Verifier} from "./ISP1Verifier.sol";
import {IRiscZeroVerifier, Receipt as R0Receipt, ReceiptClaim, ReceiptClaimLib, ExitCode, SystemExitCode, Output, OutputLib, SystemState, SystemStateLib} from "./risczero/IRiscZeroVerifier.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
/// @title AereProofRegistry — multi-prover zk proof registry on AERE chain 2800
/// @notice Verifies proofs from SP1 (Succinct Labs) and RISC Zero zkVM, then records each
/// successful verification as a permanent on-chain attestation.
///
/// Architecture:
/// - Proofs route through the SP1VerifierGateway and RiscZeroVerifierRouter.
/// That means future prover versions (SP1 v7, RISC Zero v2.0+) plug in by
/// calling addRoute on the upstream gateway — no migration of this registry.
/// - Programs (a vkey for SP1 / imageId for RISC Zero) must be registered before
/// proofs of them can be recorded. Registration is permissionless: anyone can
/// register their own program with a human-readable name + URL. The Foundation
/// can disable malicious programs but cannot disable legitimate ones.
/// - Proof submission emits ProofVerified with the program key indexed, so
/// off-chain consumers (Bank28 KYC, dApp loyalty, exchange withdrawal proofs)
/// can subscribe to events filtered by their program of interest.
///
/// The verifyXOnly view functions provide free verification without recording,
/// for dApps that need the verdict but not a permanent attestation.
contract AereProofRegistry is Ownable {
using ReceiptClaimLib for ReceiptClaim;
/// @notice The SP1 verifier gateway (routes by selector → concrete SP1 verifier version)
address public immutable sp1Gateway;
/// @notice The RISC Zero verifier router (routes by selector → concrete R0 verifier)
address public immutable risc0Router;
/// @notice Program registry — vkey (SP1) or imageId (R0) → Program metadata
enum ProverSystem { SP1, RiscZero }
struct Program {
bool enabled;
ProverSystem system;
string name;
string url;
address registrant;
uint64 registeredAt;
}
mapping(bytes32 => Program) public programs;
bytes32[] public programKeys;
/// @notice Permanent proof record
struct ProofRecord {
address submitter;
uint64 timestamp;
uint64 blockNumber;
bytes32 programKey; // vkey or imageId
bytes32 publicValuesDigest; // sha256 of public values / journalDigest
string description;
ProverSystem system;
}
mapping(uint256 => ProofRecord) public proofs;
uint256 public proofCount;
mapping(bytes32 => uint256[]) public programProofs;
mapping(address => uint256[]) public submitterProofs;
event ProgramRegistered(
bytes32 indexed programKey,
ProverSystem indexed system,
address indexed registrant,
string name,
string url
);
event ProgramDisabled(bytes32 indexed programKey, ProverSystem indexed system);
event ProofVerified(
uint256 indexed id,
address indexed submitter,
bytes32 indexed programKey,
ProverSystem system,
bytes32 publicValuesDigest,
string description,
uint64 timestamp,
uint64 blockNumber
);
error ProgramAlreadyRegistered();
error ProgramNotApproved();
error EmptyName();
error EmptyDescription();
constructor(address _sp1Gateway, address _risc0Router) Ownable() {
require(_sp1Gateway != address(0), "AereProofRegistry: sp1Gateway=0");
require(_risc0Router != address(0), "AereProofRegistry: risc0Router=0");
sp1Gateway = _sp1Gateway;
risc0Router = _risc0Router;
}
// ─── Program registration (permissionless) ────────────────────────────────
function registerSP1Program(
bytes32 vkey,
string calldata name,
string calldata url
) external {
_register(vkey, ProverSystem.SP1, name, url);
}
function registerRiscZeroProgram(
bytes32 imageId,
string calldata name,
string calldata url
) external {
_register(imageId, ProverSystem.RiscZero, name, url);
}
function _register(
bytes32 key,
ProverSystem system,
string calldata name,
string calldata url
) internal {
if (bytes(name).length == 0) revert EmptyName();
if (programs[key].registeredAt != 0) revert ProgramAlreadyRegistered();
programs[key] = Program({
enabled: true,
system: system,
name: name,
url: url,
registrant: msg.sender,
registeredAt: uint64(block.timestamp)
});
programKeys.push(key);
emit ProgramRegistered(key, system, msg.sender, name, url);
}
/// @notice Owner (Foundation) can disable a program known to be malicious or compromised.
/// This does not retroactively invalidate already-recorded proofs.
function disableProgram(bytes32 key) external onlyOwner {
Program storage p = programs[key];
require(p.registeredAt != 0, "not registered");
require(p.enabled, "already disabled");
p.enabled = false;
emit ProgramDisabled(key, p.system);
}
// ─── Proof submission (permissionless once program registered) ────────────
function submitSP1Proof(
bytes32 vkey,
bytes calldata publicValues,
bytes calldata proofBytes,
string calldata description
) external returns (uint256 id) {
Program storage p = programs[vkey];
if (!p.enabled || p.system != ProverSystem.SP1) revert ProgramNotApproved();
if (bytes(description).length == 0) revert EmptyDescription();
ISP1Verifier(sp1Gateway).verifyProof(vkey, publicValues, proofBytes);
id = _record(vkey, sha256(publicValues), description, ProverSystem.SP1);
}
function submitRiscZeroProof(
bytes32 imageId,
bytes32 journalDigest,
bytes calldata seal,
string calldata description
) external returns (uint256 id) {
Program storage p = programs[imageId];
if (!p.enabled || p.system != ProverSystem.RiscZero) revert ProgramNotApproved();
if (bytes(description).length == 0) revert EmptyDescription();
IRiscZeroVerifier(risc0Router).verify(seal, imageId, journalDigest);
id = _record(imageId, journalDigest, description, ProverSystem.RiscZero);
}
function _record(
bytes32 key,
bytes32 pvDigest,
string calldata description,
ProverSystem system
) internal returns (uint256 id) {
id = proofCount++;
proofs[id] = ProofRecord({
submitter: msg.sender,
timestamp: uint64(block.timestamp),
blockNumber: uint64(block.number),
programKey: key,
publicValuesDigest: pvDigest,
description: description,
system: system
});
programProofs[key].push(id);
submitterProofs[msg.sender].push(id);
emit ProofVerified(
id, msg.sender, key, system, pvDigest, description,
uint64(block.timestamp), uint64(block.number)
);
}
// ─── Free verification (view, no record) ──────────────────────────────────
function verifySP1Only(
bytes32 vkey,
bytes calldata publicValues,
bytes calldata proofBytes
) external view {
ISP1Verifier(sp1Gateway).verifyProof(vkey, publicValues, proofBytes);
}
function verifyRiscZeroOnly(
bytes32 imageId,
bytes32 journalDigest,
bytes calldata seal
) external view {
IRiscZeroVerifier(risc0Router).verify(seal, imageId, journalDigest);
}
// ─── Views ────────────────────────────────────────────────────────────────
function programCount() external view returns (uint256) {
return programKeys.length;
}
function submitterProofCount(address submitter) external view returns (uint256) {
return submitterProofs[submitter].length;
}
function programProofCount(bytes32 key) external view returns (uint256) {
return programProofs[key].length;
}
}