aere-docs/AIP-PQ-TX-2026-07-18.md
Aere Network e4cead319d 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:01:36 +03:00

22 KiB

AIP-8: Post-Quantum-Authorized Transaction Envelope (EIP-2718 Type 0x2A)

Preamble

Field Value
AIP 8
Title Post-Quantum-Authorized Transaction Envelope (EIP-2718 Type 0x2A)
Author AERE Foundation
Type Standards Track
Category Core
Status Draft
Created 2026-07-18
Requires 7
Ratification Foundation-ratified (pre-decentralization)

Abstract

This AIP specifies a new EIP-2718 typed transaction, TransactionType = 0x2A, whose sender authorization is a NIST post-quantum signature (Falcon-512 or ML-DSA-44) rather than a secp256k1 ECDSA signature. The transaction carries a scheme selector, a raw PQC public key, and a PQC signature; the sender address is derived from the public key, and the transaction is valid only if the signature verifies against the exact same native precompiles that are already live on AERE mainnet chain 2800 (Falcon-512 0x0AE1, ML-DSA-44 0x0AE3, activated at block 9,189,161 per AIP-7). It makes ordinary value and contract-call transactions authorizable by a quantum-resistant key with no smart-account indirection. This is a base-layer execution change requiring a coordinated Besu hard fork; it is NOT yet live. It is explicit that this does not change how AERE blocks are proposed, signed, or committed: consensus remains classical secp256k1 ECDSA QBFT and is not post-quantum. A deploy-today reference path that needs no fork (AerePQCTxAccount.sol, an ERC-4337 account) accompanies this AIP.

Motivation

AERE has live, mainnet-proven post-quantum signature verification at the precompile layer (AIP-7) and usable post-quantum authorization at the account layer (AerePQCAccount, AereThresholdAccount, AerePQCSocialRecoveryModule). What is still missing is the most basic object of all: an ordinary externally-originated transaction whose right to spend an account is a post-quantum signature.

Today every transaction on chain 2800, including one that calls a post-quantum contract, is itself authorized by a secp256k1 ECDSA signature. The account's ability to move its native balance rests on ECDSA. A cryptographically-relevant quantum computer running Shor's algorithm breaks secp256k1: it recovers the private key from the public key, and on any account that has ever sent a transaction the public key is already on-chain. Account- layer PQC (a 4337 smart account) closes this for smart-contract wallets, but it cannot protect a plain externally-owned account (EOA), and it adds a bundler/EntryPoint and a deployment step. A base-layer post-quantum transaction type closes the gap for the EOA itself: the account is a hash of a PQC public key, and only a PQC signature can spend it.

The goal is a transaction type that (1) reuses the audited, mainnet-live precompile verify paths verbatim (no new cryptography), (2) is a pure function of the transaction bytes for validation (like ECDSA sender recovery, so mempool validation reads no state), (3) fits far under the Fusaka EIP-7825 per-transaction gas cap, and (4) is a strictly additive, client-only hard fork with no re-genesis, exactly like AIP-7.

Specification

The keywords MUST, SHOULD, and MAY are to be interpreted as in RFC 2119.

Transaction type byte

The transaction type is 0x2A.

EIP-2718 constrains a valid TransactionType to the range [0x00, 0x7f]: a first byte >= 0xc0 is ambiguous with the RLP list header of a legacy transaction and is excluded, and EIP-2718 recommends implementers stay within 0x00..0x7f. Within that range the Ethereum L1 assignments are 0x01 (EIP-2930), 0x02 (EIP-1559), 0x03 (EIP-4844), and 0x04 (EIP-7702); AERE recognizes all four. 0x2A is chosen because it is unassigned by any Ethereum L1 EIP and sits well below the 0x64..0x7f band that existing L2s have informally colonized (Arbitrum 0x64..0x6c, zkSync Era 0x71+, Optimism deposit 0x7e, Celo 0x7b..0x7d), which minimizes the chance that an imported third-party tool misparses an AERE PQ transaction as some other chain's type. 0x2A is reserved henceforth in the AERE chain specification for this purpose. If Ethereum L1 later assigns 0x2A, AERE will re-evaluate to preserve equivalence, as AIP-7 commits to for the precompile band.

Signature-scheme selector

A one-byte pq_scheme selects the NIST scheme and the precompile that validates it. The values match AIP-7 and AerePQCAttestation:

pq_scheme Scheme Precompile Public-key length Signature envelope
0x01 Falcon-512 0x0AE1 897 bytes (header 0x09) `nonce(40)
0x03 ML-DSA-44 0x0AE3 1312 bytes sig, exactly 2420 bytes

pq_scheme 0x02 (Falcon-1024, 0x0AE2, 1793-byte pk) and 0x04 (SLH-DSA-128s, 0x0AE4, 32-byte pk, 7856-byte sig) MAY be enabled by the same rules; they are omitted from the mandatory set because Falcon-512 (NIST level 1, smallest signature) and ML-DSA-44 (FIPS 204, fixed-size, conservative) span the practical trade-off between signature size and margin. Any other value MUST be rejected as an invalid transaction.

RLP wire format

A type-0x2A transaction is the concatenation of the type byte and the RLP encoding of a 12-element list:

0x2A || rlp([
    chain_id,                 # scalar; MUST equal 2800 for AERE mainnet
    nonce,                    # scalar; the PQC-derived sender's account nonce
    max_priority_fee_per_gas, # scalar (EIP-1559 semantics)
    max_fee_per_gas,          # scalar (EIP-1559 semantics)
    gas_limit,                # scalar
    to,                       # 20-byte address, or empty (0x) for contract creation
    value,                    # scalar
    data,                     # byte string
    access_list,              # EIP-2930 [[address, [storageKey, ...]], ...]
    pq_scheme,                # single byte, 0x01 or 0x03 (see selector table)
    pq_public_key,            # raw NIST public key for pq_scheme
    pq_signature              # signature envelope for pq_scheme (see selector table)
])

The fee, gas, to, value, data, and access_list fields are identical in meaning to EIP-1559 (type 0x02) with an EIP-2930 access list, so existing execution and fee-market logic is unchanged. The three trailing fields replace the ECDSA (y_parity, r, s) triple.

The transaction hash is keccak256(0x2A || rlp([...all 12 fields...])), as for any typed transaction.

Sender derivation

The sender is derived from the PQC public key and the scheme, not from signature recovery:

sender = keccak256(pq_scheme || pq_public_key)[12:32]   # last 20 bytes

Binding the address to pq_scheme as well as the key means the same raw key bytes under a different scheme selector yield a different account, so there is no cross-scheme aliasing. A user migrates to post-quantum custody by moving assets to their sender address; from then on only a valid PQC signature can spend it. Unlike ECDSA, the public key is transmitted in full on every transaction (the verify precompiles are stateless), which is safe: Falcon and ML-DSA remain secure with the public key exposed. This is the point of the design, not a weakness.

Signing message and verification

pq_signature MUST be a valid signature, under pq_public_key and pq_scheme, over the 32-byte digest:

sig_hash = keccak256(0x2A || rlp([
    chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit,
    to, value, data, access_list, pq_scheme, pq_public_key
]))                                     # the 12-field list WITHOUT pq_signature

Validation MUST assemble the precompile input for pq_scheme exactly as AerePQCAttestation / AerePQCKeyRegistry do, with sig_hash as the signed message, and accept the transaction only if the corresponding precompile returns the 32-byte "valid" word 0x…01:

  • Falcon-512: input = pk(897) || sm, where sm = sigLen(2, big-endian) || nonce(40) || sig_hash(32) || esig and sigLen == esig.length; call 0x0AE1.
  • ML-DSA-44: input = pk(1312) || sig(2420) || sig_hash(32); call 0x0AE3.

The client MUST use the same audited Bouncy Castle 1.83 verify path the precompile wraps, so that transaction validation and an in-EVM STATICCALL to the precompile agree bit-for-bit on every (pq_public_key, sig_hash, pq_signature) triple. A transaction whose signature does not verify has no valid sender and is invalid: it MUST be rejected from the mempool and MUST NOT be included in a block, exactly as an ECDSA transaction with an unrecoverable signature is.

Nonce and replay handling

  • Cross-chain replay is prevented by chain_id inside both the transaction and sig_hash: a signature for chain 2800 does not verify for any other chain id.
  • Intra-chain replay and ordering are enforced by nonce, the account nonce of the PQC-derived sender, tracked in world state and incremented on inclusion, identical to an ECDSA account. A given (sender, nonce) can be mined at most once.
  • Cross-protocol replay (reusing a PQ tx signature as an AerePQCAttestation, AerePQCKeyRegistry, or ERC-4337 authorization, or vice-versa) is prevented because those surfaces sign structurally different messages: attestation/registry/account challenges are keccak256(abi.encode(DOMAIN, ...)) or abi.encodePacked(domain, hash) byte strings that can never equal the 0x2A || rlp(...) preimage of sig_hash. The leading type byte 0x2A in the signed preimage is the domain tag for this transaction type, mirroring how EIP-1559 signs keccak256(0x02 || rlp(...)).

Gas accounting

Intrinsic gas for a type-0x2A transaction is:

intrinsic = 21000
          + calldata_gas(entire type-0x2A payload, including pq_public_key + pq_signature)
          + access_list_gas          # EIP-2930: 2400 per address, 1900 per storage key
          + PQ_VERIFY_GAS[pq_scheme]  # fork constant, MUST equal the live precompile verify gas

PQ_VERIFY_GAS MUST equal the marginal verify-op gas the corresponding precompile charges, which AIP-7 measured on the pre-activation scratch fork (chain 28099) and carried to mainnet:

Scheme PQ_VERIFY_GAS (measured, AIP-7)
Falcon-512 (0x0AE1) 40,000
ML-DSA-44 (0x0AE3) 55,000
Falcon-1024 (0x0AE2) 75,000
SLH-DSA-128s (0x0AE4) 350,000

The dominant additional cost versus an EIP-1559 transaction is the calldata for the public key and signature. Approximate PQ-material sizes and the resulting calldata component (to be measured on the scratch fork; calldata is priced at EIP-2028 rates, subject to the EIP-7623 floor):

Scheme pk bytes sig-envelope bytes PQ material approx. calldata gas
Falcon-512 897 ~707 (40 + 1 + ~666) ~1,605 B ~24,000 (to be measured)
ML-DSA-44 1,312 2,420 ~3,732 B ~56,000 (to be measured)

A complete Falcon-512 PQ transaction therefore lands near 21,000 + ~24,000 + 40,000 ≈ 85,000 gas of intrinsic cost before execution, consistent with AIP-7's measured end-to-end Falcon-512 verify-and-record transaction of 86,336 gas; a ML-DSA-44 PQ transaction lands near 21,000 + ~56,000 + 55,000 ≈ 132,000. Both are a tiny fraction of the EIP-7825 per-transaction cap of 16,777,216 (2^24), leaving the full block-execution budget for the call itself.

PQ_VERIFY_GAS is folded into intrinsic gas so the sender pays for the verification work that validators perform. Because signature validity is a precondition of transaction validity, a transaction with an invalid signature is never included and thus is never charged on-chain, exactly as with a bad ECDSA signature; it simply cannot be mined.

Base-layer activation sketch (Besu fork)

The change is a strictly additive execution-layer fork of the AERE Besu build (base hyperledger/besu:26.4.0, the same tree that carries the AIP-7 precompiles). No re-genesis, no state migration. The implementation touches, at minimum:

  1. TransactionType. Add PQ_AUTHORIZED((byte) 0x2A) to org.hyperledger.besu.datatypes.TransactionType.
  2. Codec. Add a PQAuthorizedTransactionDecoder / PQAuthorizedTransactionEncoder implementing the 12-field RLP layout above and register them in the TransactionDecoder / TransactionEncoder dispatch. Extend the Transaction model to carry pqScheme, pqPublicKey, and pqSignature, and override sender computation for this type to keccak256(pqScheme || pqPublicKey)[12:], defined only when the PQC signature verifies.
  3. PQ signature verifier. A PQAuthorizedSignatureVerifier that calls the exact same Bouncy Castle 1.83 BCPQC verify path (AereFalconSupport.verify, ML-DSA verifyInternal) the 0x0AE1 / 0x0AE3 precompiles wrap. Zero new cryptography; reuse the precompile adapters through the same classloader-isolation boundary AIP-7 established.
  4. Validation. In MainnetTransactionValidator (via TransactionValidatorFactory), for type 0x2A: reconstruct sig_hash, verify the PQC signature (reject with an INVALID reason on failure), derive the sender, and validate nonce, balance, and intrinsic gas as usual.
  5. Gas. Extend the GasCalculator with pqAuthorizationIntrinsicGasCost(scheme, payload) returning the calldata component plus PQ_VERIFY_GAS[scheme], wired into transactionIntrinsicGasCost.
  6. Mempool and wire. Teach TransactionPool and the eth/68 Transactions / PooledTransactions codecs to carry the larger typed body; expose the type in the JSON-RPC transaction serialization (eth_getTransactionByHash, receipts).
  7. Fork gating. Add an AerePQTx milestone to ProtocolScheduleBuilder / the chain spec, activated at a future block or timestamp distinct from the AIP-7 precompile activation. Before activation, a 0x2A first byte is an unknown type and is rejected; after activation it is valid. Because chain 2800 runs seven QBFT validators under one operator on one client, activation is a flag-day client upgrade across the validator set, exactly as in AIP-7. A non-upgraded validator would reject the type and fork off, so the upgrade is mandatory at the fork block.
  8. Differential testing. Gate ship on a test that, for the NIST KAT vectors in pqc-fork/kat and randomized cases, asserts the transaction-validation verify agrees bit-for-bit with a STATICCALL to the matching precompile over the same (pk, sig_hash, sig), plus round-trip encode/decode and a full mine-and-receipt path on an isolated testnet.

Consensus is untouched: QBFT proposal, the committed-seal ECDSA signatures, and block structure are unchanged; type 0x2A is simply another transaction a block may contain, with its own receipt type.

Rationale

Full public key in every transaction, not a registry keyId. An alternative shrinks calldata by replacing pq_public_key with a keyId into AerePQCKeyRegistry (0x1eCa…3691). It is rejected for the base type because it makes transaction validation depend on world state (a registry lookup), which breaks the property that a transaction's validity is a pure function of its own bytes, the same property ECDSA sender recovery has, and which mempool validation relies on. The stateless full-key form keeps validation pure and matches how the live precompiles already work. A registry-indexed companion type could be specified later once the state-dependency implications for the mempool are worked through.

Sender = hash of (scheme, key), reusing the 20-byte address space. This keeps the PQ account indistinguishable from any other address to every downstream contract, tool, and explorer: balances, CALL, msg.sender, and logs all behave normally. The only thing that changes is who may originate a transaction from that address.

Type 0x2A over a higher value. Staying in the low half of the valid range and away from the L2-colonized 0x64..0x7f band reduces cross-ecosystem tool confusion, at no cost.

A base-layer type at all, given 4337 exists. ERC-4337 already gives post-quantum authorization for smart-contract wallets and is the recommended path today (see the reference implementation). But a 4337 account cannot protect a plain EOA, requires a deployed contract and a bundler, and pays EVM-execution overhead for validation. The base type is the only way to make an ordinary account post-quantum, and it prices verification at the native precompile cost rather than in-EVM. The trade-off is that it requires a hard fork and touches the entire transaction-decoding surface of the ecosystem, which is exactly why it is filed as a Draft with a shipping 4337 alternative rather than activated immediately.

Backwards Compatibility

Additive. Existing legacy, 0x01, 0x02, 0x03, and 0x04 transactions are unaffected. Before the AerePQTx fork block, a 0x2A transaction is an unknown type and is rejected by all nodes; after it, all nodes accept it. There is no change to existing accounts, contracts, receipts of other types, or the genesis state root.

Ecosystem tooling that decodes raw transactions (wallets, explorers, indexers, the RPC transaction serializer, hardware-wallet firmware, signing libraries) MUST learn type 0x2A to build, sign, display, or index these transactions. Tools that only handle types they recognize will treat a 0x2A transaction as opaque or unsupported rather than misdecode it, because the type byte is self-describing. This ecosystem surface is the principal cost of the change and the reason the ERC-4337 path is preferred for immediate use.

Because AERE runs all validators under one operator and one client, activation is a mandatory flag-day client upgrade across the validator set at the fork block, as in AIP-7. A validator that had not upgraded would reject the type and fork.

Security Considerations

This change does not make AERE consensus post-quantum. Validators continue to sign classical secp256k1 QBFT committed seals; blocks are proposed and committed exactly as today. Type 0x2A makes an individual transaction's sender authorization post-quantum. It does not make block production, finality, or the validator set post-quantum, and any claim to that effect on the basis of this AIP would be false and must not be made.

A valid signature is not authorization beyond spend rights. A "valid" precompile decision establishes only that pq_signature is a valid signature by pq_public_key over sig_hash. Freshness and ordering come from nonce; scope comes from the tx fields themselves. There is no key rotation or recovery at this layer: losing the PQC private key loses the account, the same as losing an ECDSA key. Users who need rotation or social recovery should hold assets in an ERC-7579 account guarded by AerePQCSocialRecoveryModule instead of, or in addition to, a PQ EOA.

Single operator, one client (no client-diversity safety net). Chain 2800's seven QBFT validators are one operator on one execution client. A consensus bug in the PQ-transaction validation path (for example a decoder edge case, or a divergence between the tx-validation verify and the precompile verify) would not be caught by a second live client, because there is none. This raises the bar for external audit and for the mandatory bit-for-bit differential testing in the activation sketch.

Determinism. Transaction validation, like a precompile, must be deterministic and read no node-local state or block context beyond the transaction and the account nonce/balance. Falcon verification MUST use the integer-only verify path (never the floating-point signing path). Malformed structure MUST be rejected deterministically as an invalid transaction, not faulted.

Public-key exposure is intended. The public key is on-chain from the account's first transaction. This is safe for Falcon and ML-DSA and is the mechanism by which the account is quantum-resistant; it is called out only to preempt the ECDSA-era intuition that an unspent address "hides" its key.

Not live; external audit pending. This is a Draft specification and a reference- implementation sketch. Nothing here is deployed on chain 2800. The precompiles it reuses carry an internal self-audit only (AIP-7). This transaction type MUST NOT secure material value until it is implemented, differentially tested against the live precompiles, and covered by an external security audit.

Reference Implementation and On-Chain Deployment

Not yet live. The base-layer fork above is a specification and a Besu implementation sketch, not a deployed change.

Deploy-today reference path (no fork required). The same user-visible guarantee, "a transaction is authorized by a post-quantum signature and nothing else", is delivered today, with no base-layer change, by an ERC-4337 v0.7 smart account whose sole owner is a NIST PQC key and whose validateUserOp verifies via the live precompiles:

  • Contract: contracts/contracts/pqc/AerePQCTxAccount.sol (this repository).
  • Tests: contracts/test/AerePQCTxAccount.test.js (MockPQCPrecompile across all four schemes plus a genuine Falcon-512 path through the NIST-KAT-proven AereFalcon512Verifier).
  • Verified 2026-07-18: compiled and tested under the canonical Hardhat toolchain, npx hardhat compile then npx hardhat test test/AerePQCTxAccount.test.js. The contract compiles clean and all 20 tests pass: the mock verifier across all four schemes, the domain-separation and single-use replay checks, and two genuine Falcon-512 paths through the NIST-KAT-proven verifier. This bullet records an actual machine run; it should be read as a verified result, not an untested present-tense assertion.
  • It calls the live precompiles (0x0AE1..0x0AE4) directly with the identical wire encoding proven on mainnet by AerePQCAttestation (0x465d9E3b476BF98Aa1393079e240Db5D2a9bEA6A) and AerePQCKeyRegistry (0x1eCa3c5ADcBD0b22636D8672b00faC6D89363691), and targets AereEntryPointV2 (0x8D6f40598d552fF0Cb358b6012cF4227B86aF770). Addresses are copied verbatim from sdk-js/src/addresses.ts.

The precompile band, its activation block (9,189,161), and the wire encoding are verifiable on https://rpc.aere.network (chain id 2800) as documented in AIP-7.

Released to the public domain (CC0). No rights reserved.