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

44 KiB

aip title description author discussions-to status type category created requires
TBD Native Post-Quantum Signature Verification Precompiles (SHAKE256, Falcon-512, Falcon-1024, ML-DSA-44, SLH-DSA-SHA2-128s) 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. AERE Network Foundation https://aere.network/docs.html Draft Standards Track Core 2026-07-11 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 and related rights waived via CC0.