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)), then keystoreAddress has been counterfactually initialized, and if keystoreAddress == keccak256(salt, keccak256(initialData), keccak256(initialVkey)) for some salt, initialData, and initialVkey, then initialData and initialVkey are interpreted as the signing data and update verification key associated with keystoreAddress.
    • Otherwise, if state[keystoreAddress] = (dataHash, vkeyHash) for dataHash = keccak256(data) and vkeyHash = keccak256(vkey), then data and vkey are the signing data and update verification key associated with keystoreAddress.
  • balances[keystoreAddress] is the ETH balance in wei associated with keystoreAddress.
  • nonces[keystoreAddress] is the nonce associated with keystoreAddress, defined to be the number of transactions previously initiated from keystoreAddress.

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.