Post-Quantum Nostr
A migration strategy for bringing post-quantum security to Nostr without breaking the social graph, without requiring consensus on a single post-quantum algorithm, and without forcing existing users to abandon their identities.
Table of Contents
- The Problem
- Why Bitcoin's Hash-the-Pubkey Trick Doesn't Work for Nostr
- The Strategy: Overview
- Component 1: Seed Phrase as Algorithm-Agnostic Root of Trust
- Component 2: Multi-Scheme Cross-Signed Key-Link Events
- Component 3: OpenTimestamps for Pre-Quantum Anchoring
- Component 4: Migrating Existing Users with Raw nsec
- Component 5: Quantum-Safe Self-Storage
- Component 6: Deterministic Wallet Compartmentalization
- The Complete Migration Flow
- What Remains Unsolved
- Implementation Status
The Problem
Nostr's cryptography is built entirely on secp256k1:
- Identity: Your pubkey IS your identity — it's in every event, it's your npub, it's what filters use, it's what NIP-05 verifies, it's what the social graph is built on.
- Authentication: Every event is signed with a Schnorr signature over secp256k1. Signature verification requires the public key, which is published in plaintext on every event.
- Encryption (NIP-04): Uses secp256k1 ECDH to derive a shared secret, then AES-256-CBC. The shared X coordinate is used directly as the AES key. The IV is published in plaintext.
- Encryption (NIP-44): Uses secp256k1 ECDH to derive a shared secret, then HKDF → ChaCha20 + HMAC-SHA256. The nonce is published in plaintext inside the payload.
Shor's algorithm breaks the elliptic curve discrete logarithm problem on secp256k1 in polynomial time. With a sufficiently large fault-tolerant quantum computer, an attacker who has recorded any Nostr event can:
- Extract the public key from the event.
- Run Shor's algorithm to recover the private key.
- Forge signatures, decrypt all NIP-04/NIP-44 messages, and impersonate the user.
NIP-04 vs NIP-44: No Quantum Advantage
NIP-44 is a substantial classical-security upgrade over NIP-04 (authenticated encryption, key separation, per-message keys, better cipher, length-hiding padding), but it offers zero post-quantum security advantage. Both derive their shared secret from secp256k1 ECDH. Everything NIP-44 adds (HKDF, per-message nonces, ChaCha20, HMAC-SHA256, custom padding) sits downstream of the broken ECDH step.
The NIP-44 spec itself acknowledges this:
No post-quantum security: a powerful quantum computer would be able to decrypt the messages
The symmetric primitives in both NIPs (AES-256, ChaCha20, HMAC-SHA256, HKDF-SHA256) are adequately post-quantum (~128-bit PQ security via Grover's quadratic speedup). The weakness is exclusively the secp256k1 ECDH key agreement, which has zero post-quantum security in both NIPs.
Why Bitcoin's Hash-the-Pubkey Trick Doesn't Work for Nostr
Bitcoin has limited quantum resistance because addresses are hashes of pubkeys (RIPEMD160(SHA256(pubkey))), and pubkeys are only revealed at spend time. This works because of three properties that Nostr does not have:
| Property | Bitcoin | Nostr |
|---|---|---|
| Identifier | Address (hash of pubkey) | Pubkey directly |
| Pubkey revealed | At spend time only | On every event |
| After reveal | UTXO consumed, key discarded | Identity persists, key reused |
In Nostr, the pubkey is load-bearing in ways it isn't in Bitcoin:
- The pubkey IS the identity. It's in every event, it's your npub, it's what filters use, it's what NIP-05 verifies, it's what the social graph is built on.
- The pubkey must be revealed for every event. Schnorr signature verification requires the public key. You can't verify a signature with a hash of the pubkey.
- Identity is never consumed. You publish thousands of events over years with the same pubkey. There's no "consume and rotate" moment.
If Nostr published hash(pubkey) instead, you'd get quantum resistance for exactly zero seconds — the time between key generation and your first event publication, at which point the pubkey must be revealed for signature verification.
The only ways to avoid revealing the pubkey (ZK-SNARKs, giving up public verifiability, one-time keys per event) are impractical or fundamentally change what Nostr is.
The real fix is post-quantum signatures and post-quantum key agreement, not hashing the pubkey. This document describes how to get there without breaking Nostr.
The Strategy: Overview
flowchart TD
subgraph SECURITY["Security Layers - All Quantum-Resistant"]
L1[Layer 1: Seed phrase - PQ-safe root of trust]
L2[Layer 2: Key-link event + OTS - PQ-safe identity migration]
L3[Layer 3: PQ signatures - PQ-safe future authentication]
L4[Layer 4: OTS on important events - PQ-safe historical provenance]
L5[Layer 5: OTP or PQ encryption - PQ-safe confidentiality]
end
L1 --> L2 --> L3
L1 --> L5
L2 --> L4
The strategy uses existing Nostr infrastructure (NIP-06, NIP-03) plus one new event type (a key-link NIP). No new cryptographic primitives are needed for the migration itself — PQ algorithms are used additively, and the community doesn't need to agree on which one to use.
Design Principles
- No social graph loss — existing follows and identity carry forward
- No user re-onboarding — the seed phrase becomes the new root of trust
- Backward compatible — legacy clients continue working; PQ is additive
- No algorithm consensus required — link to all viable PQ schemes now, converge later
- Pre-quantum timestamping — establish links now, before quantum computers exist
- Transparent to users — the client handles everything at seed-phrase login
Component 1: Seed Phrase as Algorithm-Agnostic Root of Trust
The BIP39 seed is a 64-byte entropy string derived from a mnemonic via PBKDF2-HMAC-SHA512 (2048 iterations). This is a symmetric KDF — no public-key cryptography involved. A quantum computer provides no advantage beyond Grover's quadratic speedup.
The seed is algorithm-agnostic. It's just entropy. You can derive any key type from it:
flowchart TD
SEED[BIP39 Seed - 64 bytes, quantum-safe]
SEED --> BIP32[BIP32 derivation - secp256k1 keypair]
SEED --> PQKEM[HKDF derivation - ML-KEM keypair]
SEED --> PQDSA[HKDF derivation - ML-DSA keypair]
SEED --> SLHDSA[HKDF derivation - SLH-DSA keypair]
SEED --> SYMKEY[HKDF derivation - symmetric encryption key]
style SEED fill:#cfc
style BIP32 fill:#fcc
style PQKEM fill:#cfc
style PQDSA fill:#cfc
style SLHDSA fill:#cfc
style SYMKEY fill:#cfc
PQ Key Derivation from BIP39 Seeds
BIP32 is specific to secp256k1. For PQ keys, use HKDF-SHA256 (quantum-resistant) to derive key material from the BIP39 seed:
For each PQ algorithm A:
pq_seed_A = HKDF-Extract(salt="nostr-pq-v1", ikm=bip39_seed)
pq_key_material_A = HKDF-Expand(pq_seed_A, info=A.identifier, L=A.key_size)
pq_keypair_A = A.keygen(pq_key_material_A)
The identifier string ("ml-dsa-65", "slh-dsa-128s", "ml-kem-768", etc.) is the only algorithm-specific part. New algorithms can be added later without changing the derivation scheme.
Seed Phrase Entropy Considerations
| Mnemonic length | Entropy | Post-quantum security (Grover's) | Assessment |
|---|---|---|---|
| 12 words | 128 bits | ~64 bits | Borderline |
| 24 words | 256 bits | ~128 bits | Adequate |
For users concerned about quantum attacks on the seed entropy itself, 24-word mnemonics are recommended. The bigger risk to seed phrases is classical (theft, phishing), not quantum.
Component 2: Multi-Scheme Cross-Signed Key-Link Events
This is the core innovation. Instead of choosing one PQ algorithm, cross-sign with all viable PQ schemes now. Let consensus emerge later.
The Standardization Uncertainty Problem
The PQ standardization landscape is still evolving:
| Scheme | NIST Status | Concern |
|---|---|---|
| ML-DSA (Dilithium) | FIPS 204, finalized 2024 | Lattice-based — could have undiscovered structural weaknesses |
| SLH-DSA (SPHINCS+) | FIPS 205, finalized 2024 | Hash-based, very conservative, but huge signatures (8-50 KB) |
| FN-DSA (Falcon) | FIPS 206, still draft | Compact but complex FFT, implementation concerns |
| ML-KEM (Kyber) | FIPS 203, finalized 2024 | Lattice-based — same class as Dilithium |
History gives reason to be cautious: SIKE was a NIST PQ finalist that was broken in 2022 using a classical algorithm that ran in an hour on a laptop. If the Nostr community had picked SIKE early, we'd be back to square one.
The Multi-Scheme Solution
flowchart TD
SEED[BIP39 Seed] --> SECP[secp256k1 keypair]
SEED --> MLDSA[ML-DSA keypair]
SEED --> SLHDSA[SLH-DSA keypair]
SEED --> FALCON[FN-DSA keypair]
SEED --> MLKEM[ML-KEM keypair]
SECP --> SIGN[Schnorr sign]
MLDSA --> SIGN2[ML-DSA sign]
SLHDSA --> SIGN3[SLH-DSA sign]
FALCON --> SIGN4[FN-DSA sign]
SIGN --> EVENT[Key-Link Event]
SIGN2 --> EVENT
SIGN3 --> EVENT
SIGN4 --> EVENT
EVENT --> OTS[OpenTimestamp via NIP-03]
OTS --> RELAY[Published to relays NOW]
RELAY --> CLIENT1[PQ-aware client - picks ML-DSA]
RELAY --> CLIENT2[PQ-aware client - picks SLH-DSA]
RELAY --> CLIENT3[Legacy client - ignores all PQ keys]
style EVENT fill:#ffa
style OTS fill:#cfc
Key-Link Event Format
A new event kind (to be assigned) containing multiple cross-signed links:
{
"kind": <new key-link kind>,
"pubkey": "<secp256k1 npub>",
"content": {
"links": [
{
"algorithm": "ml-dsa-65",
"public_key": "<ML-DSA-65 public key, base64>",
"signature": "<ML-DSA signature over the link statement, base64>"
},
{
"algorithm": "slh-dsa-128s",
"public_key": "<SLH-DSA-128s public key, base64>",
"signature": "<SLH-DSA signature over the link statement, base64>"
},
{
"algorithm": "fn-dsa-512",
"public_key": "<FN-DSA-512 public key, base64>",
"signature": "<FN-DSA signature over the link statement, base64>"
},
{
"algorithm": "ml-kem-768",
"public_key": "<ML-KEM-768 public key, base64>",
"note": "KEM key for encryption; ownership asserted by secp256k1 signature over this content"
}
],
"statement": "All keys in this event are derived from the same seed and controlled by the same entity as secp256k1 key <npub>. This link is established pre-quantum.",
"secp256k1_signature": "<Schnorr signature over the entire content>"
}
}
Each PQ signature scheme independently signs the same link statement. The secp256k1 Schnorr signature covers the entire content (including all PQ public keys and signatures), binding them together.
ML-KEM Cannot Sign
ML-KEM (Kyber) is a KEM (Key Encapsulation Mechanism), not a signature scheme. It cannot sign the link statement. Instead, its binding is proven indirectly: the secp256k1 signature covers the ML-KEM public key in the content, and the ML-KEM key is derived from the same seed. The proof is: "the entity that controls the secp256k1 key (proven by Schnorr signature) asserts that this ML-KEM key is also theirs."
This is weaker than a cross-signature but sufficient because:
- The secp256k1 signature is valid NOW (pre-quantum), establishing the link while we can trust secp256k1
- The ML-KEM key is used for encryption, not authentication — a false claim of ownership would just mean someone can't decrypt messages sent to you, which is self-correcting
Why This Is Better Than Picking One Scheme
| Concern | Pick-one approach | Multi-scheme approach |
|---|---|---|
| Scheme gets broken later | Migration fails, need new link | Other links still valid |
| Standardization changes | May need to re-link | Already covered |
| Consensus not reached | Can't proceed | Proceed immediately |
| Event size | Small | Larger (~20-30 KB), but one-time cost |
| Client complexity | Support one scheme | Support any subset |
| Race condition risk | Must publish before quantum | Published now, all schemes |
Event size is a non-issue because this is a one-time publication, not per-event overhead.
Revocation: If a Scheme Is Broken Later
If a specific PQ scheme is later found to be weak (like SIKE was):
- Clients simply stop trusting the broken scheme — no revocation event needed; clients just ignore links to algorithms on a known-broken list
- The remaining links are still valid
- Optionally, publish a new key-link event without the broken scheme, signed by the remaining valid PQ keys
Component 3: OpenTimestamps for Pre-Quantum Anchoring
NIP-03 (OpenTimestamps Attestations for Events) already exists and is implemented in nostr_core_lib. It anchors Nostr event hashes to the Bitcoin blockchain via OpenTimestamps.
Why Bitcoin Timestamping Is Quantum-Resistant
The Bitcoin proof-of-work chain is quantum-resistant — not because Bitcoin's signatures are PQ-safe (they're not for revealed pubkeys), but because re-mining historical blocks is infeasible even with a quantum computer. Grover's algorithm gives only a quadratic speedup on mining, which doesn't help retroactively alter past blocks.
An attacker who breaks your secp256k1 key via Shor's can forge new events but cannot backdate an event to before a specific Bitcoin block — they'd need to produce a valid OTS proof anchoring it to a past block, which requires either:
- Re-mining that Bitcoin block (impossible, even with a quantum computer, due to cumulative proof-of-work)
- Finding a SHA256 preimage for the event hash (still infeasible with Grover's quadratic speedup on 256-bit hashes)
The Critical Refinement: OpenTimestamp the Key-Link Event
The key-link event itself MUST be timestamped via NIP-03. This prevents a race condition attack:
Without OTS on the key-link event:
- Attacker breaks secp256k1 key via Shor's
- Attacker publishes a fraudulent key-link event linking your secp256k1 key to their PQ key
- Both real and fraudulent events have valid secp256k1 signatures (key is compromised)
- Clients can't distinguish them —
created_atis forgeable
With OTS on the key-link event:
- Your real key-link event is anchored to Bitcoin block N (before quantum computers)
- Attacker's fraudulent event is published later and cannot produce an OTS proof for block N or earlier
- Clients verify: "the key-link event with the earliest valid OTS proof wins"
- The real key-link is cryptographically distinguishable from the fraudulent one
What Should Be OpenTimestamped
Not every event needs OTS. Priority list:
- Key-link events — absolutely critical, foundation of PQ migration
- Identity-defining events — NIP-05 verification, profile events (kind 0)
- Important content — contracts, announcements, evidence
- High-value events — financial events (NIP-60 Cashu wallets, zaps)
Routine events (regular posts, reactions) probably don't need OTS — the impact of retroactive forgery is low.
Component 4: Migrating Existing Users with Raw nsec
Most Nostr users today have a raw nsec (random private key) with no seed phrase. Asking them to abandon their identity and social graph to adopt a seed phrase is a non-starter. This component addresses how to migrate them.
The Challenge
A secp256k1 private key is 32 bytes (256 bits). BIP39 encodes 11 bits per word. Encoding the old nsec requires 24 extra words. The old nsec was generated randomly — it cannot be "derived" from a seed (that's a preimage problem). It must be encoded (directly or encrypted).
Approach A: Extended Phrase (36 words)
flowchart LR
subgraph CREATION["One-time migration"]
OLD[Old nsec - 32 bytes]
NEW[Generate 12-word BIP39 phrase]
NEW --> SEED[BIP39 seed]
SEED --> ENCKEY[Derive encryption key via HKDF]
ENCKEY --> ENC[Encrypt old nsec: ChaCha20]
OLD --> ENC
ENC --> CIPHER[32 bytes encrypted nsec]
CIPHER --> WORDS[Encode as 24 BIP39 words + checksum]
NEW --> PHRASE[Full phrase: 12 + 24 = 36 words]
WORDS --> PHRASE
end
subgraph RECOVERY["Recovery with full phrase"]
FULL[36 words entered]
FULL --> FIRST[First 12 words]
FULL --> LAST[Last 24 words]
FIRST --> SEED2[BIP39 seed]
SEED2 --> ENCKEY2[Derive encryption key]
LAST --> CIPHER2[Decode to 32 bytes]
ENCKEY2 --> DEC[Decrypt]
CIPHER2 --> DEC
DEC --> OLDRECOV[Old nsec recovered]
SEED2 --> PQKEYS[PQ keys derived]
end
subgraph FUTURE["After migration - drop old key"]
FIRST12[First 12 words only]
FIRST12 --> SEED3[BIP39 seed]
SEED3 --> PQKEYS2[PQ keys only]
end
Design:
- Generate a standard 12-word BIP39 phrase (128 bits entropy)
- Derive a 32-byte encryption key from the BIP39 seed:
enc_key = HKDF-Extract(salt="nostr-legacy-key", ikm=bip39_seed) - Encrypt the old nsec:
encrypted_nsec = ChaCha20(enc_key, nonce=0, old_nsec)→ 32 bytes - Encode the 32-byte encrypted nsec as 24 BIP39 words (with its own checksum)
- Full phrase: 12 words (new seed) + 24 words (encrypted old nsec) = 36 words
Recovery with 36 words: First 12 → BIP39 seed → PQ keys + encryption key. Last 24 → decode → decrypt → old nsec. Both keys recovered.
Recovery with 12 words (after migration): First 12 → PQ keys only. Old nsec no longer needed.
Why encrypt the old nsec? The extra 24 words are meaningless without the first 12 (partial compromise protection). If someone steals only the last 24 words: useless. If someone steals only the first 12 words: they get PQ keys but not the old nsec.
Approach B: 12 Words + Relay Backup (Recommended)
flowchart TD
subgraph SETUP["One-time migration"]
OLD[Old nsec]
NEW[Generate 12-word BIP39 phrase]
NEW --> SEED[BIP39 seed]
SEED --> NEWSECP[New secp256k1 key via NIP-06]
SEED --> PQKEYS[PQ keys via HKDF]
SEED --> ENCKEY[Symmetric encryption key via HKDF]
ENCKEY --> ENC[Encrypt old nsec]
OLD --> ENC
ENC --> EVENT30078[kind 30078: encrypted old nsec]
NEWSECP --> SIGN1[Sign event with new secp256k1 key]
SIGN1 --> EVENT30078
EVENT30078 --> RELAY[Publish to relays]
OLD --> KEYLINK[Key-link event: cross-signed]
NEWSECP --> KEYLINK
PQKEYS --> KEYLINK
KEYLINK --> OTS[OpenTimestamp via NIP-03]
OTS --> BTC[Anchored in Bitcoin]
KEYLINK --> RELAY
end
subgraph RECOVER["Recovery from 12 words only"]
WORDS[12 words entered]
WORDS --> SEED2[BIP39 seed]
SEED2 --> NEWSECP2[New secp256k1 key]
SEED2 --> PQKEYS2[PQ keys]
SEED2 --> ENCKEY2[Encryption key]
NEWSECP2 --> FETCH[Fetch kind 30078 from relays]
FETCH --> CIPHER[Encrypted old nsec]
ENCKEY2 --> DEC[Decrypt]
CIPHER --> DEC
DEC --> OLDRECOV[Old nsec recovered]
end
Design:
- Generate a 12-word BIP39 phrase
- Derive from seed: new secp256k1 key (NIP-06 path), PQ keys (HKDF), symmetric encryption key (HKDF)
- Encrypt old nsec with the symmetric key:
encrypted_nsec = ChaCha20-Poly1305(enc_key, old_nsec) - Publish as kind 30078 event, signed with the new secp256k1 key
- Publish key-link event (cross-signed, OpenTimestamped via NIP-03)
- User backs up only 12 words
Recovery: 12 words → derive new key + encryption key → fetch kind 30078 from relays → decrypt → old nsec. Both keys recovered from 12 words + relay access.
No circular dependency: The kind 30078 event is found by the new pubkey (derived from the 12-word seed). The new secp256k1 key is the "storage identity." The old nsec is just data inside the event.
Approach C: Hybrid (Best of Both)
- Primary backup: 12-word phrase (PQ keys + relay recovery of old nsec)
- Optional fallback: also write down 24 extra words (encrypted old nsec) in case relays are unavailable
- If relays available: 12 words suffice
- If relays down: 12 + 24 = 36 words recover everything
Comparison
| Property | 36-word phrase | 12-word + relay backup | Hybrid |
|---|---|---|---|
| Backup size | 36 words | 12 words | 12 or 36 words |
| Relay dependency | None | Yes | Optional |
| Custom format | Yes | No | Optional |
| Self-contained | Yes | No | Yes (with 36) |
| Quantum-safe | Yes | Yes | Yes |
Component 5: Quantum-Safe Self-Storage
For self-storage use cases (e.g., kind 30078 application data), NIP-04 and NIP-44 are unnecessary and quantum-vulnerable. Two quantum-safe alternatives:
Option A: One-Time Pad (Information-Theoretic Security)
A one-time pad provides information-theoretic security — the strongest security guarantee in cryptography, stronger than any computational security (including post-quantum). It's a mathematical proof of perfect secrecy: no computer that could ever exist, in any universe governed by the laws of physics, can break it.
Requirements:
- Pad must be truly random (hardware RNG recommended, e.g., TrueRNG)
- Pad must be as long as the data
- Pad must never be reused
- Pad must be stored securely (e.g., USB drive)
For self-storage, OTP is a natural fit because you sidestep OTP's biggest limitation (key distribution between parties) — you're encrypting for yourself, so the pad lives on your device.
What a quantum attacker can and cannot do with OTP-encrypted kind 30078:
- ❌ Read your stored data — OTP is information-theoretically secure
- ✅ Forge your signature — Shor's breaks secp256k1 (identity compromised)
- ✅ Impersonate you, delete/replace your events
- ❌ Decrypt your stored data — even with your private key, the data is protected by the pad
This gives clean separation: quantum breakage of your secp256k1 key compromises your identity/authentication but NOT your stored data's confidentiality.
Critical warning: pad reuse is fatal. If any pad section is used twice, XOR of the two ciphertexts equals XOR of the two plaintexts — the encryption breaks. Multi-device sync requires an atomic reservation protocol for pad sections.
Option B: Symmetric Key from Seed (Computational PQ Security)
Derive a 256-bit symmetric key from the BIP39 seed (independent of secp256k1) and encrypt with ChaCha20-Poly1305 or AES-256-GCM:
storage_key = HKDF-Extract(salt="nostr-storage-v1", ikm=bip39_seed)
ciphertext = ChaCha20-Poly1305(storage_key, nonce, plaintext)
This is post-quantum because:
- HKDF-SHA256 is symmetric (~128-bit PQ security)
- ChaCha20 / AES-256 are symmetric (~128-bit PQ security)
- The secp256k1 key is never part of the encryption path
Tradeoff vs OTP: Simpler (no pad management, no USB drive), but computationally secure rather than information-theoretically secure. Adequate for most use cases.
Comparison
| Property | OTP | Symmetric key from seed |
|---|---|---|
| Security level | Information-theoretic (perfect secrecy) | Computational, ~128-bit PQ |
| Key material | Pad as long as all data | Single 256-bit key |
| Storage overhead | Pad on USB drive | Just the seed phrase |
| Quantum resistance | Unconditional | Conditional (Grover's is optimal) |
| Future-proofing | Permanent | Could theoretically fall to new quantum algorithms |
| Multi-device | Requires pad sync + offset coordination | Just share the seed |
| Practicality | More complex | Simpler |
Component 6: Deterministic Wallet Compartmentalization
BIP32/BIP39 deterministic wallets (NIP-06) provide meaningful quantum compartmentalization through their tree structure.
The Core Protection: Chain Codes Are Secret
When Shor's algorithm recovers a leaf private key from its published public key, the attacker gets only that key. To climb the tree (recover the parent private key), they need the parent chain code:
parent_private_key = child_private_key - IL mod n
where IL = HMAC-SHA512(parent_chain_code, parent_public_key || index)[:32]
The parent chain code is NOT published in normal Nostr usage. HMAC-SHA512 is a symmetric primitive — a quantum computer provides no advantage. Without chain codes, the attacker is stuck at the leaf level.
The Firewall Effect of Hardened Derivation
The NIP-06 path m/44'/1237'/account'/0/0 has hardened derivation at three levels. This means:
- Breaking all keys in Account 0 does NOT compromise Account 1 (hardened at
account'level) - The seed/master key remains secure even if all account keys are broken
- Hardened derivation requires the parent private key, not just the public key
The Critical Vulnerability: Chain Code Leakage
If an attacker obtains a chain code at any level AND breaks one child key at that level via Shor's, they can:
- Compute
ILusing the chain code + child public key + index - Recover the parent private key
- Derive ALL children at that level
- Continue climbing if parent chain codes are also known
Where chain codes leak: published xpubs (extended public keys), wallet software that exports xpubs, backup exposure. In standard Nostr usage, chain codes are not published.
Summary
| Scenario | Compromised? |
|---|---|
| One leaf key broken via Shor's | Only that key |
| Multiple leaf keys from same account broken | Only those keys |
| Leaf key broken + chain code at that level known | Parent + all siblings |
| Account key broken + chain code at account level known | Parent + all accounts |
| Seed phrase stolen (classical) | Everything |
| Seed safe, quantum attacker, no chain codes leaked | Only published keys |
Recommendations
- Use separate accounts for separate Nostr identities — hardened derivation contains the damage
- Never publish xpubs — chain codes are the keys to the kingdom
- Use 24-word mnemonics for adequate PQ security on the seed entropy
- Don't reuse keys across services — each identity gets its own account index
- Assume published keys WILL be broken — plan for containment, not prevention
The Complete Migration Flow
flowchart TD
subgraph NOW["Pre-quantum - NOW"]
USER[User with raw nsec OR seed phrase]
USER --> MIGRATE[Client: Enable PQ migration]
MIGRATE --> SEED[Generate or use 12-word BIP39 phrase]
SEED --> DERIVE[Derive: new secp256k1 + all PQ keys + storage key]
DERIVE --> ENCRYPT[Encrypt old nsec if migrating from raw nsec]
ENCRYPT --> STORE30078[Publish kind 30078: encrypted old nsec]
DERIVE --> KEYLINK[Publish key-link event: cross-signed by all PQ schemes]
KEYLINK --> OTS[OpenTimestamp the key-link event via NIP-03]
OTS --> BTC[Anchored in Bitcoin block]
KEYLINK --> RELAY[Published to relays]
STORE30078 --> RELAY
SEED --> BACKUP[User writes down 12-word phrase]
end
subgraph TRANSITION["Transition period - dual signing"]
DUAL[Client signs events with both secp256k1 and PQ keys]
DUAL --> LEGACY[Legacy clients verify secp256k1 signature]
DUAL --> PQAWARE[PQ-aware clients verify PQ signature]
end
subgraph FUTURE["Post-quantum - when quantum computers arrive"]
ATTACK[Attacker breaks secp256k1 via Shors]
ATTACK --> FORGE[Can forge secp256k1 signatures]
ATTACK --> CANNOT1[Cannot forge PQ signatures]
ATTACK --> CANNOT2[Cannot backdate events - OTS proofs anchor history]
ATTACK --> CANNOT3[Cannot break OTP-encrypted storage]
PQAWARE --> VERIFY[Verify key-link event via OTS]
VERIFY --> USEPQ[Switch to PQ signatures]
USEPQ --> REJECT[Reject forged secp256k1-only events]
end
subgraph RETIRE["Old key retirement"]
DROP[User drops extra backup, keeps 12 words only]
DROP --> PQONLY[Client uses PQ keys only]
PQONLY --> DONE[Migration complete]
end
NOW --> TRANSITION --> FUTURE --> RETIRE
style NOW fill:#cfc
style TRANSITION fill:#ffa
style FUTURE fill:#fcc
style RETIRE fill:#cfc
User Experience
- User clicks "Enable quantum-safe migration" in their client
- Client generates a 12-word phrase, shows it to the user to write down
- Client handles everything else automatically:
- Derives new secp256k1 key + all PQ keys from seed
- Encrypts old nsec and publishes as kind 30078 (if migrating from raw nsec)
- Publishes cross-signed key-link event
- OpenTimestamps the key-link event via NIP-03
- During transition: client signs with both old and new keys
- After quantum migration: client uses PQ keys only, old key retired
- User drops extra backup, keeps 12 words
What Remains Unsolved
Historical Event Authenticity (Partially Solved)
Past secp256k1-signed events that were NOT OpenTimestamped remain forgeable after quantum break. An attacker can create fake old events that appear to be from you.
Mitigation: OpenTimestamp important events now (NIP-03). Events with valid OTS proofs are cryptographically anchored to a pre-quantum timestamp and cannot be forged retroactively. Events without OTS proofs become untrustworthy after quantum break.
Not fully solved: Routine events (regular posts, reactions) that aren't worth OpenTimestamping will become forgeable. The impact is low for most content, but high for events used as evidence, contracts, or historical records.
Relay Trust for Recovery
Approach B (12 words + relay backup) depends on relays retaining the kind 30078 event containing the encrypted old nsec. If all relays delete it, the old nsec is lost.
Mitigations:
- Publish to multiple relays
- Use relays you control or trust
- Use the hybrid approach (Approach C) with 36-word fallback
- The event is small and signed, so it's easy to preserve
PQ Signature Size
PQ signatures are much larger than secp256k1 Schnorr signatures:
| Scheme | Signature size |
|---|---|
| secp256k1 Schnorr | 64 bytes |
| ML-DSA-65 | ~3.3 KB |
| SLH-DSA-128s | ~8 KB |
| FN-DSA-512 | ~0.7 KB |
This increases event sizes during the transition period (dual signing). After migration, events can be signed with PQ only. FN-DSA (Falcon) offers the most compact PQ signatures.
PQ Algorithm Risk
Any PQ algorithm could theoretically be broken by a new classical or quantum algorithm (as SIKE was). The multi-scheme approach hedges against this — if one scheme falls, the others remain linked. But if ALL lattice-based schemes are broken simultaneously, only SLH-DSA (hash-based, very conservative) remains as a fallback.
Implementation Status
This is a design document. The following building blocks already exist:
Existing Infrastructure
| Component | Status | Location |
|---|---|---|
| NIP-06 (key derivation from mnemonic) | Implemented | nostr_core_lib: nip006.c |
| NIP-03 (OpenTimestamps) | Implemented | nostr_core_lib: nip003.c |
| NIP-44 (encrypted payloads) | Implemented | nostr_core_lib: nip044.c |
| NIP-04 (encrypted DMs, deprecated) | Implemented | nostr_core_lib: nip004.c |
| OTP cipher (information-theoretic encryption) | Implemented | ~/lt/otp |
To Be Built
| Component | Description |
|---|---|
| Key-link NIP | New NIP defining the cross-signed key-link event format |
| PQ key derivation from BIP39 seeds | HKDF-based derivation of ML-DSA, SLH-DSA, FN-DSA, ML-KEM keys |
| PQ signature support | Integration of liboqs or similar for PQ signature verification |
| Migration tooling | Client-side tooling for migrating raw nsec users to seed phrases |
| Kind 30078 encrypted storage | Symmetric-key or OTP encryption for self-storage |
| Extended phrase encoding | 36-word phrase format (Approach A/C) |
Dependencies
- liboqs — Open Quantum Safe library for PQ algorithms
- OpenTimestamps — already integrated via NIP-03
- BIP39/BIP32 — already integrated via NIP-06
References
- NIP-03: OpenTimestamps Attestations for Events
- NIP-06: Basic key derivation from mnemonic seed phrase
- NIP-44: Encrypted Payloads (Versioned)
- BIP39: Mnemonic code for generating deterministic keys
- BIP32: Hierarchical Deterministic Wallets
- NIST FIPS 203: ML-KEM (Kyber)
- NIST FIPS 204: ML-DSA (Dilithium)
- NIST FIPS 205: SLH-DSA (SPHINCS+)
- OpenTimestamps
- Open Quantum Safe
License
This document is released into the public domain. The migration strategy described herein is intended as a community resource for the Nostr ecosystem.