Skip to main content

Background

zkSNARKs

Zero-Knowledge Succinct Non-Interactive Argument of Knowledge (zkSNARK) is a cryptographic primitive that allows one party called Prover to convince another party called Verifier that Prover knows a secret value satisflying some property. Moreover, it does so by sending only one message from Prover to Verifier (Non-Interactive) and keeping Prover's the secret value private from Verifier (Zero-Knowledge).

Let C(sec,pub){0,1}C(\texttt{sec}, \texttt{pub}) \in \{0, 1\} be a predicate that expresses the property of the Prover's secret value sec\texttt{sec} that the parties want to verify. The pub\texttt{pub} here is a public parameter that's known to both Prover and Verifier — it is also called instance. The sec\texttt{sec} is a secret parameter known only to the Verifier — another term for it is witness.

As an idealized example, consider a zkSNARK being implemented via algorithms prove\texttt{prove} and verify\texttt{verify} that is used as follows.

  • Prover and Verifier both agree on the public value pub\texttt{pub} they will use.

  • Prover runs proofprove(C,pub,sec)\texttt{proof} \gets \texttt{prove}(C, \texttt{pub}, \texttt{sec}), sends proof\texttt{proof} to Verifier.

  • Verifier runs resultverify(C,pub,proof)\texttt{result} \gets \texttt{verify}(C, \texttt{pub}, \texttt{proof}).

zkSNARK guarrantees that result=1\texttt{result} = 1 if and only if C(pub,sec)=1C(\texttt{pub}, \texttt{sec}) = 1. Otherwise, Verifier will see that result=0\texttt{result} = 0 and know that Prover's witness sec\texttt{sec} is incorrect. The proof\texttt{proof} value does not reveal anything about Prover's secret sec\texttt{sec}, and is guarranteed to be short (succinct) — much smaller than the amount of operations that CC performs. Usually the proof\texttt{proof} is also very easy to verify, even easier than computing the predicate CC.

info

In reality, the prove\texttt{prove} and verify\texttt{verify} take more parameters that carry setup data (which may depend on the predicate CC or not). We omit the setup parameters in this section for simplicity, but we will come back to them when we look at concrete code examples for proving and verifying proofs.

tip

The definition of zkSNARK we presented above requires the computation CC that is being verified to be a predicate, but this definition is robust enough to verify the evaluation of functions that output more than just one bit. Suppose that Prover wants to convince Verifier that he knows sec\texttt{sec} such that F(pub,sec)=yF(\texttt{pub}, \texttt{sec}) = y where function FF, parameter pub\texttt{pub} and output value yy are public. He could construct predicate (the =?=^? denotes testing values for equality, like == operator in C/C++/Rust)

C((pub,y),sec):=(F(pub,sec)=?y) C((\texttt{pub}, y), \texttt{sec}) := (F(\texttt{pub}, \texttt{sec}) =^? y)

which equals 11 if and only if F(pub,sec)=yF(\texttt{pub}, \texttt{sec}) = y. Then send yy together with

proofprove(C,(pub,y),sec) \texttt{proof} \gets \texttt{prove}(C, (\texttt{pub}, y), \texttt{sec})

to the Verifier who can check that the yy is indeed the correct output of FF by doing verify(C,(pub,y),proof)\texttt{verify}(C, (\texttt{pub}, y), \texttt{proof}).

What makes zkSNARKs useful for Blockchain? Whole state of the Blockchain is public, and each update to made to it is completely transparent for any participant to inspect. At the same time, the computations that implement such updates (e.g. in a smart contract) are very expensive since they have to be replicated by every node on the network. zkSNARKs can help move such computations off-chain, where they are performed cheaper and can access some private data without disclosing it to the Blockchain, and then give Blockchain a proof\texttt{proof} certifying their correctness. In addition to enabling public-state Blockchain to work with secret data, zkSNARKs can reduce the Blockchain's computational burden and move the bulk of the computational work off-chain where it's cheaper.

Constraint Systems & Circuits

zkSNARKs often represent CC as a system of equations where (pub,sec)(\texttt{pub}, \texttt{sec}) values that satisfy the predicate are also solutions (each pub\texttt{pub} and sec\texttt{sec} storing a vector of variables) to the equation system. Such equation system is called Constraint System (CS). Algorithms prove\texttt{prove} and verify\texttt{verify}, for example, accept CC encoded as a Constraint System of some specific form (each concrete zkSNARK having its own form).

Constraint Systems are also called "circuits" in zkSNARK jargon (we will use the two terms interchangably here), because it's often very convenient to view the computations that a Constraint System is expressing as evaluation of a circuit. A circuit has gates and wires connecting them. Each wire has a value associated with it, and each gate transforms the values on its input wires into the value of its output wire.

tip

The standard way to translate a circuit into its corresponding CS is to assign a CS variable to each of the circuit's wires and then add a CS constraint for each of the circuit gates ensuring that the inputs and outputs are related according to the function the gate is computing. The way a gate is incoded in CS will be dependent on the zkSNARK you use.

As an example, consider the circuit below.

An example circuit

Converting it to a CS will yield the following constraints:

w1=in1+in2w2=in3×in4w3=in2×w2w4=w1×w3 \begin{aligned} w_1 &= \texttt{in}_1 + \texttt{in}_2 \\ w_2 &= \texttt{in}_3 \times \texttt{in}_4 \\ w_3 &= \texttt{in}_2 \times w_2 \\ w_4 &= w_1 \times w_3 \\ \end{aligned}

The in\texttt{in} and ww in these expressions are variables the values of which can be assigned by pub\texttt{pub} or sec\texttt{sec}.

Circuits are a convenient model that allows one to express nested functional dependencies between the variables of a CS. Once you've produced such constraints using a circuit, you can add any other constraints (supported by your zkSNARK) that you like to the CS as well.

To summarize, constraint systems are the "true" low-level format to which our circuit description will be translated before use with a zkSNARK. But most of the time when we program Constraint Systems we like to think of them as circuits computing functions, and then applying some extra constraints to the results of those functions. For this reason, the API of fawkes-crypto is tailored to describe constraints in circuit format, but we also allow adding "raw" constraints to the CS directly, bypassing the circuit representation.

Programming a Constraint System

In order to use a zkSNARK in a practical protocol, Prover and Verifier must agree on the CS CC they're proving. The moment when CC is passed to prove\texttt{prove} or verify\texttt{verify} it will be represented by a list of equations of some specific format. But before that, a programmer needs to implement it and verify that it does what Prover and Verifier intend it to. Given how complex the CC can get in practical applications, a list of equations is not a convenient format to do that.

We need some language to describe the logic of Constraint System CC in a way that is expressive and easy to understand. Fawkes-crypto provides Rust API to do just that. We allow creating a circuit description that can be used by both Prover and Verifier, and which also aids Prover in computing CC (like in our example above when Prover needed to determine the y=F(pub,sec)y = F(\texttt{pub}, \texttt{sec})). By using one circuit description for all these tasks, we reduce boilerplate and help implement a zkSNARK application correctly.

info

What fawkes-crypto provides is called Embedded Domain Specific Language (EDSL) as opposed to regular DSLs for zkSNARKs like Circom, because it is embedded as an API in it host language, Rust. This allows for seamless integration between the target Rust app that's using a zkSNARK and the circuit implementation.

Another benefit of an abstract EDSL for describing circuits is that one can use the same circuit description written in Fawkes-crypto with multiple different zkSNARK backends. This enables flexibility and rapid prototyping of zkSNARK applications, albeit at the cost of not having your circuit description precisely tailored for the specific zkSNARK you may have chosen.