Today we are releasing Zeth, an open-source ZK block prover for Ethereum built on the RISC Zero zkVM. Zeth makes it possible to prove that a given Ethereum block is valid without relying on the validator or sync committees by doing all of the work needed to construct a new block within the zkVM. Zeth has been verified to work on several real-world blocks from Ethereum mainnet and passes all relevant tests from the official Ethereum testsuite. Building on zkVM’s Rust support and robust crates including revm, ethers, and alloy, we were able to achieve this level of compatibility in under 4 weeks of work. Through zkVM’s support for continuations and the Bonsai proving service, Zeth can generate these proofs in minutes. And with our support for onchain verification, anyone can cheaply verify those proofs on chain. In this post we will fill in the details, and if you’d like to dig in to the code check out the source and visit the RISC Zero developer’s portal.
Roughly one year ago, Vitalik’s The different types of ZK-EVMs explained that:
Ethereum was not originally designed around ZK-friendliness, so there are many parts of the Ethereum protocol that take a large amount of computation to ZK-prove. Type 1 aims to replicate Ethereum exactly, and so it has no way of mitigating these inefficiencies. At present, proofs for Ethereum blocks take many hours to produce.
While that may have been the case historically, today we are pleased to announce that Ethereum blocks can be proven in minutes, not hours, using RISC Zero’s zkVM and Bonsai proving service.
Today we are releasing Zeth, an open-source ZK block prover for Ethereum built on the RISC Zero zkVM.
Zeth makes it possible to prove that a given Ethereum block is valid (i.e., is the result of applying the given list of transactions to the parent block) without relying on the validator or sync committees. This is because Zeth does all of the work needed to construct a new block from within the zkVM, including:
After constructing the new block, Zeth calculates and outputs its hash. By running this process within the zkVM, we obtain a ZK proof that the new block is valid.
By building on RISC Zero’s zkVM and popular Rust crates (like revm, ethers, and alloy), we were able to write the first version of Zeth in under 4 weeks. Through zkVM’s support for continuations and the Bonsai proving service, we can cheaply generate these proofs in minutes. And with our support for onchain verification, we can cheaply verify these proofs on chain.
Zeth has been verified to work on several real-world blocks from Ethereum mainnet and passes all relevant tests from the official Ethereum testsuite.
Because Zeth constructs standard Ethereum blocks, it can be thought of as a Type 1 zkEVM. But it’s more than that: because Zeth was built using standard Rust crates — the same crates used by popular full nodes like Reth — we like to think of it as a Type 0 zkEVM: full protocol compatibility, together with significant code re-use.
We believe this milestone represents a major step forward for ZK technology and the Ethereum ecosystem. In this post, we discuss how we were able to write the first version of Zeth in a matter of weeks, its performance, how it works, and what this means for ZK projects.
We wrote Zeth for two reasons:
Interested in talking to us about Zeth? Reach out to us!
As a Type 0 zkEVM, Zeth enables developers to build ZK rollups with completely native EVM and Ethereum support. Together with our support for onchain proof verification, it’s never been easier to build an L2 powered by ZK.
Existing ZK rollups and zkEVM circuits are monolithic in design and lack upgradability without a high level understanding of ZK cryptography from the developer. In contrast, our zkVM based approach to Zeth enables any developer to customize and modify to suit their needs.
Zeth is open source, based on revm, and can easily be adapted to support other zkEVMs and EVM-compatible chains. This ensures easy updates to Zeth for future EIPs while providing modularity that enables developers to build their own block construction logic into Zeth.
We hope these foundations will democratize ZK rollups and zkEVMs, which previously have required multiple years of development and 100+ million dollars of funding making them unreachable for the majority of projects.
The introduction of beacon chains is, without a doubt, a boon for light clients and bridges. These technologies, which build on Ethereum’s now-well-established proof of stake model, make it easy for light clients and bridges to verify recent blocks without rebuilding those blocks — assuming everyone is playing by the rules.
Of course, the entire point of staking is to provide an economic incentive to play by the rules. But the threat of slashing is not a guarantee that something bad will never happen. External incentives can “tip the scale” in favor of shenanigans — and designing a light client or bridge that handles those shenanigans correctly is hard.
With tools like Zeth, the risk of shenanigans is greatly reduced. Light clients can integrate with Zeth simply by adding a few calls to our zkVM; and onchain applications, such as bridges, can integrate with Zeth using our onchain proof verification contracts.
In the near future, one could imagine light clients and bridges that use ZK proofs to be certain that a given block is valid. This approach would significantly reduce risk without significantly increasing the cost of verifying a block.
This is particularly important for app chains, the modular ecosystem, and new chains that don’t yet have the same level of security offered by Ethereum’s large community of full nodes.
Zeth is built on the RISC Zero zkVM, which provides a familiar programming experience powered by the RISC-V instruction set architecture. But our zkVM isn’t just a RISC-V core. We also have accelerator circuits for common cryptographic tasks, such as hashing and signature verification.
This hybrid approach (a general-purpose CPU core augmented with accelerator circuits) gives us the best of both worlds:
Thus, we were able to rapidly build Zeth using existing Rust crates from revm, ethers, and alloy. By reusing existing crates, we were able to get the first version of Zeth working in under 4 weeks. This engineering velocity would not have been possible in less mature ecosystems.
For performance, Zeth takes advantage of our accelerator circuit for ECDSA signature verification, as well as continuations — a novel feature of our ZK framework that makes it easy to quickly prove large computations using clusters of GPUs working in parallel (using either nVidia CUDA or Apple Metal). Continuations are easy to use: the feature is transparently provided to all guest programs running in the zkVM. You don’t need to make any changes to your code; it just works.
With our zkVM, we are able to cheaply generate a ZK proof of the validity an Ethereum block in minutes, not hours.
In this section we describe the performance of the Zeth block builder. Zeth is still new, so these numbers are subject to change; still, we wanted to provide some concrete data as a baseline against which future efforts can be compared.
When it comes to performance, there’s several factors to consider:
With our zkVM it’s possible to tune performance through the use of continuations. Therefore, we need to pause for a moment to discuss how continuations work.
Our zkVM implements a standard RISC-V processor. Therefore, execution proceeds in cycles. (In our circuit, most RISC-V instructions take only 1 cycle to execute, though there are some exceptions.) Simple programs typically only require a few hundred thousand cycles to execute, but more complex programs can easily require several billion cycles.
In typical ZK systems, these cycles of execution are gathered together into a single proof; as the number of cycles increases, so too does the time and memory required to generate that proof. But our zkVM is not typical. Earlier this year, we were the first to launch a new feature — continuations — that improves on this paradigm significantly.
With continuations, the proving process is split into three phases:
We’ll see these phases in action as we dive into the numbers.
Without further ado, let’s look at some numbers!
First, let’s look at the complexity of building Ethereum blocks. In the table that follows, we picked some real-world Ethereum blocks and reconstructed them using Zeth from within the zkVM.
For example, block 17606771 generated 2131 segments. Each segment represents at-most 2^20 cycles of execution, so the entire computation required at-most 2,234,515,456 cycles of execution.
In general, we see that a typical Ethereum block requires 2-4 billion cycles to construct, but sometimes as many as 9.5 billion. (At first we were surprised to see that these differences are not reflected in the transaction’s gas. But on further reflection, this makes sense: the gas system was designed with conventional execution in mind, not ZK proving.)
With continuations, this scale is easy to manage. Based on these data, a peer-to-peer network with 10,000 nodes running the zkVM prover would be sufficient to achieve maximum parallel proving performance for the largest blocks — a tiny fraction of the 700,000 validators Ethereum currently has.
To gather some basic performance data, we spun up a test instance of Bonsai with 64 GPU workers. We then asked it to prove block 17735424 (182 transactions, 3242 segments, or roughly 3.4B cycles) using Zeth.
To generate the proof, the zkVM must first split execution into segments. In the screenshot below, this is captured by the Executor task, which took 10 minutes to run. (Much of that time was spent doing AWS things, like writing to network storage. On local machines, this same task completes in under 6 minutes. We expect to lower this time significantly over the coming year.)
The executor ultimately split execution into 3242 segments. That’s a lot of segments for a mere 64 GPUs. As such, each worker node had to generate 50 segment proofs. As shown in the screenshot below, this took 35 minutes. If we had 50x as many workers, this would have only required 42 seconds.
After the segment proofs were complete, the rollup began. With 3242 segments, we need to do log_2(3242) = 12 rounds of the rollup procedure. In the early stages of the rollup, there was more work than workers; so the first stage took 1 minute, the second stage took 35 seconds, the third took 25, etc. By the 7th stage, times stabilized at a little over 5 seconds. Again, if we had more workers, every stage would have only taken 5 seconds.
Lastly, after the rollup is complete, the result is finalized, which takes another minute.
Therefore, with our under-sized cluster, we were able to generate the proof in about 50 minutes end to end (giving an effective speed of 1.1 MHz). With a properly sized cluster, we estimate the proof could be generated much quicker:
With continued improvements to our executor and proving framework, we are optimistic that this time will be reduced greatly over the coming year.
The test cluster described above was deployed to AWS. It consisted of 64 g5.xlarge proving nodes and 1 m5zn.xlarge executor node. According to Amazon, each g5.xlarge node has:
At time of writing, the on-demand price for these instances is $1.006/hr, with a reduced price of $0.402/hr for reserved instances. Meanwhile, the Amazon spec sheet indicates our m5zn.xlarge node has:
At time of writing, the on-demand price for this instance is $0.3303/hr.
We can use these numbers to generate a rough estimate for the cost of the proof describe above for block 17735424.
Recall that we deployed 64 proving nodes, and with that deployment, the proof required 50 minutes to generate (end-to-end). Ignoring idle worker time, 64 proving nodes, plus one executive node, for 50 minutes, comes to 50/60 * (64 * $0.402 + $0.3303) = $21.72. This is an over-estimate, since it assumes we are paying for idle workers. If we exclude the cost of idle workers (say, by shutting them down or having them do other work), the cost is roughly $19.61.
Remarkably, these dollar estimates are independent of the size of the cluster! All that matters is the number of segments. This is because 1 machine doing 2 jobs in sequence costs the same as 2 machines doing 1 job each in parallel. Consequently, with a larger cluster, the proof would be faster to generate but no more expensive.
There are several ways to reduce this cost even further. In addition to continued performance improvements to our zkVM, and perhaps the addition of a Keccak accelerator, we could shop around for cheaper instances. Importantly, given the low specs of the machines we used (and our zkVM’s support for nVidia Cuda and Apple Metal), this work could easily be done by a p2p network consisting of ordinary consumer PCs and Macs.
As mentioned above, we have verified a Zeth proof on Sepolia using the RISC Zero Groth16 verifier. This is a relatively new part of the RISC Zero stack, announced earlier this month. It works by using Bonsai to convert zkVM’s native STARK proofs into an equivalent SNARK proof, and submitting that proof to an onchain SNARK verifier.
If we view the transaction inputs as UTF-8 data, we see that this proof corresponds to block 17735424.
Using Bonsai, the conversion from STARK to SNARK took about 40 seconds. Verifying the SNARK onchain consumed 245,129 gas (roughly $5.90 at time of writing.)
Of course, one of the nice things about zkVM is that it can rollup several proofs into one. With this feature, an entire set of proofs can be verified onchain without using any additional gas. In this way, the onchain verification cost can be amortized across the set, resulting in lower fees for everyone involved.
As previously noted, Ethereum was not designed with ZK-friendliness in mind. As the landscape of zkEVMs shows, there are many things that could be done differently, notably around opcodes, digital signatures, and hash functions.
While these changes do improve performance, we were still able to achieve solid performance without them. When Vitalik wrote about the different types of zkEVMs last year, proving the validity of an Ethereum block took hours; today we can do it in minutes. ZK performance is improving at a rapid pace and there’s good reason to believe this trend will continue over the next several years.
Check out these resources, and come say hi on Discord.
This section is for the builders.
Roughly speaking, Zeth builds blocks the same way full nodes do: we start with a parent block, a list of transactions, and a block author; then we do a bunch of computation (verify signatures, run transactions, update the global state, etc); then we return (the hash of) the new block.
But unlike a full node, we do this from within the zkVM. This means we obtain a ZK proof that the block with the given hash is valid.
Of course, this is not without its challenges. In this section, we describe those challenges and how we addressed them.
The first challenge is cryptography. Constructing an Ethereum block requires doing a lot of it, most notably hashing (Keccak-256) and signature verification (ECDSA with secp256k1).
Our zkVM has accelerated support for working with elliptic curves, so the ECDSA signature checks aren’t so bad.
But when it comes to hashing, we aren’t so lucky. Our zkVM has accelerated support for Sha2-256, but (at time of writing) not Keccak-256. So for now, we’re simply using the Keccak implementation from the sha3 Rust crate. Through profiling, we know that a significant number of cycles goes into this. This is not optimal, but our zkVM can handle it, and we can always loop back and add a Keccak accelerator later.
In Ethereum, accounts and storage are tracked by a global Merkle Patricia Trie (MPT).
According to Etherscan, at time of writing this tree contains state for nearly 250,000,000 unique Ethereum addresses. That’s not a lot of data in the grand scheme of things, but it is enough data that one must be careful with how its stored and used. In particular, MPT performance is crucial.
But performance is not the only factor; we must also consider security.
Zeth’s block builder runs as a guest within the zkVM. This means it does not have direct access to the Ethereum p2p network or other RPC providers. Instead, it must rely on data provided by a separate program that runs outside the zkVM.
When analyzing the security of ZK apps, one must assume that outside programs are malicious, and consequently, might supply malicious data. To protect against this, ZK apps must verify that the data given to them is valid.
For the Zeth block builder, this means verifying the state of all relevant accounts and storage (i.e., the accounts and storage needed to run the given list of transactions).
Luckily, a mechanism for doing this is provided by EIP-1186, which defines a standard way to prove the state of a given account (and its storage) via Merkle inclusion.
In principle, Zeth’s block builder could verify account and storage states simply by verifying a set of EIP-1186 inclusion proofs. But this approach is not optimal.
Instead, it is preferable to use the data from EIP-1186 inclusion proofs to construct a partial MPT. This is an MPT that contains only the nodes that are relevant to the given list of transactions; the irrelevant branches are simply represented by their corresponding hashes. You can think of a partial MPT as being a kind of “union” of Merkle inclusion proofs; or, if you prefer, you can think of it as a Merkle subset proof.
The process for verifying a partial MPT is basically the same as verifying an ordinary EIP-1186 proof: calculate the root hash and compare to the parent block’s state root. If they’re equal, then you can trust the integrity of the accounts and storage held within.
After the partial MPT has been verified, the transactions can be applied and the partial MPT can be updated. The new state root can then be obtained by calculating the partial MPT’s new root hash.
This is the approach we took with the Zeth block builder. To summarize,
From within the zkVM, the Zeth block builder:
When the Zeth block builder is finished, it outputs the hash of the new block.
This hash includes a commitment to the parent block, and thus the parent’s state root (which was used to verify the original partial MPT). This means a malicious prover cannot supply invalid data for accounts and storage without also supplying an invalid parent block.
In other words: if the parent block is valid, so too is the new block generated by Zeth.
Therefore, if someone gives you a new block and a ZK proof generated by Zeth, you can check the block’s validity by checking 3 things:
If those things all check out, then the new block is valid.
Our goal with this project was to investigate the performance of block construction. For this goal, we decided to limit our scope to post-merge blocks.
Additionally, while Zeth is able to prove that a given block is valid, it does not currently prove consensus (i.e., that the block is actually contained in the canonical chain). This might change in the future, perhaps by adding a check for the verifier or sync committee signatures from within the zkVM.
Lastly, Zeth is new software. While we have performed some testing (including the Ethereum test suite and various real-world blocks), it’s possible that Zeth still contains some bugs. At time of writing, it should be regarded as experimental.