aere-contracts/contracts/pqc/mocks/MockPQCPrecompile.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

82 lines
3.8 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title MockPQCPrecompile, TEST-ONLY faithful mock of the AERE native PQC precompiles
*
* @notice Hardhat has no PQC precompile, so tests install this at 0x0AE1..0x0AE4 via
* hardhat_setCode. It parses the input EXACTLY as the live Besu precompiles do
* (per their Java sources: AereFalconSupport / MLDSA44 / SLHDSA128s) and returns
* a RAW 32-byte word 0x..01 iff the embedded signature "commitment" matches
* keccak256(pk || message). This models the real semantics: valid iff a genuine
* signature by THIS key over THIS message. Any malformed or non-matching input
* returns a raw 32-byte 0x..00 (invalid). The raw (non-ABI-encoded) return byte
* layout is identical to the live precompiles, so the caller's
* `ret.length >= 32 && ret[31] == 0x01` check behaves as it does on mainnet.
*
* The parse offsets here are the precompiles' spec offsets, NOT copied from the
* contract under test, so a positive attest passing here proves the contract
* built the input at the correct offsets. The real encoding is separately proven
* against the LIVE mainnet precompile via eth_call.
*/
contract MockPQCPrecompile {
uint8 private immutable _scheme;
constructor(uint8 scheme_) {
_scheme = scheme_;
}
// No public functions other than fallback, so an input whose leading bytes look
// like a selector can never dispatch anywhere but the verifier.
fallback() external {
bool valid = _check(msg.data);
bytes32 w = valid ? bytes32(uint256(1)) : bytes32(0);
assembly {
mstore(0x0, w)
return(0x0, 32)
}
}
function _check(bytes calldata input) internal view returns (bool) {
if (_scheme == 1) return _checkFalcon(input, 897, 0x29);
if (_scheme == 2) return _checkFalcon(input, 1793, 0x2A);
if (_scheme == 3) return _checkFixed(input, 1312, 2420);
if (_scheme == 4) return _checkFixed(input, 32, 7856);
return false;
}
// Falcon: input = pk(pkLen) || sm ; sm = sigLen(2 BE) || nonce(40) || message || esig ;
// esig = esigHeader || compressedSig ; sigLen == esig.length. (mirrors AereFalconSupport.verify)
function _checkFalcon(bytes calldata input, uint256 pkLen, uint8 esigHeader) internal pure returns (bool) {
if (input.length <= pkLen) return false;
bytes calldata pk = input[0:pkLen];
bytes calldata sm = input[pkLen:];
if (sm.length < 2 + 40 + 2) return false;
uint256 sigLen = (uint256(uint8(sm[0])) << 8) | uint256(uint8(sm[1]));
if (sigLen < 2 || 2 + 40 + sigLen > sm.length) return false;
uint256 msgLen = sm.length - 2 - 40 - sigLen;
bytes calldata esig = sm[sm.length - sigLen:];
if (esig.length < 33) return false;
if (uint8(esig[0]) != esigHeader) return false;
bytes calldata message = sm[42:42 + msgLen];
bytes32 commitment = _load(esig, 1);
return commitment == keccak256(abi.encodePacked(pk, message));
}
// ML-DSA-44 / SLH-DSA-128s: input = pk(pkLen) || sig(sigLen) || message(rest).
function _checkFixed(bytes calldata input, uint256 pkLen, uint256 sigLen) internal pure returns (bool) {
if (input.length < pkLen + sigLen + 32) return false;
bytes calldata pk = input[0:pkLen];
bytes calldata sig = input[pkLen:pkLen + sigLen];
bytes calldata message = input[pkLen + sigLen:];
bytes32 commitment = _load(sig, 0);
return commitment == keccak256(abi.encodePacked(pk, message));
}
function _load(bytes calldata s, uint256 off) internal pure returns (bytes32 r) {
assembly {
r := calldataload(add(s.offset, off))
}
}
}