Hidden Peril of ZKProof Verification in Blockchains
Blockchains verify ZKProofs once and for all, or so we hope
ZKProofs are amazing in general, but they are especially handy when it comes to blockchains. They allow private operations on shared states, make anonymous transactions possible, or achieve very high scalability even with very slow blockchains. Thus, blockchains are adopting it more and more, and today, there are even blockchains focused solely on ZKProof verification.
Nevertheless, the reality is not always this simple, and there is a huge problem of using any blockchain as it is to verify ZKProofs. The problem is not about potential attacks or the costs associated, but a direct risk in the consensus layer underlying.
In this article, we will discuss the current state, what is the problem, and what are potential solutions.
Background - Why is ZK adopted more?
It is no secret that ZK is used more and more by blockchains, or even constitutional applications. The obvious advantage is privacy, where ZK offers a nice way to build privacy preserving apps in many different use cases. Another important point is the scalability, where verifying a ZKProof is usually cheaper than repeating a computation as it is.
Let us look a bit more into the last point: Normally, ZKProof verification is known to be expensive, and even on a strong machine, verification can take a few hundred milliseconds. This may not sound like much, but compared to most EVM applications, this is much more than what it takes to execute a single smart contract call.
However, ZK has a huge advantage, which is that when working with zk-SNARKs (a specific type of ZKProofs), the verification time is independent of the computation size. This means that any computation can be verified in the exact same constant time, regardless of how hard the initial process was.
This is the basic observation behind ZK rollups or ZK based distributed applications. Instead of making the blockchain verify a lot of small proofs, these networks combine their ZKProofs into a single, all verifying ZKProof. Then, since zk-SNARKs have a constant verification time, the verification cost per proof decreases, and many TXs are “settled” on the blockchain simultaneously.
What did this result in?
Realizing this huge performance advantage, a lot of blockchains started using ZK based optimizations. The first and most well-known was of course Ethereum, where most of the TX volume moved from the original Ethereum to rollups in a very short time.
This transition resulted in blockchains becoming mainly responsible only for ZKProof verification, instead of general computation. Individual TXs are still and will always be used, of course, but as ZK matures more and more, performing everyday operations natively becomes more and more rare.
However, usage of a general-purpose blockchain for ZKProof verification actually creates a previously unconsidered risk on the consensus.
The Hidden Peril
ZKProofs are usually honest, meaning that no rollup cares to send an invalid ZKProof on chain. Thus, validators get the exact same result after each verification call: “The proof is correct”.
You may think here that this is also what we have with regular TXs. No one has infinite funds to burn on gas, so almost all TXs happening on Ethereum are somewhat valid. The crucial difference is that these TXs change the Ethereum state in a previously unknown way.
For instance, assume that you are sending money to a friend on Ethereum. It is very possible that you did not have enough balance or the TX was flawed in some other sense, so verifying your TX was valid is crucial. Nevertheless, the validator may easily suppose that you are just sending honestly, since you have nothing to gain from burning your gas fees on a meaningless TX. However, even with this assumption, the computation that validators need to perform does not really change. Your TX does not include your or your friend’s balance after the execution, and the only way of updating the state correctly is to perform the actual computation, which is the same as verifying your TX.
Now coming back to the ZK case. In ZKProofs, the state is updated independently from the verification phase. Basically, you send a proof and the new state to the chain, and only if your proof verifies, the chain updates the state as you wished. However, the verification part of the computation reveals nothing about this state update, and it is very possible for a validator to just assume your proof was honest, and update the state without verifying it.
To solidify with a real life example, think of ZK rollups. All the proofs of a rollup are sent by a single public key, and it is immediate to verify that this proof was generated by them. Since rollups are always honest and their proofs always check in, a validator can simply bypass the verification process when it sees this public key, and update the state directly.
Simply, the fundamental problem of ZK is being too easy to verify. This is just a fun way of saying it, of course. Formally, the imbalance between costs of ZK verification and the ease of understanding the state afterwards causes this dilemma. The validator has a lot to gain from not verifying a ZKProof, and they have very little to gain from verifying it. They can just assume it was valid and update state accordingly, which works perfectly in many cases.
This creates a massive risk for the consensus layer of the network. As validators have means and reasons to escape from expensive verification costs, the chain evolves into a state where no one actually verifies anything. The consensus layer works perfectly, but it secures nothing.
Native Solution Ideas
The simplest solution is to offer false proofs. If a rollup, realizing this potential attack on their system, introduces false proofs regularly, then validators will be forced to verify each proof, since they do not know which ones are valid. This is actually very similar to Solana’s solution with their Proof of History model: The random timing of invalid hashes forces validators to verify each hash taken in a history proof. Note that this is just an analogy, and Solana consensus does not include any ZK.
However, this native solution has two obvious problems. The first is the costs associated with sending false proofs, since the network still takes gas fees even when the proof is not valid. (Realize here that sending a very-easy-to-see false proof is also a problem, so a correct execution must send something that is very similar to a ZKProof and only understood at the very last possible minute). Thus, without defining clearly what a false executor would gain from doing so, it is very hard to convince people to just burn their gas fees on something that is useless for them individually, but just securing the chain in the long run.
The second problem is the potential vote stealing of validators. This is more subtle, so let’s look a bit closer at.
Why do I verify at all?
As said before, ZKProof verification is too costly compared to directly updating the state. Think that there exists a magical oracle, that just verifies proofs on your behalf, and sends a bit (true / false) as the answer. Why do you verify at all?
Unfortunately, networks should be designed for validators trying to optimize their costs, and a validator, seeing that it is impossible to be slashed by not doing the verification, can be very tempted to use such a service. Moreover, any realtime RPC can actually be seen as this service, and a clever client can use information across the gossip network to build a mock-verifier that just steals what others think on the proof. Even a small clue is enough to conclude the state of the proof, and errors can be tolerated occasionally: Even if a validator verifies something incorrectly, since only having two options for each proof, it is very easy to find the problem before the next block ends. So if the error is rare enough, verification costs of the ZKProof can be avoided totally.
Here, note again the important difference between regular EVM contract calls. Most contract execution happens just to understand how to update which state, which also depends on a lot of the existing information on the state. The case is not the same with ZK, and the only information that we need from that expensive computation is a single bit, telling us if the proof was valid or not.
So what does this tell us?
The attack scenarios described here are not immediate. They are definitely a real risk, especially on the long run, but ZK is not that much adapted yet. Nevertheless, we should think now if we want to avoid this in the future, so this is definitely something that must be discussed.
Moreover, there are already a lot of potential solutions offered to this problem. Some simple ideas may be introducing additional slashing for invalid verification or using commitment & reveal schemes to force validators to vote before seeing anyone else’s vote. Decreasing costs of verification is also a huge relief on the attack.
I guess the main idea of this article is that changing very simple parts of a design can break things fundamentally. Verifying a ZKProof instead of Solidity computation looks like an equivalent case: Both of them are just code. Yet, only one results in drastic effects in the consensus layer, which may even result in a fully centralized network in the long run.
In a nutshell, do not use concepts as it is, but think a little harder before you adopt a change. Is your architecture ready for this update, or did you just oversimplify?



