前言
本文旨在为审计人员提供以 EIP4337 标准实现的账户抽象钱包的安全审计检查项,并提供部分针对性的审计指南。本文假设审计人员已经对 EIP4337 账户抽象标准和 EIP7562 账户抽象验证范围规则标准较为熟悉,因此不再赘述这两个标准。接下来,我们将简单介绍 EIP4337 架构与钱包交易执行流程。
架构与交易执行
在 EIP4337 设计标准中,首先由外部账户(EOA)签署 UserOperation
类型的数据,并通过 RPC 提交到一个单独的 Alt Mempools。此内存池独立于以太坊内存池,其汇总了用户提交的 UserOp
数据。捆绑器(Bundler)会从此内存池中提取 UserOp
以帮助用户执行,在执行之前将进行本地模拟,模拟失败的 UserOp
将被丢弃。所有的 UserOp
执行都由捆绑器调用 EntryPoint
合约执行。EntryPoint
经过一系列验证后将调用用户的账户抽象(AA)钱包,执行用户的 calldata。在此过程中,用户需要向捆绑器支付交易上链执行的手续费,或者指定一个支付代理(Paymaster)进行代付。
以下是捆绑器通过 EntryPoint
调用用户钱包的详细过程,审计人员应熟悉此流程。
检查项
慢雾安全团队依据上述架构列出以下检查项,建议审计人员在对每个 4337 钱包进行安全审计时都确保其通过下述检查项:
-
兼容所有 EVM 兼容链
- 检查是否兼容所有 EVM 兼容链。大部分 AA 钱包可能不止部署在 Ethereum 主网,因为主网在上海升级后新增了
PUSH0
字节码,而 Solidity 在 0.8.20 版本及之后编译版本都默认为上海升级后的版本,因此其编译后的字节码可能不适用于所有的 EVM 兼容链。 - 审计人员在审计时应该检查合约编译使用的 Solidity 版本,或检查编译后的文件是否包含
PUSH0
字节码。在进行多链部署时,建议使用小于 0.8.20 版本的编译器,或者指定编译版本为paris
。 solidity
solc = 0.8.19
evm_version = paris
- 检查是否兼容所有 EVM 兼容链。大部分 AA 钱包可能不止部署在 Ethereum 主网,因为主网在上海升级后新增了
-
接口实现与返回值符合 EIP4337 标准规范
- EIP4337 标准规定钱包必须实现以下核心接口,其返回值
validationData
必须包含三个值:authorizer
、validUntil
和validAfter
。solidity
function validateUserOp (PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData);
- 同样,代付合约 Paymaster 也必须实现以下核心接口,其中
validatePaymasterUserOp
的返回值validationData
也必须包含三个值:authorizer
、validUntil
和validAfter
。solidity
function validatePaymasterUserOp (PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 maxCost) external returns (bytes memory context, uint256 validationData);
function postOp (PostOpMode mode, bytes calldata context, uint256 actualGasCost, uint256 actualUserOpFeePerGas) external;
- 当签名验证失败时,
authorizer
需要返回SIG_VALIDATION_FAILED
(即 1 值),验证成功时,authorizer
需要返回SIG_VALIDATION_SUCCESS
(即 0 值),而不是revert
。如果是其他情况导致的失败交易都必须revert
。
- EIP4337 标准规定钱包必须实现以下核心接口,其返回值
-
检查钱包调用者是否可信
- 在 EIP4337 标准中,要求钱包或代付人实现的
validateUserOp
、executeUserOp
、validatePaymasterUserOp
、postOp
、数据执行接口等函数都应该只允许可信的EntryPoint
进行调用,以避免钱包被未授权的使用导致资产丢失等风险。 solidity
function entryPoint() public view virtual override returns (IEntryPoint) { return _entryPoint;}
function execute(address dest, uint256 value, bytes calldata func) external { _requireFromEntryPointOrOwner(); _call(dest, value, func);}
function executeBatch(address[] calldata dest, uint256[] calldata value, bytes[] calldata func) external { _requireFromEntryPointOrOwner(); ...}
- 在 EIP4337 标准中,要求钱包或代付人实现的
-
检查是否实现手续费支付功能
- 在用户钱包中有充足原生代币情况下,其可以无需在
EntryPoint
中质押且无需指定代付人,即可使用钱包中的代币付款。付款费用由EntryPoint
在调用validateUserOp
函数时通过missingAccountFunds
参数传入。 - 审计人员需要检查钱包中的
validateUserOp
函数是否实现了向EntryPoint
合约转账missingAccountFunds
数额的原生代币的逻辑。 solidity
function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external virtual override returns (uint256 validationData) { ... _payPrefund(missingAccountFunds);}
- 在用户钱包中有充足原生代币情况下,其可以无需在
-
检查钱包创建方式
- 当用户钱包尚未创建时,其可以在
UserOp
中指定工厂进行钱包创建。工厂必须使用CREATE2
创建钱包,以避免创建地址受到创建顺序的干扰。 - 一些用户需要在还未完成创建之前就知道钱包地址,并向其转移资金以支付创建费用,因此审计人员在对工厂合约进行审计时,需要确保钱包创建方式使用了
CREATE2
。 solidity
function createAccount(address owner,uint256 salt) public returns (SimpleAccount ret) { address addr = getAddres
- 当用户钱包尚未创建时,其可以在
结论
通过上述检查项,审计人员可以确保账户抽象钱包在执行过程中具备足够的安全性和可靠性。这些检查项不仅涵盖了合约的基本功能和接口实现,还确保了钱包的调用者可信和手续费支付功能的实现。通过遵循这些检查项,审计人员可以有效降低账户抽象钱包的安全风险,保护用户资产安全。
参考文献
- EIP4337: Link to EIP4337 standard
- EIP7562: Link to EIP7562 standard
- Solidity Documentation: Link to Solidity documentation
通过详细的研究和专业分析,本文为审计人员提供了全面的指导,帮助他们确保账户抽象钱包的安全性和可靠性。希望本文能为您的审计工作提供有价值的参考。
Views: 0