作者: Lin Oshitani ( Nethermind Research )。感谢Matteo 、 Ahmad 、 Michal 、 Conor 、 Gustavo 、 Daniel 、 David 、Yuewang 和Ignacio的讨论和/或审阅,感谢Musa的讨论以及协助运行基准测试。
这项工作由Taiko资助,是Taiko<>Nethermind 战略合作伙伴关系的一部分。
太长不看
每个操作码的独立证明成本是多少?为了回答这个问题,我们基于 EF 的 zkEVM 基准测试工具和 imapp 团队的 Gas Cost Estimator 边际方法,在多 GPU 设置下对各个 EVM 操作码和预编译程序的每 gas 证明时间进行了基准测试。我们还使用实际证明时间测量结果,评估了 zk 周期是否能有效代表实际证明时间。
介绍
随着零知识共享 (ZK) 汇总进一步去中心化,无论是Rollup阶段还是序列器和证明器的去中心化程度都日益提高,缓解“证明器杀手”区块对其安全性和经济可持续性至关重要。这些恶意区块旨在最大化证明时间,同时保持在以太坊虚拟机 (EVM) 的 gas 限制内,从而可能引发拒绝服务攻击,或使证明过程在经济上不可行。此外,随着以太坊 L1 本身向基于 ZK 的扩容转型,它也将面临同样的挑战。
传统的EVM gas计量仅考虑执行成本,例如CPU时间、存储访问和状态增长,但无法完全捕捉证明成本。因此,零知识汇总需要额外的安全措施。一种主流方法是显式计量证明成本,并基于此指标强制执行区块限制。然而,构建此类机制需要精确的模型来描述各个操作对整体证明时间的贡献,该模型既要足够精确以防止恶意区块,又不能过度损害可用性。
为了对证明时间进行建模,我们重点关注以下核心问题:每个 EVM 操作码或预编译程序对总证明时间的贡献是多少?具体来说,如果我们添加一个消耗额外 X 个 gas 的操作码或预编译程序,在其他条件不变的情况下,它会增加多少额外的证明时间?由此,我们估算出每个操作码或预编译程序每单位 gas 的边际证明时间。
以太坊基金会之前的zkEVM 基准测试工作通过构建密集填充特定操作码或预编译代码的区块(通过循环)提供了有价值的端到端测量结果。这种方法非常适用于识别和分析证明成本极高的操作,因为目标操作的成本在整体证明时间中占据主导地位。然而,由于这些基准测试没有明确区分每次操作的成本和周围的开销(例如,将参数压入栈、弹出返回值和控制流),因此对于成本较低的操作,它们的信息量较少,因为周围的代码可能会显著影响测量结果。因此,这些基准测试不足以构建一个涵盖所有操作码和预编译代码的全面证明时间模型。
为了克服这一局限性,我们采用了imapp团队Gas Cost Estimator项目中的边际测量方法。该方法通过创建仅改变目标操作数而保持其他执行上下文不变的测试用例,将每个操作码或预编译的贡献与周围开销隔离开来。然后,我们拟合线性回归模型并取斜率来估算每单位gas的验证时间。
我们的基准测试实现基于以太坊基金会的zkEVM 基准测试工作负载工具,并由其提供支持。我们根据 gas 成本估算器的边际方法创建了一套自定义执行规范测试,并进行了扩展以更好地适应零知识证明环境(例如,一种放大方法,以防止固定零知识证明设置开销主导测量结果)。然后,我们使用 EF 基准测试工具的自定义分支执行这些测试,该分支为 SP1 添加了多 GPU 支持。
此外,我们利用基准测试结果,通过直接测量证明时间来评估 zk 周期是否能有效代表实际证明时间。我们发现,证明时间和 zk 周期之间的关系在不同的指令集和预编译版本中差异很大,这限制了基于 zk 周期的估计的准确性。
主要结果
我们首先介绍结果;对假设和实验设置感兴趣的读者可以在方法论部分找到详细信息。
在本节中,我们将介绍基准测试的主要结果,这些测试是在以下环境下运行的(有关设置的更多详细信息,请参见下面的方法论部分):
证明器:sp1-v5.2.3(带 sp1-cluster)和 risc0-v3.0.4(带
RISC0_KECCAK_PO2=15标志以防止证明器崩溃)GPU :4 x NVIDIA GeForce RTX 4090
执行客户端: reth-v1.9.3
注意:证明时间很大程度上取决于运行配置(例如,证明器标志和配置、硬件/运行时设置、证明时 GPU 的状态ETC)。因此,这些结果应解释为特定配置下的测量结果。
注:这些基准测试未进行块头验证。本地测试表明,验证对大多数操作码的影响很小;与 LOG 相关的操作码的性能提升了 4-5 倍,但它们的验证成本仍然相对较低。
每单位气体的验证时间
下方列出了 SP1 和 RISC0 架构中每个操作码/预编译程序的“每 gas 验证时间”。该指标表示使用额外一个 gas 单位对特定操作码或预编译程序产生的额外验证时间。R² 值表示线性回归的拟合优度。
更详细的数据(例如,每个操作码的回归图)托管在此处: SP1 , RISC0
一些初步观察:
密码学预编译(例如
modexp、point_evaluation)通常每个 gas 的证明时间都很长。与取模/除法相关的操作码(
mulmod、mod、div、sdiv)具有相对较高的证明时间和selfbalance。围绕 log/create(
log1、log2、log3、create、create2)的操作码在 SP1 和 RISC0 上都具有最快的每 gas 证明时间,这可能是因为它们的 gas 成本主要由数据和存储而非计算构成。总体而言,RISC0 和 SP1 中各操作码的证明时间排名大致相同,但也存在一些显著的例外(例如,
keccak256在 RISC0 中速度慢约 12 倍,而sha256在 RISC0 中速度快约 10 倍)。这可能是由于某些操作码的 zkvm 预编译版本在一个架构中存在而在另一个架构中不存在(有关证明器之间的更多比较,请参见附录中的图表:SP1/RISC0 比较)。
每个 ZK 周期的验证时间
ZKVM 引入了ZK 周期这一概念,它代表 ZKVM 执行程序验证所需的计算步骤数,类似于传统计算中的 CPU 周期。一个自然而然的问题是:ZK 周期能否很好地代表实际的验证时间?如果 ZK 周期与所有操作的验证时间呈线性相关,那么它们可以作为 ZK gas 计量的一个更简单的指标。然而,如果这种关系是非线性的,或者在不同操作类型之间存在显著差异,那么为了实现精确的 DoS 防护,必须直接测量验证时间。
为了回答这个问题,我们对每个操作码/预编译程序,以证明时间为因变量,zk周期数为自变量,拟合了线性回归模型。以下是研究结果。
在特定操作中,循环次数与证明时间的关系呈强线性(R² 值较高):循环次数翻倍,证明时间也大致翻倍。但循环次数到时间的转换率并非普遍适用,它很大程度上取决于具体操作。
下图的柱状图显示了每个操作的“每个 zk 周期的耗时”。如果周期数是一个好的指标,那么所有柱状图应该基本对齐;但实际上,它们分布很广——这可能是因为某些操作是在不同的电路中实现的(例如,自定义 zkVM 预编译)。例如,在 SP1 中, bn128_add操作每个 zk 周期耗时约 930 纳秒,而pop操作仅需约 63 纳秒,两者相差约 15 倍。这表明仅凭 zk 周期数不足以准确计量验证时间。缓解措施包括:
使用测量验证时间进行计量(如本文所述)
改进周期核算,以更好地反映验证时间(例如,改进不同电路之间的 zk 周期转换)
使用 ZK 周期作为证明时间的代理,但要保守一些,即使用每个 ZK 周期操作的最慢时间。
方法论
我们采用边际成本法,该方法最初由 imapp 团队开发,用于 L1 天然气重新定价,作为天然气成本估算器项目的一部分,以分离单个操作的验证成本。
首先,我们为每个操作码/预编译创建 4-7 个块,每个块的操作次数各不相同(0、N、2N、3NETC),同时通过保持所有其他因素在变体之间相同(堆栈设置、内存初始化、控制流和清理操作)来保持恒定的开销。
下表是 ADD 操作码的一个字节码结构示例。请注意,每个变体都恰好包含 20 个 PUSHE 和 10 个 POP——只有 ADD 操作码的数量不同。
| 操作数 | 设置 | 主要的 | 清理 |
|---|---|---|---|
| 0 | 推×20 | — | POP×10 |
| 3 | 推×20 | 加法 + (弹出 + 加法)×2 | POP×8 |
| 5 | 推×20 | 加法 + (弹出 + 加法)×4 | POP×6 |
| 10 | 推×20 | 加法 + (弹出 + 加法)×9 | POP×1 |
接下来,我们执行每个区块变体,并记录整个区块的总证明时间和消耗的 gas 量。然后,我们对所有变体拟合proving_time = α × gas_used + β 。以下是 SP1 中 ADD 操作码的示例:
斜率α表示所考虑的操作码或预编译代码每单位 gas 的边际证明时间。由于只有目标操作次数会变化,而所有其他执行上下文保持不变,因此α可以单独衡量该操作的贡献。在上面的示例中, α = 3.78µs ,这意味着在 SP1 模式下,使用 4 个 GPU 的 ADD 操作的每 gas 证明时间为3.78µs 。
操作码/预编译参数
对于操作码/预编译参数,我们使用固定的输入,这些输入经过精心挑选,力求具有代表性,并在适用情况下尽可能地涵盖“最坏情况”。我们的选择参考了现有基准测试套件中已知的最坏情况模式。使用最坏情况输入有助于避免无意中测量仅在特殊输入(例如,全零缓冲区)下才会出现的优化快速路径。
我们的工作假设是,对于给定的操作,证明时间与 gas 消耗呈线性关系,这不仅体现在不同的操作次数上,也体现在同一操作的不同论证选择上(即,gas 翻倍,证明时间也翻倍)。这基于以下两个假设:(1)gas 能准确反映计算复杂度;(2)证明时间与计算复杂度成正比。
在实践中,这一假设可能不成立,例如,因为 gas 值并不能准确反映计算复杂度,或者 ZKVM 对某些论证会表现出不同的行为。因此,我们的测量结果应解释为针对特定测试输入的结果,而对于其他输入,则仅作为近似值。对论证相关的证明成本进行更细致的分析,考察每个操作的证明时间如何随整个论证空间变化,将留待未来工作。
最后,对于像 LOG 和 CREATE 这样的操作,其 gas 消耗主要受数据大小和内存扩展而非计算量驱动,我们使用标准参数(例如,32 字节有效载荷),而不是基于 gas 消耗的最坏情况参数。使用较大的有效载荷会增加 gas 成本,但不会显著增加证明工作量,从而低估了每单位 gas 的计算证明开销。
与 EF 基准测试工具集成
我们的基准测试流程基于以太坊基金会的zkEVM 基准测试工作负载,并引入了两个关键扩展:
自定义 EEST 边缘测试/夹具:我们添加了遵循边缘方法的自定义执行规范测试(EEST)。
多 GPU SP1 支持:我们扩展了EF 的 ZKEVM 基准测试工具,以支持 SP1 集群执行,从而可以使用 4 个 GPU 来验证相同的工作负载。
通过这些新增功能,我们生成 EEST 夹具,使用 EF 工具将其转换为 zkEVM 见证输入,通过 EF 主机运行器(4 个 GPU 上的 SP1 或 RISC0)运行证明,然后对生成的指标(证明时间、gas、zk 周期)进行后处理,以拟合回归并提取每个 gas(或每个 zk 周期)的边际证明时间。
申请及后续步骤
以下是这些测量结果的一些潜在实际应用:
面向零知识库 (ZK) 的区块计量:基于每 Gas 的证明时间测量值可用于直接计量和限制区块的证明成本。具体而言,可以通过对每个操作码/预编译消耗的 Gas 进行加权,并乘以一个由其每 Gas 证明时间计算得出的系数,来定义一个“ZK Gas”指标。通过限制每个区块的总 ZK Gas 消耗,该协议可以限制最坏情况下的证明时间,并缓解证明器杀手区块的出现。
对 zkVM 实现者的指导:逐操作细分突出了哪些操作码/预编译需要改进,帮助 zkVM 团队确定优化优先级(例如,专用电路)。它还为改进 ZK 周期统计提供了一个具体信号,使 ZK 周期数在不同的电路路径上更一致地跟踪验证时间。
对客户端实施者的指导:客户端团队可以使用这些测量结果来识别 ZK 不友好的热点,并优化特定的操作码/预编译实现或执行模式,以减少证明开销。
项目后续步骤:
论证依赖型基准测试:扩展该方法以涵盖所有输入。
持续性能监控:实现流水线端到端自动化(测试夹具生成→见证生成→验证→回归→报告),以便跟踪每个操作码/预编译 zkVM/客户端的性能随时间的变化。
多维计量:研究如何将这些测量结果纳入多维气体计量中。
链接
我们的基准测试详细数据(例如,每个操作码/预编译的回归图):
用于基准测试的自定义执行规范测试: https://github.com/linoscope/execution-specs/pull/1/changes
EF 工具中 SP1 集群支持的 PR:
Ignacio 关于 EF ZK 基准测试工具的精彩演讲: https://youtu.be/D2TpmD62tjQ?si =D4mzEagb2MXX1QXy&t=1510
Jacek关于边际方法的演讲很精彩: https://www.youtube.com/watch?v =KmaFpyV9jvM
附录:SP1/RISC0 比较
下图显示了(RISC0 proving_time_per_gas) / (SP1 proving_time_per_gas)的关系,其中每proving_time_per_gas是每个验证器对应操作码的回归斜率。参考线 1.0 倍表示性能相同。大于 1.0 倍的值表示 RISC0 较慢(对于该操作码/预编译,RISC0 的每 Gas 验证时间比 SP1 长),小于 1.0 倍的值表示 SP1 较慢。









