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:
- Your real key
- 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
- Take your main public key
P_Aand concatenate it withH_B. - Compute
S = SHA256(P_A || H_B) - Map
Sto 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_cbe the x-coordinate ofchallenge_PK. -
Find
x_msuch 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_minedand also the inverse, compute the δ given a number of tries.
- 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:
- Check validity of the ring signature over
{P_A, P_mined}. - Recompute
challenge_PKfrom<P_A, H_B>. - Measure how close
P_minedis tochallenge_PK(i.e., proof-of-work difficulty). - Conclude: “Either Alice really signed, or someone else who found a matching
P_mineddid.”
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
Tneeded for a given success probability. - Check the
Key/srate 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!
