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, two recovery schemes with test vectors, and an informative mapping to EIP-8130.
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:
authority = (authority_id, predicate)authority_idis a 32-byte identifier. It is fixed at add time and is unique within the account. A rotate keeps it.predicateis 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
predicateof an existingauthority_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:
- 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.
- Witness. A witness satisfies the
predicateof 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 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:
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:
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.
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_idis the keystore's identifier for the authority. Thepredicateis 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
msgdoes 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 delegation. No revoke removes it. The remedy is a new account or, with EIP-8298 and EIP-8151, 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
SLOTS16the 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 asetAuthoritiescall. Leaves kept on chain reveal the structure of the authority set and grant no authorization.
Copyright
Copyright and related rights waived via CC0.
