diff --git a/43.md b/43.md new file mode 100644 index 0000000000..2fd7048cbe --- /dev/null +++ b/43.md @@ -0,0 +1,209 @@ +NIP-43 +====== + +CK-based DM +----------- + +`draft` `optional` + +This direct message (DM) scheme between two participants is +based on the [NIP-44](44.md) conversation key (CK), encrypted and hides +all metadata but the DM's `.created_at` field for retaining correct +filtering sort order. + +## Flow Summary + +- An user can freely send any number of DMs to another user by signing [NIP-59](59.md) gift-wrapped DM events, with the CK +acting like a private key. However, the inner event is signed by the sender's main private key; + +- At any time, specially when sending a DM to someone for the first time, an user informs the other party that the latter should +fetch messages from the first user. It is done by publishing a "Chat Request" event with a `p` tag set to the receiver's pubkey; + +- The receiver of the "Chat Request" event can choose to fetch messages from the sender by using the pubkey generated from the CK they have in common as an `{ authors: [] }` filter. The sender uses the same filter because as the CK is the same for both users, the receiver will also use it to gift-wrap reply DM events; + +- Each DM participant stores the list of individual DM chats they are tracking by updating their "Approved Chats" event. +The user client will only fetch DMs from approved chats. + +## Chat Request + +When an user sends a DM to someone for the first time, a `kind:1043` "Chat Request" event is also sent from user A to B. + +If user A has never received a reply DM from B after some time, +or a new DM from user A was sent after a long period of chat inactivity, +user A's client may send another "Chat Request" event to B just in case a previous chat request was missed +due to being mixed with a lot of other chat requests by different people. + +The chat request uses a random pubkey to hide from the public who's the requester, like a [NIP-59](59.md) Gift Wrap +but with a specific kind number to enable targeted fetch. It wraps a `kind:13` event that seals a `kind:1044` "Chat Request Intent" event. + +The seal can only be opened using A and B keys, a fact that sets the participants. +While the most inner event kind number confirms that the intention is to chat. + +The `kind:1043` event require adding [NIP-13](13.md) proof of work (PoW) +with a minimum of 16 difficulty. + +The `kind:1044` event doesn't have a `.sig` field to prevent the event without seal +from proving authorship or being republished on relays. + +It has an empty `.content`. + +It must have the same `created_at` of the `kind:1043` one to prevent +the latter's content from being replayed inside a new `kind:1043` event. + +The "Chat Request" event should be sent to atleast one of the receiver's [NIP-65](65.md) `read` relays. + +```js +{ + "kind": 1043, + "pubkey": "", + "tags": [ + ["p", ""], + ["nonce", "65962", "16"] // atleast 16 PoW difficulty + ], + "content": "", + "tags": [], + "content": "", + "kind": 1044, + "pubkey" "", + "tags": [], + "content": "", + "created_at": 1702711000 // now + // no .sig field + }))>", + "created_at": 0 + // ....other fields + }))>", + "created_at": 1702711000 // now + // ...other fields +} +``` + +### Spam Mitigation + +Because the `kind:1043` includes a `p` tag set to the recipient, anyone can send such +events to an user at a high frequency to fill their "Chat Requests" inbox. + +Below details and conventions ease the problem: + +#### Misc + +- The event has an empty `.content` to reduce value for spammers +(there is no attached message to show to the receiver, +so the spammer can't advertise URLs, for example); + +- The way this NIP splits the events into "Chat Requests" and "DMs" makes it possible to +download DMs of already approved chats freely while being hit by a Denial +of Service (DoS) attack (when someone sends a high volume of "Chat Request" events +to an user to fill their inbox), without chance of missing messages. + +#### Client + +- Receiving clients MUST ignore chat requests with [NIP-13](13.md) PoW +difficulty lower than 16. It wouldn't be possible to require this from DM events, +but as chat requests are expected to have a much lower volume than DMs, it's ok; + +- Receiving Clients should NOT strive to download and sync the complete set of chat requests, +because many may be spam. Instead, they may choose to download only the last `n` events +(`n` value depends on how long the user was offline) +and keep websocket open to receive new ones; + +- During a DoS attack (too many chat requests with close timestamps being received +while the websocket is open), the client should stop listening for new ones. +After some time, it should restart above steps; + +- Sending clients should retry chat requests by publishing new ones whenever they +consider it necessary, to account for the receiving user missing some due to previous +chat requests getting mixed with spam. + +#### Relay + +- Relays should block chat request events with byte size higher than 3KB (1543 is the expected upper limit of the byte size of such events but we leave room to size +increase due to possible changes to NIP-44) to prevent big event attack attempts. +This is possible because "Chat Request" events have a fixed number of tags +and empty content, which isn't the case with DM events; + +- Relays are encouraged to require increasing [NIP-13](13.md) PoW difficulty the more chat requests are sent to the same pubkey. The min PoW difficulty should be 16. + +## Approved Chats + +Clients are expected to fetch DMs just from approved chats. + +When receiving a chat request from user A, an user B can accept it or ignore it. +The user accepts it by adding the sender's pubkey to its [NIP-51](51.md) `kind:10043` +"Approved Chats" event's list of encrypted `p` tags. + +B's client should auto accept it if A is one of their follows or contact. Else +the client should show A's `kind:0` metadata and link to their profile to help with B's decision. + +When sending a chat request, the sender auto adds the receiver's pubkey to their own "Approved Chats" event if they weren't already in the list. + +The user should publish the event to all of their own [NIP-65](65.md) `write` relays. + +```js +{ + "kind": 10043, + "pubkey": "" + "content": ""], + // other "p" tags + ]))>", + // ...other fields +} +``` + +## Direct Message + +User A may send `kind:14` DM events to user B even though they don't +know if B will ever fetch and read them. + +Both A and B use their [NIP-44](44.md) "Conversation Key" (CK) they have in common as a private key to sign a [NIP-59](59.md) `kind:1059` wrapper event +(with `kind:13` seal). It wraps the encrypted DM event authored by the real user's pubkey. + +The CK is also used to encrypt the most inner event. + +The wrapper event has a random pubkey on the `p` tag just to look like a regular gift wrap. +In reality the two participants will fetch messages by `author`, using the pubkey derived from the CK effectivelly as a private communication channel between them. +This way no fake events can be sent to any of the participants to flood them with a DoS attack and the new messages set is downloaded quicker. + +The correct current date is used on the wrapper event. + +The inner `kind:14` event MUST NOT include a `.sig` field. + +It has a `gwsk` tag set to the private key used to sign the gift wrap, +encrypted using A's keypair as arguments when generating the CK +used just for this specific encryption task. +This way, the sender is able to delete the event. + +```js +{ + "kind": 1059, // gift wrap + "pubkey": "", + "tags": [ + ["p", ""] // just to look like a regular gift wrap + ], + "content": "", + "tags": [], + "content": "", + "kind": 14, // DM + "pubkey" "", + "tags": ["gwsk", ")>"], // encrypted to A only + "content": "Hello, User B", + "created_at": 1702711000 // now + // no .sig field + }))>", + "created_at": 0 + // ....other fields + }))>", + "created_at": 1702711000 // now + // ...other fields +} +``` + +This event should be sent to atleast one of the receiver's [NIP-65](65.md) `read` relays and also to one or more of the sender's `read` relays. +This way, an user will only have to fetch DMs from their own set of `read` relays.