從技術角度出發,我們把焦點從 ZkEVM 轉移到 ZkVM 各派系的當下的發展重點。
(前情提要:A&T Capital《Web3 趨勢報告 2023》:你該注意ZK L2、平行計算、模組化、應用鏈.. )
(背景補充:ZK 元年》ZK-rollup 的項目在 2022 年表現如何? )
寫在前面:
過去的2022年關於rollup主要的討論焦點似乎都集中在ZkEVM,但是別忘記ZkVM也是另一種擴容手段。雖然ZkEVM並不是本文的重點,但是值得回味一下ZkVM與ZkEVM之間的的幾個維度的不同之處:
- 兼容性:雖然都是擴容,但是側重點並不同,ZkEVM的側重點在於直接實現與現有EVM的兼容,而ZkVM的定位在於實現完全的擴容,也就是將dapp的邏輯以及性能提升到最優,兼容性並不是首要的。底層搭好了,EVM兼容也可以實現。
- 性能:兩者都有比較可以預見的性能方面的瓶頸,ZkEVM主要瓶頸在於兼容EVM這樣一個並不適合封裝在ZK證明系統時產生的多余成本。ZkVM的瓶頸在於因為引入了指令集ISA,導致最終輸出的約束更覆雜。
- 開發者體驗:Type II ZkEVM (如Scroll, Taiko) 主打的是對於EVM Bytecode的兼容,換句話說就是Bytecode級別及其以上的EVM程式碼都可以透過ZkEVM產生對應的零知識證明。對於ZkVM來說,有兩個方向,一個方向是做自己的DSL(如Cairo), 另一個則是目標兼容現有的比較成熟的語言如C++/Rust(如Risc0)。未來我們預計原生的solidity以太坊開發者會可以無成本遷移至ZkEVM,而更新更強大的應用則會跑在ZkVM上。
在討論ZkVM之前,我們首先思考的是如何在區塊鏈中實現ZK證明系統。大致上,有兩種方法實現電路 – 基於電路的系統(circuit based)以及基於虛擬機的系統(vm-based)。
首先,基於電路的系統的功能是將程式(program)直接轉化為約束條件(constraints)並送入證明系統(proving system);基於虛擬機的系統透過指令集(ISA)執行程式,在此進程中產生執行軌跡(execution trace)。這個執行軌跡之後會被反射成約束條件,然後被送入證明系統。
對於一個基於電路的系統,程式的計算由執行程式的每台機器(machine)進行約束。而對於基於虛擬機的系統,ISA被嵌入到電路產生器(circuit generator)中,並產生程式的約束(constraints),同時電路產生器有指令集、運行周期、內存等等限制。虛擬機提供了通用性,即任何機器都可以運行一個程式,只要該程式的運行條件在上述限制範圍內。
在虛擬機中一個zkp程式大概經歷如下的流程:
優缺點:
- 從開發者(developer)的角度來看,在基於電路的系統中開發通常需要對每個約束條件的成本有深入的了解。然而,對於編寫虛擬機程式來說,電路是靜態的,開發者需要更關心的是指令(instructions)。
- 從驗證者(verifier)的角度來看,假設使用相同的純SNARK作為後端,基於電路的系統和虛擬機在電路的通用性方面有很大的不同。電路系統對每個程式產生不同的電路,而虛擬機對不同程式產生相同的電路。這意味著,在一個rollup中,電路系統需要在L1上部署多個驗證合約(verifier contract)。
- 從應用(application)的角度來看,虛擬機透過將內存模型(memory)嵌入到設計中,使應用程式的邏輯更加覆雜,而使用電路系統的目的是為了提高程式的性能。
- 從系統複雜性(complexity)的角度來看,虛擬機將更多的複雜性納入系統,如內存模型、主機(host)和客戶(guest)之間的通信等,相比之下電路系統更簡潔。
以下是目前L1/L2中基於電路和基於虛擬機的不同的項目預覽:
虛擬機的設計原則
在虛擬機中,有兩個關鍵的設計原則。首先,確保程式被正確執行。換句話說,輸出(output)(即約束條件constraint)與輸入(input)(即程式program)應當正確配對。一般這是透過ISA指令集完成的。其次,確保編譯器(compiler)在從高級語言轉換為適當的約束格式時能正確工作。
1. ISA指令集
規定了電路產生器的工作方式。它的主要責任是將指令(instructions)正確地反射到約束條件(constraint)中,這些約束條件隨後被送入證明系統(proving system)。zk系統使用的都是RISC(精簡指令集)。有兩種ISA的選擇:
第一種是自建一個自定義的ISA(custom ISA),這在Cairo的設計中可以看到。一般來說,有如下四種類型的約束邏輯。
自定義ISA的基本設計重點是確保約束條件盡可能少,從而使程式的執行和驗證都能快速運行。
第二種是利用現有的ISA(existing ISA),這在Risc0的設計中被採用。除了以簡潔的執行時間為目標外,現有的ISA(如Risc-V)還提供了額外的好處,如對前端語言(front-end language)和後端硬體(backend hardware)友好。一個(有待解決的可能)問題是,現有的ISA會不會在驗證時間上有所落後(因為驗證時間並不是Risc-V的主要設計追求。
2. 編譯器(Compiler)
籠統地來說,編譯器會逐步將編程語言翻譯成機器程式碼。在ZK的環境下,它指的是使用C、C++、Rust等高級語言編譯成約束系統(R1CS、QAP、AIR等.)的低級程式碼表示。有兩種方法,
- 設計一個基於現有zk電路表示(existing circuit representations)的編譯器–比如說在ZK中,電路表現形式從Bellman這樣的可以直接調用的庫(library)和Circom這樣的低級語言開始。為了聚合不同的表現形式,Zokrates這樣的編譯器(身也是一個DSL)旨在提供一個抽象層,可以編譯成任意的更低級表現形式。
- 基於(現有的)編譯器基礎設施(compiler infrastructure)來建構。基本邏輯是利用一個針對多個前端和後端的中間表現形式(intermediate representation)。
Risc0的編譯器是基於multi-level intermediate representation(MLIR),可以生成多個IR(類似於LLVM)。不同的IR給開發者帶來了靈活性,因為不同的IR有各自的設計重點,例如其中有一些的優化是專門針對硬體,所以開發者可以根據自己的意願進行選擇。類似的想法在使用GCC的vnTinyRAM和TinyRAM中也可以看到。
ZkSync也是另一個利用編譯器基礎設施的範例。
此外,你還可以看到一些針對zk的編譯器基礎設施,如CirC,它也借用了LLVM的一些設計理念。
除了上述兩個最關鍵的設計步驟外,還有一些其他的考慮因素:
- 系統的安全性(security)和驗證的成本(verifier cost)之間的權衡
系統使用的比特數越高(即安全性越高),意味著驗證的成本越高。安全性反映在密鑰生成器(比如在SNARK中代表橢圓曲線)。 - 與前端和後端的兼容性(compatibility)
兼容性取決於為電路的中間表示(intermediate representation)的有效性。IR需要在正確性(程式的輸出是否與輸入相匹配+輸出是否符合證明系統)和靈活性(支援多種前端和後端)之間取得了平衡。如果IR最初是為解決像R1CS這樣的低度(low-degree)約束系統而設計的,那麼與其他更高級別(high-degree)的約束系統如AIR的兼容就很難。 - 為提高效率需要手工制作(hand-crafted)電路
使用通用模型(general purpose)的缺點是,對於一些不需要覆雜指令的簡單操作,其效率較低。
簡述一下先前的一些理論,
- Pinocchio協議之前: 實現了可驗證的計算,但驗證時間非常慢;
- Pinocchio協議: 在可驗證性和驗證成功率方面提供了理論上的可行性(即驗證的時間比執行程式的時間短),是基於電路的系統;
- TinyRAM協議: 相對於Pinocchio協議,TinyRAM更像一個虛擬機,引入了ISA,因此擺脫了一些限制,如內存訪問(RAM)、控制流(conttrol flow)等;
- vnTinyRAM協議: 使得密鑰生成(key generation)並不取決每個程式,提供了額外的通用性。擴展電路產生器,即能夠處理更大的程式。
上述模型都以SNARK作為其後端證明系統,但是特別是在處理虛擬機時,STARK和Plonk似乎是一個更合適的後端,從根本上說是由於其約束系統更適合於實現cpu一樣的邏輯。
接下來,本文會介紹三個基於STARK的虛擬機 – Risc0, MidenVM, CairoVM。簡而言之,除了都以STARK作為證明系統外,它們各自有一些不同:
- Risc0利用Risc-V來實現指令集的簡潔性。R0在MLIR進行編譯,這是LLVM-IR的一個變種,旨在支援多種現有的通用編程語言,如Rust、C++。Risc-V 還有一些額外的好處,比如對於硬體較為友好。
- Miden的目標是與以太坊虛擬機(EVM)兼容,本質上是EVM的rollup。Miden現在有自己的編程語言,但也致力於在未來支援Move。
- Cairo VM是由Starkware開發的。這三個系統所使用的STARK證明系統是由Eli Ben-Sasson發明的,目前Starkware的總裁。
讓我們更深入地了解它們的區別:
*如何讀懂上面的表格?一些注解…
●Word size(字長) – 由於這些虛擬機所基於的約束系統是AIR,其功能與CPU架構類似。所以選擇CPU字長(32/64位)比較合適。
●Memory access(內存讀取)- Risc0使用寄存器(register)的原因主要是Risc-V指令集是基於寄存器的。Miden主要使用堆棧(stack) 來儲存數據,因為AIR的功能與堆棧類似。CairoVM沒有使用通用寄存器(general-purpose register),因為Cairo模型中的內存訪問(main memory)成本較低。
●Program feed(程式執行)- 不同方法是有取舍的。例如,對於mast root方法來說,它需要在處理指令時進行解碼,因此在執行步驟較多的程式中下證明者的成本較高。Bootloading方法試圖在保持隱私的同時在證明者成本和驗證者的成本之間取得平衡。
●Non-determinism(非確定性)- 非確定性是NP-complete問題的一個重要屬性。利用非確定性有助於快速驗證過去的執行。反過來說,它增加了更多的約束條件,因此在驗證方面會有一些妥協。
●Acceleration on complex operations(覆雜運算的加速)- 有些計算在CPU上運行很慢。例如,位操作,如XOR和AND,哈希程式(hash program),如ECDSA,還有範圍檢查(range-check)……大多是區塊鏈/加密技術的原生但不是CPU原生的運算(除了位操作)。直接透過DSL來實現這些運算會很容易導致證明的周期(cycle)耗盡。
●Permutation/multiset (排列/多列組合) – 在大多數zkVM中大量使用,有兩個目的–1.透過減少儲存完整的執行軌跡(execution trace) 來降低驗證者的成本 2.證明驗證者知道完整的執行軌跡
文章最後,筆者想談談Risc0目前的發展以及其讓我興奮的原因。
R0目前的發展:
- 自研的”Zirgen “的編譯器基礎設施正在開發中。將Zirgen與一些現有的zk專用編譯器的性能進行比較會很有趣。
- 一些很有意思的的創新,如field extension,可以實現更堅實的安全參數以及在更大的整數上進行操作。
- 見證了在ZK硬體和ZK軟體公司之間的整合中看到的挑戰,Risc0使用了一個硬體抽象層,以便在硬體方面進行更好的開發。
- Still a work-in-progress! 還在開發中!
- 支援手工制作的電路(hand-crafted circuits),支援多種哈希算法。目前,專用的SHA256電路已實現,然而還不能滿足所有的需求。筆者相信具體選擇優化哪類電路取決於Risc0所提供的用例(use case)。SHA256是一個非常好的起點。另一方面,ZKVM的定位給人以靈活性,例如,只要他們不想,就不必去管Keccak 🙂
- 遞歸(recursion):這是一個很大的話題,筆者傾向於不在該報告進行深入研究。需要知道的是,隨著Risc0傾向於支援更覆雜的用例/程式,更迫切地需要遞歸。為了進一步支援遞歸,他們目前正在研究一個硬體端的GPU加速方案。
- 處理非確定性(non-determinism):這是ZKVM必須處理的一個屬性,而傳統的虛擬機是沒有這個問題的。非確定性可以幫助虛擬機執行得更快。MLIR相對更擅長處理傳統虛擬機方面的問題,而Risc0如何將非確定性嵌入到ZKVM系統設計中值得期待。
WHAT EXCITES ME:
- 簡單且可驗證!
在分布式系統中,PoW需要高水平的冗余,因為人們不信任他人,因此需要重覆執行相同的計算來達成共識。而透過利用零知識證明,狀態的實現應該和同意1+1=2一樣容易。 - 更多更實際的用例:
除了最直接的擴容外,更多有意思的用例將變得可行,比如零知識機器學習、數據分析等。相比於Cairo這樣的特定的ZK語言,Rust/C++的功能更普適且更強大,更多web2的用例跑在Risc0 VM上。 - 更具包容性/成熟的開發者社群:
對STARK和區塊鏈感興趣的開發者不必再重新學習DSL,使用Rust/C++即可。