From aec88a852877a2773343851399aa07fb1efab61d Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Fri, 19 Nov 2021 18:11:14 +0100 Subject: [PATCH 01/12] First try --- src/field/extension_field/algebra.rs | 18 +++++ src/gadgets/polynomial.rs | 20 +++++ src/gates/gate_testing.rs | 3 + src/gates/interpolation.rs | 113 +++++++++++++++++++++------ src/plonk/circuit_builder.rs | 1 + src/plonk/circuit_data.rs | 7 +- src/plonk/prover.rs | 11 +++ src/polynomial/polynomial.rs | 21 +++++ 8 files changed, 167 insertions(+), 27 deletions(-) diff --git a/src/field/extension_field/algebra.rs b/src/field/extension_field/algebra.rs index 21438262..37f70f52 100644 --- a/src/field/extension_field/algebra.rs +++ b/src/field/extension_field/algebra.rs @@ -160,12 +160,30 @@ impl, const D: usize> PolynomialCoeffsAlgebra { .fold(ExtensionAlgebra::ZERO, |acc, &c| acc * x + c) } + pub fn eval_with_powers(&self, powers: &[ExtensionAlgebra]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + pub fn eval_base(&self, x: F) -> ExtensionAlgebra { self.coeffs .iter() .rev() .fold(ExtensionAlgebra::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + + pub fn eval_base_with_powers(&self, powers: &[F]) -> ExtensionAlgebra { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } } #[cfg(test)] diff --git a/src/gadgets/polynomial.rs b/src/gadgets/polynomial.rs index 9f631c10..48a5f8b7 100644 --- a/src/gadgets/polynomial.rs +++ b/src/gadgets/polynomial.rs @@ -64,4 +64,24 @@ impl PolynomialCoeffsExtAlgebraTarget { } acc } + pub fn eval_with_powers( + &self, + builder: &mut CircuitBuilder, + powers: &[ExtensionAlgebraTarget], + ) -> ExtensionAlgebraTarget + where + F: RichField + Extendable, + { + debug_assert_eq!(self.0.len(), powers.len() + 1); + let acc = self.0[0]; + self.0[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| builder.mul_add_ext_algebra(c, x, acc)) + // let mut acc = builder.zero_ext_algebra(); + // for &c in self.0.iter().rev() { + // acc = builder.mul_add_ext_algebra(point, acc, c); + // } + // acc + } } diff --git a/src/gates/gate_testing.rs b/src/gates/gate_testing.rs index 9fe8e835..15c17ba7 100644 --- a/src/gates/gate_testing.rs +++ b/src/gates/gate_testing.rs @@ -51,7 +51,9 @@ pub(crate) fn test_low_degree, G: Gate, const ); let expected_eval_degree = WITNESS_DEGREE * gate.degree(); + dbg!(WITNESS_DEGREE, gate.degree()); + dbg!(&constraint_eval_degrees); assert!( constraint_eval_degrees .iter() @@ -151,6 +153,7 @@ pub(crate) fn test_eval_fns, G: Gate, const D let evals_t = gate.eval_unfiltered_recursively(&mut builder, vars_t); pw.set_extension_targets(&evals_t, &evals); + dbg!(builder.num_gates()); let data = builder.build(); let proof = data.prove(pw)?; verify(proof, &data.verifier_only, &data.common) diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index d97eb009..536c87bb 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -1,10 +1,10 @@ use std::marker::PhantomData; use std::ops::Range; -use crate::field::extension_field::algebra::PolynomialCoeffsAlgebra; +use crate::field::extension_field::algebra::{ExtensionAlgebra, PolynomialCoeffsAlgebra}; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; -use crate::field::field_types::RichField; +use crate::field::field_types::{Field, RichField}; use crate::field::interpolation::interpolant; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; @@ -90,9 +90,26 @@ impl, const D: usize> InterpolationGate { start..start + D } + pub fn powers_init(&self, i: usize) -> usize { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wire_shift(); + } + self.start_coeffs() + self.num_points() * D + i + } + + pub fn powers_eval(&self, i: usize) -> Range { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wires_evaluation_point(); + } + let start = self.start_coeffs() + self.num_points() * D + self.num_points() - 1 + i * D; + start..start + D + } + /// End of wire indices, exclusive. fn end(&self) -> usize { - self.start_coeffs() + self.num_points() * D + self.powers_eval(self.num_points() - 1).end } /// The domain of the points we're interpolating. @@ -138,19 +155,34 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); - let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + .collect::>(); - let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, F::Extension::ONE); + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); + let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + let ointerpolant = PolynomialCoeffsAlgebra::new(ocoeffs); + + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); + let computed_value = ointerpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let mut evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .collect::>(); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); constraints @@ -161,19 +193,33 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext(self.wires_coeff(i))) - .collect(); + .collect::>(); + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, F::ONE); + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); let interpolant = PolynomialCoeffs::new(coeffs); + let ointerpolant = PolynomialCoeffs::new(ocoeffs); - let coset = self.coset(vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { + for (i, point) in F::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { let value = vars.get_local_ext(self.wires_value(i)); - let computed_value = interpolant.eval_base(point); + let computed_value = ointerpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } - let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext(self.powers_eval(i))) + .collect::>(); let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(evaluation_point); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); constraints @@ -188,13 +234,26 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect(); + .collect::>(); + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, builder.one_extension()); + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| builder.scalar_mul_ext_algebra(p, c)) + .collect::>(); let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); + let ointerpolant = PolynomialCoeffsExtAlgebraTarget(ocoeffs); - let coset = self.coset_ext_recursive(builder, vars.local_wires[self.wire_shift()]); - for (i, point) in coset.into_iter().enumerate() { + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = interpolant.eval_scalar(builder, point); + let point = builder.constant_extension(point); + let computed_value = ointerpolant.eval_scalar(builder, point); constraints.extend( &builder .sub_ext_algebra(value, computed_value) @@ -202,9 +261,15 @@ impl, const D: usize> Gate for InterpolationG ); } - let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .collect::>(); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + let computed_evaluation_value = + interpolant.eval_with_powers(builder, &evaluation_point_powers); + // let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + // let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + // let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( &builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) @@ -238,7 +303,7 @@ impl, const D: usize> Gate for InterpolationG fn degree(&self) -> usize { // The highest power of x is `num_points - 1`, and then multiplication by the coefficient // adds 1. - self.num_points() + 2 } fn num_constraints(&self) -> usize { @@ -357,7 +422,7 @@ mod tests { #[test] fn eval_fns() -> Result<()> { - test_eval_fns::(InterpolationGate::new(2)) + test_eval_fns::(InterpolationGate::new(3)) } #[test] diff --git a/src/plonk/circuit_builder.rs b/src/plonk/circuit_builder.rs index 32ea59b6..f19d6ae9 100644 --- a/src/plonk/circuit_builder.rs +++ b/src/plonk/circuit_builder.rs @@ -687,6 +687,7 @@ impl, const D: usize> CircuitBuilder { marked_targets: self.marked_targets, representative_map: forest.parents, fft_root_table: Some(fft_root_table), + instances: self.gate_instances, }; // The HashSet of gates will have a non-deterministic order. When converting to a Vec, we diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index c2a8d6d0..c0ccbc3c 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -9,7 +9,7 @@ use crate::field::field_types::{Field, RichField}; use crate::fri::commitment::PolynomialBatchCommitment; use crate::fri::reduction_strategies::FriReductionStrategy; use crate::fri::{FriConfig, FriParams}; -use crate::gates::gate::PrefixedGate; +use crate::gates::gate::{GateInstance, PrefixedGate}; use crate::hash::hash_types::{HashOut, MerkleCapTarget}; use crate::hash::merkle_tree::MerkleCap; use crate::iop::generator::WitnessGenerator; @@ -67,10 +67,10 @@ impl CircuitConfig { rate_bits: 3, num_challenges: 2, zero_knowledge: false, - cap_height: 3, + cap_height: 4, fri_config: FriConfig { proof_of_work_bits: 15, - reduction_strategy: FriReductionStrategy::ConstantArityBits(3, 5), + reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), num_query_rounds: 26, }, } @@ -177,6 +177,7 @@ pub(crate) struct ProverOnlyCircuitData, const D: u pub representative_map: Vec, /// Pre-computed roots for faster FFT. pub fft_root_table: Option>, + pub instances: Vec>, } /// Circuit data required by the verifier, but not the prover. diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index 3f8e607d..b5175f02 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -66,6 +66,17 @@ pub(crate) fn prove, const D: usize>( .collect() ); + // let rows = (0..degree) + // .map(|i| wires_values.iter().map(|w| w.values[i]).collect::>()) + // .collect::>(); + // for (i, r) in rows.iter().enumerate() { + // let c = rows.iter().filter(|&x| x == r).count(); + // let s = prover_data.instances[i].gate_ref.0.id(); + // if c > 1 && !s.starts_with("Noop") { + // println!("{} {} {}", prover_data.instances[i].gate_ref.0.id(), i, c); + // } + // } + let wires_commitment = timed!( timing, "compute wires commitment", diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index a021ecd2..3c732208 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -120,6 +120,15 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc * x + c) } + pub fn eval_with_powers(&self, powers: &[F]) -> F { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + c * x) + } + pub fn eval_base(&self, x: F::BaseField) -> F where F: FieldExtension, @@ -130,6 +139,18 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + pub fn eval_base_with_powers(&self, powers: &[F::BaseField]) -> F + where + F: FieldExtension, + { + debug_assert_eq!(self.coeffs.len(), powers.len() + 1); + let acc = self.coeffs[0]; + self.coeffs[1..] + .iter() + .zip(powers) + .fold(acc, |acc, (&x, &c)| acc + x.scalar_mul(c)) + } + pub fn lde_multiple(polys: Vec<&Self>, rate_bits: usize) -> Vec { polys.into_iter().map(|p| p.lde(rate_bits)).collect() } From 0d5ba7e755d2aa57ccea019c36edd933871f5478 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 10:44:19 +0100 Subject: [PATCH 02/12] Working recursive test --- src/gates/interpolation.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index 536c87bb..4d110522 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -359,8 +359,17 @@ impl, const D: usize> SimpleGenerator F::Extension::from_basefield_array(arr) }; + let wire_shift = get_local_wire(self.gate.wire_shift()); + + for i in 2..self.gate.num_points() { + out_buffer.set_wire( + local_wire(self.gate.powers_init(i)), + wire_shift.exp_u64(i as u64), + ); + } + // Compute the interpolant. - let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); + let points = self.gate.coset(wire_shift); let points = points .into_iter() .enumerate() @@ -374,6 +383,12 @@ impl, const D: usize> SimpleGenerator } let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + for i in 2..self.gate.num_points() { + out_buffer.set_extension_target( + ExtensionTarget::from_range(self.gate_index, self.gate.powers_eval(i)), + evaluation_point.exp_u64(i as u64), + ); + } let evaluation_value = interpolant.eval(evaluation_point); let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); From 442c8560b00e3d66c572660d90f896f56a491b96 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 11:16:58 +0100 Subject: [PATCH 03/12] Under 2^12 with 27 query rounds --- src/gates/interpolation.rs | 49 +++++++++++++++++++++++++++++++++++--- src/plonk/circuit_data.rs | 4 ++-- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index 4d110522..9f7fa216 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -161,6 +161,10 @@ impl, const D: usize> Gate for InterpolationG .map(|i| vars.local_wires[self.powers_init(i)]) .collect::>(); powers_init.insert(0, F::Extension::ONE); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + } let ocoeffs = coeffs .iter() .zip(powers_init) @@ -181,6 +185,13 @@ impl, const D: usize> Gate for InterpolationG let mut evaluation_point_powers = (1..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); @@ -198,6 +209,10 @@ impl, const D: usize> Gate for InterpolationG .map(|i| vars.local_wires[self.powers_init(i)]) .collect::>(); powers_init.insert(0, F::ONE); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + } let ocoeffs = coeffs .iter() .zip(powers_init) @@ -218,6 +233,13 @@ impl, const D: usize> Gate for InterpolationG let evaluation_point_powers = (1..self.num_points()) .map(|i| vars.get_local_ext(self.powers_eval(i))) .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); @@ -239,6 +261,14 @@ impl, const D: usize> Gate for InterpolationG .map(|i| vars.local_wires[self.powers_init(i)]) .collect::>(); powers_init.insert(0, builder.one_extension()); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(builder.mul_sub_extension( + powers_init[i - 1], + wire_shift, + powers_init[i], + )); + } let ocoeffs = coeffs .iter() .zip(powers_init) @@ -264,6 +294,18 @@ impl, const D: usize> Gate for InterpolationG let evaluation_point_powers = (1..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + let neg_one_ext = builder.neg_one_extension(); + let neg_new_power = + builder.scalar_mul_ext_algebra(neg_one_ext, evaluation_point_powers[i]); + let constraint = builder.mul_add_ext_algebra( + evaluation_point, + evaluation_point_powers[i - 1], + neg_new_power, + ); + constraints.extend(constraint.to_ext_target_array()); + } let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); let computed_evaluation_value = interpolant.eval_with_powers(builder, &evaluation_point_powers); @@ -307,9 +349,10 @@ impl, const D: usize> Gate for InterpolationG } fn num_constraints(&self) -> usize { - // num_points * D constraints to check for consistency between the coefficients and the - // point-value pairs, plus D constraints for the evaluation value. - self.num_points() * D + D + // `num_points * D` constraints to check for consistency between the coefficients and the + // point-value pairs, plus `D` constraints for the evaluation value, plus `(D+1)*(num_points-2)` + // to check power constraints for evaluation point and wire shift. + self.num_points() * D + D + (D + 1) * (self.num_points() - 2) } } diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index c0ccbc3c..81d8b09f 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -63,7 +63,7 @@ impl CircuitConfig { num_routed_wires: 80, constant_gate_size: 5, use_base_arithmetic_gate: true, - security_bits: 93, + security_bits: 96, rate_bits: 3, num_challenges: 2, zero_knowledge: false, @@ -71,7 +71,7 @@ impl CircuitConfig { fri_config: FriConfig { proof_of_work_bits: 15, reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), - num_query_rounds: 26, + num_query_rounds: 27, }, } } From 8522026c36810ac3a242873a2877130a0ef8d7e6 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 11:39:56 +0100 Subject: [PATCH 04/12] Change file structure --- src/gates/interpolation.rs | 177 ++------- src/gates/low_degree_interpolation.rs | 525 ++++++++++++++++++++++++++ src/gates/mod.rs | 1 + 3 files changed, 553 insertions(+), 150 deletions(-) create mode 100644 src/gates/low_degree_interpolation.rs diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index 9f7fa216..d97eb009 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -1,10 +1,10 @@ use std::marker::PhantomData; use std::ops::Range; -use crate::field::extension_field::algebra::{ExtensionAlgebra, PolynomialCoeffsAlgebra}; +use crate::field::extension_field::algebra::PolynomialCoeffsAlgebra; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; -use crate::field::field_types::{Field, RichField}; +use crate::field::field_types::RichField; use crate::field::interpolation::interpolant; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; @@ -90,26 +90,9 @@ impl, const D: usize> InterpolationGate { start..start + D } - pub fn powers_init(&self, i: usize) -> usize { - debug_assert!(0 < i && i < self.num_points()); - if i == 1 { - return self.wire_shift(); - } - self.start_coeffs() + self.num_points() * D + i - } - - pub fn powers_eval(&self, i: usize) -> Range { - debug_assert!(0 < i && i < self.num_points()); - if i == 1 { - return self.wires_evaluation_point(); - } - let start = self.start_coeffs() + self.num_points() * D + self.num_points() - 1 + i * D; - start..start + D - } - /// End of wire indices, exclusive. fn end(&self) -> usize { - self.powers_eval(self.num_points() - 1).end + self.start_coeffs() + self.num_points() * D } /// The domain of the points we're interpolating. @@ -155,45 +138,19 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect::>(); - - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) - .collect::>(); - powers_init.insert(0, F::Extension::ONE); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { - constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); - } - let ocoeffs = coeffs - .iter() - .zip(powers_init) - .map(|(&c, p)| c.scalar_mul(p)) - .collect::>(); + .collect(); let interpolant = PolynomialCoeffsAlgebra::new(coeffs); - let ointerpolant = PolynomialCoeffsAlgebra::new(ocoeffs); - for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) - .into_iter() - .enumerate() - { + let coset = self.coset_ext(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = ointerpolant.eval_base(point); + let computed_value = interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } - let mut evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) - .collect::>(); - let evaluation_point = evaluation_point_powers[0]; - for i in 1..self.num_points() - 1 { - constraints.extend( - (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) - .to_basefield_array(), - ); - } + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + let computed_evaluation_value = interpolant.eval(evaluation_point); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); constraints @@ -204,44 +161,19 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext(self.wires_coeff(i))) - .collect::>(); - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) - .collect::>(); - powers_init.insert(0, F::ONE); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { - constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); - } - let ocoeffs = coeffs - .iter() - .zip(powers_init) - .map(|(&c, p)| c.scalar_mul(p)) - .collect::>(); + .collect(); let interpolant = PolynomialCoeffs::new(coeffs); - let ointerpolant = PolynomialCoeffs::new(ocoeffs); - for (i, point) in F::two_adic_subgroup(self.subgroup_bits) - .into_iter() - .enumerate() - { + let coset = self.coset(vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext(self.wires_value(i)); - let computed_value = ointerpolant.eval_base(point); + let computed_value = interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } - let evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext(self.powers_eval(i))) - .collect::>(); - let evaluation_point = evaluation_point_powers[0]; - for i in 1..self.num_points() - 1 { - constraints.extend( - (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) - .to_basefield_array(), - ); - } + let evaluation_point = vars.get_local_ext(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); - let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + let computed_evaluation_value = interpolant.eval(evaluation_point); constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); constraints @@ -256,34 +188,13 @@ impl, const D: usize> Gate for InterpolationG let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) - .collect::>(); - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) - .collect::>(); - powers_init.insert(0, builder.one_extension()); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { - constraints.push(builder.mul_sub_extension( - powers_init[i - 1], - wire_shift, - powers_init[i], - )); - } - let ocoeffs = coeffs - .iter() - .zip(powers_init) - .map(|(&c, p)| builder.scalar_mul_ext_algebra(p, c)) - .collect::>(); + .collect(); let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); - let ointerpolant = PolynomialCoeffsExtAlgebraTarget(ocoeffs); - for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) - .into_iter() - .enumerate() - { + let coset = self.coset_ext_recursive(builder, vars.local_wires[self.wire_shift()]); + for (i, point) in coset.into_iter().enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); - let point = builder.constant_extension(point); - let computed_value = ointerpolant.eval_scalar(builder, point); + let computed_value = interpolant.eval_scalar(builder, point); constraints.extend( &builder .sub_ext_algebra(value, computed_value) @@ -291,27 +202,9 @@ impl, const D: usize> Gate for InterpolationG ); } - let evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) - .collect::>(); - let evaluation_point = evaluation_point_powers[0]; - for i in 1..self.num_points() - 1 { - let neg_one_ext = builder.neg_one_extension(); - let neg_new_power = - builder.scalar_mul_ext_algebra(neg_one_ext, evaluation_point_powers[i]); - let constraint = builder.mul_add_ext_algebra( - evaluation_point, - evaluation_point_powers[i - 1], - neg_new_power, - ); - constraints.extend(constraint.to_ext_target_array()); - } + let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - let computed_evaluation_value = - interpolant.eval_with_powers(builder, &evaluation_point_powers); - // let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); - // let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); - // let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + let computed_evaluation_value = interpolant.eval(builder, evaluation_point); constraints.extend( &builder .sub_ext_algebra(evaluation_value, computed_evaluation_value) @@ -345,14 +238,13 @@ impl, const D: usize> Gate for InterpolationG fn degree(&self) -> usize { // The highest power of x is `num_points - 1`, and then multiplication by the coefficient // adds 1. - 2 + self.num_points() } fn num_constraints(&self) -> usize { - // `num_points * D` constraints to check for consistency between the coefficients and the - // point-value pairs, plus `D` constraints for the evaluation value, plus `(D+1)*(num_points-2)` - // to check power constraints for evaluation point and wire shift. - self.num_points() * D + D + (D + 1) * (self.num_points() - 2) + // num_points * D constraints to check for consistency between the coefficients and the + // point-value pairs, plus D constraints for the evaluation value. + self.num_points() * D + D } } @@ -402,17 +294,8 @@ impl, const D: usize> SimpleGenerator F::Extension::from_basefield_array(arr) }; - let wire_shift = get_local_wire(self.gate.wire_shift()); - - for i in 2..self.gate.num_points() { - out_buffer.set_wire( - local_wire(self.gate.powers_init(i)), - wire_shift.exp_u64(i as u64), - ); - } - // Compute the interpolant. - let points = self.gate.coset(wire_shift); + let points = self.gate.coset(get_local_wire(self.gate.wire_shift())); let points = points .into_iter() .enumerate() @@ -426,12 +309,6 @@ impl, const D: usize> SimpleGenerator } let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); - for i in 2..self.gate.num_points() { - out_buffer.set_extension_target( - ExtensionTarget::from_range(self.gate_index, self.gate.powers_eval(i)), - evaluation_point.exp_u64(i as u64), - ); - } let evaluation_value = interpolant.eval(evaluation_point); let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); @@ -480,7 +357,7 @@ mod tests { #[test] fn eval_fns() -> Result<()> { - test_eval_fns::(InterpolationGate::new(3)) + test_eval_fns::(InterpolationGate::new(2)) } #[test] diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs new file mode 100644 index 00000000..95b168f7 --- /dev/null +++ b/src/gates/low_degree_interpolation.rs @@ -0,0 +1,525 @@ +use std::marker::PhantomData; +use std::ops::Range; + +use crate::field::extension_field::algebra::{ExtensionAlgebra, PolynomialCoeffsAlgebra}; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::{Extendable, FieldExtension}; +use crate::field::field_types::{Field, RichField}; +use crate::field::interpolation::interpolant; +use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; +use crate::gates::gate::Gate; +use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::iop::target::Target; +use crate::iop::wire::Wire; +use crate::iop::witness::{PartitionWitness, Witness}; +use crate::plonk::circuit_builder::CircuitBuilder; +use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::polynomial::polynomial::PolynomialCoeffs; + +/// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup +/// with the given size, and whose values are extension field elements, given by input wires. +/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +#[derive(Clone, Debug)] +pub(crate) struct LowDegreeInterpolationGate, const D: usize> { + pub subgroup_bits: usize, + _phantom: PhantomData, +} + +impl, const D: usize> LowDegreeInterpolationGate { + pub fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } + + /// Wire index of the coset shift. + pub fn wire_shift(&self) -> usize { + 0 + } + + fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + pub fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + pub fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + pub fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + pub(crate) fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + pub fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() + } + + pub fn powers_init(&self, i: usize) -> usize { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wire_shift(); + } + self.end_coeffs() + i - 2 + } + + pub fn powers_eval(&self, i: usize) -> Range { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wires_evaluation_point(); + } + let start = self.end_coeffs() + self.num_points() - 2 + (i - 2) * D; + start..start + D + } + + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.powers_eval(self.num_points() - 1).end + } + + /// The domain of the points we're interpolating. + fn coset(&self, shift: F) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. + g.powers().take(size).map(move |x| x * shift) + } + + /// The domain of the points we're interpolating. + fn coset_ext(&self, shift: F::Extension) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers().take(size).map(move |x| shift.scalar_mul(x)) + } + + /// The domain of the points we're interpolating. + fn coset_ext_recursive( + &self, + builder: &mut CircuitBuilder, + shift: ExtensionTarget, + ) -> Vec> { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + g.powers() + .take(size) + .map(move |x| { + let subgroup_element = builder.constant(x.into()); + builder.scalar_mul_ext(subgroup_element, shift) + }) + .collect() + } +} + +impl, const D: usize> Gate for LowDegreeInterpolationGate { + fn id(&self) -> String { + format!("{:?}", self, D) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect::>(); + + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, F::Extension::ONE); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + } + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); + let interpolant = PolynomialCoeffsAlgebra::new(coeffs); + let ointerpolant = PolynomialCoeffsAlgebra::new(ocoeffs); + + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let computed_value = ointerpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let mut evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext(self.wires_coeff(i))) + .collect::>(); + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, F::ONE); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + } + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| c.scalar_mul(p)) + .collect::>(); + let interpolant = PolynomialCoeffs::new(coeffs); + let ointerpolant = PolynomialCoeffs::new(ocoeffs); + + for (i, point) in F::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext(self.wires_value(i)); + let computed_value = ointerpolant.eval_base(point); + constraints.extend(&(value - computed_value).to_basefield_array()); + } + + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext(self.powers_eval(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + constraints.extend( + (evaluation_point_powers[i - 1] * evaluation_point - evaluation_point_powers[i]) + .to_basefield_array(), + ); + } + let evaluation_value = vars.get_local_ext(self.wires_evaluation_value()); + let computed_evaluation_value = interpolant.eval_with_powers(&evaluation_point_powers); + constraints.extend(&(evaluation_value - computed_evaluation_value).to_basefield_array()); + + constraints + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let mut constraints = Vec::with_capacity(self.num_constraints()); + + let coeffs = (0..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) + .collect::>(); + let mut powers_init = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_init(i)]) + .collect::>(); + powers_init.insert(0, builder.one_extension()); + let wire_shift = powers_init[1]; + for i in 2..self.num_points() { + constraints.push(builder.mul_sub_extension( + powers_init[i - 1], + wire_shift, + powers_init[i], + )); + } + let ocoeffs = coeffs + .iter() + .zip(powers_init) + .map(|(&c, p)| builder.scalar_mul_ext_algebra(p, c)) + .collect::>(); + let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); + let ointerpolant = PolynomialCoeffsExtAlgebraTarget(ocoeffs); + + for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) + .into_iter() + .enumerate() + { + let value = vars.get_local_ext_algebra(self.wires_value(i)); + let point = builder.constant_extension(point); + let computed_value = ointerpolant.eval_scalar(builder, point); + constraints.extend( + &builder + .sub_ext_algebra(value, computed_value) + .to_ext_target_array(), + ); + } + + let evaluation_point_powers = (1..self.num_points()) + .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .collect::>(); + let evaluation_point = evaluation_point_powers[0]; + for i in 1..self.num_points() - 1 { + let neg_one_ext = builder.neg_one_extension(); + let neg_new_power = + builder.scalar_mul_ext_algebra(neg_one_ext, evaluation_point_powers[i]); + let constraint = builder.mul_add_ext_algebra( + evaluation_point, + evaluation_point_powers[i - 1], + neg_new_power, + ); + constraints.extend(constraint.to_ext_target_array()); + } + let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + let computed_evaluation_value = + interpolant.eval_with_powers(builder, &evaluation_point_powers); + // let evaluation_point = vars.get_local_ext_algebra(self.wires_evaluation_point()); + // let evaluation_value = vars.get_local_ext_algebra(self.wires_evaluation_value()); + // let computed_evaluation_value = interpolant.eval(builder, evaluation_point); + constraints.extend( + &builder + .sub_ext_algebra(evaluation_value, computed_evaluation_value) + .to_ext_target_array(), + ); + + constraints + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + ) -> Vec>> { + let gen = InterpolationGenerator:: { + gate_index, + gate: self.clone(), + _phantom: PhantomData, + }; + vec![Box::new(gen.adapter())] + } + + fn num_wires(&self) -> usize { + self.end() + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + // The highest power of x is `num_points - 1`, and then multiplication by the coefficient + // adds 1. + 2 + } + + fn num_constraints(&self) -> usize { + // `num_points * D` constraints to check for consistency between the coefficients and the + // point-value pairs, plus `D` constraints for the evaluation value, plus `(D+1)*(num_points-2)` + // to check power constraints for evaluation point and wire shift. + self.num_points() * D + D + (D + 1) * (self.num_points() - 2) + } +} + +#[derive(Debug)] +struct InterpolationGenerator, const D: usize> { + gate_index: usize, + gate: LowDegreeInterpolationGate, + _phantom: PhantomData, +} + +impl, const D: usize> SimpleGenerator + for InterpolationGenerator +{ + fn dependencies(&self) -> Vec { + let local_target = |input| { + Target::Wire(Wire { + gate: self.gate_index, + input, + }) + }; + + let local_targets = |inputs: Range| inputs.map(local_target); + + let num_points = self.gate.num_points(); + let mut deps = Vec::with_capacity(1 + D + num_points * D); + + deps.push(local_target(self.gate.wire_shift())); + deps.extend(local_targets(self.gate.wires_evaluation_point())); + for i in 0..num_points { + deps.extend(local_targets(self.gate.wires_value(i))); + } + deps + } + + fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut GeneratedValues) { + let local_wire = |input| Wire { + gate: self.gate_index, + input, + }; + + let get_local_wire = |input| witness.get_wire(local_wire(input)); + + let get_local_ext = |wire_range: Range| { + debug_assert_eq!(wire_range.len(), D); + let values = wire_range.map(get_local_wire).collect::>(); + let arr = values.try_into().unwrap(); + F::Extension::from_basefield_array(arr) + }; + + let wire_shift = get_local_wire(self.gate.wire_shift()); + + for i in 2..self.gate.num_points() { + out_buffer.set_wire( + local_wire(self.gate.powers_init(i)), + wire_shift.exp_u64(i as u64), + ); + } + + // Compute the interpolant. + let points = self.gate.coset(wire_shift); + let points = points + .into_iter() + .enumerate() + .map(|(i, point)| (point.into(), get_local_ext(self.gate.wires_value(i)))) + .collect::>(); + let interpolant = interpolant(&points); + + for (i, &coeff) in interpolant.coeffs.iter().enumerate() { + let wires = self.gate.wires_coeff(i).map(local_wire); + out_buffer.set_ext_wires(wires, coeff); + } + + let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); + for i in 2..self.gate.num_points() { + out_buffer.set_extension_target( + ExtensionTarget::from_range(self.gate_index, self.gate.powers_eval(i)), + evaluation_point.exp_u64(i as u64), + ); + } + let evaluation_value = interpolant.eval(evaluation_point); + let evaluation_value_wires = self.gate.wires_evaluation_value().map(local_wire); + out_buffer.set_ext_wires(evaluation_value_wires, evaluation_value); + } +} + +#[cfg(test)] +mod tests { + use std::marker::PhantomData; + + use anyhow::Result; + + use crate::field::extension_field::quadratic::QuadraticExtension; + use crate::field::extension_field::quartic::QuarticExtension; + use crate::field::field_types::Field; + use crate::field::goldilocks_field::GoldilocksField; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; + use crate::hash::hash_types::HashOut; + use crate::plonk::vars::EvaluationVars; + use crate::polynomial::polynomial::PolynomialCoeffs; + + #[test] + fn low_degree() { + test_low_degree::(LowDegreeInterpolationGate::new(4)); + } + + #[test] + fn eval_fns() -> Result<()> { + test_eval_fns::(LowDegreeInterpolationGate::new(4)) + } + + #[test] + fn test_gate_constraint() { + type F = GoldilocksField; + type FF = QuadraticExtension; + const D: usize = 2; + + /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. + fn get_wires( + gate: &LowDegreeInterpolationGate, + shift: F, + coeffs: PolynomialCoeffs, + eval_point: FF, + ) -> Vec { + let points = gate.coset(shift); + let mut v = vec![shift]; + for x in points { + v.extend(coeffs.eval(x.into()).0); + } + v.extend(eval_point.0); + v.extend(coeffs.eval(eval_point).0); + for i in 0..coeffs.len() { + v.extend(coeffs.coeffs[i].0); + } + v.extend(shift.powers().skip(2).take(gate.num_points() - 2)); + v.extend( + eval_point + .powers() + .skip(2) + .take(gate.num_points() - 2) + .flat_map(|ff| ff.0), + ); + v.iter().map(|&x| x.into()).collect::>() + } + + // Get a working row for LowDegreeInterpolationGate. + let subgroup_bits = 4; + let shift = F::rand(); + let coeffs = PolynomialCoeffs::new(FF::rand_vec(1 << subgroup_bits)); + let eval_point = FF::rand(); + let gate = LowDegreeInterpolationGate::::new(subgroup_bits); + dbg!(gate.end_coeffs()); + dbg!(gate.powers_eval(15)); + let vars = EvaluationVars { + local_constants: &[], + local_wires: &get_wires(&gate, shift, coeffs, eval_point), + public_inputs_hash: &HashOut::rand(), + }; + + assert!( + gate.eval_unfiltered(vars).iter().all(|x| x.is_zero()), + "Gate constraints are not satisfied." + ); + } +} diff --git a/src/gates/mod.rs b/src/gates/mod.rs index 93de5e97..1663fdd0 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -14,6 +14,7 @@ pub mod gate_tree; pub mod gmimc; pub mod insertion; pub mod interpolation; +pub mod low_degree_interpolation; pub mod noop; pub mod poseidon; pub(crate) mod poseidon_mds; From 6aaea002edf5607c2d380fe9ab48c52d575730a6 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 16:10:14 +0100 Subject: [PATCH 05/12] Choose between high- and low-degree interpolation gate depending on the arity --- src/fri/recursive_verifier.rs | 24 +++- src/gadgets/interpolation.rs | 101 +++++++++++++-- src/gates/gate_tree.rs | 5 +- src/gates/interpolation.rs | 158 ++++++++++++----------- src/gates/low_degree_interpolation.rs | 176 ++++++++++++-------------- src/plonk/circuit_builder.rs | 1 - src/plonk/circuit_data.rs | 3 +- 7 files changed, 280 insertions(+), 188 deletions(-) diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index a08dd99e..f543b12b 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -3,8 +3,10 @@ use crate::field::extension_field::Extendable; use crate::field::field_types::{Field, RichField}; use crate::fri::proof::{FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget}; use crate::fri::FriConfig; +use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; -use crate::gates::interpolation::InterpolationGate; +use crate::gates::interpolation::HighDegreeInterpolationGate; +use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::gates::random_access::RandomAccessGate; use crate::hash::hash_types::MerkleCapTarget; use crate::iop::challenger::RecursiveChallenger; @@ -27,6 +29,7 @@ impl, const D: usize> CircuitBuilder { arity_bits: usize, evals: &[ExtensionTarget], beta: ExtensionTarget, + common_data: &CommonCircuitData, ) -> ExtensionTarget { let arity = 1 << arity_bits; debug_assert_eq!(evals.len(), arity); @@ -43,7 +46,21 @@ impl, const D: usize> CircuitBuilder { let coset_start = self.mul(start, x); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. - self.interpolate_coset(arity_bits, coset_start, &evals, beta) + if 1 << arity_bits > common_data.quotient_degree_factor { + self.interpolate_coset::>( + arity_bits, + coset_start, + &evals, + beta, + ) + } else { + self.interpolate_coset::>( + arity_bits, + coset_start, + &evals, + beta, + ) + } } /// Make sure we have enough wires and routed wires to do the FRI checks efficiently. This check @@ -54,7 +71,7 @@ impl, const D: usize> CircuitBuilder { &self.config, max_fri_arity_bits.max(self.config.cap_height), ); - let interpolation_gate = InterpolationGate::::new(max_fri_arity_bits); + let interpolation_gate = HighDegreeInterpolationGate::::new(max_fri_arity_bits); let min_wires = random_access .num_wires() @@ -379,6 +396,7 @@ impl, const D: usize> CircuitBuilder { arity_bits, evals, betas[i], + &common_data ) ); diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 09b6329b..5d23b65b 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -1,23 +1,90 @@ +use std::ops::Range; + use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; -use crate::gates::interpolation::InterpolationGate; +use crate::gates::gate::Gate; use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; +pub(crate) trait InterpolationGate, const D: usize>: + Gate + Copy +{ + fn new(subgroup_bits: usize) -> Self; + + fn num_points(&self) -> usize; + + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { + 0 + } + + fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() + } +} + impl, const D: usize> CircuitBuilder { /// Interpolates a polynomial, whose points are a coset of the multiplicative subgroup with the /// given size, and whose values are given. Returns the evaluation of the interpolant at /// `evaluation_point`. - pub fn interpolate_coset( + pub(crate) fn interpolate_coset>( &mut self, subgroup_bits: usize, coset_shift: Target, values: &[ExtensionTarget], evaluation_point: ExtensionTarget, ) -> ExtensionTarget { - let gate = InterpolationGate::new(subgroup_bits); - let gate_index = self.add_gate(gate.clone(), vec![]); + let gate = G::new(subgroup_bits); + let gate_index = self.add_gate(gate, vec![]); self.connect(coset_shift, Target::wire(gate_index, gate.wire_shift())); for (i, &v) in values.iter().enumerate() { self.connect_extension( @@ -38,11 +105,14 @@ impl, const D: usize> CircuitBuilder { mod tests { use anyhow::Result; + use crate::field::extension_field::quadratic::QuadraticExtension; use crate::field::extension_field::quartic::QuarticExtension; use crate::field::extension_field::FieldExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::field::interpolation::interpolant; + use crate::gates::interpolation::HighDegreeInterpolationGate; + use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CircuitConfig; @@ -51,10 +121,11 @@ mod tests { #[test] fn test_interpolate() -> Result<()> { type F = GoldilocksField; - type FF = QuarticExtension; + const D: usize = 2; + type FF = QuadraticExtension; let config = CircuitConfig::standard_recursion_config(); let pw = PartialWitness::new(); - let mut builder = CircuitBuilder::::new(config); + let mut builder = CircuitBuilder::::new(config); let subgroup_bits = 2; let len = 1 << subgroup_bits; @@ -66,7 +137,7 @@ mod tests { let homogeneous_points = points .iter() .zip(values.iter()) - .map(|(&a, &b)| (>::from_basefield(a), b)) + .map(|(&a, &b)| (>::from_basefield(a), b)) .collect::>(); let true_interpolant = interpolant(&homogeneous_points); @@ -83,9 +154,21 @@ mod tests { let zt = builder.constant_extension(z); - let eval = builder.interpolate_coset(subgroup_bits, coset_shift_target, &value_targets, zt); + let eval_hd = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); + let eval_ld = builder.interpolate_coset::>( + subgroup_bits, + coset_shift_target, + &value_targets, + zt, + ); let true_eval_target = builder.constant_extension(true_eval); - builder.connect_extension(eval, true_eval_target); + builder.connect_extension(eval_hd, true_eval_target); + builder.connect_extension(eval_ld, true_eval_target); let data = builder.build(); let proof = data.prove(pw)?; diff --git a/src/gates/gate_tree.rs b/src/gates/gate_tree.rs index 704b410a..c2a517d0 100644 --- a/src/gates/gate_tree.rs +++ b/src/gates/gate_tree.rs @@ -225,11 +225,12 @@ mod tests { use super::*; use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; use crate::gates::arithmetic_extension::ArithmeticExtensionGate; use crate::gates::base_sum::BaseSumGate; use crate::gates::constant::ConstantGate; use crate::gates::gmimc::GMiMCGate; - use crate::gates::interpolation::InterpolationGate; + use crate::gates::interpolation::HighDegreeInterpolationGate; use crate::gates::noop::NoopGate; #[test] @@ -244,7 +245,7 @@ mod tests { GateRef::new(ArithmeticExtensionGate { num_ops: 4 }), GateRef::new(BaseSumGate::<4>::new(4)), GateRef::new(GMiMCGate::::new()), - GateRef::new(InterpolationGate::new(2)), + GateRef::new(HighDegreeInterpolationGate::new(2)), ]; let (tree, _, _) = Tree::from_gates(gates.clone()); diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index d97eb009..58288bba 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -6,6 +6,7 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field_types::RichField; use crate::field::interpolation::interpolant; +use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; @@ -19,77 +20,13 @@ use crate::polynomial::polynomial::PolynomialCoeffs; /// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup /// with the given size, and whose values are extension field elements, given by input wires. /// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. -#[derive(Clone, Debug)] -pub(crate) struct InterpolationGate, const D: usize> { +#[derive(Copy, Clone, Debug)] +pub(crate) struct HighDegreeInterpolationGate, const D: usize> { pub subgroup_bits: usize, _phantom: PhantomData, } -impl, const D: usize> InterpolationGate { - pub fn new(subgroup_bits: usize) -> Self { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } - - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } - - /// Wire index of the coset shift. - pub fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - pub fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - pub fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - pub fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - pub(crate) fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - pub fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - +impl, const D: usize> HighDegreeInterpolationGate { /// End of wire indices, exclusive. fn end(&self) -> usize { self.start_coeffs() + self.num_points() * D @@ -128,7 +65,77 @@ impl, const D: usize> InterpolationGate { } } -impl, const D: usize> Gate for InterpolationGate { +impl, const D: usize> InterpolationGate + for HighDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } + + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { + 0 + } + + fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } +} + +impl, const D: usize> Gate + for HighDegreeInterpolationGate +{ fn id(&self) -> String { format!("{:?}", self, D) } @@ -251,7 +258,7 @@ impl, const D: usize> Gate for InterpolationG #[derive(Debug)] struct InterpolationGenerator, const D: usize> { gate_index: usize, - gate: InterpolationGate, + gate: HighDegreeInterpolationGate, _phantom: PhantomData, } @@ -324,16 +331,17 @@ mod tests { use crate::field::extension_field::quartic::QuarticExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; - use crate::gates::interpolation::InterpolationGate; + use crate::gates::interpolation::HighDegreeInterpolationGate; use crate::hash::hash_types::HashOut; use crate::plonk::vars::EvaluationVars; use crate::polynomial::polynomial::PolynomialCoeffs; #[test] fn wire_indices() { - let gate = InterpolationGate:: { + let gate = HighDegreeInterpolationGate:: { subgroup_bits: 1, _phantom: PhantomData, }; @@ -352,12 +360,12 @@ mod tests { #[test] fn low_degree() { - test_low_degree::(InterpolationGate::new(2)); + test_low_degree::(HighDegreeInterpolationGate::new(2)); } #[test] fn eval_fns() -> Result<()> { - test_eval_fns::(InterpolationGate::new(2)) + test_eval_fns::(HighDegreeInterpolationGate::new(2)) } #[test] @@ -368,7 +376,7 @@ mod tests { /// Returns the local wires for an interpolation gate for given coeffs, points and eval point. fn get_wires( - gate: &InterpolationGate, + gate: &HighDegreeInterpolationGate, shift: F, coeffs: PolynomialCoeffs, eval_point: FF, @@ -390,7 +398,7 @@ mod tests { let shift = F::rand(); let coeffs = PolynomialCoeffs::new(vec![FF::rand(), FF::rand()]); let eval_point = FF::rand(); - let gate = InterpolationGate::::new(1); + let gate = HighDegreeInterpolationGate::::new(1); let vars = EvaluationVars { local_constants: &[], local_wires: &get_wires(&gate, shift, coeffs, eval_point), diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index 95b168f7..d38bd773 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -1,11 +1,12 @@ use std::marker::PhantomData; use std::ops::Range; -use crate::field::extension_field::algebra::{ExtensionAlgebra, PolynomialCoeffsAlgebra}; +use crate::field::extension_field::algebra::PolynomialCoeffsAlgebra; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field_types::{Field, RichField}; use crate::field::interpolation::interpolant; +use crate::gadgets::interpolation::InterpolationGate; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::Gate; use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; @@ -19,81 +20,13 @@ use crate::polynomial::polynomial::PolynomialCoeffs; /// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup /// with the given size, and whose values are extension field elements, given by input wires. /// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. -#[derive(Clone, Debug)] +#[derive(Copy, Clone, Debug)] pub(crate) struct LowDegreeInterpolationGate, const D: usize> { pub subgroup_bits: usize, _phantom: PhantomData, } impl, const D: usize> LowDegreeInterpolationGate { - pub fn new(subgroup_bits: usize) -> Self { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } - - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } - - /// Wire index of the coset shift. - pub fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - pub fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - pub fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - pub fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - pub(crate) fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - pub fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - - fn end_coeffs(&self) -> usize { - self.start_coeffs() + D * self.num_points() - } - pub fn powers_init(&self, i: usize) -> usize { debug_assert!(0 < i && i < self.num_points()); if i == 1 { @@ -123,29 +56,77 @@ impl, const D: usize> LowDegreeInterpolationGate impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers().take(size).map(move |x| shift.scalar_mul(x)) +impl, const D: usize> InterpolationGate + for LowDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } } - /// The domain of the points we're interpolating. - fn coset_ext_recursive( - &self, - builder: &mut CircuitBuilder, - shift: ExtensionTarget, - ) -> Vec> { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - g.powers() - .take(size) - .map(move |x| { - let subgroup_element = builder.constant(x.into()); - builder.scalar_mul_ext(subgroup_element, shift) - }) - .collect() + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } + + /// Wire index of the coset shift. + fn wire_shift(&self) -> usize { + 0 + } + + fn start_values(&self) -> usize { + 1 + } + + /// Wire indices of the `i`th interpolant value. + fn wires_value(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_values() + i * D; + start..start + D + } + + fn start_evaluation_point(&self) -> usize { + self.start_values() + self.num_points() * D + } + + /// Wire indices of the point to evaluate the interpolant at. + fn wires_evaluation_point(&self) -> Range { + let start = self.start_evaluation_point(); + start..start + D + } + + fn start_evaluation_value(&self) -> usize { + self.start_evaluation_point() + D + } + + /// Wire indices of the interpolated value. + fn wires_evaluation_value(&self) -> Range { + let start = self.start_evaluation_value(); + start..start + D + } + + fn start_coeffs(&self) -> usize { + self.start_evaluation_value() + D + } + + /// The number of routed wires required in the typical usage of this gate, where the points to + /// interpolate, the evaluation point, and the corresponding value are all routed. + fn num_routed_wires(&self) -> usize { + self.start_coeffs() + } + + /// Wire indices of the interpolant's `i`th coefficient. + fn wires_coeff(&self, i: usize) -> Range { + debug_assert!(i < self.num_points()); + let start = self.start_coeffs() + i * D; + start..start + D + } + + fn end_coeffs(&self) -> usize { + self.start_coeffs() + D * self.num_points() } } @@ -186,7 +167,7 @@ impl, const D: usize> Gate for LowDegreeInter constraints.extend(&(value - computed_value).to_basefield_array()); } - let mut evaluation_point_powers = (1..self.num_points()) + let evaluation_point_powers = (1..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) .collect::>(); let evaluation_point = evaluation_point_powers[0]; @@ -408,11 +389,13 @@ impl, const D: usize> SimpleGenerator let wire_shift = get_local_wire(self.gate.wire_shift()); - for i in 2..self.gate.num_points() { - out_buffer.set_wire( - local_wire(self.gate.powers_init(i)), - wire_shift.exp_u64(i as u64), - ); + for (i, power) in wire_shift + .powers() + .take(self.gate.num_points()) + .enumerate() + .skip(2) + { + out_buffer.set_wire(local_wire(self.gate.powers_init(i)), power); } // Compute the interpolant. @@ -452,6 +435,7 @@ mod tests { use crate::field::extension_field::quartic::QuarticExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; + use crate::gadgets::interpolation::InterpolationGate; use crate::gates::gate::Gate; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; use crate::gates::low_degree_interpolation::LowDegreeInterpolationGate; diff --git a/src/plonk/circuit_builder.rs b/src/plonk/circuit_builder.rs index f19d6ae9..32ea59b6 100644 --- a/src/plonk/circuit_builder.rs +++ b/src/plonk/circuit_builder.rs @@ -687,7 +687,6 @@ impl, const D: usize> CircuitBuilder { marked_targets: self.marked_targets, representative_map: forest.parents, fft_root_table: Some(fft_root_table), - instances: self.gate_instances, }; // The HashSet of gates will have a non-deterministic order. When converting to a Vec, we diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index 81d8b09f..d1b03570 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -9,7 +9,7 @@ use crate::field::field_types::{Field, RichField}; use crate::fri::commitment::PolynomialBatchCommitment; use crate::fri::reduction_strategies::FriReductionStrategy; use crate::fri::{FriConfig, FriParams}; -use crate::gates::gate::{GateInstance, PrefixedGate}; +use crate::gates::gate::PrefixedGate; use crate::hash::hash_types::{HashOut, MerkleCapTarget}; use crate::hash::merkle_tree::MerkleCap; use crate::iop::generator::WitnessGenerator; @@ -177,7 +177,6 @@ pub(crate) struct ProverOnlyCircuitData, const D: u pub representative_map: Vec, /// Pre-computed roots for faster FFT. pub fft_root_table: Option>, - pub instances: Vec>, } /// Circuit data required by the verifier, but not the prover. From e06ce5aa2f549ff90afa2fff3f9ac4b9e3929e69 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 16:41:33 +0100 Subject: [PATCH 06/12] Fix proof compression test --- src/plonk/proof.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plonk/proof.rs b/src/plonk/proof.rs index 815f807d..70e38588 100644 --- a/src/plonk/proof.rs +++ b/src/plonk/proof.rs @@ -310,6 +310,7 @@ mod tests { use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::fri::reduction_strategies::FriReductionStrategy; + use crate::gates::noop::NoopGate; use crate::iop::witness::PartialWitness; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::circuit_data::CircuitConfig; @@ -336,6 +337,9 @@ mod tests { let zt = builder.constant(z); let comp_zt = builder.mul(xt, yt); builder.connect(zt, comp_zt); + for _ in 0..100 { + builder.add_gate(NoopGate, vec![]); + } let data = builder.build(); let proof = data.prove(pw)?; verify(proof.clone(), &data.verifier_only, &data.common)?; From b7cb7e234f7afda34bd801679ffbf2c1ef23d9eb Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 17:06:40 +0100 Subject: [PATCH 07/12] Minor --- src/gadgets/interpolation.rs | 1 - src/gates/low_degree_interpolation.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 5d23b65b..9167e771 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -106,7 +106,6 @@ mod tests { use anyhow::Result; use crate::field::extension_field::quadratic::QuadraticExtension; - use crate::field::extension_field::quartic::QuarticExtension; use crate::field::extension_field::FieldExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index d38bd773..1a869941 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -427,12 +427,9 @@ impl, const D: usize> SimpleGenerator #[cfg(test)] mod tests { - use std::marker::PhantomData; - use anyhow::Result; use crate::field::extension_field::quadratic::QuadraticExtension; - use crate::field::extension_field::quartic::QuarticExtension; use crate::field::field_types::Field; use crate::field::goldilocks_field::GoldilocksField; use crate::gadgets::interpolation::InterpolationGate; From 5ea632f2a866745440cd33323ace7f55e8fac020 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 17:30:13 +0100 Subject: [PATCH 08/12] Fix size optimized test --- src/gates/gate_testing.rs | 3 --- src/gates/low_degree_interpolation.rs | 2 -- src/plonk/circuit_data.rs | 13 +++++++++++++ src/plonk/recursive_verifier.rs | 4 ++-- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/gates/gate_testing.rs b/src/gates/gate_testing.rs index 15c17ba7..9fe8e835 100644 --- a/src/gates/gate_testing.rs +++ b/src/gates/gate_testing.rs @@ -51,9 +51,7 @@ pub(crate) fn test_low_degree, G: Gate, const ); let expected_eval_degree = WITNESS_DEGREE * gate.degree(); - dbg!(WITNESS_DEGREE, gate.degree()); - dbg!(&constraint_eval_degrees); assert!( constraint_eval_degrees .iter() @@ -153,7 +151,6 @@ pub(crate) fn test_eval_fns, G: Gate, const D let evals_t = gate.eval_unfiltered_recursively(&mut builder, vars_t); pw.set_extension_targets(&evals_t, &evals); - dbg!(builder.num_gates()); let data = builder.build(); let proof = data.prove(pw)?; verify(proof, &data.verifier_only, &data.common) diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index 1a869941..c79a2a37 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -490,8 +490,6 @@ mod tests { let coeffs = PolynomialCoeffs::new(FF::rand_vec(1 << subgroup_bits)); let eval_point = FF::rand(); let gate = LowDegreeInterpolationGate::::new(subgroup_bits); - dbg!(gate.end_coeffs()); - dbg!(gate.powers_eval(15)); let vars = EvaluationVars { local_constants: &[], local_wires: &get_wires(&gate, shift, coeffs, eval_point), diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index d1b03570..41e89ea2 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -76,6 +76,19 @@ impl CircuitConfig { } } + pub fn size_optimized_recursion_config() -> Self { + Self { + security_bits: 93, + cap_height: 3, + fri_config: FriConfig { + reduction_strategy: FriReductionStrategy::ConstantArityBits(3, 5), + num_query_rounds: 26, + ..CircuitConfig::standard_recursion_config().fri_config + }, + ..CircuitConfig::standard_recursion_config() + } + } + pub fn standard_recursion_zk_config() -> Self { CircuitConfig { zero_knowledge: true, diff --git a/src/plonk/recursive_verifier.rs b/src/plonk/recursive_verifier.rs index 2dc0223b..98c866f8 100644 --- a/src/plonk/recursive_verifier.rs +++ b/src/plonk/recursive_verifier.rs @@ -408,7 +408,7 @@ mod tests { type F = GoldilocksField; const D: usize = 2; - let standard_config = CircuitConfig::standard_recursion_config(); + let standard_config = CircuitConfig::size_optimized_recursion_config(); // An initial dummy proof. let (proof, vd, cd) = dummy_proof::(&standard_config, 4_000)?; @@ -456,7 +456,7 @@ mod tests { num_routed_wires: 25, fri_config: FriConfig { proof_of_work_bits: 21, - reduction_strategy: FriReductionStrategy::MinSize(None), + reduction_strategy: FriReductionStrategy::MinSize(Some(3)), num_query_rounds: 9, }, ..high_rate_config From fa29db1dcb51ef711551d6837abf25b1e0b6389f Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 17:54:58 +0100 Subject: [PATCH 09/12] Clean low-degree interpolation gate --- src/gates/low_degree_interpolation.rs | 159 ++++++++++++++------------ 1 file changed, 86 insertions(+), 73 deletions(-) diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index c79a2a37..245c6d17 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -26,38 +26,6 @@ pub(crate) struct LowDegreeInterpolationGate, const _phantom: PhantomData, } -impl, const D: usize> LowDegreeInterpolationGate { - pub fn powers_init(&self, i: usize) -> usize { - debug_assert!(0 < i && i < self.num_points()); - if i == 1 { - return self.wire_shift(); - } - self.end_coeffs() + i - 2 - } - - pub fn powers_eval(&self, i: usize) -> Range { - debug_assert!(0 < i && i < self.num_points()); - if i == 1 { - return self.wires_evaluation_point(); - } - let start = self.end_coeffs() + self.num_points() - 2 + (i - 2) * D; - start..start + D - } - - /// End of wire indices, exclusive. - fn end(&self) -> usize { - self.powers_eval(self.num_points() - 1).end - } - - /// The domain of the points we're interpolating. - fn coset(&self, shift: F) -> impl Iterator { - let g = F::primitive_root_of_unity(self.subgroup_bits); - let size = 1 << self.subgroup_bits; - // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. - g.powers().take(size).map(move |x| x * shift) - } -} - impl, const D: usize> InterpolationGate for LowDegreeInterpolationGate { @@ -130,6 +98,40 @@ impl, const D: usize> InterpolationGate } } +impl, const D: usize> LowDegreeInterpolationGate { + /// `powers_shift(i)` is the wire index of `wire_shift^i`. + pub fn powers_shift(&self, i: usize) -> usize { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wire_shift(); + } + self.end_coeffs() + i - 2 + } + + /// `powers_evalutation_point(i)` is the wire index of `evalutation_point^i`. + pub fn powers_evaluation_point(&self, i: usize) -> Range { + debug_assert!(0 < i && i < self.num_points()); + if i == 1 { + return self.wires_evaluation_point(); + } + let start = self.end_coeffs() + self.num_points() - 2 + (i - 2) * D; + start..start + D + } + + /// End of wire indices, exclusive. + fn end(&self) -> usize { + self.powers_evaluation_point(self.num_points() - 1).end + } + + /// The domain of the points we're interpolating. + fn coset(&self, shift: F) -> impl Iterator { + let g = F::primitive_root_of_unity(self.subgroup_bits); + let size = 1 << self.subgroup_bits; + // Speed matters here, so we avoid `cyclic_subgroup_coset_known_order` which allocates. + g.powers().take(size).map(move |x| x * shift) + } +} + impl, const D: usize> Gate for LowDegreeInterpolationGate { fn id(&self) -> String { format!("{:?}", self, D) @@ -142,33 +144,35 @@ impl, const D: usize> Gate for LowDegreeInter .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) .collect::>(); - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) .collect::>(); - powers_init.insert(0, F::Extension::ONE); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { - constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { + constraints.push(powers_shift[i - 1] * shift - powers_shift[i]); } - let ocoeffs = coeffs + powers_shift.insert(0, F::Extension::ONE); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs .iter() - .zip(powers_init) + .zip(powers_shift) .map(|(&c, p)| c.scalar_mul(p)) .collect::>(); let interpolant = PolynomialCoeffsAlgebra::new(coeffs); - let ointerpolant = PolynomialCoeffsAlgebra::new(ocoeffs); + let altered_interpolant = PolynomialCoeffsAlgebra::new(altered_coeffs); for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) .into_iter() .enumerate() { let value = vars.get_local_ext_algebra(self.wires_value(i)); - let computed_value = ointerpolant.eval_base(point); + let computed_value = altered_interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } let evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .map(|i| vars.get_local_ext_algebra(self.powers_evaluation_point(i))) .collect::>(); let evaluation_point = evaluation_point_powers[0]; for i in 1..self.num_points() - 1 { @@ -190,33 +194,36 @@ impl, const D: usize> Gate for LowDegreeInter let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext(self.wires_coeff(i))) .collect::>(); - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) + + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) .collect::>(); - powers_init.insert(0, F::ONE); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { - constraints.push(powers_init[i - 1] * wire_shift - powers_init[i]); + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { + constraints.push(powers_shift[i - 1] * shift - powers_shift[i]); } - let ocoeffs = coeffs + powers_shift.insert(0, F::ONE); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs .iter() - .zip(powers_init) + .zip(powers_shift) .map(|(&c, p)| c.scalar_mul(p)) .collect::>(); let interpolant = PolynomialCoeffs::new(coeffs); - let ointerpolant = PolynomialCoeffs::new(ocoeffs); + let altered_interpolant = PolynomialCoeffs::new(altered_coeffs); for (i, point) in F::two_adic_subgroup(self.subgroup_bits) .into_iter() .enumerate() { let value = vars.get_local_ext(self.wires_value(i)); - let computed_value = ointerpolant.eval_base(point); + let computed_value = altered_interpolant.eval_base(point); constraints.extend(&(value - computed_value).to_basefield_array()); } let evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext(self.powers_eval(i))) + .map(|i| vars.get_local_ext(self.powers_evaluation_point(i))) .collect::>(); let evaluation_point = evaluation_point_powers[0]; for i in 1..self.num_points() - 1 { @@ -242,25 +249,28 @@ impl, const D: usize> Gate for LowDegreeInter let coeffs = (0..self.num_points()) .map(|i| vars.get_local_ext_algebra(self.wires_coeff(i))) .collect::>(); - let mut powers_init = (1..self.num_points()) - .map(|i| vars.local_wires[self.powers_init(i)]) + + let mut powers_shift = (1..self.num_points()) + .map(|i| vars.local_wires[self.powers_shift(i)]) .collect::>(); - powers_init.insert(0, builder.one_extension()); - let wire_shift = powers_init[1]; - for i in 2..self.num_points() { + let shift = powers_shift[0]; + for i in 1..self.num_points() - 1 { constraints.push(builder.mul_sub_extension( - powers_init[i - 1], - wire_shift, - powers_init[i], + powers_shift[i - 1], + shift, + powers_shift[i], )); } - let ocoeffs = coeffs + powers_shift.insert(0, builder.one_extension()); + // `altered_coeffs[i] = c_i * shift^i`, where `c_i` is the original coefficient. + // Then, `altered(w^i) = original(shift*w^i)`. + let altered_coeffs = coeffs .iter() - .zip(powers_init) + .zip(powers_shift) .map(|(&c, p)| builder.scalar_mul_ext_algebra(p, c)) .collect::>(); let interpolant = PolynomialCoeffsExtAlgebraTarget(coeffs); - let ointerpolant = PolynomialCoeffsExtAlgebraTarget(ocoeffs); + let altered_interpolant = PolynomialCoeffsExtAlgebraTarget(altered_coeffs); for (i, point) in F::Extension::two_adic_subgroup(self.subgroup_bits) .into_iter() @@ -268,7 +278,7 @@ impl, const D: usize> Gate for LowDegreeInter { let value = vars.get_local_ext_algebra(self.wires_value(i)); let point = builder.constant_extension(point); - let computed_value = ointerpolant.eval_scalar(builder, point); + let computed_value = altered_interpolant.eval_scalar(builder, point); constraints.extend( &builder .sub_ext_algebra(value, computed_value) @@ -277,7 +287,7 @@ impl, const D: usize> Gate for LowDegreeInter } let evaluation_point_powers = (1..self.num_points()) - .map(|i| vars.get_local_ext_algebra(self.powers_eval(i))) + .map(|i| vars.get_local_ext_algebra(self.powers_evaluation_point(i))) .collect::>(); let evaluation_point = evaluation_point_powers[0]; for i in 1..self.num_points() - 1 { @@ -328,8 +338,6 @@ impl, const D: usize> Gate for LowDegreeInter } fn degree(&self) -> usize { - // The highest power of x is `num_points - 1`, and then multiplication by the coefficient - // adds 1. 2 } @@ -395,7 +403,7 @@ impl, const D: usize> SimpleGenerator .enumerate() .skip(2) { - out_buffer.set_wire(local_wire(self.gate.powers_init(i)), power); + out_buffer.set_wire(local_wire(self.gate.powers_shift(i)), power); } // Compute the interpolant. @@ -413,10 +421,15 @@ impl, const D: usize> SimpleGenerator } let evaluation_point = get_local_ext(self.gate.wires_evaluation_point()); - for i in 2..self.gate.num_points() { + for (i, power) in evaluation_point + .powers() + .take(self.gate.num_points()) + .enumerate() + .skip(2) + { out_buffer.set_extension_target( - ExtensionTarget::from_range(self.gate_index, self.gate.powers_eval(i)), - evaluation_point.exp_u64(i as u64), + ExtensionTarget::from_range(self.gate_index, self.gate.powers_evaluation_point(i)), + power, ); } let evaluation_value = interpolant.eval(evaluation_point); From 172fdd3d89ea32c9f3ee0b9dd4b91e7e8d753c92 Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 21:20:44 +0100 Subject: [PATCH 10/12] Comments --- src/field/extension_field/algebra.rs | 2 ++ src/fri/recursive_verifier.rs | 27 +++++++++++++++++++-------- src/gadgets/interpolation.rs | 3 +++ src/gadgets/polynomial.rs | 7 ++----- src/gates/interpolation.rs | 5 ++--- src/gates/low_degree_interpolation.rs | 7 +++---- src/plonk/prover.rs | 11 ----------- src/polynomial/polynomial.rs | 2 ++ 8 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/field/extension_field/algebra.rs b/src/field/extension_field/algebra.rs index 37f70f52..93d25de4 100644 --- a/src/field/extension_field/algebra.rs +++ b/src/field/extension_field/algebra.rs @@ -160,6 +160,7 @@ impl, const D: usize> PolynomialCoeffsAlgebra { .fold(ExtensionAlgebra::ZERO, |acc, &c| acc * x + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. pub fn eval_with_powers(&self, powers: &[ExtensionAlgebra]) -> ExtensionAlgebra { debug_assert_eq!(self.coeffs.len(), powers.len() + 1); let acc = self.coeffs[0]; @@ -176,6 +177,7 @@ impl, const D: usize> PolynomialCoeffsAlgebra { .fold(ExtensionAlgebra::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. pub fn eval_base_with_powers(&self, powers: &[F]) -> ExtensionAlgebra { debug_assert_eq!(self.coeffs.len(), powers.len() + 1); let acc = self.coeffs[0]; diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index f543b12b..b31e01a8 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -46,7 +46,9 @@ impl, const D: usize> CircuitBuilder { let coset_start = self.mul(start, x); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. - if 1 << arity_bits > common_data.quotient_degree_factor { + // `HighDegreeInterpolationGate` has degree `arity`, so we use the low-degree gate if + // the arity is too large. + if arity > common_data.quotient_degree_factor { self.interpolate_coset::>( arity_bits, coset_start, @@ -66,19 +68,28 @@ impl, const D: usize> CircuitBuilder { /// Make sure we have enough wires and routed wires to do the FRI checks efficiently. This check /// isn't required -- without it we'd get errors elsewhere in the stack -- but just gives more /// helpful errors. - fn check_recursion_config(&self, max_fri_arity_bits: usize) { + fn check_recursion_config( + &self, + max_fri_arity_bits: usize, + common_data: &CommonCircuitData, + ) { let random_access = RandomAccessGate::::new_from_config( &self.config, max_fri_arity_bits.max(self.config.cap_height), ); - let interpolation_gate = HighDegreeInterpolationGate::::new(max_fri_arity_bits); + let (interpolation_wires, interpolation_routed_wires) = + if 1 << max_fri_arity_bits > common_data.quotient_degree_factor { + let gate = LowDegreeInterpolationGate::::new(max_fri_arity_bits); + (gate.num_wires(), gate.num_routed_wires()) + } else { + let gate = HighDegreeInterpolationGate::::new(max_fri_arity_bits); + (gate.num_wires(), gate.num_routed_wires()) + }; - let min_wires = random_access - .num_wires() - .max(interpolation_gate.num_wires()); + let min_wires = random_access.num_wires().max(interpolation_wires); let min_routed_wires = random_access .num_routed_wires() - .max(interpolation_gate.num_routed_wires()); + .max(interpolation_routed_wires); assert!( self.config.num_wires >= min_wires, @@ -125,7 +136,7 @@ impl, const D: usize> CircuitBuilder { let config = &common_data.config; if let Some(max_arity_bits) = common_data.fri_params.max_arity_bits() { - self.check_recursion_config(max_arity_bits); + self.check_recursion_config(max_arity_bits, common_data); } debug_assert_eq!( diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs index 9167e771..7f6f98f3 100644 --- a/src/gadgets/interpolation.rs +++ b/src/gadgets/interpolation.rs @@ -7,6 +7,9 @@ use crate::gates::gate::Gate; use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; +/// Trait for gates which interpolate a polynomial, whose points are a (base field) coset of the multiplicative subgroup +/// with the given size, and whose values are extension field elements, given by input wires. +/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. pub(crate) trait InterpolationGate, const D: usize>: Gate + Copy { diff --git a/src/gadgets/polynomial.rs b/src/gadgets/polynomial.rs index 48a5f8b7..ff7fffd7 100644 --- a/src/gadgets/polynomial.rs +++ b/src/gadgets/polynomial.rs @@ -64,6 +64,8 @@ impl PolynomialCoeffsExtAlgebraTarget { } acc } + + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. pub fn eval_with_powers( &self, builder: &mut CircuitBuilder, @@ -78,10 +80,5 @@ impl PolynomialCoeffsExtAlgebraTarget { .iter() .zip(powers) .fold(acc, |acc, (&x, &c)| builder.mul_add_ext_algebra(c, x, acc)) - // let mut acc = builder.zero_ext_algebra(); - // for &c in self.0.iter().rev() { - // acc = builder.mul_add_ext_algebra(point, acc, c); - // } - // acc } } diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index 58288bba..f7934b71 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -17,9 +17,8 @@ use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::polynomial::polynomial::PolynomialCoeffs; -/// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup -/// with the given size, and whose values are extension field elements, given by input wires. -/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +/// Interpolation gate with constraints of degree at most `1<, const D: usize> { pub subgroup_bits: usize, diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index 245c6d17..abbdb00e 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -17,9 +17,8 @@ use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; use crate::polynomial::polynomial::PolynomialCoeffs; -/// Interpolates a polynomial, whose points are a (base field) coset of the multiplicative subgroup -/// with the given size, and whose values are extension field elements, given by input wires. -/// Outputs the evaluation of the interpolant at a given (extension field) evaluation point. +/// Interpolation gate with constraints of degree 2. +/// `eval_unfiltered_recursively` uses more gates than `HighDegreeInterpolationGate`. #[derive(Copy, Clone, Debug)] pub(crate) struct LowDegreeInterpolationGate, const D: usize> { pub subgroup_bits: usize, @@ -344,7 +343,7 @@ impl, const D: usize> Gate for LowDegreeInter fn num_constraints(&self) -> usize { // `num_points * D` constraints to check for consistency between the coefficients and the // point-value pairs, plus `D` constraints for the evaluation value, plus `(D+1)*(num_points-2)` - // to check power constraints for evaluation point and wire shift. + // to check power constraints for evaluation point and shift. self.num_points() * D + D + (D + 1) * (self.num_points() - 2) } } diff --git a/src/plonk/prover.rs b/src/plonk/prover.rs index b5175f02..3f8e607d 100644 --- a/src/plonk/prover.rs +++ b/src/plonk/prover.rs @@ -66,17 +66,6 @@ pub(crate) fn prove, const D: usize>( .collect() ); - // let rows = (0..degree) - // .map(|i| wires_values.iter().map(|w| w.values[i]).collect::>()) - // .collect::>(); - // for (i, r) in rows.iter().enumerate() { - // let c = rows.iter().filter(|&x| x == r).count(); - // let s = prover_data.instances[i].gate_ref.0.id(); - // if c > 1 && !s.starts_with("Noop") { - // println!("{} {} {}", prover_data.instances[i].gate_ref.0.id(), i, c); - // } - // } - let wires_commitment = timed!( timing, "compute wires commitment", diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index 3c732208..b5a7fabd 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -120,6 +120,7 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc * x + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. pub fn eval_with_powers(&self, powers: &[F]) -> F { debug_assert_eq!(self.coeffs.len(), powers.len() + 1); let acc = self.coeffs[0]; @@ -139,6 +140,7 @@ impl PolynomialCoeffs { .fold(F::ZERO, |acc, &c| acc.scalar_mul(x) + c) } + /// Evaluate the polynomial at a point given its powers. The first power is the point itself, not 1. pub fn eval_base_with_powers(&self, powers: &[F::BaseField]) -> F where F: FieldExtension, From 9cafe9773178820b2abe58a2d7f526aa7ca6bb6d Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 22:30:32 +0100 Subject: [PATCH 11/12] Remove specific impls of `InterpolationGate` --- src/gates/interpolation.rs | 85 +++++---------------------- src/gates/low_degree_interpolation.rs | 59 +------------------ 2 files changed, 17 insertions(+), 127 deletions(-) diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index f7934b71..0225ce59 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -25,6 +25,21 @@ pub(crate) struct HighDegreeInterpolationGate, cons _phantom: PhantomData, } +impl, const D: usize> InterpolationGate + for HighDegreeInterpolationGate +{ + fn new(subgroup_bits: usize) -> Self { + Self { + subgroup_bits, + _phantom: PhantomData, + } + } + + fn num_points(&self) -> usize { + 1 << self.subgroup_bits + } +} + impl, const D: usize> HighDegreeInterpolationGate { /// End of wire indices, exclusive. fn end(&self) -> usize { @@ -64,74 +79,6 @@ impl, const D: usize> HighDegreeInterpolationGate, const D: usize> InterpolationGate - for HighDegreeInterpolationGate -{ - fn new(subgroup_bits: usize) -> Self { - Self { - subgroup_bits, - _phantom: PhantomData, - } - } - - fn num_points(&self) -> usize { - 1 << self.subgroup_bits - } - - /// Wire index of the coset shift. - fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } -} - impl, const D: usize> Gate for HighDegreeInterpolationGate { @@ -227,7 +174,7 @@ impl, const D: usize> Gate ) -> Vec>> { let gen = InterpolationGenerator:: { gate_index, - gate: self.clone(), + gate: *self, _phantom: PhantomData, }; vec![Box::new(gen.adapter())] diff --git a/src/gates/low_degree_interpolation.rs b/src/gates/low_degree_interpolation.rs index abbdb00e..1c2b41e4 100644 --- a/src/gates/low_degree_interpolation.rs +++ b/src/gates/low_degree_interpolation.rs @@ -38,63 +38,6 @@ impl, const D: usize> InterpolationGate fn num_points(&self) -> usize { 1 << self.subgroup_bits } - - /// Wire index of the coset shift. - fn wire_shift(&self) -> usize { - 0 - } - - fn start_values(&self) -> usize { - 1 - } - - /// Wire indices of the `i`th interpolant value. - fn wires_value(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_values() + i * D; - start..start + D - } - - fn start_evaluation_point(&self) -> usize { - self.start_values() + self.num_points() * D - } - - /// Wire indices of the point to evaluate the interpolant at. - fn wires_evaluation_point(&self) -> Range { - let start = self.start_evaluation_point(); - start..start + D - } - - fn start_evaluation_value(&self) -> usize { - self.start_evaluation_point() + D - } - - /// Wire indices of the interpolated value. - fn wires_evaluation_value(&self) -> Range { - let start = self.start_evaluation_value(); - start..start + D - } - - fn start_coeffs(&self) -> usize { - self.start_evaluation_value() + D - } - - /// The number of routed wires required in the typical usage of this gate, where the points to - /// interpolate, the evaluation point, and the corresponding value are all routed. - fn num_routed_wires(&self) -> usize { - self.start_coeffs() - } - - /// Wire indices of the interpolant's `i`th coefficient. - fn wires_coeff(&self, i: usize) -> Range { - debug_assert!(i < self.num_points()); - let start = self.start_coeffs() + i * D; - start..start + D - } - - fn end_coeffs(&self) -> usize { - self.start_coeffs() + D * self.num_points() - } } impl, const D: usize> LowDegreeInterpolationGate { @@ -322,7 +265,7 @@ impl, const D: usize> Gate for LowDegreeInter ) -> Vec>> { let gen = InterpolationGenerator:: { gate_index, - gate: self.clone(), + gate: *self, _phantom: PhantomData, }; vec![Box::new(gen.adapter())] From 3235a21d2b0451bbdf361d3e7df9f7ddf30de8bc Mon Sep 17 00:00:00 2001 From: wborgeaud Date: Mon, 22 Nov 2021 22:38:37 +0100 Subject: [PATCH 12/12] 2^12 shrinking recursion with 100 bits of security --- src/plonk/circuit_data.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/plonk/circuit_data.rs b/src/plonk/circuit_data.rs index 41e89ea2..bf8024df 100644 --- a/src/plonk/circuit_data.rs +++ b/src/plonk/circuit_data.rs @@ -63,15 +63,15 @@ impl CircuitConfig { num_routed_wires: 80, constant_gate_size: 5, use_base_arithmetic_gate: true, - security_bits: 96, + security_bits: 100, rate_bits: 3, num_challenges: 2, zero_knowledge: false, cap_height: 4, fri_config: FriConfig { - proof_of_work_bits: 15, + proof_of_work_bits: 16, reduction_strategy: FriReductionStrategy::ConstantArityBits(4, 5), - num_query_rounds: 27, + num_query_rounds: 28, }, } }