Trying to describe NIP-59 as primitives and not as a fixed protocol

This commit is contained in:
Vitor Pamplona 2024-03-28 18:23:30 -04:00
parent 3443b3b589
commit a34788e567

184
59.md
View File

@ -6,8 +6,7 @@ Gift Wrap
`optional` `optional`
This NIP defines a protocol for encapsulating any nostr event. This makes it possible to obscure most metadata This NIP defines event kinds and procedures for encapsulating other nostr events with variying degrees of privacy. The goal is to provide tools to obscure the right amount of metadata for separate use cases.
for a given event, perform collaborative signing, and more.
This NIP *does not* define any messaging protocol. Applications of this NIP should be defined separately. This NIP *does not* define any messaging protocol. Applications of this NIP should be defined separately.
@ -15,148 +14,185 @@ This NIP relies on [NIP-44](./44.md)'s versioned encryption algorithms.
# Overview # Overview
This protocol uses three main concepts to protect the transmission of a target event: `rumor`s, `seal`s, and `gift wrap`s. This NIP uses three main primitives to protect the metadata of an event: `rumor`s, `seal`s, and `gift wrap`s.
- A `rumor` is a regular nostr event, but is **not signed**. This means that if it is leaked, it cannot be verified. - A `rumor` is any unsigned nostr event. If it is leaked, it cannot be verified.
- A `rumor` is serialized to JSON, encrypted, and placed in the `content` field of a `seal`. The `seal` is then - A `seal` signs the encrypted rumor in it's `.content`, making the rumor verifiable without revealing it.
signed by the author of the note. The only information publicly available on a `seal` is who signed it, but not what was said. - A `gift wrap` encrypts any other signed event using random private keys to a known destination in its `tags`.
- A `seal` is serialized to JSON, encrypted, and placed in the `content` field of a `gift wrap`.
This allows the isolation of concerns across layers: The rumor carries the content itself but if it leaks it will be rejected by relays and clients and can't be authenticated. This provides a measure of deniability.
- A rumor carries the content but is unsigned, which means if leaked it will be rejected by relays and clients, The `seal` exposes the signer, but not the contents or the receiver. The `gift wrap` exposes the receiver, or an alias to the receiver, but not the signer.
and can't be authenticated. This provides a measure of deniability.
- A seal identifies the author without revealing the content or the recipient.
- A gift wrap can add metadata (recipient, tags, a different author) without revealing the true author.
# Protocol Description The 3 primitives can be used together or separatedly depending on the application.
## 1. The Rumor Event Kind ## The Seal Event Kind
A `rumor` is the same thing as an unsigned event. Any event kind can be made a `rumor` by removing the signature. A `seal` is a `kind:13` event that wraps a `rumor` and is signed with the sender's regular private key. The `seal` is **always** encrypted to a receiver's pubkey but there is no `p` tag to expose who can decrypt it. There is no way to know who the rumor is for without the receiver's or the sender's private key. The only public information in this event is who is signing it.
## 2. The Seal Event Kind
A `seal` is a `kind:13` event that wraps a `rumor` with the sender's regular key. The `seal` is **always** encrypted
to a receiver's pubkey but there is no `p` tag pointing to the receiver. There is no way to know who the rumor is for
without the receiver's or the sender's private key. The only public information in this event is who is signing it.
```js ```js
{ {
"id": "<id>", "id": "<id>",
"pubkey": "<real author's pubkey>", "pubkey": "<real author's pubkey>",
"content": "<encrypted rumor>", "created_at": now() or randomTimeUpTo2DaysInThePast(),
"kind": 13, "kind": 13,
"created_at": 1686840217,
"tags": [], "tags": [],
"content": nip44Encrypt(rumor, "<recipient pubkey>", "<author's private key>"),
"sig": "<real author's pubkey signature>" "sig": "<real author's pubkey signature>"
} }
``` ```
Tags MUST must always be empty in a `kind:13`. The inner event MUST always be unsigned. `.content` contains the cyphertext of the [NIP-44](44.md) encryption of the JSON-encoded rumor.
## 3. Gift Wrap Event Kind Tags MUST must always be empty.
A `gift wrap` event is a `kind:1059` event that wraps any other event. `tags` SHOULD include any information The inner event MUST always be unsigned.
needed to route the event to its intended recipient, including the recipient's `p` tag or [NIP-13](13.md) proof of work.
To increase privacy, `.created_at` MAY not be accurate.
## The Gift Wrap Event Kind
A `gift wrap` event is a `kind:1059` event that wraps any other event. `tags` SHOULD include any information needed to route the inner event to its intended recipient, including the recipient's `p` tag or [NIP-13](13.md) proof of work.
```js ```js
{ {
"id": "<id>", "id": "<id>",
"pubkey": "<random, one-time-use pubkey>", "pubkey": "<random, one-time-use pubkey>",
"content": "<encrypted kind 13>", "created_at": now() or randomTimeUpTo2DaysInThePast(),
"kind": 1059, "kind": 1059,
"created_at": 1686840217, "tags": [["p", "<recipient pubkey>", "<relay>"]],
"tags": [["p", "<recipient pubkey>"]], "content": nip44Encrypt(event, "<recipient pubkey>", "<random private key>"),
"sig": "<random, one-time-use pubkey signature>" "sig": "<random, one-time-use pubkey signature>"
} }
``` ```
# Encrypting Payloads `.content` contains the cyphertext of the [NIP-44](44.md) encryption of the JSON-encoded event.
Encryption is done following [NIP-44](44.md) on the JSON-encoded event. Place the encryption payload in the `.content` To increase privacy, `.created_at` MAY not be accurate.
of the wrapper event (either a `seal` or a `gift wrap`).
# Other Considerations Relays SHOULD only serve `kind 1059` events intended for the marked recipient based on user AUTH.
If a `rumor` is intended for more than one party, or if the author wants to retain an encrypted copy, a single Clients SHOULD only send wrapped events to destination relays that offer this protection.
`rumor` may be wrapped and addressed for each recipient individually.
The canonical `created_at` time belongs to the `rumor`. All other timestamps SHOULD be tweaked to thwart Relays MAY choose not to store Gift-wrapped events due to them not being publicly useful. Clients MAY choose
time-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps
SHOULD be in the past.
Relays may choose not to store gift wrapped events due to them not being publicly useful. Clients MAY choose
to attach a certain amount of proof-of-work to the wrapper event per [NIP-13](13.md) in a bid to demonstrate that to attach a certain amount of proof-of-work to the wrapper event per [NIP-13](13.md) in a bid to demonstrate that
the event is not spam or a denial-of-service attack. the event is not spam or a denial-of-service attack.
To protect recipient metadata, relays SHOULD guard access to `kind 1059` events based on user AUTH. When # Most Private Protocol
possible, clients should only send wrapped events to relays that offer this protection.
To protect recipient metadata, relays SHOULD only serve `kind 1059` events intended for the marked recipient. In cases of elevated privacy needs, the 3 primitives can be used together, one after another. In this scheme, even if all of the decrypted payloads leak, the sender and receiver are not publicly connected in a verifiable event and none of the inner event's information can be publicly verified.
When possible, clients should only send wrapped events to `read` relays for the recipient that implement
AUTH, and refuse to serve wrapped events to non-recipients.
# An Example To transfer an event from pubkey A to pubkey B, perform the following steps:
1. Turn an event into a Rumor by deleting the signature.
2. Seal the Rumor
3. Gift Wrap the Seal
4. Send the Gift Wrap to the relays of the receiver.
Let's send a wrapped `kind 1` message between two parties asking "Are you going to the party tonight?" In pseudo-code:
- Author private key: `0beebd062ec8735f4243466049d7747ef5d6594ee838de147f8aab842b15e273` ```js
{
"id": "<id>",
"pubkey": "<random-pubkey>",
"created_at": randomTimeUpTo2DaysInThePast()
"kind": 1059,
"tags": [
["p", "<pubkey-B>"],
],
"content": nip44Encrypt({
"id": "<id>",
"pubkey" "<pubkey-A>",
"created_at": "created_at": randomTimeUpTo2DaysInThePast()
"kind": 13, // seal
"tags": [],
"content": nip44Encrypt(rumor, pubkeyB, privkeyA),
"sig": "<signed by pubkey-A>"
}, pubkeyB, privkeyRandom)
"sig": "<random, one-time-use pubkey signature>"
}
```
If the `rumor` is intended for more than one party, or if the author wants to retain an encrypted copy, a single `rumor` may be sealed, wrapped and addressed for each recipient and the author individually.
The canonical `created_at` time belongs to the `rumor`. All other timestamps SHOULD be tweaked to thwart time-analysis attacks. Note that some relays don't serve events dated in the future, so all timestamps SHOULD be in the past.
# Gift Wrap as an onion routing payload
Gift wraps can be used separetedly to hide any signed event from the public and routing relays/servers.
For instance, to send an event through a push notification stack without the stack knowing about the event or who is it for, simply wrap it on a `kind 1059` event. Since the stack already has a token, the `p` tag to the receiver (which would reveal who the receiver is) is not needed.
```js
{
"id": "<id>",
"pubkey": "<random-pubkey>",
"created_at": randomTimeUpTo2DaysInThePast()
"kind": 1059,
"tags": [
["token", "<destination push notification token>"],
],
"content": nip44Encrypt(notificationEvent, pubkeyB, privkeyRandom)
"sig": "<random, one-time-use pubkey signature>"
}
```
---
# A Walkthrough Example
Let's send a private `kind 1` note between two parties asking "Are you going to the party tonight?"
- Sender private key: `0beebd062ec8735f4243466049d7747ef5d6594ee838de147f8aab842b15e273`
- Recipient private key: `e108399bd8424357a710b606ae0c13166d853d327e47a6e5e038197346bdbf45` - Recipient private key: `e108399bd8424357a710b606ae0c13166d853d327e47a6e5e038197346bdbf45`
- Ephemeral wrapper key: `4f02eac59266002db5801adc5270700ca69d5b8f761d8732fab2fbf233c90cbd` - Ephemeral wrapper key: `4f02eac59266002db5801adc5270700ca69d5b8f761d8732fab2fbf233c90cbd`
Note that this messaging protocol should not be used in practice, this is just an example. Refer to other ## 1. Create a rumor
NIPs for concrete messaging protocols that depend on gift wraps.
## 1. Create an event Create a `kind 1` event with the message, the receivers, and any other tags you want.
Create a `kind 1` event with the message, the receivers, and any other tags you want, signed by the author.
Do not sign the event. Do not sign the event.
```json ```json
{ {
"created_at": 1691518405, "id": "9dd003c6d3b73b74a85a9ab099469ce251653a7af76f523671ab828acd2a0ef9",
"content": "Are you going to the party tonight?",
"tags": [],
"kind": 1,
"pubkey": "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9", "pubkey": "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9",
"id": "9dd003c6d3b73b74a85a9ab099469ce251653a7af76f523671ab828acd2a0ef9" "created_at": 1691518405,
"kind": 1,
"tags": [],
"content": "Are you going to the party tonight?",
"sig": ""
} }
``` ```
## 2. Seal the rumor ## 2. Seal the rumor
Encrypt the JSON-encoded `rumor` with a conversation key derived using the author's private key and Encrypt the JSON-encoded `rumor` with a conversation key derived using the sender's private key and the recipient's public key. Place the result in the `.content` field of a `kind 13` `seal` event. Sign it with the sender's key.
the recipient's public key. Place the result in the `content` field of a `kind 13` `seal` event. Sign
it with the author's key.
```json ```json
{ {
"content": "AqBCdwoS7/tPK+QGkPCadJTn8FxGkd24iApo3BR9/M0uw6n4RFAFSPAKKMgkzVMoRyR3ZS/aqATDFvoZJOkE9cPG/TAzmyZvr/WUIS8kLmuI1dCA+itFF6+ULZqbkWS0YcVU0j6UDvMBvVlGTzHz+UHzWYJLUq2LnlynJtFap5k8560+tBGtxi9Gx2NIycKgbOUv0gEqhfVzAwvg1IhTltfSwOeZXvDvd40rozONRxwq8hjKy+4DbfrO0iRtlT7G/eVEO9aJJnqagomFSkqCscttf/o6VeT2+A9JhcSxLmjcKFG3FEK3Try/WkarJa1jM3lMRQqVOZrzHAaLFW/5sXano6DqqC5ERD6CcVVsrny0tYN4iHHB8BHJ9zvjff0NjLGG/v5Wsy31+BwZA8cUlfAZ0f5EYRo9/vKSd8TV0wRb9DQ=",
"kind": 13,
"created_at": 1703015180,
"pubkey": "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9",
"tags": [],
"id": "28a87d7c074d94a58e9e89bb3e9e4e813e2189f285d797b1c56069d36f59eaa7", "id": "28a87d7c074d94a58e9e89bb3e9e4e813e2189f285d797b1c56069d36f59eaa7",
"pubkey": "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9",
"created_at": 1703015180,
"kind": 13,
"tags": [],
"content": "AqBCdwoS7/tPK+QGkPCadJTn8FxGkd24iApo3BR9/M0uw6n4RFAFSPAKKMgkzVMoRyR3ZS/aqATDFvoZJOkE9cPG/TAzmyZvr/WUIS8kLmuI1dCA+itFF6+ULZqbkWS0YcVU0j6UDvMBvVlGTzHz+UHzWYJLUq2LnlynJtFap5k8560+tBGtxi9Gx2NIycKgbOUv0gEqhfVzAwvg1IhTltfSwOeZXvDvd40rozONRxwq8hjKy+4DbfrO0iRtlT7G/eVEO9aJJnqagomFSkqCscttf/o6VeT2+A9JhcSxLmjcKFG3FEK3Try/WkarJa1jM3lMRQqVOZrzHAaLFW/5sXano6DqqC5ERD6CcVVsrny0tYN4iHHB8BHJ9zvjff0NjLGG/v5Wsy31+BwZA8cUlfAZ0f5EYRo9/vKSd8TV0wRb9DQ=",
"sig": "02fc3facf6621196c32912b1ef53bac8f8bfe9db51c0e7102c073103586b0d29c3f39bdaa1e62856c20e90b6c7cc5dc34ca8bb6a528872cf6e65e6284519ad73" "sig": "02fc3facf6621196c32912b1ef53bac8f8bfe9db51c0e7102c073103586b0d29c3f39bdaa1e62856c20e90b6c7cc5dc34ca8bb6a528872cf6e65e6284519ad73"
} }
``` ```
## 3. Wrap the seal ## 3. Wrap the seal
Encrypt the JSON-encoded `kind 13` event with your ephemeral, single-use random key. Place the result Encrypt the JSON-encoded `kind 13` event with your ephemeral, single-use random key. Place the result in the `content` field of a `kind 1059`. Add a single `p` tag containing the recipient's public key. Sign the `gift wrap` using the random key generated in the previous step.
in the `content` field of a `kind 1059`. Add a single `p` tag containing the recipient's public key.
Sign the `gift wrap` using the random key generated in the previous step.
```json ```json
{ {
"content": "AhC3Qj/QsKJFWuf6xroiYip+2yK95qPwJjVvFujhzSguJWb/6TlPpBW0CGFwfufCs2Zyb0JeuLmZhNlnqecAAalC4ZCugB+I9ViA5pxLyFfQjs1lcE6KdX3euCHBLAnE9GL/+IzdV9vZnfJH6atVjvBkNPNzxU+OLCHO/DAPmzmMVx0SR63frRTCz6Cuth40D+VzluKu1/Fg2Q1LSst65DE7o2efTtZ4Z9j15rQAOZfE9jwMCQZt27rBBK3yVwqVEriFpg2mHXc1DDwHhDADO8eiyOTWF1ghDds/DxhMcjkIi/o+FS3gG1dG7gJHu3KkGK5UXpmgyFKt+421m5o++RMD/BylS3iazS1S93IzTLeGfMCk+7IKxuSCO06k1+DaasJJe8RE4/rmismUvwrHu/HDutZWkvOAhd4z4khZo7bJLtiCzZCZ74lZcjOB4CYtuAX2ZGpc4I1iOKkvwTuQy9BWYpkzGg3ZoSWRD6ty7U+KN+fTTmIS4CelhBTT15QVqD02JxfLF7nA6sg3UlYgtiGw61oH68lSbx16P3vwSeQQpEB5JbhofW7t9TLZIbIW/ODnI4hpwj8didtk7IMBI3Ra3uUP7ya6vptkd9TwQkd/7cOFaSJmU+BIsLpOXbirJACMn+URoDXhuEtiO6xirNtrPN8jYqpwvMUm5lMMVzGT3kMMVNBqgbj8Ln8VmqouK0DR+gRyNb8fHT0BFPwsHxDskFk5yhe5c/2VUUoKCGe0kfCcX/EsHbJLUUtlHXmTqaOJpmQnW1tZ/siPwKRl6oEsIJWTUYxPQmrM2fUpYZCuAo/29lTLHiHMlTbarFOd6J/ybIbICy2gRRH/LFSryty3Cnf6aae+A9uizFBUdCwTwffc3vCBae802+R92OL78bbqHKPbSZOXNC+6ybqziezwG+OPWHx1Qk39RYaF0aFsM4uZWrFic97WwVrH5i+/Nsf/OtwWiuH0gV/SqvN1hnkxCTF/+XNn/laWKmS3e7wFzBsG8+qwqwmO9aVbDVMhOmeUXRMkxcj4QreQkHxLkCx97euZpC7xhvYnCHarHTDeD6nVK+xzbPNtzeGzNpYoiMqxZ9bBJwMaHnEoI944Vxoodf51cMIIwpTmmRvAzI1QgrfnOLOUS7uUjQ/IZ1Qa3lY08Nqm9MAGxZ2Ou6R0/Z5z30ha/Q71q6meAs3uHQcpSuRaQeV29IASmye2A2Nif+lmbhV7w8hjFYoaLCRsdchiVyNjOEM4VmxUhX4VEvw6KoCAZ/XvO2eBF/SyNU3Of4SO",
"kind": 1059,
"created_at": 1703021488,
"pubkey": "18b1a75918f1f2c90c23da616bce317d36e348bcf5f7ba55e75949319210c87c",
"id": "5c005f3ccf01950aa8d131203248544fb1e41a0d698e846bd419cec3890903ac", "id": "5c005f3ccf01950aa8d131203248544fb1e41a0d698e846bd419cec3890903ac",
"sig": "35fabdae4634eb630880a1896a886e40fd6ea8a60958e30b89b33a93e6235df750097b04f9e13053764251b8bc5dd7e8e0794a3426a90b6bcc7e5ff660f54259" "pubkey": "18b1a75918f1f2c90c23da616bce317d36e348bcf5f7ba55e75949319210c87c",
"created_at": 1703021488,
"kind": 1059,
"tags": [["p", "166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99"]], "tags": [["p", "166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99"]],
"content": "AhC3Qj/QsKJFWuf6xroiYip+2yK95qPwJjVvFujhzSguJWb/6TlPpBW0CGFwfufCs2Zyb0JeuLmZhNlnqecAAalC4ZCugB+I9ViA5pxLyFfQjs1lcE6KdX3euCHBLAnE9GL/+IzdV9vZnfJH6atVjvBkNPNzxU+OLCHO/DAPmzmMVx0SR63frRTCz6Cuth40D+VzluKu1/Fg2Q1LSst65DE7o2efTtZ4Z9j15rQAOZfE9jwMCQZt27rBBK3yVwqVEriFpg2mHXc1DDwHhDADO8eiyOTWF1ghDds/DxhMcjkIi/o+FS3gG1dG7gJHu3KkGK5UXpmgyFKt+421m5o++RMD/BylS3iazS1S93IzTLeGfMCk+7IKxuSCO06k1+DaasJJe8RE4/rmismUvwrHu/HDutZWkvOAhd4z4khZo7bJLtiCzZCZ74lZcjOB4CYtuAX2ZGpc4I1iOKkvwTuQy9BWYpkzGg3ZoSWRD6ty7U+KN+fTTmIS4CelhBTT15QVqD02JxfLF7nA6sg3UlYgtiGw61oH68lSbx16P3vwSeQQpEB5JbhofW7t9TLZIbIW/ODnI4hpwj8didtk7IMBI3Ra3uUP7ya6vptkd9TwQkd/7cOFaSJmU+BIsLpOXbirJACMn+URoDXhuEtiO6xirNtrPN8jYqpwvMUm5lMMVzGT3kMMVNBqgbj8Ln8VmqouK0DR+gRyNb8fHT0BFPwsHxDskFk5yhe5c/2VUUoKCGe0kfCcX/EsHbJLUUtlHXmTqaOJpmQnW1tZ/siPwKRl6oEsIJWTUYxPQmrM2fUpYZCuAo/29lTLHiHMlTbarFOd6J/ybIbICy2gRRH/LFSryty3Cnf6aae+A9uizFBUdCwTwffc3vCBae802+R92OL78bbqHKPbSZOXNC+6ybqziezwG+OPWHx1Qk39RYaF0aFsM4uZWrFic97WwVrH5i+/Nsf/OtwWiuH0gV/SqvN1hnkxCTF/+XNn/laWKmS3e7wFzBsG8+qwqwmO9aVbDVMhOmeUXRMkxcj4QreQkHxLkCx97euZpC7xhvYnCHarHTDeD6nVK+xzbPNtzeGzNpYoiMqxZ9bBJwMaHnEoI944Vxoodf51cMIIwpTmmRvAzI1QgrfnOLOUS7uUjQ/IZ1Qa3lY08Nqm9MAGxZ2Ou6R0/Z5z30ha/Q71q6meAs3uHQcpSuRaQeV29IASmye2A2Nif+lmbhV7w8hjFYoaLCRsdchiVyNjOEM4VmxUhX4VEvw6KoCAZ/XvO2eBF/SyNU3Of4SO",
"sig": "35fabdae4634eb630880a1896a886e40fd6ea8a60958e30b89b33a93e6235df750097b04f9e13053764251b8bc5dd7e8e0794a3426a90b6bcc7e5ff660f54259"
} }
``` ```