decoupling identity and encryption keys in NIP-17.

This commit is contained in:
fiatjaf
2026-06-01 11:15:47 -03:00
parent e0f779d13f
commit a596fadeaf
+29 -9
View File
@@ -25,39 +25,60 @@ Following [NIP-59](59.md), the **unsigned** chat messages must be sealed (`kind:
```js
{
"id": "<usual hash>",
"pubkey": wrapperPublicKey,
"pubkey": wrapperKey.public,
"created_at": randomTimeUpTo2DaysInThePast,
"kind": 1059, // gift wrap
"tags": [
["p", receiverPublicKey, "<relay-url>"] // receiver
["p", receiverIdentityKey.public, "<relay-url>"] // receiver
],
"content": nip44.encrypt(
{
"id": "<usual hash>",
"pubkey": senderPublicKey,
"pubkey": senderIdentityKey.public,
"created_at": randomTimeUpTo2DaysInThePast,
"kind": 13, // seal
"tags": [], // no tags
"content": nip44.encrypt(
unsignedMessageRumor,
nip44.compute_conversation_key(senderPrivateKey, receiverPublicKey)
nip44.compute_conversation_key(senderEncryptionKey.private, receiverEncryptionKey.public)
),
"sig": "<signed by senderPrivateKey>"
"sig": "<signed by senderIdentity.private>"
},
nip44.compute_conversation_key(wrapperPrivateKey, receiverPublicKey)
nip44.compute_conversation_key(wrapperKey.private, receiverEncryptionKey.public)
),
"sig": "<signed by randomPrivateKey>"
"sig": "<signed by wrapperKey.private>"
}
```
`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.
`wrapperKey` is a new keypair, generated randomly anew for each message sent.
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.
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.
### Encryption Keys
There is a difference between the keypairs used in `nip44.compute_conversation_key()` ("encryption key") and those used to sign the seal event ("identity key").
By default, these keypairs are the same, i.e. `receiverEncryptionKey == receiverIdentityKey` and `senderIdentityKey == senderEncryptionKey`, but when an event `kind:10044` is present that changes things:
```js
{
"kind": 10044,
"pubkey": receiverIdentityKey.public,
"tags": [
["n", receiverEncryptionKey.public]
],
// other fields...
}
```
The existence of this event shows that the receiver wants to receive messages encrypted to a different key, so the sender should use that when calling `nip44.compute_conversation_key()` (both during encryption and decryption).
When sending, `p`-tagging the receiver should still use their _identity key_; and when receiving messages the receiver should still assume the `"author"` field of the seal event contains the _identity 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.
@@ -69,7 +90,6 @@ Kind `10050` indicates the user's preferred relays to receive DMs. The event MUS
["relay", "wss://inbox.nostr.wine"],
["relay", "wss://myrelay.nostr1.com"],
],
"content": "",
// other fields...
}
```