跳至内容
0%

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

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

聚合签名

我们目前的实现是分别验证 bundle 中的每个 user op。这是一种很直接的验证思路,但可能造成浪费。检查签名在 gas 上可能相当昂贵,因为这需要不少密码学运算。

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

要做到这一点,需要用到密码学中的一个概念——聚合签名。

一种支持聚合的签名方案提供了这样一种方式:给定多条用不同密钥签名的消息,可以生成一个组合签名,使得验证这个组合签名就意味着所有构成它的签名也都有效。

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

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

关于签名聚合带来的空间节省,可以参考 Vitalik 关于这个话题的推文。

引入 aggregator

我们马上会发现,一个 bundle 中的 user op 并非都能把签名聚合在一起。别忘了,钱包可以使用任意逻辑来验证给定的签名,所以同一个 bundle 里可能存在多种签名方案。

由于我们大概率无法把不同方案的签名聚合在一起,我们的 bundle 最终会包含若干组 op,每组使用一种独立的聚合方案,或者根本不使用聚合方案。

由于我们需要在链上表示各种聚合方案,并各自带有自己的逻辑,我们会用一个合约来表示每种聚合方案,我们称之为 aggregator。

一种聚合方案由两部分定义:如何把多个签名合并为一个,以及如何验证合并后的签名,所以 aggregator 会把这两个功能作为方法暴露出来:

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

由于每个钱包定义了自己的签名方案,因此是否兼容某个 aggregator、兼容哪个 aggregator,由每个钱包自行决定。

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

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

一个分组可能是这样的:

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

接下来,我们需要更新 entry point 合约,以使用新的 aggregator。

回忆一下,entry point 有一个 handleOps 方法,接收一个 op 列表。

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

新方法 handleAggregatedOps 的工作方式和 handleOps 大体相同,唯一的区别在于验证这一步。

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

示意图:执行器使用聚合器将用户操作分组,再发送至入口点进行批量验证
执行器使用聚合器将操作分组,再发送至入口点,以便同时完成验证。

我们差不多要完成了!

不过这里还有一个到现在应该已经很熟悉的问题。

bundler 想在把一组 op 加入 bundle 之前,先模拟验证并检查 aggregator 是否会通过验证,因为一旦验证失败,bundler 就得自己承担 gas 费用。但一个逻辑任意的 aggregator 完全可能在模拟时成功,却在实际执行时失败。

我们会用和 paymaster、factory 完全相同的方式来解决这个问题:限制 aggregator 能访问的存储和能使用的操作码,并要求它在 entry point 中质押 ETH,除非它不访问存储。

聚合签名的内容就是这些!

总结

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

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

附录:与 ERC-4337 的差异

虽然我们已经理清了账户抽象的整体架构,但 ERC-4337 背后的这些聪明人还想到了一些和我们上面描述的略有不同的地方。

我们来看看其中的一些吧!

1. 验证的时间范围

前面,我对钱包的 validateOp 和 paymaster 的 validatePaymasterOp 的返回类型说得比较含糊。ERC-4337 找到了一个很好的方式来利用这一点。

钱包很希望能做到的一件事,是只允许一个 user op 在某段时间内有效。否则,一个恶意的 bundler 可能把这个 operation 扣着很长时间,然后在对自己有利的时间点把它加入 bundle。

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

因此,ERC-4337 给 validateOp 一个返回值,钱包可以用它来选择一个时间范围:

这个返回值用两个前后排列的 8 字节整数来表示该 operation 有效的时间范围。

再补充一点 ERC-4337 的做法:钱包在验证失败时应该从 validateOp 返回一个哨兵值,而不是 revert,这有助于 gas 估算,因为 eth_estimateGas 不会告诉你一笔 revert 的交易用了多少 gas。

2. 钱包和 factory 的任意调用数据

前面我们说钱包的接口是:

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

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

这会作为调用数据传给钱包。

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

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

同理,在 ERC-4337 中,factory 合约实际上也没有 deployContract 方法。它们同样接收任意的调用数据,这里是来自 op 的 initCode 字段。

3. paymaster 和 factory 的紧凑数据

前面我们说 user operation 包含一些字段,用来指定 paymaster 以及传给它的数据:

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

对于 factory 和传给它的数据也是同样的情况:我们原来用了 factory 和 factoryData 两个字段,而 ERC-4337 把它们合并成了单个字段 initCode。

好了,你做到了!

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

你也能发明账户抽象

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

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

构建区块链应用

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