Files
nostr_quantum_preparation/why_what_how.md
T

16 KiB

Post-Quantum Nostr: Why, What, How

⚠️ Research prototype. This document describes an experimental pre-quantum key-commitment and identity-link system. 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.

Why: The Problem

Quantum computers will break Nostr's cryptography

Nostr's entire security model rests on secp256k1 elliptic curve cryptography:

  • Identity — your pubkey IS your identity. It's in every event, it's your npub, 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/NIP-44) — both use secp256k1 ECDH to derive a shared secret, then symmetric encryption (AES-256-CBC or ChaCha20+HMAC).

Shor's algorithm breaks the elliptic curve discrete logarithm problem in polynomial time. With a sufficiently large fault-tolerant quantum computer, an attacker who has recorded any Nostr event can:

  1. Extract the public key from the event
  2. Run Shor's algorithm to recover the private key
  3. Forge signatures, decrypt all NIP-04/NIP-44 messages, and impersonate the user

This is not a theoretical concern. NIST has already standardized post-quantum algorithms (FIPS 203, 204, 205) and published guidance prohibiting classical cryptography after 2035. Apple, Signal, WhatsApp, and OpenSSH have already deployed post-quantum encryption. The migration is happening now.

Why Bitcoin's hash-the-pubkey trick doesn't work for Nostr

Bitcoin has limited quantum resistance because addresses are hashes of pubkeys, and pubkeys are only revealed at spend time. This doesn't work for Nostr because:

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: it IS the identity, it must be revealed for signature verification, and it's reused across thousands of events over years. Hashing the pubkey would give zero seconds of quantum resistance — the pubkey must be revealed the moment you publish your first event.

The collect-now-decrypt-later threat

Even if quantum computers don't exist yet, adversaries can record encrypted Nostr messages today and decrypt them later when a quantum computer becomes available. Every NIP-04 and NIP-44 message stored on public relays is vulnerable to this attack. The symmetric ciphers (ChaCha20, AES-256) are quantum-safe, but the ECDH key agreement that produces the shared secret is not.

The standardization uncertainty problem

NIST finalized ML-KEM (Kyber), ML-DSA (Dilithium), and SLH-DSA (SPHINCS+) in 2024, but the history of post-quantum cryptography includes broken schemes — SIKE was a NIST finalist broken in 2022 by a classical algorithm that ran in an hour on a laptop. If the Nostr community picks one algorithm and it's later broken, we'd be back to square one. Picking a single algorithm is risky.

The migration problem

Most Nostr users today have a raw nsec (random private key) with no seed phrase. Their identity, social graph, NIP-05 verification, and follower relationships are all tied to that key. Any post-quantum migration that requires users to abandon their identity and start over is a non-starter. The migration must preserve identity, social graph, and user experience.


What: Our Solution

A migration strategy that brings post-quantum security to Nostr:

  • Without breaking the social graph — existing follows and identity carry forward
  • Without requiring consensus on a single PQ algorithm — link to all viable schemes now, converge later
  • Without forcing existing users to abandon their identities — the seed phrase becomes the new root of trust
  • With pre-quantum timestamping — establish links now, before quantum computers exist, anchored to the Bitcoin blockchain

The six components

  1. Seed phrase as algorithm-agnostic root of trust — A BIP39 mnemonic is pure entropy (128/256 bits). It's not tied to any cryptographic algorithm. PQ keys are derived from it deterministically using BIP32 hierarchical deterministic derivation (the same standard used by NIP-06 for secp256k1 keys), with each PQ algorithm at a different child index under the NIP-06 base path. Same seed always produces the same PQ keys. The seed is quantum-safe (symmetric KDF, Grover's quadratic speedup is negligible).

  2. Multi-scheme cross-signed key-link events — Two Nostr events (a kind 1 announcement and a kind 9999 proof carrier) that link a user's secp256k1 identity to multiple PQ keys simultaneously: ML-DSA-44 and ML-DSA-65 (lattice-based signatures), SLH-DSA-128s (hash-based signatures), Falcon-512 (lattice-based signatures), and ML-KEM-768 (lattice-based KEM for encryption). Each PQ signature scheme independently signs the attestation text. If one scheme is later broken, the others remain valid.

  3. OpenTimestamps for pre-quantum anchoring — The key-link event is timestamped via NIP-03 (OpenTimestamps), anchoring it to a Bitcoin block. This prevents a future quantum attacker from backdating a fraudulent key-link event, because they cannot produce a valid OTS proof for a past Bitcoin block (re-mining historical blocks is infeasible even with a quantum computer).

  4. Migrating existing users with raw nsec (design only — not yet implemented) — Users who logged in with a raw nsec (no seed phrase) generate a new seed phrase. A two-account migration event links their old identity (Account #1, raw nsec) to a seed-derived successor (Account #2) and its PQ keys. The old identity signs to authorize the migration. (The raw-nsec migration is a design proposal; the current implementation supports users who already have a Nostr identity.)

  5. Quantum-safe self-storage — Sensitive data stored on relays (kind 30078) can be encrypted with either a one-time pad (information-theoretic security, stronger than any computational assumption) or a symmetric key derived from the seed (128-bit PQ security via Grover's). This separates data confidentiality from identity authentication — breaking secp256k1 compromises identity but not stored data.

  6. Deterministic wallet compartmentalization — BIP32/BIP39 HD wallets (NIP-06) provide quantum compartmentalization through their tree structure. When Shor's algorithm recovers a leaf private key, the attacker cannot climb the tree without the parent chain code (a secret symmetric value). Hardened derivation at the account level means breaking one account doesn't compromise others.

What this is NOT

  • It is not a new signature scheme for every event — PQ signatures are used additively for the one-time key-link event, not for routine posts
  • It is not a consensus requirement — the community doesn't need to agree on one PQ algorithm; multiple schemes are linked simultaneously
  • It is not a hard fork — legacy clients continue working; PQ is additive
  • It is not a trust-the-server solution — all cryptography happens client-side in the browser

How: How It Works

The cryptographic chain of trust

Account #1 (existing identity, social graph knows this npub)
    |
    | signs: "I authorize this migration to these PQ keys"
    |        (secp256k1 Schnorr signature on the kind 1 and kind 9999 events,
    |         valid pre-quantum)
    v
Kind 1 announcement event (published under Account #1's pubkey)
    |
    | PQ keys sign the attestation text:
    |   ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512
    |   (each PQ signature scheme signs the human-readable content)
    |   ML-KEM-768 pubkey included in tags (KEM, no signature)
    |
    | OpenTimestamps anchor of the kind 1 event hash to Bitcoin block
    | (proves the event existed at this time, pre-quantum)
    v
Kind 9999 proof carrier event (carries the OTS proof, embeds the kind 1 event)
    |
    v
    Published to Nostr relays

Note: The seed-derived secp256k1 key (Account #2, at m/44'/1237'/0'/0/0) is derived but not used to sign in the current implementation. There is no "successor signature." The security model relies on OTS precedence — the real event is anchored pre-quantum, and a forged event cannot be backdated to before that anchor.

The NIP-QR events

The migration uses two Nostr events:

Kind 1 announcement (human-readable, visible in Nostr feeds)

{
  "kind": 1,
  "pubkey": "<Account #1 secp256k1 pubkey>",
  "content": "I am signaling that the post-quantum public keys listed in the tags of this event were generated by me...",
  "tags": [
    ["block_height", "<height>"],
    ["algorithm", "ml-dsa-44", "<base64 pubkey>", "<base64 signature>"],
    ["algorithm", "ml-dsa-65", "<base64 pubkey>", "<base64 signature>"],
    ["algorithm", "slh-dsa-128s", "<base64 pubkey>", "<base64 signature>"],
    ["algorithm", "falcon-512", "<base64 pubkey>", "<base64 signature>"],
    ["algorithm", "ml-kem-768", "<base64 pubkey>"]
  ],
  "sig": "<Account #1 Schnorr signature>"
}

The content is a human-readable attestation statement. Each PQ signature scheme signs TextEncoder.encode(content). The PQ public keys and signatures go in the algorithm tags. ML-KEM-768 has only a pubkey tag (no signature — it's a KEM).

Kind 9999 proof carrier (machine-verifiable, carries the OTS proof)

{
  "kind": 9999,
  "pubkey": "<Account #1 secp256k1 pubkey>",
  "content": "<JSON string of the full signed kind 1 event>",
  "tags": [
    ["e", "<kind 1 event id>"],
    ["sha256", "<hex SHA-256 of full signed kind 1 event JSON>"],
    ["ots", "<base64 .ots proof>"]
  ],
  "sig": "<Account #1 Schnorr signature>"
}

The wrapper embeds the full kind 1 event as JSON content (so verifiers don't need to fetch it from relays) and carries the OpenTimestamps proof. The sha256 tag is the hash that was submitted to OpenTimestamps.

PQ key derivation from seed

Post-quantum keys are deterministically derived from the BIP39 seed using BIP32 hierarchical deterministic derivation (the same standard used by NIP-06 for secp256k1 keys). Each PQ algorithm gets its own child index under the NIP-06 base path m/44'/1237'/0'/0/:

seed = PBKDF2-HMAC-SHA512(mnemonic, "mnemonic", 2048)  // standard BIP39, 64 bytes
master = BIP32 master key from seed                     // HMAC-SHA512(seed, "Bitcoin seed")

// All under m/44'/1237'/0'/0/:
child 0  → secp256k1 keypair (NIP-06, derived but not used to sign)
child 1  → 32-byte seed → ML-DSA-44 keypair
child 2  → 32-byte seed → ML-DSA-65 keypair
child 3+4 → 64 bytes concatenated, first 48 used → SLH-DSA-128s keypair
child 5+6 → 64 bytes concatenated, first 48 used → Falcon-512 keypair
child 7+8 → 64 bytes concatenated → ML-KEM-768 keypair

The BIP32 child private key (32 bytes) is used as the deterministic seed for each PQ algorithm's keygen() function. For algorithms needing more than 32 bytes, two children are concatenated (64 bytes) and truncated to the required length (first N bytes). Same seed phrase always produces the same PQ keys. Users can recover their PQ keys by entering their seed phrase in any implementation of this scheme.

The web app

A static web page (no backend with secrets) that:

  1. Signs in — uses nostr-login-lite (NIP-46 bunker, seed phrase, or browser extension) to authenticate with the user's existing Nostr identity
  2. Generates a seed phrase — 24-word (256-bit) BIP39 mnemonic, generated client-side, never sent to any server
  3. Derives PQ keys — uses @noble/post-quantum (pure JavaScript, no WASM) to generate ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512, and ML-KEM-768 keypairs from the seed via BIP32 derivation paths
  4. Signs the events — PQ signatures are created in the browser; the secp256k1 signature is requested from the user's signer (via window.nostr.signEvent()) for both the kind 1 announcement and the kind 9999 proof carrier
  5. Timestamps the kind 1 event — submits the SHA-256 of the full signed kind 1 event JSON to OpenTimestamps and embeds the proof in the kind 9999 proof carrier
  6. Publishes to relays — sends both events to Nostr relays via direct WebSocket
  7. Polls for Bitcoin confirmation — upgrades the OTS proof and republishes the kind 9999 proof carrier with the confirmed proof
  8. Shows the result — displays the complete event JSON and event ID

All cryptographic operations happen either in the browser (JavaScript/WASM) or in the user's signer (Amber, browser extension, or bunker). No private key ever touches a server.

The algorithms

Algorithm FIPS Type Key size Signature size Why we use it
ML-DSA-44 204 Signature 1312 bytes 2420 bytes PQ signature (lattice-based, Cat 2) — compatibility
ML-DSA-65 204 Signature 1952 bytes 3309 bytes Primary PQ signature (lattice-based, NIST-standardized, Cat 3)
SLH-DSA-128s 205 Signature 32 bytes 7856 bytes Conservative fallback (hash-based, very conservative assumptions, Cat 1)
Falcon-512 206 (draft) Signature 897 bytes ~666 bytes Compact PQ signatures (lattice-based, Cat 1)
ML-KEM-768 203 KEM 1184 bytes 1088 bytes (ciphertext) PQ encryption key agreement (lattice-based, used by Signal, Apple)

We use multiple schemes simultaneously because:

  • If ML-DSA is broken (lattice-based), SLH-DSA (hash-based) remains valid
  • If all lattice schemes are broken, only the hash-based fallback remains
  • The community doesn't need to agree on one algorithm — clients can verify whichever subset they support

Post-quantum scenario (when secp256k1 is broken)

  1. Attacker breaks Account #1's secp256k1 key via Shor's algorithm
  2. Attacker can forge Account #1 signatures, but:
    • Cannot forge PQ signatures — ML-DSA and SLH-DSA remain secure
    • Cannot backdate a fraudulent NIP-QR event — the real event has an OTS proof anchored to a Bitcoin block that predates quantum computers
    • Cannot decrypt PQ-encrypted messages — ML-KEM remains secure
  3. Clients verify: "the NIP-QR event with the earliest valid OTS proof wins"
  4. The real NIP-QR event (anchored pre-quantum) is cryptographically distinguishable from any fraudulent event
  5. The user's identity is preserved through the OTS-anchored migration event
  6. The user continues signing with PQ keys

Note: The current implementation does not include a seed-derived secp256k1 "successor" signature. The PQ keys self-attest by signing the attestation text, and Account #1 authorizes the migration. The mechanism that distinguishes a real migration event from a forged one after a quantum break is OTS precedence — the real event is anchored to a pre-quantum Bitcoin block, and a forged event cannot produce an OTS proof for that block or earlier.

The demo

A working demo is deployed at https://laantungir.net/quantum-prep that implements the full flow: sign in, generate seed, derive PQ keys, sign the kind 1 and kind 9999 events, publish to relays, and timestamp via OpenTimestamps. It uses:

  • @noble/post-quantum for PQ cryptography (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512, ML-KEM-768)
  • @scure/bip39 for seed phrase generation
  • @scure/bip32 for BIP32 HD key derivation (NIP-06 base path)
  • @noble/curves for secp256k1 Schnorr verification
  • nostr-login-lite for authentication
  • Direct WebSocket for relay publishing
  • OpenTimestamps calendar servers for Bitcoin timestamping

The source code is in www/index.html and www/js/pq-crypto.mjs. To deploy updates, run ./upload.sh.