use ark_crypto_primitives::sponge::Absorb; use ark_ec::{CurveGroup, Group}; use ark_ff::fields::PrimeField; use ark_std::log2; use ark_std::{cfg_into_iter, Zero}; use std::marker::PhantomData; use std::ops::Add; use ark_poly::{ univariate::{DensePolynomial, SparsePolynomial}, DenseUVPolynomial, EvaluationDomain, Evaluations, GeneralEvaluationDomain, Polynomial, }; use crate::pedersen::Commitment; use crate::transcript::Transcript; use crate::utils::*; #[derive(Clone, Debug)] pub struct CommittedInstance { phi: Commitment, betas: Vec, e: C::ScalarField, } #[derive(Clone, Debug)] pub struct Witness { w: Vec, r_w: C::ScalarField, } #[derive(Clone, Debug)] pub struct Folding { _phantom: PhantomData, } impl Folding where ::ScalarField: Absorb, ::BaseField: Absorb, { // WIP naming of functions pub fn prover( transcript: &mut Transcript, r1cs: &R1CS, // running instance instance: CommittedInstance, w: Witness, // incomming instances vec_instances: Vec>, vec_w: Vec>, ) -> ( Vec, Vec, CommittedInstance, Witness, ) { let t = instance.betas.len(); let n = r1cs.A[0].len(); assert_eq!(w.w.len(), n); assert_eq!(log2(n) as usize, t); // TODO initialize transcript let delta = transcript.get_challenge(); let deltas = powers_of_beta(delta, t); let f_w = eval_f(r1cs, &w.w); // F(X) let mut F_X: SparsePolynomial = SparsePolynomial::zero(); for i in 0..n { let lhs = pow_i_over_x::(i, &instance.betas, &deltas); let curr = &lhs * f_w[i]; F_X = F_X.add(curr); } let F_X_dense = DensePolynomial::from(F_X.clone()); transcript.add_vec(&F_X_dense.coeffs); let alpha = transcript.get_challenge(); // eval F(alpha) let F_alpha = F_X.evaluate(&alpha); // betas* let betas_star: Vec = instance .betas .iter() .zip( deltas .iter() .map(|delta_i| alpha * delta_i) .collect::>(), ) .map(|(beta_i, delta_i_alpha)| *beta_i + delta_i_alpha) .collect(); // sanity check: check that the new randomized instnace (the original instance but with // 'refreshed' randomness) satisfies the relation. assert!(check_instance( r1cs, &CommittedInstance { phi: instance.phi.clone(), betas: betas_star.clone(), e: F_alpha, }, &w, )); let mut ws: Vec> = Vec::new(); ws.push(w.w.clone()); for wj in vec_w.iter() { assert_eq!(wj.w.len(), n); ws.push(wj.w.clone()); } let k = vec_instances.len(); let H = GeneralEvaluationDomain::::new(k + 1).unwrap(); // WIP review t/d let EH = GeneralEvaluationDomain::::new(t * k + 1).unwrap(); let L_X: Vec> = lagrange_polys(H); // K(X) computation in a naive way, next iterations will compute K(X) as described in Claim // 4.5 of the paper. let mut G_evals: Vec = vec![C::ScalarField::zero(); EH.size()]; for (hi, h) in EH.elements().enumerate() { // each iteration evaluates G(h) // inner = L_0(x) * w + \sum_k L_i(x) * w_j let mut inner: Vec = vec![C::ScalarField::zero(); ws[0].len()]; for (i, w) in ws.iter().enumerate() { // Li_w = Li(X) * wj let mut Li_w: Vec> = vec![DensePolynomial::::zero(); w.len()]; for (j, wj) in w.iter().enumerate() { let Li_wj = &L_X[i] * *wj; Li_w[j] = Li_wj; } // Li_w_h = Li_w(h) = Li(h) * wj let mut Liw_h: Vec = vec![C::ScalarField::zero(); w.len()]; for (j, _) in Li_w.iter().enumerate() { Liw_h[j] = Li_w[j].evaluate(&h); } for j in 0..inner.len() { inner[j] += Liw_h[j]; } } let f_ev = eval_f(r1cs, &inner); let mut Gsum = C::ScalarField::zero(); for i in 0..n { let pow_i_betas = pow_i(i, &betas_star); let curr = pow_i_betas * f_ev[i]; Gsum += curr; } // G_evals[hi] = Gsum / Z_X.evaluate(&h); // WIP G_evals[hi] = Gsum; } let G_X: DensePolynomial = Evaluations::::from_vec_and_domain(G_evals.clone(), EH).interpolate(); let Z_X: DensePolynomial = H.vanishing_polynomial().into(); // K(X) = (G(X)- F(alpha)*L_0(X)) / Z(X) let L0_e = &L_X[0] * F_alpha; // L0(X)*F(a) will be 0 in the native case let G_L0e = &G_X - &L0_e; // TODO move division by Z_X to the prev loop let (K_X, remainder) = G_L0e.divide_by_vanishing_poly(H).unwrap(); assert!(remainder.is_zero()); transcript.add_vec(&K_X.coeffs); let gamma = transcript.get_challenge(); let e_star = F_alpha * L_X[0].evaluate(&gamma) + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); let mut phi_star: C = instance.phi.0 * L_X[0].evaluate(&gamma); for i in 0..k { phi_star += vec_instances[i].phi.0 * L_X[i + 1].evaluate(&gamma); } let mut w_star: Vec = vec_scalar_mul(&w.w, &L_X[0].evaluate(&gamma)); for i in 0..k { w_star = vec_add( &w_star, &vec_scalar_mul(&vec_w[i].w, &L_X[i + 1].evaluate(&gamma)), ); } let mut r_w_star: C::ScalarField = w.r_w * L_X[0].evaluate(&gamma); for i in 0..k { r_w_star += vec_w[i].r_w * L_X[i + 1].evaluate(&gamma); } ( F_X_dense.coeffs, K_X.coeffs, CommittedInstance { betas: betas_star, phi: Commitment(phi_star), e: e_star, }, Witness { w: w_star, r_w: w.r_w, // wip, fold also r_w (blinding used for the w commitment) }, ) } pub fn verifier( transcript: &mut Transcript, r1cs: &R1CS, // running instance instance: CommittedInstance, // incomming instances vec_instances: Vec>, // polys from P F_coeffs: Vec, K_coeffs: Vec, ) -> CommittedInstance { let t = instance.betas.len(); let n = r1cs.A[0].len(); let delta = transcript.get_challenge(); let deltas = powers_of_beta(delta, t); transcript.add_vec(&F_coeffs); let alpha = transcript.get_challenge(); let alphas = all_powers(alpha, n); // F(alpha) = e + \sum_t F_i * alpha^i let mut F_alpha = instance.e; for (i, F_i) in F_coeffs.iter().skip(1).enumerate() { F_alpha += *F_i * alphas[i + 1]; } let betas_star: Vec = instance .betas .iter() .zip( deltas .iter() .map(|delta_i| alpha * delta_i) .collect::>(), ) .map(|(beta_i, delta_i_alpha)| *beta_i + delta_i_alpha) .collect(); let k = vec_instances.len(); let H = GeneralEvaluationDomain::::new(k + 1).unwrap(); let L_X: Vec> = lagrange_polys(H); let Z_X: DensePolynomial = H.vanishing_polynomial().into(); let K_X: DensePolynomial = DensePolynomial::::from_coefficients_vec(K_coeffs); transcript.add_vec(&K_X.coeffs); let gamma = transcript.get_challenge(); let e_star = F_alpha * L_X[0].evaluate(&gamma) + Z_X.evaluate(&gamma) * K_X.evaluate(&gamma); let mut phi_star: C = instance.phi.0 * L_X[0].evaluate(&gamma); for i in 0..k { phi_star += vec_instances[i].phi.0 * L_X[i + 1].evaluate(&gamma); } // return the folded instance CommittedInstance { betas: betas_star, phi: Commitment(phi_star), e: e_star, } } } // naive impl of pow_i for betas, assuming that betas=(b, b^2, b^4, ..., b^{2^{t-1}}) fn pow_i(i: usize, betas: &Vec) -> F { // WIP check if makes more sense to do it with ifs instead of arithmetic let n = 2_u64.pow(betas.len() as u32); let b = bit_decompose(i as u64, n as usize); let mut r: F = F::one(); for (j, beta_j) in betas.iter().enumerate() { let mut b_j = F::zero(); if b[j] { b_j = F::one(); } r *= (F::one() - b_j) + b_j * beta_j; } r } fn pow_i_over_x(i: usize, betas: &Vec, deltas: &Vec) -> SparsePolynomial { assert_eq!(betas.len(), deltas.len()); let n = 2_u64.pow(betas.len() as u32); let b = bit_decompose(i as u64, n as usize); let mut r: SparsePolynomial = SparsePolynomial::::from_coefficients_vec(vec![(0, F::one())]); // start with r(x) = 1 for (j, beta_j) in betas.iter().enumerate() { if b[j] { let curr: SparsePolynomial = SparsePolynomial::::from_coefficients_vec(vec![(0, *beta_j), (1, deltas[j])]); r = r.mul(&curr); } } r } // lagrange_polys method from caulk: https://github.com/caulk-crypto/caulk/tree/8210b51fb8a9eef4335505d1695c44ddc7bf8170/src/multi/setup.rs#L300 fn lagrange_polys(domain_n: GeneralEvaluationDomain) -> Vec> { let mut lagrange_polynomials: Vec> = Vec::new(); for i in 0..domain_n.size() { let evals: Vec = cfg_into_iter!(0..domain_n.size()) .map(|k| if k == i { F::one() } else { F::zero() }) .collect(); lagrange_polynomials.push(Evaluations::from_vec_and_domain(evals, domain_n).interpolate()); } lagrange_polynomials } #[derive(Clone, Debug)] pub struct R1CS { pub A: Vec>, pub B: Vec>, pub C: Vec>, } // f(w) in R1CS context fn eval_f(r1cs: &R1CS, w: &[F]) -> Vec { let AzBz = hadamard(&mat_vec_mul(&r1cs.A, w), &mat_vec_mul(&r1cs.B, w)); let Cz = mat_vec_mul(&r1cs.C, w); vec_sub(&AzBz, &Cz) } fn check_instance( r1cs: &R1CS, instance: &CommittedInstance, w: &Witness, ) -> bool { let n = 2_u64.pow(instance.betas.len() as u32) as usize; let f_w = eval_f(r1cs, &w.w); // f(w) let mut r = C::ScalarField::zero(); for i in 0..n { r += pow_i(i, &instance.betas) * f_w[i]; } if instance.e == r { return true; } false } #[cfg(test)] mod tests { use super::*; use crate::pedersen::Pedersen; use crate::transcript::poseidon_test_config; use ark_bls12_381::{Fr, G1Projective}; use ark_std::UniformRand; pub fn to_F_matrix(M: Vec>) -> Vec> { let mut R: Vec> = vec![Vec::new(); M.len()]; for i in 0..M.len() { R[i] = vec![F::zero(); M[i].len()]; for j in 0..M[i].len() { R[i][j] = F::from(M[i][j] as u64); } } R } pub fn to_F_vec(z: Vec) -> Vec { let mut r: Vec = vec![F::zero(); z.len()]; for i in 0..z.len() { r[i] = F::from(z[i] as u64); } r } pub fn get_test_r1cs() -> R1CS { // R1CS for: x^3 + x + 5 = y (example from article // https://www.vitalik.ca/general/2016/12/10/qap.html ) let A = to_F_matrix::(vec![ vec![0, 1, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 1, 0, 0, /**/ 0, 0], vec![0, 1, 0, 0, 1, 0, /**/ 0, 0], vec![5, 0, 0, 0, 0, 1, /**/ 0, 0], // vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], ]); let B = to_F_matrix::(vec![ vec![0, 1, 0, 0, 0, 0, /**/ 0, 0], vec![0, 1, 0, 0, 0, 0, /**/ 0, 0], vec![1, 0, 0, 0, 0, 0, /**/ 0, 0], vec![1, 0, 0, 0, 0, 0, /**/ 0, 0], // vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], ]); let C = to_F_matrix::(vec![ vec![0, 0, 0, 1, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 1, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 1, /**/ 0, 0], vec![0, 0, 1, 0, 0, 0, /**/ 0, 0], // vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], vec![0, 0, 0, 0, 0, 0, /**/ 0, 0], ]); let r1cs = R1CS:: { A, B, C }; r1cs } pub fn get_test_z(input: usize) -> Vec { // z = (1, io, w) to_F_vec(vec![ 1, input, input * input * input + input + 5, // x^3 + x + 5 input * input, // x^2 input * input * input, // x^2 * x input * input * input + input, // x^3 + x 0, // pad to pow of 2 0, ]) } #[test] fn test_pow_i() { let mut rng = ark_std::test_rng(); let t = 4; let n = 16; let beta = Fr::rand(&mut rng); let betas = powers_of_beta(beta, t); let not_betas = all_powers(beta, n); for i in 0..n { assert_eq!(pow_i(i, &betas), not_betas[i]); } } #[test] fn test_pow_i_over_x() { let mut rng = ark_std::test_rng(); let t = 3; let n = 8; let beta = Fr::rand(&mut rng); let delta = Fr::rand(&mut rng); let betas = powers_of_beta(beta, t); let deltas = powers_of_beta(delta, t); // compute b + X*d, with X=rand let x = Fr::rand(&mut rng); let bxd = vec_add(&betas, &vec_scalar_mul(&deltas, &x)); // assert that computing pow_over_x of betas,deltas, is equivalent to first computing the // vector [betas+X*deltas] and then computing pow_i over it for i in 0..n { let pow_i1 = pow_i_over_x(i, &betas, &deltas); let pow_i2 = pow_i(i, &bxd); assert_eq!(pow_i1.evaluate(&x), pow_i2); } } #[test] fn test_eval_f() { let r1cs = get_test_r1cs::(); let mut z = get_test_z::(3); let f_w = eval_f(&r1cs, &z); assert!(is_zero_vec(&f_w)); z[1] = Fr::from(111); let f_w = eval_f(&r1cs, &z); assert!(!is_zero_vec(&f_w)); } // k represents the number of instances to be fold, appart from the running instance fn prepare_inputs( k: usize, ) -> ( Witness, CommittedInstance, Vec>, Vec>, ) { let mut rng = ark_std::test_rng(); let pedersen_params = Pedersen::::new_params(&mut rng, 100); // 100 is wip, will get it from actual vec let z = get_test_z::(3); let mut zs: Vec> = Vec::new(); for i in 0..k { let z_i = get_test_z::(i + 4); zs.push(z_i); } let n = z.len(); let t = log2(n) as usize; let beta = Fr::rand(&mut rng); let betas = powers_of_beta(beta, t); let witness = Witness:: { w: z.clone(), // WIP r_w: Fr::rand(&mut rng), }; let phi = Pedersen::::commit(&pedersen_params, &witness.w, &witness.r_w); let instance = CommittedInstance:: { phi, betas: betas.clone(), e: Fr::zero(), }; // same for the other instances let mut witnesses: Vec> = Vec::new(); let mut instances: Vec> = Vec::new(); for i in 0..k { let witness_i = Witness:: { w: zs[i].clone(), // WIP r_w: Fr::rand(&mut rng), }; let phi_i = Pedersen::::commit(&pedersen_params, &witness_i.w, &witness_i.r_w); let instance_i = CommittedInstance:: { phi: phi_i, betas: betas.clone(), e: Fr::zero(), }; witnesses.push(witness_i); instances.push(instance_i); } (witness, instance, witnesses, instances) } #[test] fn test_fold_native_case() { let k = 6; let (witness, instance, witnesses, instances) = prepare_inputs(k); let r1cs = get_test_r1cs::(); // init Prover & Verifier's transcript let poseidon_config = poseidon_test_config::(); let mut transcript_p = Transcript::::new(&poseidon_config); let mut transcript_v = Transcript::::new(&poseidon_config); let (F_coeffs, K_coeffs, folded_instance, folded_witness) = Folding::::prover( &mut transcript_p, &r1cs, instance.clone(), witness, instances.clone(), witnesses, ); // veriier let folded_instance_v = Folding::::verifier( &mut transcript_v, &r1cs, instance, instances, F_coeffs, K_coeffs, ); // check that prover & verifier folded instances are the same values assert_eq!(folded_instance.phi.0, folded_instance_v.phi.0); assert_eq!(folded_instance.betas, folded_instance_v.betas); assert_eq!(folded_instance.e, folded_instance_v.e); assert!(!folded_instance.e.is_zero()); // check that the folded instance satisfies the relation assert!(check_instance(&r1cs, &folded_instance, &folded_witness)); } #[test] fn test_fold_various_iterations() { let r1cs = get_test_r1cs::(); // init Prover & Verifier's transcript let poseidon_config = poseidon_test_config::(); let mut transcript_p = Transcript::::new(&poseidon_config); let mut transcript_v = Transcript::::new(&poseidon_config); let (mut running_witness, mut running_instance, _, _) = prepare_inputs(0); // fold k instances on each of num_iters iterations let k = 6; let num_iters = 10; for _ in 0..num_iters { // generate the instances to be fold let (_, _, witnesses, instances) = prepare_inputs(k); let (F_coeffs, K_coeffs, folded_instance, folded_witness) = Folding::::prover( &mut transcript_p, &r1cs, running_instance.clone(), running_witness.clone(), instances.clone(), witnesses, ); // veriier let folded_instance_v = Folding::::verifier( &mut transcript_v, &r1cs, running_instance.clone(), instances, F_coeffs, K_coeffs, ); // check that prover & verifier folded instances are the same values assert_eq!(folded_instance.phi.0, folded_instance_v.phi.0); assert_eq!(folded_instance.betas, folded_instance_v.betas); assert_eq!(folded_instance.e, folded_instance_v.e); assert!(!folded_instance.e.is_zero()); // check that the folded instance satisfies the relation assert!(check_instance(&r1cs, &folded_instance, &folded_witness)); running_witness = folded_witness; running_instance = folded_instance; } } }