Kernel v2 and the Lessons We Learned
Ever since releasing Kernel v1, we have seen a flurry of activities from developers building novel plugins on Kernel. However, developers soon ran into significant limitations that exposed some of the shortcomings of Kernel, which prompted us to start working on Kernel v2.
In this blog post, we will dive into some of the issues with Kernel v1 and how we addressed them in v2.
A fair warning: this blog is written for a technical audience who want to understand the inner workings of Kernel, especially plugin developers. Most users of ZeroDev do not need to understand what’s described in this blog.
Issues with Kernel v1
Validation and Execution are Closely Coupled
In Kernel v1, plugins modified how transactions were validated. Once validated, transactions were executed through a hardcoded execute function.
However, certain use cases required not just custom validation, but also custom execution. For example, the default execution function allowed both call and delegatecall. Some plugin developers wanted to disable delegatecall altogether for security, which was hard to do with the tightly coupled v1 architecture.
Inability to Add Custom Functions
While the default execute function was meant to be flexible, there were legitimate needs to implement custom functions (e.g., to implement a new standard like ERC-1271). With Kernel v1, there was no way to dynamically extend the contract to implement a new interface.
Inability to Change the Default Validation Function
Plugins in Kernel v1 introduced new "paths" for validation, but there was no way to update the "default path"—which validates ECDSA signatures from the wallet owner. This meant the wallet owner could always execute transactions regardless of the plugins.
For example, to build a 2FA account, you need to ensure the default ECDSA validation function is ineffective, otherwise, the 2FA plugin is defeated. This was not possible in v1.
Overlapping Storage Between Kernel and Plugins
In Kernel v1, plugins were invoked through delegatecall, meaning they shared the same storage as the Kernel. To prevent storage collision, plugins were required to use unstructured storage, sometimes known as "diamond storage."
This requirement was difficult to enforce. A plugin needed careful auditing to ensure it wasn't using storage outside its designated area, placing a heavy burden on the plugin author, auditors, and users.
Design Decisions for Kernel v2
Kernel v2 draws on the lessons learned from real-world applications building on Kernel v1. At the core of Kernel v2’s architecture are two key design decisions:
- Separation of plugin storage from kernel storage.
- Separation of validation from execution.
Separation of Plugin Storage from Kernel Storage
In Kernel v1, plugins used delegatecall and shared storage, necessitating complex and unenforceable "diamond storage" rules.
In Kernel v2, validator plugins are invoked through call. Therefore, validator plugins have no access to the Kernel’s storage, vastly reducing the surface of attack and removing the storage complexity burden from plugin authors and auditors.
Separation of Validation from Execution
Whereas Kernel v1 only had "validation plugins," Kernel v2 now introduces two classes of plugins: validators and executors.
Validators
Validators are plugins that modify how transactions are validated, similar to v1 plugins.
A notable difference is that in v2, it’s possible to replace the "default" validator. For example, if you want a 2FA account, you would set the default validator to the 2FA plugin, replacing the default ECDSA plugin and making it impossible to send transactions without going through 2FA.
Executors
Executors are plugins that add custom functions to Kernel. Each custom function is tied to a validator, meaning a call to a custom function is “routed” to a particular validator.
This ability to route each function to a different validator makes ultra-fine-grained security policy possible. For example, you can add a custom function (executor) but set routing so that it ONLY works if the user goes through 2FA (validator). If you are familiar with EIP-2535 aka "Diamond Proxies," you can think of executors as "facets."
How Kernel v2 Works
In ERC-4337, a transaction (aka “UserOp”) is processed in two phases: a validation phase and an execution phase.
Validation Phase
The EntryPoint calls the validateUserOp function on Kernel. Transactions can be executed in one of three "modes," indicated by the first few bytes of the UserOp's signature field:
- Sudo Mode (0x0): Kernel's "default validator" is invoked. In ZeroDev, this is normally the ECDSA validator, which validates a transaction if it's signed by the owner (like a regular transaction).
- Plugin Mode (0x1): Kernel "looks up" the validator to use by the function selector from the
calldata. The mapping is set through the "Enable mode." Once looked up, the validator is used to validate the transaction. - Enable Mode (0x2): Kernel "enables" a validator by associating the current function selector with the validator. The validator's address is encoded inside the
signature. This validator will then be used to validate this and every subsequent invocation of the same function in Plugin mode.
Execution Phase
In Enable Mode, Kernel associates not just the validator but also the executor with the function selector. Executors are smart contracts that actually implement the custom function.
When the EntryPoint calls the function, Kernel uses a fallback function to look up the executor associated with the function selector, then delegatecalls the executor to execute the function.
Next Steps
Today we are happy to announce that Kernel v2 has passed the initial audit and therefore entered public beta. Here are some more resources for learning more about Kernel: