aere-research/research/aip-draft-pqc-precompiles.md
Aere Network 4a0b48588c 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:30 +03:00

349 lines
44 KiB
Markdown

---
aip: TBD
title: Native Post-Quantum Signature Verification Precompiles (SHAKE256, Falcon-512, Falcon-1024, ML-DSA-44, SLH-DSA-SHA2-128s)
description: EVM precompiled contracts on AERE (chain 2800) that natively accelerate the hashing and lattice/hash signature verification used by NIST post-quantum schemes, so that schemes currently limited to view-only verification can record results on-chain within the EIP-7825 per-transaction gas cap.
author: AERE Network Foundation
discussions-to: https://aere.network/docs.html
status: Draft
type: Standards Track
category: Core
created: 2026-07-11
requires: EIP-7825 (per-transaction gas cap), RIP-7951 (P-256 precompile precedent), EIP-2537 (BLS12-381 precompiles precedent), FIPS 202, FIPS 204, FIPS 205, Falcon (NIST PQC round-3)
---
> **Status update (2026-07-16): SHIPPED.** The AerePQC hard fork is LIVE on mainnet
> chain 2800, activated at block 9,189,161 (2026-07-12). The five precompiles are
> live at the address band `0x0AE1`..`0x0AE5` (Falcon-512, Falcon-1024, ML-DSA-44,
> SLH-DSA-SHA2-128s, SHAKE256), together with the native EIP-2935 8191-block
> lookback. The as-shipped activation is recorded canonically in `aerenew/aips/AIP-7.md`.
> This document is the mechanism specification; where it proposes a `0x0900`
> reserved band (Section 1), note that the band actually adopted at activation is the
> `0x0AE1`..`0x0AE5` band from the reference implementation, as ratified in AIP-7.
## Abstract
This proposal specifies a family of native precompiled contracts for the AERE EVM (chain ID 2800) that accelerate post-quantum (PQC) signature verification. The AERE application layer already runs full, spec-complete PQC verifiers written in pure Solidity: Falcon-512, Falcon-1024, ML-DSA-44 (Dilithium2), SLH-DSA-SHA2-128s (SPHINCS+), XMSS-SHA2_10_256, and WOTS+. The dominant cost inside every one of these verifiers is `SHAKE256` / `Keccak-f[1600]` streaming, followed by lattice ring arithmetic. Two of the schemes, Falcon-1024 and ML-DSA-44, exceed the EIP-7825 per-transaction gas cap of 16,777,216 when run as a state-changing `verifyAndRecord` transaction, so they are usable today only as `eth_call` views.
This document specifies, at minimum, (1) a `SHAKE256` precompile and (2) a `Falcon-512` verify precompile, plus optional `Falcon-1024`, `ML-DSA-44`, and `SLH-DSA-SHA2-128s` verify precompiles. Each precompile has a fixed reserved address, a self-describing length-prefixed input encoding, a single-byte output (`0x01` valid, `0x00` cryptographically invalid) with a revert on malformed input, and a gas-cost model. The precompiles wrap an audited native implementation (Bouncy Castle) inside the Besu client. They were implemented and KAT-validated on an isolated Besu 26.4.0 scratch fork (chain 28099); all NIST KAT vectors including negatives pass; and they are now LIVE on AERE mainnet 2800, activated at block 9,189,161 (2026-07-12) as the AerePQC hard fork, a coordinated client-only activation with no re-genesis (recorded in AIP-7). The as-shipped precompiles live at the address band `0x0AE1`..`0x0AE5`. With the precompiles live, a full Falcon-1024 verify-and-record transaction measures 145,496 gas (0.87% of the cap) and ML-DSA-44 measures 351,050 gas (2.09%), both well within the per-transaction cap.
## Motivation
AERE runs the actual post-quantum cryptography on-chain rather than a proof-of-a-proof. The live verifiers on chain 2800 today are:
| Scheme | Standard | Contract address | On-chain status |
| --- | --- | --- | --- |
| WOTS+ (hash-based) | one-time | AerePQCVerifier `0x1cE2949e8cE3f1A77b178aF767a4455c08ec6F82` | records on-chain |
| XMSS-SHA2_10_256 | RFC 8391 | AereXmssVerifier `0x77b14E264D0bb08d304d4e0E527F0fCdFc88B112` | records on-chain |
| Falcon-512 | NIST round-3 | AereFalcon512Verifier `0x4E8e9682329e646784fB3bd01430aA4bA54D8fFC` | records on-chain |
| SLH-DSA-SHA2-128s (SPHINCS+) | FIPS 205 | AereSphincsVerifier `0xAfFc9F8d950969b46b54e77758BbFf7e000c87e6` | records on-chain |
| Falcon-1024 | NIST round-3 | AereFalcon1024Verifier `0xF0aFA59BaB2058e4B6e6B424b7f76750F1F66e36` | pure-Solidity view-only; now records on-chain via the live `0x0AE2` precompile |
| ML-DSA-44 (Dilithium2) | FIPS 204 | AereMLDSA44Verifier `0xf1F7A6Acd82D5DAf9AF3166a2F736EE52C5F85AE` | pure-Solidity view-only; now records on-chain via the live `0x0AE3` precompile |
| Falcon/Dilithium lattice core (demo) | n=64 | AereLatticeVerifier `0x60c06E6A3CC201B46A16650be096C6E45424dfD9` | n=64 demo |
We are not aware of any other public chain that verifies all of these on-chain.
There are two concrete problems the pure-Solidity implementations expose, and both are addressed by native precompiles.
**Problem 1: the hashing bottleneck.** Every Falcon, ML-DSA, and SLH-DSA verify streams `SHAKE256` (Falcon `HashToPoint`, ML-DSA `mu`, `ExpandA` via `SHAKE128`, SLH-DSA tweakable hashes). AERE implements `Keccak-f[1600]` in hand-written EVM assembly precisely because the naive array-based permutation cost roughly 1.26 million gas per permutation (see the comment in `AereFalcon512Verifier._keccakf`, repo-sourced). Even the optimized assembly permutation is executed many times per verify. A native `SHAKE256` precompile replaces thousands of EVM opcodes per permutation with a single call into a native, audited hasher.
**Problem 2: two schemes cannot record on-chain.** AERE enforces the EIP-7825 per-transaction gas cap of 16,777,216 (2^24), the same cap Ethereum L1 adopts. Repo-sourced measured costs:
- Falcon-512 fits: a full ERC-4337 userOp that performs a Falcon-512 verify through `AereEntryPointV2.handleOps` measured `gasUsed` 10,278,313, under the cap (see AerePQCAccountFactory demo, tx recorded in the SDK registry).
- SLH-DSA-SHA2-128s fits: a `verifyAndRecord` measured `gasUsed` 1,812,066 (AereSphincsVerifier recorded tx, block 8916065).
- XMSS fits: a `verifyAndRecord` measured `gasUsed` 1,561,963 (AereXmssVerifier recorded tx).
- Falcon-1024 does NOT fit: a full `verifyAndRecord` is approximately 21.7M gas (repo-sourced estimate), above the 2^24 cap, so it is a pure `view` only.
- ML-DSA-44 does NOT fit: a full `verifyAndRecord` is approximately 52.9M gas (repo-sourced estimate), above the 2^24 cap, so it is a pure `view` only.
A native Falcon-1024 and ML-DSA-44 precompile is what moves those two schemes from view-only to record-on-chain within the per-transaction cap. It also cuts the cost of the schemes that already fit (Falcon-512 dropping from roughly 10.5M gas per PQC authentication to a fixed precompile cost would make PQC-secured smart accounts, such as AerePQCAccount, cheap enough for routine use).
**Scope honesty.** These precompiles are an APPLICATION and ACCOUNT layer capability. AERE validators still sign classical secp256k1 QBFT consensus messages. Nothing in this proposal makes AERE consensus post-quantum. This proposal does not change block production, finality, or the validator signature scheme.
## Specification
The key words MUST, MUST NOT, REQUIRED, SHALL, SHOULD, MAY are to be interpreted as described in RFC 2119.
### 1. Reserved address band
AERE reserves the precompile address band `0x0000000000000000000000000000000000000900` through `0x00000000000000000000000000000000000009FF` (the "AERE PQC precompile band") for post-quantum verification precompiles. This band is chosen to sit above the Ethereum standard precompiles (`0x01` through `0x0a`), above the EIP-2537 BLS12-381 range (`0x0b` through `0x11`), and above the RIP-7951 secp256r1 / P-256 precompile at `0x100`, so that future upstream precompile allocations do not collide.
**As shipped (see AIP-7):** the mainnet activation at block 9,189,161 placed the five precompiles at the reference-implementation band `0x0AE1`..`0x0AE5` (`0x0AE1` Falcon-512, `0x0AE2` Falcon-1024, `0x0AE3` ML-DSA-44, `0x0AE4` SLH-DSA-SHA2-128s, `0x0AE5` SHAKE256) rather than the `0x0900` band proposed in the table below. The `0x0AE1`..`0x0AE5` band is the canonical, live one; the `0x0900` proposal is retained here as the original design record.
| Address | Precompile | Status in this proposal |
| --- | --- | --- |
| `0x0000000000000000000000000000000000000900` | `SHAKE256` | REQUIRED |
| `0x0000000000000000000000000000000000000901` | `FALCON512_VERIFY` | REQUIRED |
| `0x0000000000000000000000000000000000000902` | `FALCON1024_VERIFY` | OPTIONAL |
| `0x0000000000000000000000000000000000000903` | `MLDSA44_VERIFY` | OPTIONAL |
| `0x0000000000000000000000000000000000000904` | `SLHDSA_SHA2_128S_VERIFY` | OPTIONAL |
Before the activation fork, a `CALL` to any of these addresses hits an account with no code and returns success with empty return data, exactly as a call to any other empty address. Callers MUST therefore feature-detect (see Backwards Compatibility). After activation, these addresses behave as precompiles.
### 2. Common conventions
- **Endianness of length fields.** All length fields are 4-byte big-endian unsigned integers (`uint32`).
- **Output on a well-formed call.** Exactly one byte: `0x01` if the signature is valid for the given public key and message, `0x00` if it is well-formed but cryptographically invalid.
- **Revert on malformed input.** If the input cannot be parsed (total length does not equal the sum of the declared fields plus the header, a fixed-size field has the wrong length, or a required scheme header byte is wrong), the precompile MUST revert, consuming all supplied gas, matching the failure mode of existing precompiles on invalid input.
- **As shipped.** The live precompiles do NOT revert on malformed input (confirmed live 2026-07-18). The verify precompiles (`0x0AE1` through `0x0AE4`) return a 32-byte zero word and the SHAKE256 precompile (`0x0AE5`) returns empty output, treating unparseable input as a non-result rather than a hard revert. The shipped verify precompiles also return a 32-byte word (`0x00..01` valid, `0x00..00` invalid), not the single byte described in the "Output on a well-formed call" convention above. The RFC 2119 "MUST revert" and single-byte-output language in this section is the original design intent; the per-precompile "As shipped" notes in Sections 3 through 7 record the live behaviour.
- **As shipped, input framing.** The 4-byte `uint32` length-field header described in these conventions is the original design record. The live precompiles frame input differently: `SHAKE256` (`0x0AE5`) reads `outLen` as a 32-byte big-endian word (Section 3 as shipped), and the verify precompiles (`0x0AE1` through `0x0AE4`) take the NIST signed-message (`sm`) format `pk || sm`, not a `pkLen`/`sigLen`/`msgLen` header (Section 4 as shipped). Confirmed live on mainnet 2800 on 2026-07-18.
- **Determinism.** Every precompile MUST be a pure function of its input bytes. It MUST NOT read chain state, block context, or any source of nondeterminism. All AERE nodes run an identical client build so that every node computes the identical result and gas charge (see Security Considerations).
- **Integer-only.** All verification is integer arithmetic. Falcon verification, in particular, uses only the integer HashToPoint, integer NTT, and integer norm check; it does NOT use the floating-point Falcon signing path. This keeps results bit-identical across platforms.
### 3. `SHAKE256` precompile (`0x...0900`), REQUIRED
FIPS 202 `SHAKE256` extendable-output function.
**Input encoding** (packed, not ABI-encoded):
```
offset 0 : outLen (4 bytes, big-endian uint32) desired output length in bytes
offset 4 : data (all remaining bytes) message to absorb
```
**As shipped (`0x0AE5`).** The live SHAKE256 precompile does NOT use the 4-byte `uint32` framing above. It reads `outLen` as a full 32-byte big-endian word at offset 0, with the data to absorb starting at offset 32:
```
offset 0 : outLen (32 bytes, big-endian word) desired output length in bytes
offset 32 : data (all remaining bytes) message to absorb
```
Confirmed live on mainnet 2800 by `eth_call` to `0x0AE5` (2026-07-18):
- The draft 4-byte framing `0x00000020616263` (intended `outLen` 32 with data `abc`) returns empty output (`0x`); it is not parsed as a valid call under the shipped encoding.
- A 32-byte `outLen` word of 32 followed by `abc` returns `0x483366601360a8771c6863080cc4114d8db44530f8f1e1ee4f94ea37e78b5739`, which equals `shake_256(b'abc').hexdigest(32)`.
- A 32-byte `outLen` word of 32 followed by empty data returns `0x46b9dd2b0ba88d13233b3feb743eeb243fcd52ea62b81b82b50c27646ed5762f`, which equals `shake_256(b'').hexdigest(32)`.
- A 32-byte `outLen` word of 64 followed by `abc` returns 64 bytes of XOF output whose first 32 bytes are byte-identical to the `outLen` 32 result, confirming true extendable-output squeezing rather than a fixed-width digest.
The 32-byte-word encoding is the canonical, live one; the 4-byte `uint32` encoding above is retained as the original design record.
**Behavior.** Compute `SHAKE256(data)` with rate 136 bytes, domain separation `0x1F`, and `pad10*1` padding (identical to the on-chain `AereFalcon512Verifier.shake256`), then squeeze `outLen` bytes.
**Output.** Exactly `outLen` bytes.
**Malformed input.** Input shorter than 4 bytes MUST revert. An `outLen` of 0 is valid and returns empty output. Implementations MAY impose an `outLen` upper bound (for example 65536 bytes) and MUST revert above it to bound worst-case work; the bound, if any, MUST be a chain constant.
**As shipped (`0x0AE5`).** The live precompile does NOT revert on malformed or short input; it returns empty output (`0x`). The draft 4-byte framing, and any input the parser cannot interpret as a 32-byte `outLen` word followed by data, returns `0x` rather than reverting (confirmed live 2026-07-18). This matches the shipped behaviour already documented in `aerenew/docs/drafts/PQC-FORK-PAPER-DRAFT.md` (which records that "a malformed short input yields an empty return"), so annotating it here removes a paper-vs-AIP contradiction. The "MUST revert" language above is the original design intent, retained as the design record. An `outLen` of 0 returning empty output is consistent between the design and the as-shipped behaviour.
**Gas.** Measured on the scratch fork (chain 28099) as a fixed base plus a per-word charge over the data processed, matching the per-word shape of the `KECCAK256` opcode:
```
words = ceil((len(data) + outLen) / 32)
gas = SHAKE_BASE + SHAKE_WORD * words
```
Measured constants (scratch fork, chain 28099; the `SHAKE256` precompile charged 60 + 12 per word):
| Constant | Measured value | Basis |
| --- | --- | --- |
| `SHAKE_BASE` | 60 | fixed base, matching the `SHA256` precompile base (0x02) |
| `SHAKE_WORD` | 12 | 12 gas per 32-byte word, measured natively on the fork against the `KECCAK256` opcode word cost |
A standalone `SHAKE256` verify-and-record transaction on the fork measured `gasUsed` 21,470.
### 4. `FALCON512_VERIFY` precompile (`0x...0901`), REQUIRED
NIST Falcon-512 (round-3) signature verification. Parameters: `q = 12289`, ring `Z_q[x]/(x^512+1)`, degree `n = 512`, squared-norm acceptance bound `floor(beta^2) = 34034726`, nonce length 40, public key 897 bytes.
**Input encoding:**
```
offset 0 : pkLen (4 bytes, big-endian uint32) MUST equal 897
offset 4 : sigLen (4 bytes, big-endian uint32) length of the detached signature
offset 8 : msgLen (4 bytes, big-endian uint32) length of the message
offset 12 : pk (pkLen bytes) NIST public key: 0x09 header || 512 coeffs packed at 14 bits
offset 12+pkLen : sig (sigLen bytes) detached signature: nonce(40) || esig, esig[0] = 0x29
offset 12+pkLen+sigLen : msg (msgLen bytes) the signed message
```
The detached `sig` field is exactly the layout the on-chain `AereFalcon512Verifier.verifySignedMessage` reconstructs: a 40-byte salt `r` followed by `esig`, whose first byte is the Falcon-512 signature header `0x29` (`0x20 + logn`, `logn = 9`), followed by the compressed Gaussian encoding of `s2`.
**As shipped (`0x0AE1`, and the same pattern for `0x0AE2` / `0x0AE3` / `0x0AE4`).** The live verify precompiles do NOT use the 12-byte `pkLen`/`sigLen`/`msgLen` header shown above. They accept the NIST signed-message (`sm`) format `pk || sm` (for Falcon-512, `pk` is the 897-byte `0x09`-headed key and `sm` is the NIST signed message; the analogous `pk || sm` layout applies to Falcon-1024, ML-DSA-44, and SLH-DSA in Sections 5 through 7). This is exactly what the on-chain callers build (for example `AerePQCAttestation._buildInput`) and what `aerenew/docs/drafts/PQC-FORK-PAPER-DRAFT.md` and the `aerenew/conformance/` KAT vectors document authoritatively. Confirmed live by `eth_call` to `0x0AE1` on 2026-07-18: feeding the Falcon-512 KAT input `pk(897, `0x09` header) || sm` with no length-field header returns `0x00..01`. The 12-byte-header framing above is retained as the original design record; consult the fork paper and the conformance vectors for the exact shipped `sm` byte layout per scheme.
**Behavior.** Perform the full Falcon-512 verification:
1. `HashToPoint`: stream `SHAKE256(nonce || msg)` and rejection-sample the challenge polynomial `c` (16-bit big-endian samples, keep values `< 5*q = 61445`, reduce mod `q`), per the reference `hash_to_point_vartime`.
2. Decode the 897-byte public key into `h` (512 coefficients, 14-bit packed, reject coefficients `>= q` and nonzero trailing bits).
3. `comp_decode` the compressed `s2` (sign bit, 7 low bits, unary high bits; reject "-0", overflow above 2047, buffer overrun, nonzero trailing bits, and any unconsumed trailing bytes).
4. Recompute `s1 = c - s2*h` in the ring via negacyclic NTT convolution mod `q`, centered into `(-q/2, q/2]`.
5. Accept iff `||(s1, s2)||^2 <= 34034726`.
**Output.** `0x01` if valid, `0x00` if well-formed but invalid.
**Malformed input.** `pkLen != 897`, the `pk` header byte not `0x09`, the `esig` header byte not `0x29`, a `sigLen` inconsistent with the nonce plus at least a one-byte `esig`, or a total input length not equal to `12 + pkLen + sigLen + msgLen` MUST revert. A structurally valid but cryptographically failing signature returns `0x00`, it does not revert.
**As shipped (`0x0AE1`).** The live precompile does NOT revert on structurally malformed input; it returns a 32-byte zero word (`0x00..00`), the same value it returns for a structurally valid but cryptographically failing signature. A probe of `0x0AE1` with `0xdeadbeef` returns `0x0000...0000` (confirmed live 2026-07-18). Two consequences: (1) the shipped output is a full 32-byte word, not a single byte; per `aerenew/docs/drafts/PQC-FORK-PAPER-DRAFT.md`, a valid verify returns `0x00..01` and an invalid or malformed one returns `0x00..00`. (2) The "MUST revert on malformed" language above is the original design intent; the shipped precompile instead treats malformed input like a non-verifying signature and returns zero. The accept/reject decision on well-formed input is unchanged; only the malformed-input failure mode differs (revert in the design, zero word as shipped).
**Gas.** Fixed cost, since Falcon-512 verification work is bounded and near-constant per verify:
```
gas = FALCON512_VERIFY_GAS
```
Measured value (scratch fork, chain 28099): `FALCON512_VERIFY_GAS = 40000`, the marginal gas charged by the precompile verify operation; a full Falcon-512 verify-and-record transaction measured `gasUsed` 86,336, down from 10,492,455 in pure Solidity. Rationale anchor: RIP-7951 prices a native P-256 verification at a fixed roughly 3,450 gas on this same ruleset (repo-sourced from the Fusaka notes); a native Falcon-512 verify does more work (a `SHAKE256` stream plus a 512-point NTT), so the measured cost is a small multiple of the P-256 fixed cost, as expected. The value is priced so it cannot be used as a denial-of-service vector relative to its measured wall-clock cost.
### 5. `FALCON1024_VERIFY` precompile (`0x...0902`), OPTIONAL
NIST Falcon-1024. Identical pipeline to Falcon-512 with these parameter changes: ring `Z_q[x]/(x^1024+1)`, degree `n = 1024`, squared-norm bound `floor(beta^2) = 70265242`, public key 1793 bytes, public-key header `0x0A` (`logn = 10`), signature header `0x2A` (`0x20 + logn`).
**Input encoding.** Same field layout as Falcon-512, with `pkLen` MUST equal 1793, `pk` header `0x0A`, and `esig` header `0x2A`.
**Behavior, output, malformed handling.** As Falcon-512, at degree 1024.
**As shipped (`0x0AE2`).** As with Falcon-512, the live precompile does NOT revert on malformed input; it returns a 32-byte zero word, and returns a 32-byte word for well-formed calls (`0x00..01` valid, `0x00..00` invalid). A probe of `0x0AE2` with `0xdeadbeef` returns `0x0000...0000` (confirmed live 2026-07-18). A structurally valid but non-verifying signature also returns a 32-byte zero word. The "MUST revert on malformed" intent inherited from Falcon-512 is the design record, not the as-shipped behaviour.
**Gas.** Fixed cost `FALCON1024_VERIFY_GAS`. Measured value (scratch fork, chain 28099): `75000`, the marginal precompile verify-op gas; a full Falcon-1024 verify-and-record transaction measured `gasUsed` 145,496 (0.87% of the 2^24 cap). Falcon-1024 does roughly double the ring work of Falcon-512. This precompile is the mechanism that moves Falcon-1024 from view-only (its pure-Solidity `verifyAndRecord` is approximately 21.7M gas, above the 2^24 cap) to record-on-chain within the cap, now demonstrated on the scratch fork.
### 6. `MLDSA44_VERIFY` precompile (`0x...0903`), OPTIONAL
NIST ML-DSA-44 (Dilithium2, FIPS 204), internal interface (`Verify_internal`, Algorithm 8, `externalMu = false`, empty context). Parameters: `q = 8380417`, `n = 256`, `(k, l) = (4, 4)`, `d = 13`, `tau = 39`, `gamma1 = 2^17`, `gamma2 = (q-1)/88`, `beta = 78`, `omega = 80`, `lambda = 128`, public key 1312 bytes, signature 2420 bytes, `c~` 32 bytes, infinity-norm acceptance bound `||z||_inf < gamma1 - beta = 130994`.
**Input encoding:**
```
offset 0 : pkLen (4 bytes, big-endian uint32) MUST equal 1312
offset 4 : sigLen (4 bytes, big-endian uint32) MUST equal 2420
offset 8 : msgLen (4 bytes, big-endian uint32)
offset 12 : pk (1312 bytes) rho(32) || t1 (k=4 polys, 10-bit SimpleBitUnpack)
offset 12+pkLen : sig (2420 bytes) c~(32) || z (l=4 polys, 18-bit BitUnpack) || hint (omega+k = 84 bytes)
offset 12+pkLen+sigLen : msg (msgLen bytes)
```
**Behavior.** Full FIPS 204 `Verify_internal`: `pkDecode`, `sigDecode` (`BitUnpack` plus `HintBitUnpack` with strict-increasing and bound validation), `ExpandA` (SHAKE128 rejection sampling of `A_hat` directly in the NTT domain), `mu = H(H(pk,64) || M, 64)` with `H = SHAKE256`, `c = SampleInBall(c~)`, `w'approx = NTT^{-1}(A_hat o NTT(z) - NTT(c) o NTT(t1 * 2^d))` over `Z_q[x]/(x^256+1)`, `w1 = UseHint(h, w'approx)`, `c~' = H(mu || w1Encode(w1), 32)`, accept iff `||z||_inf < 130994`, the hint is valid, and `c~' == c~`.
**Output, malformed handling.** As the Falcon precompiles. `pkLen != 1312` or `sigLen != 2420` MUST revert.
**As shipped (`0x0AE3`).** As with the Falcon precompiles, the live precompile does NOT revert on malformed input; it returns a 32-byte zero word, and returns a 32-byte word for well-formed calls (`0x00..01` valid, `0x00..00` invalid). A probe of `0x0AE3` with `0xdeadbeef` returns `0x0000...0000` (confirmed live 2026-07-18). A `pkLen`/`sigLen` mismatch, or any other structurally broken input, returns a 32-byte zero word rather than reverting; a structurally valid but non-verifying signature also returns zero. The "MUST revert" language is the original design intent, retained as the design record.
**Note on interface.** This precompile implements the internal (`externalMu = false`, empty `ctx`) interface, matching the KAT the on-chain verifier is validated against. A pure ML-DSA `Verify` with a nonempty context string is a distinct interface and is out of scope for this precompile; if required it SHOULD be a separate precompile so domain separation stays explicit.
**Gas.** Fixed cost `MLDSA44_VERIFY_GAS`. Measured value (scratch fork, chain 28099): `55000`, the marginal precompile verify-op gas; a full ML-DSA-44 verify-and-record transaction measured `gasUsed` 351,050 (2.09% of the 2^24 cap). This precompile moves ML-DSA-44 from view-only (its pure-Solidity `verifyAndRecord` is approximately 52.9M gas, above the 2^24 cap) to record-on-chain within the cap, now demonstrated on the scratch fork.
### 7. `SLHDSA_SHA2_128S_VERIFY` precompile (`0x...0904`), OPTIONAL
NIST SLH-DSA-SHA2-128s (SPHINCS+-SHA2-128s-simple, FIPS 205), internal interface (`slh_verify_internal`, Algorithm 20, no pre-hash). Parameters: `n = 16`, `h = 63`, `d = 7`, `h' = 9`, `a = 12`, `k = 14`, `lg_w = 4`, `w = 16`, `len = 35`, public key 32 bytes, signature 7856 bytes.
**Input encoding:**
```
offset 0 : pkLen (4 bytes, big-endian uint32) MUST equal 32
offset 4 : sigLen (4 bytes, big-endian uint32) MUST equal 7856
offset 8 : msgLen (4 bytes, big-endian uint32)
offset 12 : pk (32 bytes) PK.seed(16) || PK.root(16)
offset 12+pkLen : sig (7856 bytes) R(16) || SIG_FORS || SIG_HT
offset 12+pkLen+sigLen : msg (msgLen bytes)
```
**Behavior.** Full FIPS 205 `slh_verify_internal`: `H_msg = MGF1-SHA256(R || PK.seed || SHA256(R || PK.seed || PK.root || M))` split into `(md, idx_tree mod 2^54, idx_leaf mod 2^9)`; FORS `pkFromSig` with base-`2^12` big-endian index extraction (the FIPS 205 change versus round-3 SPHINCS+); a `d = 7` hypertree of WOTS+ (`len = 35`) with `h' = 9` Merkle layers; tweakable hash `Th = Trunc16(SHA256(PK.seed || 0^48 || ADRSc || M))`. Accept iff the recomputed root equals `PK.root`.
**Output, malformed handling.** As above. `pkLen != 32` or `sigLen != 7856` MUST revert.
**As shipped (`0x0AE4`).** As with the precompiles above, the live precompile does NOT revert on malformed input; it returns a 32-byte zero word, and returns a 32-byte word for well-formed calls (`0x00..01` valid, `0x00..00` invalid). A probe of `0x0AE4` with `0xdeadbeef` returns `0x0000...0000` (confirmed live 2026-07-18). A `pkLen`/`sigLen` mismatch, or any other structurally broken input, returns a 32-byte zero word rather than reverting; a structurally valid but non-verifying signature also returns zero. The "MUST revert" language is the original design intent, retained as the design record.
**Note.** SLH-DSA-SHA2-128s already fits under the 2^24 cap in pure Solidity (measured `gasUsed` 1,812,066), so this precompile is a cost reduction, not an enabler. It is included so the whole PQC suite shares one native code path and one gas model. SLH-DSA is stateless and many-time, so unlike XMSS there is no one-time-per-leaf state caveat.
**Gas.** Fixed cost `SLHDSA_128S_VERIFY_GAS`. Measured value (scratch fork, chain 28099): `350000`, the marginal precompile verify-op gas; a full SLH-DSA-SHA2-128s verify-and-record transaction measured `gasUsed` 558,276 (3.33% of the 2^24 cap). SLH-DSA verification is hash-heavy (thousands of SHA-256 and tweakable-hash calls); the native cost is dominated by those hashes, so this fixed cost is larger than the lattice precompiles despite the tiny public key.
### 8. Feature/version identification
To let callers detect activation without probing behavior, the `SHAKE256` precompile at `0x...0900` MUST, when called with the exact 4-byte input `0x00000000` (an `outLen` of 0 and empty `data`), return empty output and succeed. Because an unforked node returns empty output and success for the same call, empty output is not a reliable discriminator. Therefore, in addition, AERE publishes the activation fork block number in client release notes and in the chain config, and integrators SHOULD gate PQC-precompile use on chain height, not on probing. As shipped, per the Section 3 encoding, `outLen` is read as a 32-byte word, so the 4-byte `0x00000000` is a short, malformed input rather than an `outLen` of 0 with empty data; it still returns empty output and succeeds (malformed input returns empty, it does not revert), so this activation-detection outcome is unchanged and height-gating remains the recommendation.
## Rationale
**Why precompiles and not just more Solidity optimization.** The pure-Solidity verifiers are already heavily optimized (hand-written `Keccak-f` assembly). The remaining cost is intrinsic: thousands of permutations and a full NTT per verify. Only native code removes it. This is the same reasoning that justified RIP-7951 (native P-256) and EIP-2537 (native BLS12-381) rather than in-EVM implementations.
**Why these specific schemes.** They span the NIST PQC signature families AERE already verifies on-chain: Falcon (compact lattice, small signatures), ML-DSA (module lattice, NIST primary), and SLH-DSA (stateless hash-based, conservative security). `SHAKE256` is shared by all three, which is why it is the first REQUIRED precompile: one native hasher accelerates every scheme, and it is independently useful to any contract that needs FIPS 202 XOF output.
**Why a length-prefixed packed encoding and not ABI.** Precompiles conventionally take packed, self-describing input to avoid pulling ABI decoding into consensus code. The three explicit length fields make the parse total-length-checkable in one comparison, which is what lets malformed input be rejected deterministically.
**Why revert on malformed but return `0x00` on invalid.** Distinguishing "you gave me garbage I cannot parse" from "this is a real signature that does not verify" matches every existing verify precompile and lets calling contracts treat a `0x00` as a normal boolean without try/catch, while a structurally broken call fails loudly.
**As shipped.** The live precompiles do NOT make this distinction. Malformed input returns a 32-byte zero word (verify precompiles) or empty output (SHAKE256), i.e. malformed input is treated the same as a non-verifying signature rather than failing loudly (confirmed live 2026-07-18; see Sections 3 through 7). The rationale above is the original design intent, retained as the design record. Integrating contracts on the live chain therefore MUST NOT rely on a revert to detect malformed input; they should treat a zero word or empty output as "not verified" and validate lengths themselves before the call where the distinction matters.
**Why fixed gas for the verify precompiles.** For a fixed parameter set, the verification work is bounded and nearly constant (Falcon signature length varies slightly but the norm and NTT dominate). A fixed cost is simpler and safer to reason about than a per-byte model, and it matches RIP-7951's fixed P-256 pricing. The `SHAKE256` precompile is the exception: its work scales with input and output length, so it is priced per permutation.
**Why the EIP-7825 cap matters here.** AERE deliberately keeps the same 16,777,216 per-transaction gas cap as Ethereum L1 so that AERE stays a functional peer of mainnet rather than relying on an unbounded local gas limit for correctness. That discipline is exactly why Falcon-1024 and ML-DSA-44 were view-only before the fork, and it is why the native precompile, not a higher gas limit, is the correct fix.
## Backwards Compatibility
There is no backwards-incompatible change to existing contracts. The precompile addresses were empty accounts before activation; no deployed AERE contract had code in the live `0x0AE1`..`0x0AE5` band before the fork.
Activation was a coordinated hard fork at block 9,189,161. Because AERE runs all validators under one operator, activation was a flag-day upgrade of the client build across the validator set at the fork block. There was NO re-genesis and no state migration; the fork only adds precompile behavior at otherwise-empty addresses. A node that had not upgraded would compute different results for calls into the band and would fork off; the upgrade was therefore mandatory for all validators and archive/RPC nodes at the fork block.
The existing pure-Solidity verifiers remain deployed and unchanged. They continue to work as views and (where they fit the cap) as recording transactions. Contracts MAY migrate to the precompiles for lower gas, but are not required to. A thin Solidity shim library SHOULD be published that calls the precompile when the chain is past the activation height and falls back to the existing verifier contract otherwise, so integrators get one stable interface across the fork.
## Test Cases
All precompiles are validated against the same official NIST Known-Answer-Test fixtures already committed to the repository and already used to validate the on-chain Solidity verifiers. The precompile MUST produce byte-identical accept/reject decisions to those fixtures and to the corresponding on-chain verifier, for both valid and tampered inputs (differential testing).
**Falcon-512** (`aerenew/contracts/test/falcon512_kat0.json`): official Falcon-512 round-3 KAT triple. Fields present in the fixture include `pk` (897-byte public key, `0x09` header), `nonce` (40 bytes), `msg`, `compsig`, and the full NIST `sm` blob. The fixture records the recomputed squared norm `28308410` against the acceptance bound `34034726`, so the valid vector MUST return `0x01`. Tampering any signature byte MUST return `0x00`.
**Falcon-1024** (`aerenew/contracts/test/falcon1024_kat0.json`): official Falcon-1024 round-3 KAT (source `falcon1024-KAT.rsp`, SHA-256 `036a0bf5...`). Valid vector returns `0x01`; tampered returns `0x00`.
**ML-DSA-44** (`aerenew/contracts/test/fixtures/mldsa44-acvp-tg8.json`): official NIST ACVP `ML-DSA-sigVer-FIPS204` test group 8 (ML-DSA-44, internal, `externalMu = false`), `prompt.json` SHA-256 `2a9b7fcb...`, `expectedResults.json` SHA-256 `33e0ea7d...`. All 15 cases (3 valid plus 12 crafted-invalid covering modified message, `z`, commitment, and hint) MUST reproduce NIST's `testPassed`.
**SLH-DSA-SHA2-128s** (`aerenew/contracts/test/fixtures/sphincs-sha2-128s-acvp-tg31.json`): official NIST ACVP `SLH-DSA-sigVer-FIPS205` test group 31 (SHA2-128s, internal), `prompt.json` SHA-256 `4e7beb12...`, `expectedResults.json` SHA-256 `259f5e2a...`, `pkBytes = 32`, `sigBytes = 7856`. All 14 cases (2 valid plus 12 crafted-invalid) MUST reproduce NIST's `testPassed`.
**SHAKE256**: validated against FIPS 202 `SHAKE256` test vectors, and differentially against the on-chain `AereFalcon512Verifier.shake256(input, outLen)` function, which is itself KAT-validated. For any `(data, outLen)`, the precompile output MUST equal `shake256(data, outLen)`.
**Cross-reference for XMSS and WOTS+.** XMSS-SHA2_10_256 is validated against `aerenew/contracts/test/fixtures/xmss-sha2_10_256-kat.json` (xmss-reference `@171ccbd`, pk-hash `7de72d19...`, sm-hash `8b6cb278...`). XMSS and WOTS+ already fit the cap and are not part of this precompile set, but any future XMSS/WOTS+ precompile would use the same fixture as its differential oracle.
**Required negative tests for every precompile:**
1. Total input length not equal to `12 + pkLen + sigLen + msgLen` MUST revert.
2. A fixed-size field with the wrong declared length (`pkLen`, or for ML-DSA/SLH-DSA the `sigLen`) MUST revert.
3. A wrong scheme header byte (`pk` header, `esig` header) MUST revert.
4. A structurally valid but cryptographically failing signature MUST return `0x00`, not revert.
5. An empty message (`msgLen = 0`) MUST be handled (Falcon `HashToPoint` over `nonce || ""` is well-defined).
**As shipped.** On the live mainnet precompiles, negative tests 1 through 3 (total-length mismatch, wrong fixed-field length, wrong header byte) do NOT revert; the verify precompiles return a 32-byte zero word and SHAKE256 returns empty output (confirmed live 2026-07-18). Test 4 (structurally valid but cryptographically failing) returns zero as specified. The differential accept/reject oracle on well-formed KAT input still holds in both directions; only the malformed-input failure mode differs (revert in the design, zero word or empty output as shipped). See Sections 3 through 7.
## Reference Implementation
The reference implementation is a native precompile registered in the AERE Besu client, wrapping the Bouncy Castle post-quantum providers:
- `SHAKE256`: Bouncy Castle `SHAKEDigest(256)`.
- Falcon-512 / Falcon-1024: Bouncy Castle Falcon verify (`org.bouncycastle.pqc.crypto.falcon`), fed the decoded public key and detached signature.
- ML-DSA-44: Bouncy Castle ML-DSA / Dilithium verify (`org.bouncycastle.pqc.crypto.mldsa`), internal interface, empty context.
- SLH-DSA-SHA2-128s: Bouncy Castle SLH-DSA verify (`org.bouncycastle.pqc.crypto.slhdsa`), SHA2-128s-simple parameter set.
Each precompile is a small adapter that (1) parses the packed input, (2) invokes the Bouncy Castle verify, (3) returns the verify result, and (4) charges the gas from the model in the Specification. As shipped (confirmed live 2026-07-18), a malformed or unparseable input does NOT revert: the verify precompiles return a 32-byte zero word and `SHAKE256` returns empty output. The verify result is returned as a 32-byte word (a zero word for reject) rather than a single byte, so integrating contracts MUST validate lengths themselves and treat a zero word or empty output as not-verified. The original design intent (revert on malformed structure, a single `0x01`/`0x00` byte result) is retained as the design record above and in Sections 3 through 7.
The existing pure-Solidity verifiers in `aerenew/contracts/contracts/pqc/` serve as the executable specification and the differential oracle: `AereFalcon512Verifier.sol`, `AereFalcon1024Verifier.sol`, `AereMLDSA44Verifier.sol`, `AereSphincsVerifier.sol`. The precompile MUST agree with them bit-for-bit on every KAT and on random tamper tests.
This work was implemented and KAT-validated on an isolated Besu 26.4.0 scratch fork (chain 28099); all NIST KAT vectors including negatives pass; and it is now LIVE on AERE mainnet 2800, activated at block 9,189,161 (2026-07-12) as the AerePQC hard fork, a coordinated client-only activation with no re-genesis (recorded in AIP-7). We forked Hyperledger Besu v26.4.0 (source tag 26.4.0, commit d2032017) and added five native precompiles that wrap the audited Bouncy Castle 1.83 BCPQC verifiers already present on the client classpath, introducing zero new dependencies and zero hand-rolled cryptography. The pure-Solidity AereFalcon1024Verifier and AereMLDSA44Verifier contracts remain view-only, but the Falcon-1024 and ML-DSA-44 schemes now record on-chain through the live precompiles.
On mainnet the five precompiles are live at `0x0AE1` (Falcon-512), `0x0AE2` (Falcon-1024), `0x0AE3` (ML-DSA-44, FIPS 204 internal), `0x0AE4` (SLH-DSA-SHA2-128s, FIPS 205 internal), and `0x0AE5` (SHAKE256, FIPS 202); this is the as-shipped band ratified in AIP-7, and it supersedes the `0x0900` band proposed in Section 1. All NIST KAT vectors pass via `eth_call` in both directions, positive and negative, including 15/15 ML-DSA-44 ACVP cases (12 negatives) and 14/14 SLH-DSA-SHA2-128s ACVP cases (12 negatives). A real Falcon-1024 attestation has been recorded on mainnet through AerePQCAttestation (`0x465d9E3b476BF98Aa1393079e240Db5D2a9bEA6A`, tx `0xb659…9ec1`, block 9,200,542, status 1). The gas constants above are the values measured on the fork; the following are the measured on-chain receipts:
| Scheme | Scratch-fork precompile | Marginal verify-op gas | Full verify-and-record tx gasUsed | Share of 16,777,216 cap |
| --- | --- | --- | --- | --- |
| SHAKE256 (FIPS 202) | `0x...0AE5` | 60 + 12/word | 21,470 | 0.13% |
| Falcon-512 | `0x...0AE1` | 40,000 | 86,336 | 0.51% |
| Falcon-1024 | `0x...0AE2` | 75,000 | 145,496 | 0.87% |
| ML-DSA-44 (FIPS 204) | `0x...0AE3` | 55,000 | 351,050 | 2.09% |
| SLH-DSA-SHA2-128s (FIPS 205) | `0x...0AE4` | 350,000 | 558,276 | 3.33% |
The two schemes that were view-only in pure Solidity now record on-chain with wide margin: Falcon-1024 in 145,496 gas (0.87% of the cap, down from roughly 21M) and ML-DSA-44 in 351,050 gas (2.09% of the cap, down from roughly 52.9M). Activation on AERE mainnet is complete: it landed at block 9,189,161 as a client-only hard fork with no re-genesis, and the concrete gas constants above are the measured fork values carried to mainnet, ratified in AIP-7.
## Security Considerations
**Wrap audited implementations.** The precompiles MUST wrap a maintained, audited native cryptographic library (Bouncy Castle). AERE MUST NOT hand-roll the native PQC verify. The Solidity verifiers are the differential oracle, not the security boundary; the security boundary is the wrapped native library plus the parsing adapter.
**External audit before securing material value.** These precompiles MUST be externally audited before they secure material value on mainnet. AERE has an internal self-audit only; it has not had an external audit of its contracts or client changes yet. The precompiles are now live on mainnet 2800 (block 9,189,161) but MUST be treated as unaudited and used only where a failure is not financially material until an external audit lands.
**Coordinated hard-fork activation, no re-genesis.** Activation was a flag-day hard fork at block 9,189,161. Because AERE runs all validators under a single operator, coordination was straightforward, but that same centralization is a weakness to state plainly: there is one operator, seven validators, and one client implementation (Besu). A consensus bug in a precompile is not caught by client diversity, because there is no second live client. This raises, not lowers, the bar for the external audit and for extensive differential testing; the fork carries an internal self-audit only, and an external audit is still pending before it should secure material value.
**Determinism and gas-charge agreement.** Every node MUST compute the identical accept/reject and the identical gas charge. Because there is one client build, divergence between clients is not a risk today; the risk is a nondeterministic native path (for example, a library that reads a system RNG or uses platform-dependent floating point). The verify paths used here are deterministic and integer-only (Falcon verification never touches the floating-point signing path). The adapter MUST NOT introduce any state or context read.
**Denial-of-service pricing.** Gas MUST be priced from real native microbenchmarks so that a precompile call cannot consume disproportionate wall-clock time for its gas. The `SHAKE256` precompile MUST bound `outLen` (and thus squeeze work) with a chain constant and revert above it. The verify precompiles are fixed-cost and bounded by construction because their parameter sets fix the work. Malformed input reverts and consumes all gas, so it cannot be used as a cheap probe.
**As shipped.** Malformed input does NOT revert and does not consume all gas (confirmed live 2026-07-18). The verify precompiles return a 32-byte zero word and the SHAKE256 precompile returns empty output for unparseable input; a malformed call is therefore a cheap probe rather than a gas-burning revert. The DoS argument still holds on gas grounds, because the verify precompiles remain fixed-cost and SHAKE256 remains priced per word over the processed input and output, so bounded work per call is preserved regardless of the malformed-input failure mode. The "malformed reverts and burns all gas" statement above is the original design intent, retained as the design record.
**Malleability and misuse at the application layer.** A `0x01` from a verify precompile means only that the signature is valid for the given public key and message. It does NOT establish freshness, ordering, replay protection, or key ownership. Application contracts MUST add their own nonce/domain separation, exactly as AereHybridAuth (`0xc20390C9656ECe1AE37603c84E395bC898b3FAA1`) and AerePQCAccountFactory (`0xd5315Ea7caa60d320c4f34b1bEd70dd9cc02CE58`) do today. For hash-based schemes with one-time-per-leaf state (XMSS, WOTS+), a valid verification does NOT prove the signer has not reused a leaf; that state enforcement is the application's responsibility and is out of scope for a stateless verify precompile.
**Consensus scope.** These precompiles do not make AERE consensus post-quantum. Validators continue to sign classical secp256k1 QBFT messages. This proposal is strictly an application and account layer capability. Any claim of post-quantum consensus would be false and MUST NOT be made on the basis of this proposal.
**Precompile address hygiene.** The reserved band `0x...0900`-`0x...09FF` is chosen to avoid collision with Ethereum standard precompiles, EIP-2537 (`0x0b`-`0x11`), and RIP-7951 (`0x100`). If upstream Ethereum later allocates into this band, AERE MUST re-evaluate to preserve equivalence, since AERE tracks Ethereum's ruleset (Pectra plus Fusaka today).
## Copyright
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).