跳至主要內容
進階

中繼器運作

深入了解 Nostr 中繼器的角色、通訊協議、政策設定和選擇策略。

18 分鐘

中繼器是什麼?

中繼器(Relay)是 Nostr 網路的基礎設施。它們是 WebSocket 伺服器, 負責接收、儲存和轉發事件。與傳統社交網路不同, Nostr 中繼器彼此獨立運作,沒有中央協調。

設計哲學: 中繼器是「愚蠢的」,它們只儲存和轉發事件, 不驗證內容的真實性(只驗證簽名)。 智能邏輯由客戶端處理。

通訊協議

WebSocket 連接

連接中繼器:

URL 格式:
wss://relay.example.com  (加密,推薦)
ws://relay.example.com   (未加密)

JavaScript 範例:
const ws = new WebSocket('wss://relay.damus.io')

ws.onopen = () => {
  console.log('Connected to relay')
}

ws.onmessage = (event) => {
  const message = JSON.parse(event.data)
  console.log('Received:', message)
}

ws.onerror = (error) => {
  console.error('WebSocket error:', error)
}

訊息類型

客戶端 → 中繼器:

["EVENT", <event>]
  發布事件

["REQ", <subscription_id>, <filter1>, <filter2>, ...]
  訂閱事件

["CLOSE", <subscription_id>]
  關閉訂閱

["COUNT", <subscription_id>, <filter1>, ...]
  請求符合條件的事件數量(NIP-45)

["AUTH", <signed_event>]
  認證(NIP-42)

中繼器 → 客戶端:

["EVENT", <subscription_id>, <event>]
  推送符合訂閱條件的事件

["OK", <event_id>, <success>, <message>]
  事件發布結果

["EOSE", <subscription_id>]
  End Of Stored Events - 歷史事件發送完畢

["CLOSED", <subscription_id>, <message>]
  訂閱被關閉(通常附帶原因)

["NOTICE", <message>]
  通知訊息(錯誤、警告等)

["AUTH", <challenge>]
  認證挑戰(NIP-42)

["COUNT", <subscription_id>, {"count": <n>}]
  事件計數結果(NIP-45)

過濾器

過濾器結構:

{
  "ids": ["<event_id>", ...],        // 精確匹配事件 ID
  "authors": ["<pubkey>", ...],      // 精確匹配作者
  "kinds": [1, 7, ...],              // 事件類型
  "#e": ["<event_id>", ...],         // 包含 e 標籤
  "#p": ["<pubkey>", ...],           // 包含 p 標籤
  "#<tag>": ["value", ...],          // 任意標籤過濾
  "since": 1234567890,               // 時間下限(包含)
  "until": 1234567899,               // 時間上限(包含)
  "limit": 100                       // 最多返回數量
}

查詢邏輯:
- 同一過濾器內的條件是 AND 關係
- 多個過濾器之間是 OR 關係
- 陣列內的值是 OR 關係

範例:

// 取得某用戶最近 20 則貼文
["REQ", "sub1", {
  "authors": ["pubkey123..."],
  "kinds": [1],
  "limit": 20
}]

// 取得某貼文的所有回覆和反應
["REQ", "sub2",
  {"#e": ["event123..."], "kinds": [1]},  // 回覆
  {"#e": ["event123..."], "kinds": [7]}   // 反應
]

// 取得多人的個人資料
["REQ", "sub3", {
  "authors": ["pk1...", "pk2...", "pk3..."],
  "kinds": [0]
}]

中繼器政策

每個中繼器可以設定自己的政策,決定接受哪些事件:

常見政策類型:

1. 開放中繼器
   - 接受所有有效事件
   - 可能有垃圾訊息問題
   - 適合一般用途

2. 付費中繼器
   - 需要支付費用才能發布
   - 減少垃圾訊息
   - 可能有更好的服務品質

3. 白名單中繼器
   - 只接受特定用戶的事件
   - 私人或社群使用
   - 最嚴格的控制

4. 內容審核中繼器
   - 過濾特定類型的內容
   - 可能有人工審核
   - 符合特定社群標準

5. 專用中繼器
   - 只處理特定 kind 的事件
   - 例如:只處理長篇文章
   - 優化特定用例

NIP-11 中繼器資訊:
GET https://relay.example.com
Accept: application/nostr+json

{
  "name": "My Relay",
  "description": "A Nostr relay",
  "pubkey": "operator_pubkey",
  "contact": "[email protected]",
  "supported_nips": [1, 11, 42, ...],
  "software": "nostr-relay",
  "version": "1.0.0",
  "limitation": {
    "max_message_length": 65536,
    "max_event_tags": 100,
    "payment_required": false,
    "auth_required": false
  }
}

NIP-42 認證

認證流程(NIP-42):

1. 中繼器發送挑戰
   ["AUTH", "<challenge_string>"]

2. 客戶端簽署認證事件
   {
     "kind": 22242,
     "tags": [
       ["relay", "wss://relay.example.com"],
       ["challenge", "<challenge_string>"]
     ],
     "content": ""
   }

3. 客戶端發送認證
   ["AUTH", <signed_event>]

4. 中繼器驗證並授權

使用場景:
- 付費中繼器驗證訂閱
- 限制寫入權限
- 讀取私人內容
- 獲取特殊功能

範例回應:
["OK", "<event_id>", false, "auth-required: need AUTH"]
["CLOSED", "sub1", "auth-required: need AUTH"]

選擇中繼器

選擇考量

  • • 地理位置(延遲)
  • • 正常運行時間
  • • 速度和響應時間
  • • 支援的 NIP
  • • 政策和限制

建議策略

  • • 使用多個中繼器(冗餘)
  • • 至少一個大型公共中繼器
  • • 考慮地區性中繼器
  • • 定期檢查連接狀態
  • • 備份到個人中繼器

NIP-65 中繼器列表

NIP-65 用戶中繼器列表:

發布 kind:10002 事件:
{
  "kind": 10002,
  "tags": [
    ["r", "wss://relay1.com", "read"],
    ["r", "wss://relay2.com", "write"],
    ["r", "wss://relay3.com"]  // 讀寫皆可
  ],
  "content": ""
}

標籤含義:
- "read": 客戶端應從此中繼器讀取用戶內容
- "write": 客戶端應向此中繼器寫入與用戶相關的事件
- 無標記: 讀寫皆可

客戶端行為:
1. 查詢用戶的 kind:10002 事件
2. 根據列表決定連接哪些中繼器
3. 優化連接數量和效率

常見中繼器

中繼器 類型 說明
relay.damus.io 公開 Damus 運營的主要中繼器
relay.nostr.band 公開 搜尋和索引服務
nos.lol 公開 高效能公共中繼器
relay.primal.net 公開 Primal 運營
nostr.wine 付費 付費中繼器,較少垃圾訊息

中繼器軟體

常見中繼器實現:

strfry (C++)
├── 高效能
├── 支持大多數 NIP
└── github.com/hoytech/strfry

nostr-rs-relay (Rust)
├── 穩定可靠
├── SQLite 儲存
└── github.com/scsibug/nostr-rs-relay

relay (Go)
├── 易於部署
├── 可嵌入其他應用
└── github.com/fiatjaf/relay

khatru (Go)
├── 模組化設計
├── 易於擴展
└── github.com/fiatjaf/khatru

選擇考量:
- 效能需求
- 儲存需求
- 功能需求
- 維護能力

連接策略

客戶端連接策略:

1. 初始連接
   - 連接用戶設定的中繼器
   - 連接一些預設中繼器
   - 總數通常 5-10 個

2. 動態發現
   - 從 kind:10002 發現用戶偏好的中繼器
   - 從事件的中繼器提示發現相關中繼器
   - 從 NIP-05 發現推薦中繼器

3. 連接管理
   - 監控連接狀態
   - 自動重連斷開的連接
   - 限制總連接數

4. 負載分配
   - 優先使用快速響應的中繼器
   - 分散查詢到多個中繼器
   - 避免單點依賴

範例代碼:
const pool = new SimplePool()

// 發布到多個中繼器
await pool.publish(relays, signedEvent)

// 查詢並合併結果
const events = await pool.querySync(
  relays,
  { kinds: [1], limit: 20 }
)

問題排解

連接失敗

檢查 URL 格式、網路連接、防火牆設定。 嘗試其他中繼器確認是否為特定中繼器問題。

事件發布失敗

檢查 OK 訊息的錯誤原因。可能是認證問題、 事件格式錯誤、或違反中繼器政策。

訂閱無結果

確認過濾器條件正確。檢查是否收到 EOSE。 嘗試放寬過濾條件測試。

認證要求

收到 auth-required 錯誤時,需要實現 NIP-42 認證流程。 檢查中繼器是否需要付費或白名單。

下一步: 了解 NIP 規範概覽 探索協議的各種擴展功能。

已複製連結
已複製到剪貼簿