aere-research/aips/AIP-20.md

19 KiB

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.

Errata

None.

Post-Acceptance Outcome Record

Not accepted; nothing to record.

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