use crate::mipp::MippProof; use ark_ec::{pairing::Pairing, scalar_mul::variable_base::VariableBaseMSM, CurveGroup}; use ark_ff::One; use ark_poly_commit::multilinear_pc::{ data_structures::{Commitment, CommitterKey, Proof, VerifierKey}, MultilinearPC, }; use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator}; use crate::{ dense_mlpoly::DensePolynomial, math::Math, poseidon_transcript::PoseidonTranscript, timer::Timer, }; pub struct Polynomial { m: usize, odd: usize, polys: Vec>, q: Option>, chis_b: Option>, } impl Polynomial { // Given the evaluations over the boolean hypercube of a polynomial p of size // n compute the sqrt-sized polynomials p_i as // p_i(X) = \sum_{j \in \{0,1\}^m} p(j, i) * chi_j(X) // where p(X,Y) = \sum_{i \in \{0,\1}^m} // (\sum_{j \in \{0, 1\}^{m}} p(j, i) * \chi_j(X)) * \chi_i(Y) // and m is n/2. // To handle the case in which n is odd, the number of variables in the // sqrt-sized polynomials will be increased by a factor of 2 (i.e. 2^{m+1}) // while the number of polynomials remains the same (i.e. 2^m) pub fn from_evaluations(Z: &[E::ScalarField]) -> Self { let pl_timer = Timer::new("poly_list_build"); // check the evaluation list is a power of 2 debug_assert!(Z.len() & (Z.len() - 1) == 0); let num_vars = Z.len().log_2(); let m_col = num_vars / 2; let m_row = if num_vars % 2 == 0 { num_vars / 2 } else { num_vars / 2 + 1 }; let pow_m_col = 2_usize.pow(m_col as u32); let pow_m_row = 2_usize.pow(m_row as u32); let polys: Vec> = (0..pow_m_col) .into_par_iter() .map(|i| { let z: Vec = (0..pow_m_row) .into_par_iter() // viewing the list of evaluation as a square matrix // we select by row j and column i // to handle the odd case, we add another row to the matrix i.e. // we add an extra variable to the polynomials while keeping their // number tje same .map(|j| Z[(j << m_col) | i]) .collect(); DensePolynomial::new(z) }) .collect(); debug_assert!(polys.len() == pow_m_col); debug_assert!(polys[0].len == pow_m_row); pl_timer.stop(); Self { m: m_col, odd: if num_vars % 2 == 1 { 1 } else { 0 }, polys, q: None, chis_b: None, } } // Given point = (\vec{a}, \vec{b}), compute the polynomial q as // q(Y) = // \sum_{j \in \{0,1\}^m}(\sum_{i \in \{0,1\}^m} p(j,i) * chi_i(b)) * chi_j(Y) // and p(a,b) = q(a) where p is the initial polynomial fn get_q(&mut self, point: &[E::ScalarField]) { let q_timer = Timer::new("build_q"); debug_assert!(point.len() == 2 * self.m + self.odd); let b = &point[self.m + self.odd..]; let pow_m = 2_usize.pow(self.m as u32); let chis: Vec = (0..pow_m) .into_par_iter() .map(|i| Self::get_chi_i(b, i)) .collect(); let z_q: Vec = (0..(pow_m * 2_usize.pow(self.odd as u32))) .into_par_iter() .map(|j| (0..pow_m).map(|i| self.polys[i].Z[j] * chis[i]).sum()) .collect(); q_timer.stop(); self.q = Some(DensePolynomial::new(z_q)); self.chis_b = Some(chis); } // Given point = (\vec{a}, \vec{b}) used to construct q // compute q(a) = p(a,b). pub fn eval(&mut self, point: &[E::ScalarField]) -> E::ScalarField { let a = &point[0..point.len() / 2 + self.odd]; if self.q.is_none() { self.get_q(point); } let q = self.q.clone().unwrap(); (0..q.Z.len()) .into_par_iter() .map(|j| q.Z[j] * Polynomial::::get_chi_i(&a, j)) .sum() } pub fn commit(&self, ck: &CommitterKey) -> (Vec>, E::TargetField) { let timer_commit = Timer::new("sqrt_commit"); let timer_list = Timer::new("comm_list"); // commit to each of the sqrt sized p_i let comm_list: Vec> = self .polys .par_iter() .map(|p| MultilinearPC::::commit(&ck, p)) .collect(); timer_list.stop(); let h_vec = ck.powers_of_h[self.odd].clone(); assert!(comm_list.len() == h_vec.len()); let ipp_timer = Timer::new("ipp"); let left_pairs: Vec<_> = comm_list .clone() .into_par_iter() .map(|c| E::G1Prepared::from(c.g_product)) .collect(); let right_pairs: Vec<_> = h_vec .into_par_iter() .map(|h| E::G2Prepared::from(h)) .collect(); // compute the IPP commitment let t = E::multi_pairing(left_pairs, right_pairs).0; ipp_timer.stop(); timer_commit.stop(); (comm_list, t) } // computes \chi_i(\vec{b}) = \prod_{i_j = 0}(1 - b_j)\prod_{i_j = 1}(b_j) pub fn get_chi_i(b: &[E::ScalarField], i: usize) -> E::ScalarField { let m = b.len(); let mut prod = E::ScalarField::one(); for j in 0..m { let b_j = b[j]; // iterate from first (msb) to last (lsb) bit of i // to build chi_i using the formula above if i >> (m - j - 1) & 1 == 1 { prod = prod * b_j; } else { prod = prod * (E::ScalarField::one() - b_j) }; } prod } pub fn open( &mut self, transcript: &mut PoseidonTranscript, comm_list: Vec>, ck: &CommitterKey, point: &[E::ScalarField], t: &E::TargetField, ) -> (Commitment, Proof, MippProof) { let a = &point[0..self.m + self.odd]; if self.q.is_none() { self.get_q(point); } let q = self.q.clone().unwrap(); let timer_open = Timer::new("sqrt_open"); // Compute the PST commitment to q obtained as the inner products of the // commitments to the polynomials p_i and chi_i(\vec{b}) for i ranging over // the boolean hypercube of size m. let timer_msm = Timer::new("msm"); if self.chis_b.is_none() { panic!("chis(b) should have been computed for q"); } // TODO remove that cloning - the whole option thing let chis = self.chis_b.clone().unwrap(); assert!(chis.len() == comm_list.len()); let comms: Vec<_> = comm_list.par_iter().map(|c| c.g_product).collect(); let c_u = ::msm_unchecked(&comms, &chis).into_affine(); timer_msm.stop(); let U: Commitment = Commitment { nv: q.num_vars, g_product: c_u, }; let comm = MultilinearPC::::commit(ck, &q); debug_assert!(c_u == comm.g_product); let h_vec = ck.powers_of_h[self.odd].clone(); // construct MIPP proof that U is the inner product of the vector A // and the vector y, where A is the opening vector to T let timer_mipp_proof = Timer::new("mipp_prove"); let mipp_proof = MippProof::::prove(transcript, ck, comms, chis.to_vec(), h_vec, &c_u, t).unwrap(); timer_mipp_proof.stop(); let timer_proof = Timer::new("pst_open"); // reversing a is necessary because the sumcheck code in spartan generates // the point in reverse order compared to how the polynomial commitment // expects it let mut a_rev = a.to_vec().clone(); a_rev.reverse(); // construct PST proof for opening q at a let pst_proof = MultilinearPC::::open(ck, &q, &a_rev); timer_proof.stop(); timer_open.stop(); (U, pst_proof, mipp_proof) } pub fn verify( transcript: &mut PoseidonTranscript, vk: &VerifierKey, U: &Commitment, point: &[E::ScalarField], v: E::ScalarField, pst_proof: &Proof, mipp_proof: &MippProof, T: &E::TargetField, ) -> bool { let len = point.len(); let odd = if len % 2 == 1 { 1 } else { 0 }; let a = &point[0..len / 2 + odd]; let b = &point[len / 2 + odd..len]; let timer_mipp_verify = Timer::new("mipp_verify"); // verify that U = A^y where A is the opening vector of T let res_mipp = MippProof::::verify(vk, transcript, mipp_proof, b.to_vec(), &U.g_product, T); assert!(res_mipp == true); timer_mipp_verify.stop(); // reversing a is necessary because the sumcheck code in spartan generates // the point in reverse order compared to how the polynomial commitment // expects let mut a_rev = a.to_vec().clone(); a_rev.reverse(); let timer_pst_verify = Timer::new("pst_verify"); // PST proof that q(a) is indeed equal to value claimed by the prover let res = MultilinearPC::::check(vk, U, &a_rev, v, pst_proof); timer_pst_verify.stop(); res } } #[cfg(test)] mod tests { use crate::parameters::poseidon_params; use super::*; type F = ark_bls12_377::Fr; type E = ark_bls12_377::Bls12_377; use ark_std::UniformRand; #[test] fn check_sqrt_poly_eval() { let mut rng = ark_std::test_rng(); let num_vars = 6; let len = 2_usize.pow(num_vars); let Z: Vec = (0..len).into_iter().map(|_| F::rand(&mut rng)).collect(); let r: Vec = (0..num_vars) .into_iter() .map(|_| F::rand(&mut rng)) .collect(); let p = DensePolynomial::new(Z.clone()); let res1 = p.evaluate(&r); let mut pl = Polynomial::::from_evaluations(&Z.clone()); let res2 = pl.eval(&r); assert!(res1 == res2); } #[test] fn check_commit() { // check odd case check_sqrt_poly_commit(5); // check even case check_sqrt_poly_commit(6); } fn check_sqrt_poly_commit(num_vars: u32) { let mut rng = ark_std::test_rng(); let len = 2_usize.pow(num_vars); let Z: Vec = (0..len).into_iter().map(|_| F::rand(&mut rng)).collect(); let r: Vec = (0..num_vars) .into_iter() .map(|_| F::rand(&mut rng)) .collect(); let gens = MultilinearPC::::setup(3, &mut rng); let (ck, vk) = MultilinearPC::::trim(&gens, 3); let mut pl = Polynomial::from_evaluations(&Z.clone()); let v = pl.eval(&r); let (comm_list, t) = pl.commit(&ck); let params = poseidon_params(); let mut prover_transcript = PoseidonTranscript::new(¶ms); let (u, pst_proof, mipp_proof) = pl.open(&mut prover_transcript, comm_list, &ck, &r, &t); let mut verifier_transcript = PoseidonTranscript::new(¶ms); let res = Polynomial::verify( &mut verifier_transcript, &vk, &u, &r, v, &pst_proof, &mipp_proof, &t, ); assert!(res == true); } }