G56-02+03: Update nip_proposal.md and README.md for non-replaceable kind 9999

This commit is contained in:
Laan Tungir
2026-07-24 07:17:37 -04:00
parent 5a4a822c51
commit be5afe3c91
4 changed files with 36 additions and 33 deletions
+17 -17
View File
@@ -221,18 +221,18 @@ flowchart TD
SIGN4 --> KIND1
MLKEM --> KIND1
SECP --> ACCT1[Account 1 - existing identity signs kind 1 and kind 11112]
SECP --> ACCT1[Account 1 - existing identity signs kind 1 and kind 9999]
KIND1 --> OTS[OpenTimestamp the kind 1 event hash]
OTS --> KIND11112[Kind 11112 Wrapper Event - carries OTS proof]
KIND11112 --> RELAY[Published to relays NOW]
OTS --> KIND9999[Kind 9999 Proof Carrier - carries OTS proof]
KIND9999 --> RELAY[Published to relays NOW]
RELAY --> CLIENT1[PQ-aware client - picks ML-DSA]
RELAY --> CLIENT2[PQ-aware client - picks SLH-DSA]
RELAY --> CLIENT3[Legacy client - ignores all PQ keys]
style KIND1 fill:#ffa
style KIND11112 fill:#ffa
style KIND9999 fill:#ffa
style OTS fill:#cfc
```
@@ -275,9 +275,9 @@ pending timestamp on the Bitcoin blockchain via OpenTimestamps.
Each PQ signature scheme signs `TextEncoder.encode(content)` — the human-readable attestation text. The event is signed with the user's **existing** Nostr identity (Account #1) via `window.nostr.signEvent` (NIP-07).
#### 2. Kind 11112 Wrapper (machine-verifiable, carries the OTS proof)
#### 2. Kind 9999 Proof Carrier (machine-verifiable, carries the OTS proof)
A replaceable event (kind 11112, in the 1000019999 range) that wraps the kind 1 announcement and carries the OpenTimestamps proof. The content is the full kind 1 event as JSON (so verifiers don't need to fetch it from relays).
A non-replaceable event (kind 9999, in the 09999 range) that wraps the kind 1 announcement and carries the OpenTimestamps proof. The content is the full kind 1 event as JSON (so verifiers don't need to fetch it from relays). Each publication is permanent — upgrades publish a new event with an `upgrade_of` tag referencing the previous one.
**Content:** `JSON.stringify(kind1Event)` — the full signed kind 1 event embedded as a JSON string.
@@ -292,7 +292,7 @@ The wrapper is signed with the user's existing identity (Account #1) via `window
**What the event proves:**
- Each PQ signature scheme (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512) signed the attestation text — verified by checking each PQ signature against the pubkey in its tag.
- The user's existing Nostr identity (Account #1) authorized the migration — verified by the secp256k1 Schnorr signature on the kind 1 and kind 11112 events.
- The user's existing Nostr identity (Account #1) authorized the migration — verified by the secp256k1 Schnorr signature on the kind 1 and kind 9999 events.
- The event is anchored to a pre-quantum Bitcoin block via OpenTimestamps — the event with the earliest valid OTS proof wins (see Component 3).
**What the event does NOT prove:**
@@ -334,7 +334,7 @@ If a specific PQ scheme is later found to be weak (like SIKE was):
## Component 3: OpenTimestamps for Pre-Quantum Anchoring
NIP-03 (OpenTimestamps Attestations for Events) anchors Nostr event hashes to the Bitcoin blockchain via OpenTimestamps. The implementation submits the **SHA-256 of the full signed kind 1 event JSON** (including its `id` and `sig`) to the OpenTimestamps calendar servers, and embeds the resulting `.ots` proof in the kind 11112 wrapper's `ots` tag.
NIP-03 (OpenTimestamps Attestations for Events) anchors Nostr event hashes to the Bitcoin blockchain via OpenTimestamps. The implementation submits the **SHA-256 of the full signed kind 1 event JSON** (including its `id` and `sig`) to the OpenTimestamps calendar servers, and embeds the resulting `.ots` proof in the kind 9999 proof carrier's `ots` tag.
### Why Bitcoin Timestamping Is Quantum-Resistant
@@ -364,10 +364,10 @@ The kind 1 announcement event MUST be timestamped via OpenTimestamps. This preve
The implementation timestamps the **SHA-256 of the full signed kind 1 event JSON** (computed by hashing `JSON.stringify(kind1Event)`, which includes the `id` and `sig` fields). This hash is:
1. Submitted to OpenTimestamps calendar servers to obtain a pending `.ots` proof.
2. Recorded in the kind 11112 wrapper's `sha256` tag.
3. The `.ots` proof is embedded in the kind 11112 wrapper's `ots` tag.
2. Recorded in the kind 9999 proof carrier's `sha256` tag.
3. The `.ots` proof is embedded in the kind 9999 proof carrier's `ots` tag.
The kind 11112 wrapper *carries* the OTS proof; the kind 1 event *is* what's timestamped. Verifiers re-compute the hash from the embedded kind 1 event and check it matches the `sha256` tag.
The kind 9999 proof carrier *carries* the OTS proof; the kind 1 event *is* what's timestamped. Verifiers re-compute the hash from the embedded kind 1 event and check it matches the `sha256` tag.
### Current OTS Verification Status
@@ -646,8 +646,8 @@ flowchart TD
KIND1 --> SECP1[Account 1 signs kind 1 via signer]
SECP1 --> HASH[SHA-256 of full signed kind 1 event JSON]
HASH --> OTS[Submit hash to OpenTimestamps]
OTS --> KIND11112[Build kind 11112 wrapper with OTS proof]
KIND11112 --> SECP2[Account 1 signs kind 11112 via signer]
OTS --> KIND9999[Build kind 9999 proof carrier with OTS proof]
KIND9999 --> SECP2[Account 1 signs kind 9999 via signer]
SECP2 --> RELAY[Publish both events to relays]
SEED --> BACKUP[User writes down 12-word phrase]
end
@@ -655,7 +655,7 @@ flowchart TD
subgraph CONFIRM["Bitcoin confirmation"]
POLL[Poll OTS upgrade service]
POLL --> UPGRADE[Upgrade .ots proof with Bitcoin attestation]
UPGRADE --> REPUBLISH[Republish kind 11112 with confirmed proof]
UPGRADE --> REPUBLISH[Publish new kind 9999 with confirmed proof]
end
subgraph FUTURE["Post-quantum - when quantum computers arrive"]
@@ -682,10 +682,10 @@ flowchart TD
2. Client generates a 12-word phrase, shows it to the user to write down
3. Client derives 5 PQ keypairs from the seed via BIP32 paths
4. Each PQ signature scheme signs the human-readable attestation text
5. The user's signer signs the kind 1 announcement and the kind 11112 wrapper
5. The user's signer signs the kind 1 announcement and the kind 9999 proof carrier
6. The SHA-256 of the full signed kind 1 event is submitted to OpenTimestamps
7. Both events are published to relays
8. The client polls for Bitcoin confirmation and republishes the kind 11112 wrapper with the confirmed OTS proof
8. The client polls for Bitcoin confirmation and publishes a new kind 9999 proof carrier with the confirmed OTS proof (append-only — the original remains on relays)
9. After quantum migration: clients use PQ keys, old key retired
---
@@ -747,7 +747,7 @@ The current implementation is a static web app (`www/`) that performs the full m
| PQ signing (ML-DSA-44, ML-DSA-65, SLH-DSA-128s, Falcon-512) | Implemented | `www/js/pq-crypto.mjs` |
| ML-KEM-768 keygen (KEM, no signing) | Implemented | `www/js/pq-crypto.mjs` |
| Kind 1 announcement event construction | Implemented | `www/js/pq-crypto.mjs` |
| Kind 11112 wrapper event construction | Implemented | `www/js/pq-crypto.mjs` |
| Kind 9999 proof carrier event construction | Implemented | `www/js/pq-crypto.mjs` |
| NIP-01 event ID computation and Schnorr verification | Implemented | `www/js/pq-crypto.mjs` |
| OpenTimestamps submission (pending proof) | Implemented | `www/js/pq-crypto.mjs` |
| OTS upgrade polling (via server helper) | Implemented | `www/js/pq-crypto.mjs` |
+15 -12
View File
@@ -35,7 +35,7 @@ The PQ standardization landscape is still young. SIKE was a NIST finalist broken
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 11112 wrapper** — a 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.
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.
@@ -46,8 +46,8 @@ flowchart TD
SECP --> K1[Kind 1 announcement - attesting identity signs]
PQ --> K1
K1 --> OTS[OpenTimestamps anchor to Bitcoin block]
OTS --> K11112[Kind 11112 wrapper - carries OTS proof]
K11112 --> RELAY[Published to relays]
OTS --> K9999[Kind 9999 proof carrier - carries OTS proof]
K9999 --> RELAY[Published to relays]
```
## PQ key derivation from a BIP39 seed
@@ -157,9 +157,11 @@ Future NIPs may register additional `algorithm-id` values and additional BIP32 c
}
```
## The kind 11112 wrapper event
## The kind 9999 proof carrier event
A **replaceable** event (kind 11112, in the 1000019999 range) that wraps the kind 1 announcement and carries the OpenTimestamps proof. It is signed with the attesting identity's secp256k1 key.
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.
@@ -177,7 +179,7 @@ The kind number is provisional and subject to maintainer allocation.
```json
{
"kind": 11112,
"kind": 9999,
"pubkey": "<attesting identity secp256k1 pubkey, hex>",
"content": "<JSON string of the full signed kind 1 event>",
"tags": [
@@ -203,7 +205,7 @@ With an OTS anchor, the real event is committed to a specific Bitcoin block befo
### Verification rule: earliest valid anchor wins
When a client finds multiple kind 11112 wrappers 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 wrapper's embedded kind 1 event is the canonical key-link for that identity. Later wrappers, even if validly signed, are treated as superseding only if they are signed by a PQ key already linked by the earliest wrapper.
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
@@ -217,10 +219,11 @@ Clients SHOULD perform full client-side OTS verification: parse the proof, walk
### PQ-aware clients
A PQ-aware client, when it encounters a kind 11112 wrapper for a pubkey it cares about:
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 wrapper's `content`.
2. Verifies the attesting identity's secp256k1 signature on both the kind 1 and the kind 11112 events.
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).
3. Verifies the `sha256` tag matches `sha256(JSON.stringify(kind1Event))`.
4. For each `algorithm` tag with a signature, verifies the PQ signature against the pubkey in the tag, over `TextEncoder.encode(kind1Event.content)`.
5. Verifies the OTS proof in the `ots` tag against the `sha256` digest.
@@ -231,14 +234,14 @@ After a quantum break of secp256k1, the client trusts the PQ keys from the earli
### Legacy clients
Legacy clients see the kind 1 announcement as an ordinary text note and the kind 11112 wrapper as an unknown replaceable event. They ignore both. Nothing breaks.
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 11112 events, valid pre-quantum.
- 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:**
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "post_quantum_nostr",
"version": "0.0.14",
"version": "0.0.15",
"description": "A migration strategy for bringing post-quantum security to Nostr without breaking the social graph, without requiring consensus on a single post-quantum algorithm, and without forcing existing users to abandon their identities.",
"main": "index.js",
"scripts": {
+3 -3
View File
@@ -1,5 +1,5 @@
{
"VERSION": "v0.0.14",
"VERSION_NUMBER": "0.0.14",
"BUILD_DATE": "2026-07-24T11:16:26.143Z"
"VERSION": "v0.0.15",
"VERSION_NUMBER": "0.0.15",
"BUILD_DATE": "2026-07-24T11:17:37.498Z"
}