Compare commits
23 Commits
cf4f299f7c
...
894022ba76
Author | SHA1 | Date |
---|---|---|
|
894022ba76 | |
|
b516adbf42 | |
|
90fcf4a44e | |
|
abe6fb959c | |
|
4b19bf2e40 | |
|
7dfb3b35d8 | |
|
400d975da3 | |
|
e35a1bebbc | |
|
8b541fe8cd | |
|
c3f92ca577 | |
|
020609ed9f | |
|
5d81c9100e | |
|
3a126a51a6 | |
|
4c5d5fff99 | |
|
d6fe55a6ad | |
|
7c4a2cb829 | |
|
3760a6e308 | |
|
8c45ff5d96 | |
|
fd9c627b36 | |
|
fe114c6473 | |
|
581452e845 | |
|
146148fb20 | |
|
6ce759338f |
6
11.md
6
11.md
|
@ -20,11 +20,9 @@ When a relay receives an HTTP(s) request with an `Accept` header of `application
|
||||||
"contact": <administrative alternate contact>,
|
"contact": <administrative alternate contact>,
|
||||||
"supported_nips": <a list of NIP numbers supported by the relay>,
|
"supported_nips": <a list of NIP numbers supported by the relay>,
|
||||||
"software": <string identifying relay software URL>,
|
"software": <string identifying relay software URL>,
|
||||||
"version": <string version identifier>
|
"version": <string version identifier>,
|
||||||
"privacy_policy": <a link to a text file describing the relay's privacy policy>,
|
"privacy_policy": <a link to a text file describing the relay's privacy policy>,
|
||||||
"terms_of_service": <a link to a text file describing the relay's term of service>,
|
"terms_of_service": <a link to a text file describing the relay's term of service>,
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -162,7 +160,7 @@ a specific niche kind or content. Normal anti-spam heuristics, for example, do n
|
||||||
|
|
||||||
- `created_at_upper_limit`: 'created_at' upper limit
|
- `created_at_upper_limit`: 'created_at' upper limit
|
||||||
|
|
||||||
- `default_limit`: The maximum returned events if you send a filter with the limit set to 0.
|
- `default_limit`: The maximum returned events if you send a filter without a `limit`.
|
||||||
|
|
||||||
### Event Retention
|
### Event Retention
|
||||||
|
|
||||||
|
|
4
42.md
4
42.md
|
@ -32,6 +32,8 @@ And, when sent by clients, the following form:
|
||||||
["AUTH", <signed-event-json>]
|
["AUTH", <signed-event-json>]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Clients MAY provide signed events from multiple pubkeys in a sequence of `AUTH` messages. Relays MUST treat all pubkeys as authenticated accordingly.
|
||||||
|
|
||||||
`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message.
|
`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message.
|
||||||
|
|
||||||
### Canonical authentication event
|
### Canonical authentication event
|
||||||
|
@ -69,7 +71,9 @@ relay: ["AUTH", "<challenge>"]
|
||||||
client: ["REQ", "sub_1", {"kinds": [4]}]
|
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||||
relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"]
|
relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"]
|
||||||
client: ["AUTH", {"id": "abcdef...", ...}]
|
client: ["AUTH", {"id": "abcdef...", ...}]
|
||||||
|
client: ["AUTH", {"id": "abcde2...", ...}]
|
||||||
relay: ["OK", "abcdef...", true, ""]
|
relay: ["OK", "abcdef...", true, ""]
|
||||||
|
relay: ["OK", "abcde2...", true, ""]
|
||||||
client: ["REQ", "sub_1", {"kinds": [4]}]
|
client: ["REQ", "sub_1", {"kinds": [4]}]
|
||||||
relay: ["EVENT", "sub_1", {...}]
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
relay: ["EVENT", "sub_1", {...}]
|
relay: ["EVENT", "sub_1", {...}]
|
||||||
|
|
48
47.md
48
47.md
|
@ -188,6 +188,7 @@ Request:
|
||||||
"params": {
|
"params": {
|
||||||
"invoice": "lnbc50n1...", // bolt11 invoice
|
"invoice": "lnbc50n1...", // bolt11 invoice
|
||||||
"amount": 123, // invoice amount in msats, optional
|
"amount": 123, // invoice amount in msats, optional
|
||||||
|
"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -217,7 +218,7 @@ Request:
|
||||||
"params": {
|
"params": {
|
||||||
"invoices": [
|
"invoices": [
|
||||||
{"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional
|
{"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional
|
||||||
{"id":"3da52c32a1", "invoice": "lnbc50n1..."},
|
{"id":"3da52c32a1", "invoice": "lnbc50n1...", "metadata": {} }, // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -324,7 +325,8 @@ Request:
|
||||||
"amount": 123, // value in msats
|
"amount": 123, // value in msats
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
"expiry": 213 // expiry in seconds from time invoice is created, optional
|
"expiry": 213, // expiry in seconds from time invoice is created, optional
|
||||||
|
"metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -335,7 +337,7 @@ Response:
|
||||||
"result_type": "make_invoice",
|
"result_type": "make_invoice",
|
||||||
"result": {
|
"result": {
|
||||||
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
||||||
"state": "pending",
|
"state": "pending", // optional
|
||||||
"invoice": "string", // encoded invoice, optional
|
"invoice": "string", // encoded invoice, optional
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
|
@ -369,7 +371,7 @@ Response:
|
||||||
"result_type": "lookup_invoice",
|
"result_type": "lookup_invoice",
|
||||||
"result": {
|
"result": {
|
||||||
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
||||||
"state": "pending", // can be "pending", "settled", "expired" (for invoices) or "failed" (for payments)
|
"state": "pending", // can be "pending", "settled", "expired" (for invoices) or "failed" (for payments), optional
|
||||||
"invoice": "string", // encoded invoice, optional
|
"invoice": "string", // encoded invoice, optional
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
|
@ -418,7 +420,7 @@ Response:
|
||||||
"transactions": [
|
"transactions": [
|
||||||
{
|
{
|
||||||
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
"type": "incoming", // "incoming" for invoices, "outgoing" for payments
|
||||||
"state": "pending", // can be "pending", "settled", "expired" (for invoices) or "failed" (for payments)
|
"state": "pending", // can be "pending", "settled", "expired" (for invoices) or "failed" (for payments), optional
|
||||||
"invoice": "string", // encoded invoice, optional
|
"invoice": "string", // encoded invoice, optional
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
|
@ -495,7 +497,7 @@ Notification:
|
||||||
"notification_type": "payment_received",
|
"notification_type": "payment_received",
|
||||||
"notification": {
|
"notification": {
|
||||||
"type": "incoming",
|
"type": "incoming",
|
||||||
"state": "settled",
|
"state": "settled", // optional
|
||||||
"invoice": "string", // encoded invoice
|
"invoice": "string", // encoded invoice
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
|
@ -521,7 +523,7 @@ Notification:
|
||||||
"notification_type": "payment_sent",
|
"notification_type": "payment_sent",
|
||||||
"notification": {
|
"notification": {
|
||||||
"type": "outgoing",
|
"type": "outgoing",
|
||||||
"state": "settled",
|
"state": "settled", // optional
|
||||||
"invoice": "string", // encoded invoice
|
"invoice": "string", // encoded invoice
|
||||||
"description": "string", // invoice's description, optional
|
"description": "string", // invoice's description, optional
|
||||||
"description_hash": "string", // invoice's description hash, optional
|
"description_hash": "string", // invoice's description hash, optional
|
||||||
|
@ -612,6 +614,38 @@ The **client** should check the `encryption` tag in the `info` event to determin
|
||||||
## Using a dedicated relay
|
## Using a dedicated relay
|
||||||
This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case.
|
This NIP does not specify any requirements on the type of relays used. However, if the user is using a custodial service it might make sense to use a relay that is hosted by the custodial service. The relay may then enforce authentication to prevent metadata leaks. Not depending on a 3rd party relay would also improve reliability in this case.
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
Metadata MAY be stored by the **wallet service** alongside invoices and payments. The metadata MUST be no more than 4096 characters, otherwise MUST be dropped. This is to ensure transactions do not get too large to be relayed.
|
||||||
|
|
||||||
|
NWC relays SHOULD allow at least a payload size of 64KB and **clients** SHOULD fetch small page sizes (maximum of 20 transactions per page) otherwise there is risk of `list_transactions` responses being rejected.
|
||||||
|
|
||||||
|
Here are some properties that are recognized by some NWC clients:
|
||||||
|
|
||||||
|
```jsonc
|
||||||
|
{
|
||||||
|
"comment": "string", // LUD-12 comment
|
||||||
|
"payer_data": {
|
||||||
|
"email": "string",
|
||||||
|
"name": "string",
|
||||||
|
"pubkey": "string",
|
||||||
|
}, // LUD-18 payer data
|
||||||
|
"recipient_data": {
|
||||||
|
"identifier": "string"
|
||||||
|
}, // similar to LUD-18 payer data, but to record recipient data e.g. the lightning address of the recipient
|
||||||
|
"nostr": {
|
||||||
|
"pubkey": "string",
|
||||||
|
"tags": [],
|
||||||
|
// ... rest of zap request event
|
||||||
|
}, // NIP-57 Zap Request event (kind 9734)
|
||||||
|
"tlv_records": [
|
||||||
|
{
|
||||||
|
"type": 5482373484, // tlv type
|
||||||
|
"value": "0123456789abcdef" // hex encoded tlv value
|
||||||
|
}
|
||||||
|
] // keysend TLV records (e.g. for podcasting 2.0 boostagrams)
|
||||||
|
} & Record<string, unknown>;
|
||||||
|
```
|
||||||
|
|
||||||
## Appendix
|
## Appendix
|
||||||
|
|
||||||
### Example NIP-47 info event
|
### Example NIP-47 info event
|
||||||
|
|
2
51.md
2
51.md
|
@ -54,7 +54,7 @@ Aside from their main identifier, the `"d"` tag, sets can optionally have a `"ti
|
||||||
| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) |
|
| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) |
|
||||||
| Bookmark sets | 30003 | user-defined bookmarks categories , for when bookmarks must be in labeled separate groups | `"e"` (kind:1 notes), `"a"` (kind:30023 articles), `"t"` (hashtags), `"r"` (URLs) |
|
| Bookmark sets | 30003 | user-defined bookmarks categories , for when bookmarks must be in labeled separate groups | `"e"` (kind:1 notes), `"a"` (kind:30023 articles), `"t"` (hashtags), `"r"` (URLs) |
|
||||||
| Curation sets | 30004 | groups of articles picked by users as interesting and/or belonging to the same category | `"a"` (kind:30023 articles), `"e"` (kind:1 notes) |
|
| Curation sets | 30004 | groups of articles picked by users as interesting and/or belonging to the same category | `"a"` (kind:30023 articles), `"e"` (kind:1 notes) |
|
||||||
| Curation sets | 30005 | groups of videos picked by users as interesting and/or belonging to the same category | `"a"` (kind:21 videos) |
|
| Curation sets | 30005 | groups of videos picked by users as interesting and/or belonging to the same category | `"e"` (kind:21 videos) |
|
||||||
| Kind mute sets | 30007 | mute pubkeys by kinds<br>`"d"` tag MUST be the kind string | `"p"` (pubkeys) |
|
| Kind mute sets | 30007 | mute pubkeys by kinds<br>`"d"` tag MUST be the kind string | `"p"` (pubkeys) |
|
||||||
| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) |
|
| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) |
|
||||||
| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) |
|
| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) |
|
||||||
|
|
55
53.md
55
53.md
|
@ -6,13 +6,13 @@ Live Activities
|
||||||
|
|
||||||
`draft` `optional`
|
`draft` `optional`
|
||||||
|
|
||||||
Service providers want to offer live activities to the Nostr network in such a way that participants can easily log and query by clients. This NIP describes a general framework to advertise the involvement of pubkeys in such live activities.
|
This NIP introduces event kinds to advertise live spaces and the participation of pubkeys in them.
|
||||||
|
|
||||||
## Concepts
|
## Live Streaming
|
||||||
|
|
||||||
### Live Event
|
A special event with `kind:30311` "Live Streaming Event" is defined as an _addressable event_ whose tags advertise the content and participants of a live stream.
|
||||||
|
|
||||||
A special event with `kind:30311` "Live Event" is defined as an _addressable event_ of public `p` tags. Each `p` tag SHOULD have a **displayable** marker name for the current role (e.g. `Host`, `Speaker`, `Participant`) of the user in the event and the relay information MAY be empty. This event will be constantly updated as participants join and leave the activity.
|
Each `p` tag SHOULD have a **displayable** marker name for the current role (e.g. `Host`, `Speaker`, `Participant`) of the user in the event and the relay information MAY be empty. This event will be constantly updated as participants join and leave the activity.
|
||||||
|
|
||||||
For example:
|
For example:
|
||||||
|
|
||||||
|
@ -63,13 +63,13 @@ This feature is important to avoid malicious event owners adding large account h
|
||||||
|
|
||||||
### Live Chat Message
|
### Live Chat Message
|
||||||
|
|
||||||
Event `kind:1311` is live chat's channel message. Clients MUST include the `a` tag of the activity. An `e` tag denotes the direct parent message this post is replying to.
|
Event `kind:1311` is live chat's channel message. Clients MUST include the `a` tag of the activity. An `e` tag denotes the direct parent message this post is replying to.
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"kind": 1311,
|
"kind": 1311,
|
||||||
"tags": [
|
"tags": [
|
||||||
["a", "30311:<Community event author pubkey>:<d-identifier of the community>", "<Optional relay url>", "root"],
|
["a", "30311:<Community event author pubkey>:<d-identifier of the community>", "<Optional relay url>"],
|
||||||
],
|
],
|
||||||
"content": "Zaps to live streams is beautiful.",
|
"content": "Zaps to live streams is beautiful.",
|
||||||
// other fields...
|
// other fields...
|
||||||
|
@ -84,13 +84,9 @@ Event `kind:1311` is live chat's channel message. Clients MUST include the `a` t
|
||||||
|
|
||||||
Hosts may choose to pin one or more live chat messages by updating the `pinned` tags in the live event kind `30311`.
|
Hosts may choose to pin one or more live chat messages by updating the `pinned` tags in the live event kind `30311`.
|
||||||
|
|
||||||
## Use Cases
|
### Examples
|
||||||
|
|
||||||
Common use cases include meeting rooms/workshops, watch-together activities, or event spaces, such as [zap.stream](https://zap.stream).
|
#### Live Streaming
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
### Live Streaming
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
@ -114,7 +110,7 @@ Common use cases include meeting rooms/workshops, watch-together activities, or
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Live Streaming chat message
|
#### Live Streaming chat message
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
@ -130,18 +126,13 @@ Common use cases include meeting rooms/workshops, watch-together activities, or
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Interactive Rooms and Meetings
|
## Meeting Spaces
|
||||||
-----
|
|
||||||
|
|
||||||
`draft` `optional`
|
Meeting spaces contain one or more video/audio rooms where users can join and participate in the streaming.
|
||||||
|
|
||||||
Service providers want to offer Interactive Rooms to the Nostr network in such a way that participants can easily log and query by clients. This NIP describes a general framework to advertise rooms and their associated events.
|
### Meeting Space Event (kind:30312)
|
||||||
|
|
||||||
## Concepts
|
A special event with `kind:30312` "Space Host" defines the configuration and properties of a virtual interactive space. Each space has a unique identifier and can host multiple events/meetings.
|
||||||
|
|
||||||
### Interactive Room (kind:30312)
|
|
||||||
|
|
||||||
A special event with `kind:30312` "Interactive Room" defines the configuration and properties of a virtual interactive space. Each room has a unique identifier and can host multiple events/meetings.
|
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
|
@ -162,19 +153,20 @@ A special event with `kind:30312` "Interactive Room" defines the configuration a
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Room properties:
|
Space properties:
|
||||||
* MUST be either open, private or closed. Closed means the room is not in operation.
|
* MUST be either open, private or closed. Closed means the room is not in operation.
|
||||||
* MAY specify access control policy for private rooms (e.g. invite-only, payment required)
|
* MAY specify access control policy for private rooms (e.g. invite-only, payment required)
|
||||||
* MAY persist when not in use
|
* MAY persist when not in use
|
||||||
* MUST have at least one provider with "Host" role
|
* MUST have at least one provider with "Host" role
|
||||||
* MAY have multiple providers with different roles
|
* MAY have multiple providers with different roles
|
||||||
|
|
||||||
Provider roles (p tags):
|
Provider roles (p tags):
|
||||||
* Host: Full room management capabilities
|
* Host: Full room management capabilities
|
||||||
* Moderator: Room moderation capabilities
|
* Moderator: Room moderation capabilities
|
||||||
* Speaker: Allowed to present/speak
|
* Speaker: Allowed to present/speak
|
||||||
* Optional proof field for role verification
|
* Optional proof field for role verification
|
||||||
|
|
||||||
### Room Meeting (kind:30313)
|
### Meeting Room Events (kind:30313)
|
||||||
|
|
||||||
A special event with kind:30313 represents a scheduled or ongoing meeting within a room. It MUST reference its parent room using the d tag.
|
A special event with kind:30313 represents a scheduled or ongoing meeting within a room. It MUST reference its parent room using the d tag.
|
||||||
|
|
||||||
|
@ -183,7 +175,7 @@ A special event with kind:30313 represents a scheduled or ongoing meeting within
|
||||||
"kind": 30313,
|
"kind": 30313,
|
||||||
"tags": [
|
"tags": [
|
||||||
["d", "<event-unique-identifier>"], // Required: Event identifier
|
["d", "<event-unique-identifier>"], // Required: Event identifier
|
||||||
["a", "30312:<pubkey>:<room-id>", "wss://nostr.example.com"], // Required: Reference to parent room, 'd' from 30312
|
["a", "30312:<pubkey>:<room-id>", "wss://nostr.example.com"], // Required: Reference to parent space, 'd' from 30312
|
||||||
["title", "<meeting-title>"], // Required: Meeting title
|
["title", "<meeting-title>"], // Required: Meeting title
|
||||||
["summary", "<description>"], // Optional: Meeting description
|
["summary", "<description>"], // Optional: Meeting description
|
||||||
["image", "<preview image url>"], // Optional: Meeting image
|
["image", "<preview image url>"], // Optional: Meeting image
|
||||||
|
@ -204,14 +196,15 @@ Event properties:
|
||||||
* MUST have a start time
|
* MUST have a start time
|
||||||
* MAY track participant counts
|
* MAY track participant counts
|
||||||
* MAY include participant roles specific to the event
|
* MAY include participant roles specific to the event
|
||||||
|
|
||||||
Event management:
|
Event management:
|
||||||
* Clients SHOULD update event status regularly when live
|
* Clients SHOULD update event status regularly when live
|
||||||
* Events without updates for 1 hour MAY be considered ended
|
* Events without updates for 1 hour MAY be considered ended
|
||||||
* starts and ends timestamps SHOULD be updated when status changes
|
* starts and ends timestamps SHOULD be updated when status changes
|
||||||
|
|
||||||
Examples
|
### Examples
|
||||||
|
|
||||||
Interactive Room (kind:30312)
|
#### Meeting Space (kind:30312)
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
|
@ -233,7 +226,7 @@ Interactive Room (kind:30312)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Conference Event (kind:30313)
|
#### Meeting room (kind:30313)
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
|
@ -254,11 +247,11 @@ Conference Event (kind:30313)
|
||||||
"content": ""
|
"content": ""
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
## Room Presence
|
### Room Presence
|
||||||
|
|
||||||
New `kind: 10312` provides an event which signals presence of a listener.
|
New `kind: 10312` provides an event which signals presence of a listener.
|
||||||
|
|
||||||
The presence event SHOULD be updated at regular intervals and clients SHOULD filter presence events older than
|
The presence event SHOULD be updated at regular intervals and clients SHOULD filter presence events older than
|
||||||
a given time window.
|
a given time window.
|
||||||
|
|
||||||
**This kind `10312` is a regular replaceable event, as such presence can only be indicated in one room at a time.**
|
**This kind `10312` is a regular replaceable event, as such presence can only be indicated in one room at a time.**
|
||||||
|
|
2
57.md
2
57.md
|
@ -12,7 +12,7 @@ Having lightning receipts on nostr allows clients to display lightning payments
|
||||||
|
|
||||||
## Protocol flow
|
## Protocol flow
|
||||||
|
|
||||||
1. Client calculates a recipient's lnurl pay request url from the `zap` tag on the event being zapped (see Appendix G), or by decoding their lud06 or lud16 field on their profile according to the [lnurl specifications](https://github.com/lnurl/luds). The client MUST send a GET request to this url and parse the response. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key in hex, the client should associate this information with the user, along with the response's `callback`, `minSendable`, and `maxSendable` values.
|
1. Client calculates a recipient's lnurl pay request url from the `zap` tag on the event being zapped (see Appendix G), or by decoding their lud16 field on their profile according to the [lnurl specifications](https://github.com/lnurl/luds). The client MUST send a GET request to this url and parse the response. If `allowsNostr` exists and it is `true`, and if `nostrPubkey` exists and is a valid BIP 340 public key in hex, the client should associate this information with the user, along with the response's `callback`, `minSendable`, and `maxSendable` values.
|
||||||
2. Clients may choose to display a lightning zap button on each post or on a user's profile. If the user's lnurl pay request endpoint supports nostr, the client SHOULD use this NIP to request a `zap receipt` rather than a normal lnurl invoice.
|
2. Clients may choose to display a lightning zap button on each post or on a user's profile. If the user's lnurl pay request endpoint supports nostr, the client SHOULD use this NIP to request a `zap receipt` rather than a normal lnurl invoice.
|
||||||
3. When a user (the "sender") indicates they want to send a zap to another user (the "recipient"), the client should create a `zap request` event as described in Appendix A of this NIP and sign it.
|
3. When a user (the "sender") indicates they want to send a zap to another user (the "recipient"), the client should create a `zap request` event as described in Appendix A of this NIP and sign it.
|
||||||
4. Instead of publishing the `zap request`, the `9734` event should instead be sent to the `callback` url received from the lnurl pay endpoint for the recipient using a GET request. See Appendix B for details and an example.
|
4. Instead of publishing the `zap request`, the `9734` event should instead be sent to the `callback` url received from the lnurl pay endpoint for the recipient using a GET request. See Appendix B for details and an example.
|
||||||
|
|
4
66.md
4
66.md
|
@ -28,10 +28,8 @@ Other tags include:
|
||||||
- `N` - NIPs supported by the relay
|
- `N` - NIPs supported by the relay
|
||||||
- `R` - Keys corresponding to requirements per [NIP 11](https://github.com/nostr-protocol/nips/blob/master/11.md)'s `limitations` array, including `auth`, `writes`, `pow`, and `payment`. False values should be specified using a `!` prefix, for example `!auth`.
|
- `R` - Keys corresponding to requirements per [NIP 11](https://github.com/nostr-protocol/nips/blob/master/11.md)'s `limitations` array, including `auth`, `writes`, `pow`, and `payment`. False values should be specified using a `!` prefix, for example `!auth`.
|
||||||
- `t` - A topic associated with this relay
|
- `t` - A topic associated with this relay
|
||||||
- `k` - An event kind accepted by the relay
|
- `k` - Accepted and unaccepted kinds (false values prepended by `!`)
|
||||||
- `!k` - An event kind not accepted by the relay
|
|
||||||
- `g` - A [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) geohash
|
- `g` - A [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) geohash
|
||||||
- `l` - A language tag
|
|
||||||
|
|
||||||
Tags with more than one value should be repeated, rather than putting all values in a single tag, for example `[["t", "cats"], ["t", "dogs"]]`, rather than `[["t", "cats", "dogs"]]`.
|
Tags with more than one value should be repeated, rather than putting all values in a single tag, for example `[["t", "cats"], ["t", "dogs"]]`, rather than `[["t", "cats", "dogs"]]`.
|
||||||
|
|
||||||
|
|
8
71.md
8
71.md
|
@ -85,15 +85,15 @@ Additionally `service nip96` may be included to allow clients to search the auth
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>,
|
"id": "<32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>",
|
||||||
"pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
|
"pubkey": "<32-bytes lowercase hex-encoded public key of the event creator>",
|
||||||
"created_at": <Unix timestamp in seconds>,
|
"created_at": <Unix timestamp in seconds>,
|
||||||
"kind": 21 | 22,
|
"kind": 21 | 22,
|
||||||
"content": "<summary / description of video>",
|
"content": "<summary / description of video>",
|
||||||
"tags": [
|
"tags": [
|
||||||
["title", "<title of video>"],
|
["title", "<title of video>"],
|
||||||
["published_at", "<unix timestamp>"],
|
["published_at", "<unix timestamp>"],
|
||||||
["alt", <description>],
|
["alt", "<description>"],
|
||||||
|
|
||||||
// video Data
|
// video Data
|
||||||
["imeta",
|
["imeta",
|
||||||
|
@ -108,7 +108,7 @@ Additionally `service nip96` may be included to allow clients to search the auth
|
||||||
"service nip96",
|
"service nip96",
|
||||||
],
|
],
|
||||||
|
|
||||||
["duration", <duration of video in seconds>],
|
["duration", "<duration of video in seconds>"],
|
||||||
["text-track", "<encoded `kind 6000` event>", "<recommended relay urls>"],
|
["text-track", "<encoded `kind 6000` event>", "<recommended relay urls>"],
|
||||||
["content-warning", "<reason>"],
|
["content-warning", "<reason>"],
|
||||||
["segment", <start>, <end>, "<title>", "<thumbnail URL>"],
|
["segment", <start>, <end>, "<title>", "<thumbnail URL>"],
|
||||||
|
|
|
@ -0,0 +1,139 @@
|
||||||
|
# NIP-95 - Nostr Frost Remote Signing
|
||||||
|
|
||||||
|
`draft` `optional`
|
||||||
|
|
||||||
|
## Rationale
|
||||||
|
|
||||||
|
Private keys should be exposed to as few systems - apps, operating systems, devices - as possible, ideally none to prevent single points of failure.
|
||||||
|
|
||||||
|
This NIP describes an implementation of a Flexible Round Optimized Schnorr Threshold (FROST) signature scheme for a Nostr client and one or many remote signers. The remote signer could be, for example, a webserver or a hardware device dedicated to signing Nostr events, while the client is a normal Nostr client.
|
||||||
|
|
||||||
|
## Terminology
|
||||||
|
|
||||||
|
- **Client keypair**: A local public and private key-pair which is a valid share of the FROST signature scheme and used to coordinate the signature aggregation and communicate with the remote signer(s).
|
||||||
|
- **Remote keypair**: This is a public and private key-pair which represents a share of the FROST signature scheme.
|
||||||
|
- **Root user pubkey**: The public key that the user wants to sign as. The aggregated signature produces a valid signature for this pubkey.
|
||||||
|
|
||||||
|
All pubkeys specified in this NIP are in hex format.
|
||||||
|
|
||||||
|
## Set up
|
||||||
|
|
||||||
|
The inital set up for all keys involved
|
||||||
|
|
||||||
|
### Root key
|
||||||
|
|
||||||
|
The Root key is the key-pair that is perminant and identifies the user. (i.e. when someone wants to follow this user, they would follow this Root Pubkey)
|
||||||
|
The Root key MUST publish a kind `11100` event which specifies the signature scheme that should be used to create a valid signature for this pubkey.
|
||||||
|
|
||||||
|
The list of tags are as follows:
|
||||||
|
|
||||||
|
- `p` (required, repeated) 32-bytes hex pubkey of all FROST key shares involved in the signature scheme, index of FROST share, optional signature request URL
|
||||||
|
- `threshold` (required) the number of signature shares required to create a valid aggregated signature for the root pubkey
|
||||||
|
|
||||||
|
#### Kind 11100 Event
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 11100,
|
||||||
|
"pubkey": "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52",
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
[
|
||||||
|
"p",
|
||||||
|
"eff37350d839ce3707332348af4549a96051bd695d3223af4aabce4993531d86",
|
||||||
|
"1",
|
||||||
|
"<optional url of remote signer>"
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"p",
|
||||||
|
"1ce5a164bc3482a63efd010af17ea3585b4ae8fd35dc1d1cd0bd3037ba9acb51",
|
||||||
|
"2"
|
||||||
|
],
|
||||||
|
["threshold", "2"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## The flow
|
||||||
|
|
||||||
|
1. User creates FROST key shares for their root key-pair.
|
||||||
|
2. User publishes kind `11100` event with the data about the key shares.
|
||||||
|
3. User signs into Nostr client with the Client key (a share of the Root key). Client treats the user as if they are signed in with the Root key. There are multiple ways for the user to communicate to the client that they wish to be treated as the Root key rather than the Client key, perhaps searching for the kind `11100` event or having the user specify the Root pubkey they wish to be signed in as in the case that the client key is a FROST share for multiple Root keys (i.e. the client key is tagged in multiple kind `11100` events).
|
||||||
|
4. When the user needs to produce a signature for the Root key, the client will find the Root key's kind `11100` event and coordinate the nonce communication and signature aggregations steps necessary to produce a valid Root key signature
|
||||||
|
5. The client publishes the event from the Root pubkey with the created aggregated signature.
|
||||||
|
|
||||||
|
### Signature aggregation
|
||||||
|
|
||||||
|
In order to the client to produce a valid signature, it must aggregate nonce commitments from all partial signers involved, distribute those nonce commitments to all partial signers, collect partial signatures from all partial signers involved, and finally perform a signature aggregation step.
|
||||||
|
|
||||||
|
This process scales in complexity as more partial signers are involved. In the simple case where `threshold = 2`, this coordination is rather simple:
|
||||||
|
|
||||||
|
#### Threshold = 2
|
||||||
|
|
||||||
|
1. Client generates the event id to be signed and creates a nonce commitment for the local Client key.
|
||||||
|
2. Based on the Root key's kind `11100` event, the client requests a partial signature from the remote signing key by passing the remote signer the event id to sign, the Root pubkey being signed for, the index of the Client key, and the nonce commitment that was just created.
|
||||||
|
3. The Remote signer receives the request and first generates a nonce commitment for it's own remote key, then by combining the 2 nonce comitments, the remote signer can now create a partial signature share for the remote key.
|
||||||
|
4. The Remote signer returns it's partial signature and nonce commitment to the client.
|
||||||
|
5. The client combines the nonce commitment from the remote signer with it's own to produce the same shared nonce commitment that the remote signer had generated, and then creates the partial client signature.
|
||||||
|
6. Lastely, the 2 partial signatures are combined to create a valid signature for the Root key.
|
||||||
|
|
||||||
|
Cases where `threshold > 2` are more complex as multiple rounds of communication are needed to share the nonce commitments from all signers, but the core flow is unchanged
|
||||||
|
|
||||||
|
#### Threshold > 2
|
||||||
|
|
||||||
|
1. Client generates the event id to be signed and creates a nonce commitment for the local Client key.
|
||||||
|
2. Based on the Root key's kind `11100` event, the client requests a nonce commitments for all remote signers needed for the signature. (Note: this nonce commitment step could also occur prior to the signature request and the client could cache nonce commitments for remote signers before hand).
|
||||||
|
3. Remote signers receive nonce commitment request and return a nonce commitment.
|
||||||
|
4. The Client creates aggregates the nonce commitments to create it's own partial signature.
|
||||||
|
5. The Client relays all nonce commitments back to the remote signers so they can each produce their partial signature. The remote signer then returns its partial signature back to the client.
|
||||||
|
6. Lastely, the client aggregates all partial signatures to create a valid signature for the Root key.
|
||||||
|
|
||||||
|
## Communication between client and remote signer
|
||||||
|
|
||||||
|
### Direct connection initiated by the client
|
||||||
|
|
||||||
|
In this case, the client uses the remote signer url specificed in the kind `11100` tags array as the 4th element for the remote signer's pubkey.
|
||||||
|
|
||||||
|
#### Signature request
|
||||||
|
|
||||||
|
The client sends a POST request to the specified url with body
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pubkey": "<remote signer pubkey being requested to sign>",
|
||||||
|
"event": "<event to sign JSON>",
|
||||||
|
"nonceCommitments": [["<nonce commitment>", "partial signer index"]]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Signature request response
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"nonceCommitment": "<nonce commitment>",
|
||||||
|
"partialSignature": "<partial signature"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Nonce commitment request
|
||||||
|
|
||||||
|
The client sends a GET request to the specified url with with search param of the pubkey being requested and an optional amount value
|
||||||
|
`https://remote-signer.com/?pubkey=<remote pubkey>&amount=1`
|
||||||
|
|
||||||
|
#### Nonce commitment request response
|
||||||
|
|
||||||
|
```json
|
||||||
|
["<nonce commitment>"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important notes
|
||||||
|
|
||||||
|
- It is essential that the remote signers keep track of the nonce commitments that they have generated and used to ensure that a malicious actor could not preform a nonce reuse attack.
|
||||||
|
|
||||||
|
## Coming soon...
|
||||||
|
|
||||||
|
- Relay based Client <> Remote signer communication
|
||||||
|
- [NIP-04 - Encryption & decryption](04.md)
|
||||||
|
- Key rotation flow
|
||||||
|
- Key addition flow
|
||||||
|
- Dynamic signing permissions
|
2
96.md
2
96.md
|
@ -1,3 +1,5 @@
|
||||||
|
> __Warning__ `unrecommended`: deprecated in favor of [NIP-B7](B7.md)
|
||||||
|
|
||||||
NIP-96
|
NIP-96
|
||||||
======
|
======
|
||||||
|
|
||||||
|
|
4
99.md
4
99.md
|
@ -40,7 +40,7 @@ The following tags, used for structured metadata, are standardized and SHOULD be
|
||||||
- `"<number>"` is the amount in numeric format (but included in the tag as a string)
|
- `"<number>"` is the amount in numeric format (but included in the tag as a string)
|
||||||
- `"<currency>"` is the currency unit in 3-character ISO 4217 format or ISO 4217-like currency code (e.g. `"btc"`, `"eth"`).
|
- `"<currency>"` is the currency unit in 3-character ISO 4217 format or ISO 4217-like currency code (e.g. `"btc"`, `"eth"`).
|
||||||
- `"<frequency>"` is optional and can be used to describe recurring payments. SHOULD be in noun format (hour, day, week, month, year, etc.)
|
- `"<frequency>"` is optional and can be used to describe recurring payments. SHOULD be in noun format (hour, day, week, month, year, etc.)
|
||||||
- - `"status"` (optional), the status of the listing. SHOULD be either "active" or "sold".
|
- `"status"` (optional), the status of the listing. SHOULD be either "active" or "sold".
|
||||||
|
|
||||||
#### `price` examples
|
#### `price` examples
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ The following tags, used for structured metadata, are standardized and SHOULD be
|
||||||
- €15 per month `["price", "15", "EUR", "month"]`
|
- €15 per month `["price", "15", "EUR", "month"]`
|
||||||
- £50,000 per year `["price", "50000", "GBP", "year"]`
|
- £50,000 per year `["price", "50000", "GBP", "year"]`
|
||||||
|
|
||||||
Other standard tags that might be useful.
|
Other common tags that might be useful.
|
||||||
|
|
||||||
- `"g"`, a geohash for more precise location
|
- `"g"`, a geohash for more precise location
|
||||||
|
|
||||||
|
|
68
BREAKING.md
68
BREAKING.md
|
@ -1,68 +0,0 @@
|
||||||
# Breaking Changes
|
|
||||||
|
|
||||||
This is a history of NIP changes that potentially break pre-existing implementations, in
|
|
||||||
reverse chronological order.
|
|
||||||
|
|
||||||
| Date | Commit | NIP | Change |
|
|
||||||
| ----------- | --------- | -------- | ------ |
|
|
||||||
| 2025-02-14 | [81908b6e](https://github.com/nostr-protocol/nips/commit/81908b6e) | [07](07.md), [46](46.md), [55](55.md) | `getRelays` and `get_relays` were removed |
|
|
||||||
| 2025-02-07 | [0023ca81](https://github.com/nostr-protocol/nips/commit/0023ca81) | [10](10.md) | `"mention"` marker was removed |
|
|
||||||
| 2025-01-31 | [6a4b125a](https://github.com/nostr-protocol/nips/commit/6a4b125a) | [71](71.md) | video events were changed to regular |
|
|
||||||
| 2024-12-05 | [6d16019e](https://github.com/nostr-protocol/nips/commit/6d16019e) | [46](46.md) | message encryption was changed to NIP-44 |
|
|
||||||
| 2024-11-12 | [2838e3bd](https://github.com/nostr-protocol/nips/commit/2838e3bd) | [29](29.md) | `kind: 12` and `kind: 10` were removed (use `kind: 1111` instead) |
|
|
||||||
| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | NIP-05 login was removed |
|
|
||||||
| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | `create_account` method was removed |
|
|
||||||
| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | `connect` params and result were changed |
|
|
||||||
| 2024-10-29 | [f1e8d2c4](https://github.com/nostr-protocol/nips/commit/f1e8d2c4) | [46](46.md) | bunker URL should use `remote-signer-key` |
|
|
||||||
| 2024-10-15 | [1cda2dcc](https://github.com/nostr-protocol/nips/commit/1cda2dcc) | [71](71.md) | some tags were replaced with `imeta` tag |
|
|
||||||
| 2024-10-15 | [1cda2dcc](https://github.com/nostr-protocol/nips/commit/1cda2dcc) | [71](71.md) | `kind: 34237` was dropped |
|
|
||||||
| 2024-10-07 | [7bb8997b](https://github.com/nostr-protocol/nips/commit/7bb8997b) | [55](55.md) | some fields and passing data were changed |
|
|
||||||
| 2024-08-18 | [3aff37bd](https://github.com/nostr-protocol/nips/commit/3aff37bd) | [54](54.md) | content should be Asciidoc |
|
|
||||||
| 2024-07-31 | [3ea2f1a4](https://github.com/nostr-protocol/nips/commit/3ea2f1a4) | [45](45.md) | [444ad28d](https://github.com/nostr-protocol/nips/commit/444ad28d) was reverted |
|
|
||||||
| 2024-07-30 | [444ad28d](https://github.com/nostr-protocol/nips/commit/444ad28d) | [45](45.md) | NIP-45 was deprecated |
|
|
||||||
| 2024-07-26 | [ecee40df](https://github.com/nostr-protocol/nips/commit/ecee40df) | [19](19.md) | `nrelay` was deprecated |
|
|
||||||
| 2024-07-23 | [0227a2cd](https://github.com/nostr-protocol/nips/commit/0227a2cd) | [01](01.md) | events should be sorted by id after created_at |
|
|
||||||
| 2024-06-06 | [58e94b20](https://github.com/nostr-protocol/nips/commit/58e94b20) | [25](25.md) | [8073c848](https://github.com/nostr-protocol/nips/commit/8073c848) was reverted |
|
|
||||||
| 2024-06-06 | [a6dfc7b5](https://github.com/nostr-protocol/nips/commit/a6dfc7b5) | [55](55.md) | NIP number was changed |
|
|
||||||
| 2024-05-25 | [5d1d1c17](https://github.com/nostr-protocol/nips/commit/5d1d1c17) | [71](71.md) | `aes-256-gcm` tag was removed |
|
|
||||||
| 2024-05-07 | [8073c848](https://github.com/nostr-protocol/nips/commit/8073c848) | [25](25.md) | e-tags were changed to not include entire thread |
|
|
||||||
| 2024-04-30 | [bad88262](https://github.com/nostr-protocol/nips/commit/bad88262) | [34](34.md) | `earliest-unique-commit` tag was removed (use `r` tag instead) |
|
|
||||||
| 2024-02-25 | [4a171cb0](https://github.com/nostr-protocol/nips/commit/4a171cb0) | [18](18.md) | quote repost should use `q` tag |
|
|
||||||
| 2024-02-21 | [c6cd655c](https://github.com/nostr-protocol/nips/commit/c6cd655c) | [46](46.md) | Params were stringified |
|
|
||||||
| 2024-02-16 | [cbec02ab](https://github.com/nostr-protocol/nips/commit/cbec02ab) | [49](49.md) | Password first normalized to NFKC |
|
|
||||||
| 2024-02-15 | [afbb8dd0](https://github.com/nostr-protocol/nips/commit/afbb8dd0) | [39](39.md) | PGP identity was removed |
|
|
||||||
| 2024-02-07 | [d3dad114](https://github.com/nostr-protocol/nips/commit/d3dad114) | [46](46.md) | Connection token format was changed |
|
|
||||||
| 2024-01-30 | [1a2b21b6](https://github.com/nostr-protocol/nips/commit/1a2b21b6) | [59](59.md) | `p` tag became optional |
|
|
||||||
| 2023-01-27 | [c2f34817](https://github.com/nostr-protocol/nips/commit/c2f34817) | [47](47.md) | optional expiration tag should be honored |
|
|
||||||
| 2024-01-10 | [3d8652ea](https://github.com/nostr-protocol/nips/commit/3d8652ea) | [02](02.md), [51](51.md) | list entries should be chronological |
|
|
||||||
| 2023-12-30 | [29869821](https://github.com/nostr-protocol/nips/commit/29869821) | [52](52.md) | `name` tag was removed (use `title` tag instead) |
|
|
||||||
| 2023-12-27 | [17c67ef5](https://github.com/nostr-protocol/nips/commit/17c67ef5) | [94](94.md) | `aes-256-gcm` tag was removed |
|
|
||||||
| 2023-12-03 | [0ba45895](https://github.com/nostr-protocol/nips/commit/0ba45895) | [01](01.md) | WebSocket status code `4000` was replaced by `CLOSED` message |
|
|
||||||
| 2023-11-28 | [6de35f9e](https://github.com/nostr-protocol/nips/commit/6de35f9e) | [89](89.md) | `client` tag value was changed |
|
|
||||||
| 2023-11-20 | [7822a8b1](https://github.com/nostr-protocol/nips/commit/7822a8b1) | [51](51.md) | `kind: 30001` was deprecated |
|
|
||||||
| 2023-11-20 | [7822a8b1](https://github.com/nostr-protocol/nips/commit/7822a8b1) | [51](51.md) | the meaning of `kind: 30000` was changed |
|
|
||||||
| 2023-11-11 | [cbdca1e9](https://github.com/nostr-protocol/nips/commit/cbdca1e9) | [84](84.md) | `range` tag was removed |
|
|
||||||
| 2023-11-10 | [c945d8bd](https://github.com/nostr-protocol/nips/commit/c945d8bd) | [32](32.md) | `l` tag annotations was removed |
|
|
||||||
| 2023-11-07 | [108b7f16](https://github.com/nostr-protocol/nips/commit/108b7f16) | [01](01.md) | `OK` message must have 4 items |
|
|
||||||
| 2023-10-17 | [cf672b76](https://github.com/nostr-protocol/nips/commit/cf672b76) | [03](03.md) | `block` tag was removed |
|
|
||||||
| 2023-09-29 | [7dc6385f](https://github.com/nostr-protocol/nips/commit/7dc6385f) | [57](57.md) | optional `a` tag was included in `zap receipt` |
|
|
||||||
| 2023-08-21 | [89915e02](https://github.com/nostr-protocol/nips/commit/89915e02) | [11](11.md) | `min_prefix` was removed |
|
|
||||||
| 2023-08-20 | [37c4375e](https://github.com/nostr-protocol/nips/commit/37c4375e) | [01](01.md) | replaceable events with same timestamp should be retained event with lowest id |
|
|
||||||
| 2023-08-15 | [88ee873c](https://github.com/nostr-protocol/nips/commit/88ee873c) | [15](15.md) | `countries` tag was renamed to `regions` |
|
|
||||||
| 2023-08-14 | [72bb8a12](https://github.com/nostr-protocol/nips/commit/72bb8a12) | [12](12.md), [16](16.md), [20](20.md), [33](33.md) | NIP-12, 16, 20 and 33 were merged into NIP-01 |
|
|
||||||
| 2023-08-11 | [d87f8617](https://github.com/nostr-protocol/nips/commit/d87f8617) | [25](25.md) | empty `content` should be considered as "+" |
|
|
||||||
| 2023-08-01 | [5d63b157](https://github.com/nostr-protocol/nips/commit/5d63b157) | [57](57.md) | `zap` tag was changed |
|
|
||||||
| 2023-07-15 | [d1814405](https://github.com/nostr-protocol/nips/commit/d1814405) | [01](01.md) | `since` and `until` filters should be `since <= created_at <= until` |
|
|
||||||
| 2023-07-12 | [a1cd2bd8](https://github.com/nostr-protocol/nips/commit/a1cd2bd8) | [25](25.md) | custom emoji was supported |
|
|
||||||
| 2023-06-18 | [83cbd3e1](https://github.com/nostr-protocol/nips/commit/83cbd3e1) | [11](11.md) | `image` was renamed to `icon` |
|
|
||||||
| 2023-04-13 | [bf0a0da6](https://github.com/nostr-protocol/nips/commit/bf0a0da6) | [15](15.md) | different NIP was re-added as NIP-15 |
|
|
||||||
| 2023-04-09 | [fb5b7c73](https://github.com/nostr-protocol/nips/commit/fb5b7c73) | [15](15.md) | NIP-15 was merged into NIP-01 |
|
|
||||||
| 2023-03-29 | [599e1313](https://github.com/nostr-protocol/nips/commit/599e1313) | [18](18.md) | NIP-18 was bring back |
|
|
||||||
| 2023-03-15 | [e1004d3d](https://github.com/nostr-protocol/nips/commit/e1004d3d) | [19](19.md) | `1: relay` was changed to optionally |
|
|
||||||
|
|
||||||
Breaking changes prior to 2023-03-01 are not yet documented.
|
|
||||||
|
|
||||||
## NOTES
|
|
||||||
|
|
||||||
- If it isn't clear that a change is breaking or not, we list it.
|
|
||||||
- The date is the date it was merged, not necessarily the date of the commit.
|
|
|
@ -0,0 +1,288 @@
|
||||||
|
# NIP-EE
|
||||||
|
|
||||||
|
## E2EE Messaging using the Messaging Layer Security (MLS) Protocol
|
||||||
|
|
||||||
|
`draft` `optional`
|
||||||
|
|
||||||
|
This NIP standardizes how to use the [MLS Protocol](https://www.rfc-editor.org/rfc/rfc9420.html) with Nostr for efficient and E2EE (end-to-end encrypted) direct and group messaging.
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
Originally, one-to-one direct messages (DMs) in Nostr happened via the scheme defined in [NIP-04](04.md). This NIP is not recommended because, while it encrypts the content of the message (provides decent confidentiality), it leaks significant amounts of metadata about the parties involved in the conversation (completely lacks privacy).
|
||||||
|
|
||||||
|
With the addition of [NIP-44](44.md), we have an updated encryption scheme that improves confidentiality guarantees but stops short of defining a new scheme for doing direct messages using this encryption scheme. Hence, makes little to no difference to privacy.
|
||||||
|
|
||||||
|
Most recently, [NIP-17](17.md) combines [NIP-44](44.md) encryption with [NIP-59](59.md) gift-wrapping to hide the encrypted direct message inside another set of events to ensure that it's impossible to see who is talking to who and when messages passed between the users. This largely solves the metadata leakage problem; while it's still possible to see that a user is receiving gift-wrapped events, you can't tell from whom and what kind of events are within the gift-wrap outer event. This gives some degree of deniability/repudiation but doesn't solve forward secrecy or post compromise security. That is to say, if a user's private key (or the calculated conversation key shared between two users used to encrypt messages) is compromised, the attacker will have full access to all past and future DMs sent between those users.
|
||||||
|
|
||||||
|
In addition, neither [NIP-04](04.md) or [NIP-17](17.md) attempt to solve the problem of group messages.
|
||||||
|
|
||||||
|
### Why is this important?
|
||||||
|
|
||||||
|
Without proper E2EE, Nostr cannot be used as the protocol for secure messaging clients. While clients like Signal do a fantastic job with E2EE, they still rely on centralized servers and as a result can be shut down by a powerful (i.e. state-level) actor. The goal of Nostr is not only to protect against centralized entities censoring you and your communications, but also protect against the ability of a state-level actor to stop these sorts of services from existing in the first place. By replacing centralized servers with decentralized relays, we make it nearly impossible for a centralized actor to completely stop communications between individual users.
|
||||||
|
|
||||||
|
### Goals of this NIP
|
||||||
|
|
||||||
|
1. Private _and_ Confidential DMs and Group messages
|
||||||
|
1. **Private** means that an observer cannot tell that Alice and Bob are talking to one another, or that Alice is part of a specific group. This necessarily requires protecting metadata.
|
||||||
|
2. **Confidential** means that the contents of conversations can only be viewed by the intended recipients.
|
||||||
|
2. Forward secrecy and Post-compromise security
|
||||||
|
1. **Forward secrecy** means that encrypted content in the past remains encrypted even if a key material is leaked.
|
||||||
|
2. **Post compromise security** means that leaking key material doesn't allow an attacker to continue to read messages indefinitely into the future.
|
||||||
|
3. Scales efficiently for large groups
|
||||||
|
4. Allows for the use of multiple device/clients in a single conversation/group.
|
||||||
|
|
||||||
|
### Why MLS?
|
||||||
|
|
||||||
|
This scheme adapts the Message Layer Security (MLS) protocol for use with Nostr. You can think of MLS as an evolution of the Signal Protocol. However, it significantly improves the scalability of encryption operations for large group messaging significantly (linear -> log), is built to accommodate federated environments, and also allows for graceful updating of ciphersuites and versions over time. In addition, it's very flexible and agnostic about the message content that is sent.
|
||||||
|
|
||||||
|
It's beyond the scope of this NIP to explain the MLS protocol but you can read more about it in it's [Architectural Overview](https://www.ietf.org/archive/id/draft-ietf-mls-architecture-13.html) or the [RFC](https://www.rfc-editor.org/rfc/rfc9420). MLS is on track to become an internet standard under the IETF so the protocol itself is extremely well vetted and researched. This also means there is the potential for cross network messaging interoperability in the future as MLS gains more adoption.
|
||||||
|
|
||||||
|
## Core MLS Concepts
|
||||||
|
|
||||||
|
From the [MLS Architectural Overview](https://www.ietf.org/archive/id/draft-ietf-mls-architecture-13.html):
|
||||||
|
|
||||||
|
> MLS provides a way for clients to form groups within which they can communicate securely. For example, a set of users might use clients on their phones or laptops to join a group and communicate with each other. A group may be as small as two clients (e.g., for simple person to person messaging) or as large as hundreds of thousands. A client that is part of a group is a member of that group. As groups change membership and group or member properties, they advance from one epoch to another and the cryptographic state of the group evolves.
|
||||||
|
>
|
||||||
|
> The group is represented as a tree, which represents the members as the leaves of a tree. It is used to efficiently encrypt to subsets of the members. Each member has a state called a LeafNode object holding the client's identity, credentials, and capabilities.
|
||||||
|
|
||||||
|
The MLS protocol's job is to manage and evolve the cryptographic state of a group. This includes managing the membership of a group, the cryptographic state of a group (ratchet tree, keys, and encryption/decryption/authentication of messages), and managing the evolution of the group over time.
|
||||||
|
|
||||||
|
### Groups
|
||||||
|
|
||||||
|
Groups are created by their first member, who then invites one or more other members. Groups evolve over time in blocks called `Epochs`. New epochs are proposed via one ore more `Proposal` messages and then committed to via a `Commit` message.
|
||||||
|
|
||||||
|
### Clients
|
||||||
|
|
||||||
|
The device/client pair (e.g. Primal on iOS or Coracle on web) with which a user joins the group is represented as a `LeafNode` in the tree. The terms `Client` and `Member` are interchangeable in this regard. It is not possible to share group state across multiple `Clients`. If a user joins a group from 2 separate devices, their state is separate and they will be tracked as 2 separate members of the group.
|
||||||
|
|
||||||
|
### Messages
|
||||||
|
|
||||||
|
There are several different types of messages sent within a group. Some of these are control messages that are used to update the group state over time. These include `Welcome`, `Proposal`, and `Commit` messages. Others are the actual messages that are sent between members in a group. These include `Application` messages.
|
||||||
|
|
||||||
|
Messages in MLS are "framed". Meaning that they are wrapped in a data structure that includes information about the sender, the epoch, the message index within the epoch and the message content. This framing makes it possible to authenticate and decrypt messages correctly, even if they arrive out of order.
|
||||||
|
|
||||||
|
MLS is agnostic to the "content" of the messages that are sent. This is a key feature of MLS that allows for the use of MLS for a wide variety of applications.
|
||||||
|
|
||||||
|
MLS is also agnostic to the transport protocol that is used to send messages. Obviously for us, we'll be using websockets, Nostr events and relays.
|
||||||
|
|
||||||
|
## The focus of this NIP
|
||||||
|
|
||||||
|
This NIP focuses on how to use Nostr to perform the Authentication Service and Delivery Service functions required by the MLS protocol. Most clients will choose to use an MLS implementation to handle keys, ratcheting, group state management, and other aspects of the MLS protocol itself. [OpenMLS](https://github.com/openmls/openmls) is the most actively developed library that implements MLS.
|
||||||
|
|
||||||
|
This NIP specifies the following:
|
||||||
|
|
||||||
|
1. A standardized way that Nostr clients should [create MLS groups](#creating-groups).
|
||||||
|
2. The required format of the MLS [`Credential`](#mls-credentials) that Nostr clients should use to represent a Nostr user in a group.
|
||||||
|
3. The structure of [KeyPackage Events](#keypackage-event-and-signing-keys) published to relays that allow Nostr users to be added to a group asynchronously.
|
||||||
|
4. The structure of [Group Events](#group-events) published to relays that represent the evolution of a group's state and the contents of the messages sent in the group.
|
||||||
|
|
||||||
|
## Security Considerations
|
||||||
|
|
||||||
|
This is a concise overview of the security trade-offs and considerations of this NIP in various scenarios. The NIP strives to fully maintain MLS security guarantees.
|
||||||
|
|
||||||
|
### Forward Secrecy and Post-compromise Security
|
||||||
|
|
||||||
|
- As per the MLS spec, keys are deleted as soon as they are used to encrypt or decrypt a message. This is usually handled by the MLS implementation library itself but attention needs to be paid by clients to ensure they're not storing secrets (especially the [exporter secret](#group-events)) for longer than absolutely necessary.
|
||||||
|
- This NIP maintains MLS forward secrecy and post-compromise security guarantees. You can read more about those in the MLS Architectural Overview section on [Forward Secrecy and Post-compromise Security](https://www.ietf.org/archive/id/draft-ietf-mls-architecture-15.html#name-forward-and-post-compromise).
|
||||||
|
|
||||||
|
### Leakage of various keys
|
||||||
|
|
||||||
|
- This NIP does not depend on a user's Nostr identity key for any aspect of the MLS messaging protocol. Compromise of a user's Nostr identity key does not give access to past or future messages in any MLS-based group.
|
||||||
|
- For a complete discussion of MLS key leakage, please see the Endpoint Compromise section of the [MLS Architectural Overview](https://www.ietf.org/archive/id/draft-ietf-mls-architecture-15.html#name-endpoint-compromise).
|
||||||
|
|
||||||
|
### Metadata
|
||||||
|
|
||||||
|
- The only group specific metadata published to relays is the Nostr group ID value. This value is used to identify the group in the `h` tag of the Group Message Event (`kind: 445`). These events are published ephemerally and this Nostr group ID value can be updated over the lifetime of the group by group admins. This is a tradeoff to ensure that group participants and group size are obfuscated but still makes it possible to efficiently fan out group messages to all participants. The content field of this event is a value encrypted in two separate ways (using NIP-44 and MLS) with MLS group state/keys. Only group members with up-to-date group state can decrypt and read these messages.
|
||||||
|
- A user's key package events can be used one or more times to be added to groups. There is a tradeoff inherent here: Reusing key packages (initial signing keys) carries some degree of risk but this risk is mitigated as long as a user rotates their signing key immediately upon joining a group. This step also improves the forward secrecy of the entire group.
|
||||||
|
|
||||||
|
### Device Compromise
|
||||||
|
|
||||||
|
Clients implementing this NIP should take every precaution to ensure that data is stored in a secure way on the device and is protected against unwanted access in the case that a device is compromised (e.g. encryption at rest, biometric authentication, etc.). That said, full device compromise should be viewed as a catastrophic event and any group the compromised device was a part of should be considered compromised until they can remove that member and update their group's state. Some suggestions:
|
||||||
|
|
||||||
|
- Clients should support and encourage self-destructing messages (ensuring that full transcript history isn't available on a device forever).
|
||||||
|
- Clients should regularly suggest to group admins that inactive users be removed.
|
||||||
|
- Clients should regularly suggest (or automatically) rotate a user's signing key in each of their groups.
|
||||||
|
- Clients should encrypt group state and keys on the device using a secret value that isn't part of the group state or the user's Nostr identity key.
|
||||||
|
- Clients should use secure enclave storage where possible.
|
||||||
|
|
||||||
|
For a full discussion of the security considerations of MLS, please see the Security Considerations section of the [MLS RFC](https://www.rfc-editor.org/rfc/rfc9420.html#name-security-considerations).
|
||||||
|
|
||||||
|
## Creating groups
|
||||||
|
|
||||||
|
MLS Groups are created with a random 32-byte ID value that is effectively permanent. This ID should be treated as private to the group and MUST not be published to relays in any form.
|
||||||
|
|
||||||
|
Clients must also ensure that the ciphersuite, capabilities, and extensions they use when creating the group are compatible with those advertised by the users they'd like to invite to the group. They can check this info via the user's published KeyPackage Events.
|
||||||
|
|
||||||
|
When creating a new group, the following MLS extensions MUST be used.
|
||||||
|
|
||||||
|
- [`required_capabilities`](https://docs.rs/openmls/latest/openmls/extensions/struct.RequiredCapabilitiesExtension.html)
|
||||||
|
- [`ratchet_tree`](https://docs.rs/openmls/latest/openmls/extensions/struct.RatchetTreeExtension.html)
|
||||||
|
- [`nostr_group_data`](https://github.com/rust-nostr/nostr/blob/master/mls/nostr-mls/src/extension.rs)
|
||||||
|
|
||||||
|
And the following MLS extension is highly recommended (more [here](#keypackage-event-and-signing-keys)):
|
||||||
|
- [`last_resort`](https://docs.rs/openmls/latest/openmls/extensions/struct.LastResortExtension.html)
|
||||||
|
|
||||||
|
Changes to an MLS group are affected by first creating one or more `Proposal` events and then committing to a set of proposals in a `Commit` event. These are MLS events, not Nostr events. However, for the group state to properly evolve the Commit events (which represent a specific set of proposals - like adding a new user to the group) must be published to relays for the other group members to see. See [Group Messages](#group-events) for more information.
|
||||||
|
|
||||||
|
## MLS Credentials
|
||||||
|
|
||||||
|
A `Credential` in MLS is an assertion of who the user is coupled with a signing key. When constructing `Credentials` for MLS, clients MUST use the `BasicCredential` type and set the `identity` value as the 32-byte hex-encoded public key of the user's Nostr identity key. Clients MUST not allow users to change the identity field and MUST validate that all `Proposal` messages do not attempt to change the identity field on any credential in the group.
|
||||||
|
|
||||||
|
A `Credential` also has an associated signing key. The initial signing key for a user is included in the KeyPackage event. The signing key MUST be different from the user's Nostr identity key. This signing key SHOULD be rotated over time to provide improved post-compromise security.
|
||||||
|
|
||||||
|
## Nostr Group Data Extension
|
||||||
|
|
||||||
|
As mentioned above, the `nostr_group_data` extension is a required MLS extension used to associate Nostr-specific data with an MLS group in a cryptographically secure and proveable way. This extension MUST be included as a required capability when creating a new group.
|
||||||
|
|
||||||
|
The extension stores the following data about the group:
|
||||||
|
|
||||||
|
- `nostr_group_id`: A 32-byte ID for the group. This is a different value from the group ID used by MLS and CAN be changed over time. This value is the group ID value used in the `h` tags when sending group message events.
|
||||||
|
- `name`: The name of the group.
|
||||||
|
- `description`: A short description of the group.
|
||||||
|
- `admin_pubkeys`: An array of the hex-encoded public keys of the group admins. The MLS protocol itself does not have a concept of group admins. Clients MUST check the list of `admin_pubkeys` before making any change to the group data (anything in this extension), or before changing group membership (add/remove members), or updating any other aspect of the group itself (e.g. ciphersuite, etc.). Note, all members of the group can send `Proposal` and `Commits` messages for changes to their own credentials (e.g. updating their signing key).
|
||||||
|
- `relays`: An array of the Nostr relay URLs that the group uses to publish and receive messages.
|
||||||
|
|
||||||
|
All of these values can be updated over time using MLS `Proposal` and `Commit` events (by group admins).
|
||||||
|
|
||||||
|
## KeyPackage Event and Signing Keys
|
||||||
|
|
||||||
|
Each user that wishes to be reachable via MLS-based messaging MUST first publish at least one KeyPackage event. The KeyPackage Event is used to authenticate users and create the necessary `Credential` to add members to groups in an asynchronous way. Users can publish multiple KeyPackage Events with different parameters (supporting different ciphersuites or MLS extensions, for example). KeyPackages include a signing key that is used for signing MLS messages within a group. This signing key MUST not be the same as the user's Nostr identity key.
|
||||||
|
|
||||||
|
KeyPackage reuse SHOULD be minimized. However, in normal MLS use, KeyPackages are consumed when joining a group. In order to reduce race conditions between invites for multiple groups using the same Key Package, Nostr clients SHOULD use "Last resort" KeyPackages. This requires the inclusion of the `last_resort` extension on the KeyPackage's capabilities (same as with the Group).
|
||||||
|
|
||||||
|
It's important that clients immediately rotate a user's signing key after joining a group via a last resort key package to improve post-compromise security. The signing key (the public key included in the KeyPackage Event) is used for signing within the group. Therefore, clients implementing this NIP MUST ensure that they retain access to the private key material of the signing key for each group they are a member of.
|
||||||
|
|
||||||
|
In most cases, it's assumed that clients implementing this NIP will manage the creation and rotation of KeyPackage Events.
|
||||||
|
|
||||||
|
### Example KeyPackage Event
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": <id>,
|
||||||
|
"kind": 443,
|
||||||
|
"created_at": <unix timestamp in seconds>,
|
||||||
|
"pubkey": <main identity pubkey>,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
["mls_protocol_version", "1.0"],
|
||||||
|
["ciphersuite", <MLS CipherSuite ID value e.g. "0x0001">],
|
||||||
|
["extensions", <An array of MLS Extension ID values e.g. "0x0001, 0x0002">],
|
||||||
|
["client", <client name>, <handler event id>, <optional relay url>],
|
||||||
|
["relays", <array of relay urls>],
|
||||||
|
["-"]
|
||||||
|
],
|
||||||
|
"sig": <signed with main identity key>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The `content` hex encoded serialized `KeyPackageBundle` from MLS.
|
||||||
|
- The `mls_protocol_version` tag is required and MUST be the version number of the MLS protocol version being used. For now, this is `1.0`.
|
||||||
|
- The `ciphersuite` tag is the value of the MLS ciphersuite that this KeyPackage Event supports. [Read more about ciphersuites in MLS](https://www.rfc-editor.org/rfc/rfc9420.html#name-mls-cipher-suites).
|
||||||
|
- The `extensions` tag is an array of MLS extension IDs that this KeyPackage Event supports. [Read more about MLS extensions](https://www.rfc-editor.org/rfc/rfc9420.html#name-extensions).
|
||||||
|
- (optional) The `client` tag helps other clients manage the user experience when they receive group invites but don't have access to the signing key.
|
||||||
|
- The `relays` tag identifies each of the relays that the client will attempt to publish this KeyPackage event. This allows for deletion of KeyPackage Events at a later date.
|
||||||
|
- (optional) The `-` tag can be used to ensure that KeyPackage Events are only published by their authenticated author. Read more in [NIP-70](70.md)
|
||||||
|
|
||||||
|
### Deleting KeyPackage Events
|
||||||
|
|
||||||
|
Clients SHOULD delete the KeyPackage Event on all the listed relays any time they successfully process a group request event for a given KeyPackage Event. Clients MAY also create a new KeyPackage Event at the same time.
|
||||||
|
|
||||||
|
If clients cannot process a Welcome message (e.g. because the signing key was generated on another client), clients MUST not delete the KeyPackage Event and SHOULD show a human-understandable error to the user.
|
||||||
|
|
||||||
|
### Rotating Signing Keys
|
||||||
|
|
||||||
|
Clients MUST regularly rotate the user's signing key in each group that they are a part of. The more often the signing key is rotated the stronger the post-compromise security. This rotation is done via `Proposal` and `Commit` events and broadcast to the group via a Group Event. [Read more about forward secrecy and post-compromise security inherent in MLS](https://www.rfc-editor.org/rfc/rfc9420.html#name-forward-secrecy-and-post-co).
|
||||||
|
|
||||||
|
### KeyPackage Relays List Event
|
||||||
|
|
||||||
|
A `kind: 10051` event indicates the relays that a user will publish their KeyPackage Events to. The event MUST include a list of relay tags with relay URIs. These relays SHOULD be readable by anyone the user wants to be able to contact them.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 10051,
|
||||||
|
"tags": [
|
||||||
|
["relay", "wss://inbox.nostr.wine"],
|
||||||
|
["relay", "wss://myrelay.nostr1.com"],
|
||||||
|
],
|
||||||
|
"content": "",
|
||||||
|
//...other fields
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Welcome Event
|
||||||
|
|
||||||
|
When a new user is added to a group via an MLS `Commit` message. The member who sends the `Commit` message to the group is responsible for sending the user being added to the group a Welcome Event. This Welcome Event is sent to the user as a [NIP-59](59.md) gift-wrapped event. The Welcome Event gives the new member the context they need to join the group and start sending messages.
|
||||||
|
|
||||||
|
Clients creating the Welcome Event SHOULD wait until they have received acknowledgement from relays that their Group Event with the `Commit` has been received before publishing the Welcome Event.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": <id>,
|
||||||
|
"kind": 444,
|
||||||
|
"created_at": <unix timestamp in seconds>,
|
||||||
|
"pubkey": <nostr identity pubkey of sender>,
|
||||||
|
"content": <serialized Welcome object>,
|
||||||
|
"tags": [
|
||||||
|
["e", <ID of the KeyPackage Event used to add the user to the group>],
|
||||||
|
["relays", <array of relay urls>],
|
||||||
|
],
|
||||||
|
"sig": <NOT SIGNED>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- The `content` field is required and is a serialized MLSMessage object containing the MLS `Welcome` object.
|
||||||
|
- The `e` tag is required and is the ID of the KeyPackage Event used to add the user to the group.
|
||||||
|
- The `relays` tag is required and is a list of relays clients should query for Group Events.
|
||||||
|
|
||||||
|
Welcome Events are then sealed and gift-wrapped as detailed in [NIP-59](59.md) before being published. Like all events that are sealed and gift-wrapped, `kind: 444` events MUST never be signed. This ensures that if they were ever leaked they would not be publishable to relays.
|
||||||
|
|
||||||
|
#### Large Groups
|
||||||
|
|
||||||
|
For groups above ~150 participants, welcome messages will become larger than the maximum event size allowed by Nostr. There is currently work underway on the MLS protocol to support "light" client welcomes that don't require the full Ratchet Tree state to be sent to the new member. This section will be updated with recommendations for how to handle large groups.
|
||||||
|
|
||||||
|
## Group Events
|
||||||
|
|
||||||
|
Group Events are all the messages that are sent within a group. This includes all "control" events that update the shared group state over time (`Proposal`, `Commit`) and messages sent between members of the group (`Application` messages).
|
||||||
|
|
||||||
|
Group Events are published using an ephemeral Nostr keypair to obfuscate the number and identity of group participants. Clients MUST use a new Nostr keypair for each Group Event they publish.
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": <id>,
|
||||||
|
"kind": 445,
|
||||||
|
"created_at": <unix timestamp in seconds>,
|
||||||
|
"pubkey": <ephemeral sender pubkey>,
|
||||||
|
"content": <NIP-44 encrypted serialized MLSMessage object>,
|
||||||
|
"tags": [
|
||||||
|
["h", <group id>]
|
||||||
|
],
|
||||||
|
"sig": <signed with ephemeral sender key>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- The `content` field is a [tls-style](https://www.rfc-editor.org/rfc/rfc9420.html#name-the-message-mls-media-type) serialized [`MLSMessage`](https://www.rfc-editor.org/rfc/rfc9420.html#section-6-4) object which is then encrypted according to [NIP-44](44.md). However, instead of using the sender and receivers keys to derive a `conversation_key`, the NIP-44 encryption is done using a Nostr keypair generated from the MLS [`exporter_secret`](https://www.rfc-editor.org/rfc/rfc9420.html#section-8.5) to calculate the `conversation_key` value. Essentially, you use the hex-encoded `exporter_secret` value as the private key (used as the sender key), calculate the public key for that private key (used as the receiver key), and then proceed with the standard NIP-44 scheme to encrypt and decrypt messages.
|
||||||
|
- The `exporter_secret` value should be generated with a 32-byte length and labeled `nostr`. This `exporter_secret` value is rotated on each new epoch in the group. Clients should generate a new 32-byte value each time they process a valid `Commit` message.
|
||||||
|
- The `pubkey` is the hex-encoded public key of the ephemeral sender.
|
||||||
|
- The `h` tag is the nostr group ID value (from the Nostr Group Data Extension).
|
||||||
|
|
||||||
|
### Application Messages
|
||||||
|
|
||||||
|
Application messages are the messages that are sent within the group by members. These are contained within the `MLSMessage` object. The format of these messages should be unsigned Nostr events of the appropriate kind. For normal DM or group messages, clents SHOULD use `kind: 9` chat message events. If the user reacts to a message, it would be a `kind: 7` event, and so on.
|
||||||
|
|
||||||
|
This means that once the application message has been decrypted and deserialized, clients can store those events and treat them as any other Nostr event, effectively creating a private Nostr feed of the group's activity and taking advantage of all the features of Nostr.
|
||||||
|
|
||||||
|
These inner unsigned Nostr events MUST use the member's Nostr identity key for the `pubkey` field and clients MUST check that the identity of them member who sent the message matches the pubkey of the inner Nostr event.
|
||||||
|
|
||||||
|
These Nostr events MUST remain **unsigned** to ensure that if they were to leak to relays they would not be published publicly. These Nostr events MUST not include any "h" tags or other tags that would identify the group that they belong to.
|
||||||
|
|
||||||
|
### `Commit` Message race conditions
|
||||||
|
|
||||||
|
The MLS protocol is resilient to almost all messages arriving out of order. However, the order of `Commit` messages is important for the group state to move forward from one epoch to the next correctly. Given Nostr's nature as a decentralized network, it is possible for a client to receive 2 or more `Commit` messages all attempting to update to a new epoch at the same time.
|
||||||
|
|
||||||
|
Clients sending commit messages MUST wait until they receive acknowledgement from at least one relay that their Group Message Event with the `Commit` has been received before applying the commit to their own group state.
|
||||||
|
|
||||||
|
If a client receives 2 or more `Commit` messages attempting to change same epoch, they MUST apply only one of the `Commit` messages they receive, determined by the following:
|
||||||
|
|
||||||
|
1. Using the `created_at` timestamp on the kind `445` event. The `Commit` with the lowest value for `created_at` is the message to be applied. The other `Commit` message is discarded.
|
||||||
|
2. If the `created_at` timestamp is the same for two or more `Commit` messages, the `Commit` message with the lowest value for `id` field is the message to be applied.
|
||||||
|
|
||||||
|
Clients SHOULD retain previous group state for a short period of time in order to recover from forked group state.
|
18
README.md
18
README.md
|
@ -11,11 +11,10 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
- [Message Types](#message-types)
|
- [Message Types](#message-types)
|
||||||
- [Client to Relay](#client-to-relay)
|
- [Client to Relay](#client-to-relay)
|
||||||
- [Relay to Client](#relay-to-client)
|
- [Relay to Client](#relay-to-client)
|
||||||
- [Standardized Tags](#standardized-tags)
|
- [Common Tags](#common-tags)
|
||||||
- [Criteria for acceptance of NIPs](#criteria-for-acceptance-of-nips)
|
- [Criteria for acceptance of NIPs](#criteria-for-acceptance-of-nips)
|
||||||
- [Is this repository a centralizing factor?](#is-this-repository-a-centralizing-factor)
|
- [Is this repository a centralizing factor?](#is-this-repository-a-centralizing-factor)
|
||||||
- [How this repository works](#how-this-repository-works)
|
- [How this repository works](#how-this-repository-works)
|
||||||
- [Breaking Changes](#breaking-changes)
|
|
||||||
- [License](#license)
|
- [License](#license)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -99,7 +98,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
- [NIP-90: Data Vending Machines](90.md)
|
- [NIP-90: Data Vending Machines](90.md)
|
||||||
- [NIP-92: Media Attachments](92.md)
|
- [NIP-92: Media Attachments](92.md)
|
||||||
- [NIP-94: File Metadata](94.md)
|
- [NIP-94: File Metadata](94.md)
|
||||||
- [NIP-96: HTTP File Storage Integration](96.md)
|
- [NIP-96: HTTP File Storage Integration](96.md) --- **unrecommended**: replaced by blossom APIs
|
||||||
- [NIP-98: HTTP Auth](98.md)
|
- [NIP-98: HTTP Auth](98.md)
|
||||||
- [NIP-99: Classified Listings](99.md)
|
- [NIP-99: Classified Listings](99.md)
|
||||||
- [NIP-A0: Voice Messages](A0.md)
|
- [NIP-A0: Voice Messages](A0.md)
|
||||||
|
@ -107,6 +106,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
- [NIP-B7: Blossom](B7.md)
|
- [NIP-B7: Blossom](B7.md)
|
||||||
- [NIP-C0: Code Snippets](C0.md)
|
- [NIP-C0: Code Snippets](C0.md)
|
||||||
- [NIP-C7: Chats](C7.md)
|
- [NIP-C7: Chats](C7.md)
|
||||||
|
- [NIP-EE: E2EE Messaging using MLS Protocol](EE.md)
|
||||||
|
|
||||||
## Event Kinds
|
## Event Kinds
|
||||||
| kind | description | NIP |
|
| kind | description | NIP |
|
||||||
|
@ -143,6 +143,9 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
| `44` | Channel Mute User | [28](28.md) |
|
| `44` | Channel Mute User | [28](28.md) |
|
||||||
| `62` | Request to Vanish | [62](62.md) |
|
| `62` | Request to Vanish | [62](62.md) |
|
||||||
| `64` | Chess (PGN) | [64](64.md) |
|
| `64` | Chess (PGN) | [64](64.md) |
|
||||||
|
| `443` | KeyPackage | [EE](EE.md) |
|
||||||
|
| `444` | Welcome Message | [EE](EE.md) |
|
||||||
|
| `445` | Group Event | [EE](EE.md) |
|
||||||
| `818` | Merge Requests | [54](54.md) |
|
| `818` | Merge Requests | [54](54.md) |
|
||||||
| `1018` | Poll Response | [88](88.md) |
|
| `1018` | Poll Response | [88](88.md) |
|
||||||
| `1021` | Bid | [15](15.md) |
|
| `1021` | Bid | [15](15.md) |
|
||||||
|
@ -200,8 +203,9 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
| `10020` | Media follows | [51](51.md) |
|
| `10020` | Media follows | [51](51.md) |
|
||||||
| `10030` | User emoji list | [51](51.md) |
|
| `10030` | User emoji list | [51](51.md) |
|
||||||
| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) |
|
| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) |
|
||||||
|
| `10051` | KeyPackage Relays List | [EE](EE.md) |
|
||||||
| `10063` | User server list | [Blossom][blossom] |
|
| `10063` | User server list | [Blossom][blossom] |
|
||||||
| `10096` | File storage server list | [96](96.md) |
|
| `10096` | File storage server list | [96](96.md) (deprecated) |
|
||||||
| `10166` | Relay Monitor Announcement | [66](66.md) |
|
| `10166` | Relay Monitor Announcement | [66](66.md) |
|
||||||
| `10312` | Room Presence | [53](53.md) |
|
| `10312` | Room Presence | [53](53.md) |
|
||||||
| `10377` | Proxy Announcement | [Nostr Epoxy][nostr-epoxy] |
|
| `10377` | Proxy Announcement | [Nostr Epoxy][nostr-epoxy] |
|
||||||
|
@ -307,7 +311,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
|
||||||
| `AUTH` | used to send authentication challenges | [42](42.md) |
|
| `AUTH` | used to send authentication challenges | [42](42.md) |
|
||||||
| `COUNT` | used to send requested event counts to clients | [45](45.md) |
|
| `COUNT` | used to send requested event counts to clients | [45](45.md) |
|
||||||
|
|
||||||
## Standardized Tags
|
## Common Tags
|
||||||
|
|
||||||
| name | value | other parameters | NIP |
|
| name | value | other parameters | NIP |
|
||||||
| ----------------- | ------------------------------------ | ------------------------------- | -------------------------------------------------- |
|
| ----------------- | ------------------------------------ | ------------------------------- | -------------------------------------------------- |
|
||||||
|
@ -403,10 +407,6 @@ Standards may emerge in two ways: the first way is that someone starts doing som
|
||||||
|
|
||||||
These two ways of standardizing things are supported by this repository. Although the second is preferred, an effort will be made to codify standards emerged outside this repository into NIPs that can be later referenced and easily understood and implemented by others -- but obviously as in any human system discretion may be applied when standards are considered harmful.
|
These two ways of standardizing things are supported by this repository. Although the second is preferred, an effort will be made to codify standards emerged outside this repository into NIPs that can be later referenced and easily understood and implemented by others -- but obviously as in any human system discretion may be applied when standards are considered harmful.
|
||||||
|
|
||||||
## Breaking Changes
|
|
||||||
|
|
||||||
[Breaking Changes](BREAKING.md)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
All NIPs are public domain.
|
All NIPs are public domain.
|
||||||
|
|
Loading…
Reference in New Issue