2026-07-16 20:36:25 -04:00

Nostr Quantum Preparation

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 signing 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.

https://laantungir.net/quantum-prep/

⚠️ 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 implementation status 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

  1. No social graph loss — existing follows and identity carry forward
  2. No user re-onboarding — the seed phrase becomes the new root of trust
  3. Backward compatible — legacy clients continue working; PQ is additive
  4. No algorithm consensus required — link to all viable PQ schemes now, converge later
  5. Pre-quantum timestamping — establish links now, before quantum computers exist
  6. 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.


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 09999 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).

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_at is 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 ~2030 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 ~2030 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


References


License

This document is released into the public domain. The preparation strategy described herein is intended as a community resource for the Nostr ecosystem.

S
Description
No description provided
Readme
6.8 MiB
Languages
JavaScript 87.5%
CSS 6.1%
HTML 5.7%
Shell 0.7%