aere-contracts/contracts/pqc/AereConsensusPQCAttestor.sol
Aere Network a13a649b77
Some checks are pending
contracts-ci / Install (lockfile) → compile → full test suite (push) Waiting to run
contracts-ci / PQC known-answer tests (NIST vectors) (push) Waiting to run
contracts-ci / Coverage (scoped, with artifacts) (push) Waiting to run
Initial public release
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.
2026-07-20 01:02:37 +03:00

223 lines
11 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title AereConsensusPQCAttestor, post-quantum FINALITY-ATTESTATION layer for AERE (chain 2800)
*
* @notice HONEST SCOPE. This contract does NOT replace Besu QBFT block signing.
* AERE blocks are still produced and signed by Besu validators with
* ECDSA (secp256k1), exactly as today. Replacing that internal signature
* would require forking the Besu Java consensus engine, which is out of
* scope. Instead, this contract adds a post-quantum FINALITY ATTESTATION
* layer that runs ALONGSIDE QBFT: each registered validator additionally
* signs the hash of a finalized block with Falcon-512 (a NIST PQC
* lattice signature), and submits that signature here. The signature is
* verified in full, on-chain, by the real AereFalcon512Verifier. When at
* least two thirds of the registered validators have submitted a valid
* Falcon-512 signature over the same (blockNumber, blockHash), the block
* is marked POST-QUANTUM FINALIZED and an event is emitted.
*
* This is NOT "post-quantum consensus". It is "post-quantum finality
* attestation": an independent, quantum-resistant record that a
* supermajority of validators vouched for a given block, whose validity
* does not depend on the hardness of the discrete-log problem.
*
* Decentralization tracks the validator set. With the three Foundation
* validators live today, this attestation is exactly as centralized as
* AERE consensus is today; it decentralizes as the validator set grows
* (path to 7, then 21). There is no admin backdoor on the finality
* record: the owner (Foundation) can only register validator public
* keys, and can never mark a block finalized or forge an attestation.
* The only way a block becomes post-quantum finalized is a real
* two-thirds set of on-chain-verified Falcon-512 signatures.
*/
interface IFalcon512Verifier {
/// @notice Full on-chain Falcon-512 verification (SHAKE256 HashToPoint,
/// 14-bit pk decode, comp_decode, negacyclic NTT multiply mod
/// q=12289, centering, l2-norm bound). Returns true iff valid.
function verify(
bytes memory pk,
bytes memory message,
bytes memory nonce,
bytes memory compSig
) external view returns (bool);
}
contract AereConsensusPQCAttestor {
// ----- Falcon-512 encoding constants -----
uint256 public constant NONCE_LEN = 40; // Falcon salt length
uint256 public constant PUBKEY_LEN = 897; // NIST Falcon-512 public key length
uint8 public constant PK_HEADER = 0x09; // 0x00 + logn, logn = 9
// ----- wiring -----
IFalcon512Verifier public immutable verifier;
address public owner;
// ----- validator registry -----
uint256 public totalValidators;
mapping(address => bool) public isValidator;
mapping(address => bytes) public falconPubKey; // validator -> 897-byte Falcon-512 pk
// ----- attestation records (keyed by keccak256(blockNumber, blockHash)) -----
mapping(bytes32 => uint256) public attestCount;
mapping(bytes32 => bool) public postQuantumFinalized;
mapping(bytes32 => uint256) public finalizedAtBlock; // chain block number the quorum was reached at
mapping(bytes32 => mapping(address => bool)) public hasAttested;
// ----- events -----
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
event ValidatorRegistered(address indexed validator, uint256 pubKeyLen, uint256 totalValidators);
event ValidatorKeyRotated(address indexed validator, uint256 pubKeyLen);
event BlockAttested(
address indexed validator,
address relayer,
uint256 indexed blockNumber,
bytes32 indexed blockHash,
uint256 attestCount,
uint256 totalValidators
);
event PostQuantumFinalized(
uint256 indexed blockNumber,
bytes32 indexed blockHash,
uint256 attestCount,
uint256 totalValidators
);
modifier onlyOwner() {
require(msg.sender == owner, "not owner");
_;
}
constructor(address verifierAddr) {
require(verifierAddr != address(0), "verifier=0");
verifier = IFalcon512Verifier(verifierAddr);
owner = msg.sender;
emit OwnershipTransferred(address(0), msg.sender);
}
// ==========================================================================
// Foundation: validator registry
// ==========================================================================
/// @notice Register a validator's Falcon-512 public key, or rotate an
/// existing one. Foundation only. Registering a NEW validator raises
/// totalValidators (and therefore the two-thirds quorum); rotating an
/// existing validator's key leaves the count unchanged.
/// @param validator the validator's chain address (as in the QBFT set).
/// @param pubKey the 897-byte NIST Falcon-512 public key (0x09 header).
function registerValidator(address validator, bytes calldata pubKey) external onlyOwner {
require(validator != address(0), "validator=0");
require(pubKey.length == PUBKEY_LEN, "pubkey len != 897");
require(uint8(pubKey[0]) == PK_HEADER, "pubkey header != 0x09");
falconPubKey[validator] = pubKey;
if (!isValidator[validator]) {
isValidator[validator] = true;
totalValidators += 1;
emit ValidatorRegistered(validator, pubKey.length, totalValidators);
} else {
emit ValidatorKeyRotated(validator, pubKey.length);
}
}
function transferOwnership(address newOwner) external onlyOwner {
require(newOwner != address(0), "newOwner=0");
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
// ==========================================================================
// Validator: attest a finalized block
// ==========================================================================
/// @notice Submit a validator's Falcon-512 attestation over a finalized block.
///
/// POST-QUANTUM AUTHORSHIP. The attestation is bound to `validator`
/// solely by verifying the Falcon-512 signature against that
/// validator's REGISTERED Falcon public key, in full, on-chain. It is
/// deliberately NOT bound to the ECDSA transaction sender: secp256k1
/// is exactly what a quantum computer breaks, so a post-quantum
/// attestation whose authorship rested on the tx signer would defeat
/// its own purpose. `msg.sender` is therefore an untrusted relayer
/// that only pays gas; it cannot forge an attestation, because it
/// cannot produce a valid Falcon-512 signature for a key it does not
/// hold. Each validator can relay its own attestation or delegate the
/// relay; the cryptographic authorship is the Falcon signature.
///
/// On the two-thirds threshold the block is marked post-quantum
/// finalized.
/// @param blockNumber the finalized block's number.
/// @param blockHash the finalized block's canonical hash (the signed message).
/// @param validator the registered validator whose Falcon key signed this.
/// @param falconSig nonce(40 bytes) || compSig (the Falcon comp_encode body,
/// i.e. the NIST esig with its 0x29 header byte stripped).
function attestBlock(
uint256 blockNumber,
bytes32 blockHash,
address validator,
bytes calldata falconSig
) external {
require(isValidator[validator], "not a registered validator");
require(falconSig.length > NONCE_LEN, "sig too short");
bytes32 key = attestationKey(blockNumber, blockHash);
require(!hasAttested[key][validator], "already attested");
bytes memory nonce = falconSig[0:NONCE_LEN];
bytes memory compSig = falconSig[NONCE_LEN:];
bytes memory message = abi.encodePacked(blockHash); // exactly 32 bytes
// Full, real Falcon-512 verification on-chain. No shortcut, no trust.
bool ok = verifier.verify(falconPubKey[validator], message, nonce, compSig);
require(ok, "falcon verify failed");
hasAttested[key][validator] = true;
uint256 count = attestCount[key] + 1;
attestCount[key] = count;
emit BlockAttested(validator, msg.sender, blockNumber, blockHash, count, totalValidators);
// Two-thirds quorum: count * 3 >= totalValidators * 2 (i.e. count >= ceil(2N/3)).
if (!postQuantumFinalized[key] && count * 3 >= totalValidators * 2 && totalValidators > 0) {
postQuantumFinalized[key] = true;
finalizedAtBlock[key] = block.number;
emit PostQuantumFinalized(blockNumber, blockHash, count, totalValidators);
}
}
// ==========================================================================
// Views
// ==========================================================================
/// @notice The record key for a (blockNumber, blockHash) pair.
function attestationKey(uint256 blockNumber, bytes32 blockHash) public pure returns (bytes32) {
return keccak256(abi.encode(blockNumber, blockHash));
}
/// @notice Number of validator Falcon-512 attestations recorded for a block.
function getAttestCount(uint256 blockNumber, bytes32 blockHash) external view returns (uint256) {
return attestCount[attestationKey(blockNumber, blockHash)];
}
/// @notice True once a two-thirds set of validators has post-quantum finalized the block.
function isPostQuantumFinalized(uint256 blockNumber, bytes32 blockHash) external view returns (bool) {
return postQuantumFinalized[attestationKey(blockNumber, blockHash)];
}
/// @notice Whether a specific validator has attested a given block.
function hasValidatorAttested(uint256 blockNumber, bytes32 blockHash, address validator)
external
view
returns (bool)
{
return hasAttested[attestationKey(blockNumber, blockHash)][validator];
}
/// @notice The minimum number of attestations required for post-quantum
/// finality at the current validator count: ceil(2 * totalValidators / 3).
function quorumThreshold() public view returns (uint256) {
if (totalValidators == 0) return 0;
return (2 * totalValidators + 2) / 3;
}
}