
慢雾:安全审计检查项之账户抽象钱包
前言
本文旨在为审计人员提供以 EIP4337 标准实现的账户抽象钱包的安全审计检查项,并提供部分针对性的审计指南。本文假设审计人员已经对 EIP4337 账户抽象标准和 EIP7562 账户抽象验证范围规则标准较为熟悉,因此不再赘述这两个标准。接下来,我们将简单介绍 EIP4337 架构与钱包交易执行流程。
架构
交易执行
在 EIP4337 设计标准中,首先由 EOA 签署 `UserOperation` 类型的数据,并通过 RPC 提交到一个单独的 Alt Mempools。此内存池独立于以太坊内存池,其汇总了用户提交的 UserOp 数据。Bundler 会从此内存池中提取 UserOp 以帮助用户执行,在执行之前将进行本地模拟,模拟失败的 `UserOp` 将被丢弃。所有的 UserOp 执行都由 Bundler 调用 EntryPoint 合约执行。EntryPoint 经过一系列验证后将调用用户的 AA 钱包,执行用户的 calldata。在此过程中,用户需要向 Bundler 支付交易上链执行的手续费,或者指定一个 Paymaster 进行代付。
执行详情
以下是 Bundler 通过 EntryPoint 调用用户钱包的详细过程,审计人员应熟悉此流程。
(https://www.figma.com/board/dWu1j2He9WaJin5pW6o3Hx/4337-Execution-Details_CN?node-id=0-1&t=Iy1930ilcXQY6m9v-1)
检查项
慢雾安全团队依据上述架构列出以下检查项,建议审计人员在对每个 4337 钱包进行安全审计时都确保其通过下述检查项:
1. 检查是否兼容所有 EVM 兼容链
大部分 AA 钱包可能不止部署在 Ethereum 主网,因为主网在上海升级后新增了 `PUSH0` 字节码,而 Solidity 在 0.8.20 版本及之后编译版本都默认为上海升级后的版本,因此其编译后的字节码可能不适用于所有的 EVM 兼容链。
审计人员在审计时应该检查合约编译使用的 Solidity 版本,或检查编译后的文件是否包含 `PUSH0` 字节码。在进行多链部署时,建议使用小于 0.8.20 版本的编译器,或者指定编译版本为 `paris`。
solc = "0.8.19"evm_version = "paris"
2. 检查接口实现与返回值是否符合 EIP4337 标准规范
EIP4337 标准规定钱包必须实现以下核心接口,其返回值 `validationData` 必须包含三个值:authorizer、validUntil 和 validAfter。
function validateUserOp (PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external returns (uint256 validationData);
同样的,代付合约 Paymaster 也必须实现以下核心接口,其中 `validatePaymasterUserOp` 的返回值 `validationData` 也必须包含三个值:authorizer、validUntil 和 validAfter。
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。
3. 检查钱包调用者是否可信
在 EIP4337 标准中,要求钱包或代付人实现的 validateUserOp、executeUserOp、validatePaymasterUserOp、postOp、数据执行接口等函数都应该只允许可信的 EntryPoint 进行调用,以避免钱包被未授权的使用导致资产丢失等风险。
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();
...
}
4. 检查是否实现手续费支付功能
在用户钱包中有充足原生代币情况下,其可以无需在 EntryPoint 中质押且无需指定代付人,即可使用钱包中的代币付款。付款费用由 EntryPoint 在调用 validateUserOp 函数时通过 `missingAccountFunds` 参数传入。因此审计人员需要检查钱包中的 validateUserOp 函数是否实现了向 EntryPoint 合约转账 `missingAccountFunds` 数额的原生代币的逻辑。
function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external virtual override returns (uint256 validationData) { ... _payPrefund(missingAccountFunds);}
5. 检查钱包创建方式
当用户钱包尚未创建时,其可以在 UserOp 中指定工厂进行钱包创建。工厂必须使用 `CREATE2` 创建钱包,以避免创建地址受到创建顺序的干扰。一些用户需要在还未完成创建之前就知道钱包地址,并向其转移资金以支付创建费用,因此审计人员在对工厂合约进行审计时,需要确保钱包创建方式使用了 `CREATE2`。
function createAccount(address owner,uint256 salt) public returns (SimpleAccount ret) { address addr = getAddress(owner, salt); uint256 codeSize = addr.code.length; if (codeSize > 0) { return SimpleAccount(payable(addr)); } ret = SimpleAccount(payable(new ERC1967Proxy{salt : bytes32(salt)}( address(accountImplementation), abi.encodeCall(SimpleAccount.initialize, (owner)) )));}
6. 检查重复创建同一钱包的返回值
EIP 4337 标准要求,对于已经创建的钱包,如果传入相同的数据,应返回同一地址。这是为了让客户端更容易查询地址,而无需知道钱包是否已部署,也为了避免非预期的创建失败或者创建非预期地址的钱包。
function createAccount(address owner,uint256 salt) public returns (SimpleAccount ret) { address addr = getAddress(owner, salt); uint256 codeSize = addr.code.length; if (codeSize > 0) { return SimpleAccount(payable(addr)); } ...}
7. 检查钱包创建时是否可被接管
在钱包创建前,用户可能会先向预创建地址转入手续费以供创建时支付费用,并且使得在创建同一地址时也不会交易失败。因此,审计人员需要检查钱包是否可以被抢先创建,如果可以,应该检查被抢先创建后的钱包所有权是否正确,并着重检查是否存在未参与地址计算的初始化参数,这些参数又能否被修改。
错误示例代码(entryPoint 不参与地址计算,可被抢先创建修改为恶意的 entryPoint):
function deployCounterFactualWallet(address _owner, address _entryPoint, address _handler, uint _index) public returns(address proxy){ bytes32 salt = keccak256(abi.encodePacked(_owner, address(uint160(_index)))); bytes memory deploymentData = abi.encodePacked(type(Proxy).creationCode, uint(uint160(_defaultImpl))); // solhint-disable-next-line no-inline-assembly assembly { proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) } require(address(proxy) != address(0), "Create2 call failed"); // EOA + Version tracking emit SmartAccountCreated(proxy,_defaultImpl,_owner, VERSION, _index); BaseSmartAccount(proxy).init(_owner, _entryPoint, _handler); isAccountExist[proxy] = true;}
8. 检查签名有效性验证
钱包必须支持 `validateUserOp` 接口以验证 EntryPoint 传入的 UserOp 的有效性,同样的,Paymaster 也必须支持 `validatePaymasterUserOp` 接口以验证代付信息的有效性。因此,审计人员必须在 validateUserOp/validatePaymasterUserOp 中严格检查 UserOp 中签名的有效性以避免钱包被恶意执行,或签名被重放。
function validateUserOp( PackedUserOperation calldata userOp, bytes32 userOpHash, uint256 missingAccountFunds) external virtual override returns (uint256 validationData) { _requireFromEntryPoint(); validationData = _validateSignature(userOp, userOpHash); _validateNonce(userOp.nonce); _payPrefund(missingAccountFunds);}
9. 检查是否正确实施了 ERC1271
账户抽象钱包可能实施 EIP1271 标准以支持验证来自合约的 1271 签名,标准规定了通过 `isValidSignature` 接口以实现具体的验证逻辑。审计人员需要严格检查 ERC1271 标准的实施是否符合规范,且确保签名验证逻辑安全可靠。
function isValidSignature(bytes32 _dataHash, bytes calldata _signature) public view override returns (bytes4) { // Caller should be a Safe ISafe safe = ISafe(payable(msg.sender)); bytes memory messageData = encodeMessageDataForSafe(safe, abi.encode(_dataHash)); bytes32 messageHash = keccak256(messageData); if (_signature.length == 0) { require(safe.signedMessages(messageHash) != 0, "Hash not approved"); } else { safe.checkSignatures(messageHash, _signature); } return EIP1271_MAGIC_VALUE;}
10. 检查向 EntryPoint 的质押代币是否可被永久锁定
账户抽象钱包为了支付执行费用,可能实现向 EntryPoint 质押 Native 代币的逻辑。同样的,为了提高声誉,代付合约 Paymaster 与钱包工厂合约也可能实现向 EntryPoint 质押原生代币的逻辑。EIP7562 标准要求质押实体的最小质押时间必须是 `MIN_UNSTAKE_DELAY`(1 天),因此通常质押时间是由调用者自行决定或合约硬编码决定。审计人员需要检查是否任何人都可以传入任意锁定时间 `unstakeDelaySec` 进行质押,这可能导致质押代币被永久锁定。
function addStake(uint32 unstakeDelaySec) external payable onlyOwner { entryPoint.addStake{value: msg.value}(unstakeDelaySec);}
11. 检查钱包是否可以不通过 EntryPoint 执行交易
EIP4337 标准要求 EntryPoint 通过直接调用钱包执行用户 calldata 或者调用 executeUserOp 函数以执行数据,并由钱包验证调用者与签名。因此,审计人员需要检查钱包是否允许在不通过 EntryPoint 的情况下执行调用者的数据。如果可以,审计人员需要检查钱包是否正确实施了权限检查,以避免钱包被执行恶意数据。
function execute(address dest, uint256 value, bytes calldata func) external { _requireFromEntryPointOrOwner(); _call(dest, value, func);}
12. 检查钱包是否只访问与发送者关联的存储字段
为了避免 Bundler 执行 UserOp,遭受 DoS,然后使得 Bundler 无法收回 gas 费成本,EIP7562 标准规定了只允许钱包访问与其相关联的存储,否则 Bundler 将不会接受此 UserOp。审计人员需要检查钱包中对存储的访问是否符合规定。
13. 检查执行失败后 Paymaster 的处理逻辑是否符合预期
在 EntryPoint 中,当 innerHandleOp 执行后,EntryPoint 将通过 _postExecution 函数进行后续的手续费处理逻辑,并调用 Paymaster 的 postOp 函数对手续费进行处理(例如:多退少补)。而当 innerHandleOp 执行失败时,也会通过 postExecution 进行费用处理,但此时不会触发 Paymaster 的 postOp 函数(即使用户指定了代付)。因此在审计 Paymaster 时,需要特别注意用户恶意使得 innerHandleOp 和 postOp 都执行失败的情况下,Paymaster 能否正确地处理费用,以避免 Paymaster 中的资金被耗尽。
14. 检查模块化钱包的实施是否安全
EIP4337 钱包可能支持用户自定义拓展钱包功能,如:运行社交恢复、合约升级、保险库等模块。审计人员需要检查钱包能否安全地对模块的“增加/移除/使用”进行管理。如果使用了 `DELEGATECALL` 运行模块,审计人员应确保钱包插槽存储的数据安全。
总结
上述基础检查项基于当前 EIP4337 的账户抽象标准,供审计人员对账户抽象钱包进行检查。不同的钱包实现不尽相同,且目前基于 EIP4337 的实施还处于早期阶段,因此审计人员仍需要根据钱包的实际实现情况进行严格地检查。对于正在开发的项目来说,慢雾安全团队建议开发者在开发过程中仔细考虑上述检查项。最后,文中相关链接可通过点击阅读原文跳转至 GitHub 查看。
比推快讯
更多 >>- 哈塞特:特朗普仍然希望政府关门问题得到友好解决
- 美联储 10 月降息 25 个基点的概率升至 99%
- Tether 向 OpenSats 捐赠 25 万美元遭 Jack Dorsey 质疑
- 公链活跃度近 7 日排行榜:Solana 稳居首位
- OKX 创始人 Star:不会“配合”或“默许”意见领袖喊单拉盘,将坚持中立与透明
- Strike 创始人:比特币对流动性最敏感会引领反弹,最简单的方式就是“买入”
- 富途控股招聘 DeFi 技术负责人,要求具备 8 年以上开发经验
- Aave V4 将支持新抵押资产类别,涵盖股票、ETF 和房地产等
- 彭博社:抓住 AI 热潮的加密矿企正逐渐放弃比特币
- 泰国和新加坡正调查遭美英制裁的柬埔寨太子控股集团
- 早期 UNI 投资方地址 5 小时内向 Wintermute 转出 169.7 万枚 UNI,价值 1029 万美元
- Four.Meme 将上线代币名称保护功能,防止重复创建与混淆
- CZ 参与 APRO 中文名征集活动
- AnteX 测试网正式上线,首赛季积分活动同步开启
- 交易员 Eugene:预计市场将会继续下跌而非反弹,尤其是山寨币
- 何一再次回应币安 Alpha 空投“难抢”称将解决该问题
- 分析师:比特币走势反转需价格与未平仓量同步增长,或明显资金流入
- 数据:过去 24h Binance 净流入 1.61 亿 USDT
- 美 SEC 主席:美国在加密领域已落后十年,为吸引创新建立监管框架是首要任务
- Aster 更新 Stage 3 交易积分规则:现货和永续合约交易量均包括在内,严禁刷量等欺诈行为
- 一新地址从 Binance 提出 74.4 万枚 LINK,约 1244 万美元
- 特朗普次子:我从不和父亲讨论加密货币
- COAI 上大规模盈利地址疑为量化团体,非项目方操作
- imToken 发布安全声明:钱包私钥随机性安全,无用户受影响
- 麻吉于 20 分钟前使用最后的保证金 40 倍做多 BTC,仓位价值仅 160 万美元
- 数据:过去 24 小时全网爆仓 5.96 亿美元,多单爆仓 3.35 亿美元,空单爆仓 2.61 亿美元
- 币安 Alpha:Merlin Chain(MERL)第二波空投开启,申领门槛 240 积分
- 某鲸鱼地址今日加仓 2825 枚 ETH,近 3 日囤积 4332 枚 ETH
- 数据:过去 7 天 USDC 流通量约增加 4 亿枚
- Binance:至少拥有 240 分的用户可于今日 16 时领取 Alpha 空投
- 智能合约安全平台 AgentLISA 完成 1200 万美元融资,加速智能合约安全革新
- COPX 将于 10 月 18 日 21:00 举办 X Space,解析平台的创新经济设计及探讨佣金代币化如何重塑交易激励机制
- 两只鲸鱼分别增持并质押 700 万枚和 900 万枚 FF,合计约 200 万美元
- SLERF 合约与链上价格现 4 倍价差,或由 CEX 暂停充值引发套利波动
- Vitalik 发文呼吁 ZK 与 FHE 开发者以“性能比率”衡量效率损耗
- Ethena 创始人关联地址过去 3 天从 CEX 提币 4800 万枚 ENA
- Tom Lee:市场谨慎情绪上升,但或迎反向机会
- 数据:当前加密恐慌贪婪指数为 22,处于极度恐慌状态
- Binance 宣布下架合约后,SLERF 短时上下插针涨跌波动超 20%
- 数据:某鲸鱼清仓 2.2278 万亿枚 PEPE,持有 100 天亏损 102 万美元
- Falcon Finance 将于 10 月推出首个 NFT 系列Perryverse
- OpenSea CEO:计划在 2026 年第一季度推出 SEA 代币
- 数据:6 个黑客钱包近期因高买低卖 ETH 而损失超 1,340 万美元
- 麻吉再次加仓其 ETH 多单,仓位规模升至 550 万美元
- Solana 推出 SIMD-0337 提案,引入快速领导者交接机制提升网络性能
- 聪明钱 Hanmancheol 实盘做空 COAI 爆仓,三日亏损逾 137 万美元
- 数据:Hyperliquid 平台鲸鱼当前持仓 52.44 亿美元,多空持仓比为 0.86
- 疑似 Blockchain Capital 地址向币安等 CEX 充值 2800 万枚 SKY
- 昨日比特币现货 ETF 净流出 3.6402 亿美元
- 某 TRUMP 鲸鱼地址凌晨从 OKX 提币价值 400 万美元的 TRUMP
比推专栏
更多 >>观点
比推热门文章
- 哈塞特:特朗普仍然希望政府关门问题得到友好解决
- 美联储 10 月降息 25 个基点的概率升至 99%
- Tether 向 OpenSats 捐赠 25 万美元遭 Jack Dorsey 质疑
- 公链活跃度近 7 日排行榜:Solana 稳居首位
- OKX 创始人 Star:不会“配合”或“默许”意见领袖喊单拉盘,将坚持中立与透明
- Strike 创始人:比特币对流动性最敏感会引领反弹,最简单的方式就是“买入”
- 富途控股招聘 DeFi 技术负责人,要求具备 8 年以上开发经验
- Aave V4 将支持新抵押资产类别,涵盖股票、ETF 和房地产等
- 彭博社:抓住 AI 热潮的加密矿企正逐渐放弃比特币
- 泰国和新加坡正调查遭美英制裁的柬埔寨太子控股集团