卷 VI · 余账CH 23深度 23/23

结账

这本书一共赊了 69 条账。绝大多数在各自那一章末尾还清了,剩下八笔还不清——而那八笔,恰好就是这个领域今天还在吵的地方。这一章把账收拢:一张二十三条错误直觉的自查表,一份还不清的清单,以及按方向分岔的继续往下走。

自查表 23 条欠账 8 笔继续往下走

◻ 本章先赊三条
第一条。你现在手里有的,不是「懂了零知识证明」,而是一组判据——遇到一个陌生的系统时该问哪几个问题。 第二条。这本书还清不了的那八笔账,不是我偷懒,是这个领域本身还没还清 第三条。把这本书学到的那个动作抽出来,它比零知识证明本身适用得多:「这里能不能改成一个人做、其他人抽查?」

先把主线收回来

◆ 二十三章,一句话

把一件事做完,和确认它做对了,是两笔可以分开定价的账。

分开的办法只有一个动作:先把要检查的东西改写成「错一处就错一大片」的形式,然后随机抽查几处。抽查平时不管用(第 3 章:要 99% 的把握得抽 4714 格,比整份记录还长 4.6 倍),而改写让它管用了(第 11 章:同样抽三格,0.29% → 99.9996%)。

剩下的一切都是配件:承诺保证他不能事后改答案(第 4 章),Fiat–Shamir 让裁判可以不在场(第 8 章),Merkle 树让「只打开一格」变便宜(第 13 章),递归让这一切可以叠起来(第 17 章)。

而「零知识」——那个占了名字全部注意力的性质——是这个结构的副产品:验证者只看了 34 格,他当然学不到全貌。

自查表:二十三条错误直觉

每一章都有一条。合上书之后,这张表是你真正带走的东西。

✗ 听起来对的那句话✓ 实际上
1零知识证明是一种加密技术它是关于检查的技术,隐私是附赠的开关。最大的应用(zk-rollup)根本不用那个开关
2验证者学不到东西,怎么可能被说服他学到了「这句话是真的」,仅此而已。而他看到的记录,他自己就能造
3抽查不够就多抽几次力气来自错误的扩散率,不是抽的次数。而扩散率默认握在作弊者手里
4哈希一下就算承诺了哈希只给「改不了」。「藏得住」要靠足够的随机性——不加盐时 28.2 微秒就被反推
5零知识要重复很多轮,所以慢轮数多是挑战空间小的症状。挑战 2²⁵⁶ 种时,一轮就够
6抽取器能套出秘密,这协议很危险它需要「同一个押注上两次不同的回答」,真交互做不到。除非实现让 nonce 重复了
7模拟器能造假记录,系统被攻破了他能伪造历史,不能预测未来。说服力在顺序里,不在内容里
8挑战是自己算的,多试几次就行每试一次只得到一个均匀随机值。危险的是挑战空间太小的时候
9我用的是审计过的知名库,没事Frozen Heart 命中的就是知名库。而且漏洞常在库和你代码的接缝
10电路就是程序的另一种写法没有控制流,展开到最坏情况。成本 = 乘法门的个数,和运行时间无关
11数据变长 64 倍,要检查的东西更多了增加的是冗余不是信息。冗余买到的是「局部可检查性」
12概率性验证不够严谨,我要 100%2⁻²³⁵ 比你的内存被宇宙射线翻位低六十多个数量级
13Merkle 树就是拿来做完整性校验的真正的价值是局部打开:证明「这一小块属于那个整体」而不出示整体
14zk 让计算变快了它让计算慢了几千倍。快的只有验证者,而且是拿证明者的钱换的
15可信设置泄露了,我的隐私就没了泄露的是可靠性——后果是凭空造币,不是被看光。而且不会被发现
16STARK 比 SNARK 新,所以更好STARK 是 SNARK 的一种。四个旋钮,没有一个方案全好
17证明套证明会指数爆炸每层电路固定大小,n 层是 O(n),树形合并是 O(log n)
18zk-rollup 是隐私方案它的交易数据必须公开,否则没人能重建状态。名字里的 zk 是叫顺了嘴
19用了零知识,我就匿名了匿名集 = 2^H。协议内泄露为零,而去匿名几乎全部发生在协议之外
20零知识身份让我完全匿名发证方知道、作废标记可关联、浏览器指纹唯一。它降级了泄露,没有消除
21有了 zkVM,任何程序都能被证明了「能」和「划算」隔着 10⁴–10⁶ 倍。而且硬件变快不改变这个倍数
22用了零知识 + 做过审计 = 安全证明只保证「有见证满足那堆约束」。规则是人写的,输入是人给的
23读完这本书我就懂零知识证明了你拿到的是一组判据和一张地图。实现细节(FRI 的可靠性、置换论证、曲线选择)都在书外

还不清的八笔账

这本书努力避免了「讲到魔法处就跳过」。但有八处,我只给了形状没给证明——而其中大部分,这个领域本身也还在往里填。

#欠的是什么状态
1低度测试(FRI)的完整可靠性(第 11 章那个洞)协议本身是成熟的,但学界对它最紧的那个可靠性界仍在推进;一部分实用参数依赖猜想而非已证明的界。生产系统用不用猜想界,是一个真实的争论
2随机预言机模型(第 8 章)被证明在理论上不严谨(存在只在该模型下安全的构造),但三十多年没出过因它本身而起的事故。所有非交互系统都站在它上面
3算术友好哈希的成熟度(第 17 章)MiMC / Poseidon 这一族不到十年,代数结构简洁——而这正是密码分析最喜欢的下手处。已有同类设计被迫调过参数
4仪式参与者的独立性(第 15 章)「至少一人诚实」的数学是干净的,但「他们真的互相独立吗」是一个社会学问题,没法证明
5电路是否表达了你的意图(第 22 章)形式化验证工具在进步,但覆盖面有限。这是今天事故率最高的一环
6输入是谁担保的(第 22 章)没有技术解,只能把信任往外推一层
7证明者成本(第 14、21 章)仍在 10⁴–10⁶ 倍。近年进步很快,但没有理由相信它会掉到 10² 以下
8可验证外包还没落地(第 21 章)「云厂商真的跑了我要的那个模型吗」——第 1 章那张表里这一行,今天仍然是空的

把这八笔和自查表放在一起,就是这门技术今天真实的形状:中间那一段(数学)异常坚固,两头(规格与输入)异常脆弱,而成本仍然高得只有极少数场景付得起。

继续往下走

◆ 想把数学补严
  • Justin Thaler,《Proofs, Arguments, and Zero-Knowledge》。这是今天这个领域唯一一本把交互式证明、PCP、算术化、多项式承诺、SNARK 系统串成一条线的教材,作者长期免费公开。如果只读一样东西,读它。这本书里的每一章,在那本书里都有对应的严格版本。
  • GMR 1985,《The Knowledge Complexity of Interactive Proof Systems》。零知识的原始论文,第 7 章那个模拟器就是它提出的。值得读的是它的动机部分——为什么会有人想到用「能不能被模拟」来定义「学到了什么」。
  • PCP 定理(Arora–Safra、Arora 等,1998 年整理成型)。第 12 章末尾那句话的来源:任何证明都可以改写成只需随机读常数个比特就能验证的形式。这本书讲的全部东西,是那个定理三十年后的工程化。
  • 原始论文,按需读:Groth16(2016)、PlonK(2019)、KZG(2010)、FRI 与 STARK(Ben-Sasson 等,2018)、Bulletproofs(2017)、Nova(2021)。读的顺序建议按第 16 章那张表,先读你要用的那个。
◆ 想动手写
  • 最短路径:Circom + snarkjs。写一个几十行的电路(比如「我知道一个数的哈希原像」),编译、生成证明、验证,一个下午能跑通。你会立刻看见「约束数」这个数字,第 10 章那套成本直觉会瞬间变具体。
  • 更现代的写法:Noir、Halo2。前者语法更像普通语言,后者是 Zcash 那套,能看到真实生产系统的结构。
  • 最省事的路径:RISC Zero 或 SP1。写一个普通的 Rust 函数,直接出证明——第 21 章那个抽象层。先跑通它,再回头看它替你挡掉了多少东西。
  • 练手项目建议:(1) 把第 22 章那个数独电路完整写一遍,然后故意漏掉那 8 条约束,亲手造出那份含 12 的证明;(2) 实现第 11 章的低度扩展并测量码距;(3) 用第 13 章那棵 Merkle 树做一个「证明我在名单里」的最小可用身份验证。
◆ 想看别人踩过的坑
  • 公开审计报告。去读几份主流 zk 项目的审计报告,专挑 under-constrained 那一类看。你会发现它们的形状和第 22 章那个数独完全一样。
  • Frozen Heart 的公开分析(2022)。第 9 章那件事的第一手材料,包括每个受影响项目具体漏掉了哪个参数。
  • 以太坊基金会关于 L1-zkEVM 的公开路线图与讨论。它是今天能看到的、最完整的一份「把这套东西部署到几千亿美元系统上要考虑什么」的清单——包括那些不好看的部分。
⌨ 自己跑一遍

最后一次动手,把这本书的骨架用一段代码复述一遍。下面这四十行是全书的缩微版——它没有零知识、没有 Fiat–Shamir、没有 FRI,但它有那个核心动作:编码、承诺、抽查。

import hashlib, random
P, G = 65537, 3
# ntt / lde 见第 11 章,build / open_at / verify 见第 13 章

def prove(trace, blowup=64):
    ext = lde(trace, len(trace) * blowup)     # ① 编码:让一处作弊染遍全局
    layers = build(ext)                       # ② 承诺:钉死整张表
    return ext, layers, layers[-1][0]

def spot_check(ext, layers, root, k=3):
    n = len(ext)
    seed = int.from_bytes(hashlib.sha256(root).digest(), 'big')   # ③ 挑战:Fiat–Shamir
    rnd = random.Random(seed)
    for _ in range(k):                        # ④ 抽查 + 打开
        i = rnd.randrange(n)
        if not verify(root, i, ext[i], open_at(layers, i)):
            return False
    return True

# --- 诚实证明者 ---
ext, layers, root = prove(trace)
print('诚实的表,抽 3 格:', spot_check(ext, layers, root))

# --- 作弊者:改一格再编码 ---
fake = trace[:]; fake[700] = (fake[700] + 1) % P
fext, flayers, froot = prove(fake)
same = sum(a == b for a, b in zip(ext, fext))
print('两张编码表相同的格子:%d / %d = %.4f%%' % (same, len(ext), 100*same/len(ext)))
print('作弊被抓住的概率:%.6f%%' % (100 * (1 - (same/len(ext)) ** 3)))
诚实的表,抽 3 格: True
两张编码表相同的格子:1023 / 65536 = 1.5610%
作弊被抓住的概率:99.999620%

把这四十行读三遍。这本书二十三章讲的,就是让这四行注释里的每一个词都站得住。

python3 spotcheck.py

在线跑:python.org/shell。把前面几章的 nttldebuildopen_atverify 拼进同一个文件即可,一共一百来行、零依赖。

▸ 在现实里
  • 把那个动作带出去。这本书真正想留给你的不是 zk-SNARK,是遇到任何「所有人都要重做一遍」的结构时,会先问一句:这里能不能改成一个人做、其他人抽查?——CI 里每个人都重跑全量测试、每个服务各自校验同一份配置、每台机器各自重算同一个报表、每个审计员各自复核同一批凭证。
  • 把那三个问题带出去。看到任何「可验证」的宣称,问:(1) 验证的是什么陈述?(2) 输入是谁担保的?(3) 一共有几条必须成立的假设?这三个问题在这本书之外一样好用。
  • 把「隐私是个数」带出去。不要再问「这个东西匿名吗」,问「我藏在多少人里」。这一个改动会让你对绝大多数隐私产品的判断立刻变准。
  • 把「事故不在密码学里」带出去。更一般的形式是:当系统里某一环被做到接近完美时,事故率不会归零,它只会整体搬到别的环上。这句话在这本书之外的适用面,可能比零知识本身还宽。
✗ 这个直觉是错的

「读完这本书,我算是懂零知识证明了。」

你懂的是它的形状和一组判据,这已经比绝大多数关于这个话题的讨论走得远——但离「懂」还有距离,而且我想把这个距离说清楚,因为知道自己缺什么本身就是一种资产。

这本书跳过的东西:FRI 折叠的具体协议与它的可靠性论证(第 11 章那个洞我只画了形状);PlonK 的置换论证(怎么保证「同一根线在不同位置取值一致」);查找论证(lookup,今天用来处理位运算的主流手段);椭圆曲线配对的构造;各系统的具体参数选择。这些是从「读懂」到「能写」之间的全部内容。

但这本书没跳过的东西,恰恰是大部分材料会跳过的:抽查在原始形式下为什么没用(第 3 章)、编码那一步为什么不是密码学(第 11 章)、简洁的代价由谁付(第 14 章)、以及证明成立为什么不等于结论为真(第 22 章)。判据是:如果你现在能向别人解释「为什么改一格会变成错六万四千格」,而且能说出这一步跟加密毫无关系,那这本书的目的就达到了。

✓ 结账

这一章的一句话

这门技术把「计算是否正确」这一段做到了近乎完美,于是整个系统的脆弱点全部被挤到了两头——规格是人写的,输入是人给的;而它留给你最有用的东西,不是任何一个密码学构造,是那个可以用在任何地方的问题:这里能不能改成一个人做、其他人抽查?

——全书完。

感谢你读完《抽查》。回到目录,或者去把第 11 章那四十行代码跑一遍——那个 1023 是你自己机器上数出来的,不是我说的。这本书从头到尾想让你习惯的,正是这件事。