Google 官方公告裡有一句最直白的例子:「a person visiting a website can verifiably prove he or she is over 18, without sharing anything else at all」——網站要的只是一個布林值,但現行做法幾乎都是把整張身分證掃過去。這篇要講的,是 Google 剛開源的 longfellow-zk 函式庫,怎麼用零知識證明把「證明」和「洩漏」這兩件事拆開。
零知識證明,從頭講起——年齡驗證為什麼不用交出身分證
看完這篇你會知道一件事:年齡驗證不必是「交出身分證讓對方看完整份資料」,也可以是「只證明一個布林值」。Google 在 2025 年 7 月開源了 longfellow-zk,一套用零知識證明(Zero-Knowledge Proof,ZKP)打造的函式庫,讓「我已滿 18 歲」可以變成一個能被驗證、卻不洩漏生日的數學證明。這篇文章會從「年齡驗證為什麼在 2026 年變成一個必須解的問題」講起,中間拆解 ZKP 到底在證明什麼、longfellow-zk 怎麼把抽象的證明變成可以串接既有標準的程式碼,最後談開源這個動作本身改變了什麼。
各國都在要求年齡驗證,而現行做法的隱私代價很高
先把情境攤開。歐盟的 eIDAS Regulation 預計 2026 年生效,官方原文的措辭是「encourages Member States to integrate privacy-enhancing technologies like ZKP into the European Digital Identity Wallet」——用的是「鼓勵」,不是「強制」,但方向很清楚:各國正在往「數位身分皮夾」(EUDI Wallet)這條路線走,年齡、國籍、學歷這類屬性驗證會越來越常態化地發生在網路服務上,不只是銀行開戶或機場通關才需要。
問題是現行的年齡驗證幾乎都用同一招:上傳身分證、駕照,或掃描護照。對方要的資訊只有「滿不滿 18 歲」這一個布林值,你給出去的卻是姓名、地址、證件號碼、相片、有時候還有血型或婚姻狀態——一張證件上能讀到的所有欄位,全部曝光給一個原本只需要驗證年齡的網站或 App。這不是設計失誤,是因為在 ZKP 出現以前,工程上沒有更精細的手段:驗證單一屬性最簡單的做法,就是交出整份文件讓對方自己讀。
這個做法的代價是「隱私爆炸半徑」隨著要求驗證年齡的服務數量線性增加。每多一個要求上傳身分證的網站,就多一個保存你完整個資的第三方;任何一個環節外洩,暴露的都不只是年齡,是整份身分文件。Google 這次的公告把這個現象講得很直接:開源 ZKP 函式庫是為了「meeting an urgent need」——這是官方自己的用詞,指的正是這個隱私與合規之間越拉越開的落差。
這個落差不是單一國家的立法問題,是好幾條法規線同時往「平台必須驗證使用者年齡」這個方向收斂,時間點又剛好卡在一起。歐盟的 eIDAS 是其中一條,但公告本身特別點名跟德國儲蓄銀行體系的 Sparkasse 合作——公告開頭那句「building on our partnership with Sparkasse to support EU age assurance」講的就是這件事:這不是 Google 憑空丟出一個技術展示,是延續一個已經在跑的合作案,說明開源這個函式庫背後有真實的落地需求在推動,不是純研究性質的釋出。
對平台方來說,這個窗口帶來的壓力有兩層。第一層是合規壓力:法規要求驗證年齡,不驗證就有裁罰風險。第二層是產品壓力:驗證流程如果做得太重(要求上傳證件、等待人工審核),會直接推高使用者的流失率。過去多數平台在這兩層壓力之間選擇犧牲隱私換取合規速度——上傳身分證是最快能滿足稽核要求的做法,即使代價是平台從此多背負一份完整個資的保存責任。這正是 ZKP 想解的結構性問題:讓「合規」與「隱私」不再是互斥選項。
零知識證明在做什麼——證明一件事為真,不用交出證據本身
零知識證明的核心定義,Google 官方公告寫得很精簡:「ZKP makes it possible for people to prove that something about them is true without exchanging any other data.」翻成白話:你可以證明「關於你的某個陳述為真」,而不用把支持這個陳述的原始資料交出去。
拿年齡驗證當例子最容易理解。傳統做法的邏輯是「你看我的身分證,自己確認我滿 18 歲」——驗證方拿到的是原始資料(生日),自己做判斷。ZKP 的邏輯反過來:「我(持證人)自己算過了,這裡有一個數學證明,證明『我的生日換算出的年齡 ≥ 18』這個陳述為真,你可以驗證這個證明是不是有效,但你驗證的過程完全不需要知道我的生日是哪一天」。驗證方拿到的不是資料,是一個關於資料的「證明」——證明本身不攜帶生日這個數字。
這裡容易混淆的一點是:ZKP 不是「隱藏資料再期望對方相信你」,而是給出一個可以被獨立驗證的數學保證——驗證方不需要信任持證人,也不需要看到原始資料,就能確認陳述為真。這跟「我發誓我滿18歲」的自我聲明是完全不同量級的保證:自我聲明無法被驗證,ZKP 的證明可以。
再往前推一層想:為什麼「不用交出原始資料就能驗證陳述」在數學上是可能的?直覺的答案是——驗證一個陳述為真,跟取得支持這個陳述的所有原始輸入,本來就是兩件可以分開的事。以一個更簡單的例子來說:如果有人說「這個數獨盤面有解」,你不需要看到完整的解答,只需要被說服「確實存在一組填法讓每行每列每宮都不重複」就好。零知識證明系統做的,正是把這種「被說服但不用看到解答本身」的直覺,變成一套可以被電腦執行、有嚴謹數學保證的協定——證明者跟驗證方之間可能來回好幾輪隨機挑戰,每一輪都讓「假陳述矇混過關」的機率減半,但驗證方在整個過程裡從頭到尾沒看到答案本身。這個「多輪挑戰逼近確定性」的直覺,稍後在 longfellow-zk 用的機制裡會用另一種形式出現。
下面這個 widget 把「傳統交出證件」與「ZKP 只交出證明」兩條路徑並排畫出來,重點看兩條路徑最後抵達驗證方手上的,究竟是原始資料還是一個布林結果。
對照兩條驗證路徑最終交出去的資料量 · 6 個欄位 vs 1 個布林值
要注意一個容易被誤解的地方:ZKP 不是把資料「加密後傳輸」,加密資料到了對方手上還是要解密才能讀。ZKP 給出去的東西,從頭到尾就不包含生日這個數字——不是藏起來,是根本沒被打包進證明裡。這是它跟一般的加密傳輸、甚至跟遮蔽部分欄位的做法(例如只秀後四碼)本質上的差異:遮蔽是「部分資料還是被看到」,ZKP 是「沒有原始資料被傳輸,只有一個關於資料的數學陳述被傳輸」。
longfellow-zk 怎麼把這件事變成一個能跑的協定
光有「零知識證明」這個概念還不夠用,還需要一個具體的協定告訴電腦怎麼產生、怎麼驗證這樣的證明。longfellow-zk 的 GitHub README 給的定位是:「The Longfellow library enables the construction of zero-knowledge protocols concerning legacy identity verification standards such as the ISO MDOC standard, the JWT standard, and W3 Verifiable Credentials.」——重點在「legacy identity verification standards」這幾個字:longfellow-zk 不是要發明一套新的身分格式,是讓既有的、業界已經在用的身分憑證標準(行動駕照用的 ISO MDOC、Web 常見的 JWT、W3C 的可驗證憑證)都能套上零知識證明這一層。
repo 用「libzk: A C++ Library for Zero-Knowledge Proofs」這行連結文字,指向一份 IETF 個人送件(individual submission)草案 draft-google-cfrg-libzk,文件本身的標題是「The Longfellow Zero-knowledge Scheme」。這份草案目前狀態是已過期、不再活躍的個人文件,不是通過 IETF 工作群組審核的正式標準——引用它的時候要把這個現況說清楚,不能當成核准規格看待。但它的摘要是目前能找到、最貼近機制描述的公開文字,原文寫的是:「combines the MPC-in-the-head approach for constructing ZK arguments described in Ligero with a verifiable computation protocol based on sumcheck for proving that C(x,w)=0」。
拆開來看這句話在講什麼。任何一個「陳述」都可以被寫成一個電路(circuit):輸入 x(例如「年齡門檻是 18」這個公開條件)、一個只有持證人知道的 witness w(例如真實生日),電路 C 的輸出如果是 0,代表這個陳述成立。問題是:怎麼證明「我知道一個 w,能讓 C(x, w) = 0」,同時不把 w 本身交出去?草案講的兩段式做法是:先用 MPC-in-the-head(一種模擬「多方安全計算」但其實只有一方在跑的技巧,讓證明者能對電路的計算過程做出承諾)建構論證骨架,再用 sumcheck 協定(一種讓驗證方能用很少次查詢就確認一個複雜多項式求和是否正確的技巧)壓縮驗證的計算量,讓「驗證」這一步比重新跑一次整個電路便宜得多。
這裡要特別提醒:以上是這份已過期草案自己陳述的機制取向,不是本文對 longfellow-zk 目前確切實作版本的斷言——工程上要落地整合,正確做法是直接讀 repo 裡的程式碼與正在進行中的安全審查結果,而不是只憑一份過期草案的摘要下判斷。README 也自陳這個函式庫「目前正在接受兩組獨立的學術與業界專家安全審查小組審查」,代表截至公告當下,第三方稽核還沒有跑完。
把這兩個技巧放回「證明」與「驗證」的分工來看會更清楚。MPC-in-the-head 處理的是證明「產生」端的問題:怎麼在不真的執行一場多方協定的情況下,讓證明者對電路的中間計算過程做出無法事後竄改的承諾。sumcheck 處理的是「驗證」端的問題:一旦證明者交出承諾,驗證方要怎麼用遠低於重新跑一次電路的運算量,確認這個承諾對應的求和結果是對的。兩者合起來的效果是:證明者要多做一些前置計算,換來驗證方只需要做很少的工作就能確認證明成立——這個「證明貴、驗證便宜」的不對稱正是零知識證明系統普遍追求的性質,也是為什麼這類函式庫能被放進手機皮夾 App 這種運算資源有限的裝置:手機端算證明比較花時間沒關係,真正在意延遲的是驗證方那一側,而驗證通常只需要一次輕量的檢查。
repo 的語言組成也透露一個工程上的現實:主要語言是 C++(97.7%),Go 佔一小部分(1.2%)。這代表整合 longfellow-zk 不是「引入一個 JavaScript 套件」等級的工作,而是要處理 C++ 函式庫的編譯、跨平台建置(repo 裡看得到 iOS 與 Android 的建置容器設定),以及跟既有服務語言之間的綁定(binding)問題。對照 README 給出的授權條款——Apache 2.0,屬於寬鬆授權,商業使用不需要額外取得授權——技術門檻與法務門檻是分開的兩件事:法務上可以自由採用,工程上仍然要投入建置與整合的實際工作。
下面這個 widget 用一個經典的零知識證明教學類比——不涉及 longfellow-zk 的實際密碼學參數——示範「證明者」與「驗證方」之間怎麼靠多輪挑戰把可信度墊高,同時驗證方全程沒看到答案本身。這是理解 MPC-in-the-head/sumcheck 這類協定為什麼「可驗證但不洩漏」的最小心智模型,不是機制本身的精確重現。
按「下一輪」看信心值怎麼隨挑戰次數爬升 · 最多 12 輪
每一輪,驗證方隨機挑一種檢查方式;證明者如果真的知道答案,每輪都能通過
驗證方每輪隨機挑一種檢查方式,證明者答對的機率隨輪數指數上升,卻從頭到尾沒看到答案本身,這就是零知識證明可信卻不洩漏的來源。
開源之後改變的是誰要重做一次工——發卡方、平台、皮夾三邊解耦
公告裡把開源這個動作的效果講得很直接:「Businesses and other relying organizations of all sizes can easily leverage this open source solution to meet their privacy needs.」——重點不是「多了一個函式庫」,是「以前要驗證年齡的每個平台,如果想要隱私強化的做法,得自己重新設計一套零知識證明協定;現在這件事只需要有人做一次,其他人接上就好」。
這個「解耦」具體發生在三個角色之間:發卡方(issuer,例如發駕照或身分證的機關)、持有者(holder,也就是使用者本人的裝置或皮夾 App)、驗證方(verifier,要求年齡證明的網站或服務)。在沒有共享函式庫以前,這三方之間的協定往往是兩兩客製:每個發卡方跟每個驗證方之間可能要各自談好一套介面。longfellow-zk 開源之後,因為它銜接的是 ISO MDOC、JWT、W3C VC 這些原本就存在的標準,三方只要各自對齊同一套開源實作,就不需要每一組發卡方-驗證方關係都重新設計一次證明協定。
下面這個 widget 讓你點選三個角色,看每個角色在這套協定裡實際擁有什麼、又刻意不知道什麼——這正是零知識證明系統設計的核心:每一方都被限制只看得到自己該看的那一小塊。
點任一角色看它的責任與不知道的事 · 3 個角色
發卡方 · 責任
核發帶有生日等原始屬性的憑證(例如行動駕照 ISO MDOC、或一張 W3C Verifiable Credential),對憑證內容簽章背書。只在核發當下接觸一次原始生日。
不知道:使用者之後去了哪些網站出示證明、出示了幾次。
持有者 · 責任
把發卡方核發的憑證存在自己的裝置或皮夾裡,在本地端用 longfellow-zk 這類函式庫算出「年齡 ≥ 18」的零知識證明,只把證明送出去,生日本身不離開裝置。
不知道:驗證方後續怎麼使用「已驗證通過」這個結果。
驗證方 · 責任
驗證收到的證明在數學上是否成立,只需要知道「陳述為真或不成立」,用來決定要不要放行。不需要、也不會拿到原始生日。
不知道:使用者的真實生日、姓名或其他任何未被納入陳述的個資。
這張圖也解釋了為什麼 EUDI Wallet 的整合值得一提。歐盟的路線是讓「持有者」這一角落統一長在使用者手機裡的官方皮夾 App,而不是散落在每個平台各自的帳號系統裡。官方公告的原句是「Member States can integrate this into their future EUDI Wallets, accelerating their development」——用「可以整合」而非「必須整合」,但邏輯很清楚:如果持有者端統一用同一套開源的證明產生邏輯,驗證方(任何要求年齡驗證的網站)也不用各自對接 27 個會員國、各自版本不同的身分系統,只要能驗證同一種格式的零知識證明就好。
下面這張表把「開源以前」與「開源以後」三個角色各自要做的工作量並排放,看得出重複建設的部分主要被砍在哪裡。
| 角色 | 開源前要做的事 | 開源後要做的事 |
|---|---|---|
| 發卡方 | 各自研究或委外開發一套支援隱私保護驗證的憑證簽發流程 | 核發符合 ISO MDOC / JWT / W3C VC 標準的憑證,證明產生邏輯交給共用函式庫 |
| 持有者(皮夾 App) | 如果要支援零知識證明,需要自建或整合各家不同、互不相容的證明產生程式碼 | 整合同一套開源實作,跨不同發卡方、不同驗證方共用同一套證明產生邏輯 |
| 驗證方(網站/平台) | 要嘛要求上傳完整證件掃描檔,要嘛得自行研發驗證協定 | 接上同一套開源驗證邏輯,只需驗證證明是否成立,不接觸原始個資 |
這裡也要誠實面對一個限制:開源不等於立即可用於正式環境。longfellow-zk 的 README 自己講得很清楚,目前正在接受兩組獨立的學術與業界專家安全審查小組審查——這代表截至公告當下,這份程式碼還沒有走完第三方稽核的完整流程。對任何考慮採用的團隊來說,這是在評估時機時該放進計算的一個現實條件,不是可以忽略的細節。
Take-away:零知識證明把「驗證」和「洩漏」拆成兩件事——longfellow-zk 開源後,發卡方、持有者、驗證方三邊不用各自重新發明一套協定,但它目前仍在接受安全審查,尚未走完第三方稽核,評估採用時機時這一點跟開源本身一樣重要。