Nostr WoT
Post-QuantumNostrCrypto-AgilityDirect MessagesCryptography

Post-quantum direct messages should not be opt-in

Every encrypted message on Nostr today is a future plaintext. Protection should be the default. It is opt-in instead, and that is a design failure worth naming precisely, because the fix is not on the messaging layer.

Leon Acosta

Leon Acosta

·8 min read
Post-quantum direct messages should not be opt-in

We shipped post-quantum encrypted direct messages on Nostr. They are opt-in: you publish a key, the person you are writing to publishes one, and only then does anything change.

That is the wrong default, and I want to be clear that we know it. Every encrypted message on Nostr today can be copied off a relay and kept until secp256k1 falls. Protection against that should be on for everyone, silently, the way TLS quietly moved the entire web to hybrid key exchange without asking a single user to tick a box.

It is not on by default, and the reason is not caution on our part. It is that Nostr made a decision early that removed the option.

One algorithm, welded to identity

A Nostr identity is a secp256k1 keypair. Not primarily. Only.

Your npub is the public half of that specific key on that specific curve. There is no algorithm identifier beside it, no version, no capability field, no key-set envelope. The protocol cannot express the sentence "this identity uses algorithm X", because it never occurred to the design that there would be more than one.

Compare what the same project did one layer up. NIP-44, the encryption scheme, is versioned: its payloads lead with a version byte, precisely so the scheme can change without breaking readers. The people who designed the encryption understood that algorithms have lifespans. The identity underneath it got no such treatment.

So the inconsistency is internal to Nostr, not a matter of hindsight. Versioning was a known idea, applied to the payload and skipped on the key.

Why that closes the door

Post-quantum encryption needs the recipient's ML-KEM public key, a lattice key of about 1,568 bytes with no mathematical relationship to secp256k1.

It cannot be derived from an npub, and that is not a gap in our implementation, it is a requirement. We derive post-quantum keys from the BIP-39 seed as siblings of the secp256k1 key, deliberately never from the key itself. If a post-quantum key could be computed from a Nostr private key, then Shor's algorithm recovers that private key from the published pubkey, the attacker reruns the derivation, and the post-quantum key falls with it. Any scheme where the identity implies the new key is a scheme with no post-quantum security at all.

Which leaves exactly one place to put the key: somewhere else. A separate event, published separately, found by asking a relay.

From there every consequence follows mechanically:

  • Most identities do not have one. A default has to work for everybody. This cannot work for anybody who has not acted first.
  • Discovery costs a network round trip, in front of an operation callers expect to be instant.
  • Nothing can be substituted, only added. The identity everything else depends on is still secp256k1.

Additive is optional by definition. Optional means somebody has to choose. That is what "opt-in" means here: not a stance on user consent, but the absence of anywhere else to put the decision.

Opt-in security protects the wrong people

This matters more than it sounds, because defaults are not a UX detail. They are the entire security posture for everyone who never opens a settings page.

An opt-in protection reaches people who already read about the threat, already understand harvest-now-decrypt-later, and already went looking. Those people were never the ones most at risk. The journalist, the activist, the person whose messages will matter to somebody in fifteen years, are exactly the population least likely to have found a checkbox.

And this particular protection has an expiry attached. Confidentiality against a future break can only be applied before the message is sent. Every day the default stays off is a day of traffic that no later fix can reach.

What it costs us in practice

Because post-quantum had to be a bolt-on, it rides on an optional third argument to an existing call:

js
window.nostr.nip44.encrypt(pubkey, plaintext, { scheme: 'pq', recipientKemKey })

JavaScript discards surplus arguments. A signer that has never heard of post-quantum takes that call, ignores the third argument, encrypts the ordinary way, and returns a perfectly valid ciphertext. No error. Nothing in the return value distinguishes it.

A client that assumed support would then badge the message as quantum-protected when it is not. So we had to add a way to ask:

js
window.nostr.nip44.schemes  // ["nip44", "pq"]

That marker is not a feature. It is scar tissue. In a protocol where identity declared its own algorithms, no client would need to interrogate a signer about what it can do, because the identity would already say.

What should exist instead

Identity needs to carry its algorithms, the way X.509 certificates have always carried an algorithm identifier and the way TLS negotiates a ciphersuite per connection. Concretely, for Nostr, that means an identity able to declare a key set rather than being one key: this npub, these algorithms, these public keys, signed and rotatable.

There is live discussion in that direction, including work on declaring key sets and the impersonation problems a naive version introduces.[5] It is the right layer. It is also slow, because it touches signatures, and an ML-DSA-87 signature is 4,627 bytes on every event, which is a relay storage and bandwidth question that needs relay operators at the table rather than a client shipping unilaterally.

Our kind:10203 attestation is a workaround for the absence of that. It carries algorithm identifiers, key material, seed strength and a proof of possession, which is what identity should have carried in the first place, published beside the identity because it cannot be published inside it.

I would rather describe it as a workaround than dress it up. It works, it needs no relay changes, and it should be replaced by something structural.

What we do until then

Publish the keys anyway. The half of this problem that expires is confidentiality, and it expires continuously: messages sent today are protected only if the keys exist today. Waiting for the identity layer to be fixed means losing every conversation that happens in the meantime.

So the practical position is unglamorous. Turn it on, tell people plainly what is and is not protected, and do not pretend the default is fine.

Obelisk shows each conversation in one of three states rather than a binary, because most conversations are in the middle and the middle is real protection:

StateWhat holds
Extra safeUnreadable, nobody sees who you talk to, and a future quantum computer could not open it
SafeUnreadable and nobody sees who you talk to, but a quantum computer could open it one day
BasicUnreadable, but the servers carrying it see who you talk to

NIP-17 gift wrap already hides who is talking to whom, which is genuine protection working right now. Collapsing that into "quantum-safe or not" would erase it and turn the ordinary case into a permanent warning nobody reads.

The general lesson

If you are designing a protocol today, the transferable point is not about lattices.

Put the algorithm identifier in the identity on day one, before you have any users. It costs almost nothing while the network is empty and becomes nearly impossible once every client, relay and stored event assumes one curve. Nostr versioned its encryption and not its keys, and the whole shape of this article is the bill for that.

Cryptographic agility is not a feature you add when an algorithm breaks. By then the only moves left are bolt-ons, and bolt-ons can only ever be opt-in.

The specifications

Public drafts, so a second implementation can interoperate without reading our source:

None of them has a NIP number yet. Disagreement about the wire format is more useful now than after a second client ships against it.

The identity half is covered in post-quantum Nostr identities, the implementation write-up is on QuantaKrypto, and Obelisk has a guide if you just want to switch it on.

References

  1. []nostr-protocol (2026). NIP-06: Basic key derivation from mnemonic seed phrase. GitHub.
  2. []nostr-protocol (2026). NIP-44: Versioned Encryption. GitHub.
  3. []nostr-protocol (2026). NIP-17: Private Direct Messages. GitHub.
  4. []Paul Millr and contributors (2025). NIP-44: post-quantum security (issue #1971). nostr-protocol/nips.
  5. []Vitor Pamplona (2026). NIP-A1: Key Set Declaration (PR #2424). nostr-protocol/nips.
  6. []IETF (2008). RFC 5280: Internet X.509 Public Key Infrastructure Certificate and CRL Profile. IETF.
  7. []NIST (2024). FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard. NIST.
  8. []CISA, NSA, NIST (2023). Quantum-Readiness: Migration to Post-Quantum Cryptography. CISA.
Leon Acosta

Written by

Leon Acosta

View on Nostr →

Stay Updated

Get the latest on new features, trust assertions, and services integration as they ship.

No spam, ever. Unsubscribe anytime.