Skip to content

Commit

Permalink
Update EIP-7851: multiple fixes and refinements
Browse files Browse the repository at this point in the history
Merged by EIP-Bot.
  • Loading branch information
colinlyguo authored Jan 12, 2025
1 parent a34d0be commit 05fadf7
Showing 1 changed file with 31 additions and 39 deletions.
70 changes: 31 additions & 39 deletions EIPS/eip-7851.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ eip: 7851
title: Deactivate/Reactivate a Delegated EOA's Key
description: Introduce a new precompiled contract for EOAs with delegated code to deactivate or reactivate private keys.
author: Liyi Guo (@colinlyguo)
discussions-to: https://ethereum-magicians.org/t/eip-7851-eoa-private-key-deactivation-reactivation/22344
discussions-to: https://ethereum-magicians.org/t/eip-7851-deactivate-reactivate-a-delegated-eoas-key/22344
status: Draft
type: Standards Track
category: Core
Expand All @@ -30,7 +30,7 @@ The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "S
| Constant | Value |
|-----------------------------------|----------------------|
| `PRECOMPILE_ADDRESS` | `0xTBD` |
| `PRECOMPILE_GAS_COST` | `5000` (tentative) |
| `PRECOMPILE_GAS_COST` | `5000` |

### Delegated code encoding

Expand Down Expand Up @@ -60,18 +60,18 @@ For EOAs with delegated code (begins with the prefix `0xef0100`), the following
- The transaction pool MUST implement the same validation to prevent invalid transactions from propagating across the network.
- Any [EIP-7702](./eip-7702) authorization from an authority with a deactivated delegated code MUST be considered invalid and skipped. The gas consumption rules remain unchanged, consistent with [EIP-7702](./eip-7702).

### Gas Cost
### Gas cost

The `PER_EMPTY_ACCOUNT_COST` and `PER_AUTH_BASE_COST` constants defined in [EIP-7702](./eip-7702) remain unchanged, since account code will be loaded during the authorization validation. This EIP only adds a code length check, which is a small overhead compared to existing logic.

## Rationale

### Using a precompiled contract

Alternative methods for implementing this feature include:
Alternative methods for deactivating and reactivating EOA private keys include:

- Adding a new transaction type: Introducing a new transaction type could provide a mechanism to deactivate and reactivate EOA private keys. However, reactivating the private key would rely on a delegated contract as the authorizer, which complicates defining the rules for the new transaction type.
- Deploying a regular smart contract: A regular deployed contract could track the deactivated status of each address. However, invoking this contract, which executes bytecode and accesses storage, during deactivation and reactivation, would be more expensive than using a precompiled contract. Additionally, this approach "leaks" the design of a (widely used) programming language into the Ethereum core protocol. While it poses no obvious security risks, it is not ideal from a design perspective.
- Deploying a smart contract: Using a smart contract to track the deactivated status of each address. However, calling this contract, which executes bytecode, is more expensive than a precompiled contract.

### In-protocol reactivation

Expand All @@ -83,29 +83,21 @@ The reactivation process is recommended to include a signed authorization from t

Users should delegate their EOAs only to wallets that have been thoroughly audited and follow best practices for security.

### `5000` Gas `PRECOMPILE_GAS_COST`
### `5000` gas `PRECOMPILE_GAS_COST`

The `5000` gas cost is sufficient to cover validation, computation, and storage updates for the delegated code.
The `5000` gas cost is sufficient for the precompiled contract's validation logic and storage updates.

### Alternative EOA deprecation approach

One alternative deprecation approach involves using a hard fork to edit all existing and new EOAs to upgradeable smart contracts, which utilize the original EOA private key for authorization. Users can then upgrade these smart contracts to achieve more granular permission control. However, this approach is incompatible with EOAs already delegated to smart contracts, as it will overwrite the existing smart contract implementations. The EIP aims to fill this migration gap.
One alternative deprecation approach involves using a hard fork to edit all existing and new EOAs to pre-written upgradeable smart contracts, which utilize the original EOA private key for authorization. Users can add and replace keys, or upgrade the smart contracts to other implementations. However, this approach is incompatible with EOAs already delegated to smart contracts, as it will overwrite the existing smart contract implementations. This EIP aims to fill this migration gap.

### Avoiding delegated code prefix modification

This EIP appends a byte (`0x00`) to the delegated code instead of modifying the prefix (`0xef0100`) of [EIP-7702](./eip-7702) to ensure forward compatibility. If new prefixes such as `0xef0101` are introduced in the future, changing the prefix (e.g. to `0xef01ff`) makes it unclear which prefix to restore upon reactivation.
This EIP appends a byte (`0x00`) to the delegated code instead of modifying the prefix (`0xef0100`) of [EIP-7702](./eip-7702) to ensure forward compatibility. If new prefixes such as `0xef0101` are introduced in the future, changing the prefix to represent the deactivated status (e.g. `0xef01ff`) makes it unclear which prefix to restore (`0xef0100` or `0xef0101`) upon reactivation.

### Avoiding account state changes

Another alternative is to add a bool field `deactivated` in the account state to track the status. However, this approach will introduce backward compatibility logic and more test vectors related to this optional field when enabling this EIP, because the field is not present in existing accounts.

### Forwards compatibility for removing EOAs

After all existing and future EOAs have been migrated to smart contracts. It's natural and also easy to deprecate this EIP with a single upgrade, which involves some clean-ups:

- Removing the precompiled contract.
- Removing all validation logic of the deactivation status since all EOAs are smart contracts.
- Removing the appended `0x00` byte from the delegated code of deactivated EOAs, which this EIP introduces.
An alternative is to add a `deactivated` field in the account state to track the status. However, this approach will introduce backward compatibility logic and more test vectors related to this optional field when enabling this EIP, because the field is not present in existing accounts.

## Backwards Compatibility

Expand All @@ -121,25 +113,25 @@ precompile = PrecompiledContract()
# Test 1: Valid activation and deactivation
caller = "0x0123"
delegated_addr = bytes.fromhex("1122334455667788990011223344556677889900")
active_code = PrecompiledContract.DELEGATED_CODE_PREFIX + delegated_addr
active_code = PrecompiledContract.DELEGATED_CODE_PREFIX + delegated_addr # Active state

state_db.set_code(caller, active_code)
error, gas_left = precompile.execute(caller, state_db, gas=10000)
assert error == b""
assert state_db.get_code(caller) == active_code + b"\x00" # Deactivated
assert gas_left == 10000 - PrecompiledContract.GAS_COST
assert state_db.get_code(caller) == active_code + b"\x00" # Deactivated state
assert gas_left == 10000 - PrecompiledContract.PRECOMPILE_GAS_COST

error, gas_left = precompile.execute(caller, state_db, gas=10000)
assert error == b""
assert state_db.get_code(caller) == active_code # Activated
assert gas_left == 10000 - PrecompiledContract.GAS_COST
assert state_db.get_code(caller) == active_code # Reactivated state
assert gas_left == 10000 - PrecompiledContract.PRECOMPILE_GAS_COST

# Test 2: Error cases
error, gas_left = precompile.execute(caller, state_db, gas=10000, static=True)
assert error == b"cannot call in static context"
error, gas_left = precompile.execute(caller, state_db, gas=10000, read_only=True)
assert error == b"STATICCALL disallows state modification"
assert gas_left == 0

error, gas_left = precompile.execute(caller, state_db, gas=PrecompiledContract.GAS_COST-1)
error, gas_left = precompile.execute(caller, state_db, gas=PrecompiledContract.PRECOMPILE_GAS_COST-1)
assert error == b"insufficient gas"
assert gas_left == 0

Expand All @@ -161,46 +153,46 @@ assert gas_left == 0

```python
class PrecompiledContract:
DELEGATED_CODE_PREFIX = bytes.fromhex("ef0100") # EIP-7702 prefix
GAS_COST = 5000 # PRECOMPILE_GAS_COST
DELEGATED_CODE_PREFIX = bytes.fromhex("ef0100")
PRECOMPILE_GAS_COST = 5000

def execute(self, caller, state_db, gas, static=False):
def execute(self, caller, state_db, gas, read_only=False):
"""
Toggle EOA's private key authorization between active/deactivated states.
Switch the private key state of delegated EOAs between active and deactivated.
Parameters:
- caller: The address calling the contract
- state_db: The state database
- gas: Gas provided for execution
- static: Whether called in read-only context
- read_only: Whether called in read-only context
Returns:
- Tuple of (result, gas_left)
result: error bytes on failure, empty bytes on success
gas_left: remaining gas, 0 on error
"""
# Check gas
if gas < self.GAS_COST:
if gas < self.PRECOMPILE_GAS_COST:
return b"insufficient gas", 0

# Check static call
if static:
return b"cannot call in static context", 0
# Check STATICCALL
if read_only:
return b"STATICCALL disallows state modification", 0

# Get and validate caller's code
code = state_db.get_code(caller)
if not code.startswith(self.DELEGATED_CODE_PREFIX):
return b"invalid delegated code prefix", 0

# Update code based on length
# Update delegated code based on length
if len(code) == 23: # Active state
state_db.set_code(caller, code + b"\x00") # Deactivate
elif len(code) == 24: # Deactivated state
state_db.set_code(caller, code[:-1]) # Activate
else: # Although this is not possible, it's added for completeness
return b"invalid code length", 0
return b"invalid delegated code length", 0

return b"", gas - self.GAS_COST
return b"", gas - self.PRECOMPILE_GAS_COST

class StateDB:
"""Simplified state database, omitting other account fields"""
Expand Down Expand Up @@ -240,7 +232,7 @@ This EIP does not revoke [ERC-2612](./eip-2612) permissions. EOAs with deactivat

For deactivation through EOA-signed transactions, the replay protection mechanism provided by [EIP-155](./eip-155), if enabled, can effectively prevent cross-chain message replay.

For deactivation/reactivation called by the delegated contract, the contract should ensure that the chain ID is part of the message validation process (or implement alternative replay protection mechanisms) to prevent cross-chain message replay.
For deactivation or reactivation called by the delegated contract, the contract should ensure that the chain ID is part of the message validation process (or implement alternative replay protection mechanisms) to prevent cross-chain message replay.

## Copyright

Expand Down

0 comments on commit 05fadf7

Please sign in to comment.