The Ethereum Virtual Machine (EVM) is the virtual machine that manages state and executes smart contracts on the Ethereum blockchain. zkEVMs, which aim to prove EVM execution through zero-knowledge (ZK) circuitry, have seen significant growth and expansion as an industry over the past year. Many different projects and community efforts have emerged in this space, each taking its own unique approach to the challenges and opportunities presented by the intersection of these two technologies.
One dimension on which zkEVM projects differ is the level of EVM compatibility. While EVM compatibility is a continuum, there are two major approaches: language compatibility and bytecode compatibility.
To understand the difference between these approaches, it's important to understand how the EVM executes code written in high-level languages like Solidity or Vyper.
For the EVM to be able to run behavior specified by a smart contract, there is a process in which high-level languages are compiled into bytecode to interface with the EVM. For Solidity, the typical flow is as follows:
First, the contract is written in the Solidity programming language and saved as a
.sol file. This file contains the contract's functions, variables, and other elements. The Solidity code is typically written using an integrated development environment (IDE) and follows a specific syntax and structure.
Next, the Solidity compiler is used to compile the
.sol file into bytecode. Bytecode is an efficiently stored, machine-readable representation of opcodes that can be executed on the EVM. Opcodes are low-level instructions that the EVM directly interprets and executes. The compilation process involves several steps, such as syntax checking, type checking, and optimization, to ensure that the bytecode is correct and efficient.
Once the contract has been compiled into bytecode, it can be deployed to Ethereum. This typically involves sending a transaction to the mempool with the contract's bytecode. The transaction is eventually included into an Ethereum block, and the contract gets created.
After the contract has been deployed, it can be interacted with by other contracts and users on Ethereum.
The above flow describes how contracts are deployed to Ethereum. The deployment flow to a zkEVM will vary depending on whether it is bytecode-compatible or language-compatible.
zkEVMs that execute and prove the same bytecode as the EVM are referred to as bytecode-compatible. With bytecode-compatible zkEVMs, there are no changes to this developer experience other than where the contract is deployed. The same Solidity code, compiler, and bytecode are used. Developers can continue using the exact same programming languages and tools as they usually would while also taking advantage of the scalability and cryptographic security of zero-knowledge circuits. These types of zkEVMs need to be able to replicate the behavior of the EVM by processing the same low-level opcodes and replicating their results identically. This involves mapping opcodes onto custom ZK circuits. This mapping is challenging to achieve from an engineering perspective, and opcode mapping leads to larger circuits and costlier proof generation. However, it ultimately results in a simpler experience for developers.
Language compatibility refers to the ability of a zkEVM to operate with high-level code such as Solidity. Developers can still write code in Solidity or other high-level languages, but this high-level code must be compiled down to bytecode that differs from the EVM. This requires a compilation step specific to the zkEVM network on which the contract is being deployed. Non-EVM bytecode can be designed to be more circuit-friendly, resulting in more efficient proof generation.
: Solidity is first compiled into an intermediate language known as Yul before compilation into bytecode.