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

你跑的是不是我给的那段

前二十章里,被摊成表的一直是「一道数独」「一段 MiMC」——都是人手写成电路的东西。而真正要的那句话是:「我完整地跑了你给我的这段程序,这是它的输出。」做到这一点的东西叫 zkVM:不为每个程序写电路,而是为一台 CPU 写一次电路。2026 年,为一个以太坊区块生成这样的证明,单张 H100 上约 35 秒

zkVM35 秒 / H100通用性的价格

◻ 本章先赊三条
第一条。「为一台 CPU 写一次电路」这个想法,把写电路的门槛从密码学工程师降到了会写 Rust 的人 第二条。通用性不是白拿的:每一步都要为「所有可能的指令」付钱,这是第 10 章那个 if 问题的放大版。 第三条。这门技术真正的终点站不是区块链,是「你怎么知道云厂商真的跑了你要的那个东西」——而那个问题今天还没有答案。

换一个层次去摊开

第 10 章的做法是:拿一个具体问题(数独),手写出它的 1620 条约束。这条路有个根本的麻烦——每换一个问题,就要重写一遍电路,而写电路是一门很窄的手艺,还极容易写错(第 22 章会展示写错的后果)。

zkVM 换了个层次:

不去为「你的程序」写电路,
而是为「一台 CPU」写一次电路——一台真的、有指令集的虚拟机
(今天主流选择是 RISC-V)。

这台 CPU 的电路只描述一件事:
    「给定当前的寄存器、内存、程序计数器,
      执行一条指令之后,它们应该变成什么样。」

然后把你的程序当成数据喂进去,
证明的是:「这台 CPU 从这个初始状态出发,跑了 N 步,
           到达了那个终止状态,而且这就是输出。」

于是流程变成了:用 Rust(或 C、Go)写你的程序 → 编译成 RISC-V → 扔给 zkVM → 拿到一份证明。你不需要懂这本书前二十章的任何内容。

◆ 这是一次抽象层的胜利,代价写在账单上

问题在于:电路必须能执行「任何一条指令」,所以每一步都要把所有指令的逻辑都算一遍,然后用一个选择器挑出真正该生效的那个结果。这正是第 10 章那条规律的放大版——一个 if 的代价是两个分支之和;一台 CPU 的一步,代价是所有指令之和。

再加上内存:CPU 可以访问任意地址,而第 10 章说过随机访问在电路里是奢侈品。zkVM 的做法是把所有访存记录下来、按地址排序、再证明「排序是对的且每次读到的是上一次写进去的」——这套东西叫内存一致性论证,它本身就是一大块约束。

结果是:同一段计算,用手写专用电路和用 zkVM,成本可以差一到两个数量级。这笔钱买的是「任何人都能用」。

2026 年的价格

拿一个业界公认的基准任务来量:为一个以太坊主网区块生成执行证明(也就是第 18 章那件事)。截至 2026 年的公开数字:

系统时间硬件
ZKsync Airbender约 35 秒单张 H100(不带递归压缩时约 17 秒)
SP1 Hypercube约 12 秒50–160 张 GPU
Brevis Pico约 6.9 秒GPU 集群

把这三行放在一起看,能读出两件事:

  1. 「几秒钟」是买来的,不是算法变快了。12 秒那一行用了上百张 GPU——那是第 17 章那个「门二」(切块并行 + 树形合并)在花钱。
  2. 单卡 35 秒是更值得记住的数字。它意味着这件事已经从「数据中心项目」降到了「一台工作站」的量级。以太坊 2026 年路线图里那条「本地证明设备 ≤ 10 万美元、功耗 ≤ 10 kW」,说的就是这条线。

还有一个容易被忽略的成本项:编译器。2025 年的一项研究测量了 LLVM 优化等级对 zkVM 的影响——标准优化能带来 40% 以上的提升,但针对 zkVM 特点的定制优化,收益反而比在真实 CPU 上小得多。原因正是上面那条:在这里成本是「乘法门的个数」,不是「运行时间」,而编译器几十年来优化的一直是后者。

预编译:把热点挖回专用电路

zkVM 的通用性太贵,于是所有实现都开了后门:预编译(precompile)。

程序里遇到「算一次 SHA-256」——
    走通用 CPU 电路:要模拟几千条 RISC-V 指令,每条都付全套的钱。
    走预编译:直接跳到一块手写的专用 SHA-256 电路(第 17 章那 33512 条)。

差距通常在一到两个数量级。

今天的 zkVM 普遍预编译了:各种哈希、椭圆曲线运算、大整数算术、配对。于是「通用 zkVM」在实践中是「一台通用 CPU + 一堆专用加速器」——这和真实硬件的演化路径几乎一模一样(CPU + AES-NI + SHA 扩展 + GPU)。

值得记住的一条推论:如果你的程序刚好全是预编译覆盖的操作,它会很便宜;如果它全是零碎的自定义逻辑,那就要按通用价付钱。这是 zkVM 上性能差异最大的来源,比选哪个 zkVM 重要得多。

再往外一层:zkML 和可验证外包

把同一个想法推到机器学习上,就是 zkML证明「这个输出确实是模型 M 对输入 x 算出来的」,而不必公开 M(或 x)。

它面对的困难和 zkVM 是同一族,但更极端:

困难为什么
浮点数域里没有浮点(第 10 章)。必须整个换成定点数,而定点化会改变模型输出,需要专门处理精度
非线性激活ReLU 是一个比较,而比较要 33 条约束(第 10 章)——一个有百万神经元的层,光是激活就几千万条
规模一次大模型推理是几百亿次乘加。第 14 章那个「证明比执行慢几千倍」乘上去,数字会非常难看

所以今天的 zkML 大多用在小模型、或者只证明推理的一部分(比如「我用的确实是那个被公开承诺过的权重」,而不证明整个前向传播)。「用一份证明确认云厂商真的跑了 GPT 而不是偷偷换成小模型」,今天做不到。

但这正是这门技术真正的终点站,也是它最值得关注的方向。第 1 章那张对照表里「你把模型扔给云厂商」那一行,今天仍然是空白的。

⌨ 自己跑一遍

「为一台 CPU 写一次电路」这个想法,用一台只有四条指令的玩具 CPU 就能看明白。关键是感受「每一步都要为所有指令付钱」。

P = 65537
OPS = ['ADD', 'MUL', 'SUB', 'JMPNZ']          # 一台只有 4 条指令的 CPU

def step(state, instr, sel):
    """sel 是一个 one-hot 向量:哪条指令生效。
       注意:4 条指令的结果 *全部* 都要算出来,然后加权选一个。"""
    a, b, pc = state
    results = [
        ((a + b) % P, b, pc + 1),             # ADD
        ((a * b) % P, b, pc + 1),             # MUL
        ((a - b) % P, b, pc + 1),             # SUB
        (a, b, instr if a != 0 else pc + 1),  # JMPNZ
    ]
    out = [0, 0, 0]
    for k in range(len(OPS)):                 # 加权求和 = 电路里的「选择」
        for j in range(3):
            out[j] = (out[j] + sel[k] * results[k][j]) % P
    return out

# 一步的成本:4 条指令各自的乘法 + one-hot 的约束
per_op   = {'ADD': 0, 'MUL': 1, 'SUB': 0, 'JMPNZ': 2}
onehot   = len(OPS) + 1                        # 每位 b(b−1)=0,加一条「和为 1」
select   = 3 * len(OPS)                        # 3 个输出,各做 4 次加权乘
per_step = sum(per_op.values()) + onehot + select
print('一台 4 指令 CPU,每步约 %d 条约束' % per_step)
print('如果指令集扩到 40 条:约 %d 条' % (sum(per_op.values())*10 + 41 + 3*40))
print('跑 100 万步:%.1f 亿条约束' % (per_step * 1e6 / 1e8))
一台 4 指令 CPU,每步约 20 条约束
如果指令集扩到 40 条:约 191 条
跑 100 万步:0.2 亿条约束

注意 results 那一段:四条指令的结果全都被算出来了,哪怕这一步只执行其中一条。这不是我写得笨——电路里没有跳转,只能这么写。指令集从 4 条扩到 40 条,每步成本涨了近十倍,而真实的 RISC-V 有上百条指令、还要加上内存一致性论证。

再对照一下预编译的收益:

sha_native = 33512          # 手写专用电路(第 17 章)
sha_in_vm  = 2000 * 191     # 在通用 CPU 上模拟一次 SHA-256 约需两千条指令
print('通用 CPU 上算一次 SHA-256: %d 条' % sha_in_vm)
print('走预编译:              %d 条  → 省 %.0f 倍' % (sha_native, sha_in_vm/sha_native))
# 通用 CPU 上算一次 SHA-256: 382000 条
# 走预编译:               33512 条  → 省 11 倍

python3 vm.py

在线跑:python.org/shell。想真的跑一次 zkVM,RISC Zero 和 SP1 都有「写个 Rust 函数就能出证明」的入门示例,几十行、十分钟的事——这正是这一章那个抽象层的意义。

▸ 在现实里
  • 以太坊的 L1-zkEVM 就是最大的一个 zkVM 应用。第 18 章那条时间线(16 分钟 → 16 秒)背后,是好几支团队在同一个基准上竞赛。而以太坊基金会 2026 年做的一件事很有意思:要求所有团队用统一的工具核算「可证明安全比特数」——因为各家宣称的安全参数此前口径不一。
  • 「可验证计算」这个词比区块链老得多。1990 年代就有关于「怎么把计算外包给不可信的机器」的研究,SETI@home 那类志愿计算项目当年就吃过「参与者伪造结果」的亏,只能靠重复计算和抽查来防。这本书讲的东西,正是那个问题三十年后的答案。
  • 受信执行环境(TEE)是同一个问题的另一种答案。Intel SGX、AWS Nitro 用硬件来保证「跑的确实是这段代码」。它比零知识快几个数量级(几乎没有额外开销),但要求你相信芯片厂商,而且过去几年被侧信道攻击破过多次。零知识不要求相信任何硬件,代价是慢几千倍。这又是一次「信任 vs 成本」的取舍,而且今天很多系统同时用两者。
  • 反面:不要指望它能验证「意图」。zkVM 能证明「我跑了这段字节码」,但它不能证明「这段字节码做的是你以为的事」。这条缝就是下一章的全部内容。
✗ 这个直觉是错的

「有了 zkVM,任何程序都能被证明了,那这门技术算是通用了,剩下的只是等硬件变快。」

「能」和「划算」之间隔着第 14 章那笔账。zkVM 的开销大约是原生执行的 10⁴–10⁶ 倍,也就是说:一个在你笔记本上跑 1 秒的程序,证明它要几个小时的 CPU 时间。只有当「验证者的数量」或「验证的次数」大到能摊平这笔钱时,它才成立——而这个条件在绝大多数场景里不成立。

更值得注意的是:硬件变快并不会改变那个倍数。CPU 快十倍,原生执行也快十倍,倍数不变。真正在缩小倍数的是算法(更小的域、更好的哈希、折叠方案)和专用硬件(为证明者的 NTT 和多标量乘法造的加速器)。「等摩尔定律」在这里是无效的策略。

判据是:问「这段计算会被多少人、多少次验证」。答案是「一次」的话,直接跑;答案是「几万个节点各一次」,那就是第 18 章那个场景。这本书从第 14 章起反复给的是同一条判据,因为它在每一个应用上都成立。

✓ 结账

这一章的一句话

把电路从「一个问题」提到「一台 CPU」,让任何会写代码的人都能生成证明;代价是每一步都要为所有可能的指令付钱,于是所有实现都靠预编译把热点挖回专用电路——而这门技术真正的终点站「你怎么知道云厂商跑的是不是你要的东西」,今天还是空白。

下一章是这本书最重要的一章,因为它讲的是前面二十一章全部成立、而结论仍然是错的那种情况。它有两处断裂,两处都不在密码学里:一是电路少写了约束——本机实测,数独电路漏掉 8 条,一份正中间填着 12 的「数独解」通过了全部 1612 条约束,证明合法,验证者看不出任何异常;二是输入本身就是编的——证明保证了「每一步都按规则算」,它一个字也没说「输入是真的」。