Adding post-quantum signatures to Nostr without a hard fork. Every event carries a PQ-signed new form inside today's event format, so old clients and relays keep working while new ones verify both signatures, until the old form can be pruned.
Post-quantum signatures can be added to Nostr as a soft fork inside today's event format, so no client or relay has to upgrade at the same time.
The idea
Every event gets two forms:
- New form: same shape as a NIP-01 event, but
pubkeyis a PQ public key andsigis a PQ signature. - Old form: exactly the event we publish today, signed by the
npub. It carries the new form inside its tags.
Clients publish the old form. Old clients and relays see a normal event and keep working. New clients rebuild the new form from it and verify both signatures.
The PQ pubkey is the new form's pubkey, so every event carries it. The PQ key is generated independently, not derived from the nsec.
Every event is two events by two different authors, packed into one.
There's no binding between the two keys. Nothing stops anyone from pairing any npub with any PQ key, or using several of either. Clients treat the pairing as a hint: on someone's profile, the kind 0 carries their PQ profile too, so the client can show it, follow it, or suggest following it. Liking a post works the same way: the old form, from your npub, reacts to the post's old form, and the new form, from your PQ key, reacts to its new form.
Type prefix
The new form has to be able to point at both old and new things, so in the new form every event id and pubkey starts with one byte saying what it is:
| Prefix | Means |
| --- | --- |
| 00 | old: a secp256k1 pubkey or an old-form event id |
| 01 | new: a PQ pubkey or a new-form event id |
The prefix is part of the hex, so it carries through to npub, nevent and other bech32 encodings.
This lets the new form reference events that only exist in the old form, like a reply to someone who never upgraded: the new form has ["e", "00<old id>"], and the old form has the plain ["e", "<old id>"].
The old form has no prefixes and can't point at new-only things, so its own tags and content always use old ids and npubs. The new-form versions ride along in extra tags.
Example
A reply, in its new form. The client builds and signs this first:
{
"id": "01<sha256 of [0, pubkey, created_at, kind, tags, content]>",
"pubkey": "01<PQ public key>",
"created_at": 1759200000,
"kind": 1,
"tags": [
["t", "nostr"],
["e", "01<parent's new id>", "<relay>", "reply"],
["p", "01<parent's PQ pubkey>"]
],
"content": "nice post",
"sig": "<PQ sig over id>"
}
The same reply, in its old form. The client builds this around the new form, signs it with the npub, and publishes it:
{
"id": "<NIP-01 hash, including all tags>",
"pubkey": "<npub hex>",
"created_at": 1759200000,
"kind": 1,
"tags": [
["e", "<parent's old id>", "<relay>", "reply"],
["p", "<parent's npub>"],
["t", "nostr"],
["pqsig", "<PQ sig>"],
["Q", "i:01<new id>"],
["Q", "p:01<PQ public key>"],
["Q", "t:e:01<parent's new id>", "<relay>", "reply"],
["Q", "t:p:01<parent's PQ pubkey>"]
],
"content": "nice post",
"sig": "<secp256k1 sig over id>"
}
Its normal tags use old ids and npubs, so old clients thread it correctly. The extra tags carry only what's different in the new form. ["t", "nostr"] and the content are the same in both, so they aren't repeated.
The old form commits to the new form, since its id covers pqsig and the Q tags. The new form never references its own old form, because that would be circular.
The Q tag
Old relays only index single-letter tags, and only their first value. So new-form tags are folded into one indexed value under a single letter, Q (for quantum), which is unused by current NIPs. NIP-01 filters are case-sensitive, so it never mixes with NIP-18's lowercase q.
What the old form carries:
| Tag in old form | When | Holds |
| --- | --- | --- |
| ["pqsig", …] | always | new form's signature |
| ["Q", "i:…"] | always | new form's id |
| ["Q", "p:…"] | always | new form's pubkey |
| ["pqcontent", …] | content differs | new form's content, e.g. nostr: mentions |
| ["Q", "t:<key>:<value>", …] | a single-letter tag differs | that new-form tag; extra values follow as in the original |
| ["Q:<key>", …] | a multi-letter tag differs | that new-form tag; not indexed anyway |
These keys (pqsig, pqcontent, Q, Q:<key>) are reserved in the old form, so no other tag uses them.
Key and value go together in tag[1] because that's the only element relays index: ["Q", "e", X] would index just "e", which matches every event.
The goal for now isn't upgrading relays. It's making sure they already have the new forms, so they're ready to switch. Clients keep querying as they do today. #Q is only for when a client has just a new-form id or pubkey.
This lets new clients search old relays using only new-form ids and pubkeys:
| On a relay that understands new forms | On an old relay |
| --- | --- |
| ids: [X] | #Q: ["i:X"] |
| authors: [X] | #Q: ["p:X"] |
| #e: [X] | #Q: ["t:e:X"] |
Tags that are the same in both forms, like hashtags, use the normal filter on both. Several #Q values in one filter mean OR, so combined conditions (author AND reply-to) are checked locally.
Who sees what
Every event is published wrapped in its old form, even a reply to someone who uses the new form, so clients that don't support this still see it.
| | Old relay | New relay |
| --- | --- | --- |
| Old client | Unchanged | Unchanged |
| New client | Fetches old forms, queries with #Q, verifies both sigs | Fetches old forms, verifies both sigs |
| New client, new-form-only (later) | — | Only sees and publishes new forms |
New clients keep fetching old forms at first, because building the old-form wrapper for their own replies needs the old ids and npubs of what they reference, and those only come from old forms. They verify both signatures: the secp256k1 one on the old form, and pqsig on the new form rebuilt from it.
Rebuilding the new form
The old form only carries what differs, so a client rebuilds the new form by filling the gaps from the old form itself:
pubkeyfromQ p:,sigfrompqsig,created_atandkindas they are,contentfrompqcontentif present, otherwise the old content.- Tags: leave out the reserved tags themselves. Then for each tag key, if the old form has any
Qentry for that key (t:<key>:…orQ:<key>), use those; otherwise keep the old form's tags of that key. - Order: kept old tags first, in their order, then
Q-derived tags, in their order. The id hashes tags in order, so the signer builds the new form in this order to begin with. - Hash it. The result must equal
Q i:, andpqsigmust verify againstQ p:.
Transition
Relays already store every old form, so they can extract the new form from any stored event at any time. Serving new forms needs no migration step, only a way to ask for them.
Handshake. A relay that supports this lists it in NIP-11 supported_nips. After connecting, the client sends ["FORMAT", "pq"] and the relay answers ["FORMAT", "pq", true]. From then on, that connection's events and filters use the new form. Relays that don't know the message ignore it or send a NOTICE, and the connection stays in the old form.
New-form-only clients. Each client decides for itself when to stop wrapping. A client in this mode only sees new-form events and signs once, so only other new-form clients see what it publishes. That's why it waits until almost everyone has switched.
New-form-only relays. A relay can also stop accepting old forms entirely, and prune: keep the extracted new form of each stored event and drop the old one. Nothing is lost for new clients, since the new form is complete and self-verifying. Old clients stop seeing those events on relays that have pruned.
After Q-day
The pairing only matters while old forms are still read. Once secp256k1 can be broken, anyone can forge an old form for any npub, wrapping a new form signed by their own PQ key. Extracted, that's just a valid new-form event from the attacker's own identity, and new-form-only clients never read the pairing. By then follows, mentions and replies already point at PQ keys, so the social graph carries over without the npub.
Nobody has to agree on a date for this. Every client and relay switches when it wants, and each one is safe from the moment it does.
Anyone who never published a new form loses their identity along with their npub.