Open28

Nostr というのが流行っているらしい

雪猫雪猫
雪猫雪猫

NIP-11 Relay Information Document

https://{domain}Accept: application/nostr+json ヘッダー付きでアクセスされたら JSON を返す。

NIP-15 End of Stored Events Notice

データ取得をリクエストされて全部送り終わったら EOSE を送る。

雪猫雪猫

EVENT

[
  "EVENT",
  "<subscription_id>",
  {<REQ で要求されたオブジェクト>}
]

OK

[
  "OK",
  "<id>",
  true,
  ''
]

NOTICE

[
  "NOTICE",
  "<message>"
]
雪猫雪猫

国内限定リレー

IP 制限がかかっています。

ホワイトリストリレー

使用したい場合は管理者へ問い合わせてください。

国産リレー

運用する際の課題

  • スパム対策
  • スケール
  • コスト
雪猫雪猫

リレーの制限

対象

  • kind
  • pubkey
    • フォロー
    • フォロワー
    • 独自リスト
  • 課金
    • 読み書き
    • データの保持期間
  • IP アドレス
    • リクエスト全体(インフラレイヤー)
    • 読み書きで個別に設定(アプリケーションレイヤー)
  • 投稿内容 (content, tags)
    • 正規表現
    • ハッシュタグ
  • 個人
    • pubkey が自分
    • p タグが自分宛

形式

  • ホワイトリスト
  • ブラックリスト

実装

  • NIP-42
  • 設定ファイル
雪猫雪猫

kind 0, 3, 10002 専用リレー

  • wss://purplepag.es/
  • wss://user.kindpag.es/
  • wss://directory.yabu.me/
雪猫雪猫

クライアント

使用

Client Web PWA iOS Android Note
Iris
Snort × × リレーの情報が見れる
Damus × × × 使いやすい
Amethyst × × ×

ライブラリ

https://github.com/nbd-wtf/nostr-tools
https://github.com/penpenpng/rx-nostr
https://github.com/jiftechnify/nostr-fetch
https://github.com/nostr-dev-kit/ndk

※ NDK は設計が悪いので非推奨。

参考

https://zenn.dev/ihasq/articles/26c6e28891c522
https://kappaseijin.hatenablog.com/entry/2023/02/10/212745

雪猫雪猫

NIP-05 Mapping Nostr keys to DNS-based internet identifiers

https://zenn.dev/snowcait/articles/e2361b3827d517

NIP-11 Relay Information Document

JavaScript
await fetch(`https://${domain}`, {
  method: 'GET',
  headers: {
    'Accept': 'application/nostr+json',
  },
});

NIP-07 window.nostr capability for web browsers

https://github.com/fiatjaf/nos2x

Alby は Nostr 単体では使えない。

NIP-15 End of Stored Events Notice

データ取得をリクエストして EOSE が来たら完了。
リレーがサポートしているかは NIP-11 の supported_nips を確認。

雪猫雪猫

EVENT

id, pubkey, created_at, sig は生成するので省略。

投稿

[
  "EVENT",
  {
    "kind": 1,
    "tags": [],
    "content": "<投稿内容>",
  }
]

REQ

自分に関する情報

タイムラインを取得する際に limit フィルターを使用すると安定しないので非推奨。
代わりに since + until で期間ごとに取得することを推奨。

[
  "REQ",
  "<subscription_id>",
  {"authors":["<pubkey-hex>"]}
]

CLOSE

[
  "CLOSE",
  "<subscription_id>"
]
雪猫雪猫

リレーリスト

Client kind: 10002 kind: 3
(deprecated)
Snort ⭕️ ⭕️
Iris ⭕️
Damus ⭕️
Amethyst ⭕️
kind: 10002
[
    "EVENT",
    "<subscription_id>",
    {
        "id": "<id>",
        "pubkey": "<pubkey>",
        "created_at": 1234567890,
        "kind": 10002,
        "tags": [
            [
                "r",
                "wss://eden.nostr.land"
            ],
            [
                "r",
                "wss://relay.damus.io"
            ],
            [
                "r",
                "wss://relay.snort.social"
            ],
            [
                "r",
                "wss://relay.current.fyi"
            ],
            [
                "r",
                "wss://relay-jp.nostr.wirednet.jp"
            ],
            [
                "r",
                "wss://nostr.holybea.com"
            ],
            [
                "r",
                "wss://nostr-relay.nokotaro.com"
            ],
            [
                "r",
                "wss://nos.lol/"
            ]
        ],
        "content": "",
        "sig": "<sig>"
    }
]
kind: 3
[
    "EVENT",
    "<subscription_id>",
    {
        "content": "{\"wss://eden.nostr.land\":{\"read\":true,\"write\":true},\"wss://relay.damus.io\":{\"read\":true,\"write\":true},\"wss://relay.snort.social\":{\"read\":true,\"write\":true},\"wss://relay.current.fyi\":{\"read\":true,\"write\":true},\"wss://relay-jp.nostr.wirednet.jp\":{\"read\":true,\"write\":true},\"wss://nostr.holybea.com\":{\"read\":true,\"write\":true},\"wss://nostr-relay.nokotaro.com\":{\"read\":true,\"write\":true}}",
        "created_at": 1234567890,
        "id": "<id>",
        "kind": 3,
        "pubkey": "<pubkey>",
        "sig": "<sig>",
        "tags": [
            [
                "p",
                "<pubkey>"
            ]
        ]
    }
]
雪猫雪猫

kind 0

  • 複数のデータを保持しているリレーがあるので最新を採用
雪猫雪猫

kind 6

リポスト。

Iris
{
    "id": "3e568e5fcb2e294b32efa9064a9297725de3e80cf5b645b382ada9748ee6856a",
    "pubkey": "83d52b4363d2d1bc5a098de7be67c120bfb7c0cee8efefd8eb6e42372af24689",
    "created_at": 1677391085,
    "kind": 6,
    "tags": [
        [
            "e",
            "c8843678966ae8d092830485111dc932a6c29e0e48343d87a98ac965b7f6c773",
            "",
            "mention"
        ],
        [
            "p",
            "a22a2372ed6e77d2391d4392be07547b9e8ba38394cae680219781d5061a8c67"
        ]
    ],
    "content": "",
    "sig": "f14fd6d0081b93766105de7d97b93d3d6519466b5ff476d72595f007f41de638ed4a7b4d087b53d945f73982d4a9a33d4495c72fbf868d8a5792aa718ef37389"
}
Snort
[
    "EVENT",
    "23201",
    {
        "id": "0c26843832b742bfa56ce88d860884d402b1e0a45739a55bcafb643c2c8a38e5",
        "pubkey": "83d52b4363d2d1bc5a098de7be67c120bfb7c0cee8efefd8eb6e42372af24689",
        "created_at": 1677390964,
        "kind": 6,
        "tags": [
            [
                "e",
                "c2e054959646f1eb4109cefc34dbb0100f6af1c7d3012a5b9a86f1c8af3ccab0"
            ],
            [
                "p",
                "d1d1747115d16751a97c239f46ec1703292c3b7e9988b9ebdd4ec4705b15ed44"
            ]
        ],
        "content": "{\"id\":\"c2e054959646f1eb4109cefc34dbb0100f6af1c7d3012a5b9a86f1c8af3ccab0\",\"pubkey\":\"d1d1747115d16751a97c239f46ec1703292c3b7e9988b9ebdd4ec4705b15ed44\",\"created_at\":1677390434,\"kind\":1,\"tags\":[],\"content\":\"NIP-58訳したった\\nhttps://scrapbox.io/nostr/NIP-58\",\"sig\":\"93516654ddae95be81209b90fba3523681a665500f91b4a4d6b39bc7c1ae05d14c46057ad2d709e235ec137170502fc0d64d700a672cd2ed41c1b0abb8140c2b\",\"relays\":[\"wss://nostr.holybea.com\"]}",
        "sig": "e10b205653efeaecd718487142d1cdfcc1b345c56c1e8808016cfe1f761bb01519c89ff93bab5016ea88245f086e9e77e3113534627c3e984ba9056bbf82fcf1"
    }
]
Damus
[
  "EVENT",
  "23201",
  {
    "id": "23c9aea1ac314eb2c23313fbc0704625d18578769075a7557f2ad494c2d90b12",
    "pubkey": "83d52b4363d2d1bc5a098de7be67c120bfb7c0cee8efefd8eb6e42372af24689",
    "created_at": 1677392543,
    "kind": 6,
    "tags":
      [
        [
          "e",
          "c30b34c922a4a9257f05816dd74ce8be06267d91deb3fea3d810d0d96c6d755c",
          "",
          "root",
        ],
        [
          "p",
          "4d39c23b3b03bf99494df5f3a149c7908ae1bc7416807fdd6b34a31886eaae25",
        ],
      ],
    "content": '{"pubkey":"4d39c23b3b03bf99494df5f3a149c7908ae1bc7416807fdd6b34a31886eaae25","content":"blastrはREST API (今の所POSTのみ) 付きのリレーだよ〜","id":"c30b34c922a4a9257f05816dd74ce8be06267d91deb3fea3d810d0d96c6d755c","created_at":1677392472,"sig":"816b67f363638ae53ca5954b6801a9fadc9f122513fc47be914b2f245c13506133cf25cd7e7a517b13a195647360f3d72489e78d6cf7ee33bf47f09565f9e279","kind":1,"tags":[]}',
    "sig": "68c5b35e8f4bd299700a02e23000cb0f70ff93fdaec556473aca3977b74e4aa8dd8297ac21753ffa6f16eff1a25772aaf7efc28a00cf89c75648b9e2f65008d6",
  },
]
雪猫雪猫

パブリックチャットクライアント

名前
作者
プラットフォーム ログイン その他
機能
備考
Amethyst
Vitor
Android TL, DM -
Coracle
hodlbod
Web
(PC, Mobile)
不要 TL, DM ログインしてないと日本人部屋が見えない
NostrChat
Talha
Web
(PC, Mobile)
必要 DM 要リレー編集
部屋検索は ID のみ
GARNET
murakmii
Web (PC) 不要 - 更新停止?
FreeFrom
MAAS㈱
iOS, Android
Web
0xchat
water783
iOS, Android
Nostri.chat
pablof7z
ぱぶ茶(仮)
Don
Web (PC) 不要 - WIP
nostter
雪猫
Web
(PC, Mobile)
不要 TL ホーム TL に流れてくる

ピン留め保存先

名前 プラットフォーム 保存先
Amethyst Android kind 3(NIP 違反)
Coracle Web
NostrChat Web
GARNET Web localStorage
ぱぶ茶(仮) Web
nostter Web kind 10001
雪猫雪猫

主要クライアントの NIP 準拠状況

Amethyst

  • ❌kind 1 をマークダウンとして描画
  • ❌リレーリストを kind 3 に保存
  • ❌チャンネルのピン留めを kind 3 に保存
  • ❌スパム報告時にいいねにあたる kind 7 を送信

Damus

  • ❌リレーリストを kind 3 に保存
  • ❌ e tag の扱いがおかしい

Plebstr

Snort

  • ❌kind 1 をマークダウンとして描画
  • 🔺ミュートを kind 30001 に保存
    • NIP-51 の先行実装のまま

Iris

Coracle

  • 🔺無言引用をリポストの代わりにしている
    • 一時期 NIP からリポストの仕様が消えていてそう扱うのも案の 1 つになっていた

Rabbit

nostter

  • 🔺kind 6 の content が空
    • 旧仕様。kind 5 と矛盾するので NIP を戻すべき
雪猫雪猫

Web クライアントのフレームワーク

クライアント フレームワーク 備考
nostter Svelte JP
Lumilumi Svelte JP
Astraea Svelte JP
Rabbit SolidJS JP
nosteen React JP
Nemesia Flutter JP
Coracle Svelte
Primal SolidJS
Hamstr Vue
Snort / Iris Tauri
旧 Iris React
noStrudel React
Flycat React
雪猫雪猫

NIPs

⭕️ : 必須
🔺 : 任意
- : 不要

NIP Relay Client
NIP-01 ⭕️ ⭕️
NIP-02 🔺 🔺
NIP-03
NIP-04 🔺 🔺
NIP-05 - 🔺
NIP-06
NIP-07 - 🔺
NIP-08 🔺 🔺
NIP-09 🔺 🔺
NIP-10 🔺 🔺
NIP-11 🔺 🔺
NIP-12
NIP-13
NIP-14
NIP-15 🔺 🔺
NIP-16 🔺 🔺
NIP-19 - 🔺
NIP Relay Client
NIP-20
NIP-21 - 🔺
NIP-22 🔺 🔺
NIP-23
NIP-25
NIP-26
NIP-28
NIP-33
NIP-36
NIP-40
NIP-42
NIP-50
NIP-56
NIP-57
NIP-65
雪猫雪猫

リレーから返ってくるエラー

["NOTICE","ERROR: bad req: std::get: wrong index for variant"]

送信した JSON のフォーマットがおかしい。

["NOTICE","ERROR: bad req: uneven size input to from_hex"]

npub1 形式ではなく hex で送る必要がある。

["NOTICE","ERROR: bad req: filter item too small"]

ids などに渡す文字列が不正。(ids: [""] など)

["NOTICE","ERROR: bad req: total filter items too large"]
["NOTICE","invalid: \"[2].authors\" must contain less than or equal to 1000 items"]

フィルターの各項目の配列を 1,000 未満にする。

雪猫雪猫

NIP-57 Lightning Zaps

流れ

TODO: invoice 生成について記載

  1. Zap に必要な情報を含んだ kind 9734 イベントを自分の pubkey で作成
  2. kind 9734 はリレーに送らずウォレットのサーバーへ送信
  3. ウォレットのサーバーが支払いを処理してレシートを発行
  4. レシート情報と元データ(kind 9734)を含んだ kind 9735 イベントをウォレットの pubkey で作成
  5. 元データに含まれるリレーへ kind 9735 を送信
  6. kind 9735 には #p(, #e) が付いているので相手に通知がいく&過去の Zap 情報も取得できる

具体的なフロー

  1. nip57.makeZapRequest でイベントを生成(未署名)
  2. 署名
  3. nip57.getZapEndpoint で kind 0 から LN URL を取得
  4. LN URL へリクエストを送る
  5. レスポンスの pr が invoice
  6. invoice を QR コードに変換して表示

WIP

参考資料

https://scrapbox.io/nostr/NIP-57

雪猫雪猫

アウトボックスモデル

NIP-65
旧ゴシップモデル

課題

  • デバイス毎にリレー構成を変更できない
  • フォロイーの write リレー全部を購読するとパフォーマンスが悪化する(全部は購読しなくていいことになっているが適切に選ぶのは困難)
  • 有料リレーしか購読していないユーザーのリレーへ書き込めない
  • リレーヒントが 1 リレー分しかないのでそのリレーにアクセスできないと詰む
雪猫雪猫

初心者のハードル

理解するのがやや難しい概念など。

  • 秘密鍵
  • リレー
  • クライアントの名前がバラバラなので同じ SNS として認識しづらい
  • クライアント毎に挙動が異なる場合がある
雪猫雪猫

クライアントのパフォーマンス改善ポイント

  • EOSE を待たずに随時反映する
    • 事前にソートするのは諦めて後から挿入する
    • ただし created_at が正しい保証はないので EOSE 以降の投稿は常に最新として扱う
  • pubkey や tags の情報は非同期で取得して後から反映する
    • リレーの Rate Limit に引っかからないように REQ をバッファする
    • UI スレッドをブロックしないように注意
  • キャッシュを適切に行う(バグの温床になりやすく諸刃の剣なので注意)
    • 自分の Replaceable Events, Parameterized Replaceable Events
    • フォロイーの kind 0
    • フォロワーかどうか判定したければフォロワーの kind 3
    • NIP-65 対応をするならフォロイー(+メンション等を送る可能性のある人)の kind 10002
    • 過度にキャッシュしないこと(ディスク容量、更新処理の複雑化)
  • 署名チェックを別スレッド (Web Worker) で行う
    • 大量のイベントの verify を UI スレッドで行うと処理が終わるまで固まる
    • CPU を食うのは変わらないので CPU 使用率が高いと固まるのは変わらない
  • 書き込みは初回の EOSE が返ってきた時点で完了扱いにする
雪猫雪猫

NIP-19

special relay(s) author
(event.pubkey)
kind
(event.kind)
npub pubkey - - -
nsec seckey - - -
note id - - -
nprofile pubkey - -
nevent id
nrelay relay URL - - -
naddr identifier (d tag)
雪猫雪猫

Nostr 開発のメリット/デメリット

メリット

  • バックエンドを用意しなくていい
    • リレーをサーバー兼データベースとして扱える
  • データが基本パブリック
    • データを取得するだけならログイン不要
  • ログインが手軽
    • ログインを肩代わりしてくれるブラウザ拡張がある (window.nostr.* を呼ぶだけ)
    • ブラウザ拡張を使わなくても秘密鍵があればいいので登録フローが不要
  • BOT 開発が簡単
    • 理由は上記の通り
  • サーバー(リレー)コストが安い
    • 自分で建てる必要はないが建てるとしてもそんなにコストはかからなさそう

デメリット

  • あらゆるデータのバリデーションをしないといけない
    • 中央サーバーがあるわけではないので不正なデータがあちこちに生成される
  • 複数リレーを相手にするので UX を良くしようとすると複雑になりがち
  • スパム対策が難しい
    • 秘密鍵を無限に生成できるため
  • プライベートなデータを扱うのはやや不得手
  • 大きい数字(フォロー/フォロワー数とか)を扱うのが苦手
    • フォロー数はイベントサイズの制限に、フォロワー数は REQ の制限に引っかかる
雪猫雪猫

複数のリレーを扱う

Nostr は複数のリレーでイベントが冗長化されることを前提としています。
これにより耐障害性を得ることができますがクライアントでイベントを取得する際にはいくつか注意が必要です。

limit フィルター

リレーによって保存されているイベントは異なります。
そのためフィルターで limit を指定して取得すると異なる期間のイベントが返ってきます。
これをそのままマージし次の期間のイベントを取得してしまうとイベントの取得漏れが発生します。

この問題を回避するためには limit を使わずに since / until で取得するか、すべてのリレーの EOSE を待って limit 件数を超えたものを削るかのどちらかにする必要があります。
ただどちらも一長一短あって、前者は最新のイベントが古かった場合に見つけるまでに時間がかかります。
後者はすべての EOSE を待つため時間がかかります。
前者は事前に最新のイベントを limit: 1 で取得することで緩和できますが常に REQ が倍に増えます(=時間がかかります)。

フィルターで取得可能なイベント数の上限

リレー毎に limit の上限が設定されています。
これは limit を指定していなくても適用されます。
そのため limit を使わずに since / until で取得する際も影響を受けます。

カウント

イベントの数(フォロワー数など)をカウントする際にすべてのイベントを取得する必要があります。
署名検証が結構重たいので数万件になるとクライアントで処理するのは事実上不可能と言っても過言ではありません。
NIP-45 Event Counts もありますがリレー毎に保存されているイベントは異なるのでまともに機能しません。(リレー毎にカウントしたい場合は有効)
id リストを返す提案をしているが動きはない模様。NIP-90 Data Vending Machine (DVM) が代替案としてあがっています。