nip-01: update to reflect new serialization and websocket interface.

This commit is contained in:
fiatjaf 2021-01-04 23:15:10 -03:00
parent 56eff922ad
commit 00e5667471
1 changed files with 23 additions and 43 deletions

View File

@ -7,12 +7,11 @@ Basic protocol flow description
`draft` `mandatory`
1. Each user has a keypair. Signatures, public key and encodings are done according to the [Schnorr signatures standard for the curve `secp256k1`](https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
2. Users can publish `events` to any compatible relay by calling `POST /save_event` with `Content-Type: application/json` and a JSON object describing an event:
1. Each user has a keypair. Signatures, public key and encodings are done according to the [Schnorr signatures standard for the curve `secp256k1`](https://bips.xyz/340).
2. The single object type that exists is the `event`, which has the following format on the wire:
```
{
id: <32-bytes sha256 of the the serialized event data, optional as will be recomputed by the relay>
id: <32-bytes sha256 of the the serialized event data>
pubkey: <32-bytes hex-encoded public key of the event creator>,
created_at: <unix timestamp>,
kind: <integer>,
@ -25,45 +24,26 @@ Basic protocol flow description
sig: <64-bytes signature of the sha256 hash of the serialized event data, which is the same as the "id" field>,
}
```
4. Upon receiving a call like that, the relay MUST compute the `id`, check the signature over the `id` and store the event so it can serve it later to other clients.
5. There are 3 kinds of messages a user can publish (for now, these things are extensible and will be extended):
3. To obtain the `event.id`, we `sha256` the serialized event. The serialization is done over the UTF-8 JSON-serialized string (with no indentation or extra spaces) of the following structure:
```
[
0,
<pubkey, as a hex string>,
<created_at, as a number>,
<kind, as a number>,
<tags, as an array of arrays>,
<content>, as a string
]
```
4. Users can connect to any compatible relay by opening a websocket connection to `<relay_url>/ws`. The websocket accepts the following messages:
1. `sub-key:<pubkey, as a hex string>`, subscribes to events from this key ("follow");
2. `unsub-key:<pubkey, as a hex string>`, as above, but unsubscribes ("unfollow");
3. `req-feed:<{limit, offset}, object with these optional parameters>`, requests the latest events from the pubkeys subscribed to above (the basic flow is to open a websocket, call `sub-key` many times, then call `req-feed`).
4. `req-event:<{id, limit}, object with the event id and optional parameters>`, requests one event specifically and other events related to it ("browse a thread").
5. `req-key:<{pubkey, limit, offset}, object with the pubkey and optional parameters>`, requests one pubkey specifically ("browse a user profile").
6. `<event, in object format as noted above>`, publishes an event.
5. There are 3 kinds of messages a user can publish (other NIPs may define different types):
- `0`: `set_metadata`: the `content` is set to a stringified JSON object `{name: <string>, about: <string>, picture: <url, string>}` describing the user who created the event. A relay may delete past `set_metadata` events once it gets a new one for the same pubkey.
- `1`: `text_note`: the `content` is set to the text content of a note, anything the user wants to say.
- `2`: `recommend_server`: the `content` is set to the URL (e.g., `https://somerelay.com`) of a relay the event creator wants to recommend to its followers.
6. A relay MUST serve an SSE (Server-Sent Events) stream at the path `GET /listen_events`. As querystring parameters it MUST expect `?session=<arbitrary id chosen by the client>`.
7. The relay MUST also expect the following calls:
- `POST /request_watch?session=<session id>` with body `{"keys": [<32-byte hex-encoded pubkey>, ...]}`
Upon receiving this, the relay SHOULD begin watching for new events being saved coming from the given pubkeys and return them in the SSE stream identified by the given _session id_. Events triggered by this call MUST have their SSE type set to `n`.
- `POST /request_unwatch?session=<session id>` with body `{"keys": [<32-byte hex-encoded pubkey>, ...]}`
Upon receiving this, the relay SHOULD stop notifying the given SSE stream for updates from the given pubkeys.
- `POST /request_feed?session=<session id>` with optional body `{"limit": 50, "offset": 0}`
Upon receiving this, the relay MUST return in the same SSE stream previously opened identified by the given _session id_ recent past events from all the pubkeys it may be watching on behalf of the that SSE stream. Events triggered by this call MUST have their SSE type set to `p`.
- `POST /request_user?session=<session id>` with body `{"pubkey": ..., "limit": 50, "offset": 0}`
Upon receiving this, the relay MUST return in the same SSE stream previously opened identified by the given _session id_ recent past events from the specified user, including a `set_metadata` event if the relay has it. Events triggered by this call MUST have their SSE type set to `r`.
- `POST /request_event?session=<session id>` with body `{"id": ..., "limit": 10}`
Same as above, but instead the relay returns the specified event and/or related events it has (events that reference it or events that it references). Events triggered by this call MUST have their SSE type set to `r`.
The `limit` and `offset` parameters are optional. When they're not specified the relay is free to use any defaults it want. If `limit` is specified the relay MUST use any number equal or smaller than `limit`, never greater. If `offset` is specified the relay must use it too, and if `offset` is too big for the relay it can just do as if no events were found at that range.
### Format of the SSE event (example)
```
type: p
data: {"id": "000...", "pubkey": ... etc.}
```
### Serialization of the event
The serialization of the event and `sha256` hashing of the result is what produces the event id. Then that id (which will be a 32-byte string) is signed according to BIP-340 standards to produce the event signature.
For more information, see the source code implementation of the serialization function, as it will be simpler to understand than whatever I can write here.
### Implementation notes
What a basic client should do:
1. Initiate SSE sessions with every relay it knows;
2. Send `/request_watch` to each with all the keys the user "follows";
3. Send `/request_feed` afterwards so the user sees a wall of posts upon turning the app on;
4. Send `/request_watch` and `/request_unwatch` if the user follows or unfollows someone;
5. Receive all events from the single SSE stream and filter/organize them locally, taking into account the fact that they may come as duplicates, out of order etc.
6. A relay may choose to treat different message kinds differently (for example, instead of just returning the newest events for a given profile on `req-key` it can choose to always return the latest `set_metadata` event it has even if that is very old, so the client can always see a name and picture when browsing a profile), or apply any arbitrary filtering criteria it desires to the kinds of events it will accept.