aere-docs/AERE-PQ-AGGREGATE.md
2026-10-31 14:28:24 +02:00

215 lines
14 KiB
Markdown

# AerePQAggregate: constant-cost t-of-n post-quantum committee authorization
> **Note added 2026-10-30 (consensus).** Since block 25,080,000 (AIP-22 stage 5) headers no longer carry ECDSA commit seals: each carries the Falcon-512 seal of its proposer instead, and a commit still carries the ECDSA commit seal of its validator as an additional check. Where the note of 2026-09-24 below describes the headers, it describes chain 2800 below that block.
> **Note added 2026-09-24 (consensus).** Where this document says that consensus is classical secp256k1 ECDSA and not post-quantum, read it as the per-block finality statement it was until block 24,300,000 (AIP-22 stage 4, 2026-10-25). Since that block every validator attributes a consensus vote only through its verified post-quantum seal, and a block is post-quantum final once the next anchor, at most 32 blocks later, carries the certificate that covers it; headers still carry classical ECDSA commit seals until AIP-22 stage 5 (this sentence corrected on 2026-10-25). It no longer describes all of consensus on chain 2800: since 2026-08-14 every anchor block must carry a certificate of validator post-quantum seals under its hash (Falcon-512, with SLH-DSA as well since 2026-09-04), and since 2026-09-05 (block 17,250,000) the nine AERE-client validators (nine of the ten) refuse a commit message without a valid Falcon-512 seal of its author, and since 2026-09-08 (block 17,700,000) any QBFT consensus message without one, while the tenth, the second client, enforced that seal only on commit messages until block 24,300,000 and attributes every consensus message only through it since (this clause corrected on 2026-10-12, 2026-10-25 and 2026-10-28; until 2026-10-28 it dated the refusal of every message type to 2026-09-05, the date of the commit layer alone). (Note applied to this document on 2026-10-28.)
Status: on-chain verifier and ERC-7579 module BUILT and TESTED against a mock SP1 gateway.
Off-chain SP1 aggregation circuit NOT implemented, [MEASURE], research frontier.
Scope: account and authorization layer only. This changes nothing about Aere Network
consensus, which stays classical ECDSA QBFT. What is post-quantum here is the AUTHORITY
that approves a message, not the block production.
Contracts:
- `contracts/contracts/mpc/AerePQAggregateVerifier.sol`
- `contracts/contracts/modular/AerePQAggregateModule.sol`
- Test: `contracts/test/AerePQAggregate.test.js` (17 passing)
---
## 1. The problem this answers
NIST post-quantum signatures are large. An ML-DSA-65 signature is about 3,300 bytes, an
ML-DSA-44 signature about 2,420 bytes, an SLH-DSA-128s signature about 7,856 bytes. A t-of-n
committee that authorizes on-chain by supplying t independent PQC signatures therefore pays,
on-chain, for t signature verifications plus t large calldata blobs. That cost is LINEAR in t
and, under a per-transaction gas ceiling (Aere Network keeps Fusaka's EIP-7825 cap of 2^24 gas),
it bounds how large a committee can practically authorize in one transaction. This is the same
economic wall that motivates EIP-7932 (transaction-level signature abstraction for large or
alternative signature schemes): post-quantum signatures do not fit the classical fixed-size
signature budget.
`AereThresholdPQCRegistry` (already in this repo) takes the honest linear path: it verifies t
INDEPENDENT PQC signatures on-chain through Aere Network's live precompiles. Every leg is a real
NIST PQC signature, the t-of-n participation is proven on-chain, and the cost is linear in t.
`AerePQAggregate` takes the succinct path. It moves the t (up to n) signature verifications OFF
the chain, into a zero-knowledge virtual machine (the SP1 zkVM), and verifies ONE small proof
on-chain. The on-chain cost becomes INDEPENDENT of the committee size n.
---
## 2. The scheme
### Off-chain (the SP1 aggregation circuit, [MEASURE], NOT implemented here)
A relying party commits, once, to a committee: a `root` (a binding commitment to the exact n
authorized PQC public keys, for example a Merkle root over the canonically ordered keys or the
keccak256 of the canonical key list), a threshold `t`, a size `n`, and a `scheme`.
To authorize a 32-byte `messageDigest`, an off-chain prover runs the aggregation guest program
inside the SP1 zkVM with:
- PRIVATE witness: the n committed public keys, and at least t PQC signatures over `messageDigest`.
- The circuit MUST, inside the zkVM:
1. recompute `root` from the witnessed keys (and abort if it does not match the committed root),
2. verify each of the counted signatures against a DISTINCT committed key over `messageDigest`,
using the scheme's real verification algorithm,
3. never double-count a key (distinctness by committed index),
4. output the PUBLIC values below.
- PUBLIC output (the fixed 192-byte layout the on-chain verifier decodes):
`abi.encode(bytes32 committeeRoot, uint256 threshold, uint256 committeeSize, uint256 scheme,
bytes32 messageDigest, uint256 validCount)`
where `validCount` is the number of distinct valid signatures the circuit verified, and must be
at least `threshold`.
The proof is succinct: its size and its on-chain verification cost are fixed by the SP1 verifier,
independent of n and of how many signatures the circuit checked internally.
### On-chain (BUILT and TESTED here)
`AerePQAggregateVerifier`:
1. `registerCommittee(root, t, n, scheme)` stores the committee. The n public keys are NOT stored
on-chain (storing them would reintroduce the O(n) cost the design removes); `root` is the sole
on-chain commitment to them. Permissionless, no admin, holds no funds.
2. `verifyAggregate(committeeId, messageDigest, publicValues, proofBytes)` binds the proof's public
values to the registered committee and to the caller's claimed message, then makes a SINGLE
`verifyProof` call to the deployed SP1 verifier gateway. It is fail-closed: it rejects unless
ALL hold:
- the committee is registered,
- `publicValues.committeeRoot == committee.root` (the sole authority binding),
- `publicValues.threshold / committeeSize / scheme == committee.{threshold,size,scheme}`
(defense in depth),
- `publicValues.messageDigest == messageDigest` (the caller's claimed message),
- `publicValues.validCount >= committee.threshold`,
- the SP1 proof verifies against the pinned circuit vkey (`verifyProof` REVERTS on an invalid
proof).
3. `tryVerifyAggregate(...)` is the non-reverting variant (returns bool) used by the ERC-4337 and
ERC-7579 validation paths, which SHOULD return a failure code rather than revert.
4. `recordAggregate(...)` verifies (strict) and records an on-chain provenance attestation.
The circuit is pinned. `AGGREGATE_PROGRAM_VKEY` is fixed at construction, so a registrant cannot
substitute a different circuit: every committee is proven by the same audited program through the
same immutable gateway. Deploy one verifier per circuit (for example one for the ML-DSA-65
aggregation program).
The deployed SP1 verifier gateway on Aere Network mainnet (chain 2800) is the canonical Succinct
gateway at `0x9ca479C8c52C0EbB4599319a36a5a017BCC70628`, the same gateway the parallel-execution
validity contracts and the zk light clients already call.
---
## 3. The gas argument: constant vs linear in n
The verifier does NO per-member work on-chain. There is no loop over keys, no per-signature
precompile call, no per-member calldata. It reads a fixed committee record, compares six words of
public values, and makes one `verifyProof` call. The cost is therefore fixed by the SP1 verifier
and independent of n.
Measured in the test suite (`recordAggregate`, mock gateway):
| committee size n | gas used |
| ---------------- | -------- |
| 5 | 61,799 |
| 1,000,000 | 61,823 |
n grew by 200,000x; gas moved by 24 (the calldata encoding of the larger integer, which is
O(log n), not O(n)). A linear on-chain t-of-n PQC verifier pays tens of thousands of gas PER
signature (the marginal precompile verify cost for ML-DSA-44 is about 55,000 gas, before calldata),
so at even a modest committee it hits the per-transaction gas ceiling; the aggregate path does not.
(The gas figures above are for the mock gateway. The REAL SP1 Groth16/Plonk verifier is more
expensive per call but still CONSTANT in n; the constant-vs-linear conclusion is what the on-chain
design guarantees. The absolute real-gateway number is [MEASURE].)
---
## 4. ERC-4337 and ERC-7579 portability
The authorization is exposed two ways.
- ERC-4337 authorization path. `messageDigest` is the userOpHash, so the aggregate proof is bound
to one exact operation. The account nonce inside the userOpHash makes the authorization
non-replayable at the account layer, so the verifier itself needs no separate replay nonce.
- ERC-7579 validator module. `AerePQAggregateModule` implements the type-1 validator surface from
the ERC-7579 standard (OpenZeppelin `interfaces/draft-IERC7579.sol` `IERC7579Validator`):
`onInstall` / `onUninstall` / `isModuleType`, `validateUserOp(PackedUserOperation, bytes32)`
returning the ERC-4337 code (0 success, 1 failure), and `isValidSignatureWithSender(address,
bytes32, bytes)` returning the ERC-1271 magic value `0x1626ba7e` on success. An account is bound
to a committee at install time; the module then validates each userOp (and each ERC-1271
signature) by asking the verifier whether the committee signed the digest. Both paths are
fail-closed and never revert on a bad or malformed signature, per the validator SHOULD.
Because ERC-7579 is a chain-agnostic modular-account standard, the SAME module can be installed on
a Safe (via the Safe7579 adapter), a Kernel, or a Nexus account on ANY EVM chain, and the verifier
it points at can be deployed on that chain against that chain's SP1 gateway. The rule "a t-of-n
post-quantum committee approved this userOp" then travels with the account across chains.
[VERIFY] The module assumes the account has already stripped any validator-routing prefix (ERC-7579
stacks select the validator by an address carried in the userOp nonce key, or in some stacks a
prefix on the signature) before calling the module, matching the OpenZeppelin `AccountERC7579`
dispatcher. Confirm the exact unwrapping and the PackedUserOperation field layout against the
specific account implementation you deploy under (Safe7579 / Kernel / Nexus).
---
## 5. Bank28 custody use case
Bank28 is a non-custodial neobank stack. A treasury or custody account can be governed by a t-of-n
committee of post-quantum keys (for example 3-of-5 ML-DSA-65 signers held on separate HSMs or
devices). With `AereThresholdPQCRegistry` the account pays on-chain for t independent PQC
verifications each time it moves funds, and the committee size is bounded by the per-transaction
gas ceiling. With `AerePQAggregate`:
- the committee can be large (institutional custody often wants a big quorum: many independent
approvers, several of them offline) because on-chain cost does not grow with n,
- each fund movement is one small proof, so gas and calldata are predictable and flat,
- the account is a standard ERC-7579 modular account with the validator module installed, so it
works with existing bundler and wallet infrastructure and is portable across the chains Bank28
settles on,
- the authority is quantum-durable: a quantum adversary that can forge ECDSA still cannot authorize
a movement without the committee's PQC private keys, because the underlying legs are NIST PQC
signatures proven in the zkVM.
Scope note: this authorizes ACCOUNT actions. It is not custody advice and it does not make Aere
Network consensus post-quantum. Bank28 must still follow its lawful KYC/AML obligations; this is an
authorization primitive, not a compliance control.
---
## 6. Honest status
BUILT and TESTED (this repo):
- `AerePQAggregateVerifier.sol`: committee registry, public-input binding scheme, fail-closed
strict and non-reverting verification, constant-cost single `verifyProof` call, provenance
recording.
- `AerePQAggregateModule.sol`: ERC-7579 type-1 validator module wrapping the verifier for the
ERC-4337 and ERC-1271 paths.
- 17 passing Hardhat tests against `MockSp1Verifier`, the same deployed-gateway test double used by
`AereComputeMarketV3` and the zk light clients. Its `verifyProof` REVERTS on an invalid proof,
so the fail-closed path is exercised exactly as against the production gateway. Tests cover: a
valid proof authorizes; an invalid proof rejects fail-closed; a wrong-message-digest proof
rejects; a wrong-committee-root proof rejects; below-threshold rejects; threshold/size/scheme
mismatch and malformed public values reject; unknown committee rejects; gas is constant across
n = 5 vs n = 1,000,000; and the module returns the correct ERC-4337 code and ERC-1271 magic value
on valid inputs and fails closed (without reverting) on invalid or malformed ones.
NOT implemented, [MEASURE], research frontier:
- The OFF-CHAIN SP1 aggregation guest circuit. Verifying n PQC signatures against the committee root
inside a zkVM and emitting the public values is real cryptographic engineering that is NOT in this
repository. The on-chain verifier is proven against a mock gateway that asserts the bound public
values; a real end-to-end system additionally needs the real circuit and a real SP1 proof.
- Verifying Falcon inside a zkVM is HARD: Falcon signature verification touches FFT over the ring
and, in reference implementations, floating-point Gaussian sampling paths, which are expensive and
delicate to make deterministic and constraint-friendly in a zkVM. ML-DSA (FIPS 204, Dilithium
lineage) is integer-arithmetic and modular-reduction based, with no floating point, so
ML-DSA-65 aggregation is the TRACTABLE FIRST TARGET. The pinned scheme constant in the verifier
reflects this (`SCHEME_MLDSA65 = 5`). A Falcon aggregation circuit is a later, separate verifier
deployment with its own pinned vkey.
- The real end-to-end proving cost and latency, and the real-gateway on-chain verify gas, are
[MEASURE] and depend on the circuit implementation.
Do NOT claim the aggregation works end-to-end. The claim that IS supported by this repository: the
on-chain verification and portability rail is built and fail-closed, and its cost is provably
constant in the committee size n.