1. 引言
前序博客:
- Polygon zkEVM Hexens审计报告解读
- Polygon zkEVM Spearbit审计报告解读(2022年12月版本)
主要见:
- Polygon zkEVM Security Review: January 2023 Engagement
Polygon zkEVM为提供(opcode层面兼容的)EVM等价zk-rollup,具有良好的用户体验并兼容现有以太坊生态和工具。
本轮审计重点关注:
- 1)对(PIL)状态机持续的手工和工具辅助review。
- 2)(跨越边界)理解并分析zkASM<>PIL结合。
- 3)对constant多项式的构建进行初步review。
- 4)Ethereum state tests除外状态的review,并验证除外原因。
- 5)持续对EVM指令的zkASM实现进行手工review。
Spearbit研究人员分为2组:
- 1)一组专注于状态机和PIL。
- 2)另一组专注于指令实现和测试评估。
本轮审计历时11天,共发现30个漏洞,其中致命漏洞3个,不兼容漏洞4个,低危漏洞2个,信息提示类21个,当前已解决其中26个问题。
在本审计报告中,风险分级为:
风险级别 |
影响:高 |
影响:中等 |
影响:低 |
可能性:高 |
致命 |
高 |
中等 |
可能性:中等 |
高 |
中等 |
低 |
可能性:低 |
中等 |
低 |
低 |
其中:
- 1)影响:
- 高:会导致丢失协议中 > 10 % >10\% >10%资产,或对大多数用户有重大危害。
- 中等:丢失 < 10 % <10\% <10%资产,或仅丢失一部分用户的资产,但仍不可接受。
- 低:丢失很烦人但可接收。适用于可以很容易修复的网格攻击,甚至是gas不足。
- 2)概率:
- 高:几乎确定会发生,易于操作,或不易于操作但激励诱人。
- 中等:仅有可行性可能或动机,但相对有可能发生。
- 低:需要stars to align,或者几乎没有动机。
- 3)风险级别:
- 致命:(若已部署)必须尽快修复。
- 高:(若未部署),需在部署前修复。
- 中等:应该修复。
- 低:可修复。
1.1 测试
本轮review重点关注:
- zkevm-testvectors#v0.6.0.0-rc.1。
除外的测试用例列表长度约为2200个entries,可分为如下类别:
- 预编译测试
- 交易测试(EIP-2930访问列表相关)
- selfdestruct测试
- 应使用 > 30 M >30M >30Mgas、OOC(out of counters)或资源耗尽,而失败的测试。
已研究了一些预编译测试,因为有些支持的预编译(如ecrecover)失败,原因在于其结合了多个预编译(通常是sha256)。实际并不对这些测试做调整。
当前专注于研究因资源限制,且与EVM指定相关限制导致失败的测试用例。其中包括MemoryTests、VMTests、StackTests,但因时间有限,未覆盖每个测试用例。
1.2 zkASM
本轮review重点关注:
该ROM中包含如下内容:
- ecrecover
- opcodes
- precompiles
- utilities
- transaction processing
因时间有限,重点关注:
- 交易验证
- flow-control.zkasm中的:
- comparison.zkasm中的:LT、GT、SLT、SGT、EQ、ISZERO、AND、OR、XOR、NOT、BYTE、SHR、SHL、SAR。
- stak-operations.zkasm中的:DUPx、SWAPx、POP、PUSH。
- utils.zkasm中:被以上组件使用的utilities。
1.3 PIL状态机
本轮review重点关注:
以PIL编写的所有zkEVM状态机有:
- arith状态机,部分覆盖(除曲线方程式正确性之外的所有)
- binary状态机,完全覆盖
- mem状态机,完全覆盖
- mem_align状态机,完全覆盖
- keccakf状态机,部分覆盖
- padding_kk状态机,部分覆盖
- padding_kkbit状态机,部分覆盖
- nine2one状态机,完全覆盖
- padding_pg状态机,部分覆盖
- poseidon状态机,部分覆盖
- main/rom状态机,部分覆盖
- global状态机,完全覆盖
- storage状态机,部分覆盖
2. 致命漏洞
2.1 致命漏洞1:当receiver和sender相同时,SENDALL会对余额清零
上下文见:
- zkevm-rom:create-terminate-context.zkasm#L1040
SENDALL指令实现会更新receiver余额,然后将sender余额清零,而并不检查receiver和sender是否为同一地址。运行SENDALL时若receiver与所执行合约相同,则最终余额为0。
需注意,见后面的“SENDALL在Ethereum state tests中未测试”漏洞,即当前Ethereum state tests中并未覆盖SENDALL测试。为确认,需要一些修改来运行EIP4758 test cases #1024:
- 测试框架中zkevm-testvectors:gen-inputs.js#L406处的跳过SELFDESTRUCT测试,应禁用。
- 修改go-ethereum来激活Berlin中的SENDALL。
- 修复go-ethereum实现中的gas charge bug EIP-4758 Implementation #24389,修改后的内部版本见:https://github.com/spearbit-audits/go-ethereum/tree/sendall-berlin。
- 修改运行在Berlin上的测试用例,修改后的内部版本见:https://github.com/spearbit-audits/ethereum-state-tests/tree/eip4758-berlin。
sendallBasic通过而sendallToSelf失败:
Test name: sendallToSelf.json
AssertionError: expected '0' to equal '1000000000000000000'
at Context.
(/0xPolygonHermez/zkevm-testvectors/tools/ethereum-tests/gen-inputs.js:452:92)↪→
at process.processTicksAndRejections (node:internal/process/task_queues:95:5) {
showDiff: true,
actual: '0',
expected: '1000000000000000000',
operator: 'strictEqual'
}
当SELFDESTRUCT用该边界情况来“burning” Ether时,根据测试结果来看,SENDALL提案看起来似乎并不想要该边界情况。
建议:
- 当receiver与executing合约相同时,不更新余额。
修复PR见:
- Feature/fix sendall #236
- add test SENDALL #172:每次ROM更新时,提供自动运行测试用例。
- ethereumjs-monorepo: handle sendall to itself. increase package version,以合理处理sendall。
2.2 致命漏洞2:opCALLDATALOAD/opCALLDATACOPY读取calldata位置越界
上下文见:
- zkevm-rom:calldata-returndata-code.zkasm#L24
- zkevm-rom: calldata-returndata-code.zkasm#L128
检查输入calldata offset是否在calldata边界内,使用的是:
txCalldataLen < offset。
当offset == txCalldataLen时,其会加载对应calldata[txCalldataLen]位置地址的memory word——即在地址 1024 + txCalldataLen / 32 位置的memory word。
建议:
- 使用offset < txCalldataLen。从而排除offset == txCalldataLen边界情况。
修复PR见:
- Optimize opPush and SHL #242
2.3 致命漏洞3:opJUMP/opJUMPI读取位置超过代码边界
上下文见:
- zkevm-rom:flow-control.zkasm#L5
检查jump目标在代码边界内使用的是:
bytecodeLength < jumpDst
当dst == bytecodeLength时,仍会读取code[bytecodeLength]位置处的bytecode。
建议:
- 改为检查 dst < bytecodeLength,然后JUMPNC(invalidJump)。
修复PR见:
3. 不兼容漏洞
3.1 不兼容漏洞1:缺乏对特定预编译支持,可能引起可用性和安全风险
上下文见:
Berlin协议支持9个预编译合约:
- ecrecover
- sha256
- ripemd160
- identity
- modexp
- ecadd
- ecmul
- ecpairing
- blake2f
而zkEVM仅实现了其中的2个预编译合约:ecrecover和identity。
未支持的预编译合约中:
- ripemd160和blake2f很少用到
- modexp常用于RSA验证场景,但也用于通用bignumber库中。
- ecadd/ecmul/ecpairing常用于各种ZK应用,如bridge、私人交易和游戏应用。
RSA的一个主要用例为:
- ENS的dnssec-oracl:用于验证DNSSEC enabled domains(如.xyz、.tech等等)的所有权。
- VDF验证。如https://github.com/kilic/evmvdf和https://github.com/0xProject/VDF。
缺乏这些主要表现在2方面:
- 1)可用性问题:特定项目将无法在zkEVM上部署,可能会影响大范围使用和跨链兼容性。
- 2)安全性问题:可能有的项目可部署,但特定特性可能会被破坏。可能会对在合约内锁定有资产的项目有影响。
建议:
- 注意到文档中的差异。考虑实现sha256、modexp和bn256预编译文件。
Polygon Hermez回复:
- 出于安全考虑,将revert任意对未支持预编译合约的调用,具体实现见:
- selector.zkasm#L16
- revert-precompiled.zkasm
3.2 不兼容漏洞2:BASEFEE指令返回固定0值
上下文件:
BASEFEE指令不包含在Berlin中(见Berlin Network Upgrade Specification),而是作为London(见London Network Upgrade Specification)中EIP-1559的内容首次展示。对BASEFEE并未定义明确的值范围,也未以任何形式提及BASEFEE值为0。
关注点1:
-
EIP-1559: Fee market change for ETH 1.0 chain 在区块头中定义了base_fee_per_gas,该值需遵循如下规则:
INITIAL_BASE_FEE = 1000000000
BASE_FEE_MAX_CHANGE_DENOMINATOR = 8
ELASTICITY_MULTIPLIER = 2
...
parent_gas_target = self.parent(block).gas_limit
parent_gas_limit = self.parent(block).gas_limit
# on the fork block, don't account for the ELASTICITY_MULTIPLIER to avoid
# unduly halving the gas target.
if INITIAL_FORK_BLOCK_NUMBER == block.number:
parent_gas_target = self.parent(block).gas_limit
parent_gas_limit = self.parent(block).gas_limit * ELASTICITY_MULTIPLIER
parent_base_fee_per_gas = self.parent(block).base_fee_per_gas
parent_gas_used = self.parent(block).gas_used
...
# check if the base fee is correct
if INITIAL_FORK_BLOCK_NUMBER == block.number:
expected_base_fee_per_gas = INITIAL_BASE_FEE
elif parent_gas_used == parent_gas_target:
expected_base_fee_per_gas = parent_base_fee_per_gas
elif parent_gas_used > parent_gas_target:
gas_used_delta = parent_gas_used - parent_gas_target
base_fee_per_gas_delta = max(parent_base_fee_per_gas * gas_used_delta
BASE_FEE_MAX_CHANGE_DENOMINATOR, 1)↪→
expected_base_fee_per_gas = parent_base_fee_per_gas + base_fee_per_gas_delta
else:
gas_used_delta = parent_gas_target - parent_gas_used
base_fee_per_gas_delta = parent_base_fee_per_gas * gas_used_delta
BASE_FEE_MAX_CHANGE_DENOMINATOR↪→
expected_base_fee_per_gas = parent_base_fee_per_gas - base_fee_per_gas_delta
assert expected_base_fee_per_gas == block.base_fee_per_gas, 'invalid block: base fee not correct'
通常(在生态测试和工具中),使用的最低值为7 wei。
对于以太坊主网,basefee的最低可能值为7wei。但其也依赖于fork block的初始值(INITIAL_BASE_FEE)。
证据如下。定义如下变量:
- 1) b n b_n bn表示block n n n的base-fee
- 2) g n g_n gn表示block n n n使用的总gas
- 3) t n t_n tn表示block n n n的target gas
为简化,假设block 0为EIP-1559 Hardfork block,其中 b 0 = 10 b_0=10 b0=10 gwei。根据block n n n, block n + 1 n+1 n+1的basefee定义为:
即使basefee为固定0值,basefee为7 wei是一个好的默认值,zkEVM总是可定义EIP-1559的fork block,并设置 b f o r k − b l o c k b_{fork-block} bfork−block为大于7 wei的常量值,以快速启动合适的EIP-1559机制。
需注意的是:
- 一旦basefee至少为7 wei时,其永远不能低于7 wei。若交易处理遵循EIP-1559,则7个区块后 b n > t n b_n>t_n bn>tn,则basefee将达7 wei。
- EIP-1559的一个有趣结果是,其阻止交易gasprice为0。可能值得关注下,何时sequencer会打包gasprice为0的交易。
- 若basefee为固定0值,则当 b n > t n b_n>t_n bn>tn时(即,若区块内使用的gas超过了区块gas limit的一半),EIP-1559机制将不一致。
关注点2:
- Ethereum Test Suite中未包含任何currentBaseFee为0的测试用例,且与其它部分的交互也未覆盖。
关注点3:
关注点4:
- 最近,某些工具(如hardhat: BASEFEE always returns 0 when running an eth_call with a London block #1688)正面临gas estimation子系统返回0值的问题,引起测试或部署代码失败。
建议:
- 文档说明Mainnet/Berlin差异,考虑非零值,或移除该指令。
Polygon Hermez回复:
- 基于以下2个主要原因,决定从当前zkEVM版本中移除BASEFEE opcode:
- 1)安全考虑:BASEFEE opcode并未在Berlin hardfork中实现。将实现BASEFEE opcode的合约,但由于其是硬编码的,可能会有意外情况。这回降低使用BASEFEE opcode合约的安全性。
- 2)一致性:添加或移除BASEFEE opcode并不影响zkEVM性能。由于已实现了Berlin hardfork,但未包含EIP-1559,这并无任何优势,但可能会给支持该opcode的会议带来安全问题。
3.3 不兼容漏洞3:SENDALL在Ethereum state tests中未测试
上下文见:
- zkevm-rom:create-terminate-context.zkasm#L979
Ethereum state tests中并未覆盖SENDALL指令,因SENDALL指令正在开发中EIP-4758: Deactivate SELFDESTRUCT。EIP4758 test cases #1024,2022年就有相关PR,但其与EIP的一致性尚不清楚。
关联问题为:“使用SENDALL来替换SELFDESTRUCT”。
建议:
Polygon Hermez回复:
3.4 不兼容漏洞4:使用SENDALL来替换SELFDESTRUCT
上下文见:
- zkevm-rom:create-terminate-context.zkasm#L979
SELFDESTRUCT与Berlin说明不一致。其被替换为EIP-4758: Deactivate SELFDESTRUCT
Deactivate SELFDESTRUCT by changing it to SENDALL, which does recover all funds to the caller but does not delete any code or storage,而整个账号余额仍需transfer,但该账号不被移除。此外,移除了gas refund。该EIP不清楚是否继续执行。
这有显著不同,合约开发者需留意。
同时,需注意,尽管已有相关讨论,但如何实现暂无共识。
建议:
Polygon Hermez回复:
- 当前实现会像SELFDESTRUCT那样停止。已注意到EIP-4758: Deactivate SELFDESTRUCT
Deactivate SELFDESTRUCT by changing it to SENDALL, which does recover all funds to the caller but does not delete any code or storage中并未说明。决定遵循SELFDESTRUCT,终止交易执行。
- 终止执行行为、移除SELFDESTRUCT和SENDALL指令,将在文档中详细说明。
4. 低危漏洞
4.1 低危漏洞1:
上下文见:
- zkevm-proverjs:padding_pg.pil
padding_kk状态机,将blocks以136行来表示。由于136不能整除 N N N,在table底部会有不完整block。padding_kk状态机不支持方位最后的block,以“avoid problems”。
类似的,padding_gg状态机,将blocks以56行来表示。由于56也不能整除 N N N,在table底部也会有不完整block。但padding_gg状态机似乎并不是不支持方位最后的block,来“avoid problems”。
建议:
- 经分析,未发现具体的可利用案例。但是,出于安全考虑,建议像padding_kk状态机那样,也禁止padding_gg状态机访问最后的block。
修复PR见:
- fix/padding pg crvalid #127
4.2 低危漏洞2:PC应约束为初始化为0值
上下文见:
(主状态机和storage状态机内的)PC值,均只通过plookup和编码control-flow的约束 来约束。并无约束来强化程序从entry point开始。
建议:
- 目前并不清楚该问题的影响。但建议约束Main.zkPC和Storage.pc初始化为0值:Global.L1 * Main.zkPC = 0, Global.L1 * Storage.pc = 0。
修复PR见:
- add pil pc zero constraints, remove duplicated column on Rom plookup #121
5. 信息类提示
5.1 信息类提示1:croffset应唯一确定crF_i寄存器
上下文:
- zkevm-rom:padding_kk.pil#L113-L116
croffset和crF_i寄存器由plookup约束。但是,在文档Table7中的描述是不正确的。croffset应唯一确定crF_i寄存器。当crLatch为1和crOffset为0时,crF_i寄存器中的值均不同。
对于plookup argument,当crOffset == 0和crF_0 == 1时,剩余的crF_i寄存器值应为0,这也与PIL中的一样。
建议:
Polygon Hermez回复:
- 已修改更新文档。offset递减和factors应逆序表示。
5.2 信息类提示2:mem_align.pil中的resultRd可能约束不够
上下文:
- zkevm-proverjs:mem_align.pil#L95
当RESET为0时,3个多项式 resultWr8、resultWr256 和 resultRd 均约束为0。当wr8和wr256为0时, resultWr8 和 resultWr256 也分别被约束为0。同理对 resultRd 可能应有相同的逻辑,当wr8为1或wr256为1时,应约束resultRd为0。
建议:
- 添加约束 (wr8 + wr256) * resultRd = 0
Polygon Hermez回复:
- 从可靠性来说,无需添加这种明确的约束。因为其已通过permutation checks来保证了该约束。不过为改进代码清晰度,添加了约束 (wr8 + wr256) * resultRd = 0:
5.3 信息类提示3:mem_align.pil中多余约束
上下文:
- zkevm-proverjs:mem_align.pil#L104
wr8 * wr256 = 0; 约束是多余的,因第111行代码显示 wr8’ 和 wr256’ 源自同一plookup,且相应的常量WR256和WR8可确保二者特定时期只有一个为1。
建议:
修复PR:
5.4 信息类提示4:Binary.lCout是多余的
上下文:
- zkevm-proverjs:binary.pil#L83
binary.pil中有:
lCout' = cOut;
main.pil中有:
bin {
binOpcode,
A0, A1, A2, A3, A4, A5, A6, A7,
B0, B1, B2, B3, B4, B5, B6, B7,
op0, op1, op2, op3, op4, op5, op6, op7,
carry
} is
Binary.resultBinOp {
Binary.lOpcode,
Binary.a[0], Binary.a[1], Binary.a[2], Binary.a[3], Binary.a[4], Binary.a[5], Binary.a[6], Binary.a[7],
Binary.b[0], Binary.b[1], Binary.b[2], Binary.b[3], Binary.b[4], Binary.b[5], Binary.b[6], Binary.b[7],
Binary.c[0], Binary.c[1], Binary.c[2], Binary.c[3], Binary.c[4], Binary.c[5], Binary.c[6], Binary.c[7],
Binary.lCout
};
可等价为,移除lCout并修改permutation check为:
bin' {
binOpcode',
A0', A1', A2', A3', A4', A5', A6', A7',
B0‘, B1‘, B2', B3', B4', B5', B6', B7',
op0', op1', op2', op3', op4', op5', op6', op7',
carry'
} is
Binary.resultBinOp' {
Binary.lOpcode',
Binary.a[0]', Binary.a[1], Binary.a[2]', Binary.a[3]', Binary.a[4]', Binary.a[5]', Binary.a[6]', Binary.a[7]',
Binary.b[0]', Binary.b[1]', Binary.b[2]', Binary.b[3]', Binary.b[4]', Binary.b[5]', Binary.b[6]', Binary.b[7]',
Binary.c[0]', Binary.c[1]', Binary.c[2]', Binary.c[3]', Binary.c[4]', Binary.c[5]', Binary.c[6]', Binary.c[7]',
Binary.cOut
}
建议:
Polygon Hermez回复:
5.5 信息类提示5:ModExp预编译合约已禁用,但源码仍在
上下文见:
- zkevm-rom:selector.zkasm#L16
- zkevm-rom:modexp.zkasm
ModExp预编译合约当前仅部分实现,其中包含了TODOS,看起来是不完整的。当前所review的tag中,已在selectorPrecompiled jump-table中将其禁用。
建议:
修复PR:
- Boundaries check fixes #228
5.6 信息类提示6:testing报告
总结当前试图运行的ethereum state test 格式的所有测试用例。
Ethereum State Tests:
- 启用了一些当前在Polygon zkEVM test suite中禁用的状态测试。为启用,要么需降低交易gas limit,使其小于30M gas,要么需启用测试中的Berlin hard fork。
- 完整“Ethereum Tests”报告见:zkEVM Testing Report
- 所有测试修改见内部库:https://github.com/spearbit-audits/ethereum-state-tests/tree/reduce-gas-limit
ECRecover tests:
- 某些ECRecover预编译合约测试用例未针对Berlin fork运行。启用所有丢失的测试用例后,均测试通过。
- 完整“ECRecover tests”报告见:zkEVM Testing Report
- 修改项见内部库:https://github.com/spearbit-audits/ethereum-state-tests/tree/ecrecover-all-forks
evm-benchmarks:
- lpsilon的EVM benchmark suite 已是Ethereum State test格式。需以Blockchain test format生成,并启用Berlin fork来测试zkEVM。
大多数benchmarks都有正确结果。一些有OOC错误或在测试机上提示内存不足。
- 完整“evm-benchmarks”报告见:zkEVM Testing Report
- 完整日志见:evm-benchmarks-logs.tar.xz
- 所生成的blockchain tests和filters见内部库:https://github.com/ipsilon/evm-benchmarks/tree/blockchain_tests
New arithmetic test:
- 源自Tests for arithmetic opcodes #1144,修改为Berlin hardfork之后,测试通过。
- 相应修改见内部库:https://github.com/spearbit-audits/ethereum-state-tests/tree/20230114-arith-tests-berlin
5.7 信息类提示7:mem_align常量定义的注释不正确
上下文:
- zkevm-proverjs:mem_align.pil#L50
- zkevm-proverjs:sm_mem_align.js#L27
FACTOR常量被描述为具有64行周期,后32行为0。但在executor中,后32行为0是不存在的,FACTOR简单做常规对角移动,每32行重复一次。
建议:
- 若constant生成是正确的,则应修改mem_align.pil中的注释
修复PR:
- fix factor constant comment on mem_align.pil #124
5.8 信息类提示8:可内联一些中间多项式
上下文:
一组中间变量是线性的。基于静态分析:
pol Arith.eq0_31 = Arith.y2[15]
pol Arith.eq1_31 = -262140
pol Arith.eq2_31 = -262140
pol Arith.eq3_31 = -262140
pol Arith.eq4_31 = -262140
pol Binary.RESET = (Global.CLK32[0] + Global.CLK32[16])
pol Mem.INCS = (Global.STEP + 1)
pol Mem.ISNOTLAST = (1 - Global.LLAST)
pol MemAlign.RESET = Global.CLK32[0]
pol PoseidonG.a0 = (PoseidonG.in0 + PoseidonG.C[0])
pol PoseidonG.a1 = (PoseidonG.in1 + PoseidonG.C[1])
pol PoseidonG.a10 = (PoseidonG.cap2 + PoseidonG.C[10])
pol PoseidonG.a11 = (PoseidonG.cap3 + PoseidonG.C[11])
pol PoseidonG.a2 = (PoseidonG.in2 + PoseidonG.C[2])
pol PoseidonG.a3 = (PoseidonG.in3 + PoseidonG.C[3])
pol PoseidonG.a4 = (PoseidonG.in4 + PoseidonG.C[4])
pol PoseidonG.a5 = (PoseidonG.in5 + PoseidonG.C[5])
pol PoseidonG.a6 = (PoseidonG.in6 + PoseidonG.C[6])
pol PoseidonG.a7 = (PoseidonG.in7 + PoseidonG.C[7])
pol PoseidonG.a8 = (PoseidonG.hashType + PoseidonG.C[8])
pol PoseidonG.a9 = (PoseidonG.cap1 + PoseidonG.C[9])
建议:
- 引入一种方式将它们定义为aliases,或在PIL编译时内联它们。
5.9 信息类提示9:应改进语言和工具
上下文:
与zkEVM的一个方面有关的信息/代码通常分布在多个文件中,这使得审查代码变得非常困难。还有其他基于语言的功能可以提供帮助,例如,常量定义可以直接成为PIL文件的一部分,多项式可以被键入(代码历史表明,这可能在过去部分实现)。
执行语义和验证语义可以同时存在。目前,执行者以与通过PIL文件中的约束验证多项式的方式非常相似的方式填充所承诺的多项式。
建议:
- 这是一个信息性的建议,因为在发布之前考虑和完成这样的重大更改是不现实的。
Polygon Hermez回复:
- 与多项式类型相关,移除是因为多项式内部是有限域(goldilocks)。要定义一种特定类型或范围,必须定义特定约束或plookup。
5.10 信息类提示10:移除padding_kk中的firstHash
上下文:
- zkevm-proverjs:padding_kk.pil#L38
中间多项式firstHash使用承诺多项式lastHash来表示。
firstHash仅在如下场景中使用:
pol commit firstHash;
firstHash' = lastHash;
[...]
len * firstHash = rem * firstHash;
其等价为:
len' * lastHash = rem' * lastHash;
建议:
- 当前的实现通过引入firstHash,会更清晰一点。但是,可令fristHash为alias lastHash’,具有相同的清晰度,且不用额外引入一个中间多项式。可在PIL中拥有alias syntax,或使用内联中间多项式来优化。总之,二者等价。
修复PR:
- first hash optimization #123:移除firstHash,并修改为len’ * lastHash = rem’ * lastHash;。
5.11 信息类提示11:优化opAuxPUSHB
上下文:
- zkevm-rom:stack-operations.zkasm#L169
可将2个步骤,合并为一个:【其它地方也有类似代码】
$ => A :MLOAD(isCreate)
0 - A :JMPN(opAuxPUSHBcreate)
; set bytes length to read to C
D - 1 => C
0 => A
合并为:
$ => A :MLOAD(isCreate), JMPNZ(opAuxPUSHBcreate)
; set bytes length to read to C
D - 1 => C
建议:
修复PR:
- Boundaries check fixes #228
5.12 信息类提示12:Memory expansion gas check 被切分为2个检查
上下文:
- zkevm-rom:utils.zkasm#L451
第一个out-of-gas check为:
GAS + 3 * old_size + old_size * old_size / 512 < 3 * new_size => GAS < 3 * new_size - 3*old_size - old_size * old_size / 512
第二个check为:
GAS + 3 * old_size + old_size * old_size / 512 - 3 * new_size < new_size *
new_size / 512 => GAS < 3 * new_size + new_size * new_size / 512 - 3 * old_size - old_size *
old_size / 512
建议:
修复PR:
- Fix computeGasSendCall #221:重构了saveMem函数。
5.13 信息类提示13:当前内存size存储为最后一次请求最大size,而未取整为32的倍数
上下文:
- zkevm-rom:utils.zkasm#L440
memLength变量中存储了expansion时所需的准确size,未被取整为word size的倍数。若在当前醉倒word中访问更高的地址,其仍可计算memory expansion cost(结果为0,因words数未变)。同时MSIZE实现需根据memLength来取整为word size。
建议:
- 可能的优化为:在memLength中存储已取整为word size倍数的size(即存储的即为MSIZE将返回的值)。然后memory expansion将直接与MSIZE对比该exact requested size,并仅当分配的words数增加时,才计算expansion cost。
换句话说,可认为memory expansion总是以32的倍数扩大,而不是所请求的exact size。
修复PR:
- Feature/memalign optimization #238
5.14 信息类提示14:JMP to a helper used instead of CALL
上下文:
- utils.zkasm#L303
- utils.zkasm#L60
- utils.zkasm#L71
- 等等
建议:
- 将很多类似zkPC+1 => RR :JMP(helper),改为 CALL(helper)。
修复PR:
5.15 信息类提示15:MSTORE潜在优化
上下文:
- zkevm-rom:utils.zkasm#L220
MSTOREX2使用SHLArith和SHRArith组合来存储在2个相邻的memory slots。
建议:
- 像memAlignOptionMSTORE那样,使用MEM_ALIGN_WR来优化。
Polygon zkEVM回复:
5.16 信息类提示16:lookup argument中的重复key value
上下文:
- zkevm-proverjs:main.pil#L526
inHASHPOS列在lookup argument中出现了2次,相关联的目标列Rom.inHASHPOS也在value中出现了2次:
(x in y) => ({x, x} in {y, y})
建议:
修复PR:
- add pil pc zero constraints, remove duplicated column on Rom plookup #121
5.17 信息类提示17:MSTORE32中存储了没必要的寄存器值
上下文:
- zkevm-rom:utils.zkasm#L182
MSTORE32无需存储寄存器E之前值,因其用作输入、输出寄存器。
建议:
修复PR:
- Audit jan fixes #226
- update tmpvars & expAD counters #212,改进了utils函数中的tmpVars处理。
5.18 信息类提示18:可优化Jump目标有效性检查
上下文:
- zkevm-rom:flow-control.zkasm#L91
initcode中jump目标有效性检查包括:
- 用MLOADX加载1个字节
- 使用SHRarith右移32个字节
- 将其与0x5b进行比对。
建议:
- 潜在的优化为,可跳过右移,直接与32字节0x5b00…00进行比较。这样就不会在SHARarith上浪费算术计数器。
修复PR:
5.19 信息类提示19:@zk-counters注释与代码不符
上下文见:
- zkevm-rom:flow-control.zkasm#L5
- zkevm-rom:storage-memory.zkasm#L8
- zkevm-rom:storage-memory.zkasm#L43
- 等等
注释中提及的counter数,与实际代码中的检查不一致。
建议:
Polygon zkEVM回复:
- 关于zk-counters的注释更新见:Feature/counters and comments #227
- Feature/counter tests #223:构建了特定的工具,来自动检查每个函数的zk-counters变化,因此若有代码改变引起counters增减,都能自动发现。
5.20 信息类提示20:main_executor.js中的typo
上下文:
- zkevm-proverjs:main_executor.js#L60
建议:
修复PR:
5.21 信息类提示21:padding_kkbit.pil和sm-hash论文中的typo
上下文:
- zkevm-proverjs:padding_kkbit.pil#L21
建议:
- 应为9*136 = 1224,同理sm-hash论文中也应修改。
修复PR:
附录:Polygon Hermez 2.0 zkEVM系列博客
- ZK-Rollups工作原理
- Polygon zkEVM——Hermez 2.0简介
- Polygon zkEVM网络节点
- Polygon zkEVM 基本概念
- Polygon zkEVM Prover
- Polygon zkEVM工具——PIL和CIRCOM
- Polygon zkEVM节点代码解析
- Polygon zkEVM的pil-stark Fibonacci状态机初体验
- Polygon zkEVM的pil-stark Fibonacci状态机代码解析
- Polygon zkEVM PIL编译器——pilcom 代码解析
- Polygon zkEVM Arithmetic状态机
- Polygon zkEVM中的常量多项式
- Polygon zkEVM Binary状态机
- Polygon zkEVM Memory状态机
- Polygon zkEVM Memory Align状态机
- Polygon zkEVM zkASM编译器——zkasmcom
- Polygon zkEVM哈希状态机——Keccak-256和Poseidon
- Polygon zkEVM zkASM语法
- Polygon zkEVM可验证计算简单状态机示例
- Polygon zkEVM zkASM 与 以太坊虚拟机opcode 对应集合
- Polygon zkEVM zkROM代码解析(1)
- Polygon zkEVM zkASM中的函数集合
- Polygon zkEVM zkROM代码解析(2)
- Polygon zkEVM zkROM代码解析(3)
- Polygon zkEVM公式梳理
- Polygon zkEVM中的Merkle tree
- Polygon zkEVM中Goldilocks域元素circom约束
- Polygon zkEVM Merkle tree的circom约束
- Polygon zkEVM FFT和多项式evaluate计算的circom约束
- Polygon zkEVM R1CS与Plonk电路转换
- Polygon zkEVM中的子约束系统
- Polygon zkEVM交易解析
- Polygon zkEVM 审计及递归证明
- Polygon zkEVM发布公开测试网2.0
- Polygon zkEVM测试集——创建合约交易
- Polygon zkEVM中的Recursive STARKs
- Polygon zkEVM的gas定价
- Polygon zkEVM zkProver基本设计原则 以及 Storage状态机
- Polygon zkEVM bridge技术文档
- Polygon zkEVM Trustless L2 State Management 技术文档
- Polygon zkEVM中的自定义errors
- Polygon zkEVM RPC服务
- Polygon zkEVM Prover的 RPC功能
- Polygon zkEVM PIL技术文档
- Polygon zkEVM递归证明技术文档(1)【主要描述了相关工具 和 证明的组合、递归以及聚合】
- Polygon zkEVM递归证明技术文档(2)—— Polygon zkEVM架构设计
- Polygon zkEVM递归证明技术文档(3)——代码编译及运行
- Polygon zkEVM递归证明技术文档(4)—— C12 PIL Description
- Polygon zkEVM递归证明技术文档(5)——附录:借助SNARKjs和PIL-STARK实现proof composition
- eSTARK:Polygon zkEVM的扩展STARK协议——支持lookup、permutation、copy等arguments(1)
- eSTARK:Polygon zkEVM的扩展STARK协议——支持lookup、permutation、copy等arguments(2)
- eSTARK:Polygon zkEVM的扩展STARK协议——支持lookup、permutation、copy等arguments(3)
- Polygon zkEVM的Dragon Fruit和Inca Berry升级
- Polygon zkEVM协议治理、升级及其流程
- Polygon zkEVM 节点软件release日志
- Polygon zkEVM bridge服务 release日志
- Polygon zkEVM DataStreamer
- Polygon zkEVM Goldilocks域各项运算性能
- Polygon zkEVM Hexens审计报告解读
- Polygon zkEVM Spearbit审计报告解读(2022年12月版本)