👶

Nostrに入門してみる

2024/12/21に公開

はじめに

Nostr(Notes and Other Stuff Transmitted by Relays)は、シンプルかつ柔軟なプロトコルを基盤とした分散型のソーシャルネットワークシステムです。本記事では、Nostrの基本概念から技術的な詳細、実装例に至るまで、あらゆる側面を包括的に解説します。コードスニペットを交えながら、Nostrの仕組みを深く理解していきましょう。

Nostrとは

Nostrは、「Notes and Other Stuff Transmitted by Relays」の略で、検閲抵抗性を持つ分散型のソーシャルネットワークプロトコルです。Twitterのようなマイクロブログサービスに対する代替として設計されており、中央集権的なサーバーに依存せず、ユーザー間で直接データをやり取りすることを目指しています。

Nostrの目的

  • 検閲抵抗性: 中央管理者が存在しないため、特定のコンテンツの削除や制限が困難。
  • シンプルさ: プロトコル自体が非常にシンプルで、実装が容易。
  • 拡張性: NIP(Nostr Implementation Possibilities)を通じて機能拡張が可能。

Nostrの基本構造

Nostrは主に以下の3つのコンポーネントから構成されます。

  1. イベント(Events)
  2. クライアント
  3. リレー(Relays)

イベント(Events)

イベントはNostrプロトコルの基本単位であり、ユーザーのアクション(例:投稿、フォロー)を表現します。イベントはJSON形式で表現され、以下のプロパティを含みます。

  • id: イベントのユニークな識別子(SHA-256ハッシュ)。
  • pubkey: イベントの作成者の公開鍵。
  • created_at: UNIXタイムスタンプ。
  • kind: イベントの種類(例:1はテキストノート)。
  • content: イベントの内容。
  • tags: イベントに関連するメタデータ。
  • sig: イベントの署名。

イベントの例

{
  "id": "4376c65d2f232afbe9b882a35baa4f6fe8667c4e684749af565f981833ed6a65",
  "pubkey": "6e468422dfb74a5738702a8823b9b28168abab8655faacb6853cd0ee15deee93",
  "created_at": 1673347337,
  "kind": 1,
  "content": "Walled gardens became prisons, and nostr is the first step towards tearing down the prison walls.",
  "tags": [
    ["e", "3da979448d9ba263864c4d6f14984c423a3838364ec255f03c7904b1ae77f206"],
    ["p", "bf2376e17ba4ec269d10fcc996a4746b451152be9031fa48e74553dde5526bce"]
  ],
  "sig": "908a15e46fb4d8675bab026fc230a0e3542bfade63da02d542fb78b2a8513fcd0092619a2c8c1221e581946e0191f2af505dfdf8657a414dbca329186f009262"
}

クライアント

クライアントは、ユーザーがNostrプロトコルとやり取りするためのソフトウェアツールです。デスクトップアプリやブラウザベースのアプリケーションが一般的です。クライアントはユーザーの公開鍵を使用してイベントを作成し、リレーと通信します。

リレー(Relays)

リレーは、Nostrイベントを受信、保存、配信するサーバーです。リレーは分散型であり、誰でもホスト可能です。クライアントは複数のリレーに接続することで、データの冗長性と耐障害性を確保します。

Nostrプロトコルの特徴

シンプルさ

Nostrのプロトコルは非常にシンプルです。イベントはJSON形式で表現され、WebSocketを通じてクライアントとリレー間でやり取りされます。プロトコル自体が少ない行数で定義されているため、実装が容易であり、言語やプラットフォームに依存しません。

耐障害性

中央集権的なサーバーに依存しないため、システム全体の耐障害性が高いです。リレーがダウンしても、他のリレーに接続することでサービスを継続できます。また、ユーザー自身がリレーを選択・変更できるため、特定のリレーの障害が全体に影響を与えにくくなっています。

検証可能性

公開鍵暗号を基盤としているため、イベントの真正性が容易に検証できます。各イベントには作成者の公開鍵と署名が含まれており、第三者がイベントの信頼性を確認できます。

NIP(Nostr Implementation Possibilities)

NIPは、Nostrプロトコルの拡張機能を提案・標準化するための仕組みです。NIPを通じて、新しい機能や改善点をコミュニティ全体で共有し、実装の互換性を保ちます。

NIPの役割

  • 標準化: 新しい機能やプロトコルの拡張を標準化し、異なるクライアントやリレー間での互換性を確保。
  • 協調開発: 開発者間でのコミュニケーションと協力を促進し、Nostrエコシステム全体の発展を支援。

主要なNIPの例

  • NIP-01: 基本プロトコルの仕様。
  • NIP-02: 公開フォロワーリストとニックネーム。
  • NIP-04: プライベートダイレクトメッセージ。
  • NIP-05: 「認証済み」アカウント。
  • NIP-09: イベントの削除(作成者による)。

Nostrの利用開始方法

鍵の理解と管理

Nostrアカウントは、公開鍵と秘密鍵のペアで管理されます。

  • 公開鍵(Public Key): ユーザーの識別子として機能し、他者に公開されます。npubで始まる形式で表示されます。
  • 秘密鍵(Private Key): ユーザーの署名に使用され、厳重に管理する必要があります。nsecで始まる形式で表示されます。

鍵の生成

鍵ペアは、Nostrクライアントが自動的に生成します。以下は、JavaScriptを使用した簡単な例です。

// 使用する暗号ライブラリのインポート
import { generatePrivateKey, getPublicKey, signEvent } from 'nostr-tools';

// 秘密鍵の生成
const privateKey = generatePrivateKey();
console.log('Private Key:', privateKey);

// 公開鍵の取得
const publicKey = getPublicKey(privateKey);
console.log('Public Key:', publicKey);

鍵の保存

秘密鍵はリセット不可であるため、安全な場所に保管することが重要です。推奨される方法は以下の通りです。

  • パスワードマネージャー: KeePassや1Passwordなど。
  • ハードウェアデバイス: Ledger Nanoなどのハードウェアウォレット。
  • ブラウザ拡張機能: Connect、nos2x、Albyなど。

クライアントの選択と設定

Nostrクライアントは多岐にわたり、各クライアントは独自の機能やインターフェースを提供します。以下は、一般的なクライアントの例です。

  • Damus: セキュリティに重点を置いたデスクトップアプリ。
  • Amber: Android向けのクライアント。
  • Specter: ブラウザベースのクライアント。

クライアントの設定例

以下は、ブラウザのJavaScriptコンソールを使用してNostrリレーに接続し、ノートを取得する例です。

// リレーへの接続
const ws = new WebSocket("wss://nostr-pub.wellorder.net");

// 接続が開いたときの処理
ws.addEventListener('open', function (event) {
    // サブスクリプションのリクエスト
    const subscription = JSON.stringify([
        "REQ",
        "my-sub",
        { "kinds": [1], "authors": ["35d26e4690cbe1"] }
    ]);
    ws.send(subscription);
});

// メッセージを受信したときの処理
ws.addEventListener('message', function (event) {
    const data = JSON.parse(event.data);
    if (data[0] === 'EVENT') {
        console.log('Note:', data[2]["content"]);
    }
});

このスクリプトは、指定した公開鍵の作者によるテキストノート(kind:1)をリレーから取得し、コンソールに表示します。

Nostrの技術的詳細

WebSocketを用いた通信

NostrはWebSocketを唯一の通信手段として採用しています。これにより、リアルタイムでの双方向通信が可能となり、クライアントとリレー間でのイベントの即時配信が実現します。

WebSocketの基本的な流れ

  1. 接続確立: クライアントがリレーに接続。
  2. サブスクリプション: クライアントが興味のあるイベントのフィルターをリレーに送信。
  3. イベントの受信: リレーがフィルターに合致するイベントをクライアントに送信。
  4. 切断: 必要に応じて接続を終了。

イベントの生成と検証

各イベントは、作成者の秘密鍵で署名されます。これにより、イベントの真正性が保証されます。

イベントの署名

イベントの署名は、以下の手順で行われます。

  1. イベントオブジェクトの作成: 必要なプロパティを設定。
  2. JSON文字列への変換: イベントオブジェクトをJSON形式に変換。
  3. ハッシュ計算: SHA-256ハッシュを計算。
  4. 署名生成: 秘密鍵を用いてハッシュに署名。

署名の検証

クライアントやリレーは、公開鍵を使用して署名の有効性を検証します。これにより、イベントが真正な作成者によって生成されたことが確認できます。

実装例

ここでは、簡単なNostrクライアントをJavaScriptで実装し、リレーに接続してデータを取得する方法を紹介します。

簡単なNostrクライアントの作成

以下のスクリプトは、ブラウザのコンソールで実行可能な簡単なNostrクライアントの例です。

// リレーへの接続
const ws = new WebSocket("wss://nostr-pub.wellorder.net");

// イベントを格納する配列
const notes = [];

// 接続が開いたときの処理
ws.addEventListener('open', function (event) {
    // サブスクリプションのリクエスト
    const subscription = JSON.stringify([
        "REQ",
        "my-sub",
        { "kinds": [1], "authors": ["35d26e4690cbe1"] }
    ]);
    ws.send(subscription);
});

// メッセージを受信したときの処理
ws.addEventListener('message', function (event) {
    const data = JSON.parse(event.data);
    if (data[0] === 'EVENT') {
        const noteContent = data[2]["content"];
        notes.push(noteContent);
        console.log('Note:', noteContent);
    }
});

リレーへの接続とデータの取得

上記のスクリプトでは、以下の手順でリレーからデータを取得しています。

  1. WebSocket接続の確立: 指定したリレーURLに接続します。
  2. サブスクリプションの送信: kind:1のイベント(テキストノート)を指定した公開鍵の作者から取得するようリレーにリクエストします。
  3. イベントの受信と表示: 受信したイベントの内容をコンソールに表示し、配列に格納します。

このように、Nostrはシンプルなプロトコル設計により、短いコードで基本的な機能を実装できます。

まとめ

Nostrは、そのシンプルさと柔軟性により、分散型ソーシャルネットワークの新たな可能性を切り開いています。公開鍵暗号を基盤とした信頼性の高いイベントシステム、拡張性を持つNIP、そして分散型リレーのアーキテクチャにより、検閲抵抗性と耐障害性を兼ね備えたプラットフォームを実現しています。

本記事では、Nostrの基本概念から技術的な詳細、実装例までを網羅的に解説しました。Nostrのエコシステムは日々進化しており、今後も多くの革新が期待されます。ぜひ、Nostrを活用し、分散型ソーシャルネットワークの未来を共に築いていきましょう。

参考資料

Discussion