AIP-22 described chain 2800 "today" as the chain before stages 1 to 3. A note dated 2026-09-30 says which stages are active from which block; the index row and the README summary carry the same heights. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 KiB
AIP-20: Native Post-Quantum Transactions (Type 0x50, ML-DSA Authorization)
Note added 2026-09-30. Where this document says that consensus binds every 128th block under a hybrid certificate, it describes chain 2800 from 2026-09-05 to 2026-09-29. Since block 20,746,736 (AIP-22 stage 2) a Falcon-512 certificate is carried every 32nd block and SLH-DSA seals every 128th, with at least seven of ten seals per scheme since block 20,715,632 (AIP-22 stage 1).
Preamble
| Field | Value |
|---|---|
| AIP | 20 |
| Title | Native Post-Quantum Transactions (Type 0x50, ML-DSA Authorization) |
| Author | Aere Network Foundation |
| Type | Standards Track |
| Category | Core |
| Status | Draft (specification only at the Created date, when no implementation existed; implemented in both clients and proven conformant 2026-09-17; live on testnet 28001 from block 2,212,000; on chain 2800 deployed on every node 2026-09-22 and armed for block 19,900,000, see the dated records at the end) |
| Created | 2026-09-17 |
| Requires | None (the ML-DSA-44 verify precompile at 0x0AE3 is reused only for conformance vectors, not on the transaction path) |
| Supersedes | None |
| Superseded-By | None |
| Ratification | Not ratified; testnet-gated (chain 28001 first), second-client-gated (both clients must pass the same vectors), founder gated for chain 2800 |
Abstract
This AIP introduces an EIP-2718 typed transaction, type 0x50, whose authorization is a
FIPS 204 ML-DSA signature instead of a secp256k1 ECDSA signature. The parameter set is
selected per transaction by an algorithm identifier (ML-DSA-44, ML-DSA-65 or ML-DSA-87), the
sender address is derived from the post-quantum public key, and no secp256k1 operation appears
anywhere in the validation of such a transaction. Accounts created this way hold no key that a
cryptographically relevant quantum computer could recover. The type is activated by block height,
uniformly on every node that validates or reads blocks, on the public testnet 28001 first and on
chain 2800 only through a founder-signed acceptance of this AIP (AIP-19 process).
Motivation
Every externally owned account on chain 2800 is authorized today by a secp256k1 ECDSA signature, whose security rests on the discrete logarithm problem, broken by Shor's algorithm. The chain's consensus already requires post-quantum seals on every QBFT message (SPEC section 2.6, in force since block 17,700,000) and binds every 128th block under a hybrid Falcon-512 + SLH-DSA-SHA2-128s certificate; the accounts of users are the remaining classical layer.
Post-quantum contract accounts exist on 2800 (AerePQCTxAccount, HybridAuthorizer, the ERC-4337
threshold account), but they are second-class: the transaction that carries the post-quantum
authorization is itself an ECDSA transaction from a relayer, gas is paid by that relayer, and every
wallet needs a bundler. A native type makes a post-quantum account a first-class account: it pays
its own gas, appears in eth_getTransactionByHash like any other, and needs no classical key at
all. The Foundation's stated goal for this change is to move the network past ECDSA, not to add a
post-quantum option beside it.
Specification
Transaction envelope
TransactionType = 0x50
TransactionPayload = rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit,
to, value, data, access_list, alg_id, public_key, signature])
All fields up to and including access_list have the semantics of EIP-1559 (type 0x02). The
three new fields are:
| Field | Type | Meaning |
|---|---|---|
alg_id |
uint8 | the signature algorithm and parameter set, table below |
public_key |
bytes | the raw ML-DSA public key, exact length for alg_id |
signature |
bytes | the raw ML-DSA signature, exact length for alg_id |
alg_id |
Algorithm | Public key | Signature | NIST category | Status in this AIP |
|---|---|---|---|---|---|
0x01 |
ML-DSA-44 (FIPS 204) | 1,312 B | 2,420 B | 2 | valid |
0x02 |
ML-DSA-65 (FIPS 204) | 1,952 B | 3,309 B | 3 | valid, recommended default |
0x03 |
ML-DSA-87 (FIPS 204) | 2,592 B | 4,627 B | 5 | valid, highest security |
0x10 |
SLH-DSA-SHA2-128s (FIPS 205) | 32 B | 7,856 B | 1 | reserved for a later AIP |
0x20 |
Falcon-512 (FN-DSA, FIPS 206 draft) | 897 B | variable | 1 | reserved for a later AIP |
0x80 |
hybrid flag (classical + post-quantum, both required) | reserved for a later AIP |
Any other alg_id, and any public_key or signature whose length is not exactly the length
of the table, makes the transaction invalid.
Sender address
sender = keccak256(0x50 || alg_id || public_key)[12:32]
The type byte and the algorithm identifier are part of the pre-image so that the same key material under a different algorithm, or a future type reusing ML-DSA keys, cannot claim the same address.
Signing hash and signature
signing_hash = keccak256(0x50 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas,
gas_limit, to, value, data, access_list, alg_id, public_key]))
signature = ML-DSA.Sign(secret_key, signing_hash, ctx = "")
Signing uses the FIPS 204 "pure" interface (Algorithm 2, ML-DSA.Sign) over the 32-byte
signing_hash with the empty context string. Both deterministic and hedged signing are
acceptable; verification is identical. The pre-hash variant (HashML-DSA) is not used.
A transaction is valid only if ML-DSA.Verify(public_key, signing_hash, signature, ctx = "")
returns true for the parameter set of alg_id.
Transaction hash
As for every typed transaction: keccak256(0x50 || TransactionPayload). The signature bytes are
part of the hash; two different valid signatures over the same payload are two transactions with
the same nonce, of which at most one can be included.
Validity rules
A block is valid only if every type-0x50 transaction it contains satisfies all of:
- the block number is at or above the per-node activation height
aere.pqtx.forkBlock, set uniformly on every node (before that height the type is invalid in the transaction pool and in blocks, so an un-upgraded fleet and an upgraded one behave identically below the height); alg_idis0x01,0x02or0x03and the key and signature lengths match the table;- the ML-DSA verification above succeeds;
chain_idequals the chain identifier of the network;- with
senderderived as above, the nonce, balance, gas and fee-market rules of type0x02apply unchanged (tomay be empty: contract creation is allowed, and the created address iskeccak256(rlp([sender, nonce]))[12:32]as usual).
Gas
intrinsic_gas = 21,000
+ calldata_gas(data) (the calldata pricing active at that block)
+ access_list_gas(access_list)
+ 16 * (len(public_key) + len(signature))
+ PQ_VERIFY_GAS[alg_id]
The authorization bytes are transported in every block like calldata and are charged at the
non-zero calldata rate. PQ_VERIFY_GAS is the cost of the signature verification itself:
alg_id |
PQ_VERIFY_GAS (Draft values) |
|---|---|
| ML-DSA-44 | 55,000 (equal to the precompile 0x0AE3) |
| ML-DSA-65 | 75,000 |
| ML-DSA-87 | 100,000 |
The Draft values are to be re-measured on the validator hardware, against ecrecover as the
reference, before this AIP leaves Draft; the measured table is part of the acceptance record.
Worked example, ML-DSA-65 transfer with empty calldata: 21,000 + 16 x 5,261 + 75,000 = 180,176
gas, that is about 8.6 times an ECDSA transfer.
Receipts and JSON-RPC
The receipt uses the EIP-2718 envelope with type byte 0x50 and is otherwise a type-0x02
receipt. eth_getTransactionByHash, eth_getTransactionByBlockNumberAndIndex and the block
methods return "type": "0x50", the fields algId (hex quantity), pqPublicKey and
pqSignature (hex data), and from as the derived sender; the fields v, r, s and yParity
are absent. eth_sendRawTransaction accepts the envelope. eth_signTransaction and eth_sign do
not support the type: keys are held by the client library, never by the node.
Networking
The transaction is exchanged as an opaque typed transaction in eth/68 Transactions,
NewPooledTransactionHashes and PooledTransactions messages, exactly as types 0x01 to 0x04.
A peer that does not implement the type drops it, which is one reason the activation height is
set on every node of the network, readers included, before it is reached.
Size
The largest authorization (ML-DSA-87) is 7,219 bytes; transactions remain subject to the existing maximum encoded size of the pool and of the network layer.
Migration
An account holding funds under an ECDSA key moves them to a post-quantum account with an ordinary
transfer to the derived address; nothing is converted automatically and no existing address
changes meaning. Contract-level migration helpers (AereAccountMigrator) may be used but are not
required.
Cross-client determinism
Both clients of chain 2800 (the Besu-derived client and the Nethermind-derived client) MUST derive
the sender, compute the signing hash, charge gas and accept or reject a type-0x50 transaction
identically. Conformance vectors (valid transactions for each alg_id, and invalid ones: wrong
length, wrong chain id, corrupted signature, wrong alg_id, below the activation height) are
published with the reference implementation and both clients must pass all of them before the
type is activated anywhere.
Rationale
Native instead of account abstraction. ERC-4337 accounts on 2800 already verify post-quantum signatures, but the outer transaction is ECDSA-signed by a relayer and the account cannot pay for itself. A native type removes the relayer, the bundler and the last classical signature from the path of a post-quantum user.
ML-DSA first. FIPS 204 is final (August 2024), is the NIST primary signature standard, and has
maintained implementations in the three ecosystems this network runs on: JavaScript
(@noble/post-quantum, already a dependency of this repository), Java (Bouncy Castle) and .NET.
Its three parameter sets give users a choice between size and security category; ML-DSA-65 is the
recommended default and ML-DSA-87 the highest category NIST defines. SLH-DSA (hash-based, the most
conservative assumption set) and Falcon-512 (the smallest signatures, and the scheme this
network's validators use for consensus) are reserved identifiers so that they can be added by a
later AIP without a new transaction type: this is the same crypto-agility principle the on-chain
CryptoRegistry follows.
Hash-then-sign. ML-DSA can sign arbitrary-length messages, but signing the 32-byte
signing_hash keeps the wallet flow identical to the existing typed transactions (the signer
never needs the full transaction), keeps hardware signers viable, and reuses the domain separation
that the type byte already provides.
No hybrid classical + post-quantum in v1. A mandatory ECDSA co-signature would keep exactly the
dependency this AIP removes. The 0x80 flag is reserved for users who want both signatures for a
transition period; it is a separate decision with its own AIP.
Address includes type and algorithm. It costs nothing and closes the class of "same bytes, different scheme" confusions permanently.
Backwards Compatibility
Purely additive for existing transaction types: nothing about types 0x00 to 0x04 changes. A
node without this implementation rejects any block containing a type-0x50 transaction, so the
activation is a coordinated fork: the same binary and the same aere.pqtx.forkBlock on every
validator, every public read node, the archive and the second client, before the height is
reached. Below the height every node, upgraded or not, rejects the type, so setting the parameter
early is safe.
Security Considerations
- Quantum threat model. An account authorized only by ML-DSA has no key recoverable by Shor's algorithm. Consensus messages are post-quantum enforced (SPEC 2.6). Node-to-node transport (RLPx) remains classical; it authenticates peers, not funds, and is out of scope here.
- Denial of service. ML-DSA verification is slower than ECDSA recovery. The cost is paid in gas
before execution, the pool verifies before storing, and pool admission is subject to the existing
per-peer limits; the verification cost is bounded and constant per
alg_id. - Transaction size. Up to 7,219 bytes of authorization per transaction; pool and block size accounting count them like calldata (the gas charge above makes the economics explicit).
- Signature non-uniqueness. Hedged ML-DSA signing yields different signatures for the same
message; each is a distinct transaction hash with the same nonce, exactly as two ECDSA signatures
with different nonces
kwould be; nonce rules make at most one includable. - Replay.
chain_idis in the signing pre-image; the same key yields the same address on 28001 and 2800, as with ECDSA, and the chain id prevents cross-chain replay. - Implementation divergence. Two clients, two ML-DSA implementations. The conformance vectors (FIPS 204 known-answer tests plus the transaction vectors of this AIP) and a differential test that feeds both clients the same blocks are acceptance conditions, not optional extras.
- Key generation. Clients generating ML-DSA keys MUST use a cryptographically secure random source; the reference tooling uses the platform CSPRNG.
Reference Implementation and On-Chain Deployment
Nothing described here exists at the Created date. This section is updated, dated, as each artifact lands.
Planned artifacts, in order:
- Besu-derived client (the Aere overlay):
TransactionType.AERE_PQ (0x50), aPqTransactionDecoder/PqTransactionEncoderregistered inTransactionDecoderandTransactionEncoder, a post-quantum authorization onTransactionwith sender derivation from the public key, activation gating and ML-DSA verification inMainnetTransactionValidator, intrinsic gas in the gas calculator, receipt and JSON-RPC result mappings, pool admission. - Nethermind-derived client: the same surface (
TxType, decoder, validator, intrinsic gas, signer abstraction, RPC models), with the decoder widened before any emission exists. - Reference tooling
tools/pqtx/(key generation, signing, sending) on@noble/post-quantum, and the conformance vectorsvectors/pqtx/. - A conformance gate that runs the vectors through both clients and fails on any disagreement, with negative controls (each invalid vector must be rejected by both).
- Activation on the public testnet 28001 by height, with the second client validating; then the founder-signed acceptance and the activation height on chain 2800.
2026-09-17, artifacts 1 and 3 exist; nothing is deployed. The Besu-derived implementation is
delivered as an exact delta over the production tree,
consensus-pqc/arbore-complet-2026-08-01/petice-aip20/ (17 unified patches, 6 new files, applier
APLICA-AIP20.sh with -F0), proven to contain nothing but this AIP by reproducing the tree byte
for byte from the scripted edits; its conformance test (AerePqTransactionTest, 8 tests) passed
8 of 8 on the 14 vectors in vectors/pqtx/ produced by tools/pqtx/pqtx.mjs on
@noble/post-quantum, so a JavaScript ML-DSA signature verifies under Bouncy Castle Java and every
negative vector is refused for its stated reason. Activation stays absent (aere.pqtx.forkBlock
unset means never). Not measured yet: a full distribution built with the delta, and the import of a
block carrying a type-0x50 transaction on a network; that is the testnet step (artifacts 4 and 5).
Artifact 2 (the Nethermind-derived client) exists as an anchored, idempotent patch set
(nethermind-pqc/nethermind-intree/patches/aip20-tranzactii-pq.sh: 8 new files, 16 anchored edits,
the same 14 vectors under the test data); its conformance test (AereAip20PqTransactionProofTests,
9 tests) passed 9 of 9 on 2026-09-17 with Bouncy Castle C# ML-DSA, so both clients now derive the
same sender and hash, charge the same gas (180,176 for the ML-DSA-65 example) and refuse each negative
vector for its stated reason. The first run refused the corrupted ML-DSA-87 vector for gas instead of
for the signature: the reference tool gave negative vectors a flat gas limit; fixed and the vectors
regenerated the same day. Artifact 4, the two-client proof with a negative control in each client,
is aips/aip20-conformitate/DOVEDESTE.sh, recorded in ULTIMA.txt and watched by the fact
F-AIP20-CONFORM; first recorded 2026-09-17 19:51Z: CONFORM (Besu 8 of 8, Nethermind 9 of 9, the
planted "always valid" verification turned the corrupted-signature test red in both).
2026-09-17, 21:14Z: artifact 5, first half, exists: the type is live on the public testnet 28001.
All six nodes (four Besu validators and the public reader on the delta build wt6ae52adb, the
Nethermind validator on a binary built from the anchored patch set) were armed by height, H =
2,212,000, with the same transaction refused by every node before H (Invalid transaction type on an
armed Besu node, Invalid params on an unarmed one) and the same transaction included at block
2,212,032 after H (ML-DSA-65, status 0x1, 183,176 gas), returned identically by
eth_getTransactionByHash on both clients, the sender having no secp256k1 key at all. Under load,
50 further transactions from the same key were accepted and included 50 of 50 across 25 blocks at an
unchanged block rate. The negative controls after H found one gap: the Nethermind node imported and
served type-0x50 transactions but refused every submission through its own RPC with the wallet
error -32020, because its sealer read "no ECDSA signature" as "sign it with the node's wallet"; a
17th anchored edit (TxSealer.cs) and a tenth test fixed it, the rebuilt binary was switched in on
the testnet at 21:29Z, and through Nethermind's RPC the corrupted signature is now refused as
InvalidTxSignature, the wrong chain as InvalidTxChainId, and a valid transaction was accepted and
included at block 2,213,784 as seen by both clients. Record: aips/aip20-conformitate/TESTNET-28001-2026-09-17.md.
Measured at 21:36Z the same day: ten type-0x50 transactions submitted through the Nethermind
validator's RPC were all included in block 2,214,337, a block proposed by that Nethermind validator
itself and accepted by the Besu validators, so both clients receive, relay, propose and execute the
type in both directions. The second half of artifact 5, the founder-signed acceptance and the activation
height on chain 2800, is not done and is not the author's to do.
2026-09-22, 21:45Z: artifact 5, second half, armed on chain 2800; activation height H = 19,900,000. Under the
founder's general mandate of 2026-09-22 (given in chat; the AIP-19 signed acceptance is still to be produced, so the
ratification row above stays as it is), every node that validates or reads blocks on chain 2800 runs the same binary that
carries this type (distribution wt3f12ef0d, proven to be the live binary plus exactly AIP-20 and AIP-21, see AIP-21's
record of the same day) with the same aere.pqtx.forkBlock=19900000 (Nethermind-derived client:
AERE_PQTX_FORK_BLOCK=19900000): nine Besu-derived validators, the Nethermind-derived validator, the two public readers,
and the archive unit (stopped for disk, prepared for when it restarts). Before H the binary behaves exactly as the old
one: a type-0x50 vector for chain 28001 sent through the public door is refused with Wrong chainId (a binary that knows
the type), where the old binary answered Invalid params. What is NOT yet measured on chain 2800: a block carrying a
type-0x50 transaction imported by every node after H; that is the next record, with the first such transaction.
2026-09-23, 23:03Z: the type is live on chain 2800; the first native post-quantum transaction, imported identically by every
node. H = 19,900,000 passed at about 23:03Z. Negative control first, through both clients: the same transaction with one bit of
its ML-DSA-65 signature changed was refused by the public door (Besu-derived, Invalid signature) and by the Nethermind-derived
validator's own RPC (InvalidTxSignature: AIP-20 post-quantum signature does not verify under the carried public key), each for
the signature and not for any other reason. Then one valid transaction, sent once, to its own sender with value 0 (a test key
funded for this purpose; its secret never appeared on a command line): hash
0x7cb5bdaceb475a6fee881c6a68aadf69734e78c1fd5648865c4fdde243d602e5, included in block 19,900,028 with status 1 and 180,176
gas. That block has the same hash, and carries the transaction as type 0x50, on each of the nine Besu-derived validators, the
Nethermind-derived validator, both public readers, and a node on the laptop built from the published recipe. Not measured here: a
type-0x50 transaction proposed by the Nethermind-derived validator on chain 2800 (it was measured on testnet 28001, above).
Errata
None.
Post-Acceptance Outcome Record
Not accepted; nothing to record.
Copyright
Released to the public domain (CC0). No rights reserved.