{"content":{"title":"理解账户抽象 #4：聚合签名","body":"> * 原文链接： https://www.alchemy.com/blog/account-abstraction-aggregate-signatures\r\n> * 译文出自：[登链翻译计划](https://github.com/lbc-team/Pioneer)\r\n> * 译者：[Tiny 熊](https://learnblockchain.cn/people/15)\r\n> * 本文永久链接：[learnblockchain.cn/article…](https://learnblockchain.cn/article/5483)\r\n\r\n\r\n\r\n\r\n\r\n这里理解账户抽象的第 4 篇，前面几篇为：\r\n1. [从零开始设计智能合约钱包](https://learnblockchain.cn/article/5426)； \r\n\r\n2. [使用Paymaster赞助交易](https://learnblockchain.cn/article/5432)；\r\n\r\n3. [使用 create2 创建钱包](https://learnblockchain.cn/article/5442)；\r\n\r\n4. [（本文）使用 聚合签名](https://learnblockchain.cn/article/5483)\r\n\r\n\r\n\r\n## 聚合签名\r\n\r\n我们目前的实现是分别验证捆绑在一起的每个用户操作。这是一种非常直接的验证方式，但有可能造成浪费。检查签名最终可能会导致昂贵的Gas，因为这样做需要相当多的密码运算。\r\n\r\n**我们可以用一个签名而不是许多签名来同时验证许多操作，这不是很好吗**？\r\n\r\n要做到这一点取决于密码学的一个概念，**聚合签名（aggregate signatures）**。\r\n\r\n一个支持聚合的签名方案提供了一种方法，给定多个用不同密钥签名的信息，生成一个单一的组合签名，这样，验证组合签名就意味着所有的组成签名也是有效的。\r\n\r\n支持聚合的签名方案的一个常见例子是[BLS](https://en.wikipedia.org/wiki/BLS_digital_signature)。\r\n\r\n这种优化对实现rollup特别有用，因为rollup的主要目标是数据压缩，而签名聚合让我们压缩签名部分数据大小。\r\n\r\n关于签名聚合节省空间的更多信息，请参见[Vitalik关于这个问题的推特](https://twitter.com/VitalikButerin/status/1554983955182809088)。\r\n\r\n### 聚合器的介绍\r\n\r\n马上，我们就会看到，并不是一个捆绑包（bundle）中的所有用户操作都可以将其签名聚合在一起。请记住，一个钱包被允许使用任何想要的逻辑来验证给定的签名，所以在同一个捆绑中可能有各种签名方案。\r\n\r\n由于我们可能无法聚合不同方案的签名，我们的捆绑包最终会有几组操作，每组使用不同的聚合方案或根本没有聚合方案。\r\n\r\n由于我们需要在链上代表各种聚合方案，每个方案都有自己的逻辑，我们将用一个合约来代表每个聚合方案，我们称之为**聚合器**。\r\n\r\n聚合方案是指：如何将多个签名合并成一个签名以及如何验证合并后的签名 ，所以聚合器将这两个功能作为函数公开：\r\n\r\n```php\r\ncontract Aggregator {\r\n  function aggregateSignatures(UserOperation[] ops)\r\n    returns (bytes aggregatedSignature);\r\n\r\n  function validateSignatures(UserOperation[] ops, bytes signature);\r\n}\r\n```\r\n\r\n![img](https://img.learnblockchain.cn/2023/03/05/59974.jpeg)\r\n\r\n聚合器合约将来自多个用户的操作组合成一个具有单一签名的组。\r\n\r\n由于每个钱包都在定义自己的签名方案，因此由每个钱包决定它与哪个聚合器兼容，如果有的话。\r\n\r\n**如果一个钱包想参与聚合，它得暴露一个方法来选择它的聚合器：**\r\n\r\n```php\r\ncontract Wallet {\r\n  // ...\r\n\r\n  function getAggregator() returns (address);\r\n}\r\n```\r\n\r\n使用这个新的`getAggregator`方法，捆绑者可以将具有相同聚合器的操作分组，并使用该聚合器的aggregateSignatures方法为它们计算一个组合签名。\r\n\r\n**一个组可能看起来像这样：**\r\n\r\n```css\r\nstruct UserOpsPerAggregator {\r\n  UserOperation[] ops;\r\n  address aggregator;\r\n  bytes combinedSignature;\r\n}\r\n```\r\n\r\n> 如果一个捆绑者拥有关于特定聚合器的链外知识，它可以通过硬编码签名聚合算法的原生版本进行优化，而不是作为EVM代码运行`aggregateSignatures`。\r\n\r\n接下来，我们需要更新入口点合约以利用新的聚合器。\r\n\r\n回顾一下，入口点有一个`handleOps`方法，它接收一个用户操作（OP）列表。\r\n\r\n**我们将给它一个新的方法，`handleAggregatedOps`，它做同样的事情，但接受按聚合器分组的操作(Ops)：**。\r\n\r\n```javascript\r\ncontract EntryPoint {\r\n  function handleOps(UserOperation[] ops);\r\n\r\n\tfunction handleAggregatedOps(UserOpsPerAggregator[] ops);\r\n\r\n  // ...\r\n}\r\n```\r\n\r\n这个新方法，`handleAggregatedOps`，与handleOps的工作原理基本相同。唯一的区别是它的验证步骤。\r\n\r\nhandleOps通过调用每个钱包的`validateOp`方法进行验证，而`handleAggregatedOps`将调用聚合器的`validateSignatures`方法对每个组的组合签名使用该组的聚合器。\r\n\r\n![img](https://img.learnblockchain.cn/2023/03/05/14834.jpeg)\r\n\r\n> 执行器(executor)使用聚合器(aggregator)在将用户操作发送到入口点之前将它们组合在一起，因此它们可以同时被验证。\r\n\r\n我们几乎完成了!\r\n\r\n但这里有一个问题，现在已经很熟悉了。\r\n\r\n捆绑者想要模拟验证，并检查聚合器是否会在将一组操作纳入捆绑之前验证这些操作，因为如果验证失败，捆绑者就会被迫支付Gas费。但一个具有任意逻辑的聚合器在模拟过程中很容易成功，但在执行过程中却会失败。\r\n\r\n我们将以与paymasters和工厂完全相同的方式来解决这个问题：我们限制聚合器可以访问哪些[存储](https://docs.alchemy.com/docs/smart-contract-storage-layout)以及可以使用哪些操作码，并要求它在进入点中存入ETH，除非它不访问存储。\r\n\r\n这就是聚合签名的内容!\r\n\r\n### 总结\r\n\r\n\r\n\r\n我们在这里所创建的或多或少是[ERC-4337的完整架构](https://eips.ethereum.org/EIPS/eip-4337)!在细节上有一些差异，比如一些方法的名称和参数，但没有我认为是架构上的差异。如果我的工作做得很好，你现在应该能够读懂真正的ERC-4337，并理解发生了什么。\r\n\r\n\r\n\r\n## 补遗与ERC-4337的区别\r\n\r\n虽然我们已经有了账户抽象的整体架构，但ERC-4337背后的聪明人想到了一些与我们上述描述略有不同的东西。\r\n\r\n让我们来看看其中的一些差别：\r\n\r\n### 1. 验证时间范围\r\n\r\n上面，我们文中对钱包的`validateOp`和paymaster的`validatePaymasterOp`的返回类型相当模糊。ERC-4337找到了一个很好的方法来利用这个。\r\n\r\n钱包非常想做的事情是只允许用户操作在一定时间内有效。否则，一个流氓捆绑者可以在该操作保持很长时间，然后在对捆绑者有利的时候将其纳入捆绑。\r\n\r\n钱包可能想通过在验证过程中检查`TIMESTAMP`来防御这种情况，以确保它不会在太久之后执行，但钱包做不到，因为我们在验证过程中禁止`TIMESTAMP`来阻止模拟的不准确。这意味着钱包需要另一种方式来表明在什么时候操作是有效的。\r\n\r\n**因此，ERC-4337给了validateOp一个返回值，钱包可以用它来选择一个时间范围：**\r\n\r\n```php\r\ncontract Wallet {\r\n  function validateOp(UserOperation op, uint256 requiredPayment)\r\n    returns (uint256 sigTimeRange);\r\n  // ...\r\n}\r\n```\r\n\r\n这个返回值表示操作有效的时间范围，是两个挨着的8字节整数。\r\n\r\n来自ERC-4337的另一个说明：钱包应该从`validateOp`返回一个哨兵值，而不是在验证失败的情况下回退，这有助于Gas估计，因为[eth_estimateGas](https://docs.alchemy.com/reference/eth-estimategas)不会告诉你在一个回退的交易中使用了多少Gas。\r\n\r\n### 2. 钱包和工厂的任意调用数据\r\n\r\n我们的钱包的接口是：\r\n\r\n```javascript\r\ncontract Wallet {\r\n  function validateOp(UserOperation op, uint256 requiredPayment);\r\n  function executeOp(UserOperation op);\r\n}\r\n```\r\n\r\n在ERC-4337中，钱包的实际上并没有一个名为`executeOp`的方法。\r\n\r\n**相反，用户操作（UserOperation）有一个callData字段：**\r\n\r\n```cpp\r\nstruct UserOperation {\r\n  // ...\r\n  bytes callData;\r\n}\r\n```\r\n\r\n这将作为调用数据传递给钱包。\r\n\r\n对于一个典型的智能合约，这个数据的前四个字节将被解释为一个函数选择器，其余的是函数参数。\r\n\r\n这意味着，除了必要的验证Op方法外，钱包可以定义自己的接口，用户操作可以用来调用钱包上的任意方法。\r\n\r\n按照同样的思路，在ERC-4337中，工厂合约实际上并没有`deployContract`方法。他们也接收任意的调用数据，在此案例中，是来自操作的`initCode`字段。\r\n\r\n### 3. paymaster 和工厂的数据压缩\r\n\r\n上面我们说过，用户操作包含的字段可以指定一个付款人，以及要传递给它的数据：\r\n\r\n```cpp\r\nstruct UserOperation {\r\n  // ...\r\n  address paymaster;\r\n  bytes paymasterData;\r\n}\r\n```\r\n\r\n**在ERC-4337中，这些字段被合并为一个字段，作为一种优化，其中字段的前20个字节是付款人地址，其余的是数据：**\r\n\r\n```cpp\r\nstruct UserOperation {\r\n  // ...\r\n  bytes paymasterAndData;\r\n}\r\n```\r\n\r\n对于工厂和发送到工厂的数据也是如此：虽然我们使用两个字段factory和factoryData，但ERC-4337将这些字段合并为一个字段`initCode`。\r\n\r\n关于账户抽象的分享到这里了， 我们希望你学到了很多关于账户抽象的知识。"},"author":{"user":"https://learnblockchain.cn/people/15","address":"0x49c8C7b892f82eedc98551CCb69745005A2FD079"},"history":"QmWP72YRq4eiManDMVEKLyKNS3gZw1yj6RPk2JFWgtSvbP","timestamp":1678066612,"version":1}