30 KiB
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.
Why a link, not a swap
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:
- A kind 1 announcement — a human-readable attestation whose
contentis signed by each PQ signature scheme, with the PQ public keys and signatures carried inalgorithmtags. - 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_oftag 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 overTextEncoder.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 0–9999 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 0–9999 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. 10000–19999 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 NOTsha256(JSON.stringify(event))(which is key-order-dependent and not cross-implementation-reproducible).["digest_version", "<version>"]— the canonical-digest version that produced thesha256tag (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:
pubkeyis the 64-character lowercase hex pubkey,created_atandkindare JSON integers,tagsis the JSON array of tag arrays (in event order),contentis the JSON string,idis the 64-character lowercase hex event ID,sigis 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
.otsproof. 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:
- Parses the embedded kind 1 event from the proof carrier's
content. - Verifies the attesting identity's secp256k1 signature on both the kind 1 and the kind 9999 events.
- Verifies that the proof carrier's pubkey matches the embedded kind 1 event's pubkey (identity binding).
- Verifies the
digest_versiontag is present and supported, and that thesha256tag matches the canonical digest of the embedded kind 1 event (see Canonical digest). - For each
algorithmtag with a signature, verifies the PQ signature against the pubkey in the tag, overTextEncoder.encode(kind1Event.content). - Verifies the OTS proof in the
otstag against thesha256digest. - Records the link: attesting identity → {set of PQ pubkeys}, anchored at the verified Bitcoin block height.
- 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):
- Clients stop trusting the broken scheme — no revocation event is required. Clients simply ignore
algorithmtags whosealgorithm-idis on a locally-maintained broken-scheme list (an unknown/ignored algorithm is neither valid evidence nor a failure; see Algorithm policy). - The remaining links stay valid.
- 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.