use std::ops::Range; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::extension_field::FieldExtension; use crate::field::field_types::RichField; use crate::gates::gate::Gate; use crate::iop::generator::{GeneratedValues, SimpleGenerator, WitnessGenerator}; use crate::iop::target::Target; use crate::iop::witness::{PartialWitness, PartitionWitness, Witness}; use crate::plonk::circuit_builder::CircuitBuilder; use crate::plonk::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// Computes `sum alpha^i c_i` for a vector `c_i` of `num_coeffs` elements of the extension field. #[derive(Debug, Clone)] pub struct ReducingExtensionGate { pub num_coeffs: usize, } impl ReducingExtensionGate { pub fn new(num_coeffs: usize) -> Self { Self { num_coeffs } } pub fn max_coeffs_len(num_wires: usize, num_routed_wires: usize) -> usize { ((num_routed_wires - 3 * D) / D).min((num_wires - 2 * D) / (D * 2)) } 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 } const START_COEFFS: usize = 3 * D; pub fn wires_coeff(i: usize) -> Range { Self::START_COEFFS + i * D..Self::START_COEFFS + (i + 1) * D } fn start_accs(&self) -> usize { Self::START_COEFFS + self.num_coeffs * D } fn wires_accs(&self, i: usize) -> Range { if i == self.num_coeffs - 1 { // The last accumulator is the output. return Self::wires_output(); } self.start_accs() + D * i..self.start_accs() + D * (i + 1) } } impl, const D: usize> Gate for ReducingExtensionGate { fn id(&self) -> String { format!("{:?}", self) } fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); let coeffs = (0..self.num_coeffs) .map(|i| vars.get_local_ext_algebra(Self::wires_coeff(i))) .collect::>(); let accs = (0..self.num_coeffs) .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) .collect::>(); let mut constraints = Vec::with_capacity(>::num_constraints(self)); let mut acc = old_acc; for i in 0..self.num_coeffs { constraints.push(acc * alpha + coeffs[i] - accs[i]); acc = accs[i]; } constraints .into_iter() .flat_map(|alg| alg.to_basefield_array()) .collect() } fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { let alpha = vars.get_local_ext(Self::wires_alpha()); let old_acc = vars.get_local_ext(Self::wires_old_acc()); let coeffs = (0..self.num_coeffs) .map(|i| vars.get_local_ext(Self::wires_coeff(i))) .collect::>(); let accs = (0..self.num_coeffs) .map(|i| vars.get_local_ext(self.wires_accs(i))) .collect::>(); let mut constraints = Vec::with_capacity(>::num_constraints(self)); let mut acc = old_acc; for i in 0..self.num_coeffs { constraints.extend((acc * alpha + coeffs[i] - accs[i]).to_basefield_array()); acc = accs[i]; } constraints } fn eval_unfiltered_recursively( &self, builder: &mut CircuitBuilder, vars: EvaluationTargets, ) -> Vec> { let alpha = vars.get_local_ext_algebra(Self::wires_alpha()); let old_acc = vars.get_local_ext_algebra(Self::wires_old_acc()); let coeffs = (0..self.num_coeffs) .map(|i| vars.get_local_ext_algebra(Self::wires_coeff(i))) .collect::>(); let accs = (0..self.num_coeffs) .map(|i| vars.get_local_ext_algebra(self.wires_accs(i))) .collect::>(); let mut constraints = Vec::with_capacity(>::num_constraints(self)); let mut acc = old_acc; for i in 0..self.num_coeffs { let coeff = coeffs[i]; let mut tmp = builder.mul_add_ext_algebra(acc, alpha, coeff); tmp = builder.sub_ext_algebra(tmp, accs[i]); constraints.push(tmp); acc = accs[i]; } constraints .into_iter() .flat_map(|alg| alg.to_ext_target_array()) .collect() } fn generators( &self, gate_index: usize, _local_constants: &[F], ) -> Vec>> { vec![Box::new( ReducingGenerator { gate_index, gate: self.clone(), } .adapter(), )] } fn num_wires(&self) -> usize { 2 * D + 2 * D * self.num_coeffs } fn num_constants(&self) -> usize { 0 } fn degree(&self) -> usize { 2 } fn num_constraints(&self) -> usize { D * self.num_coeffs } } #[derive(Debug)] struct ReducingGenerator { gate_index: usize, gate: ReducingExtensionGate, } impl, const D: usize> SimpleGenerator for ReducingGenerator { fn dependencies(&self) -> Vec { ReducingExtensionGate::::wires_alpha() .chain(ReducingExtensionGate::::wires_old_acc()) .chain( (0..self.gate.num_coeffs).flat_map(|i| ReducingExtensionGate::::wires_coeff(i)), ) .map(|i| Target::wire(self.gate_index, i)) .collect() } fn run_once(&self, witness: &PartitionWitness, out_buffer: &mut 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(ReducingExtensionGate::::wires_alpha()); let old_acc = extract_extension(ReducingExtensionGate::::wires_old_acc()); let coeffs = (0..self.gate.num_coeffs) .map(|i| extract_extension(ReducingExtensionGate::::wires_coeff(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, ReducingExtensionGate::::wires_output(), ); let mut acc = old_acc; for i in 0..self.gate.num_coeffs { let computed_acc = acc * alpha + coeffs[i]; out_buffer.set_extension_target(accs[i], computed_acc); acc = computed_acc; } out_buffer.set_extension_target(output, acc); } } #[cfg(test)] mod tests { use anyhow::Result; use crate::field::goldilocks_field::GoldilocksField; use crate::gates::gate_testing::{test_eval_fns, test_low_degree}; use crate::gates::reducing_extension::ReducingExtensionGate; #[test] fn low_degree() { test_low_degree::(ReducingExtensionGate::new(22)); } #[test] fn eval_fns() -> Result<()> { test_eval_fns::(ReducingExtensionGate::new(22)) } }