folding/
instance_witness.rs

1//! This module defines a list of traits and structures that are used by the
2//! folding scheme.
3//! The folding library is built over generic traits like [Instance] and
4//! [Witness] that defines the NP relation R.
5//!
6//! This module describes 3 different types of instance/witness pairs:
7//! - [Instance] and [Witness]: the original instance and witness. These are the
8//!   ones that the user must provide.
9//! - [ExtendedInstance] and [ExtendedWitness]: the instance and witness
10//!   extended by quadraticization.
11//! - [RelaxedInstance] and [RelaxedWitness]: the instance and witness related
12//!   to the relaxed/homogeneous polynomials.
13//!
14//! Note that [Instance], [ExtendedInstance] and [RelaxedInstance] are
15//! supposed to be used to encapsulate the public inputs and challenges. It is
16//! the common information the prover and verifier have.
17//! [Witness], [ExtendedWitness] and [RelaxedWitness] are supposed to be used
18//! to encapsulate the private inputs. For instance, it is the evaluations of
19//! the polynomials.
20//!
21//! A generic trait [Foldable] is defined to combine two objects of the same
22//! type using a challenge.
23
24// FIXME: for optimisation, as values are not necessarily Fp elements and are
25// relatively small, we could get rid of the scalar field objects, and only use
26// bigint where we only apply the modulus when needed.
27
28use crate::{Alphas, Evals};
29use ark_ff::{Field, One};
30use poly_commitment::commitment::{CommitmentCurve, PolyComm};
31use std::collections::BTreeMap;
32
33pub trait Foldable<F: Field> {
34    /// Combine two objects 'a' and 'b' into a new object using the challenge.
35    // FIXME: rename in fold2
36    fn combine(a: Self, b: Self, challenge: F) -> Self;
37}
38
39pub trait Instance<G: CommitmentCurve>: Sized + Foldable<G::ScalarField> {
40    /// This method returns the scalars and commitments that must be absorbed by
41    /// the sponge. It is not supposed to do any absorption itself, and the user
42    /// is responsible for calling the sponge absorb methods with the elements
43    /// returned by this method.
44    /// When called on a RelaxedInstance, elements will be returned in the
45    /// following order, for given instances L and R
46    ///
47    /// ```text
48    /// scalar = L.to_absorb().0 | L.u | R.to_absorb().0 | R.u
49    /// points_l = L.to_absorb().1 | L.extended | L.error // where extended is the commitments to the extra columns
50    /// points_r = R.to_absorb().1 | R.extended | R.error // where extended is the commitments to the extra columns
51    /// t_0 and t_1 first and second error terms
52    /// points = points_l | points_r | t_0 | t_1
53    /// ```
54    ///
55    /// A user implementing the IVC circuit should absorb the elements in the
56    /// following order:
57    ///
58    /// ```text
59    /// sponge.absorb_fr(scalar); // absorb the scalar elements
60    /// sponge.absorb_g(points); // absorb the commitments
61    /// ```
62    ///
63    /// This is the order used by the folding library in the method
64    /// `fold_instance_witness_pair`.
65    /// From there, a challenge can be coined using:
66    ///
67    /// ```text
68    /// let challenge_r = sponge.challenge();
69    /// ```
70    fn to_absorb(&self) -> (Vec<G::ScalarField>, Vec<G>);
71
72    /// Returns the alphas values for the instance
73    fn get_alphas(&self) -> &Alphas<G::ScalarField>;
74
75    /// Return the blinder that can be used while committing to polynomials.
76    fn get_blinder(&self) -> G::ScalarField;
77}
78
79pub trait Witness<G: CommitmentCurve>: Sized + Foldable<G::ScalarField> {}
80
81// -- Structures that consist of extending the original instance and witness
82// -- with the extra columns added by quadraticization.
83
84impl<G: CommitmentCurve, W: Witness<G>> ExtendedWitness<G, W> {
85    /// This method returns an extended witness which is defined as the witness itself,
86    /// followed by an empty BTreeMap.
87    /// The map will be later filled by the quadraticization witness generator.
88    fn extend(witness: W) -> ExtendedWitness<G, W> {
89        let extended = BTreeMap::new();
90        ExtendedWitness { witness, extended }
91    }
92}
93
94impl<G: CommitmentCurve, I: Instance<G>> ExtendedInstance<G, I> {
95    /// This method returns an extended instance which is defined as the instance itself,
96    /// followed by an empty vector.
97    fn extend(instance: I) -> ExtendedInstance<G, I> {
98        ExtendedInstance {
99            instance,
100            extended: vec![],
101        }
102    }
103}
104
105// -- Extended witness
106/// This structure represents a witness extended with extra columns that are
107/// added by quadraticization
108#[derive(Clone, Debug)]
109pub struct ExtendedWitness<G: CommitmentCurve, W: Witness<G>> {
110    /// This is the original witness, without quadraticization
111    pub witness: W,
112    /// Extra columns added by quadraticization to lower the degree of
113    /// expressions to 2
114    pub extended: BTreeMap<usize, Evals<G::ScalarField>>,
115}
116
117impl<G: CommitmentCurve, W: Witness<G>> Foldable<G::ScalarField> for ExtendedWitness<G, W> {
118    fn combine(a: Self, b: Self, challenge: <G>::ScalarField) -> Self {
119        let Self {
120            witness: witness1,
121            extended: ex1,
122        } = a;
123        let Self {
124            witness: witness2,
125            extended: ex2,
126        } = b;
127        // We fold the original witness
128        let witness = W::combine(witness1, witness2, challenge);
129        // And we fold the columns created by quadraticization.
130        // W <- W1 + c W2
131        let extended = ex1
132            .into_iter()
133            .zip(ex2)
134            .map(|(a, b)| {
135                let (i, mut evals) = a;
136                assert_eq!(i, b.0);
137                evals
138                    .evals
139                    .iter_mut()
140                    .zip(b.1.evals)
141                    .for_each(|(a, b)| *a += b * challenge);
142                (i, evals)
143            })
144            .collect();
145        Self { witness, extended }
146    }
147}
148
149impl<G: CommitmentCurve, W: Witness<G>> Witness<G> for ExtendedWitness<G, W> {}
150
151impl<G: CommitmentCurve, W: Witness<G>> ExtendedWitness<G, W> {
152    pub(crate) fn add_witness_evals(&mut self, i: usize, evals: Evals<G::ScalarField>) {
153        self.extended.insert(i, evals);
154    }
155
156    /// Return true if the no extra columns are added by quadraticization
157    ///
158    /// Can be used to know if the extended witness columns are already
159    /// computed, to avoid overriding them
160    pub fn is_extended(&self) -> bool {
161        !self.extended.is_empty()
162    }
163}
164
165// -- Extended instance
166/// An extended instance is an instance that has been extended with extra
167/// columns by quadraticization.
168/// The original instance is stored in the `instance` field.
169/// The extra columns are stored in the `extended` field.
170/// For instance, if the original instance has `n` columns, and the system is
171/// described by a degree 3 polynomial, an additional column will be added, and
172/// `extended` will contain `1` commitment.
173// FIXME: We should forbid cloning, for memory footprint.
174#[derive(PartialEq, Eq, Clone)]
175pub struct ExtendedInstance<G: CommitmentCurve, I: Instance<G>> {
176    /// The original instance.
177    pub instance: I,
178    /// Commitments to the extra columns added by quadraticization
179    pub extended: Vec<PolyComm<G>>,
180}
181
182impl<G: CommitmentCurve, I: Instance<G>> Foldable<G::ScalarField> for ExtendedInstance<G, I> {
183    fn combine(a: Self, b: Self, challenge: <G>::ScalarField) -> Self {
184        let Self {
185            instance: instance1,
186            extended: ex1,
187        } = a;
188        let Self {
189            instance: instance2,
190            extended: ex2,
191        } = b;
192        // Combining first the existing commitments (i.e. not the one added by
193        // quadraticization)
194        // They are supposed to be blinded already
195        let instance = I::combine(instance1, instance2, challenge);
196        // For each commitment, compute
197        // Comm(W) + c * Comm(W')
198        let extended = ex1
199            .into_iter()
200            .zip(ex2)
201            .map(|(a, b)| &a + &b.scale(challenge))
202            .collect();
203        Self { instance, extended }
204    }
205}
206
207impl<G: CommitmentCurve, I: Instance<G>> Instance<G> for ExtendedInstance<G, I> {
208    /// Return the elements to be absorbed by the sponge
209    ///
210    /// The commitments to the additional columns created by quadriticization
211    /// are appended to the existing commitments of the initial instance
212    /// to be absorbed. The scalars are unchanged.
213    fn to_absorb(&self) -> (Vec<G::ScalarField>, Vec<G>) {
214        let mut elements = self.instance.to_absorb();
215        let extended_commitments = self.extended.iter().map(|commit| {
216            assert_eq!(commit.len(), 1);
217            commit.get_first_chunk()
218        });
219        elements.1.extend(extended_commitments);
220        elements
221    }
222
223    fn get_alphas(&self) -> &Alphas<G::ScalarField> {
224        self.instance.get_alphas()
225    }
226
227    /// Returns the blinder value. It is the same as the one of the original
228    fn get_blinder(&self) -> G::ScalarField {
229        self.instance.get_blinder()
230    }
231}
232
233// -- "Relaxed"/"Homogenized" structures
234
235/// A relaxed instance is an instance that has been relaxed by the folding scheme.
236/// It contains the original instance, extended with the columns added by
237/// quadriticization, the scalar `u` and a commitment to the
238/// slack/error term.
239/// See page 15 of [Nova](https://eprint.iacr.org/2021/370.pdf).
240// FIXME: We should forbid cloning, for memory footprint.
241#[derive(PartialEq, Eq, Clone)]
242pub struct RelaxedInstance<G: CommitmentCurve, I: Instance<G>> {
243    /// The original instance, extended with the columns added by
244    /// quadriticization
245    pub extended_instance: ExtendedInstance<G, I>,
246    /// The scalar `u` that is used to homogenize the polynomials
247    pub u: G::ScalarField,
248    /// The commitment to the error term, introduced when homogenizing the
249    /// polynomials
250    pub error_commitment: PolyComm<G>,
251    /// Blinder used for the commitments to the cross terms
252    pub blinder: G::ScalarField,
253}
254
255impl<G: CommitmentCurve, I: Instance<G>> RelaxedInstance<G, I> {
256    /// Returns the elements to be absorbed by the sponge
257    ///
258    /// The scalar elements of the are appended with the scalar `u` and the
259    /// commitments are appended by the commitment to the error term.
260    pub fn to_absorb(&self) -> (Vec<G::ScalarField>, Vec<G>) {
261        let mut elements = self.extended_instance.to_absorb();
262        elements.0.push(self.u);
263        assert_eq!(self.error_commitment.len(), 1);
264        elements.1.push(self.error_commitment.get_first_chunk());
265        elements
266    }
267
268    /// Provides access to commitments to the extra columns added by
269    /// quadraticization
270    pub fn get_extended_column_commitment(&self, i: usize) -> Option<&PolyComm<G>> {
271        self.extended_instance.extended.get(i)
272    }
273
274    /// Combining the commitments of each instance and adding the cross terms
275    /// into the error term.
276    /// This corresponds to the computation `E <- E1 - c T1 - c^2 T2 + c^3 E2`.
277    /// As we do support folding of degree 3, we have two cross terms `T1` and
278    /// `T2`.
279    /// For more information, see the [top-level
280    /// documentation](crate::expressions).
281    pub(super) fn combine_and_sub_cross_terms(
282        a: Self,
283        b: Self,
284        challenge: <G>::ScalarField,
285        cross_terms: &[PolyComm<G>; 2],
286    ) -> Self {
287        // Compute E1 + c^3 E2 and all other folding of commitments. The
288        // resulting error commitment is stored in res.commitment.
289        let mut res = Self::combine(a, b, challenge);
290        let [t0, t1] = cross_terms;
291        // Eq 4, page 15 of the Nova paper
292        // Computing (E1 + c^3 E2) - c T1 - c^2 T2
293        res.error_commitment =
294            &res.error_commitment - (&(&t0.scale(challenge) + &t1.scale(challenge.square())));
295        res
296    }
297}
298
299/// A relaxed instance can be folded.
300impl<G: CommitmentCurve, I: Instance<G>> Foldable<G::ScalarField> for RelaxedInstance<G, I> {
301    /// Combine two relaxed instances into a new relaxed instance.
302    fn combine(a: Self, b: Self, challenge: <G>::ScalarField) -> Self {
303        // We do support degree 3 folding, therefore, we must compute:
304        // E <- E1 - (c T1 + c^2 T2) + c^3 E2
305        // (page 15, eq 3 of the Nova paper)
306        // The term T1 and T2 are the cross terms
307        let challenge_square = challenge * challenge;
308        let challenge_cube = challenge_square * challenge;
309        let RelaxedInstance {
310            extended_instance: extended_instance_1,
311            u: u1,
312            error_commitment: e1,
313            blinder: blinder1,
314        } = a;
315        let RelaxedInstance {
316            extended_instance: extended_instance_2,
317            u: u2,
318            error_commitment: e2,
319            blinder: blinder2,
320        } = b;
321        // We simply fold the blinders
322        //                 = 1        = 1
323        // r_E <- r_E1 + c r_T1 + c^2 r_T2 + c^3 r_E2
324        let blinder = blinder1 + challenge + challenge_square + challenge_cube * blinder2;
325        let extended_instance =
326            <ExtendedInstance<G, I>>::combine(extended_instance_1, extended_instance_2, challenge);
327        // Combining the challenges
328        // eq 3, page 15 of the Nova paper
329        let u = u1 + u2 * challenge;
330        // We do have 2 cross terms as we have degree 3 folding
331        // e1 + c^3 e^2
332        let error_commitment = &e1 + &e2.scale(challenge_cube);
333        RelaxedInstance {
334            // I <- I1 + c I2
335            extended_instance,
336            // u <- u1 + c u2
337            u,
338            // E <- E1 - (c T1 + c^2 T2) + c^3 E2
339            error_commitment,
340            blinder,
341        }
342    }
343}
344
345// -- Relaxed witnesses
346#[derive(Clone, Debug)]
347pub struct RelaxedWitness<G: CommitmentCurve, W: Witness<G>> {
348    /// The original witness, extended with the columns added by
349    /// quadriticization.
350    pub extended_witness: ExtendedWitness<G, W>,
351    /// The error vector, introduced when homogenizing the polynomials.
352    /// For degree 3 folding, it is `E1 - c T1 - c^2 T2 + c^3 E2`
353    pub error_vec: Evals<G::ScalarField>,
354}
355
356impl<G: CommitmentCurve, W: Witness<G>> RelaxedWitness<G, W> {
357    /// Combining the existing error terms with the cross-terms T1 and T2 given
358    /// as parameters.
359    ///                 existing error terms      cross terms
360    ///                /--------------------\   /-------------\
361    /// The result is `   E1    +     c^3 E2  - (c T1 + c^2 T2)`
362    /// We do have two cross terms as we work with homogeneous polynomials of
363    /// degree 3. The value is saved into the field `error_vec` of the relaxed
364    /// witness.
365    /// This corresponds to the step 4, page 15 of the Nova paper, but with two
366    /// cross terms (T1 and T2), see [top-level
367    /// documentation](crate::expressions).
368    pub(super) fn combine_and_sub_cross_terms(
369        a: Self,
370        b: Self,
371        challenge: <G>::ScalarField,
372        cross_terms: [Vec<G::ScalarField>; 2],
373    ) -> Self {
374        // Computing E1 + c^3 E2
375        let mut res = Self::combine(a, b, challenge);
376
377        // Now substracting the cross terms
378        let [e0, e1] = cross_terms;
379
380        for (res, (e0, e1)) in res
381            .error_vec
382            .evals
383            .iter_mut()
384            .zip(e0.into_iter().zip(e1.into_iter()))
385        {
386            // FIXME: for optimisation, use inplace operators. Allocating can be
387            // costly
388            // should be the same as e0 * c + e1 * c^2
389            *res -= ((e1 * challenge) + e0) * challenge;
390        }
391        res
392    }
393
394    /// Provides access to the extra columns added by quadraticization
395    pub fn get_extended_column(&self, i: &usize) -> Option<&Evals<G::ScalarField>> {
396        self.extended_witness.extended.get(i)
397    }
398}
399
400/// A relaxed/homogenized witness can be folded.
401impl<G: CommitmentCurve, W: Witness<G>> Foldable<G::ScalarField> for RelaxedWitness<G, W> {
402    fn combine(a: Self, b: Self, challenge: <G>::ScalarField) -> Self {
403        let RelaxedWitness {
404            extended_witness: a,
405            error_vec: mut e1,
406        } = a;
407        let RelaxedWitness {
408            extended_witness: b,
409            error_vec: e2,
410        } = b;
411        // We combine E1 and E2 into E1 + c^3 E2 as we do have two cross-terms
412        // with degree 3 folding
413        let challenge_cube = (challenge * challenge) * challenge;
414        let extended_witness = <ExtendedWitness<G, W>>::combine(a, b, challenge);
415        for (a, b) in e1.evals.iter_mut().zip(e2.evals.into_iter()) {
416            *a += b * challenge_cube;
417        }
418        let error_vec = e1;
419        RelaxedWitness {
420            extended_witness,
421            error_vec,
422        }
423    }
424}
425
426// -- Relaxable instance
427pub trait RelaxableInstance<G: CommitmentCurve, I: Instance<G>> {
428    fn relax(self) -> RelaxedInstance<G, I>;
429}
430
431impl<G: CommitmentCurve, I: Instance<G>> RelaxableInstance<G, I> for I {
432    /// This method takes an Instance and a commitment to zero and extends the
433    /// instance, returning a relaxed instance which is composed by the extended
434    /// instance, the scalar one, and the error commitment which is set to the
435    /// commitment to zero.
436    fn relax(self) -> RelaxedInstance<G, Self> {
437        let extended_instance = ExtendedInstance::extend(self);
438        let blinder = extended_instance.instance.get_blinder();
439        let u = G::ScalarField::one();
440        let error_commitment = PolyComm::new(vec![G::zero()]);
441        RelaxedInstance {
442            extended_instance,
443            u,
444            error_commitment,
445            blinder,
446        }
447    }
448}
449
450/// A relaxed instance is trivially relaxable.
451impl<G: CommitmentCurve, I: Instance<G>> RelaxableInstance<G, I> for RelaxedInstance<G, I> {
452    fn relax(self) -> RelaxedInstance<G, I> {
453        self
454    }
455}
456
457/// Trait to make a witness relaxable/homogenizable
458pub trait RelaxableWitness<G: CommitmentCurve, W: Witness<G>> {
459    fn relax(self, zero_poly: &Evals<G::ScalarField>) -> RelaxedWitness<G, W>;
460}
461
462impl<G: CommitmentCurve, W: Witness<G>> RelaxableWitness<G, W> for W {
463    /// This method takes a witness and a vector of evaluations to the zero
464    /// polynomial, returning a relaxed witness which is composed by the
465    /// extended witness and the error vector that is set to the zero
466    /// polynomial.
467    fn relax(self, zero_poly: &Evals<G::ScalarField>) -> RelaxedWitness<G, Self> {
468        let extended_witness = ExtendedWitness::extend(self);
469        RelaxedWitness {
470            extended_witness,
471            error_vec: zero_poly.clone(),
472        }
473    }
474}
475
476impl<G: CommitmentCurve, W: Witness<G>> RelaxableWitness<G, W> for RelaxedWitness<G, W> {
477    fn relax(self, _zero_poly: &Evals<G::ScalarField>) -> RelaxedWitness<G, W> {
478        self
479    }
480}
481
482pub trait RelaxablePair<G: CommitmentCurve, I: Instance<G>, W: Witness<G>> {
483    fn relax(
484        self,
485        zero_poly: &Evals<G::ScalarField>,
486    ) -> (RelaxedInstance<G, I>, RelaxedWitness<G, W>);
487}
488
489impl<G, I, W> RelaxablePair<G, I, W> for (I, W)
490where
491    G: CommitmentCurve,
492    I: Instance<G> + RelaxableInstance<G, I>,
493    W: Witness<G> + RelaxableWitness<G, W>,
494{
495    fn relax(
496        self,
497        zero_poly: &Evals<G::ScalarField>,
498    ) -> (RelaxedInstance<G, I>, RelaxedWitness<G, W>) {
499        let (instance, witness) = self;
500        (
501            RelaxableInstance::relax(instance),
502            RelaxableWitness::relax(witness, zero_poly),
503        )
504    }
505}
506
507impl<G, I, W> RelaxablePair<G, I, W> for (RelaxedInstance<G, I>, RelaxedWitness<G, W>)
508where
509    G: CommitmentCurve,
510    I: Instance<G> + RelaxableInstance<G, I>,
511    W: Witness<G> + RelaxableWitness<G, W>,
512{
513    fn relax(
514        self,
515        _zero_poly: &Evals<G::ScalarField>,
516    ) -> (RelaxedInstance<G, I>, RelaxedWitness<G, W>) {
517        self
518    }
519}