Merge 81f7deffaf27d379c3f457f98cf63b2f179f7a83 into 0619f370bca3485bb9c5870bc2defa03c7c3d10e

This commit is contained in:
Gustavo Passos 2025-03-19 16:35:29 -03:00 committed by GitHub
commit feeae45a77
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

316
404.md Normal file
View File

@ -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 wont 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 users 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 events authorship becomes questionable.
**Based on public information**
All components of a Ghost Event are public, so anyone could potentially forge a signature. The protocols security
relies on the difficulty of finding a matching key.
**OTS "Resistance"**
You always can move the events 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 keys 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 events `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 its never definitively tied to you.
6. **Real signer secret key leaks**
The Ghost Events 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 events 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!