nips/404.md
Gustavo Passos 81f7deffaf
fix demo url
2025-01-08 19:27:57 -03:00

13 KiB
Raw Blame History

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

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”): \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 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.
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!

{
  "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!