它到底做了什麼
憑證就是某個機構簽過名的一句話。“此人已滿18歲。”“此人可以駕駛汽車。”讓這句話有分量的是簽章:它表示有權威為這句話背書,而且事後沒有人改動過。
在紙上,在PDF裡,一個簽章涵蓋整份文件。要證明其中一行,就得把整份交出去。為了證明年齡而出示駕照,對方連你的住址、證件號碼和確切出生日期也一併知道了。這些他從沒要過,現在卻都有了。
選擇性揭露取消了這筆交易。簽發方對每一項事實分別簽章,憑證只攜帶每一項的指紋。揭露哪些由你決定,其餘的仍是指紋,不會透露任何內容。簽章依然通過驗證,因為它從來就不是蓋在一個不可分割的整塊上。
這一切都裝在驗證方掃描的那段碼裡:簽章、指紋,以及你選擇出示的事實。驗證過程中不去取任何東西,所以一道門、一輛公車、一間鄉村診所,或是巡航中的飛機,完全沒有網路也能驗證。
還差最後一塊,而它恰恰最容易被忽略。能掃描的東西就能被拍照。所以出示的人還必須用一把永不離開裝置的私鑰,為當場的挑戰值簽章。少了這一步,別人憑證的截圖也能通過。這個函式庫會拒絕任何缺少它的出示。
為了沒有訊號的那一刻而生
我做過 eCNH,巴西的數位駕照,四千多萬人在用。教會我最多的不是那個應用程式,而是路邊:警員在只有一格訊號、甚至沒有訊號的公路上掃一張駕照,必須在一秒內給出是或否。
這個答案需要的一切都裝得進碼裡。簽章證明簽發方,claim 就擺在那裡,唯一要從外面拿的只有簽發方的公鑰,而它極少變動,隨應用程式一起發出去、每週更新一次就夠了。
這類函式庫是有的。它們是企業級 SDK:笨重、綁死在某一國的規範上,而且寫得像是預設你已經在身分產業裡做事。這一份,是產品工程師在某個星期二就能加進專案的版本。
三方,其中沒有一方是伺服器
每個 claim 只簽一次
簽發方決定哪些 claim 日後可以不揭露,並為其中每一個簽下一個摘要。
只送出被要求的部分
錢包把要留下的 claim 拿掉。對剩下的內容,簽發方的簽章依然成立。
不問任何人就給出答案
簽章、效期、撤銷狀態與每一筆揭露,都拿裝置上固定的金鑰來核對。
證明你滿 18 歲,而不必交出生日
年齡查核的法規來得比工具快。常見做法是讓顧客把證件照片上傳給第三方,那是一場隱私災難,也是一起只等著發生的外洩。
選擇性揭露把這件事做對了。驗證方就算想知道出生日期也做不到,因為那個值從未離開錢包。這個性質是密碼學上的,不是隱私權政策裡的一句承諾。
// 憑證裡有姓名、地址、出生日期與證件號碼。
// 酒吧拿到的是一個布林值。
const presentation = await present(credential, { disclose: ['over_18'] })
const result = await verify(presentation, { trust })
result.claims // { over_18: true }
result.claims.birth_date // undefined,而且從未被傳輸
result.withheld // 4,而且它說不出是哪四個
真實數字,包括難看的那個
一份貼近現實的駕照。八個 claim、五年效期、一個狀態清單指標。是量出來的,不是估的:
| 憑證 | 字元數 | QR Code 版本 |
|---|---|---|
| 全部可見 | ~740 | 18,掃得很順 |
| 八個 claim 全部可揭露 | ~1590 | 27,太密 |
presenting only over_18 | ~1115 | 22,仍然偏密 |
fits() 告訴你處境。
別信我的話
試用場在你的瀏覽器裡跑起整個函式庫,並朝它打出八種真實攻擊。每一種都寫明它預期的拒絕代碼,所以你可以拿這個函式庫跟它自己的說法對質,而不是去信一份 README。
為什麼選它,以及什麼時候別選
先說五個理由,再說實話的部分。
- 約1,300行,真的讀得完小到一名工程師一個下午就能通讀一遍,明白它在做什麼。讀不了的安全,只能靠相信。
- 零相依套件執行期不從npm引入任何東西。要稽核的供應鏈只有它本身,也沒有什麼會在下週悄悄變掉。
- 無法完整驗證的,一律拒絕沒有部分通過,也沒有可以忽略的警告。每一次失敗都回傳一個有名字的原因,可以記錄也可以處理,絕不只丟回一個false。
- 在驗證發生的地方執行Node、Deno、Bun、瀏覽器、React Native 和邊緣運算環境。同一份程式碼,跑在平台內建的標準密碼學之上。
- 三種方式測試單元測試、隨 RFC 9901 一同發布的官方測試向量,以及用隨機畸形輸入反覆攻擊、專門尋找漏網情形的屬性測試。
以及什麼時候它是錯誤的工具。
- 你需要 ISO 18013-5 行動駕照那是另一套編碼,CBOR 與 COSE,也是另一套標準。這個函式庫講的是 SD-JWT,歐洲錢包和 OpenID 規範使用的格式。問題相近,工作不同。
- 你要的是一個完整錢包它負責驗證和出示,不儲存憑證,不管理裝置,也不處理開戶流程。它是一個零件,為放進你的產品而做。
- 在受監管的場景上線前需要第三方稽核目前還沒有。測試、RFC 測試向量以及每一行程式碼都是公開的,但用於受監管的部署,請為自己的審查留出預算。
安裝
npm i qredential
Node 20 以上、所有現行瀏覽器、React Native。大約 1,300 行原始碼,沒有任何執行期相依套件,正是為了讓「信任之前先讀過一遍」這件事切實可行。
用標準,不是自創
- SD-JWT, RFC 9901選擇性揭露與金鑰綁定,支援巢狀與遞迴,正是歐洲身分錢包所用的機制
- SD-JWT VC憑證的結構
- Token Status List靠一份快取副本就能運作的撤銷檢查
- base45, RFC 9285QR Code 信封,為讀碼機相容性而選
不是 ISO 18013-5 mDL,那邊是 CBOR 與 COSE 而非 JWT。它在規劃中,而用到未支援功能的憑證會被拒絕,而不是被理解一半。宣稱做了半套合規標準,比什麼都不宣稱更糟。