知識庫:抗量子加密

Nymchat 技術白皮書

Nymchat 中的後量子金鑰協商

在不使用目錄或註冊表的情況下,透過 Nostr 分發 ML-KEM-768 公鑰,並以任何公開值都無法洩露的秘密作為種子。

版本 1.0 2026年8月 適用於 Nymchat 3.74+

在通訊軟體中加入後量子金鑰交換基本上不是密碼學問題。基本組件已經標準化,且函式庫也已存在。困難之處在於,現在每個參與者都需要一個 第二 公鑰,且去中心化網路沒有明顯的地方可以放置它。本論文描述了 Nymchat 如何回答這個問題 —— 第二個金鑰從何而來、它如何傳遞給需要它的人,以及介面對於結果可以聲稱什麼 —— 並在最後一節說明該結果無法保護什麼。

1問題

Nostr 私訊是以...加密的 NIP-44,其具有兩個可分離的部分。用來混淆明文的部分——即帶有 HMAC-SHA256 標籤、並透過...進行金鑰化的 ChaCha20 HKDF (RFC 5869) — 不會受到實質 量子電腦的威脅; Grover 演算法對稱金鑰僅提供平方根級別的加速,而 256 位元足以抵銷 這點。那一半 同意 在金鑰上是橢圓曲線 Diffie-Hellman 於 secp256k1,且秀爾演算法可以直接解決離散對數問題。從對應的公鑰中復原出一個私鑰,將會追溯性地使該密鑰曾產生的每一個共享密鑰曝光。

這所產生的威脅並非要等到那樣的機器出現才會延後發生。擁有儲存能力的對手可以在今天記錄密文,並在具備解密能力時隨時進行解密。現在發送的任何在未來仍具重要性的資訊,現在就已經面臨安全性受損的風險。這正是後量子金鑰交換所要抵禦的特定攻擊,也是為什麼這項工作不能等到機器建成後才開始的原因。

1.1 本論文要回答的問題

緩解措施已為人所熟知:在進行傳統金鑰交換的同時,運行一種後量子金鑰封裝機制,因此攻擊者必須同時破解兩者才能讀取任何內容。Nymchat 使用了 ML-KEM-768 (聯邦資訊處理標準 203). 這立即引發了一個分配問題:

問題

今天若要傳訊息給 Alice,你需要一樣東西:她的 npub。若加入後量子交換,你則需要第二樣東西——她的 ML-KEM 公鑰。該金鑰儲存在哪裡?在你能傳送任何東西給她之前,該如何取得它?

npub 是獨立完整的。你可以將它寫在紙上、大聲讀出來,或從螢幕上掃描,而這就是任何人要對你進行加密所需的一切。一個 ML-KEM-768 公鑰為 1,184 位元組。它無法大聲讀出,無法放入使用者名稱中,也不適合放在一個僅有 32 位元組身份識別旁的 QR code 中。

大小是容易的部分。更難的部分在於第二把金鑰會帶來三個不同的問題,而 本文的其餘部分很大程度上是對這些問題的解答:

2設計限制

四項約束條件形塑了答案,並且在撰寫任何程式碼之前,它們就已排除了大部分顯而易見的設計。

  1. 盡可能減少秘密。 Nostr 用戶已經攜帶了恰好一個秘密, 即 nsec。每一份額外的秘密都是導致失去歷史紀錄的另一種方式,而且懂得 備份 nsec 的人將不會知道要備份其他任何東西。第 3.1 節顯示這 一點無法被直接滿足——從 nsec 衍生的後量子金鑰完全不提供 後量子保護——因此該設計僅使用一個秘密且不再增加: 一個單一的金鑰材料,每個身份生成一次,以與 nsec 相同的形式 和在相同的位置呈現,以便任何知道如何保存
  2. 沒有權威 沒有任何伺服器可以被信任用來指明哪把金鑰 屬於誰。任何這樣的伺服器都會成為訊息可以被重新導向的點。
  3. 多個裝置,一個身分。 一個 Nostr 身分通常會同時從多個用戶端使用。不論存在任何金鑰資料,最終在所有用戶端上都必須保持一致,且傳遞這些資料的路徑本身,不能被該功能所防範的對手讀取。
  4. 不接受談判。 任何關於「你支援哪些加密演算法?」的帶內交換,都是攻擊者可以透過剝離來強迫選擇較弱選項的攻擊面。設計必須不留任何可降級的空間。

3一個獨立的根秘密

關鍵決策在於 ML-KEM 解封裝金鑰是由不會被任何公開值洩露的金鑰材料所種子化。每個身分都會獲得一個僅生成一次的根秘密:

pqRoot   = 32 bytes from a CSPRNG, generated ONCE per identity

seed     = HKDF-Expand(
               HKDF-Extract(salt = "nym-pq-root-v2", IKM = pqRoot),
               info = "mlkem768/epoch/" || epoch,
               64 bytes)

(ek, dk) = ML-KEM-768.KeyGen(seed)

根節點呈現給使用者的方式就像 nsec 一樣: bech32 帶有人類可讀的前綴 nympq, 所以讀起來是 nympq1…,在身分畫面中顯示於 nsec 旁邊,透過相同的揭示互動功能呈現,使用相同的控制項進行複製,從未被記錄,也從未以明文形式傳送到任何地方。它不是密碼,也不是登入資訊。它是使用者可以儲存的金鑰材料,與 nsec 完全相同。

鹽值是刻意進行領域分離的,因此任何其他秘密都不可能推導出相同的金鑰對。 這 epoch 計數器驅動旋轉(第 9 節)。

3.1 為什麼無法從身分金鑰推導出該金鑰

很明顯的設計是利用使用者已有的秘密來為金鑰對提供種子:

seed = HKDF(salt = "…", IKM = nsec)          // do not do this

它之所以具有吸引力,是因為有四個真實的原因:不需要備份新的內容,因為 nsec 本身就是備份;每個裝置在設計上即達成一致,不會有同步協定出錯的問題;每個身分僅需一個可替換的公告,且顯然是正確的,因為裝置之間對於金鑰不會產生分歧;以及金鑰在發布前就已存在,因此客戶端可以在首次執行時將內容加密給自己。

這四種好處都毫無價值,原因只有一個。對已發布的 npub 執行 Shor 演算法會得出 nsec。種子衍生是基於 nsec 的公開演算法。因此,破解了傳統部分的對手,只需執行與其他人相同的 HKDF,即可重建後量子部分。面對「先收集、後解密」——即該功能旨在防禦的唯一威脅——以這種方式衍生的密鑰完全沒有任何作用。

萬物所遵循的規則

ML-KEM 解封金鑰必須源自於既無法從 nsec 推導,也絕未在僅限傳統加密的情況下傳輸過的熵。

該規則的第二個子句與第一個子句具有同等的重要性。一個獨立生成的秘密,若隨後透過普通的 NIP-44 訊息在用戶的設備之間進行同步,這只是增加了額外步驟的同一種失敗:攻擊者今天記錄該訊息,稍後恢復其經典金鑰,根源就會隨之瓦解。這就是為什麼下一節中的路徑會是現在這樣的樣子,以及為什麼其中沒有一個是「將其發送到您的其他設備」。

3.2 將 root 權限套用到使用者的其他裝置

第 2 節的約束條件 3 ——「一個身分,多個裝置」—— 在這裡無法透過算術來滿足,因為重點在於金鑰並非裝置之間既有共享內容的函數。它必須改由傳輸來滿足,且只有唯一的一條路徑:使用者移動了 nympq1… 編寫自己的程式碼

根顯示為 nympq1… 除了 nsec 之外,還有第二台裝置 將其貼入同一個面板中。這就是整個機制。未獲授權代碼的裝置 無法參與,正如第 4.2 節所述。

手動傳輸是刻意設定的底線,而非初步步驟。第 3.1 節的規則規定,根密鑰絕不能在僅限古典加密的情況下傳輸,而任何會使此過程自動化的機制——例如透過中繼進行同步、將其封裝至身份密鑰——都正是在違反這一點。用戶所執行的步驟,正是該保護之所以真實有效的原因。

該格式為封裝路徑(wrapped path)留下了空間:一條記錄可能攜帶一系列封裝,每一項都是在使用者可以在另一台設備上重現的密鑰(例如 Passkey PRF 輸出)下的 AEAD 封包。目前尚無產品提供此功能,在有產品提供之前, nympq1… 代碼是 唯一的橫越方式。 第 10.1 節說明了這需要多少費用。

紀錄本身位於其自身的設定類別中, nymchat-pq-root,並且像其他類別一樣進行同步。即使不攜帶任何包裝,它也完成了必要的任務:它的存在讓第二台裝置得知此身分已具備根節點,這正是阻止其鑄造一個競爭根節點的原因(第 3.3 節)。

設計筆記:無法使用新金鑰的那一個類別

nymchat-pq-root 類別必須 不是 被加密至由根衍生出的 ML-KEM 金鑰。該列持有根的唯一副本,因此將其密封在一個由根衍生出的 金鑰之下,就像是一個金鑰就在盒子裡的鎖:沒有任何設備能開啟它,包括 寫入它的那個設備。它使用經典方式進行密封——NIP-44 對自身——否則就完全不密封。這是 設計中唯一接受僅限經典保護的地方,而且它也負擔得起:該列 目前並不攜帶根,僅攜帶存在一個根的事實。

其他所有的設定類別都可以且應該使用源自根的鍵。這是唯一的 例外,而這是一個關於循環性而非強度的例外。

3.3 生成與採用

在啟動時,持有持久的身份,客戶端依此順序運作:

  1. 尋找現有的 nymchat-pq-root 紀錄。
  2. 已找到紀錄,且此裝置可以解開它 — 採用它並宣布 此身分具備後量子能力。
  3. 已找到紀錄,且此裝置無法解開它 — 不要生成新的根,且完全不要發佈任何公告。提示使用者透過輸入來連結此裝置 nympq1… 來自已有該程式碼之裝置的程式碼。
  4. 無紀錄 — 生成根,發布紀錄,公告,並顯示 nympq1… 將代碼發送給用戶一次,以便他們可以保存。

第三步是最容易出錯的一步,這也是為什麼要明確寫下順序 而不是交由每個實作自行決定。兩個各自決定生成根的裝置會產生 在同一個身分下的兩個獨立根,而這正是此順序旨在 防止的失敗情況。

4能力發佈

衍生金鑰對的公鑰部分會被發佈為一個可定址的 NIP-01 事件 — 類型 30078, 已標記 nym-pq:

{
  "kind": 30078,
  "tags": [
    ["d", "nym-pq"],
    ["t", "nym-pq"],
    ["expiration", "<unix seconds>"]
  ],
  "content": {
    "v":     2,
    "alg":   "mlkem768",
    "nym":   1,
    "epoch": 0,
    "pk2":   "<base64url ML-KEM-768 encapsulation key>",
    "exp":   <unix seconds>,
    "devices": [ ... ]
  }
}

「可尋址」意味著中繼器針對每一組 (kind, pubkey, d-tag) 僅保留一個事件,因此重新發布會原地取代先前的公告。因此,每個身份都恰好只有一個目前的記錄,這使得「查詢 Alice 的金鑰」成為一次單一且明確的擷取,而非需要進行整合比對的列表。

簽名即是約束。 此事件由身分金鑰簽署,因此「此 ML-KEM 金鑰屬於此 npub」的聲明其強度與 npub 本身完全相同。替換不同的封裝金鑰需要偽造 secp256k1 簽章。若攻擊者能做到這一點,就無需費心處理 KEM。

公告會過期。 一個七天的 NIP-40 每 24 小時重新發布一次的到期機制,使記錄維持為關於一個仍在運行而非曾經運行的客戶的陳述。根本身永遠不會過期;只有關於它的聲明會過期。

失效的公告讀起來就跟從未攜帶金鑰的公告完全一樣:同儕會發送普通的 NIP-44,每個登入端都能讀取,且客戶端會在下次重新發布時,於下一次連線時恢復後量子交換。因此,中斷通訊超過一週的代價是,在此間隙發送的訊息將失去保護——它們是使用傳統加密而非抗量子加密——且除此之外沒有其他代價。傳遞不受影響,已收到的內容不會變得無法讀取,且返回時無需採取任何行動。

那種不對稱性是失效的原因,而非其偶然產生的結果。若沒有這種機制,記錄的壽命將會超過其所指稱的金鑰:一個被清除、重置或更換了根金鑰的裝置,會留下一個持續存在的指令,要求將資料封裝到一個已無人持有的金鑰中,而在此金鑰下發送的訊息將會丟失,且雙方都不會收到錯誤提示。七天的期限限制了這個時間窗口,讓轉發器可以自行捨棄該記錄,而不必依賴用戶端來察覺。

鍵欄位命名了其格式。 領域是 pk2,而且該數字是合約的一部分,而非裝飾:它標示了該金鑰所能搭配使用的酬載格式。無法辨識該欄位的讀取者會得出「Nymchat 客戶端,無後量子金鑰」的結論,並發送普通的 NIP-44,這是一切登入端都能讀取的格式。這是正確的失敗方向,且值得將格式編號存在的目的作為一項規則來陳述:無法辨識的能力聲明必須是以犧牲保護為代價,絕不能以犧牲傳遞為代價。一個對等節點無法使用的金鑰比完全沒有金鑰還要糟糕,因為它所產生的訊息會消失,且雙方都不會收到錯誤訊息。

4.1 缺失是有意義的,且為三值的

一個微妙但重要的細節:該公告是由每個 Nymchat 用戶端發佈的,而不僅僅是 具備後量子能力的用戶端,且 key 欄位是選填的。這產生了三個可區分的 狀態,而非兩個:

已觀察方法發送行為
帶有金鑰的公告 Nymchat, 具備後量子能力 混合
公告,完全沒有密鑰 Nymchat,僅限古典模式 — 後量子已關閉,或是一個尚未與身分根 連結的裝置 古典 NIP-17
沒有公告 未知的客戶端。可能是任何 Nostr 或 Bitchat 用戶。 古典,加上相容性包裝

將第三列合併至第四列將會是重大的損失。無金鑰宣告是一種已簽署的聲明,證明發送者正在運行 Nymchat,這讓發送路徑可以跳過原本在面對無法識別的對象時必須包含的推測性跨協定封裝。

4.2 無法開啟根目錄的裝置保持沉默

宣告是可替換的:每個身分僅限一個事件,採「最後寫入者勝」原則。這正是單一紀錄設計得以運作的原因,但同時也是為何未連結的裝置若進行發布會具有危險性的原因。一台宣告了其自行鑄造之金鑰的裝置,將會覆蓋掉真實的紀錄,並導致所有對等節點開始使用其他裝置無法開啟的金鑰進行加密。

因此,一個知道根(root)存在但無法開啟它的裝置,完全不會發布任何公告。它並未損壞,也沒有被應用程式鎖定:它仍會讀取所有它擁有金鑰的消息,並仍以傳統方式發送,同時提示使用者進行連結。對於一個無法代表該身份發言的裝置而言,保持沉默才是正確的行為。

5探索與發送決策

用戶端透過兩種方式獲取對等節點的密鑰。持續性訂閱涵蓋了使用者實際通訊的對象——即開啟的對話與群組成員——因此他們的公告會如同一般事件般送達。對於首次接觸的對等節點,會在發送時執行一次性查詢,時間限制為 2.5 秒;若無法解析,訊息將以傳統方式傳送,這是加入後量子技術前就已存在的行為,而非一種新的故障模式。

負面結果會快取十分鐘而非永久快取。一位連結了新裝置,或從瀏覽器擴充功能登入轉向使用本地金鑰的使用者,會在對話過程中具備後量子能力,而永久快取的「否」將使他們在該公告的有效期內一直使用傳統加密。

5.1 為什麼不存在降級攻擊

路由決策歸結為一個問題:

pq = (we hold a signed, unexpired ML-KEM key for this recipient)

沒有能力協商、沒有支援的演算法列表,也沒有攻擊者可以透過清除欄位來強制進入較弱路徑。暫緩發布公告。 協商,且已 簽署。被剝離或扣留公告的失效模式,是訊息回歸 傳統模式——即此功能出現前的現狀——而非混合式訊息被降級 為可偽造的內容。

反之亦然,且更為重要:客戶端發送混合式 只有 當它持有金鑰時, 且持有金鑰即是接收者能夠解封裝的證明。不存在任何一種訊息 被以後量子方式發送給無法讀取它的人的狀態。

6混合式結構

Nymchat 並非取代 NIP-44。它是將其封裝。一個未經修改的 NIP-44 密文是內 層,而 ML-KEM 則在其周圍進行外部 AEAD 加密:

inner = nip44_encrypt(plaintext, conversation_key(sender, recipient))

info  = "nymchat-pq2" || sender_secp_pk || recip_secp_pk || kem_ct || recip_kem_pk
prk   = HKDF-Extract(salt = "nymchat-pq2-v1", IKM = kem_ss)
key   = HKDF-Expand(prk, info || "key",   32)
nonce = HKDF-Expand(prk, info || "nonce", 12)

outer = ChaCha20-Poly1305(key, nonce, plaintext = inner, aad = info)

payload = "pq2." || base64url(kem_ct) || "." || base64url(outer)

必須同時取得兩個秘密才能解讀訊息:外層僅會產生一個 NIP-44 加密文本,而要解開它需要傳統的 ECDH。能破解 secp256k1 的量子攻擊者雖能取得內層密鑰,但仍需面對 ML-KEM;若破解了 ML-KEM,則僅是剝離了外層,而 NIP-44 依然屹立不搖。每一項輸入都各司其職:

每次訊息都會重新執行封裝。接收者的 ML-KEM 金鑰是長期使用的,但 每條訊息都攜帶一個獨立的密文,因此也是一個獨立的 kem_ss. 這就是為什麼推導 nonce 而非將其隨機化顯得合理的原因: ChaCha20-Poly1305 會因為重複使用 (金鑰, nonce) 配對而破解,而這裡的金鑰本身對每條訊息都是新的,因此不會有任何配對會重複。

6.1 為什麼各層保持分離

另一種做法是將兩個秘密混合成一個單一的對話密鑰,並將其交給 NIP-44:

ck = HKDF-Extract(salt = "…",
                  IKM  = ecdh_x || kem_ss || …)          // do not do this

那個構造在密碼學上是健全的。它有一個結構性問題:它需要 ecdh_x, ECDH 輸出的原始 x 座標,作為金鑰材料 — 以及一個瀏覽器 擴充功能 (NIP-07) 或遠端簽署者 (NIP-46) 從不回傳該值。它代表呼叫者執行 NIP-44 並回傳密文,而這正是將金鑰存放在應用程式無法觸及之處的全部目的所在。

因此,混合秘密會排除所有將身分金鑰保存在簽署者中的登入方式,也就是說,這會排除最謹慎的使用者,且無論在金鑰衍生上做多少工作都無法改變這一點。分層處理消除了這種依賴性:NIP-44 保持完整,並由任何持有身分金鑰的對象(包括簽署者)產生,而 KEM 部分則是從用戶端直接持有的復原碼中計算出來的。簽署者登入在雙向皆為普通的後量子參與者。

代價在於頻寬,而且相當可觀。ML-KEM 密文為 1,088 位元組,並附帶在每條訊息中,經 base64url 編碼後變為 1,451 個字元;外部 AEAD 增加了 16 位元組的 Poly1305 標籤,並將其封裝的 NIP-44 有效負載擴大了三分之一。一條 50 個字元的訊息會從 176 位元組增長到 1,712 位元組,而一條 2,000 個字元的訊息則從 2,820 位元組增長到 5,238 位元組。無論訊息有多短,每條訊息的最低門檻大約是 1.5 KB,這是每次重新進行封裝而非重複使用共享密鑰所付出的代價。

6.2 自描述負載

pq2. 前綴使部署具備增量性:它是自我描述的,因此客戶端是藉由檢查有效載荷來選擇解密路徑,而非透過信任標籤或記憶對端所支援的功能。無法辨識前綴的讀取器會直接無法開啟該有效載荷,而非誤讀它;此外,在任何一方具備後量子能力之前所封裝的消息,仍可作為一般的 NIP-44 直接讀取,無需進行遷移。

隱性拒絕

ML-KEM 的解封裝(decapsulation)設計為永不失敗:給定格式錯誤的密文時,Fujisaki-Okamoto 轉換會返回一個確定性的偽隨機秘密,而非錯誤。因此,錯誤的金鑰完全不會在 KEM 層級顯現,而是以 NIP-44 內部的 HMAC 失敗形式呈現,這與傳統金鑰錯誤的方式相同。調用者對兩者的處理方式完全一致,因此該失敗不會攜帶任何區分訊號。這也是第 9.1 節中的候選清單得以運行的原因:客戶端依序嘗試每個金鑰,並由 NIP-44 判定哪一個是正確的。

6.3 禮物包裝的兩層

A NIP-17 私訊是一個 NIP-59 禮物包裝:一則未簽署的傳聞,先使用發送者的身份金鑰(種類 13)進行密封,接著再使用每則訊息生成的拋棄式金鑰(種類 1059)進行包裝。在直接持有身份金鑰的登入情況下,Nymchat 會將這兩個層次進行混合,且每一層都擁有各自的封裝。

A hybrid NIP-59 gift wrap: the rumor sealed under the sender's key, that seal wrapped under a per-message ephemeral key, both layers carrying a NIP-44 ciphertext inside a post-quantum AEAD. kind 1059 — wrap  ·  signed by a per-message ephemeral key content = pq2.<kem_ct>.<aead>   inner = NIP-44(eph, recip)   outer key = ML-KEM(recip) kind 13 — seal  ·  signed by the sender's identity key content = pq2.<kem_ct>.<aead>   inner = NIP-44(sender, recip)   outer key = ML-KEM(recip) rumor — unsigned the message: kind, content, tags, author pubkey unsigned on purpose — a signature would be portable proof
兩個加密層皆為混合式,各自具有獨立的 ML-KEM 封裝。 外層使用一次性密鑰進行金鑰化,因此封裝不會洩露發送者。

簽署者登入僅獲得外層。印章是由簽署者以一般的 NIP-44 形式產生的 —— 應用程式永遠不會看到生成它的密鑰 —— 因此無法在原地進行混合化。這對於所討論的攻擊毫無防禦作用:印章只能透過封裝存取,而封裝正是記錄器所儲存的內容。持有記錄流量的攻擊者必須在印章變得可見以進行攻擊之前,先破解 ML-KEM。

7群組訊息與部分覆蓋

群組訊息並非單一密文。它是將相同的明文分發給每位成員,且每一份副本都針對該成員自身的 ML-KEM 金鑰進行封裝。已發布金鑰的成員會獲得混合封裝;未發布金鑰的成員則會獲得傳統封裝。

這會造成一個簡單的實作方式會處理錯誤的會計問題。如果十個成員中有八個 收到混合副本,訊息是 不是 十分之八受到保護。對手需要一個 在全部十個中皆相同的明文古典副本,因此訊息僅在...時受到保護 每一個 文案是。

因此,Nymchat 在分發過程中追蹤每條訊息的覆蓋範圍——該計數僅在封裝建立時才可知——且徽章顯示「對 10 名成員中的 8 名具備抗量子能力」,而非聲稱訊息已受到保護。在無法取得計數的情況下,這對於一個 收到 群組訊息(僅由發送者計算擴散次數),介面回報的是部分而非全面的保護。僅憑自己持有的副本就聲稱擁有全面保護,對於整則訊息而言會是一種誇大。

7.1 護盾報告內容

盾牌述說著關於...的真相 訊息, 而非關於發送它的軟體:

裁定是在訊息封裝時記錄的,而非事後根據同儕的宣告重新計算。既有的密文不可能變得比原本更受保護,而一個根據新宣告來重新呈現舊訊息的介面,將會對中繼器上的位元組做出錯誤的陳述。

上述群組規則是在此基礎上疊加,而非取代:只有當每位成員的副本都受到保護時,群組訊息才算完全受保護;而收到的群組訊息若沒有覆蓋計數,則顯示為「部分」。

8寄給您自己的副本

客戶端存儲的幾項內容都是根據用戶自身的身份進行加密的:同步設置、對話列表、群組金鑰以及訊息存檔。這些內容比大多數單條訊息包含更多關於用戶的信息,因此,無論訊息本身加密得有多麼嚴密,若讓這些內容保持傳統狀態,它們將會成為最脆弱的存儲物件。它們使用相同的混合加密方式,並封裝在用戶自身的根派生金鑰中 —— 除第 3.2 節中所述的唯一例外,即... nymchat-pq-root 類別本身,無法被封印在 一把唯有它才能產生的鑰匙之下。

這些物件是設計最關鍵之處。一個設定 blob 或是一個存檔列會在同一個地方存放多年,這正是「先收集、後解密」型攻擊者所蒐集的事物形態——其重要性遠勝於任何單一訊息,因為訊息在使用者心中至少是轉瞬即逝的。

這裡規範的是格式而非金鑰。一份附有收件地址的副本必須能被...讀取。 每一個 帳戶上的裝置,因此每個裝置都會在其公告所攜帶的名冊中,宣告其可以開啟的內容,而帳戶僅會寫入所有裝置都能讀取的內容。寫入任何其他內容都會導致裝置無法存取自身的設定——第 3.2 節透過其他方式,從不同方向避免了同樣的靜默失敗。

一個值得說明的限制

一個持有身分但未持有根的裝置,無法開啟任何使用根派生密鑰封裝的內容,包括其自身的設定。這是一個刻意的結果,而非疏忽,這也是為何第 3.3 節設有此類裝置提示要求進行連結,而非鑄造一個新的根:第二個根不會讓該資料塊 (blob) 變得可讀,它只會將該身分的密鑰材料拆分為二。在使用者進行連結之前,裝置會繼續運作——它讀取其擁有密鑰的部分,並以傳統方式傳送。

驅動瀏覽器擴充功能或遠端簽名器 (NIP-46) 的裝置並不持有可用於衍生的 nsec,但它持有恢復碼,而在第 6.1 節的分層結構下,這就是後量子部分所需的一切:簽名器照常生成 NIP-44 層,而用戶端則自行對外層進行加密。這樣的登入在雙向中都是一個普通的參與者。

9旋轉

epoch 推導中的計數器使得在不需要新金鑰材料的情況下實現輪轉成為可能。遞增該計數器即可從相同的根金鑰產生一組新的金鑰對,並重新發布公告;對等節點會從可替換的記錄中獲取新金鑰。因此,輪轉不需要使用者再次記錄任何內容:根金鑰在每個身分中僅需生成一次,而由週期(epoch)來執行輪轉。

9.1 舊的 epoch 會被保留,且不會重新加密任何內容。

金鑰輪換時不會進行任何重寫。用戶端會從當前紀元向下構建至紀元減 3 的解密候選對,因此在輪換前不久封裝的消息,仍可使用發送時的現行金鑰對進行解密。

那個窗口是讓輪換得以安全進行的關鍵:若沒有它,每一次輪換都會導致任何正在進行中的事物被擱置。任何已經封裝的內容在該身分的整個生命週期內都保持可讀,因為對使用者而言,無法再開啟的訊息,比起其保護措施無法追溯改進的訊息,情況要糟糕得多(第 10.5 節)。

10這不保護的內容

一篇僅列出設計所能達成之目標的論文並非在描述一個系統,而在介面中誇大安全屬性比省略它更糟。以下內容不在本建構的防護範圍之內。

10.1 根是第二個秘密,一旦丟失便無法復原。

這就是該設計真正的代價。第 2 節中關於使用者應僅需保存單一項目的限制無法達成:單憑 nsec 無法重建後量子金鑰,因為其核心目的在於任何公開值或其他秘密都無法使其暴露。如果沒有任何裝置持有根 (root),且第 3.2 節中的任何封裝 (wraps) 都無法被開啟,則密封於根衍生金鑰下的資料將無法復原。此設計不設託管 (escrow),且刻意不設置任何可以持有託管資料的權威機構。

若手動傳輸是唯一的途徑,這比初看之下更顯尖銳。一位從不複製...的使用者 nympq1… 代碼在任何地方都只有一個副本,存放在單一設備上,失去該設備就會失去所有與之密封的後量子訊息、設定數據塊和存檔列。這 nsec 沒有幫助;那是整個設計所依賴的屬性。

任何隨後添加的封裝路徑都必須達到與手動路徑相同的標準。攻擊者會攻擊最便宜的可用路徑,因此一個方案的價值取決於其最弱恢復途徑的價值——例如,一個易於記憶的密碼短語會使整個方案的安全性僅等同於該密碼短語的強度,而封裝行正是「現在收集,稍後解密」的攻擊者所收集,並在閒暇時進行離線破解的目標資料。

10.2 身分驗證,不同於機密性

Nostr 中的每個簽名都是基於 secp256k1 的 Schnorr 簽名,這點在此保持不變。擁有量子電腦的攻擊者可以偽造簽名,並即時冒充使用者。混合金鑰交換所能抵禦的是「現在收集,稍後解密」(harvest-now-decrypt-later):攻擊者即便在今天記錄流量,事後也無法解讀。它無法讓訊息在面對已經擁有該機器的攻擊者時變得不可偽造。這種區別被刻意引入應用程式中——掛鎖圖示代表身分驗證,盾牌圖示代表機密性,兩者是不同的符號,因為一則訊息可能僅具備其中一種特性。

這也限制了第 4 節中聲明所能承諾的範圍。npub 與 ML-KEM 金鑰之間的綁定是透過 secp256k1 簽章來實現的,因此能夠偽造簽章的攻擊者可以替換成他們自己的金鑰。面對未來攻擊者的機密性並不等同於面對其真實性。

10.3 中繼資料

禮物包裝隱藏了寄件者,收件者超出了單一 p 標籤、類型以及內部訊息的時間戳記。它並未隱藏事件的存在、其大小,或中繼器接收到它的時間。本設計的任何部分均未解決流量分析問題。

10.4 離線網格

Nymchat 的藍牙網狀網路傳輸是一個具有其自身握手機制的獨立協定,且不 包含在本研究範圍內。透過網狀網路傳輸的訊息是傳統式的。

10.5 訊息已傳送

當任一方仍處於古典階段時所記錄的密文將永久保持為古典狀態。它 已經存在且無法被重新封印。保護始於雙方皆持有 後量子金鑰的那則訊息,而非功能開啟的那一刻。

11已考慮的替代方案

方法為什麼不
從身分金鑰推導出後量子金鑰 拒絕。該推導是基於 nsec 的公開演算法,量子對手能從已發布的 npub 中恢復出 nsec,因此破解了古典部分便也隨之破解了後量子部分。它解決了本文中的所有分配問題,卻對任何人都不具防禦力。第 3.1 節。
透過 NIP-44 將根傳送到使用者的其他裝置 以不同的形式因同樣的原因被拒絕。在僅限傳統加密下傳輸的根,可以被任何記錄了該訊息並在日後破解其金鑰的人所還原,而這正是該根旨在防範的對手。
在每個裝置上分別生成的 ML-KEM 金鑰對 駁回。設備將持有不同的解封密鑰,而每個身分僅有一個可替換的公告,無法承載所有密鑰。同儕會針對最後發布的密鑰進行加密,導致其他所有設備都無法讀取結果。每個身分僅使用一個根密鑰,並透過第 3.2 節的路徑進行移動,這正是避免此問題的做法。
將根部以 PIN 保護 駁回。對於持有封裝列的離線攻擊者而言,四位數 PIN 碼大約僅具備 13 位元的強度。將其與兩個 256 位元的路徑並列,會誤導對最弱封裝強度的評估。
擴展 npub 以攜帶兩組金鑰 1,184 位元組不是一個可分享的識別碼,且會破壞所有現有 Nostr 客戶端對於定義為 32 位元組地址的解析。
一個關鍵的目錄服務 重新引入了網路旨在避免的權威。誰回答了 查詢,誰就能決定誰可以讀取訊息。
將金鑰附加到每則訊息 無法解決任何問題:發送者需要 收件人的 第一條訊息之前的金鑰,而這正是因為沒有先前的訊息可以承載它。
帶內能力協商 造成了降級攻擊面。能夠剝離權限標記的攻擊者會強制 進入傳統路徑。
僅限後量子,無古典部分 捨棄了對 secp256k1 數十年的分析,以換取一個年輕得多的原語。混合方案僅在...時才會失敗。 兩個都 失敗。

12實作對等性

Nymchat 為此構建提供了兩個獨立的實作——一個是用於 Web 應用程式的 JavaScript 版本,另一個是用於行動應用程式的 Dart 版本,其中包括一個從零開始開發的 ML-KEM-768 移植版。同一個原語(primitive)的兩個實作通常是一種風險,因此它們被用來互相對照驗證,而非單純信任它們會達成一致。

Dart ML-KEM 的實作已針對官方進行驗證 NIST 自動化密碼驗證協定 ML-KEM-768 的已知答案測試 (ML-KEM-*-FIPS20325 個金鑰生成、25 個封裝與 10 個解封裝案例,作為獨立的測試套件執行。這些是 NIST 發佈用以驗證實作的向量,因此通過這些測試是該移植版本正確的證據,而不僅僅是兩個用戶端彼此達成一致的證據。除此之外,一個共享的測試向量定置(fixture)——包含種子衍生、封裝、兩種有效負載格式以及完整的包裝(gift wraps)——是從 JavaScript 參考實作生成的,並由兩個測試套件共同進行檢查。根機密(root secret)是擴展該定置而非取代它:從根到種子、從根到金鑰對、根的公開指紋,以及外層衍生的金鑰、隨機值(nonce)和關聯資料,皆為各自獨立的向量,因此這兩個用戶端對於一個...不會產生分歧。 nympq1… 程式碼是指或與訊息密封時所使用的位元組有關。任一實作中的分歧都會導致編譯失敗,而非產生其他用戶端無法開啟的訊息。

在這些之中,指紋是值得一提的。它讓裝置能夠分辨「這是我持有的根」與「這是另一個不同的根」;若一個用戶端無法複製另一個用戶端的指紋,它會將一個完全正常的紀錄誤讀為完全沒有紀錄——接著,根據第 3.3 節,鑄造第二個根並導致身份分裂。正因如此,它成為了一個攻擊向量。

Nymchat 是 開 源 在 AGPL-3.0 之下。這裡描述的加密核心是 js/nym-crypto.jsjs/modules/pq.js 在網頁用戶端,以及 lib/core/crypto/lib/features/identity/pq_registry.dart 在 行動用戶端中。

若要查看較短的非技術性解釋,請參閱 關於抗量子加密的知識庫頁面.