Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-4494: 用于 ERC-721 NFT 的 Permit

用于 ERC-721 NFT 的 ERC-712 签名授权

Authors Simon Fremaux (@dievardump), William Schwab (@wschwab)
Created 2021-11-25
Discussion Link https://ethereum-magicians.org/t/eip-extending-erc2612-style-permits-to-erc721-nfts/7519/2
Requires EIP-165, EIP-712, EIP-721

摘要

ERC-2612 中概述的 “Permit” 授权流程已被证明是 UX 方面非常有价值的进步,它为 ERC20 代币创建了无 gas 授权。此 EIP 将该模式扩展到 ERC-721 NFT。此 EIP 大量借鉴了 ERC-2612。

由于 ERC-20 和 ERC-721 代币之间的结构差异,这需要一个单独的 EIP。虽然 ERC-20 许可使用价值(被批准的 ERC-20 代币的数量)和基于所有者地址的 nonce,但 ERC-721 许可侧重于 NFT 的 tokenId,并根据 NFT 的转移递增 nonce。

动机

ERC-2612 中概述的 permit 结构允许使用签名消息(结构如 ERC-712 中所述)来创建授权。正常的基于授权的拉取流程通常涉及两个交易,一个用于授权合约,第二个用于合约拉取资产,这是一种糟糕的 UX,并且经常使新用户感到困惑,而 permit 样式的流程只需要签署消息和一个交易。ERC-2612 中可以找到更多信息。

ERC-2612 仅概述了 ERC-20 代币的 permit 架构。此 ERC 提出了 ERC-721 NFT 的架构,其中还包含一个 approve 架构,该架构将受益于基于签名消息的授权流程。

规范

本文档中“MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“MAY”和“OPTIONAL”等关键词应按照 RFC 2119 中的描述进行解释。

必须将三个新函数添加到 ERC-721

pragma solidity 0.8.10;

import "./IERC165.sol";

///
/// @dev Interface for token permits for ERC-721
///
interface IERC4494 is IERC165 {
  /// ERC165 bytes to add to interface array - set in parent contract
  ///
  /// _INTERFACE_ID_ERC4494 = 0x5604e225

  /// @notice Function to approve by way of owner signature
  // / @notice 通过所有者签名进行批准的函数
  /// @param spender the address to approve
  /// @param tokenId the index of the NFT to approve the spender on
  /// @param deadline a timestamp expiry for the permit
  /// @param sig a traditional or EIP-2098 signature
  function permit(address spender, uint256 tokenId, uint256 deadline, bytes memory sig) external;
  /// @notice Returns the nonce of an NFT - useful for creating permits
  // / @notice 返回 NFT 的 nonce - 用于创建 permit
  /// @param tokenId the index of the NFT to get the nonce of
  /// @return the uint256 representation of the nonce
  function nonces(uint256 tokenId) external view returns(uint256);
  /// @notice Returns the domain separator used in the encoding of the signature for permits, as defined by EIP-712
  // / @notice 返回用于 permit 签名编码的域分隔符,如 EIP-712 所定义
  /// @return the bytes32 domain separator
  function DOMAIN_SEPARATOR() external view returns(bytes32);
}

其语义如下:

对于所有地址 spenderuint256 tokenIddeadlinenonce,以及 bytes sig,只要 tokenId 的所有者仍然拥有它,调用 permit(spender, tokenId, deadline, sig) 就 MUST 将 spender 设置为对 tokenId 的已批准,并且 MUST 发出相应的 Approval 事件,仅当满足以下条件时:

  • 当前区块时间小于或等于 deadline
  • tokenId 的所有者不是零地址
  • nonces[tokenId] 等于 nonce
  • sigtokenId 所有者的有效 secp256k1EIP-2098 签名:
    keccak256(abi.encodePacked(
     hex"1901",
     DOMAIN_SEPARATOR,
     keccak256(abi.encode(
              keccak256("Permit(address spender,uint256 tokenId,uint256 nonce,uint256 deadline)"),
              spender,
              tokenId,
              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)
    ));
    

    换句话说,消息是以下 ERC-712 类型结构:

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

    此外:

  • 特定 tokenIdnonce (nonces[tokenId]) 必须在每次 tokenId 转移时递增
  • permit 函数必须检查签名者是否不是零地址

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

此 EIP 需要 EIP-165ERC-721 中已经需要 EIP165,但这里还需要它才能注册此 EIP 的接口。这样做将允许轻松验证 NFT 合约是否已实现此 EIP,从而使它们能够相应地进行交互。此 EIP 的接口(如 EIP-165 中定义)为 0x5604e225。实现此 EIP 的合约在被调用 0x5604e225 时,必须使 supportsInterface 函数返回 true

原理

permit 函数足以在不需要额外交易的情况下启用 safeTransferFrom 交易。

该格式避免了对未知代码的任何调用。

给出 nonces 映射是为了防止重放攻击。

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

包括 ERC-712 类型消息是因为它在 ERC-2612 中的使用,而后者又提到了许多钱包提供商的广泛采用。

虽然 ERC-2612 侧重于被批准的值,但此 EIP 侧重于通过 permit 批准的 NFT 的 tokenId。这实现了一种 ERC-20(甚至 ERC-1155)代币无法实现的灵活性,从而使单个所有者可以在同一 NFT 上授予多个 permit。这是可能的,因为每个 ERC-721 代币都是离散的(通常被称为非同质化),这允许断言该代币仍然由 owner 简单而确凿地拥有。

虽然 ERC-2612 将签名拆分为其 v,r,s 组件,但此 EIP 选择采用可变长度的 bytes 数组,以便支持 EIP-2098 签名(64 字节),这不能轻易地从 r,s,v 组件(65 字节)中分离或重建。

向后兼容性

已经有一些 ERC-721 合约实现了 permit 样式的架构,最著名的是 Uniswap v3。

它们的实现与此处的规范不同,因为:

  • permit 架构基于 owner
  • nonce 在创建 permit 时递增
  • permit 函数必须由 NFT 所有者调用,该所有者设置为 owner
  • 签名拆分为 r,s,v 而不是 bytes

上面详细介绍了在设计决策上的差异的基本原理。

测试用例

参考实现的基本测试用例可以在此处找到。

一般来说,测试套件应至少断言有关此 EIP 的任何实现的以下内容:

  • 每次转移后,nonce 都会递增
  • permit 在正确的 tokenId 上批准 spender
  • 在 NFT 转移后,无法使用该 permit
  • 过期的 permit 不能使用

参考实现

参考实现已在此处设置。

安全注意事项

在创建转移函数时应格外小心,在该函数中,可以在一个函数中使用 permit 和转移函数,以确保无效的 permit 不能以任何方式使用。这对于自动化 NFT 平台尤其重要,在这些平台上,粗心的实现可能会导致大量用户资产的compromise。

其余注意事项已从 ERC-2612 复制并进行了少量修改,并且在此同样相关:

尽管 Permit 的签名者可能心中有一个特定的方来提交他们的交易,但另一方始终可以抢先该交易并在预期方之前调用 permit。但是,对于 Permit 签名者来说,最终结果是相同的。

由于 ecrecover 预编译会悄无声息地失败,并且在给定格式错误的消息时仅返回零地址作为 signer,因此务必确保 ownerOf(tokenId) != address(0),以避免 permit 创建对任何未设置授权的 tokenId 的授权。

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

对于 permit,标准 ERC-20 授权竞争条件 也适用。

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

版权

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

Citation

Please cite this document as:

Simon Fremaux (@dievardump), William Schwab (@wschwab), "ERC-4494: 用于 ERC-721 NFT 的 Permit [DRAFT]," Ethereum Improvement Proposals, no. 4494, November 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-4494.