aere-contracts/contracts/mpc/AerePQCThreshold.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

102 lines
5.4 KiB
Solidity

// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.23;
/// @title AerePQCThreshold
/// @notice Shared post-quantum verification + envelope construction, extracted VERBATIM from the
/// proven `AereThresholdPQCRegistry` (17/17 tests, precompile envelope checked
/// byte-for-byte against the LIVE mainnet AERE precompiles 0x0AE1..0x0AE4). Both the
/// registry-style flows and the ERC-4337 `AereThresholdAccount` route through this ONE
/// verification path, so there is a single audited surface (avoids re-implementing the
/// crypto envelope and re-introducing a domain-confusion / parsing bug).
/// @dev NIST PQC schemes: 1=Falcon-512 (0x0AE1), 2=Falcon-1024 (0x0AE2), 3=ML-DSA-44 (0x0AE3,
/// FIPS 204), 4=SLH-DSA-128s (0x0AE4, FIPS 205). Application/account layer only; AERE
/// consensus is classical ECDSA QBFT.
library AerePQCThreshold {
uint8 internal constant SCHEME_FALCON512 = 1;
uint8 internal constant SCHEME_FALCON1024 = 2;
uint8 internal constant SCHEME_MLDSA44 = 3;
uint8 internal constant SCHEME_SLHDSA128S = 4;
address internal constant PRECOMPILE_FALCON512 = address(0x0AE1);
address internal constant PRECOMPILE_FALCON1024 = address(0x0AE2);
address internal constant PRECOMPILE_MLDSA44 = address(0x0AE3);
address internal constant PRECOMPILE_SLHDSA128S = address(0x0AE4);
uint256 internal constant FALCON512_PK_LEN = 897;
uint256 internal constant FALCON1024_PK_LEN = 1793;
uint256 internal constant MLDSA44_PK_LEN = 1312;
uint256 internal constant SLHDSA128S_PK_LEN = 32;
uint8 internal constant FALCON512_PK_HEADER = 0x09; // 0x00 | logn=9
uint8 internal constant FALCON1024_PK_HEADER = 0x0A; // 0x00 | logn=10
uint256 internal constant MLDSA44_SIG_LEN = 2420;
uint256 internal constant SLHDSA128S_SIG_LEN = 7856;
uint256 internal constant FALCON_NONCE_LEN = 40;
/// @notice True iff `pk` is a structurally valid public key for `scheme` (length + Falcon
/// header). A malformed key must never be accepted into a committee.
function validPubKey(uint8 scheme, bytes memory pk) internal pure returns (bool) {
if (scheme == SCHEME_FALCON512) return pk.length == FALCON512_PK_LEN && uint8(pk[0]) == FALCON512_PK_HEADER;
if (scheme == SCHEME_FALCON1024) return pk.length == FALCON1024_PK_LEN && uint8(pk[0]) == FALCON1024_PK_HEADER;
if (scheme == SCHEME_MLDSA44) return pk.length == MLDSA44_PK_LEN;
if (scheme == SCHEME_SLHDSA128S) return pk.length == SLHDSA128S_PK_LEN;
return false;
}
/// @dev Assemble (precompile, input) for a scheme, matching the live precompile spec offsets
/// exactly. `wellFormed` is false for a wrong-length signature (an invalid leg, never a
/// revert), so a malformed leg is counted as "did not verify" rather than aborting.
function buildInput(uint8 scheme, bytes memory pubKey, bytes32 message, bytes memory signature)
internal
pure
returns (address precompile, bytes memory input, bool wellFormed)
{
if (scheme == SCHEME_FALCON512 || scheme == SCHEME_FALCON1024) {
if (signature.length <= FALCON_NONCE_LEN) return (address(0), "", false);
uint256 sigLen = signature.length - FALCON_NONCE_LEN; // esig length
if (sigLen > type(uint16).max) return (address(0), "", false);
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);
input = abi.encodePacked(pubKey, sm);
precompile = scheme == SCHEME_FALCON512 ? PRECOMPILE_FALCON512 : PRECOMPILE_FALCON1024;
wellFormed = true;
} else if (scheme == SCHEME_MLDSA44) {
if (signature.length != MLDSA44_SIG_LEN) return (address(0), "", false);
input = abi.encodePacked(pubKey, signature, message); // pk(1312) || sig(2420) || message(32)
precompile = PRECOMPILE_MLDSA44;
wellFormed = true;
} else if (scheme == SCHEME_SLHDSA128S) {
if (signature.length != SLHDSA128S_SIG_LEN) return (address(0), "", false);
input = abi.encodePacked(pubKey, signature, message); // pk(32) || sig(7856) || message(32)
precompile = PRECOMPILE_SLHDSA128S;
wellFormed = true;
} else {
return (address(0), "", false);
}
}
/// @notice Verify a single PQC signature over `message` in full, on-chain, via the scheme's
/// live precompile. Returns false (never reverts) on a malformed or rejected leg.
function verify(uint8 scheme, bytes memory pubKey, bytes32 message, bytes memory signature)
internal
view
returns (bool)
{
(address precompile, bytes memory input, bool wellFormed) = buildInput(scheme, pubKey, message, signature);
if (!wellFormed) return false;
(bool ok, bytes memory ret) = precompile.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];
}
}
}