forked from DecentralizedClimateFoundation/DCIPs
346 lines
13 KiB
Markdown
346 lines
13 KiB
Markdown
---
|
|
eip: 5453
|
|
title: Endorsement - Permit for Any Functions
|
|
description: A general protocol for approving function calls in the same transaction rely on EIP-5750.
|
|
author: Zainan Victor Zhou (@xinbenlv)
|
|
discussions-to: https://ethereum-magicians.org/t/erc-5453-endorsement-standard/10355
|
|
status: Draft
|
|
type: Standards Track
|
|
category: ERC
|
|
created: 2022-08-12
|
|
requires: 165, 712, 1271, 5750
|
|
---
|
|
|
|
## Abstract
|
|
|
|
This EIP establish a general protocol for permitting approving function calls in the same transaction rely on [EIP-5750](./eip-5750.md).
|
|
Unlike a few prior art ([EIP-2612](./eip-2612.md) for [EIP-20](./eip-20.md), [EIP-4494](./eip-4494.md) for [EIP-721](./eip-721.md) that
|
|
usually only permit for a single behavior (`transfer` for EIP-20 and `safeTransferFrom` for EIP-721) and a single approver in two transactions (first a `permit(...)` TX, then a `transfer`-like TX), this EIP provides a way to permit arbitrary behaviors and aggregating multiple approvals from arbitrary number of approvers in the same transaction, allowing for Multi-Sig or Threshold Signing behavior.
|
|
|
|
<!-- TODO add context about other EIPs this EIP is designed to work with. -->
|
|
## Motivation
|
|
|
|
1. Support permit(approval) alongside a function call.
|
|
2. Support a second approval from another user.
|
|
3. Support pay-for-by another user
|
|
4. Support multi-sig
|
|
5. Support persons acting in concert by endorsements
|
|
6. Support accumulated voting
|
|
7. Support off-line signatures
|
|
|
|
<!-- TODO add details about Motivations and example use cases -->
|
|
|
|
## Specification
|
|
|
|
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
|
|
|
|
### Interfaces
|
|
|
|
The interfaces and structure referenced here are as followed
|
|
|
|
<!-- TODO add detail explanation of each method parameters. -->
|
|
|
|
```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);
|
|
}
|
|
```
|
|
|
|
See [`IERC5453.sol`](../assets/eip-5453/IERC5453.sol).
|
|
|
|
### Behavior specification
|
|
|
|
As specified in [EIP-5750 General Extensibility for Method Behaviors](./eip-5750.md), any compliant method that has an `bytes extraData` as its
|
|
last method designated for extending behaviors can conform to [EIP-5453](./eip-5453.md) as the way to indicate a permit from certain user.
|
|
|
|
1. Any compliant method of this EIP MUST be a [EIP-5750](./eip-5750.md) compliant method.
|
|
2. Caller MUST pass in the last parameter `bytes extraData` conforming a solidity memory encoded layout bytes of `GeneralExtensonDataStruct` specified in _Section Interfaces_. The following descriptions are based on when decoding `bytes extraData` into a `GeneralExtensonDataStruct`
|
|
3. In the `GeneralExtensonDataStruct`-decoded `extraData`, caller MUST set the value of `GeneralExtensonDataStruct.erc5453MagicWord` to be the `keccak256("ERC5453-ENDORSEMENT")`.
|
|
4. Caller MUST set the value of `GeneralExtensonDataStruct.erc5453Type` to be one of the supported values.
|
|
|
|
```solidity
|
|
uint256 constant ERC5453_TYPE_A = 1;
|
|
uint256 constant ERC5453_TYPE_B = 2;
|
|
```
|
|
|
|
5. When the value of `GeneralExtensonDataStruct.erc5453Type` is set to be `ERC5453_TYPE_A`, `GeneralExtensonDataStruct.endorsementPayload` MUST be abi encoded bytes of a `SingleEndorsementData`.
|
|
6. When the value of `GeneralExtensonDataStruct.erc5453Type` is set to be `ERC5453_TYPE_B`, `GeneralExtensonDataStruct.endorsementPayload` MUST be abi encoded bytes of `SingleEndorsementData[]` (a dynamic array).
|
|
|
|
7. Each `SingleEndorsementData` MUST have a `address endorserAddress;` and a 65-bytes `bytes sig` signature.
|
|
|
|
8. Each `bytes sig` MUST be an ECDSA (secp256k1) signature using private key of signer whose corresponding address is `endorserAddress` signing `validityDigest` which is the a hashTypeDataV4 of [EIP-712](./eip-712.md) of hashStruct of `ValidityBound` data structure as followed:
|
|
|
|
```solidity
|
|
bytes32 validityDigest =
|
|
eip712HashTypedDataV4(
|
|
keccak256(
|
|
abi.encode(
|
|
keccak256(
|
|
"ValidityBound(bytes32 functionParamStructHash,uint256 validSince,uint256 validBy,uint256 nonce)"
|
|
),
|
|
functionParamStructHash,
|
|
_validSince,
|
|
_validBy,
|
|
_nonce
|
|
)
|
|
)
|
|
);
|
|
```
|
|
<!-- TODO convert the solidity code into mathematical representation. -->
|
|
|
|
9. The `functionParamStructHash` MUST be computed as followed
|
|
|
|
```solidity
|
|
bytes32 functionParamStructHash = keccak256(
|
|
abi.encodePacked(
|
|
keccak256(bytes(_functionStructure)),
|
|
_functionParamPacked
|
|
)
|
|
);
|
|
return functionParamStructHash;
|
|
```
|
|
|
|
whereas
|
|
|
|
- `_functionStructure` MUST be computed as `function methodName(type1 param1, type2 param2, ...)`.
|
|
- `_functionParamPacked` MUST be computed as `enc(param1) || enco(param2) ...`
|
|
|
|
10. Upon validating that `endorserAddress == ecrecover(validityDigest, signature)` or `EIP1271(endorserAddress).isValidSignature(validityDigest, signature) == ERC1271.MAGICVALUE`, the single endorsement MUST be deemed valid.
|
|
11. Compliant method MAY choose to impose a threshold for a number of endorsements needs to be valid in the same `ERC5453_TYPE_B` kind of `endorsementPayload`.
|
|
|
|
12. The `validSince` and `validBy` are both inclusive. Implementer MAY choose to use blocknumber or timestamp. Implementor SHOULD find away to indicate whether `validSince` and `validBy` is blocknumber or timestamp.
|
|
|
|
## Rationale
|
|
|
|
1. We chose to have both `ERC5453_TYPE_A`(single-endorsement) and `ERC5453_TYPE_B`(multiple-endorsements, same nonce for entire contract) so we
|
|
could balance a wider range of use cases. E.g. the same use cases of EIP-2612 and EIP-4494 can be supported by `ERC5453_TYPE_A`. And threshold approvals can be done via `ERC5453_TYPE_B`. More complicated approval types can also be extended by defining new `ERC5453_TYPE_?`
|
|
|
|
2. We chose to include both `validSince` and `validBy` to allow maximum flexibility in expiration. This can be also be supported by EVM natively at if adopted [EIP-5081](./eip-5081.md) but EIP-5081 will not be adopted anytime soon, we choose to add these two numbers in our protocol to allow
|
|
smart contract level support.
|
|
|
|
## Backwards Compatibility
|
|
|
|
The design assumes a `bytes calldata extraData` to maximize the flexibility of future extensions. This assumption is compatible with [EIP-721](eip-721.md), [EIP-1155](eip-1155.md) and many other ERC-track EIPs. Those that aren't, such as [EIP-20](./eip-20.md), can also be updated to support it, such as using a wrapper contract or proxy upgrade.
|
|
|
|
## Reference Implementation
|
|
|
|
In addition to the specified algorithm for validating endorser signatures, we also present the following reference implementations.
|
|
|
|
```solidity
|
|
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)
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
See [`AERC5453.sol`](../assets/eip-5453/AERC5453.sol)
|
|
|
|
### Reference Implementation of `EndorsableERC721`
|
|
|
|
Here is a reference implementation of `EndorsableERC721` that achieves similar behavior of [EIP-4494](./eip-4494.md).
|
|
|
|
```solidity
|
|
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);
|
|
}
|
|
}
|
|
```
|
|
|
|
See [`EndorsableERC721.sol`](../assets/eip-5453/EndorsableERC721.sol)
|
|
|
|
### Reference Implementation of `ThresholdMultiSigForwarder`
|
|
|
|
Here is a reference implementation of ThresholdMultiSigForwarder that achieves similar behavior of multi-sig threshold approval
|
|
remote contract call like a Gnosis-Safe wallet.
|
|
|
|
```solidity
|
|
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);
|
|
}
|
|
|
|
}
|
|
|
|
```
|
|
|
|
See [`ThresholdMultiSigForwarder.sol`](../assets/eip-5453/ThresholdMultiSigForwarder.sol)
|
|
|
|
## Security Considerations
|
|
|
|
### Replay Attacks
|
|
|
|
A replay attack is a type of attack on cryptography authentication. In a narrow sense, it usually refers to a type of attack that circumvents the cryptographically signature verification by reusing an existing signature for a message being signed again. Any implementations relying on this EIP must realize that all smart endorsements described here are cryptographic signatures that are _public_ and can be obtained by anyone. They must foresee the possibility of a replay of the transactions not only at the exact deployment of the same smart contract, but also other deployments of similar smart contracts, or of a version of the same contract on another `chainId`, or any other similar attack surfaces. The `nonce`, `validSince`, and `validBy` fields are meant to restrict the surface of attack but might not fully eliminate the risk of all such attacks, e.g. see the [Phishing](#phishing) section.
|
|
|
|
### Phishing
|
|
|
|
It's worth pointing out a special form of replay attack by phishing. An adversary can design another smart contract in a way that the user be tricked into signing a smart endorsement for a seemingly legitimate purpose, but the data-to-designed matches the target application
|
|
|
|
## Copyright
|
|
|
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|