Skip to content

Instantly share code, notes, and snippets.

@darashi
Created August 11, 2025 07:31
Show Gist options
  • Save darashi/284a74838d83a8b4afbb2168727c394f to your computer and use it in GitHub Desktop.
Save darashi/284a74838d83a8b4afbb2168727c394f to your computer and use it in GitHub Desktop.
NIP-01 translated by gpt-oss:20b

NIP-01

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

draft mandatory

このNIPは、誰もが実装すべき基本プロトコルを定義する。新しいNIPは、ここで説明した構造とフローに新しい任意(または必須)のフィールド、メッセージ、機能を追加できる。

イベントと署名

各ユーザーはキー対を持つ。署名、公開鍵、エンコーディングはSchnorr署名標準(曲線 secp256k1 用)に従って行われる。

存在する唯一のオブジェクトタイプは 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が生成されるのを防ぐため、シリアライズ時には以下のルールを必ず守ること:

  • エンコーディングはUTF-8を使用すること。
  • 出力JSONには空白、改行、その他不要なフォーマットを含めないこと。
  • contentフィールド内の以下の文字は示されたようにエスケープし、その他の文字はそのまま含めること:
    • 改行 (0x0A)、\n を使用
    • ダブルクォート (0x22)、\" を使用
    • バックスラッシュ (0x5C)、\\ を使用
    • 復帰 (0x0D)、\r を使用
    • タブ (0x09)、\t を使用
    • バックスペース (0x08)、\b を使用
    • フォームフィード (0x0C)、\f を使用

Tags

各タグは1つ以上の文字列の配列で、いくつかの規則があります。以下の例を見てください:

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

タグ配列の最初の要素はタグの name または key、2番目はタグの value と呼ばれる。したがって、上記のイベントは e タグが "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36" に設定され、alt タグが "reply" に設定されていると言える。2番目以降の要素には従来の名前はない。

このNIPは、すべてのイベント種別で同じ意味で使用できる3つの標準タグを定義している。以下の通りだ。

  • e タグはイベントを参照するために使用される: ["e", <32バイトの小文字hexで別のイベントのID>, <推奨リレーURL、任意>, <32バイトの小文字hexで著者のpubkey、任意>]
  • p タグは別のユーザーを参照するために使用される: ["p", <32バイトの小文字hexでpubkey>, <推奨リレーURL、任意>]
  • a タグはアドレス可能または置換可能なイベントを参照するために使用される
    • アドレス可能なイベントの場合: ["a", "<kind integer>:<32バイトの小文字hexでpubkey>:<dタグの値>", <推奨リレーURL、任意>]
    • 通常の置換可能イベントの場合: ["a", "<kind integer>:<32バイトの小文字hexでpubkey>:", <推奨リレーURL、任意>](注意:末尾のコロンを含める)

規約として、すべての単文字(英字のみ:a-z, A-Z)のキータグはリレーによってインデックス化されることが期待される。例えば、{"#e": ["5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36"]} フィルタを使って、上記のイベント "5c83da77af1dec6d7289834998ad7aafbd9e2191396d75ec3cc27f5a77226f36" を参照するイベントをクエリまたは購読できる。タグの中で最初の値のみがインデックス化される。

種類

Kindsはクライアントが各イベントとその他のフィールドの意味を解釈する方法を示す(例:"r"タグはkind 1のイベントでは意味があるが、kind 10002のイベントでは全く別の意味を持つ)。各NIPは、他で定義されていない種類の意味を定義することがある。例えばNIP-10はソーシャルメディアアプリ用にkind:1テキストノートを指定している。

このNIPは1つの基本種類を定義する:

  • 0: ユーザメタデータcontentはイベントを作成したユーザを説明する文字列化されたJSONオブジェクト {name: <nickname or full name>, about: <short bio>, picture: <url of the image>} に設定される。 追加メタデータフィールド は設定できる。リレーは同じpubkeyに対して新しいイベントを受け取ると古いイベントを削除することがある。

また、リレー実装の実験と柔軟性を容易にするための種類範囲の規約もある:

  • kind n1000 <= n < 10000 || 4 <= n < 45 || n == 1 || n == 2 の場合、イベントは 通常 で、リレーにすべて保存されることが期待される。
  • kind n10000 <= n < 20000 || n == 0 || n == 3 の場合、イベントは 置換可能 で、各 pubkeykind の組み合わせについて最新のイベントのみがリレーに保存されるべきで、古いバージョンは破棄される可能性がある。
  • kind n20000 <= n < 30000 の場合、イベントは 一時的 で、リレーに保存されることは期待されない。
  • kind n30000 <= n < 40000 の場合、イベントは kindpubkeyd タグ値で アドレス指定 される。各 kindpubkeyd タグ値の組み合わせについて最新のイベントのみがリレーに保存されるべきで、古いバージョンは破棄される可能性がある。

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

REQメッセージで置換可能イベント(例:{"kinds":[0],"authors":[<hex-key>]})に回答する際、リレーが複数バージョンを保持していても、最新のものだけを返すべきである。

これらはあくまで規約であり、リレー実装は異なる場合がある。

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

リレイはクライアントが接続できるWebSocketエンドポイントを公開する。クライアントは各リレイに対して単一のWebSocket接続を開き、すべての購読に使用すべきだ。リレイは特定のIP/クライアント等からの接続数を制限できる。

クライアントからリレーへ: イベント送信とサブスクリプション作成

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

  • ["EVENT", <event JSON as defined above>]、イベントを公開するために使用。
  • ["REQ", <subscription_id>, <filters1>, <filters2>, ...]、イベントを要求し、新しい更新を購読するために使用。
  • ["CLOSE", <subscription_id>]、以前のサブスクリプションを停止するために使用。

<subscription_id> は最大64文字の任意の空でない文字列で、接続ごとのサブスクリプションを表す。リレーは各WebSocket接続ごとに <subscription_id> を独立して管理しなければならない。<subscription_id> はグローバルに一意であることは保証されない。

<filtersX> はそのサブスクリプションで送信されるイベントを決定する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 メッセージを受信したら、リレーはフィルタに一致するイベントを返すべきです。受信した新しいイベントは、接続が閉じられる、同じ <subscription_id>CLOSE イベントが受信される、または同じ <subscription_id> を使って新しい REQ が送信されるまで(この場合は新しいサブスクリプションが作成され、古いものが置き換えられます)同じ WebSocket に送信されるべきです。

idsauthorskinds、および #e のようなタグフィルタなど、リストを含むフィルタ属性は、1 つ以上の値を持つ JSON 配列です。条件が一致とみなされるには、配列の値の少なくとも 1 つがイベントの該当フィールドと一致する必要があります。authorskind のようなスカラーイベント属性の場合、イベントの属性はフィルタリストに含まれている必要があります。#e のようなタグ属性では、イベントが複数の値を持つ場合、イベントとフィルタ条件の値は少なくとも 1 つ共通している必要があります。

idsauthors#e#p のフィルタリストは、必ず 64 文字の小文字の16進数値を正確に含む必要があります。

sinceuntil プロパティは、サブスクリプションで返されるイベントの時間範囲を指定するために使用できます。フィルタに since が含まれている場合、created_atsince 以上のイベントはフィルタに一致するとみなされます。until は同様ですが、created_atuntil 以下である必要があります。要するに、since <= created_at <= until が成り立つ場合にイベントはフィルタに一致します。

フィルタで指定されたすべての条件がイベントに一致しなければ、フィルタを通過しません。つまり、複数の条件は && として解釈されます。

REQ メッセージに複数のフィルタが含まれる場合、いずれかのフィルタに一致するイベントが返されます。つまり、複数のフィルタは || として解釈されます。

フィルタの limit プロパティは初回クエリのみに有効で、以降は無視すべきです。limit: n がある場合、初回クエリで返されるイベントは created_at で並べた最後の n 件とみなされます。新しいイベントが先に来るべきで、同じ created_at の場合は ID が最も小さい(辞書順で最初)イベントが先頭に来ます。リレーは limit 値を初期応答で返すイベント数の指針として使用すべきです。少ないイベントを返しても構いませんが、(大幅に)多く返すとクライアントを圧倒するため避けるべきです。

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

リレーは5種類のメッセージを送信でき、すべて JSON 配列である必要があります。パターンは次のとおりです。

  • ["EVENT", <subscription_id>, <event JSON as defined above>]、クライアントから要求されたイベントを送信するために使用。
  • ["OK", <event_id>, <true|false>, <message>]EVENT メッセージの受理または拒否を示すために使用。
  • ["EOSE", <subscription_id>]保存済みイベントの終了 とリアルタイムで受信した新しいイベントの開始を示すために使用。
  • ["CLOSED", <subscription_id>, <message>]、サーバ側でサブスクリプションが終了したことを示すために使用。
  • ["NOTICE", <message>]、人間が読めるエラーメッセージやその他の情報をクライアントへ送るために使用。

この NIP では NOTICE メッセージの送信方法や扱いについては規定していない。

  • EVENT メッセージは、クライアントが REQ メッセージで開始したサブスクリプションに関連する ID でのみ送信すること。
  • OK メッセージは、クライアントから受信した EVENT メッセージへの応答として送信すること。3 番目のパラメータはリレーがイベントを受理した場合は true、それ以外は false。4 番目のパラメータは必ず存在し、3 番目が true の場合は空文字列でもよいが、false の場合は機械可読の単語プレフィックス + : + 人間可読メッセージで構成される文字列である必要がある。例:
    • ["OK", "b1a649ebe8...", true, ""]
    • ["OK", "b1a649ebe8...", true, "pow: difficulty 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"]
    • ["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"]
    • ["OK", "b1a649ebe8...", false, "restricted: not allowed to write."]
    • ["OK", "b1a649ebe8...", false, "error: could not connect to the database"]
    • ["OK", "b1a649ebe8...", false, "mute: no one was listening to your ephemeral event and it wasn't handled in any way, it was ignored"]
  • CLOSED メッセージは、リレーが REQ を拒否したときに送信する。クライアントが切断または 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-limitedinvalidrestrictedmute、およびそれらに該当しない場合は error である。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment