---
eip: 8151
title: Private Key Deactivation Aware ecRecover
description: Modify ecRecover precompile to return 32 zero bytes for keys deactivated per EIP-7851
author: Liyi Guo (@colinlyguo), Nicolas Consigny (@nconsigny)
discussions-to: https://ethereum-magicians.org/t/eip-8151-private-key-deactivation-aware-ecrecover/27690
status: Draft
type: Standards Track
category: Core
created: 2026-02-09
requires: 7851
---

## Abstract

This EIP modifies the `ecRecover` precompile at address `0x0000000000000000000000000000000000000001` to respect [EIP-7851](./eip-7851.md) key deactivation. After performing ECDSA public key recovery, the precompile checks whether the recovered address has a deactivated private key. If so, it returns 32 zero bytes (the existing failure sentinel of `ecRecover`) instead of the recovered address.

## Motivation

[EIP-7851](./eip-7851.md) enables delegated EOAs (per [EIP-7702](./eip-7702.md)) to deactivate their private keys, preventing those keys from authorizing transactions or new delegation authorizations. However, the `ecRecover` precompile is currently a pure cryptographic function with no awareness of account state. Even after an EOA's key is deactivated, on-chain signature verification through `ecRecover` continues to succeed for that key. This affects contracts that rely on `ecRecover` for signature-based authorization, such as ERC-20 contracts implementing `permit` ([ERC-2612](./eip-2612.md)). Since many such contracts are immutable and cannot be updated to add deactivation checks, modifying `ecRecover` at the protocol level is a practical path.

## Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and [RFC 8174](https://www.rfc-editor.org/rfc/rfc8174).

| Constant                   | Value                                        |
|----------------------------|----------------------------------------------|
| `ECRECOVER_ADDRESS`        | `0x0000000000000000000000000000000000000001` |
| `COLD_ACCOUNT_ACCESS_COST` | 2600                                         |
| `WARM_ACCOUNT_ACCESS_COST` | 100                                          |

### Modified `ecRecover` Behavior

Starting at the activation of this EIP, the `ecRecover` precompile at address `ECRECOVER_ADDRESS` MUST perform the following steps:

1. Perform ECDSA public key recovery from the input `(hash, v, r, s)` as currently specified, yielding a `recovered_address`.
2. If recovery fails, return 32 zero bytes and consume `3000` gas.
3. If recovery succeeds, read the account code of `recovered_address` and determine whether the key is deactivated per [EIP-7851](./eip-7851.md).
4. If `recovered_address` has a deactivated key, return 32 zero bytes.
5. Otherwise, return `recovered_address` left-padded to 32 bytes.

### Deactivation Check

An address is considered to have a deactivated key if and only if its account code has a prefix of `0xef0100` and a length of exactly 24 bytes, consistent with the deactivated state `0xef0100 || delegate_address || 0x00` defined in [EIP-7851](./eip-7851.md):

```python
code = state.get_code(recovered_address)
is_deactivated = len(code) == 24 and code[:3] == bytes.fromhex("ef0100") and code[-1] == 0x00
```

### Gas Cost

When ECDSA recovery fails, no state access is performed and the gas cost
remains `3000`.

When ECDSA recovery succeeds, the precompile MUST access the account of
`recovered_address` to read its code. This access MUST follow the
[EIP-2929](./eip-2929.md) warm/cold rules:

- If `recovered_address` is already in the transaction's `accessed_addresses` set, the additional cost is `WARM_ACCOUNT_ACCESS_COST` (100).
- Otherwise, the additional cost is `COLD_ACCOUNT_ACCESS_COST` (2600), and `recovered_address` MUST be added to the `accessed_addresses` set, making it warm for subsequent operations within the same transaction.

## Rationale

### Protocol-Level Modification

Modifying `ecRecover` at the protocol level is chosen because many deployed contracts that rely on `ecRecover` for signature-based authorization (e.g., ERC-20 `permit` implementations) are immutable and cannot be upgraded to incorporate deactivation checks. A protocol-level change ensures these existing contracts automatically benefit from key deactivation without requiring redeployment.

### Returning 32 Zero Bytes

When a deactivated key is detected, the precompile returns 32 zero bytes rather than triggering an execution failure (`success = 0`). Currently, malformed `v`, out of range `r`/`s`, and failed recovery all return 32 zero bytes with `success = 1`, and `ecRecover` has never used execution failure to signal invalid input. Treating a deactivated key as another form of "recovery failed" keeps this convention intact.

Furthermore, introducing an execution failure path would break deployed contracts that wrap `ecRecover` with a low level `staticcall` and `require(success)`, turning a "signature not valid" result into an unexpected revert. Contracts that already check for a zero return will naturally reject deactivated keys without any code changes.

### EIP-2929 Gas Accounting

Since the precompile now reads account state, the additional gas cost follows the existing [EIP-2929](./eip-2929.md) warm/cold access pattern for consistency with the rest of the protocol. This avoids introducing a new gas model and ensures that the cost of state access is fairly accounted for.

## Backwards Compatibility

For addresses whose private key has been deactivated under [EIP-7851](./eip-7851.md), `ecRecover` now returns 32 zero bytes where it previously returned the recovered address.

On every successful ECDSA recovery, the precompile now performs an additional account access, adding either `WARM_ACCOUNT_ACCESS_COST` (100) or `COLD_ACCOUNT_ACCESS_COST` (2600) to the base cost of `3000`. Transactions that invoke `ecRecover` near their gas limit MAY fail with an out-of-gas error after activation.

## Test Cases

<!-- TODO -->

## Reference Implementation

```python
COLD_ACCOUNT_ACCESS_COST = 2600
WARM_ACCOUNT_ACCESS_COST = 100
BASE_GAS = 3000

def ecrecover_gas(state, recovered_address):
    if recovered_address is None:
        return BASE_GAS
    if recovered_address in state.accessed_addresses:
        return BASE_GAS + WARM_ACCOUNT_ACCESS_COST
    state.accessed_addresses.add(recovered_address)
    return BASE_GAS + COLD_ACCOUNT_ACCESS_COST

ZERO_BYTES32 = b'\x00' * 32
DELEGATED_CODE_PREFIX = b'\xef\x01\x00'
DEACTIVATED_CODE_LEN = 24  # len(0xef0100 || address || 0x00)

def ecrecover(state, hash: bytes, v: int, r: int, s: int) -> bytes:
    # None means recovery failure
    recovered_address = ecdsa_recover(hash, v, r, s)

    if recovered_address is None:
        return ZERO_BYTES32

    # Check EIP-7851 deactivation
    code = state.get_code(recovered_address)
    if len(code) == DEACTIVATED_CODE_LEN and code[:3] == DELEGATED_CODE_PREFIX and code[-1] == 0x00:
        return ZERO_BYTES32

    return recovered_address.rjust(32, b'\x00')
```

## Security Considerations

### Contracts Not Checking for Zero Return

Contracts that use `ecrecover` but do not verify the result is non-zero are already vulnerable to accepting invalid signatures. This EIP maps deactivated keys to the same failure sentinel (32 zero bytes) and therefore does not introduce a new class of vulnerability.

### Application-Level ECDSA Verification

This EIP only modifies the `ecrecover` precompile. Contracts that perform ECDSA recovery in application-level code (e.g., pure Solidity implementations) bypass the precompile and will not observe deactivation.

### Cross-Domain / L2 Fault Proof Implications

Some systems re-execute `ecrecover` in a different context (different chain, different layer, or asynchronously at a later time) and assume it is a pure function of `(hash, v, r, s)`. Because this EIP introduces a state read (the recovered address's account code), that assumption no longer holds.

In particular, L2 fault proof systems that accelerate `ecrecover` by executing the L1 precompile and caching the result may become incorrect. Mitigations include removing such acceleration, or migrating to a pure recovery primitive (e.g., implementing secp256k1 recovery inside the fault-proof VM or via a new dedicated pure-recovery precompile in L1), so that the accelerated operation remains a pure function of its inputs.

### Multi-Chain Considerations

Key deactivation under EIP-7851 is per-chain state. A key deactivated on one chain remains active on other chains. Users SHOULD NOT assume that deactivating their key on one chain protects them across all EVM-compatible chains.

## Copyright

Copyright and related rights waived via [CC0](../LICENSE.md).
