Skip to content
Closed
Changes from 5 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2acbd64
Add Private DM
arthurfranca Jan 9, 2024
13177b2
Add info about where to publish events
arthurfranca Jan 9, 2024
43fe354
Add chat session confirmation event
arthurfranca Jan 9, 2024
49d1f73
Change wording from confirm to accept
arthurfranca Jan 9, 2024
d8e8ec1
Minor update
arthurfranca Jan 10, 2024
dc7ac14
Change dm status kind numbers
arthurfranca Jan 17, 2024
7e9083d
Rename session-privkey-A to session-privkey
arthurfranca Jan 17, 2024
d1f2199
Explain conversation keys generation
arthurfranca Jan 17, 2024
d5baa6e
Use descending order when resolving session conflict
arthurfranca Jan 18, 2024
f9921e6
Fix sealing arg when encrypting
arthurfranca Jan 29, 2024
ae1e3a6
Move the session acceptance rumor to the session channel
arthurfranca Jan 29, 2024
84dff71
Change from salt suffix to full salt
arthurfranca Feb 4, 2024
e732e56
Add AUTH suggestion
arthurfranca Feb 5, 2024
1ed44ba
Add summary flow
arthurfranca Feb 6, 2024
1d421b4
Minor fix
arthurfranca Feb 6, 2024
f50c650
Remove min PoW requirement
arthurfranca Feb 6, 2024
46ad13c
Add rate limit
arthurfranca Feb 6, 2024
1415d76
Expirement using private kind range
arthurfranca Feb 14, 2024
6ddce9f
Move rumor kinds to numbers no one is using
arthurfranca Feb 20, 2024
dbaa40d
Ditch private event kinds
arthurfranca Mar 8, 2024
f86bb32
Add closing rumor
arthurfranca Mar 19, 2024
40342cc
Detect when new device is used
arthurfranca Mar 20, 2024
2255a5c
Use lid_proof tag on LID Tracker event
arthurfranca Mar 22, 2024
995ee77
Remove PoW
arthurfranca Apr 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions 43.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
NIP-43
======

Private DM
----------

`draft` `optional`

This direct message (DM) scheme between two participants is end-to-end encrypted, hides
most metadata, has forward secrecy and aims to minimize spam.

This NIP currently uses [NIP-44](44.md) v2 encryption.

## Flow

- Before sending a DM to someone (`B`), an user (`A`) picks a random privkey to be used as chat session (`session-privkey-A`);
- `A` signs DMs with `session-privkey-A` and publishes them to `A` and `B` [NIP-65](65.md) `read` relays;
- Meanwhile, `A` sends `session-privkey-A` to `B` through B `read` relays, representing the chat session;
- `A` also stores `session-privkey-A` to themselves linking it to `B` pubkey (representing `A-B` chat session);
- Upon receiving `session-privkey-A`, `B` stores the key linking it to `A` pubkey (also representing `A-B` chat session) and sends a chat session acceptance to `A`. Otherwise `B` ignores the key.
- `B` can fetch DMs from that session's pubkey, filtering by `author`;
- `B` can send back DMs by signing them with the same `session-privkey-A`.

Note that:
- `A` can resend the same session key anytime they want while there is no acceptance from `B`, to make sure `B` didn't miss it;
- `B` could had sent a session to `A` at the same time.
In that case, the most recent of the two sessions should be used by both participants. They may also check a single
time for DMs already sent from the older session before discarding it. If the `.created_at` of both sessions are exactly the same,
the receiving user should start a new chat session;

Important:
- By convention, after three weeks the session expires.
Either `A` or `B` will need to restart the flow with a new random privkey as session
when sending new DMs.

## Chat Session

The "Chat Session Envelope" is a [NIP-59](59.md) gift wrap event but with a specific kind: `1043`.

It MUST include [NIP-13](13.md) PoW with atleast 16 difficulty.
Receiving client MUST ignore it if below 16.
Relays SHOULD block it if below 16.
Relays SHOULD require increasing PoW difficulty the more `kind:1043` events are sent to the same pubkey in a short time.
Relays SHOULD block it if of byte size higher than 3KB.

It has a `p` tag set to the recipient's pubkey.

The NIP-59 rumor is a `kind:1044` "Chat Session Request" event.
It's `.content` is the session's privkey.

By convention, the session expires 3 weeks after the rumor's `.created_at` value.

The "Chat Session Envelope" event should be sent to atleast one of the recipient's NIP-65 `read` relays.

Event example:

```js
{
"kind": 1043,
"pubkey": "<random-pubkey>",
"tags": [
["p", "<pubkey-B>"],
["nonce", "65962", "16"] // atleast 16 PoW difficulty
],
"content": "<nip44EncryptV2(JSON.stringify({
"kind": 13, // seal
"pubkey" "<pubkey-A>",
"tags": [],
"content": "<nip44EncryptV2(JSON.stringify({
"id": "<...>",
"kind": 1044,
"pubkey" "<pubkey-A>",
"tags": [],
"content": "<session-privkey-A>",
"created_at": 1702711000 // now
// no .sig field
}))>",
"created_at": 0
// ....other fields
}))>",
"created_at": 1702711000 // now
// ...other fields
}
```

### Chat Session Acceptance

It uses the same `kind:1043` wrapper.

It has a `kind:1045` "Chat Session Acceptance" rumor.

It's `.content` is set to the received "Chat Session"'s content which is enough to
accept the session identified by the `.pubkey` and the `.content` (session privkey).

## Chat Session List

The [NIP-51](51.md) `kind:10043` is the user's "Chat Session List" event.

It encrypts `s` (session) tags.

An `s` tag's 1st value is the `pubkey` of the other side of the chat.
The 2nd value is the session's `privkey`.
The 3rd value is the expiration timestamp in seconds and is three weeks ahead
of the `kind:1044`'s `.created_at` value.
The 4th value if present is set to "1", meaning the other chat participant accepted the chat session.

`s` tags of expired sessions should be kept to indicate active chats.

The user should publish the event to all of their own NIP-65 `write` relays.

Event example:

```js
{
"kind": 10043,
"pubkey": "<pubkey-B>"
"content": "<nip44EncryptV2(JSON.stringify([
["s", "<pubkey-A>", "<session-privkey-A>", "1704525400", "1"],
// other "s" tags
]))>",
// ...other fields
}
```

## Session Channel

The session channel is simply made of the set of NIP-59 `kind:1059` events
with the session pubkey as their author.

Either the sender or the recipient may delete these events because both of them know
the session's privkey. Therefore, clients should store them locally to keep their users in control
of the DMs lifecycle.

**Important**: The NIP-44 encryption when wrapping and when sealing session channel events
uses the first 23 characters of the session's `privkey` as salt suffix.
This ensures forward secrecy when creating a new session because both users
are supposed to store just the last session's `privkey`
in the "Chat Session List" event.

### DM

The DM is a `kind:14` NIP-59 rumor.

This event should be sent to atleast one of the recipient's NIP-65 `read` relays
and to atleast one of the sender's `read` relays.
This way, an user will only have to fetch DMs from their own set of `read` relays.

DM event example:

```js
{
"kind": 1059, // gift wrap
"pubkey": "<session-pubkey-A>",
"tags": [],
"content": "<nip44EncryptV2(JSON.stringify({
"kind": 13, // seal
"pubkey" "<pubkey-A>",
"tags": [],
"content": "<nip44EncryptV2(JSON.stringify({
"id": "<...>",
"kind": 14, // DM
"pubkey" "<pubkey-A>",
"tags": [],
"content": "Hello, User B",
"created_at": 1702711000 // now
// no .sig field
}))>",
"created_at": 0
// ....other fields
}))>",
"created_at": 1702711000 // now
// ...other fields
}
```

### Last Received At

Optionally, the `created_at` of a `kind:15` NIP-59 rumor
informs the last time an user's client received DMs from the other participant.

This event should be sent to atleast one of the recipient's NIP-65 `read` relays.

### Last Read At

Optionally, the `created_at` of a `kind:16` NIP-59 rumor
informs the last time an user read DMs from the other participant.

This event should be sent to atleast one of the recipient's NIP-65 `read` relays.