mirror of
https://github.com/status-im/EIPs.git
synced 2025-02-23 12:18:16 +00:00
Automatically merged updates to draft EIP(s) 3074 (#3307)
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
This commit is contained in:
parent
7874ef7aaa
commit
dd8dc94a94
188
EIPS/eip-3074.md
188
EIPS/eip-3074.md
@ -11,13 +11,11 @@ created: 2020-10-15
|
||||
|
||||
## Simple Summary
|
||||
|
||||
Creates a new precompile, analogous to `CALL` (`0xF1`), that sets `CALLER` (`0x33`) based on an ECDSA signature.
|
||||
Creates a new precompile, analogous to `CALL` (`0xF1`), that sets `CALLER` (`0x33`) and `ORIGIN` (`0x32`) based on an ECDSA signature.
|
||||
|
||||
## Abstract
|
||||
|
||||
This EIP creates two precompiles:
|
||||
- `CALL_PRECOMPILE` - forwards a `CALL`, setting `CALLER` according to an ECDSA signature, using a invoker-sponsee nonce for replay protection.
|
||||
- `NONCE_PRECOMPILE` - provides access to invoker-sponsee nonces expected by `CALL_PRECOMPILE`.
|
||||
This EIP creates a single precompile (`CALL_PRECOMPILE`) which forwards a `CALL`, setting `CALLER` and `ORIGIN` according to an ECDSA signature.
|
||||
|
||||
## Motivation
|
||||
|
||||
@ -29,92 +27,100 @@ While it is possible to emulate sponsored transactions (ex. [Gas Station Network
|
||||
|
||||
## Specification
|
||||
|
||||
Sponsored transactions are implemented with the addition of two precompiles:
|
||||
A precompile, at address `0x13`, functions like a `CALL` instruction that additionally:
|
||||
|
||||
- The first, at address `0x13`, which functions like a `CALL` instruction that additionally sets the caller address based on an ECDSA signature.
|
||||
- The second, at address `0x14`, provides access to the current nonce for the given invoker-sponsee pair.
|
||||
- sets the caller and origin addresses based on an ECDSA signature, and
|
||||
- optionally transfers Ether from the recovered account.
|
||||
|
||||
When invoked through `CALL` (`0xF1`), `DELEGATECALL` (`0xF4`), or `CALLCODE` (`0xF2`) the `CALL_PRECOMPILE` invokes the callee as if it had used `CALL` (`0xF1`.) When invoked through `STATICCALL` (`0xFA`), the `CALL_PRECOMPILE` invokes the callee as if it had used `STATICCALL` (`0xFA`.)
|
||||
|
||||
### Definitions
|
||||
|
||||
- **`CALL_PRECOMPILE`** - the specific precompile at address `0x13`, introduced by this EIP, which implements the `CALL` analogue.
|
||||
- **`NONCE_PRECOMPILE`** - the specific precompile at address `0x14`, introduced by this EIP, which exposes invoker-sponsee nonces.
|
||||
- **Transaction-like Package** - the signed arguments passed to `CALL_PRECOMPILE`.
|
||||
- **Sponsor** - the account which is responsible for paying gas fees and sending the transaction. May or may not be the same as the invoker.
|
||||
- **Sponsee** - the account which signed the transactions-like package.
|
||||
- **Invoker** - the account or contract which directly calls into `CALL_PRECOMPILE`. May or may not be the same as the sponsor.
|
||||
- **Sponsor** - the account which is responsible for paying gas fees and sending the transaction.
|
||||
- **Sponsee** - the account which signed the transaction-like package.
|
||||
- **Invoker** - the account or contract which directly calls into `CALL_PRECOMPILE`.
|
||||
- **Callee** - the target of the call from `CALL_PRECOMPILE`.
|
||||
|
||||
### Constants
|
||||
|
||||
| Constant | Value | Description |
|
||||
| ---------------- | ------ |:----------------------------------------------------------------------------------- |
|
||||
| `SPONSORED_TYPE` | `0x03` | [EIP-2718](./eip-2718.md) transaction type reserved for transaction-like packages. |
|
||||
|
||||
### API
|
||||
|
||||
#### `CALL_PRECOMPILE`
|
||||
|
||||
`CALL_PRECOMPILE` requires the following eight arguments:
|
||||
`CALL_PRECOMPILE` requires the following arguments:
|
||||
|
||||
- `nonce` - the next nonce value, as described below;
|
||||
- `to` - the address of the callee (not `CALL_PRECOMPILE`);
|
||||
- `gaslimit` - the minimum gas limit which must be provided with the call into `CALL_PRECOMPILE`;
|
||||
- `value` - the exact amount of Ether in wei to transfer from the invoker to the callee;
|
||||
- `data` - the calldata for the call into `to`; and
|
||||
- `v`, `r`, `s` - signature for the package, including chain id as specified in [EIP-155](./eip-155.md).
|
||||
- `type: uint8` - [EIP-2718](./eip-2718.md) transaction type (currently always `SPONSORED_TYPE`);
|
||||
- `sponsee: address` - address of the sponsee;
|
||||
- `nextra: uint256` - extra data, which can be used in the invoker to implement replay protection;
|
||||
- `to: address` - address of the callee (not `CALL_PRECOMPILE`);
|
||||
- `mingas: uint256` - minimum gas limit which must be provided with the call into `CALL_PRECOMPILE`;
|
||||
- `valueTotal: uint256` - exact amount of Ether in wei to be received by the callee;
|
||||
- `valueSponseeMax: uint256` - maximum amount of Ether in wei that can be deducted from the sponsee's balance;
|
||||
- `data: bytes` - the calldata for the call into `to`; and
|
||||
- `v: uint8`, `r: bytes32`, `s: bytes32` - signature for the package, including chain id as specified in [EIP-155](./eip-155.md).
|
||||
|
||||
The arguments to `CALL_PRECOMPILE` are encoded as `rlp([nonce, gaslimit, to, value, data, v, r, s])`.
|
||||
The arguments to `CALL_PRECOMPILE` are encoded as `type || abi.encode(sponsee, nextra, mingas, to, valueTotal, valueSponseeMax, data, v, r, s)`.
|
||||
|
||||
The signature (`v`, `r`, `s`) arguments are computed from `secp256k1(keccak256(rlp([nonce, gaslimit, to, value, data, invoker, chainid])))`.
|
||||
The signature (`v`, `r`, `s`) arguments are computed from `secp256k1(keccak256(type || abi.encode(nextra, mingas, to, valueTotal, valueSponseeMax, data, invoker, chainid)))`.
|
||||
|
||||
`CALL_PRECOMPILE` returns a failure without changing the nonce in the following situations:
|
||||
`CALL_PRECOMPILE` returns a failure in the following situations:
|
||||
- `type != SPONSORED_TYPE`
|
||||
- Invalid signature
|
||||
- Future or past nonce
|
||||
- Gas limit supplied with the call into `CALL_PRECOMPILE` is less than the signed `gaslimit`
|
||||
- The invoker's balance is insufficient to pay for the supplied gas plus `value`
|
||||
- The address recovered from `v`, `r`, and `s` does not match `sponsee`
|
||||
- Gas limit supplied with the call into `CALL_PRECOMPILE` is less than the signed `mingas`
|
||||
- The transaction's remaining gas is less than the signed `mingas`
|
||||
- The value sent with the call is less than `valueTotal - min(valueSponseeMax, valueTotal)`
|
||||
- The value sent with the call is greater than `valueTotal`
|
||||
- The sponsee's balance is less than `valueTotal` minus the value sent with the call
|
||||
|
||||
`CALL_PRECOMPILE` returns a success in all other cases.
|
||||
|
||||
The return data of `CALL_PRECOMPILE` will be a single byte to indicate the status of the call into callee followed immediately by the return data from that call.
|
||||
|
||||
#### `NONCE_PRECOMPILE`
|
||||
|
||||
`NONCE_PRECOMPILE` requires the following two arguments:
|
||||
|
||||
- `invoker` - the invoker address; and
|
||||
- `sponsee` - the sponsee address.
|
||||
|
||||
Assuming the calldata is the correct length, `NONCE_PRECOMPILE` will return a success, and place the nonce associated with the address pair in the return data.
|
||||
|
||||
### Nonces
|
||||
|
||||
The two precompiles will maintain a nonce for each pair of invoker address and sponsee address, in essence:
|
||||
|
||||
```
|
||||
{
|
||||
(0x1234...5678, 0xEEEE...EEEE) => 55,
|
||||
(0x1122...3344, 0xBBBB...BBBB) => 89,
|
||||
}
|
||||
```
|
||||
|
||||
Where:
|
||||
* `0x1234...5678` and `0x1122...3344` are the invoker addresses;
|
||||
* `0xEEEE...EEEE` and `0xBBBB...BBBB` are the sponsee addresses; and
|
||||
* `55` and `89` are the current nonce values for their respective pairs.
|
||||
|
||||
The nonce shall be incremented whenever a correctly signed transaction-like package containing the next nonce is submitted to `CALL_PRECOMPILE` with a sufficient gas limit.
|
||||
|
||||
### Gas Fees
|
||||
|
||||
#### `CALL_PRECOMPILE`
|
||||
|
||||
TODO: Probably something like the sum of:
|
||||
* The cost of a normal call, including calldata and signature size, etc.
|
||||
* An `SLOAD` to read the current nonce
|
||||
* An `SSTORE` to write the updated nonce
|
||||
* Cost of an ecrecover
|
||||
The gas fees for `CALL_PRECOMPILE` are calculated according the the following pseudo-code:
|
||||
|
||||
#### `NONCE_PRECOMPILE`
|
||||
```python
|
||||
S_cd = len(data) # In 256-bit words, rounded up
|
||||
|
||||
TODO: Probably something like the sum of:
|
||||
* An `SLOAD` to read the current nonce
|
||||
fees = 3200 + (12 * S_cd) + (S_cd**2 // 512)
|
||||
|
||||
if valueTotal > msg.value and valueSponseeMax != 0:
|
||||
fees += 400
|
||||
|
||||
if preconditions_good(...):
|
||||
return fees + cost_of_call(S_cd) # TODO
|
||||
else:
|
||||
return fees
|
||||
```
|
||||
|
||||
## Rationale
|
||||
|
||||
### Prefixing Callee Return Data with Status
|
||||
|
||||
It is important to differentiate between a failure in `CALL_PRECOMPILE`'s preconditions versus a failure in the callee. Correctly implementing replay protection requires the invoker to change its state even if the callee fails (to burn the nonce) but doing so if, for example, the signature failed would be nonsensical.
|
||||
|
||||
### Sponsee in Arguments
|
||||
|
||||
Including `sponsee` in the arguments to `CALL_PRECOMPILE` is a gas optimization. Without it, invokers would have to do their own `ecrecover` before calling into `CALL_PRECOMPILE` to verify/adjust any state for replay protection.
|
||||
|
||||
### Sponsoring an Account with Ether
|
||||
|
||||
Allowing `CALL_PRECOMPILE` to transfer Ether on the sponsee's behalf provides a uniform interface for all transactions a sponsee might want to perform. Sponsees with sufficient Ether might use a sponsor when swapping Ether for an ERC-20 on a subsidized exchange, or when relying on that sponsor to resubmit transactions to optimize gas pricing.
|
||||
|
||||
### Reserving an [EIP-2718](./eip-2718.md) Transaction Type
|
||||
|
||||
While clients should never directly interpret transaction-like packages as true transactions, reserving an [EIP-2718](./eip-2718.md) transaction type for transaction-like packages reduces the likelihood of a transaction-like package being misinterpreted as a true transaction.
|
||||
|
||||
### 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.
|
||||
@ -125,21 +131,47 @@ Besides better compatibility with AA, a precompile is a much less intrusive chan
|
||||
|
||||
More logic can be implemented around the call into `CALL_PRECOMPILE`, giving more control to invokers and sponsors without sacrificing security or user experience for sponsees.
|
||||
|
||||
### Nonces
|
||||
### Lack of Replay Protection
|
||||
|
||||
Other nonce schemes either do not provide enough security, or are too inefficient/inconvenient to be practical.
|
||||
|
||||
- Use sponsor nonce: every transaction from the sponsor's account invalidates every transaction-like package.
|
||||
- Use invoker nonce: every sponsored transaction from the invoker's account invalidates every other transaction-like package. Also interacts with `SELFDESTRUCT`.
|
||||
- Use sponsee nonce: the sponsee could attack the sponsor by submitting another transaction with a conflicting nonce at a higher gas price.
|
||||
|
||||
Instead, by creating an independent nonce per invoker-sponsee pair, we get some attractive properties:
|
||||
- A transaction package can only be invalidated if both the invoker and sponsee cooperate, which is nice for EOA sponsors, and necessary for AA contracts.
|
||||
- The `SELFDESTRUCT` operation doesn't introduce replay attacks.
|
||||
Earlier approaches to this problem included mechanisms for replay protection. This proposal explicitly does not handle replay protection, but instead includes a signed-but-unused field (`nextra`) which is expected to be used by invoker contracts to implement replay protection. Delegating replay protection to the invoker sidesteps the issue of giving a precompile contract its own storage, while opening the door to more innovative replay protection methods in the future.
|
||||
|
||||
### Precompile vs. Opcode
|
||||
|
||||
Opcodes do not have externally visible addresses, and therefore cannot be called directly by an EOA. Using a precompile allows a sponsor to avoid an intermediary contract call should they want to use this functionality directly.
|
||||
There are several implementation details about this operation that might evolve over time (ex. the signature algorithm.) If each evolution of sponsored transactions required a new opcode, the opcode space would be quickly exhausted.
|
||||
|
||||
Further, as a precompile, sponsored transactions can be combined with the existing call opcodes (`CALL`, `STATICCALL`, etc) to achieve different levels of state mutability. This would require several opcodes (`SIGCALL`, `SIGSTATICCALL`, etc) and would further exhaust the opcode space.
|
||||
|
||||
### Changing `SENDER` & `ORIGIN`
|
||||
|
||||
Existing contracts were built under assumptions that this proposal changes or outright breaks.
|
||||
|
||||
> `extcodesize(msg.sender) == 0` asserts that the caller is not a contract.
|
||||
|
||||
This is not strictly true without this proposal (ex. contracts under construction have no code.) Contracts relying on this assumption are therefore insecure regardless.
|
||||
|
||||
> `tx.origin` is the gas payer.
|
||||
|
||||
It is unlikely that contracts making this assumption (i.e. ones which already implement sponsored transactions) would continue to be used after this proposal is implemented.
|
||||
|
||||
> `tx.origin == msg.sender` asserts that the current execution frame is the topmost frame.
|
||||
|
||||
Several properties are unique to the topmost execution frame:
|
||||
|
||||
1. The return value is inaccessible.
|
||||
2. Reverting or returning in the topmost frame ends execution.
|
||||
3. The caller is an EOA and not a contract.
|
||||
|
||||
Properties (1) and (2) no longer hold when `tx.origin == msg.sender`, while property (3) is maintained: only an EOA can sign a transaction-like package.
|
||||
|
||||
It is unlikely, but not impossible, for a contract to only return a value when called by an EOA. It is difficult to imagine uses cases for such behavior, so invalidating property (1) seems to have low impact.
|
||||
|
||||
Property (2) would likely have the greatest impact, for two reasons: it creates the opportunity for atomicity where there was none before, and it makes pre- and post-conditions undetectable to the callee. Since the topmost frame is always executed, a contract in that frame can be certain—barring revert and out-of-gas conditions—that it will execute, regardless of the state changes it makes. With `CALL_PRECOMPILE`, an invoker could revert the callee if certain post-conditions were not met, allowing a retry at a later time. That said, a miner can break either of these assumptions by, respectively, executing two separate but adjacent transactions, or simply excluding a transaction that doesn't meet the pre- or post-conditions.
|
||||
|
||||
> Only one `tx.origin` can exist in a single transaction.
|
||||
|
||||
Reentrancy guards that rely on `tx.origin` cease to function under this proposal.
|
||||
|
||||
If setting `ORIGIN` is unacceptable, an alternative is to not set `ORIGIN` and for `CALL_PRECOMPILE` to fail if `sponsor == sponsee`.
|
||||
|
||||
## Backwards Compatibility
|
||||
|
||||
@ -155,9 +187,21 @@ TODO
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- First precompiles that require persistent storage.
|
||||
### Changing `SENDER` & `ORIGIN`
|
||||
|
||||
- `tx.origin` is not always the gas payer.
|
||||
- Checking `msg.sender == tx.origin` does not prevent reentrancy.
|
||||
- Contracts cannot prevent or detect pre- or post- conditions by checking `msg.sender == tx.origin`.
|
||||
- Return values which were only accessible to EOAs are accessible to invoker contracts.
|
||||
|
||||
### Signature Verification & Reply Protection
|
||||
|
||||
- Potential impersonation attacks if there is a bug in the signature verification.
|
||||
- Potential replay-attack problems if there is a bug in the replay protection, or if two chains share chain ids.
|
||||
- Replay protection can be poorly implemented (or even maliciously broken) in the invoker.
|
||||
|
||||
### Frontrunning
|
||||
|
||||
- Transaction-like packages can be extracted from the original sponsor's transaction and resent by another sponsor.
|
||||
|
||||
## Copyright
|
||||
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
|
||||
|
Loading…
x
Reference in New Issue
Block a user