diff --git a/src/bin/bench_recursion.rs b/src/bin/bench_recursion.rs index 8e798f72..a34bfe2e 100644 --- a/src/bin/bench_recursion.rs +++ b/src/bin/bench_recursion.rs @@ -21,7 +21,7 @@ fn main() -> Result<()> { fn bench_prove, const D: usize>() -> Result<()> { let config = CircuitConfig { - num_wires: 134, + num_wires: 126, num_routed_wires: 33, security_bits: 128, rate_bits: 3, diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 787c446d..3087f566 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -57,7 +57,7 @@ impl CircuitConfig { pub(crate) fn large_config() -> Self { Self { num_wires: 126, - num_routed_wires: 34, + num_routed_wires: 33, security_bits: 128, rate_bits: 3, num_challenges: 3, diff --git a/src/gates/mod.rs b/src/gates/mod.rs index 441383ec..987a276d 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -11,6 +11,7 @@ pub mod insertion; pub mod interpolation; pub(crate) mod noop; pub(crate) mod public_input; +pub mod reducing; #[cfg(test)] mod gate_testing; diff --git a/src/gates/reducing.rs b/src/gates/reducing.rs new file mode 100644 index 00000000..930f0456 --- /dev/null +++ b/src/gates/reducing.rs @@ -0,0 +1,176 @@ +use std::ops::Range; + +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::field::extension_field::FieldExtension; +use crate::gates::gate::Gate; +use crate::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; +use crate::target::Target; +use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +use crate::witness::PartialWitness; + +#[derive(Debug, Clone)] +pub struct ReducingGate { + pub num_coeffs: usize, +} + +impl ReducingGate { + pub fn new(num_coeffs: usize) -> Self { + Self { num_coeffs } + } + + pub fn wires_output() -> Range { + 0..D + } + pub fn wires_alpha() -> Range { + D..2 * D + } + pub fn wires_old_acc() -> Range { + 2 * D..3 * D + } + pub const START_COEFFS: usize = 3 * D; + pub fn wires_coeffs(&self) -> Range { + Self::START_COEFFS..Self::START_COEFFS + self.num_coeffs + } + pub fn start_accs(&self) -> usize { + Self::START_COEFFS + self.num_coeffs + } + pub fn wires_accs(&self, i: usize) -> Range { + self.start_accs() + 4 * i..self.start_accs() + 4 * (i + 1) + } +} + +impl, const D: usize> Gate for ReducingGate { + fn id(&self) -> String { + format!("{:?}", self) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let output = vars.get_local_ext_algebra(Self::wires_output()); + let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); + let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); + let coeffs = self + .wires_coeffs() + .map(|i| vars.local_wires[i]) + .collect::>(); + let accs = (0..self.num_coeffs) + .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) + .collect::>(); + + let mut constraints = Vec::new(); + let mut acc = old_acc; + for i in 0..self.num_coeffs { + constraints.push(acc * alpha + coeffs[i].into() - accs[i]); + acc = accs[i]; + } + + constraints.push(output - acc); + constraints + .into_iter() + .flat_map(|alg| alg.to_basefield_array()) + .collect() + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + todo!() + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + ) -> Vec>> { + vec![Box::new(ReducingGenerator { + gate_index, + gate: self.clone(), + })] + } + + fn num_wires(&self) -> usize { + 3 * D + self.num_coeffs * (D + 1) + } + + fn num_constants(&self) -> usize { + 0 + } + + fn degree(&self) -> usize { + 2 + } + + fn num_constraints(&self) -> usize { + D * (self.num_coeffs + 1) + } +} + +struct ReducingGenerator { + gate_index: usize, + gate: ReducingGate, +} + +impl, const D: usize> SimpleGenerator for ReducingGenerator { + fn dependencies(&self) -> Vec { + ReducingGate::::wires_alpha() + .chain(ReducingGate::::wires_old_acc()) + .chain(self.gate.wires_coeffs()) + .map(|i| Target::wire(self.gate_index, i)) + .collect() + } + + fn run_once(&self, witness: &PartialWitness) -> GeneratedValues { + let extract_extension = |range: Range| -> F::Extension { + let t = ExtensionTarget::from_range(self.gate_index, range); + witness.get_extension_target(t) + }; + + let alpha = extract_extension(ReducingGate::::wires_alpha()); + let old_acc = extract_extension(ReducingGate::::wires_old_acc()); + let coeffs = witness.get_targets( + &self + .gate + .wires_coeffs() + .map(|i| Target::wire(self.gate_index, i)) + .collect::>(), + ); + let accs = (0..self.gate.num_coeffs) + .map(|i| ExtensionTarget::from_range(self.gate_index, self.gate.wires_accs(i))) + .collect::>(); + let output = + ExtensionTarget::from_range(self.gate_index, ReducingGate::::wires_output()); + + let mut result = GeneratedValues::::with_capacity(self.gate.num_coeffs + 1); + let mut acc = old_acc; + for i in 0..self.gate.num_coeffs { + let computed_acc = acc * alpha + coeffs[i].into(); + result.set_extension_target(accs[i], computed_acc); + acc = computed_acc; + } + result.set_extension_target(output, acc); + + result + } +} + +#[cfg(test)] +mod tests { + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::extension_field::FieldExtension; + use crate::field::field::Field; + use crate::gates::gate::Gate; + use crate::gates::gate_testing::test_low_degree; + use crate::gates::reducing::ReducingGate; + use crate::proof::Hash; + use crate::vars::EvaluationVars; + + #[test] + fn low_degree() { + type F = CrandallField; + test_low_degree::(ReducingGate::new(22)); + } +} diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index f48cbbf1..4799007e 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -393,7 +393,7 @@ mod tests { } let config = CircuitConfig { - num_wires: 12 + 12 + 3 + 101, + num_wires: 12 + 12 + 1 + 101, num_routed_wires: 27, ..CircuitConfig::default() }; diff --git a/src/util/scaling.rs b/src/util/scaling.rs index 5cbffb83..87d907c5 100644 --- a/src/util/scaling.rs +++ b/src/util/scaling.rs @@ -7,7 +7,9 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, Frobenius}; use crate::field::field::Field; use crate::gates::arithmetic::ArithmeticExtensionGate; +use crate::gates::reducing::ReducingGate; use crate::polynomial::polynomial::PolynomialCoeffs; +use crate::target::Target; /// When verifying the composition polynomial in FRI we have to compute sums of the form /// `(sum_0^k a^i * x_i)/d_0 + (sum_k^r a^i * y_i)/d_1` @@ -90,6 +92,47 @@ impl ReducingFactorTarget { Self { base, count: 0 } } + pub fn reduce_base( + &mut self, + terms: &[Target], + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: Extendable, + { + let max_coeffs = 21; + let zero = builder.zero(); + let zero_ext = builder.zero_extension(); + let mut gate; + let mut gate_index; + let mut acc = zero_ext; + let mut reversed_terms = terms.to_vec(); + while reversed_terms.len() % max_coeffs != 0 { + reversed_terms.push(zero); + } + reversed_terms.reverse(); + for chunk in reversed_terms.chunks_exact(max_coeffs) { + gate = ReducingGate::new(max_coeffs); + gate_index = builder.add_gate(gate.clone(), Vec::new()); + + builder.route_extension( + self.base, + ExtensionTarget::from_range(gate_index, ReducingGate::::wires_alpha()), + ); + builder.route_extension( + acc, + ExtensionTarget::from_range(gate_index, ReducingGate::::wires_old_acc()), + ); + for (&t, c) in chunk.iter().zip(gate.wires_coeffs()) { + builder.route(t, Target::wire(gate_index, c)); + } + + acc = ExtensionTarget::from_range(gate_index, ReducingGate::::wires_output()); + } + + acc + } + /// Reduces a length `n` vector of `ExtensionTarget`s using `n/2` `ArithmeticExtensionGate`s. /// It does this by batching two steps of Horner's method in each gate. /// Here's an example with `n=4, alpha=2, D=1`: @@ -179,9 +222,37 @@ mod tests { use crate::circuit_data::CircuitConfig; use crate::field::crandall_field::CrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::extension_field::FieldExtension; use crate::verifier::verify; use crate::witness::PartialWitness; + fn test_reduce_gadget_base(n: usize) -> Result<()> { + type F = CrandallField; + type FF = QuarticCrandallField; + const D: usize = 4; + + let config = CircuitConfig::large_config(); + + let mut builder = CircuitBuilder::::new(config); + + let alpha = FF::rand(); + let vs = F::rand_vec(n); + + let manual_reduce = ReducingFactor::new(alpha).reduce(vs.iter().map(|&v| FF::from(v))); + let manual_reduce = builder.constant_extension(manual_reduce); + + let mut alpha_t = ReducingFactorTarget::new(builder.constant_extension(alpha)); + let vs_t = vs.iter().map(|&v| builder.constant(v)).collect::>(); + let circuit_reduce = alpha_t.reduce_base(&vs_t, &mut builder); + + builder.assert_equal_extension(manual_reduce, circuit_reduce); + + let data = builder.build(); + let proof = data.prove(PartialWitness::new())?; + + verify(proof, &data.verifier_only, &data.common) + } + fn test_reduce_gadget(n: usize) -> Result<()> { type F = CrandallField; type FF = QuarticCrandallField; @@ -221,4 +292,9 @@ mod tests { fn test_reduce_gadget_odd() -> Result<()> { test_reduce_gadget(11) } + + #[test] + fn test_reduce_gadget_base_100() -> Result<()> { + test_reduce_gadget_base(100) + } }