Tim Carstens

Victor Graf

Rami Khalil

Steven Li

Parker Thompson

Wolfgang Welz

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:

- Verifying transaction signatures.

- Verifying account & storage state against the parent block’s state root.

- Applying transactions.

- Paying fees to the block author.

- Updating the state root.

- Etc.

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:

- To make it easier for other teams to build their own ZK-powered infra: ZK rollups, zkEVMs, ZK light clients, ZK bridges, etc. Zeth provides everything you need generate ZK proofs for EVM-based blocks. This is a key component in any zkEVM or bridge. Zeth is open source and based on revm, so you can easily modify and use it in your own projects. The proofs can be verified onchain (perfect for bridges and L2s) or in native apps (perfect for full nodes and light clients).

- To investigate the performance of EVM in our zkVM, specifically for Ethereum-related tasks. (See below for an analysis of our findings.)

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:

- Support for mainstream programming languages.

- No-compromise performance for critical cryptographic operations.

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:

- Computational resources required to generate a proof.

- “Wall time” required to generate a proof (i.e., how long the user needs to wait to get their proof).

- Total cost ($) for proof generation.

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 perform the desired computation in a non-proving emulator. As we do so, we count the number of cycles that have been run so-far. At a configurable interval, we take snapshots of the program’s state. This effectively splits the execution into multiple
**segments.**Each segment is small, typically representing 1M cycles or fewer.

- The segments are distributed to a pool of workers. The workers generate ZK proofs for their given segments. Importantly, they are able to do this
. With enough workers,**in parallel**of the segments can be proven in the time required to prove only**all***1*segment. Because the segments are small, this time is typically very short (a couple dozen seconds).

- As the segment proofs are generated, they are “rolled up.” Each rollup operation takes a pair of sequential segment proofs and generates a new proof for the composition of the segments. For example, if Segment 1 proves that the program transitioned from state A to state B, and Segment 2 proves that the program transitioned from state B to state C, then the rollup proves that the program transitioned from state A to state C. With enough workers, this can be done in log(N) time, where N is the number of segments.

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.

Block no. | Transactions | Gas | Segments | Total cycles (upper bound) |

17034871 | 267 | 29,970,493 | 4675 | 4,902,092,800 |

17049942 | 206 | 29,987,306 | 3261 | 3,419,406,336 |

17095624 | 163 | 12,684,901 | 2095 | 2,196,766,720 |

17106222 | 105 | 10,781,405 | 1816 | 1,904,214,016 |

17181191 | 139 | 14,560,621 | 2007 | 2,104,492,032 |

17408122 | 135 | 26,256,143 | 2543 | 2,666,528,768 |

17495654 | 112 | 20,791,473 | 2986 | 3,131,047,936 |

17546837 | 168 | 17,279,191 | 2549 | 2,672,820,224 |

17606237 | 174 | 15,957,890 | 4079 | 4,277,141,504 |

17606771 | 139 | 12,595,376 | 2131 | 2,234,515,456 |

17640464 | 156 | 13,493,687 | 2167 | 2,272,264,192 |

17647876 | 101 | 10,542,312 | 2013 | 2,110,783,488 |

17682361 | 104 | 13,157,104 | 2105 | 2,207,252,480 |

17691128 | 112 | 14,838,663 | 9098 | 9,539,944,448 |

17735424 | 182 | 16,580,448 | 3242 | 3,399,483,392 |

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 full parallelism, the proving steps could be completed in 42 + 12 * 5 + 60 seconds, or
**2 minutes and 42 seconds**.

- If we conservatively round up and include the executor time, this comes to somewhere
**between 9 and 12 minutes**(giving an effective speed of**4.7 MHz - 6.3 MHz**.)

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:

- 1 GPU with 24 GiB of GPU memory

- 4 vCPUs with 16 GiB of memory

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:

- 4 vCPU with 16 GB of memory

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**.

- This block had 182 transactions, which comes out to
**$0.11 per transaction**.

- The total transaction value was 1.125045057 Eth, or roughly
**$2137.59**. Thus, each dollar of proving secured**$109.01**of user funds.

- That same block paid 0.117623263003047027 Eth in rewards (not including transaction fees). At time of writing, this is roughly
**$223.48**. Therefore, our proof cost roughly**8.7%**of the block reward to generate.

- Transaction fees added up to 0.03277635 Eth, or
**$62.28**, which is more than**3x**the cost of our proof.

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,

- Before we run the Zeth block builder, we run the list of transactions in a sandbox to determine which accounts and storage are relevant. (This process also lets us determine the oldest relevant predecessor block, which is needed to support blockhash() queries.)

- We fetch EIP-1186 inclusion proofs for each of the relevant accounts and storage. (We also fetch the relevant predecessor blocks.)

- We use those inclusion proofs to construct a partial MPT that contains all of the relevant data.

- We launch the zkVM, tell it to run the Zeth block builder, and supply the partial MPT together with the other inputs (the parent block, the list of transactions, etc).

From within the zkVM, the Zeth block builder:

- Verifies that the partial MPT root matches the parent block’s state root.

- Verifies the hash chain of predecessor blocks back to the parent.

- Applies the transactions.

- Updates the partial MPT.

- Uses the partial MPT’s new root hash as the state root for the new block.

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:

- Make sure the ZK proof is valid and came from Zeth. For off-chain applications, this can be checked using a function provided by the zkVM Rust crate. For on-chain applications, this can be checked using our onchain proof verifier.

- Make sure the ZK proof commits to the hash of the new block.

- Make sure the parent block has the hash you expect it to have.

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**.