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.
223 lines
11 KiB
Solidity
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;
|
|
}
|
|
}
|