本文章主要講述瞭如何在Solidity 中使用zk-SNARKs,以及如何使用ZoKrates 編譯器來生成證明和驗證合約。
這文章不會過於深入zk-SNARKs 的技術原理,這文章目的是為讓讀者能夠理解sk-SNARKs 的技術能在EVM 中達到什麼效果,如何使用,並且能在代碼中運用。
zk-SNARKs 簡介
關於zk-SNARKs 的簡短描述為,我們需要在zk 電路中編寫一段代碼,這段代碼的輸入是一些公開的數據,輸出是一些私有的數據。 zk-SNARKs 的驗證算法可以驗證這段代碼的輸出是否正確,但是驗證算法不會洩露任何私有數據。而Solidity 合約的主要目的是驗證zk-SNARKs 的驗證算法的結果,如果驗證算法的結果正確,那麼合約會執行一些操作。
也就是說,在EVM 上,只是進行了結果的驗證,並沒有進行一些複雜的計算,這些計算都是在zk 電路中進行的。而這部分zk 電路,則是在鏈下進行的,然後將結果提交到鏈上。
在Solidity 中使用zk-SNARKs
首先,我們需要知道zk-SNARKs 可以完成什麼功能,其實很簡單,我們可以簡單的認為,zk-SNARKs 可以完成對一個函數運算結果的校驗,比如說,我們有一個函數,輸入是三個數字,輸出是一個數字,我們可以使用zk-SNARKs 來校驗這個函數的輸出是否正確。但是我們並不需要知道輸入的三個數字是什麼,只需要知道這個函數的輸出即可,也就是說,在一個函數完成計算時,我們可以知道確實是有這麼三個數他能符合這個函數的輸入,並且能輸出正確結果,但是我們並不知道這三個數是什麼。
在Solidity 中,我們可以使用zk-SNARKs 來完成對一個函數的校驗,但是我們需要知道這個函數的輸入和輸出,然後我們可以使用ZoKrates 編譯器來生成zk 電路,然後將zk 電路的代碼放到Solidity 合約中,然後在合約中完成對zk 電路的驗證。
安裝ZoKrates 編譯器
安裝ZoKrates
curl -LSfs get.zokrat.es | sh
也可以選擇其他安裝方式,具體選擇查看他們的Github 頁面。
編寫zk 電路
從上一章節我們淺顯的知道,一個zk-SNARKs 電路需要的最基本的東西為:
-
一個函數- 我們需要有一個函數對數據進行運算,也就是程序 C
-
lambda – 所謂的“有毒廢料”,其實就是一個root key,我們需要通過它來生成pk 和vk
有了這兩個基礎條件,用戶就可以通過pk,目標值,輸入值來生成證明w。
隨後,我們的驗證程序通過vk,目標值,證明w 來驗證證明的正確性。
我們先假設有這麼一個第三方,他可以安全的生成lambda,然後安全的將程序和lambda 進行運算生成vk 和pk。
那麼現在有兩個新的角色,user 和project。 user 是用戶,他確確實實擁有著一些數據,project 是項目合約,他需要驗證用戶的數據是否正確。
一個函數
我們首先需要一個函數,但是我並不打算舉一些簡單例子,因為我覺得這樣做非常沒有意義,因為zk-SNARKs 的主要目的是為了驗證一些複雜的函數,而不是一些簡單的函數。
比如,我們現在需要生成一個存款憑證,有這個憑證,我們可以在任何地方取出這筆錢,但是我們並不知道這筆錢是誰的,我們只知道這筆錢是誰存的,存了多少,以及存款的時間。
首先我們需要一個存款函數,這個函數的輸入為存款的金額,和一個隨機數,然後輸出為一個存款憑證。任何擁有這個憑證的人都可以取出這筆資金。所以,實際上,我們只需要編寫驗證知道這個憑證的驗證函數即可。
關於Zok 的語法和用法這裡不過多描述,具體可以參考官網,這裡簡單解釋一下,這個函數的輸入為兩個數字,一個是存款金額,一個是隨機數,然後輸出為一個u32[8],實際上就是uint256.同時我們注意一下,參數中deposit_amount 沒有private 關鍵詞,說明這個參數是公開數據。
編譯文件
這部分內容在zokrates 中有講述方式為
運行完成後會生成一堆文件,我們需要的是proof.json, proving.key, verification.key, verifier.sol, out。
大部分其實都是模版文件生成文件可能不一樣的地方在於Verifier 合約中verifyingKey,當然,我們閱讀這個文件其實意義也不大,因為這裡面全是一大堆數字和運算。實際上我們需要看的內容就是這些∑
可以看到,我們需要兩個參數,proof 和input。至於這兩個參數是乾嘛的,我們暫時不過多深究。不過我們需要注意的是,在inputs 中,所有的共有參數都會被加入到這個數組中,在數字最開頭部分被推入。
比如,自動生成的proof.json 文件就是一個有效的數據。
至此,我們可以寫一個簡單合約。
要注意的是Verifier 合約中會出現兩個pragma solidity,記得刪掉中間那一個,保留最上面的那個,否則編譯無法通過。
測試
首先我們需要明白一下標準流程,我們需要先進行compile,setup,然後再進行compute-witness,然後再進行generate-proof,最後再進行export-verifier。
但是這套流程並不是每次都必須的,因為這個是一個完整流程。我們需要進行一下區分。
必要條件
-
compile – 編譯zk 電路- 只需要執行一次這個功能會生成out 文件和abi.json 文件,這兩個是編譯後的程序。
-
setup – 生成zk 電路的pk 和vk – 只需要執行一次這個功能會生成proving.key 和verification.key 文件,這兩個文件是zk 電路的公鑰和私鑰。實際上在進行setup 的時候會產生lambda,但是這些過程我們不需要太過於關心。
提交證明條件
-
compute-witness – 生成證明- 這個功能會生成witness 文件,這個文件是一個中間文件。
-
generate-proof – 生成證明的Proof – 這個功能會生成proof.json 文件,這個文件是證明需要提交的內容,一般來說裡面的內容就是我們需要提交到鏈上的參數。
接受證明條件
-
export-verifier – 生成verifier.sol – 這個功能會生成verifier.sol 文件,這個文件是一個合約,我們需要將這個合約部署到鏈上,然後在我們的合約中調用這個合約來驗證證明的正確性。
-
verify – 本地驗證- 這個功能會驗證證明的正確性,但是這個功能並不會生成任何文件。
編寫文件
根據上面內容,我們可以寫出一些用於測試的單元測試邏輯。
項目的基礎文件都放在: https://github.com/nishuzumi/blog/tree/main/sources/zk 中,歡迎Star。