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

/// @dev Canonical SP1 verifier / SP1VerifierGateway ABI: verifyProof RETURNS
///      NOTHING and REVERTS on an invalid proof. On chain 2800 the deployed
///      SP1VerifierGateway is 0x9ca479C8c52C0EbB4599319a36a5a017BCC70628 and the
///      SP1 Groth16 route is selected by the proof's leading 4-byte selector,
///      exactly as AereZkEthLightClient / AereRollupValidity / AereEVMValidity use it.
interface ISp1Verifier {
    function verifyProof(
        bytes32 programVKey,
        bytes calldata publicValues,
        bytes calldata proof
    ) external view;
}

/**
 * @title AereZkQbftLightClient — succinct (zk) light client of AERE's OWN QBFT finality.
 *
 * @notice The OUTBOUND direction of AERE's zk light-client stack: it lets ANY chain (or
 *         any verifier) advance a trust-minimized view of AERE (chain 2800) finality by
 *         checking an SP1 zkVM **Groth16 proof** that ONE AERE block header is FINAL
 *         under Hyperledger Besu QBFT. The proof (guest `qbft-lightclient-guest`, sharing
 *         `qbft_finality::verify_finality` verbatim with the real-header test) checks,
 *         INSIDE the zkVM:
 *           1. decode the header's QBFT `extraData` exactly as Besu `QbftExtraDataCodec`;
 *           2. the header's stored validator set equals the known (trusted) set;
 *           3. recompute the on-chain block hash (`EXCLUDE_COMMIT_SEALS_AND_ROUND_NUMBER`)
 *              and the committed-seal digest (`EXCLUDE_COMMIT_SEALS`);
 *           4. ecrecover every 65-byte committed seal over the digest and keep those that
 *              map to a DISTINCT known validator;
 *           5. assert the distinct count reaches the QBFT quorum `ceil(2N/3)`.
 *
 *         This contract does the CHEAP part on chain: it verifies the ~300k-gas Groth16
 *         proof through the live SP1 gateway, binds the proof's public values to its own
 *         trusted validator-set anchor, enforces a STRICTLY ADVANCING block number, and on
 *         success records the newly-final AERE block (number + on-chain hash). Permissionless.
 *
 * @dev    WHY THIS EXISTS. The sibling `AereZkEthLightClient` is the INBOUND direction
 *         (AERE tracking Ethereum's sync-committee finality). This is its mirror: it makes
 *         AERE's QBFT finality independently, succinctly checkable by an external verifier,
 *         so a bridge OUT of AERE rests on a proof of the committed-seal quorum instead of a
 *         multisig attestation. The hard verification (RLP layout, dual Besu hash
 *         pre-images, secp256k1 committed-seal recovery, distinct-quorum counting) is proven
 *         off-chain in the zkVM and against REAL chain-2800 headers before proving; the
 *         on-chain step is one Groth16 verify plus the lineage/monotonicity binding below.
 *
 * @dev    SECURITY MODEL — read before quoting.
 *         * On chain here we verify the Groth16 proof + bind its public values (validator-set
 *           anchor, strict block-number advance, finalized flag). Off chain (in the zkVM) we
 *           prove the committed-seal quorum. Security reduces to the honesty of `>= 2f+1` of
 *           the trusted QBFT validator set — exactly what QBFT finality means, no more.
 *         * TRUST-MINIMIZED, NOT TRUSTLESS, NOT QUANTUM-SAFE. AERE mainnet QBFT commits on
 *           classical secp256k1 ECDSA, and the proof wrap is an SP1 Groth16 SNARK over BN254
 *           (classical). Both are quantum-vulnerable. `falconVerified` is committed `false` by
 *           the guest today and is recorded, never required — it becomes meaningful only once
 *           mainnet consensus VERIFIABLY blocks on a Falcon-512 quorum certificate.
 *         * With AERE's live N=7 validator set the quorum is 5 (2f+1, f=2). All 7 are
 *           Foundation-operated, so the honest-quorum premise is an OPERATOR assumption, not
 *           an economic one; the light client is only as decentralized as that set.
 *         * VALIDATOR-SET ANCHOR IS IMMUTABLE. `trustedValidatorSetRoot` is pinned at deploy
 *           (= `keccak256(addr_0 ‖ addr_1 ‖ …)` over the set in stored order, matching the
 *           guest's `validator_set_root`). The guest proves finality against a FIXED known
 *           set, so this contract binds to that set and does NOT track validator-set changes.
 *           A validator rotation therefore requires a fresh anchor (new deployment /
 *           checkpoint). Stated honestly; rotation is a documented follow-up, not a silent gap.
 *           THIS IS NOT THEORETICAL: the first deployment (0xc9A2DCae…B1c2) anchored the then-N=3
 *           set, the live set grew to N=7 at block 9,312,565, and that client was thereby frozen
 *           out of verifying current finality — it was replaced by 0xCaDA54FA…6488 on 2026-07-16
 *           (see zk-light-client/QBFT-LIGHTCLIENT-7SET-2026-07-16.md). Treat the anchor as an
 *           expiring credential: a consumer MUST check that trustedValidatorSetRoot still equals
 *           keccak256 over the live qbft_getValidatorsByBlockNumber set before trusting this
 *           client as a finality oracle.
 *         * WEAKLY-SUBJECTIVE BOOTSTRAP: the constructor pins the validator-set anchor and an
 *           initial finalized block number; from there the client self-advances by proof.
 *         * PROGRAM BINDING: `PROGRAM_VKEY` is the SP1 vkey of the exact guest ELF. Binding
 *           the vkey binds the exact verification program.
 *
 *         PUBLIC-VALUES CONVENTION (exactly 192 bytes = 6 x 32, matching the guest's
 *         `PublicValues` `abi.encode`):
 *           abi.encode(
 *             bytes32 blockHash,          // recomputed on-chain block hash
 *             uint64  blockNumber,        // the block's height
 *             bytes32 committedSealHash,  // digest the seals were recovered over
 *             bytes32 validatorSetRoot,   // commitment to the validator set used as anchor
 *             bool    finalized,          // guest asserts true; re-checked here
 *             bool    falconVerified      // reserved PQC slot; false on mainnet today
 *           )
 *
 *         IMMUTABILITY / TRUST: SP1_VERIFIER, PROGRAM_VKEY and trustedValidatorSetRoot are
 *         fixed at deploy. There is NO owner and NO admin: advancing is permissionless.
 *         Soundness rests on the SP1 Groth16 proof plus the on-chain anchor/monotonicity checks.
 */
contract AereZkQbftLightClient {
    /// @notice SP1 verifier gateway (routes to the Groth16 verifier by selector).
    address public immutable SP1_VERIFIER;

    /// @notice SP1 vkey binding the exact qbft-lightclient guest ELF.
    bytes32 public immutable PROGRAM_VKEY;

    /// @notice Commitment to the trusted AERE validator set: keccak256 over the
    ///         addresses in stored order. Every accepted proof must be about THIS set.
    bytes32 public immutable trustedValidatorSetRoot;

    /// @notice The highest QBFT-final AERE block number advanced so far.
    uint64 public finalizedNumber;

    /// @notice The on-chain block hash of the block at `finalizedNumber`.
    bytes32 public finalizedBlockHash;

    /// @notice The committed-seal digest recorded for the latest advance.
    bytes32 public lastCommittedSealHash;

    /// @notice Whether the latest accepted proof carried a verified Falcon certificate.
    ///         False on mainnet today (classical ECDSA); recorded for transparency.
    bool public lastFalconVerified;

    /// @notice Number of proofs accepted (finality advances).
    uint64 public updateCount;

    event Bootstrapped(bytes32 validatorSetRoot, uint64 finalizedNumber, bytes32 finalizedBlockHash);
    event FinalityAdvanced(
        uint64 indexed blockNumber,
        bytes32 blockHash,
        bytes32 committedSealHash,
        bool falconVerified,
        address prover
    );

    error InvalidProof();
    error BadPublicValuesLength(uint256 got);
    error ValidatorSetMismatch(bytes32 inProof, bytes32 trusted);
    error NotFinalized();
    error StaleFinality(uint64 inProof, uint64 current);
    error ZeroBlockHash();
    error ZeroAddress();
    error ZeroValue();
    error VerifierHasNoCode();

    constructor(
        address sp1Verifier,
        bytes32 programVKey,
        bytes32 validatorSetRoot,
        uint64 bootstrapFinalizedNumber,
        bytes32 bootstrapFinalizedBlockHash
    ) {
        if (sp1Verifier == address(0)) revert ZeroAddress();
        // A code-less verifier would make verifyProof (which returns no data) succeed silently,
        // accepting every "proof". Attacker cannot reach this (no control of constructor args), but
        // it removes a deploy-time footgun; the deployed value must be the live SP1 gateway.
        if (sp1Verifier.code.length == 0) revert VerifierHasNoCode();
        if (programVKey == bytes32(0) || validatorSetRoot == bytes32(0)) revert ZeroValue();
        SP1_VERIFIER = sp1Verifier;
        PROGRAM_VKEY = programVKey;
        trustedValidatorSetRoot = validatorSetRoot;
        finalizedNumber = bootstrapFinalizedNumber;
        finalizedBlockHash = bootstrapFinalizedBlockHash;
        emit Bootstrapped(validatorSetRoot, bootstrapFinalizedNumber, bootstrapFinalizedBlockHash);
    }

    /// @notice Verify a zk proof that one AERE block is QBFT-final and, on success,
    ///         advance the tracked finalized head. Permissionless.
    /// @param publicValues abi.encode(blockHash, blockNumber, committedSealHash,
    ///                     validatorSetRoot, finalized, falconVerified), 192 bytes.
    /// @param proof        SP1 Groth16 proof bytes.
    function processProof(bytes calldata publicValues, bytes calldata proof) external {
        if (publicValues.length != 192) revert BadPublicValuesLength(publicValues.length);

        // 1) zk proof: reverts if the proof is not valid for PROGRAM_VKEY.
        try ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof) {
            // verified — did not revert
        } catch {
            revert InvalidProof();
        }

        (
            bytes32 blockHash,
            uint64 blockNumber,
            bytes32 committedSealHash,
            bytes32 validatorSetRoot,
            bool finalized,
            bool falconVerified
        ) = abi.decode(publicValues, (bytes32, uint64, bytes32, bytes32, bool, bool));

        // 2) bind the proof to THIS client's trusted anchor + monotonic head.
        if (validatorSetRoot != trustedValidatorSetRoot) {
            revert ValidatorSetMismatch(validatorSetRoot, trustedValidatorSetRoot);
        }
        if (!finalized) revert NotFinalized();
        if (blockNumber <= finalizedNumber) revert StaleFinality(blockNumber, finalizedNumber);
        if (blockHash == bytes32(0)) revert ZeroBlockHash();

        // 3) advance the tracked finalized head.
        finalizedNumber = blockNumber;
        finalizedBlockHash = blockHash;
        lastCommittedSealHash = committedSealHash;
        lastFalconVerified = falconVerified;
        unchecked {
            updateCount += 1;
        }

        emit FinalityAdvanced(blockNumber, blockHash, committedSealHash, falconVerified, msg.sender);
    }

    /// @notice Stateless verification: reverts if the proof is invalid for PROGRAM_VKEY
    ///         or the public values fail the anchor/finalized binding; otherwise returns
    ///         the decoded fields. Records nothing (no monotonicity constraint).
    function verify(bytes calldata publicValues, bytes calldata proof)
        external
        view
        returns (
            bytes32 blockHash,
            uint64 blockNumber,
            bytes32 committedSealHash,
            bytes32 validatorSetRoot,
            bool finalized,
            bool falconVerified
        )
    {
        if (publicValues.length != 192) revert BadPublicValuesLength(publicValues.length);
        ISp1Verifier(SP1_VERIFIER).verifyProof(PROGRAM_VKEY, publicValues, proof);
        (blockHash, blockNumber, committedSealHash, validatorSetRoot, finalized, falconVerified) =
            abi.decode(publicValues, (bytes32, uint64, bytes32, bytes32, bool, bool));
        if (validatorSetRoot != trustedValidatorSetRoot) {
            revert ValidatorSetMismatch(validatorSetRoot, trustedValidatorSetRoot);
        }
        if (!finalized) revert NotFinalized();
    }
}
