a16z:理解Jolt zkVM相关思考和澄清
自去年夏天的主题论文和 Lasso 部署开始,到上个月的完全开源的 Jolt 实现的发布,我们一直都在致力于 Lasso+Jolt(我们的全新简便的高性能 lookup argument 和 zkVM)技术的研究并取得稳步进展。
与现有技术相比,这一实现显示了 Jolt 的光明前景,并对 SNARK 设计中的许多传统智慧发起挑战。自发布以来,我们陆续进行更新,增加了对 Rust 标准库的支持,整合了来自 10 多名贡献者的改进,合并了近 50 个 pull 请求,并且改进了代码库的模块化性能和可扩展性。
在我们继续增强 Jolt 的同时,我想回应外界的质疑和困惑,澄清误解,分享我对一些关键问题的看法。我在本文要探讨的四部分内容是:(1)sum-check 协议与 Binius 承诺方案之间的关系,(2)sum-check 和 lookups 在 Jolt 中的作用,(3)椭圆曲线与哈希,(4)与 zkVM 相关的预编译。
1、Sum-check 协议与 Binius 承诺方案
Commitment schemes 通常被视为 SNARK 的关键组成部分。但还需注意另一个组件的作用也很重要,那就是多项式 IOP。例如,多线性多项式的 Binius 承诺方案是一个重大进步,但它必须与多项式交互式 oracle 证明(polynomial interactive oracle proof,PIOP)配对,才能证明所提交的数据实际上验证了证明者的声明。
Binius 的承诺与使用 sum-check 协议的 PIOP 高度兼容。原因很清楚(sum-check 依赖于多线性多项式,而不是单变量多项式;FRI-Binius 甚至在内部使用 sum-check),也很微妙(sum-check PIOP 天然地跨任何特征字段运行,这对于充分利用 Binius 的新性能至关重要)。Binius 的承诺与目前最常见的 PIOP 不兼容,很遗憾,这些 PIOP 不使用 sum-check。
设计一个快速的 PIOP 需要更多的洞察力,而不仅仅是「应用 sum-check」这一句话而已。Binius 使用 sum-check 协议来实现高效的多项式 IOP。Binius 论文的第 4 和第 5 部分致力于设计新的高效的基于 sum-check 的 PIOP,以与承诺方案相结合。
Binius 承诺和 Jolt 的搭配就像花生酱和果酱一样,因为 Jolt 是目前唯一一个完全基于 sum-check 协议的 zkVM。如今,Jolt 使用基于椭圆曲线加密的承诺方案,但将 Binius 承诺纳入 Jolt 是我们工作的重中之重。
2、Sum-check、lookups、性能和简洁性
是什么让 Jolt 与众不同?是因为 Jolt 是第一个(也是目前唯一一个)专门使用基于 sum-check 的多项式 IOP 的 zkVM,还是因为 Jolt 实现了 lookup 奇点(几乎所有的事情都是通过 lookups 而非约束(constraint)系统或电路来完成的)?答案是,两者兼有。与之前的 zkVM 相比,Jolt 的大部分的简洁性优势都来自于 lookups,而它的性能优势来自于 lookups 和 sum-check 的使用。
单纯的 lookup 方法对于某些指令(没有非常小的电路的指令)更好些,但是对于具有非常小的电路的其他指令可能要更差。但总的来说,单纯的 lookup 方法对性能来说只有好处没有坏处,至少在处理 256 位字段时如此。如今,Jolt prover 投入 20% 的时间在「指令执行」lookup 上,40% 的时间用于验证约束信息。添加更多约束来减少 lookups 是没有任何帮助的。
大概说来,Jolt 使用 lookups 来实现 CPU 获取 – 解码 – 执行循环的「获取」和「执行」部分。这些 lookups 速度足够快,以至于 prover 的大部分时间都用于证明它运行了「解码」,这是通过传统的约束来处理的。
单纯的 lookup 方法还会促进更简洁、更可审计的实现。这些好处很难被量化,需要时间才能被看见和认可。但在代码行数(Jolt 代码库约为 2.5 万行代码,比之前的 RISC-V zkVM 少 2 到 4 倍)和开发时间等方面,Jolt 表现出色。这样的改进要比性能上的改进难得多:虽然我预计 zkVM prover 在未来几个月的速度将比 2023 年 8 月差不多快百倍,但很难想象 zkVM 的代码行数什么时候能减少 10 倍。
3、椭圆曲线
公共话语低估了拥有针对椭圆曲线的快速 zkVM 的好处,部分原因是大家普遍对基于哈希的承诺方案(如 Binius)热情满满。
在证明关于椭圆曲线加密的声明时,基于曲线的 zkVM 可以避开非原生字段算法,而非原生字段算法会增加成百上千倍的证明时间开销。这些应用包括很多数字签名(与区块链轻客户端和基于 SNARK 的桥接相关的主要工作)的证明,Plonk/Groth16/Nova/Honk 证明的聚合,以及 Verkle 树认证路径的证明。
我乐观地认为,社区将关注基于 sum-check 的 PIOP 与 FRI-Binius 承诺方案的结合,将其作为在许多应用程序中执行 SNARK 的正确方法。即使发生这种情况,基于曲线的快速 SNARK 仍然有用,除非这个世界完全弃用椭圆曲线加密(例如,在社会完成从非量子安全的加密系统转移之后)。
小结:
基于曲线的承诺与目前的所有其他 zkVM 相竞争(所有现存其他 zkVM 都已经使用哈希承诺方案处理小字段)。
在证明关于椭圆曲线的声明时(至少在证明非原生字段算法没有重大进展的情况下),人们会想要使用结合曲线的 Jolt。
作为一个纯 zkVM,Jolt 和 Binius 相结合的承诺将比其他替代方案快很多,除非是证明关于曲线的声明或小字段证明(在这种情况下,人们将使用结合曲线的 Jolt),否则人们将使用 Jolt 和 Binius 相结合的承诺方案。
在将证明发布到链上之前,基于椭圆曲线的 SNARK 将继续用于压缩证明大小和验证者成本。在这种情况下,处理大字段的 zkVM 将发挥作用。即使在今天,人们认为基于哈希的 zkVM 项目实际上是使用在 BN254 曲线上定义的 zkVM 作为递归过程的一部分。
4、 预编译和 zkVM 基准
关于预编译及其在 zkVM 和基准测试中的作用已存在一些讨论。在我进行解释之前,先来解释一下什么是预编译应该会有所帮助,因为预编译这个词的含义在不同的上下文中有所不同。
(1)以太坊中的「预编译」
在以太坊虚拟机(EVM)中,预编译是一个经常执行的操作,并且受原生支持以提高效率。这就避免了通过冗长的 EVM 操作码序列执行这些操作所带来的大量开销和过高的 gas 成本。
「EVM 预编译」和「初始指令」(操作码)之间的区别主要是语义上的区别。例如,Keccak 哈希函数是一个 EVM 操作码,而 SHA-2 则是 EVM 预编译。预编译和操作码都是经常执行的操作,以太坊出于相同的目的对它们提供原生支持:优化效率和 gas 成本。不可否认,预编译是 EVM 的一部分,EVM 通常用于广泛地描述以太坊执行环境,包含的不仅仅是操作码。
如果 EVM 的功能与操作码基本相同,为什么还要有预编译呢?主要在于惯例问题。另一个可能的原因是,预编译由相对复杂的操作组成,比如将来可能需要更改的加密原语,如果它们没有分配操作码,则将来更改起来会更容易一些。
(2)zkVM 设计中的「预编译」
在 zkVM 设计中,预编译是指针对特定函数(如 Keccak 或 SHA 哈希)或特定一组椭圆曲线操作的具有特殊用途的 SNARK。如今的 SNARK 预编译通常是通过手动优化的约束系统来实现的(尽管随着社区转向基于 sum-check 的 SNARK,这些约束系统的性质以及它们被证明的方式将会改变)。
EVM 预编译器 zkVM 预编译之间具有深度相似性。在 Jolt 发布之前,zkVM 通过手动优化的约束系统实现初始指令,每个指令一个,就像它们实现预编译一样。所谓的 zkVM 预编译和所谓的初始指令之间的区别纯粹是语义上的。他们之间没有实际的区别。
在 Jolt 中,我们使用 lookups 来实现初始指令,而不使用传统的约束。但是选择通过约束来实现一些初始指令并没有什么大问题。(事实上,lookups 甚至可以被视为一种约束。)实际上,正如我之前说过的,一旦我们转向 Binius 承诺方案,我们可能不得不使用传统的约束来实现 RISC-V 的加法和乘法。
5、zkVM 基准测试
有了这些背景了解,下面我来谈谈我对预编译的看法,因为它们与 zkVM 和基准测试有关。
首先,在没有预编译的情况下对各种 RISC-V zkVM 进行基准测试正是对 RISC-V zkVM 进行基准测试的意义。「zkVM」一词是一个非正式的叫法,因此必然产生分歧,但在我看来,具有一个或多个预编译的 RISC-V zkVM 不再是 RISC-V 的 zkVM:它是基于 RISC-V 的新指令集的 zkVM,将每个预编译添加为初始指令。至少,添加到 zkVM 的每个预编译都会削弱 zkVM 范式的价值主张——每添加一个电路都会增加潜在的 bug 表面积,并且现有程序将无法开箱即用地利用这些新的预编译。
有些人还将 zkEVM 的 EVM 预编译概念与 zkVM 的预编概念混为一谈。但这是两个截然不同的东西。虽然 zkEVM 的一些关键操作——比如 Merkle 哈希和数字签名验证——确实比初始的 RISC-V 指令更复杂,但这并不能改变 EVM 预编译和初始 EVM 指令之间没有功能差异这样一个事实。zkEVM 必须支持 EVM 预编译,以声明与 EVM 对等。换句话说,不支持 EVM 预编译的 zkEVM 不同于像 Jolt 这样的 RISC-V zkVM,后者将使用预编译扩展 RISC-V 以外的指令集。
另一个问题是如何选择一组「公平」的函数来对 zkVM 进行基准测试。但是对于 RISC-V zkVM 来说,任何函数集都是公平的。Prover 时间几乎完全取决于 RISC-V CPU 运行的周期数,原因有两点。首先,prover 在「获取 – 解码 – 执行」循环的「执行」部分花费了一小部分时间。其次,不同的 RISC-V 指令,以及内存访问,证明时间都高度相似。(在 Jolt 中,它们都是通过离线内存检测技术来处理的。)
最后,如果使用预编译,Jolt 的表现可能不会比其他替代方案差。事实上,我预计它会表现更好,因为基于 sum-check 的预编译将是最快的,并且可以集成到 Jolt 中而没有开销,因为它专门使用了基于 sum-check 的 PIOP。在这一点上,有些人担心使用椭圆曲线承诺方案的预编译将比使用基于哈希方案的预编译差很多。如今,Jolt 使用曲线,但这并不是必须的,我们一直对转向 Binius 的计划持开放态度。
6、关于基准的广泛思考
我们进行基准测试的主要目标是确定不同证明系统的内在性能情况,在某种程度上,它们可以与它们的实现拆分开。这种方法使社区能够理解并聚焦于设计高性能且安全的 SNARK 的正确技术。但是,当试图比较两个不同的 SNARK 时,数不尽的混淆因素往往导致不可能进行严丝合缝的对比。
工程方面的努力是这些混淆因素之中的一个,尽管社区中的许多人似乎持对立观点。想法似乎是这样的:如果一个项目添加了「特性」,比如针对特定硬件的预编译或进行了优化,那么它应该在任何基准测试中都拥有「荣誉」表现。
两种观点都有其可取之处。但长远来看,后一种观点显然站不住脚。新方法在任何基准测试中都将永远处于劣势,因为那些新方法没有与旧项目可比的时间。这样的观点是对进步的阻碍。
随着时间的推移,我预计基准测试相关的混淆因素将减少。随着 SNARK 的开发工具的成熟,SNARK 获得良好的性能所需的工程方面的工作量将变少。zkVM 的成本主要取决于周期数,而不是任何特定应用程序的特性,这是一个小小的奇迹(至少对于 RISC-V 如此)。如果人们关注约束系统的选择(而不是今天的 R1CS、AIR、Plonkish 等碎片化状态),针对约束系统的 SNARK 可能也会出现类似的情况,使用约束系统大小的简单度量方法来代替周期数。
在此之前,很难在混杂因素控制不足和过度控制之间取得适当的平衡。分歧是不可避免的,而建设者们将必须提供任何一个基准背后的全部背景、细节信息和基本原理,以便社区能够理解和探讨。