详解 Kakarot zkEVM：Starknet 的 EVM 兼容之路

TL;DR

虚拟机是一个软件仿真的计算机系统，为程序提供执行环境。它可以模拟各种硬件设备，使程序在受控且兼容的环境中运行。以太坊虚拟机（EVM）是一种基于栈的虚拟机，用于执行以太坊智能合约。
zkEVM 是一种集成了零知识证明 / 有效性证明技术的 EVM。它允许使用零知识证明验证 EVM 的执行过程，而无需所有验证者重新执行 EVM。市场上有各种 zkEVM 产品，每个产品都有自己的方法和设计。
需要 zkEVM 的原因在于对在 Layer 2 上支持智能合约执行的虚拟机的需求。此外，一些项目选择使用 zkEVM 来利用 EVM 的广泛用户生态系统，并设计更友好于零知识证明的指令集。
Kakarot 是使用 Cairo 语言在 Starknet 上实现的 zkEVM。它以 Cairo 智能合约的形式模拟了 EVM 的堆栈、内存、执行和其他方面。Kakarot 面临与 Starknet 帐户系统的兼容性、成本优化和稳定性等挑战，因为 Cairo 语言尚处于实验阶段。
Warp 是将 Solidity 代码转换为 Cairo 代码的转换器，在高级语言级别提供兼容性。另一方面，Kakarot 通过实现 EVM 的操作码和预编译，提供了在 EVM 级别的兼容性。

什么是虚拟机？

要讲清楚什么是虚拟机，必须先讲当今主流的冯诺依曼架构下的计算机执行流程。运行在计算机上的种种程序，通常是由高级语言经过层层转化，最终生成机器可理解的机器码完成执行的。根据转化为机器码的方式不同，高级语言可以大致分为编译型语言与解释型语言。

编译型语言是指在代码的编写完成后，需要经过编译器的处理，将高级语言代码转换成机器码，生成可执行文件。一次编译就可以多次以较高效率执行。编译型语言的优点是因为在编译时已经将代码转换为机器码，因此执行速度快，并且可以在没有编译器的环境下运行程序，便于用户使用，不需要安装额外的软件。常见的编译型语言包括 C，C++，Go 等。

与编译型语言相对应的是解释型语言。解释型语言是指代码通过解释器逐行解释执行，直接运行在计算机上，每次运行都要重新进行翻译过程。解释型语言的优点是开发效率高，代码易于调试，但执行速度相对较慢。常见的解释型语言包括 Python，JavaScript，Ruby 等。

需要强调，语言从本质上并不区分编译型和解释型，只是在最初设计时会有一些倾向。C/C++ 绝大多数情况下是编译执行，但是也可以解释执行（Cint、Cling）。很多传统意义上的解释型语言，现在是编译成中间代码在虚拟机上执行（Python、Lua）。
知道了物理机的执行流程，现在来讲虚拟机。

虚拟机通常通过模拟不同的硬件设备来提供一个虚拟的计算机环境。不同的虚拟机可以模拟的硬件设备有所不同，但通常包括 CPU、内存、硬盘、网络接口等。

以以太坊虚拟机 EVM 为例，EVM 是一种基于堆栈的虚拟机，它被用于执行以太坊智能合约。EVM 通过模拟 CPU、内存、存储器和栈等硬件设备来提供一个虚拟的计算机环境。

具体来说，EVM 是一种基于堆栈的虚拟机，它使用堆栈来存储数据和执行指令。EVM 的指令集包括各种操作码，例如算术操作、逻辑操作、存储操作、跳转操作等，这些指令可以在 EVM 的堆栈上执行，从而完成智能合约的执行。

EVM 模拟的内存和存储器是用于存储智能合约的状态和数据的设备。EVM 将内存和存储器视为两个不同的区域，它可以通过读取和写入内存和存储器来访问智能合约的状态和数据。

EVM 模拟的栈用于存储指令的操作数和结果。EVM 的指令集中的大多数指令都是基于堆栈的，它们从栈中读取操作数并将结果推回栈中。

总之，EVM 通过模拟 CPU、内存、存储器和栈等硬件设备来提供一个虚拟的计算机环境，它可以执行智能合约的指令并存储智能合约的状态和数据。在实际运行中，EVM 会将智能合约的字节码加载到内存中，并通过执行指令集来执行智能合约的逻辑。EVM 实际取代的是上图中操作系统 + 硬件的部分。

EVM 的设计过程，显然是自下而上的，先敲定了模拟的硬件环境（堆栈、内存），再根据对应的环境设计了自己的一套汇编指令集（Opcode）与字节码（Bytecode）。尽管汇编指令集是给人看的，但是涉及到很多底层知识，对开发者的要求较高，开发起来也较繁琐，所以需要高级语言，屏蔽晦涩繁琐的底层调用，为开发者提供更好的体验。EVM 由于其汇编指令集的的定制化设计，很难直接利用传统的高级语言，索性重新一个新的高级语言以适配该虚拟机。以太坊社区为了 EVM 执行效率设计了两种编译型的高级语言 ——Solidity 和 Vyper。Solidity 自不必强调，Vyper 是 Vitalik 针对 Solidity 中存在的部分缺陷进行改进后设计的 EVM 高级语言，但是在社区没有获得很高的采用度，于是渐渐淡出历史舞台。

什么是 zkEVM

简单来讲，zkEVM 就是运用零知识证明 / 有效性证明技术的 EVM，让 EVM 的执行过程，可以通过零知识证明 / 有效性证明来更高效、低成本地验证，而不需要所有验证者都重新进行 EVM 的执行过程。

市场上的 zkEVM 产品众多，赛道火热，主要玩家包括 Starknet, zkSync, Scroll, Taiko, Linea, Polygon zkEVM（原 Polygon Hermez） 等，被 vitalik 分为了 5 类（1、2、2.5、3、4）。具体的内容可以查看 Vitalik 的博客。

为什么需要 zkEVM

这个问题需要从两方面来看。

最初的 zk Rollup 尝试，都只能实现较为简单的转账、交易功能，例如 zkSync Lite, Loopring 等。但是曾经沧海难为水，用惯了以太坊上图灵完备的 EVM，当无法通过编程创造多样的应用时，人们便开始呼唤 L2 上的虚拟机。撰写智能合约的需求，是为一。

由于 EVM 中部分设计对于生成零知识证明 / 有效性证明不友好，部分玩家选择了在底层使用对于零知识证明 / 有效性证明友好的指令集，例如 Starknet 的 Cairo Assembly 和 zkSync 的 Zinc Instruction。但是大家同时也都不愿放弃 EVM 庞大的用户生态，于是选择在上层兼容 EVM，是 3、4 类 zkEVM。还有部分玩家仍然坚持 EVM 传统指令集 Opcode，将精力放在为 Opcode 生成更高效的证明上，是 1、2 类 zkEVM。EVM 的庞大生态，是为二。

Kakarot：虚拟机上的虚拟机？

为什么能够在虚拟机上再做一个虚拟机？这个事情对于计算机从业者而言是司空见惯的，但是对于不了解计算机的用户可能没那么显然。其实很好理解，这就好像搭积木，只要下层足够牢固（有图灵完备的执行环境），就可以无上限地往上层迭加积木。但是不论搭了多少层，最后的执行还是要交给最底层的物理硬件去处理，所以层数增高会导致效率的降低。同时，由于不同积木的设计不同（虚拟机设计不同），随着积木越搭越高，积木倒塌的可能性就越大（运行出错），也就需要更高的技术水平支撑。

Kakarot 是在 Starknet 上用 Cairo 语言实现的一个 EVM，以 Cairo 智能合约形式去模拟 EVM 中堆栈、内存、执行等内容。相对而言，实现 EVM 并不是什么难事，除了使用率最高的 Go-Ethereum 中用 Golang 编写的 EVM，现存的还有使用 Python, Java, JavaScript, Rust 编写的 EVM。

Kakarot zkEVM 的技术难点在于，协议是作为 Starknet 链上合约存在的，这就带来了两个关键的问题。

兼容性 Starknet 使用的是和以太坊完全不同的账户体系，以太坊中账户分为 EOA（外部拥有账户）和 CA（合约账户），然而 Starknet 中支持原生的账户抽象，所有账户都是合约账户。同时，由于使用的密码学算法不同，用户无法使用同一个熵在 Starknet 中生成与以太坊相同的地址。
成本 由于 kakarot zkEVM 是作为合约存在于链上的，所以对于代码实现有着较高的要求，需要尽可能地面向 Gas 进行优化，降低交互成本。
稳定性 与使用 Golang, Rust, Python 等传统高级语言不同，Cairo 语言仍然处于试验阶段，从 Cairo 0 到 Cairo 1 再到 Cairo 2（或者如果你喜欢的话，Cairo 1 version 2），官方团队仍然在不断修改语言特性。同时，Cairo VM 还未得到足够的测试，不排除后续大规模重写的可能。
kakarot 协议由五个主要的组件组成（GitHub 文档中写的是四个，未包含 EOA，本文为了便于读者理解做了调整）：

Kakarot (Core)：负责执行以太坊形式的交易，同时为以太坊用户提供对应的 Starknet 账户
Contract Accounts：以太坊意义上的 CA，负责存储合约的字节码、合约中变量状态
Externally Owned Accounts：以太坊意义上的 EOA，负责将以太坊交易转发给 Kakarot Core
Account Registry: 存储以太坊账户和 Starknet 账户的对应关系。
Blockhash Registry：Blockhash 作为一个特殊的 Opcode，需要过去的区块数据，而 Kakarot 无法在链上直接获取到数据。该组件存储 block_number -> block_hash 的映射关系，由管理员写入，提供给 Kakarot Core。

据 kakarot CEO Elias Tazartes 反馈，在团队的最新版本中，放弃了 Account Resister 的设计，改为直接使用一个 31bytes 的 Starknet 地址到 20 位 EVM 地址的 mapping 来保存对应关系。在未来，为了提高互操作性以及允许 Starknet 合约注册自己的 EVM 地址，可能会重新使用 Account Register 的设计。

Starknet 上兼容 EVM：Warp 与 kakarot 有什么差异

按照 Vitalik 定义的 zkEVM 类型而言，Warp 属于 Type-4，而 kakarot 当前属于 Type-2.5。

Warp 是一个将 Solidity 代码转化为 Cairo 代码的转译器，之所以不叫编译器，大概是因为输出的 Cairo 仍是高级语言。通过 Warp，Solidity 开发者可以维持原先的开发状态，而不需要学习新的 Cairo 语言。对于很多项目方而言，Warp 降低了进入 Starknet 生态的门槛，不需要使用 Cairo 重写大量的工程代码。

转译的思想虽然简单，但是兼容性也是最差的，有部分 Solidity 代码无法很好地翻译为 Cairo，涉及到账户体系、密码算法等代码逻辑需要修改源代码才能完成迁移，具体的不支持特征可见 Warp 文档。例如，许多项目会对 EOA 账户与合约账户的执行逻辑进行区分，但是 Starknet 中所有账户都是合约账户，这部分的代码就需要修改后才能进行转译。

Warp 是在高级语言层面的兼容，kakarot 是在 EVM 层面的兼容。

EVM 的全部重写，Opcode 与 Pre-compile 的逐条实现，让 kakarot 拥有了更高原生的兼容性。毕竟，在相同的虚拟机（EVM）中执行，总是要比在不同的虚拟机（Cairo VM）中执行来得更兼容一些。Account Registry、Blockhash Registry 更是巧妙地屏蔽了不同体系下的差异，把对用户的迁移摩擦降到最小。

Kakarot 团队

感谢 kakarot 团队对本文提出的宝贵意见，特别是 Elias Tazartes。Thank you, sir!