Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 一步直接拉入自身金库,无需事先赋予中间授权。


参考文献与规范