详解zkLLVM电路编译器:除了能构建zkRollup,还能做些什么?
原文:《》by Nikita Kaskov, Mikhail Komarov
编译:ChinaDeFi
介绍
编写电路很难。编写高效电路更难。如果从零开始,那就是难上加难。最近,我们一直在编写电路,并且是同时在Mina和以太坊之间以及Solana和以太坊之间构建zkbridge。在尝试了不同的方法之后,我们认为zkLLVM是简化过程,并使之高效的关键。
编写电路时的主要问题之一是,要么需要深入了解特定证明系统的工作原理,要么需要了解自定义领域特定语言,这就限制了当代码一旦被实现后的可重用性——自定义DSL(又名Cairo、Noir、Circom等)需要生成整个库和应用程序的生态系统。此外,特定领域的语言通常构建在zkVM之上,这也是导致性能问题的主要原因之一。
无论何时,只要涉及到任何类型的zkVM,就都不可能达到合适的效率。
但如果我们不需要产生一个全新的生态系统来实现一些电路会怎样呢?如果我们能够在不牺牲效率的情况下证明主流语言呢?
zkLLVM
zkLLVM是一个基于LLVM的电路编译器,能够证明LLVM支持的任何主流开发语言:C++、Rust、JavaScript/TypeScript 和其他语言。
关键在于它是一个电路编译器,而不是虚拟机。它将高级代码作为输入,将其转换为电路,然后用于证明计算。
电路只是程序的另一种形式
在我们继续往下解释之前,需要先记住电路是由什么组成的。每一个PLONK电路基本上有两个部分:
- 约束系统,是表示算法及其对数据的约束的一组门。
- 执行跟踪,是一组内部变量赋值,表示算法正在使用的数据集。
zkLLVM直接将代码转换为不同的表示形式
由于所有数据样本的约束系统都是相同的,而执行跟踪在不同样本之间是不同的,我们就可以预先计算约束系统,然后用它来证明不同数据样本上的计算。
这正是zkLLVM所做的。这个编译器不是像传统的基于 zk 虚拟机的项目那样执行代码,而是将其编译成电路的形式,然后用于证明不同数据样本上的计算。
它快速,便宜,高效。只需将代码转换为电路,并将其交给证明生产者。不再需要自定义虚拟机。
让我们仔细看看它是如何工作的。
编译器管道生成的电路比zkVM小10倍
以前,它要求开发人员学习一种新的开发语言,深入了解目标协议计算(状态证明、数据聚合、分析等)的代码和/或特定语言的特殊性,并承担目标虚拟机每次更新所带来的无休止的维护成本。
下面是以前的例子:
- 首先,我们需要用目标zkVM的预处理器或DSL编译器支持的自定义语言编写代码。它需要我们学习一种新的语言及其特性,然后将RUST或C++上的算法转换为目标语言中非常详细的形式。这是一个非常耗时的过程,通常需要从头重写代码。此外,用目标语言编写代码并不总是可能的,因为它并不总是具有足够的表现力来表示我们想要证明的算法。
- 在用DSL重写算法之后,我们需要使用自定义DSL编译器将代码编译成目标zkVM字节码。这将花费一些时间,因为DSL编译器没有那么快——它需要获取我们的代码并生成虚拟机字节代码。生成的字节码中高达 90% 包含用于进一步 VM 处理的特定服务指令。即使是具有多个函数和循环的简单代码也会生成大量的字节代码。
- 生成的字节码只是一个开始。VM部分——这是大部分资源被浪费的地方。在zkVM方法中使用虚拟机生成电路的开销可能非常大。开销的一个主要来源是运行虚拟机所需的额外计算资源。虚拟机必须能够执行字节代码,执行任何必要的零知识证明的生成和验证,并管理整个执行环境。所有这些任务都需要大量的计算资源,这可能会增加系统的总体开销。另一个开销来源是执行字节代码所需的时间。由于虚拟机除了简单地执行代码之外,还必须执行其他任务,因此总体执行时间可能会比预期的要长。对于需要在短时间内处理大量交易的系统来说,这可能是一个大问题。
- 由zkVM生成的电路的复杂性可能使它们难以被验证和优化,这可能会进一步增加开销。零知识证明的生成和验证需要大量的计算资源,这将导致低性能和高延迟。
- 最后需要对生成的电路进行验证。我们需要安装一个特定的软件,然后将电路和数据样本作为输入并产生证明。
总的来说,虽然zkVM的概念很有前途,但是与使用虚拟机相关的开销可能会是其一大瓶颈,必须加以解决,才能使其成为实践中的有效解决方案。
使用来自主流语言(例如 C++ 或 Rust)的 zkLLVM 构建电路
一旦我们将虚拟机从管道中移除,我们就可以获得更好的性能和效率。以下是对新管道的简短描述:
- 用我们已经了解并喜爱的语言编写代码。它可以是c++、Rust、JavaScript、TypeScript或LLVM支持的任何其他语言。这是一个简单的过程,因为我们可以使用已有的代码。不需要从头重写代码,只需要添加一些注释,仅此而已。
- 编写完代码后,需要将其编译成电路。这只需要几秒钟——编译器将代码转换成另一种形式,而不需要额外的指令或开销。这是一个非常高效的过程,因为它不需要运行虚拟机或其他附加服务。一切都是由一个简单的命令行工具完成的,它是clang和rustc的替代品。我们可以将其用作开发环境或CI/CD管道的一部分。
- 生成的电路可以作为prover的输入。但是,下面一节将介绍另一种选择,这个选择中我们不需要在自己的机器上安装prover。
不需要建立一个庞大的环境来生成zk证明
zkLLVM是一个独立的工具,它也可作为clang和rustc的替代品,因此我们可以将其用作管道的一部分。它扩展了我们最喜欢的编译器和语言,能够将我们的代码编译成电路。
但这还不是全部。使用zkLLVM甚至不需要设置一个prover。如果想要证明的算法太大,不能在自己的机器上运行,那我们可以在https://proof.market上发布订单,并使用整个社区的算力。
一旦有了证明,你就可以在任何地方使用它
证明是一个简单的JSON文件,可以在任何地方使用。我们可以在自己的应用程序中使用它,也可以在https://proof.market上发布它并将其出售给社区。
如果想在去中心化平台上使用zk-proof,显然我们需要以某种方式验证它。我们已经提前考虑了这个问题,并准备了一组可以使用的验证者。它包括:
- in-EVM验证者,可用于验证以太坊上的证明。
- Starknet验证者,用Cairo语言编写。
- Solana验证者。
- 与WASM兼容的验证者。
最后,我们可以从源代码构建一个验证者,并将其集成到我们自己的应用程序或在本地运行它。
zkLLVM生态系统由强大的SDK支持
C++ SDK和RUST SDK (WIP)包含了很多有用的东西,可以用来构建我们自己的应用程序:
- 证明系统。
- 承诺方案。
- 哈希和密码。
- 签名。
- 编组和序列化。
从二进制文件安装编译器或从源代码构建它
安装工具链:
- 从官方存储库获取最新的二进制版本;
- 或者根据手册从源代码构建它。
配置项目(在使用C++管道的情况下使用CMake)
$ cmake -DCMAKE_BUILD_TYPE=Release -DCIRCUIT_ASSEMBLY_OUTPUT=TRUE ..
如常编译电路代码:
$ make circuit_examples -j$(nproc) $ make circuit_examples -j$(nproc)
因此,带有 SDK 的完整管道看起来像下图:
zkLLVM执行透明的代码转换
让我们来看看代码是如何逐步转换成电路的。同时,我们将学习一些关于编写电路的基本知识。
每个可证明的计算电路都以入口点函数开始,用[[circuit]]属性标记。该函数接受一些参数并返回结果。函数体代表一个算法,该算法将被编译成一个电路,进一步用于生成证明。
让我们以一些简单的算术逻辑代码为例,详细介绍它的编译过程:
zkLLVM编译C++代码:
#include
using namespace nil::crypto3::algebra::curves;
[[circuit]] typename pallas::base_field_type::value_type hello_world_example(typename pallas::base_field_type::value_type a,typename pallas::base_field_type::value_type b) {
typename pallas::base_field_type::value_type c = (a + b)*a + b*(a-b)*(a+b);return c;}
此 C++ 将转换为以下中间电路表示:
define dso_local __zkllvm_field_pallas_base @_Z19hello_world_exampleu26__zkllvm_field_pallas_baseu26__zkllvm_field_pallas_base(__zkllvm_field_pallas_base %0, __zkllvm_field_pallas_base %1) local_unnamed_addr #5 { %3 = add __zkllvm_field_pallas_base %0, %1 %4 = mul __zkllvm_field_pallas_base %3, %0 %5 = sub __zkllvm_field_pallas_base %0, %1 %6 = mul __zkllvm_field_pallas_base %1, %5 %7 = add __zkllvm_field_pallas_base %0, %1 %8 = mul __zkllvm_field_pallas_base %6, %7 %9 = add __zkllvm_field_pallas_base %4, %8ret __zkllvm_field_pallas_base %9}
稍后将转换为以下 PLONK约束:
constraint0: (W0 + W1 – W3 = 0)constraint1: (W3 * W0 – W4 = 0)constraint3: (W0 – W1 – W5 = 0)constraint4: (W1 * W5 – W6 = 0)constraint5: (W0 + W1 – W7 = 0)constraint6: (W6 * W7 – W8 = 0)constraint7: (W4 + W8 – W9 = 0)
gate0: ({0}, constraint0, constraint1, constraint2, constraint3, constraint4, constraint5, constraint6, constraint7)
把它放到https://proof.market上并获得证明。
{circuit_name: hello_world_example,constraints_amount: 8,gates_amount: 1,rows_amount: 1,publc_inputs_amount: 2,proof:
我们可以在 Ethereum/StarkNet/Solanet/任何地方进行验证。
$verified: true
zkLLVM能够用来做什么?
- 用zkLLVM来证明复杂的计算(又名zkGames或zkML)
- 用zkLLVM构建zkRollup或L2
- 用zkLLVM构建zkBridge
- 用zkLLVM构建zkOracle