ZKsync Era 合约预编译审计

本文对 ZKsync VM 中的几个预编译函数进行了审计,涵盖了椭圆曲线点加法、标量乘法、配对和模幂运算的系统合约。文章详细讨论了审计范围、系统概述、安全模型、发现的问题及改进建议,并强调了对这些预编译的实施和文档质量的改善需求。

目录

摘要

TypePrecompileTimelineFrom 2025-03-03To 2025-03-11LanguagesYul, SolidityTotal Issues5 (5 解决)Critical Severity Issues0 (0 解决)High Severity Issues0 (0 解决)Medium Severity Issues1 (1 解决)Low Severity Issues1 (1 解决)Notes & Additional Information3 (3 解决)

范围

我们审核了 pull request #1259matter-labs/era-contracts 仓库,提交为 886018a

审核范围包括以下文件:

 system-contracts
└── contracts
    ├── Constants.sol
    └── precompiles
        ├── EcAdd.yul
        ├── EcMul.yul
        ├── EcPairing.yul
        └── Modexp.yul

审核的预编译是由 ZKsync VM 运行的系统合约,仅负责解析输入参数。实际操作(见“系统概述”部分)是在一个用 Rust 编写的执行层的独立部分中执行,且是 VM 本身的一部分。这些组件超出了审计范围,并将在未来的审计中进行审核。

系统概述

“范围”部分列出的四个 Yul 文件在 ZKsync VM 内实现了系统合约,处理以下四个操作的输入和输出:椭圆曲线 (EC) 点加法 (EcAdd.yul)、EC 标量乘法 (EcMul.yul)、EC 配对 (EcPairing.yul) 和模幂运算 (Modexp.yul)。这些合约结构相似,主要逻辑涉及从 calldata 中提取相应输入、将其以正确格式存储在内存中,并作为输入传递给 precompileCall 函数。该函数在 VM 中执行一个通用的操作码指令,执行一个预编译(ECAddECMulECPairingModexp 等),其依据是调用合约。

在内部,precompileCall 调用 verbatim_2i_1o 函数,该函数被 VM 拦截,以将调用路由到执行层。具体而言,调用 zksync-protocol 代码库中的 execute_precompile 函数(如上所述不在范围内),将调用与相应的预编译实现匹配。因此,分别触发 ecadd_functionecmul_functionecpairing_functionmodexp_function,进行给定操作并将结果存储在内存中。然后在相应的 Yul 合约中读取结果。

Constants.sol 文件的修改涉及添加 Modexp 系统合约的地址和修改一些常量,例如在提交数据到 L1 时支持的 blob 数量。

下面,我们提供有关上述四个操作的详细信息以及它们的相应输入和输出,作为 Yul 合约处理的结果:

  • EcAdd:加法两个 EC 点,坐标为仿射表示的 (x1,y1)(x2,y2)(四个输入),作为结果产生一个第三点 (x3,y3)(两个输出)。
  • EcMul:将 EC 点 P = (x1,y1) 乘以标量 k(三个输入),作为结果产生一个第三点 kP = (x2,y2)(两个输出)。
  • EcPairing:根据 EIP-197 标准,对 alt_bn128 曲线进行的配对检查。具体而言,该检查验证等式 e(A1,B1)∗⋯∗e(Ak,Bk)=1,e(A_1, B_1) * \cdots * e(A_k, B_k) = 1, 其中 e:G1×G2↦GT 是双线性配对操作,G1 和 G2 是两个源群,GT 是目标群,A1,⋯ ,Ak 是 G1 中的 k 个 EC 点的列表,B1,⋯ ,Bk 是 G2 中的 k 个 EC 点的列表。每个 Ai 点的坐标是字段元素,因此每个 Ai 用两个字段元素 (xi,yi) 定义;每个 Bi 点的坐标是字段的二次扩展元素,所以每个 Bi 用四个字段元素 (xi1,xi2,yi1,yi2) 定义,其中这两个坐标用成对 (xi1,xi2) 和 (yi1,yi2) 表示。对于单个配对 (k=1),有 6 个输入表示点 A 和 B 的结合。总的来说,合约以 6k 个字段元素作为输入(表示所有 Ai 和 Bi 点的坐标),并读取一个布尔输出,取决于配对等式是否成立。
  • Modexp:模幂运算 r = b^e mod m,具有基 b、指数 e 和模数 m(三个输入),作为结果产生标量 r(一个输出)。

最后一条备注是,对于以上四个操作而言,设置专用合约,而不是重复使用现有的 EVM 预编译实现,是因为 ZKsync VM 运行在 L2 上。其计算的有效性通过提交给 L1 验证的 ZK 证明得以证明。这使得现有 VM 执行的所有操作都需要具有可证明的实现。不过,EcAddEcMulEcPairingModexp 合约在功能上应尽可能接近其 EVM 预编译对应物(分别为 ecAddecMulecPairingmodexp)。用户应当注意以下差异:

  • 每个预编译的调用 gas 成本与其 EVM 对应物不同。
  • Modexp 合约对输入宽度(基、指数和模数)有固定限制,该限制设置为 32 字节。
  • EcPairing 预编译的执行 gas 支出限制为 2^32 - 1

安全模型和信任假设

在审计过程中,做出了以下信任假设:

  • 处理四个电路预编译的 gas 成本准确反映了汇总操作员和节点产生的成本。
  • 所有四个 Yul 合约均没有构造函数,假设这些合约将被预先部署在正确的地址上,并具有正确的运行时字节码。

中等严重性

‘EcPairing’ 的返回长度与规范不匹配

EIP-197 引入了以太坊上的 EcPairing 预编译,并声明“返回的数据长度始终为 32 字节,并以 32 字节大端数字编码”。然而,EcPairing 合约的 fallback 函数返回了 64 字节

考虑仅返回 32 字节,以避免潜在问题,并更接近 EVM 的规范。

更新:pull request #1373 中解决,提交为 fab789f。返回值已更新为 32 字节。

低严重性

返回语句中硬编码的模长值

modexp 预编译 返回 内存中其前 32 字节的最后 modLen 字节。然而,"32" 是硬编码的。

考虑将硬编码的 "32" 替换为 MAX_MOD_BYTES_SUPPORTED(),以与其余代码保持一致,并避免如这些值将来更改时出现潜在错误。

更新:pull request #1370 中解决,提交为 e29c2be

备注与附加信息

Gas 优化

在任何计算之前,ModExp 预编译 清除 内存中的前三个字。 但是,该内存应已被 EVM 初始化为零,因为预编译只接受外部调用或交易。

假设以上在 ZKsync 上也是真实的,考虑在调用预编译时取消此检查以节省 gas。

更新:pull request #1369 中解决,提交为 de48942

缺少或误导性文档

在代码库中,发现了多处文档可以改进的实例:

  • Modexp 预编译的 return 语句之前的 这条评论 声明返回结果“假定用零右填充”。然而,sub(32, modLen) 的偏移量表明该值为左填充/右对齐。
  • uint64_perPrecompileInterpreted 输入到 unsafePackPrecompileParams 是左对齐的,与其他 四个输入字 不同。这是因为 memoryPageToReadmemoryPageToWrite 参数被保留为 0,因此可能需要文档说明。
  • 所有四个预编译的 gas 成本是针对一个未文档化的值 80_000 计算的: ECADD_GAS_COSTECMUL_GAS_COSTECPAIRING_PAIR_GAS_COSTMODEXP_GAS_COST
  • 对 EC 配对的 basepair gas 成本之间的区别不清楚。
  • 在关于 unsafePackPrecompileParams 调用的 输入长度 的注释中有一个拼写错误:第一个点的第二个坐标应为 p_y 而不是 p_x,即 (p_x, p_x, q_x_a, q_x_b, q_y_a, q_y_b) 应为 (p_x, p_y, q_x_a, q_x_b, q_y_a, q_y_b)

考虑解决上述识别出的实例,以改善代码库的可读性和可维护性。

更新:pull request #1371 中解决,提交为 3acee2f96d51e2

Modexp 缺少 SPDX 许可证标识符

Modexp.yul 文件缺少 SPDX 许可证标识符。

为保持与其他预编译的一致性,并遵循 最佳实践,考虑在 Modexp.yul 中添加 SPDX 许可证标识符。

更新:pull request #1372 中解决,提交为 553ea3a

推荐

差分模糊测试

由于采用了通过 verbatim 调用的自定义操作码,并依赖于运行 ZKsync VM 的节点,因此对代码进行全面端到端测试的挑战很大。因此,考虑到实施的预编译的复杂性,我们建议将其与以太坊的预编译进行差分模糊测试,以识别和解决任何潜在的边缘情况。

结论

此次审计中引入的更改为 ZKsync VM 中的椭圆曲线 (EC) 点加法、EC 标量乘法、EC 配对和模幂运算提供了预编译作为系统合约。审计范围内的代码处理来自 calldata 的输入解析,并以预定义格式打包传递给执行层,在那里执行实际操作。如前所述,最后一部分不在范围内,将在未来审计中检查。

审计未发现重大问题。我们识别了与规范的偏差,并提供了改进代码质量的建议。总体而言,我们发现该实现是合理且文档良好的,但建议如果可能的话,添加与以太坊预编译的差分测试。我们感谢 Matter Labs 团队对我们所有问题的详细回复。

请求审计

  • 原文链接: blog.openzeppelin.com/zk...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。