EIP-8130 Keystore Accounts
No call decision · Account abstraction · EIP status Draft · created 2025-10-14
Lets each account register multiple signing keys (actors) in an on-chain keystore and set a permission scope and expiry per key. The same permission setup works across any transaction format.
Why it is needed
Among current account abstraction proposals, the approach that leaves validation to wallet code requires nodes to pre-execute arbitrary EVM code before accepting a transaction. That in turn requires full state access, tracing infrastructure, and even a reputation system to guard against the cost of bad submissions. This proposal separates "who signed" (authentication) from "what that key can do" (authorization). Each key declares in advance an authenticator contract that checks the signature, so a node only needs to accept a small set of standard authenticators instead of running arbitrary code. Verification cost becomes predictable, and new algorithms such as passkeys or post-quantum signatures can be used just by adding one key that points to a new authenticator, without moving accounts. Also, one set of contracts and one signature-change format can manage the same key setup across multiple chains and multiple transport paths, which improves portability.
What changes
Today
- For an EOA, a single secp256k1 key is the whole account, and there is no way to change or split the key
- Using a new signature scheme such as passkeys requires deploying a new smart account
- Permission limits on session keys vary by wallet code, so a node cannot know them in advance
After EIP-8130
- Register multiple actors on one account and set a permission scope, expiry, and policy for each key
- A standard authenticator set (secp256k1, P-256, passkey, delegate) makes verification cost predictable
- A new signature algorithm only requires registering a new actor, and the account address stays the same
- The same setup works identically in plain EVM calls, ERC-4337, frame accounts, and native AA transactions
Who is affected
- Users: You can use multiple keys such as passkeys and session keys on the same account instead of being tied to a single key. You can also receive funds at the address before deployment.
- Wallet & app developers: Restricted keys such as subscription payments or spending limits can be implemented in a standard way (policies and manager contracts), and the same address and setup are reused across chains.
- Client teams: Integrating with an AA transaction type lets keystore reads and standard authenticators be implemented natively, and the result must be identical to contract execution.
- L2: The transaction validation path can adopt a policy that accepts only standard authenticators (canonical-only), and the set can be extended through a companion ERC process.
In depth
Actors and authenticators: separating authentication from authorization
An account registers actors in the keystore contract at KEYSTORE_ADDRESS. Each actor is bound to an authenticator contract, and the authenticator checks the signature with IAuthenticator.authenticate(hash, data) and returns a 32-byte identifier, actorId. The authenticator answers only "who signed", and what that actor can do is decided by the scope stored in the keystore. A consumer is any system that checks permissions through the keystore, including the native AA transaction type, EIP-8141 frame accounts, ERC-4337 accounts, and applications that verify signatures. For an EOA, its own secp256k1 key becomes an implicit admin actor (self-actor) without any keystore write, so existing accounts keep working and adoption is entirely optional.
The standard authenticator set
The initial set that natively verifying consumers must accept has four members: k1 (secp256k1, native sentinel K1_AUTHENTICATOR), p256 (P-256), passkey (WebAuthn/FIDO2), and delegate (signature delegation). delegate lets account A sign with account B's key, but a nested authenticator must not be delegate again and must be a standard authenticator, so the amount of verification is bounded. Standard authenticators are deployed at the same CREATE2 address on every chain and listed in a companion ERC (number TBD). Changing the set that is enshrined at the base layer requires a hard fork. Even when a new algorithm such as post-quantum signatures is added, an existing account only needs to register one actor that points to the new authenticator, with no migration or address change.
actor_config storage layout and scope bitmask
Each actor is packed into one slot as authenticator(20) ‖ expiry(6) ‖ scope(2) ‖ reserved(4). expiry is a uint48 Unix timestamp in seconds, where 0 means no expiry, and scope is a uint16 bitmask. scope 0x0000 means unrestricted and is also the admin test, so permission to change configuration belongs only to actors with exactly scope == 0x00. The bits are OPERATOR (0x01, initiating to any target and ERC-1271-level account signatures), SELF_PAYER (0x02, paying its own gas), SPONSOR_PAYER (0x04, sponsoring gas for others), POLICY (0x08, initiating only through the manager contract), and NONCE (0x10, using the sequential nonce channel). The check is fail-closed, so unknown bits grant no permission. Validity is settled with a single read of actor_config, which makes verification and invalidation cheap and predictable.
Actor policies: restricting a session key to a single target
Two decisions are independent. What gets stored is decided by the length of policyData. It must be empty or exactly manager (20 bytes) ‖ commitment (32 bytes), and any other length is rejected. Whether the sender is bound is decided by the POLICY bit and the absence of OPERATOR. An actor with POLICY set can only call the stored manager, and the top-level transaction value must be 0, so ETH movement happens only inside the manager and the account code. The manager contract reads the commitment (getPolicyCommitment), verifies the presented parameters, and performs only approved actions. The manager is fully trusted, so an audited non-upgradeable contract should be used, and revokeActor clears actor_config and the policy slot together. OPERATOR overrides POLICY, so OPERATOR must not be granted together with POLICY on a policy key.
Change channels, epochs, and account lock
Every actor and lock change goes through a single entry point, applySignedAccountChanges, as a batch carrying an admin signature, and anyone can relay it. The sequence of the Local channel is local_epoch (upper 32 bits) || local_sequence (lower 32 bits). The Multichain channel (chain_id 0) is a plain monotonic counter used to sync actor state across chains. If the lower half is UNSEQUENCED (2^32-1), the batch is a JIT batch that does not consume the counter and can be reused until the epoch changes. IncrementLocalEpoch cancels all unapplied local signatures from the previous epoch but does not touch live actors or the Multichain channel. Lock blocks every authority change and allows only Unlock and IncrementLocalEpoch. unlock_delay is bounded to the uint16 range (about 18.2 hours at most), which keeps an account from permanently locking its own configuration rotation.
Account creation and address derivation
createAccount(userSalt, bytecode, initialActors) derives the address with the EIP-1014 CREATE2 formula and places the runtime bytecode directly at that address without executing it. initial_actors are sorted by actorId in ascending order and their leaf hashes are combined into actors_commitment, and the address is computed with effective_salt = keccak256(user_salt || actors_commitment). Because the initial actors are committed in the salt, an attacker cannot front-run a deployment with a different actor set, and the same input yields the same address on every chain, so funds can be received at the address before deployment. expiry and manager = account cannot be expressed at creation and are added afterward through a configuration change. An already deployed account registers by calling importAccount() on itself, and it is installed only if the digest matches the actor set returned by the account code's confirmKeystoreImport().
Integration with the AA transaction type
This document also specifies integration with the native AA transaction type proposal. sender_auth and payer_auth take the form authenticator || data and are interpreted the same way as authenticateActor, and the reserved range assigns account change entries 0x02 (creation) and 0x03 (configuration change). Standard authenticators are charged a fixed, insured cost, while other authenticators are metered with a STATICCALL capped at MAX_AUTHENTICATION_GAS. The acceptance policy is set by each chain (permissive on L1, standard-only is typical on L2). The creation cost is 32,000 plus 200 per deployed byte plus 22,100 per initial actor slot write. A policy gate violation is an execution result in which the step is reverted with ActorPolicyViolation, not a validity error. An account with empty keystore state has identical validity, execution results and gas before and after the integration.
Status and progress
This EIP is in Draft status, so the specification may change. The contract internals (exact storage packing, typehashes, event ABI, function bodies) are authoritatively defined by the base organization's EIP-8130 repository (src/Keystore.sol), and implementations must match it. According to the call records, at ACDE #243 on August 13 the decision on whether to implement native account abstraction with the frame approach (EIP-8141) or with this proposal (EIP-8130) was deferred to the August 25 AA breakout and the August 27 ACDE, and the August 27 ACDE #244 also had discussion but no decision. Which approach will be adopted has not yet been decided.