Files
nostr_quantum_preparation/nip_proposal.md
T

30 KiB
Raw Blame History

NIP-QR: Post-Quantum Key-Link Attestations

draft optional

This NIP defines a way for a Nostr user to link their existing secp256k1 identity to one or more post-quantum (PQ) public keys, without changing how events are signed, without requiring the network to agree on a single PQ algorithm, and without users abandoning their identity or social graph.

It is intentionally additive. It does not modify NIP-01's event format, signature scheme, or relay behavior. Legacy clients continue to work unchanged; PQ-aware clients gain the ability to verify a pre-quantum, Bitcoin-anchored link between an identity and a set of PQ keys.

Motivation

The threat

Nostr's identity, authentication, and encryption (NIP-04, NIP-44) all rest on secp256k1. Shor's algorithm breaks secp256k1 in polynomial time on a sufficiently large fault-tolerant quantum computer. An attacker who has recorded any Nostr event can recover the private key, forge signatures, impersonate the user, and decrypt every NIP-04/NIP-44 message ever sent to them. NIP-44 itself states: "No post-quantum security."

This includes a collect-now-decrypt-later threat: encrypted messages on public relays can be stored today and decrypted once a quantum computer exists. The symmetric ciphers are quantum-safe; the secp256k1 ECDH key agreement is not.

Why Bitcoin's hash-the-pubkey trick does not help Nostr

Bitcoin has partial quantum resistance because addresses are hashes of pubkeys and pubkeys are only revealed at spend time. Nostr cannot do this: the pubkey is the identity, it must be revealed on every event for Schnorr verification, and it is reused across thousands of events over years. Hashing the pubkey would give zero seconds of quantum resistance — the pubkey is revealed the moment the first event is published.

The fix is post-quantum signatures and post-quantum key agreement, not hashing the pubkey. This NIP describes how to link PQ keys to an existing identity so that the link is established now, while secp256k1 can still be trusted, and is later verifiable even after secp256k1 is broken.

Replacing secp256k1 event signatures with PQ signatures (as proposed elsewhere) requires the whole network to agree on an algorithm, change relay validation, and re-onboard every user. This NIP takes a different approach: the user publishes a one-time attestation that links their secp256k1 identity to a set of PQ keys. The attestation is signed by the existing identity (valid now) and by each PQ signature scheme (valid forever). It is anchored to the Bitcoin blockchain via OpenTimestamps (NIP-03) so it cannot be backdated.

After a quantum break, an attacker who forges the secp256k1 key can publish a fraudulent link event, but they cannot produce an OpenTimestamps proof anchoring it to a Bitcoin block earlier than the real one. The real link is cryptographically distinguishable by its earlier Bitcoin anchor.

Why multiple schemes

The PQ standardization landscape is still young. SIKE was a NIST finalist broken in 2022 by a classical algorithm that ran in an hour on a laptop. Picking a single PQ algorithm now is risky. This NIP allows a user to link to multiple PQ schemes simultaneously. If one is later broken, the others remain valid. Clients verify whichever subset they support. No community-wide algorithm consensus is required to proceed.

Design summary

A user with an existing Nostr identity (the attesting identity) publishes two events:

  1. A kind 1 announcement — a human-readable attestation whose content is signed by each PQ signature scheme, with the PQ public keys and signatures carried in algorithm tags.
  2. A kind 9999 proof carrier — a non-replaceable event that embeds the full signed kind 1 event as JSON content and carries an OpenTimestamps proof anchoring the kind 1 event to a Bitcoin block. Upgrades (pending → confirmed) publish a new kind 9999 event with an upgrade_of tag referencing the previous one; both remain on relays permanently.

The PQ keys are derived deterministically from a BIP39 seed phrase via BIP32, at fixed child indices under the NIP-06 base path. This means a user who has backed up their seed phrase can always recover the same PQ keys, on any conforming implementation.

⚠️ Continuity is asserted, not proven. The old 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, and any in-browser signature could be faked by the same browser. The only real proof is out-of-band verification: the user takes their seed phrase to a different device, derives the keys independently, and confirms the public keys match the published event. Verifying the mnemonic against the current identity (deriving the NIP-06 secp key and requiring it to match) would provide a stronger proof, but no current Nostr signers accept seed phrases, so this would require typing a valuable mnemonic into a web page — an unacceptable risk. This mode is deferred until hardware PQ signers or seed-accepting signers exist.

flowchart TD
    SEED[BIP39 seed - recovery root] --> SECP[secp256k1 keypair - child 0]
    SEED --> PQ[PQ keypairs - children 1..8]
    SECP --> K1[Kind 1 announcement - attesting identity signs]
    PQ --> K1
    K1 --> OTS[OpenTimestamps anchor to Bitcoin block]
    OTS --> K9999[Kind 9999 proof carrier - carries OTS proof]
    K9999 --> RELAY[Published to relays]

PQ key derivation from a BIP39 seed

All PQ keys are derived from a BIP39 seed via BIP32 hierarchical deterministic derivation, the same standard NIP-06 uses for secp256k1 keys. PQ keys live at fixed child indices under the NIP-06 base path.

Base path: m/44'/1237'/0'/0/ (NIP-06 account 0, change 0)

Child index/indices Algorithm Seed length needed BIP32 path Derivation
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)

Concatenation and truncation rule (normative)

BIP32 child derivation produces exactly 32 bytes per child. Some PQ algorithms need more than 32 bytes for their keygen() seed:

  • For a 48-byte seed, derive two children, concatenate them in child-index order (lower index first) to produce 64 bytes, and use the first 48 bytes.
  • For a 64-byte seed, derive two children, concatenate them in child-index order, and use all 64 bytes.

Implementations MUST use the first N bytes after concatenation in ascending child-index order. A future implementer who takes the last 48 bytes, or concatenates in the opposite order, will produce different keys and break seed-phrase recoverability. This rule is pinned here precisely so that two independent implementations produce the same PQ keys from the same seed.

Why BIP32 and not HKDF

BIP32 is the HD-wallet standard already used by NIP-06. Using BIP32 paths for PQ keys means:

  • Consistency with NIP-06 — PQ keys are derived the same way as secp256k1 keys, at different child indices.
  • Wallet compatibility — HD wallet infrastructure (hardware wallets, Amber-style signers, bunkers) can derive these same paths.
  • Compartmentalization — hardened derivation at the account level means breaking one leaf key does not compromise siblings; the parent chain code is a secret symmetric value that a quantum computer cannot recover from a leaf public key.

Seed phrase entropy

A 12-word BIP39 mnemonic carries 128 bits of entropy (~64 bits of post-quantum security under Grover's quadratic speedup). A 24-word mnemonic carries 256 bits (~128 bits PQ). Implementations SHOULD support 24-word mnemonics for users concerned about quantum attacks on the seed entropy itself. The dominant risk to seed phrases remains classical (theft, phishing), not quantum.

The kind 1 announcement event

A regular Nostr text note (kind 1) that serves as a public, human-readable attestation. It is signed with the user's existing secp256k1 identity (the attesting identity), via NIP-07 (window.nostr.signEvent) or any conforming signer.

Content

The content is a human-readable attestation statement. The exact wording is not normative, but it MUST include the attesting identity's npub and hex pubkey, the Bitcoin block height at signing time, and a list of the PQ algorithms whose keys appear in the tags. Each PQ signature scheme signs TextEncoder.encode(content) — the UTF-8 bytes of the content string.

Example content:

I am signaling that the post-quantum public keys listed in the tags of this event
were generated by me and I hold the private keys. I may use these keys in the future
as successors to my current Nostr identity.

My current identity:
  npub: <npub>
  hex: <hex pubkey>

This attestation is established pre-quantum at Bitcoin block height <height>.

Post-quantum public keys in tags:
  ML-DSA-44 (Dilithium, FIPS 204, NIST Level 2)
  ML-DSA-65 (Dilithium, FIPS 204, NIST Level 3)
  SLH-DSA-128s (SPHINCS+, FIPS 205, NIST Level 1)
  Falcon-512 (FIPS 206 draft, NIST Level 1)
  ML-KEM-768 (Kyber, FIPS 203, NIST Level 3)

Each post-quantum key has cryptographically signed this attestation. This event is
pending timestamp on the Bitcoin blockchain via OpenTimestamps.

Tags

  • ["block_height", "<height>"] — the Bitcoin block height at signing time, as a decimal string.
  • ["algorithm", "<algorithm-id>", "<base64 pubkey>", "<base64 signature>"] — one tag per PQ signature scheme. The signature is over TextEncoder.encode(content).
  • ["algorithm", "ml-kem-768", "<base64 pubkey>"] — for ML-KEM-768. ML-KEM is a KEM, not a signature scheme, so it has no signature field. Its ownership is asserted by the attestation text and authorized by the attesting identity's secp256k1 signature over the kind 1 event (which covers the tags, including the ML-KEM pubkey).

Algorithm identifiers

The following algorithm-id strings are defined by this NIP:

algorithm-id FIPS Type Public key size Signature size
ml-dsa-44 204 Signature 1312 bytes 2420 bytes
ml-dsa-65 204 Signature 1952 bytes 3309 bytes
slh-dsa-128s 205 Signature 32 bytes 7856 bytes
falcon-512 206 (draft) Signature 897 bytes ~666 bytes
ml-kem-768 203 KEM 1184 bytes n/a (ciphertext 1088 bytes)

Future NIPs may register additional algorithm-id values and additional BIP32 child indices. Implementations MUST ignore algorithm tags whose algorithm-id they do not recognize — an unknown algorithm is neither valid evidence nor a failure; it is reported as ignored and does not affect the policy verdict.

Algorithm policy (versioned)

The mandatory set of algorithms is versioned and explicit. This NIP defines policy version 1, whose mandatory signature algorithms are ml-dsa-44, ml-dsa-65, slh-dsa-128s, and falcon-512, and whose KEM algorithm is ml-kem-768. A proof carrier is validForMigration only when every mandatory signature algorithm is present exactly once with a valid signature, the KEM algorithm is present exactly once, and all present PQ proofs verify.

A future NIP revision MAY define a new policy version that drops a scheme later found to be weak (the revocation path). The kind 1 event SHOULD include a policy_version tag recording which policy version it was authored under; verifiers evaluate the event against the policy version they support and fail closed on unsupported versions. This makes a future change detectable rather than silently breaking verification.

Example

{
  "kind": 1,
  "pubkey": "<attesting identity secp256k1 pubkey, hex>",
  "content": "I am signaling that the post-quantum public keys ...",
  "tags": [
    ["block_height", "840000"],
    ["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": "<attesting identity Schnorr signature>"
}

The kind 9999 proof carrier event

A non-replaceable event (kind 9999, in the 09999 range) that wraps the kind 1 announcement and carries the OpenTimestamps proof. It is signed with the attesting identity's secp256k1 key.

Kind 9999 is non-replaceable: each publication is permanent on relays. Upgrades (e.g. pending → confirmed OTS proof) publish a new kind 9999 event with an upgrade_of tag referencing the previous one. Both events remain on relays. Clients fetch all kind 9999 events for a pubkey and select the one with the earliest valid Bitcoin anchor.

The kind number is provisional and subject to maintainer allocation.

Kind allocation is load-bearing

The non-replaceable semantics of the 09999 regular-event range are load-bearing for this NIP's append-only upgrade design. The upgrade_of mechanism depends on every published proof carrier remaining permanently on relays so that the earliest-valid-anchor rule has the full candidate set to choose from. If maintainers allocate a kind in a replaceable range (e.g. 1000019999 or 30000+) instead, a single publication could overwrite a prior proof carrier, which would let a post-quantum attacker (who has recovered the secp256k1 key) replace the genuine pending-era evidence with a later-anchored substitute. Any final kind allocation MUST therefore be non-replaceable, and PQ-aware relays SHOULD treat NIP-09 (kind 5) deletion requests targeting proof carriers as a no-op.

Content

JSON.stringify(kind1Event) — the full signed kind 1 event embedded as a JSON string. This lets verifiers validate the kind 1 event, its PQ signatures, and its secp256k1 signature without fetching it from relays.

Tags

  • ["e", "<kind 1 event id>"] — reference to the kind 1 announcement.
  • ["sha256", "<hex canonical digest of the signed kind 1 event>"] — the digest that was submitted to OpenTimestamps. See Canonical digest for the normative construction; it is NOT sha256(JSON.stringify(event)) (which is key-order-dependent and not cross-implementation-reproducible).
  • ["digest_version", "<version>"] — the canonical-digest version that produced the sha256 tag (currently "1"). Verifiers MUST reject missing or unsupported versions.
  • ["ots", "<base64 .ots proof>"] — the OpenTimestamps proof (pending or confirmed).

Example

{
  "kind": 9999,
  "pubkey": "<attesting identity secp256k1 pubkey, hex>",
  "content": "<JSON string of the full signed kind 1 event>",
  "tags": [
    ["e", "<kind 1 event id>"],
    ["sha256", "<hex canonical digest of the signed kind 1 event>"],
    ["digest_version", "1"],
    ["ots", "<base64 .ots proof>"]
  ],
  "sig": "<attesting identity Schnorr signature>"
}

OpenTimestamps anchoring

The kind 1 announcement MUST be timestamped via OpenTimestamps (NIP-03). The digest submitted to the calendar servers is the canonical digest of the fully signed kind 1 event, recorded in the proof carrier's sha256 tag. The resulting .ots proof is embedded in the proof carrier's ots tag.

The wrapper carries the proof; the kind 1 event is what is timestamped.

Canonical digest (normative)

The OTS target digest MUST be reproducible by any conforming implementation, including non-JavaScript implementations and verifiers that re-fetch the kind 1 event from a relay (which may return the event object with different key order). Hashing JSON.stringify(event) is NOT reproducible because object key order is engine-dependent. This NIP therefore defines a canonical digest.

Digest version 1 (current). The digest is SHA-256 of the NIP-01 serialization array extended with the id and sig fields:

sha256( JSON.stringify( [ 0, pubkey, created_at, kind, tags, content, id, sig ] ) )

where:

  • pubkey is the 64-character lowercase hex pubkey,
  • created_at and kind are JSON integers,
  • tags is the JSON array of tag arrays (in event order),
  • content is the JSON string,
  • id is the 64-character lowercase hex event ID,
  • sig is the 128-character lowercase hex Schnorr signature.

This reuses the already-canonical NIP-01 array form (the same form every Nostr implementation reproduces to compute event IDs), extended with the two fields that complete a signed event. Array element order is fixed by construction, so the only residual formatting ambiguity is number/string serialization, which is already implicit in NIP-01 event-ID computation and is well-defined for the integer and string field types used here.

Versioning. The proof carrier MUST include a digest_version tag recording which canonicalization produced the digest:

  • ["digest_version", "1"] — the version-1 construction above.

Verifiers MUST reject proof carriers whose digest_version is missing (legacy v0 events, see below) or is an unsupported version. A future version MUST be defined by a separate NIP revision and MUST be detectable from this tag.

Legacy v0 events. Proof carriers published before this canonicalization carry no digest_version tag and commit to sha256(JSON.stringify(kind1Event)) (key-order-dependent). Verifiers MAY inspect these for historical completeness but MUST NOT treat them as valid for migration. A v0 event can be upgraded to v1 by publishing a new kind 9999 proof carrier (with upgrade_of referencing the original) that carries the v1 canonical digest and a freshly submitted OTS proof for that digest.

Worked example. For a kind 1 event with id = "abc...123" (64 hex), sig = "def...456" (128 hex), pubkey = "012...789" (64 hex), created_at = 1700000000, kind = 1, tags = [["algorithm","ml-dsa-44","...","..."]], content = "I am signaling...", the canonical digest is:

sha256( JSON.stringify( [ 0, "012...789", 1700000000, 1, [["algorithm","ml-dsa-44","...","..."]], "I am signaling...", "abc...123", "def...456" ] ) )

Implementations MUST compute this byte-for-byte identically. Cross-implementation conformance vectors are published with the reference implementation.

Why the kind 1 event must be anchored

Without an OTS anchor on the key-link event, a future quantum attacker who breaks the secp256k1 key can publish a fraudulent key-link event linking the identity to the attacker's PQ keys. Both the real and fraudulent events would have valid secp256k1 signatures (the key is compromised), and created_at is forgeable, so clients could not distinguish them.

With an OTS anchor, the real event is committed to a specific Bitcoin block before quantum computers exist. A fraudulent event published later cannot produce an OTS proof for that block or any earlier block — doing so would require re-mining historical Bitcoin blocks (infeasible even with a quantum computer, due to cumulative proof-of-work) or finding a SHA-256 preimage (still infeasible under Grover's quadratic speedup on 256-bit hashes).

Verification rule: earliest valid anchor wins

When a client finds multiple kind 9999 proof carriers for the same attesting identity, it verifies the OTS proof in each and selects the one whose Bitcoin attestation has the earliest block height. That proof carrier's embedded kind 1 event is the canonical key-link for that identity. Later proof carriers, even if validly signed, are treated as superseding only if they are signed by a PQ key already linked by the earliest proof carrier.

OTS proof states

An OTS proof may be pending (not yet anchored in a Bitcoin block) or confirmed (anchored). A pending proof is evidence that the digest was submitted to calendar servers; it is not an independent timestamp and depends on calendar behavior. A confirmed proof asserts a Bitcoin block commitment. Clients SHOULD treat a confirmed proof as authoritative. A pending proof can be upgraded to a confirmed proof later by re-querying the calendars; when that happens, a new proof carrier event is published carrying the upgraded proof and referencing the original via an upgrade_of tag. The original event remains on relays permanently.

Client-side verification

Clients SHOULD perform full client-side OTS verification: parse the proof, walk the op tree, validate the Merkle path, and check the attested Merkle root against the actual Bitcoin block header for the attested height. A client that cannot perform full verification MAY fall back to checking that a Bitcoin attestation is structurally present in the proof, but MUST label such a link as unverified.

Data availability (normative)

The earliest-valid-anchor rule is only meaningful among proof carriers a client can still find. Relays make no retention promises, and a post-quantum attacker who recovers the secp256k1 key can sign NIP-09 (kind 5) deletion requests targeting the genuine kind 9999 proof carriers. To keep the genuine anchor discoverable:

  • Verifiers MUST accept proof carriers supplied out-of-band (pasted JSON, imported archive file) as normatively equivalent to relay-fetched proof carriers. A proof carrier's validity does not depend on relay retention.
  • Users SHOULD retain an offline copy of the signed kind 1 event JSON and the .ots proof. The reference implementation offers a "Download proof archive" function for this purpose.
  • PQ-aware relays SHOULD ignore NIP-09 (kind 5) deletion requests targeting kind 9999 proof carriers. Kind 9999 is in the regular (non-replaceable) event range; its append-only semantics are load-bearing (see Kind allocation is load-bearing).
  • Communities and individuals SHOULD mirror proof carriers they care about. The proof carrier is self-contained (it embeds the kind 1 event and the OTS proof), so mirroring is a simple copy.

Client behavior

PQ-aware clients

A PQ-aware client, when it encounters a kind 9999 proof carrier for a pubkey it cares about:

  1. Parses the embedded kind 1 event from the proof carrier's content.
  2. Verifies the attesting identity's secp256k1 signature on both the kind 1 and the kind 9999 events.
  3. Verifies that the proof carrier's pubkey matches the embedded kind 1 event's pubkey (identity binding).
  4. Verifies the digest_version tag is present and supported, and that the sha256 tag matches the canonical digest of the embedded kind 1 event (see Canonical digest).
  5. For each algorithm tag with a signature, verifies the PQ signature against the pubkey in the tag, over TextEncoder.encode(kind1Event.content).
  6. Verifies the OTS proof in the ots tag against the sha256 digest.
  7. Records the link: attesting identity → {set of PQ pubkeys}, anchored at the verified Bitcoin block height.
  8. If multiple proof carriers exist for the same identity, applies the earliest-valid-anchor rule. Proof carriers may be gathered from relays, local archives, or user-supplied files (see Data availability).

After a quantum break of secp256k1, the client trusts the PQ keys from the earliest-anchored link for any future PQ-signed events or PQ-encrypted messages from that identity.

Legacy clients

Legacy clients see the kind 1 announcement as an ordinary text note and the kind 9999 proof carrier as an unknown event. They ignore both. Nothing breaks.

What this NIP proves and does not prove

It proves:

  • Each PQ signature scheme listed in the tags signed the attestation text. Verified by checking each PQ signature against its tag's pubkey.
  • The attesting identity authorized the link. Verified by the secp256k1 Schnorr signature on the kind 1 and kind 9999 events, valid pre-quantum.
  • The link existed at a specific pre-quantum Bitcoin block. Verified by the OTS proof.

It does not prove:

  • The PQ keys share a common seed origin. Common-origin is asserted in the attestation text and authorized by the attesting identity's signature, but it is not proven by a seed-derived signature. The security model relies on OTS precedence, not on a cryptographic proof of common origin.
  • The attesting identity still controls the PQ private keys. The link only proves the keys existed and were authorized at anchor time.

This is a deliberate design choice. The threat model is backdating of fraudulent links, which OTS defeats. Forging a link after a quantum break produces a later anchor than the real link, so the real link is cryptographically distinguishable.

Revocation and broken schemes

If a specific PQ scheme is later found to be weak (as SIKE was in 2022):

  1. Clients stop trusting the broken scheme — no revocation event is required. Clients simply ignore algorithm tags whose algorithm-id is on a locally-maintained broken-scheme list (an unknown/ignored algorithm is neither valid evidence nor a failure; see Algorithm policy).
  2. The remaining links stay valid.
  3. A user MAY publish a new key-link event omitting the broken scheme, signed by the still-valid PQ keys from the prior link. This is the revocation path described under Algorithm policy: a future policy version drops the broken scheme from the mandatory set, so an event omitting it passes under the new policy while still failing under the old policy until clients upgrade.

Algorithms: why this set

Algorithm Why included
ML-DSA-44 Lattice-based signature, FIPS 204, NIST Category 2. Compatibility with proposals that pick ML-DSA-44 alone.
ML-DSA-65 Lattice-based signature, FIPS 204, NIST Category 3. Primary PQ signature.
SLH-DSA-128s Hash-based signature, FIPS 205, NIST Category 1. Very conservative assumptions; the fallback if all lattice schemes break. Large signatures (7856 bytes) are acceptable for a one-time event.
Falcon-512 Lattice-based signature, FIPS 206 draft, NIST Category 1. Compact signatures (~666 bytes). Most provisional link: FIPS 206 is still a draft, and Falcon's signing uses floating-point FFT, which is notoriously hard to make constant-time and deterministic across platforms — a concern for a scheme whose keys must be reproducibly derivable from a seed for decades. Implementations SHOULD publish cross-platform determinism test vectors for Falcon-512.
ML-KEM-768 Lattice-based KEM, FIPS 203, NIST Category 3. For future PQ encryption (NIP-04/NIP-44 successors). Cannot sign; pubkey only.

Multiple schemes are used simultaneously so that if one class is broken, the others remain. The community does not need to agree on a single algorithm; clients verify whichever subset they support.

Out of scope

The following are explicitly not part of this NIP. They may be addressed by separate future NIPs:

  • PQ signatures on routine events. This NIP links keys; it does not change how events are signed. PQ-signed events are a separate problem.
  • PQ encryption of messages. ML-KEM-768 pubkeys are linked here so future encryption NIPs can use them, but this NIP does not define a NIP-04/NIP-44 replacement.
  • Migration of users with a raw nsec and no seed phrase. That is a key-management problem orthogonal to the link format defined here.
  • Quantum-safe self-storage (e.g. one-time pad or seed-derived symmetric encryption of kind 30078 application data). Orthogonal to identity linking.
  • HD wallet compartmentalization. A property of BIP32/NIP-06, not something this NIP re-specifies.

Prior art

This NIP builds on and is compatible with several existing community proposals:

  • PR #391 (eznix86, "NIP-101 Algorithm Transition") introduced the cross-signed transition event concept. This NIP extends it with OpenTimestamps anchoring, multi-scheme hedging, and seed-phrase-derived keys.
  • PR #2185 (trbouma, "Add NIP for PQ") proposes swapping event signatures to ML-DSA-44. This NIP is compatible with that work — a future PQ-signature NIP can use the PQ keys linked here.
  • Issue #1971 (paulmillr, "NIP-44: post-quantum security") is the main community discussion. The "whole architecture" problem identified there (breaking nsec breaks everything derived from it) is why this NIP uses a BIP39 seed phrase as the root of trust rather than deriving PQ keys from nsec.
  • PR #1647 (fiatjaf, "nip4e: Decoupling encryption from identity") separates encryption keys from identity keys. This NIP's ML-KEM-768 link is compatible with that pattern.
  • NIP-03 (OpenTimestamps Attestations for Events) provides the anchoring primitive this NIP relies on.
  • NIP-06 (Basic key derivation from seed) defines the BIP32 base path under which this NIP allocates PQ child indices.

Reference implementation

⚠️ Research prototype. The implementation is experimental and is not a complete post-quantum migration. It links PQ keys to an existing identity and anchors that link in Bitcoin; it does not yet define PQ authentication for routine events, key rotation/revocation, or PQ encryption.

A static, client-side-only web implementation exists at https://laantungir.net/quantum-prep/. Cryptographic operations (BIP39, BIP32, PQ keygen/sign/verify, OTS submission and Merkle-path verification) happen in the browser. Private keys are handled in the browser or via a NIP-07 signer extension, which may be a remote signer (NIP-46); in that case the signing key does not touch the page origin. OTS proof upgrading uses a server-side helper, and Bitcoin block-header confirmation relies on a public explorer API (mempool.space) — this is API-assisted verification, not a full Bitcoin light-client verification. Source: www/js/pq-crypto.mjs in this repository.