// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {ISP1Verifier, ISP1VerifierWithHash} from "./ISP1Verifier.sol";

/// @title AerePQStarkVerifier - ISP1Verifier-compatible adapter for the native POST-QUANTUM
///        STARK-verification precompile at 0x0AE8 (direct SP1/Plonky3 inner FRI/STARK verify, no
///        BN254 Groth16 wrap).
///
/// @notice This adapter lets every existing SP1 consumer on AERE (AereEVMValidityV2, the compliance
///         privacy pool, AereProofRegistry, AereOutboundVerifierV2, the zkML verifier, the zk light
///         clients, ...) switch from the Shor-breakable BN254 Groth16 gateway
///         (0x9ca479C8c52C0EbB4599319a36a5a017BCC70628, route 0x4388a21c) to the hash-based PQ
///         precompile by a ONE-LINE address swap: point the consumer's `SP1_VERIFIER` immutable at
///         an instance of this contract instead of at the gateway. The `verifyProof` ABI is
///         byte-identical to Succinct's ISP1Verifier (reverts on an invalid proof, returns nothing
///         on success), so no consumer code changes - only the constructor argument.
///
/// @dev    WIRE. It re-frames the SP1 call `(programVKey, publicValues, proofBytes)` into the
///         precompile's self-describing v1 envelope (see
///         docs/PQ-STARK-VERIFIER-PRECOMPILE-2026-07-18.md section 3):
///
///           magic("AS1\0") || version(1) || configId(1) || reserved(2)
///             || vkeyDigest(32) || publicValuesLen(4 BE) || publicValues
///             || proofLen(4 BE) || proofBytes
///
///         `vkeyDigest` here is passed through as the 32-byte `programVKey` (the precompile treats
///         it as the Poseidon2 digest of the recursion vk / program vk; the exact digest domain is
///         pinned in the spec). `configId` selects the frozen FRI config (1 = SP1 inner/shrink,
///         2 = SP1 wrap). The precompile returns the 32-byte word `0x..01` iff the inner STARK
///         verifies, and EMPTY otherwise; this adapter reverts on anything but that success word.
///
/// @dev    ============================ HONEST SCOPE - READ THIS ============================
///         The precompile at 0x0AE8 is a REFERENCE SKELETON as of 2026-07-18 (NOT AUDITED, NOT a
///         working verifier, and NOT activated on any AERE network - mainnet chain 2800 has exactly
///         the FIVE PQC precompiles 0x0AE1..0x0AE5). Its BabyBear field layer, wire parser, and
///         verifier structure are real and testable, but the Poseidon2 permutation, FRI folding
///         arithmetic, and SP1 recursion-AIR constraints are DELEGATED and not yet ported, so the
///         precompile FAIL-CLOSES: it returns EMPTY for every input and this adapter therefore
///         REVERTS on every proof. That is the safe direction: a consumer swapped to this adapter
///         before the core is completed + externally audited + founder-activated simply DENIES all
///         proofs; it can never accept a forged one. Do NOT deploy a real consumer against this
///         adapter until 0x0AE8 is activated per
///         docs/PQ-STARK-VERIFIER-ACTIVATION-2026-07-18.md. Nothing here changes AERE consensus,
///         which remains classical secp256k1 ECDSA QBFT.
contract AerePQStarkVerifier is ISP1VerifierWithHash {
    /// @notice The native PQ STARK-verify precompile.
    address internal constant PQ_STARK_PRECOMPILE = address(0x0AE8);

    /// @notice v1 envelope constants.
    bytes4  internal constant MAGIC        = 0x41533100; // "AS1\0"
    uint8   internal constant WIRE_VERSION = 1;

    /// @notice Which frozen FRI config the proofs use. 1 = SP1 inner/shrink, 2 = SP1 wrap.
    ///         Immutable so an instance is bound to exactly one proof family.
    uint8 public immutable CONFIG_ID;

    error WrongVerifierSelector(bytes4 received, bytes4 expected);
    error PQProofInvalid();

    /// @param configId 1 for the SP1 inner/shrink STARK, 2 for the SP1 wrap STARK.
    constructor(uint8 configId) {
        require(configId == 1 || configId == 2, "AerePQStarkVerifier: bad configId");
        CONFIG_ID = configId;
    }

    /// @inheritdoc ISP1VerifierWithHash
    /// @dev A domain-separated tag so a consumer can tell a PQ verifier apart from the BN254 one.
    ///      This is NOT the SP1 Groth16 VERIFIER_HASH; PQ proofs carry no BN254 selector.
    function VERIFIER_HASH() external pure returns (bytes32) {
        return keccak256("AerePQStarkVerifier.v1");
    }

    /// @notice The circuit/proof family this instance verifies.
    function VERSION() external pure returns (string memory) {
        return "pq-stark-v1(reference-skeleton)";
    }

    /// @notice Verify an SP1 inner FRI/STARK proof directly against the PQ precompile.
    /// @dev    Reverts (PQProofInvalid) unless the precompile returns the 32-byte success word.
    ///         Signature and semantics match Succinct's ISP1Verifier exactly, so this is a drop-in.
    /// @param programVKey  32-byte program/recursion vkey digest (Poseidon2 digest domain per spec).
    /// @param publicValues The public values the SP1 program committed, ABI-encoded as usual.
    /// @param proofBytes   The serialized Plonky3 shard (FRI/STARK) proof for this config.
    function verifyProof(
        bytes32 programVKey,
        bytes calldata publicValues,
        bytes calldata proofBytes
    ) external view {
        bytes memory envelope = abi.encodePacked(
            MAGIC,
            WIRE_VERSION,
            CONFIG_ID,
            bytes2(0),                          // reserved
            programVKey,                        // vkeyDigest(32)
            uint32(publicValues.length),        // publicValuesLen (BE)
            publicValues,
            uint32(proofBytes.length),          // proofLen (BE)
            proofBytes
        );

        (bool ok, bytes memory ret) = PQ_STARK_PRECOMPILE.staticcall(envelope);

        // Success == precompile returned exactly the 32-byte word 0x..01. EMPTY (unactivated
        // precompile, malformed input, or the skeleton's fail-closed path) -> revert. Fail-closed.
        if (!ok || ret.length != 32 || bytes32(ret) != bytes32(uint256(1))) {
            revert PQProofInvalid();
        }
    }

    /// @notice Non-reverting variant for callers that already staticcall-wrap (e.g. the compliance
    ///         pool adapter and AereProofRegistry.verifySP1Only). Returns false instead of reverting.
    function isValidProof(
        bytes32 programVKey,
        bytes calldata publicValues,
        bytes calldata proofBytes
    ) external view returns (bool) {
        bytes memory envelope = abi.encodePacked(
            MAGIC, WIRE_VERSION, CONFIG_ID, bytes2(0),
            programVKey,
            uint32(publicValues.length), publicValues,
            uint32(proofBytes.length), proofBytes
        );
        (bool ok, bytes memory ret) = PQ_STARK_PRECOMPILE.staticcall(envelope);
        return ok && ret.length == 32 && bytes32(ret) == bytes32(uint256(1));
    }
}
