本文对 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 #1259 的 matter-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 中执行一个通用的操作码指令,执行一个预编译(ECAdd
、ECMul
、ECPairing
、Modexp
等),其依据是调用合约。
在内部,precompileCall
调用 verbatim_2i_1o
函数,该函数被 VM 拦截,以将调用路由到执行层。具体而言,调用 zksync-protocol 代码库中的 execute_precompile
函数(如上所述不在范围内),将调用与相应的预编译实现匹配。因此,分别触发 ecadd_function
、ecmul_function
、ecpairing_function
或 modexp_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 执行的所有操作都需要具有可证明的实现。不过,EcAdd
、EcMul
、EcPairing
和 Modexp
合约在功能上应尽可能接近其 EVM 预编译对应物(分别为 ecAdd
、ecMul
、ecPairing
和 modexp
)。用户应当注意以下差异:
Modexp
合约对输入宽度(基、指数和模数)有固定限制,该限制设置为 32 字节。EcPairing
预编译的执行 gas 支出限制为 2^32 - 1
。在审计过程中,做出了以下信任假设:
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。
在任何计算之前,ModExp
预编译 清除 内存中的前三个字。 但是,该内存应已被 EVM 初始化为零,因为预编译只接受外部调用或交易。
假设以上在 ZKsync 上也是真实的,考虑在调用预编译时取消此检查以节省 gas。
更新: 在 pull request #1369 中解决,提交为 de48942。
在代码库中,发现了多处文档可以改进的实例:
Modexp
预编译的 return
语句之前的 这条评论 声明返回结果“假定用零右填充”。然而,sub(32, modLen)
的偏移量表明该值为左填充/右对齐。uint64_perPrecompileInterpreted
输入到 unsafePackPrecompileParams
是左对齐的,与其他 四个输入字 不同。这是因为 memoryPageToRead
和 memoryPageToWrite
参数被保留为 0,因此可能需要文档说明。80_000
计算的: ECADD_GAS_COST,ECMUL_GAS_COST,ECPAIRING_PAIR_GAS_COST,MODEXP_GAS_COST。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 中解决,提交为 3acee2f 和 96d51e2。
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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!