// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import {ISP1Verifier} from "../zkverify/ISP1Verifier.sol";

/// @dev Minimal view surface of AerePQAttestationKeyRegistry this verifier reads.
interface IAerePQAttestationKeyRegistry {
    function validatorSetRoot() external view returns (bytes32);
    function validatorCount() external view returns (uint256);
}

/**
 * @title AereFinalityCertificateVerifier, the on-chain half of Aere Network's ADDITIVE
 *        Post-Quantum Finality Certificate (chain 2800).
 *
 * @notice A Post-Quantum Finality Certificate is ONE succinct proof that a quorum of Aere
 *         validators attested to a finalized block with their HASH-BASED (post-quantum)
 *         attestation keys. Anyone, a light client, a bridge, an L2, or an AI agent, can
 *         verify with a SINGLE `verifyProof` call, at a cost INDEPENDENT of the validator
 *         count N, that an Aere block reached PQ finality, WITHOUT trusting the classical
 *         ECDSA seals and WITHOUT changing consensus.
 *
 *         The off-chain aggregation circuit (the [MEASURE] research component) proves,
 *         inside a zkVM, that at least `ceil(2N/3)` DISTINCT validators, each committed
 *         under the registry's `validatorSetRoot`, produced a valid hash-based signature
 *         over the block. This contract does the cheap on-chain part: it binds the proof's
 *         public values to the live attestation-key registry, enforces the quorum
 *         threshold, and verifies the one proof through the live SP1 gateway.
 *
 * @dev    THE HONEST BOUNDARY, read this before trusting or describing anything.
 *
 *         1. ADDITIVE, NOT A CONSENSUS FLIP. Aere consensus is STILL classical ECDSA QBFT.
 *            Validators keep signing ECDSA committed seals for block production and ALSO
 *            sign a hash-based attestation over each finalized block. This certificate
 *            proves the SECOND, additive quorum. It does not replace, gate, or flip
 *            consensus, it needs no re-genesis, and PQ finality is NOT live. Never imply
 *            Aere consensus is post-quantum: it is not.
 *
 *         2. THE OFF-CHAIN AGGREGATION zkVM CIRCUIT IS NOT IMPLEMENTED HERE. What is built
 *            and tested is the ON-CHAIN side: this verifier, the registry, the public-input
 *            binding scheme, the fail-closed checks, and a mock-gateway test. The zkVM guest
 *            that verifies N hash-based (XMSS / leanSig / SLH-DSA) signatures against the
 *            validator-set root and emits the public values is REAL, NOVEL cryptographic
 *            engineering that is NOT in this repository and is marked [MEASURE] in
 *            docs/AERE-PQ-FINALITY-CERTIFICATE.md. Verifying a stateful hash-based scheme
 *            (XMSS) or a leanSig scheme inside a zkVM, or an ML-DSA-in-zkVM alternative, is
 *            the research target. Do NOT describe the certificate as working end-to-end: the
 *            on-chain verifier is proven against a MOCK SP1 gateway that asserts the bound
 *            public values, the same test double the aggregate verifier and the light clients
 *            use.
 *
 *         3. WHY THIS IS QUANTUM-SAFE WHERE THE ECDSA PATH IS NOT. The attestation legs are
 *            hash-based signatures (post-quantum from hash collision / preimage resistance
 *            alone), and the aggregation proof is a hash-based STARK (SP1's core proof is a
 *            hash-based FRI STARK, Shor-resistant). This differs from AereZkQbftLightClient,
 *            which proves the CLASSICAL ECDSA committed-seal quorum and, when wrapped to
 *            Groth16 over BN254, is quantum-vulnerable. A quantum adversary that can forge
 *            ECDSA still cannot forge the hash-based attestations, so a PQ certificate stays
 *            sound. HONESTY: this quantum-safety property is a property of the DESIGN and of
 *            the [MEASURE] circuit once it is real; the on-chain contract alone does not
 *            establish it, and a Groth16-wrapped SP1 proof reintroduces a BN254 leg (see the
 *            doc for the STARK-verifier note).
 *
 *         4. THE PINNED CIRCUIT. PROGRAM_VKEY is the SP1 verification key of the ONE audited
 *            aggregation circuit this verifier accepts, fixed at construction. No caller can
 *            substitute a different circuit: every certificate is proven by the same pinned
 *            program through the same immutable gateway.
 *
 *         5. FAIL-CLOSED. A certificate is accepted ONLY when ALL hold, and rejected
 *            otherwise:
 *              - the registry validator set is non-empty (else EmptyValidatorSet),
 *              - the proof's validatorSetRoot equals the registry's CURRENT root
 *                (else ValidatorSetRootMismatch) -- so a rotation invalidates old certs,
 *              - the proof's validatorSetSize equals the registry's current N (else SizeMismatch),
 *              - the proof's attestationDomain equals this verifier's bound domain
 *                (else DomainMismatch) -- binds chainId + registry, blocks cross-context replay,
 *              - the proof's quorumCount is >= ceil(2N/3) (else BelowQuorum),
 *              - the proof's blockHash is non-zero (else ZeroBlockHash),
 *              - the SP1 proof verifies against the pinned vkey through the gateway
 *                (verifyProof REVERTS on an invalid proof).
 *
 * @dev    PUBLIC-VALUES LAYOUT. The aggregation guest MUST output its public values as the
 *         fixed 192-byte ABI encoding of six words:
 *           abi.encode(
 *             bytes32 blockHash,          // the finalized Aere block hash
 *             uint256 blockHeight,        // its height
 *             bytes32 validatorSetRoot,   // recomputed IN the circuit from the witnessed keys
 *             uint256 validatorSetSize,   // N committed under the root
 *             uint256 quorumCount,        // number of DISTINCT valid hash-based attestations, >= ceil(2N/3)
 *             bytes32 attestationDomain   // domain tag; each key signed keccak256(domain, blockHash, blockHeight)
 *           )
 *         The circuit is responsible for: recomputing validatorSetRoot from the witnessed
 *         keys, verifying each counted hash-based signature against a DISTINCT committed key
 *         over the domain-bound block message, and never double-counting a validator. This
 *         contract trusts the proof for those facts and binds the public values to the live
 *         registry and to the quorum threshold. See docs/AERE-PQ-FINALITY-CERTIFICATE.md.
 */
contract AereFinalityCertificateVerifier {
    /// @notice Exact byte length of the SP1 public values (six 32-byte words).
    uint256 internal constant PUBLIC_VALUES_LEN = 192;

    /// @notice Domain-separation prefix for the attestation message binding.
    bytes32 public constant DOMAIN_PREFIX = keccak256("AerePQFinalityCertificate.v1");

    // ==========================================================================
    //                              Immutable wiring
    // ==========================================================================

    /// @notice The deployed SP1 verifier gateway that checks certificate proofs on-chain.
    ///         On Aere Network mainnet (chain 2800) this is the canonical Succinct gateway
    ///         at 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628. verifyProof REVERTS on an
    ///         invalid proof, which is the fail-closed backbone of this verifier.
    ISP1Verifier public immutable SP1_GATEWAY;

    /// @notice The SP1 verification key of the ONE audited aggregation circuit this verifier
    ///         accepts. Fixed at construction; no caller can substitute another circuit.
    bytes32 public immutable PROGRAM_VKEY;

    /// @notice The post-quantum attestation-key registry this verifier binds certificates to.
    IAerePQAttestationKeyRegistry public immutable REGISTRY;

    /// @notice The domain tag the circuit must bind: keccak256(DOMAIN_PREFIX, chainId, registry).
    ///         The message each validator's hash-based key signed is
    ///         keccak256(abi.encode(attestationDomain, blockHash, blockHeight)); pinning the
    ///         domain blocks replay of a certificate to a different chain or registry.
    bytes32 public immutable attestationDomain;

    // ==========================================================================
    //                                  Types
    // ==========================================================================

    /// @notice A recorded PQ finality certificate, keyed by block hash.
    struct Certificate {
        bool exists;
        uint64 blockHeight;
        uint32 quorumCount;
        uint32 validatorSetSize;
        bytes32 validatorSetRoot;
        uint64 recordedBlock;
        address prover;
    }

    // ==========================================================================
    //                                 Storage
    // ==========================================================================

    // finalized Aere block hash => recorded certificate
    mapping(bytes32 => Certificate) private _certificates;

    /// @notice The highest Aere block height recorded PQ-final so far (a convenience head).
    uint64 public highestFinalizedHeight;
    /// @notice The block hash at `highestFinalizedHeight`.
    bytes32 public highestFinalizedBlockHash;
    /// @notice Number of certificates recorded.
    uint64 public certificateCount;

    // ==========================================================================
    //                                 Events
    // ==========================================================================

    event CertificateRecorded(
        bytes32 indexed blockHash,
        uint64 indexed blockHeight,
        uint32 quorumCount,
        uint32 validatorSetSize,
        bytes32 validatorSetRoot,
        address prover
    );

    // ==========================================================================
    //                                 Errors
    // ==========================================================================

    error ZeroGateway();
    error ZeroVKey();
    error ZeroRegistry();
    error VerifierHasNoCode();
    error BadPublicValues(); // wrong length, not the fixed 192-byte layout
    error EmptyValidatorSet();
    error ValidatorSetRootMismatch(bytes32 provenRoot, bytes32 registeredRoot);
    error SizeMismatch(uint256 provenSize, uint256 registeredSize);
    error DomainMismatch(bytes32 provenDomain, bytes32 expectedDomain);
    error BelowQuorum(uint256 provenQuorum, uint256 requiredQuorum);
    error ZeroBlockHash();
    error BlockHashMismatch(bytes32 provenBlockHash, bytes32 claimedBlockHash);
    error BlockHeightMismatch(uint256 provenHeight, uint256 claimedHeight);

    // ==========================================================================
    //                               Construction
    // ==========================================================================

    /// @param gateway     the deployed SP1 verifier gateway (chain 2800: 0x9ca4…0628).
    /// @param programVKey the SP1 vkey of the one aggregation circuit this verifier accepts.
    /// @param registry    the AerePQAttestationKeyRegistry whose root certificates bind to.
    constructor(address gateway, bytes32 programVKey, address registry) {
        if (gateway == address(0)) revert ZeroGateway();
        // A code-less gateway would make verifyProof (which returns no data) succeed silently,
        // accepting every "proof". The deployed value must be the live SP1 gateway.
        if (gateway.code.length == 0) revert VerifierHasNoCode();
        if (programVKey == bytes32(0)) revert ZeroVKey();
        if (registry == address(0)) revert ZeroRegistry();

        SP1_GATEWAY = ISP1Verifier(gateway);
        PROGRAM_VKEY = programVKey;
        REGISTRY = IAerePQAttestationKeyRegistry(registry);
        attestationDomain = keccak256(abi.encode(DOMAIN_PREFIX, block.chainid, registry));
    }

    // ==========================================================================
    //                          Certificate verification
    // ==========================================================================

    /**
     * @notice STRICT verification: reverts (fail-closed) unless the certificate proves a
     *         >= ceil(2N/3) hash-based attestation quorum over its block against the live
     *         registry. Verification is a SINGLE gateway verifyProof call; the gas does NOT
     *         grow with N (no per-validator work happens on-chain). Callable as a view by
     *         any relying contract. Returns the finalized (blockHash, blockHeight) on success.
     *
     * @param publicValues the SP1 public values, the fixed 192-byte layout documented above.
     * @param proof        the SP1 proof; its first 4 bytes select the gateway route.
     */
    function verifyCertificate(bytes calldata publicValues, bytes calldata proof)
        public
        view
        returns (bytes32 blockHash, uint64 blockHeight)
    {
        (
            bytes32 pvBlockHash,
            uint256 pvBlockHeight,
            bytes32 pvRoot,
            uint256 pvSize,
            uint256 pvQuorum,
            bytes32 pvDomain
        ) = _decode(publicValues);

        // Bind to the LIVE registry state: the current root and size are the source of truth.
        uint256 n = REGISTRY.validatorCount();
        if (n == 0) revert EmptyValidatorSet();
        bytes32 registeredRoot = REGISTRY.validatorSetRoot();

        if (pvRoot != registeredRoot) revert ValidatorSetRootMismatch(pvRoot, registeredRoot);
        if (pvSize != n) revert SizeMismatch(pvSize, n);
        if (pvDomain != attestationDomain) revert DomainMismatch(pvDomain, attestationDomain);

        uint256 required = _quorumThreshold(n);
        if (pvQuorum < required) revert BelowQuorum(pvQuorum, required);
        if (pvBlockHash == bytes32(0)) revert ZeroBlockHash();

        // Single on-chain proof check. verifyProof reverts on an invalid proof, so a bad
        // proof reverts the whole call and finalizes nothing. This is the only cryptographic
        // gate and its cost is fixed by the SP1 verifier, independent of N.
        SP1_GATEWAY.verifyProof(PROGRAM_VKEY, publicValues, proof);

        return (pvBlockHash, uint64(pvBlockHeight));
    }

    /**
     * @notice NON-REVERTING verification: returns true iff `verifyCertificate` would succeed,
     *         and false on ANY failure (malformed public values, a binding mismatch, below
     *         quorum, or an invalid proof). For consumers that prefer a boolean gate.
     */
    function tryVerifyCertificate(bytes calldata publicValues, bytes calldata proof) external view returns (bool) {
        try this.verifyCertificate(publicValues, proof) returns (bytes32, uint64) {
            return true;
        } catch {
            return false;
        }
    }

    /**
     * @notice STRICT verification bound to a CLAIMED block: like `verifyCertificate` but
     *         additionally requires the proof to finalize EXACTLY (claimedBlockHash,
     *         claimedHeight). A consumer that wants to confirm a SPECIFIC block is PQ-final
     *         (a bridge releasing funds for block X) uses this so a certificate for a
     *         different block cannot be substituted. Reverts BlockHashMismatch /
     *         BlockHeightMismatch on a mismatch.
     */
    function assertFinalizes(
        bytes32 claimedBlockHash,
        uint64 claimedHeight,
        bytes calldata publicValues,
        bytes calldata proof
    ) public view returns (bool) {
        (bytes32 blockHash, uint64 blockHeight) = verifyCertificate(publicValues, proof);
        if (blockHash != claimedBlockHash) revert BlockHashMismatch(blockHash, claimedBlockHash);
        if (blockHeight != claimedHeight) revert BlockHeightMismatch(blockHeight, claimedHeight);
        return true;
    }

    /// @notice Non-reverting variant of `assertFinalizes`.
    function tryAssertFinalizes(
        bytes32 claimedBlockHash,
        uint64 claimedHeight,
        bytes calldata publicValues,
        bytes calldata proof
    ) external view returns (bool) {
        try this.assertFinalizes(claimedBlockHash, claimedHeight, publicValues, proof) returns (bool ok) {
            return ok;
        } catch {
            return false;
        }
    }

    // ==========================================================================
    //                          Certificate recording
    // ==========================================================================

    /**
     * @notice STATE-CHANGING: verify the certificate (strict, fail-closed) and record it,
     *         keyed by the finalized block hash, so a consumer contract can later gate on
     *         PQ finality via `isFinalPQ`. Reverts unless the proof verifies, so a recorded
     *         certificate is always backed by a valid proof over a quorum. Permissionless.
     *
     * @dev    Re-recording the same block hash refreshes the record's provenance; it grants
     *         no new authority. The head (highestFinalizedHeight / BlockHash) advances only
     *         on a strictly greater height.
     */
    function recordCertificate(bytes calldata publicValues, bytes calldata proof)
        external
        returns (bytes32 blockHash, uint64 blockHeight)
    {
        (blockHash, blockHeight) = verifyCertificate(publicValues, proof);

        (, , bytes32 pvRoot, uint256 pvSize, uint256 pvQuorum, ) = _decode(publicValues);

        bool isNew = !_certificates[blockHash].exists;
        _certificates[blockHash] = Certificate({
            exists: true,
            blockHeight: blockHeight,
            quorumCount: uint32(pvQuorum),
            validatorSetSize: uint32(pvSize),
            validatorSetRoot: pvRoot,
            recordedBlock: uint64(block.number),
            prover: msg.sender
        });
        if (isNew) certificateCount += 1;

        if (blockHeight > highestFinalizedHeight) {
            highestFinalizedHeight = blockHeight;
            highestFinalizedBlockHash = blockHash;
        }

        emit CertificateRecorded(
            blockHash, blockHeight, uint32(pvQuorum), uint32(pvSize), pvRoot, msg.sender
        );
    }

    // ==========================================================================
    //                          Consumer gating / views
    // ==========================================================================

    /// @notice True iff a PQ finality certificate has been recorded for `blockHash`. This is
    ///         the gate a bridge / light client / L2 / AI agent checks before acting on Aere
    ///         finality quantum-safely.
    function isFinalPQ(bytes32 blockHash) external view returns (bool) {
        return _certificates[blockHash].exists;
    }

    /// @notice True iff `blockHash` is recorded PQ-final AND at exactly `blockHeight`.
    function isFinalPQAtHeight(bytes32 blockHash, uint64 blockHeight) external view returns (bool) {
        Certificate storage c = _certificates[blockHash];
        return c.exists && c.blockHeight == blockHeight;
    }

    /// @notice Read a recorded certificate. `exists` is false for an unknown block hash.
    function certificateOf(bytes32 blockHash)
        external
        view
        returns (
            bool exists,
            uint64 blockHeight,
            uint32 quorumCount,
            uint32 validatorSetSize,
            bytes32 validatorSetRoot,
            uint64 recordedBlock,
            address prover
        )
    {
        Certificate storage c = _certificates[blockHash];
        return (
            c.exists, c.blockHeight, c.quorumCount, c.validatorSetSize, c.validatorSetRoot, c.recordedBlock, c.prover
        );
    }

    /// @notice The quorum threshold ceil(2N/3) for the current registry validator set.
    function currentQuorumThreshold() external view returns (uint256) {
        return _quorumThreshold(REGISTRY.validatorCount());
    }

    /**
     * @notice Helper for off-chain tooling and the circuit harness: the exact 192-byte public
     *         values a certificate finalizing (blockHash, blockHeight) with `quorumCount`
     *         distinct attestations must emit, bound to the CURRENT registry root and size.
     */
    function expectedPublicValues(bytes32 blockHash, uint64 blockHeight, uint256 quorumCount)
        external
        view
        returns (bytes memory)
    {
        return abi.encode(
            blockHash,
            uint256(blockHeight),
            REGISTRY.validatorSetRoot(),
            REGISTRY.validatorCount(),
            quorumCount,
            attestationDomain
        );
    }

    /// @notice The message an attestation key signs for (blockHash, blockHeight) under this
    ///         verifier's domain. The circuit checks each hash-based signature over THIS value.
    function attestationMessage(bytes32 blockHash, uint64 blockHeight) external view returns (bytes32) {
        return keccak256(abi.encode(attestationDomain, blockHash, blockHeight));
    }

    // ==========================================================================
    //                               Internal
    // ==========================================================================

    /// @dev ceil(2N/3): the QBFT quorum. N=7 -> 5, N=9 -> 6, N=4 -> 3.
    function _quorumThreshold(uint256 n) internal pure returns (uint256) {
        return (2 * n + 2) / 3;
    }

    /// @dev Decode the fixed 192-byte public-values layout. Reverts BadPublicValues on any
    ///      other length; a correct-length blob always decodes without reverting.
    function _decode(bytes calldata publicValues)
        internal
        pure
        returns (
            bytes32 blockHash,
            uint256 blockHeight,
            bytes32 validatorSetRoot,
            uint256 validatorSetSize,
            uint256 quorumCount,
            bytes32 domain
        )
    {
        if (publicValues.length != PUBLIC_VALUES_LEN) revert BadPublicValues();
        (blockHash, blockHeight, validatorSetRoot, validatorSetSize, quorumCount, domain) =
            abi.decode(publicValues, (bytes32, uint256, bytes32, uint256, uint256, bytes32));
    }
}
