EIP.tools

EIP.tools

⚠️ DraftStandards Track: ERC

ERC-8403: Account Authority Lifecycle

A committed-root add, rotate, and revoke model for native account-abstraction authorities, checked in the account's own authorization step

Authors
Created2026-08-29
Discussion Linkhttps://ethereum-magicians.org/t/erc-8403-account-authority-lifecycle/29570
Requires

Markdown

https://raw.githubusercontent.com/AnkushinDaniil/E...

EIP-GPT summary

Contents
AbstractMotivationSpecificationAuthority treeLifecycle operationsAuthorization checkIsolationPortable recoveryMultichain authorityBinding: EIP-8141Binding: keystore accountsDatum and lifecycleRead formLimits of the Read formPortable changesTransportsRationaleGeneral principleShared authorization objectWitness binding per modelResolved verifier and declared classAuthority-set availability and recoveryAcknowledgementsBackwards CompatibilitySecurity ConsiderationsCopyright

Abstract

This standard defines a common lifecycle for the authorities that authorize a native account-abstraction account: how an authority is added, rotated, and revoked. An authority (a signing key, or more generally an authorization predicate such as a spending lock or a limit) is a leaf in a per-account committed tree, and the account commits to that tree as a single root. A transaction carries a membership proof for the acting authority, names the root it proves against, and carries a witness by that authority over the transaction; the account verifies both in its own authorization step and withholds authorization on failure. Add is a leaf insertion, rotate is a leaf replacement, and revoke is a leaf removal, each realized as a new committed root.

The lifecycle is stated over a committed root and a membership proof, not over any one account model's opcodes. It therefore binds to more than one native account-abstraction model as a peer of each: this document gives a normative binding to EIP-8141 frame transactions and an informative binding to the keystore defined by EIP-8130, stated over the keystore's interface rather than its storage and usable from any transport that reads it. The same add, rotate, and revoke then carry one meaning across more than one native account-abstraction model and transaction type, because each reduces to the same membership-and-witness check, and a change signed once over a portable digest is verified the same way on every chain and transport that reads the account's records.

Motivation

Native account-abstraction designs let a transaction reach a target while the account is named as the caller, without running the account's own entry-point code. Any rule an account enforces only inside that entry point is therefore not consulted on such a path. The account must re-express the rule where authorization is decided.

The models that decide authorization do so differently. In the code-is-authority model (EIP-8141) the account's own code runs in a frame that can withhold approval. In the keystore or registry model (EIP-8130) the account declares an authenticator and validation resolves the account's records through the keystore's interface. What is missing across both is a shared meaning for the operations wallets perform on an account's authority set: add, rotate, revoke. Without one, every model defines these independently and wallets must reconcile all of them.

A shared lifecycle also keeps each model's authorization check light. Whichever model an account uses, proving the acting authority is a leaf under the account's named committed root means the check carries neither a scan of a foreign keystore nor a live external read, so it stays admissible to a public mempool. EIP-8272 supplies exactly this primitive: it lets a transaction name a recent commitment-tree root and have clients check the reference against pre-state before any account code runs, and its own Motivation names "wallet authorization roots" as an intended use.

This standard fixes that shared meaning once, model-agnostically: it constrains what add, rotate, and revoke mean over a committed authority root, not which authenticator or account layout an implementation uses.

Specification

The key words "MUST", "MUST NOT", "REQUIRED", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.

The core (Authority tree, Lifecycle operations, Authorization check, Isolation) is stated model-neutrally. Each binding below realizes the core with one model's primitives and defines the model-specific terms it uses.

Authority tree

An account MUST associate its authorities with a single committed tree. Each leaf is an authorization:

authorization = (authority_id, predicate)
  • authority_id is a 32-byte stable handle for an authority slot or role. It is chosen at add time and is independent of the key material, so rotating the underlying key does not change it.
  • predicate is the account-defined data the account's authorization step evaluates: for example a public key, an authenticator address, a threshold, a spend ceiling, or an unlock time.

The leaf encoding MUST be a deterministic function of the authority_id and every outcome-relevant input the authorization step consumes to interpret the authority: its predicate and any class, kind, or expiry that can change an add, rotate, or revoke decision. Implementations SHOULD fold these into the committed predicate and use leaf = keccak256(authority_id || predicate), so that reconstructing the leaf reconstructs what the authority is permitted to do and not only its identity, and independent tooling can rebuild and index a tree without account-specific knowledge. A field that changes an authorization outcome but is left outside the leaf breaks portable reconstruction (see Portable recovery).

The account MUST commit to this tree as a single root it controls and is the sole writer for. How that root is referenced is a per-account, per-tier choice defined in Authorization check: the account MAY name it as a referenceable recent root that clients check against pre-state, or hold it in an account-owned slot the check reads directly. Publishing the root to an external referencing mechanism is REQUIRED only when a reader that cannot see the account's own state has to verify against it, such as another chain, a keystore peer, or a validity-proof verifier. Each binding below specifies the referencing mechanisms.

Lifecycle operations

An account changes its authorities only by publishing a new root:

  • add: insert a leaf for a new authorization, publish the new root.
  • rotate: replace an existing authority_id's predicate (for example bind a new key to the same slot), publish the new root.
  • revoke: remove a leaf, publish the new root.

An operation takes effect when the new root becomes referenceable. Each binding specifies when that is.

Where a lifecycle operation is authorized by a signature, it MUST take effect at most once on each chain. Binding the signature to the change's content alone does not ensure this, because a predicate can recur: an authority revoked and later added again returns its record to an earlier value, and a change bound only to that value applies again. The signed digest MUST therefore also cover either a sequence that the record store consumes, or a per-authority counter that the record store advances on every change to that authority and never returns to an earlier value. A counter kept in state but not covered by the signature does not satisfy this rule.

A lifecycle operation is portable when one signature over it applies the same change on every chain and through every transport that reads the account's authority records. A signature meant for such use MUST sign a digest over the change's logical content: a fixed domain, a replay domain naming either one chain or all chains, the account, the authority_id, and the prior and new predicate in a fixed typed encoding that does not depend on how any record store lays the records out. Its digest MUST also cover the sequence or counter the rule above requires. A change bound to one chain SHOULD also bind the record store it is applied to, since on that chain it is otherwise relayed history once a different record store is read there. A change signed for all chains is relayed history on each chain that applies it and is bounded as Multichain authority states. The keystore binding shows one such digest.

Authorization check

The authorization step MUST establish that the acting authorization is committed to the account under a root the validating client already trusts. It does so in one of two forms, and which one applies is set by where the account keeps its authority records, not by free choice:

  • Carried. The transaction carries the membership proof and names the committed root it proves against; the client verifies the proof and reads no mutable account state. The tree shape and proof format are account-defined, and the named root is a referenceable recent root checked against pre-state.
  • Read. The check reads the acting authority's record directly from the account's own committed state under the client's trusted state root; that read is itself the membership evidence and the carried proof has length zero. The record is the account's own and keyed by the account, so no proof is needed to bind it.

Keeping the authority tree in readable on-chain state is optional, so the Carried form is the general requirement and the Read form is the special case it permits. An account MAY use the Read form only for records it keeps in on-chain state the validating client can read directly. When the account keeps its leaves off-chain, or the reader cannot see the account's state, such as another chain, a keystore peer, or a validity-proof verifier, the transaction MUST carry the membership proof and the Read form is not available. The Read form does not waive the proof; it lets the on-chain read stand in for it where the record is present to be read. The record MAY be reached through an interface, such as a keystore, rather than by a direct storage read, and MAY be kept in contract storage, in account state, or behind a precompile, provided what the interface returns for the account depends only on state that is a client-trusted root or writable only under the account's own authority. The check then depends on the account's records as the interface exposes them, not on their layout. An authority that resolves through another account's records, such as a delegate, meets this only as the keystore binding states for it.

Whichever form is used, every state word the check depends on MUST be either a client-trusted root or writable only under the account's own authority. This is the sole-invalidator property: a pending authorization can be invalidated only by the account itself or by the passage of blocks, never by an unrelated transaction, which is what keeps the check admissible to a bounded public mempool. Each binding specifies how each form is carried and read.

Membership does not by itself authorize. The leaf set is public, so a membership proof shows only that an authority is committed to the account, not that its holder approved this transaction. The transaction MUST additionally carry a witness that satisfies the acting authorization's predicate and binds to the transaction; for a key predicate this is a signature over the transaction's canonical hash. The two are independent and both are REQUIRED: the membership proof is keyless and binds the authority to the account, while the witness binds the action to the authority. The authorization step MUST withhold authorization if either the proof or the witness fails.

The authorization check MUST run in the account's own authorization step: the step whose failure withholds authorization of the transaction. That step is satisfied either by code the account controls, or by a protocol-defined validation whose every state word is a client-trusted root or writable only under the account's own authority. Placing the check where neither the account's own code nor such a protocol-defined validation governs the outcome does not satisfy this requirement. Each binding names this step.

The authorization step MUST NOT authorize execution unless membership is established for the acting authorization against an accepted root, by either form. The two forms have different revocation and admission properties, captured as two conformance tiers; an account MUST implement at least one.

  • Tier 1 (root-bound, mempool-cheap). The check reads only the transaction-named root reference and the carried proof. It accepts any referenceable root under the account's fixed root source and grants authorization only if the proof verifies against it. It MUST NOT read mutable account or external storage. Revocation latency is bounded by the referencing mechanism's recency window: a dropped leaf's earlier root stays referenceable until it leaves the window.

  • Tier 2 (floor-bound, instant revocation). The check performs one account-owned mutable read and makes rotate and revoke effective at the next block, at the cost of the mempool-cheap admission tier. An account MAY realize this in either of two equivalent ways: it keeps a recent-root reference as in Tier 1 and additionally reads a monotonic rotation-floor slot, rejecting any reference below the floor; or it holds its current authorized root in an account-owned slot and reads that slot directly as the root, so a superseded root stops being referenceable at once and no external publication or recency window applies. The direct-slot form applies to any number of authorities; the single-authority direct read is its degenerate case. Reading the acting authority's record directly from account-owned state, rather than a root over it, is the same Read form at path length zero, which is what a keystore account does natively. Next-block revocation holds on the chain where the revoking change is applied only if no change that grants the same authority can still be applied there afterwards. For records that reach a chain by relay, including a revoke applied locally to an authority that a relayed change can grant again, Multichain authority states the bound.

The account MUST fail closed: if no accepted authorization is proven, the authorization step MUST NOT authorize execution, and the transaction is invalid.

Isolation

Authorization MUST be keyed by the account: an authority proven for one account MUST NOT authorize another. Because each account proves against its own subtree under its own root source, revoking an authority affects only that account and does not invalidate authorities held by other accounts.

Portable recovery

Portable cross-wallet recovery is an optional property of an account's authority tree. An account that claims it MUST make its current authority records and its recovery scheme discoverable from account-controlled on-chain data, without relying on wallet-local state. The scheme MUST fix the canonical encoding of each authority record, authority ordering, tree arity and shape, padding and empty-tree rules, internal-node hashing and domain separation, the membership-proof encoding, and every outcome-relevant input used to interpret an authority record. The meaning of the scheme for a published root MUST stay fixed for as long as that root is referenceable.

Given the same authority records and the same scheme, two conforming implementations MUST derive the same committed root; given also the same evaluation context, they MUST NOT disagree on whether an authority permits a given lifecycle transition. A wallet MAY verify a root under any scheme, but MUST NOT claim semantic portability for an interpretation it cannot reproduce. A scheme MAY be account-specific: the account still conforms to the core, but its recovery is portable only to wallets that implement that scheme.

Multichain authority

The authorization check is a membership proof against a named committed root; it does not require the root to originate on the chain that runs the check. An account MAY name a root sourced from another chain, provided the executing chain already imports a trust-minimized commitment to that source and the check reads only that imported commitment and the carried proof.

An account that wants one authority set across chains publishes its root on a single home chain and lets an existing cross-domain root-delivery mechanism carry the commitment to each executing chain. A transaction on an executing chain carries two proofs: the membership proof for the acting authority under the authority root, and a proof, a storage proof, that this authority root is the committed value at the account's home-chain slot, checked against the home-chain root the executing chain already imports. Both are checked against a root the validator already holds, so the check performs no live cross-domain read and stays within whichever tier it already occupies. The home chain and slot MUST be fixed when the account is deployed. Every lifecycle operation MUST be applied on the home chain before it is relied on elsewhere. The check protects the account on an executing chain only if every path that can authorize for the account there runs it; a native validation that reads a local copy of the record instead is a relayed record and has the limits stated below.

This adds no operation to the lifecycle and no rule to the authorization check. It only widens what the named root's source MAY be, from a same-chain committed root to a home-chain root delivered by an existing cross-domain mechanism. The membership proof, fail-closed, and account-bounded isolation apply unchanged; the tiers apply with the latency stated here. The executing chain MUST read a finalized home-chain root, and cross-chain revocation latency is the freshness window of the imported root: root-bound accepts any imported finalized root within the window, and floor-bound accepts only the most recent finalized home root that the import mechanism itself identifies as finalized. An import mechanism that does not identify finality cannot support floor-bound cross-chain revocation. Under floor-bound a revoke takes effect on the executing chain when its home block is finalized and imported, not at the next block. A floor kept in a slot on the executing chain and advanced by relaying home changes is a relayed record and does not give floor-bound revocation.

An account MAY instead keep its authority records in step across chains by relaying a history of changes signed for any chain and applying them in order on each chain. Such a history proves that each change was once authorized, not that no later change exists. On a chain whose copy is empty or behind, any party may apply the history up to a point it chooses, stop before a revoke, and use the result in the same transaction. A revoke made elsewhere therefore binds on that chain only once it is applied there. A revoke applied on that chain through a different channel from the one that granted the authority does not bind either, while a copy of that grant which has not yet been applied there can still be relayed. Until then, an authority added by the history is bounded on that chain if every change the account signs to be valid on all chains either removes an authority, retires pending changes without granting authority, such as an epoch increment, or writes an authority record that cannot sign lifecycle operations and carries an expiry. A change that narrows an existing authority is such a write and MUST also carry an expiry, because on a chain whose copy lacks that authority it installs the record rather than narrowing it. The bound is the latest expiry any such change gave the authority. An authority without an expiry, or one that can sign lifecycle operations, cannot be revoked on a chain the revoke has not reached unless it was granted by a change bound to that chain and its record store, or is proven against a home root as above. An account that relies on relayed history SHOULD NOT grant such an authority by a change valid on all chains. An authority that resolves through another account's records, such as a delegate, is only as current on a chain as that account's records are there, and those records always include the other account's initial actors or native key, which no relayed revoke disables. Such an authority MUST NOT be able to sign lifecycle operations and MUST carry an expiry, unless every authorization path for the other account on that chain checks it against that account's imported home root as above. Each relayed change must be signed by an authority able to sign lifecycle operations on every chain that applies it, and under this rule such an authority is chain-bound, so an account that follows it can relay history only under an authority fixed at creation or import, which Security Considerations shows cannot be revoked across chains except by a standalone revoke, and an account can sign one only if its address is the address of a secp256k1 key or its preimage fixes such an authority of a kind the record store accepts for standalone revokes.

A standalone revoke names only the account and one authority and carries no chain, sequence, or record-store address. It is signed either by an authority that the account's address preimage fixes with the power to sign lifecycle operations, or by the key whose address is the account's address, even if a later change narrowed or revoked that authority. It is not relayed history. It depends on no earlier change, so any party MAY apply it on any chain at any time, while the account is locked, and, where the record store keys records by account, before the account exists there. It binds on a chain only once it is applied there; until then the revoked authority stays usable on that chain. Once applied it permanently disables that authority for that account, including as a signer of standalone revokes on that chain: no later grant, relayed change, creation, or import re-enables it, and a grant, creation, or import naming it skips it rather than failing. A relayed change that the disabled authority signed no longer applies on that chain. On a chain whose copy had not yet applied such a change, the relayed history stops before it, and later changes there need a channel bound to that chain. An account SHOULD relay every change an authority signed to every chain before disabling it. Its signer is checked against the preimage or the address, and a signer fixed in the preimage also against its own disabled mark, and against no other record, because a standalone revoke can only disable authority and only for that one account, so such a write counts as under the account's own authority whoever submits it. A standalone revoke cannot name the authority identified by the account's own address, so it never disables the account's native key or any other authority a record store keeps under that identity. A holder of a copy of such a signing authority can still disable every other authority of the account on every chain where it is not itself disabled first, including authorities granted later, so two holders of authorities fixed in the preimage race and the loser is disabled. No revoke stops the key whose address is the account's address from signing standalone revokes. An account SHOULD fix at least two such authorities at creation, keep them apart, treat each as sensitive for as long as the address is in use on any chain, and move its assets rather than rely on a revoke once one leaks. Under EIP-8141 the authorization step MAY read such records through a keystore the transport admits for sender-keyed reads (see the keystore binding).

A shared authority is not a shared transaction: naming a cross-chain authority root makes the account's authority set common, while each transaction still carries its own replay protection and is valid on another chain only when it explicitly opts into a chain-independent domain.

Binding: EIP-8141

Terms FrameTx, VERIFY, SENDER, frame modes, APPROVE, TXPARAM, FRAMEDATALOAD, and RECENTROOTREFLOAD have the meanings defined in EIP-8141 and EIP-8272.

Under EIP-8141 the account's authorization step is a VERIFY frame targeting the account, or a SENDER frame that self-APPROVEs; withholding authorization is withholding that frame's APPROVE. Placing the check in a dispatched frame that targets another contract does not satisfy the core, because that frame does not run the account's code.

The membership proof is carried in frame-declared data and read with FRAMEDATALOAD. A Tier-1 account names its committed root as an EIP-8272 recent-root reference read with RECENTROOTREFLOAD, and MUST publish that root as an EIP-8272 recent root under a source_id it controls and is the sole writer for; the source_id is fixed in the account's code at deployment, and the one-slot effect delay of a lifecycle operation is the EIP-8272 recent-root delay. A Tier-2 account MAY instead hold its current root in an account-owned storage slot and read it with a direct load in the VERIFY frame, publishing no recent root unless an external reader needs one.

A codeless default account (EIP-8141) runs protocol-defined default behavior, which authorizes over the account's own address-committed key. That default validation is a protocol-defined authorization step whose read set is a client-trusted root, so it carries the check without deployed account code.

Binding: keystore accounts

This binding is informative. It maps the core onto a keystore: a record store that keeps each account's authority records and exposes them to the EVM through one contract interface, the same on every chain. EIP-8130 defines such a keystore. The binding is stated over that interface, authenticateActor(account, hash, auth) resolving a verified (actorId, scope), and over the logical actor configuration it resolves (authenticator, expiry, scope, and any policy commitment), not over how a chain stores them. The records MAY live in contract storage, in account state, or behind a precompile that the keystore contract calls; the binding holds for each, provided every reader, whether EVM code calling the interface or a node implementing it natively, obtains results identical to calling the interface. The binding realizes the core through the Read form without a published root, as a peer of the EIP-8141 binding.

Datum and lifecycle

An authority is an actor (actorId, config), where actorId is a 32-byte stable handle and the actor's logical configuration, with any policy commitment, is the account-defined predicate; the keystore's signed account changes that authorize and revoke actors are add, rotate, and revoke. So actorId is the authority_id, the actor configuration is the predicate, and those account changes are the lifecycle operations. This mapping depends only on the interface, so it holds across revisions that change how records are stored. Isolation already holds, because the keystore keys every record by account.

Read form

Validating an actor through the keystore resolves it with authenticateActor against the account's current records. That resolution is this standard's Read form: the record is the account's own and keyed by the account, so resolving it establishes membership at path length zero and realizes Tier 2, with no published root required, from the block in which the revoking change is applied on the executing chain, provided no unapplied change that grants the same actor remains for that chain. The dependency a node tracks is the account's records as the interface exposes them, whatever backs them: a node that implements the keystore natively tracks the locations its implementation reads, and treats any change that alters what the interface returns for the account as an invalidator.

For the canonical authenticator set the check is a pure function of the record, so a validated transaction is invalidated only by a change to the account's records, including a standalone revoke or a lock change where the keystore defines an account lock, or by nonce consumption, all keyed by the account, which satisfies the sole-invalidator property. The delegate authenticator is the one canonical case that also reads a second account: it resolves the actor through the delegated account's own records, so a change there also invalidates the transaction. That account is one the sender granted authority to, not an unrelated one, so the property holds with the delegated account counted as part of the sender's authority.

A committed root is needed only for a reader that cannot call the keystore on the executing chain, such as another chain or a validity-proof verifier. A root published over the account's records for such a reader is proven against in the Carried form, which maps onto the core unchanged. A no-mutable-read Tier 1 admission for a keystore account, where wanted, likewise needs such a published root to prove against.

Limits of the Read form

The Tier 2 revocation above holds only where no other change can grant the actor again. The following cases bound it:

  • Relayed records. A record written by a channel that anyone may relay and apply up to any point, such as the EIP-8130 multichain channel, is a relayed record, and on a chain where its revoke has not been applied it is bounded only as Multichain authority states.
  • Separate channel counters. Where the keystore sequences its channels with separate counters, as EIP-8130 does for its local and multichain channels, a revoke through the local channel does not retire an older multichain grant that has not yet been applied there; relaying that grant afterwards installs the actor again.
  • Keystore address. Where a chain-bound change binds the chain but not the keystore's address, as the EIP-8130 account-change batch digest does, a local change is bound to its record store only while the chain has one keystore; on a chain that later reads a different keystore, the account's earlier local history is also a relayed record there.
  • Unsequenced grants. An authority granted by a replayable unsequenced change is revoked on a chain only once the epoch increment that retires that change has been applied there, or, if it carries an expiry, when that expiry passes.
  • Account lock. Where the keystore defines an account lock that rejects revokes while the account is locked, a revoke on a locked account takes effect no earlier than the unlock delay after an unlock request. Any admin, including the one being revoked, can impose that delay by locking first, and can lock again when it ends if its lock is applied before the revoke, so the delay bounds nothing against an admin that can order its change first.
  • Standalone revoke. A standalone revoke (Multichain authority), where the keystore accepts one, is not one of its sequenced account changes and is not frozen by a lock. On every chain where it is applied it disables the revoked authority despite any lock, unless that authority can itself sign standalone revokes and disables its revoker there first. It does not remove authorities the revoked one added before it landed. Each of those needs its own revoke, and one kept under the account's own identity can be removed only by a sequenced revoke, so where the keystore defines an account lock it can lock the account again as above.
  • Delegate authenticator. A delegate actor is revoked on a chain only as promptly as the delegated account's own records there, which may be a relayed record.

Portable changes

Because the binding is stated over the interface, one signed change can carry the same meaning on every chain that runs the keystore and through every transport that reads it, which is the portable change of Lifecycle operations. Its digest covers the logical actor configuration in a fixed typed encoding, not a chain's storage packing of it, since two chains that store the same record differently would otherwise compute different digests for the same change. The portable change digest proposed as an update to EIP-8130 (ethereum/EIPs#12341) has this shape: it signs the chain identifier or zero for any chain, the account, the actorId, a per-actor change counter that every path advances whenever the actor's state changes, and the prior and next logical actor state, under a domain bound to the keystore address. It admits a change valid on any chain only when neither the prior nor the next state is admin, which is the bound Multichain authority requires of relayed history.

Transports

The binding does not depend on which transaction format carries an action. Each transport that validates against the keystore names the account's authorization step; the Read form, its limits, and its invalidator set are the ones above in every case.

  • AA transaction type. The AA transaction type split out of EIP-8130 into its own proposal (ethereum/EIPs#12385, type 0x79 in its current draft), where it runs with keystore integration, resolves a named sender or payer exactly as authenticateActor does. With the canonical authenticator set that is a protocol-defined validation whose authorization reads only the account's own records and nonce, so it is the account's authorization step and needs no account code. Where that type is active without keystore integration it authenticates only the key whose address is the account's address and reads no records, so for an account with such a key it is a path that does not run the check (Security Considerations).
  • EIP-8141. A frame account resolves the actor by calling the keystore interface from its VERIFY frame, which is its authorization step as in the EIP-8141 binding. Public-mempool admission of that call requires the transport to count the keystore's records for tx.sender as sender state; a proposed EIP-8141 update (ethereum/EIPs#12340) admits one such call: a read-only keystore method, made with tx.sender as the account to a keystore a chain lists, that returns the actor's authenticator, expiry, scope, and policy manager without reading the environment, wherever the keystore keeps those records, provided its code cannot change, the method reads only that account's records, and every write to an account's records is authorized by that account. The frame checks the signature itself and can enforce the actor's expiry through an expiry verifier frame whose deadline does not exceed it, so an actor with an expiry is admitted to the public mempool, while a delegate actor needs another account's records and is not admitted there.

This preserves the property that a node sees a transaction's validation cost without executing wallet code. The Security Considerations state how the Read form's invalidator set stays bounded and when a stateful authenticator widens it.

Rationale

The ideal external keystore is no external keystore: placing the authority datum in the account's own committed tree, keyed by the account, removes cross-account invalidation. One account's rotation cannot invalidate another's pending transactions, because there is no shared authority whose change fans out. A keystore that serves each account its own records, keyed by the account and written only under the account's authority, is this same datum reached through an interface: resolving a record through it is the Read form, a membership proof of length zero, not a second model. Binding to the interface rather than to a storage layout keeps that true wherever a chain keeps the records.

This also resolves the apparent choice between an account header and a keystore. A header selects the authorizing code; the tree supplies which authority is authorized as proven data. A single-authority account can stay on the header alone; multi-signature and session-key accounts keep the tree. Neither is required to adopt the other.

Because the core is stated over a committed root and a membership proof rather than over any one model's opcodes, the code-is-authority and keystore-backed models are peer bindings of it, not a primary and a special case. Each account proves against its own published root, so its authorization reduces to the same check regardless of model, without branching on the model or reading a foreign keystore during validation. This is what keeps the check light, because the expensive alternatives, simulating wallet code or reading foreign mutable state per transaction, are replaced by one proof against a named root.

Making authority_id a stable handle independent of key material lets rotate change the bound key while preserving the account's reference to that authority slot, which is what a key rotation is. Add, rotate, and revoke are expressed as root transitions rather than as protocol operations so the same lifecycle applies whether the authority is a key, a lock, or a limit. This lets an account enforce an execution-time invariant, such as a lock or a ceiling, at authorization time, on the direct-dispatch path where its entry-point code does not run.

Add and rotate are not equivalent for revocation. Add is monotone: it only inserts an authority and cannot invalidate an existing one, so it is safe against any recent root. A rotate is an add of the new predicate together with a revoke of the old, and revoke is anti-monotone: an earlier root that still proves the retired predicate MUST stop being accepted. The retiring half is the security-relevant one when a key is compromised. A planned rotation is therefore content with Tier 1, while an emergency rotation, like any prompt revocation, requires Tier 2. The retiring half inherits revoke's tier, not add's; treating a whole rotate as monotone is unsafe for exactly the case that motivates it.

The two tiers make the revocation-latency trade-off explicit rather than hidden. Tier 1 keeps the check free of mutable reads, so it fits a bounded public mempool, at the cost of revocation latency up to the recency window. Tier 2 buys next-block revocation with one mutable read, moving admission to the tier that permits it. Both keep invalidation account-bounded.

General principle

The split into two tiers is not arbitrary. The cost of an authorization check is set by how much mutable state it reads. Tier 1 reads nothing beyond its carried proof and a committed root, so it is admissible to a bounded public mempool. Tier 2 reads one account-owned monotonic value, so it moves to the admission tier that permits a mutable read. A check that would depend on live external state gains no tier here; it is outside the mempool-cheap path by construction.

The same rule applies to every input a check consumes, not only to the acting authority. Where a dependency can be committed ahead of time, an account SHOULD carry it as a proof rather than read it at authorization. A sponsor authority, for example, is proven as a leaf in the sponsor's own committed root and carried in the transaction, instead of read from the sponsor's live account. A carried capability MUST be bound to the account that consumes it, so that a proof valid for one account does not authorize another. Its revocation follows the same two tiers as an account's own authority: under Tier 1 a rotated-away capability stays provable until its last authorizing root leaves the recency window, and prompt revocation requires the granting account to keep a Tier 2 floor. Only a dependency that is genuinely live and cannot be pre-committed stays outside the cheap path.

Reducing an account's onchain authority to a single committed root also composes with validity proofs. A statement that a satisfying authority is committed under the account's root, and that its predicate accepts the transaction, is exactly what a succinct verifier can attest with the root as its public input. The lifecycle is unchanged; only the membership proof's representation varies, from a Merkle path to a proof. An account's onchain authority then reduces to one hash regardless of how rich the authority set behind it is.

Shared authorization object

This subsection is informative. The bindings differ only in how they encode one authorization. Within this standard a transaction carries, alongside its existing envelope (sender, nonce, calls, fees), a single authorization object:

(authority_id, root_ref, membership_proof, witness)
  • authority_id names the acting leaf.
  • root_ref names the committed root and its source, which also fixes the tier: a recent-root reference for Tier 1, an account-owned slot for Tier 2, a home-chain root for the multichain case.
  • membership_proof is the keyless path proving the leaf under that root; in the Read form it is empty, because the client reads the record directly from account state.
  • witness satisfies the leaf predicate over the transaction.

An EIP-8141 node reads this as the data of a VERIFY frame that targets the account; a keystore consumer (EIP-8130) reads it as the actor authentication authenticateActor resolves against the account's records. The carried bytes are the same and the verifying step differs, so within this standard's authority axis the two encodings are interconvertible. An implementation MAY also declare a cost class for its verifier (see Resolved verifier and declared class), letting a node price the check without simulating it. This object standardizes only the authority axis; call batching and gas accounting remain each binding's own and are out of scope.

Witness binding per model

This subsection is informative. Each binding already carries a witness field; this shows the mapping and the one field that must be agreed for a witness to be portable.

Under EIP-8141 the witness is an entry in the transaction's signature list, read by the account's VERIFY frame: the frame reads the membership proof from frame data, takes the verification key from the proven leaf's predicate, checks the signature, and approves. A key predicate uses a canonical signature scheme; a custom or proof-carrying predicate uses the arbitrary scheme, whose verifier is the account's own code.

Under the keystore binding (EIP-8130) the witness is the auth blob a transport passes to authenticateActor, checked by the actor's named authenticator against the actor configuration. A canonical authenticator is priced at a constant cost without execution; a non-canonical one, where the transport's acceptance policy admits it, is priced by metered execution.

The two agree on everything except the digest the witness signs, which each model currently domain-separates differently. For one witness to verify under both, the bindings MUST agree a single authorization digest computed identically from the shared fields (sender, nonce, calls, fees, the root reference, and the acting authority_id) under the chain-replay domain the Security Considerations require. Given such a digest the same witness bytes verify under either binding; without it the witness is portable in meaning but not in bytes.

A custom or proof-carrying witness, for example a validity proof, is portable in meaning across both bindings but not in mempool cost: EIP-8141 runs it as arbitrary-scheme account code and a keystore transport as a non-canonical authenticator, each priced by its own execution rather than as a canonical scheme.

Resolved verifier and declared class

This subsection is informative. It refines how the authorization step reaches the check and how a node prices it, without changing the core leaf.

A leaf's predicate already implies how its authority is checked. An implementation MAY make that verifier an explicit, tagged field of the leaf, so the check resolves the verifier from the committed root and a node prices it from a declared class before running it:

leaf = (authority_id, verifier, predicate)
verifier = verifier_kind (1 byte) || verifier_ref

Because verifier is committed in the leaf and proven by the same membership proof, the transaction still carries only (authority_id, root_ref, membership_proof, witness); a sender cannot substitute a more permissive verifier, since naming one would change the leaf and fail the proof.

Four verifier kinds, ordered by how much mutable state the check reads:

  • CANONICAL(id): a protocol-table verifier such as ECDSA, P256, or a member of the EIP-8130 canonical authenticator set. Priced from the table with no execution.
  • CIRCUIT(vk_hash): a succinct verifier keyed by a verification-key hash. Arbitrary logic, yet it reads no mutable state, so it is a cheap class and the membership proof and witness collapse into one proof.
  • BOUNDED(pattern): a verifier whose reads are confined to a fixed pattern, for example one account-owned slot. Priced by the declared pattern; a node rejects a read outside it.
  • CONTRACT(addr): arbitrary account code that MAY read arbitrary mutable state. It is the maximum of the class lattice, admitted to a public mempool only by simulation under the verify-gas cap and the trace rules of its binding, or restricted to non-public inclusion.

Cost is therefore set by state access, not by code arbitrariness: a succinct verifier is arbitrary code but a cheap class, while a contract verifier that reads mutable state is the costly one. CANONICAL and CIRCUIT are priced from the table; BOUNDED and CONTRACT are checked by the node's own trace, so a leaf cannot understate its class. Selecting which delegation an account header invokes, dispatching by a proxy's function selector, or reinterpreting a verification frame are three ways to resolve which verifier a root selects; this verifier shape is a design sketch for that convergence, not an agreed wire format. The canonical kind follows the EIP-8130 canonical authenticator set, and the direct evaluation of protocol-defined verifiers is from EIP-8141.

Authority-set availability and recovery

Because an authority is proven by a membership proof, a wallet must be able to obtain the current leaf set to build one. A committed root is a hiding commitment: it verifies a proof but cannot by itself reconstruct the leaves, so the leaf data must be available from a source that outlives any single wallet installation.

Keeping the enumerable leaf set on-chain is a recovery choice, not an authorization requirement: neither tier's check reads the leaves. An account that wants recovery from chain data alone SHOULD keep its authority tree in enumerable on-chain state, with its committed root a deterministic function of that state. Recovery is then a read of current state from any node, followed by rebuilding the canonical tree and checking its root against the committed root; a wallet's local copy is a cache, and losing it is a re-read rather than a loss. This inherits the availability of state, which every full node retains, and needs no separate backup, event replay, or checkpoint. Enumerable records alone do not fix an account-defined tree, so a foreign wallet can rebuild it only under the account's recovery scheme (see Portable recovery).

Reconstructing the committed root is a weaker property than reconstructing what each authority is permitted to do. An input that changes an add, rotate, or revoke decision but sits outside the scheme lets a foreign wallet verify the root yet mis-evaluate the authority, which is why Portable recovery makes the scheme cover every outcome-relevant input and not only the tree. A small set of standard schemes gives portable cross-wallet recovery; a custom scheme stays valid but owns its portability.

This availability is separate from the authorization check's cost tier and from where the root is referenced. The check stays stateless on the hot path, a carried proof against the committed root, while recovery is an infrequent, account-initiated read off that path. An account MAY instead keep the leaves off-chain, for privacy or to minimize state, in which case it is responsible for their availability and accepts that a lost witness cannot be rebuilt from chain data; where an on-chain record is still wanted, leaf preimages can be emitted as logs so the set can be reconstructed from history.

Acknowledgements

This standard grew out of the account-abstraction discussion on frame transactions. The following participants contributed ideas that shaped it, without implying their endorsement: taek (@leekt216), who argued for a committed authority root with membership proofs and for a shared key-lifecycle layer; Chris Hunter (@chunter-cb), whose EIP-8130 keystore and actor model framed the keystore binding, and whose Stateless Accounts proposal names the compact chain-proof this standard's committed-root binding supplies for authority state; Derek Chiang (@derekchiang), for the account-header framing; Vitalik Buterin (@vbuterin); and, on the recovery and portability boundary raised in the discussion thread, @chugarchugarr and @Anzus_GemWallet. It builds directly on EIP-8272 by Thomas Thiery (@soispoke), Vitalik Buterin (@vbuterin), and Toni WahrstΓ€tter (@nerolation).

Backwards Compatibility

This standard adds no transaction type, opcode, or consensus rule. It is a convention over primitives its bindings already provide. Accounts that do not adopt it are unaffected. An account that adopts it is authorized exactly as before on any path that runs its authorizing code.

Security Considerations

  • Opt-in. The protection exists only for accounts whose authorizing code performs the check. An account that does not is not protected on the direct-dispatch path. This is inherent to any account-defined authorization and is not specific to this standard.

  • Revocation latency (Tier 1). Under Tier 1 a revoked authority stays provable until its last authorizing root leaves the referencing mechanism's recency window. Accounts that require prompt revocation MUST use Tier 2. Rotating away a compromised key is a revoke and inherits this requirement: a planned rotation MAY stay on Tier 1, but an emergency one MUST use Tier 2.

  • Root-source liveness. This applies only to an account whose root is referenced through an external mechanism with a recency window (Tier 1, or any tier that publishes for external readers). If that root source stops publishing, the authority set freezes at the last root and, once it ages out of the window, references fail closed; such accounts SHOULD provide a fallback authority path. A Tier-2 account that holds its root in its own slot reads it directly with no recency window and no external publisher, so it has no such liveness dependency.

  • Cross-chain rotation latency. With a cross-chain authority root, a rotation on the home chain takes effect on an executing chain only once that chain imports the updated finalized home root. A rotated-away authority stays usable there until then, bounded by the imported root's freshness window; an account needing prompt cross-chain revocation MUST use floor-bound, which accepts only the most recent finalized home root the import mechanism identifies, and accepts a latency of at least home-chain finality plus import delay. This bounded staleness is inherent to any cross-chain shared-authority design.

  • Cross-chain root delivery is a dependency, not a mechanism defined here. The multichain binding assumes the executing chain imports a trust-minimized commitment to the home-chain root, for example the beacon block root exposed by EIP-4788, an L1-storage-read precompile, or a bridged state root with its own trust assumption. On a chain with no such primitive, an account falls back to a same-chain committed root, which does not follow revocations made on other chains, or to a relayed change history, whose revocation bound is only the one Multichain authority states for it. This standard does not define the delivery primitive and does not claim to provide a trust-minimized cross-domain read; the executing chain MUST read a finalized home root so an unfinalized reorg cannot revert a rotation.

  • Relayed history cannot revoke lasting authority. On a chain that imports no commitment to the account's home state, a check sees only the account's address, the block time, and data the submitter chooses. There a relayed history cut before a revoke cannot be told apart from the current one, and a key that could sign changes at the cut can extend it with changes of its own. No rule over relayed changes can therefore revoke, on such a chain, an authority granted without an expiry or one that can sign lifecycle operations; the only bound available is an expiry fixed at grant. For the same reason an authority fixed when the account is created or imported stays usable on every chain where its revoke has not been applied, including chains where anyone creates the account later. Revoking one of them across chains requires a check against an imported home root that every authorization path on that chain runs, a standalone revoke (Multichain authority) applied on that chain before the authority is used there where the account's address or preimage admits one, or moving to a new account. An account's own native key is weaker still: where a transaction type that does not run the account's check can still be signed with it, no revoke and no home-root check disables it on any chain, and the only remedy is moving to a new account.

  • Keyed nonces do not bypass the check. Under EIP-8250 each frame still runs the account's authorizing code, so choosing a different nonce domain does not skip authorization.

  • Witness digest scope. A witness binds the action to the authority only if it signs a digest covering the full call set and a chain-replay domain. A witness over a narrower digest can be replayed against a different action or on another chain; the authorization digest MUST cover calls and a replay domain.

  • Carried capabilities. A capability proven against a granting account's root and carried in the transaction MUST be bound to the consuming account, or a proof valid for one account replays on another. A revoked capability also stays provable for the granting account's Tier 1 recency window unless the granting account keeps a Tier 2 floor, so a capability meant to be promptly revocable MUST be granted under Tier 2.

  • Keystore Read form and its invalidator set. The keystore binding realizes the core's Read form by resolving the actor through the keystore's interface. For the canonical authenticator set the check is a pure function of (hash, data) and the resolved actor configuration, so a validated transaction is invalidated only by a change to the account's records, including a standalone revoke or, where the keystore defines an account lock, a lock change, or by nonce consumption, all keyed by the account, plus, for a delegate actor, a change to the delegated account's records; the node MUST track those records as a dependency of the transaction, wherever the keystore keeps them. This satisfies the sole-invalidator property and admits the Read form as a first-class Tier 2 with no published root for an unlocked account, within the limits the keystore binding states for relayed records and replayable unsequenced grants. A permissive policy that admits non-canonical authenticators reading other mutable state widens the invalidator set, requiring the node to track that state and revalidate on change, so such authenticators are the costly class and fall outside the sole-invalidator property unless their read set is confined to the account's own authority. The mempool-cheap Tier 1 Carried form still requires a published no-mutable-read root to prove against. Under EIP-8141 a VERIFY frame resolves the actor through one read-only keystore method that reads no block timestamp and enforces expiry through an expiry verifier frame, so an actor with an expiry is admitted to the public mempool, while a delegate actor needs another account's records and is not admitted there.

  • Authority-set availability and reconstruction. A committed root cannot reconstruct its own leaves. An account that publishes only the root and keeps leaves off-chain MUST ensure their availability, or a lost witness locks the account out of proving membership even though the root stays valid; keeping the tree in enumerable on-chain state removes this dependency, but does not by itself make an account-defined tree reconstructible by another wallet, which also needs the recovery scheme of Portable recovery. Reading on-chain leaf data reveals the account's authority structure but confers no authorization: membership is public, while authorizing still requires satisfying the acting authority's predicate, so this is a confidentiality trade-off, not an authorization weakness.

Copyright and related rights waived via CC0.

EIP.tools

EIP.tools

Search, read, and map Ethereum improvement proposals, ERCs, RIPs, and CAIPs from one focused interface.

Farcaster
by @apoorveth