This EIP introduces a system contract that enables an EIP-7702 delegated EOA to schedule an irreversible deactivation of its ECDSA private key for ECDSA-authenticated transaction sending and EIP-7702 authorizations. Deactivation uses a 7-day delay (cancellation window). After finalization, the key is permanently deactivated in-protocol. The system contract mapping serves as the single source of truth for key status.
EIP-7702 enables EOAs to gain smart contract wallet behavior, but the EOA's ECDSA key retains unrestricted signing authority that can override any delegated contract logic. This EIP provides a one-way migration path toward smart contract control, including post-quantum migration strategies, by allowing a delegated EOA to permanently disable its ECDSA transaction-signing authority after a safety delay.
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 and RFC 8174.
| Constant | Value |
|---|---|
SYSTEM_CONTRACT_ADDRESS |
0xTBD |
DEACTIVATION_DELAY |
604800 (7 days in seconds) |
The system contract is deployed at SYSTEM_CONTRACT_ADDRESS following the same mechanism as EIP-4788 and EIP-2935: a pre-signed deployment transaction using Nick's method is included at fork activation. Deployment parameters TBD.
Let ts = finalizationTimestamp[account]:
| Condition | Status | status() Return Value |
|---|---|---|
ts == 0 |
ACTIVE | 0 |
ts > 0 AND block.timestamp < ts |
PENDING | 1 |
ts > 0 AND block.timestamp >= ts AND account has EIP-7702 delegation designation |
DEACTIVATED | 2 |
ts > 0 AND block.timestamp >= ts AND account has no EIP-7702 delegation |
DORMANT | 3 |
DEACTIVATED indicates that the deactivation is finalized and currently enforced at the protocol level: the account is delegated and the transaction validation check rejects ECDSA-authenticated transactions and authorizations. DORMANT indicates that the deactivation has finalized in storage but is not currently enforced because the account's EIP-7702 delegation has been revoked. Re-delegating a DORMANT account will cause deactivation to take effect immediately and irreversibly; see Delegation Revocation During Pending Period.
msg.sender is not an EIP-7702 delegated EOA (code does not start with 0xef0100).finalizationTimestamp[msg.sender] != 0.finalizationTimestamp[msg.sender] = uint64(block.timestamp) + DEACTIVATION_DELAY.finalizationTimestamp[msg.sender] == 0.block.timestamp >= finalizationTimestamp[msg.sender] (already finalized).finalizationTimestamp[msg.sender] = 0.Note: cancel() intentionally omits the delegation check. This ensures an EOA that has revoked its delegation can still cancel a pending deactivation during the delay period, preventing accidental lockout.
The finalizationTimestamp mapping is at storage slot 0. The storage key for a given account is:
keccak256(abi.encode(account, uint256(0)))
The value is interpreted as a uint64 timestamp.
For any sender or authority address S whose code is an EIP-7702 delegation designation (23 bytes starting with 0xef0100), clients MUST read ts from the system contract's storage at SYSTEM_CONTRACT_ADDRESS using the key defined in Storage Layout (i.e., keccak256(abi.encode(S, uint256(0)))), interpreted as a uint64 timestamp. The key is considered deactivated when ts != 0 AND block.timestamp >= ts.
This check MUST be applied in the following contexts:
Because each deactivation check requires reading finalizationTimestamp from SYSTEM_CONTRACT_ADDRESS storage, PER_AUTH_BASE_COST as defined in EIP-7702 MUST be increased by COLD_SLOAD_COST (2100 gas) to cover the per-authority storage read. The one-time cold access to the system contract is absorbed by the base intrinsic gas (21000 gas).
Following the system contract pattern of EIP-4788, EIP-2935, and EIP-7002, this EIP uses a system contract to consolidate deactivation, cancellation, and status queries into a single on-chain entry point. Alternatives considered:
timestamp field in account state requires account RLP encoding and p2p protocol changes.Deactivation is intentionally irreversible. Allowing ECDSA key reactivation would defeat the post-quantum security guarantee. The 7-day delay provides a safety window for users who may have triggered deactivation accidentally or who detect an unauthorized deactivation attempt. During the delay period, the original ECDSA key can still cancel the deactivation.
No backwards compatibility issues. Deactivation state is maintained entirely within the system contract.
// SPDX-License-Identifier: CC0-1.0
pragma solidity ^0.8.24;
contract EIP7851KeyDeactivation {
mapping(address => uint64) public finalizationTimestamp;
uint64 public constant DEACTIVATION_DELAY = 604800; // 7 days in seconds
uint8 internal constant ACTIVE = 0;
uint8 internal constant PENDING = 1;
uint8 internal constant DEACTIVATED = 2;
uint8 internal constant DORMANT = 3;
error NotDelegatedEOA();
error NotScheduled();
error AlreadyScheduled();
error AlreadyDeactivated();
error AlreadyFinalized();
event DeactivationScheduled(address indexed account, uint64 finalizationTimestamp);
event DeactivationCancelled(address indexed account);
receive() external payable {
revert();
}
function deactivate() external {
if (!_has7702DelegationPrefix(msg.sender)) revert NotDelegatedEOA();
uint64 ts = finalizationTimestamp[msg.sender];
if (ts != 0) {
if (block.timestamp >= ts) revert AlreadyDeactivated();
revert AlreadyScheduled();
}
uint64 ft = uint64(block.timestamp) + DEACTIVATION_DELAY;
finalizationTimestamp[msg.sender] = ft;
emit DeactivationScheduled(msg.sender, ft);
}
function cancel() external {
uint64 ts = finalizationTimestamp[msg.sender];
if (ts == 0) revert NotScheduled();
if (block.timestamp >= ts) revert AlreadyFinalized();
delete finalizationTimestamp[msg.sender];
emit DeactivationCancelled(msg.sender);
}
function status(address account) external view returns (uint8) {
uint64 ts = finalizationTimestamp[account];
if (ts == 0) return ACTIVE;
if (block.timestamp < ts) return PENDING;
if (_has7702DelegationPrefix(account)) return DEACTIVATED;
return DORMANT;
}
function _has7702DelegationPrefix(address account) internal view returns (bool) {
bytes memory code = account.code;
return code.length == 23 && code[0] == 0xef && code[1] == 0x01 && code[2] == 0x00;
}
}
Deployed contracts using ECDSA signatures (e.g., permit of ERC-2612) will not automatically respect deactivation. New or upgradeable contracts SHOULD call the system contract's status() and reject signatures from addresses in the DEACTIVATED state.
For non-upgradeable contracts, the above method cannot be applied directly. The only clear path available, at the protocol level, is to modify ecRecover precompiled contract: if the private key of the recovered address is deactivated, the ecRecover precompiled contract could return a precompile contract error (or, if not adding an error return path, return a zero address or a collision-resistant address, such as 0x1). This is complemented by a companion EIP that mitigates ecRecover-related risks by making the precompile aware of private key deactivation status.
Deactivation is intentionally irreversible at the protocol level to provide post-quantum security guarantees. Users should understand that once the 7-day delay period has passed, their ECDSA key cannot be reactivated. The 7-day delay provides a safety window for users to cancel if needed.
During the 7-day delay period, the original ECDSA key can cancel the deactivation. This is intentional as it provides a safety mechanism against unauthorized deactivation attempts. However, if an attacker has access to the ECDSA key during the delay period, they can also cancel the deactivation. Users who suspect key compromise should take immediate action to secure their account through other means.
Because transaction validation only applies to delegated EOAs, revoking delegation while a deactivation is pending causes the enforcement check to stop applying. The user can continue to use the ECDSA key as a regular EOA and can cancel the pending deactivation via a direct transaction to cancel(), which intentionally omits the delegation check.
However, if the finalizationTimestamp has already passed (i.e., the deactivation has finalized in storage), cancel() can no longer be called. In this state, re-delegating the account via EIP-7702 will cause the deactivation to take effect immediately and irreversibly, because the delegation designation re-enables the validation check. Users who have a finalized deactivation in storage should be aware that any future re-delegation will permanently disable their ECDSA key, because the delegation designation re-enables the validation check. If re-delegation is intended, users SHOULD ensure the target contract is trusted, as the account will become entirely dependent on it for all future operations.
For deactivation via EOA-signed transactions, nonces and EIP-155 chain IDs provide replay protection. For deactivation via delegated contracts, the contract SHOULD implement its own replay-protection mechanisms, especially when the EOA is delegated to the same implementation across multiple chains.
Implementations SHOULD be aware that evicting transactions immediately upon deactivation may cause issues if the deactivation is reverted in a reorg. The SHOULD (rather than MUST) language for eviction provides flexibility.
Copyright and related rights waived via CC0.