Overview
Using a keystore account
Accounts on the Axiom keystore are identified by a new bytes32 keystoreAddress
format, which users can counterfactually initialize using the process described in Account Initialization. The state of the Axiom keystore is a mapping
where:
bytes data
holds the signing data for the keystore accountbytes vkey
is a verification key for a ZK circuit that ingestsdata
and verifies a ZK authentication proof for the update rule forkeystoreAddress
which will enable a user to update the(data, vkey)
pair.
The keystore state is structured as an Indexed Merkle Tree (IMT), and committed on L1 using the IMT root during each finalization on L1. These state roots can be trustlessly synced to L2s using native message passing or storage proofs, enabling users to prove their signing data against the keystore state root on L2. We use this to create a keystore-enabled smart account transaction flow, which we explain in the rest of this section.
Contract Architecture
For integration with the keystore on L2s, we deploy and maintain the following two contracts on all supported chains:
KeystoreStateOracle
: The contract which trustlessly caches keystore state roots onto the L2, exposing it to the L2’s execution environment.KeystoreValidator
: The contract which facilitates IMT Merkle proofs to verifydata
against the state root, and validates smart account authentication data againstdata
.
Deployed addresses for these contracts are available in Contract Addresses. We outline an overview of both these contracts below.
Keystore State Oracle
On the L1 bridge contract, provers persist outputRoot
s into storage during finalization, where an outputRoot
is computed as
stateRoot
is the state root after executing the transactions in the batch.withdrawalsRoot
is the withdrawals root after executing the transactions in the batch.lastValidBlockhash
is the keystore blockhash of the most recent valid block as of the batch.
The Keystore State Oracle (KSO)‘s role is to read an outputRoot
from L1, extract the stateRoot
from it, and cache it for use on the L2. The KSO can read outputRoot
s from L1 in two ways:
- L1 Merkle Trie Proof: For L2s that expose L1 blockhash access in their execution environments, the KV can read output roots from L1 via a storage proof.
- Bridge Transaction: For other L2s, the KV can receive output roots from L1 via a native bridge transaction.
Finally, the state root can be extracted by taking a claimed preimage of the outputRoot
and checking a hash equivalence.
Axiom is committed to syncing state roots for all supported L2s approximately once per hour. In cases where this sync is delayed or there is a need to expedite the propagation of a finalized state root, a manual sync—whether via storage proof or bridge transaction—can also be triggered permissionlessly.
Keystore Validator
Smart accounts on L2 can become keystore-enabled by using the Keystore Validator (KV) contract. This is an ERC-7579 and ERC-6900 compatible smart account validation module which facilitates IMT Merkle proofs to verify data
against state roots cached on the KSO, and validates smart account authentication data against data
.
To enable this integration, the KV must be installed as a module within the smart account, which requires compatibility with either the ERC-7579 or ERC-6900 standard. Without compatibility with one of these standards, the smart account cannot support the Keystore Validator.
Deployment details of the KV across all supported L2s are available in Contract Addresses.
The KV expects data
to conform to the following structure:
The KV itself will verify a (data, vkey)
pair with an IMT proof, but will outsource further authentication against data
to an external Key Data Consumer (KDC). There is (generally) a one-to-one correspondence between a vkey
and a KDC
and data[1..33]
allows a vkey
to signal how it desires to be authenticated on chain through a CODEHASH
which commits to the validation logic of a KDC
.
Expiry Checks
When Keystore IMT state roots are persisted into the KSO, they are tagged with an l1BlockTimestamp
—the timestamp of the L1 block whose blockhash against which the Merkle trie proof was verified or the bridge transaction was sent. This metadata enables the KV to perform expiry checks, leveraging ERC-4337’s validateUserOp
interface, which incorporates native timestamp validation through a return value embedding the validAfter
and validUntil
fields.
During the installation of the KV, the smart account specifies a stateRootValidityInterval
. The validAfter
and validUntil
fields are then derived as l1BlockTimestamp
and l1BlockTimestamp + stateRootValidityInterval
, respectively. Given that Axiom guarantees a refresh interval of one hour, it is recommended to set the stateRootValidityInterval
to at least 1 hour.
Caching
On L2s, needing to include a Merkle proof within every transaction can be quite expensive. To mitigate this, the KV automatically caches key data reads from the latest state root. This allows smart accounts to optionally read from cached data and skip the Merkle proof, providing users with a configurable tradeoff between L2 transaction cost and data freshness.
To use the cache, during KV installation, the smart account can specify a cacheValidityInterval
. If some cached key data that was read from a state root with an l1BlockTimestamp
is used, then the validAfter
and validUntil
fields are derived as l1BlockTimestamp
and l1BlockTimestamp + cacheValidityInterval
, respectively. Setting the cacheValidityInterval
to 0 effectively disallows use of the cache.
When caching is enabled, UIs should clearly display the currently valid set(s) of signing data across all chains. This is crucial as cached values remain valid until either the cacheValidityInterval
expires or they are overwritten by newer values. In the case the key rotation is time-sensitive (e.g. due to a compromised key), users should be presented with an option to proactively send cache-overwriting transactions to all relevant chains.
Next Steps
In the upcoming sections, we will explore interacting with the keystore, both directly and from within smart accounts on other rollups, as well as run through an example. However, if you are looking for something specific, you can go there directly:
- To set up a smart account that reads from a keystore account (without transacting on the keystore itself), check out the guide on Account Initialization.
- To learn how to construct a userOp bundle for a smart account that interacts with the keystore, including details on its structure and signature formatting, refer to the guide on Transacting on L2s.
- For information on rotating/recovering keys on the keystore, refer to the guide on Key Rotation and Recovery.