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