mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-23 20:28:21 +00:00
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
196 lines
12 KiB
Markdown
196 lines
12 KiB
Markdown
---
|
|
eip: 3074
|
|
title: AUTH and AUTHCALL opcodes
|
|
author: Sam Wilson (@SamWilsn), Ansgar Dietrichs (@adietrichs), Matt Garnett (@lightclient), Micah Zoltu (@micahzoltu)
|
|
discussions-to: https://ethereum-magicians.org/t/eip-3074-sponsored-transaction-precompile/4880
|
|
status: Draft
|
|
type: Standards Track
|
|
category: Core
|
|
created: 2020-10-15
|
|
---
|
|
|
|
## Simple Summary
|
|
|
|
Creates two new EVM instructions that authorize (via an ECDSA signature) a contract to act on behalf of an externally owned account.
|
|
|
|
## 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`.
|
|
|
|
## 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.
|
|
|
|
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.
|
|
|
|
While it is possible to emulate sponsored transactions (ex. [Gas Station Network](https://www.opengsn.org/)), these solutions require specific support in callee contracts.
|
|
|
|
## Specification
|
|
|
|
### Conventions
|
|
|
|
- **`top - N`** - the `N`th 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](./eip-2718.md) transaction type reserved for EIP-3074 signatures to prevent signature collisions.
|
|
|
|
### Context Variables
|
|
|
|
| Variable | Type | Initial Value |
|
|
| ------------------- | --------- |:------------- |
|
|
| `authorizedAccount` | `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 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.
|
|
|
|
### `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` | `authorizedAccount` |
|
|
|
|
#### 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.
|
|
|
|
`AUTH` returns the new `authorizedAccount` 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 `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`.
|
|
|
|
The call value is deducted from the balance of the executing contract. It is not paid by the `authorizedAccount`.
|
|
|
|
`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.
|
|
|
|
#### 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 `authorizedAccount` 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.
|
|
|
|
### 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.
|
|
|
|
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.
|
|
|
|
`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 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 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.
|
|
|
|
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.
|
|
|
|
### Banning `tx.origin` as Signer
|
|
|
|
The reason for banning signatures from `tx.origin` is that subsequent `AUTHCALL`s 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.
|
|
|
|
### 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.
|
|
|
|
## Backwards Compatibility
|
|
|
|
No known issues.
|
|
|
|
## Test Cases
|
|
|
|
TODO
|
|
|
|
## Implementation
|
|
|
|
TODO
|
|
|
|
## 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
|
|
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|