If you’ve been deep in the trenches of Account Abstraction (AA) and Rollup scaling, you’ve probably come across the term “Keystore Rollup”. Since this concept sits at the intersection of two of "The Three Transitions", it represent a significant step towards a more global-scale and user-friendly Ethereum ecosystem.
The aim of this blogpost is to provide a high-level yet detailed deep dive into the concept of a Keystore Rollup, what problems does it solve, an example implementation of a Keystore Micro-Rollup while covering both the advantages and disadvantages of such an approach.
As part of the two of "The Three Transitions", the Ethereum ecosystem is encouraging users to migrate to rollups (L2s) for cheaper transactions and promoting the use of Smart Contract Wallets (SCW) for better security. As a result, a user is now required to deploy a smart contract account on every rollup they wish to transact on. Unlike Externally Owned Accounts (EOAs), which are stateless and function similarly on L1 and all L2s, a smart contract account holds state enabling features like key rotation, recovery, multi-sigs and more.
This is where lies the root of the problem: each rollup’s state is isolated from other rollups. If a user wishes to update her account state, such as adding or removing a signer from a multi-sig, this change needs to be synchronized across all her SCWs on all networks. This complexity is why many wallet providers are heavily tied to one network, leaving the user with no choice but to use separate wallets. Although some providers are developing modules to synchronize these changes to all networks for the user, but even that has significant difficulties in practice:
Complexity: Building and submitting transactions, handling transaction failure, settlement or re-orgs across all networks is intricate.
Cost: If rollups provide 10-100x cost savings compared to Ethereum, but a permission change needs propagation to 10-100 networks, we haven’t really saved anything on fees.
Security: Urgent permission revoking changes, such as incase of a key becoming leaked, need to applied immediately but different networks have different block times, time to finality, censorship guarantees therefore leaving users vulnerable if changes are not applied promptly or left unapplied altogether.
Essentially, in an increasingly modular world where a user’s assets are spread across many rollups, using smart wallets becomes more challenging, and their advantages over EOAs, such as better UX and security, start to fade. Wat to do?
The fundamental idea behind a Keystore rollup is to concentrate account state in one rollup, the Keystore rollup, which can serve as the source of truth for all other rollups. All accounts, regardless of what other networks they need to transact on, refer to the Keystore Rollup to authenticate users. Instead of storing mutable permissions in the account smart contract on each rollup, an immutable link to a “virtual account” id is stored which points to the actual permission data on the Keystore Rollup. Hence, by providing a global repository for account permissions, Keystore Rollups can streamline account management for users.
Drawing an analogy to OAuth, a standard widely used in Web2 systems, the Keystore rollup functions as the Identity Provider (IdP), while all other rollups serve as Service Providers (SPs).
We can compare how such a construction addresses the shortcomings of current systems:
Complexity: is reduced by removing the need to manage state across multiple networks and propagate duplicate transactions to make permission changes.
Cost: is reduced by removing these duplicated transaction fees.
Security: is improved by guaranteeing immediate application of removed permissions which automatically (implicitly) gets applied to all networks.
While this sounds promising, the subsequent sections delve into the existing approaches, implementation, and user stories of the Keystore Rollup revealing various drawbacks and considerations, which are discussed in depth at the end of this article.
The basic architecture of a Keystore Rollup comprises several components:
Rollup node
Keystore Rollup Contract on L1
Keystore Contract on each L2
Keystore Account Factory on each L2
Validator Library and State Verification Library
Let’s briefly go over each of these one-by-one:
Rollup node
The Keystore Rollup node maintains the state, has functionality for registering and updating accounts and generating proof for an account. It periodically rolls down its state root to the L1.
Keystore Rollup Contract on L1
The Keystore Rollup Contract on L1 stores the state root and sends messages to L2 contracts to update their state root variable.
Keystore Contract on each L2
The Keystore Contract on each L2 also stores the state root for direct read-access by Smart Contract Wallets (SCWs) on that L2.
Keystore Account Factory on each L2
The Keystore Account Factory on each L2 is responsible for creating SCWs deterministically. These SCWs point to the unique key of the Keystore Rollup, allowing users to authenticate across all networks with the same key.
Validator Library and the State Verification Library
These are included with the SCW on each L2. Validator Library verifies the validity of transaction signature against the SCW config, while the State Verification Library verifies the user-provided proof against the state root of the Keystore Rollup.
While the basic architecture discussed above remains the same across different approaches, deciding on a proof scheme is a crucial detail that impacts the complexity of implementation and costs for users in how data (and its proofs) are communicated cross-chain.
The following is a comparative analysis of a figure taken from Vitalik’s blog on Deeper dive on cross-L2 reading for wallets and other use cases which mentions different approaches for proof schemes:
While the merkle tree model is not the most cost effective for the user, they are widely used everywhere in Web3 with a large amount of available tooling. For the sake of simplicity, we will implement a merkle tree model since that is the default proofing mechanism employed by Stackr Micro-Rollups as well.
In oversimplification, micro-rollups are essentially state machines at the core.
The state machine has a defined shape of the state & the genesis state.
The state machine has actions(read: transaction types) which when invoked trigger a state transition function on the machine.
The State Transition Function(STF) in effect performs computation and mutates the state of the machine.
After the STF execution, the actions are rolled together in a block & shipped to Vulcan(Stackr’s Verification Middleware).
Finally, Vulcan —
Pessimistically re-executes the actions in the block to check for validity of the STF.
Settles on L1 & DA.
Micro-Rollup’s updated state is sent to the DA.
The metadata of the verified block & the updated state root is settled to the micro-rollup’s inbox contract on L1.
The above pipeline collectively forms Stackr’s Micro-Rollup Framework.
So, why are micro-rollups uniquely suited to build a keystore?
Micro-rollups enable specialized minimal VMs
In the original proposal for a dedicated minimal rollup for keystores, Vitalik advocates for a simple implementation without a full EVM for reduced security risk. Micro-Rollups lets you selectively expose only necessary functionality through deterministic and rigid State Transition Functions (STFs) tailored to the state of the rollup, significantly reducing the attack surface area.
Micro-rollups are flexible sovereign systems
Since micro-rollups break free from the shackles of the EVM, it’s trivial to add support for different signature schemes for sending transactions to the micro-rollup. In context of a keystore rollup, this could translate to supporting transactions signed using passkeys, multi-sigs, etc.
Disclaimer: This demonstration showcases the framework's capabilities and represents a preliminary proof of concept.
When developing a micro-rollup, it's crucial to conceptualize your logic in terms of a state machine. This involves carefully considering the state of the micro-rollup - that is, the data it will hold - and the actions that will dictate the behavior of the state transition function, which in turn operates on this state.
With the above in mind, we start by designing the state of the micro-rollup using Stackr’s SDK.
Accounts are stored off-chain inside a state machine as a key-value map
User sends actions which trigger a state transition function inside the state machine
User can send action to register a new account or update an account
After each set epoch a block is generated which contains details of accounts state
The block is sent to Vulcan network for verification
If the block conforms the rules of the state machine it is approved
The block data is split between L1 and DA for settlement.
The Stackr SDK, written in TypeScript, lets you inject the application logic directly into the blockchain. This environment delivers a developer experience akin to traditional server-side or backend application development. Let’s see how:
1. As a starting point, let’s define accounts
in our state.
Bear with us, these fields are explained in the next section.
After we setup our minimum viable state, we need to define state transition functions that update the state.
2. Let’s define two functions, register
which is responsible for registering a new account with the keystore, and update
which is responsible for updating an existing account entry.
Breaking down the register
function,
The user submits an action to register a new account, providing two fields (ideally generated by the user’s wallet):
codeHash
: hash of the smart contract’s bytecode of the validation library
configHash
: hash of the SCW config (could be a public key or in the case of a multisig a concatenation of multiple public keys)
Together this makes up the data
i.e.,data = hash([codeHash, configHash])
vk
: verification key that specifies instructions for changing the data
and vk
. In this case, it’s the address who initiates the action but it could be different depending on the type of approach.
key = hash([data,vk])
: this key serves as the unique “virtual account” id and returned to the user.
Finally, the new account entry is added to the state against this key, accounts[key] = (data, vk)
.
Breaking down the update
function,
The user submits an action to update an existing account, referencing the related key
among other fields they wish to update. While it may not be immediately obvious, update functionality is crucial to enable security-critical features like recovery or changing signers.
The address initiating the action must match the current vk
.
Finally, account entry at accounts[key]
is updated with the new values.
3. Ultimately, we define how the state root is computed for L1 settlement.
As mentioned at the beginning, micro-rollup’s state root is settled on L1. It is interesting to note that the developer can chose what part of the state settles on L1 vs what part can go on DA as metadata, thereby, unlocking hybrid security assumptions.
In this case, we extract the accounts
and settle it’s merklized root on L1, thereby opening up the possibility of direct inclusion proofs of accounts in the merkle tree.
We’ve reached the point where the minimum viable system works. Surprisingly easy to give on-chain superpowers to a backend server, no?
Note: The contracts side of code is intentionally omitted as out of scope for this article. It is well discussed in Conner’s Solidity Account circuits blog post. Although that is an approach for ZK-SNARK based proof scheme, the Validator Library and State Verification Library remains similar for this type of system too.
There are two primary user stories here:
User registering a keystore account
User sending a transaction on an L2 referencing their keystore account
Breaking these down,
User can register an account with the Keystore Micro-Rollup. As described in the previous section, this would involve their wallet provider generating the codeHash
and configHash
and passing it onto the keystore. The wallet provider also deploys a new SCW KeyStoreAccount
(bundled with the Validator Library and State Verification Library) using the KeystoreAccountFactory
on each L2 the user wishes to transact on. This SCW simply stores a pointer to the immutable account key returned by the Keystore micro-rollup, which can be later used for lookups in the keystore during verification.
When submitting a transaction on any L2, the user would first need to obtain a proof (in this case, a merkle proof) for their account from the Keystore Micro-Rollup. The wallet provider would essentially need to construct a User Operation, baking in the merkleProof
, SCW config
, and codeHash
into its signature, like so: userOp.signature = abi.encode(codeHash, userOpSig, config, merkleProof)
. This will later be decoded inside the SCW at which point it will leverage the Validator Library to verify the validity of transaction signature against the SCW config, and the State Verification Library to verify the user-provided merkle proof against the state root stored in the Keystore Contract on that L2. If both checks pass, the user’s transaction is authenticated.
An interesting thing to note here is that since each (data, vk)
pair corresponds to a unique key in the Keystore, the SCW need not be deployed immediately on all L2s but the user can still obtain a counterfactual address. This is a deterministic address for a smart contract that hasn't been deployed yet, but can still be used to receive funds on any chain at the same address
Can I migrate my existing SCW to a keystore rollup?
What does it mean that the L2 “requires” a proof?
Aren’t the transactions going to be more expensive as there is more metadata?
config
, codeHash
, proof
) while also requiring extra on-chain compute for the verification of the proof. This incurs additional gas fees in both calldata and computation. As Merkle Tree of accounts grows, the size of the proof becomes larger and will cost more gas fees.What about latency between the proof creation and proof verification?
What about the cost of syncing the state root across all L2s?
What if the keystore rollup censors me?
What if the keystore rollup re-orgs?
What kinds of wallet configurations and signing methods can it support?
codeHash
and configHash
, which lets the developer write unique and custom signature verifications methods in the Validator Library. The possibilites are endless: multisigs, passkeys, rotating session keys, signing methods with aggregation like BLS or EdDSA, ZK Email, ZK-SNARKs, Quantum safe signing method like Winternitz Signatures, and more.Keystore Rollups offer a promising solution for managing smart contract wallets across networks by centralizing account states on a dedicated rollup. At first glance, they seem to simplify permission management, reduce complexity, cut costs, and enhance security. However, as we delve deeper, we see that they are not without their own challenges, particularly related to costs and latency issues.
Being a complimentary feature to a more Chain Abstracted future of seamless cross-chain interaction, ideas like Keystore Rollup play a crucial role in paving a path to a more scalable, secure, and user-friendly Ethereum ecosystem. As developers and researchers, we must strive to explore and experiment with such ideas.
https://vitalik.eth.limo/general/2023/06/09/three_transitions.html
https://notes.ethereum.org/@vbuterin/minimal_keystore_rollup
We will soon be sharing a more “complete” version of the above mentioned experiment. Until then, if you’re someone who’s looking to build a Keystore Rollup for your protocol, we’d love to talk to you and find ways to collaborate.
Meanwhile - beta access to our SDK is still open, head over to the application form and sign-up. We are letting developers in on a rolling basis.
Contact us on Discord (preferable), Twitter or shoot an email at gm[at]stackrlabs.xyz