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

/**
 * @title AereRecoveryRegistry, on-chain attested consensus-recovery evidence for Aere Network (chain 2800)
 *
 * @notice An append-only, fail-closed registry that records CONSENSUS RECOVERY events on
 *         Aere Network and binds each one to a real Falcon-512 signature verified in full,
 *         on-chain, by the LIVE native precompile at 0x0AE1. A recovery event is the triple
 *         (halt height, resume height, fault kind) that a fault caused: a validator-set drop
 *         below quorum, the resulting halt, and the subsequent in-place resume. An
 *         independent watchtower (an off-chain observer that holds a registered Falcon-512
 *         key) detects the halt, waits for quorum restoration, then submits the signed record
 *         here. No funds are ever held by this contract.
 *
 *         WHY THIS EXISTS. Aere's fault-tolerance is characterized off-chain (see
 *         docs/FAULT-TOLERANCE-CHARACTERIZATION-2026-07-14.md: 0 forks over 100 adversarial
 *         cycles, 100/100 self-resync at the f=2 boundary). This contract turns that evidence
 *         into a tamper-evident, publicly auditable on-chain trail: each recorded recovery is
 *         authenticated by a post-quantum signature, cannot be silently rewritten, and cannot
 *         be forged by any operator or admin. It is the on-chain half of the fault-injection
 *         evidence suite that de-risks the (separately founder-gated and audit-gated)
 *         post-quantum consensus flip.
 *
 *         HONEST SCOPE. This contract records ATTESTATIONS about recovery events; it does not
 *         itself observe consensus, halt the chain, or restore quorum, and it does not
 *         activate post-quantum consensus. Blocks are still produced and signed by Besu QBFT
 *         validators with ECDSA (secp256k1). The novelty is that the authenticity of a
 *         recovery record rests on a post-quantum signature (Falcon-512, NIST FIPS 206 draft
 *         line) verified by the live precompile activated on mainnet 2800 at block 9,189,161.
 *         The consensus flip stays founder-gated and audit-gated; this only makes its
 *         recovery evidence honest, reproducible, and quantum-durable.
 *
 * @dev    FALCON-512 PRECOMPILE (0x0AE1) INPUT ENCODING (exactly what the live precompile parses):
 *           input = pk || sm
 *           pk    = (0x00+logn) || packed_h                 (897 bytes, logn=9, header byte 0x09)
 *           sm    = sigLen(2, big-endian) || nonce(40) || message(32) || esig
 *           esig  = 0x29 || compressedSig,  sigLen == esig.length
 *         Output: a 32-byte word, 0x..01 valid else 0x..00. An empty / zero / short return is
 *         treated as INVALID (this is the pre-fork behaviour of an address with no precompile,
 *         so a stale caller on an un-activated chain can never record). This is FAIL-CLOSED.
 *
 * @dev    SIGNATURE ENVELOPE handed to submitRecovery():
 *           signature = nonce(40) || esig   (esig starts 0x29, so length is 40 + 1 + sigBytes)
 *         The contract supplies the 32-byte message (the record hash) itself, so the signer can
 *         never sign a different record than the one this contract binds and records.
 *
 * @dev    POST-QUANTUM AUTHORSHIP. Authorship of a recovery record is the Falcon-512 signature
 *         alone. submitRecovery() may be relayed by any msg.sender: only the holder of the
 *         registered operator private key can produce a valid signature over this contract's
 *         per-operator, per-nonce record hash, so a relayer only pays gas and can never forge
 *         or replay. Binding authorship to the ECDSA tx sender would defeat the purpose, since
 *         secp256k1 is exactly what a quantum computer breaks.
 *
 * @dev    APPEND-ONLY. Records live in a push-only array; recordId is the array index and is
 *         immutable once written. There is NO function that overwrites, deletes, or edits a
 *         record, and there is NO owner, admin, or privileged role anywhere in this contract.
 *         A strictly increasing per-operator nonce is consumed on every write, so no
 *         (record, signature) pair can be replayed to rewrite history.
 */
contract AereRecoveryRegistry {
    // ----- Falcon-512 (the only scheme this registry attests under) -----
    /// @notice Live Falcon-512 verifier precompile on chain 2800.
    address public constant PRECOMPILE_FALCON512 = address(0x0AE1);
    uint256 internal constant FALCON512_PK_LEN = 897;
    uint8 internal constant FALCON512_PK_HEADER = 0x09; // 0x00 | logn=9
    uint8 internal constant FALCON512_ESIG_HEADER = 0x29; // 0x20 | logn=9
    uint256 internal constant FALCON_NONCE_LEN = 40;

    // ----- fault kinds -----
    uint8 public constant FAULT_CRASH = 1; // one or more validators crash / go offline
    uint8 public constant FAULT_NETSPLIT = 2; // network partition, minority side halts
    uint8 public constant FAULT_BYZANTINE_PROPOSER = 3; // a proposer equivocates / signs invalidly

    // ----- domain separator for the record hash the operator signs -----
    bytes32 public constant RECORD_DOMAIN = keccak256("AereRecoveryRegistry.v1.record");

    // ----- registered operator keys -----
    struct Operator {
        address registrant; // address that registered the key (informational; not an authority)
        uint64 nonce; // strictly increasing per-operator replay counter
        bytes pubKey; // the raw Falcon-512 public key (897 bytes)
    }

    Operator[] private _operators; // operatorId is the index into this array

    // ----- recorded recovery events (append-only) -----
    struct RecoveryRecord {
        uint64 haltHeight; // block height at which progress stopped
        uint64 resumeHeight; // block height at which progress resumed (>= haltHeight)
        uint8 faultKind; // 1=CRASH, 2=NETSPLIT, 3=BYZANTINE_PROPOSER
        uint16 nValidators; // validator-set size at the event (e.g. 7)
        uint16 quorum; // commit quorum at the event (e.g. 5)
        uint16 aliveDuringFault; // validators still alive during the fault window
        uint256 operatorId; // the attesting operator key
        uint64 nonce; // the per-operator nonce this record consumed
        uint64 recordedAt; // block.number at which the record was written
        bytes32 recordHash; // the exact 32-byte message the Falcon-512 signature verified over
    }

    RecoveryRecord[] private _records; // recordId is the index; push-only, never rewritten

    // ----- events -----
    event OperatorRegistered(uint256 indexed operatorId, address indexed registrant, uint256 pubKeyLen);
    event RecoveryAttested(
        uint256 indexed recordId,
        uint256 indexed operatorId,
        uint8 indexed faultKind,
        uint64 haltHeight,
        uint64 resumeHeight,
        uint16 nValidators,
        uint16 quorum,
        uint16 aliveDuringFault,
        uint64 nonce,
        bytes32 recordHash
    );

    // ----- errors -----
    error InvalidPubKey();
    error UnknownOperator(uint256 operatorId);
    error InvalidFaultKind(uint8 faultKind);
    error InvalidHeights(uint64 haltHeight, uint64 resumeHeight);
    error InvalidValidatorSet(uint16 nValidators, uint16 quorum, uint16 aliveDuringFault);
    error InvalidSignatureLength();
    error PQCVerificationFailed();

    // ==========================================================================
    //                          Operator registration
    // ==========================================================================

    /**
     * @notice Register a Falcon-512 public key as a recovery-attesting operator (e.g. a
     *         watchtower). Permissionless: anyone may register a key. Authorship of every
     *         record submitted under this operatorId is the Falcon-512 signature alone, so a
     *         registrant is only a label, never an authority, and registering a key grants no
     *         power to forge a record. Multiple keys per address are allowed.
     * @param pubKey the raw Falcon-512 public key (897 bytes, header byte 0x09).
     * @return operatorId the identifier assigned to this key.
     */
    function registerOperator(bytes calldata pubKey) external returns (uint256 operatorId) {
        if (pubKey.length != FALCON512_PK_LEN || uint8(pubKey[0]) != FALCON512_PK_HEADER) revert InvalidPubKey();

        operatorId = _operators.length;
        _operators.push(Operator({registrant: msg.sender, nonce: 0, pubKey: pubKey}));

        emit OperatorRegistered(operatorId, msg.sender, pubKey.length);
    }

    // ==========================================================================
    //                          Recovery attestation
    // ==========================================================================

    /**
     * @notice Record a consensus-recovery event, authenticated on-chain by the live Falcon-512
     *         precompile (0x0AE1). Verifies that `signature` is a valid Falcon-512 signature by
     *         the registered operator key over this contract's record hash:
     *
     *           recordHash = keccak256(abi.encode(
     *               RECORD_DOMAIN, block.chainid, address(this), operatorId, nonce,
     *               haltHeight, resumeHeight, faultKind, nValidators, quorum, aliveDuringFault))
     *
     *         where `nonce` is the operator's current per-operator nonce. On success the record
     *         is appended, the nonce is incremented, and the event is emitted. Reverts (records
     *         nothing, advances no nonce) on: an unknown operator, an invalid fault kind,
     *         inconsistent heights, an inconsistent validator set, or an invalid signature
     *         (FAIL-CLOSED). No admin path can bypass the signature check.
     *
     * @param operatorId       the registered operator attesting this record.
     * @param haltHeight        block height at which progress stopped (>= 1).
     * @param resumeHeight      block height at which progress resumed (>= haltHeight, <= current block).
     * @param faultKind         1=CRASH, 2=NETSPLIT, 3=BYZANTINE_PROPOSER.
     * @param nValidators       validator-set size at the event (e.g. 7).
     * @param quorum            commit quorum at the event (e.g. 5), 1 <= quorum <= nValidators.
     * @param aliveDuringFault  validators still alive during the fault window (<= nValidators).
     * @param signature         the Falcon-512 signature envelope, nonce(40) || esig.
     * @return recordId         the append-only id assigned to this record.
     */
    function submitRecovery(
        uint256 operatorId,
        uint64 haltHeight,
        uint64 resumeHeight,
        uint8 faultKind,
        uint16 nValidators,
        uint16 quorum,
        uint16 aliveDuringFault,
        bytes calldata signature
    ) external returns (uint256 recordId) {
        if (operatorId >= _operators.length) revert UnknownOperator(operatorId);
        if (faultKind != FAULT_CRASH && faultKind != FAULT_NETSPLIT && faultKind != FAULT_BYZANTINE_PROPOSER) {
            revert InvalidFaultKind(faultKind);
        }
        // Heights must describe a real, already-happened halt-then-resume window.
        if (haltHeight == 0 || resumeHeight < haltHeight || resumeHeight > uint64(block.number)) {
            revert InvalidHeights(haltHeight, resumeHeight);
        }
        // Validator-set context must be internally consistent.
        if (
            nValidators == 0 || quorum == 0 || quorum > nValidators || aliveDuringFault > nValidators
        ) {
            revert InvalidValidatorSet(nValidators, quorum, aliveDuringFault);
        }

        Operator storage op = _operators[operatorId];
        uint64 nonce = op.nonce;

        bytes32 recordHash = recordChallenge(
            operatorId, nonce, haltHeight, resumeHeight, faultKind, nValidators, quorum, aliveDuringFault
        );

        if (!_verifyFalcon(op.pubKey, recordHash, signature)) revert PQCVerificationFailed();

        // Effects: consume the nonce and append the record (append-only, never rewritten).
        op.nonce = nonce + 1;

        recordId = _records.length;
        _records.push(
            RecoveryRecord({
                haltHeight: haltHeight,
                resumeHeight: resumeHeight,
                faultKind: faultKind,
                nValidators: nValidators,
                quorum: quorum,
                aliveDuringFault: aliveDuringFault,
                operatorId: operatorId,
                nonce: nonce,
                recordedAt: uint64(block.number),
                recordHash: recordHash
            })
        );

        emit RecoveryAttested(
            recordId,
            operatorId,
            faultKind,
            haltHeight,
            resumeHeight,
            nValidators,
            quorum,
            aliveDuringFault,
            nonce,
            recordHash
        );
    }

    /**
     * @notice The exact 32-byte record hash that a signature for `operatorId` must sign at a
     *         given nonce. Off-chain watchtowers query this to know what to sign.
     */
    function recordChallenge(
        uint256 operatorId,
        uint64 nonce,
        uint64 haltHeight,
        uint64 resumeHeight,
        uint8 faultKind,
        uint16 nValidators,
        uint16 quorum,
        uint16 aliveDuringFault
    ) public view returns (bytes32) {
        return keccak256(
            abi.encode(
                RECORD_DOMAIN,
                block.chainid,
                address(this),
                operatorId,
                nonce,
                haltHeight,
                resumeHeight,
                faultKind,
                nValidators,
                quorum,
                aliveDuringFault
            )
        );
    }

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

    /**
     * @dev Build the exact Falcon-512 precompile input with `message` as the signed 32-byte
     *      message, staticcall the live precompile at 0x0AE1, and return true iff it answers
     *      0x..01. Any empty / short / zero return is invalid (FAIL-CLOSED).
     */
    function _verifyFalcon(bytes memory pubKey, bytes32 message, bytes memory signature)
        internal
        view
        returns (bool)
    {
        // signature = nonce(40) || esig
        if (signature.length <= FALCON_NONCE_LEN) revert InvalidSignatureLength();
        uint256 sigLen = signature.length - FALCON_NONCE_LEN; // esig length
        if (sigLen < 2 || sigLen > type(uint16).max) revert InvalidSignatureLength();

        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(32) || esig
        bytes memory sm = abi.encodePacked(uint16(sigLen), nonce, message, esig);
        bytes memory input = abi.encodePacked(pubKey, sm);

        (bool ok, bytes memory ret) = PRECOMPILE_FALCON512.staticcall(input);
        return ok && ret.length >= 32 && ret[31] == 0x01;
    }

    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 operators (operatorIds run 0..operatorCount-1).
    function operatorCount() external view returns (uint256) {
        return _operators.length;
    }

    /// @notice Read a registered operator.
    function getOperator(uint256 operatorId)
        external
        view
        returns (address registrant, uint64 nonce, bytes memory pubKey)
    {
        if (operatorId >= _operators.length) revert UnknownOperator(operatorId);
        Operator storage op = _operators[operatorId];
        return (op.registrant, op.nonce, op.pubKey);
    }

    /// @notice The current (next unused) nonce for an operator.
    function nonceOf(uint256 operatorId) external view returns (uint64) {
        if (operatorId >= _operators.length) revert UnknownOperator(operatorId);
        return _operators[operatorId].nonce;
    }

    /// @notice Total number of recovery records (recordIds run 0..recordCount-1).
    function recordCount() external view returns (uint256) {
        return _records.length;
    }

    /// @notice Read a recorded recovery event by its append-only id.
    function getRecord(uint256 recordId)
        external
        view
        returns (
            uint64 haltHeight,
            uint64 resumeHeight,
            uint8 faultKind,
            uint16 nValidators,
            uint16 quorum,
            uint16 aliveDuringFault,
            uint256 operatorId,
            uint64 nonce,
            uint64 recordedAt,
            bytes32 recordHash
        )
    {
        if (recordId >= _records.length) revert UnknownOperator(recordId);
        RecoveryRecord storage r = _records[recordId];
        return (
            r.haltHeight,
            r.resumeHeight,
            r.faultKind,
            r.nValidators,
            r.quorum,
            r.aliveDuringFault,
            r.operatorId,
            r.nonce,
            r.recordedAt,
            r.recordHash
        );
    }
}
