V神新文:以太坊内置ZK后,Layer2驶向何方?
出品 | Odaily星球日报
编译 | Loopy Lu
今日, Vitalik Buterin 在以太坊社区发布了一篇名为《内置 ZK-EVM 可能是什么样子的?》的新文章。这篇文章探讨了 以太坊在未来的网络升级中将如何内置自己的 ZK-EVM。
众所周知,在以太坊开发缓慢的背景下,目前几乎主流 Layer 2 都已经拥有了 ZK-EVM,而当以太坊主网封装了自己的 ZK-EVM 之后,主网和 Layer 2 又是否会产生角色定位的冲突呢?Layer 1 和 Layer 2 又该如何有效的分工合作呢?
在本文中,Vitalik Buterin 强调了兼容性、数据可用性和可审计性的重要性,并探索了实现高效和有状态证明者的可能性。此外, 文章还探讨了为提高效率实现有状态证明者的可能性,并讨论了 Layer 2 项目在提供快速预确认和 MEV 缓解策略方面的作用。这篇文章反映了在保持以太坊网络灵活性的同时,通过 ZK-EVMs 推进其发展的平衡。
Odaily星球日报对原文进行了编译,文章全文如下:
optimistic rollups 和 ZK rollup 作为以太坊之上的 Layer-2 EVM 协议,都依赖于 EVM 验证。然而,这要求他们信任一个庞大的代码库,如果这个代码库中存在 bug,那么这些虚拟机就有被黑客攻击的风险。这也意味着,ZK-EVM 即便是希望与L1 EVM 保持完全等效,也需要某种形式的治理,以此来将L1 EVM 的改变复制到它们自己的 EVM 实现中。
这种并不是一种理想的情况。因为这些项目正在把以太坊协议中已经存在的功能复制到自身中——以太坊治理已经负责进行升级和修复 bug,ZK-EVM 基本上做的工作只是验证 Layer 1 以太坊区块。在未来几年中,我们预计轻客户端将变得越来越强大,很快就能够使用 ZK-SNARKs 来完全验证L1 EVM 执行。到那时,以太坊网络将实际上拥有一个封装的 ZK-EVM。因此,问题就出现了:为什么不让这个 ZK-EVM 对于 rollups 也是原生可用的呢?
这篇文章将描述“封装 ZK-EVM”的几个版本,分析其权衡、设计挑战以及不走某些特定方向的原因。实现一个协议功能的好处应该与让生态系统处理事务并保持基础协议简单的好处相比较。
我们希望从封装的 ZK-EVM 中获得哪些关键特性?
我们希望从封装 ZK-EVM 中得到哪些关键属性?
基本功能:验证以太坊区块。该协议功能(目前还未确定是操作码、预编译还是其他机制)应该至少能接受预状态根、一个区块和后状态根作为输入,并验证后状态根实际上是在预状态根之上执行该区块的结果。与以太坊的多客户端兼容。这意味着我们希望避免内置单一证明系统,而是允许不同的客户端使用不同的证明系统。
这也意味着几点:
数据可用性要求: 对于任何使用封装 ZK-EVM 证明的 EVM 执行,我们希望保证底层数据是可用的,这样使用不同证明系统的证明者就可以重新证明执行,依赖该证明系统的客户端就可以验证这些新生成的证明。
证明位于 EVM 和区块数据结构之外 : ZK-EVM 功能不会真的将 SNARK 作为 EVM 内部的输入,因为不同的客户端会期望不同类型的 SNARK。相反,它可能类似于 blob 验证:交易可以包括需要证明的(预状态、区块体、后状态)声明,操作码或预编译可以访问这些声明的内容,客户端共识规则会分别检查数据可用性和区块中所做声明的证明。
可审计性: 如果任何执行被证明,我们希望底层数据是可用 的,这样如果出现任何问题,用户和开发者都可以检查它。实际上,这增加了一个更多原因,为什么数据可用性要求很重要。
可升级性: 如果发现某个特定 ZK-EVM 方案存在 bug,我们希望能够迅速修复它。这意味着不需要硬分叉就能修复问题。这又增加了一个原因,说明在 EVM 和块数据结构之外的证明很重要。
支持“近似 EVM”: L2s的一个吸引力在于能够在执行层进行创新,并对 EVM 进行扩展。如果某个L2的 VM 只是稍微有点不同于 EVM,那么如果L2可以对与 EVM 相同的部分使用原生的协议内 ZK-EVM,并且只依赖自己的代码来处理不同的部分,这将是很好的。这可以通过设计 ZK-EVM 功能来实现,使得调用者可以指定一个位域或操作码或地址列表,这些将由外部提供的表格而不是 EVM 本身来处理。我们还可以使 gas 成本在一定程度上可定制。
“开放式”与“封闭式”多客户端系统
“多客户端思路”可能是这个列表中最有争议的要求。 一个选项是放弃多客户端并专注于一个 ZK-SNARK 方案,这将简化设计。但代价是对以太坊进行更大的“哲学转变”(因为这实际上是放弃了以太坊长期以来的多客户端思路),并引入更大的风险。在长期的未来,例如当形式验证技术变得更好时,走这条路可能会更好,但目前看来风险似乎太大。
另一个选择是封闭的多客户端系统,其中有一个固定的证明系统集合,该集合在协议内已知。例如,我们可能决定使用三个 ZK-EVM:PSE ZK-EVM、 Polygon ZK-EVM 和 Kakarot 。一个区块需要至少来自这三个中的两个的证明才有效。这比单一证明系统要好,但它使系统因为用户必须为每个存在的证明系统维护验证器,会有不可避免的治理过程来纳入新的证明系统等原因而变得不那么适应性强。
这促使我更倾向于一个开放的多客户端系统,其中证明被放置在“区块之外”,由客户端分别验证。个别用户会使用他们想要的任何客户端来验证区块,只要有至少一个证明者为该证明系统创建证明,他们就能做到这一点。证明系统将通过说服用户运行它们来获得影响力,而不是通过说服协议治理过程。然而,这种方法确实有更多的复杂性成本,正如我们将看到的。
我们在 ZK-EVM 实现中 想要什么关键特性?
除了基本的功能正确性和安全性保证之外,最重要的属性是速度。虽然可以设计一个异步的协议内置 ZK-EVM 功能,每个声明只在 N 个 slots 后返回结果,但如果我们能可靠地保证在几秒钟内就能生成一个证明,问题就会变得容易得多,这样每个区块中发生的事情就是自给自足的。
虽然今天为一个以太坊区块生成一个证明需要花费几分钟或几小时的时间,但我们知道没有理论上 的原因阻止大规模并行化:我们总是可以集结足够的 GPU 来分别证明区块执行的不同部分,然后使用递归 SNARK 将这些证明组合在一起。此外,通过 FPGA 和 ASIC 的硬件加速可以进一步优化证明过程。然而,实际达到这一点是一个不容小觑的工程挑战。
协议内 ZK-EVM 功能具体是什么样的?
类似于 EIP-4844 blob 交易,我们引入了一种包含 ZK-EVM 声明的新交易类型:
class ZKEVMClaimTransaction(Container):
pre_state_root: bytes 32
post_state_root: bytes 32
transaction_and_witness_blob_pointers: List[VersionedHash]
...
与 EIP-4844 一样,在内存池中传递的对象是交易的修改版本:
class ZKEvmClaimNetworkTransaction(Container):
pre_state_root: bytes 32
post_state_root: bytes 32
proof: bytes
transaction_and_witness_blobs: List[Bytes[FIELD_ELEMENTS_PER_BLOB * 31 ]]
后者可以转换为前者,但反之则不行。我们还扩展了区块 sidecar 对象(在 EIP-4844 中引入),以包含区块中声明的证明列表。
请注意,在实践中,我们可能希望将 sidecar 拆分为两个单独的 sidecar,一个用于 blob,一个用于证明,并且为每种类型的证明设置一个单独的子网(以及一个用于 blob 的附加子网)。
在共识层,我们添加了一个验证规则,即只有当客户端看到区块中每个声明的有效证明时,才会接受区块。证明必须是 ZK-SNARK 证明的串联,是一对的 transaction_and_witness_blobs 序列化,并且 pre_state_root 使用 (i)有效,并且 Witness (ii) 输出正确的 post_state_root. ( Block , Witness) 潜在地,客户可以选择等待多种类型证明的 M-of-N。
这里的一个注意事项是,块执行本身可以简单地被视为需要与 ZKEVMClaimTransaction 对象中提供的三元组一起检查的三元组( σpre,σpost, Proof)。
因此,用户的 ZK-EVM 实现可以取代其执行客户端;执行客户端仍将由 (i) 证明者和区块构建者使用,以及 (ii) 关心索引和存储数据以供本地使用的节点。
验证和重新验证
假设有两个以太坊客户端,其中一个使用 PSE ZK-EVM,另一个使用 Polygon ZK-EVM。假设此时,两个实现都已经发展到可以在 5 秒内证明以太坊区块执行的程度,并且对于每个证明系统,都存在足够多的独立志愿者运行硬件来生成证明。
不幸的是,由于独立证明系统没有被内置,因此它们不能在协议中得到激励;然而,我们预计与研发成本相比,运行证明者的成本较低,因此我们可以简单地使用通用机构为证明者提供公共产品资金。
假设有人发布了一个 ZKEvmClaimNetworkTransaction ,只不过他们只发布了 PSE ZK-EVM proof 的一个版本。Polygon ZK-EVM 的证明节点看到了这一点,并使用 Polygon ZK-EVM 的 proof 计算并重新发布对象。
这增加了最早的诚实节点接受一个块和最新的诚实节点接受相同的块δ之间的总最大延迟: 2 δ+Tprove(假设 Tprove<5 s )。
然而,好消息是,如果我们采用单槽(slot)最终性(finality),我们几乎可以肯定地可以将这种额外的延迟与 SSF 固有的多轮共识延迟一起“管道化”。例如,在 4 子插槽提案中,“头部投票”步骤可能只需要检查基本区块的有效性,但随后“冻结并确认”步骤将需要存在证明。
扩展:支持“近似 EVM”
ZK-EVM 特性的一个理想目标是支持“近似 EVM”:即内置了一些额外功能的 EVM。这可能包括新的预编译、新的操作码,甚至是合约可以在 EVM 或完全不同的虚拟机(例如,像 Arbitrum Stylus 中的)中编写的选项,或者甚至是具有同步交叉通信的多个并行 EVM。
一些修改可以以简单的方式支持:我们可以定义一种语言,允许 ZKEVMClaimTransaction 传递修改后的 EVM 规则的完整描述。这可以做到:
-
自定义 gas 表(用户不能减少 gas 成本,但可以增加)
-
禁用某些操作码
-
设置区块编号(这将根据硬分叉而隐含不同规则)
-
设置一个标志,用于激活一整套 EVM 更改,这些更改已针对 L2 使用而非 L1 使用进行标准化,或其他更简单的更改
为了让用户以更开放的方式添加新功能,通过引入新的预编译(或操作码),我们可以在 ZKEVMClaimNetworkTransaction 的 blob 中加入一个预编译输入/输出记录:
class PrecompileInputOutputTranscript(Container):
used_precompile_addresses: List[Address]
inputs_commitments: List[VersionedHash]
outputs: List[Bytes]
EVM 执行将按以下方式修改。初始化一个空的输入数组。每当 used_precompile_addresses 中的地址被调用时,我们向 inputs 添加一个 InputsRecord(callee_address, gas, input_calldata)对象,并将调用的 RETURNDATA 设置为 outputs[i]。最后,我们检查 used_precompile_addresses 是否总共被调用了 len(outputs)次,并且 inputs_commitments 是否与对输入的 SSZ 序列化生成 blob 承诺的结果匹配。暴露 inputs_commitments 的目的是为了让外部的 SNARK 更容易证明输入和输出之间的关系。
请注意输入和输出之间的不对称性,输入存储在哈希中,而输出存储在必须提供的字节中。这是因为执行需要由只看到输入并理解 EVM 的客户端来完成。EVM 执行已经为他们生成了输入,所以他们只需要检查生成的输入是否与声称的输入匹配,这只需要一个哈希检查。然而,输出必须完整地提供给他们,因此必须是可用的数据。
另一个有用的功能可能是允许“特权交易”,这些交易可以从任意发送者账户发起调用。这些交易可以在两个其他交易之间运行,或者在调用预编译时,作为另一个(可能也是特权的)交易的一部分运行。这可以用来允许非 EVM 机制回调到 EVM 中。
这种设计可以修改以支持新的或修改后的操作码,除了新的或修改后的预编译。即使只有预编译,这种设计也相当强大。例如:
-
通过将 used_precompile_addresses 设置为包括状态中带有某些标志的普通账户地址列表,并制作一个 SNARK 来证明它是正确构建的,你可以支持 Arbitrum Stylus 风格的功能,其中合约可以用 EVM 或 WASM(或另一个 VM)编写。特权交易可以用来允许 WASM 账户回调到 EVM 中。
-
通过添加一个外部检查,以确保多个 EVM 执行的输入/输出记录和特权交易以正确的方式匹配,你可以证明一个通过同步通道相互交谈的多个 EVM 的并行系统。
-
一种 4 型 ZK-EVM 可以通过拥有多个实现来操作:一个直接将 Solidity 或另一种高级语言转换为对 SNARK 友好的 VM,另一个将其编译为 EVM 代码并在内置的 ZK-EVM 中执行。第二种(不可避免地更慢的)实现只能在故障证明者发送一个断言存在 bug 的交易的情况下运行,如果他们能提供两者处理不同的交易,则收集赏金。
-
一种纯异步 VM 可以通过使所有调用返回零并将调用映射到添加到区块末尾的特权交易来实现。
扩展:支持有状态的证明器
上述设计的一个挑战是它完全无状态,这使得它在数据上效率低下。在理想的数据压缩情况下,与仅有状态压缩相比,使用有状态压缩的 ERC 20 发送可以高达 3 倍的空间效率。
此外,有状态的 EVM 不需要提供见证数据。在这两种情况下,原则是相同的:当我们已经知道数据是可用的,因为它是在以前的 EVM 执行中输入或产生的,那么要求数据是可用的就是一种浪费。
如果我们想让 ZK-EVM 特性具有状态,那么我们有两个选择:
1)要求σpre要么为空,要么是已声明键和值的可用数据列表,要么是某次之前执行的σpost。
2)向(σpre,σpost,Proof)三元组中添加一个对区块生成的收据R的 blob 承诺。 任何以前生成或使用的 blob 承诺,包括那些代表区块、见证、收据甚至是普通的 EIP-4844 blob 交易的承诺,可能有一些时间限制,可以在 ZKEVMClaimTransaction 中引用,并在其执行期间访问(可能通过一系列指令:“将承诺i的字节 N...N+k-1 插入到区块+见证数据的位置j”)。
选项一意味着:与其内置无状态 EVM 验证,不如内置 EVM 子链。选项二本质上是创建一个最小的内置有状态压缩算法,它使用以前使用或生成的 blob 作为字典。这两种方法都会给证明者节点带来负担,只有证明者节点需要存储更多信息;在情况二中,比起情况一更容易让这种负担有时间限制。
封闭式多证明器和链下数据的参数
一个封闭的多证明者系统,其中有一个固定数量的证明系统在一个 M-of-N 结构中,避免了上面的很多复杂性。特别是,封闭的多证明者系统不需要担心确保数据在链上。此外,一个封闭的多证明者系统将允许 ZK-EVM 证明链下执行;这使其与 EVM Plasma 解决方案兼容。
然而,一个封闭的多证明者系统增加了治理复杂性并移除了可审计性,这些是高昂的成本,需要与这些好处相权衡。
如果我们将 ZK-EVM 内置并使其成为协议特性,那么“Layer 2 项目”的角色是什么?
目前由 Layer 2 团队自己实现的 EVM 验证功能将由协议处理,但 Layer 2 项目仍然负责许多重要功能:
-
快速预确认:单个 slot 的最终确定性可能会使 Layer 1 的 slot 变慢,而 Layer 2 项目已经在为其用户提供“预确认”,这些预确认由 Layer 2 自身的安全性支持,延迟远低于一个 slot。这项服务将继续完全由 Layer 2 负责。
-
MEV(矿工可提取价值)缓解策略:这可能包括加密的 mempool、基于声誉的排序器选择,以及 Layer 1 不愿意实现的其他功能。
-
对 EVM 的扩展:Layer 2 项目可以为其用户提供对 EVM 的重大扩展。这包括“近似 EVM”和像 Arbitrum Stylus 的 WASM 支持以及对 SNARK 友好的 Cairo 语言这样的根本不同的方法。
-
面向用户和开发者的便利性:Layer 2 团队在吸引用户和项目到他们的生态系统并使他们感到受欢迎方面做了很多工作;他们通过在其网络内捕获 MEV 和拥堵费用来获得补偿。这种关系将继续存在。