Read this first
Open obelisk.ar, log in with the Nostr WoT extension, and your direct messages are gift-wrapped by default and post-quantum sealed whenever the person you are talking to publishes post-quantum keys. As far as our public index of post-quantum Nostr work shows, no other Nostr client ships this. The keys come from the 24 word seed the identity already has, the post-quantum secret is combined with the classic NIP-44 key rather than replacing it for now, and the whole thing travels inside a standard `kind:1059` gift wrap that relays already store. Hybrid is the step a single client can take on its own; replacing the key exchange outright is an ecosystem move. Event signatures are still secp256k1, so this protects confidentiality and not authenticity.
Open Obelisk, start a direct message, and the encryption protecting it is post-quantum. There is no key to generate and nothing extra to write down. The keys come from the seed phrase the account already has, and the client turns the protection on by itself once it can see that the other side supports it.
That is the design we published on 9 August running in a client people actually talk to each other in: post-quantum keys derived from the BIP39 seed a Nostr identity already uses, published in a replaceable attestation, and carried inside today's gift wrap. Obelisk is a Discord-style chat app built entirely on Nostr relays, with no backend of its own.
What is actually running
Every direct message Obelisk sends is a NIP-17 gift wrap by default: a `kind:14` rumor, sealed in a `kind:13` encrypted to the recipient, wrapped in a `kind:1059` signed by a throwaway key that is fresh for every single message. That layer hides the social graph. A relay holding the wrap can see that some key it has never seen published an envelope addressed to Bob, and cannot tell who sent it or group it with anything else.
Inside that seal, when both parties advertise post-quantum keys, the payload is our hybrid envelope: an ML-KEM-1024 encapsulation combined with the ordinary NIP-44 conversation key, sealed under XChaCha20-Poly1305. The recipient's client takes it apart without being told which scheme was used, because the envelope leads with a version byte and an algorithm byte and is self-describing.
The thread header carries one small shield that reports which of three states you are in: your messages are locked and nobody can see who you are talking to and a quantum computer could not open them; the same minus the quantum part; or a legacy NIP-04 thread where the relays can see who you are talking to. Three states rather than a binary, because a gift-wrapped conversation without post-quantum keys is genuinely protecting something and the interface should say so.
The keys are the ones the user already wrote down
Post-quantum keys are derived from the same BIP39 seed as the secp256k1 identity, as siblings of it and never from it. HKDF-SHA256 with domain-separated `info` strings gives an ML-KEM-1024 pair for encryption and an ML-DSA-87 pair for the possession proof.
The direction matters more than the fact. Deriving the post-quantum key from the private key, `pq = KDF(nsec)`, is the obvious shortcut and it is worthless: Shor's algorithm recovers the private key from the published public key, and the adversary then runs the same KDF. Deriving from the seed instead means the adversary has to invert BIP32 to get back to it, which is one way. That is the whole security property, and it is the single mistake that would silently void the scheme.
A 24 word mnemonic is required, and a 12 word one is refused rather than quietly downgraded. ML-KEM-1024 targets 256 bits; deriving it from 128 bits of seed entropy produces keys of the right size and the wrong strength, which look identical from outside. Accounts with no 24 word seed can import an independently generated pair instead, and their attestation says so.
Hybrid first, because one client cannot replace a key exchange alone
The ML-KEM shared secret is never used alone. It is combined with the classic NIP-44 conversation key through HKDF, and both go into the AEAD key.
That is a first step, not the end state. Replacing the classic key exchange outright is where Nostr should end up, and no single client can take it there: it needs relays, signers and every other client to move together, and it is a conversation for the whole ecosystem rather than a decision three teams get to make. Hybrid is the part that can ship now, unilaterally, without waiting for that conversation to finish.
It is also the part that has to ship now, because harvest-now-decrypt-later is the half of the problem with a deadline in the past. Every day this waits is another day of traffic that cannot be protected retroactively. The forgery half can be fixed whenever the ecosystem is ready, since nothing published in advance helps it anyway.
Being incremental costs very little here. Lattice cryptography is young next to elliptic curves, and a NIST candidate died this year to a flaw found by an AI-assisted search. Until the ecosystem does move, a hybrid degrades to exactly NIP-44 if ML-KEM is broken, which is where every Nostr client already stands today.
Relays never had to know
No relay changed anything to carry this. The post-quantum payload sits inside the seal, so what a relay stores is an ordinary `kind:1059` gift wrap of a slightly unusual size. Clients that do not implement the scheme are unaffected, because they never see the envelope: it is inside a layer they cannot decrypt anyway.
The one place the network is visible is the attestation. A `kind:10203` event carrying both public keys and an ML-DSA proof of possession is about 12 KB, and one relay in our four-relay test set rejects it on size. Three accept it, which is enough for discovery, and we learned that by publishing rather than by estimating.
Measured, not estimated
On the complete `kind:1059` gift wrap as a relay stores it: a two-character message costs 1,533 bytes classic and 4,605 post-quantum, a 32-character chat line 1,701 against 4,605, a 1 KB paragraph 3,921 against 7,333, and a 16 KB document 38,737 against 44,197. That is roughly 3 KB of constant overhead, almost all of it the 1,568-byte ML-KEM ciphertext expanded by base64 at each NIP-59 layer. Encryption takes about 1.3 ms. The overhead is constant rather than proportional, so it falls hardest on short messages, which is most messages.
The specification, written down
An implementation nobody else can talk to is a private protocol wearing Nostr's clothes. So the format is now four public drafts, written so a second implementation can interoperate without reading our source. None of them has a NIP number yet and none is submitted; the one number claimed in the wild is the event kind, which is on relays today.
- Draft 01, key derivation (github.com) - deriving ML-KEM and ML-DSA keys from the BIP39 seed as siblings of the secp256k1 key, the seed-strength rule, and a test vector against the NIP-06 mnemonic.
- Draft 02, key attestation (github.com) - the `kind:10203` event, its tags, the ML-DSA proof of possession that binds both keys to the npub, and the verification rules a reader must apply in full or not at all.
- Draft 03, the NIP-44 post-quantum envelope (github.com) - the wire format, the hybrid KDF, the associated data, and why decryption must produce one indistinguishable error.
- Draft 04, signer capability (github.com) - `window.nostr.nip44.schemes`, so a client can ask a signer whether it does post-quantum instead of guessing.
Draft 04 exists because of a failure mode worth naming. Post-quantum rides an optional third argument to `nip44.encrypt`, which means a signer that supports it and one that has never heard of it are shaped identically. The unaware one ignores the extra argument and returns perfectly valid classic ciphertext, and a client that assumed support would badge that as protected. A silent downgrade dressed as protection is worse than not offering the feature, so support has to be detectable rather than inferable.
The bug was in the routing, not the cryptography
Writing draft 04 turned up a real one in our own signer. A post-quantum request on a NIP-46 remote-signer account was routed to the bunker before the post-quantum code ran, and a bunker answers `nip44Encrypt` with ordinary NIP-44. The caller received classic ciphertext in response to a post-quantum request, with no way to tell the difference: exactly the downgrade the opt-in and the capability marker were both built to prevent, arriving by a route neither of them watched.
The more instructive one was in Obelisk, and it was not in any seal, signature or envelope. Those were correct throughout. Each message publishes two wraps, one to the recipient and one to yourself so your own history survives a reload, and the relay-selection code unioned the recipient's published inbox with a fallback that always contained our own relay. Both copies therefore landed on a socket already authenticated as the sender. The relay did not need to decrypt anything: the connection identified us, and it then saw two same-sized envelopes seconds apart, one addressed to Bob and one to ourselves. That states the pair outright.
It is the equivalent of posting two anonymous letters while showing the postman your ID, and addressing one of them to yourself. Four review rounds read that branch and missed it, because every one of them was checking the cryptography. The fix is an ordered ladder of relay targets used one rung at a time and never unioned, plus an explicit authentication mode per publish, and it is documented in the repository along with the residual leaks that no single client can close.
Three teams
Nostr WoT built the signer extension and the SDK: key derivation, the attestation, the capability marker, and the `@nostr-wot/pq` reference implementation on npm. QuantaKrypto did the cryptographic design and review and wrote the drafts. The Obelisk team built the client and the interface that has to explain any of this to somebody who has never heard of a relay.
That split is not incidental. Encryption needs the raw NIP-44 conversation key and decryption needs the ML-KEM secret key, and neither is exposed by NIP-07 or NIP-46. The cryptography can only live in the component holding the key material, which is the signer, and no client library can implement it on top however it is layered. The client's job is to decide when to ask for it and to say honestly what it got.
What this does not fix
Event signatures are still secp256k1. A quantum adversary can forge events in your name, and can forge the attestation itself, since the binding between an npub and its post-quantum keys rests on a classical signature. Nothing inside this scheme changes that, and no client can change it alone: an ML-DSA-87 signature is 4,627 bytes added to every event on the network, which is a relay-economics decision.
What is defended is confidentiality against an adversary recording traffic today and decrypting it later. That is the half that cannot be fixed after the fact, and the half already accruing damage. "Quantum secure" is the wrong claim to make to a user. "The messages being recorded today stay unreadable" is the right one.
Metadata has residuals too. If both parties use the same popular relay, it receives both wraps regardless of what any client does. Fuzzing `created_at` decorrelates the claimed time and does nothing about real arrival time. And the self-copy still tells our own relay that we sent a message, though no longer to whom.
The point
The interesting result is not that the cryptography works. It is that a post-quantum transition can be shipped inside an existing protocol without asking relays to change, without breaking clients that do not participate, and without asking a single user to write down a new seed phrase. The hard parts were not the lattices. They were making a signer say what it can do, and keeping a privacy guarantee from being thrown away by the routing underneath it.
References
- Obelisk (obelisk.ar) - the client, relay-only, with post-quantum direct messages live.
- A post-quantum transition for Nostr identities - the proposal this implements, published 9 August 2026.
- Post-quantum NIP drafts (github.com) - all four drafts, plus what they deliberately do not cover.
- DM metadata privacy (github.com) - how gift wrap leaks the social graph when the routing is wrong, and the rules for changing it.
- @nostr-wot/pq (npmjs.com) - the reference implementation of the envelope and the attestation.
- Nostr WoT extension (github.com) - derives the keys from an existing seed phrase and publishes the attestation.
- nostr-pqc (github.com) - the public index of post-quantum Nostr work, which is the basis for the claim that no other client ships this.
- NIP-17: Private Direct Messages (github.com) - the gift-wrapped DM format the post-quantum payload rides inside.
- NIP-44: Versioned encryption (github.com) - the conversation key that is combined with the ML-KEM secret rather than replaced by it.
- Issue #1971: NIP-44 post-quantum security (github.com) - Paul Miller's discussion, open since July 2025, and the earliest serious treatment of this problem in Nostr.
- FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard (nist.gov) - ML-KEM.
- FIPS 204: Module-Lattice-Based Digital Signature Standard (nist.gov) - ML-DSA.
- AI-found flaw sinks HAWK, a NIST PQC candidate - why the hybrid construction is not optional.