跳至内容
0%

账户抽象第 4 部分:聚合签名

发布于 2023年2月14日2 分钟阅读

聚合签名

我们目前的实现是对 bundle 中的每个 user op 分别进行验证。这是一种非常直接的验证思路,但可能存在浪费。校验签名在 gas 上的开销可能偏高,因为这需要相当多的密码学运算。

如果能用一个签名同时验证多个 op,而不是逐一验证,岂不是更好?

要做到这一点,需要依赖密码学中的一个概念——聚合签名(aggregate signatures)

支持聚合的签名方案提供了这样一种方式:给定多条用不同密钥签名的消息,可以生成一个合并后的签名,使得对该合并签名的验证能够蕴含所有组成签名均有效。

支持聚合的签名方案的一个常见例子是 BLS

这项优化对实现 rollup 尤为有用,因为 rollup 的主要目标就是数据压缩,而签名聚合让我们能够压缩签名部分。

关于签名聚合带来的空间节省,可参见 Vitalik 关于此话题的推文

引入 aggregator

我们马上会发现,并非 bundle 中所有 user op 的签名都可以聚合在一起。请记住,钱包可以使用任意逻辑来验证收到的签名,因此同一个 bundle 中可能存在多种不同的签名方案。

由于我们通常无法把不同方案的签名聚合在一起,最终的 bundle 会由若干组 op 构成,每一组使用某一种特定的聚合方案,或者不使用任何聚合方案。

由于链上需要表示多种聚合方案,且每种方案都有自己的逻辑,我们让每种聚合方案由一个合约来表示,我们称之为 aggregator

一种聚合方案由两部分定义:如何将多个签名合并为一个,以及如何验证合并后的签名。因此,aggregator 会以方法的形式暴露这两个功能:

聚合器合约将多个用户的操作合并为一组,使用单一签名。
聚合器合约将多个用户的操作合并为一组,使用单一签名。

由于每个钱包都定义了自己的签名方案,因此是否兼容某个 aggregator(如果有的话)由每个钱包自行决定。

如果一个钱包想要参与聚合,它会暴露一个方法来选择自己的 aggregator:

借助这个新的 getAggregator 方法,bundler 可以把使用相同 aggregator 的 op 分为一组,并使用该 aggregator 的 aggregateSignatures 方法为它们计算出一个合并签名。

一个分组大致如下:

💡 如果 bundler 对某个特定 aggregator 有链下的了解,它可以进行优化:直接硬编码该签名聚合算法的原生版本,而不是将 aggregateSignatures 作为 EVM 代码来运行。

接下来,我们需要更新入口点合约,以便使用这些新的 aggregator。

回想一下,入口点有一个 handleOps 方法,接收一个 op 列表。

我们给它添加一个新方法, handleAggregatedOps,它做的事情基本相同,但接收的是按 aggregator 分组的 op:

新方法 handleAggregatedOps 的工作方式与 handleOps 基本相同,唯一的区别在于验证步骤。

handleOps 是通过调用每个钱包的 validateOp 方法来完成验证的,而 handleAggregatedOps 则会改为对每一组的合并签名,使用该组的 aggregator 调用其 validateSignatures 方法。

图示:executor 借助聚合器将用户操作分组,再发送给 entry point 进行批量验证
executor 使用聚合器将操作分组,再发送给 entry point,以便一次性完成验证。

我们快完成了!

但这里还有一个到目前为止已经很熟悉的问题。

bundler 希望通过模拟验证来确认 aggregator 会验证通过一组 op,之后才将它们纳入 bundle,因为一旦验证失败,bundler 就得自己支付 gas。但一个具有任意逻辑的 aggregator,完全可能在模拟阶段成功,却在实际执行阶段失败。

我们会用和处理 paymaster、factory 时完全相同的方法来解决这个问题:限制 aggregator 能访问的存储以及能使用的操作码,并要求它在入口点中质押 ETH,除非它不访问存储。

聚合签名的部分到此结束!

总结

我们在这里构建出来的东西,基本上就是 ERC-4337 的完整架构!在一些细节上会有所不同,比如某些方法的名称和参数,但在架构层面,我认为已经没有任何差异了。如果我做得还不错,你现在应该已经能够阅读真正的 ERC-4337 并理解其中的内容。

如果你读到了这里,非常感谢你阅读我的讲解!希望这篇文章对你的帮助,能和它在写作过程中对我的帮助一样多。

附录:与 ERC-4337 的差异

尽管我们已经理清了账户抽象的整体架构,但 ERC-4337 背后的开发者们还考虑到了一些与上文描述略有不同的地方。

我们来逐一了解一下!

1. 验证时间范围

上文中,关于钱包的 validateOp 和 paymaster 的 validatePaymasterOp 的返回类型,我说得比较含糊。ERC-4337 找到了一种很好的方式来利用这一点。

钱包很希望能够做到的一件事,是只允许 user op 在一段特定的时间内有效。否则,恶意的 bundler 可能会把这个 op 扣留很长时间,然后在对自己更有利的时机才把它纳入某个 bundle。

钱包可能想通过在验证阶段检查 TIMESTAMP 来防范这一点,以确保它不会离当前时间太远,但它做不到,因为我们已经禁止在验证阶段使用 TIMESTAMP,以免模拟结果不准确。这意味着钱包需要另一种方式来表明该操作在哪个时间范围内有效。

因此,ERC-4337 给 validateOp 增加了一个返回值,供钱包用来选择时间范围:

这个返回值用两个先后排列的 8 字节整数,表示该操作有效的时间范围。

关于 ERC-4337 还有一点:钱包在验证失败时应当从 validateOp 返回一个哨兵值,而不是 revert,这样有助于 gas 估算,因为 eth_estimateGas 无法告诉你一笔 revert 的交易实际消耗了多少 gas。

2. 钱包和 factory 的任意 call data

我们之前说过,钱包的接口是:

在 ERC-4337 中,钱包实际上并没有一个叫做 executeOp 的方法。

取而代之的是,user operation 上有一个 callData 字段:

这个字段会作为 call data 传给钱包。

对于一般的智能合约来说,这段数据的前四个字节会被解释为函数选择器,其余部分则是函数参数。

这意味着,除了必需的 validateOp 方法之外,钱包可以自行定义接口,user operation 也可以被用来调用钱包上的任意方法。

同理,在 ERC-4337 中,factory 合约实际上也没有 deployContract 方法。它们同样接收任意的 call data,此处来自 op 的 initCode 字段。

3. paymaster 和 factory 的紧凑数据

上文中我们说过,user operation 包含用于指定 paymaster 以及传给它的数据的字段:

在 ERC-4337 中,作为一种优化,这些字段被合并成了一个字段,其中该字段的前 20 个字节是 paymaster 地址,其余部分是数据:

factory 及传给它的数据也是同样的道理:虽然我们之前用了 factoryfactoryData 两个字段,但 ERC-4337 把它们合并成了一个字段 initCode

好了,你做到了!

希望你从中学到了很多关于账户抽象的知识。

你本可以自己发明账户抽象

错过了这个四部分系列的开头?回去从头读起吧!

  1. 账户抽象 第一部分:保护我们的资产
  2. 账户抽象 第二部分:用 Paymaster 赞助交易
  3. 账户抽象 第三部分:钱包创建
Background gradient

构建区块链应用

Alchemy 将最强大的 Web3 开发者产品和工具与资源、社区及专业支持结合在一起。