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