跳轉到

D-20261030-02|Ed25519 改 RFC 8032 嚴格驗簽

背景與驅動力

產品所有 Ed25519 驗簽都經 src/key-manager/ed25519.ts 的 verifyMessage,內部呼叫 @noble/curves(2.2.0)的 ed25519.verify 且未傳選項。該 wrapper 的預設是 zip215: true:接受非正規編碼(y ≥ p)的公鑰與 R,也不拒絕小階公鑰。驗簽方程式為 cofactored。

量測回合訊息 WebCrypto 驗簽器時(issue 001118),語料比對顯示 noble 接受、WebCrypto 拒絕的案例在 Chromium 有 76 筆、Firefox 有 80 筆,類別為小階點、非正規編碼與全零公鑰;1000 筆合法簽章兩者一致(issue 001128)。全零公鑰解碼為 4 階點,配全零簽章或 S=0 時對任意訊息都可能驗過。只要任何流程接受玩家自選的公鑰,同一把鑰就能讓多組相互矛盾的訊息同時「驗過」,破壞簽章的不可否認性。

驗簽語意是共識行為:同一則訊息在不同 peer、不同 repo 的判定必須一致。pinning 與 signaling 都以 vendor:sync 收錄 main 的 key-manager/ed25519.ts(pinning 另收帳本驗證檔,signaling 另收註冊證明與信令封包驗證),必須同步改變。

2026-10-30 維護者裁決:main、pinning 與 signaling 三個 repo 一律改 RFC 8032 嚴格驗簽(zip215: false),身分建立入口另拒小階點與全零公鑰;回合簽章鑰宣告在使用前同樣拒收,使 WebCrypto 與 noble 的接受集合相同;pre_launch 不保留相容;同意同步 pinning 與 signaling 的 vendored 程式。

考慮過的選項

  • 全面改嚴格驗簽,並在身分入口拒收小階與全零公鑰(採納)。驗簽與入口雙重把關;入口拒收讓錯誤在建立身分時就失敗,不必等到第一次驗簽。
  • 只改驗簽、不加入口檢查:noble 嚴格模式本身已拒絕小階公鑰(見「決定」),但入口不檢查時,含小階鑰的身分仍可註冊、宣告並占用資源,到驗簽時才失敗(否決)。
  • 維持 zip215: true,只在入口拒收小階公鑰:仍接受非正規編碼,與各瀏覽器 WebCrypto 的接受集合差距不變,也無法涵蓋未經入口的公鑰來源(否決)。

決定

1. 驗簽改嚴格

  • verifyMessage 改以 ed25519.verify(signature, message, publicKey, { zip215: false }) 驗簽。在 @noble/curves 2.2.0,嚴格模式:公鑰 A 與 R 都依 RFC 8032 嚴格解碼(y 必須 < p,x=0 時符號位必須為 0);S 必須 < L;A 為小階點時直接拒絕;驗簽方程式維持 cofactored。全零公鑰解碼為小階點,因此在驗簽層同樣被拒。
  • 這是唯一的驗簽實作;所有產品驗簽路徑都經過它,不另設寬鬆入口。
  • 依 專案生命週期.md 的 pre_launch,不保留 ZIP-215 的相容讀取,也不為既有本機資料做遷移。誠實金鑰(由助記詞 seed 經 deriveKeyPair 派生)的公鑰不可能是小階點,誠實簽章在兩種模式下判定相同。

2. 身分入口拒收小階與全零公鑰

在把外部提供的 32 bytes 當成身分公鑰接受的位置,先檢查該公鑰可依 RFC 8032 嚴格解碼、不是小階點、不是全零 bytes;不通過即與「格式不符」同樣處理(回 null/拒收)。入口如下:

入口 位置 現行行為
PeerId 解碼 src/key-manager/ed25519.ts 的 peerIdToPublicKey(所有以 PeerId 取公鑰的驗簽與 isCanonicalPeerId 都經過它) 只檢查 multicodec 前綴與長度
帳本事件 signer 欄位 src/ledger/ledger-event-schema.ts 的 isCanonicalPeerId 經 peerIdToPublicKey,再比對重新編碼一致
回合簽章鑰宣告 src/network-sync/mesh.ts 的 control key 解碼(raceKeyAnnounceDigest 背書的 publicKey);拒收發生在該鑰收入回合鑰表、交給驗簽器之前 只檢查長度=ED25519_PUBLIC_KEY_BYTES 與身分簽章
硬體金鑰提供者 src/key-manager/key-manager.ts 的 setHardwareProvider 不檢查提供者回報的公鑰
建立/匯入 profile src/key-manager/key-manager.ts 的 createProfile 等以 seed 派生的路徑 由 seed 派生,誠實流程不會產生小階鑰;檢查屬防禦性

檢查函式放在 key-manager/ed25519.ts,與 verifyMessage 共用同一個嚴格解碼,供上述入口呼叫。

3. 涵蓋範圍盤點

經 verifyMessage 的驗簽點(main src/):

  • key-manager/key-manager.ts(verify)、key-manager/signed-payload.ts(verifySignedPayload,下游含 chat-system/evidence.ts、dmca/provider-dmca-client.ts、matchmaking/start-grid.ts、room-runtime/host-room.ts、race-messages/transport.ts、bootstrap/race-deps.ts 注入的 verify)。
  • bootstrap/race-deps.ts(RaceIdentity.verify,下游含 network-sync/mesh.ts 的回合鑰宣告、race-runtime/match-orchestration.ts 的結算與錨簽章、matchmaking/loadout.ts、matchmaking/settlement-exchange.ts)。
  • ledger/checkpoint.ts、ledger/ledger-admission.ts、ledger/ledger-api.ts、ledger/receive-validation.ts、economy/governance.ts、moderation/attestation.ts、spectator/replay.ts、signaling-service/register-auth.ts。
  • network-sync/input-signature-verifier.ts(回合訊息;noble 為最終判定,見第 4 項)。

pinning 的 vendored 副本(vendor:sync 自 main 同步):core/vendor/key-manager/ed25519.ts、key-manager.ts、signed-payload.ts,core/vendor/ledger/checkpoint.ts、checkpoint-store.ts、initial-checkpoint-proof.ts、ledger-admission.ts、ledger-api.ts、ledger-event-schema.ts、receive-validation.ts;pinning 自有呼叫點 auth/signed-request.ts、dmca/signed-inbox.ts 經 verifySignedPayload。這些檔案隨 main 改動由 vendor:sync 同步,不在 pinning 手改。

signaling 的 vendored 副本(vendor:sync 自 main 同步,目前與 main 逐位元相同):core/protocol/vendor/key-manager/ed25519.ts、signed-payload.ts,core/protocol/vendor/signaling-service/register-auth.ts、signal-envelope.ts;signaling 自有呼叫點 core/room.ts(註冊證明與信令封包)、adapters/node/server.ts 與 adapters/worker/token-object.ts(TURN token 的註冊證明)。同樣只經 vendor:sync 同步,不在 signaling 手改。

不經 verifyMessage 的第三方驗簽不在本決策範圍:libp2p 節點身分(@libp2p/crypto,瀏覽器以 WebCrypto 驗、失敗時退回其內建 noble 預設)與 OrbitDB entry 身分(OrbitDB keystore)。它們不作為 Open4WD 帳本、比賽或簽章訊息的權威簽署者。

4. 與 WebCrypto 快速路徑的關係

network-sync/input-signature-verifier.ts 在 WebCrypto 接受且 S < L 時直接採用、不再呼叫 noble;拒絕、拋錯或逾時才以 noble 判定。這個捷徑成立的前提是「WebCrypto 接受 ⊆ noble 接受」,canon 寫作「接受集合與 noble 相同」(程式架構/network-sync.md 第 7.1 節「驗簽器」)。

  • 改嚴格後,原本「noble 接受、WebCrypto 拒絕」的類別(非正規編碼、小階點、全零公鑰)改為兩者皆拒,判定更一致,noble 複核的次數也會減少。
  • noble 嚴格模式會拒絕小階公鑰,WebCrypto 的 RFC 8032 實作不一定拒絕;若讓這類公鑰進入驗簽器,快速路徑可能接受 noble 拒絕的訊息。回合訊息的公鑰全部來自第 2 項的回合簽章鑰宣告,且該入口在使用前即拒收小階與全零公鑰,因此進入驗簽器的公鑰都不是小階點,WebCrypto 與嚴格 noble 的接受集合相同,canon「接受集合與 noble 相同」維持成立。
  • 實作時以 001118 的語料在嚴格 noble 下重跑比對,並把「不存在 WebCrypto 接受、noble 拒絕」改釘在嚴格模式與入口檢查之上。noble 仍是最終權威,這點不變。

後果與影響

  • 非正規編碼、小階或全零公鑰的簽章一律驗不過;含這類公鑰的 PeerId 視為格式不符。誠實金鑰與誠實簽章不受影響。
  • 驗簽語意變更屬共識行為:main、pinning 與 signaling 必須同版生效。pre_launch 階段直接改,不需相容層。
  • conformance:ledger/signing-digest 只涵蓋 digest 推導、不驗簽章語意,向量不變;實作時須確認其中帶簽章的範例(conformance-signed)在嚴格模式下仍驗過。另需新增嚴格模式的負向測試(非正規 A、非正規 R、小階 A、全零公鑰、S ≥ L)。
  • 待實作時處理的檔案:
  • main:src/key-manager/ed25519.ts、src/key-manager/key-manager.ts、src/network-sync/mesh.ts、src/ledger/ledger-event-schema.ts(若檢查不完全落在 peerIdToPublicKey)、src/network-sync/input-signature-verifier.ts 與其 spec(以 001118 語料在嚴格模式重跑、前提改釘嚴格模式),以及對應 spec。
  • pinning:上列 vendored 檔經 vendor:sync 同步,check:vendor 對齊。
  • signaling:core/protocol/vendor/key-manager/ed25519.ts 與 core/protocol/vendor/MANIFEST.json 經 vendor:sync 同步,check:vendor 對齊。
  • specs:資安規範.md 第 3 節、程式架構/key-manager.md 第 3 節的 verifyMessage 範例、程式架構/security.md 第 2 節、程式架構/network-sync.md 第 7.1 節「驗簽器」、使用技術.md 簽章列。
  • 與既有決策的關係:不修訂既有決策的決定。D-20261029-03 的驗簽器以 noble 為最終判定,本決策改的是 noble 的模式;其捷徑前提依第 4 項重新驗證。
  • 當日歷史記錄需補條目並回引本決策。

定案紀錄

2026-10-30 使用者審閱後同意採納:main、pinning、signaling 一律改 RFC 8032 嚴格驗簽(zip215: false),回合金鑰與身分入口先拒小階與全零公鑰,不保留相容;實作後以嚴格模式重跑 001118 對照語料。