folding/
lib.rs

1//! This library implements basic components to fold computations expressed as
2//! multivariate polynomials of any degree. It is based on the "folding scheme"
3//! described in the [Nova](https://eprint.iacr.org/2021/370.pdf) paper.
4//! It implements different components to achieve it:
5//! - [quadraticization]: a submodule to reduce multivariate polynomials
6//!   to degree `2`.
7//! - [decomposable_folding]: a submodule to "parallelize" folded
8//!   computations.
9//!
10//! Examples can be found in the directory `examples`.
11//!
12//! The folding library is meant to be used in harmony with the library `ivc`.
13//! To use the library, the user has to define first a "folding configuration"
14//! described in the trait [FoldingConfig].
15//! After that, the user can provide folding compatible expressions and build a
16//! folding scheme [FoldingScheme]. The process is described in the module
17//! [expressions].
18// TODO: the documentation above might need more descriptions.
19
20use ark_ec::AffineRepr;
21use ark_ff::{Field, One, Zero};
22use ark_poly::{EvaluationDomain, Evaluations, Radix2EvaluationDomain};
23use core::{fmt::Debug, hash::Hash, iter::successors};
24use error_term::{compute_error, ExtendedEnv};
25use expressions::{folding_expression, FoldingColumnTrait, IntegratedFoldingExpr};
26use instance_witness::{Foldable, RelaxableInstance, RelaxablePair};
27use kimchi::circuits::gate::CurrOrNext;
28use mina_poseidon::FqSponge;
29use poly_commitment::{commitment::CommitmentCurve, PolyComm, SRS};
30use quadraticization::ExtendedWitnessGenerator;
31use std::{
32    rc::Rc,
33    sync::atomic::{AtomicUsize, Ordering},
34};
35
36// Make available outside the crate to avoid code duplication
37pub use error_term::Side;
38pub use expressions::{ExpExtension, FoldingCompatibleExpr};
39pub use instance_witness::{Instance, RelaxedInstance, RelaxedWitness, Witness};
40
41pub mod columns;
42pub mod decomposable_folding;
43
44mod error_term;
45
46pub mod eval_leaf;
47pub mod expressions;
48pub mod instance_witness;
49pub mod quadraticization;
50pub mod standard_config;
51
52/// Define the different structures required for the examples (both internal and
53/// external)
54pub mod checker;
55
56// Simple type alias as ScalarField/BaseField is often used. Reduce type
57// complexity for clippy.
58// Should be moved into FoldingConfig, but associated type defaults are unstable
59// at the moment.
60type ScalarField<C> = <<C as FoldingConfig>::Curve as AffineRepr>::ScalarField;
61type BaseField<C> = <<C as FoldingConfig>::Curve as AffineRepr>::BaseField;
62
63// 'static seems to be used for expressions. Can we get rid of it?
64pub trait FoldingConfig: Debug + 'static {
65    type Column: FoldingColumnTrait + Debug + Eq + Hash;
66
67    // in case of using docomposable folding, if not it can be just ()
68    type Selector: Clone + Debug + Eq + Hash + Copy + Ord + PartialOrd;
69
70    /// The type of an abstract challenge that can be found in the expressions
71    /// provided as constraints.
72    type Challenge: Clone + Copy + Debug + Eq + Hash;
73
74    /// The target curve used by the polynomial commitment
75    type Curve: CommitmentCurve;
76
77    /// The SRS used by the polynomial commitment. The SRS is used to commit to
78    /// the additional columns that are added by the quadraticization.
79    type Srs: SRS<Self::Curve>;
80
81    /// For Plonk, it will be the commitments to the polynomials and the challenges
82    type Instance: Instance<Self::Curve> + Clone;
83
84    /// For PlonK, it will be the polynomials in evaluation form that we commit
85    /// to, i.e. the columns.
86    /// In the generic prover/verifier, it would be `kimchi_msm::witness::Witness`.
87    type Witness: Witness<Self::Curve> + Clone;
88
89    type Structure: Clone;
90
91    type Env: FoldingEnv<
92        <Self::Curve as AffineRepr>::ScalarField,
93        Self::Instance,
94        Self::Witness,
95        Self::Column,
96        Self::Challenge,
97        Self::Selector,
98        Structure = Self::Structure,
99    >;
100}
101
102/// Describe a folding environment.
103/// The type parameters are:
104/// - `F`: The field of the circuit/computation
105/// - `I`: The instance type, i.e the public inputs
106/// - `W`: The type of the witness, i.e. the private inputs
107/// - `Col`: The type of the column
108/// - `Chal`: The type of the challenge
109/// - `Selector`: The type of the selector
110pub trait FoldingEnv<F: Zero + Clone, I, W, Col, Chal, Selector> {
111    /// Structure which could be storing useful information like selectors, etc.
112    type Structure;
113
114    /// Creates a new environment storing the structure, instances and
115    /// witnesses.
116    fn new(structure: &Self::Structure, instances: [&I; 2], witnesses: [&W; 2]) -> Self;
117
118    /// Obtains a given challenge from the expanded instance for one side.
119    /// The challenges are stored inside the instances structs.
120    fn challenge(&self, challenge: Chal, side: Side) -> F;
121
122    /// Returns the evaluations of a given column witness at omega or zeta*omega.
123    fn col(&self, col: Col, curr_or_next: CurrOrNext, side: Side) -> &[F];
124
125    /// similar to [Self::col], but folding may ask for a dynamic selector directly
126    /// instead of just column that happens to be a selector
127    fn selector(&self, s: &Selector, side: Side) -> &[F];
128}
129
130type Evals<F> = Evaluations<F, Radix2EvaluationDomain<F>>;
131
132pub struct FoldingScheme<'a, CF: FoldingConfig> {
133    pub expression: IntegratedFoldingExpr<CF>,
134    pub srs: &'a CF::Srs,
135    pub domain: Radix2EvaluationDomain<ScalarField<CF>>,
136    pub zero_vec: Evals<ScalarField<CF>>,
137    pub structure: CF::Structure,
138    pub extended_witness_generator: ExtendedWitnessGenerator<CF>,
139    quadraticization_columns: usize,
140}
141
142impl<'a, CF: FoldingConfig> FoldingScheme<'a, CF> {
143    pub fn new(
144        constraints: Vec<FoldingCompatibleExpr<CF>>,
145        srs: &'a CF::Srs,
146        domain: Radix2EvaluationDomain<ScalarField<CF>>,
147        structure: &CF::Structure,
148    ) -> (Self, FoldingCompatibleExpr<CF>) {
149        let (expression, extended_witness_generator, quadraticization_columns) =
150            folding_expression(constraints);
151        let zero = <ScalarField<CF>>::zero();
152        let evals = core::iter::repeat(zero).take(domain.size()).collect();
153        let zero_vec = Evaluations::from_vec_and_domain(evals, domain);
154        let final_expression = expression.clone().final_expression();
155        let scheme = Self {
156            expression,
157            srs,
158            domain,
159            zero_vec,
160            structure: structure.clone(),
161            extended_witness_generator,
162            quadraticization_columns,
163        };
164        (scheme, final_expression)
165    }
166
167    /// Return the number of additional columns added by quadraticization
168    pub fn get_number_of_additional_columns(&self) -> usize {
169        self.quadraticization_columns
170    }
171
172    /// This is the main entry point to fold two instances and their witnesses.
173    /// The process is as follows:
174    /// - Both pairs are relaxed.
175    /// - Both witnesses and instances are extended, i.e. all polynomials are
176    ///   reduced to degree 2 and additional constraints are added to the
177    ///   expression.
178    /// - While computing the commitments to the additional columns, the
179    ///   commitments are added into a list to absorb them into the sponge later.
180    /// - The error terms are computed and committed.
181    /// - The sponge absorbs the commitments and challenges.
182    #[allow(clippy::type_complexity)]
183    pub fn fold_instance_witness_pair<A, B, Sponge>(
184        &self,
185        a: A,
186        b: B,
187        fq_sponge: &mut Sponge,
188    ) -> FoldingOutput<CF>
189    where
190        A: RelaxablePair<CF::Curve, CF::Instance, CF::Witness>,
191        B: RelaxablePair<CF::Curve, CF::Instance, CF::Witness>,
192        Sponge: FqSponge<BaseField<CF>, CF::Curve, ScalarField<CF>>,
193    {
194        let a = a.relax(&self.zero_vec);
195        let b = b.relax(&self.zero_vec);
196
197        let u = (a.0.u, b.0.u);
198
199        let (left_instance, left_witness) = a;
200        let (right_instance, right_witness) = b;
201        let env = ExtendedEnv::new(
202            &self.structure,
203            [left_instance, right_instance],
204            [left_witness, right_witness],
205            self.domain,
206            None,
207        );
208        // Computing the additional columns, resulting of the quadritization
209        // process.
210        // Side-effect: commitments are added in both relaxed (extended) instance.
211        let env: ExtendedEnv<CF> =
212            env.compute_extension(&self.extended_witness_generator, self.srs);
213
214        // Computing the error terms
215        let error: [Vec<ScalarField<CF>>; 2] = compute_error(&self.expression, &env, u);
216        let error_evals = error.map(|e| Evaluations::from_vec_and_domain(e, self.domain));
217
218        // Committing to the cross terms
219        // Default blinder for committing to the cross terms
220        let blinders = PolyComm::new(vec![ScalarField::<CF>::one()]);
221        let error_commitments = error_evals
222            .iter()
223            .map(|e| {
224                self.srs
225                    .commit_evaluations_custom(self.domain, e, &blinders)
226                    .unwrap()
227                    .commitment
228            })
229            .collect::<Vec<_>>();
230        let error_commitments: [PolyComm<CF::Curve>; 2] = error_commitments.try_into().unwrap();
231
232        let error: [Vec<_>; 2] = error_evals.map(|e| e.evals);
233
234        // sanity check to verify that we only have one commitment in polycomm
235        // (i.e. domain = poly size)
236        assert_eq!(error_commitments[0].len(), 1);
237        assert_eq!(error_commitments[1].len(), 1);
238
239        let t_0 = &error_commitments[0].get_first_chunk();
240        let t_1 = &error_commitments[1].get_first_chunk();
241
242        // Absorbing the commitments into the sponge
243        let to_absorb = env.to_absorb(t_0, t_1);
244
245        fq_sponge.absorb_fr(&to_absorb.0);
246        fq_sponge.absorb_g(&to_absorb.1);
247
248        let challenge = fq_sponge.challenge();
249
250        let (
251            [relaxed_extended_left_instance, relaxed_extended_right_instance],
252            [relaxed_extended_left_witness, relaxed_extended_right_witness],
253        ) = env.unwrap();
254
255        let folded_instance = RelaxedInstance::combine_and_sub_cross_terms(
256            // FIXME: remove clone
257            relaxed_extended_left_instance.clone(),
258            relaxed_extended_right_instance.clone(),
259            challenge,
260            &error_commitments,
261        );
262
263        let folded_witness = RelaxedWitness::combine_and_sub_cross_terms(
264            relaxed_extended_left_witness,
265            relaxed_extended_right_witness,
266            challenge,
267            error,
268        );
269        FoldingOutput {
270            folded_instance,
271            folded_witness,
272            t_0: error_commitments[0].clone(),
273            t_1: error_commitments[1].clone(),
274            relaxed_extended_left_instance,
275            relaxed_extended_right_instance,
276            to_absorb,
277        }
278    }
279
280    /// Fold two relaxable instances into a relaxed instance.
281    /// It is parametrized by two different types `A` and `B` that represent
282    /// "relaxable" instances to be able to fold a normal and "already relaxed"
283    /// instance.
284    pub fn fold_instance_pair<A, B, Sponge>(
285        &self,
286        a: A,
287        b: B,
288        error_commitments: [PolyComm<CF::Curve>; 2],
289        fq_sponge: &mut Sponge,
290    ) -> RelaxedInstance<CF::Curve, CF::Instance>
291    where
292        A: RelaxableInstance<CF::Curve, CF::Instance>,
293        B: RelaxableInstance<CF::Curve, CF::Instance>,
294        Sponge: FqSponge<BaseField<CF>, CF::Curve, ScalarField<CF>>,
295    {
296        let a: RelaxedInstance<CF::Curve, CF::Instance> = a.relax();
297        let b: RelaxedInstance<CF::Curve, CF::Instance> = b.relax();
298
299        // sanity check to verify that we only have one commitment in polycomm
300        // (i.e. domain = poly size)
301        assert_eq!(error_commitments[0].len(), 1);
302        assert_eq!(error_commitments[1].len(), 1);
303
304        let to_absorb = {
305            let mut left = a.to_absorb();
306            let right = b.to_absorb();
307            left.0.extend(right.0);
308            left.1.extend(right.1);
309            left.1.extend([
310                error_commitments[0].get_first_chunk(),
311                error_commitments[1].get_first_chunk(),
312            ]);
313            left
314        };
315
316        fq_sponge.absorb_fr(&to_absorb.0);
317        fq_sponge.absorb_g(&to_absorb.1);
318
319        let challenge = fq_sponge.challenge();
320
321        RelaxedInstance::combine_and_sub_cross_terms(a, b, challenge, &error_commitments)
322    }
323
324    #[allow(clippy::type_complexity)]
325    /// Verifier of the folding scheme; returns a new folded instance,
326    /// which can be then compared with the one claimed to be the real
327    /// one.
328    pub fn verify_fold<Sponge>(
329        &self,
330        left_instance: RelaxedInstance<CF::Curve, CF::Instance>,
331        right_instance: RelaxedInstance<CF::Curve, CF::Instance>,
332        t_0: PolyComm<CF::Curve>,
333        t_1: PolyComm<CF::Curve>,
334        fq_sponge: &mut Sponge,
335    ) -> RelaxedInstance<CF::Curve, CF::Instance>
336    where
337        Sponge: FqSponge<BaseField<CF>, CF::Curve, ScalarField<CF>>,
338    {
339        let to_absorb = {
340            let mut left = left_instance.to_absorb();
341            let right = right_instance.to_absorb();
342            left.0.extend(right.0);
343            left.1.extend(right.1);
344            left.1
345                .extend([t_0.get_first_chunk(), t_1.get_first_chunk()]);
346            left
347        };
348
349        fq_sponge.absorb_fr(&to_absorb.0);
350        fq_sponge.absorb_g(&to_absorb.1);
351
352        let challenge = fq_sponge.challenge();
353
354        RelaxedInstance::combine_and_sub_cross_terms(
355            // FIXME: remove clone
356            left_instance.clone(),
357            right_instance.clone(),
358            challenge,
359            &[t_0, t_1],
360        )
361    }
362}
363
364/// Output of the folding prover
365pub struct FoldingOutput<C: FoldingConfig> {
366    /// The folded instance, containing, in particular, the result `C_l + r C_r`
367    pub folded_instance: RelaxedInstance<C::Curve, C::Instance>,
368    /// Folded witness, containing, in particular, the result of the evaluations
369    /// `W_l + r W_r`
370    pub folded_witness: RelaxedWitness<C::Curve, C::Witness>,
371    /// The error terms of degree 1, see the top-level documentation of
372    /// [crate::expressions]
373    pub t_0: PolyComm<C::Curve>,
374    /// The error terms of degree 2, see the top-level documentation of
375    /// [crate::expressions]
376    pub t_1: PolyComm<C::Curve>,
377    /// The left relaxed instance, including the potential additional columns
378    /// added by quadritization
379    pub relaxed_extended_left_instance: RelaxedInstance<C::Curve, C::Instance>,
380    /// The right relaxed instance, including the potential additional columns
381    /// added by quadritization
382    pub relaxed_extended_right_instance: RelaxedInstance<C::Curve, C::Instance>,
383    /// Elements to absorbed in IVC, in the same order as done in folding
384    pub to_absorb: (Vec<ScalarField<C>>, Vec<C::Curve>),
385}
386
387impl<C: FoldingConfig> FoldingOutput<C> {
388    #[allow(clippy::type_complexity)]
389    pub fn pair(
390        self,
391    ) -> (
392        RelaxedInstance<C::Curve, C::Instance>,
393        RelaxedWitness<C::Curve, C::Witness>,
394    ) {
395        (self.folded_instance, self.folded_witness)
396    }
397}
398
399/// Combinators that will be used to fold the constraints,
400/// called the "alphas".
401/// The alphas are exceptional, their number cannot be known ahead of time as it
402/// will be defined by folding.
403/// The values will be computed as powers in new instances, but after folding
404/// each alpha will be a linear combination of other alphas, instand of a power
405/// of other element. This type represents that, allowing to also recognize
406/// which case is present.
407#[derive(Debug, Clone)]
408pub enum Alphas<F: Field> {
409    Powers(F, Rc<AtomicUsize>),
410    Combinations(Vec<F>),
411}
412
413impl<F: Field> PartialEq for Alphas<F> {
414    fn eq(&self, other: &Self) -> bool {
415        // Maybe there's a more efficient way
416        self.clone().powers() == other.clone().powers()
417    }
418}
419
420impl<F: Field> Eq for Alphas<F> {}
421
422impl<F: Field> Foldable<F> for Alphas<F> {
423    fn combine(a: Self, b: Self, challenge: F) -> Self {
424        let a = a.powers();
425        let b = b.powers();
426        assert_eq!(a.len(), b.len());
427        let comb = a
428            .into_iter()
429            .zip(b)
430            .map(|(a, b)| a + b * challenge)
431            .collect();
432        Self::Combinations(comb)
433    }
434}
435
436impl<F: Field> Alphas<F> {
437    pub fn new(alpha: F) -> Self {
438        Self::Powers(alpha, Rc::new(AtomicUsize::from(0)))
439    }
440
441    pub fn new_sized(alpha: F, count: usize) -> Self {
442        Self::Powers(alpha, Rc::new(AtomicUsize::from(count)))
443    }
444
445    pub fn get(&self, i: usize) -> Option<F> {
446        match self {
447            Alphas::Powers(alpha, count) => {
448                let _ = count.fetch_max(i + 1, Ordering::Relaxed);
449                let i = [i as u64];
450                Some(alpha.pow(i))
451            }
452            Alphas::Combinations(alphas) => alphas.get(i).cloned(),
453        }
454    }
455
456    pub fn powers(self) -> Vec<F> {
457        match self {
458            Alphas::Powers(alpha, count) => {
459                let n = count.load(Ordering::Relaxed);
460                let alphas = successors(Some(F::one()), |last| Some(*last * alpha));
461                alphas.take(n).collect()
462            }
463            Alphas::Combinations(c) => c,
464        }
465    }
466}