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 be a predicate that expresses the property of the Prover's secret value that the parties want to verify. The here is a public parameter that's known to both Prover and Verifier — it is also called instance. The 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 and that is used as follows.
-
Prover and Verifier both agree on the public value they will use.
-
Prover runs , sends to Verifier.
-
Verifier runs .
zkSNARK guarrantees that if and only if . Otherwise, Verifier will see that and know that Prover's witness is incorrect. The value does not reveal anything about Prover's secret , and is guarranteed to be short (succinct) — much smaller than the amount of operations that performs. Usually the is also very easy to verify, even easier than computing the predicate .
In reality, the and take more parameters that carry setup data (which may depend on the predicate 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.
The definition of zkSNARK we presented above requires the computation 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 such
that where function , parameter
and output value are public. He could construct predicate
(the denotes testing values for equality, like ==
operator in
C/C++/Rust)
which equals if and only if . Then send together with
to the Verifier who can check that the is indeed the correct output of by doing .
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 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 as a system of equations where values that satisfy the predicate are also solutions (each and storing a vector of variables) to the equation system. Such equation system is called Constraint System (CS). Algorithms and , for example, accept 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.
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.
Converting it to a CS will yield the following constraints:
The and in these expressions are variables the values of which can be assigned by or .
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 they're proving. The moment when is passed to or 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 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 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 (like in our example above when Prover needed to determine the ). By using one circuit description for all these tasks, we reduce boilerplate and help implement a zkSNARK application correctly.
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.