Taiko 如何验证 L2 区块

  • taikoxyz
  • 发布于 2025-04-02 11:58
  • 阅读 118

本文档详细介绍了 Taiko 如何通过协议、客户端和电路协同工作来验证 L2 区块。文章深入探讨了交易列表的有效性证明、锚定交易(Anchor Transactions)的验证、块元数据(Block Metadata)的一致性、全局变量的正确性验证、块头的完整性验证以及跨链信号存储的验证,确保 L2 区块的有效性和安全性。

Taiko 如何证明 L2 区块

本文档详细说明了 Taiko 协议、客户端和电路如何协同工作以证明 L2 区块。

数据

交易列表

Layer 2 (L2) 区块的交易列表 txList 最终将成为 Layer 1 (L1) 共识层 (CL) 中的一个 blob 的一部分 - 目前它位于 calldata 中。 它应该是一个 RLP 编码的 L2 交易列表,只有其长度和 commitment 对 L1 执行层 (EL) 可用。长度在提议区块时进行检查,但我们需要使用 ZKP 来证明 txList 是否有效。

一个有效的 txList (直到 issue #13724 被启用和实现):

  • 其字节大小小于协议常量 blockMaxTxListBytes (也在合约中强制执行);
  • 可以 RLP 解码为交易列表,且没有尾随空格;
  • 包含的交易 (有效和无效) 不超过协议常量 blockMaxTransactions;
  • 所有有效交易的总 gas limit 不超过协议常量 blockMaxGasLimit;

ZKP 必须证明 txList 是否有效。对于无效的 txList,相应的 L2 区块将只有一个锚定交易。

对于有效的 txList,ZKP 还必须证明其中包含的每笔交易是否有效。所有有效交易将成为实际 L2 区块的一部分,其中锚定交易作为第一笔交易。

一笔有效的交易 (在以太坊黄皮书中定义):

  • 具有有效的交易签名。
  • 具有有效的交易 nonce (等同于发送方账户当前的 nonce)。
  • 发送方账户上没有部署合约代码 (参见 Feist 等人的EIP-3607 [2021])。
  • gas limit 不小于交易使用的内在 gas g0
  • 发送方账户余额至少包含前期支付所需的成本 v0
  • 交易的 gas limit 小于或等于区块中剩余的 gas 量 (区块 gas limit 为协议常量 blockMaxGasLimit)。
  • 交易的 base fee 大于或等于区块的 base fee。
切片和一致性

请注意,txList 的数据不能假定为可直接为 L1 合约访问,因此 ZKP 应进一步证明所选的 txList 是给定 blob 数据的一个切片。当 EIP-4844 合并到以太坊时,blobHash 操作码将可用。

锚定交易

每个 L2 区块都只有一个锚定函数调用作为其第一笔交易。

ZKP 必须证明 TaikoL2.anchor(...) 是区块中的第一笔交易,具有正确的输入参数和 gas limit,由所谓的 golden-touch 地址签名,并且该交易已成功执行。

  • 锚定交易的 to 地址必须是已注册的 taiko_l2 地址,该地址被哈希到 ZKP instance 中。并且 tx.origin 必须是golden touch 地址。
  • 锚定交易的 ABI 必须是:
function anchor(
 bytes32 l1Hash,
 bytes32 l1SignalRoot,
 uint64 l1Height,
 uint64 parentGasUsed
) external;
  • 一个电路将验证 l1Hashl1SignalRootl1SignalServiceAddress 之间的一致性。
  • l1SignalServiceAddressl2SignalServiceAddressparentGasUsed 直接被哈希到 ZKP 的 instance 中。
  • l1Heightl1Hash 都是区块元数据 (meta.l1Heightmeta.l1Hash) 的一部分,metaHash 用于计算 ZKP instance。
  • l1SignalRoot 是证据的一部分,也用于计算 ZKP instance。
  • 交易的状态码为 1 (成功)。
  • 交易的 tx.originmsg.sender 必须是 LibAnchorSignature.K_GOLDEN_TOUCH_ADDRESS
  • 交易的签名必须与 LibAnchorSignature.signTransaction(...) 相同。
  • 交易费用必须为 0。

请注意,锚定交易会发出一个 Anchored 事件,这可能有助于 ZKP 验证区块变量。见下文。

锚定签名

ZKP 还应检查锚定交易的签名:

  • 签名者必须是 TaikoL2.GOLDEN_TOUCH_ADDRESS
  • 如果计算出的 r 不是 0,则签名必须使用 1 作为 k 值,否则,k 必须是 2。请参阅 LibL2Signer.sol

区块元数据

这个 struct 代表了一个提议的 L2 区块。这个数据将被哈希,并成为电路公共输入的一部分。

struct BlockMetadata {
 uint64 id;
 uint64 timestamp;
 uint64 l1Height;
 bytes32 l1Hash;
 bytes32 mixHash;
 bytes32 blobHash;
 uint24 txListByteStart;
 uint24 txListByteEnd;
 uint32 gasLimit;
 address proposer;
 address treasury;
 TaikoData.EthDeposit[] depositsProcessed;
}
  • id: 表示 L2 中的区块高度。
  • timestamp: L2 中的区块时间戳。
  • l1Height: L1 中的实际区块高度。
  • l1Hash: L1 中的实际区块哈希。
  • mixHash: 用于容纳多个 L2 区块的加盐随机数,这些 L2 区块可以放入一个 L1 区块中。
  • blobHash: L2 中交易列表的哈希。
  • txListByteStart: L2 中交易列表的字节起始位置。
  • txListByteEnd: L2 中交易列表的字节结束位置。
  • gasLimit: L2 区块的 Gas limit。
  • proposer: L2 中提议者的地址。
  • treasury: L2 中 base fee 的去向地址。
  • depositsProcessed: 构成 depositRoot 的已启动的 L1->L2 以太坊存款。

全局变量

以下 区块级别变量 可供 EVM 访问,但它们的值不是 MPT 的一部分,因此我们需要另一种方法来验证其正确性。

  • blockhash(uint blockNumber) returns (bytes32): 给定区块的哈希,当 blocknumber 是 256 个最近的区块之一时; 否则返回零
  • block.basefee (uint): 当前区块的 base fee (EIP-3198修改后的 EIP-1559)
  • block.chainid (uint): 当前链 ID
  • block.coinbase (address payable): 当前区块矿工的地址
  • block.prevrandao (uint): block.prevrandao 的别名 (EIP-4399)
  • block.gaslimit (uint): 当前区块 Gas limit
  • block.number (uint): 当前区块号
  • block.prevrandao (uint): 由信标链提供的随机数
  • block.timestamp (uint): 自 Unix epoch 以来,当前区块时间戳 (以秒为单位)

我们需要验证当在 EVM 中访问这些变量时,它们的值与当前世界状态、区块的元数据和实际 L2 区块的区块头是否一致:

  • blockhash: EVM 允许访问最近 256 个区块的哈希值。所有这些哈希值都可以在 plonk 查找表中找到。ZKP 必须证明查找表与 L2 的历史值一致。
  • blockhash(block.number - 1),具有与区块头中相同的值,并且也是 L2 上父区块哈希的相同值。
  • 其他 255 个哈希值,blockhash(block.number - 256)blockhash(block.number - 2) 在锚定交易中进行检查,以简化电路。因此,只要锚定交易经过 ZK 证明,这些 255 个祖先哈希值就会被间接证明。
  • block.basefee: 经验证是在锚定交易中的正确值。
  • block.chainid: 此字段也由锚定交易检查,因此不需要额外的 ZKP 电路。
  • block.coinbase: ZKP 必须验证该值必须与 meta.proposer 相同。同样,元数据哈希是 ZK instance 的一部分。
  • block.prevrandao: 现在与 block.prevrandao 相同,因此我们只检查 block.prevrandao
  • block.gaslimit: ZKP 必须验证此值必须等于 meta.gasLimit
  • block.number: 必须对照区块头和 meta.id 检查此值。
  • block.prevrandao: 必须对照 L2 区块头中的 mixHash 字段和 meta.mixHash 检查此值。
  • block.timestamp: 必须对照 L2 区块头中的 timestamp 字段和 meta.proposedAt 检查此值。

区块头

并非所有区块头数据都可在 L1 合约中使用; 因此,ZKP 必须验证区块头的完整性,并确保区块头哈希到与 evidence.blockHash 相同的值,evidence.blockHash 是 ZK instance 输入的一部分。

struct BlockHeader {
 bytes32 parentHash;
 bytes32 ommersHash;
 address proposer;
 bytes32 stateRoot;
 bytes32 transactionsRoot;
 bytes32 receiptsRoot;
 bytes32[8] logsBloom;
 uint256 difficulty;
 uint128 height;
 uint64 gasLimit;
 uint64 gasUsed;
 uint64 timestamp;
 bytes extraData;
 bytes32 mixHash;
 uint64 nonce;
 uint256 baseFeePerGas;
 bytes32 withdrawalsRoot;
}

此外,ZKP 还必须证明以下几点:

  • parentHash 必须与 evidence.parentHash 相同。
  • ommersHash 必须是 [] 的 keccak256,或 0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347
  • proposer 必须是 meta.proposer (如上所述,重复)。
  • logsBloom 必须是一个包含所有零的 bytes32[8]
  • difficulty == 0。
  • height 必须是 meta.id
  • gasLimit == meta.gasLimit
  • gasUsed 必须是区块中所有有效交易使用的所有 gas 的总和。
  • timestamp == meta.proposedAt
  • extraData == ""。
  • mixHash == meta.mixHash
  • nonce == 0。
  • baseFeePerGas == block.basefee

请注意,上面的一些标头字段检查是全局变量部分中完成的检查的重复项。

信号存储

ZKP 还需要证明跨链信号服务的存储根具有正确的值。

  • 对于 L2 信号服务: 信号服务的 L1 存储根是锚定交易中的第二个参数。ZKP 应通过使用 MPT 证明,根据 meta.l1Hash 中存储的状态根,针对 l1SignalServiceAddress 帐户,来验证 L1 信号服务地址的存储根是否具有给定的值。此 MPT 证明必须由 L2 客户端从 L1 节点查询。

  • 对于 L1 信号服务: L2 存储根验证将在电路中使用 MPT 证明,根据 l2SignalServiceAddress 帐户的后区块状态根来完成。

EIP-1559

在 Taiko L2 协议中,basefee 不是被烧毁,而是转移到指定的 treasury 地址。为了确保此过程的完整性,ZKP 需要验证 Taiko L1 合约指定的 treasury 地址是否确实是预期的接收者。

LibProving 验证

公共输入参数的实际值必须与用于哈希 ZKP instance 的值一致 (参见 LibProving)。

电路中的数据交叉验证

为了帮助人们可视化上述所有元素。 这是一个图表:

%%{init: {'theme': 'base', 'themeVariables': { 'fontSize': '13px'}}}%%

graph LR
classDef default stroke-width:4px,stroke:#EA27C2,fill:#EA27C2,color:#FFF;
classDef transition stroke-width:4px,stroke:#FF715B,fill:#FF715B,color:#FFF;
classDef otherCircuits stroke-width:4px,stroke:#6ECEB0,fill:#6ECEB0,color:#FFF;
classDef constant stroke-width:4px,stroke:#323745,fill:#323745,color:#FFF;
classDef group stroke-width:2px,stroke:#EA27C2,fill:#FFD2F630;

m_id --- h_height --- v_block_number;
m_h1_height --- a_l1_height;
m_gas_limit --- h_gas_limit --- v_block_gaslimit;
m_timestamp --- h_timestamp --- v_block_timestamp;
m_txlist_first ---|<=| m_txlist_last --- |<= len| tx_list;
m_h1_hash --- a_l1_hash;
m_mix_hash --- h_mix_hash --- v_block_prevrando;
tx_list -.->|keccak| m_txlist_hash;
m_proposer --- h_proposer;
h_parent_hash --- v_blockhash_1 & e_parent_hash;

l2_treasury -.-> m_treasury;

v_block_chainid -.-> dot1;
v_blockhash_others -.-> dot1 -.->|keccak| s_public_input_hash;

v_block_basefee -.-> h_basefee;

v_block_gaslimit -.-> dot2;
v_block_timestamp -.-> dot2;
s_parent_timestamp -.-> dot2;
s_gas_excess -.-> dot2 ---|calcBasefee| v_block_basefee;

m_processed_deposits -.->|keccak| dot4;

b_signal_root --- a_l1_signal_root;
h_gas_used --- e_gas_used;

BlockMetadata -.-> dot4((" ")) --- |keccak| e_meta_hash -.-> dot3((" ")) -.->|keccak| zk_instance;
e_parent_hash & e_block_hash & e_signal_root & e_graffiti & e_prover & e_parent_gas_used & e_gas_used -.-> dot3;
b_l1_signal_service_addr -.-> dot3;
b_l2_signal_service_addr -.-> dot3;
b_l1_taiko_addr -.-> dot3;

e_signal_root --- s_signal_root
e_parent_gas_used --- a_parent_gas_used

h_gas_limit ---|>=| h_gas_used

BlockHeader -.->|abiencode & keccak| dot5((" ")) o--- e_block_hash

BlockEvidence ~~~ L1Storage;

subgraph BlockMetadata[Block Metadata]
m_id(id)
m_gas_limit(gasLimit)
m_timestamp(timestamp)
m_h1_height(h1Height)
m_h1_hash(h1Hash)
m_mix_hash(mixHash)
m_txlist_hash(blobHash)
m_txlist_first(txListByteStart)
m_txlist_last(txListByteEnd)
m_treasury(treasury)
m_proposer(proposer)
l2_treasury("L2 basefee goes to treasury"):::constant;
tx_list("txList\n(blob or calldata)"):::constant;
m_processed_deposits("ethDepositsProcessed"):::constant
end

BlockMetadata:::group

subgraph BlockHeader[Block Header]
h_height(height)
h_gas_limit(gasLimit)
h_gas_used(gasUsed)
h_timestamp(timestamp)
h_mix_hash(mixHash)
h_proposer(proposer)
h_parent_hash(parentHash)
h_ommers_hash("ommersHash = keccak([])")
h_state_root(stateRoot)
h_transactions_root(transactionsRoot)
h_receipts_root(receiptsRoot)
h_logs_bloom("logsBloom = []")
h_difficulty("difficulty = 0")
h_extra_data("extraData = ''")
h_nonce("nonce = 0")
h_basefee(basefee)
end

BlockHeader:::group

subgraph GlobalVariables[Global Variables]
v_block_number(block.number)
v_block_gaslimit(block.gaslimit)
v_block_timestamp(block.timestamp)
v_block_prevrando(block.prevrando)
v_blockhash_1("blockhash(1)")
v_blockhash_others("blockhash(2..256)")
v_block_chainid("block.chainid")
v_block_basefee("block.basefee")
dot1((" "))
dot2((" "))
end

GlobalVariables:::group

subgraph Anchor [Anchor Tx]
a_l1_height(l1Height)
a_l1_hash(l1Hash)
a_parent_gas_used(parentGasUsed)
a_l1_signal_root(l1SignalRoot)
end

Anchor:::group

subgraph L1Storage[L1 Storage]
b_l1_taiko_addr[/taikoL1Address/]
b_l1_signal_service_addr[/L1 signalServiceAddress/]
b_l2_signal_service_addr[/L2 signalServiceAddress/]
b_signal_root[/stateRoot/]
end

L1Storage:::group

subgraph L2Storage[L2 Storage]
s_public_input_hash[/publicInputHash/]
s_parent_timestamp[/parentTimestamp/]
s_gas_excess[/gasExcess/]
s_signal_root[/stateRoot/]
end

L2Storage:::group

subgraph BlockEvidence
e_meta_hash(metaHash)
e_parent_hash(parentHash):::transition
e_block_hash(blockHash)
e_signal_root(stateRoot)
e_graffiti(graffiti)
e_prover(prover)
e_parent_gas_used(parentGasUsed):::transition
e_gas_used(gasUsed)
end

BlockEvidence:::group

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

0 条评论

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