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/field/extension_field/target.rs b/src/field/extension_field/target.rs index 6083e2c4..97576ac6 100644 --- a/src/field/extension_field/target.rs +++ b/src/field/extension_field/target.rs @@ -115,6 +115,13 @@ impl, const D: usize> CircuitBuilder { arr[0] = t; ExtensionTarget(arr) } + + pub fn convert_to_ext_algebra(&mut self, et: ExtensionTarget) -> ExtensionAlgebraTarget { + let zero = self.zero_extension(); + let mut arr = [zero; D]; + arr[0] = et; + ExtensionAlgebraTarget(arr) + } } /// Flatten the slice by sending every extension target to its D-sized canonical representation. diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs index 262d91a2..ed4d3750 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -11,7 +11,7 @@ use crate::proof::{ FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget, HashTarget, OpeningSetTarget, }; use crate::target::Target; -use crate::util::scaling::ReducingFactorTarget; +use crate::util::reducing::ReducingFactorTarget; use crate::util::{log2_strict, reverse_index_bits_in_place}; impl, const D: usize> CircuitBuilder { @@ -198,9 +198,9 @@ impl, const D: usize> CircuitBuilder { &proof.unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS, config.zero_knowledge) [common_data.partial_products_range()], ) - .map(|&e| self.convert_to_ext(e)) + .copied() .collect::>(); - let single_composition_eval = alpha.reduce(&single_evals, self); + let single_composition_eval = alpha.reduce_base(&single_evals, self); let single_numerator = self.sub_extension(single_composition_eval, precomputed_reduced_evals.single); let single_denominator = self.sub_extension(subgroup_x, zeta); @@ -213,9 +213,9 @@ impl, const D: usize> CircuitBuilder { .unsalted_evals(PlonkPolynomials::ZS_PARTIAL_PRODUCTS, config.zero_knowledge) .iter() .take(common_data.zs_range().end) - .map(|&e| self.convert_to_ext(e)) + .copied() .collect::>(); - let zs_composition_eval = alpha.reduce(&zs_evals, self); + let zs_composition_eval = alpha.reduce_base(&zs_evals, self); let g = self.constant_extension(F::Extension::primitive_root_of_unity(degree_log)); let zeta_right = self.mul_extension(g, zeta); diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index 27e775e6..8417dac1 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -10,7 +10,7 @@ use crate::merkle_proofs::verify_merkle_proof; use crate::plonk_challenger::Challenger; use crate::plonk_common::PlonkPolynomials; use crate::proof::{FriInitialTreeProof, FriProof, FriQueryRound, Hash, OpeningSet}; -use crate::util::scaling::ReducingFactor; +use crate::util::reducing::ReducingFactor; use crate::util::{log2_strict, reverse_bits, reverse_index_bits_in_place}; /// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity 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..9404cf93 --- /dev/null +++ b/src/gates/reducing.rs @@ -0,0 +1,228 @@ +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; + +/// Computes `sum alpha^i c_i` for a vector `c_i` of `num_coeffs` elements of the base field. +#[derive(Debug, Clone)] +pub struct ReducingGate { + pub num_coeffs: usize, +} + +impl ReducingGate { + 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).min((num_wires - 3 * D) / (D + 1)) + } + + 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_coeffs(&self) -> Range { + Self::START_COEFFS..Self::START_COEFFS + self.num_coeffs + } + fn start_accs(&self) -> usize { + Self::START_COEFFS + self.num_coeffs + } + 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 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 + .into_iter() + .flat_map(|alg| alg.to_basefield_array()) + .collect() + } + + fn eval_unfiltered_base(&self, vars: EvaluationVarsBase) -> Vec { + let output = vars.get_local_ext(Self::wires_output()); + let alpha = vars.get_local_ext(Self::wires_alpha()); + let old_acc = vars.get_local_ext(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(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 + .into_iter() + .flat_map(|alg| alg.to_basefield_array()) + .collect() + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> 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 { + let mut tmp = builder.mul_ext_algebra(acc, alpha); + let coeff = builder.convert_to_ext_algebra(coeffs[i]); + tmp = builder.add_ext_algebra(tmp, 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(), + })] + } + + 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::gates::gate_testing::test_low_degree; + use crate::gates::reducing::ReducingGate; + + #[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/polynomial/commitment.rs b/src/polynomial/commitment.rs index 356cd976..a2c9001b 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -15,7 +15,7 @@ use crate::plonk_common::PlonkPolynomials; use crate::polynomial::polynomial::{PolynomialCoeffs, PolynomialValues}; use crate::proof::{FriProof, FriProofTarget, Hash, HashTarget, OpeningSet, OpeningSetTarget}; use crate::timed; -use crate::util::scaling::ReducingFactor; +use crate::util::reducing::ReducingFactor; use crate::util::{log2_ceil, log2_strict, reverse_bits, reverse_index_bits_in_place, transpose}; /// Two (~64 bit) field elements gives ~128 bit security. diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index cc92ccdb..ef6b8cff 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -4,7 +4,7 @@ use crate::context; use crate::field::extension_field::Extendable; use crate::plonk_challenger::RecursiveChallenger; use crate::proof::{HashTarget, ProofWithPublicInputsTarget}; -use crate::util::scaling::ReducingFactorTarget; +use crate::util::reducing::ReducingFactorTarget; use crate::vanishing_poly::eval_vanishing_poly_recursively; use crate::vars::EvaluationTargets; @@ -381,7 +381,7 @@ mod tests { let mut builder = CircuitBuilder::::new(config.clone()); let _two = builder.two(); let _two = builder.hash_n_to_hash(vec![_two], true).elements[0]; - for _ in 0..5000 { + for _ in 0..10000 { let _two = builder.mul(_two, _two); } let data = builder.build(); @@ -411,4 +411,76 @@ mod tests { verify(recursive_proof, &data.verifier_only, &data.common) } + + #[test] + #[ignore] + fn test_recursive_recursive_verifier() -> Result<()> { + env_logger::init(); + type F = CrandallField; + const D: usize = 4; + let config = CircuitConfig { + num_wires: 126, + num_routed_wires: 33, + security_bits: 128, + rate_bits: 3, + num_challenges: 3, + zero_knowledge: false, + fri_config: FriConfig { + proof_of_work_bits: 1, + reduction_arity_bits: vec![2, 2, 2, 2, 2, 2], + num_query_rounds: 40, + }, + }; + let (proof_with_pis, vd, cd) = { + let (proof_with_pis, vd, cd) = { + let mut builder = CircuitBuilder::::new(config.clone()); + let _two = builder.two(); + let _two = builder.hash_n_to_hash(vec![_two], true).elements[0]; + for _ in 0..10000 { + let _two = builder.mul(_two, _two); + } + let data = builder.build(); + ( + data.prove(PartialWitness::new())?, + data.verifier_only, + data.common, + ) + }; + verify(proof_with_pis.clone(), &vd, &cd)?; + + let mut builder = CircuitBuilder::::new(config.clone()); + let mut pw = PartialWitness::new(); + let pt = proof_to_proof_target(&proof_with_pis, &mut builder); + set_proof_target(&proof_with_pis, &pt, &mut pw); + + let inner_data = VerifierCircuitTarget { + constants_sigmas_root: builder.add_virtual_hash(), + }; + pw.set_hash_target(inner_data.constants_sigmas_root, vd.constants_sigmas_root); + + builder.add_recursive_verifier(pt, &config, &inner_data, &cd); + + let data = builder.build(); + let recursive_proof = data.prove(pw)?; + (recursive_proof, data.verifier_only, data.common) + }; + + verify(proof_with_pis.clone(), &vd, &cd)?; + let mut builder = CircuitBuilder::::new(config.clone()); + let mut pw = PartialWitness::new(); + let pt = proof_to_proof_target(&proof_with_pis, &mut builder); + set_proof_target(&proof_with_pis, &pt, &mut pw); + + let inner_data = VerifierCircuitTarget { + constants_sigmas_root: builder.add_virtual_hash(), + }; + pw.set_hash_target(inner_data.constants_sigmas_root, vd.constants_sigmas_root); + + builder.add_recursive_verifier(pt, &config, &inner_data, &cd); + + builder.print_gate_counts(0); + let data = builder.build(); + let recursive_proof = data.prove(pw)?; + verify(recursive_proof, &data.verifier_only, &data.common) + } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 83a97881..9176563a 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,6 +1,6 @@ pub mod marking; pub mod partial_products; -pub mod scaling; +pub mod reducing; pub(crate) mod timing; use crate::field::field::Field; diff --git a/src/util/scaling.rs b/src/util/reducing.rs similarity index 70% rename from src/util/scaling.rs rename to src/util/reducing.rs index 5cbffb83..6a4a1168 100644 --- a/src/util/scaling.rs +++ b/src/util/reducing.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,50 @@ impl ReducingFactorTarget { Self { base, count: 0 } } + /// Reduces a length `n` vector of `Target`s using `n/21` `ReducingGate`s (with 33 routed wires and 126 wires). + pub fn reduce_base( + &mut self, + terms: &[Target], + builder: &mut CircuitBuilder, + ) -> ExtensionTarget + where + F: Extendable, + { + let max_coeffs_len = ReducingGate::::max_coeffs_len( + builder.config.num_wires, + builder.config.num_routed_wires, + ); + self.count += terms.len() as u64; + let zero = builder.zero(); + let zero_ext = builder.zero_extension(); + let mut acc = zero_ext; + let mut reversed_terms = terms.to_vec(); + while reversed_terms.len() % max_coeffs_len != 0 { + reversed_terms.push(zero); + } + reversed_terms.reverse(); + for chunk in reversed_terms.chunks_exact(max_coeffs_len) { + let gate = ReducingGate::new(max_coeffs_len); + let 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`: @@ -182,6 +228,33 @@ mod tests { 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 +294,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) + } } diff --git a/src/vanishing_poly.rs b/src/vanishing_poly.rs index 540ab86b..e3e4284c 100644 --- a/src/vanishing_poly.rs +++ b/src/vanishing_poly.rs @@ -9,7 +9,7 @@ use crate::plonk_common; use crate::plonk_common::{eval_l_1_recursively, ZeroPolyOnCoset}; use crate::target::Target; use crate::util::partial_products::{check_partial_products, check_partial_products_recursively}; -use crate::util::scaling::ReducingFactorTarget; +use crate::util::reducing::ReducingFactorTarget; use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; /// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random