【项目学习】ERC-4337 抽象账户项目审计过程中需要注意的安全问题

发布时间 2023-07-08 20:57:12作者: 阿菜ACai

抽象账户是什么

抽象账户(也有叫合约钱包)是 EIP-4337 提案提出的一个标准。简单来说就是通过智能合约来实现一个“账户(account)”,在合约中自行实现签名验证的逻辑。这样,就使得该合约拥有了“签发交易”的能力。通过抽象账户签发的“交易”我们叫做用户操作(UserOperation,简称Op)。用户对Op进行签名以后,将其发给一个叫Bundler的链下程序,它负责将所有抽象账户所提交的Op打包,然后提交到链上合约EntryPoint中。EntryPoint合约收到Op后,会调用对应的抽象账户来验证其签名,验证通过后会将Op中的calldata取出,对抽象账户进行调用。整个简化版的流程就是这样,省略了诸如paymaster和aggregator之类的角色,想要更全面的了解的话可以阅读以下的文章。

参考文章:

当你阅读完上面的文章后,此时你应该对抽象账户有了一个大概的了解了,这时我们再来简单整理一下整个业务流程吧。

流程如下:

  1. 用户将 Op 进行签名,并发送给 Bundler;
  2. Bundler 调用 Entry Point 中的 handleOps 函数;
  3. EntryPoint 将 UserOperation 作为参数调用 Wallet Contract 中的 validateUserOp,验证所有需要验证的交易。只有在验证成功的情况下才会继续执行后续操作;
  4. 此时 Entry Point 会先确认 UserOperation 中指定的 Paymaster 的状况,例如是否拥有足够的 ETH 来支付这笔交易的手续费;
  5. 接着,Entry Point 调用 Paymaster Contract 中的 validatePaymasterUserOp,确认 Paymaster 愿意支付这笔交易的手续费。如果 Paymaster 愿意支付,那么这笔交易就会继续,否则就会失败;
  6. 然后调用 Wallet Contract 并执行 UserOperation 本身指定的内容;
  7. 接着调用 Paymaster Contract 的 Post-Op,Post-Op 处理直接赞助或划扣用户 ERC20 代币代付等自定义逻辑;
  8. Entry Point 在 Paymaster 质押的 ETH 余额中扣减交易所需支付的 gas fee;
  9. 最后 Entry Point 收集所有交易所需支付的 gas fee 总额 refund 给 Bundler ,交易执行完毕。

同时引用两张Stackup的流程图,帮助大家更直观的了解整个过程:
整个抽象账户项目的架构,各个角色之间的关系:
image

当用户发起一笔Op的时候,整个业务流程是如何运作的:
image

在审计过程中需要注意的安全问题

针对抽象账户这个项目的特点,在审计过程中有一些关键点需要特别留意一下。当然,具体的情况根据每个项目的不同而不同,这里只是抛砖引玉,审计工作还是要落实到具体的代码上。

签名问题

抽象账户发送一笔Op,需要进行两次签名检查。

  1. 第一个是在抽象账户中对Op进行签名检查,确保该Op是对应的抽象账户签发的。这个检查是必须要的。
  2. 第二个是用户使用paymaster的时候,需要提供paymaster所签名的信息进行校验。这个检查只有在使用paymaster服务的时候需要进行,不是必须的。

当进行以上这两种的签名与检查时,我们需要注意以下的问题:

  1. 签名是否能够避免重放攻击:具体包括抽象账户的重放攻击,跨链的重放攻击,针对仿盘的重放攻击,硬分叉的重放攻击。在进行审计时要留意签名内容是否包含抽象账户的 nonce 值, chianID,项目合约地址等信息。
  2. 签名的打包方式:在对签名内容进行打包时是否严谨,是否会出现不同的Op进行打包后得到相同字节码的情况。
  3. 抽象账户的签名验证实现:由于Op的签名是需要抽象账户自行实现的,需要针对其具体的实现方法进行分析,确保验证签名的过程严谨准确。

gas计算问题

Gas计算问题始终贯穿整个项目,Op 中采用了多个变量来规定gas上限,gas priority,gas 价格等。具体可以参考以下样例:

UserOperation 结构体,用于表示用户在智能合约中的操作请求:
  - sender:发送此请求的账户地址。
  - nonce:请求的唯一标识符,用于防止重复请求攻击。
  - initCode:如果设置了此字段,则表示要创建一个新的账户合约,并将其初始化为指定的字节码。
  - callData:要在此操作期间执行的方法调用数据。
  - callGasLimit:用于限制此调用允许使用的最大 gas 数量。
  - verificationGasLimit:用于验证此用户操作是否有效的所需 gas 量。
  - preVerificationGas:在 handleOps 方法中计算 gas 费用之前,添加到付款总额中的附加 gas 数量。这是为了覆盖批处理的开销。
  - maxFeePerGas 和 maxPriorityFeePerGas:与 EIP-1559 中的 gas 相关参数相同,用于计算最终的 gas 费用。
  - paymasterAndData:如果设置了此字段,则表示支付方地址和特定于支付方的数据。支付方将为交易支付费用,而不是发送请求的账户。
  - signature:发件人验证的签名,涵盖整个请求、EntryPoint 地址和链 ID。

审计过程中要确认每个gas的相关设置是否经过检查,取值是否安全。为了确保gas消耗在limit范围内,所以会在交易执行过程中计算实际gas消耗。在审计过程中需要确认gas消耗的计算是否严谨,是否与对应的limit值进行对比检查。其次是Op中的参数设置过大或过小时是否会造成资损或运行错误等问题。

代币计价问题

代币计价问题通常发生在EntryPoint提供了使用ERC20代币来支付gas费用,或paymaster合约提供了使用ERC20代币来代付gas费用的情况下。首先计算得出执行Op所需要总的gas消耗,然后通过预言机/Defi/签名中的价格来计算等价的ERC20代币,最后从抽象账户获取所需要的ERC20完成gas的代付。

在审计的过程中要注意的问题有:

  1. 用来代付的token是否在白名单的范围内,是否为正规的代币,是否存在使用恶意代币进行支付的隐患。
  2. 从预言机渠道获取代币价格时,需要注意价格的 decimal 和 token 对应的 decimal 在计算过程中是否使用正确。预言机地址是否限制在白名单中。预言机获取的价格是否具有时效性,避免获取到过时的价格。
  3. 从Defi中获取代币价格或兑换代币时,是否对滑点进行了限制,是否存在闪电贷影响代币兑换价格的风险。在swap操作中,tokenIn和tokenOut的计算与使用是否恰当。
  4. 在签名中获取价格时,要注意签名价格是否具有时效性,在价格剧烈波动的场景下这种策略是否会造成用户或者项目方的资产损失。签名的价格是否与代币一一对应。是否存在中心化风险。

钱包逻辑问题

合约钱包/抽象账户至少需要实现下面两个函数,在对钱包部分进行审计时,要重点关注以上两个函数的实现情况,以及验签是否有问题,有没有做好权限管理,在调用的过程中是否存在重入风险等。

  1. 校验 User Operation 签名的函数,以供 EntryPoint 在 validation loop 中调用。其中的签名方法由合约钱包自行实现。
  2. 发起交易的函数,以供EntryPoint 在 execution loop 中调用。

还有一个重点就是对钱包的代理模式进行检查,了解清楚其代理模式。

  1. 明确implement合约(逻辑合约)的替换方式。
  2. EntryPoint是否对钱包合约的implement有限定的要求,是否要求implement合约要在白名单的地址中选取,在与钱包进行交互时是否对implement进行检查,implement是否会在Op执行的过程中进行替换。
  3. 在钱包Proxy合约中的变量,是否能够通过修改implement的方式进行修改。如果能够被修改,是否会对业务流程造成影响,是否会存在安全隐患。

后记

以上就是在抽象账户审计过程中总结归纳的一些经验,不能说是一个全面的审计Check List,但是希望能够给初次接触此类项目的同学一下入手的角度。由于抽象账户这个概念涉及的内容很多,局限于个人的学识,这篇文章还存在很多没能覆盖到的方面,也希望各位师傅能够提出您的看法与建议,大家一起交流学习。