diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index dae4e07..3c300fd 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -169,7 +169,7 @@ pub struct MetaCircuit { // another permutation between wires (B, C, D) which allows the same with D // instead of A. pub(crate) permutations: Vec>, - pub(crate) permutation_queries: Vec>>, + pub(crate) permutation_queries: Vec>, } impl Default for MetaCircuit { @@ -203,7 +203,7 @@ impl MetaCircuit { let mut queries = vec![]; for wire in wires { - queries.push(self.query_advice(*wire, 0)); + queries.push(self.query_advice_index(*wire, 0)); } self.permutation_queries.push(queries); @@ -225,8 +225,7 @@ impl MetaCircuit { Polynomial::Fixed(index) } - /// Query an advice wire at a relative position - pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { + fn query_advice_index(&mut self, wire: AdviceWire, at: i32) -> usize { let at = Rotation(at); { let len = self.rotations.len(); @@ -237,7 +236,12 @@ impl MetaCircuit { let index = self.advice_queries.len(); self.advice_queries.push((wire, at)); - Polynomial::Advice(index) + index + } + + /// Query an advice wire at a relative position + pub fn query_advice(&mut self, wire: AdviceWire, at: i32) -> Polynomial { + Polynomial::Advice(self.query_advice_index(wire, at)) } /// Create a new gate diff --git a/src/plonk/domain.rs b/src/plonk/domain.rs index 6ebfb5c..3203fb2 100644 --- a/src/plonk/domain.rs +++ b/src/plonk/domain.rs @@ -29,6 +29,7 @@ pub struct EvaluationDomain { ifft_divisor: G::Scalar, extended_ifft_divisor: G::Scalar, t_evaluations: Vec, + barycentric_weight: G::Scalar, } impl EvaluationDomain { @@ -99,6 +100,10 @@ impl EvaluationDomain { G::Scalar::batch_invert(&mut t_evaluations); } + // The barycentric weight of 1 over the evaluation domain + // 1 / \prod_{i != 0} (1 - omega^i) + let barycentric_weight = G::Scalar::from(n).invert().unwrap(); + EvaluationDomain { n, k, @@ -113,6 +118,7 @@ impl EvaluationDomain { ifft_divisor, extended_ifft_divisor, t_evaluations, + barycentric_weight, } } @@ -260,4 +266,8 @@ impl EvaluationDomain { } point } + + pub fn get_barycentric_weight(&self) -> G::Scalar { + self.barycentric_weight + } } diff --git a/src/plonk/verifier.rs b/src/plonk/verifier.rs index a8347b5..76cbd62 100644 --- a/src/plonk/verifier.rs +++ b/src/plonk/verifier.rs @@ -25,15 +25,9 @@ impl Proof { // Sample x_1 challenge let x_1: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - // Check permutations - // Compute [omega^0, omega^1, ..., omega^{params.n - 1}] - let mut omega_powers = Vec::with_capacity(params.n as usize); - { - let mut cur = C::Scalar::one(); - for _ in 0..params.n { - omega_powers.push(cur); - cur *= &srs.domain.get_omega(); - } + // Hash each permutation product commitment + for c in &self.permutation_product_commitments { + hash_point(&mut transcript, c).expect("proof cannot contain points at infinity"); } // Sample x_2 challenge, which keeps the gates linearly independent. @@ -47,7 +41,7 @@ impl Proof { // Sample x_3 challenge, which is used to ensure the circuit is // satisfied with high probability. let x_3: C::Scalar = get_challenge_scalar(Challenge(transcript.squeeze().get_lower_128())); - let xn = x_3.pow(&[params.n as u64, 0, 0, 0]); + let x_3n = x_3.pow(&[params.n as u64, 0, 0, 0]); // Hash together all the openings provided by the prover into a new // transcript on the scalar field. @@ -58,6 +52,9 @@ impl Proof { .iter() .chain(self.fixed_evals.iter()) .chain(self.h_evals.iter()) + .chain(self.permutation_product_evals.iter()) + .chain(self.permutation_product_inv_evals.iter()) + .chain(self.permutation_evals.iter().flat_map(|evals| evals.iter())) { transcript_scalar.absorb(*eval); } @@ -82,46 +79,45 @@ impl Proof { h_eval += &evaluation; } - // Evaluate permutation polynomial at first point + // First element in each permutation product should be 1 // l_0(X) * (1 - z(X)) = 0 - for eval in self.permutation_product_evals.iter() { - h_eval *= &x_2; + { + // TODO: bubble this error up + let denominator = (x_3 - &C::Scalar::one()).invert().unwrap(); - let mut l0_eval = (C::Scalar::from_u64(params.n) * &(xn * &x_3 - &C::Scalar::one())) - * &(x_3 - &C::Scalar::one()).invert().unwrap(); - l0_eval *= &(C::Scalar::one() - &eval); + for eval in self.permutation_product_evals.iter() { + h_eval *= &x_2; - h_eval += &l0_eval; + let mut tmp = denominator; // 1 / (x_3 - 1) + tmp *= &(x_3n - &C::Scalar::one()); // (x_3^n - 1) / (x_3 - 1) + tmp *= &srs.domain.get_barycentric_weight(); // l_0(x_3) + tmp *= &(C::Scalar::one() - &eval); // l_0(X) * (1 - z(X)) + + h_eval += &tmp; + } } - // Evaluate permutation polynomial at subsequent points - for (perm_idx, queries) in srs.meta.permutation_queries.iter().enumerate() { + // z(X) \prod (p(X) + \beta s_i(X) + \gamma) - z(omega^{-1} X) \prod (p(X) + \delta^i \beta X + \gamma) + for (permutation_index, queries) in srs.meta.permutation_queries.iter().enumerate() { h_eval *= &x_2; - // queries is a vector of polynomials - let evals: Vec = queries + let mut left = self.permutation_product_evals[permutation_index]; + for (advice_eval, permutation_eval) in queries .iter() - .map(|poly| { - poly.evaluate( - &|index| self.fixed_evals[index], - &|index| self.advice_evals[index], - &|a, b| a + &b, - &|a, b| a * &b, - &|a, scalar| a * &scalar, - ) - }) - .collect(); - - let mut left = self.permutation_product_inv_evals[perm_idx]; - let mut cur_delta = x_0 * &x_3; - for eval in evals.iter() { - left *= &(*eval + &cur_delta + &x_1); - cur_delta *= &C::Scalar::DELTA; + .map(|&query_index| self.advice_evals[query_index]) + .zip(self.permutation_evals[permutation_index].iter()) + { + left *= &(advice_eval + &(x_0 * permutation_eval) + &x_1); } - let mut right = self.permutation_product_evals[perm_idx]; - for (perm_eval, eval) in self.permutation_evals[perm_idx].iter().zip(evals.iter()) { - right *= &(*eval + &(x_0 * perm_eval) + &x_1); + let mut right = self.permutation_product_inv_evals[permutation_index]; + let mut current_delta = x_0; + for advice_eval in queries + .iter() + .map(|&query_index| self.advice_evals[query_index]) + { + right *= &(advice_eval + ¤t_delta + &x_1); + current_delta *= &C::Scalar::DELTA; } h_eval += &left; @@ -133,10 +129,10 @@ impl Proof { let mut cur = C::Scalar::one(); for eval in &self.h_evals { expected_h_eval += &(cur * eval); - cur *= &xn; + cur *= &x_3n; } - if h_eval != (expected_h_eval * &(xn - &C::Scalar::one())) { + if h_eval != (expected_h_eval * &(x_3n - &C::Scalar::one())) { return false; } @@ -182,8 +178,38 @@ impl Proof { } let current_index = (*srs.meta.rotations.get(&Rotation::default()).unwrap()).0; - for (h_commitment, h_eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { - accumulate(current_index, *h_commitment, *h_eval); + for (commitment, eval) in self.h_commitments.iter().zip(self.h_evals.iter()) { + accumulate(current_index, *commitment, *eval); + } + + // Handle permutation arguments, if any exist + if !srs.meta.permutations.is_empty() { + // Open permutation product commitments at x_3 + for (commitment, eval) in self + .permutation_product_commitments + .iter() + .zip(self.permutation_product_evals.iter()) + { + accumulate(current_index, *commitment, *eval); + } + // Open permutation commitments for each permutation argument at x_3 + for (commitment, eval) in srs + .permutation_commitments + .iter() + .zip(self.permutation_evals.iter()) + .flat_map(|(commitments, evals)| commitments.iter().zip(evals.iter())) + { + accumulate(current_index, *commitment, *eval); + } + let current_index = (*srs.meta.rotations.get(&Rotation(-1)).unwrap()).0; + // Open permutation product commitments at \omega^{-1} x_3 + for (commitment, eval) in self + .permutation_product_commitments + .iter() + .zip(self.permutation_product_inv_evals.iter()) + { + accumulate(current_index, *commitment, *eval); + } } } @@ -210,7 +236,7 @@ impl Proof { // We can compute the expected f_eval at x_6 using the q_evals provided // by the prover and from x_5 let mut f_eval = C::Scalar::zero(); - for (&row, &point_index) in srs.meta.rotations.iter() { + for (&row, point_index) in srs.meta.rotations.iter() { let mut eval = self.q_evals[point_index.0]; let point = srs.domain.rotate_omega(x_3, row);