ZKStack跨链架构内幕 — 第二部分:网关结算和递归证明

本文是ZKStack跨链架构深度解析的第二部分,深入探讨了ZKStack协议升级的技术结构,该升级引入了一个ZKChain生态系统,通过在另一个ZKChain(而不是L1)上结算,从而实现简便的新rollup ZKChain创建、内置跨链功能和更低的结算成本。

引言

欢迎来到我们深入研究 ZKStack 跨链架构的第二部分。这个分为两个部分的系列文章探讨了即将到来的 ZKStack 协议升级的技术结构,该升级引入了一个 ZKChain 生态系统,该生态系统允许轻松创建新的 rollup ZKChain,内置跨链功能,并通过在另一个 ZKChain 而不是 L1 上结算来降低结算成本。 在第一部分中,我们描述了三个关键的 Merkle 树实现,它们构成了 ZKChain 生态系统中安全跨层通信的支柱:L2ToL1LogsTreeChainTreeSharedTree。我们探讨了这些树的结构、它们的叶子和根,以及它们如何共同实现为 ZKStack 的跨链能力提供支持的分层结算架构。

第二部分将这种理论基础付诸实践,通过展示这种树层次结构的最重要用例:即一个 ZKChain 如何构建其跨链代币转账的递归证明,该 ZKChain 在一个特殊的白名单 ZKChain 上结算,该 ZKChain 在 ZK Stack 中命名为 Gateway(而不是直接在 L1 上结算)。我们将演练从代币存款到提款完成的完整过程,演示 Gateway 如何充当中间结算层,同时保持最终可以追溯到 L1 的安全保证。

通过具体的日志包含证明示例,我们将展示 第一部分 中描述的树结构如何实现无缝的跨链通信,无论是在 L2 到 L1 还是 L2 到 L2 层之间,同时保持向后兼容性并降低结算成本。

一般来说,任何允许其他 ZKChain 在其上结算的 ZKChain 都被称为结算层。在撰写本文时,除了 L1 之外,Gateway 是唯一可用的结算层。

本系列文章描述了截至撰写本文时的 ZKChain 生态系统的当前状态及其内部程序。随着生态系统的发展,一些描述的功能可能会在未来进行更新。


在 Gateway 上结算意味着什么?

当创建一个 L2 ZKChain 时,会在 L1 上部署一组智能合约,统称为 ZKChain 的 DiamondProxy。这组智能合约执行关于 ZKChain 状态转换的关键操作(最值得注意的是,批量提交和验证),并存储关于该链的任何状态和配置。默认情况下,L1 托管为所有新创建的 ZKChain 托管 DiamondProxy,并且是任何新创建的 L2 ZKChain 的结算层。

在稍后的任何时候,L2 ZKChain 都有可能将其结算层“迁移”到 Gateway。在这种情况下,将为 ZKChain 在 Gateway 上部署一个新的 DiamondProxy,并使用来自 L1 的状态对其进行初始化,并且 ZKChain 现在与 Gateway 就批处理发布进行交互。请注意,反向程序,即基本上将 ZKChain 的结算层从 Gateway 迁移到 L1 也是可能的。

即使 Gateway 是某些 ZKChain 的结算层,L1 以太坊网络也是唯一能保证所有 ZKChain 安全的链。本质上,Gateway 本身在 L1 上结算,并且它的每个批次还包含对其上结算的所有 ZKChain 的批次的承诺。因此,所有 ZKChain 最终都会在某个时候在 L1 上结算。不同之处在于,某些链在 L1 上发布其所有数据,而其他链通过 Gateway 的批次将数据的聚合承诺发布到 L1。


通用 ZKChain 结算 - FullRootHash

ZKChain 的交易以批次形式发布到其结算层。特别是,在结算层上结算批次意味着在结算层的 DiamondProxy提交证明执行批次,无论是 L1 还是 Gateway

为了提交批次,每个 ZKChain 都使用 fullRootHash,它本身不是一个 merkle 根,而是两个独立的 merkle 根的哈希值:

  • ZKChain 的 L2ToL1LogsTreelocalLogsRootHash:表示批次中包含的已完成或已启动的跨链交易;
  • ZKChain 的 SharedTreeaggregatedRoot:其 取决于 ZKChain 是否为结算层:

    • 常规 ZKChain:一个默认的空树,用其自身 chainId 的空叶子初始化,在所有批次中保持相同
    • Gateway:一个根哈希,它提交给所有在 Gateway 上结算的 ZKChain 的 chainRoots

img1-3

这种通用结算方案不仅保留了三个 Merkle 树的结构,而且保持了相同的 Rollup-To-SettlementLayer 通信风格,无论涉及的层如何,即 L2 到 L2 还是 L2 到 L1 的承诺,从而保持了向后兼容性。


日志包含证明

我们在本部分的主要目的是揭示 L1 上的 L2 日志包含的证明。具体来说,考虑一个在 Gateway 上结算的 ZKChain 的 L2 到 L1 的日志,该日志作为 L1 上结算的批次中 ZKChain 的 L2ToL1LogsTree 中的叶子。由于此日志已提交到 Gateway 的批次,因此用户应该能够构建一个包含证明来证明其存在。例如,该日志可能是代币转账的日志,用户应证明其存在,以便在 L1 上解锁 这笔 资金。

请记住,跨链代币转账遵循以下一般原则:

  • lock-and-mint:从代币的原始链桥接出去,也称为存款
  • burn-and-unlock:桥接回代币的原始链,也称为取款

即使转移日志始终通过 fullRootHash 提交,包含证明的格式也取决于生成日志的 ZKChain 是否在 L2 或 L1 上结算。你能明白为什么吗?

在下文中,我们将遵循 L1 代币取款的用例来说明两种情况下的包含证明结构。为了完整起见,并为希望更深入地研究代码库的读者提供一些额外的指导,我们首先描述在提取资金之前的两个初步步骤:i) 将资金从 L1 桥接到 ZKChain 和 ii) 在 ZKChain 上启动取款。

预备知识

将资金存入 ZKChain

想象一下,用户将 ERC20 代币从 L1 转移到 ZKChain。这可以通过调用 Bridgehub 合约的 requestL2TransactionTwoBridges 函数来实现。此函数直接从 ZKChain 的 DiamondProxy facet 发送 rollup 优先级交易请求。

根据目的地是在 L1 还是 Gateway 上结算的 ZKChain,将遵循不同的流程:

它们有不同的路线,因为目前,ZKChain 仅接受来自其结算层的跨链交互。

从 ZKChain 提取资金

假设用户在一段时间后想将其资金提取回 L1。用户通过调用 ZKChain 上 L2AssetRouter 合约的 withdraw 函数来启动提取,该函数将首先烧毁 ZKChain 上的资金,然后简单地通过 L1Messenger 合约向 L1 发送消息

请注意,此消息将成为一个 L2ToL1Log 叶子,该叶子构成了提交给 L1 或 Gateway 的批次的 fullRootHash


所需的证明:完成 L1 上的取款

为了完成 L1 上的取款,用户需要使用以下信息调用 L1AssetRouter 合约上的 finalizeWithdrawal 函数:

  • chainId:ZKChain 的 chainId,在该链上启动了取款,
  • l2BatchNumberl2TxNumberInBatch:批次号和批次内的交易号,用于验证取款,
  • l2MessageIndexL2ToL1LogsTree 的叶子索引,稍后将结合 merkleProof 使用,
  • messagebytes 消息本身,用于形成 L2ToL1LogsTree 中包含 相应日志,
  • merkleProof:包含证明,用于验证来自 message L2Log 位于来自 chainId ZKChain 批次号 l2BatchNumber L2ToL1LogsTree 索引 l2MessageIndex 处。

merkleProof 的结构取决于 ZKChain 是在 L1 上还是在 Gateway 上结算。让我们分别分析这两种情况。


L1 结算 ZKChain 的 L2 日志包含证明

L2 链在 L1 上结算。因此,为了验证 L2 链代币取款,需要证明 L2 日志包含在 L1 上 DiamondProxy 上。

当 L2 ZKChain 在 L1 上结算批次时,它会将其日志根,即 fullRootHash 存储在该链的相应 DiamondProxy 中。

img2-3

回想一下,fullRootHash 结合了 L2ToL1LogsTreeSharedTree 的根,即 fullRootHash = hash(localLogsRootHash, aggregatedRoot)

因此,merkleProof 应包含一个路径,该路径从索引 l2MessageIndex 处的 L2ToL1Log 一直哈希到 localLogsRootHash (L2ToL1LogsTree 的根),以及一个额外的元素 aggregatedRoot,以到达提交的 fullRootHash。这使 merkleProof 的路径长度等于 L2ToL1LogsTree 的高度 + 1。


Gateway 结算 ZKChain 的 L3 日志包含证明

现在让我们考虑一条在 Gateway 上结算的 L2 链,而 Gateway 又在 L1 上结算。要验证此链的代币取款,需要在 L1 上 ZKChain DiamondProxy 上证明 L2 日志包含,但是,由于相应的 aggregatedRoot 是用 Gateway 的批次提交的,因此需要通过 L1 上 Gateway DiamondProxy

img3-3

回想一下,在这种情况下,用户的取款日志(即 L2 日志)位于 L1 提交 fullRootHash 的右子树中,如下所示:

  • L2 日志是 ZKChain L2ToL1LogsTree L2ToL1Log 叶子。
  • ZKChain L2ToL1LogsTree LocalLogsRootHash 与一个空的 SharedTree(即默认的 aggregatedRoot)一起哈希,以计算特定批次 ZKChain fullRootHash。此根在 Gateway 上结算。
  • Gateway 将结算 fullRootHash 附加到 ZKChain chainTree,从而导致更新 chainRoot
  • Gateway 更新 SharedTreechainRoot 叶子,从而导致更新 aggregatedRoot
  • Gateway 将其自身 LocalLogsRootHashaggregatedRoot 进行哈希处理,以检索最终 fullRootHash,该哈希与批次一起提交到 L1。

因此,在这种情况下,merkleProof 应包含一个路径,该路径从 ZKChain L2ToL1LogsTree 日志叶子开始,一直到 SharedTree aggregatedRoot

证明验证包括以下 3 个步骤:

  • 步骤 1:重建 ZKChain fullRootHash。与先前描述 L2 日志包含类似,使用 LocalLogsTree merkle 路径从索引 l2MessageIndex L2ToL1Log 叶子开始重建 LocalLogsRootHash,最后与链 默认 aggregatedRoot 进行哈希处理。
  • 步骤 2:使用 ChainTree merkle 路径重建 ZKChain ChainTree chainRoot,该路径从步骤 1 中计算 fullRootHash 叶子开始。
  • 步骤 3:通过使用从步骤 2 中计算 ChainRoot 叶子开始 SharedTree merkle 路径重建 aggregatedRoot

通过聚合 merkleProof 中上述 3 个步骤 merkle 路径,我们得到了一条通向 aggregatedRoot 路径。为了形成 L1 上提交 最终 Gateway fullRootHash,还有一个技巧。

请注意,fullRootHash = hash(LocalLogsRootHash, aggregatedRoot),其中 aggregatedRoot 在右侧哈希,而 Gateway LocalLogsRootHash 在左侧。为了将 Gateway LocalLogsRootHash 附加到最终聚合 mekleProof 上最终哈希 左侧,需要专门设计步骤 3 中 chainRoot 索引。具体而言,chainRoot index 变为 index + 2^N,其中 $N$ 是 SharedTree 高度。这确保了最终哈希将与哈希 右侧 ChainRoot 一起进行。

这样就完成了递归证明构造,以验证 L1 上 Gateway 结算 ZKChain 日志包含。


结论

通过利用 Gateway ZKChain 作为中间结算层,并设计此递归证明方案,ZK Stack 保持了一致 Rollup-to-SettlementLayer 通信风格,确保了向后兼容性,并允许可扩展且高效 跨链交易和证明机制。

请求审计

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

0 条评论

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