EIP-3009: 签名划转与原子化代扣标准 (Transfer With Authorization)
EIP-3009 定义了基于 EIP-712 签名的代币直接转移与代扣授权扩展标准。
与 EIP-2612(仅更新 allowance 映射、仍需随后调用 transferFrom)不同,EIP-3009 允许签名人授权直接将资金从其账户中划出或接收,一步完成资金物理转移(Zero-step Transfer)。该标准由 Centre 机构(Circle & Coinbase)制定,并在以太坊原生 USDC (FiatTokenV2 及以上版本) 中作为核心支付基础设施全面部署。
核心接口规范
EIP-3009 定义了单向划转(Transfer)、双向接收(Receive)以及作废授权的核心接口:
interface IERC3009 {
/**
* @notice 基于签名直接执行转账操作(任何人均可作为 relayer 代为提交)
*/
function transferWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @notice 基于签名接收转账,强制要求调用者(msg.sender)必须等于 to 地址,防范抢跑
*/
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @notice 提前作废指定的一组授权 Nonce
*/
function cancelAuthorization(
address authorizer,
bytes32 nonce,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @notice 查询指定地址的 Nonce 状态
*/
function authorizationState(address authorizer, bytes32 nonce) external view returns (bool);
}
状态机模型:位图散列 Nonce vs 自增 Nonce
EIP-3009 与 EIP-2612 在防重放状态机设计上存在根本区别:
| 设计维度 | EIP-2612 (Permit) | EIP-3009 (Transfer With Authorization) |
|---|---|---|
| Nonce 类型 | 严格单调递增整数 uint256 | 任意 32 字节随机哈希 bytes32 |
| 执行时序约束 | 强时序耦合(Nonce \(N\) 未被执行前,无法执行 \(N+1\)) | 完全乱序并发(Out-of-order Execution) |
| 存储标记结构 | 单一存储槽更新 _nonces[owner]++ | 离散状态映射 mapping(address => mapping(bytes32 => bool)) |
| 适用业务场景 | 账户额度变更、借贷按序存入 | 高并发微支付、无序批量结算、去中心化电商结账 |
核心数学定义与 TypeHash
1. 结构体类型哈希
bytes32 public constant TRANSFER_WITH_AUTHORIZATION_TYPEHASH = keccak256(
"TransferWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);
bytes32 public constant RECEIVE_WITH_AUTHORIZATION_TYPEHASH = keccak256(
"ReceiveWithAuthorization(address from,address to,uint256 value,uint256 validAfter,uint256 validBefore,bytes32 nonce)"
);
2. 结构化哈希计算
$$ \text{structHash} = \text{keccak256}\Big(\text{TYPEHASH} ;\parallel; from ;\parallel; to ;\parallel; value ;\parallel; validAfter ;\parallel; validBefore ;\parallel; nonce\Big) $$
$$ \text{Digest} = \text{keccak256}\Big(\texttt{0x1901} ;\parallel; \text{DOMAIN_SEPARATOR} ;\parallel; \text{structHash}\Big) $$
防抢跑机制:receiveWithAuthorization 的安全设计
在去中心化支付与自动化订单结算中,单纯依赖 transferWithAuthorization 存在被套利机器人(MEV Searcher)在公开内存池中“前置截留(Front-running)”代为提交的现象(虽然资金依然流向指定 to,但破坏了收款合约要求由其发起调用的原子上下文)。
receiveWithAuthorization 强制引入了调用者绑定:
function receiveWithAuthorization(
address from,
address to,
uint256 value,
uint256 validAfter,
uint256 validBefore,
bytes32 nonce,
uint8 v, bytes32 r, bytes32 s
) external {
// 强制防抢跑约束:仅允许收款人(to)以 msg.sender 身份触发
require(msg.sender == to, "FiatTokenV2: caller must be the payee");
// 校验有效时间区间
require(block.timestamp > validAfter, "FiatTokenV2: auth not yet valid");
require(block.timestamp < validBefore, "FiatTokenV2: auth expired");
// 校验并作废 Nonce
require(!_authorizationStates[from][nonce], "FiatTokenV2: auth already used");
_authorizationStates[from][nonce] = true;
// 验签并直接划转资金
bytes32 digest = _computeDigest(...);
require(ECDSA.recover(digest, v, r, s) == from, "FiatTokenV2: invalid signature");
_transfer(from, to, value);
}
收款合约(如商户网关、质押合约)可以在自身业务逻辑中调用该函数,安全地将用户的 USDC 一步直接拉入自身金库,无需事先赋予中间授权。