This EIP adds a post-quantum public key registry to the beacon state. Each validator registers one hash-based public key, bound to its existing validator index: a leanXMSS key, used for every message the validator signs once duties switch to it.
The leanXMSS key is a two-layer tree: a top tree of 2**16 one-time keys, each of which certifies a bottom tree of 2**16 one-time keys, for a lifetime of 2**32 leaf indices. No step of key generation handles more than 2**16 one-time keys. Every signature carries its leaf index, and the signer chooses how leaf indices are allocated to messages, provided no leaf index signs two different messages. All hashing uses Keccak-256.
Registration is a new beacon block operation carrying the public key and a proof of possession under it. It is authorized either by the validator's current BLS signing key (register-only) or by the validator's withdrawal address through a new EIP-7685 request (register or replace). The registered keys are dormant: no duty is authenticated with them in this fork. A later fork switches duties to them. For that fork, the registry provides the fraction of stake with registered keys, and a post-quantum public key for every registered validator.
Every consensus-layer signature today is a BLS12-381 signature: attestations, block proposals, RANDAO reveals, aggregation selection proofs, sync committee messages, payload attestations, inclusion lists, proposer preferences, voluntary exits, and the deposit proof of possession. BLS relies on the hardness of discrete logarithms in pairing-friendly groups, which Shor's algorithm breaks. A cryptographically relevant quantum computer (CRQC) recovers a validator's secret key from its public key, and every validator's public key is in the beacon state.
BLS therefore has to be replaced, and this EIP splits the replacement into two forks. Generating a post-quantum key involves the validator's secret key material, requires new tooling, and, for staking pools, requires coordination between node operators and stake owners. If this work happens at the fork that switches the signature scheme, the number of ready validators is only known at that fork. If a CRQC appears before the switch, no post-quantum key exists to fall back on, and any message signed with BLS after that point may be forged.
A separate registry fork has the following benefits:
Each validator registers one leanXMSS key for all of its messages. The Rationale explains this choice and the two ways a signer can allocate leaf indices.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
The consensus-layer changes apply to the consensus specifications (ethereum/consensus-specs) on top of Heze. The execution-layer changes apply on top of the execution-layer fork paired with it. The fork that activates this EIP is assigned by the relevant meta EIP. Functions and constants not defined here keep their meaning from those specifications.
All post-quantum hashing in this EIP uses keccak256, the Keccak-256 hash function of the execution layer, in the cell encoding of leanVM. The input is split into 128-byte chunks, the last one possibly shorter, and consecutive chunks are joined by the 8 bytes CELL_GAP. Inputs of at most 128 bytes are therefore hashed unchanged. On longer inputs, each 136-byte Keccak-256 block carries 128 input bytes, which keeps the 16-byte cells of the leanVM verifier aligned.
public_param is a 16-byte per-key public parameter. The address names the hash call within the key:
Address types 0, 5 and 7 are used only by the signer, to derive the chain secrets, the public parameter, and the randomizer.
leanXMSS is a two-layer XMSS (RFC 8391) over target-sum Winternitz one-time keys (WOTS+C), in the generalized XMSS framework1, as specified by the leanXMSS specification of leanVM. Its parameters are:
| Name | Value | Description |
|---|---|---|
XMSS_CHAINS | 58 | Hash chains per one-time key, each with positions 0 to 3 |
XMSS_TARGET_SUM | 106 | Sum of the digits of every codeword |
XMSS_TREE_HEIGHT | 16 | Height of the trees of each layer |
| digest, public parameter, randomizer | 16 bytes each | Size of every chain value, Merkle node, P and rho |
| message | 32 bytes | A message hash, here an SSZ signing root |
The key has two layers:
layer = 0) holds 2**32 one-time keys, grouped into 2**16 bottom trees of height 16. One-time key i belongs to bottom tree i >> 16. Node indices on the bottom layer are global across its trees.layer = 1) is one tree of height 16 over 2**16 one-time keys. Top key t signs the 16-byte root of bottom tree t, and nothing else.P: 32 bytes.A signature at leaf index i carries i, the bottom one-time signature of the message by bottom key i with its path, and the top one-time signature of the root of bottom tree i >> 16 by top key i >> 16 with its path.
The secret key is a 32-byte seed. Key generation, signing, and the derivation of P and of the randomizer are specified by leanXMSS and are not consensus-critical. Signing is deterministic: signing the same message at the same leaf index twice produces a byte-identical signature. Finding a codeword takes about 2**20 randomizer trials per signature.
| Name | Value | Description |
|---|---|---|
DOMAIN_PQ_KEY_REGISTRATION | DomainType('0x12000000') | BLS authorization of a registration by the validator's signing key |
DOMAIN_PQ_KEY_POSSESSION | DomainType('0x13000000') | Proof of possession under the registered leanXMSS key |
Both domains are computed against the genesis fork version, as for BLSToExecutionChange, so that registrations remain valid across forks.
| Name | Value | Description |
|---|---|---|
PQ_KEYS_VERSION_UNSET | Uint8(0) | Marks a validator with no registered keys |
PQ_KEYS_VERSION_1 | Uint8(1) | Two-layer leanXMSS over Keccak-256, as specified above |
PQ_POSSESSION_XMSS_INDEX | Uint32(0) | Leaf index at which the proof of possession is signed |
PQ_KEY_AUTHORIZATION_REQUEST_TYPE | Bytes1('0x06') | EIP-7685 request type of a key authorization |
The request type value is provisional and MUST be unique across all active EIP-7685 request types. Allocation is coordinated in the consensus specifications.
| Name | Value | Description |
|---|---|---|
MAX_PQ_KEY_REGISTRATIONS | Uint64(2**3) (= 8) | Maximum registrations per beacon block |
MAX_PQ_KEY_AUTHORIZATION_REQUESTS_PER_PAYLOAD | Uint64(2**4) (= 16) | Maximum key authorization requests per execution payload |
PQ_KEY_AUTHORIZATION_EXPIRY | Epoch(2**8) (= 256) | Epochs during which an owner authorization can be consumed |
| Name | SSZ equivalent | Description |
|---|---|---|
Hash16 | ByteVector[16] | A Keccak-256 output truncated to 16 bytes: a chain value, a Merkle node, a randomizer, or the public parameter P |
XMSSPublicKey | ByteVector[32] | Top-tree root followed by the public parameter |
The SSZ serialization of an XMSSSignature is exactly the leanXMSS signature: the leaf index, then the bottom layer, then the top layer.
The lists are progressive (EIP-7916), following the convention of the Gloas and Heze state.
BeaconBlockBody gains one field, appended after the existing fields:
BeaconState gains two fields, appended after the existing fields. The Validator container is unchanged.
ExecutionRequests gains one field, appended after the existing fields:
get_execution_requests_list appends (PQ_KEY_AUTHORIZATION_REQUEST_TYPE, execution_requests.pq_key_authorizations) to its list of typed requests.
No state transition in this EIP uses get_pq_registered_balance. It is the readiness signal against which the signature switch is scheduled, and clients SHOULD expose it.
Clients are expected to maintain a map from registered keys to validator indices, as they do for BLS public keys.
add_validator_to_registryadd_validator_to_registry appends an unset entry to pq_public_keys, preserving the invariant len(state.pq_public_keys) == len(state.validators):
PQPublicKeys() is the all-zero entry, whose version is PQ_KEYS_VERSION_UNSET.
process_operationsprocess_pq_key_registrationThe two authorization paths differ in what they may do:
On both paths, the operation carries the full key and proof, can be submitted by anyone, and is verified in the same way.
get_pq_key_authorization_position and remove_pq_key_authorizationapply_parent_execution_payloadapply_parent_execution_payload processes the key authorization requests of the parent payload after the existing requests:
process_pq_key_authorization_requestAs with other execution-layer requests, an invalid request is ignored and does not invalidate the block.
process_pending_pq_key_authorizationsCalled from process_epoch after process_registry_updates. It drops the authorizations that have expired.
At the fork epoch, the upgrade function initializes one unset entry per registry member and an empty authorization queue:
A new global topic pq_key_registration carries SignedPQKeyRegistration messages. The following validations MUST pass before forwarding a signed_registration on the network, where registration = signed_registration.message and the head state is the node's state at its current head. They are listed in order, cheapest first.
registration.public_keys.version is PQ_KEYS_VERSION_1.signed_registration is the first message received with its hash_tree_root.registration.validator_index is a validator in the head state, and its exit_epoch is FAR_FUTURE_EPOCH.signed_registration.signature is G2_POINT_AT_INFINITY (owner path):
registration.validator_index whose public_keys_root is hash_tree_root(registration.public_keys). Such an authorization exists only after the withdrawal address has paid the request fee on the execution layer.signed_registration is the first valid operator-path registration received for registration.validator_index, as for bls_to_execution_change.signed_registration.signature is a valid BLS signature of registration by the validator's public key.registration.public_keys is not registered by another validator in the head state.signed_registration.possession is a valid proof of possession of registration.The key authorization request contract follows the EIP-7002 contract design: the same queue and storage layout, the same fee mechanism, the same end-of-block system call, and no Solidity-compatible ABI. It differs only in its input, its record, and the constants below.
| Name | Value | Description |
|---|---|---|
PQ_KEY_AUTHORIZATION_REQUEST_PREDEPLOY_ADDRESS | TBD | Address of the contract |
PQ_KEY_AUTHORIZATION_REQUEST_TYPE | 0x06 | EIP-7685 request type |
MAX_PQ_KEY_AUTHORIZATION_REQUESTS_PER_BLOCK | 16 | Records dequeued per block |
TARGET_PQ_KEY_AUTHORIZATION_REQUESTS_PER_BLOCK | 2 | Per-block count above which the fee rises |
MIN_REQUEST_FEE | 1 | Minimum fee, in wei |
REQUEST_FEE_UPDATE_FRACTION | 17 | Controls the fee's rate of change |
A call from any address other than SYSTEM_ADDRESS, with exactly 80 bytes of calldata and msg.value at least the current fee, submits a request:
| Bytes | Field | Description |
|---|---|---|
0:48 | validator_pubkey | BLS public key of the validator |
48:80 | public_keys_root | hash_tree_root of the PQPublicKeys being authorized |
The contract queues the record source_address (20) ++ validator_pubkey (48) ++ public_keys_root (32), where source_address is msg.sender, and performs no further check. A call with empty calldata returns the current fee. The end-of-block system call dequeues up to MAX_PQ_KEY_AUTHORIZATION_REQUESTS_PER_BLOCK records, oldest first, and returns their concatenation as the request data, which is the SSZ serialization of a list of PQKeyAuthorizationRequest. If the system call fails, or if there is no code at the contract address once this EIP is active, the block MUST be invalid.
The contract's bytecode and deployment transaction are to be provided with the reference implementation.
The key registered under PQ_KEYS_VERSION_1 is registered to sign every validator message once duties switch to it. This EIP does not authenticate any duty with it.
A signature carries its leaf index, and xmss_verify accepts any leaf index. A validator's signer MUST NOT sign two different messages at the same leaf index. Leaf index PQ_POSSESSION_XMSS_INDEX is reserved for the proof of possession: a signer MUST NOT sign at it any message other than the registration it submits. The signer chooses how it allocates the other leaf indices to messages, as described in Leaf allocation.
The reasons for separating the registry from the signature switch are given in the Motivation. The registered keys are not used for any duty. They cannot cause a missed attestation or a slashing, so the registry can be deployed before the aggregation and networking changes are ready. The fork that switches duties defines how the registered keys are used, including how a replacement of keys already in use takes effect.
The operator path can only register keys. The owner path can replace them, before and after the switch. A validator that finds a bug in its key generation, or loses its post-quantum seed, replaces its keys through the owner path and does not need to exit.
A stateful signature scheme signs at most one message per leaf index. Attestations fit this rule: one attestation per slot, and two different attestations in the same slot are slashable. Other duties do not: a proposer also attests in its slot, a sync committee member signs a message every slot in addition to its attestation, proposer preferences are re-signed after a reorganization, and voluntary exits are not tied to a slot.
A second, stateless scheme such as leanSPHINCS would sign these messages without leaf management, at the cost of a second key, a second verifier, and larger signatures. This EIP registers one leanXMSS key instead, and leaves leaf allocation to the signer. The signature carries its leaf index, so the verifier needs no schedule, and new duties do not need a new key.
The key is not used for randomness: hash-based signatures are not unique, and EIP-8321 replaces the BLS RANDAO reveal with a hash-chain commitment.
The signer allocates leaf indices to messages in one of two ways:
2**32 - 1 messages in total, whatever the duties.k consecutive leaf indices, [k * s, k * s + k) for slot s, one per duty the validator may perform in that slot. The schedule needs no state beyond slashing protection, and the lifetime becomes 2**32 / k slots: with k = 8, 2**29 slots, about 204 years at 12-second slots.Both are compatible with the same registered key and the same verifier. Leaf index 0 falls in slot 0 under fixed allocation, the genesis slot, which is never a duty slot after this fork, so it is reserved for the proof of possession under both. Because signing is deterministic, a registration that is resubmitted, for example after its block was orphaned or under the other authorization path, carries a byte-identical proof and reuses no leaf.
A flat XMSS tree of height 32 has a smaller signature, but its root depends on all 2**32 one-time keys: key generation takes about 9 * 10**11 Keccak-f permutations. Range keys cover only a window of indices and fill the rest of the tree with pseudo-random nodes. This reduces key generation, but fixes the key's lifetime at registration and still requires one large computation before registration.
The two-layer tree limits each step to 2**16 one-time keys. The single-layer column is derived from the leanXMSS cost formulas with one tree of height 32:
| Flat tree, height 32 | Two layers, 16 + 16 | |
|---|---|---|
| Key generation (public key) | 2**32 one-time keys | 2**16 one-time keys |
| Ongoing work | None | One bottom tree of 2**16 one-time keys per 2**16 leaf indices |
| Signature | 1460 bytes | 2404 bytes |
| Verification | 109 Keccak-f permutations | 186 Keccak-f permutations |
The public key, which is the only registered value, depends only on the 2**16 one-time keys of the top tree. A bottom tree is generated before its first leaf index is used. Hash-based schemes have no threshold variant, so a distributed validator generates and holds the key inside a multi-party computation, which then covers 2**16 one-time keys for the public key, against 2**32 for a flat tree. The leanXMSS specification sketches a threshold signer in which signing uses a threshold pseudorandom function, and multi-party computation is needed only at key generation and once per bottom tree. A signer holds at most the top tree and one bottom tree, each with 2**17 - 1 nodes of 16 bytes.
The cost is one additional one-time signature per message: 944 more bytes of signature and 77 more Keccak-f permutations, so aggregators prove about 1.7 times as many permutations per signature. The split between the two layers is part of PQ_KEYS_VERSION_1, and a later version can change it.
Registering each bottom root in the state would keep a one-layer signature, but adds about sixteen certifications per slot to every block with a million validators.
Every registration proves possession of the key by signing the registration itself. The proof has three purposes:
AttestationData, and sync committee and payload timeliness signatures cover a block root. None of these signed messages contains the validator index. If two validators could register the same key, one could claim the other's signatures as its own participation. The registry therefore requires keys to be unique.The proof is 2404 of the 2541 bytes of a registration. It is kept on chain because the signature switch relies on every registered key being a working key for its validator.
The registry has to support solo stakers, staking pools whose withdrawal address is a contract, and distributed validators. This EIP therefore accepts two authorities.
0x00 withdrawal credentials, which EIP-8365 and EIP-8433 retire.The two paths have different powers. A BLS signature can be forged by an adversary with a CRQC, so the operator path is register-only: once keys are registered, a forged BLS signature cannot change them. Only the owner path, which does not depend on BLS, can replace keys. If BLS breaks before the switch, every key registered before the break is authentic, and any key registered through the operator path after the break can be overridden by the validator's owner.
The owner path authorizes a 32-byte root on the execution layer and delivers the key and proof on the consensus layer. Storing the full registration in an EIP-7002-style queue would take about 80 storage slots per request; with the commitment, each record is 100 bytes.
Each registry entry carries a version byte. Version 0 marks a validator with no registered keys, and version 1 is the single parameter set of this EIP: two-layer leanXMSS over Keccak-256. The version byte identifies the verifier of each entry, independently of the fork that registered it.
Registering keys under several hash functions, so that the hash function could be chosen after registration, multiplies the registry size and the key generation work. This EIP fixes the hash function and the parameters instead.
The hash function is Keccak-256, the hash function of the execution layer. It is the original submission underlying SHA-3 (FIPS 202), differing from SHA3-256 only in its padding. Keccak has been analyzed publicly since the SHA-3 competition, and no collision or preimage attack on full-round Keccak-256 is known. The security of the signature scheme rests on the hash function alone, so this EIP uses a hash function with a long cryptanalytic record that every execution client already implements.
The cell encoding leaves inputs of at most 128 bytes unchanged. It changes only the leaf hash, whose 960-byte input spans several blocks, so that every block carries whole 16-byte cells for the leanVM verifier.
Arithmetization-oriented hashes such as Poseidon are cheaper to prove under some conditions but have a shorter cryptanalytic record. Proving systems over binary fields, such as leanVM, prove standard hash functions such as Keccak-256, so the aggregation proofs do not require an arithmetization-oriented hash.
The keys are stored in a list parallel to validators, as EIP-8321 does for RANDAO commitments. The Validator container and its generalized indices do not change, so light clients and proofs against them are not affected until the switch. The fork that switches duties may move the keys into the validator record, since that fork changes the meaning of Validator.pubkey.
The deposit contract's event layout is fixed, and its events are converted into fixed-size DepositRequest records (EIP-6110) with no field for post-quantum keys. Existing validators also need a registration path that does not involve a deposit. A new validator registers through the same path once its deposit has been processed and it has an index, which can be before its activation. Setting keys at deposit time requires a new deposit format, which is part of the fork that retires BLS deposits.
A registration is 2541 bytes, dominated by the proof of possession. MAX_PQ_KEY_REGISTRATIONS = 8 bounds the extra block size at about 20 KiB, and the verification cost at eight BLS verifications and 1,488 Keccak-f permutations per block. At that rate a million validators register in 125,000 full blocks, about 17 days of 12-second slots.
The owner path is bounded by the request fee of the execution-layer contract, at a target of 2 authorizations per block and a maximum of 16, and by the expiry of pending authorizations.
This EIP requires a hard fork of the consensus and execution layers. It changes no existing duty: every message is still authenticated with BLS, and a validator that never registers keys is unaffected by this fork. The new execution-layer request type extends the request list committed in the block header (EIP-7685), and the engine API carries it with the other execution requests.
Test vectors are to be provided in the consensus specifications and the Ethereum cryptography specifications, covering at least:
0, 2**16 - 1, 2**16, and 2**32 - 1, and rejection of a digest that is not a codeword and of a signature whose top layer certifies a different bottom root.PQ_POSSESSION_XMSS_INDEX.len(state.pq_public_keys) == len(state.validators) after new validators are added.leanXMSS over Keccak-256 is specified and implemented in the leanVM repository, with a Lean 4 proof of its classical security. The consensus-layer changes and the request contract are to be provided before this EIP moves beyond Draft.
A registration through the operator path is only as authentic as the BLS signature that authorizes it. Before a CRQC exists, it is authentic. After a CRQC exists, an adversary can register keys of its choice for any validator that has not registered yet. Validators SHOULD therefore register early. Registrations made before a break cannot be changed through the operator path, and the owner path can override any registration made after it.
The owner path is only as secure as the withdrawal address. An externally owned account secured by ECDSA is broken by the same adversary, who can then authorize keys of its choice. That adversary also controls the validator's stake, through withdrawals and exits, so the registry does not add to its power. Securing withdrawal addresses is the execution layer's part of the migration.
Two different messages signed at the same leaf index reveal enough chain values to forge further signatures at that index. The registry relies on three rules to prevent this:
0, the signer MUST sign only the registration it intends to submit. Signing is deterministic, so resubmitting the same registration is safe.The registry rejects a key that another validator has already registered. Without that rule, a validator could register another's key and claim its signatures as its own participation, since the signed messages do not name the validator. The proof of possession signs the validator index, so a copied key and its proof cannot be registered for a different index. A key that is published but not yet registered cannot be squatted.
A registered key proves that its holder could sign once, at registration time. It does not prove that the validator will run post-quantum software, keep its seed, or keep its signer state consistent at the switch. The registered balance is an upper bound on readiness, and the switch SHOULD be scheduled with a margin above the two-thirds threshold that finality needs.
The leanXMSS specification proves 127 bits of classical strong unforgeability in the random oracle model, in Lean 4, for the scheme as specified here. The parameters target 64 bits of quantum security, NIST level 1. That target is not a proved bound.
MAX_PQ_KEY_REGISTRATIONS registrations per block, about 20 KiB, eight BLS verifications and 1,488 Keccak-f permutations.PQ_KEY_AUTHORIZATION_EXPIRY epochs.The leanXMSS seed SHOULD NOT be derived from the validator's BLS key or from the hash-chain seed of EIP-8321. Losing the signer state risks leaf reuse, even if the seed is kept. Keystores for leanXMSS keys SHOULD follow EIP-8310, which extends ERC-2335.
Copyright and related rights waived via CC0.