Nostr Quantum Preparation
/tmp/sb_test_save.jpg
TLDR
Problem
Quantum computers will break secp256k1 which nostr relies on for its public private key pair. This means that given an npub, a quantum computer will be able to derive your nsec, read all your encrypted data and sign events as you.
We have new cryptography that we think is secure in a post quantum world that we can use in nostr, but the field is still evolving, and we have no idea of how much time we have left to migrate nostr. It could be we have 1 year or 100 years until quantum computers arrive. This makes settling on new cryptography difficult.
Solution
This project is an interim solution to the problem for those who are concerned about keeping their identities secure through the transition, and also to give developers breathing room.
This project takes what I have found as the likely post quantum algorithms, and will generate for you a new public private key pair for all of them.
I have currently used the following post quantum algorithms:
- ML-DSA-44
- ML-DSA-65
- SLH-DSA-128s
- Falcon-512
- ML-KEM-768
This app will generate public-private keypairs for each of these, publish on nostr your public key for each, and sign it with your current Nostr identity via your favorite signer (the nsec never leaves the signer).
You are making a statement to the world: "Here are post quantum pubkeys. If quantum computers hit, you know that the person using these can only be me."
This entire kind 1 event is then hashed and stamped into a block on Bitcoin using OpenTimestamps.
The OpenTimestamps pending proof is published to nostr as a (new) kind 9999 event. If the app is left open, it will watch for the confirmation that your event has been timestamped on Bitcoin, and then publish a second kind 9999 with the upgraded timestamp.
You can then store the new seed phrase that was generated, along with the kind 9999 event(s), and if quantum computers suddenly appear, you have an identity that could only have been created by you.
It's interesting to note that even if a nostr consensus decides not to use any of these algorithms, as long as any of them are resistant to quantum attacks, you can use it to prove your transition from your current nostr identity to your new nostr identity.
I invite you to try it out, and give me any feedback. Run through it with a test identity before you use it on your real identity.
Note that I consider using this app low stakes, in that you are not revealing to the app your current private key. You are using a nostr signer. And also, you don't HAVE to use these public keys in the future. They are just sitting out there in case you ever need them. And if you screw up, or lose your seed phrase, just run the whole thing again.
⚠️ Warning. Do not enter a valuable existing mnemonic into the web app. This project links post-quantum keys to an existing Nostr identity and anchors that link in Bitcoin. It does not, by itself, make a Nostr identity post-quantum secure: complete migration requires companion protocols (PQ event authentication, rotation, encryption, client adoption) that are not yet specified or implemented. See the section for what exists today.
A preparation strategy for the eventual post-quantum migration of 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
Nostr's identity, authentication, and encryption (NIP-04 and NIP-44) all rely on secp256k1. Shor's algorithm breaks secp256k1 in polynomial time on a sufficiently large quantum computer, so an attacker who has recorded any of your events can recover your private key, forge signatures, decrypt your messages, and impersonate you.
NIP-44 is a real classical-security upgrade over NIP-04, but it offers zero post-quantum advantage: both derive their shared secret from secp256k1 ECDH, and everything NIP-44 adds (HKDF, ChaCha20, HMAC, padding) sits downstream of that broken step. The symmetric primitives themselves (AES-256, ChaCha20, HMAC-SHA256) are adequately post-quantum; the weakness is exclusively the ECDH key agreement.
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 preparation]
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 preparation 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 64 bytes of entropy derived from a mnemonic via PBKDF2-HMAC-SHA512 — a symmetric KDF with no public-key cryptography, so a quantum computer gains nothing beyond Grover's quadratic speedup. The seed is algorithm-agnostic: you can derive any key type from it.
All keys — both secp256k1 and PQ — are derived from the BIP39 seed via BIP32 hierarchical deterministic derivation, the same standard NIP-06 uses for secp256k1. PQ keys are derived at different child indices under the NIP-06 base path, using the BIP32 child private key (32 bytes) as the deterministic seed for each PQ algorithm's keygen().
Base path: m/44'/1237'/0'/0/ (NIP-06 account 0, change 0)
| Child index | Algorithm | Seed length | BIP32 path | How derived |
|---|---|---|---|---|
| 0 | secp256k1 (NIP-06) | 32 bytes | m/44'/1237'/0'/0/0 | Standard BIP32 — private key used directly |
| 1 | ML-DSA-44 | 32 bytes | m/44'/1237'/0'/0/1 | Single child — 32-byte private key is the PQ seed |
| 2 | ML-DSA-65 | 32 bytes | m/44'/1237'/0'/0/2 | Single child — 32-byte private key is the PQ seed |
| 3 + 4 | SLH-DSA-128s | 48 bytes | m/44'/1237'/0'/0/3 + m/44'/1237'/0'/0/4 | Two children concatenated (64 bytes), first 48 used |
| 5 + 6 | Falcon-512 | 48 bytes | m/44'/1237'/0'/0/5 + m/44'/1237'/0'/0/6 | Two children concatenated (64 bytes), first 48 used |
| 7 + 8 | ML-KEM-768 | 64 bytes | m/44'/1237'/0'/0/7 + m/44'/1237'/0'/0/8 | Two children concatenated (64 bytes) |
Truncation rule (pinned for compatibility): when a PQ seed needs more than 32 bytes, two BIP32 children are derived and concatenated (64 bytes total). For 48-byte seeds, the first 48 bytes are used; for 64-byte seeds, all 64 bytes are used. Implementations must use the first N bytes — taking the last 48 would produce different keys and break compatibility.
⚠️ Continuity is asserted, not proven (G56-06). The old Nostr identity authorizes the PQ keys by signing an event that contains them. This is an authorization, not a cryptographic proof that the PQ keys were derived from the seed phrase shown to the user. A compromised browser could display one mnemonic while publishing attacker-controlled PQ keys. The only real proof is out-of-band verification: after the key link is published, take the seed phrase to a different device, derive the keys independently, and confirm the public keys match the published event. This is the one step a compromised browser cannot fake.
Seed entropy
The implementation defaults to 24-word (256-bit) mnemonics, with a 12-word option for testing. Under Grover's quadratic speedup, 12 words leaves only ~64 bits of PQ security; 24 words gives ~128 bits. Use 24 words for a real PQ recovery root. The bigger risk to seed phrases is classical (theft, phishing), not quantum — treat the phrase as a high-value secret and store it offline.
Secret material lifetime
On sign-out, the app zeroizes all Uint8Array secret buffers (BIP39 seed, secp private key, five PQ secret keys), clears the DOM, and clears the clipboard. JavaScript garbage collection does not guarantee erasure — the browser runtime may retain copies internally. The real fix is moving key derivation and signing into a hardware or native signer boundary; the in-browser zeroization is a best-effort reduction of the exposure window, not a guarantee.
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 and let consensus emerge later. The PQ standardization landscape is still evolving (ML-DSA and ML-KEM are finalized, Falcon is still draft, SIKE was a NIST finalist broken in 2022 on a laptop), so hedging across schemes hedges against any single scheme falling.
The key link uses two Nostr events:
1. Kind 1 Announcement (human-readable, visible in Nostr feeds)
A regular Nostr text note whose content is a human-readable attestation naming the user's npub and listing the PQ algorithms. The PQ public keys and signatures go in algorithm tags. Each PQ signature scheme signs TextEncoder.encode(content); the event is signed with the user's existing Nostr identity via window.nostr.signEvent (NIP-07).
2. Kind 9999 Proof Carrier (machine-verifiable, carries the OTS proof)
A non-replaceable event (kind 9999, in the 0–9999 range) whose content is the full signed kind 1 event as JSON (so verifiers don't need to fetch it from relays) and whose tags carry the OpenTimestamps proof. Each publication is permanent — upgrades publish a new kind 9999 with an upgrade_of tag referencing the previous one; both remain on relays. The non-replaceable range is load-bearing for this append-only design.
Tags: ['e', '<kind1 id>'], ['sha256', '<hex canonical digest of the signed kind 1 event>'], ['digest_version', '<version>'], ['ots', '<base64 .ots proof>'], ['ots_status', 'pending'|'confirmed'], and optionally ['upgrade_of', '<previous proof carrier id>']. The wrapper is signed with the user's existing identity.
What this proves and what it does not
Proves:
- Each PQ signature scheme (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512) signed the attestation text — verified against the pubkey in its tag.
- The user's existing Nostr identity authorized the key link — verified by the secp256k1 Schnorr signature on both events.
- The event is anchored to a pre-quantum Bitcoin block via OpenTimestamps — the event with the earliest valid OTS proof wins.
Does NOT prove:
- The PQ keys cannot cryptographically prove they share a common seed origin. They prove only that five PQ keypairs exist and each signed the attestation. Common-origin is asserted in the attestation and authorized by the existing identity's signature, not proven by a seed-derived signature.
- The seed-derived secp256k1 key (child 0) is derived but not used to sign. There is no "successor signature."
This is deliberate. The security model relies on OTS precedence (the real event is anchored pre-quantum; a forged event cannot be backdated) rather than a seed-derived signature.
ML-KEM cannot sign
ML-KEM (Kyber) is a KEM (Key Encapsulation Mechanism), not a signature scheme, so it cannot sign the attestation. Its public key is included in an algorithm tag without a signature field; its ownership is asserted by the attestation text and authorized by the existing identity's signature over the kind 1 event (which covers the tags). This is weaker than a cross-signature but sufficient: the secp256k1 signature is valid now (pre-quantum), and the ML-KEM key is for encryption, not authentication — a false ownership claim is self-correcting (the claimant couldn't decrypt messages sent to that key).
Revocation
If a PQ scheme is later found to be weak (like SIKE), clients simply stop trusting it — no revocation event needed. The remaining links stay valid. A user may optionally publish a new key-link event omitting the broken scheme, signed by the still-valid PQ keys.
Component 3: OpenTimestamps for Pre-Quantum Anchoring
NIP-03 (OpenTimestamps Attestations for Events) anchors Nostr event hashes to the Bitcoin blockchain. The implementation submits the canonical digest of the fully signed kind 1 event to OpenTimestamps calendar servers and embeds the resulting .ots proof in the kind 9999 proof carrier's ots tag. The proof carrier carries the proof; the kind 1 event is what's timestamped.
The canonical digest (version 1) is sha256(JSON.stringify([0, pubkey, created_at, kind, tags, content, id, sig])) — the NIP-01 event-ID serialization array extended with the id and sig fields. This is reproducible by any conforming implementation (including non-JS ones and verifiers that re-fetch the event from a relay with different key order), unlike sha256(JSON.stringify(event)) which is key-order-dependent. The proof carrier records the digest version in a digest_version tag; verifiers fail closed on unknown versions. Legacy v0 proof carriers (pre-canonicalization) committed to sha256(JSON.stringify(event)) and are accepted only for historical inspection, not as valid for the key link.
Why Bitcoin timestamping is quantum-resistant
Bitcoin's 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 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 re-mine that block (impossible) or find a SHA-256 preimage (still infeasible).
Why the key-link event must be anchored
Without OTS on the key-link event:
- Attacker breaks secp256k1 key via Shor's
- Attacker publishes a fraudulent key-link event linking your 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 event is anchored to Bitcoin block N (before quantum computers)
- The 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
Current OTS verification status
Trust model. The current OTS verification is multi-explorer-checked, not a full Bitcoin light-client verification. Treat results accordingly.
The current implementation parses the .ots proof, walks the full Merkle path, and validates the resulting Merkle root against a Bitcoin block header fetched from two independent public explorer APIs (blockstream.info and mempool.space). The two providers are cross-checked and the verifier fails closed on disagreement, making coordinated false responses harder. The result's trustMode field reports the achieved trust level:
multi-explorer-checked— both providers agreed on the Merkle root (strongest available mode).single-explorer-checked— only one provider responded (weaker; the UI labels it accordingly).structural-only— no Bitcoin attestation was verified (pending or none).
Proof upgrading (asking calendars for a confirmed proof) is delegated to a server-side helper.
Not yet done: full light-client verification — validating block headers, proof-of-work, difficulty, and chain linkage independently, without trusting any explorer API. Because the block header is trusted from explorer APIs (even when cross-checked), a compromised/colluding set of explorers could still falsify a confirmation. The UI labels results as "multi-explorer-checked (cross-verified)" or "single-explorer-checked (trusted API)," never as "trustless." The vendored javascript-opentimestamps library in resources/ provides the primitives for full light-client verification, which is planned for a future release.
Component 4: Migrating Existing Users with Raw nsec (Design Only)
Status: Design only — not yet implemented. The current implementation supports users who already have a Nostr identity via a NIP-07 signer. The raw-nsec preparation below is future work.
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. The challenge is that a raw nsec is 32 random bytes — it can't be "derived" from a seed (that's a preimage problem), so it must be encoded (directly or encrypted) alongside a new seed phrase.
Three approaches have been designed:
- Approach A — 36-word phrase: Generate a 12-word BIP39 seed, derive an encryption key from it, encrypt the old nsec with ChaCha20, and encode the 32-byte ciphertext as 24 more BIP39 words. The full 36-word phrase recovers both keys; the first 12 alone recover the PQ keys after the eventual migration.
- Approach B — 12 words + relay backup (recommended): Generate a 12-word phrase, derive the new secp key, PQ keys, and a symmetric encryption key, encrypt the old nsec, and publish it as a kind 30078 event signed with the new key. Recovery needs only the 12 words plus relay access to fetch and decrypt the event.
- Approach C — Hybrid: Primary backup is 12 words (relay recovery); optional fallback is also writing the 24 extra encrypted-nsec words in case relays are unavailable.
All three are quantum-safe. Approach B is recommended because it minimizes what the user must back up.
Component 5: Quantum-Safe Self-Storage (Design Only)
Status: Design only — not yet implemented.
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 are proposed:
- One-time pad (OTP): Information-theoretic security — the strongest guarantee in cryptography, stronger than any computational security including post-quantum. The pad must be truly random, as long as the data, never reused, and stored securely (e.g., USB drive). Self-storage sidesteps OTP's usual key-distribution problem since you're encrypting for yourself. Pad reuse is fatal: reusing any section breaks the encryption.
- Symmetric key from seed: Derive a 256-bit key from the BIP39 seed (independent of secp256k1) via HKDF and encrypt with ChaCha20-Poly1305 or AES-256-GCM. Simpler than OTP (no pad management), computationally PQ-secure (~128-bit), and adequate for most use cases.
OTP gives clean separation: a quantum break of your secp256k1 key compromises identity/authentication but not stored-data confidentiality.
Component 6: Deterministic Wallet Compartmentalization
BIP32/BIP39 deterministic wallets (NIP-06) provide meaningful quantum compartmentalization through their tree structure. The key insight is that chain codes are secret — when Shor's recovers a leaf private key from its published public key, the attacker gets only that key. To climb the tree they need the parent chain code, which is a symmetric HMAC-SHA512 primitive with no quantum advantage. Without chain codes, the attacker is stuck at the leaf level.
The NIP-06 path m/44'/1237'/account'/0/0 has hardened derivation at three levels, so breaking all keys in Account 0 does not compromise Account 1, and the seed/master key remains secure even if all account keys are broken.
| 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 identities; never publish xpubs (chain codes are the keys to the kingdom); use 24-word mnemonics; don't reuse keys across services; assume published keys will be broken and plan for containment, not prevention.
What Remains Unsolved
- Historical event authenticity (partially solved): Past secp256k1-signed events that were not OpenTimestamped remain forgeable after a quantum break. OpenTimestamping important events now (NIP-03) anchors them cryptographically; routine events that aren't worth timestamping will become forgeable, but the impact is low for most content.
- Relay trust for recovery: Approach B (Component 4, design only) depends on relays retaining the kind 30078 event containing the encrypted old nsec. Mitigations: publish to multiple relays, use relays you control, or use the hybrid 36-word fallback.
- PQ signature size: PQ signatures are much larger than secp256k1 Schnorr (64 bytes) — ML-DSA-44 ~2.4 KB, ML-DSA-65 ~3.3 KB, SLH-DSA-128s ~8 KB, Falcon-512 ~0.7 KB. The kind 1 announcement is ~20–30 KB total, a one-time cost for the key-link event.
- PQ algorithm risk: Any PQ algorithm could theoretically be broken (as SIKE was). The multi-scheme approach hedges; if all lattice-based schemes fall simultaneously, only SLH-DSA (hash-based, very conservative) remains.
- Common-origin not cryptographically proven: See Component 2. OTS precedence, not a seed-derived signature, is the mechanism that distinguishes real from forged events after a quantum break.
Implementation Status
What is implemented
The current implementation is a static web app (www/) that performs the full preparation flow for users who already have a Nostr identity:
| Component | Status | Location |
|---|---|---|
| BIP39 seed phrase generation (24-word default, 12-word option, with optional user entropy) | Implemented | www/js/pq-crypto.mjs |
| BIP32 key derivation (secp256k1 + 5 PQ keypairs) | Implemented | www/js/pq-crypto.mjs |
| PQ signing (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512) | Implemented | www/js/pq-crypto.mjs |
| ML-KEM-768 keygen (KEM, no signing) | Implemented | www/js/pq-crypto.mjs |
| Kind 1 announcement event construction | Implemented | www/js/pq-crypto.mjs |
| Kind 9999 proof carrier event construction | Implemented | www/js/pq-crypto.mjs |
| NIP-01 event ID computation and Schnorr verification | Implemented | www/js/pq-crypto.mjs |
| OpenTimestamps submission (pending proof) | Implemented | www/js/pq-crypto.mjs |
| OTS upgrade polling (via server helper) | Implemented | www/js/pq-crypto.mjs |
| OTS full Merkle-path verification, multi-explorer cross-checked (blockstream + mempool) | Implemented | www/js/pq-crypto.mjs |
| Canonical proof carrier selection (earliest valid Bitcoin anchor wins) | Implemented | www/js/pq-crypto.mjs |
| Signer output validation (G56-05: reject mutated/invalid signer responses) | Implemented | www/js/pq-crypto.mjs |
| Proof archive export/import (offline verification without a relay) | Implemented | www/js/pq-crypto.mjs, www/verify.html |
| Versioned PQ algorithm policy (F-D3: mandatory/KEM/ignored sets) | Implemented | www/js/nip-qr-policy.mjs |
| Relay publishing (WebSocket) | Implemented | www/index.html |
| Verification page (query relay, paste event JSON, or import proof archive) | Implemented | www/verify.html |
| NIP-07 signer integration (nostr-login-lite) | Implemented | www/index.html |
| Test suite + deterministic test vectors (seed → pubkeys) | Implemented | test/pq-crypto.test.mjs, test/vectors/ |
What is not yet implemented
| Component | Description |
|---|---|
| Full light-client Bitcoin verification | Validate block headers, proof-of-work, difficulty, and chain linkage independently of any explorer API (the vendored javascript-opentimestamps library in resources/ provides primitives). The current path cross-checks two explorer APIs but still trusts them for the header rather than verifying PoW/difficulty/chain-linkage itself. |
| Raw-nsec preparation (Component 4) | Encrypt old nsec, publish kind 30078, 36-word phrase encoding |
| Quantum-safe self-storage (Component 5) | OTP or symmetric-key encryption for kind 30078 data |
| PQ event authentication / rotation / revocation | Companion protocols for signing future events with PQ keys, rotating/revoking keys, PQ encryption |
| Independent implementation | A second, independent implementation reproducing canonical encoding, derivation, and selection (deterministic test vectors already exist in test/vectors/; a third-party reimplementation is the remaining step). |
| Relay event-size interop testing | The kind 1 announcement is ~20–30 KiB; the kind 9999 proof carrier embeds it plus OTS proof data. The UI shows the event size and warns if it exceeds ~60 KiB (some relays reject large events). Testing against target relay policies and a compact binary format remain as future work. |
| Reproducible-build verification of the deployed bundle | The deployed www/pq-crypto.bundle.js is a build artifact produced by build-pq-bundle.js from the audited source in www/js/pq-crypto.mjs. Users must currently trust that the deployed bundle matches the source. Reproducible-build verification (so anyone can rebuild the bundle byte-for-byte and confirm the deployed file matches) is on the roadmap. |
Dependencies
@noble/post-quantum— pure-JS PQ algorithms (ML-DSA, SLH-DSA, Falcon, ML-KEM)@noble/curves— secp256k1 Schnorr@noble/hashes— SHA-256@scure/bip39— BIP39 mnemonic generation/validation@scure/bip32— BIP32 HD wallet derivation@scure/base— bech32 (npub) encoding- OpenTimestamps — calendar servers for timestamping
- — vendored OTS library (used as a fixture source; full light-client integration pending)
- nostr-login-lite — NIP-07 / NIP-46 authentication
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+)
- NIST FIPS 206: Falcon (draft) — Falcon-512 signature scheme
- OpenTimestamps
- Open Quantum Safe
License
This document is released into the public domain. The preparation strategy described herein is intended as a community resource for the Nostr ecosystem.

