原文:《 =nil; zkLLVM Circuit Compiler. 》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生成的電路的複雜性可能使它們難以被驗證和優化,這可能會進一步增加開銷。零知識證明的生成和驗證需要大量的計算資源,這將導致低性能和高延遲。
  • 最後需要對生成的電路進行驗證。我們需要安裝一個特定的軟件,然後將電路和數據樣本作為輸入並產生證明。

詳解zkLLVM電路編譯器:除了能構建zkRollup,還能做些什麼?

總的來說,雖然zkVM的概念很有前途,但是與使用虛擬機相關的開銷可能會是其一大瓶頸,必須加以解決,才能使其成為實踐中的有效解決方案。

使用來自主流語言(例如C++ 或Rust)的zkLLVM 構建電路

一旦我們將虛擬機從管道中移除,我們就可以獲得更好的性能和效率。以下是對新管道的簡短描述:

  • 用我們已經了解並喜愛的語言編寫代碼。它可以是c++、Rust、JavaScript、TypeScript或LLVM支持的任何其他語言。這是一個簡單的過程,因為我們可以使用已有的代碼。不需要從頭重寫代碼,只需要添加一些註釋,僅此而已。
  • 編寫完代碼後,需要將其編譯成電路。這只需要幾秒鐘——編譯器將代碼轉換成另一種形式,而不需要額外的指令或開銷。這是一個非常高效的過程,因為它不需要運行虛擬機或其他附加服務。一切都是由一個簡單的命令行工具完成的,它是clang和rustc的替代品。我們可以將其用作開發環境或CI/CD管道的一部分。
  • 生成的電路可以作為prover的輸入。但是,下面一節將介紹另一種選擇,這個選擇中我們不需要在自己的機器上安裝prover。

詳解zkLLVM電路編譯器:除了能構建zkRollup,還能做些什麼?

不需要建立一個龐大的環境來生成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電路編譯器:除了能構建zkRollup,還能做些什麼?

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*(ab)*(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