// 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 ); } }