交易证明验证每个交易签名,确认由根标识的 merkle patricia trietransactionsRoot包含所有交易(仅此而已),并使 EVM 证明可以通过交易表轻松访问交易数据。
一. 事务编码
存在不同类型的事务编码。在 zkEVM 的第一次迭代中,我们将仅支持 EIP-155 的传统交易。我们计划稍后添加对非传统 (EIP-2718) 事务的支持。
1.1 传统类型
rlp([nonce, gasPrice, gas, to, value, data, sig_v, r, s])
在 EIP-155 之前:
要签名的散列数据:(nonce, gasprice, gas, to, value, data)withsig_v = {0,1} + 27
EIP-155 之后:
要签名的散列数据:(nonce, gasprice, gas, to, value, data, chain_id, 0, 0)withsig_v = {0,1} + CHAIN_ID * 2 + 35
其中{0,1}是secp256k1签名过程中公钥对应的曲线点y值的奇偶性。
1.2. 非传统 (EIP-2718) 类型
来自https://eips.ethereum.org/EIPS/eip-1559和https://eips.ethereum.org/EIPS/eip-2718
0x02 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas, destination, amount, data, access_list, signature_y_parity, signature_r, signature_s])
要签名的散列数据:TODO
二.电路行为
使用以下公共输入:chain_id, transactionsRoot
.
对于定义为参数(nonce, gas_price, gas, to, value, data, sig_v, sig_r, sig_s)并用作公共输入(nonce, gas_price, gas, to, value, data, from)
的每笔交易,电路会验证以下内容:
txSignData: bytes = rlp([nonce, gas_price, gas, to, value, data, chain_id, 0, 0])
txSignHash: word = keccak(txSignData)
sig_parity: {0, 1} = sig_v - 35 - chain_id / 2
ecdsa_recover(txSignHash, sig_parity, sig_r, sig_s) = pubKey
或等效地verify(txSignHash, sig_r, sig_s, pubKey) = true
-
fromAddress = keccak(pubKey)[-20:]
-
交易参数的 rlp 编码(步骤 1)将使用自定义 rlp 编码小工具完成,与 MPT 电路使用的 rlp 编码隔离。
- 签名消息 keccak 哈希验证(步骤 2)将在 keccak 电路中完成;tx 电路将对 keccak 表进行一次查找(使用 RLC 将 rlp 编码事务累积为单个值)。
- 从消息和签名中恢复公钥(步骤 3)将在 ECDSA 电路中完成;tx 电路将对 ECDSA 表进行查找。
- 公钥 keccak 哈希验证(步骤 5)将在 keccak 电路中完成;tx 电路将查找 keccak 表。
电路根据这些信息构建 TxTable:
Where:
- Gas = gas
- GasTipCap = 0
- GasFeeCap = 0
- CallerAddress = fromAddress
- CalleeAddress = to
- IsCreate = 1 if to is None else 0
- CallDataLength = len(data)
- CallData[$ByteIndex] = data[$ByteIndex]
0 TxID | 1 Tag | 2 Index | 3 value |
---|---|---|---|
TxContextFieldTag | |||
$TxID | Nonce | 0 | $value: raw |
$TxID | Gas | 0 | $value: raw |
$TxID | GasPrice | 0 | $value: rlc |
$TxID | GasTipCap | 0 | $value: 0 |
$TxID | GasFeeCap | 0 | $value: 0 |
$TxID | CallerAddress | 0 | $value: raw |
$TxID | CalleeAddress | 0 | $value: raw |
$TxID | IsCreate | 0 | $value: raw |
$TxID | Value | 0 | $value: rlc |
$TxID | CallDataLength | 0 | $value: raw |
$TxID | CallData | $ByteIndex | $value: raw |
表格的形状有一些限制,例如:
- 对于每个 Tx,每个标签必须只出现一次,除了CallData可以重复但只能ByteIndex从 0 开始的顺序。
- TxID必须从 1 开始,并为每个事务按顺序增加
- 当 Tag 值是 CallData 时, 必须在 0 到 255 之间
由于交易表是根据公共输入构建的,因此验证者(例如 zkRollup 中的 L1 智能合约)需要验证表的所有行是否都使用交易数据正确构建。由于表格结构是在电路外部验证的,因此无需在电路内部验证相同的约束。
1. 交易树
对于每个事务,tx 电路还必须准备用于构建事务 trie 的键和值。这些键和值用于查找 MPT 表,以验证用对应于事务的键值构建的树是否具有根值transactionsRoot。
通过查找 MPT 表,我们证明当我们从一个空的 MPT 开始,并执行与每个事务对应的键值插入链时,我们会到达一个具有根值的 Trie transactionsRoot。
每个 MPT 更新都使用以下参数:
- Key = rlp(tx_index)
- Value = rlp([nonce, gas_price, gas, to, value, data, sig_v, r, s])
- ValuePrev = 0
注意:交易 trie 更新条目的 MPT 查找表的形状尚未定义。
注意:用于 Transaction Trie 的 MPT 证明不需要删除支持。
go-ethereum参考:
三. 电路行为快捷方式
对于交易电路的第一个实现,我们将应用一些快捷方式作为简化。对于每笔交易,以下值将作为有效的公共输入提供(并且不会在电路中验证):
- txSignHash(这意味着电路不需要计算txSignData)
特别是对于 zkRollup,我们将计算 L1 合约中的txSignData和 txSignHash作为验证过程的一部分。
我们还将跳过对正确构建事务树的验证。目前,MPT 电路正在为帐户存储尝试和状态尝试更新的需要而指定和实施,这意味着与 Tx 电路相比在使用上存在一些差异:
- 需要在 MPT 中为这些 Tx 电路特定查找定义查找表(与 State Trie 和帐户存储查找分开)。在这里,我们从头开始构建一个 trie 并获得它的根。
- 虽然 State Trie 和 Account Storage Trie 插入使用大小有界的叶子,但对于 Transactions Trie,叶子是 Transaction 的 RLP,其中包含可变大小的 calldata。这意味着 MPT 电路需要适应可变长度的叶值。
一旦 MPT 的第一次迭代(满足状态电路查找需求的迭代)完成,我们将着手处理。
对于此实现,Tx 表扩展为如下所示:
0 TxID | 1 Tag | 2 Index | 3 value |
---|---|---|---|
TxContextFieldTag | |||
$TxID | Nonce | 0 | $value: raw |
$TxID | Gas | 0 | $value: raw |
$TxID | GasPrice | 0 | $value: rlc |
$TxID | GasTipCap | 0 | $value: 0 |
$TxID | GasFeeCap | 0 | $value: 0 |
$TxID | CallerAddress | 0 | $value: raw |
$TxID | CalleeAddress | 0 | $value: raw |
$TxID | IsCreate | 0 | $value: raw |
$TxID | Value | 0 | $value: rlc |
$TxID | CallDataLength | 0 | $value: raw |
$TxID | TxSignHash | $value: rlc | |
$TxID | CallData | $ByteIndex | $value: raw |
对于 ECDSA 签名验证,我们不会对 ECDSA 表进行查找,而是对每笔交易使用 ECDSA 验证小工具。由于可变长度的 CallData,每个事务使用可变数量的行,我们将重新排列表,以便每个事务以这样的固定偏移量开始(通过在末尾移动所有 CallData 行):
对于每笔交易:
0 TxID | 1 Tag | 2 Index | 3 value |
---|---|---|---|
TxContextFieldTag | |||
$TxID | Nonce | 0 | $value: raw |
$TxID | Gas | 0 | $value: raw |
$TxID | GasPrice | 0 | $value: rlc |
$TxID | GasTipCap | 0 | $value: 0 |
$TxID | GasFeeCap | 0 | $value: 0 |
$TxID | CallerAddress | 0 | $value: raw |
$TxID | CalleeAddress | 0 | $value: raw |
$TxID | IsCreate | 0 | $value: raw |
$TxID | Value | 0 | $value: rlc |
$TxID | CallDataLength | 0 | $value: raw |
$TxID | TxSignHash | $value: rlc |
这种结构是重复的MAX_TXS。当事务数小于MAX_TXS时,未使用事务对应的行将使用 continue 具有连续TxIDs 但将所有值设置为 0。特别是,签名验证在 时禁用CallerAddress == 0。
然后表格继续:对于每个事务: | 0 TxID | 1 Tag | 2 Index | 3 value | | --- | --- | --- | --- | | | TxContextFieldTag | | | | $TxID | CallData | $ByteIndex | $value: raw |
这些行是重复的MAX_CALLDATA_BYTES时间。当所有事务调用数据的总字节数小于MAX_CALLDATA_BYTES时,未使用事务对应的行将使用CallData带有 的标记 TxID = 0。
以这种方式组织表允许将每个事务的值CallerAddress和 TxSignHash每个事务的值放在固定的偏移量处。这使得可以将这些值的复制约束添加到单元格到另一个执行签名验证和哈希查找的区域。
1. 图表
2. 签名验证和随机线性组合
我们不是通过查找 ECDSA 表来验证 ECDSA 签名,而是halo2wrong直接使用 ECDSA 芯片。
我们还扩展了MainGateECDSA 芯片的底层组件,以构建这些数据的随机线性组合:
- PublicKey - 从签名验证中分解,然后用于查找 keccak 表作为输入
- PublicKeyHash - 用于查找 keccak 表作为输出
- TxSignHash - 从签名验证中分解,然后用于复制到 tx 表
扩展的随机线性组合门在第二阶段需要一个额外的列来包含 RLC 的运行总和,它逐块累积输入,最终构建整个输入的 RLC。
3. 变更摘要
- 跳过对事务树的正确构造的验证(无 MPT 表查找)
- 跳过对交易的 RLC 的验证以获取要签名的消息
- 跳过对交易RLC哈希的验证,获取要签名的消息哈希
- 将 TxSignHash Tag 添加到 tx 表(从 L1 智能合约中计算的公共输入设置)
- 与其通过查找 ECDSA 表来验证 ECDSA 签名,不如直接使用 ECDSA 芯片。
- 这需要重新排列 tx 表,以便对于每个事务,我们可以在固定偏移处找到其 CallerAddress 值和 TxSignHash 值,以对签名验证小工具进行复制约束。为此,我们将所有事务的 CallData 标记移到表的末尾,并在中间(固定偏移区域和动态偏移区域之间)和末尾定义填充。
四. 代码
https://github.com/scroll-tech/zkevm-specs/blob/master/src/zkevm_specs/tx.py