证明我十八岁了
网吧门口、酒吧门口、一个要求年龄验证的网站——你被要求证明「我满 18 岁」。你想说的是 1 比特:是,或者否。你实际递过去的是姓名、证件号、住址、出生日期、照片,加起来三十多比特,足够在十四亿人里唯一地定位你。这一章讲怎么只递那 1 比特,以及为什么真正的难点完全不在密码学。
先把那笔超额支付算出来
「我是否满 18 岁」这个问题的答案,只有两种可能。
信息量 = log₂ 2 = 1 比特。
而唯一定位中国的一个人,需要多少比特?
log₂(14.1 亿) = 30.4 比特。
一张身份证上写着:姓名、性别、民族、出生日期、住址、18 位证件号。
光是那个证件号就远超 30 比特。
你为了传 1 比特,交出去了足以唯一识别你的 30 多比特。超额三十倍。
而「以为自己没暴露多少」是一种非常顽固的错觉。1997 年 Latanya Sweeney 做过一个至今被反复引用的实验:仅凭「出生日期 + 性别 + 五位邮编」,就能唯一识别 87% 的美国人。这三样在任何人眼里都不算敏感信息。
这也解释了上一章那个匿名集:你不需要被拍照才会被认出来,你只需要交出足够多的「不敏感」信息。
换成一句话
零知识版本的动作是这样的:
你手里有 一份官方签发的电子证件(里面有你的出生日期),
以及签发机构的签名。
你出示的 一份证明,内容是:
「⟦ 我有一份被官方签名的证件 ⟧,
且其中的出生日期 ≤ 2008-08-21,
且这份证件在官方名单里、没有被吊销。」
对方看到 一个「通过」,和一份几百字节的证明。
他不知道你的名字、证件号、住址,
甚至不知道你的确切生日——只知道它早于那个日期。
这句话要多少条约束?第 10 章那张成本表可以直接查:
一次 32 位「≥」比较(拆位 + 拼回去) 33 条 外加:证件在官方名单里(16 层 MiMC Merkle 路径) 2912 条 ★ 整个「我满 18 岁且证件有效」电路 2945 条约束 同一件事换 SHA-256 做 Merkle 536225 条 ← 第 17 章那个 184 倍 Groth16 证明大小 128 字节 一张身份证照片 约 200 KB 倍数 1600×
2945 条约束——对照第 10 章那道数独的 1620 条,这是同一个量级的东西。在一台普通笔记本上,生成这份证明是几秒钟的事,在手机上也完全可行。这不是一个需要等硬件进步的应用。
把「出示证件」拆成「回答一个谓词」。一旦你这样看,能拆的东西到处都是:
| 今天的做法 | 你真正需要回答的那句话 | 超额 |
|---|---|---|
| 出示身份证证明成年 | 「生日 ≤ 某日」 | 30 比特 → 1 |
| 交完整银行流水申请补贴 | 「年收入 < 门槛」 | 几百比特 → 1 |
| 交完整成绩单申请奖学金 | 「GPA ≥ 3.5」 | 几十比特 → 1 |
| 为了证明有资格投票而登记全部信息 | 「我在选民名册里,且还没投过」 | 30 比特 → 1 |
| 体检报告交给保险公司 | 「不属于这几类高危」 | 大量 → 几比特 |
注意这不是「加密」能解决的问题——加密解决「别人看不到」,而这里对方必须能验证。这是这本书第 1 章那句话最日常的一个落点:这门技术是关于「检查」的,不是关于「藏」的。
难点在哪(不在密码学)
技术已经够便宜了,那为什么你还在掏身份证?三个卡点,一个都不在这本书前十九章的内容里:
| 卡点 | 问题 | 今天的状态 |
|---|---|---|
| 1. 发证方 | 必须有官方机构签发一份可以被电路验证的电子证件。没有这一步,你证明的只是「我知道一串我自己编的数」 | 这是全部工程量的九成。电子护照(ICAO 芯片)已经有官方签名,这是目前最现成的入口 |
| 2. 撤销 | 证件丢了、作废了怎么办?零知识证明不会自动过期 | 要维护一棵「有效证件」的 Merkle 树并持续更新,而更新本身会泄露时间信息 |
| 3. 重复使用 | 如果每次证明都完全无法关联,那我可以把同一份证件用无数次——「一人一票」「每人限领一次」就没法保证了 | 用第 19 章那个作废标记:同一份证件在同一场景下算出同一个标记。但这就引入了可关联性——同一个人在同一网站的多次访问会被串起来。 |
第三行那个矛盾值得单独记住:「完全不可关联」和「防止重复使用」是直接冲突的两个目标。你只能在中间选一个点,而选点的依据是场景——投票要防重复,看成人内容不需要。这类取舍在这本书里已经出现过很多次了,它是这门技术的常态而不是例外。
这个方向在业界叫可验证凭证(Verifiable Credentials)或自主身份(Self-Sovereign Identity),标准由 W3C 维护。它比零知识大——大部分可验证凭证的实现并不用零知识,只是把签名的证书做成结构化数据。
零知识在这里的作用是加上「选择性披露」:不出示整份凭证,只出示对某个谓词的证明。这个能力常被称为 ZKP-based selective disclosure 或 anonymous credentials(后者的思想可以追溯到 David Chaum 1985 年的论文,比 zk-SNARK 早了三十年)。
先把那笔超额支付算清楚,一行算术就够,但值得亲手打一遍:
import math
print('「我满 18 岁吗」这个问题 %.1f 比特' % math.log2(2))
for name, pop in [('唯一定位一个中国人', 1.41e9),
('唯一定位一个地球人', 8.1e9),
('唯一定位一个北京人', 2.18e7)]:
print('%s %.1f 比特' % (name, math.log2(pop)))
# 一张身份证上的字段大约值多少比特?
fields = {'出生日期': math.log2(365 * 100), '性别': 1,
'住址(精确到小区)': math.log2(3e5), '姓名': math.log2(1e6),
'18 位证件号': math.log2(1.41e9)}
for k, v in fields.items():
print('%-18s %.1f 比特' % (k, v))
print('随便三样加起来就超过 30 比特了。')
「我满 18 岁吗」这个问题 1.0 比特 唯一定位一个中国人 30.4 比特 唯一定位一个地球人 32.9 比特 唯一定位一个北京人 24.4 比特 出生日期 15.2 比特 性别 1.0 比特 住址(精确到小区) 18.2 比特 姓名 19.9 比特 18 位证件号 30.4 比特 随便三样加起来就超过 30 比特了。
再数一遍那个电路的约束,直接套第 10、17 章的成本表:
cmp32 = 32 + 1 # 一次 32 位比较:拆 32 位 + 一条拼回去
mimc_hash = 91 * 2 # MiMC:91 轮,每轮一次立方
sha_hash = 33512 # 第 17 章亲手数出来的
depth = 16 # 名单有 65536 条记录
print('MiMC 版: %d + %d×%d = %d 条' % (cmp32, mimc_hash, depth, cmp32 + mimc_hash*depth))
print('SHA 版: %d + %d×%d = %d 条' % (cmp32, sha_hash, depth, cmp32 + sha_hash*depth))
print('证明 128 字节,身份证照片 200 KB,倍数 %d×' % round(200*1024/128))
# MiMC 版: 33 + 182×16 = 2945 条
# SHA 版: 33 + 33512×16 = 536225 条
# 证明 128 字节,身份证照片 200 KB,倍数 1600×
值得动手的一件事:把 depth 从 16 改成 31(名单有 20 亿条,全国人口级别)。约束数只从 2945 涨到 5675——因为 Merkle 是对数的(第 13 章)。「全国规模」和「一个城市规模」在这里几乎不差钱。
python3 -c "import math;print('%.1f'%math.log2(1.41e9), 33+182*16, 33+33512*16)"
在线跑:python.org/shell。想看真实项目,找 anon-aadhaar(印度身份系统)、zk-passport 类的开源实现——它们验证的是电子护照芯片里那份官方签名。
- 电子护照是今天最现成的入口。符合 ICAO 标准的护照芯片里有一份签发国的数字签名,用手机 NFC 就能读。「发证方」这个最难的一环,已经被全世界的边检系统解决了——所以近年好几个项目都从这里切入。
- 欧盟的数字身份钱包(EUDI Wallet)。eIDAS 2.0 法规在 2024 年生效,要求成员国向公民提供数字身份钱包,其中明确写了「选择性披露」的要求。这是这一章那个想法第一次被写进一部大型法域的法律里。(进度以官方公告为准。)
- 移动驾照(ISO/IEC 18013-5 mDL)。标准里定义了「只出示年龄断言(age_over_18)而不出示生日」的字段。注意它的基础版本用的不是零知识,而是「发证方预先签好一堆分开的断言」——一个不那么优雅但今天就能用的方案。这是一个很好的提醒:解决问题的未必是最漂亮的技术。
- 反面:年龄验证正在变成法律要求。英美和欧洲多地近年立法要求成人内容平台做年龄验证,而各家的默认实现是「上传证件」或「刷脸」——也就是这一章开头那个 30 比特的做法,只是从线下搬到了线上,还额外造出了一批新的证件数据库。这一章描述的技术,正是为了避免这个后果而存在的。
「有了零知识身份,我就能完全匿名地上网了——我证明我是成年人,而没有人知道我是谁。」
「没有人知道你是谁」这句话至少有三个漏洞,而且都不在密码学里。
一、发证方知道。那份电子证件是官方发的,官方手里有全量名单。如果发证方和验证方合谋(或被同一份法院命令要求配合),零知识提供的保护会大打折扣。真正的防线是「发证方不知道你在哪里用了它」,而这要靠协议设计保证,不是自动来的。
二、作废标记会串起来。为了防止一份证件被无限次使用,系统会要求一个可重复检测的标记;于是同一个人在同一网站的多次访问,天然是可关联的。能不能跨网站关联,取决于标记里有没有把网站身份掺进去——这是一个必须显式设计的细节,漏了就全网可关联。
三、上一章那一整章。时间、IP、行为模式。你的证明可能是完美的,而你的浏览器指纹是唯一的。
正确的期待是:它把「我把整张证件交给了一个陌生网站」降级成「我告诉了它一个是非题的答案」。这个改进非常大、非常实在,但它不是隐形斗篷。判据还是第 19 章那一条:先问「谁能看到什么」,把参与方一个个列出来,而不是问「用没用零知识」。
这一章的一句话
「出示证件」在信息论上是一次三十倍的超额支付,而把它换成「回答一个是非题」在技术上已经足够便宜;真正拦着它的是发证方、撤销和「防重复使用」——最后这一条还和「完全不可关联」直接冲突。
卷 V 到此结束。
最后一卷回到技术本身,但换一个方向:这门技术做不到什么。下一章先看它正在被推向的边界——不再是「证明我知道一个数」或「我解了一道数独」,而是「我完整地跑了这个程序,这是它的输出」。2026 年,为一个以太坊区块生成这样的证明,单张 H100 上约 35 秒。而再下一章会告诉你,即使这一切都完美运转,证明成立仍然不等于你想的那件事成立——中间有两处断裂,两处都不在密码学里。