零知識證明 Learn by Coding:libsnark 入門篇

買賣虛擬貨幣
libsnark 是目前實現 zk-SNARKs 電路最重要的框架,在眾多私密交易或隱私計算相關專案間廣泛應用,其中最著名當然要數 Zcash。Zcash 在 Sapling 版本升級前一直使用 libsnark 來實現電路(之後才替換為 bellman)。毫不誇張地說,libsnark 支撐並促進了 zk-SNARKs 技術的首次大規模應用,填補了零知識證明技術從最新理論到工程實現間的空缺。希望透過本系列文章,所有開發者都能親自上手實踐,在短時間內迅速入門 libsnark,一步步瞭解 libsnark 的基本概念,學會如何開發 zk-SNARKs 電路,完成證明的生成和驗證,最終將零知識證明應用到真實業務中去。1. zk-SNARKs 和 libsnark 背景簡介零知識證明,可能是目前最具應用前景和想象力的密碼學黑科技。而 zk-SNARKs 正是一類零知識證明方案的簡稱,全稱為 Zero-Knowledge Succinct Non-interactive Arguments of Knowledge。這一名字幾乎包含了其所有技術特徵,即可以在不洩露任何其他資訊的前提下證明一個命題的正確性,並且最終生成的證明具有簡潔性(Succinct),也就是說最終生成的證明足夠小,並且與計算量大小無關,是一個常數。用白話說就是,你理論上可以在不暴露任何隱私的情況下向其他所有人證明某件事,並且生成的證明體積很小,校驗成本很低,與需要證明的內容計算量無關。聽起來簡直太美好了!zk-SNARKs 能應用到很多場景,比如隱私保護、區塊鏈擴容、可驗證計算等。本文不介紹 zk-SNARKS 和零知識證明的理論細節,不熟悉或想深入瞭解的同學可閱讀其他文章或論文。如 Vitalik 寫的關於 zk-SNARKs 著名的三篇博文。
https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649https://medium.com/@VitalikButerin/exploring-elliptic-curve-pairings-c73c1864e627https://medium.com/@VitalikButerin/zk-snarks-under-the-hood-b33151a013f6或者閱讀向程@HUST寫的「深入淺出零知識證明之zk-SNARKs」,還有東澤寫的「淺談零知識證明之二:簡短無互動證明(SNARK)」。當然也歡迎關注安比實驗室「探索零知識證明」系列和「從零開始學習 zk-SNARK」系列,以及從安比實驗室維護的「零知識證明學習資源彙總」中查詢更多資料。「深入淺出零知識證明之zk-SNARKs」:
https://www.yuque.com/u428635/scg32w/edmn74「淺談零知識證明之二:簡短無互動證明(SNARK)」:https://mp.weixin.qq.com/s/623bceLkCjgtFHB6W3D0oA「探索零知識證明」系列:https://sec-bit.github.io/blog/2019/07/31/zero-knowledge-and-proof/「從零開始學習 zk-SNARK」系列:https://sec-bit.github.io/blog/2019/12/25/learn-zk-snark-from-zero-part-one/
「零知識證明學習資源彙總」:https://sec-bit.github.io/blog/2019/11/07/zkp-learning-resources/工程實現背後的理論基礎是近年來(尤其是 2013 年以來)零知識證明特別是 zk-SNARKs 方向的一系列重要論文。如以下最著名的數篇:· [GGPR13] Quadratic span programs and succinct NIZKs without PCPs , Rosario Gennaro, Craig Gentry, Bryan Parno, Mariana Raykova, EUROCRYPT 2013· [PGHR13] Pinocchio: Nearly Practical Verifiable Computation , Bryan Parno, Craig Gentry, Jon Howell, Mariana Raykova, IEEE Symposium on Security and Privacy (Oakland) 2013· [BCGTV13] SNARKs for C: Verifying Program Executions Succinctly and in Zero Knowledge , Eli Ben-Sasson, Alessandro Chiesa, Daniel Genkin, Eran Tromer, Madars Virza, CRYPTO 2013· [BCIOP13] Succinct non-interactive arguments via linear interactive Proofs , Nir Bitansky, Alessandro Chiesa, Yuval Ishai, Rafail Ostrovsky, Omer Paneth, Theory of Cryptography Conference 2013
· [BCTV14a] Succinct non-interactive zero knowledge for a von Neumann architecture , Eli Ben-Sasson, Alessandro Chiesa, Eran Tromer, Madars Virza, USENIX Security 2014· [BCTV14b] Scalable succinct non-interactive arguments via cycles of elliptic curves , Eli Ben-Sasson, Alessandro Chiesa, Eran Tromer, Madars Virza, CRYPTO 2014· [Groth26] On the Size of Pairing-based Non-interactive Arguments , Jens Groth, EUROCRYPT 2016

libsnark 的開發者們亦是這個領域內頂尖的學者或研究牛人,如 Eran Tromer 更是以上多篇論文的共同作者。

紮實的理論基礎和工程能力,讓 libsnark 的作者們能夠化繁為簡,將形如下圖的高深理論和複雜公式逐一實現,高度工程化地抽象出簡潔的介面供廣大開發者方便地呼叫。向這些將非凡的理論研究推廣至更大規模應用的先鋒們致敬。

下圖是 libsnark 的模組總覽圖,摘自 libsnark 程式碼貢獻量第一作者 Madars Virza 在 MIT 的博士論文(https://madars.org/phd-thesis/)。

libsnark 框架提供了多個通用證明系統的實現,其中使用較多的是 BCTV14a 和 Groth26。
檢視 libsnark/libsnark/zk_proof_systems 路徑,就能發現 libsnark 對各種證明系統的具體實現,並且均按不同類別進行了分類,還附上了實現依照的具體論文。

其中:

· zk_proof_systems/ppzksnark/r1cs_ppzksnark 對應的是 BCTV14a
· zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark 對應的是 Groth26

如果想研究這兩個協議的實現細節可直接從這兩個目錄入手。ppzksnark 是指 preprocessing zkSNARK。這裡的 pp/preprocessing 其實就是指我們常說的 trusted setup,即在證明生成和驗證之前,需要透過一個生成演算法來建立相關的公共引數(proving key 和 verification key)。我們也把這個提前生成的引數稱為 「公共參考串」(Common Reference String),或簡稱為 CRS(https://sec-bit.github.io/blog/2019/11/01/from-interactive-zkp-to-non-interactive-zkp)。

2. 基本原理與步驟

利用 libsnark 庫開發 zk-SNARKs 應用從原理上可簡要概括為以下四個步驟:

· 將待證明的命題表達為 R1CS (Rank One Constraint System)
· 使用生成演算法(G)為該命題生成公共引數
· 使用證明演算法(P)生成 R1CS 可滿足性的證明
· 使用驗證演算法(V)來驗證證明

不妨用一個十分簡短(簡化)的例子,來對照上面四個步驟。該例子模仿自這篇文章:
https://media.consensys.net/introduction-to-zksnarks-with-examples-3283b554fc3b

有這樣一個函式 C(x, out),用於判斷秘密 x 是否滿足等式 x^3 + x + 5 == out,若滿足則返回 true。

function C(x, out) {
  return ( x^3 + x + 5 == out );
}
第一步,我們需要將函式 C(x, out) 在 libsnark 中進行表達。此處先省略,後面介紹詳細過程。

第二步,對應下面的 Generator 函式(G),lambda 為隨機產生,也就是常說的 trusted setup 過程中產生的 "toxic waste"。人們喜歡稱它為“有毒廢物”,是因為它必須被妥善處理(如必須銷燬,不能讓任何人知道),否則會影響證明協議安全。

lambda <- random()
(pk, vk) = G(C, lambda)

最終生成 proving key (pk) 和 verification key (vk)。

第三步,對應使用 Prove 函式(P)生成證明。這裡想證明的是 prover 知道一個秘密值 x 和計算結果 out 可使等式滿足。因此將 x、out 還有 pk 作為輸入一起傳給 P,最終生成證明 proof。

proof = P(pk, out, x)

第四步,對應使用 Verify 函式(V)驗證證明,將 proof、out 還有 vk 傳給 G,即可在不暴露秘密的情況下證明存在一個秘密值可使等式滿足。

V(vk, out, proof) ?= true

而開發者主要工作量就集中在第一步,需要按照 libsnark 的介面規則手寫 C++ 電路程式碼來描述命題,由程式碼構造 R1CS 約束。整個過程也即對應下圖的 Computation -> Arithmetic Circuit -> R1CS。

3. 搭建 zk-SNARKs 應用開發環境

下面進入動手環節,快速上手 libsnark,跑通例子。
先下載本文對應的 libsnark 最小可用例子程式碼庫 libsnark_abc。

git clone https://github.com/sec-bit/libsnark_abc.git

透過 git submodule 拉取 libsnark 程式碼。

cd libsnark_abc
git submodule update --init --recursive

參考 libsnark 專案文件完成相關依賴安裝。以 Ubuntu 16.04 LTS 為例,需安裝以下元件:

sudo apt-get install build-essential cmake git libgmp3-dev libprocps4-dev python-markdown libboost-all-dev libssl-dev
初始化 build 資料夾。

mkdir build && cd build && cmake ..

這步在 macOS 系統可能會遇到問題,參考這個 issue 處理。或嘗試使用以下命令:

mkdir build && cd build && CPPFLAGS=-I/usr/local/opt/openssl/include LDFLAGS=-L/usr/local/opt/openssl/lib PKG_CONFIG_PATH=/usr/local/opt/openssl/lib/pkgconfig cmake -DWITH_PROCPS=OFF -DWITH_SUPERCOP=OFF ..

成功後,依舊在 build 目錄進行編譯。

make

編譯成功後,在 build/src 目錄中可看到 3 個二進位制檔案。

main
range
test

到這兒,你就以及完成示例專案的編譯啦。嘗試執行示例程式碼吧。

./src/main

最終出現如下日誌,則說明一切正常。你已順利擁有了 zkSNARK 應用開發環境,併成功跑了第一個 zk-SNARKs 的 demo。

4. 理解示例程式碼

下面我們一起來仔細瞅瞅程式碼。示例專案包含了 3 份程式碼(也可檢視文末附錄)。

不妨先看看 src/main.cpp。這個例子來自 Howard Wu 的 libsnark_tutorial,他也是 libsnark 作者之一哦。本文 libsnark_abc 的專案結構就是依照他的 libsnark_tutorial 搭建,屬於“官方推薦風格” ,請放心食用 。

只有區區幾十行程式碼,其中 run_r1cs_gg_ppzksnark() 是主要部分。很容易發現,真正起作用的實質程式碼只有下面 5 行。

r1cs_gg_ppzksnark_keypair<ppT> keypair = r1cs_gg_ppzksnark_generator<ppT>(example.constraint_system);
r1cs_gg_ppzksnark_processed_verification_key<ppT> pvk = r1cs_gg_ppzksnark_verifier_process_vk<ppT>(keypair.vk);
r1cs_gg_ppzksnark_proof<ppT> proof = r1cs_gg_ppzksnark_prover<ppT>(keypair.pk, example.primary_input, example.auxiliary_input);
const bool ans = r1cs_gg_ppzksnark_verifier_strong_IC<ppT>(keypair.vk, example.primary_input, proof);
const bool ans2 = r1cs_gg_ppzksnark_online_verifier_strong_IC<ppT>(pvk, example.primary_input, proof);

僅從“超長”的函式名就能看出來每步是在做什麼,但是卻看不到如何構造電路的細節。實際上這裡僅僅是呼叫了自帶的 r1cs_example,隱去了實現細節。

既然如此,那讓我們透過一個更直觀的例子來學習電路細節。研究 src/test.cpp,這個例子改編自 Christian Lundkvist 的 libsnark-tutorial(https://github.com/christianlundkvist/libsnark-tutorial)。

程式碼開頭僅引用了三個標頭檔案,分別是:

#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <libsnark/gadgetlib1/pb_variable.hpp>

前面提到 r1cs_gg_ppzksnark 對應的是 Groth26 方案。這裡加了 gg 是為了區別 r1cs_ppzksnark(也就是 BCTV14a 方案),表示 Generic Group Model(通用群模型)。Groth26 安全性證明依賴 Generic Group Model,以更強的安全假設換得了更好的效能和更短的證明。

第一個標頭檔案是為了引入 default_r1cs_gg_ppzksnark_pp 型別,第二個則為了引入證明相關的各個介面。pb_variable 則是用來定義電路相關的變數。

下面需要進行一些初始化,定義使用的有限域,並初始化曲線引數。這是相當於每次的準備工作。

typedef libff::Fr<default_r1cs_gg_ppzksnark_pp> FieldT;
default_r1cs_gg_ppzksnark_pp::init_public_params();

接下來就需要明確「待證命題」是什麼。這裡不妨沿用之前的例子,證明秘密 x 滿足等式 x^3 + x + 5 == out。這實際也是 Vitalik 博文 "Quadratic Arithmetic Programs: from Zero to Hero"(https://medium.com/@VitalikButerin/quadratic-arithmetic-programs-from-zero-to-hero-f6d558cea649) 中用的例子。如果對下面的變化陌生,可嘗試閱讀該博文。

透過引入中間變數 sym_1、y、sym_2 將 x^3 + x + 5 = out 扁平化為若干個二次方程式,幾個只涉及簡單乘法或加法的式子,對應到算術電路中就是乘法門和加法門。你可以很容易地在紙上畫出對應的電路。

x * x = sym_1
sym_1 * x = y
y + x = sym_2
sym_2 + 5 = out

通常文章到這裡便會順著介紹如何按照 R1CS 的形式編排上面的幾個等式,並一步步推匯出具體對應的向量。這對理解如何把 Gate 轉換為 R1CS 有幫助,然而卻不是本文的核心目的。所以此處省略一百字。

下面定義與命題相關的變數。首先建立的 protoboard 是 libsnark 中的一個重要概念,顧名思義就是原型板或者麵包板,用來快速搭建電路,在 zk-SNARKs 電路中則是用來關聯所有變數、元件和約束。接下來的程式碼定義了所有需要外部輸入的變數以及中間變數。

// Create protoboard
protoboard<FieldT> pb;
// Define variables
pb_variable<FieldT> x;
pb_variable<FieldT> sym_1;
pb_variable<FieldT> y;
pb_variable<FieldT> sym_2;
pb_variable<FieldT> out;

下面將各個變數與 protoboard 連線,相當於把各個元器件插到“麵包板”上。allocate() 函式的第二個 string 型別變數僅是用來方便 DEBUG 時的註釋,方便 DEBUG 時檢視日誌。

out.allocate(pb, "out");
x.allocate(pb, "x");
sym_1.allocate(pb, "sym_1");
y.allocate(pb, "y");
sym_2.allocate(pb, "sym_2");
pb.set_input_sizes(1);

注意,此處第一個與 pb 連線的是 out 變數。我們知道 zk-SNARKs 中有 public input 和 private witness 的概念,分別對應 libsnark 中的 primary 和 auxiliary 變數。那麼如何在程式碼中進行區分呢?我們需要藉助 set_input_sizes(n) 來宣告與 protoboard 連線的 public/primary 變數的個數 n。在這裡 n = 1,表明與 pb 連線的前 n = 1 個變數是 public 的,其餘都是 private 的。

至此, 所有變數都已經順利與 protoboard 相連,下面需要確定的是這些變數間的約束關係。這個也很好理解,類似元器件插至麵包板後,需要根據電路需求確定他們之間的關係再連線焊接。如下呼叫 protoboard 的 add_r1cs_constraint() 函式,為 pb 新增形如 a * b = c 的 r1cs_constraint。即 r1cs_constraint<FieldT>(a, b, c) 中引數應該滿足 a * b = c。根據註釋不難理解每個等式和約束之間的關係。

// x*x = sym_1
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(x, x, sym_1));
// sym_1 * x = y
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sym_1, x, y));
// y + x = sym_2
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(y + x, 1, sym_2));
// sym_2 + 5 = ~out
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sym_2 + 5, 1, out));

至此,變數間的約束也已新增完成,針對命題的電路已構建完畢。下面進入前文提到的“四個步驟”中的第二步:使用生成演算法(G)為該命題生成公共引數(pk 和 vk),即 trusted setup。生成出來的 proving key 和 verification key 分別可以透過 keypair.pk 和 keypair.vk 獲得。

const r1cs_constraint_system<FieldT> constraint_system = pb.get_constraint_system();
const r1cs_gg_ppzksnark_keypair<default_r1cs_gg_ppzksnark_pp> keypair = r1cs_gg_ppzksnark_generator<default_r1cs_gg_ppzksnark_pp>(constraint_system);

進入第三步,生成證明。先為 public input 以及 witness 提供具體數值。不難發現,x = 3, out = 35 是原始方程的一個解。則依次為 x、out 以及各個中間變數賦值。

pb.val(out) = 35;
pb.val(x) = 3;
pb.val(sym_1) = 9;
pb.val(y) = 27;
pb.val(sym_2) = 30;

再把 public input 以及 witness 的數值傳給 prover 函式進行證明,可分別透過 pb.primary_input() 和 pb.auxiliary_input() 訪問。生成的證明用 proof 變數儲存。

const r1cs_gg_ppzksnark_proof<default_r1cs_gg_ppzksnark_pp> proof = r1cs_gg_ppzksnark_prover<default_r1cs_gg_ppzksnark_pp>(keypair.pk, pb.primary_input(), pb.auxiliary_input());

最後我們使用 verifier 函式校驗證明。如果 verified = true 則說明證明驗證成功。

bool verified = r1cs_gg_ppzksnark_verifier_strong_IC<default_r1cs_gg_ppzksnark_pp>(keypair.vk, pb.primary_input(), proof);

從日誌輸出中可以看出驗證結果為 true,R1CS 約束數量為 4,public input 和 private input 數量分別為 1 和 4。日誌輸出符合預期。

實際應用中,trusted setup、prove、verify 會由不同角色分別開展,最終實現的效果就是 prover 給 verifier 一段簡短的 proof 和 public input,verifier 可以自行校驗某命題是否成立。對於前面的例子,就是能在不知道方程的解 x 具體是多少的情況下,驗證 prover 知道一個秘密的 x 可以使得 x^3 + x + 5 = out 成立。

透過短短的幾十行程式碼,你就可以很輕易地操控學術界 zk-SNARKs 最新研究成果。

5. 再次上手實踐

經過上面的例子,我們已經瞭解了利用 libsnark 庫開發 zk-SNARKs 電路的所有重要步驟。

現在不妨用新的例子來鞏固一下:在不洩露秘密數字大小的前提下,證明數字小於 60。

這個在常規程式裡用一個運算子就能完成的事情,在 libsnark 下面應該如何表示呢?

zk-SNARKs 電路開發的主要工作量和難點在於如何用程式碼“精確”地描述命題中的所有約束。一旦描述不“精確”,則要麼是漏掉約束、要麼是寫錯約束,最終電路想要證明的內容則會與原命題相差甚遠。上一節的例子只涉及簡單的乘法和加法,與 r1cs_constraint 最基本的形式一致,因此約束的表達相對容易。除此之外幾乎所有的約束都不是很直觀,作為初學者很難正確地描述約束細節。

幸好 libsnark 已經為我們實現了大量基礎電路小元件。gadgetlib1 和 gadgetlib2 下提供了許多可以直接使用的 gadget。其中 gadgetlib1 更常用一些,裡面收集了包括 sha256 在內的 hash 計算、merkle tree、pairing 等電路實現。

DangDangDang,gadgetlib1/gadgets/basic_gadgets.hpp 中的 comparison_gadget 正是我們所需。

comparison_gadget(protoboard<FieldT>& pb,
                    const size_t n,
                    const pb_linear_combination<FieldT> &A,
                    const pb_linear_combination<FieldT> &B,
                    const pb_variable<FieldT> &less,
                    const pb_variable<FieldT> &less_or_eq,
                    const std::string &annotation_prefix="")

該 gadget 需要傳入的引數較多:n 表示位數,A 和 B 分別為需要比較的兩個數,less 和 less_or_eq 用來標記兩個數的關係是「小於」還是「小於或等於」。該 gadget 實現的原理簡單來講是把 A 和 B 的比較,轉化為 2^n + B - A 按位表示。具體實現還用到了其餘多個基礎 gadget,可以透過 comparison_gadget<FieldT>::generate_r1cs_constraints() 研究。

這裡需要建立以下變數,並將 x 和 max 與 pb 相連,把 max 值設為 60,代表數值上限。

protoboard<FieldT> pb;
pb_variable<FieldT> x, max;
pb_variable<FieldT> less, less_or_eq;
x.allocate(pb, "x");
max.allocate(pb, "max");
pb.val(max)= 60;

使用 comparison_gadget 建立 cmp,並把前面的引數填入,並呼叫 gadget 自帶的 generate_r1cs_constraints() 方法。同時另外新增一個約束,要求 less * 1 = 1,也就是 less 必須為 true。

comparison_gadget<FieldT> cmp(pb, 10, x, max, less, less_or_eq, "cmp");
cmp.generate_r1cs_constraints();
pb.add_r1cs_constraint(r1cs_constraint<FieldT>(less, 1, FieldT::one()));

輸入 witness(秘密值 x),比如讓 x = 18。這裡還需要呼叫 comparison_gadget 的 generate_r1cs_witness 方法。

// Add witness values
pb.val(x) = 18; // secret
cmp.generate_r1cs_witness();

其餘部分和上一個例子一致,即可在不洩露秘密數字大小的前提下,證明某數字小於 60。同理,就實現一個對數值作最大和最小值限定的 “range proof”。

在強大基礎庫的幫助下,我們又用更短的程式碼實現了證明需求。

6. What's NEXT?

讀到這裡,相信大家都對 libsnark 的使用方法和 zk-SNARKs 電路開發有了一個初步的瞭解。

你或許已經發現,libsnark 的使用方法較簡單,而真正的重點在於 zk-SNARKs 電路開發。正如前面提過的,必須用程式碼“精確”描述待證命題中的所有約束,“漏掉”或“寫錯”約束都會讓證明內容與原本意圖大相徑庭,從而導致證明無意義。

如何正確高效地把真實業務邏輯轉化為 zk-SNARKs 電路程式碼,這正是我們開發者需要不斷研究和練習的。

好在我們已經有了一個 libsnark 試驗場,可以很方便地自由修改、新增程式碼來嘗試。

不論多複雜的電路實現,都是透過一個個更簡單地「電路元件」組合封裝而形成。因此 libsnark 自帶的基礎庫是一個非常重要的學習資料——既要學習它們的使用方法,又要研究其實現原理。

我們也能透過閱讀其他專案的電路實現來了解如何將 ZKP 應用到實際業務中,如 HarryR 的 ethsnarks-miximus(https://github.com/HarryR/ethsnarks-miximus)  和 Loopring 的 protocol3-circuits(https://github.com/Loopring/protocol3-circuits)。從這些專案中可以學習到如何工程化地開發更大規模的電路,以及與電路效能相關的各種設計最佳化細節,同時對電路約束規模會有更深刻的理解。

同時也歡迎大家繼續關注安比實驗室「零知識證明 Learn by Coding:libsnark 系列」後續文章,下次我們將嘗試從 zk-SNARKs 與智慧合約的結合、電路模組化開發、更復雜的 libsnark 實現案例、電路開發過程中容易踩的坑等角度來進一步討論。

7. 附錄

main.cpp

第一個例子 main.cpp,呼叫 libsnark 官方 example 的示例程式碼。透過該例子可瞭解 libsnark 的基本使用流程和主要函式。

#include <libff/common/default_types/ec_pp.hpp>
#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/relations/constraint_satisfaction_problems/r1cs/examples/r1cs_examples.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/r1cs_gg_ppzksnark.hpp>
using namespace libsnark;
/**
 * The code below provides an example of all stages of running a R1CS GG-ppzkSNARK.
 *
 * Of course, in a real-life scenario, we would have three distinct entities,
 * mangled into one in the demonstration below. The three entities are as follows.
 * (1) The "generator", which runs the ppzkSNARK generator on input a given
 *     constraint system CS to create a proving and a verification key for CS.
 * (2) The "prover", which runs the ppzkSNARK prover on input the proving key,
 *     a primary input for CS, and an auxiliary input for CS.
 * (3) The "verifier", which runs the ppzkSNARK verifier on input the verification key,
 *     a primary input for CS, and a proof.
 */
template<typename ppT>
bool run_r1cs_gg_ppzksnark(const r1cs_example<libff::Fr<ppT> > &example)
{
    libff::print_header("R1CS GG-ppzkSNARK Generator");
    r1cs_gg_ppzksnark_keypair<ppT> keypair = r1cs_gg_ppzksnark_generator<ppT>(example.constraint_system);
    printf("\n"); libff::print_indent(); libff::print_mem("after generator");
    libff::print_header("Preprocess verification key");
    r1cs_gg_ppzksnark_processed_verification_key<ppT> pvk = r1cs_gg_ppzksnark_verifier_process_vk<ppT>(keypair.vk);
    libff::print_header("R1CS GG-ppzkSNARK Prover");
    r1cs_gg_ppzksnark_proof<ppT> proof = r1cs_gg_ppzksnark_prover<ppT>(keypair.pk, example.primary_input, example.auxiliary_input);
    printf("\n"); libff::print_indent(); libff::print_mem("after prover");
    libff::print_header("R1CS GG-ppzkSNARK Verifier");
    const bool ans = r1cs_gg_ppzksnark_verifier_strong_IC<ppT>(keypair.vk, example.primary_input, proof);
    printf("\n"); libff::print_indent(); libff::print_mem("after verifier");
    printf("* The verification result is: %s\n", (ans ? "PASS" : "FAIL"));
    libff::print_header("R1CS GG-ppzkSNARK Online Verifier");
    const bool ans2 = r1cs_gg_ppzksnark_online_verifier_strong_IC<ppT>(pvk, example.primary_input, proof);
    assert(ans == ans2);
    return ans;
}
template<typename ppT>
void test_r1cs_gg_ppzksnark(size_t num_constraints, size_t input_size)
{
    r1cs_example<libff::Fr<ppT> > example = generate_r1cs_example_with_binary_input<libff::Fr<ppT> >(num_constraints, input_size);
    const bool bit = run_r1cs_gg_ppzksnark<ppT>(example);
    assert(bit);
}
int main () {
    default_r1cs_gg_ppzksnark_pp::init_public_params();
    test_r1cs_gg_ppzksnark<default_r1cs_gg_ppzksnark_pp>(1000, 100);
    return 0;
}
test.cpp

第二個例子 test.cpp。這個例子具體展示瞭如何利用 libsnark 構建一個最簡單的電路。

#include <libsnark/common/default_types/r1cs_gg_ppzksnark_pp.hpp>
#include <libsnark/zk_proof_systems/ppzksnark/r1cs_gg_ppzksnark/r1cs_gg_ppzksnark.hpp>
#include <libsnark/gadgetlib1/pb_variable.hpp>
using namespace libsnark;
using namespace std;
int main () {
    typedef libff::Fr<default_r1cs_gg_ppzksnark_pp> FieldT;
    // Initialize the curve parameters
    default_r1cs_gg_ppzksnark_pp::init_public_params();

    // Create protoboard
    protoboard<FieldT> pb;
    // Define variables
    pb_variable<FieldT> x;
    pb_variable<FieldT> sym_1;
    pb_variable<FieldT> y;
    pb_variable<FieldT> sym_2;
    pb_variable<FieldT> out;
    // Allocate variables to protoboard
    // The strings (like "x") are only for debugging purposes    
    out.allocate(pb, "out");
    x.allocate(pb, "x");
    sym_1.allocate(pb, "sym_1");
    y.allocate(pb, "y");
    sym_2.allocate(pb, "sym_2");
    // This sets up the protoboard variables
    // so that the first one (out) represents the public
    // input and the rest is private input
    pb.set_input_sizes(1);
    // Add R1CS constraints to protoboard
    // x*x = sym_1
    pb.add_r1cs_constraint(r1cs_constraint<FieldT>(x, x, sym_1));
    // sym_1 * x = y
    pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sym_1, x, y));
    // y + x = sym_2
    pb.add_r1cs_constraint(r1cs_constraint<FieldT>(y + x, 1, sym_2));
    // sym_2 + 5 = ~out
    pb.add_r1cs_constraint(r1cs_constraint<FieldT>(sym_2 + 5, 1, out));

    const r1cs_constraint_system<FieldT> constraint_system = pb.get_constraint_system();
    // generate keypair
    const r1cs_gg_ppzksnark_keypair<default_r1cs_gg_ppzksnark_pp> keypair = r1cs_gg_ppzksnark_generator<default_r1cs_gg_ppzksnark_pp>(constraint_system);
    // Add public input and witness values
    pb.val(out) = 35;
    pb.val(x) = 3;
    pb.val(sym_1) = 9;
    pb.val(y) = 27;
    pb.val(sym_2) = 30;
    // generate proof
    const r1cs_gg_ppzksnark_proof<default_r1cs_gg_ppzksnark_pp> proof = r1cs_gg_ppzksnark_prover<default_r1cs_gg_ppzksnark_pp>(keypair.pk, pb.primary_input(), pb.auxiliary_input());
    // verify
    bool verified = r1cs_gg_ppzksnark_verifier_strong_IC<default_r1cs_gg_ppzksnark_pp>(keypair.vk, pb.primary_input(), proof);
    cout << "Number of R1CS constraints: " << constraint_system.num_constraints() << endl;
    cout << "Primary (public) input: " << pb.primary_input() << endl;
    cout << "Auxiliary (private) input: " << pb.auxiliary_input() << endl;
    cout << "Verification status: " << verified << endl;
}

range

免責聲明:

  1. 本文版權歸原作者所有,僅代表作者本人觀點,不代表鏈報觀點或立場。
  2. 如發現文章、圖片等侵權行爲,侵權責任將由作者本人承擔。
  3. 鏈報僅提供相關項目信息,不構成任何投資建議

推荐阅读

;