Alert Source Discuss
🚧 Stagnant Standards Track: ERC

ERC-6464: 多操作员、按代币的 ERC-721 授权

扩展 ERC-721,允许代币所有者授权多个操作员,以便在每个代币的基础上控制其资产。

Authors Cristian Espinoza (@crisgarner), Simon Fremaux (@dievardump), David Huber (@cxkoda), and Arran Schlosberg (@aschlosberg)
Created 2023-02-02
Discussion Link https://ethereum-magicians.org/t/fine-grained-erc721-approval-for-multiple-operators/12796
Requires EIP-165, EIP-721

摘要

ERC-721 并未预见到授权多个操作员来代表其所有者管理特定代币的情况。这导致了 setApprovalForAll() 的建立,并成为授权操作员的主要方式,这种方式赋予了被授权地址控制所有资产的权限,并造成了不必要的广泛安全风险,这种风险已在大量的网络钓鱼攻击中被利用。 本 EIP 通过引入细粒度的链上授权机制来扩展 ERC-721,该机制允许所有者在每个代币的基础上授权多个特定操作员;这消除了不必要的访问权限,并将漏洞利用的范围缩小到最小。 所提供的参考实现进一步实现了以每个所有者或每个代币为基础,廉价地撤销所有授权。

动机

ERC-721 中定义的 NFT 标准允许代币所有者“授权”任意地址来控制他们的代币——被授权的地址被称为“操作员”。 定义了两种类型的授权:

  1. approve(address,uint256) 提供了一种机制,仅允许单个操作员被授权给定的 tokenId;以及
  2. setApprovalForAll(address,bool) 切换是否为 msg.sender 拥有的每个代币授权操作员。

随着多个 NFT 市场的推出,如果卖家希望允许每个市场在出售时转移代币,则有必要能够为一个特定代币授权多个操作员。 然而,如果没有使用 setApprovalForAll(),就没有实现此目的的机制。 这与最小权限原则相冲突,并创建了一个攻击向量,该向量通过网络钓鱼来获取恶意(即零成本)卖方签名来利用,这些签名由合法的市场合约执行。

因此,本 EIP 定义了一种细粒度的方法来授权多个操作员,但范围限定于特定代币。

目标

  1. 易于市场采用; 需要对现有工作流程进行最小的更改。
  2. 易于链下授权索引服务采用。
  3. 简单地撤销授权; 即不需要每次授权都撤销。

非目标

  1. 保护 NFT 的安全措施,除了通过限制操作员授权的范围。
  2. ERC-1155 半同质化代币的兼容性。 然而,我们注意到,本文描述的机制也适用于 ERC-1155 代币类型,而无需授权所有其他类型。

规范

本文档中的关键词“必须”,“禁止”,“需要”,“应该”,“不应该”,“推荐”,“不推荐”,“可以”和“可选”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。

为了符合本 EIP,合约必须实现 IERC6464(本文定义)以及 ERC165ERC721 接口; 分别参见 ERC-165 和 ERC-721。

/**
 * @notice 扩展 ERC-721 以包含每个代币的多个操作员授权。
 * @dev 授权的链下索引器 SHOULD 假定如果见证了 `ERC721.Approval(…)` 或
 * `ERC721.ApprovalForAll(…, true)` 事件而没有相应的撤销,即使发出了
 * `ExplicitApprovalFor(…, false)`。
 * @dev TODO: 此接口的 ERC-165 标识符待定。
 */
interface IERC6464 is ERC721 {
    /**
     * @notice 当显式授予或撤销代币的授权时发出。
     */
    event ExplicitApprovalFor(
        address indexed operator,
        uint256 indexed tokenId,
        bool approved
    );

    /**
     * @notice 当所有显式授权(由 `setExplicitApprovalFor()` 函数授予)对于所有代币都被撤销时发出。
     * @dev 必须在调用 `revokeAllExplicitApprovals()` 时发出。
     */
    event AllExplicitApprovalsRevoked(address indexed owner);

    /**
     * @notice 当所有显式授权(由 `setExplicitApprovalFor()` 函数授予)对于特定代币被撤销时发出。
     * @param owner 必须是根据 ERC721 的 `ownerOf(tokenId)`; 在因转移而撤销的情况下,这必须是在相应的 `ERC721.Transfer()` 事件中预期发出的 `from` 地址。
     */
    event AllExplicitApprovalsRevoked(
        address indexed owner,
        uint256 indexed tokenId
    );

    /**
     * @notice 批准操作员代表其所有者管理资产。
     * @dev 如果 `msg.sender` 不是当前 NFT 所有者,或当前所有者的授权操作员,则抛出异常。
     * @dev 通过此方法设置的授权必须在将代币转移给新所有者时撤销; 相当于
     * 调用 `revokeAllExplicitApprovals(tokenId)`,包括相关的事件。
     * @dev 必须发出 `ApprovalFor(operator, tokenId, approved)`。
     * @dev 不得对任何标准 ERC721 授权设置器/getter 产生影响。
     */
    function setExplicitApproval(
        address operator,
        uint256 tokenId,
        bool approved
    ) external;

    /**
     * @notice 批准操作员代表其所有者管理代币。
     * @dev 必须等同于为数组中的每个 `tokenId` 调用 `setExplicitApprovalFor(operator, tokenId, approved)`。
     */
    function setExplicitApproval(
        address operator,
        uint256[] memory tokenIds,
        bool approved
    ) external;

    /**
     * @notice 撤销 `msg.sender` 授予的所有显式授权。
     * @dev 必须发出 `AllExplicitApprovalsRevoked(msg.sender)`。
     */
    function revokeAllExplicitApprovals() external;

    /**
     * @notice 撤销为指定代币授予的所有显式授权。
     * @dev 如果 `msg.sender` 不是当前 NFT 所有者,或当前所有者的授权操作员,则抛出异常。
     * @dev 必须发出 `AllExplicitApprovalsRevoked(msg.sender, tokenId)`。
     */
    function revokeAllExplicitApprovals(uint256 tokenId) external;

    /**
     * @notice 查询地址是否为代币的授权操作员。
     */
    function isExplicitlyApprovedFor(address operator, uint256 tokenId)
        external
        view
        returns (bool);
}

interface IERC6464AnyApproval is ERC721 {
    /**
     * @notice 如果满足以下任何条件,则返回 true:
     * 1. `isExplicitlyApprovedFor(operator, tokenId) == true`; 或者
     * 2. `isApprovedForAll(ownerOf(tokenId), operator) == true`; 或者
     * 3. `getApproved(tokenId) == operator`。
     * @dev 如果引入了其他授权操作员的机制,则必须扩展此条件。 该条件
     * 必须包括所有授权方法。
     */
    function isApprovedFor(address operator, uint256 tokenId)
        external
        view
        returns (bool);
}

理由

有待扩展的草案说明

  1. 通过新引入的方法授予的授权称为显式授权,以便于将它们与通过标准 ERC721.approve()ERC721.setApprovalForAll() 函数授予的授权区分开来。 但是,它们遵循相同的意图:授权操作员代表所有者行事。
  2. isApprovedFor() 抽象到 IERC6464AnyApproval 接口中,而不是将其保留在 IERC6464 中,可以实现纯 IERC6464 实现的模块化,同时还可以标准化与特定实现和任何未来授权 EIP 交互时检查授权的接口。
  3. AllExplicitApprovalsRevoked(address,uint256) 中包含索引的所有者地址有助于对现有授权进行链下索引。
  4. 关于 IERC6464AnyApproval:随着授权机制数量的增加,市场集成变得繁琐,因为他们必须查询多个接口以检查他们是否被授权管理代币。 这提供了一个简化的接口,旨在简化他们的数据导入。

向后兼容性

编写此扩展是为了允许对原始 ERC-721 规范进行尽可能小的更改,同时仍然提供一种机制来授予、撤销和跟踪每个代币的多个操作员的授权。

扩展的合约与所有现有平台保持完全兼容。

注意关于授权类型之间相互作用的 安全注意事项 小节中的 其他风险

参考实现

TODO:当实现到位时,添加指向 assets 目录的内部链接。

包括通过递增 nonce 广泛撤销授权的有效机制。

安全考虑

威胁模型

缓解措施

其他风险

TODO: 与 setApprovalForAll() 的相互作用。

需要讨论。

版权

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

Citation

Please cite this document as:

Cristian Espinoza (@crisgarner), Simon Fremaux (@dievardump), David Huber (@cxkoda), and Arran Schlosberg (@aschlosberg), "ERC-6464: 多操作员、按代币的 ERC-721 授权 [DRAFT]," Ethereum Improvement Proposals, no. 6464, February 2023. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-6464.