Nostrに入門してみた

## はじめに

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`: イベントの署名。

#### イベントの例

```json
{
  "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を使用した簡単な例です。

```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リレーに接続し、ノートを取得する例です。

```javascript
// リレーへの接続
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クライアントの例です。

```javascript
// リレーへの接続
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を活用し、分散型ソーシャルネットワークの未来を共に築いていきましょう。

## 参考資料

- [Nostr公式GitHubリポジトリ](https://github.com/nostr-protocol/nostr)
- [Awesome Nostr](https://github.com/nbd-wtf/awesome-nostr) - Nostrに関するリソースのキュレーションリスト
- [Nostr-rs-relay](https://github.com/nostr-protocol/nostr-rs-relay) - Rustで実装されたNostrリレー
- [NIP-01: Basic Protocol](https://github.com/nostr-protocol/nips/blob/master/01.md)
- [Relay Wizard](https://relaywizard.com/) - リレーのセットアップスクリプト
- [Relay Runner](https://relayrunner.com/) - リレーの詳細なセットアップガイド