NIP-01
Basic protocol flow description
基本的なプロトコルの流れの説明
Nostrの根幹となるいくつかの仕組みが定義されている 実装は、必須 mandatory とされています
仕様について
イベントと署名
署名、公開鍵暗号、エンコーディングには、secp256k1曲線を用いたシュノア署名標準を用いる。 オブジェクトの種類は、event ただ一つである。eventは通信上、次のフォーマットに従う。
code:_.json
{
// シリアライズされたイベントデータのSHA-256(32バイト)を小文字の16進数で表記したもの
"id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>
// 公開鍵(32バイト)を小文字の16進数で表記したもの
"pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
// UNIXタイムスタンプ(秒単位)
"created_at": <unix timestamp in seconds>,
// イベントの種類
"kind": <integer>,
// タグ
"tags": [
... // 他の種類のタグが後に追加される可能性がある
],
// 任意の文字列
"content": <arbitrary string>,
// シリアライズされたイベントデータのSHA-256(IDフィールドと同じ)に対する署名を16進数で表記したもの
"sig": <64-bytes hex of the signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
}
訳注:
32-bytesとあるが、実際には64文字(=64バイト)のIDや秘密鍵が使われている模様(nostr-tools等)
16進数表記すると1文字あたり4ビットで、32バイトのデータは64文字でエンコードされる。そう考えると辻褄が合う
後のフィルターの説明でも64文字という記述がある
event.idを算出するには、イベントをシリアライズしてそのSHA-256を計算する。
シリアライズするには、次の構造を持つUTF-8のJSONシリアライズされた文字列(空白文字や改行文字を含まない)を生成する。
code:_.json
[
0,
<pubkey, as a (lowercase) hex string>,
<created_at, as a number>,
<kind, as a number>,
<tags, as an array of arrays of non-null strings>,
<content, as a string>
]
クライアントとリレーの通信
クライアントからリレー: イベントの送信と購読の作成
クライアントは、これらの3種類のメッセージを送信できる。これはJSON文字列でなければならない。
["EVENT", イベントの内容]
イベントを投稿するときに使う
["REQ", 購読ID, フィルターのJSON]
サーバから送ってきてほしいイベントの情報を伝え、購読するために使う 購読IDは空でない、最大64文字の任意の文字列
以前は「ランダムな文字列」としか書かれていなかったが、このコミット で仕様が明確化された 実装ノート: いくつかの実装では、profile:公開鍵等のような一意なIDを生成して送っている REQを受け取ったときのサーバ側の動き
フィルターに一致するイベントを自身のデータベースからクライアントに返すべき(SHOULD)
REQ受信後に届いたイベントで、フィルターに一致するものをクライアントに転送すべき
同じ購読IDで新しくREQが送られた場合、過去の購読を上書きするべき
["CLOSE", 購読ID]
購読を停止したいときに使う
訳注: 他のメッセージとしてCOUNTやAUTHが定義されている
フィルターは、購読にどのようなイベントが送信されるかを定めるJSONオブジェクトである。次の属性を持つことができる。
code:filter.json
{
"ids": <a list of event ids or prefixes>,
"authors": <a list of pubkeys or prefixes, the pubkey of an event must be one of these>,
"kinds": <a list of a kind numbers>,
"#e": <a list of event ids that are referenced in an "e" tag>,
"#p": <a list of pubkeys that are referenced in a "p" tag>,
"since": <an integer unix timestamp, events must be newer than this to pass>,
"until": <an integer unix timestamp, events must be older than this to pass>,
"limit": <maximum number of events to be returned in the initial query>
}
フィルターの属性がリストである場合(ids, kinds, #eなど)
例: ["REQ", "12345", { "authors": ["abc...xyz", "123...789"] }]
一つ以上の値を含むJSONの配列として表現される
リストの中の値のいずれかが、イベント内の関連するフィールドに一致していれば、条件一致したと見なす
条件一致したと見なすには、イベントの属性が・・・
単一の値を持つ場合(kindなど)
イベントに含まれる値が、リストに含まれていなければならない
複数の値を持つ場合(#eなどのタグ属性)
イベントの値と条件のリストで、共通するものが少なくとも一つはなければならない
ids と authors のリストの扱い
小文字の16進文字列にすること
64文字が完全一致するか、イベントの先頭部分が一致すれば、条件一致したと見なす
先頭一致を使うと
たくさんの値を問い合わせたいときにフィルタをよりコンパクトにできる
クライアントが特定の著者やイベントを検索しようとしていることを隠し、ある種のプライバシーを実現する
フィルターの属性を複数指定する場合
例: ["REQ", "12345", { "kinds": [...], "authors": [...] }]
すべての条件がイベントに一致すれば、条件一致したと見なす
&& (AND検索) と見做される
フィルター自体を複数指定する場合
["REQ", "12345", { "条件1": [] }, { "条件2": [] }] のような場合
複数のフィルタのうちいずれかが一致すれば、条件一致したと見なす
|| (OR検索) と見做される
limitは初回取得(リレーに保存済みのイベントをまとめて取得するフェーズ)に対してのみ有効。limit: nが指定されている場合、初回取得で最新n個のイベントを返す。指定数未満のイベントを返しても問題ないが、クライアントがデータに圧倒されないように、リレーにはリクエストされた数を超えるイベントを返さないことが期待される。
リレーからクライアントへの通信
リレーは、これらの3種類のメッセージを送信できる。これはJSON文字列でなければならない。
["EVENT", <購読ID>, <イベントのJSON>]
クライアントが要求したイベントを送るために使う
クライアントが(REQメッセージを使って)前もって開始した購読に関連する購読IDと共に送信しなければならない(MUST)
["EOSE", <subscription_id>]
End of stored events(「保存されたイベントの終わり」)を表す
新しくリアルタイムに受信されたイベントの始まりを意味する
["NOTICE", <message>]
human-readable(人が読める)なエラーメッセージなどをクライアントに送るために使う
このNIPでは NOTICEをどのように送信すべきで、どのように解釈されるべきかを定義しない
訳注
結果を伝えるコマンドとして"OK"が定義されている(→NIP-20) 0: set_metadata
content に次のJSONオブジェクトを文字列化して含める
{name: <username>, about: <string>, picture: <url, string>}
リレーは、同じ公開鍵に対する新しいset_metadataを受け取ったら、過去のものを削除してもよい。
1: text_note
content に投稿の内容となるテキストを含める
the content is set to the text content of a note (anything the user wants to say)
Markdownは使わないこと。[]()のような内容をどうやって解釈するかを推測する必要はない。解釈可能なコンテンツには他の種類のイベントを使うこと。
非プレインテキストの投稿はNIP-16で定義される 1000〜10000 の kind を使うべき 2: recommend_server
contentには、投稿者がフォロワーにおすすめしたいリレーのURLを含める
例: wss://somerelay.com
WebSocket接続は各リレーにつき1つだけ
クライアントはそれぞれのリレーに対し、1つを超えるWebSocket接続を行うべきではない
1つの接続上で数に限りなく購読が行えるため、クライアントはそうすべき
tags配列の中身について
サブ配列の1つ目にはタグ識別子を入れ、その後ろに任意で情報(常に文字列)を含めて良い
["タグ識別子", 任意で情報を含めてもよい]
イベントが言及している人の公開鍵を指定する
引用したいイベントのIDを示す
推奨リレーURL <recommended relay url>
pタグとeタグには、推奨されるリレーのURLを含められる(空 "" でも良い)
「タグ付けされたイベント」や「タグ付けされたプロフィールに由来するイベント」を取得するときに、クライアントが接続できるようにするため
推奨リレーアドレスは無視されてもよい(MAY)が、検閲への耐性を高め、クライアント間でリレーのアドレスを拡散するために存在している
最新のメタデータイベント(kind:0)の使用
クライアントは、created_atフィールドをメタデータイベントの作成日時を調べるために利用し、到着順に関わらず他の古いメタデータイベントを新しいメタデータイベントで完全に置き換えるべきである。クライアントは古いメタデータイベントに存在するいかなるフィールドも、新しいメタデータイベントの空のフィールドにマージするべきではない。