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

/// @notice Minimal surface of AerePQCMessageVerifier a consumer needs to gate an
///         action on a valid post-quantum signature by a registered signer.
interface IAerePQCMessageVerifier {
    function verifyBySigner(uint256 signerId, bytes32 domainTag, bytes calldata payload, bytes calldata signature)
        external
        view
        returns (bool);

    function messageDigest(bytes32 domainTag, bytes calldata payload) external view returns (bytes32);

    function signerMessageDigest(uint256 signerId, bytes32 domainTag, bytes calldata payload)
        external
        view
        returns (bytes32);
}

/**
 * @title  AerePQCMessageVerifier, on-chain verifier of post-quantum-signed messages for
 *         cross-chain bridging and oracle attestations (AERE chain 2800)
 *
 * @notice A permissionless, non-custodial primitive that lets a bridge relayer, oracle,
 *         or off-chain attestor PUBLISH a NIST post-quantum public key and then have its
 *         messages (cross-chain packets, price/NAV attestations, proofs) checked, in full,
 *         on-chain, against that key. Verification rests on AERE's LIVE native PQC
 *         precompiles, so this contract is deployable TODAY on mainnet 2800. No funds are
 *         ever held.
 *
 *         What it adds over a raw precompile call:
 *           1. a SIGNER REGISTRY with owner-gated ROTATION and REVOCATION, so a bridge/
 *              oracle keeps a stable `signerId` while it rolls its PQC key;
 *           2. a DOMAIN-SEPARATED message format binding chainId + this deployment + a
 *              domain tag + the payload, so a signature can never be replayed across
 *              chains, contracts, domains, or (for signer-bound checks) key epochs;
 *           3. `verifyMessage` for stateless verification of any NIST PQC signature over
 *              an arbitrary-length message, plus signer-bound helpers a consumer gates on.
 *
 *         HONEST SCOPE. Application-layer PQC signature verification. This does NOT change
 *         AERE consensus, which remains classical ECDSA QBFT (Foundation validators). What
 *         is new and live is the set of native precompiles this contract calls, activated
 *         on mainnet 2800 at block 9,189,161, each wrapping the audited Bouncy Castle NIST
 *         verifiers. This contract makes those precompiles USABLE for the bridging/oracle
 *         message-authentication pattern.
 *
 * @dev    LIVE PRECOMPILES USED (mainnet 2800, all activated at block 9,189,161):
 *           scheme 1 = Falcon-512     at 0x...0AE1
 *           scheme 2 = Falcon-1024    at 0x...0AE2
 *           scheme 3 = ML-DSA-44      at 0x...0AE3  (FIPS 204, internal interface)
 *           scheme 4 = SLH-DSA-128s   at 0x...0AE4  (FIPS 205 SPHINCS+, internal interface)
 *
 *         The precompiles ML-KEM-768 (0x...0AE6) and Falcon HashToPoint (0x...0AE7) are
 *         TESTNET-ONLY and NOT live on mainnet; this contract deliberately does not use
 *         them, so it stays deployable on mainnet today. (ML-KEM is a KEM, not a signature
 *         scheme, so it would not belong here regardless.)
 *
 * @dev    PRECOMPILE INPUT ENCODING (byte-identical to AerePQCAttestation, separately
 *         proven against the live mainnet precompiles via eth_call in scripts/pqc):
 *
 *         Falcon-512 / Falcon-1024 (0x0AE1 / 0x0AE2):
 *           input = pk || sm
 *           pk    = (0x00+logn) || packed_h            (897 bytes logn=9, 1793 bytes logn=10)
 *           sm    = sigLen(2, big-endian) || nonce(40) || message || esig
 *           esig  = (0x20+logn) || compressedSig,  sigLen == esig.length
 *
 *         ML-DSA-44 (0x0AE3):    input = pk(1312) || sig(2420) || message
 *         SLH-DSA-128s (0x0AE4): input = pk(32)   || sig(7856) || message
 *
 *         Output for every scheme: a 32-byte word, 0x..01 valid else 0x..00. An empty/zero
 *         return is treated as INVALID (this is the pre-fork behaviour of an address with
 *         no precompile, so a stale caller can never accept a signature).
 *
 * @dev    SIGNATURE ENVELOPE handed to every verify* function:
 *           Falcon (1,2):     signature = nonce(40) || esig            (esig starts 0x29 / 0x2A)
 *           ML-DSA-44 (3):    signature = sig, exactly 2420 bytes
 *           SLH-DSA-128s (4): signature = sig, exactly 7856 bytes
 *         Unlike AerePQCAttestation, `message` here is arbitrary-length: a bridge packet or
 *         oracle report can be signed directly, and the domain-separated helpers commit a
 *         32-byte digest.
 */
contract AerePQCMessageVerifier {
    // ----- scheme identifiers (match AerePQCAttestation / AerePQCKeyRegistry) -----
    uint8 public constant SCHEME_FALCON512 = 1;
    uint8 public constant SCHEME_FALCON1024 = 2;
    uint8 public constant SCHEME_MLDSA44 = 3;
    uint8 public constant SCHEME_SLHDSA128S = 4;

    // ----- live precompile addresses on chain 2800 -----
    address public constant PRECOMPILE_FALCON512 = address(0x0AE1);
    address public constant PRECOMPILE_FALCON1024 = address(0x0AE2);
    address public constant PRECOMPILE_MLDSA44 = address(0x0AE3);
    address public constant PRECOMPILE_SLHDSA128S = address(0x0AE4);

    // ----- public key sizes / headers -----
    uint256 internal constant FALCON512_PK_LEN = 897;
    uint256 internal constant FALCON1024_PK_LEN = 1793;
    uint256 internal constant MLDSA44_PK_LEN = 1312;
    uint256 internal constant SLHDSA128S_PK_LEN = 32;
    uint8 internal constant FALCON512_PK_HEADER = 0x09; // 0x00 | logn=9
    uint8 internal constant FALCON1024_PK_HEADER = 0x0A; // 0x00 | logn=10

    // ----- signature sizes (fixed-length schemes) -----
    uint256 internal constant MLDSA44_SIG_LEN = 2420;
    uint256 internal constant SLHDSA128S_SIG_LEN = 7856;
    uint256 internal constant FALCON_NONCE_LEN = 40;

    // ----- domain separator for the message digest -----
    /// @notice Root domain tag mixed into every digest, keeping AERE PQC message digests
    ///         disjoint from AerePQCAttestation / AerePQCKeyRegistry / the recovery module,
    ///         so a signature made for one primitive can never be replayed on another.
    bytes32 public constant MESSAGE_DOMAIN = keccak256("AerePQCMessageVerifier.v1.message");

    // ----- signer lifecycle -----
    // NONE=0 (never registered), ACTIVE=1, REVOKED=2 (terminal). A rotation keeps the same
    // signerId ACTIVE and advances its epoch; a revoked signer can never verify again.
    uint8 public constant STATUS_NONE = 0;
    uint8 public constant STATUS_ACTIVE = 1;
    uint8 public constant STATUS_REVOKED = 2;

    struct Signer {
        address owner; // the address that may rotate/revoke this signer
        uint8 scheme; // current scheme (one of SCHEME_*)
        uint8 status; // one of STATUS_*
        uint32 epoch; // advances on every rotation; bound into signer digests
        bytes pubKey; // current raw NIST public key for the scheme
    }

    Signer[] private _signers; // signerId is the index into this array

    // ----- events -----
    event SignerRegistered(uint256 indexed signerId, address indexed owner, uint8 indexed scheme, uint256 pubKeyLen);
    event SignerRotated(
        uint256 indexed signerId, address indexed owner, uint8 newScheme, uint32 newEpoch, uint256 pubKeyLen
    );
    event SignerRevoked(uint256 indexed signerId, address indexed owner);
    event SignerOwnerTransferred(uint256 indexed signerId, address indexed oldOwner, address indexed newOwner);

    error InvalidScheme(uint8 scheme);
    error InvalidPubKey();
    error UnknownSigner(uint256 signerId);
    error NotSignerOwner(uint256 signerId);
    error SignerNotActive(uint256 signerId);
    error ZeroAddress();

    // ==========================================================================
    //                              Signer registry
    // ==========================================================================

    /**
     * @notice Register a PQC signer public key and bind it to msg.sender. The registrant
     *         becomes the signer OWNER (the only party that can later rotate or revoke it).
     *         Multiple signers per address are allowed; each call returns a fresh signerId.
     *         The key length (and, for Falcon, the header byte) is validated against the
     *         scheme so a malformed key cannot be stored.
     * @param scheme one of 1=Falcon-512, 2=Falcon-1024, 3=ML-DSA-44, 4=SLH-DSA-128s.
     * @param pubKey the raw NIST public key for the scheme (see contract NatSpec).
     * @return signerId the identifier assigned to this signer.
     */
    function registerSigner(uint8 scheme, bytes calldata pubKey) external returns (uint256 signerId) {
        _validatePubKey(scheme, pubKey);

        signerId = _signers.length;
        _signers.push(Signer({owner: msg.sender, scheme: scheme, status: STATUS_ACTIVE, epoch: 0, pubKey: pubKey}));

        emit SignerRegistered(signerId, msg.sender, scheme, pubKey.length);
    }

    /**
     * @notice Rotate a signer's key in place. Only the signer owner may call, and only while
     *         the signer is ACTIVE. The signerId is preserved (consumers keep pointing at the
     *         same signer), the new key becomes current, and `epoch` advances by one. Because
     *         signer-bound digests include the epoch, any message signed for a prior epoch is
     *         invalid after rotation even if the same key material is re-used. The successor
     *         may use any of the four schemes.
     * @param signerId  the signer to rotate.
     * @param newScheme the scheme of the successor key.
     * @param newPubKey the raw NIST public key for the successor.
     */
    function rotateSigner(uint256 signerId, uint8 newScheme, bytes calldata newPubKey) external {
        Signer storage s = _requireSigner(signerId);
        if (s.owner != msg.sender) revert NotSignerOwner(signerId);
        if (s.status != STATUS_ACTIVE) revert SignerNotActive(signerId);
        _validatePubKey(newScheme, newPubKey);

        s.scheme = newScheme;
        s.pubKey = newPubKey;
        s.epoch += 1;

        emit SignerRotated(signerId, msg.sender, newScheme, s.epoch, newPubKey.length);
    }

    /**
     * @notice Permanently revoke a signer you own. Terminal: a revoked signer can never
     *         verify again (verifyBySigner returns false) and cannot be reactivated.
     */
    function revokeSigner(uint256 signerId) external {
        Signer storage s = _requireSigner(signerId);
        if (s.owner != msg.sender) revert NotSignerOwner(signerId);
        if (s.status != STATUS_ACTIVE) revert SignerNotActive(signerId);
        s.status = STATUS_REVOKED;
        emit SignerRevoked(signerId, msg.sender);
    }

    /**
     * @notice Hand a signer's control to a new owner (e.g. rotating operational custody of a
     *         bridge relayer). Only the current owner may call, only while ACTIVE.
     */
    function transferSignerOwner(uint256 signerId, address newOwner) external {
        if (newOwner == address(0)) revert ZeroAddress();
        Signer storage s = _requireSigner(signerId);
        if (s.owner != msg.sender) revert NotSignerOwner(signerId);
        if (s.status != STATUS_ACTIVE) revert SignerNotActive(signerId);
        address old = s.owner;
        s.owner = newOwner;
        emit SignerOwnerTransferred(signerId, old, newOwner);
    }

    function _validatePubKey(uint8 scheme, bytes calldata pubKey) internal pure {
        if (scheme == SCHEME_FALCON512) {
            if (pubKey.length != FALCON512_PK_LEN || uint8(pubKey[0]) != FALCON512_PK_HEADER) revert InvalidPubKey();
        } else if (scheme == SCHEME_FALCON1024) {
            if (pubKey.length != FALCON1024_PK_LEN || uint8(pubKey[0]) != FALCON1024_PK_HEADER) revert InvalidPubKey();
        } else if (scheme == SCHEME_MLDSA44) {
            if (pubKey.length != MLDSA44_PK_LEN) revert InvalidPubKey();
        } else if (scheme == SCHEME_SLHDSA128S) {
            if (pubKey.length != SLHDSA128S_PK_LEN) revert InvalidPubKey();
        } else {
            revert InvalidScheme(scheme);
        }
    }

    function _requireSigner(uint256 signerId) internal view returns (Signer storage s) {
        if (signerId >= _signers.length) revert UnknownSigner(signerId);
        s = _signers[signerId];
    }

    // ==========================================================================
    //                        Domain-separated message format
    // ==========================================================================

    /**
     * @notice The 32-byte digest a signature must commit to for a message under `domainTag`.
     *         Binds MESSAGE_DOMAIN + chainId + this deployment + the domain tag + the payload,
     *         so the SAME payload signed for one (chain, contract, domain) is a DIFFERENT
     *         digest on any other, and a signature can never be replayed across them.
     *         This form is NOT bound to a signer, for stateless multi-party checks.
     * @param domainTag an application-chosen tag separating message classes (e.g. a bridge
     *                  route id or oracle feed id).
     * @param payload   the message bytes being attested (arbitrary length).
     */
    function messageDigest(bytes32 domainTag, bytes calldata payload) public view returns (bytes32) {
        return keccak256(abi.encode(MESSAGE_DOMAIN, block.chainid, address(this), domainTag, payload));
    }

    /**
     * @notice The 32-byte digest for a message bound to a specific signer AND its current
     *         rotation epoch. Additionally binds `signerId` and `epoch`, so a signature is
     *         scoped to one signer and one key generation: after a rotation (epoch advances)
     *         or a re-registration under a new signerId, an old signature no longer verifies.
     *         Off-chain signers query this (or reconstruct it) to know exactly what to sign.
     */
    function signerMessageDigest(uint256 signerId, bytes32 domainTag, bytes calldata payload)
        public
        view
        returns (bytes32)
    {
        Signer storage s = _requireSigner(signerId);
        return keccak256(
            abi.encode(MESSAGE_DOMAIN, block.chainid, address(this), signerId, s.epoch, domainTag, payload)
        );
    }

    // ==========================================================================
    //                          Verification (views)
    // ==========================================================================

    /**
     * @notice Stateless PQC verification of `signature` by `pubKey` over an arbitrary-length
     *         `message`, via the live precompile for `scheme`. No registry or binding: the
     *         message is used exactly as given, so anyone can check any NIST PQC signature via
     *         eth_call for free (e.g. against the official KAT vectors). Fail-closed: returns
     *         false, never reverts, on a bad scheme/length or a rejecting precompile.
     * @return true iff the live precompile accepts the signature.
     */
    function verifyMessage(uint8 scheme, bytes calldata pubKey, bytes calldata message, bytes calldata signature)
        external
        view
        returns (bool)
    {
        return _verify(scheme, pubKey, message, signature);
    }

    /**
     * @notice Domain-separated verification against a caller-supplied public key: verifies
     *         `signature` (by `pubKey`, `scheme`) over messageDigest(domainTag, payload).
     *         Stateless (no signer registry). Fail-closed.
     */
    function verifyDomainMessage(
        uint8 scheme,
        bytes calldata pubKey,
        bytes32 domainTag,
        bytes calldata payload,
        bytes calldata signature
    ) external view returns (bool) {
        bytes32 digest = messageDigest(domainTag, payload);
        return _verify(scheme, pubKey, abi.encodePacked(digest), signature);
    }

    /**
     * @notice Signer-bound, domain-separated verification: verifies `signature` under a
     *         REGISTERED signer's CURRENT key and scheme, over signerMessageDigest(signerId,
     *         domainTag, payload). This is the check a consumer gates an action on. Fail-closed:
     *         returns false for an unknown or REVOKED signer, or a rejecting precompile. Because
     *         the digest binds the signer's epoch, a signature made before a rotation is rejected.
     */
    function verifyBySigner(uint256 signerId, bytes32 domainTag, bytes calldata payload, bytes calldata signature)
        external
        view
        returns (bool)
    {
        if (signerId >= _signers.length) return false;
        Signer storage s = _signers[signerId];
        if (s.status != STATUS_ACTIVE) return false;
        bytes32 digest =
            keccak256(abi.encode(MESSAGE_DOMAIN, block.chainid, address(this), signerId, s.epoch, domainTag, payload));
        return _verify(s.scheme, s.pubKey, abi.encodePacked(digest), signature);
    }

    // ==========================================================================
    //                            Internal verifier
    // ==========================================================================

    /**
     * @dev Build the exact precompile input for `scheme` with `message` as the signed message,
     *      staticcall the live precompile, and return true iff it answers 0x..01. Fail-closed:
     *      any length/scheme that cannot form a valid input, or an empty/short/zero return,
     *      yields false (never reverts), mirroring AerePQCKeyRegistry._verify.
     */
    function _verify(uint8 scheme, bytes memory pubKey, bytes memory message, bytes memory signature)
        internal
        view
        returns (bool)
    {
        (bool built, address precompile, bytes memory input) = _tryBuildInput(scheme, pubKey, message, signature);
        if (!built) return false;
        (bool ok, bytes memory ret) = precompile.staticcall(input);
        return ok && ret.length >= 32 && ret[31] == 0x01;
    }

    /// @dev Assemble (precompile, input) for a scheme (byte-identical layout to
    ///      AerePQCAttestation / AerePQCKeyRegistry). Returns built=false on any length/scheme
    ///      that cannot form a valid input, so verification fails closed rather than reverting.
    ///      `message` is arbitrary length (a bridge packet, oracle report, or 32-byte digest).
    function _tryBuildInput(uint8 scheme, bytes memory pubKey, bytes memory message, bytes memory signature)
        internal
        pure
        returns (bool built, address precompile, bytes memory input)
    {
        if (scheme == SCHEME_FALCON512 || scheme == SCHEME_FALCON1024) {
            // signature = nonce(40) || esig
            if (signature.length <= FALCON_NONCE_LEN) return (false, address(0), "");
            uint256 sigLen = signature.length - FALCON_NONCE_LEN; // esig length
            if (sigLen > type(uint16).max) return (false, address(0), "");

            bytes memory nonce = _slice(signature, 0, FALCON_NONCE_LEN);
            bytes memory esig = _slice(signature, FALCON_NONCE_LEN, sigLen);

            // sm = sigLen(2, big-endian) || nonce(40) || message || esig
            bytes memory sm = abi.encodePacked(uint16(sigLen), nonce, message, esig);
            input = abi.encodePacked(pubKey, sm);
            precompile = scheme == SCHEME_FALCON512 ? PRECOMPILE_FALCON512 : PRECOMPILE_FALCON1024;
        } else if (scheme == SCHEME_MLDSA44) {
            if (signature.length != MLDSA44_SIG_LEN) return (false, address(0), "");
            input = abi.encodePacked(pubKey, signature, message); // pk(1312) || sig(2420) || message
            precompile = PRECOMPILE_MLDSA44;
        } else if (scheme == SCHEME_SLHDSA128S) {
            if (signature.length != SLHDSA128S_SIG_LEN) return (false, address(0), "");
            input = abi.encodePacked(pubKey, signature, message); // pk(32) || sig(7856) || message
            precompile = PRECOMPILE_SLHDSA128S;
        } else {
            return (false, address(0), "");
        }
        built = true;
    }

    function _slice(bytes memory data, uint256 start, uint256 len) internal pure returns (bytes memory out) {
        out = new bytes(len);
        for (uint256 i = 0; i < len; i++) {
            out[i] = data[start + i];
        }
    }

    // ==========================================================================
    //                                  Views
    // ==========================================================================

    /// @notice Number of registered signers (signerIds run 0..signerCount-1).
    function signerCount() external view returns (uint256) {
        return _signers.length;
    }

    /// @notice Read a registered signer. Reverts on an unknown signerId.
    function getSigner(uint256 signerId)
        external
        view
        returns (address owner, uint8 scheme, uint8 status, uint32 epoch, bytes memory pubKey)
    {
        Signer storage s = _requireSigner(signerId);
        return (s.owner, s.scheme, s.status, s.epoch, s.pubKey);
    }

    /// @notice Lifecycle status of a signer (STATUS_NONE for a never-registered id).
    function statusOf(uint256 signerId) external view returns (uint8) {
        if (signerId >= _signers.length) return STATUS_NONE;
        return _signers[signerId].status;
    }

    /// @notice True iff the signer exists and is ACTIVE (not revoked).
    function isActiveSigner(uint256 signerId) external view returns (bool) {
        if (signerId >= _signers.length) return false;
        return _signers[signerId].status == STATUS_ACTIVE;
    }

    /// @notice The current rotation epoch of a signer (0 at registration).
    function epochOf(uint256 signerId) external view returns (uint32) {
        return _requireSigner(signerId).epoch;
    }

    /// @notice The live precompile address for a scheme (reverts on an unknown scheme).
    function precompileFor(uint8 scheme) external pure returns (address) {
        if (scheme == SCHEME_FALCON512) return PRECOMPILE_FALCON512;
        if (scheme == SCHEME_FALCON1024) return PRECOMPILE_FALCON1024;
        if (scheme == SCHEME_MLDSA44) return PRECOMPILE_MLDSA44;
        if (scheme == SCHEME_SLHDSA128S) return PRECOMPILE_SLHDSA128S;
        revert InvalidScheme(scheme);
    }
}

/**
 * @title  AerePQCGatedAction, minimal example consumer that gates an action on a valid PQC
 *         signature by a registered signer
 *
 * @notice Reference for how a bridge receiver or oracle sink authenticates messages with
 *         AerePQCMessageVerifier. An action executes iff a registered signer produced a valid
 *         post-quantum signature over the (domain-separated) payload, and each logical action
 *         executes at most once. The payload is opaque here (a bridge packet, an oracle report,
 *         a proof commitment); a real consumer would decode and act on it.
 *
 * @dev    Replay protection: a logical action is identified by (signerId, payload) under this
 *         consumer's ACTION_DOMAIN, and marked consumed on first success. Combined with the
 *         verifier's chain/contract/domain/epoch binding, a signature accepted here cannot be
 *         replayed on another chain, another consumer, another domain, or a rotated key, and the
 *         same action cannot be executed twice. Authentication is the PQC signature alone, so any
 *         relayer may submit it (msg.sender is not trusted), which is the point: the authority is
 *         post-quantum, not the secp256k1 tx sender.
 */
contract AerePQCGatedAction {
    IAerePQCMessageVerifier public immutable verifier;

    /// @notice Domain tag for this consumer's messages (disjoint from other consumers/domains).
    bytes32 public constant ACTION_DOMAIN = keccak256("AerePQCGatedAction.v1.action");

    /// @notice actionId => already executed.
    mapping(bytes32 => bool) public consumed;
    /// @notice Total actions executed.
    uint256 public actionCount;

    event ActionExecuted(uint256 indexed signerId, bytes32 indexed actionId, bytes payload);

    error BadVerifier();
    error NotAttested();
    error AlreadyConsumed(bytes32 actionId);

    constructor(IAerePQCMessageVerifier verifier_) {
        if (address(verifier_) == address(0)) revert BadVerifier();
        verifier = verifier_;
    }

    /// @notice The identifier under which (signerId, payload) is single-use in this consumer.
    function actionId(uint256 signerId, bytes calldata payload) public pure returns (bytes32) {
        return keccak256(abi.encode(signerId, payload));
    }

    /**
     * @notice Execute the gated action iff `signature` is a valid PQC signature by `signerId`'s
     *         current key over this consumer's domain-separated `payload`. Permissionless (any
     *         relayer may submit). Reverts (nothing recorded) if the signature does not verify or
     *         the action was already consumed.
     */
    function executeIfAttested(uint256 signerId, bytes calldata payload, bytes calldata signature) external {
        bytes32 id = keccak256(abi.encode(signerId, payload));
        if (consumed[id]) revert AlreadyConsumed(id);
        if (!verifier.verifyBySigner(signerId, ACTION_DOMAIN, payload, signature)) revert NotAttested();

        consumed[id] = true;
        actionCount += 1;
        emit ActionExecuted(signerId, id, payload);
    }
}
