作者:Cynic
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!