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

本文深入探讨了 ZKStack 的跨链架构,重点介绍了通过在 Gateway 上进行结算来降低结算成本,并保持安全保证。文章详细解释了 L2 到 L1 日志包含证明的构建过程,以及如何通过 Gateway 验证在 L1 上进行结算的 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 的新 DiamondProxy 部署在 Gateway 上,它使用来自 L1 的状态进行初始化,并且 ZKChain 现在与 Gateway 就批量发布进行交互。 请注意,反向过程也是可能的,本质上是将 ZKChain 的结算层从 Gateway 迁移到 L1。

即使 Gateway 是某些 ZKChain 的结算层,L1 Ethereum 网络也是唯一保证所有 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 面发送一个 rollup 优先级交易请求。

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

它们有不同的路由,因为目前,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:包含证明,用于验证来自此 messageL2Log 是否位于来自具有 chainId 的 ZKChain 的批号为 l2BatchNumberL2ToL1LogsTree 的索引 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 一直哈希到 localLogsRootHashL2ToL1LogsTree 的根),以及一个额外的元素 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 的 L2ToL1LogsTreeL2ToL1Log 叶子。
  • ZKChain 的 L2ToL1LogsTree 的根 LocalLogsRootHash 与一个空的 SharedTree(即默认的 aggregatedRoot)一起哈希,以计算特定批次的 ZKChain 的 fullRootHash。 此根在 Gateway 上结算。
  • Gateway 将结算的 fullRootHash 附加到 ZKChain 的 chainTree,从而导致更新的 chainRoot
  • Gateway 更新 SharedTree 中的新 chainRoot 叶子,从而导致更新的 aggregatedRoot
  • Gateway 使用 aggregatedRoot 哈希其自身的 LocalLogsRootHash,以检索最终的 fullRootHash,该哈希与批次一起提交到 L1。

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

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

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

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

请注意,fullRootHash = hash(LocalLogsRootHash, aggregatedRoot),其中 aggregatedRoot 在右侧哈希,而 Gateway 的 LocalLogsRootHash 在左侧。 为了将 Gateway 的 LocalLogsRootHash 附加到最终聚合的 mekleProof 的左侧,需要特别制作步骤 3 中 chainRoot 的索引。 具体来说,chainRootindex 变为 index + 2^N,其中 NSharedTree 的高度。 这确保了最终哈希将与哈希右侧的 ChainRoot 一起进行。

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


结论

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

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

0 条评论

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