豆泥關心的難題.
EN ← 回到豆泥部落格
← All essays

數位身分 有備而來:理想的數位皮夾開發報告 第 4 篇

讓數位皮夾實現零知識證明,同時免費部署服務到 Cloudflare

“以有備而來與 OpenAC 電路做出不揭露生日的年齡證明,從 Mac 驗證、Cloudflare 容器部署到 Prepare 快取,記錄實測秒數、費用、與 SD-JWT-VC 的隱私權衡及政策建議。”

Anthropic Claude Fable 5.1 18 分鐘 #2026-09-05-zero-knowledge-age-proof-from-phone-to-cloudflare
AI 生成資訊

模型:Anthropic Claude Fable 5.1

生成日期:2026年9月5日

提問:依有備而來 iOS 原始碼、twdiw-vp-verifier-lite 原始碼與部署紀錄、zkID 論文與 README 效能表、Cloudflare 官方文件、以及 2026 年 9 月 4 至 5 日在 iPhone 14、Mac 與 Cloudflare 容器上的實測秒數,撰寫零知識年齡證明的開發報告、三路徑對照與政策建議。

這是「有備而來:理想的數位皮夾開發報告」第四篇。本文依 2026 年 9 月 4 日至 5 日的原始碼、Cloudflare 部署與實機操作撰寫。所有秒數來自 App 與驗證器自動記錄的單機樣本,數量很少,只報原始值與中位數,不稱為 p95。測試用真實卡片時只記錄階段、結果、耗時與錯誤類型;本文與程式碼都不收錄出生日期、統一編號或任何揭露欄位。

前三篇處理的是出示。政府卡片與 MyData 資料收進手機後,以選擇性揭露交給查驗方核對姓名、門號末五碼或駕照種類。本篇處理更進一步的需求。持卡人連出生日期都不交出,只回答「已滿 18 歲」,查驗方仍能以密碼學確認這個回答沒有作假。

這個需求已在 iPhone、Mac 與 Cloudflare 容器上完整跑通。代價有兩項。手機需要數秒計算證明,驗證端需要常駐一個占用約半 GB 記憶體的程式。以下記錄過程、數字與取捨。

零知識證明的原理

以便利商店的年齡確認為例,有三種做法。

第一種是出示整張身分證。店員取得姓名、住址、統一編號和出生日期,用途只是一個是非題。

第二種是數位皮夾的選擇性揭露,也就是前幾篇採用的 SD-JWT-VC。卡片裡每個欄位各自封存,出示時只打開出生日期那一欄。店員看不到姓名住址,但真實的出生日期仍然交了出去。

第三種是零知識證明。手機保留出生日期,產生一份數學證明,其內容僅為「有一張發卡者簽章過的卡,上面的出生日期不晚於 2008 年 9 月 5 日」。店員的系統驗證這份證明未經偽造,即可確認持卡人已滿 18 歲,全程沒有看見那一天是哪一天。

這個做法把資料最少化推到極限。查驗方拿不到的資料無從外洩、留存或與其他資料庫比對。此外,每次出示的證明都重新隨機化,兩個查驗方各自收到的證明放在一起,也無法推出屬於同一個人。

相較於 SD-JWT-VC,隱私保護的差異有兩點。選擇性揭露交出欄位原值,零知識證明只交出述詞的真假。選擇性揭露的卡片有一把穩定的持有人金鑰與 DID,每次出示都帶著同一個識別子;零知識證明每次重新遮罩,識別子不再穩定。

零知識證明的限制同樣明確。手機需要數秒計算證明,第一次更久。驗證端無法在一般的無伺服器函式內執行,需要一個能載入四百多 MB 驗證金鑰的程式。證明本身 155 KB,約為一般出示的二十倍。電路只接受特定形狀的欄位,卡片沒有出生日期欄位就無法產生年齡證明。工具鏈仍在早期,相關論文發表於 2025 到 2026 年。此外,證明的可信度仍取決於誰簽了那張卡。以自行簽署的出生日期產生的年齡證明,隱藏的是自填的日期,並不因此取得政府背書。

有備而來使用分頁,零知識證明區有建立隱私年齡證明與查驗隱私年齡證明兩列
圖一。有備而來「使用」分頁的零知識證明區。舊的 MOICA 持有證明已從選單移除,只留年齡述詞證明的建立與查驗兩列,避免兩種零知識證明並列造成混淆。

本階段完成的工作

本階段設定三個目標,全數完成。

  1. 查驗網站 verifier.mashbean.net/zkp 新增一頁,能建立一次性的零知識年齡查驗,並顯示每一段耗時。
  2. 有備而來裡的 MyData 自發身分證與政府卡片都能建立年齡證明;實際能跑完的是自發身分證,原因見「誠實邊界」。
  3. 三條驗證路徑的秒數都由程式自動記錄,可與 SD-JWT-VC 出示並排比較。

另有兩項原本不在清單上的工作。驗證端遷入 Cloudflare Containers,不再依賴一台開機的 Mac;手機端實作了論文設計的 Prepare 快取,重複出示的建證時間從 8.1 秒降到 2.7 秒。

採用的既有專案

整條鏈路由幾個開源專案接起來,本專案的工作是讓它們對準台灣的卡片、手機與部署環境。

層次專案在這裡的角色
電路與證明系統ethereum/zkID(OpenAC,commit b395e09jwt_2kshow 兩個 Circom 電路。前者驗證發卡者的 ES256 簽章並承諾隱藏的欄位,後者證明述詞並綁定查驗方 nonce
證明系統Spartan2(openac-sdk 分支,Hyrax 承諾,P-256)無 trusted setup 的 SNARK;手機端證明省記憶體,代價是驗證端要載入整個電路
見證生成circom-scotia、witnesscalc_adapter把 SD-JWT 的位元組展開成電路輸入
iOS 綁定Mopro 0.3.5把 Rust 證明器包成 XCFramework;有備而來以 Native/OpenACAge overlay 加上自己的 predicate.rs,只開放「生日不晚於截止日」這一種述詞
手機端有備而來(Swift)、Secure Enclave 每卡金鑰、行動自然人憑證、MyData自發身分證由自然人憑證簽署;每張卡有獨立金鑰,用來對 nonce 簽章
驗證端原生服務openac-age-verifier(Rust、axum)載入兩把驗證金鑰,執行 verify_linked,比對 6 加 156 個公開值
驗證端平台Cloudflare Workers、Durable Objects、Containers、Workers BuildsWorker 管 session 與信任查詢,容器跑密碼學,Workers Builds 建映像
對照路徑jose、@sd-jwtSD-JWT-VC 出示的驗證,跑在 Worker 內
參考實作PSE 的 go-zkid-verifier、openac-rsa-x509-swift、moica-revocation-smt第一代 MOICA 持有證明的路線;本次未採用,理由見決策鏈
官方資料TWDIW official app 原始碼、數位發展部 DID 信任清單 API政府卡片的發卡者信任判斷

所有依賴都釘在特定 commit。手機端的 XCFramework 與電路素材放在 GitHub release openac-age-v1,驗證金鑰的壓縮與解壓後 SHA-256 各自釘死,映像建置與服務啟動各檢查一次。release 若被重新發佈,建置會失敗,來路不明的金鑰不會上線。

網頁查驗流程

查驗方在網頁選擇來源、填入年齡門檻與目的,取得一次性 QR。持卡人以有備而來掃碼、確認同意畫面、在手機上計算證明,證明送回網站後,網站顯示是非與秒數。

建立一筆零知識年齡查驗的頁面,選擇證明來源、年齡門檻與目的,列出述詞與個資告知
圖二。查驗方只決定「至少幾歲」。頁面在 QR 出現前就寫明皮夾會回傳什麼、不會回傳什麼,並附個資告知;截止日以臺北時間往前推 N 年。

QR 內是一段短 JSON,含一個 32 位元組的 nonce、截止日、來源、最低年齡、用途,以及證明的回傳網址。網址只接受允許清單上的 HTTPS 站台,手機在解碼時檢查一次,開連線前再檢查一次。第三方印製的 QR 無法把證明導向其他位置。

有備而來的同意畫面,寫明查驗方想確認是否已滿 18 歲、目的、來源,生日與卡片不離開手機,完成的證明會送到 verifier.mashbean.net
圖三。同意畫面說明四件事,包括對方問什麼、為了什麼、用哪張卡、證明會送去哪裡。最後一項是與面對面藍牙流程唯一的差別,因此額外標示。
有備而來顯示網站已驗證這個證明至少 18 歲,建立證明 2,189 加 467 毫秒,網站驗證 157 毫秒,往返 814 毫秒
圖四。手機端收到網站的判定與秒數。這一筆是 Prepare 快取命中的結果,建證耗時 2.7 秒。

證明包內含兩份證明、述詞的公開參數(欄位名稱、日期格式、截止日、門檻)、發卡者的 did:key,以及手機端的建證耗時。其中沒有出生日期、姓名或任何卡片欄位。Worker 核對述詞與 session 一致、解析發卡者金鑰、政府卡片另查官方信任清單,再把兩份證明轉給原生服務。Worker 不保存證明,結果推送到瀏覽器後 session 立即刪除。

從 Mac 到 Cloudflare 的部署經驗

驗證無法在 Worker 內執行的原因

一個 Worker isolate 的記憶體上限 128 MiB,程式碼上限 10 MB。OpenAC 的 Prepare 驗證金鑰是 412 MB。這個差距來自證明系統的設計。Spartan2 加 Hyrax 不需要 trusted setup,手機端證明時記憶體負擔輕,代價是驗證端必須讀入整個電路(374 MB 的 R1CS)進行評估,驗證金鑰的大小幾乎等於電路本身。

對照 Groth16 一類系統,驗證金鑰僅數百 bytes、驗證耗時數毫秒,以 JavaScript 在 Worker 內即可執行,但每個電路需要一次 trusted setup,且手機端要持有數 GB 的 zkey 才能產生證明,手機無法負擔。PSE 的 go-zkid-verifier 同樣是原生程式,理由相同。

驗證端真正需要的是一個具備 1 GiB 以上記憶體的程序,任何能提供這個條件的平台都可以承擔。

在 Mac 上以隧道提供服務

原生服務先在 Mac 上執行。載入兩把金鑰 0.33 到 0.39 秒,常駐 429 MB;驗證一份真證明後峰值 625 MB。服務以 Bearer token 保護,未授權請求回 401,偽造證明回明確錯誤。

對外連線使用 Cloudflare 隧道。具名隧道無法建立,因為這台機器的 cloudflared 憑證屬於另一個帳號,wrangler 的 OAuth token 又沒有 DNS 寫入權限;改用 quick tunnel,網址每次重啟都會改變,以腳本自動把新網址寫進 Worker secret。第一份真證明在這條路徑上完成驗證。

遷入 Cloudflare Containers

容器需要 Workers Paid 方案,映像必須是 linux/amd64,建置映像需要 Docker。開發用的 Apple Silicon 機器未安裝 Docker。Cloudflare 文件確認 Workers Builds 的建置環境可以執行 Dockerfile,連接 GitHub 後推一個 commit 到 main,2 分 36 秒後映像建置完成、容器啟動。過程中有三個問題值得記錄。

第一,secret 不能與 var 同名。設定檔原先把 ZKP_VERIFIER_URL 宣告為 var,之後以 wrangler secret put 寫入同名 secret 被拒絕。兩個值都改為 secret 後解決。

第二,Worker 呼叫容器的方式。@cloudflare/containerscontainerFetch(url, init, port)RequestInit 當位置參數傳過 Durable Object 的 RPC 邊界,body 與 headers 無法送達。容器本身健康,Worker 卻收到 502。改為建立一個 Request 交給 stub 的 fetch,由 Container 基底類別代理到預設埠,問題解決。

第三,規格與速度。容器選用 basic(1/4 vCPU、1 GiB),因為 625 MB 的峰值裝得下,且免費額度換算後每月約 25 小時的醒著時間,是 standard-1 的四倍。代價反映在後面的秒數表,驗證從 Mac 的 0.16 秒變成 5 秒,冷啟動 24.5 秒。

容器只宣告在示範站的設定檔。開源 repo 的一鍵部署仍然留在免費方案,以 secret 指向自建的驗證服務。

衍生費用

項目方案與額度這次的實際
Cloudflare Workers Paid每月 5 美元,含 Durable Objects既有方案,示範站原已部署於此
Containers 免費額度每月 25 GiB-hours 記憶體、375 vCPU-minutes、200 GB-hours 磁碟basic 醒著時計 1 GiB,約 25 小時免費;閒置 10 分鐘休眠,休眠不計費
超額費率記憶體 0.0000025 美元/GiB-s、CPU 0.000020 美元/vCPU-s測試量沒有超額;basic 全天不休眠約每月多 7 美元,standard-1 約 27 美元
Mac 隧道路線0 美元需要一台開機的機器,網址會變
GitHub release 託管 80 MB XCFramework 與金鑰0 美元每支手機第一次建證下載約 76 MB 素材
開發時間不計價本報告涵蓋的工作約兩個工作天

三條驗證路徑的實測

以下數據來自同一張自發身分證、同一個查驗網站、同一支 iPhone 14。

路徑手機建證後端驗證Worker 全程證明大小
SD-JWT-VC 出示(Worker 內驗證)不需要毫秒級,頁面四捨五入顯示 0 ms毫秒級約 7 到 8 KB
零知識,Mac 驗證(隧道)首次 12.2 秒;快取命中 2.7 秒首次 1,885 ms;之後 157 到 200 ms586 到 907 msPrepare 109 KB 加 Show 46 KB
零知識,Cloudflare basic 容器同上暖機 4,973 ms;冷啟動 24,473 ms暖機 5,833 ms;冷啟動 25,108 ms同上

App 端從掃碼到收到判定的全程,SD-JWT-VC 出示自發證件 0.22 秒、政府卡片 0.40 秒;零知識年齡證明 7.28 秒。差距 7.06 秒,幾乎全在手機建證。

查驗網站的比較表,SD-JWT-VC 出示各段 0 ms,零知識年齡證明皮夾建證 12,159 ms、驗證 1,885 ms、Worker 全程 4,156 ms
圖五。查驗網站把同一個瀏覽器分頁內最近一次的兩種流程並排。這一筆零知識是第一次建證(無快取)且驗證在 Mac 上,數字只存在瀏覽器的 sessionStorage,不含任何欄位值。
有備而來診斷頁,政府皮夾裡的卡 SD-JWT-VC 出示全程 0.40 秒、零知識尚未量測;自發 MyData 證件 SD-JWT-VC 0.22 秒、零知識 7.28 秒、手機建證 2.66 秒、網站驗證 0.16 秒、差異加 7.06 秒
圖六。手機端的診斷頁做同樣的並排,數字來自 App 的單調時鐘。政府卡片那一列的零知識「尚未量測」,原因見誠實邊界。

容器上的兩次真證明

查驗網站顯示已證明持有人至少 18 歲,後端驗證 24,473 ms,Worker 全程 25,108 ms
圖七。切換到容器後的第一份真證明。判定正確,後端驗證 24.5 秒,是容器剛啟動加上 1/4 vCPU 的結果。
查驗網站顯示已證明持有人至少 18 歲,後端驗證 4,973 ms,Worker 全程 5,833 ms
圖八。緊接著的第二份。驗證降到 5 秒,這是 basic 規格的穩態。瓶頸在 vCPU,記憶體並非限制。

Cloudflare 這一段的目標是把零知識驗證模組的部署成本壓到一個示範專案可負擔的範圍,並且不需要任何人開著電腦。目標達成,代價是速度。具名規格把 vCPU 和記憶體綁在一起,要取得 2 個 vCPU 就得購買 8 GiB,而這項工作只用 625 MB;自訂小記憶體加多 vCPU 屬於企業方案。示範用途先維持 basic,現場對人展示時再升級。

Prepare 快取

年齡證明由兩份證明組成。Prepare 驗證發卡者簽章並承諾隱藏欄位,只與卡片有關;Show 才綁定查驗方的 nonce 與截止日。zkID 論文的設計意圖是新卡加入時離線執行一次 Prepare,存下可重用的預運算狀態;出示時取用、重新隨機化、再執行 Show。

有備而來原本每次都全部重算。本次改為第一次建證後保存 Prepare 的三個產物,之後每次只做重新遮罩與 Show。實測數字如下。

第一次(無快取)第二次起(命中)
Prepare7,586 ms2,189 到 2,617 ms
Show523 ms467 到 542 ms
手機建證合計8,109 ms2,656 到 3,159 ms

少掉的 5.4 秒是被跳過的 proveJwt。命中後剩下的 2.2 秒幾乎都是重新遮罩(reblind)。這一步每次必跑,它換上新的隨機遮罩,讓兩個查驗方拿到的證明無法對上。省略它可以更快,但不可關聯性隨之消失。

快取有其代價。存下來的 witness 含裝置金鑰與正規化後的出生日期,敏感程度等同卡片本身。它以憑證同等的檔案保護等級存放、排除備份、最多保留 8 筆且最舊先淘汰、刪卡即清除、「清除所有本機資料」時整個目錄移除。手機自檢不通過且使用了快取時,丟棄該筆重建一次,避免把損壞的快取誤判為卡片問題。

與論文比較

zkID 論文與 README 提供 iPhone 17 的數字。下表把三者並列。

項目論文 iPhone 17本文 iPhone 14本文驗證端
Prepare 證明2,102 到 2,987 ms(另 key setup 3,254 到 3,499 ms)首次含 witnesscalc 11,478 ms;無快取 7,586 ms不適用
Prepare 重新遮罩856 到 884 ms約 2,200 ms不適用
Show 證明加遮罩115 到 129 ms467 到 542 ms不適用
Prepare 驗證137 到 151 ms不適用Mac 157 到 200 ms;容器 4,973 ms
證明大小Prepare 109.29 kB、Show 40.41 kBPrepare 109 KB、Show 46 KB同左
手機證明峰值記憶體2.27 GiB未量驗證端 625 MB

證明大小與論文一致,代表電路、金鑰與序列化沒有偏差。iPhone 14 的每一步都比 iPhone 17 慢兩到四倍,比例合理。論文指出快取後每次出示約一秒,本文量到 2.7 秒,差異來自較舊的處理器與每次必跑的 reblind。驗證端在 Mac 上與論文的手機驗證同量級,在 1/4 vCPU 的容器上慢 25 到 100 倍,差距由平台規格決定,實作本身沒有問題。

架構決策鏈

每一步都有一個被放棄的選項。

  1. 以年齡述詞證明取代第一代 MOICA 持有證明。 有備而來八月做過另一種零知識證明,證明「一張真的自然人憑證簽了這份東西」。它的驗證金鑰 968 MB、證明 294 到 398 KB、驗證 12 到 14 秒,並帶有六條無法移除的限制,包括簽章材料可重放、統一編號會揭露給內政部、沒有全域唯一性、nullifier 對所有查驗方相同、不證明效期、撤銷根未錨定。它證明的是持有,無法證明任何欄位。年齡述詞證明由查驗方先給 nonce、綁在電路的公開輸入裡,證明的是一個具體問題的答案,符合實際查驗的需求。
  2. 查驗方先給 nonce。 證明在收到請求之後才建立,重放問題在協定層就消失,不需要事後比對。
  3. 驗證端離開 Worker,先落在 Mac。 128 MiB 無法容納 412 MB。Mac 讓第一份真證明在可以直接觀察 log 的環境完成驗證,之後再遷移。
  4. 從隧道遷入容器。 隧道網址會變、機器要開著;容器讓示範不依賴任何人的電腦,Workers Builds 省去本機 Docker。
  5. 選用 basic,暫不使用 standard-1。 量到 625 MB 峰值後決定,並在原生服務加上同時驗證上限 2,確保兩個證明同時到達也不會超過 1 GiB。速度的代價列在表中,留給示範以外的需求再升級。
  6. Prepare 快取。 論文的設計本意,實測有效,代價是手機多存一份機密,以同等保護與生命週期管理處理。
  7. 兩種來源共用同一套電路,結果分開標示。 政府卡片的證明會查官方信任清單;自發身分證的證明標示「自發、非政府背書」。隱藏出生日期的機制相同,信任關係不同,頁面不能讓兩者看起來一樣。

誠實邊界

有備而來首頁,國民身分證卡面欄位已遮罩,政府皮夾卡片有駕照電子卡與門號電子卡,下方是 MyData 資料保險箱
圖九。皮夾裡的三種來源。目前只有國民身分證(MyData 自發)能做年齡證明,駕照電子卡與門號電子卡都沒有揭露出生日期,電路無從證明。

政策建議

一、把資料最少化寫成階梯,以「問題」定義用途

完整出示、選擇性揭露、述詞證明,是三個台階。政策與採購規格除了列出「需要哪些欄位」,更應寫明「需要回答什麼問題」。年齡門檻、是否設籍某縣市、是否持有某類資格,這類是非題應走述詞證明;需要人名核對的流程才走選擇性揭露。查驗方需要的是答案,多數情況下並不需要原始資料。

二、隱私與代價的權衡,兩邊都要算

面向SD-JWT-VC 選擇性揭露零知識述詞證明
查驗方得到欄位原值是非
跨查驗方可關聯性穩定持有人金鑰與 DID,可關聯每次重新遮罩,不可關聯;自發卡的發卡者 DID 是例外
手機端成本毫秒首次約 8 到 12 秒,快取後約 3 秒
驗證端成本Worker 內毫秒常駐 0.4 到 0.6 GB 的程式,CPU 密集,秒級
部署成本免費方案可行需要容器或主機;示範可在每月 5 美元內
證明大小約 7 到 8 KB約 155 KB
適用卡片任何 SD-JWT-VC欄位形狀要對得上電路;目前只有帶生日的卡
成熟度標準已 Final研究專案,2025 到 2026 年

零知識證明在隱私上的優勢明確,成本上的劣勢也同樣具體。政策應把它定位為高風險用途的選項,預設做法仍可維持選擇性揭露。成本應由查驗方承擔,持卡人不應為此付出額外代價。

三、發卡端決定了零知識能不能發生

第三方皮夾做不出政府卡片的年齡證明,卡住的地方在發卡端。具體建議有三項。

  1. 政府卡片若可能用於年齡相關用途,發卡時把出生日期以電路可讀的固定格式放進可揭露欄位,並公布欄位名稱與格式。
  2. 公布一份與 OpenAC 一類電路相容的 SD-JWT profile,規範 cnf.jwk_sd 陣列與揭露的位元組形狀,讓皮夾不必逆向工程。
  3. 更長期,由發卡者直接提供 ZK-ready 憑證,或參與電路的審查與金鑰發布,讓「政府背書」與「隱藏欄位」兩者不必二選一。

四、驗證端是公共基礎設施的候選

零知識驗證需要一個常駐半 GB、CPU 密集的程式,小型業者各自維護一個並不合理。政府或公協會可以考慮三種做法。提供共用的驗證服務,讓業者以 API 呼叫;公開釘死的驗證金鑰與容器映像,讓部署變成一鍵;或投入驗證端更輕的證明系統研究,例如以 Groth16 包裝或遞迴壓縮。沒有第二層基礎設施,零知識會停在展示階段。

五、不可關聯性應寫進規格

每次出示重新隨機化、查驗方先給 nonce、不以穩定 DID 當識別子,這三件事應成為驗證者與皮夾的規範要求。本次實作證明它們在手機上可行,代價是每次多兩秒。規格若不要求,實作者會為了那兩秒省略它。

六、把秒數寫進使用者介面與採購文件

本文每一個秒數都由程式自動記錄並顯示給雙方。查驗方在頁面看見「後端驗證 4,973 ms」,持卡人在手機看見「建立證明 2,189 加 467 毫秒」。這是知情同意的一部分。使用者有權知道為隱私付出了幾秒,採購方有義務把這幾秒寫進規格,避免上線後才發現櫃台排隊。

七、治理邊界

述詞證明通過不等於政府背書,自發與發卡者簽署的結果必須在畫面上區分。驗證端零持久化、只記錄判定與秒數、不記錄證明與識別子,這些在本文的實作中都已做到,但示範站並非正式服務,也沒有官方驗證者資格。高風險決策仍需要另一個可核對的因素。

接下來

  1. 找到一張帶出生日期欄位的政府卡片,把「政府卡片零知識」那一列量出來;或向發卡端提出欄位需求。
  2. 依現場需求決定容器規格,或改用固定後端主機;把冷啟動與 60 秒逾時的關係量清楚。
  3. 做零揭露的持有證明電路,讓沒有生日欄的卡片至少能證明「這張卡是某發卡者簽的」。
  4. 增加樣本,讓中位數與最大值有統計意義;把測試矩陣的 A2、G1、W1、W2 補齊。
  5. 對 OpenID4VP 與 SD-JWT 執行 conformance,讓零知識路徑能與正式規格並存。
  6. 把本文的成本表變成可重算的試算表,讓其他部署者能估算自己的帳單。

零知識證明在本專案裡從概念變成了可量測的秒數。它能做到的事很明確,查驗方只拿到一個是非,兩個查驗方湊不出同一個人。它做不到的事同樣明確,發卡端不給欄位就沒有述詞,驗證端不給 CPU 就沒有速度。政策的工作是決定這些代價由誰承擔。