API
Overview
Technically, the API fawkes-crypto provides for describing circuits for use in
R1CS and Plonk backends are different. Each is enabled using r1cs and plonk
Cargo features. In this section we start with high-level overview of their
common principles and then we dive into the concrete code examples of using
each in the later sections.
Fawkes-crypto API uses the following vocabulary for describing Constraint Systems.
The code examples we give here are have some technicalities (like borrowing and wrapper types) simplified in them to convey the intuition — this is best read as pseudocode. We will look at the formally correct, runnable examples in the next sections.
- 
trait CSdescribes the whole Constraint System. It has methods for allocating new variables in and as well as adding new constraints to the system.The field over which the CSconstraints work is given by the associated typeCS::Fr.Two main implementors of this trait are struct BuildCSandstruct DebugCS. The former one is the format which proving and verifying functions accept, while the latter is useful for the circuit.
- 
struct CNum<CS>is a reference to a specific variable in the CS. For example, if you allocate a new variable withfn CS::alloc(self, value: Option<CS::Fr>) -> CNum<CS>you get a return value of type CNum<CS>. If you're Prover, you should passvalue = Some(x)to set this new variable's value tox. If you're Verifier, you passvalue = Nonesince you may not know the value of this variable (and it's not your job to compute it).If you want to enforce a constraint on some variables of your CS, you will also refer to the variables using CNum<CS>. For example, givena, b, c : CNum<CS>, to add the constraint to your CS you may call a function like// a * b == c
 fn CS::enforce_mul(a: CNum<CS>, b: CNum<CS>, c: CNum<CS>);If you have a x: CNum<CS>that refers to a secret variable, i.e. one from , and you want to make it public moving it to you callfn CS::inputize(n: CNum<CS>);You can also do regular arithmetic operations on CNums. This applies operations to the wires in the circuit model of our CS, creating new wires (and leaving input wires available for further use, of course). For example, if we havea, b: CNum<CS>, we can dolet c = a * b. This willalloca new variable forc(using the correctvaluederived as the product of values ofaandb), and then doenforce_mul(a, b, c).
- 
struct CBool<CS>is just a regularCNum<CS>which was restricted (using a constraint) to hold only values 0 or 1 (inCS::Fr). It can be used for if-then-else/switch constructs which we will see shortly.
- 
trait Signal<CS>generalizesCNum<CS>,CBool<CS>as well as fixed-size vectors and pairs of otherSignals. You can also implementSignaltrait for your own types (such types will typically store some number ofCNums andCBools in them).A Signal<CS>is something you can copy, test for equality with aSignal<CS>of the same type, allocate, inputize — all with a single call to the corresponding method.Since Signal<CS>is a generic type that circuit operates on, it can be passed in the "then" and "else" branches of the conditional "switch" operation:// Returns self if bit is true, if_else otherwise
 fn Signal::switch(&self, bit: CBool<C>, if_else: Self) -> SelfYou can do let (x, y) = (a,b).switch(bit, (c, d))to implement conditional assignmentin your CS. This works because (CNum<CS>, CNum<CS>)is an instance ofSignal<CS>.
- 
Num<Fp>is a wrapper type forFpfield. We use it to implement traits forFp. It's a rather technical detail, it's safe to viewNum::from(x)asx.
Now we will look at more concrete (runnable) code examples that use R1CS and
Plonk constraints. They are enabled using either r1cs or plonk Cargo
flags. Once you enable one of the two flags, the corresponding definitions of
CS, CNum, CBool will appear in cicuit::{cs, num, bool}.
Plonk Constraints
In Fawkes-crypto circuits are described as functions of type
circuit: Fn(Signal<BuildCS<Fr>>, Signal<BuildCS<Fr>>)
The two arguments are pub and sec. Their concrete types can be anything
that implements Signal, e.g. CNum<_>, CBool<_>, fixed-size vectors
of CNum or CBool. The circuit doesn't accept the cs: BuildCS<Fr>
itself, because Signal<_> values store smart reference to the CS value they
correspond to. Both pub and sec must, of course, belong to the same CS.
The pub and sec here can have different types. For example, you could make
pub: (CNum<_>, CNum<_>) and sec: (CNum<_>, CNum<_>, CNum<_>) to have 2
public inputs and 3 secret ones.
To verify a circuit using Plonk constraints (Halo2 backend with KZG10 commitments), one does the following steps.
- 
Pick public parameters and generate Verifier and Prover keys. use fawkes_crypto::backend::plonk::{
 engines::Bn256,
 setup::setup,
 Parameters
 };
 // Generate parameters.
 //
 // The k=10 here is a parameter of KZG10 commitment scheme. See its paper
 // for details. And Bn256 is one of the available "engines" that
 // fawkes-crypto implements for Plonk; for details, see
 // backend::plonk::engines module.
 let parameters = Parameters::<Bn256>::setup(10);
 // Sample keys. The vk goes to Verifier, pk goes to Prover
 let (vk, pk) = setup(¶meters, circuit);cautionThis step is also called trusted setup. It must be executed either by a trusted third party or in a Secure Multi-Party Computation protocol, Prover and Verifier must not see the intermediate values from setupcomputation.
- 
Generate the proof. This step is done by the Prover using the public inputs puband secret inputsec.use fawkes_crypto::backend::plonk::prover;
 let (inputs, proof) = prover::prove(
 ¶meters,
 &pk,
 &pub, // actual value of pub to pass to circuit
 &sec, // actual value of sec to pass to circuit
 circuit,
 );Prover communicates both inputsandproofto the verifier. Theproofis the we've introduced before. Andinputsis the list of all public inputs of thecircuit, it will include bothpuband all the other public inputs thatcircuitwill create and assign values to duing its execution. It may contain some computation results that the Prover wants to reveal to the Verifier, for example.
- 
Verify the proof. This is done by the Verifier. use fawkes_crypto::backend::plonk::verifier;
 let result: bool = verifier::verify(
 ¶meters,
 &vk,
 &proof,
 &inputs,
 );This uses proofandinputsthat Verifier got from the Prover.cautionDepending on the application, the Verifier may need to perform some checks on inputsvalue before using it inverify. For example, make sure that values included ininputsare the ones that Verifier expects.This is highly application specific, therefore we don't show this in code above. 
Both setup and prove will call the circuit function that you pass to them
to build the circuit and extract the information they need from it.
R1CS Constraints
R1CS constraints in fawkes-crypto are described with a function of the same type as in Plonk (presented in the previous section):
circuit: Fn(Signal<BuildCS<Fr>>, Signal<BuildCS<Fr>>)
To have your circuit proven, you perform the following steps.
- 
Generate setup parameters. This is trusted setup, it must be performed by a trusted party or in an MPC protocol. use fawkes_crypto::backend::bellman_groth16::{
 engines::Bn256,
 setup::setup
 }
 // The Bn256 is one of the engines fawkes-crypto provides. See
 // fawkes_crypto::backend::bellman_groth16 for more options.
 let params = setup::<Bn256, _, _, _>(circuit);
- 
Prove the circuit.use fawkes_crypto::backend::bellman_groth16::prover;
 let (inputs, proof) = prover::prove(¶ms, &pub, &sec, circuit);
- 
Verify the circuit.use fawkes_crypto::backend::bellman_groth16::verifier;
 let res = verifier::verify(¶ms.get_vk(), &snark_proof, &inputs);
Like in Plonk which we describe in the previous section, setup from step 1
shown above must be performed by a trusted party. Neither Prover nor Verifier
should be allowed to tamper with it. And in step 3, Verifier must validate the
contents of inputs (application-dependent).
The API for R1CS constraints is quite similar to Plonk. The main differences are:
- 
Setup is called differently. The available setup parameters and the list of engines are different due to R1CS and Plonk working over different backends and polynomial commitment schemes (which imposes its own restrictions on the fields over which your constraints are applied). 
- 
The methods of trait CSare slightly different. Plonk'sCSprovidesenforce_mulandenforce_addwhile R1CS has onlyenforcefor enforcing multiplications (equivalent toenforce_mul). This is due to R1CS allowing linear operations for free, with no constraint overhead, so you can just create freshCNums whenever you need additions. In other words, when you dolet a: CNum<CS> = CNum<_>::alloc(rcs, …);
 let b: CNum<CS> = CNum<_>::alloc(rcs, …);
 let c: CNum<CS> = a + b;in Plonk, this causes a call to rcs.borrow_mut().enforce_add(&a, &b, &c)creating an extra constraint inrcs. In R1CS, the same code will not modifyrcsin any way — all the information aboutc's relation toaandbwill be stored insidecitself.
Cicuits Library
Fawkes-crypto has a few useful circuits defined for the programmer to import when building their Constraint Systems.
- 
circuit::bitifydefines circuits for bit-decomposition ofCNum<CS>values intoVec<CBool<CS>>as well as circuits for comparingCNum<CS>values for inequality with and .
- 
circuit::eccdefines curcuits for elliptic-curve operations.
- 
circuit::poseidonandcircuit::eddsaposeidonimplement the Poseidon hash.
- 
circuit::mux3implements Pedersen hash.