mirror of
https://github.com/nostr-protocol/nips.git
synced 2024-12-26 10:15:52 -05:00
236 lines
11 KiB
Markdown
236 lines
11 KiB
Markdown
NIP-01
|
|
======
|
|
|
|
Nostr Protocol
|
|
--------------
|
|
|
|
`draft` `mandatory`
|
|
|
|
This NIP defines the mandatory part of the Nostr protocol. New NIPs may add new optional (or mandatory) fields, messages, and features to the structures and flows described here.
|
|
|
|
# Events
|
|
|
|
Event is the only object type available. It is a hashed and signed payload in the following format:
|
|
|
|
```jsonc
|
|
{
|
|
"id": <string 32-byte lowercase hex-encoded sha256 of the serialized event data>,
|
|
"pubkey": <string 32-byte lowercase hex-encoded public key used to sign>,
|
|
"created_at": <integer unix timestamp in seconds>,
|
|
"kind": <integer between 0 and 65535>,
|
|
"tags": [
|
|
[<string tag1-name>, <string tag1-value>, ...],
|
|
[<string tag2-name>, <string tag2-value>, ...],
|
|
...
|
|
],
|
|
"content": <string>,
|
|
"sig": <string 64-byte lowercase hex of the signature of the id>
|
|
}
|
|
```
|
|
|
|
Each user has a keypair and is identified by their public key (`.pubkey`).
|
|
|
|
## Signing
|
|
|
|
Signatures and encodings are done according to the [Schnorr signatures standard for the curve `secp256k1`](https://bips.xyz/340).
|
|
|
|
To assemble the `.id`, hex encode the result of a `sha256` of the UTF-8 byte array of a JSON-serialized string in the following structure:
|
|
|
|
```js
|
|
[
|
|
0,
|
|
<string pubkey, as a lowercase hex>,
|
|
<integer created_at>,
|
|
<integer kind>,
|
|
<stringarray tags, as an array of arrays of non-null strings>,
|
|
<string content>
|
|
]
|
|
```
|
|
|
|
The JSON serialization MUST follow these rules:
|
|
- Minified: No whitespace, line breaks, or other unnecessary formatting used
|
|
- Escape Set: ONLY the following characters MUST be escaped:
|
|
- line break: `0x0A` to `\n`
|
|
- double quote: `0x22` to `\"`
|
|
- backslash: `0x5C` to `\\`
|
|
- carriage return: `0x0D` to `\r`
|
|
- tab character: `0x09` to `\t`
|
|
- backspace: `0x08` to `\b`
|
|
- form feed: `0x0C` to `\f`
|
|
|
|
Then proceed to sign the `.id` with the user's private key.
|
|
|
|
## Verifying
|
|
|
|
To verify the authenticity of an event:
|
|
1. Recalculate the `.id` and check against the incoming `.id`
|
|
2. Verify if the signature is valid for the `.id` and `.pubkey`.
|
|
|
|
## Kinds
|
|
|
|
The `.kind` attribute specifies the meaning of an event, its tags and it is further defined in the various NIPs in this repository
|
|
|
|
Specific ranges define storage behaviors:
|
|
|
|
| Name | Range | SHOULD retain |
|
|
| ------------- | --------------------------------- | ------------------------------------------------------ |
|
|
| Regular | ` 1000 <= kind < 10000` | All events |
|
|
| Replaceable | `10000 <= kind < 20000` OR `0, 3` | The newest event for each `pubkey` and `kind` |
|
|
| Ephemeral | `20000 <= kind < 30000` | Nothing |
|
|
| Parameterized | `30000 <= kind < 40000` | The newest event for each `pubkey`, `kind` and `d`-tag |
|
|
|
|
Parameterized Replaceable events MUST include a `d`-tag with an identifier value to be used as a reference. Tag values that reference the newest replaceable MUST use this format: `<kind>:<32-byte lowercase hex of a pubkey>:<d-tag value>`.
|
|
|
|
In case of replaceable events with the same timestamp, the event with the lowest `.id` (first in lexical order) SHOULD be retained. Older versions MAY be kept but SHOULD not be returned on queries.
|
|
|
|
This NIP defines the `kind:0` as **User Metadata**. The `.content` is set to a stringified JSON object `{name: <user's name, string>, about: <string>, picture: <url, string>}`. Since other attributes might exist, Clients SHOULD preserve any unsupported attribute when updating this event.
|
|
|
|
## Tags
|
|
|
|
Each tag is an array of strings of arbitrary size. Their meaning is determined by the event `.kind`. Tags with the same name might have entirely different meanings in different kinds.
|
|
|
|
The first element of the tag array is referred to as the tag _name_ and the second as the tag _value_. All elements after the second do not have a conventional name.
|
|
|
|
All single-letter (only English alphabet letters: a-z, A-Z) tag names are indexed by relays for faster queries.
|
|
|
|
This NIP defines the format of 3 standard tags: `e`, `p`, and `a`. Tags `e`, `p` can be used to reference events and pubkeys respectively, and tag `a` references the latest version of a replaceable event. Clients MAY `a`- and `e`-tag parameterized replaceables simultaneously.
|
|
|
|
| Name | Value | Other Params |
|
|
| ---- | ---------------------------------------------------------- | ----------------------- |
|
|
| `e` | `<32-byte lowercase hex of an event id>` | `<relay URL, optional>` |
|
|
| `p` | `<32-byte lowercase hex of a pubkey>` | `<relay URL, optional>` |
|
|
| `a` | `<kind>:<32-byte lowercase hex of a pubkey>:` | `<relay URL, optional>` |
|
|
| `a` | `<kind>:<32-byte lowercase hex of a pubkey>:<d-tag value>` | `<relay URL, optional>` |
|
|
|
|
The `<d-tag value>` component of the `a` tag is an empty string (`""`) when referencing non-parameterized but replaceable events.
|
|
|
|
# Relay Protocol
|
|
|
|
Nostr has two main components: Clients & Relays. Users run a client to publish and fetch events from one or more Relays via WebSockets.
|
|
|
|
Relays are not expected to communicate with one another. It's the Client's responsibility to discover which relays have the event set their user wants to see.
|
|
|
|
Both sides SHOULD verify the `.id` and the signature of each event upon receipt.
|
|
|
|
Clients SHOULD open a single WebSocket connection to each relay and use it for all their subscriptions. Relays MAY limit the number of connections from specific IP/client/etc.
|
|
|
|
All messages are defined as JSON arrays, where the first item determines the message type.
|
|
|
|
## Subscriptions
|
|
|
|
Clients fetch events using a **subscription** message with one or more filters. Upon receipt, the Relay MUST query its database, return all events that match the filter and keep applying the filter to all connections, returning new events in real-time as they arrive.
|
|
|
|
Subscriptions stay indefinitely open until either side closes the subscription or the connection.
|
|
|
|
### Subscription Requests
|
|
|
|
To request or update a subscription, send a `REQ` message with the format
|
|
|
|
```js
|
|
["REQ", <string subscription_id>, <string filter1>, <string filter2>, ...]
|
|
```
|
|
|
|
To stop receiving events from a subscription:
|
|
```js
|
|
["CLOSE", <string subscription_id>]
|
|
```
|
|
|
|
A `REQ` message on an existing subscription overrides the previous subscription.
|
|
|
|
`subscription_id` is a non-empty string with a maximum length of 64 chars. Relays MUST manage `<subscription_id>`s independently for each WebSocket connection. `<subscription_id>`s are not globally unique.
|
|
|
|
`filterX` is a stringified JSON object that determines what events will be sent in that subscription, it can have the following attributes:
|
|
|
|
```js
|
|
{
|
|
"ids": [<string id1>, <string id2>, ...],
|
|
"authors": [<string pubkey1>, <string pubkey2>, ...],
|
|
"kinds": [<integer kind1>, <integer kind2>, ...],
|
|
"#<tag1-name (single-letter a-zA-Z)>": [<string tag1 value1>, <string tag1 value2>, ...],
|
|
"#<tag2-name (single-letter a-zA-Z)>": [<string tag2 value1>, <string tag2 value2>, ...],
|
|
...,
|
|
"since": <integer unix timestamp in seconds, events must be newer than this to pass>,
|
|
"until": <integer unix timestamp in seconds, events must be older than this to pass>,
|
|
"limit": <integer maximum number of events to return, sorted by created_at descending>
|
|
}
|
|
```
|
|
|
|
A `REQ` message may contain multiple filters which are interpreted as an OR statement: events that match any of the filters are to be returned.
|
|
|
|
Attributes are a logical AND statement: all present attributes must match for the filter to pass.
|
|
|
|
Array attributes (i.e. `ids`, `authors`, `kinds`, and tag filters) represent a logical OR statement. At least one of the arrays' values must match the respective field in the event to be considered a match. In the case of tag attributes such as `#e`, for which an event may have multiple values, the event and filter condition values must have at least one item in common.
|
|
|
|
The `ids`, `authors`, `#e`, and `#p` filter lists MUST contain exact 64-character lowercase hex values.
|
|
|
|
The `since` and `until` attributes are used to specify the time range of events returned in the subscription. An event matches the filter if `since <= created_at <= until` holds.
|
|
|
|
The `limit` attribute informs the maximum number of events to return, sorted by `.created_at` from newest to oldest. It operates over the previously stored events and is ignored afterward.
|
|
|
|
### Subscription Responses
|
|
|
|
The `EVENT` message is used to send events requested by clients:
|
|
|
|
```js
|
|
["EVENT", <string subscription_id>, <string JSON-serialized event>]
|
|
```
|
|
|
|
Once all matching events are sent, an `EOSE` message follows to indicate the _end of stored events_ and the beginning of events newly received in real-time.
|
|
|
|
```js
|
|
["EOSE", <string subscription_id>]
|
|
```
|
|
|
|
`CLOSED` messages can be when the relay refuses to fulfill a `REQ` or when it decides to kill a subscription before a client has disconnected or sent a `CLOSE`, a `CLOSED` message is sent:
|
|
|
|
```js
|
|
["CLOSED", <string subscription_id>, <string prefix:message>]
|
|
```
|
|
|
|
The message MUST be a string formed by a machine-readable single-word prefix followed by a `:` and then a human-readable message.
|
|
|
|
## Publishing
|
|
|
|
To send an event to the Relay, Clients send a broadcast message in the format:
|
|
|
|
```js
|
|
["EVENT", <string JSON-serialized event>]
|
|
```
|
|
|
|
Relays reply with an ACK message in the format of:
|
|
|
|
```js
|
|
["OK", <string event_id>, <bool accepted>, <string prefix:message>]
|
|
```
|
|
|
|
`OK` messages MUST be sent in response to `EVENT` messages received from clients, they must have the 3rd parameter set to `true` when an event has been accepted by the relay, `false` otherwise. The 4th parameter MUST always be present, but MAY be an empty string when the 3rd is `true`.
|
|
|
|
If present, the message MUST be a string formed by a machine-readable single-word prefix followed by a `:` and then a human-readable message.
|
|
|
|
## Notices
|
|
|
|
Notices are human-readable warnings that might help explain or debug the behavior of a given relay or filter.
|
|
|
|
```js
|
|
["NOTICE", <string message>]
|
|
```
|
|
|
|
## Message Prefixes
|
|
|
|
The standardized machine-readable prefixes for `OK` and `CLOSED` are: `duplicate`, `pow`, `blocked`, `rate-limited`, `invalid`, and `error` for when none of that fits.
|
|
|
|
Some examples:
|
|
* `["OK", "b1a649ebe8...", true, ""]`
|
|
* `["OK", "b1a649ebe8...", true, "pow: difficulty 25>=24"]`
|
|
* `["OK", "b1a649ebe8...", true, "duplicate: already have this event"]`
|
|
* `["OK", "b1a649ebe8...", false, "blocked: you are banned from posting here"]`
|
|
* `["OK", "b1a649ebe8...", false, "blocked: please register your pubkey at https://my-expensive-relay.example.com"]`
|
|
* `["OK", "b1a649ebe8...", false, "rate-limited: slow down there, chief"]`
|
|
* `["OK", "b1a649ebe8...", false, "invalid: event creation date is too far off from the current time"]`
|
|
* `["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"]`
|
|
* `["OK", "b1a649ebe8...", false, "error: could not connect to the database"]`
|
|
* `["CLOSED", "sub1", "duplicate: sub1 already opened"]`
|
|
* `["CLOSED", "sub1", "unsupported: filter contains unknown elements"]`
|
|
* `["CLOSED", "sub1", "error: could not connect to the database"]`
|
|
* `["CLOSED", "sub1", "error: shutting down idle subscription"]`
|