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}