mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-22 08:25:53 -05:00
165 lines
9.6 KiB
Markdown
165 lines
9.6 KiB
Markdown
NIP-17
|
||
======
|
||
|
||
Private Direct Messages
|
||
-----------------------
|
||
|
||
`draft` `optional`
|
||
|
||
This NIP defines an encrypted direct messaging scheme using [NIP-44](44.md) encryption and [NIP-59](59.md) seals and gift wraps.
|
||
|
||
## Direct Message Kind
|
||
|
||
Kind `14` is a chat message. `p` tags identify one or more receivers of the message.
|
||
|
||
```jsonc
|
||
{
|
||
"id": "<usual hash>",
|
||
"pubkey": "<sender-pubkey>",
|
||
"created_at": "<current-time>",
|
||
"kind": 14,
|
||
"tags": [
|
||
["p", "<receiver-1-pubkey>", "<relay-url>"],
|
||
["p", "<receiver-2-pubkey>", "<relay-url>"],
|
||
["e", "<kind-14-id>", "<relay-url>", "reply"] // if this is a reply
|
||
["subject", "<conversation-title>"],
|
||
// rest of tags...
|
||
],
|
||
"content": "<message-in-plain-text>",
|
||
}
|
||
```
|
||
|
||
`.content` MUST be plain text. Fields `id` and `created_at` are required.
|
||
|
||
Tags that mention, quote and assemble threading structures MUST follow [NIP-10](10.md).
|
||
|
||
Kind `14`s MUST never be signed. If it is signed, the message might leak to relays and become **fully public**.
|
||
|
||
## Chat Rooms
|
||
|
||
The set of `pubkey` + `p` tags defines a chat room. If a new `p` tag is added or a current one is removed, a new room is created with clean message history.
|
||
|
||
Clients SHOULD render messages of the same room in a continuous thread.
|
||
|
||
An optional `subject` tag defines the current name/topic of the conversation. Any member can change the topic by simply submitting a new `subject` to an existing `pubkey` + `p`-tags room. There is no need to send `subject` in every message. The newest `subject` in the thread is the subject of the conversation.
|
||
|
||
## Encrypting
|
||
|
||
Following [NIP-59](59.md), the **unsigned** `kind:14` chat message must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually.
|
||
|
||
```jsonc
|
||
{
|
||
"id": "<usual hash>",
|
||
"pubkey": randomPublicKey,
|
||
"created_at": randomTimeUpTo2DaysInThePast(),
|
||
"kind": 1059, // gift wrap
|
||
"tags": [
|
||
["p", receiverPublicKey, "<relay-url>"] // receiver
|
||
],
|
||
"content": nip44Encrypt(
|
||
{
|
||
"id": "<usual hash>",
|
||
"pubkey": senderPublicKey,
|
||
"created_at": randomTimeUpTo2DaysInThePast(),
|
||
"kind": 13, // seal
|
||
"tags": [], // no tags
|
||
"content": nip44Encrypt(unsignedKind14, senderPrivateKey, receiverPublicKey),
|
||
"sig": "<signed by senderPrivateKey>"
|
||
},
|
||
randomPrivateKey, receiverPublicKey
|
||
),
|
||
"sig": "<signed by randomPrivateKey>"
|
||
}
|
||
```
|
||
|
||
The encryption algorithm MUST use the latest version of [NIP-44](44.md).
|
||
|
||
Clients MUST verify if pubkey of the `kind:13` is the same pubkey on the `kind:14`, otherwise any sender can impersonate others by simply changing the pubkey on `kind:14`.
|
||
|
||
Clients SHOULD randomize `created_at` in up to two days in the past in both the seal and the gift wrap to make sure grouping by `created_at` doesn't reveal any metadata.
|
||
|
||
The gift wrap's `p`-tag can be the receiver's main pubkey or an alias key created to receive DMs without exposing the receiver's identity.
|
||
|
||
Clients CAN offer disappearing messages by setting an `expiration` tag in the gift wrap of each receiver or by not generating a gift wrap to the sender's public key
|
||
|
||
## Publishing
|
||
|
||
Kind `10050` indicates the user's preferred relays to receive DMs. The event MUST include a list of `relay` tags with relay URIs.
|
||
|
||
```jsonc
|
||
{
|
||
"kind": 10050,
|
||
"tags": [
|
||
["relay", "wss://inbox.nostr.wine"],
|
||
["relay", "wss://myrelay.nostr1.com"],
|
||
],
|
||
"content": "",
|
||
// other fields...
|
||
}
|
||
```
|
||
|
||
Clients SHOULD publish kind `14` events to the `10050`-listed relays. If that is not found that indicates the user is not ready to receive messages under this NIP and clients shouldn't try.
|
||
|
||
## Relays
|
||
|
||
It's advisable that relays do not serve `kind:1059` to clients other than the ones tagged in them.
|
||
|
||
It's advisable that users choose relays that conform to these practices.
|
||
|
||
Clients SHOULD guide users to keep `kind:10050` lists small (1-3 relays) and SHOULD spread it to as many relays as viable.
|
||
|
||
## Benefits & Limitations
|
||
|
||
This NIP offers the following privacy and security features:
|
||
|
||
1. **No Metadata Leak**: Participant identities, each message's real date and time, event kinds, and other event tags are all hidden from the public. Senders and receivers cannot be linked with public information alone.
|
||
2. **No Public Group Identifiers**: There is no public central queue, channel or otherwise converging identifier to correlate or count all messages in the same group.
|
||
3. **No Moderation**: There are no group admins: no invitations or bans.
|
||
4. **No Shared Secrets**: No secret must be known to all members that can leak or be mistakenly shared
|
||
5. **Fully Recoverable**: Messages can be fully recoverable by any client with the user's private key
|
||
6. **Optional Forward Secrecy**: Users and clients can opt-in for "disappearing messages".
|
||
7. **Uses Public Relays**: Messages can flow through public relays without loss of privacy. Private relays can increase privacy further, but they are not required.
|
||
8. **Cold Storage**: Users can unilaterally opt-in to sharing their messages with a separate key that is exclusive for DM backup and recovery.
|
||
|
||
The main limitation of this approach is having to send a separate encrypted event to each receiver. Group chats with more than 100 participants should find a more suitable messaging scheme.
|
||
|
||
## Implementation
|
||
|
||
Clients implementing this NIP should by default only connect to the set of relays found in their `kind:10050` list. From that they should be able to load all messages both sent and received as well as get new live updates, making it for a very simple and lightweight implementation that should be fast.
|
||
|
||
When sending a message to anyone, clients must then connect to the relays in the receiver's `kind:10050` and send the events there, but can disconnect right after unless more messages are expected to be sent (e.g. the chat tab is still selected). Clients should also send a copy of their outgoing messages to their own `kind:10050` relay set.
|
||
|
||
## Examples
|
||
|
||
This example sends the message `Hola, que tal?` from `nsec1w8udu59ydjvedgs3yv5qccshcj8k05fh3l60k9x57asjrqdpa00qkmr89m` to `nsec12ywtkplvyq5t6twdqwwygavp5lm4fhuang89c943nf2z92eez43szvn4dt`.
|
||
|
||
The two final GiftWraps, one to the receiver and the other to the sender, are:
|
||
|
||
```json
|
||
{
|
||
"id":"2886780f7349afc1344047524540ee716f7bdc1b64191699855662330bf235d8",
|
||
"pubkey":"8f8a7ec43b77d25799281207e1a47f7a654755055788f7482653f9c9661c6d51",
|
||
"created_at":1703128320,
|
||
"kind":1059,
|
||
"tags":[
|
||
[ "p", "918e2da906df4ccd12c8ac672d8335add131a4cf9d27ce42b3bb3625755f0788"]
|
||
],
|
||
"content":"AsqzdlMsG304G8h08bE67dhAR1gFTzTckUUyuvndZ8LrGCvwI4pgC3d6hyAK0Wo9gtkLqSr2rT2RyHlE5wRqbCOlQ8WvJEKwqwIJwT5PO3l2RxvGCHDbd1b1o40ZgIVwwLCfOWJ86I5upXe8K5AgpxYTOM1BD+SbgI5jOMA8tgpRoitJedVSvBZsmwAxXM7o7sbOON4MXHzOqOZpALpS2zgBDXSAaYAsTdEM4qqFeik+zTk3+L6NYuftGidqVluicwSGS2viYWr5OiJ1zrj1ERhYSGLpQnPKrqDaDi7R1KrHGFGyLgkJveY/45y0rv9aVIw9IWF11u53cf2CP7akACel2WvZdl1htEwFu/v9cFXD06fNVZjfx3OssKM/uHPE9XvZttQboAvP5UoK6lv9o3d+0GM4/3zP+yO3C0NExz1ZgFmbGFz703YJzM+zpKCOXaZyzPjADXp8qBBeVc5lmJqiCL4solZpxA1865yPigPAZcc9acSUlg23J1dptFK4n3Tl5HfSHP+oZ/QS/SHWbVFCtq7ZMQSRxLgEitfglTNz9P1CnpMwmW/Y4Gm5zdkv0JrdUVrn2UO9ARdHlPsW5ARgDmzaxnJypkfoHXNfxGGXWRk0sKLbz/ipnaQP/eFJv/ibNuSfqL6E4BnN/tHJSHYEaTQ/PdrA2i9laG3vJti3kAl5Ih87ct0w/tzYfp4SRPhEF1zzue9G/16eJEMzwmhQ5Ec7jJVcVGa4RltqnuF8unUu3iSRTQ+/MNNUkK6Mk+YuaJJs6Fjw6tRHuWi57SdKKv7GGkr0zlBUU2Dyo1MwpAqzsCcCTeQSv+8qt4wLf4uhU9Br7F/L0ZY9bFgh6iLDCdB+4iABXyZwT7Ufn762195hrSHcU4Okt0Zns9EeiBOFxnmpXEslYkYBpXw70GmymQfJlFOfoEp93QKCMS2DAEVeI51dJV1e+6t3pCSsQN69Vg6jUCsm1TMxSs2VX4BRbq562+VffchvW2BB4gMjsvHVUSRl8i5/ZSDlfzSPXcSGALLHBRzy+gn0oXXJ/447VHYZJDL3Ig8+QW5oFMgnWYhuwI5QSLEyflUrfSz+Pdwn/5eyjybXKJftePBD9Q+8NQ8zulU5sqvsMeIx/bBUx0fmOXsS3vjqCXW5IjkmSUV7q54GewZqTQBlcx+90xh/LSUxXex7UwZwRnifvyCbZ+zwNTHNb12chYeNjMV7kAIr3cGQv8vlOMM8ajyaZ5KVy7HpSXQjz4PGT2/nXbL5jKt8Lx0erGXsSsazkdoYDG3U",
|
||
"sig":"a3c6ce632b145c0869423c1afaff4a6d764a9b64dedaf15f170b944ead67227518a72e455567ca1c2a0d187832cecbde7ed478395ec4c95dd3e71749ed66c480"
|
||
}
|
||
```
|
||
|
||
```json
|
||
{
|
||
"id":"162b0611a1911cfcb30f8a5502792b346e535a45658b3a31ae5c178465509721",
|
||
"pubkey":"626be2af274b29ea4816ad672ee452b7cf96bbb4836815a55699ae402183f512",
|
||
"created_at":1702711587,
|
||
"kind":1059,
|
||
"tags":[
|
||
[ "p", "44900586091b284416a0c001f677f9c49f7639a55c3f1e2ec130a8e1a7998e1b"]
|
||
],
|
||
"content":"AsTClTzr0gzXXji7uye5UB6LYrx3HDjWGdkNaBS6BAX9CpHa+Vvtt5oI2xJrmWLen+Fo2NBOFazvl285Gb3HSM82gVycrzx1HUAaQDUG6HI7XBEGqBhQMUNwNMiN2dnilBMFC3Yc8ehCJT/gkbiNKOpwd2rFibMFRMDKai2mq2lBtPJF18oszKOjA+XlOJV8JRbmcAanTbEK5nA/GnG3eGUiUzhiYBoHomj3vztYYxc0QYHOx0WxiHY8dsC6jPsXC7f6k4P+Hv5ZiyTfzvjkSJOckel1lZuE5SfeZ0nduqTlxREGeBJ8amOykgEIKdH2VZBZB+qtOMc7ez9dz4wffGwBDA7912NFS2dPBr6txHNxBUkDZKFbuD5wijvonZDvfWq43tZspO4NutSokZB99uEiRH8NAUdGTiNb25m9JcDhVfdmABqTg5fIwwTwlem5aXIy8b66lmqqz2LBzJtnJDu36bDwkILph3kmvaKPD8qJXmPQ4yGpxIbYSTCohgt2/I0TKJNmqNvSN+IVoUuC7ZOfUV9lOV8Ri0AMfSr2YsdZ9ofV5o82ClZWlWiSWZwy6ypa7CuT1PEGHzywB4CZ5ucpO60Z7hnBQxHLiAQIO/QhiBp1rmrdQZFN6PUEjFDloykoeHe345Yqy9Ke95HIKUCS9yJurD+nZjjgOxZjoFCsB1hQAwINTIS3FbYOibZnQwv8PXvcSOqVZxC9U0+WuagK7IwxzhGZY3vLRrX01oujiRrevB4xbW7Oxi/Agp7CQGlJXCgmRE8Rhm+Vj2s+wc/4VLNZRHDcwtfejogjrjdi8p6nfUyqoQRRPARzRGUnnCbh+LqhigT6gQf3sVilnydMRScEc0/YYNLWnaw9nbyBa7wFBAiGbJwO40k39wj+xT6HTSbSUgFZzopxroO3f/o4+ubx2+IL3fkev22mEN38+dFmYF3zE+hpE7jVxrJpC3EP9PLoFgFPKCuctMnjXmeHoiGs756N5r1Mm1ffZu4H19MSuALJlxQR7VXE/LzxRXDuaB2u9days/6muP6gbGX1ASxbJd/ou8+viHmSC/ioHzNjItVCPaJjDyc6bv+gs1NPCt0qZ69G+JmgHW/PsMMeL4n5bh74g0fJSHqiI9ewEmOG/8bedSREv2XXtKV39STxPweceIOh0k23s3N6+wvuSUAJE7u1LkDo14cobtZ/MCw/QhimYPd1u5HnEJvRhPxz0nVPz0QqL/YQeOkAYk7uzgeb2yPzJ6DBtnTnGDkglekhVzQBFRJdk740LEj6swkJ",
|
||
"sig":"c94e74533b482aa8eeeb54ae72a5303e0b21f62909ca43c8ef06b0357412d6f8a92f96e1a205102753777fd25321a58fba3fb384eee114bd53ce6c06a1c22bab"
|
||
}
|
||
```
|