Skip to content

Latest commit

 

History

History
163 lines (116 loc) · 15.8 KB

01.md

File metadata and controls

163 lines (116 loc) · 15.8 KB

NIP-01

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

ドラフト 必須 著者:fiatjaf 著者:distbit 著者:scsibug 著者:kukks 著者:jb55 著者:semisol 著者:cameri 著者:Giszmo

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

イベントと署名

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

ただ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>
]

タグ

タグはそれぞれ任意長の文字列の配列であり、関連していくつかの慣例があります。以下の例を見てください。

{
  ...,
  "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 タグ。(パラメータ付き・無し)置換可能イベントを参照するために用いる。
    • パラメータ付き置換可能イベントの場合: ["a", <kind整数>:<pubkeyの32バイト小文字16進数文字列>:<dタグの値>, <おすすめリレーURL、省略可能>]
    • パラメータ無し置換可能イベントの場合: ["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の範囲についての以下のような慣例もあります。

  • kind n が範囲 1000 <= n < 10000 の場合は、イベントは**通常(regular)**で、全てがリレーに保存されることが期待されます。
  • kind n が範囲 10000 <= n < 20000 || n == 0 || n == 3 の場合は、イベントは**置換可能(replaceable)**で、最新のイベントだけがリレーに保持されなければなりません(MUST)。また、古いバージョンは破棄してもかまいません(MAY)。
  • kind n が範囲 20000 <= n < 30000 の場合は、イベントは**一時的(ephemeral)**で、リレーに保存されないことが期待されます。
  • kind n が範囲 30000 <= n < 40000 の場合は、イベントは**パラメータ付き置換可能(parameterized replaceable)**で、pubkeykinddタグの最初の値の組み合わせについて、最新のイベントのみがリレーに保存されなければなりません(MUST)。また、古いバージョンは破棄してかまいません(MAY)。

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

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

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

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

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

WebSocketステータスコードの意味

  • WebSocketがリレーからステータスコード4000でクローズされた場合は、クライアントは再接続を試みてはいけません。

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

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

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

<購読ID> は任意の、空でない最大64文字の文字列で、購読を区別するために用います。リレーはWebSocket接続ごとに独立して<購読ID>を管理しなければなりません。同一文字列の<購読ID>であっても、コネクションごとに異なる購読として扱うべきです。

<フィルタ> は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 event pubkeys etc>,
  "since": <an integer unix timestamp in seconds, events must be newer than this to pass>,
  "until": <an integer unix timestamp in seconds, events must be older than this to pass>,
  "limit": <maximum number of events relays SHOULD return in the initial query>
}

REQメッセージを受信すると、リレーは内部データベースに問い合わせてフィルターにマッチするイベントを返し、そのフィルターを保存しておいて、以降に受信した全てのイベントをWebSocketがクローズされるまで同じWebSocketに送信すべきです(SHOULD)。同一の<購読ID>を持つCLOSEイベントを受信するか、同一の<購読ID>を使って新たなREQが送信されると、リレーは既存の購読を更新しなければなりません(MUST)。

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

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

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

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

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

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

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

リレーは4種類のメッセージを送信できます。メッセージはJSON配列で、以下のパターンでなければなりません。

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

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

  • EVENTメッセージは、クライアントが(前述のREQメッセージを用いて)以前に開始した購読IDと紐付けられたものだけが送信されなければなりません(MUST)。

  • OKメッセージは、クライアントから受信したEVENTメッセージに対する返信として送信されなければなりません(MUST)。3番目のパラメータは、リレーがメッセージを受理する場合はtureを、そうでなければfalseを指定しなければなりません。4番目のパラメータも必須で(MUST)、3番目がtrueである場合は空文字列でもかまいません(MAY)が、そうでなければ、機械可読な一語のプレフィクスに続けて:と人間可読なメッセージを含まなければなりません(MUST)。標準化されている機械可読なプレフィクスは、duplicatepowblockedrate-limitedinvaliderror(他のどれにも当てはまらない場合)です。例は以下の通りです。

    • ["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. Is your system clock in sync?"] 無効: イベント作成日が現在時刻から大きくずれています。システムクロックは同期されていますか?
    • ["OK", "b1a649ebe8...", false, "pow: difficulty 26 is less than 30"] pow: 難易度26は30よりも小さいです
    • ["OK", "b1a649ebe8...", false, "error: could not connect to the database"] エラー: データベースに接続できませんでした