# AIP-20: Native Post-Quantum Transactions (Type 0x50, ML-DSA Authorization) ## 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.