---
title: "账户抽象第 4 部分：聚合签名"
description: "了解 ERC-4337 在聚合签名方面的突破。使用单个加密签名简化多个 user op 的验证，提升交易效率。"
---

## 聚合签名

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

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

要做到这一点，需要依赖密码学中的一个概念——**聚合签名（aggregate signatures）**。

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

支持聚合的签名方案的一个常见例子是 [BLS](https://en.wikipedia.org/wiki/BLS_digital_signature)。

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

关于签名聚合带来的空间节省，可参见 [Vitalik 关于此话题的推文](https://twitter.com/VitalikButerin/status/1554983955182809088)。

### 引入 aggregator

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

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

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

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

<ImageBlock
  src="https://media.alchemy.com/1704793497-aggregator-contract-combining-user-ops-to-single-signature.jpeg"
  alt="聚合器合约将多个用户的操作合并为一组，使用单一签名。"
  width={960}
  height={540}
  caption="聚合器合约将多个用户的操作合并为一组，使用单一签名。"
/>

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

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

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

**一个分组大致如下：**

<CalloutBlock>

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

</CalloutBlock>

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

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

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

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

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

<ImageBlock
  src="https://media.alchemy.com/1703863779-account-abstraction-key-concepts.jpeg"
  alt="图示：executor 借助聚合器将用户操作分组，再发送给 entry point 进行批量验证"
  width={960}
  height={540}
  caption="executor 使用聚合器将操作分组，再发送给 entry point，以便一次性完成验证。"
/>

我们快完成了！

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

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

我们会用和处理 paymaster、factory 时完全相同的方法来解决这个问题：限制 aggregator 能访问的[存储](https://www.alchemy.com/docs/smart-contract-storage-layout)以及能使用的操作码，并要求它在入口点中质押 ETH，除非它不访问存储。

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

### 总结

我们在这里构建出来的东西，基本上就是 [ERC-4337 的完整架构](https://eips.ethereum.org/EIPS/eip-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](https://www.alchemy.com/docs/chains/ethereum/ethereum-api-endpoints/eth-estimate-gas) 无法告诉你一笔 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 及传给它的数据也是同样的道理：虽然我们之前用了 `factory` 和 `factoryData` 两个字段，但 ERC-4337 把它们合并成了一个字段 `initCode`。

好了，你做到了！

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

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

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

1. [账户抽象 第一部分：保护我们的资产](https://www.alchemy.com/overviews/what-is-account-abstraction)
1. [账户抽象 第二部分：用 Paymaster 赞助交易](https://www.alchemy.com/overviews/what-is-account-abstraction-paymasters)
1. [账户抽象 第三部分：钱包创建](https://www.alchemy.com/overviews/what-is-account-abstraction-wallet-creation)
