-
@ Gustavo
2025-01-08 17:45:09I 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: https://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 npub12262qa4uhw7u8gdwlgmntqtv7aye8vdcmvszkqwgs0zchel6mz7s6cgrkj npub1l2vyh47mk2p0qlsku7hg0vn29faehy9hy34ygaclpn66ukqp3afqutajft npub1acg6thl5psv62405rljzkj8spesceyfz2c32udakc2ak0dmvfeyse9p35c have raised.
There is a "working" example (I'm not a cryptographer, have mercy on me): https://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 hashH_B
and timestampt_B
. This block anchors the event in time.2.2. Derive a Challenge Point
- Take your main public key
P_A
and concatenate it withH_B
. - Compute
S = SHA256(P_A || H_B)
- 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 ofchallenge_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. -
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 (eithersk_A
orsk_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_PK
from<P_A, H_B>
. - Measure how close
P_mined
is tochallenge_PK
(i.e., proof-of-work difficulty). - 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!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": "...<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: 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! - Ring Signature over exactly two keys: