前言:本文我們使用兩個公開庫circom、snarkjs嘗試建構一個簡單的零知識證明。其中,circom庫用于建構和編譯代數電路,snarkjs庫是zk-snarks協定的獨立實作。本文使用的方法主要參考的是snarkjs庫0.4.6官方教程(英文)和博文circom與snarkjs經典教程(中文)。感謝這兩篇文獻的作者。
另,github上面也給出了一個較為完整的英文版的tutorial,感興趣的盆友可以點選連結學習。
目錄
準備:安裝Node.js
1. 安裝circom&snarkjs
2. 可信設定
3. 建構電路
4. 生成groth16算法密鑰
5. 計算見證(witness)
6. 證明與驗證
準備:安裝Node.js
具體方法參考我的另一篇博文。
1. 安裝circom&snarkjs
在終端輸入如下安裝指令:
npm install -g circom
npm install -g snarkjs
在執行第二條指令時出現git報錯:
上網搜尋發現可能原因是(1) ipv6,(2) 之前開了網絡代理,git預設走代理通道,導緻現在關掉代理之後用不了了。
方法1:在終端輸入如下指令禁用ipv6:
networksetup -setv6off Wi-Fi
方法2: 首先檢視git配置,在終端輸入“git config --global --list ”,若發現傳回内容中包含http.https或者https.https字樣,說明設定了網絡代理。輸入如下指令撤掉代理即可:
git config --global --unset http.proxy
git config --global --unset https.proxy
我遇到的是問題(1),使用方法1解決了。
2. 可信設定
2.1 建立一個新檔案夾,用于包含後續所有檔案:
mkdir factor
cd factor
2.2 執行new指令:
snarkjs powersoftau new bn128 12 pot12_0000.ptau -v
* 解釋:
[new]:用于開啟一個新的 powers of tau ceremony (該叫法摘自snarkjs庫0.4.6官方教程)
[bn128]:選擇bn128曲線(可選的還有bls12-381曲線,bn和bls的簡介見後面“補充”部分)
[12]:規定了我們接下來要建構的電路的兩個限制。限制1:電路中constraints(稍後會解釋)的數量不能多于
個;限制2:電路中parameter的數量不能多于
個。
* 效果:
此條指令會建立一個pot12_0000.ptau檔案,執行指令後會列印以下字段:
* 補充
參考博文zk-SNARK零知識證明曲線選擇及Electric Coin Co. 官網的相關内容,Barreto-Naehrig (BN) 和Barreto-Lynn-Scott (BLS)都是pairing-friendly橢圓曲線。不同之處在于,BN曲線若想要達到128-bit的安全性,需要q≈2384,相應的BN曲線的階數r也會提高到2384量級,r值的增大會影響multi-exponentiation、FFT等運算性能,進而影響zk-SNARK以及安全多方計算的執行效率,同時也會影響key檔案不必要的增大。 而對于BLS曲線,當q≈2384 且 embedding degree k=12時,具有128-bit的安全性,而相應的階數r≈2256,遠小于BN曲線的2384量級。
2.3 執行contribute指令
snarkjs powersoftau contribute pot12_0000.ptau pot12_0001.ptau --name="First contribution" -v
* 解釋:
[contribute] 該指令将上一步建立的pot12_0000.ptau檔案作為輸入,并輸出一個新的ptau檔案pot12_0001.ptau。ptau檔案包含所有已經進行過的challenge和response計算。
[name] 這一字段内你可以任意填入一些内容。在後面執行驗證(第6步)的時候這個内容會被列印出來。
* 效果:
執行此條指令,會被要求輸入一些随機内容,輸入後列印如下内容:
2.4 第二次contribute
snarkjs powersoftau contribute pot12_0001.ptau pot12_0002.ptau --name="Second contribution" -v -e="some random text"
* 解釋:
此條指令與上一條功能相同,不同點在于,加入了[-e]指令,直接輸入了一些随機值(上一步是互動式地輸入随機值,這一步直接将随機值寫入了指令中)。
* 效果:
2.5 第三次contribute(第三方contribute)
snarkjs powersoftau export challenge pot12_0002.ptau challenge_0003
snarkjs powersoftau challenge contribute bn128 challenge_0003 response_0003 -e="some random text"
snarkjs powersoftau import response pot12_0002.ptau response_0003 pot12_0003.ptau -n="Third contribution name"
不太确定這步跟上兩步的異同,但是執行完成後會生成pot12_0003.ptau檔案:
2.6 驗證
snarkjs powersoftau verify pot12_0003.ptau
如果驗證通過,在列印資訊的第一行會顯示“[INFO] snarkJS: Powers Of tau file OK!” :
2.7 引入random beacon
snarkjs powersoftau beacon pot12_0003.ptau pot12_beacon.ptau 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon"
此條指令會生成一個pot12_beacon.ptau檔案。
2.8 生成公開資訊
snarkjs powersoftau prepare phase2 pot12_beacon.ptau pot12_final.ptau -v
* 解釋:
[prepare phase2]:生成多項式在點
、點
、點
的值的密文。
此指令将pot12_beacon.ptau檔案作為輸入,生成一個新檔案pot12_final.ptau。
2.9 最終驗證
snarkjs powersoftau verify pot12_final.ptau
* 解釋:
在開始下一步(建構電路)之前,執行一個最終驗證。
* 效果:
執行此條指令,部分列印資訊如下:
3. 建構電路
我們準備證明的問題是:我們知道數字a和b(秘密),它們相乘得到c(公開值)。
3.1 建立一個circom檔案:
vim circuit.circom
檔案内容如下:
1. template Multiplier() {
2. signal private input a;
3. signal private input b;
4. signal output c;
5. c <== a*b;
6. }
7.
8. component main = Multiplier();
* 解釋:
2-4行:這個電路有兩個private輸入信号,即a和b;有一個輸出信号,即c。
第5行:輸入和輸出使用
<==
運算符進行關聯。 在circom中,<==運算符做兩件事。 首先是連接配接信号。 第二個是施加限制。在本例中,我們使用
<==
将
c
連接配接到
a
和
b
,同時将
c
限制為
a * b
的值,即電路做的事情是讓強制信号
c
為
a*b
的值。
第8行:在聲明
Multiplier
模闆之後, 我們使用名為
main
的元件執行個體化它。編譯電路時,必須始終有一個名為
main
的元件。
(對第5行和第8行的解釋摘自博文circom與snarkjs經典教程)
3.2 編譯電路
circom circuit.circom --r1cs --wasm --sym -v
3.3 用snarkjs檢視編譯後的電路
snarkjs r1cs info circuit.r1cs
獲得如下輸出:
3.4 列印constraints
snarkjs r1cs print circuit.r1cs circuit.sym
列印資訊如下:
3.5 将電路輸出成json格式
snarkjs r1cs export json circuit.r1cs circuit.r1cs.json
cat circuit.r1cs.json
執行這兩條語句後會列印json字元串。
4. 生成groth16算法密鑰
** 步驟4.2--4.7類似于上文步驟2.3--2.7, 2.9
4.1 設定
snarkjs groth16 setup circuit.r1cs pot12_final.ptau circuit_0000.zkey
* 解釋:
此指令生成zkey,即零知識證明密鑰,包含證明密鑰(proving key)和驗證密鑰(verification key)。每個電路都單獨生成一個zkey,我們可以驗證一個zkey是否屬于某個電路。
Note that(the output of the
circuit_0000.zkey
zkey
command above) does not include any contributions yet, so it cannot be used in a final circuit.
——摘自snarkjs庫0.4.6官方教程
4.2 zkey Contribute
snarkjs zkey contribute circuit_0000.zkey circuit_0001.zkey --name="1st Contributor Name" -v
此指令會生成一個新的circuit_0001.zkey檔案,裡面包含了一個contribution。這一步會要求輸入一些随機内容,如下:
4.3 第二次contribute
snarkjs zkey contribute circuit_0001.zkey circuit_0002.zkey --name="Second contribution Name" -v -e="Another random entropy"
4.4 第三次contribution(第三方contribute)
snarkjs zkey export bellman circuit_0002.zkey challenge_phase2_0003
snarkjs zkey bellman contribute bn128 challenge_phase2_0003 response_phase2_0003 -e="some random text"
snarkjs zkey import bellman circuit_0002.zkey response_phase2_0003 circuit_0003.zkey -n="Third contribution name"
4.5 驗證
snarkjs zkey verify circuit.r1cs pot12_final.ptau circuit_0003.zkey
4.6 引入random beacon
snarkjs zkey beacon circuit_0003.zkey circuit_final.zkey 0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f 10 -n="Final Beacon phase2"
4.7 驗證最終zkey
snarkjs zkey verify circuit.r1cs pot12_final.ptau circuit_final.zkey
4.8 輸出驗證密鑰
snarkjs zkey export verificationkey circuit_final.zkey verification_key.json
5. 計算見證(witness)
witness中包含與已知電路所有constraints相比對的信号,可以了解為問題的答案。在我們的例子中,一個見證包含a、b、c。我們先生成witness,然後再用witness生成零知識證明。
5.1 建立input檔案
建立一個json檔案:
vim input.json
在json檔案中寫入如下内容并儲存:
{"a": 3, "b": 11}
5.2 運作計算指令
snarkjs wtns calculate circuit.wasm input.json witness.wtns
5.3 檢查witness計算有無錯誤
snarkjs wtns debug circuit.wasm input.json witness.wtns circuit.sym --trigger --get --set
* 效果:
此指令會列印我們輸入的a、b以及相應的輸出c:
6. 證明與驗證
6.1 建構零知識證明
snarkjs groth16 prove circuit_final.zkey witness.wtns proof.json public.json
* 解釋:
這一指令生成檔案proof.json和public.json,前者包含證明,後者包含公開輸入和公開輸出。
* 注:
第5步(計算witness)和第6.1步(生成證明)可以合并為一條語句:
snarkjs groth16 fullprove input.json circuit.wasm circuit_final.zkey proof.json public.json
6.2 驗證
snarkjs groth16 verify verification_key.json public.json proof.json
* 效果:
若驗證成功将列印“OK!”: