Jan 8, 2025

Ghost Events - NIP-404

I just published the new version of my NIP-404. It basically a way to have real ephemeral interactions on Nostr, so developers can model Stories and messages that disappear etc and providing a good level of plausible deniability. Here is the new version: github.com/nostr-protocol/nips/pull/1676

This is a follow-up of a previous proposal I did in last November. I tried to tackle most of issues that

have raised.

There is a "working" example (I'm not a cryptographer, have mercy on me): github.com/gu1p/nip404_demo

TL;DR;

NIP-404: Ghost Events

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.

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 δ 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 = SHA256(P_A || H_B)
    
  3. Map S to secp256k1 (RFC 9380 “Hashing to Elliptic Curves”):
    challenge_PK = HashToCurve(S)
    

Anyone can verify this point by performing the same steps.

2.3. Pick a “Mined Key” P_mined

  • Let x_c be the x-coordinate of challenge_PK.

  • Find x_m such that |x_m - x_c| <= δ and (x_m, y_m) is a valid secp256k1 point.

  • δ reflects the “difficulty” of finding such a key.

  • In the proposal I have provide a way to get a δ given the number of tries and target probability you expect someone to mine the P_mined and also the inverse, compute the δ given a number of tries.

  1. Compute $T$ (number of tries) given $\delta$ and target probability.

2.4. Create a Ring Signature

Form a ring of two public keys: {P_A, P_mined}.
Use your real private key to sign, producing a ring signature that proves one of the private keys (either sk_A or 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_mined}.
  2. Recompute challenge_PK from <P_A, H_B>.
  3. Measure how close P_mined is to challenge_PK (i.e., proof-of-work difficulty).
  4. Conclude: “Either Alice really signed, or someone else who found a matching P_mined did.”

Practical Tips

  • Picking δ:

    • Decide how quickly you want deniability to set in. If you pick a very small δ, 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 δ, but accept that forging a second “mined” key becomes easier.
    • If you want avoid OTS, pick a larger δ and reference older blocks.
  • Verifying a Received Ghost Event:

    • Check the actual δ 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": "...<ring signature>...."
}

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:


Bob: The challenge public key is:
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!