EIPs/EIPS/eip-3074.md
Ansgar Dietrichs 49287123a0
Automatically merged updates to draft EIP(s) 3074 (#3415)
Hi, I'm a bot! This change was automatically merged because:

 - It only modifies existing Draft, Review, or Last Call EIP(s)
 - The PR was approved or written by at least one author of each modified EIP
 - The build is passing
2021-03-19 09:57:30 +13:00

18 KiB

eip title author discussions-to status type category created
3074 AUTH and AUTHCALL opcodes Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu) https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880 Review Standards Track Core 2020-10-15

Simple Summary

Allow externally owned accounts (EOAs) to delegate control of their account to a contract.

Abstract

This EIP introduces two EVM instructions AUTH and AUTHCALL. The first sets a context variable authorized based on an ECDSA signature. The second sends a call as the authorized. This essentially delegates control of the EOA to smart contract.

Motivation

Adding more functionlity to EOAs has been a long-standing feature request. The requests have spanned from implementing batching capabilities, allowing for gas sponsoring, expirations, scripting, and beyond. These changes often mean increased complexity and rigidity of the protocol. In some cases, it also means increased attack surfaces.

This EIP takes a different approach. Instead of enshrining these capabilities in the protocol as transaction validity requirements, it allows users to delegate control of their EOA to a contract. This gives developers a flexible framework for developing novel transaction schemes for EOAs. A good analogy for the benefit this EIP provides is that it's similar to allowing any EOA to become a smart contract wallet without deploying a contract.

Although this EIP provides great benefit to individual users, the leading motivation for this EIP is "sponsored transactions". This is where the fee for a transaction is provided by a different account than the one that originates the call.

With the extraordinary growth of tokens on Ethereum, it has become common for EOAs to hold valuable assets without holding any ether at all. Today, these assets must be converted to ether before they can be used to pay gas fees. However, without ether to pay for the conversion, it's impossible to convert them. Sponsored transactions break the circular dependency.

Specification

Conventions

  • top - N - the Nth most recently pushed value on the EVM stack, where top - 0 is the most recent.
  • || - byte concatenation operator.

Constants

Constant Value
TYPE 0x03

TYPE is an EIP-2718 transaction type reserved for EIP-3074 signatures to prevent signature collisions.

Context Variables

Variable Type Initial Value
authorized address unset

The context variable authorized shall indicate the active account for AUTHCALL instructions in the current frame of execution. If set, authorized shall only contain an account which has given the contract authorization to act on its behalf. An unset value shall indicate that no such account is set and that there is not yet an active account for AUTHCALL instructions in the current frame of execution.

The variable has the same scope as the program counter -- authorized persists throughout a single frame of execution of the contract, but is not passed through any calls (including DELEGATECALL). If the same contract is being executed in separate execution frames (ex. a CALL to self), both frames shall have independent values for authorized. Initially in each frame of execution, authorized is always unset, even if a previous execution frame for the same contract has a value.

AUTH (0xf6)

A new opcode AUTH shall be created at 0xf6. It shall take four stack element inputs and returns one stack element.

Input

Stack Value
top - 0 commit
top - 1 yParity
top - 2 r
top - 3 s

Output

Stack Value
top - 0 authorized

Behavior

The arguments (yParity, r, s) are interpreted as an ECDSA signature on the secp256k1 curve over the message keccak256(TYPE || paddedInvokerAddress || commit), where:

  • paddedInvokerAddress is the address of the contract executing AUTH, left-padded with zeroes to a total of 32 bytes (ex. 0x000000000000000000000000AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA).
  • commit, one of the arguments passed into AUTH, is a 32-byte value that can be used to commit to specific additional validity conditions in the invoker's pre-processing logic (e.g. a nonce for replay protection).

If the signature is valid, the signerAddress is recovered. Signature validity and signer recovery is handled analogous to transaction signatures, including the stricter s range for preventing ECDSA malleability. Note that yParity is expected to be 0 or 1. If signerAddress != tx.origin, the context variable authorized is set to signerAddress. In any other case, i.e. if the signature is invalid or signerAddress == tx.origin, authorized is reset to an unset value.

AUTH returns the new authorized if set, or 0 otherwise.

Gas Cost

The gas cost for AUTH is 3000. This is the same cost as for the ecrecover precompile.

AUTHCALL (0xf7)

A new opcode AUTHCALL shall be created at 0xf7. It shall take seven stack elements and return one stack element. It matches the behavior of the existing CALL (0xF1) instruction, except where noted below.

Input

Stack Value
top - 0 gas
top - 1 addr
top - 2 value
top - 3 argsOffset
top - 4 argsLength
top - 5 retOffset
top - 6 retLength

Output

Stack Value
top - 0 success

Behavior

AUTHCALL is interpreted the same as CALL, except for:

  • If authorized is unset, execution is considered invalid and must exit the current execution frame immediately (in the same way as a stack underflow or invalid jump).
  • Otherwise, the caller address for the call is set to authorized.

The call value is deducted from the balance of the executing contract. It is not paid by the authorized.

AUTHCALL must increase the call depth by one. AUTHCALL must not increase the call depth by two as if it first called into the authorized account and then into the target.

The return data area accessed with RETURNDATASIZE (0x3d) and RETURNDATACOPY (0x3e) must be set in the same way as the CALL instruction.

Importantly, AUTHCALL does not reset authorized, but leaves it unchanged.

Gas Cost

AUTHCALL has the same gas cost as CALL.

As with CALL, the gas cost for the opcode itself (both the static and the dynamic portion) is always charged, independent of whether the call is actually executed. The gas passed into the call is calculated following EIP-150 and is refunded partially if the call returns with unused gas left, or completely if the call is not executed at all because of a failing pre-check.

Rationale

Throwing for Unset authorized During AUTHCALL

A well-behaved contract should never reach an AUTHCALL without having successfully set authorized beforehand. The safest behavior, therefore, is to exit the current frame of execution immediately. This is especially important in the context of transaction sponsoring / relaying, which is expected to be one of the main use cases for this EIP. In a sponsored transaction, the inability to distinguish between a sponsee-attributable fault (like a failing sub-call) and a sponsor-attributable fault (like a failing AUTH) is especially dangerous and should be prevented because it charges unfair fees to the sponsee.

Reserving an EIP-2718 Transaction Type

While clients should never interpret EIP-3074 signed messages as transactions, reserving an EIP-2718 transaction type reduces the likelihood of this occurring by accident.

Another Sponsored Transaction EIP

There are two general approaches to separating the "fee payer" from the "action originator".

The first is introducing a new transaction. This requires significant changes to clients to support and is generally less upgradeable than other solutions (e.g. this EIP). This approach is also not immediately compatible with account abstraction (AA). These proposals require a signed transaction from the sponsor's account, which is not possible from an AA contract, because it has no private key to sign with. The main advantage of new transaction types is that the validity requirements are enforced by the protocol, therefore invalid transactions do not pollute block space.

The other main approach is to introduce a new mechanism in the EVM to masquerade as other accounts. This EIP introduces AUTH and AUTHCALL to make calls as EOAs. There are many different permutations of this mechanism. An alternative mechanism would be add an opcode that can make arbitrary calls based on a similar address creation scheme as CREATE2. Although this mechanism would not benefit users today, it would immediately allow for those accounts to send and receive ether -- making it feel like a more first-class primitive.

Besides better compatibility with AA, introducing a new mechanism into the EVM is a much less intrusive change than a new transaction type. This approach requires no changes in existing wallets, and little change in other tooling.

AUTHCALL's single deviation from CALL is to set CALLER. It implements the minimal functionality to enable sender abstraction for sponsored transactions. This single mindedness makes AUTHCALL significantly more composable with existing Ethereum features.

More logic can be implemented around the AUTHCALL instruction, giving more control to invokers and sponsors without sacrificing security or user experience for sponsees.

What to Sign?

As originally written, this proposal specified a precompile with storage to track nonces. Since a precompile with storage is unprecedented, a revision moved replay protection into the invoker contract, necessitating a certain level of user trust in the invoker. Expanding on this idea of trusted invokers, the other signed fields were eventually eliminated, one by one, until only invoker and commit remained.

The invoker binds a particular signed message to a single invoker. If invoker was not part of the message, any invoker could reuse the signature to completely compromise the EOA. This allows users to trust that their message will be validated as they expect, particularly the values committed to in commit.

Understanding commit

Earlier iterations of this EIP included mechanisms for replay protection, and also signed over value, gas, and other arguments to AUTHCALL. After further investigation, we revised this EIP to its current state: explicitly delegate these responsibilities to the invoker contract.

A user will specifically interact with an invoker they trust. Because they trust this contract to execute faithfully, they will "commit" to certain properties of a call they would like to make by computing a hash of the call values. They can be certain that the invoker will only allow they call to proceed if it is able to verify the values committed to (e.g. a nonce to protect against replay attacks). This certainty arises from the commit value that is signed over by the user. This is the hash of values which the invoker will validate. A safe invoker should accept the values from the user and compute the commit hash itself. This ensures that invoker operated on the same input that user authorized.

auth message format

Using commit as a hash of values allows for invokers to implement arbitrary constraints. For example, they could allow accounts to have N parallel nonces. Or, they could allow a user to commit to multiple calls with a single signature. This would allow mult-tx flows, such as ERC-20 approve-transfer actions, to be condensed into a single transaction with a single signature verification. A commitment to multiple calls would look something like the diagram below.

multi-call auth message

Invoker Contracts

The invoker contract is a trustless intermediary between the sponsor and sponsee. A sponsee signs over invoker to require they transaction to be processed only by a contract they trust. This allows them to interact with sponsors without needing to trust them.

Choosing an invoker is similar to choosing a smart contract wallet implementation. It's important to choose one that has been thoroughly reviewed, tested, and accepted by the community as secure. We expect a few invoker designs to be utilized by most major transaction relay providers, with a few outliers that offer more novel mechanisms.

An important note is that invoker contracts MUST NOT be upgradeable. If an invoker can be redeployed to the same address with different code, it would be possible to redeploy the invoker with code that does not properly verify commit and any account that signed a message over that invoker would be compromised. Although this sounds scare, it is no different than using a smart contract wallet via DELEGATECALL. If the wallet is redeployed with different logic, all wallet using its code could be compromised.

Banning tx.origin as Signer

The reason for banning signatures from tx.origin is that subsequent AUTHCALLs would result in msg.sender == tx.origin. This however is a frequently used pattern to test for top-level execution (i.e. being called directly from an EOA). Banning tx.origin as signer keeps this invariant intact.

In its current form, this restriction complicates the use of the EIP for simple transaction batching, as the sender of the outer transaction must be different from the signing account. It is possible to lift or otherwise mitigate this restriction in the future. Potential alternative approaches are:

  • Simply allow tx.origin as signer. This would require research into the impact on existing applications.
  • Set tx.origin to a constant ENTRY_POINT address for AUTHCALLs.
  • Set tx.origin to the invoker address for AUTHCALLs.
  • Set tx.origin to a special address derived from any of the sender, invoker, and/or signer addresses.

On Call Depth

The EVM limits the maximum number of nested calls, and naively allowing a sponsor to manipulate the call depth before reaching the invoker would introduce a griefing attack against the sponsee. That said, with the 63/64th gas rule, and the cost of AUTHCALL, the stack is effectively limited to a much smaller depth than the hard maximum by the gas parameter.

It is, therefore, sufficient for the invoker to guarantee a minimum amount of gas, because there is no way to reach the hard maximum call depth with any reasonable (i.e. less than billions) amount of gas.

Source of value

Any non-zero value passed into an AUTHCALL is deducted from the invoker's balance. A natural alternative source for value would be the authorized account. However, deducting value from an EOA mid-execution is problematic, as it breaks important invariants for handling pending transactions. Specifically:

  • Transaction pools expect transactions for a given EOA to only turn invalid when other transactions from the same EOA are included into a block, increasing its nonce and (possibly) decreasing its balance. Deducting value from the authorized account would make transaction invalidation an unpredictable side effect of any smart contract execution.
  • Similarly, miners rely on the ability to statically pick a set of valid transactions from their transaction pool to include into a new block. Deducting value from the authorized account would break this ability, increasing the overhead and thus the time for block creation.

At the same time, the ability to directly take ether out of the authorized account is an important piece of functionality and thus an desired future addition via an additional opcode similar to AUTHCALL. The prerequisite for that would be to find satisfying mitigations to the transaction invalidation concerns outlined above. One potential avenue for that could be the addition of account access lists similar to EIP-2930, used to signal accounts whose balance can be reduced as a side effect of the transaction (without on their own constituting authorization to do so).

Backwards Compatibility

No known issues.

Test Cases

TODO

Implementation

https://github.com/quilt/go-ethereum/tree/eip-3074

Security Considerations

The following is a non-exhaustive list of checks/pitfalls/conditions that invokers should be wary of:

  • Replay protection should be implemented by the invoker, and included in commit. Without it, a malicious actor can reuse a signature, repeating its effects.
  • value should be included in commit. Without it, a malicious sponsor could cause unexpected effects in the callee.
  • gas should be included in commit. Without it, a malicious sponsor could cause the callee to run out of gas and fail, griefing the sponsee.
  • The current chain id should be included in commit and checked on every transaction. Without it, a malicious sponsor could replay a signature on a different chain.
  • addr and calldata should be included in commit. Without them, a malicious actor may call arbitrary functions in arbitrary contracts.

A poorly implemented invoker can allow a malicious actor to take near complete control over a signer's EOA.

Copyright and related rights waived via CC0.