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

106 lines
4.6 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;
/**
* @title AereMLKEM768 - on-chain ML-KEM-768 (FIPS 203) encapsulation verifier
* @notice Thin view wrapper over the AERE native ML-KEM-768 precompile at 0x0AE6.
* ML-KEM-768 is AERE's post-quantum CONFIDENTIALITY primitive: every other
* native PQC precompile is a signature or hash (post-quantum authenticity),
* this one makes a Module-Lattice KEM key-agreement transcript verifiable
* on chain 2800.
*
* The precompile performs a DETERMINISTIC ML-KEM.Encaps: given an
* encapsulation key `ek` (1184 bytes) and 32-byte encapsulation coins `m`,
* it returns the ciphertext `ct` (1088 bytes) and shared secret `ss` (32
* bytes) that FIPS-203 Encaps(ek, m) produces. A relying contract compares
* the recomputed (ct, ss) against a claimed transcript; equality proves the
* KEM step was performed honestly with the stated coins. Serves the UMBRA
* PQXDH handshake settlement path and the AERE PQC key registry.
*
* @dev The precompile returns EMPTY (0 bytes) for any malformed input (wrong
* length). Callers must treat an empty return as failure.
*
* @dev AUD-CRYPTO-4 - THE SHARED SECRET RETURNED HERE IS PUBLIC. Encapsulation is
* DETERMINISTIC in the public inputs (ek, m), both supplied as plain calldata,
* so the shared secret `ss` (FIPS-203 "K") is fully derivable by any observer
* and returning it in returndata leaks nothing beyond those public inputs. This
* contract is a TRANSCRIPT-VERIFICATION tool only (prove that Encaps(ek, m)
* yields a claimed (ct, ss)); it is NOT a live key-agreement channel. Do NOT
* feed it secret coins `m` or treat `ss` as confidential key material. The
* precompile does not perform an explicit FIPS-203 ek validity round-trip; this
* is harmless for the transcript-equality use, but callers must not rely on it
* to screen a malformed ek.
*/
contract AereMLKEM768 {
/// @dev Native precompile address (AERE Besu PQC fork).
address public constant MLKEM768 = 0x0000000000000000000000000000000000000AE6;
uint256 public constant EK_LEN = 1184; // encapsulation key
uint256 public constant M_LEN = 32; // coins
uint256 public constant CT_LEN = 1088; // ciphertext
uint256 public constant SS_LEN = 32; // shared secret
/**
* @notice Deterministically encapsulate to `ek` using coins `m`.
* @param ek 1184-byte ML-KEM-768 encapsulation key.
* @param m 32-byte encapsulation randomness (coins).
* @return ok true iff the precompile returned a well-formed (ct, ss).
* @return ct 1088-byte ciphertext.
* @return ss 32-byte shared secret.
*/
function encapsulate(bytes memory ek, bytes memory m)
public
view
returns (bool ok, bytes memory ct, bytes memory ss)
{
if (ek.length != EK_LEN || m.length != M_LEN) {
return (false, "", "");
}
bytes memory input = bytes.concat(ek, m);
(bool success, bytes memory out) = MLKEM768.staticcall(input);
if (!success || out.length != CT_LEN + SS_LEN) {
return (false, "", "");
}
ct = new bytes(CT_LEN);
ss = new bytes(SS_LEN);
for (uint256 i = 0; i < CT_LEN; i++) {
ct[i] = out[i];
}
for (uint256 i = 0; i < SS_LEN; i++) {
ss[i] = out[CT_LEN + i];
}
return (true, ct, ss);
}
/**
* @notice Verify a claimed ML-KEM-768 transcript (ek, m -> ct, ss).
* @return valid true iff Encaps(ek, m) reproduces exactly the claimed ct and ss.
*/
function verifyTranscript(
bytes memory ek,
bytes memory m,
bytes memory claimedCt,
bytes memory claimedSs
) external view returns (bool valid) {
(bool ok, bytes memory ct, bytes memory ss) = encapsulate(ek, m);
if (!ok) return false;
return keccak256(ct) == keccak256(claimedCt) && keccak256(ss) == keccak256(claimedSs);
}
/**
* @notice Recompute only the shared secret for (ek, m). Convenience for a
* relying contract that already holds the ciphertext off-chain.
* @dev AUD-CRYPTO-4 footgun: the returned `ss` is PUBLIC (deterministic in the
* public inputs ek, m). This is a transcript-verification convenience, NOT
* a confidential key-agreement primitive; never pass secret coins `m`.
*/
function sharedSecret(bytes memory ek, bytes memory m)
external
view
returns (bool ok, bytes memory ss)
{
bytes memory ct;
(ok, ct, ss) = encapsulate(ek, m);
}
}