Keystore State and Addresses
Table of Contents
The keystore supports a read-write state, indexed by a new keystore address, and a write-only withdrawals state, indexed by withdrawal hash. We explain the address format and each state in this section.
Keystore Address Format
Addresses on the keystore are bytes32
values. They may be generated offchain using counterfactual initialization using the following parameters:
bytes32 salt
- a salt to enable address uniqueness.bytes initialData
- the initial signing data associated with the address.bytes initialVkey
- the initial verification key for updates for the address.
The keystore address is then given by keystoreAddress = keccak256(salt, keccak256(initialData), keccak256(initialVkey))
.
Keystore State
Keystore Read-Write State
The read-write state consists of the following mappings, which are indexed by keystore address. They are committed to using a siloed Indexed Merkle tree.
state: mapping(keystoreAddress: bytes32 => (dataHash: bytes32, vkeyHash: bytes32));
balances: mapping(keystoreAddress: bytes32 => balance: uint256);
nonces: mapping(keystoreAddress: bytes32 => nonce: uint256);
These mappings store the following data:
state[keystoreAddress]
is defined to support counterfactual initialization, meaning:- If
state[keystoreAddress] == (bytes32(0), bytes32(0))
, thenkeystoreAddress
has been counterfactually initialized, and ifkeystoreAddress == keccak256(salt, keccak256(initialData), keccak256(initialVkey))
for somesalt
,initialData
, andinitialVkey
, theninitialData
andinitialVkey
are interpreted as the signing data and update verification key associated withkeystoreAddress
. - Otherwise, if
state[keystoreAddress] = (dataHash, vkeyHash)
fordataHash = keccak256(data)
andvkeyHash = keccak256(vkey)
, thendata
andvkey
are the signing data and update verification key associated withkeystoreAddress
.
- If
balances[keystoreAddress]
is the ETH balance in wei associated withkeystoreAddress
.nonces[keystoreAddress]
is the nonce associated withkeystoreAddress
, defined to be the number of transactions previously initiated fromkeystoreAddress
.
To authenticate a (dataHash, vkeyHash)
pair for a keystore account, we use the following data structure:
struct KeystoreAccount {
bytes32 keystoreAddress;
bytes32 salt;
bytes32 dataHash;
bytes vkey;
}
The authentication of KeystoreAccount
against a keystore state is given by the following function:
function authenticateKeystoreAccount(KeystoreAccount acct) {
if (state[acct.keystoreAddress] != (bytes32(0), bytes32(0))) {
require(state[acct.keystoreAddress] == (acct.dataHash, keccak256(acct.vkey)));
require(acct.salt == bytes32(0));
} else {
require(acct.keystoreAddress == keccak256(acct.salt, acct.dataHash, keccak256(acct.vkey)));
}
}
Note: Although the keystore state contains dataHash
and vkeyHash
instead of data
and vkey
, the preimages of dataHash
and vkeyHash
will have appeared in rollup DA for accounts which have transacted on the keystore. Keystore nodes are expected to store these preimages and enable users to retrieve them via a JSON-RPC API.
Keystore Withdrawals State
The write-only withdrawals state is given by the following mapping, which is also committed to using a siloed Indexed Merkle tree:
withdrawals: mapping(withdrawalHash: bytes32 => withdrawal: Withdrawal);
for a Withdrawal
represented by
struct Withdrawal {
address to;
uint256 amt;
}
and withdrawalHash = keccak256(abi.encodePacked(keystoreAddress, nonce))
, where keystoreAddress
is the keystore address of the user initiating the withdrawal and nonce
is the nonce of the withdrawal transaction.