I hope that through this series of articles, all developers can get started and practice in person, quickly get started with libsnark in a short time, understand the basic concepts of libsnark step by step, learn how to develop zkSNARKs circuits, complete the generation and verification of proofs, and finally have zero knowledge Proofs apply to real business.
1.Introduction to zkSNARKs and libsnark
Zeroknowledge proof is probably the most promising cryptographic black technology at present. And zkSNARKs is the abbreviation of a type of zeroknowledge proof scheme, which is called ZeroKnowledge Succinct Noninteractive Arguments of Knowledge. This name contains almost all of its technical characteristics, that is, it can prove the correctness of a proposition without revealing any other information, and the resulting certificate is concise (Succinct), which means that the resulting certificate is small enough , And has nothing to do with the amount of calculation, it is a constant. In other words, you can theoretically prove something to everyone without exposing any privacy, and the generated proof is small in size and low in verification cost, and has nothing to do with the amount of content calculation that needs to be proven. It sounds so wonderful!
zkSNARKs can be applied to many scenarios, such as privacy protection, blockchain expansion, verifiable computing, etc. This article does not introduce the theoretical details of zkSNARKS and zeroknowledge proofs. Students who are not familiar with or want to learn more can read other articles or papers.
For example, three famous blog posts about zkSNARKs by Vitalik.

https://medium.com/@VitalikButerin/quadraticarithmeticprogramsfromzerotoherof6d558cea649 
https://medium.com/@VitalikButerin/exploringellipticcurvepairingsc73c1864e627 
https://medium.com/@VitalikButerin/zksnarksunderthehoodb33151a013f6
Of course, you are also welcome to pay attention to the series of "Exploring Zero Knowledge Proof" and "Learning from scratch by zkSNARK" series by Abe Labs, and find more information from the "Zero Knowledge Proof Learning Resources Summary" maintained by Abe Labs.
 "ZkSNARKs indepth explanation of zeroknowledge proofs": https://www.yuque.com/u428635/scg32w/edmn74
 "Talking about Zeroknowledge Proof 2: Short NonInteraction Proof (SNARK)": https://mp.weixin.qq.com/s/623bceLkCjgtFHB6W3D0oA
 "Explore Zero Knowledge Proof" series: https://secbit.github.io/blog/2019/07/31/zeroknowledgeandproof/
 "Learn zkSNARK from scratch" series: https://secbit.github.io/blog/2019/12/25/learnzksnarkfromzeropartone/
 "Zeroknowledge proof learning resource summary": https://secbit.github.io/blog/2019/11/07/zkplearningresources/

[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 BenSasson, Alessandro Chiesa, Daniel Genkin, Eran Tromer, Madars Virza, CRYPTO 2013 
[BCIOP13] Succinct noninteractive arguments via linear interactive Proofs, Nir Bitansky, Alessandro Chiesa, Yuval Ishai, Rafail Ostrovsky, Omer Paneth, Theory of Cryptography Conference 2013 
[BCTV14a] Succinct noninteractive zero knowledge for a von Neumann architecture, Eli BenSasson, Alessandro Chiesa, Eran Tromer, Madars Virza, USENIX Security 2014 
[BCTV14b] Scalable succinct noninteractive arguments via cycles of elliptic curves, Eli BenSasson, Alessandro Chiesa, Eran Tromer, Madars Virza, CRYPTO 2014 
[Groth16] On the Size of Pairingbased Noninteractive Arguments, Jens Groth, EUROCRYPT 2016
The solid theoretical foundation and engineering ability allow the authors of libsnark to reduce complexity, implement the advanced theories and complex formulas as shown in the following figure, and abstract a simple interface highly engineered for the convenience of developers. Pay tribute to these pioneers who extended extraordinary theoretical research to larger scale applications.
The figure below is an overview of the modules of libsnark, taken from the doctoral dissertation of Madars Virza, the first author of libsnark code contribution (https://madars.org/phdthesis/).
The libsnark framework provides multiple implementations of the general proof system, of which the more commonly used are BCTV14a and Groth16.
Looking at the path of libsnark / libsnark / zk_proof_systems , we can find the specific implementation of various certification systems by libsnark, and they are classified according to different categories, and specific papers on which the implementation is based are also attached.
among them:

zk_proof_systems / ppzksnark / r1cs_ppzksnark corresponds to BCTV14a 
zk_proof_systems / ppzksnark / r1cs_gg_ppzksnark corresponds to Groth16
2. Basic principles and steps
Using the libsnark library to develop zkSNARKs applications can be briefly summarized in principle as the following four steps:

Express the proposition to be proved as R1CS (Rank One Constraint System) 
Generate a common parameter for the proposition using a generation algorithm ( G ) 
Proof of R1CS Satisfaction Using Proof Algorithm ( P ) 
Use verification algorithm ( V ) to verify proof
https://media.consensys.net/introductiontozksnarkswithexamples3283b554fc3b
There is such a function C (x, out), which is used to determine whether the secret x satisfies the equation x ^ 3 + x + 5 == out, and returns true if it is satisfied.
function C (x, out) {
return (x ^ 3 + x + 5 == out);
}
lambda <random ()
(pk, vk) = G (C, lambda)
proof = P (pk, out, x)
V (vk, out, proof)? = True
3.Set up zkSNARKs application development environment
Let's get into the handson section, quickly get started with libsnark, and run through the examples.
Download the libsnark minimum code library libsnark_abc corresponding to this article first.
git clone https://github.com/secbit/libsnark_abc.git
Pull the libsnark code through the git submodule.
cd libsnark_abc
git submodule update init recursive
sudo aptget install buildessential cmake git libgmp3dev libprocps4dev pythonmarkdown libboostalldev libssldev
mkdir build && cd build && cmake ..
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 ..
make
main
range
test
./src/main
Eventually, the following log shows that everything is normal. You have successfully owned the zkSNARK application development environment and successfully ran the first demo of zkSNARKs .
4.Understand the sample code
Let's go through the code carefully. The sample project contains 3 codes (see also the appendix at the end of the article).
There are only dozens of lines of code, of which run_r1cs_gg_ppzksnark () is the main part. It is easy to see that the actual code that really works is only the following 5 lines.
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);
Just from the "extra long" function name, you can see what each step is doing, but you can't see the details of how to construct the circuit. In fact, it just calls the builtin r1cs_example, which hides the implementation details.
That being the case, let's learn the details of the circuit with a more intuitive example . Study src / test.cpp, this example is adapted from Christian Lundkvist's libsnarktutorial (https://github.com/christianlundkvist/libsnarktutorial).
Only three header files are referenced at the beginning of the code:
#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>
As mentioned earlier, r1cs_gg_ppzksnark corresponds to the Groth16 scheme. Gg is added here to distinguish r1cs_ppzksnark (that is, the BCTV14a solution), which stands for Generic Group Model. Groth16 security proof relies on the Generic Group Model, in exchange for stronger security assumptions for better performance and shorter proofs.
The first header file is to introduce the default_r1cs_gg_ppzksnark_pp type, and the second header file is to introduce various interfaces related to proof. pb_variable is used to define circuitrelated variables.
Next you need to perform some initialization, define the finite fields used, and initialize the curve parameters. This is equivalent to each preparation.
typedef libff :: Fr <default_r1cs_gg_ppzksnark_pp> FieldT;
default_r1cs_gg_ppzksnark_pp :: init_public_params ();
Next, we need to clarify what the "proposition to prove" is. Here we may follow the previous example and prove that the secret x satisfies the equation x ^ 3 + x + 5 == out. This is actually the example used in the Vitalik blog post "Quadratic Arithmetic Programs: from Zero to Hero" (https://medium.com/@VitalikButerin/quadraticarithmeticprogramsfromzerotoherof6d558cea649). If you are new to the changes below, try reading this blog post.
By introducing the intermediate variables sym_1, y, and sym_2, flatten x ^ 3 + x + 5 = out into several quadratic equations, some of which only involve simple multiplication or addition. Corresponding to arithmetic circuits are multiplication gates and additions. Method . You can easily draw the corresponding circuit on paper.
x * x = sym_1
sym_1 * x = y
y + x = sym_2
sym_2 + 5 = out
// 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;
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);
// 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 ();
const r1cs_gg_ppzksnark_keypair <default_r1cs_gg_ppzksnark_pp> keypair = r1cs_gg_ppzksnark_generator <default_r1cs_gg_ppzksnark_pp> (constraint_system);
pb.val (out) = 35;
pb.val (x) = 3;
pb.val (sym_1) = 9;
pb.val (y) = 27;
pb.val (sym_2) = 30;
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 ());
bool verified = r1cs_gg_ppzksnark_verifier_strong_IC <default_r1cs_gg_ppzksnark_pp> (keypair.vk, pb.primary_input (), proof);
With just a few dozen lines of code, you can easily manipulate the latest research results of academic zkSNARKs.
5, get started again
After the above example, we have learned all the important steps for developing zkSNARKs circuits using the libsnark library.
Now let's consolidate with a new example: Prove that the number is less than 60 without revealing the size of the secret number .
How should this be done in a regular program with an operator under libsnark?
The main workload and difficulty of zkSNARKs circuit development lies in how to accurately describe all constraints in the proposition with code. Once the description is not "precise", either the constraint is missed or the constraint is written incorrectly, and the content that the final circuit wants to prove will be far from the original proposition. The example in the previous section only involved simple multiplication and addition, which is consistent with the most basic form of r1cs_constraint, so the expression of constraints is relatively easy. In addition, almost all constraints are not very intuitive, and it is difficult for a beginner to describe the constraint details correctly.
Fortunately, libsnark has implemented a lot of basic circuit widgets for us. There are many gadgets available directly under gadgetlib1 and gadgetlib2. Among them, gadgetlib1 is more commonly used, which includes hash calculation, merkle tree, pairing and other circuit implementations including sha256.
DangDangDang , comparisonlib in gadgetlib1 / gadgets / basic_gadgets.hpp is exactly what we need.
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 = "")
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 <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 ()));
// Add witness values
pb.val (x) = 18; // secret
cmp.generate_r1cs_witness ();
6.What's NEXT?
After reading this, I believe that everyone has a preliminary understanding of the use of libsnark and zkSNARKs circuit development.
You may have found that the use of libsnark is relatively simple, and the real focus is on zkSNARKs circuit development. As mentioned earlier, all constraints in the proposition to be proved must be described with the code "exactly". "Missing" or "mistyped" constraints can make the content of the proof very different from the original intention, which makes the proof meaningless.
How to correctly and efficiently translate real business logic into zkSNARKs circuit code is exactly what our developers need to continue to study and practice.
Fortunately, we already have a libsnark proving ground, and we can easily modify and add code to try it out.
No matter how complicated the circuit is, it is formed by simpler "circuit components" combined packaging. Therefore, the basic library that comes with libsnark is a very important learning materialyou must learn both how to use them and study the implementation principles.
We can also learn how to apply ZKP to actual business by reading the circuit implementation of other projects, such as HarryR's ethsnarksmiximus (https://github.com/HarryR/ethsnarksmiximus) and Loopring's protocol3circuits (https : //github.com/Loopring/protocol3circuits). From these projects, you can learn how to engineeringly develop largerscale circuits, and various design optimization details related to circuit performance. At the same time, you will have a deeper understanding of the scale of circuit constraints.
At the same time, everyone is welcome to continue to pay attention to the followup article of the "Zeroknowledge Proof: Learn by Coding: libsnark series" by Abe Labs. Next time we will try to combine zkSNARKs and smart contracts, circuit modular development, more complex libsnark implementation cases, Further discussions are made on the pits that are easy to step on during circuit development.
7.Appendix
main.cpp
#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 GGppzkSNARK.
*
* Of course, in a reallife 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 GGppzkSNARK 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 GGppzkSNARK 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 GGppzkSNARK 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 GGppzkSNARK 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;
}
#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;
}
#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>
#include <libsnark / gadgetlib1 / gadgets / basic_gadgets.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;
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 <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 ()));
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 witness values
pb.val (x) = 18; // secret
cmp.generate_r1cs_witness ();
// 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;
}