---
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).
