Skip to content

Latest commit

 

History

History
175 lines (130 loc) · 16.7 KB

01.md

File metadata and controls

175 lines (130 loc) · 16.7 KB

NIP-01

基本的なプロトコルフローの説明

draft mandatory

このNIPは、誰もが実装すべき基本的なプロトコルを定義する。新しいNIPによって、新たにオプショナルな(もしくは必須の)フィールド・メッセージ・機能が、ここで記述される構造やフローに追加される可能性がある。

イベントと署名

ユーザーは各々が鍵ペアを持つ。署名、公開鍵、エンコーディングはSchnorr signatures standard for the curve secp256k1 (secp256k1曲線シュノア署名標準)に従う。

ただ1つだけ存在するオブジェクトの型がeventで、以下のようなフォーマットで伝送される。

{
  "id": <32-bytes lowercase hex-encoded sha256 of the serialized event data>,
  "pubkey": <32-bytes lowercase hex-encoded public key of the event creator>,
  "created_at": <unix timestamp in seconds>,
  "kind": <integer between 0 and 65535>,
  "tags": [
    [<arbitrary string>...],
    // ...
  ],
  "content": <arbitrary string>,
  "sig": <64-bytes lowercase hex of the signature of the sha256 hash of the serialized event data, which is the same as the "id" field>
}

event.idを得るため、そのイベントをシリアライズしてsha256を計算する。具体的には、以下の構造をUTF-8 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>
]

同一のイベントに対して異なるイベントIDが生成されるような実装の差異を防ぐため、シリアライズを行う際には以下のルールに従わなければならない(MUST): エンコーディングにはUTF-8を使用するべきだ。

  • ホワイトスペースや改行、その他の不必要なフォーマットは出力のJSONに含めてはならない。
  • 以下の文字列以外はエスケープせず、そのまま含めるべきだ:
    • 改行0x0A\n
    • 二重引用符0x22\"
    • バックスラッシュ0x5C\\
    • キャリッジリターン0x0D\r
    • タブ文字0x09\t
    • バックスペース0x08\b
    • 改ページ0x0C\f

タグ

タグはそれぞれ1以上の文字列の配列で、関連していくつかの慣例がある。以下の例を見てみよう。

{
  "tags": [
    ["e", "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36", "wss://nostr.example.com"],
    ["p", "f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca"],
    ["a", "30023:f7234bd4c1394dda46d09f35bd384dd30cc552ad5541990f98844fb06676e9ca:abcd", "wss://nostr.example.com"],
    ["alt", "reply"],
    // ...
  ],
  // ...
}

タグ配列の最初の要素をタグの 名前 または キー と呼び、2番目をタグの と呼ぶ。したがって、上のイベントではeタグが"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"に設定されていて、altタグが"reply"に設定されている、などと言える。2番目より後の要素には慣用的な呼び方はない。

このNIPでは標準的なタグを3つ定義する。これらのタグは、あらゆるイベントのkindにおいて同じ意味で用いられる。以下の通りだ。

  • eタグ。イベントを参照するために用いる: ["e", <他のイベントIDの32バイト小文字16進数文字列>, <おすすめのリレーURL、省略可能>]
  • pタグ。別のユーザを参照するために用いる: ["p", <pubkeyの32バイト小文字16進数文字列>, <おすすめのリレーURL、省略可能>]
  • aタグ。アドレス指定可能 (addressable) または置換可能 (replaceable) イベントを参照するために用いる。
    • アドレス指定可能 (addressable) イベントの場合: ["a", <kind整数>:<pubkeyの32バイト小文字16進数文字列>:<dタグの値>, <おすすめリレーURL、省略可能>]
    • 置換可能 (replaceable) イベントの場合: ["a", <kind整数>:<pubkeyの32バイト小文字16進数文字列>:, <おすすめリレーURL、省略可能>]

慣例として、全ての1文字(英語のアルファベットa-zとA-Zに限る)キーをもつタグは、リレーによってインデクスされることが期待される。これにより、例えば、イベント"5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"を参照しているイベントをクエリしたり購読するために、{"#e": ["5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"]}フィルタを使える。

Kinds

Kindはクライアントがイベントやイベントのフィールドをどう解釈すべきかを決める(たとえば、"r"タグのkind 1における意味は、kind 10002のイベントにおける意味と全く異なる可能性がある)。それぞれのNIPにおいて、他の場所で定義されていないkindの意味を定義する可能性がある。このNIPでは、以下のように2つの基本的なkindを定義する。

  • 0: メタデータ: contentは文字列化されたJSONオブジェクト{name: <ユーザ名>, about: <文字列>, picture: <URL、文字列>}で、イベントを作成したユーザのことを記述する。リレーは、同一のpubkeyから新しいイベントを受信した際、過去のイベントを削除できる。
  • 1: テキスト投稿: contentはノート(ユーザが発言したいこと)をプレーンテキストで指定する。パースが必要なコンテンツ(マークダウンやHTMLなど)を使用すべきではない。クライアントもコンテンツをそのようにパースをしてはならない。

実験を容易にし、リレーの実装を柔軟にするため、kindの範囲についての以下のような慣例もある。

  • kindnが範囲1000 <= n < 10000 || 4 <= n < 45 || n == 1 || n == 2の場合は、イベントは**通常(regular)**で、全てがリレーに保存されることが期待される。
  • kindnが範囲10000 <= n < 20000 || n == 0 || n == 3の場合は、イベントは**置換可能(replaceable)**で、最新のイベントだけがリレーに保持されなければならない(MUST)。また、古いバージョンは破棄してもかまわない(MAY)。
  • kindnが範囲20000 <= n < 30000の場合は、イベントは**一時的(ephemeral)**で、リレーに保存されないことが期待される。
  • kindnが範囲30000 <= n < 40000の場合は、イベントは**アドレス指定可能 (addressable)**で、pubkeykinddタグの最初の値の組み合わせについて、最新のイベントのみがリレーに保存されなければならない(MUST)。また、古いバージョンは破棄してかまわない(MAY)。

置換可能イベントが同じタイムスタンプを持っている場合は、最も小さいID(辞書順で最初)のイベントが保持され、他のイベントは破棄されるべきだ。

置換可能イベントに対するREQメッセージ(たとえば{"kinds":[0],"authors":[<hex-key>]})に応答する場合には、リレーが仮に複数のバージョンを保持していたとしても、最新の1つだけを返すべきだ(SHOULD)。

これらは単なる慣例にすぎず、リレーの実装は異なる可能性がある。

クライアントとリレー間の通信

リレーは、クライアントが接続するためのWebSocketエンドポイントを公開する。クライアントは1つのリレーに対して1つだけWebSocketコネクションを開き、全ての購読でそれを使うべきだ(SHOULD)。リレーは特定のIP、クライアント、その他によってコネクション数を制限してもかまわない(MAY)。

クライアントからリレーへ: イベントの送信と購読の作成

クライアントは3種類のメッセージを送信できる。メッセージはJSON配列で、以下のパターンでなければならない。

  • ["EVENT", <前述のイベントJSON>] イベントを送信するために使う。
  • ["REQ", <購読ID>, <フィルタ1>, <フィルタ2>, ...] イベントを要求し、更新を購読するために使う。
  • ["CLOSE", <購読ID>] 既存の購読を中止するために使う。

<購読ID>は任意の、空でない最大64文字の文字列である。これがコネクションごとの購読を表現する。リレーは<購読ID>をWebSocket接続ごとに独立して管理しなければならない(MUST)。<購読ID>はグローバルに一意であることを保証するものではない。

<フィルタX>はJSONオブジェクトで、どんなイベントがその購読で転送されるかを決める。以下の属性をもつ可能性がある:

{
  "ids": <a list of event ids>,
  "authors": <a list of lowercase pubkeys, the pubkey of an event must be one of these>,
  "kinds": <a list of a kind numbers>,
  "#<single-letter (a-zA-Z)>": <a list of tag values, for #e — a list of event ids, for #p — a list of pubkeys, etc.>,
  "since": <an integer unix timestamp in seconds. Events must have a created_at >= to this to pass>,
  "until": <an integer unix timestamp in seconds. Events must have a created_at <= to this to pass>,
  "limit": <maximum number of events relays SHOULD return in the initial query>
}

REQメッセージを受信すると、リレーはフィルターにマッチするイベントを返す。新しく受信したイベントは接続が閉じられるか、同じ<subscripttion_id>CLOSEイベントが受信されるか、同じ<subscription_id> を使用して新しいREQが送信されるまで、同じWebSocketに送信されるべきだ(SHOULD)。(この場合、新しい購読が作成され、古い購読が置き換えられる。)

リストをとるフィルター属性(idsauthorskind#eのようなタグフィルタ)は、1つ以上の要素を持つJSON配列である。その条件がマッチしたとみなされるためには、配列の値のうち少なくとも1つがイベントの関連するフィールドと一致しなければならない。authorskindのようなスカラーのイベント属性の場合は、イベントの属性値がフィルターのリストに含まれなければならない。#eなどのタグ属性のように、イベントが複数の値を持ちうる場合は、イベントとフィルタの条件値が少なくとも1つの共通する要素を持たなければならない。

idsauthors#e#pのフィルタのリストが要素として含むのは、64文字の小文字の16進数文字列でなければならない(MUST)。

sinceuntilプロパティは、購読で返されるイベントの時刻の範囲を指定するために使用できる。もし、フィルタにsinceプロパティがあれば、since以上のcreated_atを持つイベントがマッチする。untilプロパティも同様で、until以下のcreated_atを持つイベントがマッチする。つまり、条件since <= created_at <= untilが満たされるとき、イベントがフィルタにマッチする。

イベントがあるフィルタを通過するためには、フィルタに指定された全ての条件がマッチしなければならない。つまり、複数の条件は&&条件として解釈される。

1つのREQメッセージに複数のフィルタを含むことができる。この場合、いずれかのフィルタにマッチするイベントが返される。つまり、複数のフィルタは||条件として解釈される。

フィルタのlimitプロパティは、初期クエリにのみ影響を及ぼし、それ以降は無視されなければならない(MUST)。limit: nが存在する場合、初期クエリでは最新のnイベントがcreated_at順に返されることが想定される。新しいイベントが最初に表示され、同時刻の場合はidが最も小さい (辞書式順序で最初の) イベントが最初に表示されるべきである。limitの指定よりも少ないイベントを返すことは問題ない。しかし、クライアントに対して不必要に負荷を与えることを避けるため、リレーは要求されたよりも(はるかに)多くのイベントを返すことがないよう期待される。

リレーからクライアントへ: イベントの送信と通知

リレーは5種類のメッセージを送信できる。メッセージはJSON配列で、以下のパターンでなければならない。

  • ["EVENT", <購読ID>, <前述のイベントJSON>]クライアントから要求されたイベントを送信するために使う。
  • ["OK", <イベントID>, <true|false>, <メッセージ>]``EVENTメッセージの受理または拒否を通知するために使う。
  • ["EOSE", <購読ID>]保存されているイベントの終わり(End Of Stored Events)、リアルタイムに受信されたイベントの開始を示す。
  • ["CLOSED", <購読ID>, <メッセージ>]サーバー側で購読が終了したことを示す。
  • ["NOTICE", <メッセージ>]人間可読なエラーメッセージやその他の事柄をクライアントへ送信するために使う。

このNIPではNOTICEメッセージをどのように送信し、またどのように扱うべきかについてのルールは定義しない。

  • EVENTメッセージは、クライアントが(前述のREQメッセージを用いて)以前に開始した購読IDと紐付けられたものだけが送信されなければならない(MUST)。
  • OKメッセージは、クライアントから受信したEVENTメッセージに対する返信として送信されなければならない(MUST)。3番目のパラメータは、リレーがメッセージを受理する場合はtrueを、そうでなければfalseを指定しなければならない。4番目のパラメータも必須で(MUST)、3番目がtrueの場合は空文字列でもかまわない(MAY)が、そうでなければ、機械可読な一語のプレフィクスに続けて:と人間可読なメッセージを含まなければならない(MUST)。例は以下の通り:
    • ["OK", "b1a649ebe8...", true, ""]
    • ["OK", "b1a649ebe8...", true, "pow: difficulty 25>=24"] pow: 難易度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"] ブロックされている: https://my-expensive-relay.example.comでpubkeyを登録せよ
    • ["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"] pow: 難易度26は30よりも小さい
    • ["OK", "b1a649ebe8...", false, "error: could not connect to the database"] エラー: データベースに接続できなかった
  • CLOSEDメッセージは、リレーがREQの処理を拒否するときに、その応答として送信されなければならない (MUST) 。また、クライアントが切断したりCLOSEを送信する前に、リレーの側から購読を強制終了する場合にも送信される。このメッセージは機械可読なプレフィクスと人間可読なメッセージを含んだOKメッセージと同じパターンを使用する。例は以下の通り:
    • ["CLOSED", "sub1", "unsupported: filter contains unknown elements"] サポートされていない: フィルターに不明な要素が含まれている
    • ["CLOSED", "sub1", "error: could not connect to the database"] エラー: データベースに接続できなかった
    • ["CLOSED", "sub1", "error: shutting down idle subscription"] エラー: アイドル状態の購読をシャットダウン
  • OKCLOSEDの標準化されている機械可読なプレフィクスは、duplicatepowblockedrate-limitedinvaliderror(他のどれにも当てはまらない場合)である。