详解以太坊区块验证器Zeth:第一个Type 0 zkEVM
原文标题:Announcing Zeth: the first Type Zero zkEVM
作者:Tim Cartens, Victor Graf, Rami Khalil, Steven Li, Parker Thompson, Wolfgang Welz, Zeth Collaboration
编译:bayemon.eth, ChainCatcher
8月25日,基于 RISC Zero zkVM 的以太坊开源 ZK 区块验证器 Zeth 公开发布。Zeth 通过在 zkVM 中完成构建新区块所需的全部工作,而无需依赖验证器或同步委员会。Zeth 已在以太坊主网的多个真实区块上得到验证,并通过了以太坊官方测试套件的所有相关测试。Zeth 能够在4周之内实现基于 zkVM 的 Rust 支持和包括 revm、etherthers 和 alloy 在内的强大板块。通过 zkVM 对连续性和 Bonsai 证明服务的支持,Zeth 可以在几分钟内生成这些证明。有了Zeth对链上验证的支持,任何人都能以低成本在链上验证这些证明。在这篇文章中,我们将介绍更多细节,如果你想深入了解代码,请查看源代码并访问 RISC Zero 开发者门户网站。
摘要
大约一年前, Vitalik 在zkEVM的不同类型中阐述:
以太坊最初并不是围绕 ZK 友好性设计的,因此以太坊协议中有许多部分需要大量计算来进行 ZK 验证。1类EVM的目标是完全复制以太坊,因此它无法缓解这些低效问题。目前,以太坊区块的证明需要数小时才能完成。
虽然在以太坊发展史上已经有相关案例可以实现,但今天我们很高兴地宣布,使用 RISC Zero 的 zkVM 和 Bonsai 服务的以太坊区块证明只需几分钟,而不是几小时。
Zeth:可验证的以太坊区块生成
今天,我们在 RISC Zero zkVM 上为以太坊发布了开源 ZK 区块验证器 Zeth。
Zeth 可以证明给定的以太坊区块是有效的,而无需依赖验证器或同步委员会。这是因为 Zeth 在 zkVM 中完成了生成新区块所需的所有工作,包括:
- 验证交易签名
- 根据父区块的状态根验证账户和存储状态。
- 应用交易
- 向区块作者支付费用。
- 更新状态根
- 其他生成区块需要的工作
生成新区块后,Zeth 会计算并输出它的哈希值。通过在 zkVM 中运行这一过程,我们就能获得新区块有效的 ZK 证明。
通过利用 RISC Zero 的 zkVM 和流行的 Rust crates(如 revm、ether 和 alloy),我们在不到 4 周的时间内就编写出了 Zeth 的第一个版本。通过 zkVM 对连续性和 Bonsai 证明服务的支持,证明生成可以在仅仅几分钟之内完成。有了对链上验证的支持,我们就能以低成本在链上验证这些证明。
Zeth 已在以太坊主网的多个真实区块上得到验证,并通过了以太坊官方测试套件的所有相关测试。
由于 Zeth 构建了标准的以太坊区块,因此可以将其视为 1 类 zkEVM。但它的意义远不止于此:因为 Zeth 是使用标准的 Rust 代码板(与 Reth 等流行的全节点使用的代码板相同)构建的,所以我们更愿意将其视为 0 类 zkEVM:完全的协议兼容性,以及大量的代码复用。
这一里程碑代表着 ZK 技术和以太坊生态系统向前迈进了一大步。在这篇文章中,我们将讨论如何在几周内编写出 Zeth 的第一个版本、它的性能、工作原理以及这对 ZK 项目的意义。
RISC Zero 使构建 zk rollup、zkEVM、轻型客户端和桥接器变得更加容易
我们编写 Zeth 出于以下两个原因:
- 让其他团队更容易构建他们自己的 ZK 驱动的基础设施:ZK rollup、zkEVM、ZK 轻型客户端、ZK 桥接器等。Zeth 提供了为基于 EVM 的区块生成 ZK 证明所需的一切。这是任何 zkEVM 或桥接器的关键组件。Zeth 基于 revm 开源,因此项目开发者可以轻松修改或使用。证明可以在链上验证(非常适合桥接器和 L2),也可以在本地应用程序中验证(非常适合全节点和轻客户端)。
- 在Zeth的 zkVM 中进行 EVM 性能的相关研究,特别是与以太坊相关的任务。(调查结果分析见下文)。
zk rollup 与 zkEVM
作为 0 类 zkEVM,Zeth 使开发人员能够构建具有完全本地 EVM 和以太坊兼容的 zk rollup。加之Zeth对链上证明验证的支持,构建由 ZK 驱动的 L2 扩容方案将变得无比简单。
现有的 ZK rollups 和 zkEVM 电路在设计上都是单片式的,如果开发人员不具备对 ZK 密码学的高水平理解,就无法进行升级。相比之下,基于 zkVM 的 Zeth 方法使任何开发人员都能根据自己的需要进行定制和修改。
Zeth 是基于 revm 的开放源代码,调整以支持其他 zkEVM 和 EVM 兼容链是一件相对容易的事。因此,Zeth 对于未来EIP的更新也会有相对更快的反应速度。此外,Zeth同时提供了模块化功能,使开发人员能够在其中构建自己的区块构建逻辑。
我们希望Zeth做出的努力能使 zk rollup和 zkEVMs 民主化,要知道zk驱动的L2解决方案以前需要多年的研究开发与超过 1 亿多美元的资金,这是绝大多数项目都无法负担的消耗。
轻型客户端与桥
毫无疑问,信标链的引入是轻客户端和桥接器的福音。这些技术建立在以太坊现已成熟的PoS模型之上,在每个人都遵守规则的前提下,使轻客户端和桥接器可以轻松验证最近的区块,而无需重建这些区块。
当然,质押的全部意义在于为遵守规则的节点提供经济激励。但是,Slashing加诸于节点的威胁并不能完全避免作恶——外部激励因素会使 利益的"天平 "永远向作恶方倾斜——而设计一个能正确处理这些恶性行为的轻型客户端或桥梁是很难的。
有了 Zeth 这样的工具,节点作恶的风险就大大降低了。轻客户端只需向zkVM添加几个调用借口,就能与Zeth集成;桥接器等链上应用程序可使用我们的链上证明验证合约与Zeth集成。
在不久的将来,我们可以想象轻客户端和桥接器使用 ZK 证明来确定给定区块是否有效。这种方法将大大降低风险,同时又不会显著增加验证区块的成本。
这对于应用程序链、模块化生态系统和新链来说尤为重要,因为它们还不具备以太坊大型完整节点社区所提供的同等级别的安全。
良好的基础简化项目开发流程
Zeth 基于 RISC Zero zkVM ,以 RISC-V 指令集架构为动力,能为开发者提供熟悉的编程体验。但我们的 zkVM 不仅仅是一个 RISC-V 内核。我们还为哈希算法和签名验证等常见加密任务配备了加速电路。
这种混合方法(通用 CPU 内核与加速电路的结合)为我们提供了两全其美的解决方案:
- 支持主流编程语言。
- 关键加密操作的性能不打折扣。
因此,我们能够使用来自 revm、ether 和 alloy 的现有 Rust 代码包快速构建 Zeth。通过复用现有的板块,我们在不到 4 周的时间内就完成了 Zeth 的第一个版本。这种速度在不太成熟的生态系统中是不可能实现的。
在性能方面,Zeth 利用了我们用于 ECDSA 签名验证的加速器电路以及连续性–这是我们 ZK 框架的一项新功能,可轻松使用并行工作的 GPU 集群(使用 nVidia CUDA 或 Apple Metal)快速证明大型计算。Continuations 易于使用:该功能透明地提供给在 zkVM 中运行的所有访客程序,即无需对代码进行任何修改,它就能正常运行。
有了我们的 zkVM,我们就能在几分钟内,而不是几小时内,快速生成以太坊区块有效性的 ZK 证明。
性能
我们将介绍 Zeth 区块生成器的性能。Zeth 仍是一个新产品,因此这些数据可能会发生变化;不过,我们希望提供一些具体数据,作为未来工作的基准线。
说到性能,有几个因素需要考虑:
- 生成证明所需的计算资源。
- 生成证明所需的 "墙上时间"(即用户需要等待多长时间才能获得证明)。
- 生成证明的总成本(美元)。
连续性
Zeth的 zkVM 可以通过使用连续运行来调整性能。因此,我们需要暂停一下,讨论一下“连续运行”是何种工作原理。
我们的 zkVM 实现了标准的 RISC-V 处理器。因此,执行以周期为单位。(在我们的电路中,大多数 RISC-V 指令的执行只需 1 个周期,当然也有例外)。简单的程序通常只需要几十万个周期来执行,但更复杂的程序可能需要几十亿个周期。
在典型的 ZK 系统中,这些执行周期汇集成一个证明;随着周期数的增加,生成证明所需的时间和内存也会增加。但我们的 zkVM 并不遵循这些陈规,今年早些时候,我们率先推出了一项连续性新功能,改进了传统证明模式生成的弊端。
在连续性方面,证明过程分为三个阶段:
我们在非证明仿真器中执行所需的计算。在此过程中,我们会计算迄今为止已运行的循环次数。在可配置的时间间隔内,我们对程序的状态进行快照。这就有效地将执行过程分割成多个片段。每个片段都很小,通常代表 100 万个或更少的周期。
这些片段被分配给一组证明生成工人。他们为其给定的程序段生成 ZK 证明。重要的是,他们可以并行地完成这项工作。只要有足够多的工人,就能在证明一个程序段所需的时间内证明所有程序段。由于网段较小,因此所需的时间通常很短(几十秒)。
在生成分段证明时,它们会最终进行rollup。每次 "rollup "操作都会获取一对连续的分段证明,并为这些分段的组合生成一个新的证明。例如,如果片段 1 证明程序从状态 A 过渡到了状态 B,而片段 2 证明程序从状态 B 过渡到了状态 C,那么rollup就证明程序从状态 A 过渡到了状态 C。如果有足够多的工作人员,这可以在 log(N) 时间内完成,其中 N 是片段的数量。
当我们深入研究这些数字时,将会看到这些阶段的实际效果。
构建一个以太坊区块有多难?
首先,让我们来看看构建以太坊区块的复杂程度。在下表中,我们选取了一些现实世界中的以太坊区块,并在 zkVM 中使用 Zeth 对它们进行了重构。
例如,区块 17606771 产生 2131 个区段。每个区段最多代表 2^20 个执行周期,因此整个计算最多需要 2,234,515,456 个执行周期。
一般来说,我们看到一个典型的以太坊区块需要 20-4 亿个周期来构建,但有时多达 95 亿个周期。(起初,我们惊讶地发现这些差异并没有反映在交易的Gas中。但进一步思考后,这就说得通了:Gas系统在设计时考虑的是常规执行,而不是 ZK 证明)。
有了连续性,这种规模就很容易管理了。根据这些数据,一个有 10,000 个节点运行 zkVM 校验器的点对点网络足以实现最大区块的最高并行验证性能,而这只是以太坊目前拥有的 700,000 个验证器的一小部分。
生成证明需要多长时间?
为了收集一些基本的性能数据,我们启动了一个带有 64 个 GPU Worker 的 Bonsai 测试实例。然后,我们要求它使用 Zeth 证明区块 17735424(182 个事务,3242 个区段,或大约 3.4B 个周期)。
要生成证明,zkVM 必须首先将执行分割成多个区段。在下面的截图中,Executor 任务捕捉到了这一过程,该任务运行了 10 分钟。(其中大部分时间是在做 AWS 的事情,比如写入网络存储)。在本地机器上,同样的任务只需不到 6 分钟即可完成。我们希望在未来一年内大大缩短这个时间)。
执行器最终将执行分成了 3242 个片段。对于仅有的 64 个 GPU 来说,这是一个很大的分段。因此,每个工作节点必须生成 50 个分段证明。如下图所示,这需要 35 分钟。如果我们有 50 倍的工作节点,则只需要 42 秒。
分段证明完成后,开始rollup。由于有 3242 个分段,我们需要执行 log_2(3242) = 12 轮的rollup过程。在rollup的早期阶段,工作量比工人多;因此第一阶段耗时 1 分钟,第二阶段耗时 35 秒,第三阶段耗时 25 秒,等等。到了第七阶段,时间稳定在 5 秒多一点。同样,如果我们有更多的工人,每个阶段都只需要 5 秒钟。
rollup完成后,结果将被最终确定,这又需要一分钟时间。
因此,在集群规模不足的情况下,我们能够在大约 50 分钟内生成证明(有效速度为 1.1 MHz)。如果集群规模适当,我们估计生成证明的速度会更快:
在完全并行的情况下,证明步骤可在 42 + 12 * 5 + 60 秒或 2 分 42 秒内完成。
如果我们保守地四舍五入并将执行器时间计算在内,则时间大约在 9 到 12 分钟之间(有效速度为 4.7 MHz – 6.3 MHz)。
随着我们对执行器和证明框架的不断改进,我们乐观地认为,在未来一年内,这个时间将大大缩短。
生成证明的资源消耗
上述测试集群已部署到 AWS。它由 64 个 g5.xlarge 证明节点和 1 个 m5zn.xlarge 执行节点组成。根据亚马逊的说法,每个 g5.xlarge 节点都有
- 1 个 GPU,拥有 24 GiB GPU 内存
- 4 个 vCPU,内存容量为 16 GiB
在撰写本文时,这些实例的按需价格为 1.006 美元/小时,保留实例的优惠价格为 0.402 美元/小时。与此同时,亚马逊规格表显示我们的 m5zn.xlarge 节点拥有
- 4 vCPU,16 GB 内存
在撰写本文时,该实例的按需价格为 0.3303 美元/小时。
我们可以使用这些数字来粗略估算上文描述的区块 17735424 的证明成本。
回想一下,我们部署了 64 个证明节点,在这种部署下,生成证明需要 50 分钟(端到端)。忽略空闲的工人时间,64 个证明节点加上一个执行节点,50 分钟的成本为 50/60 * (64 * 0.402 + 0.3303) = 21.72 美元。这是一个高估的数字,因为它假定我们要为闲置工人付费。如果我们不考虑闲置工人的成本(例如,关闭他们或让他们从事其他工作),成本大约为 19.61 美元。
- 这个区块有 182 笔交易,即每笔交易 0.11 美元。
- 交易总价值为 1.125045057 Eth,约合 2137.59 美元。因此,每 1 美元的证明可获得 109.01 美元的用户资金。
- 同一区块支付的奖励为 0.117623263003047027 Eth(不包括交易费)。在撰写本文时,这大约是 223.48 美元。因此,我们的证明大约花费了区块奖励的 8.7%。
- 交易费加起来为 0.03277635 Eth,即 62.28 美元,是我们证明成本的 3 倍多。
值得注意的是,这些美元估算与集群的规模无关!重要的是分段的数量。这是因为,1 台机器依次完成 2 项工作的成本与 2 台机器并行完成 1 项工作的成本相同。因此,如果集群规模更大,生成证明的速度会更快,但成本不会更高。
有几种方法可以进一步降低成本。除了继续提高 zkVM 的性能,或许还可以添加 Keccak 加速器,我们还可以四处寻找更便宜的实例。重要的是,鉴于我们使用的机器规格较低(而且我们的 zkVM 支持 nVidia Cuda 和 Apple Metal),这项工作可以通过由普通消费 PC 和 Mac 组成的 p2p 网络轻松完成。
链上验证
如上所述,我们使用 RISC Zero Groth16 校验器验证了 Sepolia 上的 Zeth 证明。这是 RISC Zero 协议栈中相对较新的一部分,于本月初发布。它的工作原理是使用 Bonsai 将 zkVM 的原生 STARK 证明转换为等价的 SNARK 证明,并将该证明提交给链上 SNARK 校验器。
如果我们将交易输入视为 UTF-8 数据,我们就会看到该证明对应于区块 17735424。
使用 Bonsai,从 STARK 到 SNARK 的转换耗时约 40 秒。验证 SNARK 上链消耗了 245,129 瓦斯(撰写本文时约为 5.90 美元)。
当然,zkVM 的一个优点是它可以将多个证明合并成一个。有了这项功能,就可以在链上验证一整套证明,而无需使用任何额外的气体。这样,链上验证的成本就可以分摊到整套证明中,从而降低了每个人的费用。
这对以太坊来说意味着什么
如前所述,以太坊在设计时并没有考虑到 ZK 的友好性。正如 zkEVMs 的情况所显示的那样,有很多事情可以用不同的方式来完成,特别是在操作码、数字签名和哈希函数方面。
虽然这些改动确实提高了性能,但我们仍然能够在不使用这些改动的情况下实现稳定的性能。维塔利克去年撰文介绍不同类型的 zkEVM 时,证明一个以太坊区块的有效性需要几个小时;而现在,我们可以在几分钟内完成。ZK 性能正在飞速提升,我们有理由相信,这一趋势将在未来几年内持续下去。
附录:Zeth的工作原理
这一部分是为开发商准备的。
粗略地说,Zeth 构建区块的方式与完整节点相同:我们从父区块、事务列表和区块作者开始,然后进行大量计算(验证签名、运行事务、更新全局状态等),最后返回新区块的哈希值。
但与完整节点不同的是,我们是在 zkVM 中完成这些工作的。这意味着我们会获得一个 ZK 证明,证明具有给定哈希值的区块是有效的。
当然,这并非没有挑战。在本节中,我们将介绍这些挑战以及我们是如何应对的。
密码学
第一个挑战是密码学。构建一个以太坊区块需要做大量的工作,其中最主要的是散列(Keccak-256)和签名验证(ECDSA 与 secp256k1)。
我们的 zkVM 对椭圆曲线有加速支持,因此 ECDSA 签名校验并不难。
但说到散列,我们就没那么幸运了。我们的 zkVM 对 Sha2-256 提供了加速支持,但(在撰写本文时)不支持 Keccak-256。因此,目前我们只使用了 sha3 Rust crate 中的 Keccak 实现。通过剖析,我们知道这需要花费大量的周期。这并不是最佳方案,但我们的 zkVM 可以处理它,而且我们还可以循环使用,并在以后添加 Keccak 加速器。
账户和存储:性能和安全
在以太坊中,账户和存储由全球 Merkle Patricia Trie(MPT)跟踪。
根据 Etherscan 的数据,在撰写本文时,这棵树包含了近 250,000,000 个唯一以太坊地址的状态。从整体上看,这并不是一个很大的数据量,但它足以让我们对其存储和使用方式慎之又慎。尤其是,MPT 的性能至关重要。
但性能并不是唯一的因素,我们还必须考虑安全性。
Zeth 的区块生成器是以客户身份在 zkVM 中运行的。这意味着它无法直接访问以太坊 p2p 网络或其他 RPC 提供商。相反,它必须依赖运行在 zkVM 外部的独立程序提供的数据。
在分析 ZK 应用程序的安全性时,我们必须假设外部程序是恶意的,因此可能会提供恶意数据。为了防止这种情况,ZK 应用程序必须验证提供给它们的数据是否有效。
对于 Zeth 区块生成器来说,这意味着要验证所有相关账户和存储的状态(即运行给定交易列表所需的账户和存储)。
幸运的是,EIP-1186 提供了这样一种机制,它定义了一种通过 Merkle 包含来证明给定账户(及其存储)状态的标准方法。
原则上,Zeth 的区块生成器可以通过验证一组 EIP-1186 包含证明来验证账户和存储状态。但这种方法并不理想。
相反,最好是使用 EIP-1186 包含证明中的数据来构建部分 MPT。这是一种 MPT,只包含与给定交易列表相关的节点;不相关的分支只用相应的哈希值表示。你可以把部分 MPT 视为梅克尔包含证明的一种 "联合";或者,如果你愿意,也可以把它视为梅克尔子集证明。
验证部分 MPT 的过程与验证普通 EIP-1186 证明基本相同:计算根哈希值,然后与父块的状态根进行比较。如果两者相等,则可以信任其中账户和存储的完整性。
部分 MPT 经过验证后,就可以应用交易并更新部分 MPT。新的状态根可以通过计算部分 MPT 的新根哈希值获得。
- 在运行 Zeth 区块生成器之前,我们先在沙箱中运行事务列表,以确定哪些账户和存储是相关的。(这个过程还能让我们确定最老的相关前代区块,这是支持 blockhash() 查询所必需的)。
- 我们为每个相关账户和存储获取 EIP-1186 包含证明。(我们还会获取相关的前代区块)。
- 我们使用这些包含证明来构建包含所有相关数据的部分 MPT。
- 我们启动 zkVM,让它运行 Zeth 区块生成器,并提供部分 MPT 和其他输入(父区块、交易列表等)。
在 zkVM 中,Zeth 块生成器:
- 验证部分 MPT 根是否与父块的状态根相匹配。
- 验证前代区块的哈希链,直到父区块。
- 应用事务。
- 更新部分 MPT。
- 使用部分 MPT 的新根哈希值作为新区块的状态根。
Zeth区块生成器完成后,会输出新区块的哈希值。
这个哈希值包括对父区块的承诺,因此也包括父区块的状态根(用于验证原始的部分 MPT)。这意味着恶意验证者如果不提供无效的父块,就无法为账户和存储提供无效数据。
换句话说:如果父块是有效的,那么Zeth生成的新块也是有效的。
因此,如果有人给你一个新区块和一个由 Zeth 生成的 ZK 证明,你可以通过检查以下三点来检查该区块的有效性:
- 确保 ZK 证明有效且来自 Zeth。对于链外应用,可使用 zkVM Rust crate 提供的函数进行检查。对于链上应用,可使用我们的链上证明验证器进行检查。
- 确保 ZK 证明提交了新区块的哈希值。
- 确保父区块拥有你所期望的哈希值。
如果这些都检查出来了,那么新区块就是有效的。
局限性和未来改进
我们这个项目的目标是研究区块构造的性能。为此,我们决定将范围限制在合并后区块上。
此外,虽然 Zeth 能够证明给定区块是有效的,但目前还不能证明共识(即该区块确实包含在正则链中)。这一点在未来可能会有所改变,或许可以通过在 zkVM 中添加验证者或同步委员会签名的检查来实现。
最后,Zeth 是一款新软件。虽然我们已经进行了一些测试(包括以太坊测试套件和各种现实世界的区块),但 Zeth 可能仍然包含一些错误。在撰写本文时,它应被视为实验性软件。