As discussed in Using Keystore Accounts, the Keystore Validator (KV) is the central smart contract connecting the keystore to smart accounts on L2s. The KV’s main functions are to persist keystore state roots, facilitate proof verification of keystore-located signing data against the state roots, and validate authentication data against the keystore-based signing data.

The first two of these functions take place in functions on the KV, while validation of authentication data takes place in external contracts called Key Data Consumers (KDC) which are deployed through and registered in the KV. These contracts are responsible for defining how key data is parsed and used to validate authentications.

The KV maintains a registry of KDCs that can be permissionlessly updated. For KDC deployment and registration, the KV exposes the following interface:

/// Calls CREATE directly with `bytecode` and stores the resulting `address` as
/// the value of a mapping with key `creationCodehash`.
function deployAndRegisterKeyDataConsumer(bytes memory bytecode) external;

For a KDC to be available for use to the KV, it must be deployed and registered via the above function. An important artifact of the deployment is the creation codehash, defined by

creationCodeHash = keccak256(bytecode)

which is used as the key of a mapping from creationCodehash to deployedAddress and will be referenced in the validation flow against the Keystore Validator.

Implementing a Key Data Consumer

All KDCs must implement the following interface:

/// Receives verified key data and validates `authData` against it
function consumeKeyData(bytes calldata keyData, bytes calldata authData, bytes32 userOpHash)
        external;

where

  • keyData is the verified key data
  • authData is the authentication data to be validated
  • userOpHash is the hash of the user operation

As an example, a key data consumer could implement m-of-n ECDSA signature verification logic. In this scenario:

  • keyData would encode the threshold m and a list of n valid signers
  • authData would be a list of m signatures authenticating a transaction
  • userOpHash would be the digest signed by the signatures

As mentioned in Using Keystore Accounts, using the creationCodehash to identify a piece of EVM execution allows for a (data, vkey) pair to signal its desired on-chain validation logic. This allows deployments of the same key data consumer across different rollups to be identifiable without reference to a permissioned registry.

There are some considerations to keep in mind when using creationCodehash to identify a piece of EVM execution. For example, consider a contract whose execution reads from a storage slot which can later be updated. In this case, the EVM bytecode committed to by creationCodehash can behave differently depending on the storage value, which is not committed to by creationCodehash.

The developer of the key data consumer must therefore ensure that the creationCodehash upholds its intended semantic value. We strongly encourage developers to follow the guidelines below.

  • Creation code execution should not return runtime code that is not a subset of the creation code.
  • The consumeKeyData() function should not read storage slots which can later be updated.
  • The contract should not perform external calls to addresses that are not precompiles.
    • If you do perform an external call to a non-precompile address, check that codesize != 0.
  • Be careful with calling a precompile that is not supported on all networks. Low-level calls to empty addresses will silently succeed!
    • If the expected behavior of the precompile on supported networks has returndata, check that returndatasize() != 0.

The above is not an exhaustive list but extra care should be taken to implement the key data consumer around the assumption that it will be uniquely identified by its creationCodehash.

While it cannot be enforced in EVM, the consumeKeyData() function should be a pure function. If new functionality is required, it can be achieved by deploying a new key data consumer and initiating a corresponding update on the keystore. This approach ensures that each EVM validation of a keystore authentication is bound to a unique creationCodehash and thus committed to in the keystore data.

Example Implementation

To see an example implementation of a key data consumer, see the Keystore Periphery.