NIP-01
Basic protocol flow description
/icons/hr.icon
基本的なプロトコルの流れの説明
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>
]
タグ
各タグは、いくつかの規約を持つ任意の長さの文字列の配列。以下に例を示す:
code:tags.json
{
...,
"tags": [
...
],
...
}
タグ配列の最初の要素はタグの 名前 または キー、その次の要素はタグの値とみなされる。よって、上記イベントは"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"(という値)がセットされた eタグ、"reply"がセットされたaltタグを持つ、のように言うことができる。それ以降のすべての要素は慣習名を持たない。
このNIPは、すべてのイベントの種類にわたって同じ意味を持つ、3つの標準タグを定義する。以下の通り:
eタグ ... イベントを参照: ["e", <他のイベントのID(32バイト小文字16進数)>, <推奨リレーURL(任意)>]
pタグ... 他のユーザを参照: ["p", <公開鍵(32バイト小文字16進数)>, <推奨リレーURL(任意)>]
aタグ: パラメータつき上書き可能イベントを参照: ["a", <kindの整数>:<公開鍵(32バイト小文字16進数)>:<dタグの値>, <推奨リレーURL(任意)>]
慣習として、すべてのキーが1文字(英語のアルファベット文字 a-z, A-Z に限る)のタグはリレーによって索引付けられる(to be indexed)ことが期待されている。これは、例えば {"#e": "5c83da7..."}というフィルターを用いて イベント "5c83da7..." を参照するイベントを検索・購読できるようにするため。
Kind(イベントの種類)
イベントの種類 (kind) は、クライアントが各イベントやそのフィールドの意味をどう解釈すべきかを指定する(例: "r"タグはkind 1のイベントとkind 10002のイベントでは全く違う意味を持つ)。各NIPは、他で定義されていないkindの意味を定義してよい。このNIPは2つの基本的なkindを定義する: 0: メタデータ (プロフィール)
content に次のJSONオブジェクトを文字列化して含める
{name: <username>, about: <string>, picture: <url, string>}
リレーは、同じ公開鍵に対する新しいmetadataを受け取ったら、過去のものを削除してもよい。
1: テキスト投稿
content に投稿の内容となるテキストを含める
the content is set to the text content of a note (anything the user wants to say)
パースを必要とするコンテンツ(MarkdownやHTML)を使うべきでない。クライアントもコンテンツをそのようにパースすべきでない。
さらに、実験を容易にし、リレー実装に柔軟性をもたらす、kindの範囲に関する慣習がある:
1000 <= n < 10000: 通常イベント
リレーはこれらのイベントをすべて保存する
10000 <= n < 20000,0, 3: 置換可能(replaceable)イベント
リレーは各pubkeyとkindの組み合わせについて最新のイベントのみを保存すべき(SHOULD)
古いものは破棄する
20000 <= n < 30000: 一時(ephemeral)イベント
リレーはこれらのイベントを保存しない
30000 <= n <40000: パラメータ付き置換可能(parametarized replaceable)イベント
リレーは各pubkey, kind, dタグ(の値)の組み合わせについて最新のイベントのみを保存
古いものは破棄する
同じタイムスタンプを持つ上書き可能イベントが複数ある場合、最小の(辞書順で先にくる)idを持つイベントが保持され、その他は破棄される。
以上は慣習にすぎず、実際のリレー実装は異なることがある。
クライアントとリレーの通信
WebSocket接続は各リレーにつき1つだけ
クライアントはそれぞれのリレーに対し、1つを超えるWebSocket接続を行うべきではない
1つの接続上で数に限りなく購読が行えるため、クライアントはそうすべき
WebSocketステータスコードの意味
リレーがWebsocketをステータスコード4000でクローズした場合、クライアントは再接続するべきではない。
注: WebSocketのcloseコード4000は「私的用途向け」として予約されている範囲の番号。
クライアントからリレー: イベントの送信と購読の作成
クライアントは、これらの3種類のメッセージを送信できる。これはJSON文字列でなければならない。
["EVENT", イベントの内容]
イベントを投稿するときに使う
["REQ", 購読ID, フィルターのJSON]
サーバから送ってきてほしいイベントの情報を伝え、購読するために使う 購読IDは空でない、最大64文字の任意の文字列
以前は「ランダムな文字列」としか書かれていなかったが、このコミット で仕様が明確化された 実装ノート: いくつかの実装では、profile:公開鍵等のような一意なIDを生成して送っている REQを受け取ったときのサーバ側の動き
フィルターに一致するイベントを自身のデータベースからクライアントに返すべき(SHOULD)
REQ受信後に届いたイベントで、フィルターに一致するものをクライアントに転送すべき
同じ購読IDで新しくREQが送られた場合、過去の購読を上書きするべき
["CLOSE", 購読ID]
購読を停止したいときに使う
訳注: 他のメッセージとしてCOUNTやAUTHが定義されている
フィルターは、購読にどのようなイベントが送信されるかを定めるJSONオブジェクトである。次の属性を持つことができる。
code:filter.json
{
// イベントのID、もしくは先頭部分(プレフィクス)
"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"タグで参照されたイベントIDのリスト
"#e": <a list of event ids that are referenced in an "e" tag>,
// "p"タグで参照された公開鍵のリスト
"#p": <a list of pubkeys that are referenced in a "p" tag>,
// UNIXタイムスタンプ(秒単位の整数値)。通過するには、イベントの created_at がこの値以上でなければならない。
"since": <an integer unix timestamp in seconds. Events must have a created_at >= to this to pass>,
// UNIXタイムスタンプ(秒単位の整数値)。通過するには、イベントの created_at がこの値以下でなければならない。
"until": <an integer unix timestamp in seconds. Events must have a created_at <= to 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 、#e、#pは小文字の16進数でちょうど64文字でなければならない(MUST)
sinceとuntil
これらの属性で購読で返されるイベントの期間を指定できる。
フィルタがsince属性を含む場合、created_atがsince以上のイベントがフィルタに合致するとみなす。
フィルタがuntil属性を含む場合、created_atがuntil以下のイベントがフィルタに合致すると考える。
すなわちsince <= created_at <= untilであるならばイベントはそのフィルタにマッチする。
フィルターの属性を複数指定する場合
例: ["REQ", "12345", { "kinds": [...], "authors": [...] }]
すべての条件がイベントに一致すれば、条件一致したと見なす
&& (AND検索) と見做される
フィルター自体を複数指定する場合
["REQ", "12345", { "条件1": [] }, { "条件2": [] }] のような場合
複数のフィルタのうちいずれかが一致すれば、条件一致したと見なす
|| (OR検索) と見做される
limitは初回取得(リレーに保存済みのイベントをまとめて取得するフェーズ)に対してのみ有効。limit: nが指定されている場合、初回取得で最新n個のイベントを返す。より新しいイベントが最初に来るべきである。同位(訳注:同じ時刻)の場合にはIDの最も小さいイベント(辞書順で最初のもの)が最初にくるべきである。指定数未満のイベントを返しても問題ないが、クライアントがデータに圧倒されないように、リレーには要求された数を(大きく)超えるイベントを返さないことが期待される。
リレーからクライアントへの通信
リレーは、これらの3種類のメッセージを送信できる。これはJSON文字列でなければならない。
["EVENT", <購読ID>, <イベントのJSON>]
クライアントが要求したイベントを送るために使う
クライアントが(REQメッセージを使って)前もって開始した購読に関連する購読IDと共に送信しなければならない(MUST)
["OK", <イベントID>, <true|false>, <メッセージ>]
クライアントから送られてきたイベントの受理または拒否を表す
リレーがイベントを受理した場合、3番めの引数はtrueとする。このとき4番目の引数は空でもよい(MAY)
リレーがイベントを拒否した場合、3番めの引数はfalseとし、4番目の引数は <機械向けの1単語のprefix>:<人間向けのメッセージ>という形の文字列としなければならない(MUST)。
標準の機械向けprefix: duplicate, pow, blocked, rate-limited, invalid, error(他に当てはまらない場合)
["EOSE", <subscription_id>]
End of stored events(「保存されたイベントの終わり」)を表す
新しくリアルタイムに受信されたイベントの始まりを意味する
["NOTICE", <message>]
human-readable(人が読める)なエラーメッセージなどをクライアントに送るために使う
このNIPでは NOTICEをどのように送信すべきで、どのように解釈されるべきかを定義しない