Alert Source Discuss

EIP-: ```markdown

Authors

---
eip: 5453
title: 授权 - 任意函数的许可
description: 一个通用的协议,用于批准同一交易中的函数调用,依赖于 ERC-5750。
author: Zainan Victor Zhou (@xinbenlv)
discussions-to: https://ethereum-magicians.org/t/erc-5453-endorsement-standard/10355
status: Last Call
last-call-deadline: 2023-09-27
type: Standards Track
category: ERC
created: 2022-08-12
requires: 165, 712, 1271, 5750
---

## 摘要

本 EIP 建立了一个通用的协议,用于允许批准同一交易中的函数调用,该协议依赖于 [ERC-5750](/docs/eips/EIPS/eip-5750/)。
与之前的一些技术 ([ERC-2612](/docs/eips/EIPS/eip-2612/) 针对 [ERC-20](/docs/eips/EIPS/eip-20/)`ERC-4494` 针对 [ERC-721](/docs/eips/EIPS/eip-721/)) 不同,
它们通常只允许单一行为(ERC-20 的 `transfer` 和 ERC-721 的 `safeTransferFrom`)以及两笔交易中的单一批准者(首先是 `permit(...)` 交易,然后是类似 `transfer` 的交易),本 EIP 提供了一种方法来允许任意行为,并在同一交易中聚合来自任意数量的批准者的多个批准,从而实现多重签名或阈值签名行为。

## 动机

1. 支持与函数调用一起的 permit(approval)。
2. 支持来自另一个用户的第二次批准。
3. 支持由另一个用户支付
4. 支持多重签名
5. 支持通过授权协同行动的人员
6. 支持累积投票
7. 支持离线签名

## 规范

本文档中使用的关键词 "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" 和 "OPTIONAL" 按照 RFC 2119 和 RFC 8174 中的描述进行解释。

### 接口

此处引用的接口和结构如下

```solidity
pragma solidity ^0.8.9;

struct ValidityBound {
    bytes32 functionParamStructHash;
    uint256 validSince;
    uint256 validBy;
    uint256 nonce;
}

struct SingleEndorsementData {
    address endorserAddress; // 32
    bytes sig; // dynamic = 65
}

struct GeneralExtensionDataStruct {
    bytes32 erc5453MagicWord;
    uint256 erc5453Type;
    uint256 nonce;
    uint256 validSince;
    uint256 validBy;
    bytes endorsementPayload;
}

interface IERC5453EndorsementCore {
    function eip5453Nonce(address endorser) external view returns (uint256);
    function isEligibleEndorser(address endorser) external view returns (bool);
}

interface IERC5453EndorsementDigest {
    function computeValidityDigest(
        bytes32 _functionParamStructHash,
        uint256 _validSince,
        uint256 _validBy,
        uint256 _nonce
    ) external view returns (bytes32);

    function computeFunctionParamHash(
        string memory _functionName,
        bytes memory _functionParamPacked
    ) external view returns (bytes32);
}

interface IERC5453EndorsementDataTypeA {
    function computeExtensionDataTypeA(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address endorserAddress,
        bytes calldata sig
    ) external view returns (bytes memory);
}


interface IERC5453EndorsementDataTypeB {
    function computeExtensionDataTypeB(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address[] calldata endorserAddress,
        bytes[] calldata sigs
    ) external view returns (bytes memory);
}

参见 IERC5453.sol

行为规范

ERC-5750 方法行为的通用可扩展性 中所指定的,任何符合规范的方法,其最后一个方法具有 bytes extraData,用于扩展行为,都可以符合 ERC-5453,以此作为指示来自特定用户的许可的方式。

  1. 本 EIP 的任何符合规范的方法必须是 ERC-5750 兼容的方法。
  2. 调用者必须传入最后一个参数 bytes extraData,该参数符合 接口部分 中指定的 GeneralExtensionDataStruct 的 solidity 内存编码布局字节。以下描述基于将 bytes extraData 解码为 GeneralExtensionDataStruct 时的情况。
  3. GeneralExtensionDataStruct-解码的 extraData 中,调用者必须将 GeneralExtensionDataStruct.erc5453MagicWord 的值设置为 keccak256("ERC5453-ENDORSEMENT")
  4. 调用者必须将 GeneralExtensionDataStruct.erc5453Type 的值设置为支持的值之一。
uint256 constant ERC5453_TYPE_A = 1;
uint256 constant ERC5453_TYPE_B = 2;
  1. 如果 GeneralExtensionDataStruct.erc5453Type 的值设置为 ERC5453_TYPE_A,则 GeneralExtensionDataStruct.endorsementPayload 必须是 SingleEndorsementData 的 abi 编码字节。
  2. 如果 GeneralExtensionDataStruct.erc5453Type 的值设置为 ERC5453_TYPE_B,则 GeneralExtensionDataStruct.endorsementPayload 必须是 SingleEndorsementData[](动态数组)的 abi 编码字节。

  3. 每个 SingleEndorsementData 必须有一个 address endorserAddress; 和一个 65 字节的 bytes sig 签名。

  4. 每个 bytes sig 必须是使用签名者的私钥的 ECDSA (secp256k1) 签名,签名者的对应地址是 endorserAddress,用于签名 validityDigest,它是 EIP-712 的 hashTypeDataV4,表示 ValidityBound 数据结构的 hashStruct,如下所示:
bytes32 validityDigest =
    eip712HashTypedDataV4(
        keccak256(
            abi.encode(
                keccak256(
                    "ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)"
                ),
                functionParamStructHash,
                _validSince,
                _validBy,
                _nonce
            )
        )
    );
  1. functionParamStructHash 必须按如下方式计算
        bytes32 functionParamStructHash = keccak256(
            abi.encodePacked(
                keccak256(bytes(_functionStructure)),
                _functionParamPacked
            )
        );
        return functionParamStructHash;

其中

  • _functionStructure 必须计算为 function methodName(type1 param1, type2 param2, ...)
  • _functionParamPacked 必须计算为 enc(param1) || enco(param2) ...
  1. 在验证 endorserAddress == ecrecover(validityDigest, signature)EIP1271(endorserAddress).isValidSignature(validityDigest, signature) == ERC1271.MAGICVALUE 之后,单个授权必须被认为是有效的。
  2. 符合规范的方法可以选择为同一 ERC5453_TYPE_B 类型的 endorsementPayload 中需要有效的授权数量设置一个阈值。

  3. validSincevalidBy 都包含在内。实现者可以选择使用块号或时间戳。实现者应该找到一种方法来指示 validSincevalidBy 是块号还是时间戳。

理论依据

  1. 我们选择同时拥有 ERC5453_TYPE_A (单次授权) 和 ERC5453_TYPE_B (多次授权,整个合约的 nonce 相同),这样我们 可以平衡更广泛的用例。例如,ERC-2612 和 ERC-4494 的相同用例可以通过 ERC5453_TYPE_A 来支持。阈值批准可以通过 ERC5453_TYPE_B 来完成。更复杂的批准类型也可以通过定义新的 ERC5453_TYPE_? 来扩展。

  2. 我们选择同时包含 validSincevalidBy,以允许最大的到期灵活性。如果采用 ERC-5081,这也可以在 EVM 本机支持,但 ERC-5081 不会很快被采用,我们选择在我们的协议中添加这两个数字,以允许智能合约级别的支持。

向后兼容性

该设计假定使用 bytes calldata extraData 以最大限度地提高未来扩展的灵活性。这种假设与 ERC-721ERC-1155 和许多其他 ERC 跟踪 EIP 兼容。那些不兼容的,例如 ERC-20,也可以更新以支持它,例如使用包装器合约或代理升级。

参考实现

除了指定的验证授权人签名的算法外,我们还提供以下参考实现。

pragma solidity ^0.8.9;

import "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol";
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol";

import "./IERC5453.sol";

abstract contract AERC5453Endorsible is EIP712,
    IERC5453EndorsementCore, IERC5453EndorsementDigest, IERC5453EndorsementDataTypeA, IERC5453EndorsementDataTypeB {
    // ...

    function _validate(
        bytes32 msgDigest,
        SingleEndorsementData memory endersement
    ) internal virtual {
        require(
            endersement.sig.length == 65,
            "AERC5453Endorsible: wrong signature length"
        );
        require(
            SignatureChecker.isValidSignatureNow(
                endersement.endorserAddress,
                msgDigest,
                endersement.sig
            ),
            "AERC5453Endorsible: invalid signature"
        );
    }
    // ...

    modifier onlyEndorsed(
        bytes32 _functionParamStructHash,
        bytes calldata _extensionData
    ) {
        require(_isEndorsed(_functionParamStructHash, _extensionData));
        _;
    }

    function computeExtensionDataTypeB(
        uint256 nonce,
        uint256 validSince,
        uint256 validBy,
        address[] calldata endorserAddress,
        bytes[] calldata sigs
    ) external pure override returns (bytes memory) {
        require(endorserAddress.length == sigs.length);
        SingleEndorsementData[]
            memory endorsements = new SingleEndorsementData[](
                endorserAddress.length
            );
        for (uint256 i = 0; i < endorserAddress.length; ++i) {
            endorsements[i] = SingleEndorsementData(
                endorserAddress[i],
                sigs[i]
            );
        }
        return
            abi.encode(
                GeneralExtensionDataStruct(
                    MAGIC_WORLD,
                    ERC5453_TYPE_B,
                    nonce,
                    validSince,
                    validBy,
                    abi.encode(endorsements)
                )
            );
    }
}

参见 AERC5453.sol

EndorsableERC721 的参考实现

这是一个 EndorsableERC721 的参考实现,它实现了与 ERC-4494 类似的行为。

pragma solidity ^0.8.9;

contract EndorsableERC721 is ERC721, AERC5453Endorsible {
    //...

    function mint(
        address _to,
        uint256 _tokenId,
        bytes calldata _extraData
    )
        external
        onlyEndorsed(
            _computeFunctionParamHash(
                "function mint(address _to,uint256 _tokenId)",
                abi.encode(_to, _tokenId)
            ),
            _extraData
        )
    {
        _mint(_to, _tokenId);
    }
}

参见 EndorsableERC721.sol

ThresholdMultiSigForwarder 的参考实现

这是一个 ThresholdMultiSigForwarder 的参考实现,它实现了类似于 Gnosis-Safe 钱包的多重签名阈值批准远程合约调用的行为。

pragma solidity ^0.8.9;

contract ThresholdMultiSigForwarder is AERC5453Endorsible {
    //...
    function forward(
        address _dest,
        uint256 _value,
        uint256 _gasLimit,
        bytes calldata _calldata,
        bytes calldata _extraData
    )
        external
        onlyEndorsed(
            _computeFunctionParamHash(
                "function forward(address _dest,uint256 _value,uint256 _gasLimit,bytes calldata _calldata)",
                abi.encode(_dest, _value, _gasLimit, keccak256(_calldata))
            ),
            _extraData
        )
    {
        string memory errorMessage = "Fail to call remote contract";
        (bool success, bytes memory returndata) = _dest.call{value: _value}(
            _calldata
        );
        Address.verifyCallResult(success, returndata, errorMessage);
    }

}

参见 ThresholdMultiSigForwarder.sol

安全注意事项

重放攻击

重放攻击是一种针对密码学认证的攻击类型。从狭义上讲,它通常指的是一种通过重用现有签名来再次签名消息,从而绕过密码学签名验证的攻击类型。任何依赖此 EIP 的实现都必须意识到,此处描述的所有智能授权都是 公开的 加密签名,并且可以被任何人获取。他们必须预见到不仅在同一智能合约的精确部署中,而且在类似智能合约的其他部署中,或在另一个 chainId 上的同一合约版本中,或任何其他类似攻击面上的交易重放的可能性。noncevalidSincevalidBy 字段旨在限制攻击面,但可能无法完全消除所有此类攻击的风险,例如,请参阅 网络钓鱼 部分。

网络钓鱼

值得指出的是一种特殊的网络钓鱼重放攻击。攻击者可以设计另一个智能合约,使用户被诱骗为看似合法的目的签署智能授权,但设计的数据与目标应用程序相匹配

版权

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

Citation

Please cite this document as:

, "EIP-: ```markdown," Ethereum Improvement Proposals, no. , . [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-.