// SPDX-License-Identifier: MIT pragma solidity 0.8.23; /// @dev Generated Halo2 (bn254/KZG) verifier ABI: verifyProof returns a /// 32-byte true on success and REVERTS (empty data) on any failure. interface IHalo2Verifier { function verifyProof(bytes calldata proof, uint256[] calldata instances) external returns (bool); } /** * @title AereHalo2ProofAnchor — on-chain record of verified Halo2 proofs * @notice Thin, admin-less anchor over a generated Halo2 Solidity verifier * (privacy-scaling-explorations/halo2-solidity-verifier, bn254/KZG, * Bdfg21/SHPLONK batch opening). The bound verifier for this * deployment is AereHalo2CubicVerifier: a real 4-row standard-PLONK * circuit proving knowledge of a private x with x^3 + x + 5 == out, * where out is the single public instance. * * verifyAndRecord() forwards (proof, instances) to the verifier; * the verifier reverts on an invalid proof, so reaching the record * line means the proof is genuinely valid. Records are append-only: * (sender, keccak256(proof), keccak256(instances), block, time). * * IMMUTABILITY: the verifier address is fixed at deploy; no owner, * no admin, no pause. */ contract AereHalo2ProofAnchor { /// @notice The generated Halo2 verifier this anchor is bound to. address public immutable VERIFIER; struct Record { address sender; bytes32 proofHash; // keccak256 of the raw proof bytes bytes32 instancesHash; // keccak256(abi.encodePacked(instances)) uint64 blockNumber; uint64 timestamp; } Record[] private _records; /// @notice Number of successful verifications per proof hash. mapping(bytes32 => uint256) public verificationCountByProofHash; event Halo2ProofVerified( address indexed sender, bytes32 indexed proofHash, bytes32 instancesHash, uint256 index ); error ZeroVerifier(); error ProofRejected(); constructor(address verifier) { if (verifier == address(0)) revert ZeroVerifier(); VERIFIER = verifier; } /** * @notice Verify a Halo2 proof through the bound verifier and record it. * @param proof Raw proof bytes as produced by the halo2 prover * (Keccak256Transcript, ProverSHPLONK). * @param instances Public instances (for AereHalo2CubicVerifier: [out]). * @return index Index of the stored record. */ function verifyAndRecord(bytes calldata proof, uint256[] calldata instances) external returns (uint256 index) { // The generated verifier reverts on an invalid proof; bubble that up // as a clean custom error if it somehow returns false instead. bool ok = IHalo2Verifier(VERIFIER).verifyProof(proof, instances); if (!ok) revert ProofRejected(); bytes32 proofHash = keccak256(proof); bytes32 instancesHash = keccak256(abi.encodePacked(instances)); index = _records.length; _records.push(Record({ sender: msg.sender, proofHash: proofHash, instancesHash: instancesHash, blockNumber: uint64(block.number), timestamp: uint64(block.timestamp) })); verificationCountByProofHash[proofHash] += 1; emit Halo2ProofVerified(msg.sender, proofHash, instancesHash, index); } /// @notice Total number of recorded verifications. function recordCount() external view returns (uint256) { return _records.length; } /// @notice Full record at `index` (reverts if out of range). function getRecord(uint256 index) external view returns (Record memory) { return _records[index]; } }