upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--01.md13
-rw-r--r--03.md4
-rw-r--r--04.md2
-rw-r--r--05.md6
-rw-r--r--09.md2
-rw-r--r--11.md262
-rw-r--r--13.md2
-rw-r--r--17.md115
-rw-r--r--18.md24
-rw-r--r--19.md3
-rw-r--r--21.md20
-rw-r--r--22.md12
-rw-r--r--24.md13
-rw-r--r--25.md60
-rw-r--r--26.md4
-rw-r--r--29.md54
-rw-r--r--30.md5
-rw-r--r--34.md104
-rw-r--r--37.md39
-rw-r--r--38.md4
-rw-r--r--39.md11
-rw-r--r--40.md2
-rw-r--r--42.md6
-rw-r--r--43.md146
-rw-r--r--44.md115
-rw-r--r--45.md120
-rw-r--r--46.md17
-rw-r--r--47.md380
-rw-r--r--50.md2
-rw-r--r--51.md24
-rw-r--r--52.md144
-rw-r--r--53.md162
-rw-r--r--54.md144
-rw-r--r--55.md58
-rw-r--r--57.md7
-rw-r--r--58.md2
-rw-r--r--59.md35
-rw-r--r--60.md39
-rw-r--r--61.md26
-rw-r--r--62.md2
-rw-r--r--66.md231
-rw-r--r--69.md9
-rw-r--r--70.md2
-rw-r--r--71.md91
-rw-r--r--72.md52
-rw-r--r--73.md20
-rw-r--r--77.md175
-rw-r--r--84.md2
-rw-r--r--85.md166
-rw-r--r--86.md6
-rw-r--r--87.md142
-rw-r--r--88.md2
-rw-r--r--89.md4
-rw-r--r--90.md16
-rw-r--r--96.md2
-rw-r--r--99.md6
-rw-r--r--A0.md60
-rw-r--r--A4.md56
-rw-r--r--B0.md22
-rw-r--r--BE.md137
-rw-r--r--BREAKING.md68
-rw-r--r--C0.md4
-rw-r--r--EE.md292
-rw-r--r--README.md88
64 files changed, 2722 insertions, 1121 deletions
diff --git a/01.md b/01.md
index f7f592c..eb9f60c 100644
--- a/01.md
+++ b/01.md
@@ -4,7 +4,7 @@ NIP-01
4Basic protocol flow description 4Basic protocol flow description
5------------------------------- 5-------------------------------
6 6
7`draft` `mandatory` 7`draft` `mandatory` `relay`
8 8
9This NIP defines the basic protocol that should be implemented by everybody. New NIPs may add new optional (or mandatory) fields and messages and features to the structures and flows described here. 9This NIP defines the basic protocol that should be implemented by everybody. New NIPs may add new optional (or mandatory) fields and messages and features to the structures and flows described here.
10 10
@@ -14,7 +14,7 @@ Each user has a keypair. Signatures, public key, and encodings are done accordin
14 14
15The only object type that exists is the `event`, which has the following format on the wire: 15The only object type that exists is the `event`, which has the following format on the wire:
16 16
17```jsonc 17```yaml
18{ 18{
19 "id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>, 19 "id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>,
20 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 20 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
@@ -85,7 +85,7 @@ As a convention, all single-letter (only english alphabet letters: a-z, A-Z) key
85 85
86### Kinds 86### Kinds
87 87
88Kinds specify how clients should interpret the meaning of each event and the other fields of each event (e.g. an `"r"` tag may have a meaning in an event of kind 1 and an entirely different meaning in an event of kind 10002). Each NIP may define the meaning of a set of kinds that weren't defined elsewhere. [NIP-10](10.md), for instance, especifies the `kind:1` text note for social media applications. 88Kinds specify how clients should interpret the meaning of each event and the other fields of each event (e.g. an `"r"` tag may have a meaning in an event of kind 1 and an entirely different meaning in an event of kind 10002). Each NIP may define the meaning of a set of kinds that weren't defined elsewhere. [NIP-10](10.md), for instance, specifies the `kind:1` text note for social media applications.
89 89
90This NIP defines one basic kind: 90This NIP defines one basic kind:
91 91
@@ -120,7 +120,7 @@ Clients can send 3 types of messages, which must be JSON arrays, according to th
120 120
121`<filtersX>` is a JSON object that determines what events will be sent in that subscription, it can have the following attributes: 121`<filtersX>` is a JSON object that determines what events will be sent in that subscription, it can have the following attributes:
122 122
123```json 123```yaml
124{ 124{
125 "ids": <a list of event ids>, 125 "ids": <a list of event ids>,
126 "authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>, 126 "authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>,
@@ -144,7 +144,7 @@ All conditions of a filter that are specified must match for an event for it to
144 144
145A `REQ` message may contain multiple filters. In this case, events that match any of the filters are to be returned, i.e., multiple filters are to be interpreted as `||` conditions. 145A `REQ` message may contain multiple filters. In this case, events that match any of the filters are to be returned, i.e., multiple filters are to be interpreted as `||` conditions.
146 146
147The `limit` property of a filter is only valid for the initial query and MUST be ignored afterwards. When `limit: n` is present it is assumed that the events returned in the initial query will be the last `n` events ordered by the `created_at`. Newer events should appear first, and in the case of ties the event with the lowest id (first in lexical order) should be first. It is safe to return less events than `limit` specifies, but it is expected that relays do not return (much) more events than requested so clients don't get unnecessarily overwhelmed by data. 147The `limit` property of a filter is only valid for the initial query and MUST be ignored afterwards. When `limit: n` is present it is assumed that the events returned in the initial query will be the last `n` events ordered by the `created_at`. Newer events should appear first, and in the case of ties the event with the lowest id (first in lexical order) should be first. Relays SHOULD use the `limit` value to guide how many events are returned in the initial response. Returning fewer events is acceptable, but returning (much) more should be avoided to prevent overwhelming clients.
148 148
149### From relay to client: sending events and notices 149### From relay to client: sending events and notices
150 150
@@ -170,8 +170,9 @@ This NIP defines no rules for how `NOTICE` messages should be sent or treated.
170 * `["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]` 170 * `["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]`
171 * `["OK", "b1a649ebe8...", false, "restricted: not allowed to write."]` 171 * `["OK", "b1a649ebe8...", false, "restricted: not allowed to write."]`
172 * `["OK", "b1a649ebe8...", false, "error: could not connect to the database"]` 172 * `["OK", "b1a649ebe8...", false, "error: could not connect to the database"]`
173 * `["OK", "b1a649ebe8...", false, "mute: no one was listening to your ephemeral event and it wasn't handled in any way, it was ignored"]`
173- `CLOSED` messages MUST be sent in response to a `REQ` when the relay refuses to fulfill it. It can also be sent when a relay decides to kill a subscription on its side before a client has disconnected or sent a `CLOSE`. This message uses the same pattern of `OK` messages with the machine-readable prefix and human-readable message. Some examples: 174- `CLOSED` messages MUST be sent in response to a `REQ` when the relay refuses to fulfill it. It can also be sent when a relay decides to kill a subscription on its side before a client has disconnected or sent a `CLOSE`. This message uses the same pattern of `OK` messages with the machine-readable prefix and human-readable message. Some examples:
174 * `["CLOSED", "sub1", "unsupported: filter contains unknown elements"]` 175 * `["CLOSED", "sub1", "unsupported: filter contains unknown elements"]`
175 * `["CLOSED", "sub1", "error: could not connect to the database"]` 176 * `["CLOSED", "sub1", "error: could not connect to the database"]`
176 * `["CLOSED", "sub1", "error: shutting down idle subscription"]` 177 * `["CLOSED", "sub1", "error: shutting down idle subscription"]`
177- The standardized machine-readable prefixes for `OK` and `CLOSED` are: `duplicate`, `pow`, `blocked`, `rate-limited`, `invalid`, `restricted`, and `error` for when none of that fits. 178- The standardized machine-readable prefixes for `OK` and `CLOSED` are: `duplicate`, `pow`, `blocked`, `rate-limited`, `invalid`, `restricted`, `mute` and `error` for when none of that fits.
diff --git a/03.md b/03.md
index 74e010c..3cd40a0 100644
--- a/03.md
+++ b/03.md
@@ -12,8 +12,8 @@ This NIP defines an event with `kind:1040` that can contain an [OpenTimestamps](
12{ 12{
13 "kind": 1040 13 "kind": 1040
14 "tags": [ 14 "tags": [
15 ["e", <event-id>, <relay-url>], 15 ["e", <target-event-id>, <relay-url>],
16 ["alt", "opentimestamps attestation"] 16 ["k", "<target-event-kind>"]
17 ], 17 ],
18 "content": <base64-encoded OTS file data> 18 "content": <base64-encoded OTS file data>
19} 19}
diff --git a/04.md b/04.md
index a561a2f..107d872 100644
--- a/04.md
+++ b/04.md
@@ -6,7 +6,7 @@ NIP-04
6Encrypted Direct Message 6Encrypted Direct Message
7------------------------ 7------------------------
8 8
9`final` `unrecommended` `optional` 9`final` `unrecommended` `optional` `relay`
10 10
11A special event with kind `4`, meaning "encrypted direct message". It is supposed to have the following attributes: 11A special event with kind `4`, meaning "encrypted direct message". It is supposed to have the following attributes:
12 12
diff --git a/05.md b/05.md
index 7e823fb..57a3936 100644
--- a/05.md
+++ b/05.md
@@ -6,11 +6,11 @@ Mapping Nostr keys to DNS-based internet identifiers
6 6
7`final` `optional` 7`final` `optional`
8 8
9On events of kind `0` (`user metadata`) one can specify the key `"nip05"` with an [internet identifier](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1) (an email-like address) as the value. Although there is a link to a very liberal "internet identifier" specification above, NIP-05 assumes the `<local-part>` part will be restricted to the characters `a-z0-9-_.`, case-insensitive. 9On events of kind `0` (`user metadata`) one can specify the key `"nip05"` with an [internet identifier](https://datatracker.ietf.org/doc/html/rfc5322#section-3.4.1) (an email-like address) as the value. Although there is a link to a very liberal "internet identifier" specification above, the `<local-part>` part MUST only use characters `a-z0-9-_.`.
10 10
11Upon seeing that, the client splits the identifier into `<local-part>` and `<domain>` and use these values to make a GET request to `https://<domain>/.well-known/nostr.json?name=<local-part>`. 11Upon seeing that, the client splits the identifier into `<local-part>` and `<domain>` and use these values to make a GET request to `https://<domain>/.well-known/nostr.json?name=<local-part>`.
12 12
13The result should be a JSON document object with a key `"names"` that should then be a mapping of names to hex formatted public keys. If the public key for the given `<name>` matches the `pubkey` from the `user metadata` event, the client then concludes that the given pubkey can indeed be referenced by its identifier. 13The result should be a JSON document object with a key `"names"` that should then be a mapping of names to hex formatted public keys, in lowercase. If the public key for the given `<name>` matches the `pubkey` from the `user metadata` event, the client then concludes that the given pubkey can indeed be referenced by its identifier.
14 14
15### Example 15### Example
16 16
@@ -73,7 +73,7 @@ For example, if after finding that `bob@bob.com` has the public key `abc...def`,
73 73
74### Public keys must be in hex format 74### Public keys must be in hex format
75 75
76Keys must be returned in hex format. Keys in NIP-19 `npub` format are only meant to be used for display in client UIs, not in this NIP. 76Keys must be returned in hex format, in lowercase. Keys in NIP-19 `npub` format are only meant to be used for display in client UIs, not in this NIP.
77 77
78### Showing just the domain as an identifier 78### Showing just the domain as an identifier
79 79
diff --git a/09.md b/09.md
index 23ffeab..f061464 100644
--- a/09.md
+++ b/09.md
@@ -4,7 +4,7 @@ NIP-09
4Event Deletion Request 4Event Deletion Request
5---------------------- 5----------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9A special event with kind `5`, meaning "deletion request" is defined as having a list of one or more `e` or `a` tags, each referencing an event the author is requesting to be deleted. Deletion requests SHOULD include a `k` tag for the kind of each event being requested for deletion. 9A special event with kind `5`, meaning "deletion request" is defined as having a list of one or more `e` or `a` tags, each referencing an event the author is requesting to be deleted. Deletion requests SHOULD include a `k` tag for the kind of each event being requested for deletion.
10 10
diff --git a/11.md b/11.md
index 8229bd4..d1acab7 100644
--- a/11.md
+++ b/11.md
@@ -4,9 +4,9 @@ NIP-11
4Relay Information Document 4Relay Information Document
5-------------------------- 5--------------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9Relays may provide server metadata to clients to inform them of capabilities, administrative contacts, and various server attributes. This is made available as a JSON document over HTTP, on the same URI as the relay's websocket. 9Relays may provide server metadata to clients to inform them of capabilities, administrative contacts, and various server attributes. This is made available as a JSON document over HTTP, on the same URI as the relay's websocket.
10 10
11When a relay receives an HTTP(s) request with an `Accept` header of `application/nostr+json` to a URI supporting WebSocket upgrades, they SHOULD return a document with the following structure. 11When a relay receives an HTTP(s) request with an `Accept` header of `application/nostr+json` to a URI supporting WebSocket upgrades, they SHOULD return a document with the following structure.
12 12
@@ -17,14 +17,12 @@ When a relay receives an HTTP(s) request with an `Accept` header of `application
17 "banner": <a link to an image (e.g. in .jpg, or .png format)>, 17 "banner": <a link to an image (e.g. in .jpg, or .png format)>,
18 "icon": <a link to an icon (e.g. in .jpg, or .png format>, 18 "icon": <a link to an icon (e.g. in .jpg, or .png format>,
19 "pubkey": <administrative contact pubkey>, 19 "pubkey": <administrative contact pubkey>,
20 "self": <relay's own pubkey>,
20 "contact": <administrative alternate contact>, 21 "contact": <administrative alternate contact>,
21 "supported_nips": <a list of NIP numbers supported by the relay>, 22 "supported_nips": <a list of NIP numbers supported by the relay>,
22 "software": <string identifying relay software URL>, 23 "software": <string identifying relay software URL>,
23 "version": <string version identifier> 24 "version": <string version identifier>,
24 "privacy_policy": <a link to a text file describing the relay's privacy policy>, 25 "terms_of_service": <a link to a text file describing the relay's term of service>
25 "terms_of_service": <a link to a text file describing the relay's term of service>,
26
27
28} 26}
29``` 27```
30 28
@@ -35,11 +33,11 @@ Field Descriptions
35 33
36### Name 34### Name
37 35
38A relay may select a `name` for use in client software. This is a string, and SHOULD be less than 30 characters to avoid client truncation. 36A relay may select a `name` for use in client software. This is a string, and SHOULD be less than 30 characters to avoid client truncation.
39 37
40### Description 38### Description
41 39
42Detailed plain-text information about the relay may be contained in the `description` string. It is recommended that this contain no markup, formatting or line breaks for word wrapping, and simply use double newline characters to separate paragraphs. There are no limitations on length. 40Detailed plain-text information about the relay may be contained in the `description` string. It is recommended that this contain no markup, formatting or line breaks for word wrapping, and simply use double newline characters to separate paragraphs. There are no limitations on length.
43 41
44### Banner 42### Banner
45 43
@@ -58,36 +56,34 @@ Icon is a compact visual representation of the relay for use in UI with limited
58 56
59### Pubkey 57### Pubkey
60 58
61An administrative contact may be listed with a `pubkey`, in the same format as Nostr events (32-byte hex for a `secp256k1` public key). If a contact is listed, this provides clients with a recommended address to send encrypted direct messages (See [NIP-17](17.md)) to a system administrator. Expected uses of this address are to report abuse or illegal content, file bug reports, or request other technical assistance. 59An administrative contact may be listed with a `pubkey`, in the same format as Nostr events (32-byte hex for a `secp256k1` public key). If a contact is listed, this provides clients with a recommended address to send encrypted direct messages (See [NIP-17](17.md)) to a system administrator. Expected uses of this address are to report abuse or illegal content, file bug reports, or request other technical assistance.
62 60
63Relay operators have no obligation to respond to direct messages. 61Relay operators have no obligation to respond to direct messages.
64 62
63### Self
64
65A relay MAY maintain an identity independent from its administrator using the `self` field, which MUST be a 32-byte hex public key. This allows relays to respond to requests with events published either in advance or on demand by their own key.
66
65### Contact 67### Contact
66 68
67An alternative contact may be listed under the `contact` field as well, with the same purpose as `pubkey`. Use of a Nostr public key and direct message SHOULD be preferred over this. Contents of this field SHOULD be a URI, using schemes such as `mailto` or `https` to provide users with a means of contact. 69An alternative contact may be listed under the `contact` field as well, with the same purpose as `pubkey`. Use of a Nostr public key and direct message SHOULD be preferred over this. Contents of this field SHOULD be a URI, using schemes such as `mailto` or `https` to provide users with a means of contact.
68 70
69### Supported NIPs 71### Supported NIPs
70 72
71As the Nostr protocol evolves, some functionality may only be available by relays that implement a specific `NIP`. This field is an array of the integer identifiers of `NIP`s that are implemented in the relay. Examples would include `1`, for `"NIP-01"` and `9`, for `"NIP-09"`. Client-side `NIPs` SHOULD NOT be advertised, and can be ignored by clients. 73As the Nostr protocol evolves, some functionality may only be available by relays that implement a specific `NIP`. This field is an array of the integer identifiers of `NIP`s that are implemented in the relay. Examples would include `1`, for `"NIP-01"` and `9`, for `"NIP-09"`. Client-side `NIPs` SHOULD NOT be advertised, and can be ignored by clients.
72 74
73### Software 75### Software
74 76
75The relay server implementation MAY be provided in the `software` attribute. If present, this MUST be a URL to the project's homepage. 77The relay server implementation MAY be provided in the `software` attribute. If present, this MUST be a URL to the project's homepage.
76 78
77### Version 79### Version
78 80
79The relay MAY choose to publish its software version as a string attribute. The string format is defined by the relay implementation. It is recommended this be a version number or commit identifier. 81The relay MAY choose to publish its software version as a string attribute. The string format is defined by the relay implementation. It is recommended this be a version number or commit identifier.
80
81### Privacy Policy
82
83The relay owner/admin MAY choose to link to a privacy policy document, which describes how the relay utilizes user data. Data collection, data usage, data retention, monetization of data, and third party data sharing SHOULD be included.
84 82
85### Terms of Service 83### Terms of Service
86 84
87The relay owner/admin MAY choose to link to a terms of service document. 85The relay owner/admin MAY choose to link to a terms of service document.
88 86
89
90
91Extra Fields 87Extra Fields
92------------ 88------------
93 89
@@ -162,113 +158,7 @@ a specific niche kind or content. Normal anti-spam heuristics, for example, do n
162 158
163- `created_at_upper_limit`: 'created_at' upper limit 159- `created_at_upper_limit`: 'created_at' upper limit
164 160
165- `default_limit`: The maximum returned events if you send a filter with the limit set to 0. 161- `default_limit`: The maximum returned events if you send a filter without a `limit`.
166
167### Event Retention
168
169There may be a cost associated with storing data forever, so relays
170may wish to state retention times. The values stated here are defaults
171for unauthenticated users and visitors. Paid users would likely have
172other policies.
173
174Retention times are given in seconds, with `null` indicating infinity.
175If zero is provided, this means the event will not be stored at
176all, and preferably an error will be provided when those are received.
177
178```jsonc
179{
180 "retention": [
181 {"kinds": [0, 1, [5, 7], [40, 49]], "time": 3600},
182 {"kinds": [[40000, 49999]], "time": 100},
183 {"kinds": [[30000, 39999]], "count": 1000},
184 {"time": 3600, "count": 10000}
185 ],
186 // other fields...
187}
188```
189
190`retention` is a list of specifications: each will apply to either all kinds, or
191a subset of kinds. Ranges may be specified for the kind field as a tuple of inclusive
192start and end values. Events of indicated kind (or all) are then limited to a `count`
193and/or time period.
194
195It is possible to effectively blacklist Nostr-based protocols that rely on
196a specific `kind` number, by giving a retention time of zero for those `kind` values.
197While that is unfortunate, it does allow clients to discover servers that will
198support their protocol quickly via a single HTTP fetch.
199
200There is no need to specify retention times for _ephemeral events_ since they are not retained.
201
202### Content Limitations
203
204Some relays may be governed by the arbitrary laws of a nation state. This
205may limit what content can be stored in clear-text on those relays. All
206clients are encouraged to use encryption to work around this limitation.
207
208It is not possible to describe the limitations of each country's laws
209and policies which themselves are typically vague and constantly shifting.
210
211Therefore, this field allows the relay operator to indicate which
212countries' laws might end up being enforced on them, and then
213indirectly on their users' content.
214
215Users should be able to avoid relays in countries they don't like,
216and/or select relays in more favorable zones. Exposing this
217flexibility is up to the client software.
218
219```jsonc
220{
221 "relay_countries": [ "CA", "US" ],
222 // other fields...
223}
224```
225
226- `relay_countries`: a list of two-level ISO country codes (ISO 3166-1 alpha-2) whose
227 laws and policies may affect this relay. `EU` may be used for European Union countries. A `*` can be used for global relays.
228
229Remember that a relay may be hosted in a country which is not the
230country of the legal entities who own the relay, so it's very
231likely a number of countries are involved.
232
233
234### Community Preferences
235
236For public text notes at least, a relay may try to foster a
237local community. This would encourage users to follow the global
238feed on that relay, in addition to their usual individual follows.
239To support this goal, relays MAY specify some of the following values.
240
241```jsonc
242{
243 "language_tags": ["en", "en-419"],
244 "tags": ["sfw-only", "bitcoin-only", "anime"],
245 "posting_policy": "https://example.com/posting-policy.html",
246 // other fields...
247}
248```
249
250- `language_tags` is an ordered list
251 of [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag) indicating
252 the major languages spoken on the relay. A `*` can be used for global relays.
253
254- `tags` is a list of limitations on the topics to be discussed.
255 For example `sfw-only` indicates that only "Safe For Work" content
256 is encouraged on this relay. This relies on assumptions of what the
257 "work" "community" feels "safe" talking about. In time, a common
258 set of tags may emerge that allow users to find relays that suit
259 their needs, and client software will be able to parse these tags easily.
260 The `bitcoin-only` tag indicates that any *altcoin*, *"crypto"* or *blockchain*
261 comments will be ridiculed without mercy.
262
263- `posting_policy` is a link to a human-readable page which specifies the
264 community policies for the relay. In cases where `sfw-only` is True, it's
265 important to link to a page which gets into the specifics of your posting policy.
266
267The `description` field should be used to describe your community
268goals and values, in brief. The `posting_policy` is for additional
269detail and legal terms. Use the `tags` field to signify limitations
270on content, or topics to be discussed, which could be machine
271processed by appropriate client software.
272 162
273### Pay-to-Relay 163### Pay-to-Relay
274 164
@@ -288,82 +178,68 @@ Relays that require payments may want to expose their fee schedules.
288 178
289### Examples 179### Examples
290 180
291As of 25 March 2025 the following command provided these results: 181```yaml
292 182~> curl -H "Accept: application/nostr+json" https://nostr.wine | jq
293```bash
294curl -H "Accept: application/nostr+json" https://jellyfish.land | jq
295```
296
297```json
298{ 183{
299 "name": "JellyFish", 184 "contact": "wino@nostr.wine",
300 "description": "Stay Immortal!", 185 "description": "A paid nostr relay for wine enthusiasts and everyone else.",
301 "banner": "https://image.nostr.build/7fdefea2dec1f1ec25b8ce69362566c13b2b7f13f1726c2e4584f05f64f62496.jpg",
302 "pubkey": "bf2bee5281149c7c350f5d12ae32f514c7864ff10805182f4178538c2c421007",
303 "contact": "hi@dezh.tech",
304 "software": "https://github.com/dezh-tech/immortal",
305 "supported_nips": [
306 1,
307 9,
308 11,
309 13,
310 17,
311 40,
312 42,
313 59,
314 62,
315 70
316 ],
317 "version": "immortal - 0.0.9",
318 "relay_countries": [
319 "*"
320 ],
321 "language_tags": [
322 "*"
323 ],
324 "tags": [],
325 "posting_policy": "https://jellyfish.land/tos.txt",
326 "payments_url": "https://jellyfish.land/relay",
327 "icon": "https://image.nostr.build/2547e9ec4b23589e09bc7071e0806c3d4293f76284c58ff331a64bce978aaee8.jpg",
328 "retention": [],
329 "fees": { 186 "fees": {
330 "subscription": [ 187 "admission": [
331 {
332 "amount": 3000,
333 "period": 2628003,
334 "unit": "sats"
335 },
336 { 188 {
337 "amount": 8000, 189 "amount": 18888000,
338 "period": 7884009, 190 "unit": "msats"
339 "unit": "sats"
340 },
341 {
342 "amount": 15000,
343 "period": 15768018,
344 "unit": "sats"
345 },
346 {
347 "amount": 28000,
348 "period": 31536036,
349 "unit": "sats"
350 } 191 }
351 ] 192 ]
352 }, 193 },
194 "icon": "https://image.nostr.build/30acdce4a81926f386622a07343228ae99fa68d012d54c538c0b2129dffe400c.png",
353 "limitation": { 195 "limitation": {
354 "auth_required": false, 196 "auth_required": false,
355 "max_message_length": 70000, 197 "created_at_lower_limit": 94608000,
356 "max_subid_length": 256, 198 "created_at_upper_limit": 300,
357 "max_subscriptions": 350, 199 "max_event_tags": 4000,
200 "max_limit": 1000,
201 "max_message_length": 524288,
202 "max_subid_length": 71,
203 "max_subscriptions": 50,
358 "min_pow_difficulty": 0, 204 "min_pow_difficulty": 0,
359 "payment_required": true, 205 "payment_required": true,
360 "restricted_writes": true, 206 "restricted_writes": true
207 },
208 "name": "nostr.wine",
209 "payments_url": "https://nostr.wine/invoices",
210 "pubkey": "4918eb332a41b71ba9a74b1dc64276cfff592e55107b93baae38af3520e55975",
211 "software": "https://nostr.wine",
212 "supported_nips": [ 1, 2, 4, 9, 11, 40, 42, 50, 70, 77 ],
213 "terms_of_service": "https://nostr.wine/terms",
214 "version": "0.3.3"
215}
216
217~> curl -H "Accept: application/nostr+json" https://nostr.land | jq
218{
219 "description": "[✨ NFDB] nostr.land family of relays (fi-01 [tiger])",
220 "name": "[✨ NFDB] nostr.land",
221 "pubkey": "52b4a076bcbbbdc3a1aefa3735816cf74993b1b8db202b01c883c58be7fad8bd",
222 "software": "NFDB",
223 "icon": "https://i.nostr.build/b3thno790aodH8lE.jpg",
224 "supported_nips": [ 1, 2, 4, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 27, 28, 30, 31, 32, 34, 35, 36, 37, 38, 39, 40, 42, 44, 46, 47, 48, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 65, 68, 69, 71, 72, 73, 75, 78, 84, 88, 89, 90, 92, 99 ],
225 "version": "1.0.0",
226 "limitation": {
227 "payment_required": true,
228 "max_message_length": 65535,
361 "max_event_tags": 2000, 229 "max_event_tags": 2000,
362 "max_content_length": 70000, 230 "max_subscriptions": 200,
363 "created_at_lower_limit": 0, 231 "auth_required": false
364 "created_at_upper_limit": 2147483647, 232 },
365 "default_limit": 500, 233 "payments_url": "https://nostr.land",
366 "max_limit": 5000 234 "fees": {
367 } 235 "subscription": [
236 {
237 "amount": 4000000,
238 "unit": "msats",
239 "period": 2592000
240 }
241 ]
242 },
243 "terms_of_service": "https://nostr.land/terms"
368} 244}
369``` 245```
diff --git a/13.md b/13.md
index cf5b1ac..d7e7725 100644
--- a/13.md
+++ b/13.md
@@ -4,7 +4,7 @@ NIP-13
4Proof of Work 4Proof of Work
5------------- 5-------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9This NIP defines a way to generate and interpret Proof of Work for nostr notes. Proof of Work (PoW) is a way to add a proof of computational work to a note. This is a bearer proof that all relays and clients can universally validate with a small amount of code. This proof can be used as a means of spam deterrence. 9This NIP defines a way to generate and interpret Proof of Work for nostr notes. Proof of Work (PoW) is a way to add a proof of computational work to a note. This is a bearer proof that all relays and clients can universally validate with a small amount of code. This proof can be used as a means of spam deterrence.
10 10
diff --git a/17.md b/17.md
index 4383308..3ccdc77 100644
--- a/17.md
+++ b/17.md
@@ -4,34 +4,40 @@ NIP-17
4Private Direct Messages 4Private Direct Messages
5----------------------- 5-----------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9This NIP defines an encrypted direct messaging scheme using [NIP-44](44.md) encryption and [NIP-59](59.md) seals and gift wraps. 9This NIP defines an encrypted chat scheme which uses [NIP-44](44.md) encryption and [NIP-59](59.md) seals and gift wraps.
10 10
11## Direct Message Kind 11Any event sent to an encrypted chat MUST NOT be signed, and MUST be encrypted as described in [NIP-59](./59.md) and illustrated below. Omitting signatures makes messages deniable in case they are accidentally or maliciously leaked, while still allowing the recipient to authenticate them.
12
13By convention, `kind 14` direct messages, `kind 15` file messages, and [`kind 7` reactions](./25.md) may be sent to an encrypted chat.
14
15## Kind Definitions
16
17### Chat Message
12 18
13Kind `14` is a chat message. `p` tags identify one or more receivers of the message. 19Kind `14` is a chat message. `p` tags identify one or more receivers of the message.
14 20
15```jsonc 21```jsonc
16{ 22{
17 "id": "<usual hash>", 23 "id": "<usual hash>",
18  "pubkey": "<sender-pubkey>", 24 "pubkey": "<sender-pubkey>",
19 "created_at": "<current-time>", 25 "created_at": "<current-time>",
20  "kind": 14, 26 "kind": 14,
21  "tags": [ 27 "tags": [
22    ["p", "<receiver-1-pubkey>", "<relay-url>"], 28 ["p", "<receiver-1-pubkey>", "<relay-url>"],
23    ["p", "<receiver-2-pubkey>", "<relay-url>"], 29 ["p", "<receiver-2-pubkey>", "<relay-url>"],
24    ["e", "<kind-14-id>", "<relay-url>"] // if this is a reply 30 ["e", "<kind-14-id>", "<relay-url>"] // if this is a reply
25 ["subject", "<conversation-title>"], 31 ["subject", "<conversation-title>"],
26    // rest of tags... 32 // rest of tags...
27  ], 33 ],
28  "content": "<message-in-plain-text>", 34 "content": "<message-in-plain-text>",
29} 35}
30``` 36```
31 37
32`.content` MUST be plain text. Fields `id` and `created_at` are required. 38`.content` MUST be plain text. Fields `id` and `created_at` are required.
33 39
34An `e` tag denotes the direct parent message this post is replying to. 40An `e` tag denotes the direct parent message this post is replying to.
35 41
36`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md). 42`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md).
37 43
@@ -39,9 +45,7 @@ An `e` tag denotes the direct parent message this post is replying to.
39["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"] 45["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"]
40``` 46```
41 47
42Kind `14`s MUST never be signed. If it is signed, the message might leak to relays and become **fully public**. 48## File Message
43
44## File Message Kind
45 49
46```jsonc 50```jsonc
47{ 51{
@@ -65,21 +69,20 @@ Kind `14`s MUST never be signed. If it is signed, the message might leak to rela
65} 69}
66``` 70```
67 71
68Kind 15 is used for sending encrypted file event messages: 72Kind `15` is used for sending encrypted file event messages:
69 73
70- `file-type`: Specifies the MIME type of the attached file (e.g., `image/jpeg`, `audio/mpeg`, etc.). 74- `file-type`: Specifies the MIME type of the attached file (e.g., `image/jpeg`, `audio/mpeg`, etc.) before encryption.
71- `encryption-algorithm`: Indicates the encryption algorithm used for encrypting the file. Supported algorithms may include `aes-gcm`, `chacha20-poly1305`,`aes-cbc` etc. 75- `encryption-algorithm`: Indicates the encryption algorithm used for encrypting the file. Supported algorithms: `aes-gcm`.
72- `decryption-key`: The decryption key that will be used by the recipient to decrypt the file. 76- `decryption-key`: The decryption key that will be used by the recipient to decrypt the file.
73- `decryption-nonce`: The decryption nonce that will be used by the recipient to decrypt the file. 77- `decryption-nonce`: The decryption nonce that will be used by the recipient to decrypt the file.
74- `content`: The URL of the file (`<file-url>`). 78- `content`: The URL of the file (`<file-url>`).
75- `x` containing the SHA-256 hexencoded string of the file. 79- `x` containing the SHA-256 hexencoded string of the encrypted file.
76- `size` (optional) size of file in bytes 80- `ox` containing the SHA-256 hexencoded string of the file before encryption.
77- `dim` (optional) size of the file in pixels in the form `<width>x<height>` 81- `size` (optional) size of the encrypted file in bytes
82- `dim` (optional) size in pixels in the form `<width>x<height>`
78- `blurhash`(optional) the [blurhash](https://github.com/woltapp/blurhash) to show while the client is loading the file 83- `blurhash`(optional) the [blurhash](https://github.com/woltapp/blurhash) to show while the client is loading the file
79- `thumb` (optional) URL of thumbnail with same aspect ratio (encrypted with the same key, nonce) 84- `thumb` (optional) URL of thumbnail with same aspect ratio (encrypted with the same key, nonce)
80- `fallback` (optional) zero or more fallback file sources in case `url` fails 85- `fallback` (optional) zero or more fallback file sources in case `url` fails (encrypted with the same key, nonce)
81
82Just like kind 14, kind `15`s MUST never be signed.
83 86
84## Chat Rooms 87## Chat Rooms
85 88
@@ -87,34 +90,34 @@ The set of `pubkey` + `p` tags defines a chat room. If a new `p` tag is added or
87 90
88Clients SHOULD render messages of the same room in a continuous thread. 91Clients SHOULD render messages of the same room in a continuous thread.
89 92
90An optional `subject` tag defines the current name/topic of the conversation. Any member can change the topic by simply submitting a new `subject` to an existing `pubkey` + `p`-tags room. There is no need to send `subject` in every message. The newest `subject` in the thread is the subject of the conversation. 93An optional `subject` tag defines the current name/topic of the conversation. Any member can change the topic by simply submitting a new `subject` to an existing `pubkey` + `p` tags room. There is no need to send `subject` in every message. The newest `subject` in the chat room is the subject of the conversation.
91 94
92## Encrypting 95## Encrypting
93 96
94Following [NIP-59](59.md), the **unsigned** `kind:14` & `kind:15` chat messages must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually. 97Following [NIP-59](59.md), the **unsigned** chat messages must be sealed (`kind:13`) and then gift-wrapped (`kind:1059`) to each receiver and the sender individually.
95 98
96```jsonc 99```js
97{ 100{
98 "id": "<usual hash>", 101 "id": "<usual hash>",
99  "pubkey": randomPublicKey, 102 "pubkey": randomPublicKey,
100  "created_at": randomTimeUpTo2DaysInThePast(), 103 "created_at": randomTimeUpTo2DaysInThePast(),
101 "kind": 1059, // gift wrap 104 "kind": 1059, // gift wrap
102  "tags": [ 105 "tags": [
103    ["p", receiverPublicKey, "<relay-url>"] // receiver 106 ["p", receiverPublicKey, "<relay-url>"] // receiver
104  ], 107 ],
105  "content": nip44Encrypt( 108 "content": nip44Encrypt(
106    { 109 {
107 "id": "<usual hash>", 110 "id": "<usual hash>",
108      "pubkey": senderPublicKey, 111 "pubkey": senderPublicKey,
109      "created_at": randomTimeUpTo2DaysInThePast(), 112 "created_at": randomTimeUpTo2DaysInThePast(),
110      "kind": 13, // seal 113 "kind": 13, // seal
111      "tags": [], // no tags 114 "tags": [], // no tags
112      "content": nip44Encrypt(unsignedKind14, senderPrivateKey, receiverPublicKey), 115 "content": nip44Encrypt(unsignedKind14, senderPrivateKey, receiverPublicKey),
113      "sig": "<signed by senderPrivateKey>" 116 "sig": "<signed by senderPrivateKey>"
114    }, 117 },
115    randomPrivateKey, receiverPublicKey 118 randomPrivateKey, receiverPublicKey
116  ), 119 ),
117  "sig": "<signed by randomPrivateKey>" 120 "sig": "<signed by randomPrivateKey>"
118} 121}
119``` 122```
120 123
@@ -124,9 +127,9 @@ Clients MUST verify if pubkey of the `kind:13` is the same pubkey on the `kind:1
124 127
125Clients SHOULD randomize `created_at` in up to two days in the past in both the seal and the gift wrap to make sure grouping by `created_at` doesn't reveal any metadata. 128Clients SHOULD randomize `created_at` in up to two days in the past in both the seal and the gift wrap to make sure grouping by `created_at` doesn't reveal any metadata.
126 129
127The gift wrap's `p`-tag can be the receiver's main pubkey or an alias key created to receive DMs without exposing the receiver's identity. 130The gift wrap's `p` tag can be the receiver's main pubkey or an alias key created to receive DMs without exposing the receiver's identity.
128 131
129Clients CAN offer disappearing messages by setting an `expiration` tag in the gift wrap of each receiver or by not generating a gift wrap to the sender's public key 132Clients MAY offer disappearing messages by setting an `expiration` tag in the gift wrap of each receiver or by not generating a gift wrap to the sender's public key. This tag SHOULD be included on the `kind 13` seal as well, in case it leaks.
130 133
131## Publishing 134## Publishing
132 135
@@ -144,15 +147,13 @@ Kind `10050` indicates the user's preferred relays to receive DMs. The event MUS
144} 147}
145``` 148```
146 149
147Clients SHOULD publish kind `14` events to the `10050`-listed relays. If that is not found that indicates the user is not ready to receive messages under this NIP and clients shouldn't try. 150Clients SHOULD publish the gift-wrapped `kind 1059` events that contain the sealed rumors to the relays listed in the recipient’s kind 10050 event. If that is not found that indicates the user is not ready to receive messages under this NIP and clients shouldn't try.
148 151
149## Relays 152## Relays
150 153
151It's advisable that relays do not serve `kind:1059` to clients other than the ones tagged in them. 154Relays MAY protect message metadata by only serving `kind:1059` events to users p-tagged on the event (enforced using [NIP 42 AUTH](./42.md)).
152 155
153It's advisable that users choose relays that conform to these practices. 156Clients SHOULD guide users to keep `kind:10050` lists small (1-3 relays) and SHOULD spread them to as many relays as viable.
154
155Clients SHOULD guide users to keep `kind:10050` lists small (1-3 relays) and SHOULD spread it to as many relays as viable.
156 157
157## Benefits & Limitations 158## Benefits & Limitations
158 159
@@ -169,12 +170,6 @@ This NIP offers the following privacy and security features:
169 170
170The main limitation of this approach is having to send a separate encrypted event to each receiver. Group chats with more than 100 participants should find a more suitable messaging scheme. 171The main limitation of this approach is having to send a separate encrypted event to each receiver. Group chats with more than 100 participants should find a more suitable messaging scheme.
171 172
172## Implementation
173
174Clients implementing this NIP should by default only connect to the set of relays found in their `kind:10050` list. From that they should be able to load all messages both sent and received as well as get new live updates, making it for a very simple and lightweight implementation that should be fast.
175
176When sending a message to anyone, clients must then connect to the relays in the receiver's `kind:10050` and send the events there but can disconnect right after unless more messages are expected to be sent (e.g. the chat tab is still selected). Clients should also send a copy of their outgoing messages to their own `kind:10050` relay set.
177
178## Examples 173## Examples
179 174
180This example sends the message `Hola, que tal?` from `nsec1w8udu59ydjvedgs3yv5qccshcj8k05fh3l60k9x57asjrqdpa00qkmr89m` to `nsec12ywtkplvyq5t6twdqwwygavp5lm4fhuang89c943nf2z92eez43szvn4dt`. 175This example sends the message `Hola, que tal?` from `nsec1w8udu59ydjvedgs3yv5qccshcj8k05fh3l60k9x57asjrqdpa00qkmr89m` to `nsec12ywtkplvyq5t6twdqwwygavp5lm4fhuang89c943nf2z92eez43szvn4dt`.
@@ -188,7 +183,7 @@ The two final GiftWraps, one to the receiver and the other to the sender, respec
188 "created_at":1703128320, 183 "created_at":1703128320,
189 "kind":1059, 184 "kind":1059,
190 "tags":[ 185 "tags":[
191 [ "p", "918e2da906df4ccd12c8ac672d8335add131a4cf9d27ce42b3bb3625755f0788"] 186 ["p", "918e2da906df4ccd12c8ac672d8335add131a4cf9d27ce42b3bb3625755f0788"]
192 ], 187 ],
193 "content":"AsqzdlMsG304G8h08bE67dhAR1gFTzTckUUyuvndZ8LrGCvwI4pgC3d6hyAK0Wo9gtkLqSr2rT2RyHlE5wRqbCOlQ8WvJEKwqwIJwT5PO3l2RxvGCHDbd1b1o40ZgIVwwLCfOWJ86I5upXe8K5AgpxYTOM1BD+SbgI5jOMA8tgpRoitJedVSvBZsmwAxXM7o7sbOON4MXHzOqOZpALpS2zgBDXSAaYAsTdEM4qqFeik+zTk3+L6NYuftGidqVluicwSGS2viYWr5OiJ1zrj1ERhYSGLpQnPKrqDaDi7R1KrHGFGyLgkJveY/45y0rv9aVIw9IWF11u53cf2CP7akACel2WvZdl1htEwFu/v9cFXD06fNVZjfx3OssKM/uHPE9XvZttQboAvP5UoK6lv9o3d+0GM4/3zP+yO3C0NExz1ZgFmbGFz703YJzM+zpKCOXaZyzPjADXp8qBBeVc5lmJqiCL4solZpxA1865yPigPAZcc9acSUlg23J1dptFK4n3Tl5HfSHP+oZ/QS/SHWbVFCtq7ZMQSRxLgEitfglTNz9P1CnpMwmW/Y4Gm5zdkv0JrdUVrn2UO9ARdHlPsW5ARgDmzaxnJypkfoHXNfxGGXWRk0sKLbz/ipnaQP/eFJv/ibNuSfqL6E4BnN/tHJSHYEaTQ/PdrA2i9laG3vJti3kAl5Ih87ct0w/tzYfp4SRPhEF1zzue9G/16eJEMzwmhQ5Ec7jJVcVGa4RltqnuF8unUu3iSRTQ+/MNNUkK6Mk+YuaJJs6Fjw6tRHuWi57SdKKv7GGkr0zlBUU2Dyo1MwpAqzsCcCTeQSv+8qt4wLf4uhU9Br7F/L0ZY9bFgh6iLDCdB+4iABXyZwT7Ufn762195hrSHcU4Okt0Zns9EeiBOFxnmpXEslYkYBpXw70GmymQfJlFOfoEp93QKCMS2DAEVeI51dJV1e+6t3pCSsQN69Vg6jUCsm1TMxSs2VX4BRbq562+VffchvW2BB4gMjsvHVUSRl8i5/ZSDlfzSPXcSGALLHBRzy+gn0oXXJ/447VHYZJDL3Ig8+QW5oFMgnWYhuwI5QSLEyflUrfSz+Pdwn/5eyjybXKJftePBD9Q+8NQ8zulU5sqvsMeIx/bBUx0fmOXsS3vjqCXW5IjkmSUV7q54GewZqTQBlcx+90xh/LSUxXex7UwZwRnifvyCbZ+zwNTHNb12chYeNjMV7kAIr3cGQv8vlOMM8ajyaZ5KVy7HpSXQjz4PGT2/nXbL5jKt8Lx0erGXsSsazkdoYDG3U", 188 "content":"AsqzdlMsG304G8h08bE67dhAR1gFTzTckUUyuvndZ8LrGCvwI4pgC3d6hyAK0Wo9gtkLqSr2rT2RyHlE5wRqbCOlQ8WvJEKwqwIJwT5PO3l2RxvGCHDbd1b1o40ZgIVwwLCfOWJ86I5upXe8K5AgpxYTOM1BD+SbgI5jOMA8tgpRoitJedVSvBZsmwAxXM7o7sbOON4MXHzOqOZpALpS2zgBDXSAaYAsTdEM4qqFeik+zTk3+L6NYuftGidqVluicwSGS2viYWr5OiJ1zrj1ERhYSGLpQnPKrqDaDi7R1KrHGFGyLgkJveY/45y0rv9aVIw9IWF11u53cf2CP7akACel2WvZdl1htEwFu/v9cFXD06fNVZjfx3OssKM/uHPE9XvZttQboAvP5UoK6lv9o3d+0GM4/3zP+yO3C0NExz1ZgFmbGFz703YJzM+zpKCOXaZyzPjADXp8qBBeVc5lmJqiCL4solZpxA1865yPigPAZcc9acSUlg23J1dptFK4n3Tl5HfSHP+oZ/QS/SHWbVFCtq7ZMQSRxLgEitfglTNz9P1CnpMwmW/Y4Gm5zdkv0JrdUVrn2UO9ARdHlPsW5ARgDmzaxnJypkfoHXNfxGGXWRk0sKLbz/ipnaQP/eFJv/ibNuSfqL6E4BnN/tHJSHYEaTQ/PdrA2i9laG3vJti3kAl5Ih87ct0w/tzYfp4SRPhEF1zzue9G/16eJEMzwmhQ5Ec7jJVcVGa4RltqnuF8unUu3iSRTQ+/MNNUkK6Mk+YuaJJs6Fjw6tRHuWi57SdKKv7GGkr0zlBUU2Dyo1MwpAqzsCcCTeQSv+8qt4wLf4uhU9Br7F/L0ZY9bFgh6iLDCdB+4iABXyZwT7Ufn762195hrSHcU4Okt0Zns9EeiBOFxnmpXEslYkYBpXw70GmymQfJlFOfoEp93QKCMS2DAEVeI51dJV1e+6t3pCSsQN69Vg6jUCsm1TMxSs2VX4BRbq562+VffchvW2BB4gMjsvHVUSRl8i5/ZSDlfzSPXcSGALLHBRzy+gn0oXXJ/447VHYZJDL3Ig8+QW5oFMgnWYhuwI5QSLEyflUrfSz+Pdwn/5eyjybXKJftePBD9Q+8NQ8zulU5sqvsMeIx/bBUx0fmOXsS3vjqCXW5IjkmSUV7q54GewZqTQBlcx+90xh/LSUxXex7UwZwRnifvyCbZ+zwNTHNb12chYeNjMV7kAIr3cGQv8vlOMM8ajyaZ5KVy7HpSXQjz4PGT2/nXbL5jKt8Lx0erGXsSsazkdoYDG3U",
194 "sig":"a3c6ce632b145c0869423c1afaff4a6d764a9b64dedaf15f170b944ead67227518a72e455567ca1c2a0d187832cecbde7ed478395ec4c95dd3e71749ed66c480" 189 "sig":"a3c6ce632b145c0869423c1afaff4a6d764a9b64dedaf15f170b944ead67227518a72e455567ca1c2a0d187832cecbde7ed478395ec4c95dd3e71749ed66c480"
@@ -202,7 +197,7 @@ The two final GiftWraps, one to the receiver and the other to the sender, respec
202 "created_at":1702711587, 197 "created_at":1702711587,
203 "kind":1059, 198 "kind":1059,
204 "tags":[ 199 "tags":[
205 [ "p", "44900586091b284416a0c001f677f9c49f7639a55c3f1e2ec130a8e1a7998e1b"] 200 ["p", "44900586091b284416a0c001f677f9c49f7639a55c3f1e2ec130a8e1a7998e1b"]
206 ], 201 ],
207 "content":"AsTClTzr0gzXXji7uye5UB6LYrx3HDjWGdkNaBS6BAX9CpHa+Vvtt5oI2xJrmWLen+Fo2NBOFazvl285Gb3HSM82gVycrzx1HUAaQDUG6HI7XBEGqBhQMUNwNMiN2dnilBMFC3Yc8ehCJT/gkbiNKOpwd2rFibMFRMDKai2mq2lBtPJF18oszKOjA+XlOJV8JRbmcAanTbEK5nA/GnG3eGUiUzhiYBoHomj3vztYYxc0QYHOx0WxiHY8dsC6jPsXC7f6k4P+Hv5ZiyTfzvjkSJOckel1lZuE5SfeZ0nduqTlxREGeBJ8amOykgEIKdH2VZBZB+qtOMc7ez9dz4wffGwBDA7912NFS2dPBr6txHNxBUkDZKFbuD5wijvonZDvfWq43tZspO4NutSokZB99uEiRH8NAUdGTiNb25m9JcDhVfdmABqTg5fIwwTwlem5aXIy8b66lmqqz2LBzJtnJDu36bDwkILph3kmvaKPD8qJXmPQ4yGpxIbYSTCohgt2/I0TKJNmqNvSN+IVoUuC7ZOfUV9lOV8Ri0AMfSr2YsdZ9ofV5o82ClZWlWiSWZwy6ypa7CuT1PEGHzywB4CZ5ucpO60Z7hnBQxHLiAQIO/QhiBp1rmrdQZFN6PUEjFDloykoeHe345Yqy9Ke95HIKUCS9yJurD+nZjjgOxZjoFCsB1hQAwINTIS3FbYOibZnQwv8PXvcSOqVZxC9U0+WuagK7IwxzhGZY3vLRrX01oujiRrevB4xbW7Oxi/Agp7CQGlJXCgmRE8Rhm+Vj2s+wc/4VLNZRHDcwtfejogjrjdi8p6nfUyqoQRRPARzRGUnnCbh+LqhigT6gQf3sVilnydMRScEc0/YYNLWnaw9nbyBa7wFBAiGbJwO40k39wj+xT6HTSbSUgFZzopxroO3f/o4+ubx2+IL3fkev22mEN38+dFmYF3zE+hpE7jVxrJpC3EP9PLoFgFPKCuctMnjXmeHoiGs756N5r1Mm1ffZu4H19MSuALJlxQR7VXE/LzxRXDuaB2u9days/6muP6gbGX1ASxbJd/ou8+viHmSC/ioHzNjItVCPaJjDyc6bv+gs1NPCt0qZ69G+JmgHW/PsMMeL4n5bh74g0fJSHqiI9ewEmOG/8bedSREv2XXtKV39STxPweceIOh0k23s3N6+wvuSUAJE7u1LkDo14cobtZ/MCw/QhimYPd1u5HnEJvRhPxz0nVPz0QqL/YQeOkAYk7uzgeb2yPzJ6DBtnTnGDkglekhVzQBFRJdk740LEj6swkJ", 202 "content":"AsTClTzr0gzXXji7uye5UB6LYrx3HDjWGdkNaBS6BAX9CpHa+Vvtt5oI2xJrmWLen+Fo2NBOFazvl285Gb3HSM82gVycrzx1HUAaQDUG6HI7XBEGqBhQMUNwNMiN2dnilBMFC3Yc8ehCJT/gkbiNKOpwd2rFibMFRMDKai2mq2lBtPJF18oszKOjA+XlOJV8JRbmcAanTbEK5nA/GnG3eGUiUzhiYBoHomj3vztYYxc0QYHOx0WxiHY8dsC6jPsXC7f6k4P+Hv5ZiyTfzvjkSJOckel1lZuE5SfeZ0nduqTlxREGeBJ8amOykgEIKdH2VZBZB+qtOMc7ez9dz4wffGwBDA7912NFS2dPBr6txHNxBUkDZKFbuD5wijvonZDvfWq43tZspO4NutSokZB99uEiRH8NAUdGTiNb25m9JcDhVfdmABqTg5fIwwTwlem5aXIy8b66lmqqz2LBzJtnJDu36bDwkILph3kmvaKPD8qJXmPQ4yGpxIbYSTCohgt2/I0TKJNmqNvSN+IVoUuC7ZOfUV9lOV8Ri0AMfSr2YsdZ9ofV5o82ClZWlWiSWZwy6ypa7CuT1PEGHzywB4CZ5ucpO60Z7hnBQxHLiAQIO/QhiBp1rmrdQZFN6PUEjFDloykoeHe345Yqy9Ke95HIKUCS9yJurD+nZjjgOxZjoFCsB1hQAwINTIS3FbYOibZnQwv8PXvcSOqVZxC9U0+WuagK7IwxzhGZY3vLRrX01oujiRrevB4xbW7Oxi/Agp7CQGlJXCgmRE8Rhm+Vj2s+wc/4VLNZRHDcwtfejogjrjdi8p6nfUyqoQRRPARzRGUnnCbh+LqhigT6gQf3sVilnydMRScEc0/YYNLWnaw9nbyBa7wFBAiGbJwO40k39wj+xT6HTSbSUgFZzopxroO3f/o4+ubx2+IL3fkev22mEN38+dFmYF3zE+hpE7jVxrJpC3EP9PLoFgFPKCuctMnjXmeHoiGs756N5r1Mm1ffZu4H19MSuALJlxQR7VXE/LzxRXDuaB2u9days/6muP6gbGX1ASxbJd/ou8+viHmSC/ioHzNjItVCPaJjDyc6bv+gs1NPCt0qZ69G+JmgHW/PsMMeL4n5bh74g0fJSHqiI9ewEmOG/8bedSREv2XXtKV39STxPweceIOh0k23s3N6+wvuSUAJE7u1LkDo14cobtZ/MCw/QhimYPd1u5HnEJvRhPxz0nVPz0QqL/YQeOkAYk7uzgeb2yPzJ6DBtnTnGDkglekhVzQBFRJdk740LEj6swkJ",
208 "sig":"c94e74533b482aa8eeeb54ae72a5303e0b21f62909ca43c8ef06b0357412d6f8a92f96e1a205102753777fd25321a58fba3fb384eee114bd53ce6c06a1c22bab" 203 "sig":"c94e74533b482aa8eeeb54ae72a5303e0b21f62909ca43c8ef06b0357412d6f8a92f96e1a205102753777fd25321a58fba3fb384eee114bd53ce6c06a1c22bab"
diff --git a/18.md b/18.md
index 5d55f84..3b94df8 100644
--- a/18.md
+++ b/18.md
@@ -21,18 +21,12 @@ reposted.
21 21
22## Quote Reposts 22## Quote Reposts
23 23
24Quote reposts are `kind 1` events with an embedded `q` tag of the note being 24Mentions to [NIP-21](21.md) entities like `nevent`, `note` and `naddr` on any
25quote reposted. The `q` tag ensures quote reposts are not pulled and included 25event must be converted into `q` tags. The `q` tag ensures quote reposts are
26as replies in threads. It also allows you to easily pull and count all of the 26not pulled and included as replies in threads. It also allows you to easily
27quotes for a post. 27pull and count all of the quotes for a post. The syntax follows
28 28
29`q` tags should follow the same conventions as NIP 10 `e` tags, with the exception 29`["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"]`
30of the `mark` argument.
31
32`["q", <event-id>, <relay-url>, <pubkey>]`
33
34Quote reposts MUST include the [NIP-21](21.md) `nevent`, `note`, or `naddr` of the
35event in the content.
36 30
37## Generic Reposts 31## Generic Reposts
38 32
@@ -40,6 +34,12 @@ Since `kind 6` reposts are reserved for `kind 1` contents, we use `kind 16`
40as a "generic repost", that can include any kind of event inside other than 34as a "generic repost", that can include any kind of event inside other than
41`kind 1`. 35`kind 1`.
42 36
43`kind 16` reposts SHOULD contain a `k` tag with the stringified kind number 37`kind 16` reposts SHOULD contain a `"k"` tag with the stringified kind number
44of the reposted event as its value. 38of the reposted event as its value.
45 39
40When reposting a replaceable event, the repost SHOULD include an `"a"` tag with
41the event coordinate (`kind:pubkey:d-tag`) of the reposted event.
42
43If the `"a"` tag is not present, it indicates that a specific version of a replaceable
44event is being reposted, in which case the `content` field must contain the full
45JSON string of the reposted event.
diff --git a/19.md b/19.md
index 3ea8e11..eb350a5 100644
--- a/19.md
+++ b/19.md
@@ -34,7 +34,7 @@ These are the possible bech32 prefixes with `TLV`:
34 34
35 - `nprofile`: a nostr profile 35 - `nprofile`: a nostr profile
36 - `nevent`: a nostr event 36 - `nevent`: a nostr event
37 - `naddr`: a nostr _replaceable event_ coordinate 37 - `naddr`: a nostr _addressable event_ coordinate
38 - `nrelay`: a nostr relay (deprecated) 38 - `nrelay`: a nostr relay (deprecated)
39 39
40These possible standardized `TLV` types are indicated here: 40These possible standardized `TLV` types are indicated here:
@@ -67,3 +67,4 @@ These possible standardized `TLV` types are indicated here:
67 67
68- `npub` keys MUST NOT be used in NIP-01 events or in NIP-05 JSON responses, only the hex format is supported there. 68- `npub` keys MUST NOT be used in NIP-01 events or in NIP-05 JSON responses, only the hex format is supported there.
69- When decoding a bech32-formatted string, TLVs that are not recognized or supported should be ignored, rather than causing an error. 69- When decoding a bech32-formatted string, TLVs that are not recognized or supported should be ignored, rather than causing an error.
70- Bech32-formatted strings SHOULD be limited in size to 5000 characters.
diff --git a/21.md b/21.md
index 988485d..800e490 100644
--- a/21.md
+++ b/21.md
@@ -12,9 +12,27 @@ The scheme is `nostr:`.
12 12
13The identifiers that come after are expected to be the same as those defined in [NIP-19](19.md) (except `nsec`). 13The identifiers that come after are expected to be the same as those defined in [NIP-19](19.md) (except `nsec`).
14 14
15## Examples 15#### Examples
16 16
17- `nostr:npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9` 17- `nostr:npub1sn0wdenkukak0d9dfczzeacvhkrgz92ak56egt7vdgzn8pv2wfqqhrjdv9`
18- `nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p` 18- `nostr:nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p`
19- `nostr:note1fntxtkcy9pjwucqwa9mddn7v03wwwsu9j330jj350nvhpky2tuaspk6nqc` 19- `nostr:note1fntxtkcy9pjwucqwa9mddn7v03wwwsu9j330jj350nvhpky2tuaspk6nqc`
20- `nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm` 20- `nostr:nevent1qqstna2yrezu5wghjvswqqculvvwxsrcvu7uc0f78gan4xqhvz49d9spr3mhxue69uhkummnw3ez6un9d3shjtn4de6x2argwghx6egpr4mhxue69uhkummnw3ez6ur4vgh8wetvd3hhyer9wghxuet5nxnepm`
21
22### Linking HTML pages to Nostr entities
23
24`<link>` tags with `rel="alternate"` can be used to associate webpages to Nostr events, in cases where the same content is served via the two mediums (for example, a web server that exposes Markdown articles both as HTML pages and as `kind:30023` events served under itself as a relay or through some other relay). For example:
25
26```
27<head>
28 <link rel="alternate" href="nostr:naddr1qqyrzwrxvc6ngvfkqyghwumn8ghj7enfv96x5ctx9e3k7mgzyqalp33lewf5vdq847t6te0wvnags0gs0mu72kz8938tn24wlfze6qcyqqq823cph95ag" />
29</head>
30```
31
32Likewise, `<link>` tags with `rel="me"` or `rel="author"` can be used to assign authorship of webpages to Nostr profiles. For example:
33
34```
35<head>
36 <link rel="me" href="nostr:nprofile1qyxhwumn8ghj7mn0wvhxcmmvqyd8wumn8ghj7un9d3shjtnhv4ehgetjde38gcewvdhk6qpq80cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwswpnfsn" />
37</head>
38```
diff --git a/22.md b/22.md
index be07e53..daad6ea 100644
--- a/22.md
+++ b/22.md
@@ -45,7 +45,7 @@ Tags `K` and `k` MUST be present to define the event kind of the root and the pa
45 45
46`I` and `i` tags create scopes for hashtags, geohashes, URLs, and other external identifiers. 46`I` and `i` tags create scopes for hashtags, geohashes, URLs, and other external identifiers.
47 47
48The possible values for `i` tags – and `k` tags, when related to an extenal identity – are listed on [NIP-73](73.md). 48The possible values for `i` tags – and `k` tags, when related to an external identity – are listed on [NIP-73](73.md).
49Their uppercase versions use the same type of values but relate to the root item instead of the parent one. 49Their uppercase versions use the same type of values but relate to the root item instead of the parent one.
50 50
51`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md). 51`q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md).
@@ -143,13 +143,13 @@ A comment on a website's url looks like this:
143 "tags": [ 143 "tags": [
144 // referencing the root url 144 // referencing the root url
145 ["I", "https://abc.com/articles/1"], 145 ["I", "https://abc.com/articles/1"],
146 // the root "kind": for an url, the kind is its domain 146 // the root "kind": for an url
147 ["K", "https://abc.com"], 147 ["K", "web"],
148 148
149 // the parent reference (same as root for top-level comments) 149 // the parent reference (same as root for top-level comments)
150 ["i", "https://abc.com/articles/1"], 150 ["i", "https://abc.com/articles/1"],
151 // the parent "kind": for an url, the kind is its domain 151 // the parent "kind": for an url
152 ["k", "https://abc.com"] 152 ["k", "web"]
153 ] 153 ]
154 // other fields 154 // other fields
155} 155}
@@ -192,7 +192,7 @@ A reply to a podcast comment:
192 // this is a reference to the above comment 192 // this is a reference to the above comment
193 ["e", "80c48d992a38f9c445b943a9c9f1010b396676013443765750431a9004bdac05", "wss://example.relay", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"], 193 ["e", "80c48d992a38f9c445b943a9c9f1010b396676013443765750431a9004bdac05", "wss://example.relay", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"],
194 // the parent comment kind 194 // the parent comment kind
195 ["k", "1111"] 195 ["k", "1111"],
196 ["p", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"] 196 ["p", "252f10c83610ebca1a059c0bae8255eba2f95be4d1d7bcfa89d7248a82d9f111"]
197 ] 197 ]
198 // other fields 198 // other fields
diff --git a/24.md b/24.md
index 5904efb..0094d58 100644
--- a/24.md
+++ b/24.md
@@ -8,8 +8,7 @@ Extra metadata fields and tags
8 8
9This NIP keeps track of extra optional fields that can added to events which are not defined anywhere else but have become _de facto_ standards and other minor implementation possibilities that do not deserve their own NIP and do not have a place in other NIPs. 9This NIP keeps track of extra optional fields that can added to events which are not defined anywhere else but have become _de facto_ standards and other minor implementation possibilities that do not deserve their own NIP and do not have a place in other NIPs.
10 10
11kind 0 11### kind 0
12======
13 12
14These are extra fields not specified in NIP-01 that may be present in the stringified JSON of metadata events: 13These are extra fields not specified in NIP-01 that may be present in the stringified JSON of metadata events:
15 14
@@ -19,24 +18,22 @@ These are extra fields not specified in NIP-01 that may be present in the string
19 - `bot`: a boolean to clarify that the content is entirely or partially the result of automation, such as with chatbots or newsfeeds. 18 - `bot`: a boolean to clarify that the content is entirely or partially the result of automation, such as with chatbots or newsfeeds.
20 - `birthday`: an object representing the author's birth date. The format is { "year": number, "month": number, "day": number }. Each field MAY be omitted. 19 - `birthday`: an object representing the author's birth date. The format is { "year": number, "month": number, "day": number }. Each field MAY be omitted.
21 20
22### Deprecated fields 21#### Deprecated fields
23 22
24These are fields that should be ignored or removed when found in the wild: 23These are fields that should be ignored or removed when found in the wild:
25 24
26 - `displayName`: use `display_name` instead. 25 - `displayName`: use `display_name` instead.
27 - `username`: use `name` instead. 26 - `username`: use `name` instead.
28 27
29kind 3 28### kind 3
30======
31 29
32These are extra fields not specified in NIP-02 that may be present in the stringified JSON of follow events: 30These are extra fields not specified in NIP-02 that may be present in the stringified JSON of follow events:
33 31
34### Deprecated fields 32#### Deprecated fields
35 33
36 - `{<relay-url>: {"read": <true|false>, "write": <true|false>}, ...}`: an object of relays used by a user to read/write. [NIP-65](65.md) should be used instead. 34 - `{<relay-url>: {"read": <true|false>, "write": <true|false>}, ...}`: an object of relays used by a user to read/write. [NIP-65](65.md) should be used instead.
37 35
38tags 36### tags
39====
40 37
41These tags may be present in multiple event kinds. Whenever a different meaning is not specified by some more specific NIP, they have the following meanings: 38These tags may be present in multiple event kinds. Whenever a different meaning is not specified by some more specific NIP, they have the following meanings:
42 39
diff --git a/25.md b/25.md
index 72109c0..7ac4fb1 100644
--- a/25.md
+++ b/25.md
@@ -7,38 +7,36 @@ Reactions
7 7
8`draft` `optional` 8`draft` `optional`
9 9
10A reaction is a `kind 7` event that is used to react to other events. 10A reaction is a `kind 7` event that is used to indicate user reactions to other events. A
11reaction's `content` field MUST include user-generated-content indicating the value of the
12reaction (conventionally `+`, `-`, or an emoji).
11 13
12The generic reaction, represented by the `content` set to a `+` string, SHOULD 14A reaction with `content` set to `+` or an empty string MUST be interpreted as a "like" or "upvote".
13be interpreted as a "like" or "upvote". 15A reaction with `content` set to `-` MUST be interpreted as a "dislike" or "downvote".
14 16
15A reaction with `content` set to `-` SHOULD be interpreted as a "dislike" or 17A reaction with `content` set to an emoji or [NIP-30](30.md) custom emoji SHOULD NOT be interpreted
16"downvote". It SHOULD NOT be counted as a "like", and MAY be displayed as a 18as a "like" or "dislike". Clients MAY instead display this emoji reaction on the post.
17downvote or dislike on a post. A client MAY also choose to tally likes against
18dislikes in a reddit-like system of upvotes and downvotes, or display them as
19separate tallies.
20
21The `content` MAY be an emoji, or [NIP-30](30.md) custom emoji in this case it MAY be interpreted as a "like" or "dislike",
22or the client MAY display this emoji reaction on the post. If the `content` is an empty string then the client should
23consider it a "+".
24 19
25Tags 20Tags
26---- 21----
27 22
28There MUST be always an `e` tag set to the `id` of the event that is being reacted to. The `e` tag SHOULD include a relay hint pointing to a relay where the event being reacted to can be found. If a client decides to include other `e`, which not recommended, the target event `id` should be last of the `e` tags. 23There MUST be always an `e` tag set to the `id` of the event that is being reacted to. The `e` tag SHOULD include a relay hint pointing to a relay where the event being reacted to can be found. If a client decides to include other `e`, which not recommended, the target event `id` should be last of the `e` tags.
29 24
30The SHOULD be a `p` tag set to the `pubkey` of the event being reacted to. If a client decides to include other `p` tags, which not recommended, the target event `pubkey` should be last the `p` tags. 25There SHOULD be a `p` tag set to the `pubkey` of the event being reacted to. If a client decides to include other `p` tags, which not recommended, the target event `pubkey` should be last the `p` tags.
31 26
32If the event being reacted to is an addressable event, an `a` SHOULD be included together with the `e` tag, it must be set to the coordinates (`kind:pubkey:d-tag`) of the event being reacted to. 27If the event being reacted to is an addressable event, an `a` SHOULD be included together with the `e` tag, it must be set to the coordinates (`kind:pubkey:d-tag`) of the event being reacted to.
33 28
34The reaction SHOULD include a `k` tag with the stringified kind number of the reacted event as its value. 29The `e` and `a` tags SHOULD include relay and pubkey hints. The `p` tags SHOULD include relay hints.
30
31The reaction event MAY include a `k` tag with the stringified kind number of the reacted event as its value.
35 32
36**Example code** 33**Example code**
37 34
38```swift 35```swift
39func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> NostrEvent { 36func make_like_event(pubkey: String, privkey: String, liked: NostrEvent, hint: String) -> NostrEvent {
40 tags.append(["e", liked.id, liked.source_relays.first ?? ""]) 37 var tags: [[String]] = []
41 tags.append(["p", liked.pubkey]) 38 tags.append(["e", liked.id, hint, liked.pubkey])
39 tags.append(["p", liked.pubkey, hint])
42 tags.append(["k", String(liked.kind)]) 40 tags.append(["k", String(liked.kind)])
43 let ev = NostrEvent(content: "+", pubkey: pubkey, kind: 7, tags: tags) 41 let ev = NostrEvent(content: "+", pubkey: pubkey, kind: 7, tags: tags)
44 ev.calculate_id() 42 ev.calculate_id()
@@ -47,25 +45,39 @@ func make_like_event(pubkey: String, privkey: String, liked: NostrEvent) -> Nost
47} 45}
48``` 46```
49 47
50Reactions to a website 48External Content Reactions
51--------------------- 49---------------------
52 50
53If the target of the reaction is a website, the reaction MUST be a `kind 17` event and MUST include an `r` tag with the website's URL. 51If the target of a reaction is not a native nostr event, the reaction MUST be a `kind 17` event and MUST include [NIP-73](73.md) external content `k` + `i` tags to properly reference the content.
54 52
53_Reacting to a website:_
55```jsonc 54```jsonc
56{ 55{
57 "kind": 17, 56 "kind": 17,
58 "content": "⭐", 57 "content": "⭐",
59 "tags": [ 58 "tags": [
60 ["r", "https://example.com/"] 59 ["k", "web"],
60 ["i", "https://example.com"]
61 ], 61 ],
62 // other fields...
63} 62}
64``` 63```
65 64
66URLs SHOULD be [normalized](https://datatracker.ietf.org/doc/html/rfc3986#section-6), so that reactions to the same website are not omitted from queries. 65_Reacting to a podcast episode:_
67A fragment MAY be attached to the URL, to react to a section of the page. 66```jsonc
68It should be noted that a URL with a fragment is not considered to be the same URL as the original. 67{
68 "kind": 17,
69 "content": "+",
70 "tags": [
71 ["k", "podcast:guid"],
72 ["i", "podcast:guid:917393e3-1b1e-5cef-ace4-edaa54e1f810", "https://fountain.fm/show/QRT0l2EfrKXNGDlRrmjL"],
73 ["k", "podcast:item:guid"],
74 ["i", "podcast:item:guid:PC20-229", "https://fountain.fm/episode/DQqBg5sD3qFGMCZoSuLF"]
75 ],
76}
77```
78
79
80
69 81
70Custom Emoji Reaction 82Custom Emoji Reaction
71--------------------- 83---------------------
diff --git a/26.md b/26.md
index 050041b..6cd32f4 100644
--- a/26.md
+++ b/26.md
@@ -1,4 +1,4 @@
1> __Warning__ `unrecommended`: adds unecessary burden for little gain 1> __Warning__ `unrecommended`: adds unnecessary burden for little gain
2 2
3NIP-26 3NIP-26
4======= 4=======
@@ -6,7 +6,7 @@ NIP-26
6Delegated Event Signing 6Delegated Event Signing
7----------------------- 7-----------------------
8 8
9`draft` `optional` 9`draft` `optional` `relay`
10 10
11This NIP defines how events can be delegated so that they can be signed by other keypairs. 11This NIP defines how events can be delegated so that they can be signed by other keypairs.
12 12
diff --git a/29.md b/29.md
index 601d63a..f39d4d8 100644
--- a/29.md
+++ b/29.md
@@ -4,7 +4,7 @@ NIP-29
4Relay-based Groups 4Relay-based Groups
5------------------ 5------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9This NIP defines a standard for groups that are only writable by a closed set of users. They can be public for reading by external users or not. 9This NIP defines a standard for groups that are only writable by a closed set of users. They can be public for reading by external users or not.
10 10
@@ -48,14 +48,6 @@ The roles supported by the group as to having some special privilege assigned to
48 48
49Users with any roles that have any privilege can be considered _admins_ in a broad sense and be returned in the `kind:39001` event for a group. 49Users with any roles that have any privilege can be considered _admins_ in a broad sense and be returned in the `kind:39001` event for a group.
50 50
51## Unmanaged groups
52
53Unmanaged groups are impromptu groups that can be used in any public relay unaware of NIP-29 specifics. They piggyback on relays' natural white/blacklists (or lack of) but aside from that are not actively managed and won't have any admins, group state or metadata events.
54
55In `unmanaged` groups, everybody is considered to be a member.
56
57Unmanaged groups can transition to managed groups, in that case the relay master key just has to publish moderation events setting the state of all groups and start enforcing the rules they choose to.
58
59## Event definitions 51## Event definitions
60 52
61These are the events expected to be found in NIP-29 groups. 53These are the events expected to be found in NIP-29 groups.
@@ -83,7 +75,7 @@ Any user can send a kind `9021` event to the relay in order to request admission
83} 75}
84``` 76```
85 77
86The optional `code` tag may be used by the relay to preauthorize acceptances in `closed` groups, together with the `kind:9009` `create-invite` moderation event. 78The optional `code` tag may be used by the relay to preauthorize acceptance, together with the `kind:9009` `create-invite` moderation event.
87 79
88- *leave request* (`kind:9022`) 80- *leave request* (`kind:9022`)
89 81
@@ -120,21 +112,21 @@ Clients can send these events to a relay in order to accomplish a moderation act
120 112
121Each moderation action uses a different kind and requires different arguments, which are given as tags. These are defined in the following table: 113Each moderation action uses a different kind and requires different arguments, which are given as tags. These are defined in the following table:
122 114
123| kind | name | tags | 115| kind | name | tags |
124| --- | --- | --- | 116| --- | --- | --- |
125| 9000 | `put-user` | `p` with pubkey hex and optional roles | 117| 9000 | `put-user` | `p` with pubkey hex and optional roles |
126| 9001 | `remove-user` | `p` with pubkey hex | 118| 9001 | `remove-user` | `p` with pubkey hex |
127| 9002 | `edit-metadata` | fields from `kind:39000` to be modified | 119| 9002 | `edit-metadata` | fields to be modified, and optionally `unrestricted`, `open`, `visible` `public` |
128| 9005 | `delete-event` | `e` with event id hex | 120| 9005 | `delete-event` | `e` with event id hex |
129| 9007 | `create-group` | | 121| 9007 | `create-group` | |
130| 9008 | `delete-group` | | 122| 9008 | `delete-group` | |
131| 9009 | `create-invite` | | 123| 9009 | `create-invite` | arbitrary `code` |
132 124
133It's expected that the group state (of who is an allowed member or not, who is an admin and with which permission or not, what are the group name and picture etc) can be fully reconstructed from the canonical sequence of these events. 125It's expected that the group state (of who is an allowed member or not, who is an admin and with which permission or not, what are the group name and picture etc) can be fully reconstructed from the canonical sequence of these events.
134 126
135### Group metadata events 127### Group metadata events
136 128
137These events contain the group id in a `d` tag instead of the `h` tag. They MUST be created by the relay master key only and a single instance of each (or none) should exist at all times for each group. They are merely informative but should reflect the latest group state (as it was changed by moderation events over time). 129These events contain the group id in a `d` tag instead of the `h` tag. They MUST be created by the relay master key only (as stated by the [NIP-11](11.md) `"self"` pubkey) and a single instance of each (or none) should exist at all times for each group. They are merely informative but should reflect the latest group state (as it was changed by moderation events over time).
138 130
139- *group metadata* (`kind:39000`) (optional) 131- *group metadata* (`kind:39000`) (optional)
140 132
@@ -142,8 +134,6 @@ This event defines the metadata for the group -- basically how clients should di
142 134
143If the group is forked and hosted in multiple relays, there will be multiple versions of this event in each different relay and so on. 135If the group is forked and hosted in multiple relays, there will be multiple versions of this event in each different relay and so on.
144 136
145When this event is not found, clients may still connect to the group, but treat it as having a different status, `unmanaged`,
146
147```jsonc 137```jsonc
148{ 138{
149 "kind": 39000, 139 "kind": 39000,
@@ -153,14 +143,18 @@ When this event is not found, clients may still connect to the group, but treat
153 ["name", "Pizza Lovers"], 143 ["name", "Pizza Lovers"],
154 ["picture", "https://pizza.com/pizza.png"], 144 ["picture", "https://pizza.com/pizza.png"],
155 ["about", "a group for people who love pizza"], 145 ["about", "a group for people who love pizza"],
156 ["public"], // or ["private"] 146 ["private"],
157 ["open"] // or ["closed"] 147 ["closed"]
158 ] 148 ]
159 // other fields... 149 // other fields...
160} 150}
161``` 151```
162 152
163`name`, `picture` and `about` are basic metadata for the group for display purposes. `public` signals the group can be _read_ by anyone, while `private` signals that only AUTHed users can read. `open` signals that anyone can request to join and the request will be automatically granted, while `closed` signals that members must be pre-approved or that requests to join will be manually handled. 153- `name`, `picture` and `about` are basic metadata for the group for display purposes.
154- `private` indicates that only members can _read_ group messages. Omitting this tag indicates that anyone can read group messages.
155- `restricted` indicates that only members can _write_ messages to the group. Omitting this tag indicates that anyone can send messages to the group.
156- `hidden` indicates that relays should hide group metadata from non-members. Omitting this tag indicates that anyone can request group metadata events.
157- `closed` indicates that join requests are ignored. Omitting this tag indicates that users can expect join requests to be honored.
164 158
165- *group admins* (`kind:39001`) (optional) 159- *group admins* (`kind:39001`) (optional)
166 160
@@ -227,18 +221,12 @@ The process through which the relay decides what roles to support and how to han
227 221
228### Checking your own membership in a group 222### Checking your own membership in a group
229 223
230The latest of either `kind:9000` or `kind:9001` events present in a group should tell a user that they are currently members of the group or if they were removed. In case none of these exist the user is assumed to not be a member of the group -- unless the group is `unmanaged`, in which case the user is assumed to be a member. 224The latest of either `kind:9000` or `kind:9001` events present in a group should tell a user that they are currently members of the group or if they were removed. In case none of these exist the user is assumed to not be a member of the group.
231 225
232### Adding yourself to a group 226### Adding yourself to a group
233 227
234When a group is `open`, anyone can send a `kind:9021` event to it in order to be added, then expect a `kind:9000` event to be emitted confirming that the user was added. The same happens with `closed` groups, except in that case a user may only send a `kind:9021` if it has an invite code. 228Anyone can send a `kind:9021` join request to a group. The relay may then generate a `kind:9000` event immediately, or defer that decision to an administator. If a group is `closed`, join requests are not honored unless they include an invite code.
235 229
236### Storing your list of groups 230### Storing your list of groups
237 231
238A definition for `kind:10009` was included in [NIP-51](51.md) that allows clients to store the list of groups a user wants to remember being in. 232A definition for `kind:10009` was included in [NIP-51](51.md) that allows clients to store the list of groups a user wants to remember being in.
239
240### Using `unmanaged` relays
241
242To prevent event leakage, when using `unmanaged` relays, clients should include the [NIP-70](70.md) `-` tag, as just the `previous` tag won't be checked by other `unmanaged` relays.
243
244Groups MAY be named without relay support by adding a `name` to the corresponding tag in a user's `kind 10009` group list.
diff --git a/30.md b/30.md
index 54a8b37..c9b9ebe 100644
--- a/30.md
+++ b/30.md
@@ -9,13 +9,14 @@ Custom Emoji
9Custom emoji may be added to **kind 0**, **kind 1**, **kind 7** ([NIP-25](25.md)) and **kind 30315** ([NIP-38](38.md)) events by including one or more `"emoji"` tags, in the form: 9Custom emoji may be added to **kind 0**, **kind 1**, **kind 7** ([NIP-25](25.md)) and **kind 30315** ([NIP-38](38.md)) events by including one or more `"emoji"` tags, in the form:
10 10
11``` 11```
12["emoji", <shortcode>, <image-url>] 12["emoji", <shortcode>, <image-url>, <emoji-set-address>]
13``` 13```
14 14
15Where: 15Where:
16 16
17- `<shortcode>` is a name given for the emoji, which MUST be comprised of only alphanumeric characters and underscores. 17- `<shortcode>` is a name given for the emoji, which MUST be comprised of only alphanumeric characters and underscores.
18- `<image-url>` is a URL to the corresponding image file of the emoji. 18- `<image-url>` is a URL to the corresponding image file of the emoji.
19- `<emoji-set-address>` is an optional address pointer (`kind:pubkey:d-tag`) to the kind 30030 emoji set ([NIP-51](51.md)) the emoji belongs to.
19 20
20For each emoji tag, clients should parse emoji shortcodes (aka "emojify") like `:shortcode:` in the event to display custom emoji. 21For each emoji tag, clients should parse emoji shortcodes (aka "emojify") like `:shortcode:` in the event to display custom emoji.
21 22
@@ -46,7 +47,7 @@ In kind 1 events, the `content` should be emojified.
46 "kind": 1, 47 "kind": 1,
47 "content": "Hello :gleasonator: 😂 :ablobcatrainbow: :disputed: yolo", 48 "content": "Hello :gleasonator: 😂 :ablobcatrainbow: :disputed: yolo",
48 "tags": [ 49 "tags": [
49 ["emoji", "ablobcatrainbow", "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.png"], 50 ["emoji", "ablobcatrainbow", "https://gleasonator.com/emoji/blobcat/ablobcatrainbow.png", "30030:79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6:blobcats"],
50 ["emoji", "disputed", "https://gleasonator.com/emoji/Fun/disputed.png"], 51 ["emoji", "disputed", "https://gleasonator.com/emoji/Fun/disputed.png"],
51 ["emoji", "gleasonator", "https://gleasonator.com/emoji/Gleasonator/gleasonator.png"] 52 ["emoji", "gleasonator", "https://gleasonator.com/emoji/Gleasonator/gleasonator.png"]
52 ], 53 ],
diff --git a/34.md b/34.md
index 56ede85..d35c0eb 100644
--- a/34.md
+++ b/34.md
@@ -10,7 +10,7 @@ This NIP defines all the ways code collaboration using and adjacent to [`git`](h
10 10
11## Repository announcements 11## Repository announcements
12 12
13Git repositories are hosted in Git-enabled servers, but their existence can be announced using Nostr events, as well as their willingness to receive patches, bug reports and comments in general. 13Git repositories are hosted in Git-enabled servers, but their existence can be announced using Nostr events. By doing so the author asserts themselves as a maintainer and expresses a willingness to receive patches, bug reports and comments in general, unless `t` tag `personal-fork` is included.
14 14
15```jsonc 15```jsonc
16{ 16{
@@ -25,6 +25,7 @@ Git repositories are hosted in Git-enabled servers, but their existence can be a
25 ["relays", "<relay-url>", ...], // relays that this repository will monitor for patches and issues 25 ["relays", "<relay-url>", ...], // relays that this repository will monitor for patches and issues
26 ["r", "<earliest-unique-commit-id>", "euc"], 26 ["r", "<earliest-unique-commit-id>", "euc"],
27 ["maintainers", "<other-recognized-maintainer>", ...], 27 ["maintainers", "<other-recognized-maintainer>", ...],
28 ["t","personal-fork"], // optionally indicate author isn't a maintainer
28 ["t", "<arbitrary string>"], // hashtags labelling the repository 29 ["t", "<arbitrary string>"], // hashtags labelling the repository
29 ] 30 ]
30} 31}
@@ -45,7 +46,7 @@ An optional source of truth for the state of branches and tags in a repository.
45 "kind": 30618, 46 "kind": 30618,
46 "content": "", 47 "content": "",
47 "tags": [ 48 "tags": [
48 ["d", "<repo-id>"], // matches the identifier in the coresponding repository announcement 49 ["d", "<repo-id>"], // matches the identifier in the corresponding repository announcement
49 ["refs/<heads|tags>/<branch-or-tag-name>","<commit-id>"] 50 ["refs/<heads|tags>/<branch-or-tag-name>","<commit-id>"]
50 ["HEAD", "ref: refs/heads/<branch-name>"] 51 ["HEAD", "ref: refs/heads/<branch-name>"]
51 ] 52 ]
@@ -66,13 +67,17 @@ The `refs` tag can be optionally extended to enable clients to identify how many
66} 67}
67``` 68```
68 69
69## Patches 70## Patches and Pull Requests (PRs)
70 71
71Patches can be sent by anyone to any repository. Patches to a specific repository SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag. Patch events SHOULD include an `a` tag pointing to that repository's announcement address. 72Patches and PRs can be sent by anyone to any repository. Patches and PRs to a specific repository SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag. Patch and PR events SHOULD include an `a` tag pointing to that repository's announcement address.
72 73
73Patches in a patch set SHOULD include a NIP-10 `e` `reply` tag pointing to the previous patch. 74Patches SHOULD be used if each event is under 60kb, otherwise PRs SHOULD be used.
74 75
75The first patch revision in a patch revision SHOULD include a NIP-10 `e` `reply` to the original root patch. 76### Patches
77
78Patches in a patch set SHOULD include a [NIP-10](10.md) `e` `reply` tag pointing to the previous patch.
79
80The first patch revision in a patch revision SHOULD include a [NIP-10](10.md) `e` `reply` to the original root patch.
76 81
77```jsonc 82```jsonc
78{ 83{
@@ -103,9 +108,66 @@ The first patch revision in a patch revision SHOULD include a NIP-10 `e` `reply`
103 108
104The first patch in a series MAY be a cover letter in the format produced by `git format-patch`. 109The first patch in a series MAY be a cover letter in the format produced by `git format-patch`.
105 110
111### Pull Requests
112
113The PR or PR update tip SHOULD be successfully pushed to `refs/nostr/<[PR|PR-Update]-event-id>` in all repositories listed in its `clone` tag before the event is signed.
114
115An attempt SHOULD be made to push this ref to all repositories listed in the repository's announcement event's `"clone"` tag, for which their is reason to believe the user might have write access. This includes each [grasp server](https://njump.me/naddr1qvzqqqrhnypzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqy28wumn8ghj7un9d3shjtnwva5hgtnyv4mqqpt8wfshxuqlnvh8x) which can be identified using this method: `clone` tag includes `[http|https]://<grasp-path>/<valid-npub>/<string>.git` and `relays` tag includes `[ws/wss]://<grasp-path>`.
116
117Clients MAY fallback to creating a 'personal-fork' `repository announcement` listing other grasp servers, e.g. from the `User grasp list`, for the purpose of serving the specified commit(s).
118
119```jsonc
120{
121 "kind": 1618,
122 "content": "<markdown text>",
123 "tags": [
124 ["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"],
125 ["r", "<earliest-unique-commit-id-of-repo>"] // so clients can subscribe to all PRs sent to a local git repo
126 ["p", "<repository-owner>"],
127 ["p", "<other-user>"], // optionally send the PR to another user to bring it to their attention
128
129 ["subject", "<PR-subject>"],
130 ["t", "<PR-label>"], // optional
131 ["t", "<another-PR-label>"], // optional
132
133 ["c", "<current-commit-id>"], // tip of the PR branch
134 ["clone", "<clone-url>", ...], // at least one git clone url where commit can be downloaded
135 ["branch-name", "<branch-name>"], // optional recommended branch name
136
137 ["e", "<root-patch-event-id>"], // optionally indicate PR is a revision of an existing patch, which should be closed
138 ["merge-base", "<commit-id>"], // optional: the most recent common ancestor with the target branch
139 ]
140}
141```
142
143### Pull Request Updates
144
145A PR Update changes the tip of a referenced PR event.
146
147```jsonc
148{
149 "kind": 1619,
150 "content": "",
151 "tags": [
152 ["a", "30617:<base-repo-owner-pubkey>:<base-repo-id>"],
153 ["r", "<earliest-unique-commit-id-of-repo>"] // so clients can subscribe to all PRs sent to a local git repo
154 ["p", "<repository-owner>"],
155 ["p", "<other-user>"], // optionally send the PR to another user to bring it to their attention
156
157 // NIP-22 tags
158 ["E", "<pull-request-event-id>"],
159 ["P", "<pull-request-author>"],
160
161 ["c", "<current-commit-id>"], // updated tip of PR
162 ["clone", "<clone-url>", ...], // at least one git clone url where commit can be downloaded
163 ["merge-base", "<commit-id>"], // optional: the most recent common ancestor with the target branch
164 ]
165}
166```
167
106## Issues 168## Issues
107 169
108Issues are Markdown text that is just human-readable conversational threads related to the repository: bug reports, feature requests, questions or comments of any kind. Like patches, these SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag. 170Issues are Markdown text that is just human-readable conversational threads related to the repository: bug reports, feature requests, questions or comments of any kind. Like patches, these SHOULD be sent to the relays specified in that repository's announcement event's `"relays"` tag.
109 171
110Issues may have a `subject` tag, which clients can utilize to display a header. Additionally, one or more `t` tags may be included to provide labels for the issue. 172Issues may have a `subject` tag, which clients can utilize to display a header. Additionally, one or more `t` tags may be included to provide labels for the issue.
111 173
@@ -125,11 +187,11 @@ Issues may have a `subject` tag, which clients can utilize to display a header.
125 187
126## Replies 188## Replies
127 189
128Replies to either a `kind:1621` _issue_ or a `kind:1617` _patch_ event should follow [NIP-22 comment](22.md). 190Replies to either a `kind:1621` (_issue_), `kind:1617` (_patch_) or `kind:1618` (_pull request_) event should follow [NIP-22 comment](22.md).
129 191
130## Status 192## Status
131 193
132Root Patches and Issues have a Status that defaults to 'Open' and can be set by issuing Status events. 194Root Patches, PRs and Issues have a Status that defaults to 'Open' and can be set by issuing Status events.
133 195
134```jsonc 196```jsonc
135{ 197{
@@ -139,7 +201,7 @@ Root Patches and Issues have a Status that defaults to 'Open' and can be set by
139 "kind": 1633, // Draft 201 "kind": 1633, // Draft
140 "content": "<markdown text>", 202 "content": "<markdown text>",
141 "tags": [ 203 "tags": [
142 ["e", "<issue-or-original-root-patch-id-hex>", "", "root"], 204 ["e", "<issue-or-PR-or-original-root-patch-id-hex>", "", "root"],
143 ["e", "<accepted-revision-root-id-hex>", "", "reply"], // for when revisions applied 205 ["e", "<accepted-revision-root-id-hex>", "", "reply"], // for when revisions applied
144 ["p", "<repository-owner>"], 206 ["p", "<repository-owner>"],
145 ["p", "<root-event-author>"], 207 ["p", "<root-event-author>"],
@@ -150,7 +212,7 @@ Root Patches and Issues have a Status that defaults to 'Open' and can be set by
150 ["r", "<earliest-unique-commit-id-of-repo>"] 212 ["r", "<earliest-unique-commit-id-of-repo>"]
151 213
152 // optional for `1631` status 214 // optional for `1631` status
153 ["e", "<applied-or-merged-patch-event-id>", "", "mention"], // for each 215 ["q", "<applied-or-merged-patch-event-id>", "<relay-url>", "<pubkey>"], // for each
154 // when merged 216 // when merged
155 ["merge-commit", "<merge-commit-id>"] 217 ["merge-commit", "<merge-commit-id>"]
156 ["r", "<merge-commit-id>"] 218 ["r", "<merge-commit-id>"]
@@ -161,12 +223,26 @@ Root Patches and Issues have a Status that defaults to 'Open' and can be set by
161} 223}
162``` 224```
163 225
164The Status event with the largest created_at date is valid. 226The most recent Status event (by `created_at` date) from either the issue/patch author or a maintainer is considered valid.
165 227
166The Status of a patch-revision defaults to either that of the root-patch, or `1632` (Closed) if the root-patch's Status is `1631` and the patch-revision isn't tagged in the `1631` event. 228The Status of a patch-revision is to either that of the root-patch, or `1632` (_Closed_) if the root-patch's Status is `1631` (_Applied/Merged_) and the patch-revision isn't tagged in the `1631` (_Applied/Merged_) event.
167 229
230## User grasp list
231
232List of [grasp servers](https://njump.me/naddr1qvzqqqrhnypzpgqgmmc409hm4xsdd74sf68a2uyf9pwel4g9mfdg8l5244t6x4jdqy28wumn8ghj7un9d3shjtnwva5hgtnyv4mqqpt8wfshxuqlnvh8x) the user generally wishes to use for NIP-34 related activity. It is similar in function to the NIP-65 relay list and NIP-B7 blossom list.
233
234The event SHOULD include a list of `g` tags with grasp service websocket URLs in order of preference.
235
236```jsonc
237{
238 "kind": 10317,
239 "content": "",
240 "tags": [
241 ["g", "<grasp-service-websocket-url>"], // zero or more grasp sever urls
242 ]
243}
244```
168 245
169## Possible things to be added later 246## Possible things to be added later
170 247
171- "branch merge" kind (specifying a URL from where to fetch the branch to be merged)
172- inline file comments kind (we probably need one for patches and a different one for merged files) 248- inline file comments kind (we probably need one for patches and a different one for merged files)
diff --git a/37.md b/37.md
index 1ade59d..ca0fdfb 100644
--- a/37.md
+++ b/37.md
@@ -1,50 +1,57 @@
1NIP-37 1NIP-37
2====== 2======
3 3
4Draft Events 4Draft Wraps
5------------ 5-----------
6 6
7`draft` `optional` 7`draft` `optional`
8 8
9This NIP defines kind `31234` as a private wrap for drafts of any other event kind. 9This NIP defines kind `31234` as an encrypted storage for unsigned draft events of any other kind.
10 10
11The draft event is JSON-stringified, [NIP44-encrypted](44.md) to the signer's public key and placed inside the `.content` of the event. 11The draft is JSON-stringified, [NIP44-encrypted](44.md) to the signer's public key and placed inside the `.content`.
12 12
13An additional `k` tag identifies the kind of the draft event. 13`k` tags identify the kind of the draft.
14 14
15```js 15```js
16{ 16{
17 "kind": 31234, 17 "kind": 31234,
18 "tags": [ 18 "tags": [
19 ["d", "<identifier>"], 19 ["d", "<identifier>"],
20 ["k", "<kind of the draft event>"], 20 ["k", "<kind of the draft event>"], // required
21 ["e", "<anchor event event id>", "<relay-url>"], 21 ["expiration", "now + 90 days"] // recommended
22 ["a", "<anchor event address>", "<relay-url>"],
23 ], 22 ],
24 "content": nip44Encrypt(JSON.stringify(draft_event)), 23 "content": nip44Encrypt(JSON.stringify(draft_event)),
25 // other fields 24 // other fields
26} 25}
27``` 26```
28 27
29A blanked `.content` means this draft has been deleted by a client but relays still have the event. 28A blanked `.content` field signals that the draft has been deleted.
30 29
31Tags `e` and `a` identify one or more anchor events, such as parent events on replies. 30[NIP-40](40.md) `expiration` tags are recommended.
31
32Clients SHOULD publish kind `31234` events to relays listed on kind `10013` below.
32 33
33## Relay List for Private Content 34## Relay List for Private Content
34 35
35Kind `10013` indicates the user's preferred relays to store private events like Drafts. The event MUST include a list of `relay` URLs in private tags. Private tags are JSON Stringified, NIP-44-encrypted to the signer's keys and placed inside the .content of the event. 36Kind `10013` indicates the user's preferred relays to store private events like Draft Wraps.
37
38The event MUST include a list of `relay` URLs in private tags. Private tags are JSON Stringified, [NIP44-encrypted](44.md) to the signer's keys and placed inside the .content of the event.
36 39
37```js 40```js
38{ 41{
39 "kind": 10013, 42 "kind": 10013,
40 "tags": [], 43 "tags": [],
41 "content": nip44Encrypt(JSON.stringify([ 44 "content": nip44Encrypt(
42 ["relay", "wss://myrelay.mydomain.com"] 45 JSON.stringify(
43 ])) 46 [
47 ["relay", "wss://myrelay.mydomain.com"]
48 ]
49 )
50 )
44 //...other fields 51 //...other fields
45} 52}
46``` 53```
47 54
48Relays listed in this event SHOULD be authed and only allow downloads to events signed by the authed user. 55It's recommended that Private Storage relays SHOULD be [NIP-42](42.md)-authed and only allow downloads of events signed by the authed user.
49 56
50Clients SHOULD publish kind `10013` events to the author's [NIP-65](65.md) `write` relays. 57Clients MUST publish kind `10013` events to the author's [NIP-65](65.md) `write` relays.
diff --git a/38.md b/38.md
index ece5e5f..27359f1 100644
--- a/38.md
+++ b/38.md
@@ -50,11 +50,11 @@ The status MAY include an `r`, `p`, `e` or `a` tag linking to a URL, profile, no
50 50
51The `content` MAY include emoji(s), or [NIP-30](30.md) custom emoji(s). If the `content` is an empty string then the client should clear the status. 51The `content` MAY include emoji(s), or [NIP-30](30.md) custom emoji(s). If the `content` is an empty string then the client should clear the status.
52 52
53# Client behavior 53## Client behavior
54 54
55Clients MAY display this next to the username on posts or profiles to provide live user status information. 55Clients MAY display this next to the username on posts or profiles to provide live user status information.
56 56
57# Use Cases 57## Use Cases
58 58
59* Calendar nostr apps that update your general status when you're in a meeting 59* Calendar nostr apps that update your general status when you're in a meeting
60* Nostr Nests that update your general status with a link to the nest when you join 60* Nostr Nests that update your general status with a link to the nest when you join
diff --git a/39.md b/39.md
index 3777ac5..8a3247f 100644
--- a/39.md
+++ b/39.md
@@ -8,16 +8,11 @@ External Identities in Profiles
8 8
9## Abstract 9## Abstract
10 10
11Nostr protocol users may have other online identities such as usernames, profile pages, keypairs etc. they control and they may want to include this data in their profile metadata so clients can parse, validate and display this information. 11Users can declare their control over one or more online identities such as usernames, profile pages, keypairs in kind `10011` using `i` tags.
12
13## `i` tag on a metadata event
14
15A new optional `i` tag is introduced for `kind 0` metadata event defined in [NIP-01](01.md):
16 12
17```jsonc 13```jsonc
18{ 14{
19 "id": <id>, 15 "kind": 10011,
20 "pubkey": <pubkey>,
21 "tags": [ 16 "tags": [
22 ["i", "github:semisol", "9721ce4ee4fceb91c9711ca2a6c9a5ab"], 17 ["i", "github:semisol", "9721ce4ee4fceb91c9711ca2a6c9a5ab"],
23 ["i", "twitter:semisol_public", "1619358434134196225"], 18 ["i", "twitter:semisol_public", "1619358434134196225"],
@@ -28,7 +23,7 @@ A new optional `i` tag is introduced for `kind 0` metadata event defined in [NIP
28} 23}
29``` 24```
30 25
31An `i` tag will have two parameters, which are defined as the following: 26An `i` tag MUST have two parameters, which are defined as the following:
321. `platform:identity`: This is the platform name (for example `github`) and the identity on that platform (for example `semisol`) joined together with `:`. 271. `platform:identity`: This is the platform name (for example `github`) and the identity on that platform (for example `semisol`) joined together with `:`.
332. `proof`: String or object that points to the proof of owning this identity. 282. `proof`: String or object that points to the proof of owning this identity.
34 29
diff --git a/40.md b/40.md
index 909747f..9450b3c 100644
--- a/40.md
+++ b/40.md
@@ -4,7 +4,7 @@ NIP-40
4Expiration Timestamp 4Expiration Timestamp
5-------------------- 5--------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9The `expiration` tag enables users to specify a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays. 9The `expiration` tag enables users to specify a unix timestamp at which the message SHOULD be considered expired (by relays and clients) and SHOULD be deleted by relays.
10 10
diff --git a/42.md b/42.md
index fdc5d10..c2b6ded 100644
--- a/42.md
+++ b/42.md
@@ -4,7 +4,7 @@ NIP-42
4Authentication of clients to relays 4Authentication of clients to relays
5----------------------------------- 5-----------------------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9This NIP defines a way for clients to authenticate to relays by signing an ephemeral event. 9This NIP defines a way for clients to authenticate to relays by signing an ephemeral event.
10 10
@@ -32,6 +32,8 @@ And, when sent by clients, the following form:
32["AUTH", <signed-event-json>] 32["AUTH", <signed-event-json>]
33``` 33```
34 34
35Clients MAY provide signed events from multiple pubkeys in a sequence of `AUTH` messages. Relays MUST treat all pubkeys as authenticated accordingly.
36
35`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message. 37`AUTH` messages sent by clients MUST be answered with an `OK` message, like any `EVENT` message.
36 38
37### Canonical authentication event 39### Canonical authentication event
@@ -69,7 +71,9 @@ relay: ["AUTH", "<challenge>"]
69client: ["REQ", "sub_1", {"kinds": [4]}] 71client: ["REQ", "sub_1", {"kinds": [4]}]
70relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"] 72relay: ["CLOSED", "sub_1", "auth-required: we can't serve DMs to unauthenticated users"]
71client: ["AUTH", {"id": "abcdef...", ...}] 73client: ["AUTH", {"id": "abcdef...", ...}]
74client: ["AUTH", {"id": "abcde2...", ...}]
72relay: ["OK", "abcdef...", true, ""] 75relay: ["OK", "abcdef...", true, ""]
76relay: ["OK", "abcde2...", true, ""]
73client: ["REQ", "sub_1", {"kinds": [4]}] 77client: ["REQ", "sub_1", {"kinds": [4]}]
74relay: ["EVENT", "sub_1", {...}] 78relay: ["EVENT", "sub_1", {...}]
75relay: ["EVENT", "sub_1", {...}] 79relay: ["EVENT", "sub_1", {...}]
diff --git a/43.md b/43.md
new file mode 100644
index 0000000..d0742b4
--- /dev/null
+++ b/43.md
@@ -0,0 +1,146 @@
1NIP-43
2======
3
4Relay Access Metadata and Requests
5----------------------------------
6
7`draft` `optional` `relay`
8
9This NIP defines a way for relays to advertise membership lists, and for clients to request admission to relays on behalf of users.
10
11## Membership Lists
12
13Relays MAY publish a `kind 13534` event which indicates pubkeys that have access to a given relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
14
15The following tags are required:
16
17- A [NIP 70](./70.md) `-` tag
18- A `member` tag containing a hex pubkey should be included for each member
19
20This list should not be considered exhaustive or authoritative. To determine membership, both a `kind 13534` event by the relay, and a `kind 10010` event by the member should be consulted.
21
22Example:
23
24```jsonc
25{
26 "kind": 13534,
27 "pubkey": "<nip11.self>",
28 "tags": [
29 ["-"],
30 ["member", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"],
31 ["member", "ee1d336e13779e4d4c527b988429d96de16088f958cbf6c074676ac9cfd9c958"]
32 ],
33 // ...other fields
34}
35```
36
37## Add User
38
39Relays MAY publish a `kind 8000` event when a member is added to the relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
40
41The following tags are required:
42
43- A [NIP 70](./70.md) `-` tag
44- A `p` tag indicating the member's hex pubkey
45
46Example:
47
48```jsonc
49{
50 "kind": 8000,
51 "pubkey": "<nip11.self>",
52 "tags": [
53 ["-"],
54 ["p", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"]
55 ],
56 // ...other fields
57}
58```
59
60## Remove User
61
62Relays MAY publish a `kind 8001` event when a member is removed from the relay. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
63
64The following tags are required:
65
66- A [NIP 70](./70.md) `-` tag
67- A `p` tag indicating the member's hex pubkey
68
69Example:
70
71```jsonc
72{
73 "kind": 8001,
74 "pubkey": "<nip11.self>",
75 "tags": [
76 ["-"],
77 ["p", "c308e1f882c1f1dff2a43d4294239ddeec04e575f2d1aad1fa21ea7684e61fb5"]
78 ],
79 // ...other fields
80}
81```
82
83## Join Request
84
85A user MAY send a `kind 28934` to a relay in order to request admission. It MUST have a `claim` tag containing an invite code. The event's `created_at` MUST be now, plus or minus a few minutes.
86
87```jsonc
88{
89 "kind": 28934,
90 "pubkey": "<user pubkey>",
91 "tags": [
92 ["-"],
93 ["claim", "<invite code>"]
94 ],
95 // ...other fields
96}
97```
98
99Upon receiving a claim, a relay MUST notify the client as to what the status of the claim is using an `OK` message. Failed claims SHOULD use the same standard `"restricted: "` prefix specified by NIP 42.
100
101Relays SHOULD update their `kind 13534` member list and MAY publish a `kind 8000` "add member" event.
102
103Some examples:
104
105```
106["OK", <event-id>, false, "restricted: that invite code is expired."]
107["OK", <event-id>, false, "restricted: that is an invalid invite code."]
108["OK", <event-id>, true, "duplicate: you are already a member of this relay."]
109["OK", <event-id>, true, "info: welcome to wss://relay.bunk.skunk!"]
110```
111
112## Invite Request
113
114Users may request a claim string from a relay by making a request for `kind 28935` events. This event MUST be signed by the pubkey specified in the `self` field of the relay's [NIP 11](./11.md) document.
115
116```jsonc
117{
118 "kind": 28935,
119 "pubkey": "<nip11.self>",
120 "tags": [
121 ["-"],
122 ["claim", "<invite code>"],
123 ],
124 // ...other fields
125}
126```
127
128Note that these events are in the `ephemeral` range, which means relays must explicitly opt-in to this behavior by generating claims on the fly when requested. This allows relays to improve security by issuing a different claim for each request, only issuing claims to certain users, or expiring claims.
129
130## Leave Request
131
132A user MAY send a `kind 28936` to a relay in order to request that their access be revoked. The event's `created_at` MUST be now, plus or minus a few minutes. This event MUST include a [NIP 70](./70.md) `-` tag.
133
134```jsonc
135{
136 "kind": 28936,
137 "tags": [["-"]],
138 // ...other fields
139}
140```
141
142Relays SHOULD update their `kind 13534` member list and MAY publish a `kind 8001` "remove member" event.
143
144## Implementation
145
146Clients MUST only request `kind 28935` events from and send `kind 28934` events to relays which include this NIP in the `supported_nips` section of its [NIP 11](./11.md) relay information document.
diff --git a/44.md b/44.md
index 8ba966c..47b8990 100644
--- a/44.md
+++ b/44.md
@@ -84,12 +84,12 @@ NIP-44 version 2 has the following design characteristics:
84 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76) 84 - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32), `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
854. Add padding 854. Add padding
86 - Content must be encoded from UTF-8 into byte array 86 - Content must be encoded from UTF-8 into byte array
87 - Validate plaintext length. Minimum is 1 byte, maximum is 4294967296 bytes 87 - Validate plaintext length. Minimum is 1 byte, maximum is 4,294,967,295 bytes
88 - Padding format is: `[plaintext_length: u16][plaintext][zero_bytes]`
89 - Padding algorithm is related to powers-of-two, with min padded msg size of 32 bytes 88 - Padding algorithm is related to powers-of-two, with min padded msg size of 32 bytes
90 - Plaintext length is encoded in big-endian: 89 - Plaintext length prefix is encoded in big-endian:
91 - if smaller than 65536, as a u16 in the first 2 bytes of the padded blob; 90 - If length is less than 65536: prefix is 2 bytes (`u16`), format is `[plaintext_length: u16][plaintext][zero_bytes]`
92 - if greater than 65536, the first 6 bytes of the padded blob, the first 2 being zero and the other 4 being the actual encoded length as u32 91 - If length is 65536 or greater: prefix is 6 bytes (2 zero bytes + `u32`), format is `[0x00, 0x00][plaintext_length: u32][plaintext][zero_bytes]`
92 - A zero value in the first 2 bytes signals the extended format; since valid plaintext is at least 1 byte, a u16 length of 0 is otherwise invalid
935. Encrypt padded content 935. Encrypt padded content
94 - Use ChaCha20, with key and nonce from step 3 94 - Use ChaCha20, with key and nonce from step 3
956. Calculate MAC (message authentication code) 956. Calculate MAC (message authentication code)
@@ -114,8 +114,8 @@ validation rules, refer to BIP-340.
1142. Decode base64 1142. Decode base64
115 - Base64 is decoded into `version, nonce, ciphertext, mac` 115 - Base64 is decoded into `version, nonce, ciphertext, mac`
116 - If the version is unknown, implementations must indicate that the encryption version is not supported 116 - If the version is unknown, implementations must indicate that the encryption version is not supported
117 - Validate length of base64 message to prevent DoS on base64 decoder: it can be in range from 132 to 87472 chars 117 - Validate minimum length of base64 message to prevent DoS on base64 decoder: it must be at least 132 chars
118 - Validate length of decoded message to verify output of the decoder: it can be in range from 99 to 65603 bytes 118 - Validate minimum length of decoded message to verify output of the decoder: it must be at least 99 bytes
1193. Calculate conversation key 1193. Calculate conversation key
120 - See step 1 of [encryption](#Encryption) 120 - See step 1 of [encryption](#Encryption)
1214. Calculate message keys 1214. Calculate message keys
@@ -126,12 +126,23 @@ validation rules, refer to BIP-340.
1266. Decrypt ciphertext 1266. Decrypt ciphertext
127 - Use ChaCha20 with key and nonce from step 3 127 - Use ChaCha20 with key and nonce from step 3
1287. Remove padding 1287. Remove padding
129 - Read the first 2 bytes, 129 - Read the first 2 bytes as a big-endian u16
130 - if they're zero, read the next 4 bytes as the u32 big-endian plaintext length; 130 - If zero, read the next 4 bytes as a big-endian u32 plaintext length (6-byte prefix total)
131 - otherwise interpret those 2 bytes as the u16 plaintext length 131 - Otherwise, use those 2 bytes as the u16 plaintext length (2-byte prefix total)
132 - Verify that the length of sliced plaintext matches the value of the two BE bytes 132 - Verify that the length of sliced plaintext matches the decoded length
133 - Verify that calculated padding from step 3 of the [encryption](#Encryption) process matches the actual padding 133 - Verify that calculated padding from step 3 of the [encryption](#Encryption) process matches the actual padding
134 134
135### Implementation considerations
136
137The theoretical maximum plaintext size is 2^32 - 1 bytes (~4 GB). Implementations SHOULD enforce
138their own maximum payload size based on platform and resource constraints, rejecting oversized payloads
139early in `decode_payload` (before base64 decoding) to prevent denial-of-service. Decryption may require
140several times the payload size in working memory due to base64 decoding, byte array slicing, and
141padding operations. For reference, JVM-based systems are limited to ~2 GB contiguous arrays, and mobile
142devices may have significantly less available memory. Note that `calc_padded_len` can return values up
143to 2^32, which exceeds the range of unsigned 32-bit integers; implementations must use 64-bit (or
144larger) arithmetic for padding calculations.
145
135### Details 146### Details
136 147
137- Cryptographic methods 148- Cryptographic methods
@@ -152,6 +163,9 @@ validation rules, refer to BIP-340.
152 - `x[i:j]`, where `x` is a byte array and `i, j <= 0` returns a `(j - i)`-byte array with a copy of the 163 - `x[i:j]`, where `x` is a byte array and `i, j <= 0` returns a `(j - i)`-byte array with a copy of the
153 `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x`. 164 `i`-th byte (inclusive) to the `j`-th byte (exclusive) of `x`.
154- Constants `c`: 165- Constants `c`:
166 - `min_plaintext_size` is 1. 1 byte msg is padded to 32 bytes.
167 - `max_plaintext_size` is 4294967295 (2^32 - 1).
168 - `extended_prefix_threshold` is 65536. Lengths below this use a 2-byte u16 prefix; lengths at or above use a 6-byte prefix (2 zero bytes + u32).
155- Functions 169- Functions
156 - `base64_encode(string)` and `base64_decode(bytes)` are Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648), with padding) 170 - `base64_encode(string)` and `base64_decode(bytes)` are Base64 ([RFC 4648](https://datatracker.ietf.org/doc/html/rfc4648), with padding)
157 - `concat` refers to byte array concatenation 171 - `concat` refers to byte array concatenation
@@ -159,6 +173,9 @@ validation rules, refer to BIP-340.
159 - `utf8_encode(string)` and `utf8_decode(bytes)` transform string to byte array and back 173 - `utf8_encode(string)` and `utf8_decode(bytes)` transform string to byte array and back
160 - `write_u8(number)` restricts number to values 0..255 and encodes into Big-Endian uint8 byte array 174 - `write_u8(number)` restricts number to values 0..255 and encodes into Big-Endian uint8 byte array
161 - `write_u16_be(number)` restricts number to values 0..65535 and encodes into Big-Endian uint16 byte array 175 - `write_u16_be(number)` restricts number to values 0..65535 and encodes into Big-Endian uint16 byte array
176 - `write_u32_be(number)` restricts number to values 0..4294967295 and encodes into Big-Endian uint32 byte array
177 - `read_uint16_be(bytes)` reads 2 bytes as a Big-Endian unsigned 16-bit integer
178 - `read_uint32_be(bytes)` reads 4 bytes as a Big-Endian unsigned 32-bit integer
162 - `zeros(length)` creates byte array of length `length >= 0`, filled with zeros 179 - `zeros(length)` creates byte array of length `length >= 0`, filled with zeros
163 - `floor(number)` and `log2(number)` are well-known mathematical methods 180 - `floor(number)` and `log2(number)` are well-known mathematical methods
164 181
@@ -178,51 +195,51 @@ def calc_padded_len(unpadded_len):
178 if unpadded_len <= 32: 195 if unpadded_len <= 32:
179 return 32 196 return 32
180 else: 197 else:
181 return chunk * (floor((len - 1) / chunk) + 1) 198 return chunk * (floor((unpadded_len - 1) / chunk) + 1)
182 199
183# Converts unpadded plaintext to padded bytearray 200# Converts unpadded plaintext to padded bytearray
184def pad(plaintext): 201def pad(plaintext):
185 unpadded = utf8_encode(plaintext) 202 unpadded = utf8_encode(plaintext)
186 unpadded_len = len(plaintext) 203 unpadded_len = len(unpadded)
187 if (unpadded_len < 1 or 204 if (unpadded_len < c.min_plaintext_size or
188 unpadded_len > 4294967295): raise Exception('invalid plaintext length') 205 unpadded_len > c.max_plaintext_size): raise Exception('invalid plaintext length')
189 if unpadded_len > 65536: 206 if unpadded_len >= c.extended_prefix_threshold:
190 prefix = concat( 207 prefix = concat([0, 0], write_u32_be(unpadded_len)) # 6 bytes
191 [0, 0],
192 write_u32_be(unpadded_len),
193 )
194 else: 208 else:
195 prefix = write_u16_be(unpadded_len) 209 prefix = write_u16_be(unpadded_len) # 2 bytes
196 suffix = zeros(calc_padded_len(unpadded_len) - unpadded_len) 210 suffix = zeros(calc_padded_len(unpadded_len) - unpadded_len)
197 return concat(prefix, unpadded, suffix) 211 return concat(prefix, unpadded, suffix)
198 212
199# Converts padded bytearray to unpadded plaintext 213# Converts padded bytearray to unpadded plaintext
200def unpad(padded): 214def unpad(padded):
201 unpadded_len = read_uint16_be(padded[0:2]) 215 first_two = read_uint16_be(padded[0:2])
202 if unpadded_len == 0: 216 if first_two == 0:
203 unpadded_len = read_uint32_be(padded[2:6]) 217 unpadded_len = read_uint32_be(padded[2:6])
204 unpadded = padded[6:6+unpadded_len] 218 if unpadded_len < c.extended_prefix_threshold: raise Exception('invalid padding')
219 prefix_len = 6
205 else: 220 else:
206 unpadded = padded[2:2+unpadded_len] 221 unpadded_len = first_two
207 222 prefix_len = 2
223 unpadded = padded[prefix_len:prefix_len+unpadded_len]
208 if (unpadded_len == 0 or 224 if (unpadded_len == 0 or
209 len(unpadded) != unpadded_len or 225 len(unpadded) != unpadded_len or
210 len(padded) != 2 + calc_padded_len(unpadded_len)): raise Exception('invalid padding') 226 len(padded) != prefix_len + calc_padded_len(unpadded_len)): raise Exception('invalid padding')
211 return utf8_decode(unpadded) 227 return utf8_decode(unpadded)
212 228
213# metadata: always 65b (version: 1b, nonce: 32b, max: 32b) 229# metadata: always 65b (version: 1b, nonce: 32b, mac: 32b)
214# plaintext: 1b to 0xffff 230# plaintext: 1b to 0xffffffff
215# padded plaintext: 32b to 0xffff 231# padded plaintext (small, <65536): 32b to 0x10000, with 2b prefix -> 34b to 0x10000+2
216# ciphertext: 32b+2 to 0xffff+2 232# padded plaintext (large, >=65536): 0x10000 to 0x100000000, with 6b prefix -> 0x10006 to 0x100000000+6
217# raw payload: 99 (65+32+2) to 65603 (65+0xffff+2) 233# ciphertext: same as padded plaintext (chacha20 doesn't change length)
218# compressed payload (base64): 132b to 87472b 234# raw payload (small): 99 (65+34) to 65603 (65+0x10000+2)
235# raw payload (large): 65607 (65+0x10006) to 4294967367 (65+0x100000000+6)
219def decode_payload(payload): 236def decode_payload(payload):
220 plen = len(payload) 237 plen = len(payload)
221 if plen == 0 or payload[0] == '#': raise Exception('unknown version') 238 if plen == 0 or payload[0] == '#': raise Exception('unknown version')
222 if plen < 132 or plen > 87472: raise Exception('invalid payload size') 239 if plen < 132: raise Exception('invalid payload size')
223 data = base64_decode(payload) 240 data = base64_decode(payload)
224 dlen = len(d) 241 dlen = len(data)
225 if dlen < 99 or dlen > 65603: raise Exception('invalid data size'); 242 if dlen < 99: raise Exception('invalid data size');
226 vers = data[0] 243 vers = data[0]
227 if vers != 2: raise Exception('unknown version ' + vers) 244 if vers != 2: raise Exception('unknown version ' + vers)
228 nonce = data[1:33] 245 nonce = data[1:33]
@@ -308,3 +325,27 @@ The file also contains intermediate values. A quick guidance with regards to its
308- `invalid.encrypt_msg_lengths` 325- `invalid.encrypt_msg_lengths`
309- `invalid.get_conversation_key`: calculating conversation_key must throw an error 326- `invalid.get_conversation_key`: calculating conversation_key must throw an error
310- `invalid.decrypt`: decrypting message content must throw an error 327- `invalid.decrypt`: decrypting message content must throw an error
328
329#### Extended length prefix test vectors
330
331The following test vectors exercise the boundary between the 2-byte u16 prefix and the 6-byte
332extended prefix. Since the payloads are too large to include inline, SHA-256 checksums of the
333plaintext and base64-encoded payload are provided (following the `encrypt_decrypt_long_msg` pattern).
334
335All vectors use the same `conversation_key` and `nonce` as above. Plaintext is the byte `0x61`
336(`'a'`) repeated to the specified length.
337
338```
339conversation_key: c41c775356fd92eadc63ff5a0dc1da211b268cbea22316767095b2871ea1412d
340nonce: 0000000000000000000000000000000000000000000000000000000000000001
341```
342
343| plaintext_len | prefix | padded_len | plaintext_sha256 | payload_sha256 |
344|---|---|---|---|---|
345| 65535 | u16 (2 bytes) | 65536 | `6e1bebca6a8229364a162a72ef064826c4cd7457bf54f190ef782bd9deff3e42` | `6d8c2810d1e870fbaa1f0a0937126cca837a15f9260e27060c331d70a3c0bc84` |
346| 65536 | extended (6 bytes) | 65536 | `bf718b6f653bebc184e1479f1935b8da974d701b893afcf49e701f3e2f9f9c5a` | `b7b4edb36ba92e267d322d56d9aebc22e7fa96ff52e3c12adc07f07a43cbc616` |
347| 65537 | extended (6 bytes) | 81920 | `008ffc88d3c96a9f307524eb361e47c5222a887fc45fa0c1fb8d429c5c23b430` | `eeb7c7c5373894ea2c1547cfd3ccb15d5a0b2d619da852e5c79df792dcc9e435` |
348
349Note that 65535 and 65536 both have a `padded_len` of 65536, but the total padded-with-prefix
350sizes differ: 65538 (2 + 65536) vs 65542 (6 + 65536). The jump to 65537 triggers the next
351padding bucket at 81920.
diff --git a/45.md b/45.md
index 219368e..794dbe8 100644
--- a/45.md
+++ b/45.md
@@ -4,7 +4,7 @@ NIP-45
4Event Counts 4Event Counts
5------------ 5------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9Relays may support the verb `COUNT`, which provides a mechanism for obtaining event counts. 9Relays may support the verb `COUNT`, which provides a mechanism for obtaining event counts.
10 10
@@ -14,47 +14,137 @@ Some queries a client may want to execute against connected relays are prohibiti
14 14
15## Filters and return values 15## Filters and return values
16 16
17This NIP defines the verb `COUNT`, which accepts a subscription id and filters as specified in [NIP 01](01.md) for the verb `REQ`. Multiple filters are OR'd together and aggregated into a single count result. 17This NIP defines the verb `COUNT`, which accepts a query id and filters as specified in [NIP 01](01.md) for the verb `REQ`. Multiple filters are OR'd together and aggregated into a single count result.
18 18
19``` 19```
20["COUNT", <subscription_id>, <filters JSON>...] 20["COUNT", <query_id>, <filters JSON>...]
21``` 21```
22 22
23Counts are returned using a `COUNT` response in the form `{"count": <integer>}`. Relays may use probabilistic counts to reduce compute requirements. 23Counts are returned using a `COUNT` response in the form `{"count": <integer>}`. Relays may use probabilistic counts to reduce compute requirements.
24In case a relay uses probabilistic counts, it MAY indicate it in the response with `approximate` key i.e. `{"count": <integer>, "approximate": <true|false>}`. 24In case a relay uses probabilistic counts, it MAY indicate it in the response with `approximate` key i.e. `{"count": <integer>, "approximate": <true|false>}`.
25 25
26``` 26```
27["COUNT", <subscription_id>, {"count": <integer>}] 27["COUNT", <query_id>, {"count": <integer>}]
28``` 28```
29 29
30Whenever the relay decides to refuse to fulfill the `COUNT` request, it MUST return a `CLOSED` message. 30Whenever the relay decides to refuse to fulfill the `COUNT` request, it MUST return a `CLOSED` message.
31 31
32## HyperLogLog
33
34Relays may return an HyperLogLog value together with the count, hex-encoded.
35
36```
37["COUNT", <subscription_id>, {"count": <integer>, "hll": "<hex>"}]
38```
39
40This is so it enables merging results from multiple relays and yielding a reasonable estimate of reaction counts, comment counts and follower counts, while saving many millions of bytes of bandwidth for everybody.
41
42### Algorithm
43
44This section describes the steps a relay should take in order to return HLL values to clients.
45
461. Upon receiving a filter, if it is eligible (see below) for HyperLogLog, compute the deterministic `offset` for that filter (see below);
472. Initialize 256 registers to `0` for the HLL value;
483. For all the events that are to be counted according to the filter, do this:
49 1. Read the byte at position `offset` of the event `pubkey`, its value will be the register index `ri`;
50 2. Count the number of leading zero bits starting at position `offset+1` of the event `pubkey` and add `1`;
51 3. Compare that with the value stored at register `ri`, if the new number is bigger, store it.
52
53That is all that has to be done on the relay side, and therefore the only part needed for interoperability.
54
55On the client side, these HLL values received from different relays can be merged (by simply going through all the registers in HLL values from each relay and picking the highest value for each register, regardless of the relay).
56
57And finally the absolute count can be estimated by running some methods I don't dare to describe here in English, it's better to check some implementation source code (also, there can be different ways of performing the estimation, with different quirks applied on top of the raw registers).
58
59### `offset` computation
60
61The `offset` (used in the HLL computation above) is derived deterministically from the filter sent by the client to the relay. The formula for obtaining the `offset` value is as follows:
62
63 1. Take the first tag attribute in the filter (with the `#` prefix);
64 2. From that, take its first item (it will be a string);
65 3. Obtain a 32-byte hex string from it:
66 - if the string is an event id or pubkey hex, use it as it is;
67 - if the string is an address (`<kind>:<pubkey>:<d-tag>`), use the `<pubkey>` part;
68 - if the string is anything else, hash it with a `sha256()` and take the result as a hex string;
69 4. From the 64-character hex string obtained before, take the character at position `32`;
70 5. Read that character as a base-16 number;
71 6. Add 8 to it: the result is the `offset`.
72
73For cases not covered above (filters without a tag attribute, for example), behavior isn't yet defined. This NIP may be modified later to specify those, but for now there isn't a use case that justifies using HLL in those circumstances.
74
75### Rationale
76
77The value of `offset` must be deterministic because that's the only way to allow relays to cache the HLL values so they don't have to count thousands of events from the database on every query. It also allows relays to precompute HLL values for any given target `<id>` or `<pubkey>` without having to store the events themselves directly, which can be handy in case of reactions, for example.
78
79### Common filters
80
81Some relays may decide to cache or precompute HLL values for some common canonical queries, and also to refrain from counting events that do not match these specs. These are such queries (this NIP can be modified later if more common useful queries are discovered and start being used):
82
83- **reaction count**: `{"#e": ["<id>"], "kinds": [7]}`
84- **repost count**: `{"#e": ["<id>"], "kinds": [6]}`
85- **quote count**: `{"#q": ["<id>"], "kinds": [1, 1111]}`
86- **reply count**: `{"#e": ["<id>"], "kinds": [1]}`
87- **comment count**: `{"#E": ["<id>"], "kinds": [1111]}`
88- **follower count**: `{"#p": ["<pubkey>"], "kinds": [3]}`
89
90Notice that these queries only include 1 tag attribute with always a single item in it, which means that implementors don't have to check the order in which these attributes show up in the filter.
91
92### Attack vectors
93
94One could mine a pubkey with a certain number of zero bits in the exact place where the HLL algorithm described above would look for them in order to artificially make its reaction or follow "count more" than others. For this to work a different pubkey would have to be created for each different target (event id, followed profile etc). This approach is not very different than creating tons of new pubkeys and using them all to send likes or follow someone in order to inflate their number of followers. The solution is the same in both cases: clients should not fetch these reaction counts from open relays that accept everything, they should base their counts on relays that perform some form of filtering that makes it more likely that only real humans are able to publish there and not bots or artificially-generated pubkeys.
95
96### `hll` encoding
97
98The value `hll` value must be the concatenation of the 256 registers, each being a uint8 value (i.e. a byte). Therefore `hll` will be a 512-character hex string.
99
100### Client-side usage
101
102This algorithm also allows clients to combine HLL responses received from relays with HLL counts computed locally from raw events. It's recommended that clients keep track of HLL values locally and add to these on each message received from relays. For example:
103
104 - a client wants to keep track of the number of reactions an event Z has received over time;
105 - the client has decided it will read reactions from relays A, B and C (the NIP-65 "read" relays of Z's author);
106 - of these, only B and C support HLL responses, so the client fetches both and merges them locally;
107 - then the client fetches all reaction events from A then manually applies each event to the HLL from the previous step, using the same algorithm described above;
108 - finally, the client reads the estimate count from the HLL and displays that to the user;
109 - optionally the client may store that HLL value (together with some "last-read-date" for relay A) and repeat the process again later:
110 - this time it only needs to fetch the new reactions from A and add those to the HLL
111 - and redownload the HLL values from B and C and just reapply them to the local value.
112
113This procedure allows the client to download much less data.
114
32## Examples 115## Examples
33 116
34### Followers count 117### Count notes and reactions
35 118
36``` 119```
37["COUNT", <subscription_id>, {"kinds": [3], "#p": [<pubkey>]}] 120["COUNT", <query_id>, {"kinds": [1, 7], "authors": [<pubkey>]}]
38["COUNT", <subscription_id>, {"count": 238}] 121["COUNT", <query_id>, {"count": 5}]
39``` 122```
40 123
41### Count posts and reactions 124### Count notes approximately
42 125
43``` 126```
44["COUNT", <subscription_id>, {"kinds": [1, 7], "authors": [<pubkey>]}] 127["COUNT", <query_id>, {"kinds": [1]}]
45["COUNT", <subscription_id>, {"count": 5}] 128["COUNT", <query_id>, {"count": 93412452, "approximate": true}]
129```
130
131### Followers count with HyperLogLog
132
133```
134["COUNT", <subscription_id>, {"kinds": [3], "#p": [<pubkey>]}]
135["COUNT", <subscription_id>, {"count": 16578, "hll": "0607070505060806050508060707070706090d080b0605090607070b07090606060b0705070709050807080805080407060906080707080507070805060509040a0b06060704060405070706080607050907070b08060808080b080607090a06060805060604070908050607060805050d05060906090809080807050e0705070507060907060606070708080b0807070708080706060609080705060604060409070a0808050a0506050b0810060a0908070709080b0a07050806060508060607080606080707050806080c0a0707070a080808050608080f070506070706070a0908090c080708080806090508060606090906060d07050708080405070708"}]
46``` 136```
47 137
48### Count posts approximately 138### Reaction counts with HyperLogLog
49 139
50``` 140```
51["COUNT", <subscription_id>, {"kinds": [1]}] 141["COUNT", <subscription_id>, {"kinds": [7], "#e": [<id>]}]
52["COUNT", <subscription_id>, {"count": 93412452, "approximate": true}] 142["COUNT", <subscription_id>, {"count": 2044, "hll": "01ef070505060806050508060707070706090d080b0605090607070b07090606060b0705070709050807080805080407060906080707080507070805060509040a0b06060704060405070706080607050907070b08060808080b080607090a06060805060604070908050607060805050d05060906090809080807050e0705070507060907060606070708080b0807070708080706060609080705060604060409070a0808050a0506050b0810060a0908070709080b0a07050806060508060607080606080707050806080c0a0707070a080808050608080f070506070706070a0908090c080708080806090508060606090906060d07050708080405070708"}]
53``` 143```
54 144
55### Relay refuses to count 145### Relay refuses to count
56 146
57``` 147```
58["COUNT", <subscription_id>, {"kinds": [4], "authors": [<pubkey>], "#p": [<pubkey>]}] 148["COUNT", <query_id>, {"kinds": [1059], "#p": [<pubkey>]}]
59["CLOSED", <subscription_id>, "auth-required: cannot count other people's DMs"] 149["CLOSED", <query_id>, "auth-required: cannot count other people's DMs"]
60``` 150```
diff --git a/46.md b/46.md
index 1701964..cdc1118 100644
--- a/46.md
+++ b/46.md
@@ -97,18 +97,25 @@ Each of the following are methods that the _client_ sends to the _remote-signer_
97 97
98| Command | Params | Result | 98| Command | Params | Result |
99| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------- | 99| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------- |
100| `connect` | `[<remote-signer-pubkey>, <optional_secret>, <optional_requested_permissions>]` | "ack" OR `<required-secret-value>` | 100| `connect` | `[<remote-signer-pubkey>, <optional_secret>, <optional_requested_perms>]` | `"ack"` OR `<required-secret-value>` |
101| `sign_event` | `[<{kind, content, tags, created_at}>]` | `json_stringified(<signed_event>)` | 101| `sign_event` | `[<{kind, content, tags, created_at}>]` | `json_stringified(<signed_event>)` |
102| `ping` | `[]` | "pong" | 102| `ping` | `[]` | `"pong"` |
103| `get_public_key` | `[]` | `<user-pubkey>` | 103| `get_public_key` | `[]` | `<user-pubkey>` |
104| `nip04_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip04_ciphertext>` | 104| `nip04_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip04_ciphertext>` |
105| `nip04_decrypt` | `[<third_party_pubkey>, <nip04_ciphertext_to_decrypt>]` | `<plaintext>` | 105| `nip04_decrypt` | `[<third_party_pubkey>, <nip04_ciphertext_to_decrypt>]` | `<plaintext>` |
106| `nip44_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip44_ciphertext>` | 106| `nip44_encrypt` | `[<third_party_pubkey>, <plaintext_to_encrypt>]` | `<nip44_ciphertext>` |
107| `nip44_decrypt` | `[<third_party_pubkey>, <nip44_ciphertext_to_decrypt>]` | `<plaintext>` | 107| `nip44_decrypt` | `[<third_party_pubkey>, <nip44_ciphertext_to_decrypt>]` | `<plaintext>` |
108| `switch_relays` | `[]` | `["<relay-url>", "<relay-url>", ...]` OR `null` |
108 109
109### Requested permissions 110### Requested permissions
110 111
111The `connect` method may be provided with `optional_requested_permissions` for user convenience. The permissions are a comma-separated list of `method[:params]`, i.e. `nip44_encrypt,sign_event:4` meaning permissions to call `nip44_encrypt` and to call `sign_event` with `kind:4`. Optional parameter for `sign_event` is the kind number, parameters for other methods are to be defined later. Same permission format may be used for `perms` field of `metadata` in `nostrconnect://` string. 112The `connect` method may be provided with `optional_requested_perms` for user convenience. The permissions are a comma-separated list of `method[:params]`, i.e. `nip44_encrypt,sign_event:4` meaning permissions to call `nip44_encrypt` and to call `sign_event` with `kind:4`. Optional parameter for `sign_event` is the kind number, parameters for other methods are to be defined later. Same permission format may be used for `perms` field of `metadata` in `nostrconnect://` string.
113
114### Switching relays
115
116At all times, the _remote-signer_ should be in control of what relays are being used for the connection between it and the _client_. Therefore it should be possible for it to evolve its set of relays over time as old relays go out of operation and new ones appear. Even more importantly, in the case of the connection initiated by the _client_ the client may pick relays completely foreign to the _remote-signer_'s preferences, so it must be possible for it to switch those immediately.
117
118Therefore, compliant clients should send a `switch_relays` request immediately upon establishing a connection (always, or at reasonable intervals). Upon receiving such requests, the _remote-signer_ should reply with its updated list of relays, or `null` if there is nothing to be changed. Immediately upon receiving an updated relay list, the _client_ should update its local state and send further requests on the new relays. The `remote-signer` should then be free to disconnect from the previous relays if that is desired.
112 119
113## Response Events `kind:24133` 120## Response Events `kind:24133`
114 121
@@ -204,7 +211,7 @@ _client_ should display (in a popup or new tab) the URL from the `error` field a
204 211
205### Announcing _remote-signer_ metadata 212### Announcing _remote-signer_ metadata
206 213
207_remote-signer_ MAY publish it's metadata by using [NIP-05](05.md) and [NIP-89](89.md). With NIP-05, a request to `<remote-signer>/.well-known/nostr.json?name=_` MAY return this: 214_remote-signer_ MAY publish its metadata by using [NIP-05](05.md) and [NIP-89](89.md). With NIP-05, a request to `<remote-signer>/.well-known/nostr.json?name=_` MAY return this:
208```jsonc 215```jsonc
209{ 216{
210 "names":{ 217 "names":{
diff --git a/47.md b/47.md
index 84f710e..6830820 100644
--- a/47.md
+++ b/47.md
@@ -28,15 +28,16 @@ Fundamentally NWC is communication between a **client** and **wallet service** b
28 28
29 4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI. 29 4. Once the payment is complete the **wallet service** will send an encrypted `response` (kind 23195) to the **user** over the relay(s) in the URI.
30 30
31 5. The **wallet service** may send encrypted notifications (kind 23196) of wallet events (such as a received payment) to the **client**. 31 5. The **wallet service** may send encrypted notifications (kind 23197) of wallet events (such as a received payment) to the **client**.
32 32
33## Events 33## Events
34 34
35There are four event kinds: 35There are four event kinds:
36
36- `NIP-47 info event`: 13194 37- `NIP-47 info event`: 13194
37- `NIP-47 request`: 23194 38- `NIP-47 request`: 23194
38- `NIP-47 response`: 23195 39- `NIP-47 response`: 23195
39- `NIP-47 notification event`: 23196 40- `NIP-47 notification event`: 23197 (23196 for backwards compatibility with NIP-04)
40 41
41### Info Event 42### Info Event
42 43
@@ -46,34 +47,71 @@ The content should be a plaintext string with the supported capabilities space-s
46 47
47If the **wallet service** supports notifications, the info event SHOULD contain a `notifications` tag with the supported notification types space-separated, eg. `payment_received payment_sent`. 48If the **wallet service** supports notifications, the info event SHOULD contain a `notifications` tag with the supported notification types space-separated, eg. `payment_received payment_sent`.
48 49
50It should also contain supported encryption modes as described in the [Encryption](#encryption) section. For example:
51
52```jsonc
53{
54 "kind": 13194,
55 "tags": [
56 ["encryption", "nip44_v2 nip04"], // List of supported encryption schemes as described in the Encryption section.
57 ["notifications", "payment_received payment_sent"]
58 // ...
59 ],
60 "content": "pay_invoice get_balance make_invoice lookup_invoice list_transactions get_info notifications",
61 // ...
62}
63```
64
49### Request and Response Events 65### Request and Response Events
50 66
51Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to. 67Both the request and response events SHOULD contain one `p` tag, containing the public key of the **wallet service** if this is a request, and the public key of the **client** if this is a response. The response event SHOULD contain an `e` tag with the id of the request event it is responding to.
52Optionally, a request can have an `expiration` tag that has a unix timestamp in seconds. If the request is received after this timestamp, it should be ignored. 68Optionally, a request can have an `expiration` tag that has a unix timestamp in seconds. If the request is received after this timestamp, it should be ignored.
53 69
54The content of requests and responses is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure: 70The content of requests and responses is encrypted with [NIP44](44.md), and is a JSON-RPCish object with a semi-fixed structure.
55 71
56Request: 72**Important note for backwards-compatibility:** The initial version of the protocol used [NIP04](04.md). If a **wallet service** or client app does not include the `encryption` tag in the
57```jsonc 73`info` or request events, it should be assumed that the connection is using NIP04 for encryption. See the [Encryption](#encryption) section for more information.
74
75Example request:
76
77```js
58{ 78{
59 "method": "pay_invoice", // method, string 79 "kind" 23194,
60 "params": { // params, object 80 "tags": [
61 "invoice": "lnbc50n1..." // command-related data 81 ["encryption", "nip44_v2"],
62 } 82 ["p", "03..." ] // public key of the wallet service.
83 // ...
84 ],
85 "content": nip44_encrypt({ // Encryption type corresponds to the `encryption` tag.
86 "method": "pay_invoice", // method, string
87 "params": { // params, object
88 "invoice": "lnbc50n1..." // command-related data
89 }
90 }),
63} 91}
64``` 92```
65 93
66Response: 94Example response:
67```jsonc 95
96```js
68{ 97{
69 "result_type": "pay_invoice", //indicates the structure of the result field 98 "kind" 23195,
70 "error": { //object, non-null in case of error 99 "tags": [
71 "code": "UNAUTHORIZED", //string error code, see below 100 ["p", "03..." ] // public key of the requesting client app
72 "message": "human readable error message" 101 ["e", "1234"] // id of the request event this is responding to
73 }, 102 // ...
74 "result": { // result, object. null in case of error. 103 ],
75 "preimage": "0123456789abcdef..." // command-related data 104 "content": nip44_encrypt({ // Encrypted using the scheme requested by the client.
76 } 105 "result_type": "pay_invoice", //indicates the structure of the result field
106 "error": { //object, non-null in case of error
107 "code": "UNAUTHORIZED", //string error code, see below
108 "message": "human readable error message"
109 },
110 "result": { // result, object. null in case of error.
111 "preimage": "0123456789abcdef..." // command-related data
112 }
113 })
114 // ...
77} 115}
78``` 116```
79 117
@@ -83,9 +121,9 @@ If the command was successful, the `error` field must be null.
83 121
84### Notification Events 122### Notification Events
85 123
86The notification event SHOULD contain one `p` tag, the public key of the **client**. 124The notification event is a kind 23197 event SHOULD contain one `p` tag, the public key of the **client**.
87 125
88The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPCish object with a semi-fixed structure: 126The content of notifications is encrypted with [NIP44](44.md) (or NIP-04 for legacy client apps), and is a JSON-RPCish object with a semi-fixed structure:
89 127
90```jsonc 128```jsonc
91{ 129{
@@ -96,7 +134,6 @@ The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPC
96} 134}
97``` 135```
98 136
99
100### Error codes 137### Error codes
101- `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds. 138- `RATE_LIMITED`: The client is sending commands too fast. It should retry in a few seconds.
102- `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented. 139- `NOT_IMPLEMENTED`: The command is not known or is intentionally not implemented.
@@ -105,6 +142,7 @@ The content of notifications is encrypted with [NIP04](04.md), and is a JSON-RPC
105- `RESTRICTED`: This public key is not allowed to do this operation. 142- `RESTRICTED`: This public key is not allowed to do this operation.
106- `UNAUTHORIZED`: This public key has no wallet connected. 143- `UNAUTHORIZED`: This public key has no wallet connected.
107- `INTERNAL`: An internal error. 144- `INTERNAL`: An internal error.
145- `UNSUPPORTED_ENCRYPTION`: The encryption type of the request is not supported by the wallet service.
108- `OTHER`: Other error. 146- `OTHER`: Other error.
109 147
110## Nostr Wallet Connect URI 148## Nostr Wallet Connect URI
@@ -148,6 +186,7 @@ Request:
148 "params": { 186 "params": {
149 "invoice": "lnbc50n1...", // bolt11 invoice 187 "invoice": "lnbc50n1...", // bolt11 invoice
150 "amount": 123, // invoice amount in msats, optional 188 "amount": 123, // invoice amount in msats, optional
189 "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional
151 } 190 }
152} 191}
153``` 192```
@@ -166,42 +205,6 @@ Response:
166Errors: 205Errors:
167- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. 206- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar.
168 207
169### `multi_pay_invoice`
170
171Description: Requests payment of multiple invoices.
172
173Request:
174```jsonc
175{
176 "method": "multi_pay_invoice",
177 "params": {
178 "invoices": [
179 {"id":"4da52c32a1", "invoice": "lnbc1...", "amount": 123}, // bolt11 invoice and amount in msats, amount is optional
180 {"id":"3da52c32a1", "invoice": "lnbc50n1..."},
181 ],
182 }
183}
184```
185
186Response:
187
188For every invoice in the request, a separate response event is sent. To differentiate between the responses, each
189response event contains a `d` tag with the id of the invoice it is responding to; if no id was given, then the
190payment hash of the invoice should be used.
191
192```jsonc
193{
194 "result_type": "multi_pay_invoice",
195 "result": {
196 "preimage": "0123456789abcdef...", // preimage of the payment
197 "fees_paid": 123, // value in msats, optional
198 }
199}
200```
201
202Errors:
203- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar.
204
205### `pay_keysend` 208### `pay_keysend`
206 209
207Request: 210Request:
@@ -236,44 +239,6 @@ Response:
236Errors: 239Errors:
237- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar. 240- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar.
238 241
239### `multi_pay_keysend`
240
241Description: Requests multiple keysend payments.
242
243Has an array of keysends, these follow the same semantics as `pay_keysend`, just done in a batch
244
245Request:
246```jsonc
247{
248 "method": "multi_pay_keysend",
249 "params": {
250 "keysends": [
251 {"id": "4c5b24a351", "pubkey": "03...", "amount": 123},
252 {"id": "3da52c32a1", "pubkey": "02...", "amount": 567, "preimage": "abc123..", "tlv_records": [{"type": 696969, "value": "77616c5f6872444873305242454d353736"}]},
253 ],
254 }
255}
256```
257
258Response:
259
260For every keysend in the request, a separate response event is sent. To differentiate between the responses, each
261response event contains a `d` tag with the id of the keysend it is responding to; if no id was given, then the
262pubkey should be used.
263
264```jsonc
265{
266 "result_type": "multi_pay_keysend",
267 "result": {
268 "preimage": "0123456789abcdef...", // preimage of the payment
269 "fees_paid": 123, // value in msats, optional
270 }
271}
272```
273
274Errors:
275- `PAYMENT_FAILED`: The payment failed. This may be due to a timeout, exhausting all routes, insufficient capacity or similar.
276
277### `make_invoice` 242### `make_invoice`
278 243
279Request: 244Request:
@@ -284,7 +249,8 @@ Request:
284 "amount": 123, // value in msats 249 "amount": 123, // value in msats
285 "description": "string", // invoice's description, optional 250 "description": "string", // invoice's description, optional
286 "description_hash": "string", // invoice's description hash, optional 251 "description_hash": "string", // invoice's description hash, optional
287 "expiry": 213 // expiry in seconds from time invoice is created, optional 252 "expiry": 213, // expiry in seconds from time invoice is created, optional
253 "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc, optional
288 } 254 }
289} 255}
290``` 256```
@@ -295,6 +261,7 @@ Response:
295 "result_type": "make_invoice", 261 "result_type": "make_invoice",
296 "result": { 262 "result": {
297 "type": "incoming", // "incoming" for invoices, "outgoing" for payments 263 "type": "incoming", // "incoming" for invoices, "outgoing" for payments
264 "state": "pending", // optional
298 "invoice": "string", // encoded invoice, optional 265 "invoice": "string", // encoded invoice, optional
299 "description": "string", // invoice's description, optional 266 "description": "string", // invoice's description, optional
300 "description_hash": "string", // invoice's description hash, optional 267 "description_hash": "string", // invoice's description hash, optional
@@ -328,6 +295,7 @@ Response:
328 "result_type": "lookup_invoice", 295 "result_type": "lookup_invoice",
329 "result": { 296 "result": {
330 "type": "incoming", // "incoming" for invoices, "outgoing" for payments 297 "type": "incoming", // "incoming" for invoices, "outgoing" for payments
298 "state": "pending", // can be "pending", "settled", "accepted" (for hold invoices), "expired" (for invoices) or "failed" (for payments), optional
331 "invoice": "string", // encoded invoice, optional 299 "invoice": "string", // encoded invoice, optional
332 "description": "string", // invoice's description, optional 300 "description": "string", // invoice's description, optional
333 "description_hash": "string", // invoice's description hash, optional 301 "description_hash": "string", // invoice's description hash, optional
@@ -376,6 +344,7 @@ Response:
376 "transactions": [ 344 "transactions": [
377 { 345 {
378 "type": "incoming", // "incoming" for invoices, "outgoing" for payments 346 "type": "incoming", // "incoming" for invoices, "outgoing" for payments
347 "state": "pending", // can be "pending", "settled", "accepted" (for hold invoices), "expired" (for invoices) or "failed" (for payments), optional
379 "invoice": "string", // encoded invoice, optional 348 "invoice": "string", // encoded invoice, optional
380 "description": "string", // invoice's description, optional 349 "description": "string", // invoice's description, optional
381 "description_hash": "string", // invoice's description hash, optional 350 "description_hash": "string", // invoice's description hash, optional
@@ -440,6 +409,89 @@ Response:
440} 409}
441``` 410```
442 411
412### `make_hold_invoice`
413
414Creates a hold invoice using a pre-generated preimage.
415
416Request:
417```jsonc
418{
419 "method": "make_hold_invoice",
420 "params": {
421 "amount": 123, // value in msats
422 "description": "string", // invoice's description, optional
423 "description_hash": "string", // invoice's description hash, optional
424 "expiry": 213 // expiry in seconds from time invoice is created for a payment to be initiated, optional. This does not determine how long a payment can be held (see `settle_deadline`)
425 "payment_hash": "string" // Payment hash for the payment generated from the preimage
426 "min_cltv_expiry_delta": 144 // The minimum CLTV delta to use for the final hop, optional
427 }
428}
429```
430
431Response:
432```jsonc
433{
434 "result_type": "make_hold_invoice",
435 "result": {
436 "type": "incoming", // "incoming" for invoices, "outgoing" for payments
437 "invoice": "string", // encoded invoice, optional
438 "description": "string", // invoice's description, optional
439 "description_hash": "string", // invoice's description hash, optional
440 "payment_hash": "string", // Payment hash for the payment
441 "amount": 123, // value in msats
442 "created_at": unixtimestamp, // invoice/payment creation time
443 "expires_at": unixtimestamp, // invoice expiration time, optional if not applicable
444 "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
445 }
446}
447```
448
449### `cancel_hold_invoice`
450
451Cancels a hold invoice using the payment hash
452
453Request:
454```jsonc
455{
456 "method": "cancel_hold_invoice",
457 "params": {
458 "payment_hash": "string" // Payment hash for the payment generated from the preimage
459 }
460}
461```
462
463Response:
464```jsonc
465{
466 "result_type": "cancel_hold_invoice",
467 "result": {}
468}
469```
470
471### `settle_hold_invoice`
472
473Settles a hold invoice using the preimage
474
475
476Request:
477```jsonc
478{
479 "method": "settle_hold_invoice",
480 "params": {
481 "preimage": "string" // preimage for the payment
482 }
483}
484```
485
486Response:
487```jsonc
488{
489 "result_type": "settle_hold_invoice",
490 "result": {}
491}
492```
493
494
443## Notifications 495## Notifications
444 496
445### `payment_received` 497### `payment_received`
@@ -452,6 +504,7 @@ Notification:
452 "notification_type": "payment_received", 504 "notification_type": "payment_received",
453 "notification": { 505 "notification": {
454 "type": "incoming", 506 "type": "incoming",
507 "state": "settled", // optional
455 "invoice": "string", // encoded invoice 508 "invoice": "string", // encoded invoice
456 "description": "string", // invoice's description, optional 509 "description": "string", // invoice's description, optional
457 "description_hash": "string", // invoice's description hash, optional 510 "description_hash": "string", // invoice's description hash, optional
@@ -477,6 +530,7 @@ Notification:
477 "notification_type": "payment_sent", 530 "notification_type": "payment_sent",
478 "notification": { 531 "notification": {
479 "type": "outgoing", 532 "type": "outgoing",
533 "state": "settled", // optional
480 "invoice": "string", // encoded invoice 534 "invoice": "string", // encoded invoice
481 "description": "string", // invoice's description, optional 535 "description": "string", // invoice's description, optional
482 "description_hash": "string", // invoice's description hash, optional 536 "description_hash": "string", // invoice's description hash, optional
@@ -492,6 +546,30 @@ Notification:
492} 546}
493``` 547```
494 548
549### `hold_invoice_accepted`
550
551Description: Sent when a payer accepts (locks in) a hold invoice. To avoid locking up funds in channels the hold invoice SHOULD be settled or canceled within a few minutes of receiving this event.
552
553Notification:
554```jsonc
555{
556 "notification_type": "hold_invoice_accepted",
557 "notification": {
558 "type": "incoming",
559 "state": "accepted", // optional
560 "invoice": "string", // encoded invoice
561 "description": "string", // invoice's description, optional
562 "description_hash": "string", // invoice's description hash, optional
563 "payment_hash": "string", // Payment hash for the payment
564 "amount": 123, // value in msats
565 "created_at": unixtimestamp, // invoice/payment creation time
566 "expires_at": unixtimestamp, // invoice expiration time
567 "settle_deadline": blocknumber, // invoice can only be safely settled or canceled before this block number.
568 "metadata": {} // generic metadata that can be used to add things like zap/boostagram details for a payer name/comment/etc.
569 }
570}
571```
572
495## Example pay invoice flow 573## Example pay invoice flow
496 574
4970. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect://` deeplink or configure the connection details manually. 5750. The user scans the QR code generated by the **wallet service** with their **client** application, they follow a `nostr+walletconnect://` deeplink or configure the connection details manually.
@@ -499,9 +577,89 @@ Notification:
4992. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment. 5772. **wallet service** verifies that the author's key is authorized to perform the payment, decrypts the payload and sends the payment.
5003. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage. 5783. **wallet service** responds to the event by sending an event with kind `23195` and content being a response either containing an error message or a preimage.
501 579
580## Encryption
581
582The initial version of NWC used [NIP-04](04.md) for encryption which has been deprecated and replaced by [NIP-44](44.md). NIP-44 should always be preferred for encryption, but there may be legacy cases
583where the **wallet service** or **client** has not yet migrated to NIP-44. The **wallet service** and **client** should negotiate the encryption method to use based on the `encryption` tag in the `info` event.
584
585The encryption tag can contain either `nip44_v2` or `nip04`. The absence of this tag implies that the wallet only supports `nip04`.
586
587| Encryption code | Use | Notes |
588|-----------------|----------------------|---------------------------------------------------------|
589| `nip44_v2` | NIP-44 | Required |
590| `nip04` | NIP-04 | Deprecated and only here for backward compatibility |
591| `<not present>` | NIP-04 | Deprecated and only here for backward compatibility |
592
593The negotiation works as follows.
594
5951. The **wallet service** includes an `encryption` tag in the `info` event. This tag contains a space-separated list of encryption schemes that the **wallet service** supports (eg. `nip44_v2 nip04`)
5962. The **client application** includes an `encryption` tag in each request event. This tag contains the encryption scheme which should be used for the request. The **client application** should always prefer nip44 if supported by the **wallet service**.
597
598When a **client application** establishes a connection, it should read the info event and look for the `encryption` tag.
599
600**Absence of this tag implies that the wallet only supports nip04.**
601
602If the `encryption` tag is present, the **client application** will choose optimal encryption supported by both itself, and the **wallet service**, which should always be nip44 if possible.
603
604### Request events
605
606When a **client application** sends a request event, it should include a `encryption` tag with the encryption scheme it is using. The scheme MUST be supported by the **wallet service** as indicated by the info event.
607For example, if the client application supports nip44, the request event might look like:
608
609```jsonc
610{
611 "kind": 23194,
612 "tags": [
613 ["encryption", "nip44_v2"],
614 // ...
615 ],
616 // ...
617}
618```
619
620If the **wallet service** does not support the specified encryption scheme, it will return an `UNSUPPORTED_ENCRYPTION` error. Absence of the `encryption` tag indicates use of nip04 for encryption.
621
622### Notification events
623
624If a **wallet service** supports both nip04 and nip44, it should publish two notification events for each notification - kind 23196 encrypted with NIP-04, and kind 23197 encrypted with NIP-44. If the **wallet service** only supports nip44, it should only publish kind 23197 events.
625
626The **client** should check the `encryption` tag in the `info` event to determine which encryption schemes the **wallet service** supports, and listen to the appropriate notification event.
627
502## Using a dedicated relay 628## Using a dedicated relay
503This 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. 629This 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.
504 630
631## Metadata
632Metadata 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.
633
634NWC 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.
635
636Here are some properties that are recognized by some NWC clients:
637
638```jsonc
639{
640 "comment": "string", // LUD-12 comment
641 "payer_data": {
642 "email": "string",
643 "name": "string",
644 "pubkey": "string",
645 }, // LUD-18 payer data
646 "recipient_data": {
647 "identifier": "string"
648 }, // similar to LUD-18 payer data, but to record recipient data e.g. the lightning address of the recipient
649 "nostr": {
650 "pubkey": "string",
651 "tags": [],
652 // ... rest of zap request event
653 }, // NIP-57 Zap Request event (kind 9734)
654 "tlv_records": [
655 {
656 "type": 5482373484, // tlv type
657 "value": "0123456789abcdef" // hex encoded tlv value
658 }
659 ] // keysend TLV records (e.g. for podcasting 2.0 boostagrams)
660} & Record<string, unknown>;
661```
662
505## Appendix 663## Appendix
506 664
507### Example NIP-47 info event 665### Example NIP-47 info event
@@ -513,6 +671,7 @@ This NIP does not specify any requirements on the type of relays used. However,
513 "created_at": 1713883677, 671 "created_at": 1713883677,
514 "kind": 13194, 672 "kind": 13194,
515 "tags": [ 673 "tags": [
674 [ "encryption", "nip44_v2 nip04" ],
516 [ 675 [
517 "notifications", 676 "notifications",
518 "payment_received payment_sent" 677 "payment_received payment_sent"
@@ -522,3 +681,30 @@ This NIP does not specify any requirements on the type of relays used. However,
522 "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8" 681 "sig": "31f57b369459b5306a5353aa9e03be7fbde169bc881c3233625605dd12f53548179def16b9fe1137e6465d7e4d5bb27ce81fd6e75908c46b06269f4233c845d8"
523} 682}
524``` 683```
684
685### Example Hold Invoice Support Flow
686
6871. Client generates a 32-byte hex-encoded preimage.
6882. Computes SHA-256 to derive payment hash.
6893. Sends `make_hold_invoice` with payment hash and desired parameters.
6904. Waits for `hold_invoice_accepted` notification.
6915. Upon receiving notification, either:
692
693 * Calls `settle_hold_invoice` with the original preimage to release funds, or
694 * Calls `cancel_hold_invoice` with payment hash to abort.
695
696### Deep-links
697
698Wallet applications can register deeplinks in mobile systems to make it possible to create a linking UX that doesn't require the user scanning a QR code or pasting some code.
699
700`nostrnwc://connect` and `nostrnwc+{app_name}://connect` can be registered by wallet apps and queried by apps that want to receive an NWC pairing code.
701
702All URI parameters, MUST be URI-encoded.
703
704URI parameters:
705 * `appicon` -- URL to an icon of the client that wants to create a connection.
706 * `appname` -- Name of the client that wants to create a connection.
707 * `callback` -- URI schema the wallet should open with the connection string
708
709Once a connection has been created by the wallet, it should be returned to the client by opening the callback with the following parameters
710 * `value` -- NWC pairing code (e.g. `nostr+walletconnect://...`)
diff --git a/50.md b/50.md
index 9eea1f8..08ce4fd 100644
--- a/50.md
+++ b/50.md
@@ -4,7 +4,7 @@ NIP-50
4Search Capability 4Search Capability
5----------------- 5-----------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9## Abstract 9## Abstract
10 10
diff --git a/51.md b/51.md
index 5583435..f9d6e56 100644
--- a/51.md
+++ b/51.md
@@ -8,7 +8,7 @@ Lists
8 8
9This NIP defines lists of things that users can create. Lists can contain references to anything, and these references can be **public** or **private**. 9This NIP defines lists of things that users can create. Lists can contain references to anything, and these references can be **public** or **private**.
10 10
11Public items in a list are specified in the event `tags` array, while private items are specified in a JSON array that mimics the structure of the event `tags` array, but stringified and encrypted using the same scheme from [NIP-04](04.md) (the shared key is computed using the author's public and private key) and stored in the `.content`. 11Public items in a list are specified in the event `tags` array, while private items are specified in a JSON array that mimics the structure of the event `tags` array, but stringified and encrypted using the same scheme from [NIP-44](44.md) (the shared key is computed using the author's public and private key) and stored in the `.content`. An earlier version of this specification used [NIP-04](04.md) for encryptions. Those are now deprecated. For backward compatibility, Clients can automatically discover if the encryption is NIP-04 or NIP-44 by searching for "iv" in the ciphertext and decrypting accordingly.
12 12
13When new items are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order. 13When new items are added to an existing list, clients SHOULD append them to the end of the list, so they are stored in chronological order.
14 14
@@ -22,16 +22,19 @@ For example, _mute list_ can contain the public keys of spammers and bad actors
22 22
23| name | kind | description | expected tag items | 23| name | kind | description | expected tag items |
24| --- | --- | --- | --- | 24| --- | --- | --- | --- |
25| Follow list | 3 | microblogging basic follow list, see [NIP-02](02.md) | `"p"` (pubkeys -- with optional relay hint and petname) |
25| Mute list | 10000 | things the user doesn't want to see in their feeds | `"p"` (pubkeys), `"t"` (hashtags), `"word"` (lowercase string), `"e"` (threads) | 26| Mute list | 10000 | things the user doesn't want to see in their feeds | `"p"` (pubkeys), `"t"` (hashtags), `"word"` (lowercase string), `"e"` (threads) |
26| Pinned notes | 10001 | events the user intends to showcase in their profile page | `"e"` (kind:1 notes) | 27| Pinned notes | 10001 | events the user intends to showcase in their profile page | `"e"` (kind:1 notes) |
27| Read/write relays | 10002 | where a user publishes to and where they expect mentions | see [NIP-65](65.md) | 28| Read/write relays | 10002 | where a user publishes to and where they expect mentions | see [NIP-65](65.md) |
28| Bookmarks | 10003 | uncategorized, "global" list of things a user wants to save | `"e"` (kind:1 notes), `"a"` (kind:30023 articles), `"t"` (hashtags), `"r"` (URLs) | 29| Bookmarks | 10003 | uncategorized, "global" list of things a user wants to save | `"e"` (kind:1 notes), `"a"` (kind:30023 articles) |
29| Communities | 10004 | [NIP-72](72.md) communities the user belongs to | `"a"` (kind:34550 community definitions) | 30| Communities | 10004 | [NIP-72](72.md) communities the user belongs to | `"a"` (kind:34550 community definitions) |
30| Public chats | 10005 | [NIP-28](28.md) chat channels the user is in | `"e"` (kind:40 channel definitions) | 31| Public chats | 10005 | [NIP-28](28.md) chat channels the user is in | `"e"` (kind:40 channel definitions) |
31| Blocked relays | 10006 | relays clients should never connect to | `"relay"` (relay URLs) | 32| Blocked relays | 10006 | relays clients should never connect to | `"relay"` (relay URLs) |
32| Search relays | 10007 | relays clients should use when performing search queries | `"relay"` (relay URLs) | 33| Search relays | 10007 | relays clients should use when performing search queries | `"relay"` (relay URLs) |
33| Simple groups | 10009 | [NIP-29](29.md) groups the user is in | `"group"` ([NIP-29](29.md) group id + relay URL + optional group name), `"r"` for each relay in use | 34| Simple groups | 10009 | [NIP-29](29.md) groups the user is in | `"group"` ([NIP-29](29.md) group id + relay URL + optional group name), `"r"` for each relay in use |
35| Relay feeds | 10012 | user favorite browsable relays (and relay sets) | `"relay"` (relay URLs) and `"a"` (kind:30002 relay set) |
34| Interests | 10015 | topics a user may be interested in and pointers | `"t"` (hashtags) and `"a"` (kind:30015 interest set) | 36| Interests | 10015 | topics a user may be interested in and pointers | `"t"` (hashtags) and `"a"` (kind:30015 interest set) |
37| Media follows | 10020 | multimedia (photos, short video) follow list | `"p"` (pubkeys -- with optional relay hint and petname) |
35| Emojis | 10030 | user preferred emojis and pointers to emoji sets | `"emoji"` (see [NIP-30](30.md)) and `"a"` (kind:30030 emoji set) | 38| Emojis | 10030 | user preferred emojis and pointers to emoji sets | `"emoji"` (see [NIP-30](30.md)) and `"a"` (kind:30030 emoji set) |
36| DM relays | 10050 | Where to receive [NIP-17](17.md) direct messages | `"relay"` (see [NIP-17](17.md)) | 39| DM relays | 10050 | Where to receive [NIP-17](17.md) direct messages | `"relay"` (see [NIP-17](17.md)) |
37| Good wiki authors | 10101 | [NIP-54](54.md) user recommended wiki authors | `"p"` (pubkeys) | 40| Good wiki authors | 10101 | [NIP-54](54.md) user recommended wiki authors | `"p"` (pubkeys) |
@@ -49,14 +52,18 @@ Aside from their main identifier, the `"d"` tag, sets can optionally have a `"ti
49| --- | --- | --- | --- | 52| --- | --- | --- | --- |
50| Follow sets | 30000 | categorized groups of users a client may choose to check out in different circumstances | `"p"` (pubkeys) | 53| Follow sets | 30000 | categorized groups of users a client may choose to check out in different circumstances | `"p"` (pubkeys) |
51| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) | 54| Relay sets | 30002 | user-defined relay groups the user can easily pick and choose from during various operations | `"relay"` (relay URLs) |
52| 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) | 55| Bookmark sets | 30003 | user-defined bookmarks categories , for when bookmarks must be in labeled separate groups | `"e"` (kind:1 notes), `"a"` (kind:30023 articles) |
53| 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) | 56| 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) |
54| Curation sets | 30005 | groups of videos picked by users as interesting and/or belonging to the same category | `"a"` (kind:21 videos) | 57| Curation sets | 30005 | groups of videos picked by users as interesting and/or belonging to the same category | `"e"` (kind:21 videos) |
58| Curation sets | 30006 | groups of pictures picked by users as interesting and/or belonging to the same category | `"e"` (kind:20 pictures) |
55| Kind mute sets | 30007 | mute pubkeys by kinds<br>`"d"` tag MUST be the kind string | `"p"` (pubkeys) | 59| Kind mute sets | 30007 | mute pubkeys by kinds<br>`"d"` tag MUST be the kind string | `"p"` (pubkeys) |
56| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) | 60| Interest sets | 30015 | interest topics represented by a bunch of "hashtags" | `"t"` (hashtags) |
57| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) | 61| Emoji sets | 30030 | categorized emoji groups | `"emoji"` (see [NIP-30](30.md)) |
58| Release artifact sets | 30063 | group of artifacts of a software release | `"e"` (kind:1063 [file metadata](94.md) events), `"a"` (software application event) | 62| Release artifact sets | 30063 | group of artifacts of a software release | `"e"` (kind:1063 [file metadata](94.md) events), `"a"` (software application event) |
59| App curation sets | 30267 | references to multiple software applications | `"a"` (software application event) | 63| App curation sets | 30267 | references to multiple software applications | `"a"` (software application event) |
64| Calendar | 31924 | a set of events categorized in any way | `"a"` (calendar event event) |
65| Starter packs | 39089 | a named set of profiles to be shared around with the goal of being followed together | `"p"` (pubkeys) |
66| Media starter packs | 39092 | same as above, but specific to multimedia (photos, short video) clients | `"p"` (pubkeys) |
60 67
61### Deprecated standard lists 68### Deprecated standard lists
62 69
@@ -98,9 +105,9 @@ Some clients have used these lists in the past, but they should work on transiti
98 "kind": 30004, 105 "kind": 30004,
99 "tags": [ 106 "tags": [
100 ["d", "jvdy9i4"], 107 ["d", "jvdy9i4"],
101 ["name", "Yaks"], 108 ["title", "Yaks"],
102 ["picture", "https://cdn.britannica.com/40/188540-050-9AC748DE/Yak-Himalayas-Nepal.jpg"], 109 ["image", "https://cdn.britannica.com/40/188540-050-9AC748DE/Yak-Himalayas-Nepal.jpg"],
103 ["about", "The domestic yak, also known as the Tartary ox, grunting ox, or hairy cattle, is a species of long-haired domesticated cattle found throughout the Himalayan region of the Indian subcontinent, the Tibetan Plateau, Gilgit-Baltistan, Tajikistan and as far north as Mongolia and Siberia."], 110 ["description", "The domestic yak, also known as the Tartary ox, grunting ox, or hairy cattle, is a species of long-haired domesticated cattle found throughout the Himalayan region of the Indian subcontinent, the Tibetan Plateau, Gilgit-Baltistan, Tajikistan and as far north as Mongolia and Siberia."],
104 ["a", "30023:26dc95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:95ODQzw3ajNoZ8SyMDOzQ"], 111 ["a", "30023:26dc95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:95ODQzw3ajNoZ8SyMDOzQ"],
105 ["a", "30023:54af95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:1-MYP8dAhramH9J5gJWKx"], 112 ["a", "30023:54af95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:1-MYP8dAhramH9J5gJWKx"],
106 ["a", "30023:f8fe95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:D2Tbd38bGrFvU0bIbvSMt"], 113 ["a", "30023:f8fe95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:D2Tbd38bGrFvU0bIbvSMt"],
@@ -128,7 +135,6 @@ Some clients have used these lists in the past, but they should work on transiti
128 ["e", "340e0326b340e0326b4941ed78ba340e0326b4941ed78ba340e0326b49ed78ba"], // PWA 135 ["e", "340e0326b340e0326b4941ed78ba340e0326b4941ed78ba340e0326b49ed78ba"], // PWA
129 ["a", "32267:d6dc95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:com.example.app"] // Reference to parent software application 136 ["a", "32267:d6dc95542e18b8b7aec2f14610f55c335abebec76f3db9e58c254661d0593a0c:com.example.app"] // Reference to parent software application
130 ], 137 ],
131 "content": "Example App is a decentralized marketplace for apps",
132 "sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd" 138 "sig": "a9a4e2192eede77e6c9d24ddfab95ba3ff7c03fbd07ad011fff245abea431fb4d3787c2d04aad001cb039cb8de91d83ce30e9a94f82ac3c5a2372aa1294a96bd"
133} 139}
134``` 140```
@@ -159,6 +165,6 @@ val private_items = [
159 ["p", "07caba282f76441955b695551c3c5c742e5b9202a3784780f8086fdcdc1da3a9"], 165 ["p", "07caba282f76441955b695551c3c5c742e5b9202a3784780f8086fdcdc1da3a9"],
160 ["a", "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4"], 166 ["a", "a55c15f5e41d5aebd236eca5e0142789c5385703f1a7485aa4b38d94fd18dcc4"],
161] 167]
162val base64blob = nip04.encrypt(json.encode_to_string(private_items)) 168val base64blob = nip44.encrypt(json.encode_to_string(private_items))
163event.content = base64blob 169event.content = base64blob
164``` 170```
diff --git a/52.md b/52.md
index cc2625a..060b38f 100644
--- a/52.md
+++ b/52.md
@@ -12,63 +12,71 @@ Unlike the term `calendar event` specific to this NIP, the term `event` is used
12 12
13## Calendar Events 13## Calendar Events
14 14
15There are two types of calendar events represented by different kinds: date-based and time-based calendar events. Calendar events are not required to be part of a [calendar](#calendar). 15There are two types of calendar events represented by different kinds: _date-based_ and _time-based_ calendar events.
16 16
17### Date-Based Calendar Event 17These tags are common to both types of calendar events:
18
19This kind of calendar event starts on a date and ends before a different date in the future. Its use is appropriate for all-day or multi-day events where time and time zone hold no significance. e.g., anniversary, public holidays, vacation days.
20
21#### Format
22
23The format uses an _addressable event_ of `kind:31922`.
24
25The `.content` of these events should be a detailed description of the calendar event. It is required but can be an empty string.
26 18
27The list of tags are as follows: 19* `d` (required) a short unique string identifier. Generated by the client creating the calendar event.
28* `d` (required) universally unique identifier (UUID). Generated by the client creating the calendar event.
29* `title` (required) title of the calendar event 20* `title` (required) title of the calendar event
30* `start` (required) inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than `end`, if it exists. 21* `summary` (optional) brief description of the calendar event
31* `end` (optional) exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as `start`. 22* `image` (optional) url of an image to use for the event
32* `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call 23* `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call
33* `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location 24* `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location
34* `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting 25* `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting
35* `t` (optional, repeated) hashtag to categorize calendar event 26* `t` (optional, repeated) hashtag to categorize calendar event
36* `r` (optional, repeated) references / links to web pages, documents, video calls, recorded videos, etc. 27* `r` (optional, repeated) references / links to web pages, documents, video calls, recorded videos, etc.
28* `a` (repeated) reference tag to kind `31924` calendar event requesting to be included in Calendar
37 29
38The following tags are deprecated: 30The following tags are deprecated:
31
39* `name` name of the calendar event. Use only if `title` is not available. 32* `name` name of the calendar event. Use only if `title` is not available.
40 33
41```jsonc 34Calendar events are _not_ required to be part of a [calendar](#calendar).
35
36## Collaborative Calendar Event Requests
37
38Calendar events can include an `a` tag referencing a calendar (kind 31924) to request addition to that calendar. When a calendar event includes such a reference, clients should interpret this as a request to add the event to the referenced calendar by referencing it with an `a` tag.
39
40This enables collaborative calendar management where multiple users can contribute events to calendars they do not own, subject to the calendar owner's approval.
41
42### Date-Based Calendar Event
43
44This kind of calendar event starts on a date and ends before a different date in the future. Its use is appropriate for all-day or multi-day events where time and time zone hold no significance. e.g., anniversary, public holidays, vacation days.
45
46It's an _addressable event_ of `kind:31922`.
47
48The `.content` of these events SHOULD be a description of the calendar event.
49
50Aside from the common tags, this also takes the following tags:
51
52* `start` (required) inclusive start date in ISO 8601 format (YYYY-MM-DD). Must be less than `end`, if it exists.
53* `end` (optional) exclusive end date in ISO 8601 format (YYYY-MM-DD). If omitted, the calendar event ends on the same date as `start`.
54
55Example:
56
57```yaml
42{ 58{
43 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>, 59 "id": <32-bytes lowercase hex-encoded SHA-256 of the serialized event data>,
44 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 60 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
45 "created_at": <Unix timestamp in seconds>, 61 "created_at": <unix timestamp in seconds>,
46 "kind": 31922, 62 "kind": 31922,
47 "content": "<description of calendar event>", 63 "content": "<description of calendar event>",
48 "tags": [ 64 "tags": [
49 ["d", "<UUID>"], 65 ["d", "<random-identifier>"],
50 66
51 ["title", "<title of calendar event>"], 67 ["title", "<title of calendar event>"],
52 68
53 // Dates 69 // dates
54 ["start", "<YYYY-MM-DD>"], 70 ["start", "<YYYY-MM-DD>"],
55 ["end", "<YYYY-MM-DD>"], 71 ["end", "<YYYY-MM-DD>"],
56 72
57 // Location 73 // location
58 ["location", "<location>"], 74 ["location", "<location>"],
59 ["g", "<geohash>"], 75 ["g", "<geohash>"],
60 76
61 // Participants 77 // participants
62 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"], 78 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"],
63 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"], 79 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"],
64
65 // Hashtags
66 ["t", "<tag>"],
67 ["t", "<tag>"],
68
69 // Reference links
70 ["r", "<url>"],
71 ["r", "<url>"]
72 ] 80 ]
73} 81}
74``` 82```
@@ -77,97 +85,72 @@ The following tags are deprecated:
77 85
78This kind of calendar event spans between a start time and end time. 86This kind of calendar event spans between a start time and end time.
79 87
80#### Format 88It's an _addressable event_ of `kind:31923`.
81 89
82The format uses an _addressable event_ kind `31923`. 90The `.content` of these events should be a description of the calendar event. It is required but can be an empty string.
83 91
84The `.content` of these events should be a detailed description of the calendar event. It is required but can be an empty string. 92Aside from the common tags, this also takes the following tags:
85 93
86The list of tags are as follows:
87* `d` (required) universally unique identifier (UUID). Generated by the client creating the calendar event.
88* `title` (required) title of the calendar event
89* `start` (required) inclusive start Unix timestamp in seconds. Must be less than `end`, if it exists. 94* `start` (required) inclusive start Unix timestamp in seconds. Must be less than `end`, if it exists.
90* `end` (optional) exclusive end Unix timestamp in seconds. If omitted, the calendar event ends instantaneously. 95* `end` (optional) exclusive end Unix timestamp in seconds. If omitted, the calendar event ends instantaneously.
91* `start_tzid` (optional) time zone of the start timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica` 96* `start_tzid` (optional) time zone of the start timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`
92* `end_tzid` (optional) time zone of the end timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`. If omitted and `start_tzid` is provided, the time zone of the end timestamp is the same as the start timestamp. 97* `end_tzid` (optional) time zone of the end timestamp, as defined by the IANA Time Zone Database. e.g., `America/Costa_Rica`. If omitted and `start_tzid` is provided, the time zone of the end timestamp is the same as the start timestamp.
93* `summary` (optional) brief description of the calendar event 98* `D` (required) the day-granularity unix timestamp on which the event takes place, calculated as `floor(unix_seconds() / seconds_in_one_day)`. Multiple tags SHOULD be included to cover the event's timeframe.
94* `image` (optional) url of an image to use for the event
95* `location` (optional, repeated) location of the calendar event. e.g. address, GPS coordinates, meeting room name, link to video call
96* `g` (optional) [geohash](https://en.wikipedia.org/wiki/Geohash) to associate calendar event with a searchable physical location
97* `p` (optional, repeated) 32-bytes hex pubkey of a participant, optional recommended relay URL, and participant's role in the meeting
98* `l` (optional, repeated) label to categorize calendar event. e.g. `audiospace` to denote a scheduled event from a live audio space implementation such as cornychat.com
99* `t` (optional, repeated) hashtag to categorize calendar event
100* `r` (optional, repeated) references / links to web pages, documents, video calls, recorded videos, etc.
101
102The following tags are deprecated:
103* `name` name of the calendar event. Use only if `title` is not available.
104 99
105```jsonc 100```yaml
106{ 101{
107 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>, 102 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>,
108 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 103 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
109 "created_at": <Unix timestamp in seconds>, 104 "created_at": <unix timestamp in seconds>,
110 "kind": 31923, 105 "kind": 31923,
111 "content": "<description of calendar event>", 106 "content": "<description of calendar event>",
112 "tags": [ 107 "tags": [
113 ["d", "<UUID>"], 108 ["d", "<random-identifier>"],
114 109
115 ["title", "<title of calendar event>"], 110 ["title", "<title of calendar event>"],
116 ["summary", "<brief description of the calendar event>"], 111 ["summary", "<brief description of the calendar event>"],
117 ["image", "<string with image URI>"], 112 ["image", "<string with image URI>"],
118 113
119 // Timestamps 114 // timestamps
120 ["start", "<Unix timestamp in seconds>"], 115 ["start", "<unix timestamp in seconds>"],
121 ["end", "<Unix timestamp in seconds>"], 116 ["end", "<unix timestamp in seconds>"],
117 ["D", "82549"],
122 118
123 ["start_tzid", "<IANA Time Zone Database identifier>"], 119 ["start_tzid", "<IANA Time Zone Database identifier>"],
124 ["end_tzid", "<IANA Time Zone Database identifier>"], 120 ["end_tzid", "<IANA Time Zone Database identifier>"],
125 121
126 // Location 122 // location
127 ["location", "<location>"], 123 ["location", "<location>"],
128 ["g", "<geohash>"], 124 ["g", "<geohash>"],
129 125
130 // Participants 126 // participants
131 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"], 127 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"],
132 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"], 128 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>", "<role>"],
133
134 // Labels (example using com.cornychat namespace denoting the event as an audiospace)
135 ["L", "com.cornychat"],
136 ["l", "audiospace", "com.cornychat"],
137
138 // Hashtags
139 ["t", "<tag>"],
140 ["t", "<tag>"],
141
142 // Reference links
143 ["r", "<url>"],
144 ["r", "<url>"]
145 ] 129 ]
146} 130}
147``` 131```
148 132
149## Calendar 133## Calendar
150 134
151A calendar is a collection of calendar events, represented as a custom replaceable list event using kind `31924`. A user can have multiple calendars. One may create a calendar to segment calendar events for specific purposes. e.g., personal, work, travel, meetups, and conferences. 135A calendar is a collection of calendar events, represented as a custom _addressable list_ event using kind `31924`. A user can have multiple calendars. One may create a calendar to segment calendar events for specific purposes. e.g., personal, work, travel, meetups, and conferences.
152 136
153### Format 137Calendars can accept event requests from other users. When calendar events reference a calendar via an `a` tag, this represents a request for inclusion.
154 138
155The `.content` of these events should be a detailed description of the calendar. It is required but can be an empty string. 139The `.content` of these events should be a detailed description of the calendar. It is required but can be an empty string.
156 140
157The format uses a custom replaceable list of kind `31924` with a list of tags as described below:
158* `d` (required) universally unique identifier. Generated by the client creating the calendar. 141* `d` (required) universally unique identifier. Generated by the client creating the calendar.
159* `title` (required) calendar title 142* `title` (required) calendar title
160* `a` (repeated) reference tag to kind `31922` or `31923` calendar event being responded to 143* `a` (repeated) reference tag to kind `31922` or `31923` calendar event being responded to
161 144
162```json 145```yaml
163{ 146{
164 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>, 147 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>,
165 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 148 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
166 "created_at": <Unix timestamp in seconds>, 149 "created_at": <unix timestamp in seconds>,
167 "kind": 31924, 150 "kind": 31924,
168 "content": "<description of calendar>", 151 "content": "<description of calendar>",
169 "tags": [ 152 "tags": [
170 ["d", "<UUID>"], 153 ["d", "<random-identifier>"],
171 ["title", "<calendar title>"], 154 ["title", "<calendar title>"],
172 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional relay url>"], 155 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional relay url>"],
173 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional relay url>"] 156 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional relay url>"]
@@ -191,13 +174,12 @@ The RSVP MUST have an `a` tag of the event coordinates to the calendar event, an
191 174
192The RSVP MAY tag the author of the calendar event it is in response to using a `p` tag so that clients can easily query all RSVPs that pertain to the author. 175The RSVP MAY tag the author of the calendar event it is in response to using a `p` tag so that clients can easily query all RSVPs that pertain to the author.
193 176
194### Format 177The RSVP is an _addressable event_ of `kind:31925`.
195
196The format uses an _addressable event_ kind `31925`.
197 178
198The `.content` of these events is optional and should be a free-form note that adds more context to this calendar event response. 179The `.content` of these events is optional and should be a free-form note that adds more context to this calendar event response.
199 180
200The list of tags are as follows: 181The list of tags is as follows:
182
201* `a` (required) coordinates to a kind `31922` or `31923` calendar event being responded to. 183* `a` (required) coordinates to a kind `31922` or `31923` calendar event being responded to.
202* `e` (optional) event id of a kind `31922` or `31923` calendar event being responded to. 184* `e` (optional) event id of a kind `31922` or `31923` calendar event being responded to.
203* `d` (required) universally unique identifier. Generated by the client creating the calendar event RSVP. 185* `d` (required) universally unique identifier. Generated by the client creating the calendar event RSVP.
@@ -205,17 +187,17 @@ The list of tags are as follows:
205* `fb` (optional) `free` or `busy`. Determines if the user would be free or busy for the duration of the calendar event. This tag must be omitted or ignored if the `status` label is set to `declined`. 187* `fb` (optional) `free` or `busy`. Determines if the user would be free or busy for the duration of the calendar event. This tag must be omitted or ignored if the `status` label is set to `declined`.
206* `p` (optional) pubkey of the author of the calendar event being responded to. 188* `p` (optional) pubkey of the author of the calendar event being responded to.
207 189
208```json 190```yaml
209{ 191{
210 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>, 192 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>,
211 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 193 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
212 "created_at": <Unix timestamp in seconds>, 194 "created_at": <unix timestamp in seconds>,
213 "kind": 31925, 195 "kind": 31925,
214 "content": "<note>", 196 "content": "<note>",
215 "tags": [ 197 "tags": [
216 ["e", "<kind 31922 or 31923 event id", "<optional recommended relay URL>"] 198 ["e", "<kind 31922 or 31923 event id", "<optional recommended relay URL>"]
217 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional recommended relay URL>"], 199 ["a", "<31922 or 31923>:<calendar event author pubkey>:<d-identifier of calendar event>", "<optional recommended relay URL>"],
218 ["d", "<UUID>"], 200 ["d", "<random-identifier>"],
219 ["status", "<accepted/declined/tentative>"], 201 ["status", "<accepted/declined/tentative>"],
220 ["fb", "<free/busy>"], 202 ["fb", "<free/busy>"],
221 ["p", "<hex pubkey of kind 31922 or 31923 event>", "<optional recommended relay URL>"] 203 ["p", "<hex pubkey of kind 31922 or 31923 event>", "<optional recommended relay URL>"]
@@ -223,10 +205,6 @@ The list of tags are as follows:
223} 205}
224``` 206```
225 207
226## Unsolved Limitations
227
228* No private events
229
230## Intentionally Unsupported Scenarios 208## Intentionally Unsupported Scenarios
231 209
232### Recurring Calendar Events 210### Recurring Calendar Events
diff --git a/53.md b/53.md
index 69d1c9f..a0270e5 100644
--- a/53.md
+++ b/53.md
@@ -6,13 +6,13 @@ Live Activities
6 6
7`draft` `optional` 7`draft` `optional`
8 8
9Service 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. 9This NIP introduces event kinds to advertise live spaces and the participation of pubkeys in them.
10 10
11## Concepts 11## Live Streaming
12 12
13### Live Event 13A 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.
14 14
15A 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. 15Each `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.
16 16
17For example: 17For example:
18 18
@@ -63,13 +63,13 @@ This feature is important to avoid malicious event owners adding large account h
63 63
64### Live Chat Message 64### Live Chat Message
65 65
66Event `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. 66Event `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.
67 67
68```jsonc 68```jsonc
69{ 69{
70 "kind": 1311, 70 "kind": 1311,
71 "tags": [ 71 "tags": [
72 ["a", "30311:<Community event author pubkey>:<d-identifier of the community>", "<Optional relay url>", "root"], 72 ["a", "30311:<Community event author pubkey>:<d-identifier of the community>", "<Optional relay url>"],
73 ], 73 ],
74 "content": "Zaps to live streams is beautiful.", 74 "content": "Zaps to live streams is beautiful.",
75 // other fields... 75 // other fields...
@@ -84,13 +84,9 @@ Event `kind:1311` is live chat's channel message. Clients MUST include the `a` t
84 84
85Hosts may choose to pin one or more live chat messages by updating the `pinned` tags in the live event kind `30311`. 85Hosts may choose to pin one or more live chat messages by updating the `pinned` tags in the live event kind `30311`.
86 86
87## Use Cases 87### Examples
88 88
89Common use cases include meeting rooms/workshops, watch-together activities, or event spaces, such as [zap.stream](https://zap.stream). 89#### Live Streaming
90
91## Example
92
93### Live Streaming
94 90
95```json 91```json
96{ 92{
@@ -114,7 +110,7 @@ Common use cases include meeting rooms/workshops, watch-together activities, or
114} 110}
115``` 111```
116 112
117### Live Streaming chat message 113#### Live Streaming chat message
118 114
119```json 115```json
120{ 116{
@@ -129,3 +125,143 @@ Common use cases include meeting rooms/workshops, watch-together activities, or
129 "sig": "997f62ddfc0827c121043074d50cfce7a528e978c575722748629a4137c45b75bdbc84170bedc723ef0a5a4c3daebf1fef2e93f5e2ddb98e5d685d022c30b622" 125 "sig": "997f62ddfc0827c121043074d50cfce7a528e978c575722748629a4137c45b75bdbc84170bedc723ef0a5a4c3daebf1fef2e93f5e2ddb98e5d685d022c30b622"
130} 126}
131``` 127```
128
129## Meeting Spaces
130
131Meeting spaces contain one or more video/audio rooms where users can join and participate in the streaming.
132
133### Meeting Space Event (kind:30312)
134
135A 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.
136
137```jsonc
138{
139 "kind": 30312,
140 "tags": [
141 ["d", "<unique identifier>"], // Required: Room identifier
142 ["room", "<name of the room>"], // Required: Display name
143 ["summary", "<description>"], // Optional: Room description
144 ["image", "<preview image url>"], // Optional: Room image
145 ["status", "<open, private, closed>"], // Required: Room accessibility
146 ["service", "<url>"], // Required: URL to access the room
147 ["endpoint", "<url>"], // Optional: API endpoint for status/info
148 ["t", "<hashtag>"], // Optional: Multiple hashtags allowed
149 ["p", "<pubkey>", "<url>", "<role>", "<proof>"], // Required: At least one provider
150 ["relays", "<url>", "<url>", /*...*/] // Optional: Preferred relays
151 ],
152 "content": "" // Usually empty, may contain additional metadata
153}
154```
155
156Space properties:
157* MUST be either open, private or closed. Closed means the room is not in operation.
158* MAY specify access control policy for private rooms (e.g. invite-only, payment required)
159* MAY persist when not in use
160* MUST have at least one provider with "Host" role
161* MAY have multiple providers with different roles
162
163Provider roles (p tags):
164* Host: Full room management capabilities
165* Moderator: Room moderation capabilities
166* Speaker: Allowed to present/speak
167* Optional proof field for role verification
168
169### Meeting Room Events (kind:30313)
170
171A special event with kind:30313 represents a scheduled or ongoing meeting within a room. It MUST reference its parent room using the d tag.
172
173```jsonc
174{
175 "kind": 30313,
176 "tags": [
177 ["d", "<event-unique-identifier>"], // Required: Event identifier
178 ["a", "30312:<pubkey>:<room-id>", "wss://nostr.example.com"], // Required: Reference to parent space, 'd' from 30312
179 ["title", "<meeting-title>"], // Required: Meeting title
180 ["summary", "<description>"], // Optional: Meeting description
181 ["image", "<preview image url>"], // Optional: Meeting image
182 ["starts", "<unix timestamp>"], // Required: Start time
183 ["ends", "<unix timestamp>"], // Optional: End time
184 ["status", "<planned, live, ended>"], // Required: Meeting status
185 ["total_participants", "<number>"], // Optional: Total registered
186 ["current_participants", "<number>"], // Optional: Currently active
187 ["p", "<pubkey>", "<url>", "<role>"], // Optional: Participant with role
188 ],
189 "content": "" // Usually empty, may contain additional metadata
190}
191```
192
193Event properties:
194* MUST reference parent room via d tag
195* MUST have a status (planned/live/ended)
196* MUST have a start time
197* MAY track participant counts
198* MAY include participant roles specific to the event
199
200Event management:
201* Clients SHOULD update event status regularly when live
202* Events without updates for 1 hour MAY be considered ended
203* starts and ends timestamps SHOULD be updated when status changes
204
205### Examples
206
207#### Meeting Space (kind:30312)
208
209```jsonc
210{
211 "kind": 30312,
212 "tags": [
213 ["d", "main-conference-room"],
214 ["room", "Main Conference Hall"],
215 ["summary", "Our primary conference space"],
216 ["image", "https://example.com/room.jpg"],
217 ["status", "open"],
218 ["service", "https://meet.example.com/room"],
219 ["endpoint", "https://api.example.com/room"],
220 ["t", "conference"],
221 ["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca", "wss://nostr.example.com/", "Owner"],
222 ["p", "14aeb..8dad4", "wss://provider2.com/", "Moderator"],
223 ["relays", "wss://relay1.com", "wss://relay2.com"]
224 ],
225 "content": ""
226}
227```
228
229#### Meeting room (kind:30313)
230
231```jsonc
232{
233 "kind": 30313,
234 "tags": [
235 ["d", "annual-meeting-2025"],
236 ["a", "30312:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:main-conference-room", "wss://nostr.example.com"]
237 ["title", "Annual Company Meeting 2025"],
238 ["summary", "Yearly company-wide meeting"],
239 ["image", "https://example.com/meeting.jpg"],
240 ["starts", "1676262123"],
241 ["ends", "1676269323"],
242 ["status", "live"],
243 ["total_participants", "180"],
244 ["current_participants", "175"],
245 ["p", "91cf9..4e5ca", "wss://provider1.com/", "Speaker"],
246 ],
247 "content": ""
248}
249```
250### Room Presence
251
252New `kind: 10312` provides an event which signals presence of a listener.
253
254The presence event SHOULD be updated at regular intervals and clients SHOULD filter presence events older than
255a given time window.
256
257**This kind `10312` is a regular replaceable event, as such presence can only be indicated in one room at a time.**
258
259```json
260{
261 "kind": 10312,
262 "tags": [
263 ["a" , "<room-a-tag>", "<relay-hint>", "root"],
264 ["hand", "1"] // hand raised flag
265 ]
266}
267```
diff --git a/54.md b/54.md
index 3a02150..ead362d 100644
--- a/54.md
+++ b/54.md
@@ -8,57 +8,115 @@ Wiki
8 8
9This NIP defines `kind:30818` (an _addressable event_) for descriptions (or encyclopedia entries) of particular subjects, and it's expected that multiple people will write articles about the exact same subjects, with either small variations or completely independent content. 9This NIP defines `kind:30818` (an _addressable event_) for descriptions (or encyclopedia entries) of particular subjects, and it's expected that multiple people will write articles about the exact same subjects, with either small variations or completely independent content.
10 10
11Articles are identified by lowercase, normalized ascii `d` tags. 11Articles are identified by lowercase, normalized `d` tags.
12 12
13### Articles 13## Articles
14```json 14```json
15{ 15{
16 "content": "A wiki is a hypertext publication collaboratively edited and managed by its own audience.", 16 "content": "A wiki is a hypertext publication collaboratively edited and managed by its own audience.",
17 "tags": [ 17 "tags": [
18 ["d", "wiki"], 18 ["d", "wiki"],
19 ["title", "Wiki"], 19 ["title", "Wiki"]
20 ] 20 ]
21} 21}
22``` 22```
23 23
24### `d` tag normalization rules 24## `d` tag normalization rules
25 25
26- Any non-letter character MUST be converted to a `-`. 26- All letters with uppercase/lowercase variants MUST be converted to lowercase.
27- All letters MUST be converted to lowercase. 27- Whitespace MUST be converted to `-`.
28- Punctuation and symbols SHOULD be removed.
29- Multiple consecutive `-` SHOULD be collapsed to a single `-`.
30- Leading and trailing `-` SHOULD be removed.
31- Non-ASCII letters (e.g., Japanese, Chinese, Arabic, Cyrillic) MUST be preserved as UTF-8.
32- Numbers MUST be preserved.
28 33
29### Content 34For example:
35- `"Wiki Article"` → `"wiki-article"`
36- `"What's Up?"` → `"whats-up"`
37- `" Hello World "` → `"hello-world"`
38- `"Article 1"` → `"article-1"`
39- `"ウィキペディア"` → `"ウィキペディア"` (Japanese, no case change)
40- `"Ñoño"` → `"ñoño"` (Spanish, lowercased)
41- `"Москва"` → `"москва"` (Russian, lowercased)
42- `"日本語 Article"` → `"日本語-article"` (mixed scripts)
30 43
31The `content` should be Asciidoc with two extra functionalities: **wikilinks** and **nostr:...** links. 44## Content
32 45
33Unlike normal Asciidoc links `http://example.com[]` that link to external webpages, wikilinks `[[]]` link to other articles in the wiki. In this case, the wiki is the entirety of Nostr. Clicking on a wikilink should cause the client to ask relays for events with `d` tags equal to the target of that wikilink. 46The `content` should be [Djot](https://djot.net/) with two special functionalities:
34 47
35Wikilinks can take these two forms: 481. Links can have target URIs in [NIP-21](21.md) format, like `[Bob](nostr:npub1...)`.
492. When a reference can't be found for a reference-style link, it should link to the wiki article with that name instead (wikilink behavior). The target is normalized following the `d` tag normalization rules above.
36 50
37 1. `[[Target Page]]` -- in this case it will link to the page `target-page` (according to `d` tag normalization rules above) and be displayed as `Target Page`; 51For example:
38 2. `[[target page|see this]]` -- in this case it will link to the page `target-page`, but will be displayed as `see this`.
39 52
40`nostr:...` links, as per [NIP-21](21.md), should link to profiles or arbitrary Nostr events. Although it is not recommended to link to specific versions of articles -- instead the _wikilink_ syntax should be preferred, since it should be left to the reader and their client to decide what version of any given article they want to read. 53```djot
54Bitcoin is a [cryptocurrency][] invented by [Satoshi Nakamoto][].
41 55
42### Optional extra tags 56See also: [proof of work][] and [lightning network][Lightning Network].
57
58[Satoshi Nakamoto]: nostr:npub1satoshi...
59```
60
61In the article above:
62- `[cryptocurrency][]` links to the wiki article with `d` tag `"cryptocurrency"` (no reference defined, so it becomes a wikilink)
63- `[Satoshi Nakamoto][]` links to the npub (reference is defined)
64- `[proof of work][]` links to the article with `d` tag `"proof-of-work"`
65- `[lightning network][Lightning Network]` links to `"lightning-network"`, displays as "lightning network"
66
67Wikilinks also work with non-Latin scripts following the same normalization rules:
68- `[ビットコイン][]` → links to article with `d` tag `"ビットコイン"`
69- `[Japanese Article][日本語 記事]` → links to `"日本語-記事"`, displays as "Japanese Article"
70- `[Биткойн][]` → links to article with `d` tag `"биткойн"` (Cyrillic, lowercased)
71
72[NIP-21](21.md) `nostr:` links can also be used to link to profiles or arbitrary Nostr events. It is not recommended to link to specific versions of articles — the wikilink syntax should be preferred instead, since it should be left to the reader and their client to decide what version of any given article they want to read.
73
74## Optional extra tags
43 75
44 - `title`: for when the display title should be different from the `d` tag. 76 - `title`: for when the display title should be different from the `d` tag.
45 - `summary`: for display in lists. 77 - `summary`: for display in lists.
46 - `a` and `e`: for referencing the original event a wiki article was forked from. 78 - `a` and `e`: for referencing the original event a wiki article was forked from.
47 79
48### Merge Requests 80## Merge Requests
49 81
50Event `kind:818` represents a request to merge from a forked article into the source. It is directed to a pubkey and references the original article and the modified event. 82Event `kind:818` represents a request to merge from a forked article into the source. It is directed to a pubkey and references the original article and the modified event.
51 83
52[INSERT EVENT EXAMPLE] 84```json
85{
86 "content": "I added information about the block size limit",
87 "kind": 818,
88 "tags": [
89 ["a", "30818:<destination-pubkey>:bitcoin", "<relay-url>"],
90 ["e", "<version-against-which-the-modification-was-made>", "<relay-url>"],
91 ["p", "<destination-pubkey>"],
92 ["e", "<version-to-be-merged>", "<relay-url>", "source"]
93 ]
94}
95```
53 96
54### Redirects 97- `.content`: an optional explanation detailing why this merge is being requested.
98- `a` tag: tag of the article which should be modified (i.e. the target of this merge request).
99- `e` tag: optional version of the article on which this modification is based.
100- `e` tag with `source` marker: the ID of the event that should be merged. This event id MUST be of a `kind:30818` as defined in this NIP.
55 101
56Event `kind:30819` is also defined to stand for "wiki redirects", i.e. if one thinks `Shell structure` should redirect to `Thin-shell structure` they can issue one of these events instead of replicating the content. These events can be used for automatically redirecting between articles on a client, but also for generating crowdsourced "disambiguation" pages ([common in Wikipedia](https://en.wikipedia.org/wiki/Help:Disambiguation)). 102The destination pubkey can create [NIP-25](25.md) reactions that tag the `kind:818` event with `+` or `-` to accept or reject the merge request.
57 103
58[INSERT EVENT EXAMPLE] 104## Redirects
59 105
60How to decide what article to display 106Event `kind:30819` is also defined to stand for "wiki redirects", i.e. if one thinks "BTC" should redirect to "Bitcoin" they can issue one of these events instead of replicating the content. These events can be used for automatically redirecting between articles on a client, but also for generating crowdsourced "disambiguation" pages ([common in Wikipedia](https://en.wikipedia.org/wiki/Help:Disambiguation)).
61------------------------------------- 107
108```json
109{
110 "kind": 30819,
111 "tags": [
112 ["d", "btc"],
113 ["a", "30818:<pubkey>:bitcoin", "<relay-url>"]
114 ],
115 "content": ""
116}
117```
118
119## How to decide what article to display
62 120
63As there could be many articles for each given name, some kind of prioritization must be done by clients. Criteria for this should vary between users and clients, but some means that can be used are described below: 121As there could be many articles for each given name, some kind of prioritization must be done by clients. Criteria for this should vary between users and clients, but some means that can be used are described below:
64 122
@@ -78,44 +136,24 @@ As there could be many articles for each given name, some kind of prioritization
78 136
79[NIP-51](51.md) lists can also be used to create a list of users that are trusted only in the context of wiki authorship or wiki curationship. 137[NIP-51](51.md) lists can also be used to create a list of users that are trusted only in the context of wiki authorship or wiki curationship.
80 138
81Forks 139## Forks
82--------- 140
83Wiki-events can tag other wiki-events with a `fork` marker to specify that this event came from a different version. Both `a` and `e` tags SHOULD be used and have the `fork` marker applied, to identify the exact version it was forked from. 141Wiki-events can tag other wiki-events with a `fork` marker to specify that this event came from a different version. Both `a` and `e` tags SHOULD be used and have the `fork` marker applied, to identify the exact version it was forked from.
84 142
85Deference 143## Deference
86--------- 144
87Wiki-events can tag other wiki-events with a `defer` marker to indicate that it considers someone else's entry as a "better" version of itself. If using a `defer` marker both `a` and `e` tags SHOULD be used. 145Wiki-events can tag other wiki-events with a `defer` marker to indicate that it considers someone else's entry as a "better" version of itself. If using a `defer` marker both `a` and `e` tags SHOULD be used.
88 146
89This is a stronger signal of trust than a `+` reaction. 147This is a stronger signal of trust than a `+` reaction.
90 148
91This marker is useful when a user edits someone else's entry; if the original author includes the editor's changes and the editor doesn't want to keep/maintain an independent version, the `link` tag could effectively be a considered a "deletion" of the editor's version and putting that pubkey's WoT weight behind the original author's version. 149This marker is useful when a user edits someone else's entry; if the original author includes the editor's changes and the editor doesn't want to keep/maintain an independent version, the `defer` tag could effectively be considered a "deletion" of the editor's version and putting that pubkey's WoT weight behind the original author's version.
92
93Why Asciidoc?
94-------------
95
96Wikitext is [garbage](nostr:nevent1qqsqt0gcggry60n72uglhuhypdlmr2dm6swjj69jex5v530gcpazlzsprpmhxue69uhhyetvv9ujumn0wdmksetjv5hxxmmdqy28wumn8ghj7un9d3shjtnyv9kh2uewd9hsygpm7rrrljungc6q0tuh5hj7ue863q73qlheu4vywtzwhx42a7j9n5ueneex) and Markdown is not powerful enough (besides being too freeform and unspecified and prone to generate incompatibilities in the future).
97
98Asciidoc has a strict spec, multiple implementations in many languages, and support for features that are very much necessary in a wiki article, like _sidebars_, _tables_ (with rich markup inside cells), many levels of _headings_, _footnotes_, _superscript_ and _subscript_ markup and _description lists_. It is also arguably easier to read in its plaintext format than Markdown (and certainly much better than Wikitext).
99 150
100# Appendix 1: Merge requests 151## Why Djot?
101Users can request other users to get their entries merged into someone else's entry by creating a `kind:818` event.
102
103```json
104{
105 "content": "I added information about how to make hot ice-creams",
106 "kind": 818,
107 "tags": [
108 [ "a", "30818:<destination-pubkey>:hot-ice-creams", "<relay-url>" ],
109 [ "e", "<version-against-which-the-modification-was-made>", "<relay-url>" ],
110 [ "p", "<destination-pubkey>" ],
111 [ "e", "<version-to-be-merged>", "<relay-url>", "source" ]
112 ]
113}
114```
115 152
116`.content`: an optional explanation detailing why this merge is being requested. 153[Djot](https://djot.net/) is a markup language created by John MacFarlane (author of Pandoc and co-author of CommonMark). It was chosen for the following reasons:
117`a` tag: tag of the article which should be modified (i.e. the target of this merge request).
118`e` tag: optional version of the article in which this modifications is based
119`e` tag with `source` marker: the ID of the event that should be merged. This event id MUST be of a `kind:30818` as defined in this NIP.
120 154
121The destination-pubkey is the pubkey being requested to merge something into their article can create [[NIP-25]] reactions that tag the `kind:818` event with `+` or `-` 155- **Well-defined spec**: Unlike Markdown (many incompatible dialects) or Asciidoc (spec tied to Ruby implementation), Djot has a clear, standalone specification.
156- **Native implementations**: Available in JavaScript, Lua, Rust, Go, and other languages without transpilation.
157- **Rich features**: Supports superscript, subscript, footnotes, tables, definition lists, and math — features useful for encyclopedic content.
158- **Familiar syntax**: Similar to basic Markdown, making it easy to learn.
159- **Fast parsing**: Designed for efficient linear-time parsing.
diff --git a/55.md b/55.md
index 6d2b92a..72a844a 100644
--- a/55.md
+++ b/55.md
@@ -8,7 +8,7 @@ Android Signer Application
8 8
9This NIP describes a method for 2-way communication between an Android signer and any Nostr client on Android. The Android signer is an Android Application and the client can be a web client or an Android application. 9This NIP describes a method for 2-way communication between an Android signer and any Nostr client on Android. The Android signer is an Android Application and the client can be a web client or an Android application.
10 10
11# Usage for Android applications 11## Usage for Android applications
12 12
13The Android signer uses Intents (to accept/reject permissions manually) and Content Resolvers (to accept/reject permissions automatically in background if the user allowed it) to communicate between applications. 13The Android signer uses Intents (to accept/reject permissions manually) and Content Resolvers (to accept/reject permissions automatically in background if the user allowed it) to communicate between applications.
14 14
@@ -38,7 +38,7 @@ fun isExternalSignerInstalled(context: Context): Boolean {
38} 38}
39``` 39```
40 40
41## Using Intents 41### Using Intents
42 42
43To get the result back from the Signer Application you should use `registerForActivityResult` or `rememberLauncherForActivityResult` in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result. 43To get the result back from the Signer Application you should use `registerForActivityResult` or `rememberLauncherForActivityResult` in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.
44 44
@@ -91,7 +91,7 @@ Signer MUST answer multiple permissions with an array of results
91val results = listOf( 91val results = listOf(
92 Result( 92 Result(
93 package = signerPackageName, 93 package = signerPackageName,
94 result = eventSignture, 94 result = eventSignature,
95 id = intentId 95 id = intentId
96 ) 96 )
97) 97)
@@ -107,7 +107,14 @@ Send the Intent:
107launcher.launch(intent) 107launcher.launch(intent)
108``` 108```
109 109
110### Methods 110### Initiating a connection
111
112- Client send a get_public_key `Intent`
113- Signer responds with the user `pubkey` and it's `packageName`
114- Client saves the `pubkey` and `packageName` somewhere and NEVER calls `get_public_key` again
115- Client now can send `content resolvers` and `Intents` for the other methods
116
117#### Methods
111 118
112- **get_public_key** 119- **get_public_key**
113 - params: 120 - params:
@@ -203,10 +210,10 @@ launcher.launch(intent)
203 context.startActivity(intent) 210 context.startActivity(intent)
204 ``` 211 ```
205 - result: 212 - result:
206 - If the user approved intent it will return the **signature** and **id** fields 213 - If the user approved intent it will return the **result** and **id** fields
207 214
208 ```kotlin 215 ```kotlin
209 val encryptedText = intent.data?.getStringExtra("signature") 216 val encryptedText = intent.data?.getStringExtra("result")
210 // the id you sent 217 // the id you sent
211 val id = intent.data?.getStringExtra("id") 218 val id = intent.data?.getStringExtra("id")
212 ``` 219 ```
@@ -242,7 +249,7 @@ launcher.launch(intent)
242 ```kotlin 249 ```kotlin
243 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText")) 250 val intent = Intent(Intent.ACTION_VIEW, Uri.parse("nostrsigner:$encryptedText"))
244 intent.`package` = "com.example.signer" 251 intent.`package` = "com.example.signer"
245 intent.putExtra("type", "nip04_decrypt") 252 intent.putExtra("type", "nip44_decrypt")
246 // to control the result in your application in case you are not waiting the result before sending another intent 253 // to control the result in your application in case you are not waiting the result before sending another intent
247 intent.putExtra("id", "some_id") 254 intent.putExtra("id", "some_id")
248 // Send the current logged in user pubkey 255 // Send the current logged in user pubkey
@@ -283,7 +290,7 @@ launcher.launch(intent)
283 val id = intent.data?.getStringExtra("id") 290 val id = intent.data?.getStringExtra("id")
284 ``` 291 ```
285 292
286## Using Content Resolver 293### Using Content Resolver
287 294
288To get the result back from Signer Application you should use contentResolver.query in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result. 295To get the result back from Signer Application you should use contentResolver.query in Kotlin. If you are using another framework check the documentation of your framework or a third party library to get the result.
289 296
@@ -295,34 +302,9 @@ For the other types Signer Application returns the column "result"
295 302
296If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application 303If the user chose to always reject the event, signer application will return the column "rejected" and you should not open signer application
297 304
298### Methods 305Clients SHOULD save the user pubkey locally and avoid calling the `get_public_key` after the user is logged in to the Client
299 306
300- **get_public_key** 307#### Methods
301 - params:
302
303 ```kotlin
304 val result = context.contentResolver.query(
305 Uri.parse("content://com.example.signer.GET_PUBLIC_KEY"),
306 listOf("login"),
307 null,
308 null,
309 null
310 )
311 ```
312 - result:
313 - Will return the **pubkey** in the result column
314
315 ```kotlin
316 if (result == null) return
317
318 if (it.getColumnIndex("rejected") > -1) return
319
320 if (result.moveToFirst()) {
321 val index = it.getColumnIndex("result")
322 if (index < 0) return
323 val pubkey = it.getString(index)
324 }
325 ```
326 308
327- **sign_event** 309- **sign_event**
328 - params: 310 - params:
@@ -482,7 +464,7 @@ If the user chose to always reject the event, signer application will return the
482 } 464 }
483 ``` 465 ```
484 466
485# Usage for Web Applications 467## Usage for Web Applications
486 468
487You should consider using [NIP-46: Nostr Connect](46.md) for a better experience for web applications. When using this approach, the web app can't call the signer in the background, so the user will see a popup for every event you try to sign. 469You should consider using [NIP-46: Nostr Connect](46.md) for a better experience for web applications. When using this approach, the web app can't call the signer in the background, so the user will see a popup for every event you try to sign.
488 470
@@ -496,7 +478,7 @@ You can configure the `returnType` to be **signature** or **event**.
496 478
497Android intents and browser urls have limitations, so if you are using the `returnType` of **event** consider using the parameter **compressionType=gzip** that will return "Signer1" + Base64 gzip encoded event json 479Android intents and browser urls have limitations, so if you are using the `returnType` of **event** consider using the parameter **compressionType=gzip** that will return "Signer1" + Base64 gzip encoded event json
498 480
499## Methods 481### Methods
500 482
501- **get_public_key** 483- **get_public_key**
502 - params: 484 - params:
@@ -547,7 +529,7 @@ Android intents and browser urls have limitations, so if you are using the `retu
547 window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=decrypt_zap_event&callbackUrl=https://example.com/?event=`; 529 window.href = `nostrsigner:${eventJson}?compressionType=none&returnType=signature&type=decrypt_zap_event&callbackUrl=https://example.com/?event=`;
548 ``` 530 ```
549 531
550## Example 532### Example
551 533
552```js 534```js
553<!DOCTYPE html> 535<!DOCTYPE html>
diff --git a/57.md b/57.md
index c37126e..91eac30 100644
--- a/57.md
+++ b/57.md
@@ -12,7 +12,7 @@ Having lightning receipts on nostr allows clients to display lightning payments
12 12
13## Protocol flow 13## Protocol flow
14 14
151. 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. 151. 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.
162. 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. 162. 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.
173. 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. 173. 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.
184. 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. 184. 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.
@@ -37,6 +37,7 @@ In addition, the event MAY include the following tags:
37 37
38- `e` is an optional hex-encoded event id. Clients MUST include this if zapping an event rather than a person. 38- `e` is an optional hex-encoded event id. Clients MUST include this if zapping an event rather than a person.
39- `a` is an optional event coordinate that allows tipping addressable events such as NIP-23 long-form notes. 39- `a` is an optional event coordinate that allows tipping addressable events such as NIP-23 long-form notes.
40- `k` is the stringified kind of the target event.
40 41
41Example: 42Example:
42 43
@@ -49,7 +50,8 @@ Example:
49 ["amount", "21000"], 50 ["amount", "21000"],
50 ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"], 51 ["lnurl", "lnurl1dp68gurn8ghj7um5v93kketj9ehx2amn9uh8wetvdskkkmn0wahz7mrww4excup0dajx2mrv92x9xp"],
51 ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"], 52 ["p", "04c915daefee38317fa734444acee390a8269fe5810b2241e5e6dd343dfbecc9"],
52 ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"] 53 ["e", "9ae37aa68f48645127299e9453eb5d908a0cbb6058ff340d528ed4d37c8994fb"],
54 ["k", "1"]
53 ], 55 ],
54 "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322", 56 "pubkey": "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322",
55 "created_at": 1679673265, 57 "created_at": 1679673265,
@@ -151,6 +153,7 @@ Example `zap receipt`:
151 ["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"], 153 ["p", "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245"],
152 ["P", "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"], 154 ["P", "97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322"],
153 ["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"], 155 ["e", "3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8"],
156 ["k", "1"],
154 ["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"], 157 ["bolt11", "lnbc10u1p3unwfusp5t9r3yymhpfqculx78u027lxspgxcr2n2987mx2j55nnfs95nxnzqpp5jmrh92pfld78spqs78v9euf2385t83uvpwk9ldrlvf6ch7tpascqhp5zvkrmemgth3tufcvflmzjzfvjt023nazlhljz2n9hattj4f8jq8qxqyjw5qcqpjrzjqtc4fc44feggv7065fqe5m4ytjarg3repr5j9el35xhmtfexc42yczarjuqqfzqqqqqqqqlgqqqqqqgq9q9qxpqysgq079nkq507a5tw7xgttmj4u990j7wfggtrasah5gd4ywfr2pjcn29383tphp4t48gquelz9z78p4cq7ml3nrrphw5w6eckhjwmhezhnqpy6gyf0"],
155 ["description", "{\"pubkey\":\"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322\",\"content\":\"\",\"id\":\"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d\",\"created_at\":1674164539,\"sig\":\"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d\",\"kind\":9734,\"tags\":[[\"e\",\"3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"relays\",\"wss://relay.damus.io\",\"wss://nostr-relay.wlvs.space\",\"wss://nostr.fmt.wiz.biz\",\"wss://relay.nostr.bg\",\"wss://nostr.oxtr.dev\",\"wss://nostr.v0l.io\",\"wss://brb.io\",\"wss://nostr.bitcoiner.social\",\"ws://monad.jb55.com:8080\",\"wss://relay.snort.social\"]]}"], 158 ["description", "{\"pubkey\":\"97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322\",\"content\":\"\",\"id\":\"d9cc14d50fcb8c27539aacf776882942c1a11ea4472f8cdec1dea82fab66279d\",\"created_at\":1674164539,\"sig\":\"77127f636577e9029276be060332ea565deaf89ff215a494ccff16ae3f757065e2bc59b2e8c113dd407917a010b3abd36c8d7ad84c0e3ab7dab3a0b0caa9835d\",\"kind\":9734,\"tags\":[[\"e\",\"3624762a1274dd9636e0c552b53086d70bc88c165bc4dc0f9e836a1eaf86c3b8\"],[\"p\",\"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\"],[\"relays\",\"wss://relay.damus.io\",\"wss://nostr-relay.wlvs.space\",\"wss://nostr.fmt.wiz.biz\",\"wss://relay.nostr.bg\",\"wss://nostr.oxtr.dev\",\"wss://nostr.v0l.io\",\"wss://brb.io\",\"wss://nostr.bitcoiner.social\",\"ws://monad.jb55.com:8080\",\"wss://relay.snort.social\"]]}"],
156 ["preimage", "5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f"] 159 ["preimage", "5d006d2cf1e73c7148e7519a4c68adc81642ce0e25a432b2434c99f97344c15f"]
diff --git a/58.md b/58.md
index 23921bd..0011997 100644
--- a/58.md
+++ b/58.md
@@ -11,7 +11,7 @@ user profiles:
11 11
121. A "Badge Definition" event is defined as an addressable event with kind `30009` having a `d` tag with a value that uniquely identifies the badge (e.g. `bravery`) published by the badge issuer. Badge definitions can be updated. 121. A "Badge Definition" event is defined as an addressable event with kind `30009` having a `d` tag with a value that uniquely identifies the badge (e.g. `bravery`) published by the badge issuer. Badge definitions can be updated.
13 13
142. A "Badge Award" event is a kind `8` event with a single `a` tag referencing a "Badge Definition" event and one or more `p` tags, one for each pubkey the badge issuer wishes to award. Awarded badges are immutable and non-transferrable. 142. A "Badge Award" event is a kind `8` event with a single `a` tag referencing a "Badge Definition" event and one or more `p` tags, one for each pubkey the badge issuer wishes to award. Awarded badges are immutable and non-transferable.
15 15
163. A "Profile Badges" event is defined as an _addressable event_ with kind `30008` with a `d` tag with the value `profile_badges`. 163. A "Profile Badges" event is defined as an _addressable event_ with kind `30008` with a `d` tag with the value `profile_badges`.
17Profile badges contain an ordered list of pairs of `a` and `e` tags referencing a `Badge Definition` and a `Badge Award` for each badge to be displayed. 17Profile badges contain an ordered list of pairs of `a` and `e` tags referencing a `Badge Definition` and a `Badge Award` for each badge to be displayed.
diff --git a/59.md b/59.md
index ddeb83c..e570151 100644
--- a/59.md
+++ b/59.md
@@ -4,7 +4,7 @@ NIP-59
4Gift Wrap 4Gift Wrap
5--------- 5---------
6 6
7`optional` 7`optional` `relay`
8 8
9This NIP defines a protocol for encapsulating any nostr event. This makes it possible to obscure most metadata 9This NIP defines a protocol for encapsulating any nostr event. This makes it possible to obscure most metadata
10for a given event, perform collaborative signing, and more. 10for a given event, perform collaborative signing, and more.
@@ -13,7 +13,7 @@ This NIP *does not* define any messaging protocol. Applications of this NIP shou
13 13
14This NIP relies on [NIP-44](./44.md)'s versioned encryption algorithms. 14This NIP relies on [NIP-44](./44.md)'s versioned encryption algorithms.
15 15
16# Overview 16## Overview
17 17
18This protocol uses three main concepts to protect the transmission of a target event: `rumor`s, `seal`s, and `gift wrap`s. 18This protocol uses three main concepts to protect the transmission of a target event: `rumor`s, `seal`s, and `gift wrap`s.
19 19
@@ -29,13 +29,13 @@ This allows the isolation of concerns across layers:
29- A seal identifies the author without revealing the content or the recipient. 29- A seal identifies the author without revealing the content or the recipient.
30- A gift wrap can add metadata (recipient, tags, a different author) without revealing the true author. 30- A gift wrap can add metadata (recipient, tags, a different author) without revealing the true author.
31 31
32# Protocol Description 32## Protocol Description
33 33
34## 1. The Rumor Event Kind 34### 1. The Rumor Event Kind
35 35
36A `rumor` is the same thing as an unsigned event. Any event kind can be made a `rumor` by removing the signature. 36A `rumor` is the same thing as an unsigned event. Any event kind can be made a `rumor` by removing the signature.
37 37
38## 2. The Seal Event Kind 38### 2. The Seal Event Kind
39 39
40A `seal` is a `kind:13` event that wraps a `rumor` with the sender's regular key. The `seal` is **always** encrypted 40A `seal` is a `kind:13` event that wraps a `rumor` with the sender's regular key. The `seal` is **always** encrypted
41to 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 41to 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
@@ -55,7 +55,7 @@ without the receiver's or the sender's private key. The only public information
55 55
56Tags MUST always be empty in a `kind:13`. The inner event MUST always be unsigned. 56Tags MUST always be empty in a `kind:13`. The inner event MUST always be unsigned.
57 57
58## 3. Gift Wrap Event Kind 58### 3. Gift Wrap Event Kind
59 59
60A `gift wrap` event is a `kind:1059` event that wraps any other event. `tags` SHOULD include any information 60A `gift wrap` event is a `kind:1059` event that wraps any other event. `tags` SHOULD include any information
61needed to route the event to its intended recipient, including the recipient's `p` tag or [NIP-13](13.md) proof of work. 61needed to route the event to its intended recipient, including the recipient's `p` tag or [NIP-13](13.md) proof of work.
@@ -72,12 +72,12 @@ needed to route the event to its intended recipient, including the recipient's `
72} 72}
73``` 73```
74 74
75# Encrypting Payloads 75## Encrypting Payloads
76 76
77Encryption is done following [NIP-44](44.md) on the JSON-encoded event. Place the encryption payload in the `.content` 77Encryption is done following [NIP-44](44.md) on the JSON-encoded event. Place the encryption payload in the `.content`
78of the wrapper event (either a `seal` or a `gift wrap`). 78of the wrapper event (either a `seal` or a `gift wrap`).
79 79
80# Other Considerations 80## Other Considerations
81 81
82If a `rumor` is intended for more than one party, or if the author wants to retain an encrypted copy, a single 82If a `rumor` is intended for more than one party, or if the author wants to retain an encrypted copy, a single
83`rumor` may be wrapped and addressed for each recipient individually. 83`rumor` may be wrapped and addressed for each recipient individually.
@@ -97,7 +97,12 @@ To protect recipient metadata, relays SHOULD only serve `kind 1059` events inten
97When possible, clients should only send wrapped events to `read` relays for the recipient that implement 97When possible, clients should only send wrapped events to `read` relays for the recipient that implement
98AUTH, and refuse to serve wrapped events to non-recipients. 98AUTH, and refuse to serve wrapped events to non-recipients.
99 99
100# An Example 100When adding expiration tags to both `seal` and `gift wrap` layers, implementations SHOULD use independent random timestamps for each layer. Using different `created_at` values increases timing variance and helps protect against metadata correlation attacks.
101
102Since signing keys are random, relays SHOULD delete `kind 1059` events whose p-tag matches the signer of
103[NIP-09](09.md) deletions or [NIP-62](62.md) vanish requests.
104
105## An Example
101 106
102Let's send a wrapped `kind 1` message between two parties asking "Are you going to the party tonight?" 107Let's send a wrapped `kind 1` message between two parties asking "Are you going to the party tonight?"
103 108
@@ -108,7 +113,7 @@ Let's send a wrapped `kind 1` message between two parties asking "Are you going
108Note that this messaging protocol should not be used in practice, this is just an example. Refer to other 113Note that this messaging protocol should not be used in practice, this is just an example. Refer to other
109NIPs for concrete messaging protocols that depend on gift wraps. 114NIPs for concrete messaging protocols that depend on gift wraps.
110 115
111## 1. Create an event 116### 1. Create an event
112 117
113Create a `kind 1` event with the message, the receivers, and any other tags you want, signed by the author. 118Create a `kind 1` event with the message, the receivers, and any other tags you want, signed by the author.
114Do not sign the event. 119Do not sign the event.
@@ -124,7 +129,7 @@ Do not sign the event.
124} 129}
125``` 130```
126 131
127## 2. Seal the rumor 132### 2. Seal the rumor
128 133
129Encrypt the JSON-encoded `rumor` with a conversation key derived using the author's private key and 134Encrypt the JSON-encoded `rumor` with a conversation key derived using the author's private key and
130the recipient's public key. Place the result in the `content` field of a `kind 13` `seal` event. Sign 135the recipient's public key. Place the result in the `content` field of a `kind 13` `seal` event. Sign
@@ -142,7 +147,7 @@ it with the author's key.
142} 147}
143``` 148```
144 149
145## 3. Wrap the seal 150### 3. Wrap the seal
146 151
147Encrypt the JSON-encoded `kind 13` event with your ephemeral, single-use random key. Place the result 152Encrypt the JSON-encoded `kind 13` event with your ephemeral, single-use random key. Place the result
148in the `content` field of a `kind 1059`. Add a single `p` tag containing the recipient's public key. 153in the `content` field of a `kind 1059`. Add a single `p` tag containing the recipient's public key.
@@ -160,13 +165,13 @@ Sign the `gift wrap` using the random key generated in the previous step.
160} 165}
161``` 166```
162 167
163## 4. Broadcast Selectively 168### 4. Broadcast Selectively
164 169
165Broadcast the `kind 1059` event to the recipient's relays only. Delete all the other events. 170Broadcast the `kind 1059` event to the recipient's relays only. Delete all the other events.
166 171
167# Code Samples 172## Code Samples
168 173
169## JavaScript 174### JavaScript
170 175
171```javascript 176```javascript
172import {bytesToHex} from "@noble/hashes/utils" 177import {bytesToHex} from "@noble/hashes/utils"
diff --git a/60.md b/60.md
index 4d0c709..8c836ac 100644
--- a/60.md
+++ b/60.md
@@ -16,13 +16,13 @@ The purpose of this NIP is:
16 16
17This NIP doesn't deal with users' *receiving* money from someone else, it's just to keep state of the user's wallet. 17This NIP doesn't deal with users' *receiving* money from someone else, it's just to keep state of the user's wallet.
18 18
19# High-level flow 19## High-level flow
201. A user has a `kind:17375` event that represents a wallet. 201. A user has a `kind:17375` event that represents a wallet.
212. A user has `kind:7375` events that represent the unspent proofs of the wallet. -- The proofs are encrypted with the user's private key. 212. A user has `kind:7375` events that represent the unspent proofs of the wallet. -- The proofs are encrypted with the user's private key.
223. A user has `kind:7376` events that represent the spending history of the wallet -- This history is for informational purposes only and is completely optional. 223. A user has `kind:7376` events that represent the spending history of the wallet -- This history is for informational purposes only and is completely optional.
23 23
24## Wallet Event 24### Wallet Event
25```jsonc 25```javascript
26{ 26{
27 "kind": 17375, 27 "kind": 17375,
28 "content": nip44_encrypt([ 28 "content": nip44_encrypt([
@@ -40,16 +40,17 @@ Tags:
40* `mint` - Mint(s) this wallet uses -- there MUST be one or more mint tags. 40* `mint` - Mint(s) this wallet uses -- there MUST be one or more mint tags.
41* `privkey` - Private key used to unlock P2PK ecash. MUST be stored encrypted in the `.content` field. **This is a different private key exclusively used for the wallet, not associated in any way to the user's Nostr private key** -- This is only used for receiving [NIP-61](61.md) nutzaps. 41* `privkey` - Private key used to unlock P2PK ecash. MUST be stored encrypted in the `.content` field. **This is a different private key exclusively used for the wallet, not associated in any way to the user's Nostr private key** -- This is only used for receiving [NIP-61](61.md) nutzaps.
42 42
43## Token Event 43### Token Event
44Token events are used to record unspent proofs. 44Token events are used to record unspent proofs.
45 45
46There can be multiple `kind:7375` events for the same mint, and multiple proofs inside each `kind:7375` event. 46There can be multiple `kind:7375` events for the same mint, and multiple proofs inside each `kind:7375` event.
47 47
48```jsonc 48```javascript
49{ 49{
50 "kind": 7375, 50 "kind": 7375,
51 "content": nip44_encrypt({ 51 "content": nip44_encrypt({
52 "mint": "https://stablenut.umint.cash", 52 "mint": "https://stablenut.umint.cash",
53 "unit": "sat",
53 "proofs": [ 54 "proofs": [
54 // one or more proofs in the default cashu format 55 // one or more proofs in the default cashu format
55 { 56 {
@@ -69,21 +70,23 @@ There can be multiple `kind:7375` events for the same mint, and multiple proofs
69 * `.content` is a [NIP-44](44.md) encrypted payload: 70 * `.content` is a [NIP-44](44.md) encrypted payload:
70 * `mint`: The mint the proofs belong to. 71 * `mint`: The mint the proofs belong to.
71 * `proofs`: unencoded proofs 72 * `proofs`: unencoded proofs
73 * `unit` the base unit the proofs are denominated in (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
72 * `del`: token-ids that were destroyed by the creation of this token. This assists with state transitions. 74 * `del`: token-ids that were destroyed by the creation of this token. This assists with state transitions.
73 75
74When one or more proofs of a token are spent, the token event should be [NIP-09](09.md)-deleted and, if some proofs are unspent from the same token event, a new token event should be created rolling over the unspent proofs and adding any change outputs to the new token event (the change output should include a `del` field). 76When one or more proofs of a token are spent, the token event should be [NIP-09](09.md)-deleted and, if some proofs are unspent from the same token event, a new token event should be created rolling over the unspent proofs and adding any change outputs to the new token event (the change output should include a `del` field).
75 77
76The `kind:5` _delete event_ created in the [NIP-09](09.md) process MUST have a tag `["k", "7375"]` to allow easy filtering by clients interested in state transitions. 78The `kind:5` _delete event_ created in the [NIP-09](09.md) process MUST have a tag `["k", "7375"]` to allow easy filtering by clients interested in state transitions.
77 79
78## Spending History Event 80### Spending History Event
79Clients SHOULD publish `kind:7376` events to create a transaction history when their balance changes. 81Clients SHOULD publish `kind:7376` events to create a transaction history when their balance changes.
80 82
81```jsonc 83```javascript
82{ 84{
83 "kind": 7376, 85 "kind": 7376,
84 "content": nip44_encrypt([ 86 "content": nip44_encrypt([
85 [ "direction", "in" ], // in = received, out = sent 87 [ "direction", "in" ], // in = received, out = sent
86 [ "amount", "1" ], 88 [ "amount", "1" ],
89 [ "unit", "sat" ],
87 [ "e", "<event-id-of-created-token>", "", "created" ] 90 [ "e", "<event-id-of-created-token>", "", "created" ]
88 ]), 91 ]),
89 "tags": [ 92 "tags": [
@@ -93,6 +96,7 @@ Clients SHOULD publish `kind:7376` events to create a transaction history when t
93``` 96```
94 97
95* `direction` - The direction of the transaction; `in` for received funds, `out` for sent funds. 98* `direction` - The direction of the transaction; `in` for received funds, `out` for sent funds.
99* `unit` the base unit of the amount (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
96 100
97Clients MUST add `e` tags to create references of destroyed and created token events along with the marker of the meaning of the tag: 101Clients MUST add `e` tags to create references of destroyed and created token events along with the marker of the meaning of the tag:
98* `created` - A new token event was created. 102* `created` - A new token event was created.
@@ -103,24 +107,25 @@ All tags can be [NIP-44](44.md) encrypted. Clients SHOULD leave `e` tags with a
103 107
104Multiple `e` tags can be added, and should be encrypted, except for tags with the `redeemed` marker. 108Multiple `e` tags can be added, and should be encrypted, except for tags with the `redeemed` marker.
105 109
106# Flow 110## Flow
107A client that wants to check for user's wallets information starts by fetching `kind:10019` events from the user's relays, if no event is found, it should fall back to using the user's [NIP-65](65.md) relays. 111A client that wants to check for user's wallets information starts by fetching `kind:10019` events from the user's relays, if no event is found, it should fall back to using the user's [NIP-65](65.md) relays.
108 112
109## Fetch wallet and token list 113### Fetch wallet and token list
110From those relays, the client should fetch wallet and token events. 114From those relays, the client should fetch wallet and token events.
111 115
112`"kinds": [17375, 7375], "authors": ["<my-pubkey>"]` 116`"kinds": [17375, 7375], "authors": ["<my-pubkey>"]`
113 117
114## Fetch proofs 118### Fetch proofs
115 119
116## Spending token 120### Spending token
117If Alice spends 4 sats from this token event 121If Alice spends 4 sats from this token event
118```jsonc 122```javascript
119{ 123{
120 "kind": 7375, 124 "kind": 7375,
121 "id": "event-id-1", 125 "id": "event-id-1",
122 "content": nip44_encrypt({ 126 "content": nip44_encrypt({
123 "mint": "https://stablenut.umint.cash", 127 "mint": "https://stablenut.umint.cash",
128 "unit": "sat",
124 "proofs": [ 129 "proofs": [
125 { "id": "1", "amount": 1 }, 130 { "id": "1", "amount": 1 },
126 { "id": "2", "amount": 2 }, 131 { "id": "2", "amount": 2 },
@@ -134,12 +139,13 @@ If Alice spends 4 sats from this token event
134 139
135Her client: 140Her client:
136* MUST roll over the unspent proofs: 141* MUST roll over the unspent proofs:
137```jsonc 142```javascript
138{ 143{
139 "kind": 7375, 144 "kind": 7375,
140 "id": "event-id-2", 145 "id": "event-id-2",
141 "content": nip44_encrypt({ 146 "content": nip44_encrypt({
142 "mint": "https://stablenut.umint.cash", 147 "mint": "https://stablenut.umint.cash",
148 "unit": "sat",
143 "proofs": [ 149 "proofs": [
144 { "id": "1", "amount": 1 }, 150 { "id": "1", "amount": 1 },
145 { "id": "2", "amount": 2 }, 151 { "id": "2", "amount": 2 },
@@ -153,12 +159,13 @@ Her client:
153* MUST delete event `event-id-1` 159* MUST delete event `event-id-1`
154* SHOULD add the `event-id-1` to the `del` array of deleted token-ids. 160* SHOULD add the `event-id-1` to the `del` array of deleted token-ids.
155* SHOULD create a `kind:7376` event to record the spend 161* SHOULD create a `kind:7376` event to record the spend
156```jsonc 162```javascript
157{ 163{
158 "kind": 7376, 164 "kind": 7376,
159 "content": nip44_encrypt([ 165 "content": nip44_encrypt([
160 [ "direction", "out" ], 166 [ "direction", "out" ],
161 [ "amount", "4" ], 167 [ "amount", "4" ],
168 [ "unit", "sat" ],
162 [ "e", "<event-id-1>", "", "destroyed" ], 169 [ "e", "<event-id-1>", "", "destroyed" ],
163 [ "e", "<event-id-2>", "", "created" ], 170 [ "e", "<event-id-2>", "", "created" ],
164 ]), 171 ]),
@@ -166,12 +173,12 @@ Her client:
166} 173}
167``` 174```
168 175
169## Redeeming a quote (optional) 176### Redeeming a quote (optional)
170When creating a quote at a mint, an event can be used to keep the state of the quote ID, which will be used to check when the quote has been paid. These events should be created with an expiration tag [NIP-40](40.md) of 2 weeks (which is around the maximum amount of time a Lightning payment may be in-flight). 177When creating a quote at a mint, an event can be used to keep the state of the quote ID, which will be used to check when the quote has been paid. These events should be created with an expiration tag [NIP-40](40.md) of 2 weeks (which is around the maximum amount of time a Lightning payment may be in-flight).
171 178
172However, application developers SHOULD use local state when possible and only publish this event when it makes sense in the context of their application. 179However, application developers SHOULD use local state when possible and only publish this event when it makes sense in the context of their application.
173 180
174```jsonc 181```javascript
175{ 182{
176 "kind": 7374, 183 "kind": 7374,
177 "content": nip44_encrypt("quote-id"), 184 "content": nip44_encrypt("quote-id"),
diff --git a/61.md b/61.md
index ebb8af3..5842d6b 100644
--- a/61.md
+++ b/61.md
@@ -8,19 +8,19 @@ Nutzaps
8 8
9A Nutzap is a P2PK Cashu token in which the payment itself is the receipt. 9A Nutzap is a P2PK Cashu token in which the payment itself is the receipt.
10 10
11# High-level flow 11## High-level flow
12Alice wants to nutzap 1 sat to Bob because of an event `event-id-1` she liked. 12Alice wants to nutzap 1 sat to Bob because of an event `event-id-1` she liked.
13 13
14## Alice nutzaps Bob 14### Alice nutzaps Bob
151. Alice fetches event `kind:10019` from Bob to see the mints Bob trusts. 151. Alice fetches event `kind:10019` from Bob to see the mints Bob trusts.
162. She mints a token at that mint (or swaps some tokens she already had in that mint) P2PK-locked to the pubkey Bob has listed in his `kind:10019`. 162. She mints a token at that mint (or swaps some tokens she already had in that mint) P2PK-locked to the pubkey Bob has listed in his `kind:10019`.
173. She publishes a `kind:9321` event to the relays Bob indicated with the proofs she minted. 173. She publishes a `kind:9321` event to the relays Bob indicated with the proofs she minted.
18 18
19## Bob receives the nutzap 19### Bob receives the nutzap
201. At some point, Bob's client fetches `kind:9321` events p-tagging him from his relays. 201. At some point, Bob's client fetches `kind:9321` events p-tagging him from his relays.
212. Bob's client swaps the token into his wallet. 212. Bob's client swaps the token into his wallet.
22 22
23# Nutzap informational event 23## Nutzap informational event
24```jsonc 24```jsonc
25{ 25{
26 "kind": 10019, 26 "kind": 10019,
@@ -39,7 +39,7 @@ Alice wants to nutzap 1 sat to Bob because of an event `event-id-1` she liked.
39* `mint`: mints the user is explicitly agreeing to use to receive funds on. Clients SHOULD not send money on mints not listed here or risk burning their money. Additional markers can be used to list the supported base units of the mint. 39* `mint`: mints the user is explicitly agreeing to use to receive funds on. Clients SHOULD not send money on mints not listed here or risk burning their money. Additional markers can be used to list the supported base units of the mint.
40* `pubkey`: Public key that MUST be used to P2PK-lock receiving nutzaps -- implementations MUST NOT use the target user's main Nostr public key. This public key corresponds to the `privkey` field encrypted in a user's [nip-60](60.md) _wallet event_. 40* `pubkey`: Public key that MUST be used to P2PK-lock receiving nutzaps -- implementations MUST NOT use the target user's main Nostr public key. This public key corresponds to the `privkey` field encrypted in a user's [nip-60](60.md) _wallet event_.
41 41
42## Nutzap event 42### Nutzap event
43Event `kind:9321` is a nutzap event published by the sender, p-tagging the recipient. The outputs are P2PK-locked to the public key the recipient indicated in their `kind:10019` event. 43Event `kind:9321` is a nutzap event published by the sender, p-tagging the recipient. The outputs are P2PK-locked to the public key the recipient indicated in their `kind:10019` event.
44 44
45Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu compatibility). 45Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu compatibility).
@@ -51,8 +51,10 @@ Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu
51 "pubkey": "<sender-pubkey>", 51 "pubkey": "<sender-pubkey>",
52 "tags": [ 52 "tags": [
53 [ "proof", "{\"amount\":1,\"C\":\"02277c66191736eb72fce9d975d08e3191f8f96afb73ab1eec37e4465683066d3f\",\"id\":\"000a93d6f8a1d2c4\",\"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"b00bdd0467b0090a25bdf2d2f0d45ac4e355c482c1418350f273a04fedaaee83\\\",\\\"data\\\":\\\"02eaee8939e3565e48cc62967e2fde9d8e2a4b3ec0081f29eceff5c64ef10ac1ed\\\"}]\"}" ], 53 [ "proof", "{\"amount\":1,\"C\":\"02277c66191736eb72fce9d975d08e3191f8f96afb73ab1eec37e4465683066d3f\",\"id\":\"000a93d6f8a1d2c4\",\"secret\":\"[\\\"P2PK\\\",{\\\"nonce\\\":\\\"b00bdd0467b0090a25bdf2d2f0d45ac4e355c482c1418350f273a04fedaaee83\\\",\\\"data\\\":\\\"02eaee8939e3565e48cc62967e2fde9d8e2a4b3ec0081f29eceff5c64ef10ac1ed\\\"}]\"}" ],
54 [ "unit", "sat" ],
54 [ "u", "https://stablenut.umint.cash" ], 55 [ "u", "https://stablenut.umint.cash" ],
55 [ "e", "<nutzapped-event-id>", "<relay-hint>" ], 56 [ "e", "<nutzapped-event-id>", "<relay-hint>" ],
57 [ "k", "<nutzapped-kind>"],
56 [ "p", "e9fbced3a42dcf551486650cc752ab354347dd413b307484e4fd1818ab53f991" ], // recipient of nutzap 58 [ "p", "e9fbced3a42dcf551486650cc752ab354347dd413b307484e4fd1818ab53f991" ], // recipient of nutzap
57 ] 59 ]
58} 60}
@@ -61,29 +63,30 @@ Clients MUST prefix the public key they P2PK-lock with `"02"` (for nostr<>cashu
61* `.content` is an optional comment for the nutzap 63* `.content` is an optional comment for the nutzap
62* `.tags`: 64* `.tags`:
63 * `proof` is one or more proofs P2PK-locked to the public key the recipient specified in their `kind:10019` event and including a DLEQ proof. 65 * `proof` is one or more proofs P2PK-locked to the public key the recipient specified in their `kind:10019` event and including a DLEQ proof.
66 * `unit` the base unit the proofs are denominated in (eg: `sat`, `usd`, `eur`). Default: `sat` if omitted.
64 * `u` is the mint the URL of the mint EXACTLY as specified by the recipient's `kind:10019`. 67 * `u` is the mint the URL of the mint EXACTLY as specified by the recipient's `kind:10019`.
65 * `p` is the Nostr identity public key of nutzap recipient. 68 * `p` is the Nostr identity public key of nutzap recipient.
66 * `e` is the event that is being nutzapped, if any. 69 * `e` is the event that is being nutzapped, if any.
67 70
68# Sending a nutzap 71## Sending a nutzap
69 72
70* The sender fetches the recipient's `kind:10019`. 73* The sender fetches the recipient's `kind:10019`.
71* The sender mints/swaps ecash on one of the recipient's listed mints. 74* The sender mints/swaps ecash on one of the recipient's listed mints.
72* The sender P2PK-locks to the recipient's specified public key in their `kind:10019` 75* The sender P2PK-locks to the recipient's specified public key in their `kind:10019`
73 76
74# Receiving nutzaps 77## Receiving nutzaps
75 78
76Clients should REQ for nutzaps: 79Clients should REQ for nutzaps:
77* Filtering with `#u` for mints they expect to receive ecash from. 80* Filtering with `#u` for mints they expect to receive ecash from.
78 * this is to prevent even interacting with mints the user hasn't explicitly signaled. 81 * this is to prevent even interacting with mints the user hasn't explicitly signaled.
79* Filtering with `since` of the most recent `kind:7376` event the same user has created. 82* Filtering with `since` of the most recent `kind:7376` event the same user has created.
80 * this can be used as a marker of the nutzaps that have already been swaped by the user -- clients might choose to use other kinds of markers, including internal state -- this is just a guidance of one possible approach. 83 * this can be used as a marker of the nutzaps that have already been swapped by the user -- clients might choose to use other kinds of markers, including internal state -- this is just a guidance of one possible approach.
81 84
82`{ "kinds": [9321], "#p": ["my-pubkey"], "#u": ["<mint-1>", "<mint-2>"], "since": <latest-created_at-of-kind-7376> }`. 85`{ "kinds": [9321], "#p": ["my-pubkey"], "#u": ["<mint-1>", "<mint-2>"], "since": <latest-created_at-of-kind-7376> }`.
83 86
84Upon receiving a new nutzap, the client should swap the tokens into a wallet the user controls, either a [NIP-60](60.md) wallet, their own LN wallet or anything else. 87Upon receiving a new nutzap, the client should swap the tokens into a wallet the user controls, either a [NIP-60](60.md) wallet, their own LN wallet or anything else.
85 88
86## Updating nutzap-redemption history 89### Updating nutzap-redemption history
87When claiming a token the client SHOULD create a `kind:7376` event and `e` tag the original nutzap event. This is to record that this token has already been claimed (and shouldn't be attempted again) and as signaling to the recipient that the ecash has been redeemed. 90When claiming a token the client SHOULD create a `kind:7376` event and `e` tag the original nutzap event. This is to record that this token has already been claimed (and shouldn't be attempted again) and as signaling to the recipient that the ecash has been redeemed.
88 91
89Multiple `kind:9321` events can be tagged in the same `kind:7376` event. 92Multiple `kind:9321` events can be tagged in the same `kind:7376` event.
@@ -94,6 +97,7 @@ Multiple `kind:9321` events can be tagged in the same `kind:7376` event.
94 "content": nip44_encrypt([ 97 "content": nip44_encrypt([
95 [ "direction", "in" ], // in = received, out = sent 98 [ "direction", "in" ], // in = received, out = sent
96 [ "amount", "1" ], 99 [ "amount", "1" ],
100 [ "unit", "sat" ],
97 [ "e", "<7375-event-id>", "<relay-hint>", "created" ] // new token event that was created 101 [ "e", "<7375-event-id>", "<relay-hint>", "created" ] // new token event that was created
98 ]), 102 ]),
99 "tags": [ 103 "tags": [
@@ -105,7 +109,7 @@ Multiple `kind:9321` events can be tagged in the same `kind:7376` event.
105 109
106Events that redeem a nutzap SHOULD be published to the sender's [NIP-65](65.md) "read" relays. 110Events that redeem a nutzap SHOULD be published to the sender's [NIP-65](65.md) "read" relays.
107 111
108## Verifying a Cashu Zap 112### Verifying a Cashu Zap
109When listing or counting zaps received by any given event, observer clients SHOULD: 113When listing or counting zaps received by any given event, observer clients SHOULD:
110 114
111* check that the receiving user has issued a `kind:10019` tagging the mint where the cashu has been minted. 115* check that the receiving user has issued a `kind:10019` tagging the mint where the cashu has been minted.
@@ -115,7 +119,7 @@ When listing or counting zaps received by any given event, observer clients SHOU
115 119
116All these checks can be done offline (as long as the observer has the receiver mints' keyset and their `kind:10019` event), so the process should be reasonably fast. 120All these checks can be done offline (as long as the observer has the receiver mints' keyset and their `kind:10019` event), so the process should be reasonably fast.
117 121
118## Final Considerations 122### Final Considerations
1191. Clients SHOULD guide their users to use NUT-11 (P2PK) and NUT-12 (DLEQ proofs) compatible-mints in their `kind:10019` event to avoid receiving nutzaps anyone can spend. 1231. Clients SHOULD guide their users to use NUT-11 (P2PK) and NUT-12 (DLEQ proofs) compatible-mints in their `kind:10019` event to avoid receiving nutzaps anyone can spend.
1202. Clients SHOULD normalize and deduplicate mint URLs as described in NIP-65. 1242. Clients SHOULD normalize and deduplicate mint URLs as described in NIP-65.
1213. A nutzap event MUST include proofs in one of the mints the recipient has listed in their `kind:10019` and published to the NIP-65 relays of the recipient, failure to do so may result in the recipient donating the tokens to the mint since the recipient might never see the event. 1253. A nutzap event MUST include proofs in one of the mints the recipient has listed in their `kind:10019` and published to the NIP-65 relays of the recipient, failure to do so may result in the recipient donating the tokens to the mint since the recipient might never see the event.
diff --git a/62.md b/62.md
index a00ddfc..e0398d8 100644
--- a/62.md
+++ b/62.md
@@ -4,7 +4,7 @@ NIP-62
4Request to Vanish 4Request to Vanish
5----------------- 5-----------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9This NIP offers a Nostr-native way to request a complete reset of a key's fingerprint on the web. This procedure is legally binding in some jurisdictions, and thus, supporters of this NIP should truly delete events from their database. 9This NIP offers a Nostr-native way to request a complete reset of a key's fingerprint on the web. This procedure is legally binding in some jurisdictions, and thus, supporters of this NIP should truly delete events from their database.
10 10
diff --git a/66.md b/66.md
index 7ab3786..9f06ff5 100644
--- a/66.md
+++ b/66.md
@@ -4,139 +4,46 @@ NIP-66
4Relay Discovery and Liveness Monitoring 4Relay Discovery and Liveness Monitoring
5------------------- 5-------------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9You want to find relays. You may want to discover relays based on criteria that's up to date. You may even want to ensure that you have a complete dataset. You probably want to filter relays based on their reported liveness. 9This NIP defines events for relay discovery and the announcement of relay monitors.
10 10
11In its purest form: 11## Relay Discovery Events
12 12
13```json 13`30166` relay discovery events document relay characteristics inferred either from a relay's [NIP 11](https://github.com/nostr-protocol/nips/blob/master/11.md) document, or via probing.
14{
15 "kind": 30166,
16 "created_at": 1722173222,
17 "content": "{}",
18 "tags": [
19 [ "d", "wss://somerelay.abc/" ]
20 ],
21 "pubkey": "<pubkey>",
22 "sig": "<signature>",
23 "id": "<eventid>"
24}
25```
26
27This event signals that the relay at `wss://somerelay.abc/` was reported "online" by `<pubkey>` at timestamp `1722173222`. This event **MAY** be extended upon to include more information.
28
29## Kinds
30`NIP-66` defines two (2) event kinds, `30166` and `10166`
31
32| kind | name | description |
33|-------|----------------------------|-----------------------------------------------------------------------------------------|
34| [30166](#k30166) | Relay Discovery | An addressable event that is published by a monitor when a relay is online |
35| [10166](#k10166) | Relay Monitor Announcement | An RE that stores data that signals the intent of a pubkey to monitor relays and publish `30166` events at a regular _frequency_ |
36
37## Ontology
38- `Relay Operator`: someone who operates a relay
39- `Monitor`: A pubkey that monitors relays and publishes `30166` events at the frequency specified in their `10166` event.
40- `Ad-hoc Monitor`: A pubkey that monitors relays and publishes `30166` events at an irregular frequency.
41- `Monitor Service`: A group or individual that monitors relays using one or more `Monitors`.
42- `Check`: a specific data point that is tested or aggregated by a monitor.
43
44## `30166`: "Relay Discovery" <a id="k30166"></a>
45
46### Summary
47`30166` is a `NIP-33` addressable event, referred to as a "Relay Discovery" event. These events are optimized with a small footprint for protocol-level relay Discovery.
48
49### Purpose
50Discovery of relays over nostr.
51
52### Schema
53
54#### Content
55`30166` content fields **SHOULD** include the stringified JSON of the relay's NIP-11 informational document. This data **MAY** be provided for informational purposes only.
56
57#### `created_at`
58The `created_at` field in a NIP-66 event should reflect the time when the relay liveness (and potentially other data points) was checked.
59
60#### `tags`
61
62##### Meta Tags (unindexed)
63- `rtt-open` The relay's open **round-trip time** in milliseconds.
64- `rtt-read` The relay's read **round-trip time** in milliseconds.
65- `rtt-write` The relay's write **round-trip time** in milliseconds.
66
67_Other `rtt` values **MAY** be present. This NIP should be updated if there is value found in more `rtt` values._
68
69##### Single Letter Tags (indexed)
70- `d` The relay URL/URI. The `#d` tag **must** be included in the `event.tags[]` array. Index position `1` **must** be the relay websocket URL/URI. If a URL it **SHOULD** be [normalized](https://datatracker.ietf.org/doc/html/rfc3986#section-6). For relays not accessible via conventional means but rather by an npub/pubkey, an npub/pubkey **MAY** be used in place of a URL.
71 ```json
72 [ "d", "wss://somerelay.abc/"]
73 ```
74
75- `n`: Network
76 ```json
77 [ "n", "clearnet" ]
78 ```
79
80- `T`: Relay Type. Enumerated [relay type](https://github.com/nostr-protocol/nips/issues/1282) formatted as `PascalCase`
81 ```json
82 ["T", "PrivateInbox" ]
83 ```
84
85- `N`: Supported Nips _From NIP-11 "Informational Document" `nip11.supported_nips[]`_
86 ```json
87 [ "N", "42" ]
88 ```
89
90- `R`: Requirements _NIP-11 "Informational Document" `nip11.limitations.payment_required`, `nip11.limitations.auth_required` and/or any other boolean value within `nip11.limitations[]` that is added in the future_
91 ```json
92 [ "R", "payment" ],
93 [ "R", "auth" ],
94 ```
95 Since the nostr protocol does not currently support filtering on whether an indexed tag **is** or **is not** set, to make "public" and "no auth" relays discoverable requires a `!` flag
96 14
97 ```json 15Information corresponding to field in a relay's NIP 11 document MAY contradict actual values if monitors find that a different policy is implemented than is advertised.
98 [ "R", "!payment" ], //no payment required, is public
99 [ "R", "!auth" ], //no authentication required
100 ```
101 16
102- `t`: "Topics" _From NIP-11 "Informational Document" `nip11.tags[]`_ 17`content` MAY include the stringified JSON of the relay's NIP-11 informational document.
103 ```json
104 [ "t", "nsfw" ]
105 ```
106 18
107- `k`: Accepted/Blocked Kinds [`NIP-22`] 19The only required tag is the `d` tag, which MUST be set to the relay's [normalized](https://datatracker.ietf.org/doc/html/rfc3986#section-6) URL. For relays not accessible via URL, a hex-encoded pubkey MAY be used instead.
108 ```json
109 [ "k", "0" ],
110 [ "k", "3" ],
111 [ "k", "10002" ]
112 ```
113 20
114 or for blocked kinds 21Other tags include:
115 22
116 ```json 23- `rtt-open` - The relay's open round-trip time in milliseconds.
117 [ "k", "!0" ] 24- `rtt-read` - The relay's read round-trip time in milliseconds.
118 [ "k", "!3" ], 25- `rtt-write` - The relay's write round-trip time in milliseconds.
119 [ "k", "!10002" ] 26- `n` - The relay's network type. SHOULD be one of `clearnet`, `tor`, `i2p`, `loki`
120 ``` 27- `T` - The relay type. Enumerated [relay type](https://github.com/nostr-protocol/nips/issues/1282) formatted as `PascalCase`, e.g. `PrivateInbox`
28- `N` - NIPs supported by the relay
29- `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`.
30- `t` - A topic associated with this relay
31- `k` - Accepted and unaccepted kinds (false values prepended by `!`)
32- `g` - A [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) geohash
121 33
122- `g`: `NIP-52` `g` tags (geohash) 34Tags 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"]]`.
123 ```json
124 [ "g", "9r1652whz" ]
125 ```
126 35
127- `30166` **MAY** be extended with global tags defined by other NIPs that do no collide with locally defined indices, including but not limited to: `p`, `t`, `e`, `a`, `i` and `l/L`. 36Example:
128 37
129#### Robust Example of a `30166` Event
130_Relay was online, and you can filter on a number of different tags_
131```json 38```json
132{ 39{
133 "id": "<eventid>", 40 "id": "<eventid>",
134 "pubkey": "<monitor's pubkey>", 41 "pubkey": "<monitor's pubkey>",
135 "created_at": "<created_at [some recent date ...]>", 42 "created_at": "<created_at [some recent date ...]>",
136 "signature": "<signature>", 43 "signature": "<signature>",
137 "content": "{}", 44 "content": "<optional nip 11 document>",
138 "kind": 30166, 45 "kind": 30166,
139 "tags": [ 46 "tags": [
140 ["d","wss://some.relay/"], 47 ["d","wss://some.relay/"],
141 ["n", "clearnet"], 48 ["n", "clearnet"],
142 ["N", "40"], 49 ["N", "40"],
@@ -144,64 +51,28 @@ _Relay was online, and you can filter on a number of different tags_
144 ["R", "!payment"], 51 ["R", "!payment"],
145 ["R", "auth"], 52 ["R", "auth"],
146 ["g", "ww8p1r4t8"], 53 ["g", "ww8p1r4t8"],
147 ["p", "somehexkey..."],
148 ["l", "en", "ISO-639-1"], 54 ["l", "en", "ISO-639-1"],
149 ["t", "nsfw" ], 55 ["t", "nsfw" ],
150 ["rtt-open", 234 ] 56 ["rtt-open", "234" ]
151 ] 57 ]
152} 58}
153``` 59```
154 60
155## `10166`: "Relay Monitor Announcement" Events <a id="k10166"></a> 61## Relay Monitor Announcements
156
157### Summary
158`10166` is a replacable event herein referred to as "Relay Monitor Announcement" events. These events contain information about a publisher's intent to monitor and publish data as `30166` events. This event is optional and is intended for monitors who intend to provide monitoring services at a regular and predictable frequency.
159
160### Purpose
161To provide a directory of monitors, their intent to publish, their criteria and parameters of monitoring activities. Absence of this event implies the monitor is ad-hoc and does not publish events at a predictable frequency, and relies on mechanisms to infer data integrity, such as web-of-trust.
162
163### Schema
164
165#### Standard Tags
166 62
167- `frequency` The frequency **in seconds** at which the monitor publishes events. A string-integer at index `1` represents the expected frequency the monitor will publish `30166` events. There should only be `1` frequency per monitor. 63Kind `10166` relay monitor announcements advertise the author's intent to publish `30166` events. This event is optional and is intended for monitors who intend to provide monitoring services at a regular and predictable frequency.
168 64
169 ```json 65Tags include:
170 [ "frequency", "3600" ]
171 ```
172 66
173- `timeout` (optional) The timeout values for various checks conducted by a monitor. Index `1` is the monitor's timeout in milliseconds. Index `2` describes what test the timeout is used for. If no index `2` is provided, it is inferred that the timeout provided applies to all tests. These values can assist relay operators in understanding data signaled by the monitor in _Relay Discovery Events_. 67- `frequency` - The frequency in seconds at which the monitor publishes events.
174 ```json 68- `timeout` (optional) - The timeout values for various checks conducted by a monitor. Index `1` is the monitor's timeout in milliseconds. Index `2` describes what test the timeout is used for. If no index `2` is provided, it is inferred that the timeout provided applies to all tests.
175 [ "timeout", "2000", "open" ], 69- `c` - a lowercase string describing the checks conducted by a monitor. Examples include `open`, `read`, `write`, `auth`, `nip11`, `dns`, and `geo`.
176 [ "timeout", "2000", "read" ], 70- `g` - [NIP-52](https://github.com/nostr-protocol/nips/blob/master/52.md) geohash tag
177 [ "timeout", "3000", "write" ],
178 [ "timeout", "2000", "nip11" ],
179 [ "timeout", "4000", "ssl" ]
180 ```
181 71
182#### Indexed Tags 72Monitors SHOULD also publish a `kind 0` profile and a `kind 10002` relay selections event.
183- `c` "Checks" **SHOULD** be a lowercase string describing the check(s) conducted by a monitor. Due to the rapidly evolving nature of relays, enumeration is organic and not strictly defined. But examples of some checks could be websocket `open/read/write/auth`, `nip11` checks, `dns` and `geo` checks, and and any other checks the monitor may deem useful.. Other checks **MAY** be included. New types of checks **SHOULD** be added to this NIP as they are needed.
184 ```json
185 [ "c", "ws" ],
186 [ "c", "nip11" ],
187 [ "c", "dns" ],
188 [ "c", "geo" ],
189 [ "c", "ssl" ],
190 ```
191 73
192- `g`: `NIP-52` `g` tags (geohash) 74Example:
193 ```json
194 [ "g", "9r1652whz" ]
195 ```
196 75
197- Any other globally defined indexable tags **MAY** be included as found necessary.
198
199### Other Requirements
200Monitors **SHOULD** have the following
201- A published `0` (NIP-1) event
202- A published `10002` (NIP-65) event that defines the relays the monitor publishes to.
203
204### Robust Example of a `10166` Event
205```json 76```json
206{ 77{
207 "id": "<eventid>", 78 "id": "<eventid>",
@@ -209,46 +80,24 @@ Monitors **SHOULD** have the following
209 "created_at": "<created_at [some recent date ...]>", 80 "created_at": "<created_at [some recent date ...]>",
210 "signature": "<signature>", 81 "signature": "<signature>",
211 "content": "", 82 "content": "",
212 "tags": [ 83 "tags": [
213
214 [ "timeout", "open", "5000" ], 84 [ "timeout", "open", "5000" ],
215 [ "timeout", "read", "3000" ], 85 [ "timeout", "read", "3000" ],
216 [ "timeout", "write", "3000" ], 86 [ "timeout", "write", "3000" ],
217 [ "timeout", "nip11", "3000" ], 87 [ "timeout", "nip11", "3000" ],
218
219 [ "frequency", "3600" ], 88 [ "frequency", "3600" ],
220
221 [ "c", "ws" ], 89 [ "c", "ws" ],
222 [ "c", "nip11" ], 90 [ "c", "nip11" ],
223 [ "c", "ssl" ], 91 [ "c", "ssl" ],
224 [ "c", "dns" ], 92 [ "c", "dns" ],
225 [ "c", "geo" ] 93 [ "c", "geo" ]
226
227 [ "g", "ww8p1r4t8" ] 94 [ "g", "ww8p1r4t8" ]
228 ] 95 ]
229} 96}
230``` 97```
231 98
232## Methodology 99 ## Risk Mitigation
233 100
234### Monitors 101 - Clients MUST NOT require `30166` events to function. Absence of monitoring data MUST NOT prevent relay connections.
2351. A _Relay Monitor_ checks the liveness and potentially other attributes of a relay. 102 - A monitor may publish erroneous `30166` events, either by misconfiguration or malicious intent.
236 103 - Clients SHOULD NOT trust a single source. Defenses include: web-of-trust filtering, querying multiple monitors, and discarding filter results if they would remove an unreasonable proportion of relays.
2372. _Relay Monitor_ publishes a kind `30166` note when a relay it is monitoring is online. If the monitor has a `10166` event, events should be published at the frequency defined in their `10166` note.
238
239_Any pubkey that publishes `30166` events **SHOULD** at a minimum be checking that the relay is available by websocket and behaves like a relay_
240
241### Clients
2421. In most cases, a client **SHOULD** filter on `30166` events using either a statically or dynamically defined monitor's `pubkey` and a `created_at` value respective of the monitor's published `frequency`. If the monitor has no stated frequency, other mechanisms should be employed to determine data integrity.
243
2442. _Relay Liveness_ is subjectively determined by the client, starting with the `frequency` value of a monitor.
245
2463. The liveness of a _Relay Monitor_ can be subjectively determined by detecting whether the _Relay Monitor_ has published events with respect to `frequency` value of any particular monitor.
247
2484. The reliability and trustworthiness of a _Relay Monitor_ could be established via web-of-trust, reviews or similar mechanisms.
249
250## Risk Mitigation
251
252- When a client implements `NIP-66` events, the client should have a fallback if `NIP-66` events cannot be located.
253
254- A `Monitor` or `Ad-hoc Monitor` may publish erroneous `30166` events, intentionally or otherwise. Therefor, it's important to program defensively to limit the impact of such events. This can be achieved with web-of-trust, reviews, fallbacks and/or data-aggregation for example.
diff --git a/69.md b/69.md
index 603016e..997cac6 100644
--- a/69.md
+++ b/69.md
@@ -41,7 +41,8 @@ Events are [addressable events](01.md#kinds) and use `38383` as event kind, a p2
41 ["name", "Nakamoto"], 41 ["name", "Nakamoto"],
42 ["g", "<geohash>"], 42 ["g", "<geohash>"],
43 ["bond", "0"], 43 ["bond", "0"],
44 ["expiration", "1719391096"], 44 ["expires_at", "1719391096"],
45 ["expiration", "1719995896"],
45 ["y", "lnp2pbot"], 46 ["y", "lnp2pbot"],
46 ["z", "order"] 47 ["z", "order"]
47 ], 48 ],
@@ -55,7 +56,7 @@ Events are [addressable events](01.md#kinds) and use `38383` as event kind, a p2
55- `d` < Order ID >: A unique identifier for the order. 56- `d` < Order ID >: A unique identifier for the order.
56- `k` < Order type >: `sell` or `buy`. 57- `k` < Order type >: `sell` or `buy`.
57- `f` < Currency >: The asset being traded, using the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) standard. 58- `f` < Currency >: The asset being traded, using the [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217) standard.
58- `s` < Status >: `pending`, `canceled`, `in-progress`, `success`. 59- `s` < Status >: `pending`, `canceled`, `in-progress`, `success`, `expired`.
59- `amt` < Amount >: The amount of Bitcoin to be traded, the amount is defined in satoshis, if `0` means that the amount of satoshis will be obtained from a public API after the taker accepts the order. 60- `amt` < Amount >: The amount of Bitcoin to be traded, the amount is defined in satoshis, if `0` means that the amount of satoshis will be obtained from a public API after the taker accepts the order.
60- `fa` < Fiat amount >: The fiat amount being traded, for range orders two values are expected, the minimum and maximum amount. 61- `fa` < Fiat amount >: The fiat amount being traded, for range orders two values are expected, the minimum and maximum amount.
61- `pm` < Payment method >: The payment method used for the trade, if the order has multiple payment methods, they should be separated by a comma. 62- `pm` < Payment method >: The payment method used for the trade, if the order has multiple payment methods, they should be separated by a comma.
@@ -67,7 +68,8 @@ Events are [addressable events](01.md#kinds) and use `38383` as event kind, a p2
67- `name` [Name]: The name of the maker. 68- `name` [Name]: The name of the maker.
68- `g` [Geohash]: The geohash of the operation, it can be useful in a face to face trade. 69- `g` [Geohash]: The geohash of the operation, it can be useful in a face to face trade.
69- `bond` [Bond]: The bond amount, the bond is a security deposit that both parties must pay. 70- `bond` [Bond]: The bond amount, the bond is a security deposit that both parties must pay.
70- `expiration` < Expiration\>: The expiration date of the order ([NIP-40](40.md)). 71- `expires_at` < Expires At\>: The expiration date of the event being published in `pending` status, after this time the event status SHOULD be changed to `expired`.
72- `expiration` < Expiration\>: The expiration date of the event, after this time the relay SHOULD delete it ([NIP-40](40.md)).
71- `y` < Platform >: The platform that created the order. 73- `y` < Platform >: The platform that created the order.
72- `z` < Document >: `order`. 74- `z` < Document >: `order`.
73 75
@@ -80,6 +82,7 @@ Currently implemented on the following platforms:
80- [Mostro](https://github.com/MostroP2P/mostro) 82- [Mostro](https://github.com/MostroP2P/mostro)
81- [@lnp2pBot](https://github.com/lnp2pBot/bot) 83- [@lnp2pBot](https://github.com/lnp2pBot/bot)
82- [Robosats](https://github.com/RoboSats/robosats/pull/1362) 84- [Robosats](https://github.com/RoboSats/robosats/pull/1362)
85- [Peach Bitcoin](https://github.com/Peach2Peach/peach-nostr-announcer-bot)
83 86
84## References 87## References
85 88
diff --git a/70.md b/70.md
index 043d5fb..32853d3 100644
--- a/70.md
+++ b/70.md
@@ -4,7 +4,7 @@ NIP-70
4Protected Events 4Protected Events
5---------------- 5----------------
6 6
7`draft` `optional` 7`draft` `optional` `relay`
8 8
9When the `"-"` tag is present, that means the event is "protected". 9When the `"-"` tag is present, that means the event is "protected".
10 10
diff --git a/71.md b/71.md
index d3b0925..0124edd 100644
--- a/71.md
+++ b/71.md
@@ -20,12 +20,31 @@ Nothing except cavaliership and common sense prevents a _short_ video from being
20 20
21The format uses a _regular event_ kind `21` for _normal_ videos and `22` for _short_ videos. 21The format uses a _regular event_ kind `21` for _normal_ videos and `22` for _short_ videos.
22 22
23## Addressable Video Events
24
25For content that may need updates after publication (such as correcting metadata, descriptions, or handling URL migrations), addressable versions are available:
26
27- Kind `34235` for _addressable normal videos_
28- Kind `34236` for _addressable short videos_
29
30These addressable events follow the same format as their regular counterparts but include a `d` tag as a unique identifier and can be updated while maintaining the same addressable reference. This is particularly useful for:
31
32- Metadata corrections (descriptions, titles, tags) without republishing
33- Preservation of imported content IDs from legacy platforms
34- URL migration when hosting changes
35- Platform migration tracking
36
23The `.content` of these events is a summary or description on the video content. 37The `.content` of these events is a summary or description on the video content.
24 38
25The primary source of video information is the `imeta` tags which is defined in [NIP-92](92.md) 39The primary source of video information is the `imeta` tags which is defined in [NIP-92](92.md)
26 40
27Each `imeta` tag can be used to specify a variant of the video by the `dim` & `m` properties. 41Each `imeta` tag can be used to specify a variant of the video by the `dim` & `m` properties.
28 42
43This NIP defines the following additional `imeta` properties aside from those listed in [NIP-92](92.md) & [NIP-94](94.md):
44
45* `duration` (recommended) the duration of the video/audio in seconds (floating point number)
46* `bitrate` (recommended) the average bitrate of the video/audio in bits/sec
47
29Example: 48Example:
30```json 49```json
31[ 50[
@@ -39,6 +58,8 @@ Example:
39 "fallback https://myotherserver.com/1080/12345.mp4", 58 "fallback https://myotherserver.com/1080/12345.mp4",
40 "fallback https://andanotherserver.com/1080/12345.mp4", 59 "fallback https://andanotherserver.com/1080/12345.mp4",
41 "service nip96", 60 "service nip96",
61 "bitrate 3000000",
62 "duration 29.223"
42 ], 63 ],
43 ["imeta", 64 ["imeta",
44 "dim 1280x720", 65 "dim 1280x720",
@@ -50,6 +71,8 @@ Example:
50 "fallback https://myotherserver.com/720/12345.mp4", 71 "fallback https://myotherserver.com/720/12345.mp4",
51 "fallback https://andanotherserver.com/720/12345.mp4", 72 "fallback https://andanotherserver.com/720/12345.mp4",
52 "service nip96", 73 "service nip96",
74 "bitrate 2000000",
75 "duration 29.24"
53 ], 76 ],
54 ["imeta", 77 ["imeta",
55 "dim 1280x720", 78 "dim 1280x720",
@@ -61,6 +84,7 @@ Example:
61 "fallback https://myotherserver.com/720/12345.m3u8", 84 "fallback https://myotherserver.com/720/12345.m3u8",
62 "fallback https://andanotherserver.com/720/12345.m3u8", 85 "fallback https://andanotherserver.com/720/12345.m3u8",
63 "service nip96", 86 "service nip96",
87 "duration 29.21"
64 ], 88 ],
65] 89]
66``` 90```
@@ -71,10 +95,12 @@ The `image` tag contains a preview image (at the same resolution). Multiple `ima
71 95
72Additionally `service nip96` may be included to allow clients to search the authors NIP-96 server list to find the file using the hash. 96Additionally `service nip96` may be included to allow clients to search the authors NIP-96 server list to find the file using the hash.
73 97
98### Required tags for addressable events:
99* `d` - Unique identifier for this video (user-chosen string, required for kinds 34235, 34236)
100
74### Other tags: 101### Other tags:
75* `title` (required) title of the video 102* `title` (required) title of the video
76* `published_at`, for the timestamp in unix seconds (stringified) of the first time the video was published 103* `published_at`, for the timestamp in unix seconds (stringified) of the first time the video was published
77* `duration` (optional) video duration in seconds
78* `text-track` (optional, repeated) link to WebVTT file for video, type of supplementary information (captions/subtitles/chapters/metadata), optional language code 104* `text-track` (optional, repeated) link to WebVTT file for video, type of supplementary information (captions/subtitles/chapters/metadata), optional language code
79* `content-warning` (optional) warning about content of NSFW video 105* `content-warning` (optional) warning about content of NSFW video
80* `alt` (optional) description for accessibility 106* `alt` (optional) description for accessibility
@@ -83,17 +109,20 @@ Additionally `service nip96` may be included to allow clients to search the auth
83* `p` (optional, repeated) 32-bytes hex pubkey of a participant in the video, optional recommended relay URL 109* `p` (optional, repeated) 32-bytes hex pubkey of a participant in the video, optional recommended relay URL
84* `r` (optional, repeated) references / links to web pages 110* `r` (optional, repeated) references / links to web pages
85 111
112### Optional tags for imported content:
113* `origin` - Track original platform and ID: `["origin", "<platform>", "<external-id>", "<original-url>", "<optional-metadata>"]`
114
86```jsonc 115```jsonc
87{ 116{
88 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>, 117 "id": "<32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>",
89 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>, 118 "pubkey": "<32-bytes lowercase hex-encoded public key of the event creator>",
90 "created_at": <Unix timestamp in seconds>, 119 "created_at": <Unix timestamp in seconds>,
91 "kind": 21 | 22, 120 "kind": 21 | 22,
92 "content": "<summary / description of video>", 121 "content": "<summary / description of video>",
93 "tags": [ 122 "tags": [
94 ["title", "<title of video>"], 123 ["title", "<title of video>"],
95 ["published_at", "<unix timestamp>"], 124 ["published_at", "<unix timestamp>"],
96 ["alt", <description>], 125 ["alt", "<description>"],
97 126
98 // video Data 127 // video Data
99 ["imeta", 128 ["imeta",
@@ -108,7 +137,6 @@ Additionally `service nip96` may be included to allow clients to search the auth
108 "service nip96", 137 "service nip96",
109 ], 138 ],
110 139
111 ["duration", <duration of video in seconds>],
112 ["text-track", "<encoded `kind 6000` event>", "<recommended relay urls>"], 140 ["text-track", "<encoded `kind 6000` event>", "<recommended relay urls>"],
113 ["content-warning", "<reason>"], 141 ["content-warning", "<reason>"],
114 ["segment", <start>, <end>, "<title>", "<thumbnail URL>"], 142 ["segment", <start>, <end>, "<title>", "<thumbnail URL>"],
@@ -127,3 +155,56 @@ Additionally `service nip96` may be included to allow clients to search the auth
127 ] 155 ]
128} 156}
129``` 157```
158
159## Addressable Event Example
160
161```jsonc
162{
163 "id": <32-bytes lowercase hex-encoded SHA-256 of the the serialized event data>,
164 "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
165 "created_at": <Unix timestamp in seconds>,
166 "kind": 34235 | 34236,
167 "content": "<summary / description of video>",
168 "tags": [
169 ["d", "<unique-identifier>"],
170 ["title", "<title of video>"],
171 ["published_at", "<unix timestamp>"],
172 ["alt", "<description for accessibility>"],
173
174 // video data
175 ["imeta",
176 "url https://example.com/media.mp4",
177 "m video/mp4",
178 "dim 480x480",
179 "blurhash eVF$^OI:${M{%LRjWBoLoLaeR*",
180 "image https://example.com/thumb.jpg",
181 "x 3093509d1e0bc604ff60cb9286f4cd7c781553bc8991937befaacfdc28ec5cdc"
182 ],
183
184 ["duration", <duration in seconds>],
185 ["content-warning", "<reason>"],
186
187 // origin tracking for imported content
188 ["origin", "<platform>", "<external-id>", "<original-url>", "<optional-metadata>"],
189
190 // participants
191 ["p", "<32-bytes hex of a pubkey>", "<optional recommended relay URL>"],
192
193 // hashtags
194 ["t", "<tag>"],
195 ["t", "<tag>"],
196
197 // reference links
198 ["r", "<url>"]
199 ]
200}
201```
202
203## Referencing Addressable Events
204
205To reference an addressable video:
206
207```
208["a", "34235:<pubkey>:<d-tag-value>", "<relay-url>"] // for normal videos
209["a", "34236:<pubkey>:<d-tag-value>", "<relay-url>"] // for short videos
210```
diff --git a/72.md b/72.md
index 582410a..210376e 100644
--- a/72.md
+++ b/72.md
@@ -8,7 +8,7 @@ Moderated Communities (Reddit Style)
8 8
9The goal of this NIP is to enable public communities. It defines the replaceable event `kind:34550` to define the community and the current list of moderators/administrators. Users that want to post into the community, simply tag any Nostr event with the community's `a` tag. Moderators may issue an approval event `kind:4550`. 9The goal of this NIP is to enable public communities. It defines the replaceable event `kind:34550` to define the community and the current list of moderators/administrators. Users that want to post into the community, simply tag any Nostr event with the community's `a` tag. Moderators may issue an approval event `kind:4550`.
10 10
11# Community Definition 11## Community Definition
12 12
13`Kind:34550` SHOULD include any field that helps define the community and the set of moderators. `relay` tags MAY be used to describe the preferred relay to download requests and approvals. A community definition event's `d` tag MAY double as its name, but if a `name` tag is provided, it SHOULD be displayed instead of the `d` tag. 13`Kind:34550` SHOULD include any field that helps define the community and the set of moderators. `relay` tags MAY be used to describe the preferred relay to download requests and approvals. A community definition event's `d` tag MAY double as its name, but if a `name` tag is provided, it SHOULD be displayed instead of the `d` tag.
14 14
@@ -39,22 +39,58 @@ The goal of this NIP is to enable public communities. It defines the replaceable
39} 39}
40``` 40```
41 41
42# Posting to a community 42## Posting to a community
43 43
44Any Nostr event can be posted to a community. Clients MUST add one or more community `a` tags, each with a recommended relay. 44[NIP-22](22.md) kind 1111 events SHOULD be used for text notes posted to a community, with the `A` tag always scoped to the community definition.
45
46### Top-level posts
47
48For top-level posts, the uppercase and lowercase NIP-22 tags should both refer to the community definition itself.
49
50```jsonc
51{
52 "kind": 1111,
53 "tags": [
54 ["A", "34550:<community-author-pubkey>:<community-d-identifier>", "<optional-relay-url>"],
55 ["a", "34550:<community-author-pubkey>:<community-d-identifier>", "<optional-relay-url>"],
56 ["P", "<community-author-pubkey>", "<optional-relay-url>"],
57 ["p", "<community-author-pubkey>", "<optional-relay-url>"],
58 ["K", "34550"],
59 ["k", "34550"],
60 ],
61 "content": "Hi everyone. It's great to be here!",
62 // other fields...
63}
64```
65
66### Nested replies
67
68For nested replies, the uppercase tags should still refer to the community definition, while the lowercase tags should refer to the parent post or reply.
45 69
46```jsonc 70```jsonc
47{ 71{
48 "kind": 1, 72 "kind": 1111,
49 "tags": [ 73 "tags": [
50 ["a", "34550:<community event author pubkey>:<community-d-identifier>", "<optional-relay-url>"], 74 // community definition itself
75 ["A", "34550:<community-author-pubkey>:<community-d-identifier>", "<optional-relay-url>"],
76 ["P", "<community-author-pubkey>", "<optional-relay-url>"],
77 ["K", "34550"],
78
79 // parent post or reply
80 ["e", "<parent-event-id>", "<optional-relay-url>"],
81 ["p", "<parent-event-author-pubkey>", "<optional-relay-url>"],
82 ["k", "<parent-event-kind>"] // most likely "1111"
51 ], 83 ],
52 "content": "hello world", 84 "content": "Agreed! Welcome everyone!",
53 // other fields... 85 // other fields...
54} 86}
55``` 87```
56 88
57# Moderation 89### Backwards compatibility note
90
91Previously kind 1 events were used for posts in communities, with an "a" tag pointing to the community. For backwards compatibility, clients MAY still query for kind 1 events, but SHOULD NOT use them for new posts. Instead, clients SHOULD use kind 1111 events with the `A` and `a` tags as described above.
92
93## Moderation
58 94
59Anyone may issue an approval event to express their opinion that a post is appropriate for a community. Clients MAY choose which approval events to honor, but SHOULD at least use ones published by the group's defined moderators. 95Anyone may issue an approval event to express their opinion that a post is appropriate for a community. Clients MAY choose which approval events to honor, but SHOULD at least use ones published by the group's defined moderators.
60 96
@@ -91,6 +127,6 @@ Since relays are instructed to delete old versions of a replaceable event, the `
91 127
92Clients SHOULD evaluate any non-`34550:*` `a` tag as posts to be approved for all `34550:*` `a` tags. 128Clients SHOULD evaluate any non-`34550:*` `a` tag as posts to be approved for all `34550:*` `a` tags.
93 129
94# Cross-posting 130## Cross-posting
95 131
96Clients MAY support cross-posting between communities by posting a NIP 18 `kind 6` or `kind 16` repost to one or more communities using `a` tags as described above. The `content` of the repost MUST be the original event, not the approval event. 132Clients MAY support cross-posting between communities by posting a NIP 18 `kind 6` or `kind 16` repost to one or more communities using `a` tags as described above. The `content` of the repost MUST be the original event, not the approval event.
diff --git a/73.md b/73.md
index d883e79..3e975ae 100644
--- a/73.md
+++ b/73.md
@@ -6,7 +6,7 @@ External Content IDs
6 6
7`draft` `optional` 7`draft` `optional`
8 8
9There are certain established global content identifiers such as [Book ISBNs](https://en.wikipedia.org/wiki/ISBN), [Podcast GUIDs](https://podcastnamespace.org/tag/guid), and [Movie ISANs](https://en.wikipedia.org/wiki/International_Standard_Audiovisual_Number) that are useful to reference in nostr events so that clients can query all the events assosiated with these ids. 9There are certain established global content identifiers such as [Book ISBNs](https://en.wikipedia.org/wiki/ISBN), [Podcast GUIDs](https://podcastnamespace.org/tag/guid), and [Movie ISANs](https://en.wikipedia.org/wiki/International_Standard_Audiovisual_Number) that are useful to reference in nostr events so that clients can query all the events associated with these ids.
10 10
11 11
12`i` tags are used for referencing these external content ids, with `k` tags representing the external content id kind so that clients can query all the events for a specific kind. 12`i` tags are used for referencing these external content ids, with `k` tags representing the external content id kind so that clients can query all the events for a specific kind.
@@ -18,6 +18,7 @@ There are certain established global content identifiers such as [Book ISBNs](ht
18| URLs | "`<URL, normalized, no fragment>`" | "web" | 18| URLs | "`<URL, normalized, no fragment>`" | "web" |
19| Books | "isbn:`<id, without hyphens>`" | "isbn" | 19| Books | "isbn:`<id, without hyphens>`" | "isbn" |
20| Geohashes | "geo:`<geohash, lowercase>`" | "geo" | 20| Geohashes | "geo:`<geohash, lowercase>`" | "geo" |
21| Countries | "iso3166:`<code, uppercase>`" | "iso3166" |
21| Movies | "isan:`<id, without version part>`" | "isan" | 22| Movies | "isan:`<id, without version part>`" | "isan" |
22| Papers | "doi:`<id, lowercase>`" | "doi" | 23| Papers | "doi:`<id, lowercase>`" | "doi" |
23| Hashtags | "#`<topic, lowercase>`" | "#" | 24| Hashtags | "#`<topic, lowercase>`" | "#" |
@@ -43,11 +44,26 @@ For the webpage "https://myblog.example.com/post/2012-03-27/hello-world" the "i"
43] 44]
44``` 45```
45 46
47### Geohashes:
48
49- Geohash: `["i", "geo:ezs42e44yx96"]` - https://www.movable-type.co.uk/scripts/geohash.html
50
51Geohashes are a geocoding system that encodes geographic locations into short strings of letters and digits. They MUST be lowercase.
52
53### Countries:
54
55ISO 3166 codes can reference countries (ISO 3166-1 alpha-2) or subdivisions like states/provinces (ISO 3166-2).
56
57- Country (Venezuela): `["i", "iso3166:VE"]`
58- Subdivision (California, USA): `["i", "iso3166:US-CA"]`
59
60ISO 3166 codes MUST be uppercase. More info: https://en.wikipedia.org/wiki/ISO_3166
61
46### Books: 62### Books:
47 63
48- Book ISBN: `["i", "isbn:9780765382030"]` - https://isbnsearch.org/isbn/9780765382030 64- Book ISBN: `["i", "isbn:9780765382030"]` - https://isbnsearch.org/isbn/9780765382030
49 65
50Book ISBNs MUST be referenced _**without hyphens**_ as many book search APIs return the ISBNs without hyphens. Removing hypens from ISBNs is trivial, whereas adding the hyphens back in is non-trivial requiring a library. 66Book ISBNs MUST be referenced _**without hyphens**_ as many book search APIs return the ISBNs without hyphens. Removing hyphens from ISBNs is trivial, whereas adding the hyphens back in is non-trivial requiring a library.
51 67
52### Podcasts: 68### Podcasts:
53 69
diff --git a/77.md b/77.md
new file mode 100644
index 0000000..ff4eba7
--- /dev/null
+++ b/77.md
@@ -0,0 +1,175 @@
1NIP-77
2======
3
4Negentropy Syncing
5------------------
6
7`draft` `optional` `relay`
8
9This document describes a protocol extension for syncing events. It works for both client-relay and relay-relay scenarios. If both sides of the sync have events in common, then this protocol will use less bandwidth than transferring the full set of events (or even just their IDs).
10
11It is a Nostr-friendly wrapper around the [Negentropy](https://github.com/hoytech/negentropy) protocol, which uses a technique called [Range-Based Set Reconciliation](https://logperiodic.com/rbsr.html).
12
13Since Negentropy is a binary protocol, this wrapper hex-encodes its messages. The specification for Negentropy Protocol V1 is attached as an appendix to this NIP below.
14
15## High-Level Protocol Description
16
17We're going to call the two sides engaged in the sync the client and the relay (even though the initiator could be another relay instead of a client).
18
19* (1) Client (initiator) chooses a filter, and retrieves the set of events that it has locally that match this filter (or uses a cache), and constructs an initial message.
20* (2) Client sends a `NEG-OPEN` message to the relay, which includes the filter and the initial message.
21* (3) Relay selects the set of events that it has locally that match the filter (or uses a cache).
22* (4) Relay constructs a response and returns it to the client in a `NEG-MSG` message.
23* (5) Client parses the message to learn about IDs it has (and relay needs) and IDs it needs (and relay has).
24 * If client wishes to continue, then it constructs a new message and sends it to the relay in a `NEG-MSG` message. Goto step 4.
25 * If client wishes to stop, then it sends a `NEG-CLOSE` message or disconnects the websocket.
26
27The above protocol only results in the client learning about IDs it has/needs, and does not actually transfer events. Given these IDs, the client can upload events it has with `EVENT`, and/or download events it needs with `REQ`. This can be performed over the same websocket connection in parallel with subsequent `NEG-MSG` messages. If a client is only interested in determining the number of unique events (ie, reaction counts), it may choose to not download/upload at all.
28
29## Nostr Messages
30
31### Initial message (client to relay):
32
33```jsonc
34[
35 "NEG-OPEN",
36 <subscription ID string>,
37 <filter>,
38 <initialMessage, hex-encoded>
39]
40```
41
42* The subscription ID is used by each side to identify which query a message refers to. It only needs to be long enough to distinguish it from any other concurrent subscriptions on this websocket connection (an integer that increments once per `NEG-OPEN` is fine). Subscription IDs are in a separate namespace from `REQ` subscription IDs. If a `NEG-OPEN` is issued for a currently open subscription ID, the existing subscription is first closed.
43* The filter is as described in [NIP-01](01.md).
44* `initialMessage` is the initial Negentropy binary message, hex-encoded. See appendix.
45
46### Error message (relay to client):
47
48If a request cannot be serviced by the relay, an error is returned to the client:
49
50```jsonc
51[
52 "NEG-ERR",
53 <subscription ID string>,
54 <reason code string>
55]
56```
57
58Error reasons are the same format as in NIP-01. They should begin with a machine-readable single-word prefix, followed by a `:` and then a human-readable message with more information.
59
60The current suggested error reasons are
61
62* `blocked`
63 * Relays can optionally reject queries that would require them to process too many records, or records that are too old
64 * The maximum number of records that can be processed can optionally be returned as the 4th element in the response
65 * Example: `blocked: this query is too big`
66* `closed`
67 * Because the `NEG-OPEN` queries may be stateful, relays may choose to time-out inactive queries to recover memory resources
68 * Example: `closed: you took too long to respond!`
69
70After a `NEG-ERR` is issued, the subscription is considered to be closed.
71
72### Subsequent messages (bidirectional):
73
74Relay and client alternate sending each other `NEG-MSG`s:
75
76```jsonc
77[
78 "NEG-MSG",
79 <subscription ID string>,
80 <message, hex-encoded>
81]
82```
83
84* `message` is a Negentropy binary message, hex-encoded. Both message directions use the same format. See appendix.
85
86### Close message (client to relay):
87
88When finished, the client should tell the relay it can release its resources with a `NEG-CLOSE`:
89
90```jsonc
91[
92 "NEG-CLOSE",
93 <subscription ID string>
94]
95```
96
97
98## Appendix: Negentropy Protocol V1
99
100### Preparation
101
102There are two protocol participants: Client and server. The client creates an initial message and transmits it to the server, which replies with its own message in response. The client continues querying the server until it is satisfied, and then terminates the protocol. Messages in either direction have the same format.
103
104Each participant has a collection of records. A records consists of a 64-bit numeric timestamp and a 256-bit ID. Each participant starts by sorting their items according to timestamp, ascending. If two timestamps are equal then items are sorted lexically by ID, ascending by first differing byte. Items may not use the max uint64 value (`2**64 - 1`) as a timestamp since this is reserved as a special "infinity" value.
105
106The goal of the protocol is for the client to learn the set of IDs that it has and the server does not, and the set of items that the server has and it does not.
107
108### `Varint`
109
110Varints (variable-sized unsigned integers) are represented as base-128 digits, most significant digit first, with as few digits as possible. Bit eight (the high bit) is set on each byte except the last.
111
112 Varint := <Digit+128>* <Digit>
113
114### `Id`
115
116IDs are represented as byte-strings of length `32`:
117
118 Id := Byte{32}
119
120### `Message`
121
122A reconciliation message is a protocol version byte followed by an ordered list of ranges:
123
124 Message := <protocolVersion (Byte)> <Range>*
125
126The current protocol version is 1, represented by the byte `0x61`. Protocol version 2 will be `0x62`, and so forth. If a server receives a message with a protocol version that it cannot handle, it should reply with a single byte containing the highest protocol version it supports, allowing the client to downgrade and retry its message.
127
128Each Range corresponds to a contiguous section of the timestamp/ID space. The first Range starts at timestamp 0 and an ID of 0 bytes. Ranges are always adjacent (no gaps). If the last Range doesn't end at the special infinity value, an implicit `Skip` to infinity Range is appended. This means that the list of Ranges always covers the full timestamp/ID space.
129
130### `Range`
131
132A Range consists of an upper bound, a mode, and a payload:
133
134 Range := <upperBound (Bound)> <mode (Varint)> <payload (Skip | Fingerprint | IdList)>
135
136The contents of the payload is determined by mode:
137
138* If `mode = 0`, then payload is `Skip`, meaning the sender does not wish to process this Range further. This payload is empty:
139
140 Skip :=
141
142* If `mode = 1`, then payload is a `Fingerprint`, which is a [digest](#fingerprint-algorithm) of all the IDs the sender has within the Range:
143
144 Fingerprint := Byte{16}
145
146* If `mode = 2`, the payload is `IdList`, a variable-length list of all IDs the sender has within the Range:
147
148 IdList := <length (Varint)> <ids (Id)>*
149
150
151### `Bound`
152
153Each Range is specified by an *inclusive* lower bound and an *exclusive* upper bound. As defined above, each Range only includes an upper bound: the lower bound of a Range is the upper bound of the previous Range, or 0 timestamp/0 ID for the first Range.
154
155A Bound consists of an encoded timestamp and a variable-length disambiguating prefix of an ID (in case multiple items have the same timestamp):
156
157 Bound := <encodedTimestamp (Varint)> <length (Varint)> <idPrefix (Byte)>*
158
159* The timestamp is encoded specially. The infinity timestamp is encoded as `0`. All other values are encoded as `1 + offset`, where offset is the difference between this timestamp and the previously encoded timestamp. The initial offset starts at `0` and resets at the beginning of each message.
160
161 Offsets are always non-negative since the upper bound's timestamp is greater than or equal to the lower bound's timestamp, ranges in a message are always encoded in ascending order, and ranges never overlap.
162
163* The size of `idPrefix` is encoded in `length`, and can be between `0` and `32` bytes, inclusive. This allows implementations to use the shortest possible prefix to separate the first record of this Range from the last record of the previous Range. If these records' timestamps differ, then the length should be 0, otherwise it should be the byte-length of their common ID-prefix plus 1.
164
165 If the `idPrefix` length is less than `32` then the omitted trailing bytes are implicitly 0 bytes.
166
167
168### Fingerprint Algorithm
169
170The fingerprint of a Range is computed with the following algorithm:
171
172* Compute the addition mod 2<sup>256</sup> of the element IDs (interpreted as 32-byte little-endian unsigned integers)
173* Concatenate with the number of elements in the Range, encoded as a [Varint](#varint)
174* Hash with SHA-256
175* Take the first 16 bytes
diff --git a/84.md b/84.md
index ee416a5..1094f55 100644
--- a/84.md
+++ b/84.md
@@ -23,7 +23,7 @@ or obvious non-useful information from the query string.
23### Attribution 23### Attribution
24Clients MAY include one or more `p` tags, tagging the original authors of the material being highlighted; this is particularly 24Clients MAY include one or more `p` tags, tagging the original authors of the material being highlighted; this is particularly
25useful when highlighting non-nostr content for which the client might be able to get a nostr pubkey somehow 25useful when highlighting non-nostr content for which the client might be able to get a nostr pubkey somehow
26(e.g. prompting the user or reading a `<meta name="nostr:nprofile1..." />` tag on the document). A role MAY be included as the 26(e.g. prompting the user or reading a `<link rel="me" href="nostr:nprofile1..." />` tag on the document). A role MAY be included as the
27last value of the tag. 27last value of the tag.
28 28
29```jsonc 29```jsonc
diff --git a/85.md b/85.md
new file mode 100644
index 0000000..587095d
--- /dev/null
+++ b/85.md
@@ -0,0 +1,166 @@
1NIP-85
2======
3
4Trusted Assertions
5------------------
6
7`draft` `optional`
8
9Certain Webs of Trust calculations require access to a large volume of events and/or computing power, making it virtually impossible to perform them directly on clients. This NIP allows users to offload such calculations to declared trusted service providers, and for these providers to publish signed "Trusted Assertion" events for the user client's consumption.
10
11## Assertion Events
12
13Trusted Assertions are always addressable (replaceable) events with the `d` tag pointing to the "subject" of the assertion. This NIP currently recognizes four distinct target "subjects" on which such calculations can be performed: *pubkeys*, *regular events*, *addressable events*, and *nip73 identifiers*. Each subject type is mapped to an event kind:
14
15| Subject | Event Kind | `d` tag value |
16| ------------------ | -------------- | ----------------- |
17| User | 30382 | `<pubkey>` |
18| Event | 30383 | `<event_id>` |
19| Addressable Event | 30384 | `<event_address>` |
20| NIP-73 Identifier | 30385 | `<i-tag>` |
21
22Calculation results are saved in pre-defined tags whose syntax and semantics are agreed upon by providers and clients.
23
24Example of ranking a pubkey with a web of trust score of `89`:
25
26```jsonc
27{
28 "kind": 30382,
29 "pubkey": "<service pubkey>",
30 "tags": [
31 ["d", "e88a691e98d9987c964521dff60025f60700378a4879180dcbbb4a5027850411"], // target user's public key
32 ["rank", "89"],
33 ],
34 "content": "",
35 //...
36}
37```
38
39Service providers MUST use different service keys for distinct algorithms, including a key per user when the algorithm is personalized to that user's point of view or settings.
40
41## Kind 30382: Users as Subject:
42
43The following result types have been declared:
44
45| Result type | Tag name | Tag value format |
46| ----------------------- | ---------------------- | ----------------- |
47| Follower Count | `followers` | int |
48| User Rank | `rank` | int, norm 0-100 |
49| First Post Time | `first_created_at` | unix timestamp |
50| Post Count | `post_cnt` | int |
51| Reply Count | `reply_cnt` | int |
52| Reactions Count | `reactions_cnt` | int |
53| Zap Amount Received | `zap_amt_recd` | int, sats |
54| Zap Amount Sent | `zap_amt_sent` | int, sats |
55| Zap Number Received | `zap_cnt_recd` | int |
56| Zap Number Sent | `zap_cnt_sent` | int |
57| Avg Zap Amount/day recd | `zap_avg_amt_day_recd` | int, sats |
58| Avg Zap Amount/day sent | `zap_avg_amt_day_sent` | int, sats |
59| Reports Received | `reports_cnt_recd` | int |
60| Reports Sent | `reports_cnt_sent` | int |
61| Common Topics | `t` | string |
62| Generally active start | `active_hours_start` | int, 0-24, UTC |
63| Generally active end | `active_hours_end` | int, 0-24, UTC |
64
65Each provider can offer their own ways to calculate such values. For instance, the Follower Count of one trust provider might remove the user's muted public keys while another provider keeps them. Users can then choose how they want to see this information in their preferred client by picking a provider that aligns with their view.
66
67## Kind 30383: Events as Subject
68
69Providers can rate individual events with the following tags:
70
71| Result type | Tag name | Tag value format |
72| ----------------------- | ---------------------- | ----------------- |
73| Event Rank | `rank` | int, norm 0-100 |
74| Event Comment Count | `comment_cnt` | int |
75| Event Quote Count | `quote_cnt` | int |
76| Event Repost Count | `repost_cnt` | int |
77| Event Reaction Count | `reaction_cnt` | int |
78| Event Zap Count | `zap_cnt` | int |
79| Event Zap Amount | `zap_amount` | int, sats |
80
81## Kind 30384: Addressables as Subject
82
83Providers can rate all versions of addressable events using the following tags:
84
85| Result type | Tag name | Tag value format |
86| ------------------------- | ---------------------- | ----------------- |
87| Address Rank | `rank` | int, norm 0-100 |
88| Address Comment Count | `comment_cnt` | int |
89| Address Quote Count | `quote_cnt` | int |
90| Address Repost Count | `repost_cnt` | int |
91| Address Reaction Count | `reaction_cnt` | int |
92| Address Zap Count | `zap_cnt` | int |
93| Address Zap Amount | `zap_amount` | int, sats |
94
95## Kind 30385: External identifier as Subject
96
97Providers can rate books, locations, movies, websites, and hashtags using [NIP-73](73.md) identifiers.
98
99| Result type | Tag name | Tag value format |
100| ----------------- | ---------------------- | ----------------- |
101| Rank | `rank` | int, norm 0-100 |
102| Comment Count | `comment_cnt` | int |
103| Reaction Count | `reaction_cnt` | int |
104
105NIP-73 `k` tags should be added to the event as well.
106
107## Declaring Trusted Service Providers
108
109Kind `10040` lists the user's authorized providers for each result. Each `kind:tag` is followed by the `pubkey` of the service and the relay where the results are published. Users can specify these publicly or privately by JSON-stringifying and encrypting the tag list in the `.content` using NIP-44.
110
111```js
112{
113 "kind": 10040,
114 "tags": [
115 ["<kind:tag>", "<service key>", "<relay hint>"],
116
117 // examples
118 ["30382:rank", "4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe", "wss://nip85.nostr.band"],
119 ["30382:rank", "3d842afecd5e293f28b6627933704a3fb8ce153aa91d790ab11f6a752d44a42d", "wss://nostr.wine"],
120 ["30382:zap_amt_sent", "4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe", "wss://nip85.nostr.band"],
121 ],
122 "content": nip44Encrypt(JSON.stringify([
123 ["<kind:tag>", "<service key>", "<relay hint>"],
124
125 // examples
126 ["30383:rank", "4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe", "wss://nip85.nostr.band"],
127 ["30384:rank", "4fd5e210530e4f6b2cb083795834bfe5108324f1ed9f00ab73b9e8fcfe5f12fe", "wss://nip85.nostr.band"],
128 ]),
129 //...
130}
131```
132
133If the provider offers several algorithms or multiple points of view of an algorithm, the key listed in each tag SHOULD point to the key created for each algorithm or point of view.
134
135## Final Considerations
136
137Service providers SHOULD update Trusted Assertions as fast as new information arrives, but only if the contents of each event actually change to avoid re-downloading the same information.
138
139Service providers MAY limit access to the results by using paid relays.
140
141In TAs, `p`, `e`, and `a` tags with the same value as the `d` tag MAY be used to add a relay hint to the home relay of that user or event.
142
143## Appendix 1: Service provider discoverability
144
145Service Providers SHOULD sign a kind `0` of each service key that explains who controls the key and what the current version of the algorithm is about.
146
147```jsonc
148{
149 "kind": 0,
150 "pubkey": "<service pubkey>",
151 "tags": [],
152 "content": "{
153 \"name\" = \"Vitor's Brainstormer\",
154 \"about\" = \"A Web of Trust algorithm from Vitor's point of view that considers Follows and Mutes, but no reports, and gives extra score points for anyone around Boston\",
155 \"picture\" = \"https://brainstorm.com/logo.png\",
156 \"website\" = \"https://brainstorm.com\"
157 }",
158 // other fields...
159}
160```
161
162Clients wishing to offer a list of Service Providers to their users SHOULD:
1631. Download kind `10040` events of the user's follow list
1642. Connect to each of the listed relays and download the kind `0` of the respective service keys
1653. Parse the kind `0` and collect the `website` property
1664. Load the OpenGraph tags of that website and display them as clickable items
diff --git a/86.md b/86.md
index 6f64eee..b9a8cc2 100644
--- a/86.md
+++ b/86.md
@@ -34,12 +34,18 @@ This is the list of **methods** that may be supported:
34* `banpubkey`: 34* `banpubkey`:
35 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]` 35 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]`
36 - result: `true` (a boolean always set to `true`) 36 - result: `true` (a boolean always set to `true`)
37* `unbanpubkey`:
38 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]`
39 - result: `true` (a boolean always set to `true`)
37* `listbannedpubkeys`: 40* `listbannedpubkeys`:
38 - params: `[]` 41 - params: `[]`
39 - result: `[{"pubkey": "<32-byte-hex>", "reason": "<optional-reason>"}, ...]`, an array of objects 42 - result: `[{"pubkey": "<32-byte-hex>", "reason": "<optional-reason>"}, ...]`, an array of objects
40* `allowpubkey`: 43* `allowpubkey`:
41 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]` 44 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]`
42 - result: `true` (a boolean always set to `true`) 45 - result: `true` (a boolean always set to `true`)
46* `unallowpubkey`:
47 - params: `["<32-byte-hex-public-key>", "<optional-reason>"]`
48 - result: `true` (a boolean always set to `true`)
43* `listallowedpubkeys`: 49* `listallowedpubkeys`:
44 - params: `[]` 50 - params: `[]`
45 - result: `[{"pubkey": "<32-byte-hex>", "reason": "<optional-reason>"}, ...]`, an array of objects 51 - result: `[{"pubkey": "<32-byte-hex>", "reason": "<optional-reason>"}, ...]`, an array of objects
diff --git a/87.md b/87.md
new file mode 100644
index 0000000..0010038
--- /dev/null
+++ b/87.md
@@ -0,0 +1,142 @@
1NIP-87
2======
3
4Ecash Mint Discoverability
5--------------------------------
6
7`draft` `optional`
8
9This NIP describes `kind:38173`, `kind:38172` and `kind:38000`: a way to discover ecash mints, their capabilities, and people who recommend them.
10
11## Rationale
12
13Nostr's discoverability and transparent event interaction is one of its most interesting/novel mechanics.
14This NIP provides a simple way for users to discover ecash mints recommended by other users and to interact with them.
15
16### Parties involved
17
18There are three actors to this workflow:
19
20* An ecash mint operator, announces their mint and its capabilities.
21 * Publishes `kind:38173` or `kind:38172`, detailing how to connect to it and its capabilities.
22* user A, who recommends an ecash mint
23 * Publishes `kind:38000`
24* user B, who seeks a recommendation for an ecash mint
25 * Queries for `kind:38000` and, based on results, queries for `kind:38173`/`kind:38172`
26
27## Events
28
29### Recommendation event
30```json
31{
32 "kind": 38000,
33 "pubkey": <recommender-user-pubkey>,
34 "tags": [
35 ["k", "38173"],
36 ["d", "<d-identifier>"],
37 ["u", <recommended-fedimint-invite-code>],
38 ["a", "38173:fedimint-pubkey:<d-identifier>", "wss://relay1"]
39 ],
40 "content": "I trust this mint with my life"
41}
42```
43
44The recommendation event is a parameterized-replaceable event so that a user can change edit their recommendation without creating a new event.
45
46The `d` tag in `kind:38000` is the `kind:38173`/`kind:38172` event identifier this event is recommending, if no event exists, the `d` tag can still be calculated from the mint's pubkey/id.
47The `k` tag is the kind number that corresponds to the event kind that the user is recommending, in this case `kind:38173` for fedimints and `kind:38172` for cashu mints.
48
49Optional `u` tags can be added to give a recommend way to connect to the mint.
50The value of the tag is the URL or invite code of the ecash mint.
51Multiple `u` tags can appear on the same `kind:38000`.
52
53`a` tags are used to point to the `kind:38173`/`kind:38172` event of the ecash mint.
54The first value of the tag is the `kind:38173`/`kind:38172` event identifier, the second value of the tag is a relay hint.
55This is used to correctly point to the mint's `kind:38173`/`kind:38172` event in case there are duplicates claiming to be the same mint.
56
57The content can be used to give a review.
58
59## Ecash Mint Information
60
61Cashu mints SHOULD publish `kind:38172` events to announce their capabilities and how to connect to them.
62
63For cashu mints, the `u` tag SHOULD be the URL to the cashu mint and it should list the nuts that the cashu mint supports.
64
65The `d` tag SHOULD be the mint's pubkey (found when querying `/v1/info`), this way users can query by pubkey and find the mint announcement.
66
67An `n` tag SHOULD be added to signify the network the cashu mint is on (either `mainnet`, `testnet`, `signet`, or `regtest`)
68
69```json
70{
71 "kind": 38172,
72 "pubkey": "<application-pubkey>",
73 "content": "<optional-kind:0-style-metadata>",
74 "tags": [
75 ["d", <cashu mint pubkey>],
76 ["u", "https://cashu.example.com"],
77 ["nuts", "1,2,3,4,5,6,7"],
78 ["n", "mainnet"]
79 ]
80}
81```
82
83Fedimints SHOULD publish `kind:38173` events to announce their capabilities and how to connect to them.
84
85For fedimints, it should list all known fedimint invite codes in `u` tags and it should list the modules it supports.
86
87The `d` tag SHOULD be the federation id, this way users can query by federation id and find the fedimint announcement.
88
89An `n` tag SHOULD be added to signify the network the fedimint is on (either `mainnet`, `testnet`, `signet`, or `regtest`)
90
91```json
92{
93 "kind": 38173,
94 "pubkey": "<application-pubkey>",
95 "content": "<optional-kind:0-style-metadata>",
96 "tags": [
97 ["d", <federation-id>],
98 ["u", "fed11abc.."],
99 ["u", "fed11xyz.."],
100 ["modules", "lightning,wallet,mint"],
101 ["n", "signet"]
102 ]
103}
104```
105
106* `content` is an optional `metadata`-like stringified JSON object, as described in NIP-01. This content is useful when the pubkey creating the `kind:38173` is not a normal user. If `content` is empty, the `kind:0` of the pubkey should be used to display mint information (e.g. name, picture, web, LUD16, etc.)
107
108## Example
109
110### User A recommends some mints
111User A might be a user of a cashu mint. Using a client, user A publishes an event recommending the cashu mint they use.
112
113```json
114{
115 "kind": 38000,
116 "tags": [
117 ["u", "fed11abc..", "fedimint"],
118 ["u", "https://cashu.example.com", "cashu"],
119 ["a", "38173:fedimint-pubkey:<d-identifier>", "wss://relay1", "fedimint"],
120 ["a", "38172:cashu-mint-pubkey:<d-identifier>", "wss://relay2", "cashu"]
121 ],
122 ...
123}
124```
125
126### User B finds a mint
127User B wants to use an ecash wallet, they need to find a mint.
128
129User B's wallet client queries for `kind:38000` events, looking for recommendations for ecash mints.
130
131```json
132["REQ", <id>, [{ "kinds": [38000], "authors": [<user>, <users-contact-list>], "#k": ["38173"] }]]
133```
134
135User B, who follows User A, sees that `kind:38000` event and tries to connect to the corresponding mints.
136
137### Alternative query bypassing `kind:38000`
138Alternatively, users might choose to query directly for `kind:38173` for an event kind. Clients SHOULD be careful doing this and use spam-prevention mechanisms or querying high-quality restricted relays to avoid directing users to malicious handlers.
139
140```json
141["REQ", <id>, [{ "kinds": [38173], "authors": [...] }]]
142```
diff --git a/88.md b/88.md
index 331cd27..9971623 100644
--- a/88.md
+++ b/88.md
@@ -48,7 +48,7 @@ Example Event
48 48
49The response event is a `kind:1018` event. It contains an e tag with the poll event it is referencing, followed by one or more response tags. 49The response event is a `kind:1018` event. It contains an e tag with the poll event it is referencing, followed by one or more response tags.
50 50
51- **response** : The tag contains "response" as it's first positional argument followed by the option Id selected. 51- **response** : The tag contains "response" as its first positional argument followed by the option Id selected.
52 52
53The responses are meant to be published to the relays specified in the poll event. 53The responses are meant to be published to the relays specified in the poll event.
54 54
diff --git a/89.md b/89.md
index 24aa3c5..dff4ea1 100644
--- a/89.md
+++ b/89.md
@@ -47,7 +47,7 @@ Multiple `a` tags can appear on the same `kind:31989`.
47The second value of the tag SHOULD be a relay hint. 47The second value of the tag SHOULD be a relay hint.
48The third value of the tag SHOULD be the platform where this recommendation might apply. 48The third value of the tag SHOULD be the platform where this recommendation might apply.
49 49
50## Handler information 50### Handler information
51```jsonc 51```jsonc
52{ 52{
53 "kind": 31990, 53 "kind": 31990,
@@ -76,7 +76,7 @@ Multiple tags might be registered by the app, following NIP-19 nomenclature as t
76 76
77A tag without a second value in the array SHOULD be considered a generic handler for any NIP-19 entity that is not handled by a different tag. 77A tag without a second value in the array SHOULD be considered a generic handler for any NIP-19 entity that is not handled by a different tag.
78 78
79# Client tag 79## Client tag
80When publishing events, clients MAY include a `client` tag. Identifying the client that published the note. This tag is a tuple of `name`, `address` identifying a handler event and, a relay `hint` for finding the handler event. This has privacy implications for users, so clients SHOULD allow users to opt-out of using this tag. 80When publishing events, clients MAY include a `client` tag. Identifying the client that published the note. This tag is a tuple of `name`, `address` identifying a handler event and, a relay `hint` for finding the handler event. This has privacy implications for users, so clients SHOULD allow users to opt-out of using this tag.
81 81
82```jsonc 82```jsonc
diff --git a/90.md b/90.md
index 785b539..a33d90a 100644
--- a/90.md
+++ b/90.md
@@ -58,7 +58,7 @@ All tags are optional.
58 * `<input-type>`: The way this argument should be interpreted. MUST be one of: 58 * `<input-type>`: The way this argument should be interpreted. MUST be one of:
59 * `url`: A URL to be fetched of the data that should be processed. 59 * `url`: A URL to be fetched of the data that should be processed.
60 * `event`: A Nostr event ID. 60 * `event`: A Nostr event ID.
61 * `job`: The output of a previous job with the specified event ID. The dermination of which output to build upon is up to the service provider to decide (e.g. waiting for a signaling from the customer, waiting for a payment, etc.) 61 * `job`: The output of a previous job with the specified event ID. The determination of which output to build upon is up to the service provider to decide (e.g. waiting for a signaling from the customer, waiting for a payment, etc.)
62 * `text`: `<data>` is the value of the input, no resolution is needed 62 * `text`: `<data>` is the value of the input, no resolution is needed
63 * `<relay>`: If `event` or `job` input-type, the relay where the event/job was published, otherwise optional or empty string 63 * `<relay>`: If `event` or `job` input-type, the relay where the event/job was published, otherwise optional or empty string
64 * `<marker>`: An optional field indicating how this input should be used within the context of the job 64 * `<marker>`: An optional field indicating how this input should be used within the context of the job
@@ -68,7 +68,7 @@ All tags are optional.
68* `relays`: List of relays where Service Providers SHOULD publish responses to 68* `relays`: List of relays where Service Providers SHOULD publish responses to
69* `p`: Service Providers the customer is interested in. Other SPs MIGHT still choose to process the job 69* `p`: Service Providers the customer is interested in. Other SPs MIGHT still choose to process the job
70 70
71## Encrypted Params 71### Encrypted Params
72 72
73If the user wants to keep the input parameters a secret, they can encrypt the `i` and `param` tags with the service provider's 'p' tag and add it to the content field. Add a tag `encrypted` as tags. Encryption for private tags will use [NIP-04 - Encrypted Direct Message encryption](04.md), using the user's private and service provider's public key for the shared secret 73If the user wants to keep the input parameters a secret, they can encrypt the `i` and `param` tags with the service provider's 'p' tag and add it to the content field. Add a tag `encrypted` as tags. Encryption for private tags will use [NIP-04 - Encrypted Direct Message encryption](04.md), using the user's private and service provider's public key for the shared secret
74 74
@@ -122,7 +122,7 @@ Service providers publish job results, providing the output of the job result. T
122* `amount`: millisats that the Service Provider is requesting to be paid. An optional third value can be a bolt11 invoice. 122* `amount`: millisats that the Service Provider is requesting to be paid. An optional third value can be a bolt11 invoice.
123* `i`: The original input(s) specified in the request. 123* `i`: The original input(s) specified in the request.
124 124
125## Encrypted Output 125### Encrypted Output
126 126
127If the request has encrypted params, then output should be encrypted and placed in `content` field. If the output is encrypted, then avoid including `i` tag with input-data as clear text. 127If the request has encrypted params, then output should be encrypted and placed in `content` field. If the output is encrypted, then avoid including `i` tag with input-data as clear text.
128Add a tag encrypted to mark the output content as `encrypted` 128Add a tag encrypted to mark the output content as `encrypted`
@@ -180,7 +180,7 @@ Service providers can give feedback about a job back to the customer.
180Any job feedback event MIGHT include results in the `.content` field, as described in the [Job Result](#job-result-kind6000-6999) section. This is useful for service providers to provide a sample of the results that have been processed so far. 180Any job feedback event MIGHT include results in the `.content` field, as described in the [Job Result](#job-result-kind6000-6999) section. This is useful for service providers to provide a sample of the results that have been processed so far.
181 181
182 182
183# Protocol Flow 183## Protocol Flow
184 184
185* Customer publishes a job request (e.g. `kind:5000` speech-to-text). 185* Customer publishes a job request (e.g. `kind:5000` speech-to-text).
186* Service Providers MAY submit `kind:7000` job-feedback events (e.g. `payment-required`, `processing`, `error`, etc.). 186* Service Providers MAY submit `kind:7000` job-feedback events (e.g. `payment-required`, `processing`, `error`, etc.).
@@ -191,24 +191,24 @@ Job feedback (`kind:7000`) and Job Results (`kind:6000-6999`) events MAY include
191 191
192Customers can always either pay the included `bolt11` invoice or zap the event requesting the payment and service providers should monitor for both if they choose to include a bolt11 invoice. 192Customers can always either pay the included `bolt11` invoice or zap the event requesting the payment and service providers should monitor for both if they choose to include a bolt11 invoice.
193 193
194## Notes about the protocol flow 194### Notes about the protocol flow
195The flow is deliberately ambiguous, allowing vast flexibility for the interaction between customers and service providers so that service providers can model their behavior based on their own decisions/perceptions of risk. 195The flow is deliberately ambiguous, allowing vast flexibility for the interaction between customers and service providers so that service providers can model their behavior based on their own decisions/perceptions of risk.
196 196
197Some service providers might choose to submit a `payment-required` as the first reaction before sending a `processing` or before delivering results, some might choose to serve partial results for the job (e.g. a sample), send a `payment-required` to deliver the rest of the results, and some service providers might choose to assess likelihood of payment based on an npub's past behavior and thus serve the job results before requesting payment for the best possible UX. 197Some service providers might choose to submit a `payment-required` as the first reaction before sending a `processing` or before delivering results, some might choose to serve partial results for the job (e.g. a sample), send a `payment-required` to deliver the rest of the results, and some service providers might choose to assess likelihood of payment based on an npub's past behavior and thus serve the job results before requesting payment for the best possible UX.
198 198
199It's not up to this NIP to define how individual vending machines should choose to run their business. 199It's not up to this NIP to define how individual vending machines should choose to run their business.
200 200
201# Cancellation 201## Cancellation
202A job request might be canceled by publishing a `kind:5` delete request event tagging the job request event. 202A job request might be canceled by publishing a `kind:5` delete request event tagging the job request event.
203 203
204# Appendix 1: Job chaining 204## Appendix 1: Job chaining
205A Customer MAY request multiple jobs to be processed as a chain, where the output of a job is the input of another job. (e.g. podcast transcription -> summarization of the transcription). This is done by specifying as input an event id of a different job with the `job` type. 205A Customer MAY request multiple jobs to be processed as a chain, where the output of a job is the input of another job. (e.g. podcast transcription -> summarization of the transcription). This is done by specifying as input an event id of a different job with the `job` type.
206 206
207Service Providers MAY begin processing a subsequent job the moment they see the prior job's result, but they will likely wait for a zap to be published first. This introduces a risk that Service Provider of job #1 might delay publishing the zap event in order to have an advantage. This risk is up to Service Providers to mitigate or to decide whether the service provider of job #1 tends to have good-enough results so as to not wait for an explicit zap to assume the job was accepted. 207Service Providers MAY begin processing a subsequent job the moment they see the prior job's result, but they will likely wait for a zap to be published first. This introduces a risk that Service Provider of job #1 might delay publishing the zap event in order to have an advantage. This risk is up to Service Providers to mitigate or to decide whether the service provider of job #1 tends to have good-enough results so as to not wait for an explicit zap to assume the job was accepted.
208 208
209This gives a higher level of flexibility to service providers (which sophisticated service providers would take anyway). 209This gives a higher level of flexibility to service providers (which sophisticated service providers would take anyway).
210 210
211# Appendix 2: Service provider discoverability 211## Appendix 2: Service provider discoverability
212Service Providers MAY use NIP-89 announcements to advertise their support for job kinds: 212Service Providers MAY use NIP-89 announcements to advertise their support for job kinds:
213 213
214```jsonc 214```jsonc
diff --git a/96.md b/96.md
index 3828e76..28bc8a8 100644
--- a/96.md
+++ b/96.md
@@ -1,3 +1,5 @@
1> __Warning__ `unrecommended`: deprecated in favor of [NIP-B7](B7.md)
2
1NIP-96 3NIP-96
2====== 4======
3 5
diff --git a/99.md b/99.md
index 724ce5f..2b6a97a 100644
--- a/99.md
+++ b/99.md
@@ -8,7 +8,7 @@ Classified Listings
8 8
9This NIP defines `kind:30402`: an addressable event to describe classified listings that list any arbitrary product, service, or other thing for sale or offer and includes enough structured metadata to make them useful. 9This NIP defines `kind:30402`: an addressable event to describe classified listings that list any arbitrary product, service, or other thing for sale or offer and includes enough structured metadata to make them useful.
10 10
11The category of classifieds includes a very broad range of physical goods, services, work opportunities, rentals, free giveaways, personals, etc. and is distinct from the more strictly structured marketplaces defined in [NIP-15](15.md) that often sell many units of specific products through very specific channels. 11The specification supports a broad range of use cases physical goods, services, work opportunities, rentals, free giveaways, personals, etc. To promote interoperability between clients implementing NIP-99 for e-commerce, you can find the extension proposal [here](https://github.com/GammaMarkets/market-spec/blob/main/spec.md) which standardizes the e-commerce use case while maintaining the specification's lightweight and flexible nature. While [NIP-15](15.md) provides a strictly structured marketplace specification, NIP-99 has emerged as a simpler and more flexible alternative.
12 12
13The structure of these events is very similar to [NIP-23](23.md) long-form content events. 13The structure of these events is very similar to [NIP-23](23.md) long-form content events.
14 14
@@ -40,7 +40,7 @@ The following tags, used for structured metadata, are standardized and SHOULD be
40 - `"<number>"` is the amount in numeric format (but included in the tag as a string) 40 - `"<number>"` is the amount in numeric format (but included in the tag as a string)
41 - `"<currency>"` is the currency unit in 3-character ISO 4217 format or ISO 4217-like currency code (e.g. `"btc"`, `"eth"`). 41 - `"<currency>"` is the currency unit in 3-character ISO 4217 format or ISO 4217-like currency code (e.g. `"btc"`, `"eth"`).
42 - `"<frequency>"` is optional and can be used to describe recurring payments. SHOULD be in noun format (hour, day, week, month, year, etc.) 42 - `"<frequency>"` is optional and can be used to describe recurring payments. SHOULD be in noun format (hour, day, week, month, year, etc.)
43- - `"status"` (optional), the status of the listing. SHOULD be either "active" or "sold". 43- `"status"` (optional), the status of the listing. SHOULD be either "active" or "sold".
44 44
45#### `price` examples 45#### `price` examples
46 46
@@ -48,7 +48,7 @@ The following tags, used for structured metadata, are standardized and SHOULD be
48- €15 per month `["price", "15", "EUR", "month"]` 48- €15 per month `["price", "15", "EUR", "month"]`
49- £50,000 per year `["price", "50000", "GBP", "year"]` 49- £50,000 per year `["price", "50000", "GBP", "year"]`
50 50
51Other standard tags that might be useful. 51Other common tags that might be useful.
52 52
53- `"g"`, a geohash for more precise location 53- `"g"`, a geohash for more precise location
54 54
diff --git a/A0.md b/A0.md
new file mode 100644
index 0000000..884bf2a
--- /dev/null
+++ b/A0.md
@@ -0,0 +1,60 @@
1NIP-A0
2======
3
4Voice Messages
5-----------
6
7**Status:** Draft
8
9This NIP defines new events `kind: 1222` for root messages and `kind: 1244` for reply messages to be used for short voice messages, typically up to 60 seconds in length.
10
11## Specification
12
13### Event Kind `1222` and Kind `1244`
14
15The `kind: 1222` event is defined as follows:
16
17- `content`: MUST be a URL pointing directly to an audio file.
18 - The audio file SHOULD be in `audio/mp4` (.m4a) format using AAC or Opus encoding. Clients MAY support other common audio formats like `audio/ogg`, `audio/webm`, or `audio/mpeg` (mp3), but `audio/mp4` is recommended for broad compatibility and efficiency.
19 - The audio duration SHOULD be no longer than 60 seconds. Clients publishing `kind: 1222` events SHOULD enforce this limit or provide a clear warning to the user if exceeded.
20- `tags`:
21 - Tags MAY be included as per other NIPs (e.g., `t` for hashtags, `g` for geohash, etc.).
22
23 The `kind: 1244` event is defined as follows:
24
25- To be used for replies, `kind: 1244` events MUST follow the structure of `NIP-22`.
26- `content`: MUST be a URL pointing directly to an audio file.
27 - The audio file SHOULD be in `audio/mp4` (.m4a) format using AAC or Opus encoding. Clients MAY support other common audio formats like `audio/ogg`, `audio/webm`, or `audio/mpeg` (mp3), but `audio/mp4` is recommended for broad compatibility and efficiency.
28 - The audio duration SHOULD be no longer than 60 seconds. Clients publishing `kind: 1222` events SHOULD enforce this limit or provide a clear warning to the user if exceeded.
29- `tags`:
30 - Tags MAY be included as per other NIPs (e.g., `t` for hashtags, `g` for geohash, etc.).
31
32
33## Visual representation with `imeta` (NIP-92) tag (optional)
34
35The following imeta (NIP-92) tags MAY be included so clients can render a visual preview without having to download the audio file first:
36
37- `waveform`: amplitude values over time, space separated full integers, less than 100 values should be enough to render a nice visual
38- `duration`: audio length in seconds
39
40## Examples
41
42### Root Voice Message Example
43
44```json
45{
46 "content": "https://blossom.primal.net/5fe7df0e46ee6b14b5a8b8b92939e84e3ca5e3950eb630299742325d5ed9891b.mp4",
47 "created_at": 1752501052,
48 "id": "...",
49 "kind": 1222,
50 "pubkey": "...",
51 "sig": "...",
52 "tags": [
53 [
54 "imeta",
55 "url https://blossom.primal.net/5fe7df0e46ee6b14b5a8b8b92939e84e3ca5e3950eb630299742325d5ed9891b.mp4",
56 "waveform 0 7 35 8 100 100 49 8 4 16 8 10 7 2 20 10 100 100 100 100 100 100 15 100 100 100 25 60 5 4 3 1 0 100 100 15 100 29 88 0 33 11 39 100 100 19 4 100 42 35 5 0 1 5 0 0 11 38 100 94 17 11 44 58 5 100 100 100 55 14 72 100 100 57 6 1 14 2 16 100 100 40 16 100 100 6 32 14 13 41 36 16 14 6 3 0 1 2 1 6 0",
57 "duration 8"
58 ]
59 ]
60} \ No newline at end of file
diff --git a/A4.md b/A4.md
new file mode 100644
index 0000000..36751c1
--- /dev/null
+++ b/A4.md
@@ -0,0 +1,56 @@
1NIP-A4
2======
3
4Public Messages
5---------------
6
7`draft` `optional`
8
9This NIP defines kind `24` as a simple plaintext message to one or more Nostr users.
10
11The `.content` contains the message. `p` tags identify one or more receivers.
12
13```jsonc
14{
15 "pubkey": "<sender-pubkey>",
16 "kind": 24,
17 "tags": [
18 ["p", "<receiver>", "<relay-url>"],
19 ],
20 "content": "<message-in-plain-text>",
21}
22```
23
24Messages MUST be sent to the [NIP-65](65.md) inbox relays of each receiver and the outbox relay of the sender.
25
26Kind `24` is designed to be shown and replied to from notification screens. The goal is to allow clients to
27support this feature without having to worry about chat history. There are no message chains. The concept of a
28"thread", a "thread root", or a "chatroom" does not exist in this system, as messages can start and continue
29without any syntactic connection to each other. `e` tags must not be used.
30
31This kind is not designed to be displayed on feeds, but anyone can see and reply to messages that may not be for them.
32
33## Advanced Support
34
35[NIP-40](40.md) `expiration` tags are recommended. Since there is no concept of a chatroom, it is unlikely that these messages will
36make sense as time goes on.
37
38[NIP-18](18.md) quote repost `q` tags MAY be used when citing events in the `.content` with [NIP-21](21.md).
39
40```json
41["q", "<event-id> or <event-address>", "<relay-url>", "<pubkey-if-a-regular-event>"]
42```
43
44[NIP-25](25.md) reactions MUST add a `k` tag to `24`.
45
46[NIP-57](57.md) zaps MUST include the `k` tag to `24`
47
48[NIP-21](21.md) links that use [NIP-19](19.md)'s `nevent1` MUST include a `kind` of `24`. Links that are not `kind:24` are not expected to be rendered natively by the client.
49
50[NIP-92](92.md) `imeta` tags SHOULD be added for image and video links.
51
52## Warnings
53
54There MUST be no expectation of privacy in this kind. It is just a public reply, but without a root note.
55
56Avoid confusing this kind with Kind `14` rumors in [NIP-17](17.md) DMs. This kind is signed and designed for public consumption.
diff --git a/B0.md b/B0.md
index 0dcefa7..22ed910 100644
--- a/B0.md
+++ b/B0.md
@@ -6,31 +6,23 @@ Web Bookmarking
6 6
7`draft` `optional` 7`draft` `optional`
8 8
9This NIP defines `kind:39701` (an _addressable event_) for a URI as a web bookmark which uses the HTTP (Hypertext transfer protocol) scheme. 9This NIP defines `kind:39701` for a URI as editable web bookmark which uses the HTTP scheme.
10These web bookmark events are _addressable_ and deletable per [NIP-09](09.md).
11
12### Editability
13
14Web bookmarks are meant to be editable, so they should include a `d` tag with an identifier for the bookmark. Clients should take care to only publish and read these events from relays that implement that. If they don't do that they should also take care to hide old versions of the same bookmark they may receive.
15 10
16### Format 11### Format
17 12
18The format uses an _addressable event_ of `kind:39701`. 13The format uses `kind:39701`.
19 14
20The `.content` of these events should be a detailed description of the web bookmark. It is required but can be an empty string. 15The `.content` should be a detailed description of the web bookmark. It can be an empty string.
21 16
22The `d` tag is required. 17The `d` tag is just their URL without the scheme, which is always and everywhere assumed to be `https://` or `http://`.
23 18
24In this way web bookmarks events can be queried by the `d` tag by clients, which is just their URL without the scheme, which is always and everywhere assumed to be `https://` or `http://`. 19In this way web bookmarks events can be queried by the `d` tag by clients.
25
26The querystring and the hash must be removed entirely, unless their requirement is explicitly stated either by the user or by some hardcoded list of URLs that rely on querystrings for basic routing provided by the client.
27 20
28### Metadata 21### Metadata
29 22
30For the date of the last update the `.created_at` field should be used. For "tags"/"hashtags" (i.e. topics about which the event might be of relevance) the `t` tag should be used. 23Metadata fields can be added as tags to the event as necessary.
31
32Other metadata fields can be added as tags to the event as necessary.
33 24
25* `"t"`, for "tags"/"hashtags" (i.e. topics about which the event might be of relevance)
34* `"published_at"`, for the timestamp in unix seconds (stringified) of the first time the bookmark was published 26* `"published_at"`, for the timestamp in unix seconds (stringified) of the first time the bookmark was published
35* `"title"`, title about bookmark and can be used as a attribute for the HTML link element 27* `"title"`, title about bookmark and can be used as a attribute for the HTML link element
36 28
diff --git a/BE.md b/BE.md
new file mode 100644
index 0000000..50062d9
--- /dev/null
+++ b/BE.md
@@ -0,0 +1,137 @@
1NIP-BE
2======
3
4Nostr BLE Communications Protocol
5---------------------------------
6
7`draft` `optional`
8
9This NIP specifies how Nostr apps can use BLE to communicate and synchronize with each other. The BLE protocol follows a client-server pattern, so this NIP emulates the WS structure in a similar way, but with some adaptations to its limitations.
10
11## Device advertisement
12A device advertises itself with:
13- Service UUID: `0000180f-0000-1000-8000-00805f9b34fb`
14- Data: Device UUID in ByteArray format
15
16## GATT service
17The device exposes a Nordic UART Service with the following characteristics:
18
191. Write Characteristic
20 - UUID: `87654321-0000-1000-8000-00805f9b34fb`
21 - Properties: Write
22
232. Read Characteristic
24 - UUID: `12345678-0000-1000-8000-00805f9b34fb`
25 - Properties: Notify, Read
26
27## Role assignment
28
29When one device initially finds another advertising the service, it will read the service's data to get the device UUID and compare it with its own advertised device UUID. For this communication, the device with the highest ID will take the role of GATT Server (Relay), the other will be considered the GATT Client (Client) and will proceed to establish the connection.
30
31For devices whose purpose will require a single role, its device UUID will always be:
32
33- GATT Server: `FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF`
34- GATT Client: `00000000-0000-0000-0000-000000000000`
35
36## Messages
37
38All messages will follow [NIP-01](/01.md) message structure. For a given message, a compression stream (DEFLATE) is applied to the message to generate a byte array. Depending on the BLE version, the byte array can be too large for a single message (20-23 bytes in BLE 4.2, 256 bytes in BLE > 4.2). In that case, this byte array is split into any number of batches following the structure:
39
40```
41[batch index (first 2 bytes)][batch n][is last batch (last byte)]
42```
43After reception of all batches, the other device can then join them and decompress. To ensure reliability, only 1 message will be read/written at a time. MTU can be negotiated in advance. The maximum size for a message is 64KB; bigger messages will be rejected.
44
45## Examples
46
47This example implements a function to split and compress a byte array into chunks, as well as another function to join and decompress them in order to obtain the initial result:
48
49```kotlin
50fun splitInChunks(message: ByteArray): Array<ByteArray> {
51 val chunkSize = 500 // define the chunk size
52 var byteArray = compressByteArray(message)
53 val numChunks = (byteArray.size + chunkSize - 1) / chunkSize // calculate the number of chunks
54 var chunkIndex = 0
55 val chunks = Array(numChunks) { ByteArray(0) }
56
57 for (i in 0 until numChunks) {
58 val start = i * chunkSize
59 val end = minOf((i + 1) * chunkSize, byteArray.size)
60 val chunk = byteArray.copyOfRange(start, end)
61
62 // add chunk index to the first 2 bytes and last chunk flag to the last byte
63 val chunkWithIndex = ByteArray(chunk.size + 2)
64 chunkWithIndex[0] = chunkIndex.toByte() // chunk index
65 chunk.copyInto(chunkWithIndex, 1)
66 chunkWithIndex[chunkWithIndex.size - 1] = numChunks.toByte()
67
68 // store the chunk in the array
69 chunks[i] = chunkWithIndex
70
71 chunkIndex++
72 }
73
74 return chunks
75}
76
77fun joinChunks(chunks: Array<ByteArray>): ByteArray {
78 val sortedChunks = chunks.sortedBy { it[0] }
79 var reassembledByteArray = ByteArray(0)
80 for (chunk in sortedChunks) {
81 val chunkData = chunk.copyOfRange(1, chunk.size - 1)
82 reassembledByteArray = reassembledByteArray.copyOf(reassembledByteArray.size + chunkData.size)
83 chunkData.copyInto(reassembledByteArray, reassembledByteArray.size - chunkData.size)
84 }
85
86 return decompressByteArray(reassembledByteArray)
87}
88
89```
90
91## Workflows
92
93### Client to relay
94
95- Any message the client wants to send to a relay will be a write message.
96- Any message the client receives from a relay will be a read message.
97
98### Relay to client
99
100The relay should notify the client about any new event matching subscription's filters by using the Notify action of the Read Characteristic. After that, the client can proceed to read messages from the relay.
101
102### Device synchronization
103
104Given the nature of BLE, it is expected that the direct connection between two devices might be extremely intermittent, with gaps of hours or even days. That's why it's crucial to define a synchronization process by following [NIP-77](./77.md) but with an adaptation to the limitations of the technology.
105
106After two devices have successfully connected and established the Client-Server roles, the devices will use half-duplex communication to intermittently send and receive messages.
107
108#### Half-duplex synchronization
109
110Right after the 2 devices connect, the Client starts the workflow by sending the first message.
111
1121. Client - Writes ["NEG-OPEN"](/77.md#initial-message-client-to-relay) message.
1132. Server - Sends `write-success`.
1143. Client - Sends `read-message`.
1154. Server - Responds with ["NEG-MSG"](./77.md#subsequent-messages-bidirectional) message.
1165. Client -
117 1. If the Client has messages missing on the Server, it writes one `EVENT`.
118 2. If the Client doesn't have any messages missing on the Server, it writes `EOSE`. In this case, subsequent messages to the Server will be empty while the Server claims to have more notes for the Client.
1196. Server - Sends `write-success`.
1207. Client - Sends `read-message`.
1218. Server -
122 1. If the Server has messages missing on the Client, it responds with one `EVENT`.
123 2. If the Client doesn't have any messages missing on the Server, it responds with `EOSE`. In this case, subsequent responses to the Client will be empty.
1249. If the Client detects that the devices are not synchronized yet, jump to step 5.
12510. After the two devices detect that there are no more missing events on both ends, the workflow will pause at this point.
126
127#### Half-duplex event spread
128
129While two devices are connected and synchronized, it might happen that one of them receives a new message from another connected peer. Devices MUST keep track of which notes have been sent to its peers while they are connected. If the newly received event is detected as missing in one of the connected and synchronized peers:
130
1311. If the peer is a Server:
132 1. Client - It writes the `EVENT`.
133 2. Server - Sends `write-success`.
1342. If the peer is a Client:
135 1. Server - It will send an empty notification to the Client.
136 2. Client - Sends `read-message`.
137 3. Server - Responds with the `EVENT`.
diff --git a/BREAKING.md b/BREAKING.md
deleted file mode 100644
index c620a3f..0000000
--- a/BREAKING.md
+++ /dev/null
@@ -1,68 +0,0 @@
1# Breaking Changes
2
3This is a history of NIP changes that potentially break pre-existing implementations, in
4reverse chronological order.
5
6| Date | Commit | NIP | Change |
7| ----------- | --------- | -------- | ------ |
8| 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 |
9| 2025-02-07 | [0023ca81](https://github.com/nostr-protocol/nips/commit/0023ca81) | [10](10.md) | `"mention"` marker was removed |
10| 2025-01-31 | [6a4b125a](https://github.com/nostr-protocol/nips/commit/6a4b125a) | [71](71.md) | video events were changed to regular |
11| 2024-12-05 | [6d16019e](https://github.com/nostr-protocol/nips/commit/6d16019e) | [46](46.md) | message encryption was changed to NIP-44 |
12| 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) |
13| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | NIP-05 login was removed |
14| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | `create_account` method was removed |
15| 2024-11-12 | [926a51e7](https://github.com/nostr-protocol/nips/commit/926a51e7) | [46](46.md) | `connect` params and result were changed |
16| 2024-10-29 | [f1e8d2c4](https://github.com/nostr-protocol/nips/commit/f1e8d2c4) | [46](46.md) | bunker URL should use `remote-signer-key` |
17| 2024-10-15 | [1cda2dcc](https://github.com/nostr-protocol/nips/commit/1cda2dcc) | [71](71.md) | some tags were replaced with `imeta` tag |
18| 2024-10-15 | [1cda2dcc](https://github.com/nostr-protocol/nips/commit/1cda2dcc) | [71](71.md) | `kind: 34237` was dropped |
19| 2024-10-07 | [7bb8997b](https://github.com/nostr-protocol/nips/commit/7bb8997b) | [55](55.md) | some fields and passing data were changed |
20| 2024-08-18 | [3aff37bd](https://github.com/nostr-protocol/nips/commit/3aff37bd) | [54](54.md) | content should be Asciidoc |
21| 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 |
22| 2024-07-30 | [444ad28d](https://github.com/nostr-protocol/nips/commit/444ad28d) | [45](45.md) | NIP-45 was deprecated |
23| 2024-07-26 | [ecee40df](https://github.com/nostr-protocol/nips/commit/ecee40df) | [19](19.md) | `nrelay` was deprecated |
24| 2024-07-23 | [0227a2cd](https://github.com/nostr-protocol/nips/commit/0227a2cd) | [01](01.md) | events should be sorted by id after created_at |
25| 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 |
26| 2024-06-06 | [a6dfc7b5](https://github.com/nostr-protocol/nips/commit/a6dfc7b5) | [55](55.md) | NIP number was changed |
27| 2024-05-25 | [5d1d1c17](https://github.com/nostr-protocol/nips/commit/5d1d1c17) | [71](71.md) | `aes-256-gcm` tag was removed |
28| 2024-05-07 | [8073c848](https://github.com/nostr-protocol/nips/commit/8073c848) | [25](25.md) | e-tags were changed to not include entire thread |
29| 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) |
30| 2024-02-25 | [4a171cb0](https://github.com/nostr-protocol/nips/commit/4a171cb0) | [18](18.md) | quote repost should use `q` tag |
31| 2024-02-21 | [c6cd655c](https://github.com/nostr-protocol/nips/commit/c6cd655c) | [46](46.md) | Params were stringified |
32| 2024-02-16 | [cbec02ab](https://github.com/nostr-protocol/nips/commit/cbec02ab) | [49](49.md) | Password first normalized to NFKC |
33| 2024-02-15 | [afbb8dd0](https://github.com/nostr-protocol/nips/commit/afbb8dd0) | [39](39.md) | PGP identity was removed |
34| 2024-02-07 | [d3dad114](https://github.com/nostr-protocol/nips/commit/d3dad114) | [46](46.md) | Connection token format was changed |
35| 2024-01-30 | [1a2b21b6](https://github.com/nostr-protocol/nips/commit/1a2b21b6) | [59](59.md) | `p` tag became optional |
36| 2023-01-27 | [c2f34817](https://github.com/nostr-protocol/nips/commit/c2f34817) | [47](47.md) | optional expiration tag should be honored |
37| 2024-01-10 | [3d8652ea](https://github.com/nostr-protocol/nips/commit/3d8652ea) | [02](02.md), [51](51.md) | list entries should be chronological |
38| 2023-12-30 | [29869821](https://github.com/nostr-protocol/nips/commit/29869821) | [52](52.md) | `name` tag was removed (use `title` tag instead) |
39| 2023-12-27 | [17c67ef5](https://github.com/nostr-protocol/nips/commit/17c67ef5) | [94](94.md) | `aes-256-gcm` tag was removed |
40| 2023-12-03 | [0ba45895](https://github.com/nostr-protocol/nips/commit/0ba45895) | [01](01.md) | WebSocket status code `4000` was replaced by `CLOSED` message |
41| 2023-11-28 | [6de35f9e](https://github.com/nostr-protocol/nips/commit/6de35f9e) | [89](89.md) | `client` tag value was changed |
42| 2023-11-20 | [7822a8b1](https://github.com/nostr-protocol/nips/commit/7822a8b1) | [51](51.md) | `kind: 30001` was deprecated |
43| 2023-11-20 | [7822a8b1](https://github.com/nostr-protocol/nips/commit/7822a8b1) | [51](51.md) | the meaning of `kind: 30000` was changed |
44| 2023-11-11 | [cbdca1e9](https://github.com/nostr-protocol/nips/commit/cbdca1e9) | [84](84.md) | `range` tag was removed |
45| 2023-11-10 | [c945d8bd](https://github.com/nostr-protocol/nips/commit/c945d8bd) | [32](32.md) | `l` tag annotations was removed |
46| 2023-11-07 | [108b7f16](https://github.com/nostr-protocol/nips/commit/108b7f16) | [01](01.md) | `OK` message must have 4 items |
47| 2023-10-17 | [cf672b76](https://github.com/nostr-protocol/nips/commit/cf672b76) | [03](03.md) | `block` tag was removed |
48| 2023-09-29 | [7dc6385f](https://github.com/nostr-protocol/nips/commit/7dc6385f) | [57](57.md) | optional `a` tag was included in `zap receipt` |
49| 2023-08-21 | [89915e02](https://github.com/nostr-protocol/nips/commit/89915e02) | [11](11.md) | `min_prefix` was removed |
50| 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 |
51| 2023-08-15 | [88ee873c](https://github.com/nostr-protocol/nips/commit/88ee873c) | [15](15.md) | `countries` tag was renamed to `regions` |
52| 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 |
53| 2023-08-11 | [d87f8617](https://github.com/nostr-protocol/nips/commit/d87f8617) | [25](25.md) | empty `content` should be considered as "+" |
54| 2023-08-01 | [5d63b157](https://github.com/nostr-protocol/nips/commit/5d63b157) | [57](57.md) | `zap` tag was changed |
55| 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` |
56| 2023-07-12 | [a1cd2bd8](https://github.com/nostr-protocol/nips/commit/a1cd2bd8) | [25](25.md) | custom emoji was supported |
57| 2023-06-18 | [83cbd3e1](https://github.com/nostr-protocol/nips/commit/83cbd3e1) | [11](11.md) | `image` was renamed to `icon` |
58| 2023-04-13 | [bf0a0da6](https://github.com/nostr-protocol/nips/commit/bf0a0da6) | [15](15.md) | different NIP was re-added as NIP-15 |
59| 2023-04-09 | [fb5b7c73](https://github.com/nostr-protocol/nips/commit/fb5b7c73) | [15](15.md) | NIP-15 was merged into NIP-01 |
60| 2023-03-29 | [599e1313](https://github.com/nostr-protocol/nips/commit/599e1313) | [18](18.md) | NIP-18 was bring back |
61| 2023-03-15 | [e1004d3d](https://github.com/nostr-protocol/nips/commit/e1004d3d) | [19](19.md) | `1: relay` was changed to optionally |
62
63Breaking changes prior to 2023-03-01 are not yet documented.
64
65## NOTES
66
67- If it isn't clear that a change is breaking or not, we list it.
68- The date is the date it was merged, not necessarily the date of the commit.
diff --git a/C0.md b/C0.md
index c88e145..de78fe8 100644
--- a/C0.md
+++ b/C0.md
@@ -23,9 +23,9 @@ The `.content` field contains the actual code snippet text.
23- `extension` - File extension (without the dot). Examples: "js", "py", "rs" 23- `extension` - File extension (without the dot). Examples: "js", "py", "rs"
24- `description` - Brief description of what the code does 24- `description` - Brief description of what the code does
25- `runtime` - Runtime or environment specification (e.g., "node v18.15.0", "python 3.11") 25- `runtime` - Runtime or environment specification (e.g., "node v18.15.0", "python 3.11")
26- `license` - License under which the code is shared (e.g., "MIT", "GPL-3.0", "Apache-2.0") 26- `license` - License under which the code (along with any related data contained within the event, when available, such as the description) is shared. This MUST be a standard [SPDX](https://spdx.org/licenses/) short identifier (e.g., "MIT", "GPL-3.0-or-later", "Apache-2.0") when available. An additional parameter containing a reference to the actual text of the license MAY be provided. This tag can be repeated, to indicate multi-licensing, allowing recipients to use the code under any license of choosing among the referenced ones
27- `dep` - Dependency required for the code to run (can be repeated) 27- `dep` - Dependency required for the code to run (can be repeated)
28- `repo` - Reference to a repository where this code originates 28- `repo` - Reference to a repository where this code originates. This MUST be either a standard URL or, alternatively, the address of a [NIP-34](34.md) Git repository announcement event in the form `"30617:<32-bytes hex a pubkey>:<d tag value>"`. If a repository announcement is referenced, a recommended relay URL where to find the event should be provided as an additional parameter
29 29
30## Format 30## Format
31 31
diff --git a/EE.md b/EE.md
new file mode 100644
index 0000000..dacb152
--- /dev/null
+++ b/EE.md
@@ -0,0 +1,292 @@
1> __Warning__ `unrecommended`: superseded by the [Marmot Protocol](https://github.com/marmot-protocol/marmot)
2
3NIP-EE
4======
5
6E2EE Messaging using the Messaging Layer Security (MLS) Protocol
7----------------------------------------------------------------
8
9`final` `unrecommended` `optional`
10
11This 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.
12
13## Context
14
15Originally, 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).
16
17With 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.
18
19Most 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.
20
21In addition, neither [NIP-04](04.md) or [NIP-17](17.md) attempt to solve the problem of group messages.
22
23### Why is this important?
24
25Without 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.
26
27### Goals of this NIP
28
291. Private _and_ Confidential DMs and Group messages
30 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.
31 2. **Confidential** means that the contents of conversations can only be viewed by the intended recipients.
322. Forward secrecy and Post-compromise security
33 1. **Forward secrecy** means that encrypted content in the past remains encrypted even if a key material is leaked.
34 2. **Post compromise security** means that leaking key material doesn't allow an attacker to continue to read messages indefinitely into the future.
353. Scales efficiently for large groups
364. Allows for the use of multiple device/clients in a single conversation/group.
37
38### Why MLS?
39
40This 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.
41
42It'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.
43
44## Core MLS Concepts
45
46From the [MLS Architectural Overview](https://www.ietf.org/archive/id/draft-ietf-mls-architecture-13.html):
47
48> 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.
49>
50> 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.
51
52The 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.
53
54### Groups
55
56Groups 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.
57
58### Clients
59
60The 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.
61
62### Messages
63
64There 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.
65
66Messages 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.
67
68MLS 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.
69
70MLS 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.
71
72## The focus of this NIP
73
74This 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.
75
76This NIP specifies the following:
77
781. A standardized way that Nostr clients should [create MLS groups](#creating-groups).
792. The required format of the MLS [`Credential`](#mls-credentials) that Nostr clients should use to represent a Nostr user in a group.
803. The structure of [KeyPackage Events](#keypackage-event-and-signing-keys) published to relays that allow Nostr users to be added to a group asynchronously.
814. 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.
82
83## Security Considerations
84
85This 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.
86
87### Forward Secrecy and Post-compromise Security
88
89- 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.
90- 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).
91
92### Leakage of various keys
93
94- 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.
95- 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).
96
97### Metadata
98
99- 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.
100- 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.
101
102### Device Compromise
103
104Clients 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:
105
106- Clients should support and encourage self-destructing messages (ensuring that full transcript history isn't available on a device forever).
107- Clients should regularly suggest to group admins that inactive users be removed.
108- Clients should regularly suggest (or automatically) rotate a user's signing key in each of their groups.
109- 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.
110- Clients should use secure enclave storage where possible.
111
112For 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).
113
114## Creating groups
115
116MLS 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.
117
118Clients 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.
119
120When creating a new group, the following MLS extensions MUST be used.
121
122- [`required_capabilities`](https://docs.rs/openmls/latest/openmls/extensions/struct.RequiredCapabilitiesExtension.html)
123- [`ratchet_tree`](https://docs.rs/openmls/latest/openmls/extensions/struct.RatchetTreeExtension.html)
124- [`nostr_group_data`](https://github.com/rust-nostr/nostr/blob/master/mls/nostr-mls/src/extension.rs)
125
126And the following MLS extension is highly recommended (more [here](#keypackage-event-and-signing-keys)):
127- [`last_resort`](https://docs.rs/openmls/latest/openmls/extensions/struct.LastResortExtension.html)
128
129Changes 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.
130
131## MLS Credentials
132
133A `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.
134
135A `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.
136
137## Nostr Group Data Extension
138
139As 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.
140
141The extension stores the following data about the group:
142
143- `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.
144- `name`: The name of the group.
145- `description`: A short description of the group.
146- `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).
147- `relays`: An array of the Nostr relay URLs that the group uses to publish and receive messages.
148
149All of these values can be updated over time using MLS `Proposal` and `Commit` events (by group admins).
150
151## KeyPackage Event and Signing Keys
152
153Each 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.
154
155KeyPackage 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).
156
157It'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.
158
159In most cases, it's assumed that clients implementing this NIP will manage the creation and rotation of KeyPackage Events.
160
161### Example KeyPackage Event
162
163```json
164 {
165 "id": <id>,
166 "kind": 443,
167 "created_at": <unix timestamp in seconds>,
168 "pubkey": <main identity pubkey>,
169 "content": "",
170 "tags": [
171 ["mls_protocol_version", "1.0"],
172 ["ciphersuite", <MLS CipherSuite ID value e.g. "0x0001">],
173 ["extensions", <An array of MLS Extension ID values e.g. "0x0001, 0x0002">],
174 ["client", <client name>, <handler event id>, <optional relay url>],
175 ["relays", <array of relay urls>],
176 ["-"]
177 ],
178 "sig": <signed with main identity key>
179}
180```
181
182- The `content` hex encoded serialized `KeyPackageBundle` from MLS.
183- 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`.
184- 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).
185- 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).
186- (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.
187- 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.
188- (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)
189
190### Deleting KeyPackage Events
191
192Clients 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.
193
194If 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.
195
196### Rotating Signing Keys
197
198Clients 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).
199
200### KeyPackage Relays List Event
201
202A `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.
203
204```json
205{
206 "kind": 10051,
207 "tags": [
208 ["relay", "wss://inbox.nostr.wine"],
209 ["relay", "wss://myrelay.nostr1.com"],
210 ],
211 "content": "",
212 //...other fields
213}
214```
215
216### Welcome Event
217
218When 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.
219
220Clients 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.
221
222```json
223{
224 "id": <id>,
225 "kind": 444,
226 "created_at": <unix timestamp in seconds>,
227 "pubkey": <nostr identity pubkey of sender>,
228 "content": <serialized Welcome object>,
229 "tags": [
230 ["e", <ID of the KeyPackage Event used to add the user to the group>],
231 ["relays", <array of relay urls>],
232 ],
233 "sig": <NOT SIGNED>
234}
235```
236
237- The `content` field is required and is a serialized MLSMessage object containing the MLS `Welcome` object.
238- The `e` tag is required and is the ID of the KeyPackage Event used to add the user to the group.
239- The `relays` tag is required and is a list of relays clients should query for Group Events.
240
241Welcome 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.
242
243#### Large Groups
244
245For 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.
246
247## Group Events
248
249Group 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).
250
251Group 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.
252
253```json
254{
255 "id": <id>,
256 "kind": 445,
257 "created_at": <unix timestamp in seconds>,
258 "pubkey": <ephemeral sender pubkey>,
259 "content": <NIP-44 encrypted serialized MLSMessage object>,
260 "tags": [
261 ["h", <group id>]
262 ],
263 "sig": <signed with ephemeral sender key>
264}
265```
266- 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.
267- 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.
268- The `pubkey` is the hex-encoded public key of the ephemeral sender.
269- The `h` tag is the nostr group ID value (from the Nostr Group Data Extension).
270
271### Application Messages
272
273Application 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.
274
275This 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.
276
277These 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.
278
279These 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.
280
281### `Commit` Message race conditions
282
283The 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.
284
285Clients 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.
286
287If 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:
288
2891. 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.
2902. 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.
291
292Clients SHOULD retain previous group state for a short period of time in order to recover from forked group state.
diff --git a/README.md b/README.md
index b665f70..e533ff4 100644
--- a/README.md
+++ b/README.md
@@ -11,11 +11,10 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
11- [Message Types](#message-types) 11- [Message Types](#message-types)
12 - [Client to Relay](#client-to-relay) 12 - [Client to Relay](#client-to-relay)
13 - [Relay to Client](#relay-to-client) 13 - [Relay to Client](#relay-to-client)
14- [Standardized Tags](#standardized-tags) 14- [Common Tags](#common-tags)
15- [Criteria for acceptance of NIPs](#criteria-for-acceptance-of-nips) 15- [Criteria for acceptance of NIPs](#criteria-for-acceptance-of-nips)
16- [Is this repository a centralizing factor?](#is-this-repository-a-centralizing-factor) 16- [Is this repository a centralizing factor?](#is-this-repository-a-centralizing-factor)
17- [How this repository works](#how-this-repository-works) 17- [How this repository works](#how-this-repository-works)
18- [Breaking Changes](#breaking-changes)
19- [License](#license) 18- [License](#license)
20 19
21--- 20---
@@ -44,7 +43,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
44- [NIP-23: Long-form Content](23.md) 43- [NIP-23: Long-form Content](23.md)
45- [NIP-24: Extra metadata fields and tags](24.md) 44- [NIP-24: Extra metadata fields and tags](24.md)
46- [NIP-25: Reactions](25.md) 45- [NIP-25: Reactions](25.md)
47- [NIP-26: Delegated Event Signing](26.md) --- **unrecommended**: adds unecessary burden for little gain 46- [NIP-26: Delegated Event Signing](26.md) --- **unrecommended**: adds unnecessary burden for little gain
48- [NIP-27: Text Note References](27.md) 47- [NIP-27: Text Note References](27.md)
49- [NIP-28: Public Chat](28.md) 48- [NIP-28: Public Chat](28.md)
50- [NIP-29: Relay-based Groups](29.md) 49- [NIP-29: Relay-based Groups](29.md)
@@ -59,6 +58,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
59- [NIP-39: External Identities in Profiles](39.md) 58- [NIP-39: External Identities in Profiles](39.md)
60- [NIP-40: Expiration Timestamp](40.md) 59- [NIP-40: Expiration Timestamp](40.md)
61- [NIP-42: Authentication of clients to relays](42.md) 60- [NIP-42: Authentication of clients to relays](42.md)
61- [NIP-43: Relay Access Metadata and Requests](43.md)
62- [NIP-44: Encrypted Payloads (Versioned)](44.md) 62- [NIP-44: Encrypted Payloads (Versioned)](44.md)
63- [NIP-45: Counting results](45.md) 63- [NIP-45: Counting results](45.md)
64- [NIP-46: Nostr Remote Signing](46.md) 64- [NIP-46: Nostr Remote Signing](46.md)
@@ -88,25 +88,31 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
88- [NIP-72: Moderated Communities](72.md) 88- [NIP-72: Moderated Communities](72.md)
89- [NIP-73: External Content IDs](73.md) 89- [NIP-73: External Content IDs](73.md)
90- [NIP-75: Zap Goals](75.md) 90- [NIP-75: Zap Goals](75.md)
91- [NIP-77: Negentropy Syncing](77.md)
91- [NIP-78: Application-specific data](78.md) 92- [NIP-78: Application-specific data](78.md)
92- [NIP-7D: Threads](7D.md) 93- [NIP-7D: Threads](7D.md)
93- [NIP-84: Highlights](84.md) 94- [NIP-84: Highlights](84.md)
95- [NIP-85: Trusted Assertions](85.md)
94- [NIP-86: Relay Management API](86.md) 96- [NIP-86: Relay Management API](86.md)
97- [NIP-87: Ecash Mint Discoverability](87.md)
95- [NIP-88: Polls](88.md) 98- [NIP-88: Polls](88.md)
96- [NIP-89: Recommended Application Handlers](89.md) 99- [NIP-89: Recommended Application Handlers](89.md)
97- [NIP-90: Data Vending Machines](90.md) 100- [NIP-90: Data Vending Machines](90.md)
98- [NIP-92: Media Attachments](92.md) 101- [NIP-92: Media Attachments](92.md)
99- [NIP-94: File Metadata](94.md) 102- [NIP-94: File Metadata](94.md)
100- [NIP-96: HTTP File Storage Integration](96.md) 103- [NIP-96: HTTP File Storage Integration](96.md) --- **unrecommended**: replaced by blossom APIs
101- [NIP-98: HTTP Auth](98.md) 104- [NIP-98: HTTP Auth](98.md)
102- [NIP-99: Classified Listings](99.md) 105- [NIP-99: Classified Listings](99.md)
106- [NIP-A0: Voice Messages](A0.md)
107- [NIP-A4: Public Messages](A4.md)
103- [NIP-B0: Web Bookmarks](B0.md) 108- [NIP-B0: Web Bookmarks](B0.md)
104- [NIP-B7: Blossom](B7.md) 109- [NIP-B7: Blossom](B7.md)
110- [NIP-BE: Nostr BLE Communications Protocol](BE.md)
105- [NIP-C0: Code Snippets](C0.md) 111- [NIP-C0: Code Snippets](C0.md)
106- [NIP-C7: Chats](C7.md) 112- [NIP-C7: Chats](C7.md)
113- [NIP-EE: E2EE Messaging using MLS Protocol](EE.md) --- **unrecommended**: superseded by the [Marmot Protocol](https://github.com/marmot-protocol/marmot)
107 114
108## Event Kinds 115## Event Kinds
109
110| kind | description | NIP | 116| kind | description | NIP |
111| ------------- | ------------------------------- | -------------------------------------- | 117| ------------- | ------------------------------- | -------------------------------------- |
112| `0` | User Metadata | [01](01.md) | 118| `0` | User Metadata | [01](01.md) |
@@ -130,10 +136,11 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
130| `20` | Picture | [68](68.md) | 136| `20` | Picture | [68](68.md) |
131| `21` | Video Event | [71](71.md) | 137| `21` | Video Event | [71](71.md) |
132| `22` | Short-form Portrait Video Event | [71](71.md) | 138| `22` | Short-form Portrait Video Event | [71](71.md) |
139| `24` | Public Message | [A4](A4.md) |
133| `30` | internal reference | [NKBIP-03] | 140| `30` | internal reference | [NKBIP-03] |
134| `31` | external web reference | [NKBIP-03] | 141| `31` | external web reference | [NKBIP-03] |
135| `32` | hardcopy reference | [NKBIP-03] | 142| `32` | hardcopy reference | [NKBIP-03] |
136| `33` | prompt reference | [NKBIP-03] | 143| `33` | prompt reference | [NKBIP-03] |
137| `40` | Channel Creation | [28](28.md) | 144| `40` | Channel Creation | [28](28.md) |
138| `41` | Channel Metadata | [28](28.md) | 145| `41` | Channel Metadata | [28](28.md) |
139| `42` | Channel Message | [28](28.md) | 146| `42` | Channel Message | [28](28.md) |
@@ -141,6 +148,9 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
141| `44` | Channel Mute User | [28](28.md) | 148| `44` | Channel Mute User | [28](28.md) |
142| `62` | Request to Vanish | [62](62.md) | 149| `62` | Request to Vanish | [62](62.md) |
143| `64` | Chess (PGN) | [64](64.md) | 150| `64` | Chess (PGN) | [64](64.md) |
151| `443` | KeyPackage | [Marmot](marmot) |
152| `444` | Welcome Message | [Marmot](marmot) |
153| `445` | Group Event | [Marmot](marmot) |
144| `818` | Merge Requests | [54](54.md) | 154| `818` | Merge Requests | [54](54.md) |
145| `1018` | Poll Response | [88](88.md) | 155| `1018` | Poll Response | [88](88.md) |
146| `1021` | Bid | [15](15.md) | 156| `1021` | Bid | [15](15.md) |
@@ -150,9 +160,13 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
150| `1063` | File Metadata | [94](94.md) | 160| `1063` | File Metadata | [94](94.md) |
151| `1068` | Poll | [88](88.md) | 161| `1068` | Poll | [88](88.md) |
152| `1111` | Comment | [22](22.md) | 162| `1111` | Comment | [22](22.md) |
163| `1222` | Voice Message | [A0](A0.md) |
164| `1244` | Voice Message Comment | [A0](A0.md) |
153| `1311` | Live Chat Message | [53](53.md) | 165| `1311` | Live Chat Message | [53](53.md) |
154| `1337` | Code Snippet | [C0](C0.md) | 166| `1337` | Code Snippet | [C0](C0.md) |
155| `1617` | Patches | [34](34.md) | 167| `1617` | Patches | [34](34.md) |
168| `1618` | Pull Requests | [34](34.md) |
169| `1619` | Pull Request Updates | [34](34.md) |
156| `1621` | Issues | [34](34.md) | 170| `1621` | Issues | [34](34.md) |
157| `1622` | Git Replies (deprecated) | [34](34.md) | 171| `1622` | Git Replies (deprecated) | [34](34.md) |
158| `1630`-`1633` | Status | [34](34.md) | 172| `1630`-`1633` | Status | [34](34.md) |
@@ -171,6 +185,10 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
171| `7374` | Reserved Cashu Wallet Tokens | [60](60.md) | 185| `7374` | Reserved Cashu Wallet Tokens | [60](60.md) |
172| `7375` | Cashu Wallet Tokens | [60](60.md) | 186| `7375` | Cashu Wallet Tokens | [60](60.md) |
173| `7376` | Cashu Wallet History | [60](60.md) | 187| `7376` | Cashu Wallet History | [60](60.md) |
188| `7516` | Geocache log | [geocaching][geocaching] |
189| `7517` | Geocache proof of find | [geocaching][geocaching] |
190| `8000` | Add User | [43](43.md) |
191| `8001` | Remove User | [43](43.md) |
174| `9000`-`9030` | Group Control Events | [29](29.md) | 192| `9000`-`9030` | Group Control Events | [29](29.md) |
175| `9041` | Zap Goal | [75](75.md) | 193| `9041` | Zap Goal | [75](75.md) |
176| `9321` | Nutzap | [61](61.md) | 194| `9321` | Nutzap | [61](61.md) |
@@ -187,15 +205,24 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
187| `10006` | Blocked relays list | [51](51.md) | 205| `10006` | Blocked relays list | [51](51.md) |
188| `10007` | Search relays list | [51](51.md) | 206| `10007` | Search relays list | [51](51.md) |
189| `10009` | User groups | [51](51.md), [29](29.md) | 207| `10009` | User groups | [51](51.md), [29](29.md) |
208| `10011` | External Identities | [39](39.md) |
209| `10012` | Favorite relays list | [51](51.md) |
190| `10013` | Private event relay list | [37](37.md) | 210| `10013` | Private event relay list | [37](37.md) |
191| `10015` | Interests list | [51](51.md) | 211| `10015` | Interests list | [51](51.md) |
192| `10019` | Nutzap Mint Recommendation | [61](61.md) | 212| `10019` | Nutzap Mint Recommendation | [61](61.md) |
213| `10020` | Media follows | [51](51.md) |
193| `10030` | User emoji list | [51](51.md) | 214| `10030` | User emoji list | [51](51.md) |
194| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) | 215| `10050` | Relay list to receive DMs | [51](51.md), [17](17.md) |
216| `10051` | KeyPackage Relays List | [Marmot](marmot) |
195| `10063` | User server list | [Blossom][blossom] | 217| `10063` | User server list | [Blossom][blossom] |
196| `10096` | File storage server list | [96](96.md) | 218| `10096` | File storage server list | [96](96.md) (deprecated) |
197| `10166` | Relay Monitor Announcement | [66](66.md) | 219| `10166` | Relay Monitor Announcement | [66](66.md) |
220| `10312` | Room Presence | [53](53.md) |
221| `10377` | Proxy Announcement | [Nostr Epoxy][nostr-epoxy] |
222| `11111` | Transport Method Announcement | [Nostr Epoxy][nostr-epoxy] |
198| `13194` | Wallet Info | [47](47.md) | 223| `13194` | Wallet Info | [47](47.md) |
224| `13534` | Membership Lists | [43](43.md) |
225| `14388` | User Sound Effect Lists | [Corny Chat][cornychat-usersoundlist] |
199| `17375` | Cashu Wallet Event | [60](60.md) | 226| `17375` | Cashu Wallet Event | [60](60.md) |
200| `21000` | Lightning Pub RPC | [Lightning.Pub][lnpub] | 227| `21000` | Lightning Pub RPC | [Lightning.Pub][lnpub] |
201| `22242` | Client Authentication | [42](42.md) | 228| `22242` | Client Authentication | [42](42.md) |
@@ -204,12 +231,16 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
204| `24133` | Nostr Connect | [46](46.md) | 231| `24133` | Nostr Connect | [46](46.md) |
205| `24242` | Blobs stored on mediaservers | [Blossom][blossom] | 232| `24242` | Blobs stored on mediaservers | [Blossom][blossom] |
206| `27235` | HTTP Auth | [98](98.md) | 233| `27235` | HTTP Auth | [98](98.md) |
234| `28934` | Join Request | [43](43.md) |
235| `28935` | Invite Request | [43](43.md) |
236| `28936` | Leave Request | [43](43.md) |
207| `30000` | Follow sets | [51](51.md) | 237| `30000` | Follow sets | [51](51.md) |
208| `30001` | Generic lists | 51 (deprecated) | 238| `30001` | Generic lists | 51 (deprecated) |
209| `30002` | Relay sets | [51](51.md) | 239| `30002` | Relay sets | [51](51.md) |
210| `30003` | Bookmark sets | [51](51.md) | 240| `30003` | Bookmark sets | [51](51.md) |
211| `30004` | Curation sets | [51](51.md) | 241| `30004` | Curation sets | [51](51.md) |
212| `30005` | Video sets | [51](51.md) | 242| `30005` | Video sets | [51](51.md) |
243| `30006` | Picture sets | [51](51.md) |
213| `30007` | Kind mute sets | [51](51.md) | 244| `30007` | Kind mute sets | [51](51.md) |
214| `30008` | Profile Badges | [58](58.md) | 245| `30008` | Profile Badges | [58](58.md) |
215| `30009` | Badge Definition | [58](58.md) | 246| `30009` | Badge Definition | [58](58.md) |
@@ -228,7 +259,12 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
228| `30166` | Relay Discovery | [66](66.md) | 259| `30166` | Relay Discovery | [66](66.md) |
229| `30267` | App curation sets | [51](51.md) | 260| `30267` | App curation sets | [51](51.md) |
230| `30311` | Live Event | [53](53.md) | 261| `30311` | Live Event | [53](53.md) |
262| `30312` | Interactive Room | [53](53.md) |
263| `30313` | Conference Event | [53](53.md) |
231| `30315` | User Statuses | [38](38.md) | 264| `30315` | User Statuses | [38](38.md) |
265| `30382` | User Trusted Assertion | [85](85.md) |
266| `30383` | Event Trusted Assertion | [85](85.md) |
267| `30384` | Addressable Trusted Assertion | [85](85.md) |
232| `30388` | Slide Set | [Corny Chat][cornychat-slideset] | 268| `30388` | Slide Set | [Corny Chat][cornychat-slideset] |
233| `30402` | Classified Listing | [99](99.md) | 269| `30402` | Classified Listing | [99](99.md) |
234| `30403` | Draft Classified Listing | [99](99.md) | 270| `30403` | Draft Classified Listing | [99](99.md) |
@@ -244,24 +280,42 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
244| `31924` | Calendar | [52](52.md) | 280| `31924` | Calendar | [52](52.md) |
245| `31925` | Calendar Event RSVP | [52](52.md) | 281| `31925` | Calendar Event RSVP | [52](52.md) |
246| `31989` | Handler recommendation | [89](89.md) | 282| `31989` | Handler recommendation | [89](89.md) |
247| `31990` | Handler information | [89](89.md) | | 283| `31990` | Handler information | [89](89.md) |
248| `32267` | Software Application | | | 284| `32267` | Software Application | |
285| `32388` | User Room Favorites | [Corny Chat][cornychat-roomfavorites] |
286| `33388` | High Scores | [Corny Chat][cornychat-highscores] |
287| `34235` | Addressable Video Event | [71](71.md) |
288| `34236` | Addressable Short Video Event | [71](71.md) |
289| `34388` | Sound Effects | [Corny Chat][cornychat-soundeffects] |
249| `34550` | Community Definition | [72](72.md) | 290| `34550` | Community Definition | [72](72.md) |
291| `38172` | Cashu Mint Announcement | [87](87.md) |
292| `38173` | Fedimint Announcement | [87](87.md) |
293| `37516` | Geocache listing | [geocaching](geocaching) |
250| `38383` | Peer-to-peer Order events | [69](69.md) | 294| `38383` | Peer-to-peer Order events | [69](69.md) |
251| `39000-9` | Group metadata events | [29](29.md) | 295| `39000-9` | Group metadata events | [29](29.md) |
296| `39089` | Starter packs | [51](51.md) |
297| `39092` | Media starter packs | [51](51.md) |
252| `39701` | Web bookmarks | [B0](B0.md) | 298| `39701` | Web bookmarks | [B0](B0.md) |
253 299
254[NUD: Custom Feeds]: https://wikifreedia.xyz/cip-01/ 300[NUD: Custom Feeds]: https://wikifreedia.xyz/cip-01/
255[nostrocket]: https://github.com/nostrocket/NIPS/blob/main/Problems.md 301[nostrocket]: https://github.com/nostrocket/NIPS/blob/main/Problems.md
256[lnpub]: https://github.com/shocknet/Lightning.Pub/blob/master/proto/autogenerated/client.md 302[lnpub]: https://github.com/shocknet/Lightning.Pub/blob/master/proto/autogenerated/client.md
303[cornychat-usersoundlist]: https://cornychat.com/datatypes#kind14388usersoundeffectslist
257[cornychat-slideset]: https://cornychat.com/datatypes#kind30388slideset 304[cornychat-slideset]: https://cornychat.com/datatypes#kind30388slideset
258[cornychat-linkset]: https://cornychat.com/datatypes#kind31388linkset 305[cornychat-linkset]: https://cornychat.com/datatypes#kind31388linkset
306[cornychat-roomfavorites]: https://cornychat.com/datatypes#kind32388roomfavorites
307[cornychat-highscores]: https://cornychat.com/datatypes#kind33388highscores
308[cornychat-soundeffects]: https://cornychat.com/datatypes#kind34388soundeffectsets
259[joinstr]: https://gitlab.com/1440000bytes/joinstr/-/blob/main/NIP.md 309[joinstr]: https://gitlab.com/1440000bytes/joinstr/-/blob/main/NIP.md
260[NKBIP-01]: https://wikistr.com/nkbip-01*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1 310[NKBIP-01]: https://wikistr.com/nkbip-01*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1
261[NKBIP-02]: https://wikistr.com/nkbip-02*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1 311[NKBIP-02]: https://wikistr.com/nkbip-02*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1
262[NKBIP-03]: https://wikistr.com/nkbip-03*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1 312[NKBIP-03]: https://wikistr.com/nkbip-03*fd208ee8c8f283780a9552896e4823cc9dc6bfd442063889577106940fd927c1
263[blossom]: https://github.com/hzrd149/blossom 313[blossom]: https://github.com/hzrd149/blossom
264[Tidal-nostr]: https://wikistr.com/tidal-nostr 314[Tidal-nostr]: https://wikistr.com/tidal-nostr
315[geocaching]: https://nostrhub.io/naddr1qvzqqqrcvypzppscgyy746fhmrt0nq955z6xmf80pkvrat0yq0hpknqtd00z8z68qqgkwet0vdskx6rfdenj6etkv4h8guc6gs5y5
316[nostr-epoxy]: https://github.com/Origami74/nostr-epoxy-reverse-proxy
317[marmot]: https://github.com/marmot-protocol/marmot
318
265 319
266## Message types 320## Message types
267 321
@@ -287,13 +341,15 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
287| `AUTH` | used to send authentication challenges | [42](42.md) | 341| `AUTH` | used to send authentication challenges | [42](42.md) |
288| `COUNT` | used to send requested event counts to clients | [45](45.md) | 342| `COUNT` | used to send requested event counts to clients | [45](45.md) |
289 343
290## Standardized Tags 344## Common Tags
291 345
292| name | value | other parameters | NIP | 346| name | value | other parameters | NIP |
293| ----------------- | ------------------------------------ | ------------------------------- | -------------------------------------------------- | 347| ----------------- | ------------------------------------ | ------------------------------- | -------------------------------------------------- |
294| `a` | coordinates to an event | relay URL | [01](01.md) | 348| `a` | coordinates to an event | relay URL | [01](01.md) |
295| `A` | root address | relay URL | [22](22.md) | 349| `A` | root address | relay URL | [22](22.md) |
350| `c` | commit id | | [34](34.md) |
296| `d` | identifier | -- | [01](01.md) | 351| `d` | identifier | -- | [01](01.md) |
352| `D` | day | -- | [52](52.md) |
297| `e` | event id (hex) | relay URL, marker, pubkey (hex) | [01](01.md), [10](10.md) | 353| `e` | event id (hex) | relay URL, marker, pubkey (hex) | [01](01.md), [10](10.md) |
298| `E` | root event id | relay URL | [22](22.md) | 354| `E` | root event id | relay URL | [22](22.md) |
299| `f` | currency code | -- | [69](69.md) | 355| `f` | currency code | -- | [69](69.md) |
@@ -321,6 +377,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
321| `alt` | summary | -- | [31](31.md) | 377| `alt` | summary | -- | [31](31.md) |
322| `amount` | millisatoshis, stringified | -- | [57](57.md) | 378| `amount` | millisatoshis, stringified | -- | [57](57.md) |
323| `bolt11` | `bolt11` invoice | -- | [57](57.md) | 379| `bolt11` | `bolt11` invoice | -- | [57](57.md) |
380| `branch-name` | branch name suggestion | -- | [34](34.md) |
324| `challenge` | challenge string | -- | [42](42.md) | 381| `challenge` | challenge string | -- | [42](42.md) |
325| `client` | name, address | relay URL | [89](89.md) | 382| `client` | name, address | relay URL | [89](89.md) |
326| `clone` | git clone URL | -- | [34](34.md) | 383| `clone` | git clone URL | -- | [34](34.md) |
@@ -334,6 +391,8 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
334| `expiration` | unix timestamp (string) | -- | [40](40.md) | 391| `expiration` | unix timestamp (string) | -- | [40](40.md) |
335| `file` | full path (string) | -- | [35](35.md) | 392| `file` | full path (string) | -- | [35](35.md) |
336| `goal` | event id (hex) | relay URL | [75](75.md) | 393| `goal` | event id (hex) | relay URL | [75](75.md) |
394| `merge-base` | commit id | | [34](34.md) |
395| `HEAD` | `ref: refs/heads/<branch-name>` | | [34](34.md) |
337| `image` | image URL | dimensions in pixels | [23](23.md), [52](52.md), [58](58.md) | 396| `image` | image URL | dimensions in pixels | [23](23.md), [52](52.md), [58](58.md) |
338| `imeta` | inline metadata | -- | [92](92.md) | 397| `imeta` | inline metadata | -- | [92](92.md) |
339| `license` | License of the shared content | -- | [C0](C0.md) | 398| `license` | License of the shared content | -- | [C0](C0.md) |
@@ -350,6 +409,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
350| `repo` | Reference to the origin repository | -- | [C0](C0.md) | 409| `repo` | Reference to the origin repository | -- | [C0](C0.md) |
351| `runtime` | Runtime or environment specification | -- | [C0](C0.md) | 410| `runtime` | Runtime or environment specification | -- | [C0](C0.md) |
352| `server` | file storage server url | -- | [96](96.md) | 411| `server` | file storage server url | -- | [96](96.md) |
412| `sound` | shortcode, sound url, image url | -- | [51](51.md) |
353| `subject` | subject | -- | [14](14.md), [17](17.md), [34](34.md) | 413| `subject` | subject | -- | [14](14.md), [17](17.md), [34](34.md) |
354| `summary` | summary | -- | [23](23.md), [52](52.md) | 414| `summary` | summary | -- | [23](23.md), [52](52.md) |
355| `thumb` | badge thumbnail | dimensions in pixels | [58](58.md) | 415| `thumb` | badge thumbnail | dimensions in pixels | [58](58.md) |
@@ -382,10 +442,6 @@ Standards may emerge in two ways: the first way is that someone starts doing som
382 442
383These 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. 443These 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.
384 444
385## Breaking Changes
386
387[Breaking Changes](BREAKING.md)
388
389## License 445## License
390 446
391All NIPs are public domain. 447All NIPs are public domain.