EIP-: ```markdown
    
    
      
        
       
    
    
      
        
       
    
  
  
  | Authors | 
|---|
Table of Contents
---
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,以此作为指示来自特定用户的许可的方式。
- 本 EIP 的任何符合规范的方法必须是 ERC-5750 兼容的方法。
 - 调用者必须传入最后一个参数 
bytes extraData,该参数符合 接口部分 中指定的GeneralExtensionDataStruct的 solidity 内存编码布局字节。以下描述基于将bytes extraData解码为GeneralExtensionDataStruct时的情况。 - 在 
GeneralExtensionDataStruct-解码的extraData中,调用者必须将GeneralExtensionDataStruct.erc5453MagicWord的值设置为keccak256("ERC5453-ENDORSEMENT")。 - 调用者必须将 
GeneralExtensionDataStruct.erc5453Type的值设置为支持的值之一。 
uint256 constant ERC5453_TYPE_A = 1;
uint256 constant ERC5453_TYPE_B = 2;
- 如果 
GeneralExtensionDataStruct.erc5453Type的值设置为ERC5453_TYPE_A,则GeneralExtensionDataStruct.endorsementPayload必须是SingleEndorsementData的 abi 编码字节。 - 
    
如果
GeneralExtensionDataStruct.erc5453Type的值设置为ERC5453_TYPE_B,则GeneralExtensionDataStruct.endorsementPayload必须是SingleEndorsementData[](动态数组)的 abi 编码字节。 - 
    
每个
SingleEndorsementData必须有一个address endorserAddress;和一个 65 字节的bytes sig签名。 - 每个 
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
            )
        )
    );
functionParamStructHash必须按如下方式计算
        bytes32 functionParamStructHash = keccak256(
            abi.encodePacked(
                keccak256(bytes(_functionStructure)),
                _functionParamPacked
            )
        );
        return functionParamStructHash;
其中
_functionStructure必须计算为function methodName(type1 param1, type2 param2, ...)。_functionParamPacked必须计算为enc(param1) || enco(param2) ...
- 在验证 
endorserAddress == ecrecover(validityDigest, signature)或EIP1271(endorserAddress).isValidSignature(validityDigest, signature) == ERC1271.MAGICVALUE之后,单个授权必须被认为是有效的。 - 
    
符合规范的方法可以选择为同一
ERC5453_TYPE_B类型的endorsementPayload中需要有效的授权数量设置一个阈值。 validSince和validBy都包含在内。实现者可以选择使用块号或时间戳。实现者应该找到一种方法来指示validSince和validBy是块号还是时间戳。
理论依据
- 
    
我们选择同时拥有
ERC5453_TYPE_A(单次授权) 和ERC5453_TYPE_B(多次授权,整个合约的 nonce 相同),这样我们 可以平衡更广泛的用例。例如,ERC-2612 和ERC-4494的相同用例可以通过ERC5453_TYPE_A来支持。阈值批准可以通过ERC5453_TYPE_B来完成。更复杂的批准类型也可以通过定义新的ERC5453_TYPE_?来扩展。 - 
    
我们选择同时包含
validSince和validBy,以允许最大的到期灵活性。如果采用ERC-5081,这也可以在 EVM 本机支持,但ERC-5081不会很快被采用,我们选择在我们的协议中添加这两个数字,以允许智能合约级别的支持。 
向后兼容性
该设计假定使用 bytes calldata extraData 以最大限度地提高未来扩展的灵活性。这种假设与 ERC-721、ERC-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);
    }
}
      
      
         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 上的同一合约版本中,或任何其他类似攻击面上的交易重放的可能性。nonce、validSince 和 validBy 字段旨在限制攻击面,但可能无法完全消除所有此类攻击的风险,例如,请参阅 网络钓鱼 部分。
网络钓鱼
值得指出的是一种特殊的网络钓鱼重放攻击。攻击者可以设计另一个智能合约,使用户被诱骗为看似合法的目的签署智能授权,但设计的数据与目标应用程序相匹配
版权
版权和相关权利已通过 CC0 放弃。 ```
Citation
Please cite this document as:
, "EIP-: ```markdown," Ethereum Improvement Proposals, no. , . [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-.