Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
377 lines
17 KiB
Solidity
377 lines
17 KiB
Solidity
// 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
|
|
);
|
|
}
|
|
}
|