Keystore Validator Module
Table of Contents
- Actors for the Keystore Validator
- Key Data Consumers
- Immutability and Trust Assumptions of the Module
The Keystore Validator (KV) is the core contract connecting rollups to the Axiom Keystore. It is a validation module for both ERC-6900 and ERC-7579 smart accounts which facilitates reading the keystore state and authenticating userOps against data from the reads. There are three primary actors interacting with the KV:
- Keystore state syncers verify finalized keystore state roots at a certain L1 block timestamp in the module.
- User smart accounts install the module, read the keystore state and use the data to authenticate userOps.
- Consumer registrars deploy and add key data consumers to the module's consumer registry.
We give an overview of the details of each actor below.
Actors for the Keystore Validator
Keystore State Syncer
The exact role of the keystore state syncer (KSS) changes slightly depending on the L2. On L2s like OP Stack that support reading an L1 blockhash from L2, the module provides the interface below for verifying a keystore state root.
/// Caches
function cacheBlockhash() external;
function cacheKeystoreStateRoot(StorageProof calldata storageProof) external;
The KSS will cache an L1 blockhash in the module's storage, after which it can verify a keystore state root against the L1 blockhash with an L1 storage proof.
For L2s that do not enshrine L1 blockhash access, the module will expose the following alternative interface.
/// On L1 Broadcaster contract
function sendKeystoreStateRoot() external {
bytes32 keystoreStateRoot = keystoreBridge.latestStateRoot();
l2Bridge.sendCrossChainMessage(
keystoreValidatorModule, abi.encodeCall(cacheKeystoreStateRoot, (keystoreStateRoot, block.timestamp))
);
}
/// On L2
function cacheKeystoreStateRoot(bytes32 keystoreStateRoot, uint256 timestamp) external onlyBridge;
The KSS will initiate a bridge transaction from L1 to send the keystore state root to the module on L2.
User Smart Accounts
For smart accounts, the module supports both ERC-6900 and ERC-7579 validateUserOp(..)
interfaces which call the same underlying logic.
/// ERC-6900
function validateUserOp(uint32, PackedUserOperation calldata userOp, bytes32 userOpHash) external;
/// ERC-7579
function validateUserOp(PackedUserOperation calldata userOp, bytes32 userOpHash) external;
Other forms of validation (such as ERC-6900's validateRuntime(..)
) are not supported.
Consumer Registrars
As discussed in Key Data Consumers, the module uses a codehash
to identify an external contract to outsource authentication against keyData
to. For this to work, the contract must be deployed and registered in the module's consumer registry.
The consumer registrar facilitates deployment and registration of key data consumer contracts. It does this by exposing the following interface:
function deployAndRegisterKeyDataConsumer(bytes memory bytecode) external;
This will deploy the provided bytecode
and register its creationCodehash
in the module's consumer registry where creationCodehash = keccak256(bytecode)
.
Key Data Consumers
Key data consumers are separate smart contracts that serve as external components of the KV, responsible for defining how keyData
is processed and validated against. The keyData
includes metadata that informs the KV which consumer contract to invoke for validation. Within the validation flow, once the KV has verified the authenticity of the keyData
, it will forward the keyData
to the consumer contract for further external use. The KV expects that keyData
has the following format:
keyData[0] - domain separator (should be 0x00)
keyData[1..33] - key data consumer codehash
keyData[33..] - arbitrary key data
The codehash
uniquely identifies the logic embedded within the consumer contract and effectively allows a certain keystoreAddress
to signal how its keyData
should be validated.
All key data consumer contracts must implement the following interface:
function consumeKeyData(bytes calldata keyData, bytes calldata authData, bytes32 userOpHash)
external;
As an example, a key data consumer could implement signature verification logic. In this scenario:
keyData
would be an encoding of a threshold and a list of valid signersauthData
would be a list of signaturesuserOpHash
would be the digest signed by the signatures
Architecturally, separating the retrieval of the key data (in the keystore) from its actual use allows the former to be immutable while allowing for new forms of the latter to be introduced permissionlessly via external consumer contracts.
Immutability and Trust Assumptions of the Module
The module is deployed immutably on all supported L2s. However, Axiom will retain the ability to update the storage proof verification logic in the future to follow potential upgrades to the Ethereum L1 state trie. Unfortunately, because Ethereum L1 may change in future hard forks, there is no clear path at present to completely ossifying this module.