sonobe/folding-schemes/src/folding/protogalaxy/folding.rs
folding-schemes/src/folding/protogalaxy/folding.rsRaw
1/// Implements the scheme described in [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
2use ark_crypto_primitives::sponge::Absorb;
3use ark_ec::{CurveGroup, Group};
4use ark_ff::PrimeField;
5use ark_poly::{
6 univariate::{DensePolynomial, SparsePolynomial},
7 DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial,
8};
9use ark_std::log2;
10use ark_std::{cfg_into_iter, Zero};
11use rayon::iter::{IntoParallelIterator, ParallelIterator};
12use std::marker::PhantomData;
13
14use super::traits::ProtoGalaxyTranscript;
15use super::utils::{all_powers, betas_star, exponential_powers};
16use super::ProtoGalaxyError;
17use super::{CommittedInstance, Witness};
18
19use crate::ccs::r1cs::R1CS;
20use crate::transcript::Transcript;
21use crate::utils::{bit::bit_decompose, vec::*};
22use crate::Error;
23
24#[derive(Clone, Debug)]
25/// Implements the protocol described in section 4 of
26/// [ProtoGalaxy](https://eprint.iacr.org/2023/1106.pdf)
27pub struct Folding<C: CurveGroup> {
28 _phantom: PhantomData<C>,
29}
30impl<C: CurveGroup> Folding<C>
31where
32 <C as Group>::ScalarField: Absorb,
33 <C as CurveGroup>::BaseField: Absorb,
34{
35 #![allow(clippy::type_complexity)]
36 /// implements the non-interactive Prover from the folding scheme described in section 4
37 pub fn prove(
38 transcript: &mut (impl Transcript<C> + ProtoGalaxyTranscript<C>),
39 r1cs: &R1CS<C::ScalarField>,
40 // running instance
41 instance: &CommittedInstance<C>,
42 w: &Witness<C::ScalarField>,
43 // incomming instances
44 vec_instances: &[CommittedInstance<C>],
45 vec_w: &[Witness<C::ScalarField>],
46 ) -> Result<
47 (
48 CommittedInstance<C>,
49 Witness<C::ScalarField>,
50 Vec<C::ScalarField>, // F_X coeffs
51 Vec<C::ScalarField>, // K_X coeffs
52 ),
53 Error,
54 > {
55 if vec_instances.len() != vec_w.len() {
56 return Err(Error::NotSameLength(
57 "vec_instances.len()".to_string(),
58 vec_instances.len(),
59 "vec_w.len()".to_string(),
60 vec_w.len(),
61 ));
62 }
63 let d = 2; // for the moment hardcoded to 2 since it only supports R1CS
64 let k = vec_instances.len();
65 let t = instance.betas.len();
66 let n = r1cs.A.n_cols;
67 if w.w.len() != n {
68 return Err(Error::NotSameLength(
69 "w.w.len()".to_string(),
70 w.w.len(),
71 "n".to_string(),
72 n,
73 ));
74 }
75 if log2(n) as usize != t {
76 return Err(Error::NotEqual);
77 }
78 if !(k + 1).is_power_of_two() {
79 return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongNumInstances(k)));
80 }
81
82 // absorb the committed instances
83 transcript.absorb_committed_instance(instance)?;
84 for ci in vec_instances.iter() {
85 transcript.absorb_committed_instance(ci)?;
86 }
87
88 let delta = transcript.get_challenge();
89 let deltas = exponential_powers(delta, t);
90
91 let f_w = eval_f(r1cs, &w.w)?;
92
93 // F(X)
94 let F_X: SparsePolynomial<C::ScalarField> =
95 calc_f_from_btree(&f_w, &instance.betas, &deltas).expect("Error calculating F[x]");
96 let F_X_dense = DensePolynomial::from(F_X.clone());
97 transcript.absorb_vec(&F_X_dense.coeffs);
98
99 let alpha = transcript.get_challenge();
100
101 // eval F(alpha)
102 let F_alpha = F_X.evaluate(&alpha);
103
104 // betas*
105 let betas_star = betas_star(&instance.betas, &deltas, alpha);
106
107 // sanity check: check that the new randomized instance (the original instance but with
108 // 'refreshed' randomness) satisfies the relation.
109 #[cfg(test)]
110 tests::check_instance(
111 r1cs,
112 &CommittedInstance {
113 phi: instance.phi,
114 betas: betas_star.clone(),
115 e: F_alpha,
116 },
117 w,
118 )?;
119
120 let ws: Vec<Vec<C::ScalarField>> = std::iter::once(w.w.clone())
121 .chain(
122 vec_w
123 .iter()
124 .map(|wj| {
125 if wj.w.len() != n {
126 return Err(Error::NotSameLength(
127 "wj.w.len()".to_string(),
128 wj.w.len(),
129 "n".to_string(),
130 n,
131 ));
132 }
133 Ok(wj.w.clone())
134 })
135 .collect::<Result<Vec<Vec<C::ScalarField>>, Error>>()?,
136 )
137 .collect::<Vec<Vec<C::ScalarField>>>();
138
139 let H =
140 GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?;
141 let G_domain = GeneralEvaluationDomain::<C::ScalarField>::new((d * k) + 1)
142 .ok_or(Error::NewDomainFail)?;
143 let L_X: Vec<DensePolynomial<C::ScalarField>> = lagrange_polys(H);
144
145 // K(X) computation in a naive way, next iterations will compute K(X) as described in Claim
146 // 4.5 of the paper.
147 let mut G_evals: Vec<C::ScalarField> = vec![C::ScalarField::zero(); G_domain.size()];
148 for (hi, h) in G_domain.elements().enumerate() {
149 // each iteration evaluates G(h)
150 // inner = L_0(x) * w + \sum_k L_i(x) * w_j
151 let mut inner: Vec<C::ScalarField> = vec![C::ScalarField::zero(); ws[0].len()];
152 for (i, w) in ws.iter().enumerate() {
153 // Li_w_h = (Li(X)*wj)(h) = Li(h) * wj
154 let mut Liw_h: Vec<C::ScalarField> = vec![C::ScalarField::zero(); w.len()];
155 for (j, wj) in w.iter().enumerate() {
156 Liw_h[j] = (&L_X[i] * *wj).evaluate(&h);
157 }
158
159 for j in 0..inner.len() {
160 inner[j] += Liw_h[j];
161 }
162 }
163 let f_ev = eval_f(r1cs, &inner)?;
164
165 let mut Gsum = C::ScalarField::zero();
166 for (i, f_ev_i) in f_ev.iter().enumerate() {
167 let pow_i_betas = pow_i(i, &betas_star);
168 let curr = pow_i_betas * f_ev_i;
169 Gsum += curr;
170 }
171 G_evals[hi] = Gsum;
172 }
173 let G_X: DensePolynomial<C::ScalarField> =
174 Evaluations::<C::ScalarField>::from_vec_and_domain(G_evals, G_domain).interpolate();
175 let Z_X: DensePolynomial<C::ScalarField> = H.vanishing_polynomial().into();
176 // K(X) = (G(X) - F(alpha)*L_0(X)) / Z(X)
177 // Notice that L0(X)*F(a) will be 0 in the native case (the instance of the first folding
178 // iteration case).
179 let L0_e = &L_X[0] * F_alpha;
180 let G_L0e = &G_X - &L0_e;
181 // Pending optimization: move division by Z_X to the prev loop
182 let (K_X, remainder) = G_L0e.divide_by_vanishing_poly(H).ok_or(Error::ProtoGalaxy(
183 ProtoGalaxyError::CouldNotDivideByVanishing,
184 ))?;
185 if !remainder.is_zero() {
186 return Err(Error::ProtoGalaxy(ProtoGalaxyError::RemainderNotZero));
187 }
188
189 transcript.absorb_vec(&K_X.coeffs);
190
191 let gamma = transcript.get_challenge();
192
193 let e_star =
194 F_alpha * L_X[0].evaluate(&gamma) + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
195
196 let mut phi_star: C = instance.phi * L_X[0].evaluate(&gamma);
197 for i in 0..k {
198 phi_star += vec_instances[i].phi * L_X[i + 1].evaluate(&gamma);
199 }
200 let mut w_star: Vec<C::ScalarField> = vec_scalar_mul(&w.w, &L_X[0].evaluate(&gamma));
201 let mut r_w_star: C::ScalarField = w.r_w * L_X[0].evaluate(&gamma);
202 for i in 0..k {
203 let L_X_at_i1 = L_X[i + 1].evaluate(&gamma);
204 w_star = vec_add(&w_star, &vec_scalar_mul(&vec_w[i].w, &L_X_at_i1))?;
205 r_w_star += vec_w[i].r_w * L_X_at_i1;
206 }
207
208 Ok((
209 CommittedInstance {
210 betas: betas_star,
211 phi: phi_star,
212 e: e_star,
213 },
214 Witness {
215 w: w_star,
216 r_w: r_w_star,
217 },
218 F_X_dense.coeffs,
219 K_X.coeffs,
220 ))
221 }
222
223 /// implements the non-interactive Verifier from the folding scheme described in section 4
224 pub fn verify(
225 transcript: &mut (impl Transcript<C> + ProtoGalaxyTranscript<C>),
226 r1cs: &R1CS<C::ScalarField>,
227 // running instance
228 instance: &CommittedInstance<C>,
229 // incomming instances
230 vec_instances: &[CommittedInstance<C>],
231 // polys from P
232 F_coeffs: Vec<C::ScalarField>,
233 K_coeffs: Vec<C::ScalarField>,
234 ) -> Result<CommittedInstance<C>, Error> {
235 let t = instance.betas.len();
236 let n = r1cs.A.n_cols;
237
238 // absorb the committed instances
239 transcript.absorb_committed_instance(instance)?;
240 for ci in vec_instances.iter() {
241 transcript.absorb_committed_instance(ci)?;
242 }
243
244 let delta = transcript.get_challenge();
245 let deltas = exponential_powers(delta, t);
246
247 transcript.absorb_vec(&F_coeffs);
248
249 let alpha = transcript.get_challenge();
250 let alphas = all_powers(alpha, n);
251
252 // F(alpha) = e + \sum_t F_i * alpha^i
253 let mut F_alpha = instance.e;
254 for (i, F_i) in F_coeffs.iter().skip(1).enumerate() {
255 F_alpha += *F_i * alphas[i + 1];
256 }
257
258 let betas_star = betas_star(&instance.betas, &deltas, alpha);
259
260 let k = vec_instances.len();
261 let H =
262 GeneralEvaluationDomain::<C::ScalarField>::new(k + 1).ok_or(Error::NewDomainFail)?;
263 let L_X: Vec<DensePolynomial<C::ScalarField>> = lagrange_polys(H);
264 let Z_X: DensePolynomial<C::ScalarField> = H.vanishing_polynomial().into();
265 let K_X: DensePolynomial<C::ScalarField> =
266 DensePolynomial::<C::ScalarField>::from_coefficients_vec(K_coeffs);
267
268 transcript.absorb_vec(&K_X.coeffs);
269
270 let gamma = transcript.get_challenge();
271
272 let e_star =
273 F_alpha * L_X[0].evaluate(&gamma) + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma);
274
275 let mut phi_star: C = instance.phi * L_X[0].evaluate(&gamma);
276 for i in 0..k {
277 phi_star += vec_instances[i].phi * L_X[i + 1].evaluate(&gamma);
278 }
279
280 // return the folded instance
281 Ok(CommittedInstance {
282 betas: betas_star,
283 phi: phi_star,
284 e: e_star,
285 })
286 }
287}
288
289// naive impl of pow_i for betas, assuming that betas=(b, b^2, b^4, ..., b^{2^{t-1}})
290fn pow_i<F: PrimeField>(i: usize, betas: &Vec<F>) -> F {
291 // WIP check if makes more sense to do it with ifs instead of arithmetic
292
293 let n = 2_u64.pow(betas.len() as u32);
294 let b = bit_decompose(i as u64, n as usize);
295
296 let mut r: F = F::one();
297 for (j, beta_j) in betas.iter().enumerate() {
298 let mut b_j = F::zero();
299 if b[j] {
300 b_j = F::one();
301 }
302 r *= (F::one() - b_j) + b_j * beta_j;
303 }
304 r
305}
306
307/// calculates F[x] using the optimized binary-tree technique
308/// described in Claim 4.4
309/// of [Protogalaxy](https://eprint.iacr.org/2023/1106.pdf)
310fn calc_f_from_btree<F: PrimeField>(
311 fw: &[F],
312 betas: &[F],
313 deltas: &[F],
314) -> Result<SparsePolynomial<F>, Error> {
315 let fw_len = fw.len();
316 let betas_len = betas.len();
317 let deltas_len = deltas.len();
318
319 // ensure our binary tree is full
320 if !fw_len.is_power_of_two() {
321 return Err(Error::ProtoGalaxy(ProtoGalaxyError::BTreeNotFull(fw_len)));
322 }
323
324 if betas_len != deltas_len {
325 return Err(Error::ProtoGalaxy(ProtoGalaxyError::WrongLenBetas(
326 betas_len, deltas_len,
327 )));
328 }
329
330 let mut layers: Vec<Vec<SparsePolynomial<F>>> = Vec::new();
331 let leaves: Vec<SparsePolynomial<F>> = fw
332 .iter()
333 .copied()
334 .map(|e| SparsePolynomial::<F>::from_coefficients_slice(&[(0, e)]))
335 .collect();
336 layers.push(leaves.to_vec());
337 let mut currentNodes = leaves.clone();
338 while currentNodes.len() > 1 {
339 let index = layers.len();
340 layers.push(vec![]);
341 for (i, ni) in currentNodes.iter().enumerate().step_by(2) {
342 let left = ni.clone();
343 let right = SparsePolynomial::<F>::from_coefficients_vec(vec![
344 (0, betas[layers.len() - 2]),
345 (1, deltas[layers.len() - 2]),
346 ])
347 .mul(¤tNodes[i + 1]);
348
349 layers[index].push(left + right);
350 }
351 currentNodes = layers[index].clone();
352 }
353 let root_index = layers.len() - 1;
354 Ok(layers[root_index][0].clone())
355}
356
357// lagrange_polys method from caulk: https://github.com/caulk-crypto/caulk/tree/8210b51fb8a9eef4335505d1695c44ddc7bf8170/src/multi/setup.rs#L300
358fn lagrange_polys<F: PrimeField>(domain_n: GeneralEvaluationDomain<F>) -> Vec<DensePolynomial<F>> {
359 let mut lagrange_polynomials: Vec<DensePolynomial<F>> = Vec::new();
360 for i in 0..domain_n.size() {
361 let evals: Vec<F> = cfg_into_iter!(0..domain_n.size())
362 .map(|k| if k == i { F::one() } else { F::zero() })
363 .collect();
364 lagrange_polynomials.push(Evaluations::from_vec_and_domain(evals, domain_n).interpolate());
365 }
366 lagrange_polynomials
367}
368
369// f(w) in R1CS context. For the moment we use R1CS, in the future we will abstract this with a
370// trait
371fn eval_f<F: PrimeField>(r1cs: &R1CS<F>, w: &[F]) -> Result<Vec<F>, Error> {
372 let Az = mat_vec_mul_sparse(&r1cs.A, w)?;
373 let Bz = mat_vec_mul_sparse(&r1cs.B, w)?;
374 let Cz = mat_vec_mul_sparse(&r1cs.C, w)?;
375 let AzBz = hadamard(&Az, &Bz)?;
376 vec_sub(&AzBz, &Cz)
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use ark_pallas::{Fr, Projective};
383 use ark_std::UniformRand;
384
385 use crate::ccs::r1cs::tests::{get_test_r1cs, get_test_z};
386 use crate::commitment::{pedersen::Pedersen, CommitmentProver};
387 use crate::transcript::poseidon::{poseidon_test_config, PoseidonTranscript};
388
389 pub(crate) fn check_instance<C: CurveGroup>(
390 r1cs: &R1CS<C::ScalarField>,
391 instance: &CommittedInstance<C>,
392 w: &Witness<C::ScalarField>,
393 ) -> Result<(), Error> {
394 if instance.betas.len() != log2(w.w.len()) as usize {
395 return Err(Error::NotSameLength(
396 "instance.betas.len()".to_string(),
397 instance.betas.len(),
398 "log2(w.w.len())".to_string(),
399 log2(w.w.len()) as usize,
400 ));
401 }
402
403 let f_w = eval_f(r1cs, &w.w)?; // f(w)
404
405 let mut r = C::ScalarField::zero();
406 for (i, f_w_i) in f_w.iter().enumerate() {
407 r += pow_i(i, &instance.betas) * f_w_i;
408 }
409 if instance.e == r {
410 return Ok(());
411 }
412 Err(Error::NotSatisfied)
413 }
414
415 #[test]
416 fn test_pow_i() {
417 let mut rng = ark_std::test_rng();
418 let t = 4;
419 let n = 16;
420 let beta = Fr::rand(&mut rng);
421 let betas = exponential_powers(beta, t);
422 let not_betas = all_powers(beta, n);
423
424 #[allow(clippy::needless_range_loop)]
425 for i in 0..n {
426 assert_eq!(pow_i(i, &betas), not_betas[i]);
427 }
428 }
429
430 #[test]
431 fn test_eval_f() {
432 let r1cs = get_test_r1cs::<Fr>();
433 let mut z = get_test_z::<Fr>(3);
434
435 let f_w = eval_f(&r1cs, &z).unwrap();
436 assert!(is_zero_vec(&f_w));
437
438 z[1] = Fr::from(111);
439 let f_w = eval_f(&r1cs, &z).unwrap();
440 assert!(!is_zero_vec(&f_w));
441 }
442
443 // k represents the number of instances to be fold, appart from the running instance
444 #[allow(clippy::type_complexity)]
445 fn prepare_inputs(
446 k: usize,
447 ) -> (
448 Witness<Fr>,
449 CommittedInstance<Projective>,
450 Vec<Witness<Fr>>,
451 Vec<CommittedInstance<Projective>>,
452 ) {
453 let mut rng = ark_std::test_rng();
454 let pedersen_params = Pedersen::<Projective>::new_params(&mut rng, 100); // 100 is wip, will get it from actual vec
455
456 let z = get_test_z::<Fr>(3);
457 let mut zs: Vec<Vec<Fr>> = Vec::new();
458 for i in 0..k {
459 let z_i = get_test_z::<Fr>(i + 4);
460 zs.push(z_i);
461 }
462
463 let n = z.len();
464 let t = log2(n) as usize;
465
466 let beta = Fr::rand(&mut rng);
467 let betas = exponential_powers(beta, t);
468
469 let witness = Witness::<Fr> {
470 w: z.clone(),
471 r_w: Fr::rand(&mut rng),
472 };
473 let phi =
474 Pedersen::<Projective>::commit(&pedersen_params, &witness.w, &witness.r_w).unwrap();
475 let instance = CommittedInstance::<Projective> {
476 phi,
477 betas: betas.clone(),
478 e: Fr::zero(),
479 };
480 // same for the other instances
481 let mut witnesses: Vec<Witness<Fr>> = Vec::new();
482 let mut instances: Vec<CommittedInstance<Projective>> = Vec::new();
483 #[allow(clippy::needless_range_loop)]
484 for i in 0..k {
485 let witness_i = Witness::<Fr> {
486 w: zs[i].clone(),
487 r_w: Fr::rand(&mut rng),
488 };
489 let phi_i =
490 Pedersen::<Projective>::commit(&pedersen_params, &witness_i.w, &witness_i.r_w)
491 .unwrap();
492 let instance_i = CommittedInstance::<Projective> {
493 phi: phi_i,
494 betas: betas.clone(),
495 e: Fr::zero(),
496 };
497 witnesses.push(witness_i);
498 instances.push(instance_i);
499 }
500
501 (witness, instance, witnesses, instances)
502 }
503
504 #[test]
505 fn test_fold_native_case() {
506 let k = 7;
507 let (witness, instance, witnesses, instances) = prepare_inputs(k);
508 let r1cs = get_test_r1cs::<Fr>();
509
510 // init Prover & Verifier's transcript
511 let poseidon_config = poseidon_test_config::<Fr>();
512 let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
513 let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
514
515 let (folded_instance, folded_witness, F_coeffs, K_coeffs) = Folding::<Projective>::prove(
516 &mut transcript_p,
517 &r1cs,
518 &instance,
519 &witness,
520 &instances,
521 &witnesses,
522 )
523 .unwrap();
524
525 // veriier
526 let folded_instance_v = Folding::<Projective>::verify(
527 &mut transcript_v,
528 &r1cs,
529 &instance,
530 &instances,
531 F_coeffs,
532 K_coeffs,
533 )
534 .unwrap();
535
536 // check that prover & verifier folded instances are the same values
537 assert_eq!(folded_instance.phi, folded_instance_v.phi);
538 assert_eq!(folded_instance.betas, folded_instance_v.betas);
539 assert_eq!(folded_instance.e, folded_instance_v.e);
540 assert!(!folded_instance.e.is_zero());
541
542 // check that the folded instance satisfies the relation
543 check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
544 }
545
546 #[test]
547 fn test_fold_various_iterations() {
548 let r1cs = get_test_r1cs::<Fr>();
549
550 // init Prover & Verifier's transcript
551 let poseidon_config = poseidon_test_config::<Fr>();
552 let mut transcript_p = PoseidonTranscript::<Projective>::new(&poseidon_config);
553 let mut transcript_v = PoseidonTranscript::<Projective>::new(&poseidon_config);
554
555 let (mut running_witness, mut running_instance, _, _) = prepare_inputs(0);
556
557 // fold k instances on each of num_iters iterations
558 let k = 7;
559 let num_iters = 10;
560 for _ in 0..num_iters {
561 // generate the instances to be fold
562 let (_, _, witnesses, instances) = prepare_inputs(k);
563
564 let (folded_instance, folded_witness, F_coeffs, K_coeffs) =
565 Folding::<Projective>::prove(
566 &mut transcript_p,
567 &r1cs,
568 &running_instance,
569 &running_witness,
570 &instances,
571 &witnesses,
572 )
573 .unwrap();
574
575 // veriier
576 let folded_instance_v = Folding::<Projective>::verify(
577 &mut transcript_v,
578 &r1cs,
579 &running_instance,
580 &instances,
581 F_coeffs,
582 K_coeffs,
583 )
584 .unwrap();
585
586 // check that prover & verifier folded instances are the same values
587 assert_eq!(folded_instance.phi, folded_instance_v.phi);
588 assert_eq!(folded_instance.betas, folded_instance_v.betas);
589 assert_eq!(folded_instance.e, folded_instance_v.e);
590 assert!(!folded_instance.e.is_zero());
591
592 // check that the folded instance satisfies the relation
593 check_instance(&r1cs, &folded_instance, &folded_witness).unwrap();
594
595 running_witness = folded_witness;
596 running_instance = folded_instance;
597 }
598 }
599}