--- eip: 8403 title: Account Authority Lifecycle description: An account keeps its keys and rules as one root or as records, and its check accepts only the current ones. author: Daniil Ankushin (@AnkushinDaniil) discussions-to: https://ethereum-magicians.org/t/erc-8403-account-authority-lifecycle/29570 status: Draft type: Standards Track category: ERC created: 2026-08-29 requires: 8141 --- ## Abstract An authority is a key or rule that controls an account, held as a 32-byte identifier and a predicate. An account commits to its authorities as one root, or keeps them as records that its authorization check reads. The account authorizes a transaction only if the acting authority is in its current authority state and a witness satisfies the predicate over the whole transaction. Add, rotate, and revoke change that state. An authority is rejected no later than the block after its revoke is applied. An account on another chain can take its authority state from a home account by a sync, and a revoke reaches it with that sync. This document gives a binding to [EIP-8141](./eip-8141.md), two recovery schemes with test vectors, and an informative mapping to [EIP-8130](./eip-8130.md). ## Motivation Native account abstraction lets several keys and rules control one account. Each account model defines add, rotate, and revoke in its own way. Under EIP-8141 each account defines them in its own code, so a wallet cannot read or change the authorities of an account that another wallet made. A user cannot know how long a stolen key keeps working after a revoke. A rule in the account's code does not bind every call. Once an EIP-8141 transaction is authorized, its `SENDER` frames call other contracts as the account. The account's code does not run. A limit on a key has to be checked when the transaction is authorized. ## 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 and RFC 8174. ### Authority An authority is a pair: ```text authority = (authority_id, predicate) ``` - `authority_id` is a 32-byte identifier. It is fixed at add time and is unique within the account. A rotate keeps it. - `predicate` is the account-defined data that decides what the authority may do. Examples are a public key, a verifier address, a threshold, a spend limit, and an expiry. The authority set is the set of authorities that an account holds on one chain. An account keeps its authority set in one of two ways: - **Root.** Each authority is a leaf of a tree. The account commits to the tree as one root. The root that the account holds now is the current root. The tree shape and the proof format are account-defined. - **Records.** The account keeps each authority as a record in state that the authorization check reads. The records that the account holds now are the current records. The authority state is the current root or the current records. A leaf MUST commit to the `authority_id` and the `predicate`. The tree encoding MUST make a leaf distinguishable from an inner node. ### Lifecycle operations A lifecycle operation changes the authority set: - **add** puts a new authority in the set. - **rotate** replaces the `predicate` of an existing `authority_id`. - **revoke** removes an authority from the set. A lifecycle operation takes effect when it changes the authority state. The first authority state MUST be fixed by the account's initcode or by the data that derives the account's address, or be authorized by the key of that address. After that, a lifecycle operation is applied by a transaction that the account authorized, as a sync, or as a detached change. An account need not accept syncs or detached changes. A sync replaces the authority state of an account, the follower, with a state that its home account held on another chain, the home chain. Any party can submit a sync. The first authority state of a follower MUST hold no authority. The data that fixes it MUST also fix the home chain, the home account, the source of home state, and a maximum age. On a follower, every mutable value in its own storage that the authorization check or a sync reads MUST be writable only by a sync. A sync MUST apply only the state that the source of home state reports for the home account at the end of one home block. That block MUST be later than the block of the last applied sync, and MUST NOT be older than the maximum age. A sync MUST NOT apply on the home chain. A detached change is any other lifecycle operation that any party can submit, because it carries its own witness and its own replay protection. A detached change MUST apply at most once on each chain. It MUST apply only if a witness satisfies the `predicate` of an authority that is in the authority state at that time. That witness MUST cover the change and all of the following: - the account; - one chain, or an explicit marker for all chains; - a sequence value that the change consumes; - for an account that keeps a root, the root that the change replaces when it applies. ### Authorization check The authorization step is the step that grants or withholds the account's authorization of a transaction. Each binding names it. The authorization check is the account's own logic in that step. The protocol's checks of the nonce and the balance are not part of it. The authorization step MUST NOT authorize a transaction unless both of the following hold: 1. **Membership.** The acting authority is in the current authority state. For a root, a membership proof shows that the leaf of the acting authority is under the current root. For records, the check reads the record of the acting authority. 2. **Witness.** A witness satisfies the `predicate` of the acting authority. The witness MUST bind to the whole transaction: the account, the chain, the replay protection, and every call. Every mutable state value that the check reads MUST be writable only under the account's own authority or by a sync. ### Recovery scheme A recovery scheme is a specification that lets a wallet read an account's authority set from the chain, produce the data that the check needs, and submit a lifecycle operation. Two implementations of one scheme read the same authority set and agree on what each authority may do. A scheme has one nonzero 32-byte identifier. A change to a scheme needs a new identifier. An account declares at most one scheme, by its identifier. An account that declares a scheme MUST keep its authority set readable from the chain in the way the scheme states. For an account that keeps a root, the root that the scheme derives from that set MUST equal the current root. An account that declares no scheme still conforms. This standard defines two schemes, `SLOTS16` and `SLOTS16_SYNC`, under the EIP-8141 binding. ### Binding: EIP-8141 Terms of [EIP-8141](./eip-8141.md) keep the meanings defined there. An account under this binding has code. It keeps its current root or its current records in its own storage. #### Authorization step The authorization step is a frame that targets the account and whose flags allow an approval scope. The account withholds authorization by not calling `APPROVE`. A check that takes a protocol-validated entry of the `signatures` list as the witness MUST withhold `APPROVE` unless the `msg` of that entry is empty. The signature is then made over the canonical signature hash, which covers the chain, the sender, the nonce, and every frame. #### Discovery An account under this binding MUST implement: ```solidity interface IERC8403 { event AuthorityStateChanged(bytes32 indexed root); function authorityRoot() external view returns (bytes32 root, bytes32 scheme); } ``` `root` MUST be the current root, or zero for an account that keeps records. `scheme` is the identifier of the recovery scheme that the account declares, or zero if it declares none. The account MUST emit `AuthorityStateChanged` with the new `root` when, and only when, a lifecycle operation changes its authority state. Fixing the first authority state emits no `AuthorityStateChanged`. #### Scheme `SLOTS16` The identifier of this scheme is `SLOTS16`. It is `keccak256("ERC8403_SLOTS16")` over the ASCII bytes of the string. The authority set has 16 slots, numbered 0 to 15. The `authority_id` is the slot number as a 32-byte unsigned integer. The `predicate` is one 32-byte word. It holds the 20-byte resolved signer of a protocol-validated entry of the `signatures` list, padded on the left with zeros. A zero `predicate` marks an empty slot. Every authority may authorize every frame. The tree is binary and has 16 leaves. Leaf `id` is `keccak256(0x00 || id || predicate)`, or 32 zero bytes for an empty slot. A node is `keccak256(0x01 || left || right)`. The root of a set is the node at height 4, and the root of the empty set is zero. The account stores the current root in storage slot 0 and no `predicate`. An account that declares this scheme MUST implement: ```solidity interface IERC8403Slots16 { event AuthoritySet(bytes32[16] predicates); function setAuthorities(bytes32[16] calldata predicates) external; function validate(uint256 id, bytes32 predicate, bytes32[4] calldata siblings) external; } ``` The data of the frame that runs the check is a call to `validate`. `validate` computes a root from `keccak256(0x00 || id || predicate)` and the siblings. `siblings[k]` is the sibling at height `k`, counted from the leaf. The running hash is the left child at height `k` if bit `k` of `id` is zero. Bit 0 is the least significant bit. `validate` MUST revert unless `predicate` is not zero, the computed root equals the current root, the `msg` of entry 0 of the `signatures` list is empty, and the resolved signer of that entry, padded on the left with zeros, equals `predicate`. Otherwise it MUST call `APPROVE` with the `allowed_scope` of its frame. A lifecycle operation is applied by a `SENDER` frame that calls `setAuthorities` on the account. `predicates` is the whole new authority set, one word for each slot. After the first authority state, a slot that changes from zero is an add. A slot that changes to zero is a revoke. Any other change of a slot is a rotate. `setAuthorities` MUST revert if the caller is not the account, if a word of `predicates` is `2**160` or more, or if every word is zero. Otherwise the root of `predicates` MUST become the current root, and the account MUST emit `AuthoritySet` with `predicates`. A wallet reads the authority set from the last `AuthoritySet` log of the account. A first authority state that no `AuthoritySet` log holds MUST be slot 0 alone, and only a party that knows the `predicate` of that slot can check it against the current root. #### Scheme `SLOTS16_SYNC` The identifier `SLOTS16_SYNC` is `keccak256("ERC8403_SLOTS16_SYNC")` over the ASCII bytes of the string. An account that declares this scheme is a follower. Its home account declares `SLOTS16`. Its initcode fixes the identifier of the home chain, the home account, an adapter, and a maximum age in seconds. The adapter is the source of home state. The follower stores a current root and a home block number. Both start at zero. A follower MUST implement `validate` as `SLOTS16` states, and `IERC8403Slots16Sync`. `home` returns the fixed values and the stored home block number. ```solidity interface IERC8403Slots16Sync { function home() external view returns (uint256 chainId, address account, address adapter, uint256 maxAge, uint256 blockNumber); function sync(uint256 blockNumber, bytes calldata proof) external; } interface IERC8403HomeAdapter { function read(uint256 chainId, address account, bytes32 slot, uint256 blockNumber, bytes calldata proof) external view returns (bytes32 value, uint256 timestamp); } ``` `read` returns the `value` of storage slot `slot` of `account` at the end of block `blockNumber` of chain `chainId`, and the `timestamp` of that block. It reverts unless `proof` shows both. For an account or a slot that does not exist at that block, `value` is zero. The adapter defines the format of `proof`, so a wallet needs the specification of the adapter to submit a sync. `sync` calls `read` on the adapter with the fixed `chainId` and `account`, slot 0, `blockNumber`, and `proof`. It MUST revert if that call does not return both values, if the identifier of the executing chain is `chainId`, if `blockNumber` is not greater than the stored home block number, or if `timestamp` is more than `maxAge` seconds before the timestamp of the executing block. Otherwise it MUST store `blockNumber`, and `value` MUST become the current root. A `timestamp` later than the timestamp of the executing block does not make `sync` revert. A wallet reads the authority set from the home account as `SLOTS16` states, as of the stored home block number. ### Mapping: EIP-8130 keystore This section is informative. A keystore keeps each account's authority records and tells a caller which of the account's authorities made a given signature. EIP-8130 defines one. It maps to this standard as follows: - The `authority_id` is the keystore's identifier for the authority. The `predicate` is what the keystore stores under that identifier. - The account keeps records, not a root. The check reads the current record of the acting authority through the keystore. - The authorization step is the step that asks the keystore about the account. Whether a public mempool admits that read is a rule of the transaction type. - Add, rotate, and revoke are the keystore's signed account changes. They are detached changes. Where the keystore derives the identifier from the key, replacing a key is a revoke and an add, not a rotate. - A revoke takes effect on a chain from the block that applies it, unless a change that grants the same authority can still be applied there afterwards. A change for all chains that the chain has not yet applied is such a change. So is a change that consumes no sequence value, which does not meet the rule that a detached change applies at most once. - An authority that is verified through state that the account cannot write, such as a delegate, does not meet the rule that the check reads only state writable under the account's own authority. ## Rationale ### One authority state The rules of the check and of the lifecycle are written once, over the authority state. A root answers membership with a proof. Records answer it with a read. A root needs one word of account state for any number and size of authorities. The state of records grows with the set. The `authority_id` is an identifier that a rotate keeps. A wallet can then follow one authority across key changes. Add, rotate, and revoke are changes of the authority state, not protocol operations. The same lifecycle then applies to a key or a limit. Membership and witness are separate because a leaf is not a secret. A membership proof shows that an authority belongs to the account. It does not show that the holder approved this transaction. The witness shows that. ### Why the current root Suppose the check read no storage and accepted any root that the account had published in some recent period. A revoked key could then prove itself against an earlier root for that whole period. With that authorization it could write a new root that holds it again. The period would bound nothing for an authority able to change the set. A check that reads the current root closes this. A proof against an earlier root fails from the transaction that applies the revoke. Under EIP-8141 the read costs no public mempool eligibility, because validation for the sender may depend on the sender's storage. ### Why a sequence and the replaced root A detached change for an account that keeps a root writes a root that its author built from one tree. If another change is applied first, the detached change would write back a tree without that change, and undo a revoke. So it applies only on the root that it replaces. That rule alone does not prevent replay. A root value can recur: an add followed by a revoke of the same authority returns a deterministic tree to its earlier root. A consumed sequence value does not recur. A transaction needs neither rule, because its proof binds it to one root and its nonce is consumed. ### Why a small scheme A fixed tree would exclude other membership evidence, such as a validity proof. The standard therefore fixes the check and leaves the tree to a recovery scheme. `AuthorityStateChanged` lets a wallet follow changes without reading storage layouts. `setAuthorities` takes the whole set, so the account computes every root itself and the leaves of every such root are in one log. The call needs no proof of the set that it replaces, because the proof in the check already binds the transaction to the current root. A set that no log holds is one slot, so no wallet depends on a log from the frame that creates the account. ### Why a sync A follower reads home state once for each change, not in every transaction. Vitalik Buterin described the mechanism in 2023, and Base has implemented it. A party can choose an old home block, so the block order and the maximum age bound that choice. A follower starts with no authority, because an authority of a first state stays usable where no revoke has been applied. A sync on the home chain could restore a replaced root. ## Backwards Compatibility This standard adds no transaction type, opcode, or consensus rule. Accounts that do not adopt it are unaffected. ## Test Cases This vector is for `SLOTS16`. Slot 0 holds the signer `0x7E5F4552091A69125d5DfCb7b8C2659029395Bdf`. Slot 10 holds the signer `0x2B5AD5c4795c026514f8317c7a215E218DcCD6cF`. These are the `SECP256K1` addresses of the private keys 1 and 2. The other slots are empty. | Value | Hex | | --- | --- | | `SLOTS16` | `0xd57d8ea8a9c7e0985d8cc400e0b7162f086f57e9b994099a71b0844e7b682b62` | | Root of the first authority state, slot 0 alone | `0x762505e8bac4f4e1dea165c6f4dc08e6406e0677e0023da61b7b3a9eb288b9a6` | | Leaf 10 | `0x0abb98a84605f618e2a0b4e9cfcd466acbcbfb327886e779306d441739c82049` | | Node over slots 0 to 7 | `0xdeab47e7915584c519b77ae1e6fb494be44c455065ecb1dc286399aac8ef9394` | | Current root | `0xa9c1f4b3e9b45817a1c9dd4ce66ad208f0c5917d38b2a37e380e1954efe5d30e` | | Current root after the revoke of slot 0 | `0xcab22dfae4cdaf3d73c65b61582707399c8f1ccc4e592b2818399fd0eb9be998` | | `SLOTS16_SYNC` | `0x2bb847cd2a21945abc5c2efe79af09b5e95a319743bbd1270bc3e13d8acedb94` | | Selector of `sync` | `0x5c599843` | The siblings for slot 10 are a zero word, the empty nodes of heights 1 and 2, and the node over slots 0 to 7. The frame data of the check for slot 10 is the selector `0x806afe1e`, then `id`, `predicate`, and the siblings, 196 bytes in all. The frame data that revokes slot 0 is the selector `0x1feb3d6a` and 16 words, 516 bytes in all. Word 10 is the `predicate` of slot 10. The other words are zero. Before the revoke, in a frame that targets `tx.sender` with flags 3, `validate` calls `APPROVE` with scope 3 for that check data if entry 0 of the `signatures` list is a signature by key 2 with an empty `msg`. It reverts if that `msg` is not empty, or if key 1 made the signature. It reverts for an empty slot with a zero `predicate` and the true siblings. After the revoke the root has changed, so it reverts for that check data. `setAuthorities` reverts if all 16 words are zero, if a word is `2**160` or more, or if the caller is not the account. For `SLOTS16_SYNC`, a follower on chain 10 fixes `chainId` 1 and `maxAge` 3600. Its adapter reports the current root above for block 100 with timestamp 1000000. Before the first sync, `validate` reverts for the check data above. At timestamp 1003600, `sync` for block 100 applies, and `validate` then accepts that check data under the conditions above. A second `sync` for block 100 reverts. At timestamp 1003600, a `sync` for block 101 with timestamp 1000012 and the same root applies and stores 101. A `sync` for block 102 with timestamp 1003601 and the same root applies at timestamp 1003600. On a new follower, `sync` for block 100 reverts at timestamp 1003601, and at timestamp 1003600 on chain 1. ## Security Considerations - **Revocation latency.** The check rejects a removed authority no later than the next block. What a removed authority did before the revoke stands. - **Authority over the set.** An authority that can authorize lifecycle operations can remove every other authority. Between two such authorities, the change that is applied first wins. Under EIP-8141 every authority has this power, unless the check limits the frames it approves or the account is a follower. A detached change that grants an authority can apply after a revoke of that authority, until its sequence value is consumed. - **Witness scope.** A witness over less than the whole transaction can be replayed with other calls or on another chain. Under EIP-8141 a signature over an explicit 32-byte `msg` does not cover the frames. - **One chain at a time.** A revoke on one chain does not revoke on another. An authority of the first authority state stays usable on every chain where no revoke of it has been applied, including a chain where the account is created later. On any chain, a party can apply a history of detached changes for all chains up to any point and stop before a revoke. On that chain an authority that such a change grants is bounded only by an expiry in its `predicate`. - **Sync.** An authority revoked on the home chain can act on a follower until a sync from a home block at or after the revoke is applied there. No party is obliged to submit that sync. A sync from a block before the revoke can apply while the follower's clock is at most the maximum age past the timestamp of the last home block before the revoke. Under EIP-8141 a public mempool does not admit a transaction that runs a sync before its check, so through a public mempool another account pays for the first sync, and for a sync when no authority under the current root is usable. The adapter, any state that it reads, or other code at its address on another chain, can set the root of every follower that names it. It also decides when a home block is final. If it reports a block that the home chain later drops, a sync of the block that replaces it reverts, and a revoke in that block reaches the follower only with a sync of a later block. If no home block reaches the follower within the maximum age, the follower keeps its last root. - **Native key.** Under EIP-8141 an account with no code has one authority, the key of its address, and no lifecycle. That key also controls an account whose code it can replace, such as an [EIP-7702](./eip-7702.md) delegation. No revoke removes it. The remedy is a new account or, with [EIP-8298](./eip-8298.md) and [EIP-8151](./eip-8151.md), adopting regular code at the same address, which stops the key from sending transactions and from passing `ecrecover`. A change of code can make an earlier root current again. - **Availability of leaves.** A root does not reveal its leaves. A wallet that cannot obtain the leaves cannot prove membership. Under `SLOTS16` the leaves are in a log, and a node can stop serving old logs. A copy from any source can be checked against the current root. An authority that holds a copy can publish it again with a `setAuthorities` call. Leaves kept on chain reveal the structure of the authority set and grant no authorization. ## Copyright Copyright and related rights waived via [CC0](../LICENSE.md).