Users create keystore accounts by counterfactually initializing them offchain without making a keystore transaction. This process will be facilitated by wallet interfaces and parallels the creation of an Ethereum EOA by offline generation of an ECDSA keypair before use.

To counterfactually initialize a keystore account, users should choose an arbitrary bytes32 salt and the desired bytes data and bytes vkey for the account. The account’s keystoreAddress can then be computed as keccak256(salt, keccak256(data), keccak256(vkey)).

To understand how this works, recall that the keystore state is a mapping keystoreAddress -> (data, vkey), where vkey is the verification key for a circuit governing the update rules for keystoreAddress while data represents arbitrary input interpreted according to the logic specified by the vkey. Together, (data, vkey) represent the authentication data that encapsulates the update rule for the state at keystoreAddress. To support counterfactual initialization, the keystore interprets the state in two ways:

  • If the keystore account at keystoreAddress is not initialized, the authentication data (data, vkey) with the ability to update keystoreAddress is interpreted as (initData, initVkey) where keccak256(salt, keccak256(initData), keccak256(initVkey)) == keystoreAddress and salt is the 32-byte salt used to initialize the keystore account.
  • Otherwise, the authentication data is simply the (data, vkey) at keystoreAddress in the keystore state.

Example Account Initialization

We will give an example of how to generate a keystore address and signing data for a 1-of-2 ECDSA multisig. The vkey we will use is a verification key for a ZK proof which verifies that at least 1 signer from a group of 2 has produced an ECDSA signature. This vkey expects the structure of data to be:

Solidity
abi.encodePacked(0x00, abi.encode(codehash, threshold, signers))

Let’s unpack the inputs there.

  • 0x00 and codehash are for compatibility with the Keystore Validator.
  • threshold is the number of signers that must sign a message for it to be valid.
  • signers is the list of allowed signers (associated with the keystoreAddress).

We will use the following values:

  • codehash = 0x0b2f6abb18102fa8a316ceda8a3f73b5eab33bb790d5bd92ff3995a9364adf97
  • threshold = 1
  • signers = [0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266, 0x70997970C51812dc3A010C7d01b50e0d17dc79C8]

The hash of our vkey is 0xa53fe8d9f71ced9b27711b393df4d425ea68ee941b625a1c4cbcbd773811d8df.

The salt we will use is 0x000000000000000000000000000000000000000000000000000000001a1c6a5e.

So then we have our counterfactual keystoreAddress as

Solidity
keccak256(0x000000000000000000000000000000000000000000000000000000001a1c6a5e, keccak256(data), 0xa53fe8d9f71ced9b27711b393df4d425ea68ee941b625a1c4cbcbd773811d8df)

which is 0xa1ac2bbfda5f73c7c4bbafc08f0629040d2941c69e70243b22d7ed5384c5dd74.

In the next section, we discuss using a keystore account on an L2 smart contract wallet.