Alert Source Discuss
Standards Track: ERC

ERC-2612: EIP-20 签名授权的 Permit 扩展

通过 EIP-712 secp256k1 签名进行 EIP-20 授权

Authors Martin Lundfall (@Mrchico)
Created 2020-04-13
Requires EIP-20, EIP-712

摘要

可以说,EIP-20 代币成功的主要原因之一在于 approvetransferFrom 之间的相互作用,这使得代币不仅可以在外部拥有账户 (EOA) 之间转移,还可以通过抽象出 msg.sender 作为代币访问控制的定义机制,在其他合约中用于特定于应用程序的条件下。

然而,这种设计的一个限制因素源于 EIP-20 approve 函数本身是根据 msg.sender 定义的。这意味着用户涉及 EIP-20 代币的 初始操作 必须由 EOA 执行(但请参阅下面的注释)。如果用户需要与智能合约交互,那么他们需要进行 2 笔交易(approve 和智能合约调用,该调用将在内部调用 transferFrom)。即使在支付给另一个人的简单用例中,他们也需要持有 ETH 来支付交易 gas 费用。

此 ERC 使用一个新函数 permit 扩展了 EIP-20 标准,该函数允许用户使用签名消息修改 allowance 映射,而不是通过 msg.sender

为了改善用户体验,签名数据按照 EIP-712 进行结构化,该结构已在主要 RPC 提供商中得到广泛采用。

注意: EIP-20 必须由 EOA 执行,除非拥有代币的地址实际上是一个合约钱包。虽然合约钱包解决了许多与此 EIP 相同的动机问题,但它们目前在生态系统中的采用率很低。合约钱包存在 UX 问题 – 因为它们将合约钱包的 EOA owner 与合约钱包本身(旨在代表 owner 执行操作并持有其所有资金)分离,因此需要专门设计用户界面来支持它们。permit 模式获得了许多相同的好处,同时几乎不需要更改用户界面。

动机

虽然 EIP-20 代币在以太坊生态系统中已经无处不在,但从协议的角度来看,它们的地位仍然是二等代币。允许用户在不持有任何 ETH 的情况下与以太坊交互的能力一直是一个长期存在的目标,并且是许多 EIP 的主题。

到目前为止,这些提案中的许多提案的采用率非常低,而已被采纳的提案(例如 EIP-777)引入了许多附加功能,导致主流合约中出现意外行为。

此 ERC 提出了一种替代解决方案,该解决方案旨在尽可能地小,并且仅解决 一个问题:EIP-20 approve 方法中缺少抽象。

虽然可能很想为每个 EIP-20 函数引入 *_by_signature 对等项,但出于以下两个原因,它们有意地从该 EIP-20 中排除:

  • 此类函数的所需细节(例如关于 transfer_by_signature 的费用、可能的批处理算法的决策)因用例而异,并且,
  • 它们可以使用 permit 和其他辅助合约的组合来实现,而不会损失通用性。

规范

除了 EIP-20 之外,符合要求的合约还必须实现 3 个新函数:

function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external
function nonces(address owner) external view returns (uint)
function DOMAIN_SEPARATOR() external view returns (bytes32)

它们的语义如下:

对于所有地址 ownerspender、uint256s valuedeadlinenonce、uint8 v、bytes32 rs, 调用 permit(owner, spender, value, deadline, v, r, s) 将设置 allowance[owner][spender]value, 将 nonces[owner] 递增 1, 并发出相应的 Approval 事件, 当且仅当满足以下条件时:

  • 当前区块时间小于或等于 deadline
  • owner 不是零地址。
  • nonces[owner](在状态更新之前)等于 nonce
  • rsv 是来自 owner 的消息的有效 secp256k1 签名:

如果未满足任何这些条件,则 permit 调用必须恢复。

keccak256(abi.encodePacked(
   hex"1901",
   DOMAIN_SEPARATOR,
   keccak256(abi.encode(
            keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
            owner,
            spender,
            value,
            nonce,
            deadline))
))

其中 DOMAIN_SEPARATOR 根据 EIP-712 定义。DOMAIN_SEPARATOR 应该对于合约和链是唯一的,以防止来自其他域的重放攻击, 并且满足 EIP-712 的要求,但在其他方面不受约束。 DOMAIN_SEPARATOR 的常见选择是:

DOMAIN_SEPARATOR = keccak256(
    abi.encode(
        keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),
        keccak256(bytes(name)),
        keccak256(bytes(version)),
        chainid,
        address(this)
));

换句话说,该消息是 EIP-712 类型结构:

{
  "types": {
    "EIP712Domain": [
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "version",
        "type": "string"
      },
      {
        "name": "chainId",
        "type": "uint256"
      },
      {
        "name": "verifyingContract",
        "type": "address"
      }
    ],
    "Permit": [
      {
        "name": "owner",
        "type": "address"
      },
      {
        "name": "spender",
        "type": "address"
      },
      {
        "name": "value",
        "type": "uint256"
      },
      {
        "name": "nonce",
        "type": "uint256"
      },
      {
        "name": "deadline",
        "type": "uint256"
      }
    ],
  },
  "primaryType": "Permit",
  "domain": {
    "name": erc20name,
    "version": version,
    "chainId": chainid,
    "verifyingContract": tokenAddress
  },
  "message": {
    "owner": owner,
    "spender": spender,
    "value": value,
    "nonce": nonce,
    "deadline": deadline
  }
}

请注意,在此定义中,我们没有提到 msg.senderpermit 函数的调用者可以是任何地址。

理由

permit 函数足以使涉及 EIP-20 代币的任何操作都可以使用代币本身来支付,而不是使用 ETH。

给出 nonces 映射是为了进行重放保护。

permit 的常见用例是,中继器代表 owner 提交 Permit。在这种情况下,中继方实际上被赋予了一个免费选项来提交或扣留 Permit。如果这引起关注,owner 可以通过将 deadline 设置为不久将来的值来限制 Permit 的有效时间。可以将 deadline 参数设置为 uint(-1) 以创建实际上永不过期的 Permit

包含EIP-712类型消息是因为它在许多钱包提供商中得到了广泛采用。

向后兼容性

在实际的合约中实现的代币合约中,已经有一些 permit 函数,最著名的是在 dai.sol 中引入的函数。

它的实现与此处的演示略有不同,因为:

  • 它没有采用 value 参数,而是采用一个布尔值 allowed,将批准设置为 0 或 uint(-1)
  • deadline 参数改为称为 expiry。这不仅仅是一个语法更改,因为它会影响签名消息的内容。

在代币 Stake(以太坊地址 0x0Ae055097C6d159879521C384F1D2123D1f195e6)中也有一种实现,其 ABI 与 dai 相同,但语义不同:它允许用户发布“过期的批准”,该批准仅允许在 expiry >= block.timestamp 时发生 transferFrom

此处提供的规范与 Uniswap V2 中的实现一致。

当 EIP 已经广泛部署时,添加了在 permit 无效时恢复的要求,但目前它与所有已发现的实现一致。

安全考虑

虽然 Permit 的签署人可能心中有一个特定的提交其交易的方,但另一方始终可以抢先执行此交易并在预期方之前调用 permit。但是,对于 Permit 签署人来说,最终结果是相同的。

由于当给定格式错误的消息时,ecrecover 预编译会静默失败,并且仅返回零地址作为 signer,因此务必确保 owner != address(0),以避免 permit 创建批准以花费属于零地址的“僵尸资金”。

签名的 Permit 消息是可审查的。中继方始终可以选择在收到 Permit 后不提交它,从而扣留提交它的选项。deadline 参数是对其的一种缓解措施。如果签名方持有 ETH,他们也可以自己提交 Permit,这可能会使先前签名的 Permit 无效。

批准的标准 EIP-20 竞争条件 (SWC-114) 也适用于 permit

如果 DOMAIN_SEPARATOR 包含 chainId 并且是在合约部署时定义的,而不是为每个签名重建的,则在未来链分裂的情况下,存在链之间可能发生重放攻击的风险。

版权

通过 CC0 放弃版权和相关权利。

Citation

Please cite this document as:

Martin Lundfall (@Mrchico), "ERC-2612: EIP-20 签名授权的 Permit 扩展," Ethereum Improvement Proposals, no. 2612, April 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2612.