--- eip: 7598 title: Use contract signature for signed transfer description: An ERC to extend ERC-3009 Transfer with Authorization to support ERC-1271 signature validation. author: Yvonne Zhang (@yvonnezhangc), Aloysius Chan (@circle-aloychan) discussions-to: https://ethereum-magicians.org/t/add-erc-contract-signature-validation-extension-for-erc-3009-transfer-with-authorization/18158 status: Draft type: Standards Track category: ERC created: 2024-01-15 requires: 1271, 3009 --- # EIP: Contract signature validation extension for [ERC-3009](./eip-3009.md) Transfer with Authorization ## Abstract This proposal aims to extend the functionality of the existing [ERC-3009](./eip-3009.md) standard, "Transfer With Authorization," to support transfer operations initiated by smart contract wallets. ## Motivation The existing [ERC-3009](./eip-3009.md) standard enables asset transfers with ECDSA signatures. However, as smart contract wallets become more prevalent in the ecosystem, the current standard is no longer sufficient. This proposal aims to enhance the usability and composeability of the standard by extending ERC-3009 with smart contract wallet signature validation, as defined in [ERC-1271](./eip-1271.md). By incorporating this extension, users will have greater flexibility in managing their assets while ensuring a secure authorization process. ## Specification The following events and interfaces must still be present given the initial spec defined in [ERC-3009](./eip-3009.md). - Event `AuthorizationUsed`. - Constants `TRANSFER_WITH_AUTHORIZATION_TYPEHASH` and `RECEIVE_WITH_AUTHORIZATION_TYPEHASH`. - View function interface `authorizationState(address authorizer, bytes32 nonce)` In addition, the following interfaces must be added to be compliant with the standard: ``` /** * @notice Execute a transfer with a signed authorization * @param from Payer's address (Authorizer) * @param to Payee's address * @param value Amount to be transferred * @param validAfter The time after which this is valid (unix time) * @param validBefore The time before which this is valid (unix time) * @param nonce Unique nonce * @param signature Unstructured bytes signature signed by an EOA wallet or a contract wallet */ function transferWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes memory signature ) external; /** * @notice Receive a transfer with a signed authorization from the payer * @dev This has an additional check to ensure that the payee's address matches * the caller of this function to prevent front-running attacks. (See security * considerations) * @param from Payer's address (Authorizer) * @param to Payee's address * @param value Amount to be transferred * @param validAfter The time after which this is valid (unix time) * @param validBefore The time before which this is valid (unix time) * @param nonce Unique nonce * @param signature Unstructured bytes signature signed by an EOA wallet or a contract wallet */ function receiveWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes memory signature ) external; ``` Optional: The `AuthorizationCanceled` event and `CANCEL_AUTHORIZATION_TYPEHASH` constant as defined in the [ERC-3009](./eip-3009.md) spec. ``` /** * @notice Attempt to cancel an authorization * @param authorizer Authorizer's address * @param nonce Nonce of the authorization * @param signature Unstructured bytes signature signed by an EOA wallet or a contract wallet */ function cancelAuthorization( address authorizer, bytes32 nonce, bytes memory signature ) external; ``` ## Rationale By replacing the existing V, R, S signature validation scheme and introducing support for unstructured bytes input, contract developers can use a unified interface to validate signature from both EOAs and SC wallets. This allows for the utilization of different signature schemes and algorithms fitting the wallet type, paving the way for smart contract wallets and advanced wallet types to enhance their signature validation processes, promoting flexibility and innovation. ## Backwards Compatibility This proposal is fully backward-compatible with the existing ERC-3009 standard. Contracts that currently rely on the V, R, S signature validation scheme will continue to function without any issues. In the event that both the existing V, R, S signature validation scheme and the new unstructured bytes signature validation need to be supported for backward compatibility, developers can reduce duplicates by adapting the following code block as an example: ``` function transferWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, uint8 v, bytes32 r, bytes32 s ) external { transferWithAuthorization(owner, spender, value, deadline, abi.encodePacked(r, s, v)); } ``` ## Reference Implementation ``` /** * @notice Execute a transfer with a signed authorization * @dev EOA wallet signatures should be packed in the order of r, s, v. * @param from Payer's address (Authorizer) * @param to Payee's address * @param value Amount to be transferred * @param validAfter The time after which this is valid (unix time) * @param validBefore The time before which this is valid (unix time) * @param nonce Unique nonce * @param signature Signature byte array produced by an EOA wallet or a contract wallet */ function _transferWithAuthorization( address from, address to, uint256 value, uint256 validAfter, uint256 validBefore, bytes32 nonce, bytes memory signature ) internal { require(now > validAfter, "Authorization is not yet valid"); require(now < validBefore, "Authorization is expired"); require(!_authorizationStates[authorizer][nonce], "Authorization is used or canceled"); bytes32 digest = keccak256(abi.encodePacked( hex"1901", DOMAIN_SEPARATOR, keccak256(abi.encode( TRANSFER_WITH_AUTHORIZATION_TYPEHASH, from, to, value, validAfter, validBefore, nonce )) )); require( // Check for both ECDSA signature and ERC-1271 signature. A sample SignatureChecker is available at // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7bd2b2a/contracts/utils/cryptography/SignatureChecker.sol SignatureChecker.isValidSignatureNow( owner, typedDataHash, signature ), "Invalid signature" ); _authorizationStates[authorizer][nonce] = true; emit AuthorizationUsed(authorizer, nonce); _transfer(from, to, value); } ``` ## Security Considerations - For contract wallets, the security of `transferWithAuthorization`, `receiveWithAuthorization`, and `cancelAuthorization` rely on `ContractWallet.isValidSignature()` to ensure the signature bytes represent the desired execution from contract wallet owner(s). Contract wallet developers must exercise caution when implementing custom signature validation logic to ensure the security of their contracts. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).