Skip to content
VaalRLPublic

About

🔥 去中心化、無中央資料庫的端對端加密即時通訊軟體,結合 Nostr 與 WebRTC,重現 MSN 經典體驗(震動、音樂狀態、離線留言) | Decentralized, end-to-end encrypted messenger with no central database, built on Nostr + WebRTC and reviving the classic MSN Messenger experience (nudges, music status, offline messages).

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

🌐 English · English version

Cinderous

Life is short, connect buddies.

去中心化、無中央資料庫的即時通訊軟體。融合 Nostr 協議 與 WebRTC,高度還原早期 MSN Messenger 的經典互動體驗(震動、音樂狀態、離線留言),並以「零伺服器維護成本」為營運目標。

🔥 官方網站:https://vaalrl.github.io/Cinderous/ | 🌐 在瀏覽器開啟(網頁版):https://cinderous.cinderous1.workers.dev | 💻 下載桌面版:GitHub Releases

簡介

Cinderous 透過「狀態信令」與「巨量資料傳輸」雙軌混合網路,達成極致隱私與低延遲:

  • 零知識身分:首次啟動於本機生成 secp256k1 金鑰對(Nostr/NIP-01),公鑰(npub)即為全網唯一 ID,無帳號密碼。同一裝置可並存多個身分(工作/個人)。
  • 本機優先・靜態加密:對話與私鑰留在裝置;localStorage/OPFS 上的資料以裝置金鑰(由 nsec 導出)AES-256-GCM 加密(ADR-0112),Tauri 桌面另把私鑰託給 OS 金鑰庫。明文永不上雲。
  • 端到端加密:訊息以 Nostr NIP-17/44/59 Gift Wrap 加密後才離開裝置——中繼站看不到內容,寄件者也以一次性金鑰隱藏(訊息層社交圖譜不可見;在線訂閱仍會讓 relay 得知你的聯絡人集合,見 docs/SECURITY.md 殘餘)。
  • 換裝置有路、預設純本機:預設純本機(裝置全毀=身分終止);也提供選擇性的還原路徑——本地密碼記住(Argon2id)、裝置配對搬家(P2P 全程加密)、加密雲端備份與備份碼(NIP-49),全程仍是密文,明文/私鑰不外流。

技術架構摘要

雙軌混合網路(依連線狀態動態切換):

引擎 技術 定位
引擎 A:狀態與信令 Nostr 協議(Cloudflare Workers;SQLite-backed Durable Objects) 離線留言暫存、線上狀態廣播、WebRTC 初始 SDP 信令交換
引擎 B:巨量資料傳輸 WebRTC 直連(P2P) 即時互動:震動(Nudge)、動畫快遞、大檔案傳輸,繞過中繼站達成毫秒級延遲

資料生命週期靠 NIP-40(7 天過期)與 Ephemeral Events(Kind 20000-29999 純記憶體轉發、不寫資料庫),確保 Cloudflare 永久免費額度不被耗盡。

終端平台

  • 桌面端(第一優先):Rust Tauri + React/TypeScript,可打包成 Windows(MSI/NSIS)/macOS/Linux 安裝檔;私鑰託 OS 金鑰庫、背景 WebSocket 長連線、原生檔案存取(公司儲存槽等)。同一套 React UI 也能直接跑在瀏覽器(開發與 web 執行環境),以加密 localStorage/OPFS 落地——並非捨棄網頁,而是把「明文絕不落盤」做到瀏覽器(ADR-0112)。
  • 行動端(規劃中):目前為 React Native Web(與桌面共用同一套 RelayChatBackend engine、功能高度對齊);Android 已有測試版 APK(見 Releases,Capacitor 殼,自 v0.0.14 起);🔴 它目前以 debug 憑證簽署且可被偵錯——能接上 USB 偵錯的人可讀取行程記憶體,而 session 期間私鑰必然在裡面,請勿用它處理你真正在意的對話(ADR-0335,詳見 docs/SECURITY.md)。iOS 尚未推出(另需 macOS 建置)。原生通知與背景推播待接。
  • 中繼站:Cloudflare Worker(Durable Objects 扇出),另有 Node 版可自架於容器。

完整技術規格見 PRD.md,模組邊界與資料流見 ARCHITECTURE.md。

專案結構

.
├── apps/
│   ├── desktop/        # Tauri + React + TypeScript(第一優先平台)
│   │   ├── src/        # React/TS 前端 UI 與瀏覽器 demo(demo.html / webrtc.html)
│   │   └── src-tauri/  # Rust:SQLite、Nostr WebSocket、WebRTC、金鑰管理
│   └── mobile/         # React Native + SQLite(輔助平台,預留)
├── packages/
│   └── core/           # 共用 TS:Nostr 事件、secp256k1 簽章、NIP-44/17/59、型別、Kind 常數
├── relay/              # Cloudflare Worker(Nostr 中繼站;Durable Objects 扇出,離線留言預留 DO SQLite)
└── docs/adr/           # 架構決策紀錄(ADR)

目前實作狀態

從核心協定到桌面/行動客戶端與企業模式皆已實作並通過測試(pnpm monorepo、TypeScript strict、全程 TDD)。摘要如下(詳見 docs/adr/ 逾 330 份決策紀錄):

核心通訊

  • Nostr 中繼:心跳/上線狀態、Ephemeral 扇出、離線留言(NIP-17/44/59 Gift Wrap + NIP-40 過期)
  • WebRTC:真實 P2P(RTCPeerConnection/ICE/TURN)語音/視訊通話、資料通道分塊檔案傳輸、雙軌降級與自動改道
  • 多裝置:QR/配對搬家(P2P,AES-256-GCM)、加密雲端備份(opt-in)、備份碼(NIP-49)、多身分並存;我的裝置清單+移除裝置(ADR-0322:簽章裝置目錄、加密子鑰逐台分發)——⚠ 那買到的是裝置遺失/被偷/舊機沒清,不是身分私鑰外洩的解藥,設定頁如實說明並顯示裝置金鑰的保護等級(ADR-0323)

即時通體驗(懷舊 MSN 風)

  • 敲一下(Nudge/震動)、正在輸入、正在聽(音樂狀態)、自訂狀態文字、隱身、上線狀態本機還原
  • 表情回應、收回訊息、已讀回條、@提及、內嵌回覆/對話串、限時訊息(閱後即焚)
  • 群組(建立/成員管理)、貼圖(內建+自製)、對話背景、私人便條(加密)、媒體相簿
  • 頭像廣播、本地暱稱/標籤、依聯絡人音效、訊息請求/封鎖/移除聯絡人

企業模式(公司帳號)

  • 組織名冊(管理者簽章)、邀請碼入職、公司帳號金鑰託管+離職接管

  • 公司設定(歡迎詞/上下班時間、下班自動靜音)、訊息保留天數、公司儲存槽(檔案存放)

  • 企業成員鎖公司座;桌面與行動端皆可管理(唯「企業主收儲存槽落盤」限桌面=需原生檔案系統)

  • 測試:core 639/engine 570/desktop 714/mobile 441/relay 197/website 68/theme 37/cli 30/i18n 16/brand 4(TS 共 2,716)+ Rust,含 relay↔client 端到端整合測試與行動端 jsdom 互動測試(ADR-0328)。

  • 可眼見:桌面客戶端可打包成安裝檔(見上「終端平台」),亦可直接在瀏覽器跑(/);另有端到端 demo.html/真實 WebRTC webrtc.html。

  • 完整施工計畫(現況、各平台待辦、M6–M9 進階功能、相依順序):見 docs/ROADMAP.md。

  • 使用者教學(給一般使用者:身分與鑰匙、加好友、本地密碼、換裝置三條路、搬家、常見問題):見 docs/使用手冊_User-Guide.md。

  • 前端開發指南(給社群開發者:三層架構、重用 core/i18n、消費 ChatBackend 接自己的 UI、AGPL):見 docs/前端開發指南_Frontend-Guide.md(ADR-0074)。

開發起始步驟(Getting Started)

需求:Node 22+、pnpm 10+(Rust 測試另需 stable toolchain)。

# 1. 取得原始碼並安裝 workspace 相依
git clone https://github.com/VaalRL/Cinderous.git && cd Cinderous
pnpm install

# 2. 驗證環境(全綠代表就緒)
pnpm -r test            # 全部 TS 測試(core / engine / i18n / relay / desktop / mobile)
pnpm -r typecheck       # 全部型別檢查

# 3. 起本機真實 relay,再跑桌面前端(瀏覽器開發、不需 Tauri)
pnpm --filter @cinderous/relay build:dev && pnpm --filter @cinderous/relay dev   # ws://localhost:8787
pnpm --filter @cinderous/desktop dev                                          # 前端;開 /?relay=ws://localhost:8787

想接自己的前端、加語言、做擴充或貢獻核心? 開發者入口見 .github/CONTRIBUTING.md(三層架構、環境、參與方式、規範),接前端的完整教學見 docs/前端開發指南_Frontend-Guide.md。

常用指令

任務 指令
桌面前端(懷舊即時通風格) pnpm --filter @cinderous/desktop dev,開啟 /(另有端到端 demo /demo.html、真實 WebRTC /webrtc.html)
前端建置 pnpm --filter @cinderous/desktop build
共用核心測試 pnpm --filter @cinderous/core test
中繼站測試 pnpm --filter @cinderous/relay test
本機真實 relay(開發用) pnpm --filter @cinderous/relay build:dev && pnpm --filter @cinderous/relay dev(起 ws://localhost:8787;前端開 /?relay=ws://localhost:8787 即連真實 relay)
Rust 測試 cargo test(於 apps/desktop/src-tauri/)
桌面端開發(需 Tauri 工具鏈) pnpm --filter @cinderous/desktop tauri dev
中繼站本地開發 / 部署(需 wrangler) wrangler dev / wrangler deploy(於 relay/)

🚀 在 Cloudflare Workers 架設中繼站(Nostr / WebRTC 信令 relay)

中繼站(relay/)是一個自建的最小 Nostr relay,跑在 Cloudflare Workers 上,以 Durable Object 持有所有 WebSocket 連線並做記憶體扇出。它的職責是:

  • 線上狀態廣播(心跳 Kind 20000)、正在輸入中(20001)、音樂狀態(20002);
  • WebRTC 的 SDP / ICE 信令交換(NIP-59 包封的 ephemeral 事件,Kind 21000-21999);
  • 離線留言暫存(NIP-17/59 Gift Wrap + NIP-40 過期;message-store 邏輯已實作並測試——每收件人配額、以 #p 為索引。持久化:Cloudflare 版用 SQLite-backed Durable Object(new_sqlite_classes)、Node 自架版用內建 SQL)。

WebRTC 一旦 P2P 打通,震動、檔案傳輸等資料完全走點對點,永遠不經過 relay。 Ephemeral 事件只在記憶體轉發、不落地;relay 看不到訊息明文,也看不到(經 Gift Wrap 隱藏的)社交圖譜。

前置需求

  • 一個 Cloudflare 帳號
  • Node 22+、pnpm 10+
  • 注意:本 relay 使用 Durable Objects。Durable Objects 目前需要 Workers 付費方案(約 US$5/月) 或符合 Cloudflare 當前免費方案條件——部署前請以 官方計費說明 為準。

步驟

# 1. 取得原始碼並安裝相依(會建立 workspace 連結,wrangler 打包時需要)
git clone <your-fork-url> cinder && cd cinder
pnpm install

# 2. 登入 Cloudflare(會開瀏覽器授權)
cd relay
npx --yes wrangler@4 login

# 3. 本地開發:在 ws://127.0.0.1:8787 起一個本機 relay
npx --yes wrangler@4 dev

# 4. 部署到 Cloudflare,取得 wss://cinder-relay.<你的帳號>.workers.dev
pnpm run deploy

⚠ 用 npx 而不是 pnpm dlx:pnpm 10 的建置腳本需明示同意,pnpm dlx wrangler 會以 ERR_PNPM_IGNORED_BUILDS 中止(--allow-build 在 dlx 下無效)。

部署設定在 relay/wrangler.toml:Worker 名稱、進入點 src/worker.ts、 以及 Durable Object 綁定(RELAY_ROOM → RelayRoom 類別與 migration)。wrangler 會直接打包 TypeScript,無需額外建置步驟。可在 wrangler.toml 修改 name 換成你自己的 Worker 名稱。

部署是本機動作,不經 CI

🔴 推上 GitHub 不會部署中繼站。 這個專案沒有任何自動部署流程——.github/workflows/ 裡沒有 wrangler、也沒有 Cloudflare 憑證。中繼站永遠是由人在自己的機器上跑 pnpm run deploy 推上去的。

這表示兩件事,自架者要知道:

  • 未提交的本機改動一樣推得上去。 部署內容與 git 歷史之間沒有強制連結,部署前請確認 工作樹是你想要的狀態(git status)。
  • 沒有紀錄說「線上這一版對應哪個 commit」。 想知道線上跑的是哪一版,查它的 NIP-11 version 欄位(見下方「部署完怎麼確認」)。

兩個會讓部署失敗的坑

① 有多個 Cloudflare 帳號時,wrangler 會挑錯

它可能打向你另一個帳號,回一個看起來像憑證壞掉的錯誤:

Authentication error [code: 10000]

憑證沒壞,是帳號選錯了。明示帳號 ID 就好(在 Cloudflare 後台首頁右側或網址列看得到):

CLOUDFLARE_ACCOUNT_ID=<你的帳號 ID> pnpm run deploy

② 用 API token 部署時,權限要兩項不是一項

不想每次都開瀏覽器登入(或要放進自己的自動化)的話,用 API token:

CLOUDFLARE_API_TOKEN=<token> CLOUDFLARE_ACCOUNT_ID=<帳號 ID> pnpm run deploy

token 的權限要勾兩項,缺第二項會得到和上面一模一樣的 10000 錯誤:

權限 為什麼需要
Workers Scripts: Edit 上傳 Worker
User → Memberships: Read wrangler 會查你在該帳號的角色

部署完怎麼確認

部署指令回報成功不等於站真的活著。實測兩件事:

curl https://cinder-relay.<你的子網域>.workers.dev/healthz
# 預期:ok

curl -H "Accept: application/nostr+json" https://cinder-relay.<你的子網域>.workers.dev
# 預期:一份 JSON,其中 "version" 是你剛部署的版號

/healthz 回純文字 ok。不要用首頁判斷——合體模式下首頁本來就會回網頁版, 看它回不回 HTML 是判斷不出中繼站死活的。

NIP-11 的 version 欄位是確認「線上跑的是不是我剛推的那一版」的唯一方式。

選配:中繼站與網頁前端合體成同一個 Worker(ADR-0354)

想要「一個網域、一次部署」的話,多打一個指令就好:

pnpm run deploy:unified   # 在 relay/ 目錄下

它會先建置網頁前端(apps/desktop,mode=unified)再連同中繼站一起部署。兩者的差別:

指令 結果 適合誰
pnpm run deploy 只有中繼站(預設) 想跑一座公共錨點的人
pnpm run deploy:unified 中繼站 + 網頁前端 自架給自己和朋友用的人

🔴 合體部署必須設 run_worker_first = true([env.unified.assets] 裡已經設好了)。 Cloudflare Static Assets 預設是資產優先:請求先比對資產清單,命中就直接回檔案, Worker 的程式碼根本不會執行——而中繼站的入口 / 正好命中 index.html。少了這一行, / 會回 200 與一段 HTML,客戶端不會看到錯誤,只會看到握不了手的 WebSocket。

同樣的理由,合體部署後請用 GET /healthz(純文字 ok)判斷中繼站死活,不要再看 /。

連線測試

relay 講標準 Nostr 的 REQ / EVENT / CLOSE 協定,可用 @cinderous/core 的 RelayClient:

import { RelayClient, createHeartbeat, generateSecretKey } from "@cinderous/core";

const ws = new WebSocket("wss://cinder-relay.<你的帳號>.workers.dev");
const sk = generateSecretKey();
const client = new RelayClient(
  { send: (data) => ws.send(data) },
  { onEvent: (subId, event) => console.log("收到", subId, event) },
);

ws.addEventListener("message", (m) => client.receive(m.data));
ws.addEventListener("open", () => {
  client.subscribe("presence", [{ kinds: [20000] }]); // 訂閱上線心跳
  client.publish(createHeartbeat(sk));                 // 廣播自己的心跳
});

桌面/前端可把這個 wss:// 位址設定為連線端點(見 apps/desktop/src/relay-source.ts)。

容量與成本

Ephemeral 心跳會隨上線人數扇出,請參考 docs/adr/0006 的容量估算(免費層約可支撐數十位並行使用者,並列出心跳間隔/合併/抖動等可調旋鈕)。

加上離線留言(DO SQLite,下一步)

離線留言需要持久化。DO 已是 SQLite-backed(wrangler.toml 的 new_sqlite_classes);於 worker.ts 把 RelayCore 接上以 DO SQLite 為後備的 MessageStore(行為已由 relay/src/message-store.ts 定義並測試:NIP-40 過期、每收件人配額、以 #p 為索引)。詳見 docs/adr/0005。

開發規範

本專案的開發硬規則如下:

  • 單一真實來源(SSOT):產品需求看 PRD.md,模組與資料流看 ARCHITECTURE.md。
  • Architecture First / Search First:先定位模組、再讀現有實作、最後才動手。
  • Fix First:延伸既有設計,不建立 v2、new_*、*_enhanced 平行路徑。
  • TDD:功能程式碼遵循 Red → Green → Refactor,測試即文件。
  • 本地優先與低延遲:任何變更都不得破壞訊息即時性與隱私預設。

貢獻

歡迎參與!送 PR 前請留意:

  • 先讀 PRD.md(產品規格)、ARCHITECTURE.md(模組與資料流)與 docs/adr/(既有決策)。
  • 功能程式碼採 TDD;送交前確保 pnpm -r typecheck 與 pnpm -r test 全綠(CI 也會檢查)。
  • 架構/協定/隱私取捨等決策,請新增一份 ADR(格式見 docs/adr/0000-template.md)。
  • 隱私是第一原則:任何變更都不得讓明文或私鑰離開裝置,或在未經 Gift Wrap 隱藏的情況下洩漏社交圖譜。

提交即表示你同意以本專案授權(AGPL-3.0)釋出你的貢獻。

安全性

這是一套處理端到端加密與私鑰的軟體,目前仍在開發階段、尚未經過安全稽核,請勿用於高風險場景。 發現安全問題請以私下管道回報,不要開公開 issue。完整的安全政策、加密盤點、威脅模型逐項盤點與已知限制清單見 docs/SECURITY.md。

授權

本專案採用 GNU Affero General Public License v3.0(AGPL-3.0)——詳見 LICENSE。

這代表你可以自由使用、修改與散布;但若你散布修改版,或將修改版作為網路服務(例如自架本中繼站)提供給他人, 就必須以相同授權公開你的原始碼(AGPL 第 13 條)。此選擇是為了確保所有衍生版本與自架的 relay 都對使用者保持透明、可審計,貫徹本專案的隱私與去中心化精神。

Copyright (C) 2026 Cinderous contributors.

About

🔥 去中心化、無中央資料庫的端對端加密即時通訊軟體,結合 Nostr 與 WebRTC,重現 MSN 經典體驗(震動、音樂狀態、離線留言) | Decentralized, end-to-end encrypted messenger with no central database, built on Nostr + WebRTC and reviving the classic MSN Messenger experience (nudges, music status, offline messages).

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages