Skip to content
Open
Changes from all commits
Commits
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
23 changes: 21 additions & 2 deletions 17.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,25 @@ The set of `pubkey` + `p` tags defines a chat room. If a new `p` tag is added or

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 chat room is the subject of the conversation.

## Conversation Wrapper Profile

To allow receivers to query chat messages from relays without decrypting each event, the gift wrap is signed by a deterministic keypair called a "conversation wrapper profile". Both parties can derive this independently using the same HKDF function they use for [NIP-44](44.md), but with a different salt:

```
shared_x = secp256k1_ecdh(private_key_a, public_key_b)
wrapper_private_key = hkdf_extract(
IKM = shared_x,
salt = utf8_encode('nip17-w-' + epoch + '-' + counter)
)
```

- `epoch = math.floor(now() / 1_000_000).string()` (~12 days per epoch). The `shared_x` is the same ECDH shared secret used in [NIP-44](44.md) conversation key derivation.
- `counter` is just the string `"1"` for the vast majority of situations (such that it can almost be safely hardcoded to `"1"`), but in the very rare case in which it doesn't yield a valid secret key, both parties will increment it to `"2"` and so on until it does.

The wrapper public key is the `pubkey` of the gift wrap event, which means a specific conversation can be queried directly with `{authors: [wrapper_pubkey]}`.

Since the key changes each epoch, it provides some level of privacy against an observer in case these gift-wraps leak, as the observer won't be able to link conversations across epochs.

## Encrypting

Following [NIP-59](59.md), the **unsigned** chat messages must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually.
Expand Down Expand Up @@ -46,13 +65,13 @@ Following [NIP-59](59.md), the **unsigned** chat messages must be sealed (`kind:
},
nip44.compute_conversation_key(wrapperPrivateKey, receiverPublicKey)
),
"sig": "<signed by randomPrivateKey>"
"sig": "<signed by wrapperPrivateKey>"
}
```

`unsignedMessageRumor` is a rumor (an unsigned event, as per [NIP-59](59.md)), usually a `kind:14`, but could also be a different kind, see [Message Rumor Definitions](#message-rumor-definitions) below.

`wrapperPrivateKey` and `wrappedPublicKey` are a new keypair, generated randomly anew for each message sent.
`wrapperPrivateKey` and `wrapperPublicKey` form the conversation wrapper profile keypair, derived deterministically from the shared secret as described in the [Conversation Wrapper Profile](#conversation-wrapper-profile) section.

Clients MUST verify if pubkey of the `kind:13` is the same pubkey as that of the `unsignedMessageRumor`, otherwise any sender can impersonate any other by simply changing the pubkey on the rumor.

Expand Down