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

387 lines
22 KiB
Markdown

# 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) || esig` (esig starts `0x29`) |
| `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.
## Copyright
Released to the public domain (CC0). No rights reserved.