G56-02+03: Update nip_proposal.md and README.md for non-replaceable kind 9999
This commit is contained in:
@@ -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 10000–19999 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 0–9999 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
@@ -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 10000–19999 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 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.
|
||||
|
||||
@@ -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
@@ -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
@@ -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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user