Scroll zkEVM 电路与审计
1. 主要电路
zkEVM 由许多电路组成,每个电路负责检查 EVM 的某个方面。这些电路最后以某种方式进行聚合或组合,共同完成交易执行的证明。下图显示了这些电路和表格之间的关系:
其中有一些较小的子电路,例如 ECDSA 电路和操作码相关的子电路,不会以影响电路的组合方式与其他表和电路交互,因此为了清楚起见,它们没有在图中显示。
EVM Circuit
以太坊虚拟机(Ethereum Virtual Machine,简称 EVM)是一个状态机,它定义了以太坊协议中有效状态转换的规则。这意味着它规定了一个确定性函数,根据当前的 EVM 状态计算下一个有效的 EVM 状态。EVM 的执行部分使用操作码(opcodes)来实现这些状态转换,从而产生执行轨迹(Execution trace)。EVM 电路的目标是构建与执行轨迹相对应的约束系统,可以通过后端的零知识证明系统进行证明。
EVM 电路的高级设计思想在某种程度上类似于 EVM 本身的设计(例如 go-ethereum)。在 go-ethereum 中,解释器循环遍历执行轨迹上的所有指令操作码。在每个指令中,解释器帮助检查相关的上下文信息,如 gas、堆栈、内存等,然后将操作码发送到 JumpTable,从中获取该操作码应执行的详细操作。
类似地,在 EVM 电路中,Scroll 根据执行轨迹中的步骤构建执行步骤,并为操作码和执行上下文提供证明。对于每个执行步骤,会施加一组约束来检查上下文信息。对于每个操作码,会施加一组约束来检查操作码的行为。在执行轨迹中,相同的操作码应具有相同的约束。Scroll 使用选择器来「打开」执行轨迹中相同操作码的所有步骤,并使用后端的证明系统证明它们的行为。
State Circuit
在执行过程中,EVM 的所有读写操作都记录在 rw_table 中,并按计数器变量 rw_counter 排序。而 state Circuit 的目的就是证明正确的生成了 rw_table。
MPT Circuit
Merkle Patricia Tree 是以太坊存储层使用的关键数据结构之一。在 Scroll 的 zkevm- Circuits 中,将原始 MPT 修改为 zkTrie,它本质上是一个稀疏二进制 Merkle Patricia Trie。在 zkevm- Circuits 中,Scroll 使用 MPT 表来逐步跟踪 MPT 操作的状态转换。MPT 表具有以下表布局:
MPT 电路的目标是验证上述 MPT 表的正确性,即确保 MPT 表中记录的每次更新都会导致正确的更改。为了实现这个目标,MPT 电路使用约束系统来强制执行 MPT 的唯一更改。这意味着对于 MPT 表中的每个更新,MPT 电路会确保只有一种可能的更改方式。这样可以防止意外或非法的更改,并确保 MPT 的完整性和正确性。特别地,当 MPT 由于帐户或存储的更新而发生更改时,MPT 电路必须证明这次更新会导致正确的根更改。这意味着 MPT 电路需要验证更新操作是否按照规定的规则进行,并且确保根哈希正确地反映了所有更改的结果。
Keccak Circuit
Scroll 在遵循 NIST Keccak 规范、Keccak 团队 Keccak 规范的条件下实现了他们自己的 Keccak256。
而 Keccak 电路则用于证明 Keccak256 运算结果的正确性。这部分电路的实现复杂,主要因为 keccak256 算法本身就是 zk-unfriendly 的。
Tx Circuit
Tx 电路提供了验证交易正确性的约束条件。它主要检查交易的以下几个方面:
1. CallDataLength 和累积 CallDataGasCost 的正确性:通过自定义门和查找 tx 表中 tx 的最后一行 call data 字节;
2. TxSign 和 TxHash 相关数据的正确性:通过查找 RLP 表和 Keccak 表;
3. 证明「若 tx_type 为 L1Msg,则 msg_hash」的正确性:通过查找 RLP 表进行验证;
4. 通过 ECDSA 正确执行 tx 签名,并且能够正确的从 ECDSA 签名中恢复调用者地址:通过查找 sig 表进行验证;
5. tx id、cum_num_txs 和 call_data_length 等的正确过渡行为。
6. 一些基本约束,如一些指示变量的布尔值等。
Bytecode Circuit
EVM 电路需要查找存储正确字节码信息的字节码表。这确保了合约中存储的字节与表中加载的字节相同。而字节码电路的目的是约束上述字节码表的正确性。这包括:
1. 与标签(tag)的边界行为相关的约束:首行和末行的约束条件,从 tag==byte 转换到 header 以及反之的转换,从 header 转换到 header 的转换;
2. 约束代码大小:包括通过约束字节码的最后一个字节的索引来计算字节码的长度;
3. 约束代码哈希:对代码哈希中字节的 RLC 行为进行正确约束,并通过查找 Keccak 表来验证代码哈希;
4. 确保 PUSH 行为的正确性:is_code = push_data_left == 0(必须是布尔值),并通过查找 push_table 来确保 PUSH1-PUSH32 的推送数据大小;
5. 确保在一个字节码中每行的正确传播。
2. 安全审计
不同的链拥有各自的自定义业务模块功能,这些模块通常会修改 EVM 中的预编译合约以及操作码,其中 Scroll zkEVM 作为一种基于零知识证明的二层扩容方案,该方案使用电路重构了相关操作码并根据执行跟踪生成证明,这个复杂的实现极大增加了审计难度。Beosin 安全专家评估后认为,目前 zkEVM 安全审计主要分为以下几个方面:
1. GAS:zkEVM 电路在生成执行跟踪对应的证明时,会同时校验交易耗费 gas 的正确性。如果在操作码的实现电路中高频次地使用没有约束的自由变量,可能导致证明生成失败或其他未知错误。
2. 内存安全:部分 zkEVM 电路实现的数学基础是多项式承诺,如 Scroll 使用的 KZG 承诺。而多项式计算不会自动对齐,因此如果电路缺乏约束会导致取值域与计算机程序中的字节范围不一致,在部分合约开发者开启了 gas 优化的情况下,数据的紧凑排列可能导致内存安全问题,如 Polygon zkEVM 中的 BYTE_C4096 常数多项式。多项式允许参数的取值范围超过字节的最大取值范围 255,这在一些采取 AMM 模式的交易所中,可能导致恶意的 Sequencer 伪造参数获利。本质上,这一类的漏洞都是由于电路表示的数值有效范围与程序的变量取值范围不一致导致的,如 Beosin 安全研究员在 Snarkjs 库中发现的漏洞 CVE-2023-33252。
3. 操作码安全:zkEVM 操作码实现时,存在普遍的欠约束等安全问题,尤其是精度问题。例如底层电路在实现两个数的比较时,如果程序中比较运算的精度为 1 个字节,那么电路约束需要规定取值范围,否则电路中运算的精度将远超程序精度,导致结果错误。
4. 安全 EIP 支持:EIP-2、EIP-155 等安全类 eip 的支持。
5. Sequencer 中心化问题:目前 Scroll 生成的证明全部依赖 Sequencer 生成的执行跟踪,如果 Sequencer 作恶,zkEVM 无法保护用户资产安全。
6. 兼容性问题:zkEVM 根据执行踪迹生成电路证明并在合约验证,即使 Sequencer 进行微小的升级,也可能导致底层语言级别生成的执行踪迹存在较大差异。
Scroll 的未来展望
1. Scroll 目前采用了两层 KZG 版本的 Halo2 证明系统,使用 GPU 硬件加速加快证明生成的速度,目前瓶颈转移到了见证生成和复制数据这部分。此外,Roller 的中心化程度和硬件运行费用也是 Scroll 在未来发展多阶段证明系统需要考虑的部分。
2. 因为 EVM 执行踪迹是动态变化的,会存在各种各样的电路约束和规模。目前为满足动态变化执行踪迹,每步执行踪迹都需要满足最大的电路规模,造成额外的内存浪费。
3. Scroll 的 Roller 目前预计通过网络交易费用获利,然而当前 Scroll 网络的用户数和交易费用无法满足 Roller 和排序器的运行费用。在未来,Scroll 网络如何进行经济激励以吸引用户和维持网络稳定运行是一个需要思考的问题。