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>
344 lines
21 KiB
Markdown
344 lines
21 KiB
Markdown
# 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:
|
|
|
|
1. 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);
|
|
2. `alg_id` is `0x01`, `0x02` or `0x03` and the key and signature lengths match the table;
|
|
3. the ML-DSA verification above succeeds;
|
|
4. `chain_id` equals the chain identifier of the network;
|
|
5. with `sender` derived as above, the nonce, balance, gas and fee-market rules of type `0x02`
|
|
apply unchanged (`to` may be empty: contract creation is allowed, and the created address is
|
|
`keccak256(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 `k` would be; nonce rules make at most one includable.
|
|
- **Replay.** `chain_id` is 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:
|
|
|
|
1. Besu-derived client (the Aere overlay): `TransactionType.AERE_PQ (0x50)`, a
|
|
`PqTransactionDecoder` / `PqTransactionEncoder` registered in `TransactionDecoder` and
|
|
`TransactionEncoder`, a post-quantum authorization on `Transaction` with sender derivation from
|
|
the public key, activation gating and ML-DSA verification in `MainnetTransactionValidator`,
|
|
intrinsic gas in the gas calculator, receipt and JSON-RPC result mappings, pool admission.
|
|
2. Nethermind-derived client: the same surface (`TxType`, decoder, validator, intrinsic gas,
|
|
signer abstraction, RPC models), with the decoder widened before any emission exists.
|
|
3. Reference tooling `tools/pqtx/` (key generation, signing, sending) on `@noble/post-quantum`, and
|
|
the conformance vectors `vectors/pqtx/`.
|
|
4. 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).
|
|
5. 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.
|