// Copyright (c) 2023 Espresso Systems (espressosys.com) // This file is part of the HyperPlonk library. // You should have received a copy of the MIT License // along with the HyperPlonk library. If not, see . use crate::{ custom_gate::CustomizedGates, errors::HyperPlonkErrors, structs::HyperPlonkParams, witness::WitnessColumn, }; use arithmetic::{evaluate_opt, VirtualPolynomial}; use ark_ec::PairingEngine; use ark_ff::PrimeField; use ark_poly::DenseMultilinearExtension; use std::{borrow::Borrow, sync::Arc}; use subroutines::pcs::{prelude::Commitment, PolynomialCommitmentScheme}; use transcript::IOPTranscript; /// An accumulator structure that holds a polynomial and /// its opening points #[derive(Debug)] pub(super) struct PcsAccumulator> { // sequence: // - prod(x) at 5 points // - w_merged at perm check point // - w_merged at zero check points (#witness points) // - selector_merged at zero check points (#selector points) // - w[0] at r_pi pub(crate) num_var: usize, pub(crate) polynomials: Vec, pub(crate) commitments: Vec, pub(crate) points: Vec, pub(crate) evals: Vec, } impl PcsAccumulator where E: PairingEngine, PCS: PolynomialCommitmentScheme< E, Polynomial = Arc>, Point = Vec, Evaluation = E::Fr, Commitment = Commitment, >, { /// Create an empty accumulator. pub(super) fn new(num_var: usize) -> Self { Self { num_var, polynomials: vec![], commitments: vec![], points: vec![], evals: vec![], } } /// Push a new evaluation point into the accumulator pub(super) fn insert_poly_and_points( &mut self, poly: &PCS::Polynomial, commit: &PCS::Commitment, point: &PCS::Point, ) { assert!(poly.num_vars == point.len()); assert!(poly.num_vars == self.num_var); let eval = evaluate_opt(poly, point); self.evals.push(eval); self.polynomials.push(poly.clone()); self.points.push(point.clone()); self.commitments.push(*commit); } /// Batch open all the points over a merged polynomial. /// A simple wrapper of PCS::multi_open pub(super) fn multi_open( &self, prover_param: impl Borrow, transcript: &mut IOPTranscript, ) -> Result { Ok(PCS::multi_open( prover_param.borrow(), self.polynomials.as_ref(), self.points.as_ref(), self.evals.as_ref(), transcript, )?) } } /// Build MLE from matrix of witnesses. /// /// Given a matrix := [row1, row2, ...] where /// row1:= (a1, a2, ...) /// row2:= (b1, b2, ...) /// row3:= (c1, c2, ...) /// /// output mle(a1,b1,c1, ...), mle(a2,b2,c2, ...), ... #[macro_export] macro_rules! build_mle { ($rows:expr) => {{ let mut res = Vec::with_capacity($rows.len()); let num_vars = log2($rows.len()) as usize; let num_mles = $rows[0].0.len(); for i in 0..num_mles { let mut cur_coeffs = Vec::new(); for row in $rows.iter() { cur_coeffs.push(row.0[i]) } res.push(Arc::new(DenseMultilinearExtension::from_evaluations_vec( num_vars, cur_coeffs, ))) } Ok(res) }}; } /// Sanity-check for HyperPlonk SNARK proving pub(crate) fn prover_sanity_check( params: &HyperPlonkParams, pub_input: &[F], witnesses: &[WitnessColumn], ) -> Result<(), HyperPlonkErrors> { // public input length must be no greater than num_constraints if pub_input.len() > params.num_constraints { return Err(HyperPlonkErrors::InvalidProver(format!( "Public input length {} is greater than num constraits {}", pub_input.len(), params.num_pub_input ))); } // public input length if pub_input.len() != params.num_pub_input { return Err(HyperPlonkErrors::InvalidProver(format!( "Public input length is not correct: got {}, expect {}", pub_input.len(), params.num_pub_input ))); } if !pub_input.len().is_power_of_two() { return Err(HyperPlonkErrors::InvalidProver(format!( "Public input length is not power of two: got {}", pub_input.len(), ))); } // witnesses length for (i, w) in witnesses.iter().enumerate() { if w.0.len() != params.num_constraints { return Err(HyperPlonkErrors::InvalidProver(format!( "{}-th witness length is not correct: got {}, expect {}", i, w.0.len(), params.num_constraints ))); } } // check public input matches witness[0]'s first 2^ell elements for (i, (&pi, &w)) in pub_input .iter() .zip(witnesses[0].0.iter().take(pub_input.len())) .enumerate() { if pi != w { return Err(HyperPlonkErrors::InvalidProver(format!( "The {:?}-th public input {:?} does not match witness[0] {:?}", i, pi, w ))); } } Ok(()) } /// build `f(w_0(x),...w_d(x))` where `f` is the constraint polynomial /// i.e., `f(a, b, c) = q_l a(x) + q_r b(x) + q_m a(x)b(x) - q_o c(x)` in /// vanilla plonk pub(crate) fn build_f( gates: &CustomizedGates, num_vars: usize, selector_mles: &[Arc>], witness_mles: &[Arc>], ) -> Result, HyperPlonkErrors> { // TODO: check that selector and witness lengths match what is in // the gate definition for selector_mle in selector_mles.iter() { if selector_mle.num_vars != num_vars { return Err(HyperPlonkErrors::InvalidParameters(format!( "selector has different number of vars: {} vs {}", selector_mle.num_vars, num_vars ))); } } for witness_mle in witness_mles.iter() { if witness_mle.num_vars != num_vars { return Err(HyperPlonkErrors::InvalidParameters(format!( "selector has different number of vars: {} vs {}", witness_mle.num_vars, num_vars ))); } } let mut res = VirtualPolynomial::::new(num_vars); for (coeff, selector, witnesses) in gates.gates.iter() { let coeff_fr = if *coeff < 0 { -F::from(-*coeff as u64) } else { F::from(*coeff as u64) }; let mut mle_list = vec![]; if let Some(s) = *selector { mle_list.push(selector_mles[s].clone()) } for &witness in witnesses.iter() { mle_list.push(witness_mles[witness].clone()) } res.add_mle_list(mle_list, coeff_fr)?; } Ok(res) } pub(crate) fn eval_f( gates: &CustomizedGates, selector_evals: &[F], witness_evals: &[F], ) -> Result { let mut res = F::zero(); for (coeff, selector, witnesses) in gates.gates.iter() { let mut cur_value = if *coeff < 0 { -F::from(-*coeff as u64) } else { F::from(*coeff as u64) }; cur_value *= match selector { Some(s) => selector_evals[*s], None => F::one(), }; for &witness in witnesses.iter() { cur_value *= witness_evals[witness] } res += cur_value; } Ok(res) } // check perm check subclaim: // proof.witness_perm_check_eval ?= perm_check_sub_claim.expected_eval // Q(x) := prod(x) - p1(x) * p2(x) // + alpha * frac(x) * g1(x) * ... * gk(x) // - alpha * f1(x) * ... * fk(x) // // where p1(x) = (1-x1) * frac(x2, ..., xn, 0) // + x1 * prod(x2, ..., xn, 0), // and p2(x) = (1-x1) * frac(x2, ..., xn, 1) // + x1 * prod(x2, ..., xn, 1) // and gi(x) = (wi(x) + beta * perms_i(x) + gamma) // and fi(x) = (wi(x) + beta * s_id_i(x) + gamma) #[allow(clippy::too_many_arguments)] pub(crate) fn eval_perm_gate( prod_evals: &[F], frac_evals: &[F], witness_perm_evals: &[F], id_evals: &[F], perm_evals: &[F], alpha: F, beta: F, gamma: F, x1: F, ) -> Result { let p1_eval = frac_evals[1] + x1 * (prod_evals[1] - frac_evals[1]); let p2_eval = frac_evals[2] + x1 * (prod_evals[2] - frac_evals[2]); let mut f_prod_eval = F::one(); for (&w_eval, &id_eval) in witness_perm_evals.iter().zip(id_evals.iter()) { f_prod_eval *= w_eval + beta * id_eval + gamma; } let mut g_prod_eval = F::one(); for (&w_eval, &p_eval) in witness_perm_evals.iter().zip(perm_evals.iter()) { g_prod_eval *= w_eval + beta * p_eval + gamma; } let res = prod_evals[0] - p1_eval * p2_eval + alpha * (frac_evals[0] * g_prod_eval - f_prod_eval); Ok(res) } #[cfg(test)] mod test { use super::*; use ark_bls12_381::Fr; use ark_ff::PrimeField; use ark_poly::MultilinearExtension; #[test] fn test_build_gate() -> Result<(), HyperPlonkErrors> { test_build_gate_helper::() } fn test_build_gate_helper() -> Result<(), HyperPlonkErrors> { let num_vars = 2; // ql = 3x1x2 + 2x2 whose evaluations are // 0, 0 |-> 0 // 0, 1 |-> 2 // 1, 0 |-> 0 // 1, 1 |-> 5 let ql_eval = vec![F::zero(), F::from(2u64), F::zero(), F::from(5u64)]; let ql = Arc::new(DenseMultilinearExtension::from_evaluations_vec(2, ql_eval)); // W1 = x1x2 + x1 whose evaluations are // 0, 0 |-> 0 // 0, 1 |-> 0 // 1, 0 |-> 1 // 1, 1 |-> 2 let w_eval = vec![F::zero(), F::zero(), F::from(1u64), F::from(2u64)]; let w1 = Arc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval)); // W2 = x1 + x2 whose evaluations are // 0, 0 |-> 0 // 0, 1 |-> 1 // 1, 0 |-> 1 // 1, 1 |-> 2 let w_eval = vec![F::zero(), F::one(), F::from(1u64), F::from(2u64)]; let w2 = Arc::new(DenseMultilinearExtension::from_evaluations_vec(2, w_eval)); // Example: // q_L(X) * W_1(X)^5 - W_2(X) // is represented as // vec![ // ( 1, Some(id_qL), vec![id_W1, id_W1, id_W1, id_W1, id_W1]), // (-1, None, vec![id_W2]) // ] let gates = CustomizedGates { gates: vec![(1, Some(0), vec![0, 0, 0, 0, 0]), (-1, None, vec![1])], }; let f = build_f(&gates, num_vars, &[ql.clone()], &[w1.clone(), w2.clone()])?; // Sanity check on build_f // f(0, 0) = 0 assert_eq!(f.evaluate(&[F::zero(), F::zero()])?, F::zero()); // f(0, 1) = 2 * 0^5 + (-1) * 1 = -1 assert_eq!(f.evaluate(&[F::zero(), F::one()])?, -F::one()); // f(1, 0) = 0 * 1^5 + (-1) * 1 = -1 assert_eq!(f.evaluate(&[F::one(), F::zero()])?, -F::one()); // f(1, 1) = 5 * 2^5 + (-1) * 2 = 158 assert_eq!(f.evaluate(&[F::one(), F::one()])?, F::from(158u64)); // test eval_f { let point = [F::zero(), F::zero()]; let selector_evals = ql.evaluate(&point).unwrap(); let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()]; let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?; // f(0, 0) = 0 assert_eq!(eval_f, F::zero()); } { let point = [F::zero(), F::one()]; let selector_evals = ql.evaluate(&point).unwrap(); let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()]; let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?; // f(0, 1) = 2 * 0^5 + (-1) * 1 = -1 assert_eq!(eval_f, -F::one()); } { let point = [F::one(), F::zero()]; let selector_evals = ql.evaluate(&point).unwrap(); let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()]; let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?; // f(1, 0) = 0 * 1^5 + (-1) * 1 = -1 assert_eq!(eval_f, -F::one()); } { let point = [F::one(), F::one()]; let selector_evals = ql.evaluate(&point).unwrap(); let witness_evals = [w1.evaluate(&point).unwrap(), w2.evaluate(&point).unwrap()]; let eval_f = eval_f(&gates, &[selector_evals], &witness_evals)?; // f(1, 1) = 5 * 2^5 + (-1) * 2 = 158 assert_eq!(eval_f, F::from(158u64)); } Ok(()) } }