Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NIP 404 - Ghost Events #1676

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
316 changes: 316 additions & 0 deletions 404.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
# 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

**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.

**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 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.


### 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 (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.

---

## 2. Detailed Protocol

There is a demo here: [Ghost Events Demo](https://github.com/gu1p/nip404_demo/blob/master/main.py)

### 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", "<H_B>"]`
- `["ghost", "block-hash-timestamp", "<t_B>"]`

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. Client & Relay Behavior

### 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.

### 3.2. Relays

- Treat Ghost Events like any other events.
- Optionally index them for specialized queries by `ghost` tags.

---

## 4. 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. **Real signer secret key leaks**
The Ghost Event’s deniability of all events is completely compromised.
---

## 5. 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", <Block Hash>]` and so forth, and post to relays.
6. **Verify**
Clients confirm ring signature validity and check the PoW distance.

---

## 6. 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.

---

## 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": "f9d32cace9ead9457d121041c4c17a779ab84cecf90f08d90ccacd9673f1bab4",
"pubkey": "npub1r587vuykqhf8k7x4e06380edc7mr7pkz59r44tausmlwq970wkyqv9dh85",
"created_at": 1736350383,
"kind": 1,
"tags": [
[
"ghost",
"block-hash",
"00000000000000000000c75dc9d3296751a8bb62b2463fbc49035ee75ab45f39"
],
[
"ghost",
"block-hash-timestamp",
1736264059
]
],
"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!