本文是机制说明,不是事件报道。规范依据为 EIP-7702 定稿版本。
TL;DR
7702 的签名本质上是一份可被任何人提交的代码控制授权,不是一笔交易。
- 签名者只对三个字段负责:
chain_id、委托目标address、账户nonce。签名对象是keccak256(0x05 ‖ rlp([chain_id, address, nonce]))。 - 它不是 EIP-712。 没有
domainSeparator、没有primaryType、没有字段名 —— 除非固件专门实现0x05的解析,用户看到的就是一串 32 字节哈希。 - 没有过期时间。
nonce是唯一能让一张已签名的授权失效的东西。 - 所以 nonce spray 的真正目的不是提高命中率,而是让「取消」失去意义 —— 烧掉一个 nonce 只废掉一张,其余仍然有效。
- 即使外层交易执行失败,委托也不会回滚。 一笔看起来「失败」的 Type-4 交易,代码可能已经装好了。
- 任何持有 ETH 的账户都能替签名者提交并付 gas。签名与上链是分离的两件事。
一、签名对象:三个字段、一个魔数
一条授权元组由六个值组成,但真正被签名的只有前三个:
| 字段 | 长度 | 含义 |
|---|---|---|
chain_id |
32 字节 | 授权生效的链;0 表示所有链通用 |
address |
20 字节 | 委托目标合约;0x0 表示清除委托 |
nonce |
8 字节 | 必须等于签名账户当时的 nonce |
y_parity |
1 字节 | secp256k1 恢复标识(0/1) |
r |
32 字节 | ECDSA 签名 |
s |
32 字节 | ECDSA 签名,且必须 ≤ secp256k1n/2(EIP-2 低 s 要求) |
签名哈希的定义非常直接:
msg = keccak256(0x05 ‖ rlp([chain_id, address, nonce]))0x05 是 7702 专用的魔数,用来和普通交易签名(0x00/0x01/0x02/0x03)区分开。
它不是 EIP-712 的 domainSeparator 结构,也没有 primaryType、字段名和钱包可渲染的语义。 结果是:硬件钱包和签名界面拿到的是一串 32 字节哈希,除非固件专门实现 0x05 的解析,否则用户实际是在盲签一个自己看不懂的授权。
这是 7702 与传统交易签名最本质的区别。 普通交易签名至少绑定了 to、value、data、gas;7702 授权只绑定「把账户代码指向谁」。
它不承诺目标合约会做什么,也不承诺代码之后不会变 —— 如果目标地址是可升级代理,签名时是良性实现,升级后就可以变成 sweep 合约。
两个边界值得记住:authority 必须能通过 ecrecover 恢复,因此合约账户不能作为授权者;授权签名本身不能广播,必须由一条 Type-4 交易携带,但携带者可以是任何人。
二、上链处理:两层签名与九个步骤
一条 Type-4 交易里其实有两层签名:
- 发送者签名 —— 签整条交易的 RLP 载荷(含
authorization_list),决定谁付 gas、谁的 nonce 被消耗; - 授权者签名 —— 对每个元组单独签名,决定哪个账户的代码被改写。
两者可以完全无关。 这正是「赞助」(sponsorship)的设计初衷:dApp 替用户付 gas,用户只签名授权。也正是币安案中 operator 能替 Binance 14 提交授权的原因。
链上处理顺序是:先递增发送者 nonce,再处理授权列表,最后执行 calldata。对每个元组依次执行九步:
1. chain_id == 0 或 == 当前链 ID
2. nonce < 2^64 - 1
3. authority = ecrecover(msg, y_parity, r, s),且 s <= secp256k1n/2
4. 把 authority 加入 accessed_addresses(预热)
5. authority 的代码为空,或已经是 0xef0100 开头的委托
6. authority 的当前 nonce == 元组 nonce
7. 若 authority 非空,向全局退款计数器加 12,500 gas
8. 写入 code = 0xef0100 ‖ address;若 address 为 0,则清空代码
9. authority.nonce += 1任何一步失败,只跳过该元组,不回滚整笔交易,继续处理下一条。同一 authority 出现多条有效元组时,以最后一条有效的地址为准。
Gas 结构解释了那笔 1,258,562
规范定义两个常量:PER_EMPTY_ACCOUNT_COST = 25,000,PER_AUTH_BASE_COST = 12,500。
预收按条数,退款按有效性:交易内在成本里按 25,000 × 授权列表长度预收,只对 authority 非空的元组向全局退款计数器加 12,500。
于是币安那笔授权交易的账单可以精确复原:
50 × 25,000 = 1,250,000 50 条元组预收
− 12,500 唯一有效元组命中已存在账户
+ 21,000 交易内在 gas
+ 62 calldata 等
= 1,258,562一分不差。 这条账还顺带说明了一件事:49 条诱饵不是免费的,每条实打实烧掉 25,000 gas,而发送者为真假元组一并付费。
三、nonce:唯一的「有效期」
7702 没有过期时间。nonce 是唯一能让一张已签名的授权失效的东西。 理解这一点,才能理解整个攻防博弈。
第一,授权 nonce 与普通交易 nonce 是同一个计数器。 签署并上链一条授权,会把账户 nonce 推高 1;这会让一笔同 nonce 的待处理普通交易永久失效。反过来,如果账户先发了一笔普通交易,之前签好的同 nonce 授权也会失效。
第二,多条元组按顺序校验,nonce 会边处理边涨。 以币安案的窗口(18,914,775–18,914,824,实际 nonce 809)为例:
| 排列 | 结果 | nonce 变化 |
|---|---|---|
| 升序 775→824 | 775–808 过低跳过,809 命中后 810…824 依次生效 | 连推 16 次(命中那条 + 其后 15 条) |
| 降序 824→775 | 824–810 过高跳过,809 命中,808–775 过低跳过 | 只涨 1 次 |
攻击者选择降序,正是为了只让一条生效 —— 避免把热钱包自己的交易队列打乱。
第三,nonce 也是防御手段,但会被「喷洒」破解。 如果怀疑某条恶意授权在 mempool 里,账户可以立刻发一笔 0 值自转交易,用掉当前 nonce,让那条授权失效。
但攻击者预签 50 条不同 nonce 的授权后,烧掉一个 nonce 只会废掉其中一张,其余 49 张仍然有效。
这才是 nonce spray 的真正目的:不是提高命中率,而是让「取消」这件事失去意义。
第四,chain_id = 0 是跨链重放开关。 设 0 时,同一条签名在所有 EVM 链上都有效,只要各链 nonce 恰好匹配。币安案的 50 条全部是 chain_id = 1,所以那条授权无法在 BNB Chain、Polygon 等链上重放 —— 多链流出必须另有解释。
四、执行上下文:签名之后账户变成了什么
委托生效后,账户的 code 字段变成 23 字节的 0xef0100 ‖ address。此后:
- 任何对它的
CALL、DELEGATECALL、STATICCALL,都会加载目标合约的代码,但在 EOA 自己的上下文里执行:address(this)是 EOA,msg.sender是调用者,存储读写落在 EOA 的存储上; - 对下游代币合约来说,
msg.sender就是该钱包本身,因此不需要任何 ERC-20 approve; - 代码读取出现不对称:
EXTCODESIZE返回 23(委托指示符的长度),而委托执行中的CODESIZE返回目标合约代码的长度。规范明确「EXTCODEHASH等外部读取操作不自动跟随委托,而作用在委托指示符本身」—— 这意味着常规的「EOA 检测」(EXTCODESIZE == 0)会失效,但委托目标是可以直接从链上解出来的; - 委托链只跟随第一跳:如果目标合约本身也是委托账户,客户端不会继续递归;
- 委托不会因为外层交易 revert 而回滚。 规范原文是「已处理的委托指示符不会被回滚」。一笔看起来「失败」的 Type-4 交易,仍然可能已经把代码装好了 —— 这对模拟和监控是个陷阱。
清除委托只有一条路:用当前 nonce 再签一条 address = 0x0 的授权,把代码重置为空。没有管理员、没有时间锁、没有链上开关。
在清除之前,所有新入账资产都自动暴露在委托逻辑之下。
五、套回币安案:签名流程在哪一步失效
把上面的机制套回 10-29 的事件,流程可以逐步复原:
- 攻击者构造 50 条元组,
chain_id = 1、address = 0x63e842…、nonce 从 18,914,824 递减到 18,914,775; - 签名侧对这 50 个哈希逐一签名 —— 这需要 50 次 ECDSA,正常人工操作几乎不可能,通常意味着自动化签名服务或批量预签;
- operator 把 50 条签名打包进一条 Type-4 交易,自己作为发送者支付 0.006545 ETH(约 14.5 美元);
- 上链时账户 nonce 是 18,914,809,只有这一条命中;代码写入,nonce 变为 18,914,810;
- 3 分 24 秒后,operator 发一条普通交易调用
sweep(address,address[]),代码在热钱包上下文里执行,转走 167,464 ETH、16.15 亿 USDT 和 64 个代币; - 此后 3.5 小时内又调用 12 次,其中多次是 23,100 gas 的空轮询。
失效点不在链上,而在第 2 步:签名侧把一个「改写账户代码」的请求,当成了普通消息签名来处理。
如果签名策略能解析 0x05 载荷,它会看到三件事 —— 目标是一个未验证合约、nonce 窗口跨 50 个值、chain_id 为主网。这三条里任何一条都足以拦截。但它看到的可能只是一段十六进制哈希。
六、结论
EIP-7702 的签名流程可以压缩成一句话:用私钥对「链 ID + 目标地址 + nonce」签一个哈希,任何人把它包进 Type-4 交易,账户代码就被永久改写。
它没有过期、没有撤销按钮、没有执行回滚,唯一的失效机制是 nonce 被消耗 —— 而这一条已经被 nonce spray 针对性地绕过了。
因此,安全的签名流程必须把重心放在签名之前,而不是链上监控:
- 解析
0x05载荷并展示目标地址与chain_id - 对委托目标做代码哈希白名单,拒绝未验证或可升级合约
- 把「代码委托」与「资产转移」拆成两套密钥和两套审批
- 对热钱包直接禁用 Type-4
- 签名后监控 mempool 中是否有人替你提交
底线判断。 7702 把「签名」从一次具体操作的授权,升级成了「账户控制权的托管」。
判断一个钱包或交易所是否真正理解这一点,只看一件事:它的签名界面能不能在用户按下确认前,把 0xef0100‖address 里的那个地址,用人话显示出来。显示不出来,流程再多重也只是盲签。
参考资料
配套阅读:EIP-7702 攻击链复盘:18 小时准备,一条签名,一个机器人、币安热钱包失守。本文为机制说明,规范依据 EIP-7702 定稿版本。DYOR。