Skip to content

Commit

Permalink
Some updates to 3074 (#3400)
Browse files Browse the repository at this point in the history
* authroizedAccount to just authorized

* reword motivation

* misc cleanup

* expand rationale, add a couple pics

* fix spelling
  • Loading branch information
lightclient authored Mar 16, 2021
1 parent 37c2216 commit 6314830
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 24 deletions.
71 changes: 47 additions & 24 deletions EIPS/eip-3074.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,17 @@ Creates two new EVM instructions that authorize (via an ECDSA signature) a contr

## Abstract

This EIP introduces two EVM instructions `AUTH` and `AUTHCALL`. The first sets a context variable `authorizedAccount` based on an ECDSA signature. The second sends a call as the `authorizedAccount`.
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`.

## Motivation

Sponsored transactions—the separation of fee payment from transaction content—have been a long standing feature request. Unlike similar proposals, this EIP specifies a method of implementing sponsored transactions that allows both externally owned accounts (EOAs) and [EIP-2938](./eip-2938.md) contracts to act as sponsors.
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.

With the explosion of tokens built on Ethereum, especially stable coins, it has become common for EOAs to hold valuable assets without holding any Ether at all. These assets must be converted to Ether before they can be used to pay gas fees, but without Ether to pay for the conversion, it's impossible to convert them. Sponsored transactions break the circular dependency.
This EIP takes a different approach. Instead of enshrining these capabilities in the protocol as transaction validity requirements, it provides developers with a flexible framework for developing novel transaction schemes for EOAs. A good way to think about this is that this EIP allows any EOA to become a smart contract wallet *without* deploying a contract.

While it is possible to emulate sponsored transactions (ex. [Gas Station Network](https://www.opengsn.org/)), these solutions require specific support in callee contracts.
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

Expand All @@ -44,11 +46,11 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network

| Variable | Type | Initial Value |
| ------------------- | --------- |:------------- |
| `authorizedAccount` | `address` | unset |
| `authorized` | `address` | unset |

The context variable `authorizedAccount` shall indicate the active account for `AUTHCALL` instructions in the current frame of execution. If set, `authorizedAccount` 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 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 -- `authorizedAccount` 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 `authorizedAccount`. Initially in each frame of execution, `authorizedAccount` is always unset, even if a previous execution frame for the same contract has a value.
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`)

Expand All @@ -67,18 +69,18 @@ A new opcode `AUTH` shall be created at `0xf6`. It shall take four stack element

| Stack | Value |
| ---------- | ------------------- |
| `top - 0` | `authorizedAccount` |
| `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 `authorizedAccount` is set to `signerAddress`.
In any other case, i.e. if the signature is invalid or `signerAddress == tx.origin`, `authorizedAccount` is reset to an unset value.
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 `authorizedAccount` if set, or `0` otherwise.
`AUTH` returns the new `authorized` if set, or `0` otherwise.

#### Gas Cost

Expand All @@ -93,7 +95,7 @@ A new opcode `AUTHCALL` shall be created at `0xf7`. It shall take seven stack el
| Stack | Value |
| --------- | ------------ |
| `top - 0` | `gas` |
| `top - 1` | `addr` |
| `top - 1` | `addr` |
| `top - 2` | `value` |
| `top - 3` | `argsOffset` |
| `top - 4` | `argsLength` |
Expand All @@ -110,16 +112,16 @@ A new opcode `AUTHCALL` shall be created at `0xf7`. It shall take seven stack el

`AUTHCALL` is interpreted the same as `CALL`, except for:

- If `authorizedAccount` 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 `authorizedAccount`.
- 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 `authorizedAccount`.
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 `authorizedAccount`, but leaves it unchanged.
Importantly, `AUTHCALL` does not reset `authorized`, but leaves it unchanged.

#### Gas Cost

Expand All @@ -129,33 +131,54 @@ As with `CALL`, the gas cost for the opcode itself (both the static and the dyna

## Rationale

### Throwing for Unset `authorizedAccount` During `AUTHCALL`
### Throwing for Unset `authorized` During `AUTHCALL`

A well-behaved contract should never reach an `AUTHCALL` without having successfully set `authorizedAccount` 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.
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](./eip-2718.md) Transaction Type

While clients should never interpret EIP-3074 signed messages as transactions, reserving an [EIP-2718](./eip-2718.md) transaction type reduces the likelihood of this occurring by accident.

### Another Sponsored Transaction EIP

Other approaches to sponsored transactions, which rely on introducing a new transaction type, are 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.
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, an instruction 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.
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?

Earlier approaches to this problem included mechanisms for replay protection, and also signed over value, gas, and other arguments to `AUTHCALL`. Instead, this proposal explicitly delegates these responsibilities to the invoker contract.
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](../assets/eip-3074/auth-msg.png)

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](../assets/eip-3074/auth-msg-multi-call.png)


### Invoker Contracts

As originally written, this proposal specified a precompile with storage to track nonces. Since a precompile with storage is unprecedented, a later revision moved replay protection into the invoker contract, necessitating a certain level of user trust in the invoker, while also opening the door to more creative replay protection schemes in the future. Building on this idea of trusted invokers, the other signed fields in the "transaction-like package" were eliminated until only `invoker` and `commit` remained.
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.

The motivation for including `invoker` is to bind a particular signed message to a single invoker. If `invoker` was not part of the message, a malicious invoker could reuse the signature to impersonate the EOA.
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.

Finally, `commit` should be used by invoker contracts to implement replay protection and security around calldata, value, and other parameters. For example, an invoker may assume `commit` to be `keccak256(abi.encode(gas, value, nonce))`, guaranteeing that the sponsee intended to set those parameters to those specific values. Without `commit`, invokers would not be able to determine if other values (eg. `gas`, `value`, calldata, etc.) had been tampered with.
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

Expand Down
Binary file added assets/eip-3074/auth-msg-multi-call.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/eip-3074/auth-msg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 6314830

Please sign in to comment.