探索Keystore和跨链一致性

本文深入探讨了跨链Keystore的设计与实现,旨在解决多链环境下智能合约钱包签名者管理的一致性问题。

简介

在最近提出一个跨链Keystore以管理钱包签名者的建议之后——并受到Vitalik初始博客文章的启发——Base研发团队开始了这一项目,因为我们坚信账户抽象的潜力。在这篇文章中,我们回顾了开发实用Keystore实现的旅程,并强调了在此过程中遇到的挑战。

最初,当我们根据Vitalik的愿景构建Keystore时,我们发现依赖ZK技术用于这一目的带来了显著的缺点。构建、维护和操作一个Minimal Keystore (ZK) Rollup被证明在技术上和经济上都非常复杂,促使我们转向更简单的智能合约方法。在这一第二次尝试中,我们意识到使用Keystore来强制执行跨链签名者一致性存在不可忽视的风险,主要是因为跨链消息传递是脆弱的,并依赖于可以在硬分叉后失效的存储证明。

经过深入探索,我们得出了结论:Keystore尚未为其最初打算的用例做好准备:确保跨链签名者一致性(这在撤销泄露密钥时最有价值)。尽管如此,我们仍然看到Keystore在跨多个链复制签名者状态以及更高效地启用高级钱包功能(例如,时间延迟的或ZK电子邮件恢复)方面具有显著的实用性。

在这篇博客中,我们分享我们的见解和经验,以帮助其他人加速他们自己的评估和探索。我们仍对这一基本概念深信不疑,并相信生态系统应该朝着其最终实现的方向努力。

我们首先解释了为何需要Keyspace,引入Keystore的概念。接着简要探讨了我们尝试的可行的高层架构及转向更简单的仅智能合约设计的理由。之后,阐述了如何在现有钱包上启用Keyspace及其解锁的功能。还讨论了Keyspace的当前局限性。最后,文件以对项目当前状态和未来潜力的讨论结束。此外,文末还附有关于跨链状态读取当前状态的附录,并在文中多次提及。


1. Keystore

挑战

随着生态系统日益多链,管理跨多个链的智能合约钱包成为一个愈发严峻的挑战。用户通常在多个链上部署他们的钱包——如以太坊、Base或Arbitrum——以利用低交易费用或特定DApp等独特特征。然而,维护这些网络之间一致且安全的签名者配置迅速变得繁琐且容易出错。

例如,如果用户想要更新他们钱包的签名者配置——例如增加备份签名者或转换为多重签名设置——这通常需要在每个链上手动更新签名者设置(如果这甚至可能的话)。这个过程效率低下,引入潜在的安全风险,并复杂化了钱包管理。此外,在密钥被泄露的情况下,撤销或替换签名者密钥成为一个时间敏感且复杂的任务,因为必须在每个链上单独进行。

Keystore概念旨在通过提供一个全球可访问且标准化的钱包签名者管理解决方案来解决这些挑战。它提供签名者配置的单一真实来源,实现无缝的跨链互操作性,同时执行强大的访问控制机制。通过Keystore,用户可以更高效和可靠地更新签名者设置或应对安全事件,涵盖所有连接的链。

有了这样的背景,我们将深入探讨Keystore概念的细节、其架构以及在探索和开发过程中遇到的挑战。

Keystore

Keystore概念源于跨链简化钱包签名者管理的需求。其想法很简单:暴露一个独特且全球可用的Keystore,任何在钱包部署的链上均可访问,以确定哪些签名者密钥控制该钱包。一般而言,可以将此Keystore视为钱包标识符与签名者配置之间的映射:

{
    "alice.eth": {config: {signers: ["0xal1ce1", "0xal1ce2"], threshold: 1}},
    "bob.eth": {config: {signers: ["0xb0b1", "0xb0b2"]}}
}

Keystore的确切结构(及其数据)并不是如上所示,但这个表示形式依然有助于可视化。

在ERC-4337流程中,当试图执行UserOp时,将访问此Keystore。为了授权执行,钱包将以某种方式访问此全球可用的Keystore,以确定当前控制钱包的签名者,并验证该UserOp是否由此签名者签名。

Keystore签名者配置并不是固定的;它们以默认值初始化,但可以被修改。例如,如下所示,alice.eth可能希望增加一个新的签名者或调整她的阈值。

{
    "alice.eth": {config: {signers: ["0xal1ce1", "0xal1ce2", "0xal1ce3"], threshold: 2}},
    "bob.eth": {config: {signers: ["0xb0b1", "0xb0b2"]}}
}

显然,bob.eth不应能够修改alice.eth的签名者配置。因此,我们需要将Keystore重新思考为一个授权的“钱包标识符”到“签名者配置”的映射,同时对更新签名者配置执行严格的访问控制规则。

{
    "alice.eth": {

        authorize(sender, newConfig) -> bool: someMultiSigLogic,
        config: {
            signers: ["0xal1ce1", "0xal1ce2", "0xal1ce3"],
            threshold: 2,
        }
    },

    "bob.eth": {

        authorize(sender, newConfig) -> bool: someMultiOwnableLogic,
        config: {
            signers: ["0xb0b1", "0xb0b2"],
        }
    }
}

重要的是要注意,由于每个标识符可以指定自己的授权逻辑,而不是整个Keystore共有单一的授权逻辑。尽管上述表示形式有些简化,但它有效地展示了Keystore的目标。重申一下,Keystore是一个独特的、全球可用的、授权的“钱包标识符”到“签名者配置”的映射。

Keyspace是我们首次尝试实现Keystore——一个具体解决方案,钱包今天可以使用以增强其跨链能力。尽管这一概念看起来简单,但开发一个实用和功能性实现存在自己的一系列挑战,主要由于当前跨链状态读取的局限性。


2. 一般Keystore架构

如何使Keystore在跨链之间全球可用?

在钱包跨多个L2和替代L1部署的背景下,建立一个普遍可访问的中央真实来源是一个挑战。

抛开替代L1(这些需要依赖像预言机这样的解决方案,或完全选择不使用),基本上有两种方法——都基于L2可以,或最终能够,访问L1状态的假设*

  1. 直接在L1上实施Keystore。

  2. 仅在L1上提交Keystore状态。

都基于L2可以,或最终能够,访问L1状态 *: 跨链状态读取在 跨链状态读取 _附录中得到了进一步探讨。

直接在L1上实施Keystore

第一种方法假设Keystore可以采取智能合约的形式,直接部署在L1上,用户将提交交易以更新他们的签名者配置。然后,在L2上,用户必须提供他们在L1上签名者配置的状态证明,以被允许使用他们最新的签名者。在这种方法中,L1被用于:

  1. 存储完整的Keystore状态

  2. 运行每个Keystore签名者更新的授权逻辑

这种方法的缺点是,更新每次钱包签名者配置时都需要发送一次交易到L1,这在撰写时大约会花费5美元(假设是简单且低成本的授权逻辑)。

仅在L1上提交Keystore状态

第二种方法通过避免每次钱包签名者配置更新时都发送一次交易到L1,减轻了第一种方法的缺点。相反,一个新的组件将被引入以聚合和批量处理Keystore签名者更新,仅向L1发布更新的Keystore状态的单一承诺。听起来像一个rollup,对吧?在这个场景中,L1将负责:

  1. 存储Keystore状态的承诺

  2. 验证每个新承诺在当前状态下是否有效

新组件需要:

  1. 维护完整的Keystore状态

  2. 执行每个Keystore签名者更新的授权逻辑

  3. 将Keystore签名者更新聚合并打包成承诺,以便在L1上发布

假定在新组件上存储Keystore状态和执行授权逻辑要比在L1上执行相同操作经济得多。

第一种方法的缺点(直接在L1上实施Keystore)使其对钱包来说不切实际。因此,本文余下部分将重点关注第二种方法。

如何在L1上承诺Keystore状态?

在前一节中,我们探讨了如何将Keystore状态承诺到L1并启用高效的跨链状态共享(或者至少是我们对此的最佳尝试)。然而,这种方法引入了一个新的组件,负责处理和应用Keystore签名者更新,并定期将承诺发布到L1上的Keystore状态。该组件有效地充当一个rollup,可以是:

  1. 一个完全独立的minimal (ZK) rollup

  2. 一个通用的(EVM)rollup

一个完全独立的(ZK) rollup

第一种方法涉及设计一个专用于Keystore的最小rollup (MKSR),利用ZK批量处理Keystore签名者更新,并实现相对快速的最终性*。整个Keystore状态将存储在一个索引/稀疏Merkle树中,从而通过在L1上发布其根实现高效的承诺。为了确保在承诺之间过渡时的正确性,rollup还将提交ZK证明(连同新的Keystore状态承诺),该证明将在L1上进行验证。不同的授权逻辑将实现为ZK电路,每个Keystore标识符(用户)将指定执行哪个ZK电路(ECDSA密钥、P256密钥、多重可拥有、多重签名等)以授权他们的更新。

相对快速的最终性* :目前,实现近乎瞬时(<10秒)最终性受到若干技术限制的阻碍。具体而言,证明生成需要几分钟,而存储在L1上的Keystore状态承诺在经过几分钟延迟后才对L2可见。

这一设计在Vitalik的这篇文章中有所描述。Michael de Hoog,Base首席软件工程师,在这篇文章中介绍了一种技术实现,依赖于底层PLONK电路,最终,在迭代之后,我们切换到了使用ZK VMs。切换到ZK VMs对于简化证明聚合(通过无限递归)、优化电路开发(通过启用Rust编程而不是底层Gnark),并利用这方面的贡献者每天所做的持续性能和可用性增强至关重要。

经过对这种方法的实验,发现我们识别出来的利弊如下:

  • 优点:
  1. 我们对MKSR拥有完全的控制,这使我们在实现目标时具有灵活性。

  2. 我们实现了相对快速的最终性,使得Keystore状态的变化在几分钟后对所有链可见。

  3. 这种无关方法没有涉及到特定的团队或协议,有利于未来嵌入到EVM中。

  • 缺点:
  1. 我们不得不处理从零开始实施ZK rollup相关的技术挑战。

  2. 我们在实现快速最终性与降低每次Keystore更新的成本之间面临权衡。为此,我们设想可能使rollup要么使用证明聚合器,要么成为一个聚合器。目标是增加一次提交到L1之前被证明的内容数量。

  3. 为了降低在ZK VM中发生关键性错误的风险,我们必须实施多重证明,并计划支持SP1、RiscZero和Intel TEE。

  4. 希望使用Keystore的钱包团队需要将他们的逻辑在Rust代码(用于实现为ZK电路的授权逻辑)和嵌入其钱包合约中的Solidity代码之间拆分(那将读取所知的最新Keystore承诺、提取用户的签名者配置并确保它被用于签署正在执行的UserOp)。

在测试完这种方法后,我们发现通过ZK实现的相对快速最终性并不足以弥补附带的复杂性权衡和技术限制(例如,快速过期的签名)。为了鼓励智能钱包团队的采用,我们需要近乎瞬时的最终性(<10秒)。

通用(EVM)rollup

我们得出的结论是,使Keystore状态更新立即可用跨链的唯一方法是:

  1. 在相关链上设置Keystore状态,通过重放状态更新交易。

  2. 使用跨链同步,在直接设置不可行时同步Keystore状态。

根据这一观察,在通用EVM rollup上实现Keystore似乎是一个合适的解决方案。事实上,我们的第二个方法基于首次实现的见解:

  • 通过利用现有的rollup来托管Keystore状态和逻辑,简化了Keyspace。

  • 允许设置Keystore状态,使更新立即在相关链上可用。

  • 实施跨链同步以在设置不可能时同步Keystore状态。

在这个设计中,Keystore仅由部署在所有支持链上的智能合约组成。一条链被指定为主链,承载主Keystore,而其他链则作为副本链运作,每条链承载一个副本Keystore。

我们在这里开始实施这一方法的早期版本。然而,这个版本仍然存在着我们在MKSR解决方案中面临的一个主要问题:由于主Keystore状态被“压缩”为单一承诺(主链状态根),签名过期过快。经过几天深挖承诺和加密累加器后,我们得出了结论,没有能够同时满足以下所有条件的解决方案:

  1. 对(无界)元素集合进行承诺。

  2. 生成包含和排除证明。

  3. 给定承诺C1,生成元素E1的包含证明P1,更改另一个元素E2的值并生成新的承诺C2,使得P1仍然能够验证C2。

我们通过意识到一个全球唯一的Keystore不再是必需的来解决这个问题。相反,每个用户可以独立管理自己的Keystore,并承担维护其跨链签名者配置一致性的责任。这使我们能够消除全球Keystore,用用户特定的记录替换它,并直接嵌入每个钱包中的独立Keystore(这也因此嵌入了上述描述的授权逻辑)。以下图提供了这种架构的高层概述:

post image跨链Keystore状态重放与主-副本架构

在步骤1中,Alice更新她的Keystore签名者配置,将0xal1ce设置为她的当前签名者。然后,在步骤1.5中,Alice将她的新Keystore状态设置到相关链。一旦设置,Alice在这些副本链上的钱包可以立即使用她更新的Keystore签名者配置来验证UserOps。有两个要点需要注意:

  1. 步骤1和1.5都运行相同的授权逻辑,以确保根据当前签名者配置允许更新Alice的Keystore签名者配置。

  2. 设置状态时不强制一致性*,这意味着Alice可以在副本链上设置不同的签名者配置。

设置状态时不强制一致性*: 重要的是要注意,当使用跨链同步时,主Keystore仅在此时充当真实来源。设置状态并不依赖任何真实来源。

步骤1.5可能会失败,因为默认情况下,设置状态仅涉及重放Keystore签名者配置更新。例如,如果更新交易依赖于某些链上可用但在某些副本链上缺失的特定状态,这可能会失败。在这种情况下,Alice可以依赖我们实现的不同跨链同步机制(见步骤3)来将她的Keystore状态同步到副本链。

以下是这种方法的优缺点总结:

  • 优点:
  1. 不需要实施、运行和维护一个专用的minimal ZK rollup。

  2. 现在Keystore完全作为在单一主链和多个副本链上部署的Solidity合约实现。这包括之前必须由钱包团队实施的作为ZK电路的授权逻辑。

  3. 用户可以设置其Keystore状态,立即在跨链上使用其新签名者。

  • 缺点:
  1. 快速设置状态对维护钱包安全至关重要,特别是在撤销签名者时。设置状态的任何延迟都可能造成漏洞,使钱包资金面临风险,例如,如果签名者密钥被泄露。

  2. 实施同步回退是复杂的,因为这要求证明从主链到副本链的存储。这一复杂性在两个链都是L2(甚至L3)的情况下加剧,因为需要嵌套证明。

在实施这种方法时,我们发现了一个微妙但关键的细节(在MKSR实现中也存在):一致性带来风险。因此,以上的优缺点中没有提及一致性(跨链Keystore状态的一致性),而是将在Keystore一致性部分进行探讨。

然而,这种新方法为钱包用户提供了更好的整体用户体验(对于用户和开发者)。本文余下部分将重点关注通用EVM rollup方法及其独特的一系列挑战。


3. 启用Keystore的钱包

集成Keystore

到目前为止,将Keystore集成到现有钱包涉及三步骤:

  1. 选择主链。

  2. 继承Keystore合约。

  3. 正确设置状态。

选择主链

如上所述,Keystore架构依赖选择主链作为真实来源。该主链提供规范的Keystore状态,副本Keystore可以在需要时从中同步其状态。

虽然对于可以选择哪条链作为主链没有具体限制,但一些关键考虑因素应指导此决策:

  1. Keystore的安全性与主链的安全假设息息相关。

  2. Keystore的安全性还依赖于与副本链同步状态所使用的方法。只要可能,优先考虑“无信任”解决方案而不是基于预言机的解决方案,以增强可靠性和安全性。

继承Keystore合约

值得注意的是,在当前设计中,钱包充当其自己的Keystore。因此,下一步是通过直接继承Keystore合约来扩展钱包合约。

在此过程中,钱包团队需要实现以下特定方法:

  1. _extractConfigHashFromMasterChain: 实现所需的逻辑,从主链提取签名者配置哈希,确保可以从任何支持的副本链访问它。

  2. _hookIsNewConfigAuthorized: 实现授权新签名者配置的逻辑。这对应于前面提到的authorize方法。

  3. _hookApplyNewConfig: 实现解析和缓存新签名者配置的逻辑,直接存储在钱包合约的存储中。这种缓存通过消除在每个钱包交易或UserOp中提供主链存储证明的需求来改善用户体验。此外,此Hook可用于执行任意逻辑,例如在适当时进行钱包升级。

  4. hookIsNewConfigValid: 实现验证新签名者配置有效性的逻辑。这个可选Hook帮助确保钱包不会因意外更新为无效签名者配置而变砖。

当前的存储库中包括以下示例:

正确设置状态

上述,我们呈现了一个高层架构图示,演示如何通过允许用户手动将其Keystore签名者配置设置到不同链上来维护良好的钱包用户体验,与当前标准相当。

事实上,设置状态类似于Coinbase智能钱包团队实现的重放交易机制,其中一个关键区别是:它重放的是整个签名者配置(即Keystore状态),而不是仅仅重放一笔交易。这个区别突显了Keystore的主要目的:直接同步状态而非交易历史。

在主链上,不需要特别处理,因为签名者配置将始终反映最新状态。对签名者配置的更新源自发送到主链的交易,在主链中维护真实来源。

在副本链上,设置状态则完全取决于钱包团队。然而,可以考虑几个关键因素:

  1. 撤销签名者:撤销签名者时,更新的状态必须立即设置到所有副本链上*。不这样做可能会显著削弱钱包安全性,因为被撤销的签名者可能继续使用钱包,包括在副本链上将其升级为恶意实现。

  2. 优先处理活跃链:优先更新用户经常交互的链的状态,以确保在这些链上的最佳用户体验。

  3. 懒惰设置状态:对于用户很少交互的副本链,可以推迟在实际使用之前再进行状态设置。此过程可以与UserOp捆绑在一起,以最小化开销。

到所有副本链上* : 我们承认在所有现有和未来的链上重放撤销可能并不实际。实际上,钱包提供者可能只在用户最相关的链上重放撤销,这可能导致在没有应用签名者撤销的其他链上钱包面临被妥协的风险。在_已知问题与局限性_部分提供了有关“弱”签名者撤销概念的更详细解释。

解锁的功能

签名者配置一致性

通过继承Keystore合约,钱包暴露了所有必要方法,可以立即设置其签名者配置或在必要时同步主链上的状态。

Vitalik最初提出两种方法来实施Keystore:

  1. 轻版:仅在签名者配置更新期间强制Keystore状态一致性。

  2. 重版:为每个交易或UserOp强制Keystore状态一致性。

这两种方法都旨在在某个时刻强制状态一致性,以提供一定程度的安全保障。然而,问题在于细节,正如我们将在Keystore一致性部分中讨论的那样,我们认为强制状态一致性(无论在何阶段)会给钱包带来更多缺点和脆弱性,而不是实际好处。

因此,我们的方法故意避免强制状态一致性,并鼓励以下操作:

  1. 手动设置状态:通过设置Keystore状态,每个钱包可以缓存其自己的Keystore签名者配置。这确保在大多数情况下,用户只需发送他们的原始交易,而无需额外步骤。

  2. 在回退时同步状态:Keystore还提供所需的跨链消息原语,以在设置状态不可行时同步用户的签名者配置。

  3. 额外收益:在主链上,Keystore几乎是免费的,因为它充当用户签名者配置的真实来源。

高级功能的基础

通过复制整个签名者配置状态——而不是仅依赖于基于交易的重放——将Keystore集成到钱包中解锁了之前不可能或需要大量工作来实现的高级功能。尽管这种方法开辟了各种用例,但我们预计其主要应用是实现恢复监护人。

众所周知,用户可能会丢失对其密钥的访问。在这种情况下,触发一个恢复过程(类似Web2中的恢复过程)是非常希望的。诸如时间延迟恢复或更近期的ZK电子邮件恢复等功能可以显著增强恢复场景中的用户体验。

通常,恢复监护人依赖于读取区块链状态以授权恢复——例如,验证何时启动恢复,或检查DKIM证书注册以验证ZK电子邮件恢复。在多链钱包中,这通常要求所需的数据在每个支持的链上均可用。然而,使用Keyspace时,我们重放的是Keystore的最新完整状态,而不是单个签名者的变化,这使得恢复过程可以在一条链上执行,然后再桥接到其他链。


4. Keystore的局限性

虽然将Keystore集成到钱包中提供了签名者配置的跨链同步并启用了新功能的发展,但它也引入了一系列挑战。

维护反事实地址

使用反事实地址对于任何使用EOA的人来说都是熟悉的。你可以离线创建一个新的密钥对,安全地共享公钥,并开始在相应地址上接收资金。

钱包旨在利用CREATE2操作码来生成反事实地址,从而复制此功能。尽管CREATE2允许用户提前知道其钱包地址,并在钱包尚未部署的链上接收资金,但它也带来了显著的缺点。

工厂局限性

由于CREATE2只能通过智能合约调用,因此它需要使用工厂合约。为了生成相同的地址,CREATE2依赖于以下条件:

  1. 依赖相同的工厂:在所有链上必须始终使用相同的工厂合约。一旦工厂用于部署钱包,则无法轻松更换为现有用户的新版本。虽然使工厂可升级是一个选择——因为只相关工厂合约地址——但这将需要增加管理升级的权限,通常是不可取的。此外,如下所述,升级工厂可能根本不是现有用户的一个实际解决方案。

  2. 固定合约字节码:部署合约的字节码(初始化代码)必须保持不变。初始化代码包含实际的合约部署代码和构造函数参数。因此,用户一旦在特定链上部署其钱包,维护在新链上相同地址就要求使用完全相同的实现_以及相同的构造函数参数*_进行部署。

以及相同的构造函数参数*:这引发了在部署到新链时可能会导致钱包变砖的风险,特别是如果用户不再有权访问其原始签名者配置——可能是由于很久以前的更新。我们希望状态设置交易是可以重放的(我们强烈建议确保重放能力),这应显著降低此类问题的可能性。如果状态无法设置,则应有来自主Keystore的同步作为一种后备,防止钱包变砖。

链无关的Keystore

如前所述,CREATE2依赖于部署相同的初始化代码以产生一致的地址。虽然这对钱包构造参数施加了约束——例如,要求钱包始终以相同的初始签名者配置进行部署——但这也对Keystore实现引入了复杂性。

由于钱包自身围绕其Keystore(继承Keystore合约)运作,Keystore合约代码必须保持链无关,以便在其被部署的任意链上无缝运行。这需要解决两个关键挑战:

  1. 在不同链上方法曝光:Keystore必须根据它是部署在主链还是副本链上而暴露不同的方法。

  2. 跨链状态读取:无论钱包部署于哪个副本链,它必须能够从主链中读取签名者配置,以便在设置状态不可行时仍能够使用同步作为回退。跨链状态读取的逻辑高度依赖于正在读取的具体链(即副本链)。然而,为每个副本链实现单独的逻辑则不可行,因为这将导致不同的钱包合约字节码,从而导致不同的部署地址。

虽然第一点相对直观(例如,使用block.chainid来确定钱包是否部署在主链上,并相应地启用或禁用相关方法),但第二点则更为微妙。

如在跨链状态读取附录中讨论的,副本链读取主链的能力与其访问L1状态的能力密切相关。然而,不同的副本链使用不同的方法来检索L1状态(例如,获取最近的L1状态根)。

我们当前的做法实现了不同链在访问最近的L1状态根时使用的各种方法。在同步时,用户可以提供一个通用的L1StateRootProof,指定执行以检索最近的L1状态根的逻辑。从这个L1状态根,可以提取出主链上的Keystore状态。

_这种方法的一个示例是_extractConfigHashFromMasterChain方法的实现。在内部,它调用L1StateRootLib.verify方法,该方法管理最近L1状态根的通用检索。

_目前,“通用”逻辑支持从OPStack链上的L1Block合约和适用于兼容EIP-4788的链中的BeaconStateRoots合约提取状态根。

实现从最近L1状态根中提取出的机制可以进一步开发。

Keystore一致性

虽然利用Keystore钱包提供了一种简单的方法来跨链管理签名者配置,但人们可能最初会认为Keystore还确保了所有链上钱包签名者配置的一致性。这一假设的主要动机是使跨链有效撤销签名者成为可能。尽管这确实是我们最初实施的目标,但我们意识到这带来的缺点多于好处。

理想情况下,我们的目标是:

  1. 主Keystore签名者配置立即在所有副本链上可见。

  2. 在所有时间强制Keystore签名者配置的一致性——其主要动机是实现跨链有效的签名者撤销。

第一点取决于主链的最终性。在当前实现中,一致性只能以“最终”形式强制,最小的相关时间间隔是主链在L1上结算所需的时间。

第二点则更为复杂。强制最终一致性引入了显著的wallet bricking风险。如跨链状态读取附录中所讨论,为了从主链读取状态,副本Keystore必须实现逻辑:

  1. 访问L1状态根:这可以通过OPStack的L1Block合约实现,利用EIP-4788或通过等效解决方案。

  2. 验证L1存储证明:Keystore必须支持验证L1存储证明,以确认主链的状态。

  3. 验证主链存储证明:Keystore必须支持验证主链存储证明,以确认主Keystore的状态。

但是,这些步骤可能由于以下原因而失败:

  1. 副本链上的硬分叉:硬分叉可能使L1Block合约或等效解决方案过时,导致Keystore无法访问最近的L1状态根。

  2. L1存储的变化:切换到Verkle Trees用于L1存储可能会破坏依赖于验证L1 MPT证明的Keystore实现。

  3. 主链上的硬分叉:主链上的硬分叉可能会改变其在L1上结算状态的方式,使所有副本Keystore无法验证主Keystore存储证明。

这些场景是可能发生的,因为链需要定期更新以进化和改进。如果发生此类变化,部署到副本链的钱包可能会被卡住。用户将无法证明他们的主Keystore签名者配置,而不必升级他们的钱包实现以支持新的证明逻辑。然而,升级是有权限的,并受到一致性检查的保护,制造了一个鸡与蛋问题。

从这种情况下恢复的可靠方法可能是使用本机提现和存款工作流程,从主链向副本链传播Keystore签名者配置。然而,这种方法需要链特定(或者生态系统特定的,例如性基于OPStack的)的实现,而这些在钱包部署后无法轻易修改以支持新链。现有钱包如果部署到新链且未在初始版本中实施存款流程,则可能无法从主链同步其Keystore配置。

因此,我们目前相信,强制一致性——无论是即时还是最终的,无论是针对所有操作还是仅限于签名者配置更新——都不值得引入的复杂性,也不值得可能使用户钱包变砖的风险。

我们承认,我们目前的解决方案并不完美,因为它没有解决签名者撤销的问题。然而,我们认为生态系统尚未准备好有效解决这一问题,因为仍需更强的跨链消息原语以保证抗硬分叉。

为澄清,Keyspace目前 不提供跨链的签名者撤销保证。相反,它提供实用方法,使用户能够轻松地传播其当前的Keystore签名者配置跨链,钱包团队可利用这些方法实现高级功能。

5. Keyspace的现状与未来

现状

我们对Keystore的实现相较于现有钱包解决方案提供了良好的改进。然而,它仍有已知问题和局限性,我们计划在Keyspace的未来版本中进行解决,随着跨链状态读取能力的不断演进。

主要优势

目前的Keystore实现:

  • 基于“主-副本”链架构。

  • 包含一个单一智能合约,供钱包继承。

  • 允许钱包签名者配置的跨链状态共享。

此实现为现有钱包解决方案引入了两个关键进展:

  1. 简化的配置一致性:实施Keystore暴露了在不同链间维护用户签名者配置一致性所需的逻辑。

  2. 支持通用状态同步:我们的示例实现演示了如何在用户Keystore配置中存储用户钱包的实现地址。同样,钱包供应商团队可以利用Keystore通过在配置中嵌入自定义数据来开发其他功能。

已知问题与局限性

在设计Keystore架构时,我们必须平衡两个关键考量:

  1. 抵御链升级的能力:确保钱包未来不会因链升级而变砖。

  2. 有效的跨链签名者撤销:实现各链(包括未来链)上的一致和可靠的签名者撤销。

后者要求强制对用户钱包的所有操作进行最终一致性。然而,正如在Keystore一致性部分讨论的那样,强制实行这一点带来了风险,在链升级的情况下可能导致用户钱包变砖。目前可用的跨链状态读取解决方案使上述两个考量互斥*_——除非引入可信方来执行用户所有钱包的无权限升级。

互斥*:这一局限性可以通过依赖于本机存款和提现,在链之间发送跨链消息来部分减少。然而,如在_Keystore一致性_部分所述,这种方法在不同L2堆栈上并不具备良好的扩展性。

由于链升级是不可避免的,且不清楚何时会在跨链状态读取上取得重大改进,因此我们设计了Keystore的初始实现,以优先考虑对链升级的弹性,尽管以牺牲_弱签名者撤销*_为代价。**

虽然这是一个真实的问题,但重要的是要强调,这一问题同样影响传统EOA(不具备撤销的概念)以及当前钱包实现(当前不允许撤销签名者)。Keyspace的未来版本将专注于解决这一问题,以提供增强的跨链一致性和强大的签名者撤销能力。

弱签名者撤销*:在此上下文中,“弱”意味着撤销签名者并不自动适用于所有现有和未来的链。相反,被撤销的签名者必须在所有相关链上“手动”撤销。

未来

Keyspace的长远目标是:

  1. 确立钱包配置的唯一真实来源。

  2. 提供签名者撤销的强保证。

  3. 实现近乎瞬时的最终性,确保钱包配置更改立即在所有链上反映。

  4. 保持成本效益和易用性。

实现这些目标的路径依赖于三个主要里程碑。

高效与弹性的L1状态读取

如在跨链状态读取附录中详细说明,跨链间共享状态在很大程度上依赖于每条链获取L1状态的能力。目前,生态系统是分散的,不同链的L1状态访问方法各异(例如,通过L1区块哈希或利用EIP-4788)。一些链,如在撰写时的Arbitrum,完全缺乏信任的L1状态访问支持(除非使用来自L1的存款交易,这会很昂贵)。

此外,所有这些方法都依赖于在链下构建证明(通常是MPT证明),并在链上提交以供验证。随着L1改变其状态表示(例如,过渡到Verkle树),这些证明很可能会失效。

解决这些挑战是改进Keyspace的关键第一步。实现这一点将需要实施和广泛采用新提案,例如L1SLOAD、更具雄心的REMOTESTATICCALL,或等效解决方案,以抵抗L1硬分叉。

转向专用的Keystore rollup

一旦解决从任何层读取L1状态的能力,下一项挑战是从其发布在L1上的承诺获取主链状态。与从L1获取状态类似,我们的目标是使这一过程尽可能鲁棒,以抵御可能影响其状态如何提交到L1的主链升级。

允许钱包供应商团队选择自己的主链简化了Keyspace的快速采用(符合“快速运输”的精神),但这并非理想解决方案。不同主链使用各种方法来提交其状态到L1,并将独立地演变和升级,这使得Keyspace生态系统的管理复杂性越来越高。随着这些链的改进和升级,从它们发布到L1的承诺获取其状态的方法也会出现分歧,进一步复杂化系统。

第二个主要步骤将是(重新)引入一个Minimal Keystore Rollup,以作为所有钱包供应商团队的通用主链。通过这种实现,Keyspace的韧性将仅依赖于这单一链的升级(例如,提交其状态的方式到L1)。这种方法简化了架构,从依赖m条主链的n条副本链过渡到依赖单一通用主链的n条副本链,有效将复杂性从n²问题降低到n问题。

最近围绕基于和原生rollup的巨大热潮都可能大大有利于MKSR并简化其实施:

  1. 使MKSR成为一个Based Rollup将通过继承L1的活跃性和去中心化来简化其排序。

  2. 使MKSR成为一个原生rollup将实现批量更新的Keystore状态转换的高效验证。

转换为通用主链对于实现最终里程碑也是至关重要的,使Keyspace转变为我们设想的全面解决方案。

将Keyspace嵌入EVM

最后,一旦Keyspace依赖单一的通用Minimal Keystore Rollup (MKSR),我们可以设想未来将其嵌入EVM。在这种情况下,所有节点将运行一个MKSR节点,同时运行一个L1节点,通过类似于L1SLOAD操作码的方式无缝访问MKSR:KEYSTORESLOAD。

这一最终里程碑将开启本地的、用户友好的多链钱包的新纪元,完全消除提供MKSR状态证明的需要。


6. 结论

我们设想的长期Keystore是一个重要的创新,旨在提升在日益多链的世界中钱包用户的体验。通过统一跨不同链的签名者管理,它不仅简化互动,还通过签名者撤销提高安全性——这一功能如今传统EOA根本不提供。

然而,为了实现Keystore的全部潜力——包括启用签名者撤销,这对减轻密钥被泄露时的风险至关重要,并增加了一层迫切需要的保护——必须解决一个关键挑战:缺乏标准化、强大且可访问的L1状态从L2网络检索的方法。如果没有这种跨链可访问性,实现一致且安全的解决方案依然遥不可及。未来,克服这一障碍对于实现账户抽象的益处以及确保用户享受无缝、安全和统一的钱包体验至关重要。

随着我们继续探索和创新,跨生态系统的协作将是弥合这些技术差距的关键。最终,解决这些问题将为更加强韧和用户友好的多链未来铺平道路。


附录:跨链状态读取

在呈现Keystore架构时,我们假设L2可以,或最终能够,访问L1状态。这一能力对于同步用户在不同链上部署的钱包合约中的Keystore配置至关重要。

本节首先假设所有链都可以访问L1状态,展示这如何使几乎无阻碍的跨链状态读取成为可能。然后专门关注L2(和L3)当前如何访问L1状态,并探讨这种能力在未来可能如何演变。

建议阅读Vitalik有关与钱包相关的跨L2读取的优秀文章

从L1状态读取到LN状态读取

众所周知,L2将其状态根存储在部署在L1的rollup合约中。在未来,L3将在L2上结算,而L2将继续在L1上结算。在这个生态系统中,从任何链访问L1状态的能力是跨链状态读取的关键推动力。

在以下子部分中,我们假设所有链(L2、L3等)都具备 直接读取L1状态的能力。让我们探讨在这种情况下状态读取的工作方式。

L1到L2的状态读取

L1到L2的状态读取是直接的。以下是如何从L2(C_L2)合约读取L1合约(C_L1)的存储槽S0的最新值:在C_L2中,直接读取L1状态以获取C_L1合约中S0槽的最新值。就这样。

post imageL2合约(C_L2)直接从L1部署的合约(C_L1)存储中读取值

然而,总是需要直接在L1上更新状态来启用跨链读取的做法非常昂贵。因此,直接在L2之间进行状态读取将是一个更高效的解决方案。

L2到L2的状态读取

让我们在前一个示例上构建,看看存储槽S0的L2合约C_L2_SRC如何被部署到另一L2(C_L2_DST)的合约访问。再一次,直接从L1读取状态的能力对于此过程至关重要:

  1. 在C_L2_DST中,执行L1存储读取以获取在L2_SRC rollup合约中提交的最新C_L2_DST状态根。

  2. 在C_L2_DST中,接受针对C_L2_SRC合约中S0槽的存储证明并根据第一步中读取的L2_SRC状态根进行验证。

post imageL2合约(C_L2_DST)通过L1状态和存储证明从另一L2合约(C_L2_SRC)读取存储

这个过程比L1到L2的状态读取稍微复杂一些,因为它涉及在链下构建存储证明,并提取到目标L2合约上。通过提供证明,该合约可以通过在检索的状态根上进行验证来无需信任地利用共享状态。

在上述示例的基础上,这种方法似乎可以普遍适用于跨任意级别层读取状态。

LX到LY的状态读取

基础的观察是,第N+1层会将其状态根部署到第N层,递归延续,最终到达第1层(L1)。从这里我们可以看到,L2到L3、L3到L3,甚至L2到L5的状态读取可以通过递归嵌套存储证明的方式实现,直至达到目标层。

让我们看看L5到L3的状态读取将如何工作:

  1. 在C_L3_DST中,读取L2_SRC发布到L1的状态根。

  2. 在C_L3_DST中,接受四层存储证明并根据第一步中获取的L2_SRC状态根进行验证。此证明包含以下步骤:

  3. 根据L2_SRC状态根提取L3_SRC状态根。

  4. 根据L3_SRC状态根提取L4_SRC状态根。

  5. 根据L4_SRC状态根提取L5_SRC状态根。

  6. 根据L5_SRC状态根提取在合约C_L5_SRC中的槽S的存储值。

这种嵌套证明方法使得跨任意层级的状态读取在信任下方块内得以实现。然而,一个显而易见的缺点是证明的复杂性和大小随着跨越的层级数线性增长*。这是因为该过程始终从L1开始,并迭代层级结构直至到达读取状态的源层。

证明的复杂性和大小随着跨越的层级数线性增长*: 尽管在天真MPT证明中确实存在这种情况,但并不完全准确,因为这取决于使用的证明类型https://learnblockchain.cn/article/6026#what-kinds-of-proof-schemes-can-we-use)。

从L2读取L1状态

所有先前方案都依赖于L2(及更高层)直接访问L1状态的能力,这是构建(可能是嵌套的)存储证明所需的源L2状态根的关键。

尽管直接读取L1状态尚不可能——但将来可能变得可行——L2当前可以访问最近的L1状态。通常这涉及到检索最近的承诺以验证提供的L1状态根。一旦L1状态根被验证,任何存储槽都可以通过生成对应的存储证明来验证。

目前,大多数L2可以检索最近的L1区块哈希,从而证明最近的L1状态(通过验证完整的L1区块头)。然而,大多数链共享相同的局限性:较老的L1区块哈希很快变得无法访问,从而使证明较旧的L1状态变得困难。

一些L2的EIP-4788的采用引入了一种通过利用信标区块根访问L1状态的新方法。此过程无需增加显著的复杂性,可以稍微延长证明的有效性,并通过进一步努力,甚至可以启用访问任何历史的L1状态。

最后,正在开发其他提案,以简化L1状态读取,甚至使L2能够直接执行L1静态调用。

在OPStack L2上访问L1区块哈希

OPStack链提供L1Block合约,该合约提供有关最近已知的L1区块的信息。根据文档,“该合约中的值每个epoch(每个L1区块)更新一次”。

虽然从所有OPStack L2直接访问最新的L1区块是非常方便的,但有两点值得注意:

  1. L2所知的最新L1区块通常比当前L1区块落后几块。

  2. 由于L1Block合约频繁更新,直接依赖于它进行跨链状态读取是不可靠的。

第一点引入了一定的延迟,通常由几个L1区块组成。在撰写时,这个延迟大约是~10个L1区块(取决于链的重组风险容忍度),换算下来大约是2分钟延迟。尽管不是理想,但通常对大多数跨链状态读取用例是可以接受的。

然而,第二点则提出了一个更大的挑战。生成的存储证明仅在一个L1的epoch期间有效(当前为12秒),使其在许多场景中变得不可行。下面的示意图对此揭示得更加清晰。

post image存储证明有效期仅在L1Block合约持续返回同一L1区块哈希时保持有效

幸运的是,通过利用区块哈希操作码可以实现快速改进,允许使用较旧的L1区块。ReadFromC_L1合约中的逻辑将变为:

  1. 不从L1Block合约检索最新已知的L1区块哈希。

  2. 接受任意L2区块头并计算其哈希。

  3. 使用区块哈希操作码验证提供的L2区块头是否在最近的256个L2区块内。

  4. 接受L1Block合约的存储证明,证明所存储的L1区块哈希,并根据在步骤2中提供的L2区块头的状态根进行验证。

  5. 接受任意L1区块头,计算其哈希并确保其与在步骤4中证明的哈希匹配。

  6. 使用L1区块头(状态根)验证L1存储证明。

post image提供L1Block合约的存储证明及结合使用区块哈希操作码,将L1存储证明的有效期延长至多256个L2区块(~8.5分钟)

这种稍微复杂的方法将L1存储证明的有效期从12秒延长至大约8.5分钟。尽管这是一个显著的改进,能够启用许多用例,但对于需要证明非常旧L1状态的场景依然不够充分。

利用EIP-4788信标区块根

EIP-4788通过8191大小的环缓冲区链上公开信标链区块根,提供约一天的覆盖。虽然该提案主要旨在公开信标链信息,但这些区块根也可用于证明执行状态根(即L1状态根)。

证明L1状态的过程类似于以前方法的实现,不过这里的L1状态根——用于验证任何L1存储证明的基础——是来自最近的信标区块根而非最近的L1区块哈希。

post image使用EIP-4788证明最近的L1状态根将L1存储证明的有效期延长至大约一天

Wilson Cusack实现了这一方法的工作示例

_本文提供了关于信标区块是如何(序列化并)Merkle化的概述,并解释了如何利用其根来证明特定字段(例如执行状态根)。_

通过稍多的努力,信标区块根还可以用于访问信标状态。自Capella分叉以来,该状态包括一个附加式累加器L1状态,每8192个槽(基本上是信标根环缓冲满时)被提交。通过生成此类证明,可以对非常旧的L1状态进行长期的证明。

未来改进

当前所有的L1状态读取机制都依赖于生成L1存储证明。尽管这并不是特别复杂,但这些证明对L1状态的结构和承诺方式产生了重大依赖。如果这一结构发生变化(例如,过渡到Verkle树),那么之前生成的所有L1存储证明将变得无效。

未来的改进旨在通过使L2能够直接读取L1状态来解决这一挑战。这是一个活跃的开发领域,目前有几种潜在方法正在探索:

  1. L1SLOAD预编译(通过RIP-7728):这将引入一个基础预编译,直接从L1合约加载存储槽,消除生成和提交L1 MPT证明的需要。

  2. REMOTESTATICCALL操作码:这是一种更先进的方法,能够使L2合约直接对L1合约执行静态调用,从而扩展L1状态访问的功能和灵活性。

这两个方法目前仍处于实验阶段,可能不会很快纳入协议,因其可能引发的重大担忧

从L3读取L1状态

尽管可能会出现新提案来改进此过程,但目前从L3(甚至更高层)读取L1状态的最简单、最直接的方法是通过使用存款交易传播L1状态根。

这种方法对于L1到L2状态读取并不可行,因为L1的高交易费用。然而,这似乎是一个切实有效的选项,适用于L2到L3状态读取,并可以用于将L1状态传播到L3。同样,如果将来引入其他层,直接从下一级使用存款交易看似也是一种合理的方法。


共同构建

如果没有生态系统内许多团队的创新,我们无法研究和开发像Keyspace这样创造性的系统——我们将继续合作,推动可能中的边界。

如果你有兴趣参与创造性和雄心勃勃的项目,以使下一个十亿人上链,我们在招聘——我们期待听到你的声音。

订阅此博客并在社交媒体上关注我们,以随时了解最新动态:XBase团队在X上) | Farcaster | Discord

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

0 条评论

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