// 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; } }