-
Notifications
You must be signed in to change notification settings - Fork 6k
Add EIP: Post-Quantum Keystore for Stateful Keys #11820
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
eth-bot
merged 10 commits into
ethereum:master
from
ch4r10t33r:eip-pq-keystore-leansig-2to32
Jul 1, 2026
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
3ae925f
feat: Add new pq-keystore EIP
ch4r10t33r 912cae6
Update leanxmss scheme params to leanSig 2^32 production set
ch4r10t33r 6d5e760
Fix CI: assign EIP number 11820, shorten title, drop unreferenced req…
ch4r10t33r 86d47bf
Fix CI: eipw section order, ERC-2335 refs, CC0 license link
ch4r10t33r e7ab583
Make EIP hash-agnostic; remove author Tom Wambsgans
ch4r10t33r cf66036
Assign EIP number 8310 and rename file
ch4r10t33r 99ff210
Remove stray Magicians draft accidentally committed
ch4r10t33r 10b3e45
Set discussions-to to the Ethereum Magicians thread
ch4r10t33r e055404
Address editor review: category Interface, define abbreviations, RFC …
ch4r10t33r decf3ed
Lowercase RFC 2119 keywords outside the Specification section
ch4r10t33r File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,197 @@ | ||
| --- | ||
| eip: 8310 | ||
| title: Post-Quantum Keystore for Stateful Keys | ||
| description: A keystore format for XMSS and other consumable signing keys, defining state authority, durability ordering, and safe import/export. | ||
| author: Anshal Shukla (@anshalshukla), Benedikt Wagner (@benedikt-wagner), Gajinder Singh (@g11tech), Guillaume Ballet (@gballet), Justin Drake (@JustinDrake), Kolby Moroz Liebl (@KolbyML), Parthasarathy Ramanujam (@ch4r10t33r), Shariq Naiyer (@shariqnaiyer), Thomas Coratger (@tcoratger), Unnawut Leepaisalsuwanna (@unnawut) | ||
| discussions-to: https://ethereum-magicians.org/t/eip-8310-post-quantum-keystore-for-stateful-keys/28853 | ||
| status: Draft | ||
| type: Standards Track | ||
| category: Interface | ||
| created: 2026-06-19 | ||
| requires: 2335, 3076 | ||
| --- | ||
|
|
||
| ## Abstract | ||
|
|
||
| This standard defines a keystore format for **stateful hash-based signing keys**, specifically eXtended Merkle Signature Scheme (XMSS) as used by the Lean Ethereum consensus layer (hereafter `leanxmss`). It extends [ERC-2335](./eip-2335.md) so that the encrypted secret may be an XMSS seed and adds the machinery a *consumable* key requires: an explicit scheme-parameter block, a non-authoritative capacity snapshot, normative rules for where the authoritative signing state lives, a commit-before-sign durability requirement, reserved leaf ranges for concurrent signers, and import/export semantics that forbid silently resetting a key's signing position. | ||
|
|
||
| The encryption upgrades (Advanced Encryption Standard with 256-bit keys (AES-256) and Authenticated Encryption with Associated Data (AEAD)) are minor. The substance of this EIP is **state management**, because an XMSS key is destroyed by leaf reuse and the existing keystore model assumes keys are immutable, freely copyable, and restore-safe, three assumptions that are unsafe for consumable keys. | ||
|
|
||
| ## Motivation | ||
|
|
||
| [ERC-2335](./eip-2335.md) stores a 32-byte BLS scalar. Lean Ethereum replaces BLS validator keys with XMSS for post-quantum security. An XMSS secret can also be reduced to a small seed, so the *container* needs only modest change. The danger is elsewhere. | ||
|
|
||
| Each XMSS signature consumes a one-time Winternitz One-Time Signature Plus (WOTS+) leaf identified by an index that must not ever be reused. Reuse of a leaf across two distinct messages enables forgery and is therefore key-destroying. This single property breaks three behaviors that are safe today and that operators, wallets, and clients currently take for granted: | ||
|
|
||
| 1. **Immutability.** A 2335 keystore never changes after creation. An XMSS key advances state on every signature. | ||
| 2. **Free copying.** A BLS keystore may run on two machines harmlessly. Two copies of an XMSS key signing independently will reuse leaves. | ||
| 3. **Restore-safety.** Restoring a BLS keystore from any backup is safe. Restoring a *stale* XMSS state rolls back the high-water mark and invites reuse. | ||
|
|
||
| Validators already solve an isomorphic problem for BLS: [EIP-3076](./eip-3076.md) slashing protection enforces "never sign two different messages for the same slot." For the synchronized XMSS variant, where one leaf is bound to one epoch/slot, *"never reuse a leaf" and "never double-sign a slot" are the same invariant*. This EIP makes that coupling normative rather than coincidental, and specifies the remaining cases (off-protocol signers such as builder-bid signing) that fall outside the slashing-protection database. | ||
|
|
||
| ## Specification | ||
|
|
||
| The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", and "MAY" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174). | ||
|
|
||
| ### 1. Terminology | ||
|
|
||
| - **Consumable key**: a signing key whose security depends on never reusing an index (e.g. XMSS, Leighton-Micali Signatures (LMS)). Contrast with *reusable* keys (ECDSA, BLS). | ||
| - **Leaf index**: the position of the one-time key consumed by a signature. | ||
| - **High-water mark**: the highest leaf index that has been (or is reserved as) consumed. Signing is permitted only strictly above it. | ||
| - **Authoritative state**: the single source of truth for the high-water mark that a signer consults and advances. It is NOT the keystore file. | ||
| - **Synchronized mode**: leaf index is a deterministic function of consensus position (`leaf = f(epoch)` or `f(slot)`); state authority is the slashing-protection database. | ||
| - **Counter mode**: leaf index is a free-running counter not bound to consensus position (e.g. off-protocol signing); state authority is a dedicated atomically-updated store. | ||
|
|
||
| ### 2. File format | ||
|
|
||
| This EIP defines keystore `version` **5**. A v5 keystore is a v4 keystore with the changes below. | ||
|
|
||
| #### 2.1 Cipher and Key Derivation Function (KDF) | ||
|
|
||
| - `crypto.cipher.function` MUST be an AEAD or 256-bit construction. `aes-256-gcm` is RECOMMENDED. `aes-128-ctr` MUST NOT be used in v5. | ||
| - `crypto.kdf.function` MAY be `argon2id` in addition to the `scrypt` and `pbkdf2` of v4. Parameters MUST target current memory-hardness guidance. | ||
|
|
||
| These are precautionary against Grover and do not affect the consumable-key semantics below. | ||
|
|
||
| #### 2.2 Encrypted secret | ||
|
|
||
| `crypto.cipher.message` is the encrypted XMSS **seed**, not an expanded private key. The seed length is defined by the scheme parameters. The keystore stores only material required to *recover* the key (see §6); it does not store live signing state. | ||
|
|
||
| #### 2.3 New `scheme` object | ||
|
|
||
| ```json | ||
| "scheme": { | ||
| "name": "leanxmss", | ||
| "params": { | ||
| "hash": "<hash identifier>", | ||
| "tree_height": 32, | ||
| "hypertree_layers": 1, | ||
| "encoding": "target_sum", | ||
| "winternitz_w": 8, | ||
| "dimension": 46, | ||
| "target_sum": 200, | ||
| "index_mode": "synchronized", | ||
| "activation_epoch": 0, | ||
| "lifetime_leaves": 4294967296 | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| - `scheme.name` MUST identify the signature scheme. A reader that does not recognize `scheme.name` MUST refuse to use the keystore. | ||
| - `scheme.params` MUST contain every parameter required to deterministically regenerate the public key and to verify a signature. These fields are **immutable**; a writer MUST NOT alter them after creation. | ||
| - `hash` identifies the tweakable hash function and its underlying field. This specification is **hash-agnostic**: any hash meeting the security requirements of the signature scheme MAY be used, and `hash` records the concrete choice so that a verifier can reproduce it. The Poseidon1 hash (for example over the KoalaBear field) is still being evaluated for consideration as the recommended instantiation. | ||
| - `tree_height` is the height of the (single) Merkle tree; with `hypertree_layers` it determines `lifetime_leaves = 2^(tree_height × hypertree_layers)`. | ||
| - `hypertree_layers` is the number of stacked Merkle trees. `leanxmss` is a single-tree Generalized XMSS, so this MUST be `1`. | ||
| - `encoding` identifies the incomparable encoding. `leanxmss` uses `target_sum`. | ||
| - `winternitz_w` is the base of each hash chain (`BASE` in leanSig). | ||
| - `dimension` is the number of hash chains (the hypercube dimension). Together with `winternitz_w` and `target_sum` it fully specifies the target-sum encoding and is REQUIRED to regenerate the public key and verify a signature. | ||
| - `target_sum` is the target sum of the target-sum encoding. | ||
| - `index_mode` MUST be `synchronized` or `counter` per §1. | ||
|
|
||
| The non-hash values above are the recommended production parameter set for a 2^32 key lifetime, matching leanSig's `SIGAbortingTargetSumLifetime32Dim46Base8` instantiation (`LOG_LIFETIME = 32`, `DIMENSION = 46`, `BASE = 8`, `TARGET_SUM = 200`). The format is hash-agnostic; the concrete hash function (for example Poseidon1 over KoalaBear) is still being evaluated for consideration and is recorded in the `hash` field. | ||
|
|
||
| #### 2.4 New `state` snapshot object | ||
|
|
||
| ```json | ||
| "state": { | ||
| "authoritative": false, | ||
| "authority": "slashing-protection", | ||
| "reference": "<opaque locator, optional>", | ||
| "capacity": { "total": 4294967296, "consumed": 12345, "remaining": 4294954951 }, | ||
| "high_water": 12345, | ||
| "reserved_ranges": [] | ||
| } | ||
| ``` | ||
|
|
||
| - `state.authoritative` MUST be `false` in any keystore file. The file is a snapshot for operator visibility only and MUST NOT be consulted as the high-water mark at signing time. | ||
| - `state.authority` MUST be one of `slashing-protection`, `external`, or `embedded` and MUST be consistent with `scheme.params.index_mode`: | ||
| - `synchronized` ⇒ `slashing-protection`. | ||
| - `counter` ⇒ `external` (a dedicated state store). | ||
| - `embedded` is RESERVED and SHOULD NOT be used for production validator keys; it exists only for self-contained single-signer tooling and, if used, the file ceases to be immutable. | ||
| - `capacity` and `high_water` are informational. A consumer MUST treat the authoritative store, not these fields, as binding. | ||
|
|
||
| ### 3. State authority | ||
|
|
||
| A signer MUST consult and advance a single authoritative state when producing a signature. | ||
|
|
||
| - In **synchronized mode**, the authoritative state is the [EIP-3076](./eip-3076.md) slashing-protection database. The leaf consumed by a signature is `f(epoch)` (or `f(slot)`); refusing to double-sign a slot is exactly refusing to reuse a leaf. Implementations MUST bind the XMSS key and its slashing-protection history as an inseparable unit: any operation that moves, imports, or exports the key MUST move, import, or export the corresponding history atomically with it. | ||
| - In **counter mode**, the authoritative state is a dedicated store keyed by the keystore `uuid`, holding the current counter. It MUST provide the same durability and atomicity guarantees as §4. | ||
|
|
||
| ### 4. Commit-before-sign ordering | ||
|
|
||
| For every signature, an implementation MUST, in order: | ||
|
|
||
| 1. Determine the candidate leaf index. | ||
| 2. Verify the index is strictly greater than the authoritative high-water mark (or, in synchronized mode, that the slot/epoch has not been signed). | ||
| 3. **Durably persist** the advanced high-water mark, written, flushed (`fsync` or platform equivalent), and committed via atomic rename or an equivalently atomic transaction, *before* step 5. | ||
| 4. Compute the signature. | ||
| 5. Release the signature to the caller. | ||
|
|
||
| Releasing a signature before the state advance is durable is a violation: a crash between release and persistence causes index reuse on restart. Step 3 MUST complete before step 5; steps 3 and 4 MAY be reordered relative to each other, but neither may precede step 2. | ||
|
|
||
| ### 5. Reserved leaf ranges | ||
|
|
||
| To support concurrent or high-throughput signers (e.g. builder-bid signing) without serializing every signature on one lock, a keystore MAY partition its leaf space into disjoint **reserved ranges**: | ||
|
|
||
| ```json | ||
| "reserved_ranges": [ | ||
| { "id": "signer-a", "start": 0, "end": 524287, "high_water": 410022 }, | ||
| { "id": "signer-b", "start": 524288, "end": 1048575, "high_water": 524288 } | ||
| ] | ||
| ``` | ||
|
|
||
| - Ranges MUST be disjoint. A signer assigned a range MUST consume only indices within `[start, end]` and MUST maintain its own authoritative high-water mark for that range under the §4 ordering. | ||
| - A range MUST NOT be assigned to more than one signer instance simultaneously. | ||
| - Exhaustion of a range MUST cause the signer to refuse rather than wrap or borrow from another range. | ||
|
|
||
| This is the recommended mechanism for the builder-bid exhaustion case, where a single global counter is too contended. | ||
|
|
||
| ### 6. Import, export, recover, resume | ||
|
|
||
| This EIP distinguishes two operations that legacy tooling conflates: | ||
|
|
||
| - **Recover**: reconstruct the key (regenerate the hypertree, derive the public key) from the encrypted seed. This is always safe and uses only the keystore file. | ||
| - **Resume signing**: begin producing signatures. This requires the *latest authoritative state* and is unsafe from the keystore file alone. | ||
|
|
||
| Therefore: | ||
|
|
||
| - An implementation MUST NOT begin signing from a keystore that lacks authoritative state. "Import with empty/fresh state" MUST be a hard error for a consumable key, not a warning. | ||
| - Export of a consumable key MUST include or reference its authoritative state. A bare keystore file MAY be exported for *recovery and verification only* and, if so, MUST be flagged such that no consumer will sign from it. | ||
| - Restoring a keystore together with a *stale* state snapshot is the catastrophic case. Implementations MUST detect a regression of the high-water mark and refuse to sign until reconciled with the latest authoritative state. | ||
|
|
||
| ## Rationale | ||
|
|
||
| **Why extend 2335 rather than define a new format.** The encryption envelope, KDF abstraction, and modular design of 2335 are sound and widely implemented; only the cipher strength and the assumption of a fixed-size reusable secret needed changing. Reusing the envelope preserves tooling and review surface. | ||
|
|
||
| **Why couple to 3076 instead of a bespoke state mechanism.** In synchronized mode the slashing-protection invariant and the leaf-uniqueness invariant are identical. Defining a second, parallel state mechanism would create two sources of truth that can disagree, the worst outcome for a consumable key. Making the slashing DB authoritative gives the key a single, already-battle-tested guardrail. | ||
|
|
||
| **Why a non-authoritative snapshot in the file.** Operators need to see capacity and remaining leaves without a separate tool, but a mutable field in a portable file is a reuse trap. Marking it `authoritative: false` keeps visibility while denying it any role at signing time. | ||
|
|
||
| **Why commit-before-sign is normative, not advisory.** It is the single ordering rule that prevents crash-induced reuse. Everything else in the spec is bookkeeping around it. | ||
|
|
||
| **Synchronized vs counter.** Synchronized keys map cleanly onto consensus duties and the slashing DB. Off-protocol duties (builder-bid signing being the motivating example) have no slot to key on and need a free counter and its own durable store. Both are specified so a single deployment can hold keys of each kind. *Open issue for discussion:* whether the lean profile mandates synchronized-only for protocol keys and confines counter mode to clearly-separated signing services. | ||
|
|
||
| ## Backwards Compatibility | ||
|
|
||
| v5 is not backwards compatible with v4 consumers, which assume a reusable 32-byte secret and have no notion of state authority. v4 and v5 keystores are distinguished by the `version` field and the presence of the `scheme` object. A v5-aware client must refuse to operate on a consumable key as if it were a v4 reusable key. v4 BLS keystores remain valid under their own standard and are out of scope. | ||
|
|
||
| ## Test Cases | ||
|
|
||
| To be added. At minimum: (1) round-trip encrypt/decrypt of a `leanxmss` seed; (2) refusal to sign on high-water regression; (3) crash-injection between signature computation and state persistence demonstrating no reuse on restart; (4) disjointness and exclusive-assignment checks for reserved ranges. <!-- TODO --> | ||
|
|
||
| ## Reference Implementation | ||
|
|
||
| To be added. <!-- TODO --> | ||
|
|
||
| ## Security Considerations | ||
|
|
||
| - **Leaf reuse is catastrophic and silent.** Unlike a slashing event, reuse may produce no immediate on-chain penalty while fully compromising the key. The §4 ordering and §6 import rules are the primary defenses and must not be relaxed for performance. | ||
| - **Stale-restore is the dominant operational risk.** Backups protect the seed but not the state. Operators must be guided toward recover-then-reconcile, never restore-and-sign. Detection of high-water regression (§6) is required. | ||
| - **Cloning across hosts.** Running the same key on two hosts without disjoint reserved ranges guarantees reuse. Range assignment (§5) must be exclusive. | ||
| - **State store integrity.** An attacker able to roll back the authoritative state can induce reuse. The state store should have integrity protection commensurate with the keystore itself. | ||
| - **Encryption strength.** AES-256 and a memory-hard KDF mitigate Grover-accelerated password search; they do not and need not protect the signing scheme, which is post-quantum by construction. | ||
| - **Capacity exhaustion as availability risk.** A key that exhausts its leaves can no longer sign. Capacity monitoring (§2.4) and conservative `lifetime_leaves` selection are operational requirements, not optional. | ||
|
|
||
| ## Copyright | ||
|
|
||
| Copyright and related rights waived via [CC0](../LICENSE.md). | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.