ivc/lib.rs
1//! This crate provides a circuit to achieve Incremental Verifiable
2//! Computation (IVC) based on a variant of the folding scheme described in the
3//! paper [Nova](https://eprint.iacr.org/2021/370.pdf). For the rest of the
4//! document, we do suppose that the curve is BN254.
5//!
6//! Note that in the rest of this document, we will mix the terms
7//! "relaxation/relaxed" with "homogeneisation/homogeneous" polynomials. The
8//! reason is explained in the [folding] library. The term "running relaxed
9//! instance" can be rephrased as "an accumulator of evaluations of the
10//! homogeneised polynomials describing the computation and commitments to them".
11//!
12//! ## A recap of the Nova IVC scheme
13//!
14//! The [Nova paper](https://eprint.iacr.org/2021/370.pdf) describes a IVC
15//! scheme to be used by the folding scheme described in the same paper.
16//!
17//! The IVC scheme uses different notations for different concepts:
18//! - `F` is the function to be computed. In our codebase, we call it the
19//! "application circuit".
20//! - `F'` is the augmented function with the verifier computation. In our
21//! codebase, we call it the "IVC circuit + APP circuit", or also the "joint
22//! circuit".
23//! - `U_i` is the accumulator of the function `F'` up to step `i`. For clarity,
24//! we will sometimes use the notation `acc_i` to denote the accumulator. The
25//! accumulator in the [folding] library is called the "left instance". It is a
26//! "running relaxed instance".
27//! - `u_i` is the current instance of the function `F'` to be folded into the
28//! accumulator. In the [folding] library, it is called the "right instance".
29//! - `U_(i + 1)` is the accumulator of the function `F'` up to step `i + 1`,
30//! i.e. the accumulation of `U_i` and `u_i`. In the [folding] library, it is
31//! called the "output instance" or the "folded output". The "folded output" and
32//! the "left instance" should be seen as both accumulators, and the right
33//! instance should be seen as the difference between both.
34//!
35//! The IVC scheme described in Fig 4 works as follow, for a given step `i`:
36//! - In the circuit (or also called "the augmented function `F'`"), the prover
37//! will compute a hash of the left instance `acc_i`. It is a way to "compress"
38//! the information of the previous computation. It is described by the notation
39//! `u_i.x ?= Hash(i, z0, zi, U_i)` in the Fig 4 on the left.
40//! The value `u_i.x` is provided as a public input to the circuit. In the code,
41//! we could summarize with the following pseudo code:
42//! ```text
43//! left_commitments.into_iter().for_each(|comm| {
44//! env.process_hash(comm, LeftCommitment)
45//! })
46//! env.assert_eq(env.output_left, env.hash_state[LeftCommitment])
47//! ```
48//! - The prover will also compute the expected hash for the next iteration, by
49//! computing the hash of the output instance `U_(i + 1)`. It is described by
50//! the notation `u_(i + 1).x ?= Hash(i + 1, z0, zi, U_(i + 1))` in the Fig 4 on
51//! the right. Note that `U_(i + 1)` will be, at step `i + 1`, the new left
52//! input. The value of the hash computed at step `i` will be compared at step
53//! `i + 1`. In the code, we could summarize with the following pseudo-code:
54//! ```text
55//! output_commitments.into_iter().for_each(|comm| {
56//! env.process_hash(comm, OutputCommitment)
57//! })
58//! env.assert_eq(env.output_left, env.hash_state[OutputCommitment])
59//! ```
60//!
61//! The order of the execution is encoded in the fact the hash contains the
62//! step `i` when we check the left input and `i + 1` when we compress the
63//! folded output. The fact that the prover encodes the computation on the same
64//! initial input is encoded by adding the initial value `z0` into the hash for
65//! both hash computation.
66//! Therefore, the Nova IVC scheme encodes a **sequential** and **incremental**
67//! computation, which can be verified later using a SNARK on the accumulated
68//! instance.
69//!
70//! The job regarding the correct accumulation of the commitments and the
71//! scalars are done in the middle square, i.e. `acc_(i + 1) = NIFS.V(acc_i,
72//! u_i)`. It performs foreign field elliptic curve additions and scalars
73//! additions.
74//!
75// The documentation below is outdated. Please, update it after bifolding is
76// implemented.
77//! ## Our design
78//!
79//! The circuit is implemented using the generic interpreter provided by the
80//! crate [kimchi_msm].
81//! The particularity of the implementation is that it doesn't use a cycle of
82//! curves, aims to be as generic as possible, defer the scalar
83//! multiplications to compute them in bulk and rely on the Poseidon hash.
84//! Our argument will be mostly based on the state of the hash after we
85//! executed the whole computation.
86//!
87//! The IVC circuit is divided into different sections/sets of constraints
88//! described by multivariate polynomials, each
89//! activated by (public) selectors that are defined at setup time.
90//! The number of columns of the original computation defines the shape of the
91//! circuit as it will define the values of the selectors.
92//!
93//! First, the folding scheme we use is described in the crate
94//! [folding](folding::expressions) and is based on degree-3 polynomials.
95//! We do suppose that each constraint are reduced to degree 2, and the third
96//! degree is used to encode the aggregation of constraints.
97//!
98//! In the [Nova paper](https://eprint.iacr.org/2021/370.pdf), to provide
99//! incremental verifiable computation, the authors propose a folding scheme
100//! where the verifier has to compute the followings:
101//!
102//! ```text
103//! // Accumulation of the homogeneous value `u`:
104//! u'' = u + r u'
105//! // Accumulation of all the PIOP challenges:
106//! for each challenge c_i:
107//! c_i'' = c_i + r c_i'
108//! for each alpha_i (aggregation of constraints):
109//! alpha_i'' = alpha_i + r alpha_i'
110//! // Accumulation of the blinders for the commitment:
111//! blinder'' = blinder + r + r^2 + r^3 blinder'
112//! // Accumulation of the error terms (scalar multiplication)
113//! E = E1 + r T0 + r^2 T1 + r^3 E2
114//! // Randomized accumulation of the instance commitments (scalar multiplication)
115//! for i in 0..N_COLUMNS
116//! (C_i)_O = (C_i)_L + r (C_i)_R
117//! ```
118//!
119//! The accumulation of the challenges, the homogeneous value and the blinders
120//! are done trivially as they are scalar field values.
121//! After that, the verifier has to perform foreign field ellictic curve
122//! additions and scalar multiplications `(r T0)`, `(r^2 T1)`, `(r^3 E2)` and
123//! `(r (C_i)_R)` for each column.
124//!
125//! First, we decide to defer the computations of
126//! the scalar multiplications, and we reduce the verifier work to compute only
127//! the foreign field elliptic curve additions. Therefore, the verifier has
128//! access already to the
129//! result of `r T0`, `r^2 T1`, `r^3 E2` and `r (C_i)_R`, and must only perform the
130//! addition. We call the commitments `r T0` the "scaled" commitment to `T0`, and
131//! the same for the others. The commitments `(C_i)_L`, `(C_i)_R` and `(C_i)_O` are
132//! called the "instance commitments".
133//!
134//! To perform foreign field elliptic curve addition, we split the commitments
135//! into 17 chunks of 15 bits and use additive lookups as described in
136//! [kimchi_msm::logup]. These 17 chunks will be used later to compute the
137//! scalar multiplications using a variant of the scheme described in [the MSM
138//! RFC](https://github.com/o1-labs/rfcs/blob/main/0013-efficient-msms-for-non-native-pickles-verification.md)
139//!
140//! The first component of our circuit is a hash function.
141//! We decided to use the Poseidon hash function, and implemented a generic one
142//! using the generic interpreter in [crate::poseidon_8_56_5_3_2]. The Poseidon
143//! instance we decided to go with is the traditional full/partial rounds. For a
144//! security of 128 bits, a substitution box of 5 and a state of 3 elements, we
145//! need 8 full rounds and 56 partial rounds for the scalar field BN254.
146//! Therefore, we can "absorb" 2 field elements per row (the third element is
147//! kept as a buffer in the Sponge construction).
148//!
149//! The cost of a single Poseidon hash in terms of constraints is 432
150//! constraints, and requires 435 columns, in addition to 192 round constants
151//! considered as constants "selectors" in our constraints.
152//! Note that having 432 constraints per Poseidon hash means that we must also
153//! have 432 constraints to accumulate the challenges used to combine the
154//! constraints. It is worth noting that these alphas are the same for all
155//! poseidon hashes.
156//! Combining constraints is done by using another selector, on a single row,
157//! see below (TODO).
158//! Note that the columns used by the poseidon hash must also be aggregated
159//! while running the IVC.
160//!
161//! The Poseidon hash is used to absorb all the instance commitments, the
162//! challenges and the scaled commitments.
163//! In our circuit, we first start by absorbing all the elliptic curve points. A
164//! particularity is that we will use different Poseidon instances for each
165//! "side" of the addition. For each point, we do assume (*at the moment*) that
166//! each point is encoded as 2 field elements in the field of the circuit
167//! (FIXME: there is negligeable probability that it wraps over, as the
168//! coordinates are in the base field).
169// We will change this soon, by absorbing the coordinate x, and the sign of y.
170// If we compute on one row the hash and on the next row the ECADD, the sign of
171// y can be computed on the ECADD row, and accessed by the poseidon constraints.
172//! For a given set of coordinates `(x, y)`, and by supposing an initial state of
173//! our permutation `(s0, s1, s2)`, we will compute on a single row the absorbtion
174//! of `(x, y)`, which consists of updating the state `(s0, s1, s2)` to
175//! `(s0 + x, s1 + y, s2)`.
176//! For each side, we will initialize a new Poseidon state, and we will keep
177//! absorbing each column of the circuit.
178//!
179//! We end up with the following shape:
180//! ```text
181//! | q_poseidon | s0 | s1 | s2 | s0 + x | s1 + y | ... | s0' | s1' | s2' | side |
182//! ```
183//! where `(s0', s1', s2')` is the final state of the Poseidon permutation after
184//! the execution of all the rounds, and `q_poseidon` will be a (public
185//! selector) that will be set to `1` on the row that Poseidon will need to be
186//! executed, `0` otherwise.
187//!
188//! ## IVC circuit: base case
189//!
190//! The base case of the IVC circuit is supported by multiplying each constraint
191//! by a fold_iteration column. As it is `0` for the base case, the whole
192//! circuit is "turned off".
193//! We do not want to keep this in the final version, and aim to replace it with
194//! a method that does not increase the degree of the constraints by one. The
195//! column value is also under-constrained for now.
196//!
197//! The code for the base case is handled in
198//! [crate::ivc::interpreter::ivc_circuit_base_case] and the corresponding
199//! constraints are in [crate::ivc::constraints::constrain_ivc]. The fold
200//! iteration column is set at each row by each process_* function in the
201//! interpreter.
202
203pub mod expr_eval;
204pub mod ivc;
205pub mod plonkish_lang;
206
207/// Poseidon hash function with 55 full rounds, 0 partial rounds, sbox 7, a
208/// state of 3 elements and constraints of degree 2
209pub mod poseidon_55_0_7_3_2;
210
211/// Poseidon hash function with 55 full rounds, 0 partial rounds, sbox 7,
212/// a state of 3 elements and constraints of degree 7
213pub mod poseidon_55_0_7_3_7;
214
215/// Poseidon hash function with 8 full rounds, 56 partial rounds, sbox 5, a
216/// state of 3 elements and constraints of degree 2
217pub mod poseidon_8_56_5_3_2;
218
219/// Poseidon parameters for 55 full rounds, 0 partial rounds, sbox 7, a state of
220/// 3 elements
221pub mod poseidon_params_55_0_7_3;
222
223pub mod prover;
224pub mod verifier;