From a38e8f5e3bb64297e708ba0d2de41d2ba47d85db Mon Sep 17 00:00:00 2001 From: Gustavo Passos Date: Wed, 8 Jan 2025 11:22:35 -0300 Subject: [PATCH 1/3] NIP 404 - Description --- 404.md | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 404.md diff --git a/404.md b/404.md new file mode 100644 index 00000000..d240a526 --- /dev/null +++ b/404.md @@ -0,0 +1,163 @@ +# NIP-404: Ghost Events + +`draft` `optional` + +This NIP introduces **Ghost Events**—a protocol for creating events that are **plausibly deniable** with a **ephemeral** +nature, providing a weak binding to the author's identity. It leverages ring signatures, elliptic curve point-hashing, and a distance-based proof-of-work that references Bitcoin block hashes for chronological anchoring. + +--- + +## 1. Motivation & Overview + +**Plausible Deniability** + Nostr events are permanently linked to the signing public key. Ghost Events only *weakly* link to a user’s main key, so the user can later deny authorship. + +**Ephemerality** + Each Ghost Event references a Bitcoin block, providing a **time anchor**. As time passes, more “mined keys” satisfying the same proof-of-work can appear, so the event’s authorship becomes questionable. + +**Based on public information** + All components of a Ghost Event are public, so anyone can forge one referencing your key. This ensures that no one can definitively prove you authored it. + + +**Maintain Main Identity** + You keep your main key for official posts, but can also create Ghost Events that won’t tarnish your identity if they become embarrassing or sensitive later. + +### High-Level Mechanics + +- **Ring Signature** over exactly two keys: + 1) Your real key; + 2) A “mined” key—found by solving a proof-of-work puzzle. +- **Distance-based PoW**: The mined key’s public key must be within distance $\delta$ of a “challenge point” derived from your public key and a Bitcoin block hash. +- **Deniability**: Verifiers can only tell that *one* of the two keys signed the event, not which one. Because all components are public, anyone can forge a Ghost Event referencing your key. + +--- + +## 2. Detailed Protocol + +### 2.1. Reference a Bitcoin Block + +Pick a Bitcoin block $B$ with hash $H_B$ and timestamp $t_B$. This block anchors the event in time. + +### 2.2. Derive a Challenge Point + +1. Take your main public key $P_A$ and concatenate it with $H_B$. +2. Compute $S = \mathrm{SHA256}(P_A \,\|\, H_B)$. +3. Map $S$ to secp256k1 ([RFC 9380 “Hashing to Elliptic Curves”](https://www.rfc-editor.org/rfc/rfc9380)): $\mathrm{challenge_PK} = \mathrm{HashToCurve}(S).$ + +Anyone can verify this point by performing the same steps. + +### 2.3. Pick a “Mined Key” $P_{\mathrm{mined}}$ + +- Let $x_c$ be the $x$-coordinate of $\mathrm{challenge_PK}$. +- Find $x_m$ such that $| x_m - x_c | \le \delta$ and $(x_m,y_m)$ is a valid secp256k1 point. +- $\delta$ reflects the “difficulty” of finding such a key. + +### 2.4. Create a Ring Signature + +Form a ring of two public keys: $\{P_A, P_{\mathrm{mined}}\}$. +Use *your real private key* to sign, producing a ring signature that proves **one** of the private keys (either $\mathrm{sk}_A$ or $\mathrm{sk_mined}$) signed—but not which one. + +### 2.5. Publish the Ghost Event + +Publish a standard Nostr event (`kind`, `content`, etc.) plus tags: + +- `["ghost", "block-hash", ""]` +- `["ghost", "block-hash-timestamp", ""]` + +The event’s `sig` field is the **ring signature**. Verifiers can: + +1. Check validity of the ring signature over $\{P_A, P_{\mathrm{mined}}\}$. +2. Recompute $\mathrm{challenge_PK}$ from $\langle P_A, H_B\rangle$. +3. Measure how close $P_{\mathrm{mined}}$ is to $\mathrm{challenge_PK}$ (i.e., proof-of-work difficulty). +4. Conclude: “Either Alice really signed, or someone else who found a matching $P_{\mathrm{mined}}$ did.” + +--- + +## 3. Ephemerality & Deniability + +- Over time, more keys can be found that also satisfy the distance requirement. This undermines any **unique** link to your real key. +- The older the referenced block, the more plausible it is that another party found a matching key in that time window. + +--- + +## 4. Client & Relay Behavior + +### 4.1. Clients + +- Display “Deniable” or “Ghost” for events with `ghost` tags. +- Show $\delta$ to indicate how hard it is to find a matching mined key. +- Emphasize **time**: if the event references an older block, collisions become more likely, thus increasing deniability. + +### 4.2. Relays + +- Treat Ghost Events like any other events. +- Optionally index them for specialized queries by `ghost` tags. +- No special handling is required to maintain the protocol’s security. + +--- + +## 5. Security Considerations + +1. **Ring Signature Robustness** + Any weaknesses in the ring signature could leak which key was used. + +2. **Choice of $\delta$** + A smaller $\delta$ means harder PoW but stronger initial binding to your identity. A larger $\delta$ lowers the barrier for collisions, boosting deniability sooner. + +3. **Block Hash Trust** + Must rely on a valid, widely recognized Bitcoin chain tip. + +4. **Long-Term Attacks** + As time passes or computational power grows, collisions become easier. + +5. **Anyone Can Forge** + Attackers can craft a Ghost Event referencing *your* key without your involvement, ensuring it’s never definitively tied to you. + +--- + +## 6. Example Workflow + +1. **Alice Picks a Block** + She picks a recent (or somewhat older) Bitcoin block $B$. +2. **Compute Challenge** + $S = \mathrm{SHA256}(P_A \| H_B)$, then $\mathrm{challenge_PK} = \mathrm{HashToCurve}(S)$. +3. **Select Mined Key** + Choose $P_{\mathrm{mined}}$ with $| x_m - x_c | \le \delta$. + Ideally, clients will offer smart UIs to help pick $\delta$ to match the desired level of deniability. + + We also can combine that with [NIP-40](https://github.com/nostr-protocol/nips/blob/master/40.md) tags. +4. **Ring Signature** + The ring is $\{P_A, P_{\mathrm{mined}}\}$. Alice signs with $\mathrm{sk}_A $. +5. **Publish** + Attach `["ghost", "block-hash", ]` and so forth, and post to relays. +6. **Verify** + Clients confirm ring signature validity and check the PoW distance. + +--- + +## 7. OpenTimestamps (OTS) "Resistance" + +Even if the event is OTS-stamped at publication, Ghost Events remain deniable because: + +- They can reference arbitrary *older* blocks and with a smaller $\delta$, moving the event’s creation window further back, +but also making it the total PoW harder. +- Thus, an OTS proof can confirm the event *existed*, but not that *only you* could have created it, as all components +to forge the event are public and were available since the block was mined. + +--- + +## 8. Example Ghost Event JSON + +```json +{ + "id": "eb4cc8b3ff2b6f2c000ad353...", + "pubkey": "npub1al1c3......", + "created_at": 1699876543, + "kind": 1, + "tags": [ + ["ghost", "block-hash", "00000000000000000004f13f..."], + ["ghost", "block-hash-timestamp", "1699876000"] + ], + "content": "Having a little fun tonight... 🍻", + "sig": "f6c3e7564db51ac8dbe2c790fa12785b036ebc64..." +} From 32cd8aef2d204aa8568a15358296b3c7007e26f3 Mon Sep 17 00:00:00 2001 From: Gustavo Passos Date: Wed, 8 Jan 2025 13:31:15 -0300 Subject: [PATCH 2/3] Comments on difficult and Pow --- 404.md | 213 +++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 183 insertions(+), 30 deletions(-) diff --git a/404.md b/404.md index d240a526..4fa02e9c 100644 --- a/404.md +++ b/404.md @@ -9,24 +9,33 @@ nature, providing a weak binding to the author's identity. It leverages ring sig ## 1. Motivation & Overview +**Maintain Main Identity** + You keep your main key for official posts, but can also create Ghost Events that won’t tarnish your identity if they + become embarrassing or sensitive later. + **Plausible Deniability** - Nostr events are permanently linked to the signing public key. Ghost Events only *weakly* link to a user’s main key, so the user can later deny authorship. + Nostr events are permanently linked to the signing public key. Ghost Events only *weakly* link to a user’s main key, + so the user can later deny authorship. **Ephemerality** - Each Ghost Event references a Bitcoin block, providing a **time anchor**. As time passes, more “mined keys” satisfying the same proof-of-work can appear, so the event’s authorship becomes questionable. + Each Ghost Event references a Bitcoin block, providing a **time anchor**. As time passes, more “mined keys” + satisfying the same proof-of-work can appear, so the event’s authorship becomes questionable. **Based on public information** - All components of a Ghost Event are public, so anyone can forge one referencing your key. This ensures that no one can definitively prove you authored it. + All components of a Ghost Event are public, so anyone could potentially forge a signature. The protocol’s security + relies on the difficulty of finding a matching key. +**OTS "Resistance"** + You always can move the event’s creation window further back by referencing older blocks, and, at the same time, + increasing the total PoW difficulty. Thus, an OTS proof can confirm the event *existed*, but not that *only you* + could have created it. -**Maintain Main Identity** - You keep your main key for official posts, but can also create Ghost Events that won’t tarnish your identity if they become embarrassing or sensitive later. ### High-Level Mechanics - **Ring Signature** over exactly two keys: 1) Your real key; - 2) A “mined” key—found by solving a proof-of-work puzzle. + 2) A “mined” key—found by solving a proof-of-work puzzle (attacker) or randomly (by the real signer). - **Distance-based PoW**: The mined key’s public key must be within distance $\delta$ of a “challenge point” derived from your public key and a Bitcoin block hash. - **Deniability**: Verifiers can only tell that *one* of the two keys signed the event, not which one. Because all components are public, anyone can forge a Ghost Event referencing your key. @@ -34,6 +43,8 @@ nature, providing a weak binding to the author's identity. It leverages ring sig ## 2. Detailed Protocol +There is a demo here: [Ghost Events Demo](https://nostr-protocol.github.io/ghost-events-demo/) + ### 2.1. Reference a Bitcoin Block Pick a Bitcoin block $B$ with hash $H_B$ and timestamp $t_B$. This block anchors the event in time. @@ -72,31 +83,22 @@ The event’s `sig` field is the **ring signature**. Verifiers can: 4. Conclude: “Either Alice really signed, or someone else who found a matching $P_{\mathrm{mined}}$ did.” --- +## 3. Client & Relay Behavior -## 3. Ephemerality & Deniability - -- Over time, more keys can be found that also satisfy the distance requirement. This undermines any **unique** link to your real key. -- The older the referenced block, the more plausible it is that another party found a matching key in that time window. - ---- - -## 4. Client & Relay Behavior - -### 4.1. Clients +### 3.1. Clients - Display “Deniable” or “Ghost” for events with `ghost` tags. - Show $\delta$ to indicate how hard it is to find a matching mined key. - Emphasize **time**: if the event references an older block, collisions become more likely, thus increasing deniability. -### 4.2. Relays +### 3.2. Relays - Treat Ghost Events like any other events. - Optionally index them for specialized queries by `ghost` tags. -- No special handling is required to maintain the protocol’s security. --- -## 5. Security Considerations +## 4. Security Considerations 1. **Ring Signature Robustness** Any weaknesses in the ring signature could leak which key was used. @@ -105,7 +107,7 @@ The event’s `sig` field is the **ring signature**. Verifiers can: A smaller $\delta$ means harder PoW but stronger initial binding to your identity. A larger $\delta$ lowers the barrier for collisions, boosting deniability sooner. 3. **Block Hash Trust** - Must rely on a valid, widely recognized Bitcoin chain tip. + Must rely on a valid, widely recognized Bitcoin chain tip. 4. **Long-Term Attacks** As time passes or computational power grows, collisions become easier. @@ -113,9 +115,11 @@ The event’s `sig` field is the **ring signature**. Verifiers can: 5. **Anyone Can Forge** Attackers can craft a Ghost Event referencing *your* key without your involvement, ensuring it’s never definitively tied to you. +6. **Real signer secret key leaks** + The Ghost Event’s deniability of all events is completely compromised. --- -## 6. Example Workflow +## 5. Example Workflow 1. **Alice Picks a Block** She picks a recent (or somewhat older) Bitcoin block $B$. @@ -135,7 +139,7 @@ The event’s `sig` field is the **ring signature**. Verifiers can: --- -## 7. OpenTimestamps (OTS) "Resistance" +## 6. OpenTimestamps (OTS) "Resistance" Even if the event is OTS-stamped at publication, Ghost Events remain deniable because: @@ -146,18 +150,167 @@ to forge the event are public and were available since the block was mined. --- -## 8. Example Ghost Event JSON +## 7. Challenge Picking & Verification + +### 7.1 Probability & Difficulty + +Ghost Events rely on a “distance” $\delta$ to measure how “close” a mined key must be to the challenge point. A smaller $\delta$ makes finding that key *harder*; a larger $\delta$ makes it *easier*. + +To quantify this difficulty, we can use a simple probabilistic model: + +Probability of finding at least one valid key in: +$$T \text{ tries} = 1 - \bigl(1 - \tfrac{\delta}{p}\bigr)^{T} $$ + +where $p$ is the size of the search space (typically the secp256k1 curve order or field modulus). + +### 7.2. Given $T$ tries & desired probability \(P\), solve for $\delta$ + +We rearrange: + +$1 - (1 - \tfrac{\delta}{p})^T = P \quad\Longrightarrow\quad \delta = p \,\Bigl[1 - (1 - P)^{1/T}\Bigr].$ + +A larger $\delta$ leads to fewer tries needed for success—but also to **weaker** deniability. + +### 7.3. Given $\delta$ & desired probability \(P\), solve for $T$ + +We rearrange: + +$$ +1 - (1 - \tfrac{\delta}{p})^T = P +\quad\Longrightarrow\quad +T \;\ge\; \frac{\ln(1 - P)}{\ln\bigl(1 - \tfrac{\delta}{p}\bigr)}. +$$ + +Hence if $\delta$ is already fixed (e.g., from a received Ghost Event), you can compute how many attempts were likely needed to find that key with a certain success probability. + +--- + +### Example Functions + +Below are **reference** implementations demonstrating how to: + +1. **Compute $\delta$** given the number of tries and target probability. +2. **Compute $T$** (number of tries) given $\delta$ and target probability. + +```python +def find_interval_given_tries(tries: int, target_prob: float) -> int: + """ + Given 'tries' and a 'target_prob' (like 0.5 or 0.95), + return the minimal delta (distance) needed to reach that probability. + """ + if target_prob <= 0: + return 0 + if target_prob >= 1: + return int(Decimal(SECP256K1_CURVE_ORDER)) # entire space + + target_prob_dec = Decimal(target_prob) + + # Solve 1 - (1 - delta/p)^T = target_prob for delta + one_minus_tp = Decimal(1) - target_prob_dec + exponent_part = one_minus_tp ** (Decimal(1) / Decimal(tries)) + ratio = Decimal(1) - exponent_part + delta_dec = Decimal(SECP256K1_CURVE_ORDER) * ratio + + # Round up to ensure >= target_prob + return int(delta_dec.to_integral_value(rounding="ROUND_CEILING")) + + +def find_tries_given_interval(delta: int, target_prob: float) -> int: + """ + Given a distance 'delta' and 'target_prob', + return how many tries (T) are needed to reach that probability. + """ + if target_prob <= 0: + return 0 + if delta >= SECP256K1_CURVE_ORDER: + return 1 # 100% success in one try + if target_prob >= 1: + raise ValueError("Impossible to achieve probability >= 1 unless delta spans entire space.") + + target_prob_dec = Decimal(target_prob) + delta_dec = Decimal(delta) + + # Probability of success in one try = delta / p + x = delta_dec / Decimal(SECP256K1_CURVE_ORDER) + + # We solve (1 - x)^T <= 1 - target_prob + lhs = Decimal(1) - x + rhs = Decimal(1) - target_prob_dec + T_float = rhs.ln() / lhs.ln() + + T_int = int(T_float.to_integral_value(rounding="ROUND_CEILING")) + return max(T_int, 1) +``` + +- **Edge Cases** + - If `target_prob <= 0`, the probability requirement is trivial (0 tries or 0 distance). + - If `target_prob >= 1` and `delta < p`, it is impossible to guarantee 100% success in finite tries. + - If `delta >= p`, you already span the entire space, so 1 try suffices. + +--- + +### Practical Tips + +- **Picking $\delta$**: + - Decide how quickly you want deniability to set in. If you pick a very small $\delta$, it requires more CPU time + for an attacker to replicate—but also temporarily ties the event more strongly to you. + - If you want faster deniability, pick a larger $\delta$, but accept that forging a second “mined” key becomes easier. + - If you want avoid OTS, pick a larger $\delta$ and reference older blocks. + +- **Verifying a Received Ghost Event**: + - Check the actual $\delta$ provided. + - Estimate the tries $T$ needed for a given success probability. + - Check the $Key/s$ rate necessary to produce the event since the block was mined. + + +--- + +## 7. Example Ghost Event JSON + +**Alice:** Today is a good day to post wild picture of me on Nostr! Maybe I will regret it later... +**Alice:** Let post as a ghost event! +**Alice:** I will set a PoW of 86400000000 tries for a 30% of success. It is an easy one! + ```json { - "id": "eb4cc8b3ff2b6f2c000ad353...", - "pubkey": "npub1al1c3......", - "created_at": 1699876543, + "id": "f9d32cace9ead9457d121041c4c17a779ab84cecf90f08d90ccacd9673f1bab4", + "pubkey": "npub1r587vuykqhf8k7x4e06380edc7mr7pkz59r44tausmlwq970wkyqv9dh85", + "created_at": 1736350383, "kind": 1, "tags": [ - ["ghost", "block-hash", "00000000000000000004f13f..."], - ["ghost", "block-hash-timestamp", "1699876000"] + [ + "ghost", + "block-hash", + "00000000000000000000c75dc9d3296751a8bb62b2463fbc49035ee75ab45f39" + ], + [ + "ghost", + "block-hash-timestamp", + 1736264059 + ] ], - "content": "Having a little fun tonight... 🍻", - "sig": "f6c3e7564db51ac8dbe2c790fa12785b036ebc64..." + "content": "Hi! This is Alice... Here is a picture of me drinking a beer!", + "sig": "{\"members\": [{\"x\": \"67662445654153687740586373927886816071496898620020884082657466854018749152574\", \"y\": \"36654242403647812025958692266000451967507088411740075397012431742148926139504\"}, {\"x\": \"13145165751859428441328823552188587896062471095368091519302299383344531469704\", \"y\": \"88317938924061092515290554803088470753630337048796286302890952317743305618744\"}], \"e0\": \"39084049882600833852841500746712733990015986042592648801067646082050173502362\", \"s\": [\"31193407423453445855440968148549028682673465359135742084404121637747636355393\", \"58541132732934108006164799904805991239360069925983344774829987032327853747970\"]}" } +``` + +**Bob:** Wow! There is an event from Alice! +**Bob:** Let's check the signature to make sure it is from Alice +**Bob:** Alice didn't sign this event! +**Bob:** I see some ghost tags here... +**Bob:** It is a ghost event! +**Bob:** There are 2 possible signers! +**Bob:** Alice is one of the possible signers! +It was produced, allegedly, after the block a 00000000000000000000c75dc9d3296751a8bb62b2463fbc49035ee75ab45f39 +**Bob:** It says that the block was mined at 1736264059 +**Bob:** Let see if this block is real! +**Bob:** The block is real! +**Bob:** Let's check the PoW +**Bob:** The other signer is: npub1jktevjlge6vjp2yfaltrlvuv99th2uxaxy8eglvcsvr7e2r9h5lq2978dz +**Bob:** The challenge public key is: npub1jktevjldsr7kr52f90602gfhmc8cac9quq6yy8k9vy94w2qptsasz9cfps +**Bob:** Let's figure out how hard it was to mine the private key +**Bob:** The distance between the challenge and the alleged mined public key is 494635222290174920048599093528121665305037827746663102318405983997 +**Bob:** For having 50% of chance of figuring out this key, the signer should have done 83496150448 tries +**Bob:** The signer should have mined 967231 keys per second, since the block was mined +**Bob:** It is not a hard PoW! \ No newline at end of file From 81f7deffaf27d379c3f457f98cf63b2f179f7a83 Mon Sep 17 00:00:00 2001 From: Gustavo Passos Date: Wed, 8 Jan 2025 19:27:57 -0300 Subject: [PATCH 3/3] fix demo url --- 404.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/404.md b/404.md index 4fa02e9c..bb241853 100644 --- a/404.md +++ b/404.md @@ -43,7 +43,7 @@ nature, providing a weak binding to the author's identity. It leverages ring sig ## 2. Detailed Protocol -There is a demo here: [Ghost Events Demo](https://nostr-protocol.github.io/ghost-events-demo/) +There is a demo here: [Ghost Events Demo](https://github.com/gu1p/nip404_demo/blob/master/main.py) ### 2.1. Reference a Bitcoin Block @@ -313,4 +313,4 @@ It was produced, allegedly, after the block a 00000000000000000000c75dc9d3296751 **Bob:** The distance between the challenge and the alleged mined public key is 494635222290174920048599093528121665305037827746663102318405983997 **Bob:** For having 50% of chance of figuring out this key, the signer should have done 83496150448 tries **Bob:** The signer should have mined 967231 keys per second, since the block was mined -**Bob:** It is not a hard PoW! \ No newline at end of file +**Bob:** It is not a hard PoW!