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_builder.rs b/src/circuit_builder.rs index 5d286a46..57f5b7ac 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -301,8 +301,8 @@ impl, const D: usize> CircuitBuilder { self.targets_to_constants.get(&target).cloned() } - pub fn push_context(&mut self, ctx: &str) { - self.context_log.push(ctx, self.num_gates()); + pub fn push_context(&mut self, level: log::Level, ctx: &str) { + self.context_log.push(ctx, level, self.num_gates()); } pub fn pop_context(&mut self) { 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/context_tree.rs b/src/context_tree.rs index 6482f71d..7ec5214c 100644 --- a/src/context_tree.rs +++ b/src/context_tree.rs @@ -1,9 +1,11 @@ -use log::debug; +use log::{log, Level}; /// The hierarchy of contexts, and the gate count contributed by each one. Useful for debugging. pub(crate) struct ContextTree { /// The name of this scope. name: String, + /// The level at which to log this scope and its children. + level: log::Level, /// The gate count when this scope was created. enter_gate_count: usize, /// The gate count when this scope was destroyed, or None if it has not yet been destroyed. @@ -16,6 +18,7 @@ impl ContextTree { pub fn new() -> Self { Self { name: "root".to_string(), + level: Level::Debug, enter_gate_count: 0, exit_gate_count: None, children: vec![], @@ -43,18 +46,22 @@ impl ContextTree { } } - pub fn push(&mut self, ctx: &str, current_gate_count: usize) { + pub fn push(&mut self, ctx: &str, mut level: log::Level, current_gate_count: usize) { assert!(self.is_open()); + // We don't want a scope's log level to be stronger than that of its parent. + level = level.max(self.level); + if let Some(last_child) = self.children.last_mut() { if last_child.is_open() { - last_child.push(ctx, current_gate_count); + last_child.push(ctx, level, current_gate_count); return; } } self.children.push(ContextTree { name: ctx.to_string(), + level, enter_gate_count: current_gate_count, exit_gate_count: None, children: vec![], @@ -83,6 +90,7 @@ impl ContextTree { pub fn filter(&self, current_gate_count: usize, min_delta: usize) -> Self { Self { name: self.name.clone(), + level: self.level, enter_gate_count: self.enter_gate_count, exit_gate_count: self.exit_gate_count, children: self @@ -100,7 +108,8 @@ impl ContextTree { fn print_helper(&self, current_gate_count: usize, depth: usize) { let prefix = "| ".repeat(depth); - debug!( + log!( + self.level, "{}{} gates to {}", prefix, self.gate_count_delta(current_gate_count), @@ -114,9 +123,16 @@ impl ContextTree { /// Creates a named scope; useful for debugging. #[macro_export] -macro_rules! context { +macro_rules! with_context { + ($builder:expr, $level:expr, $ctx:expr, $exp:expr) => {{ + $builder.push_context($level, $ctx); + let res = $exp; + $builder.pop_context(); + res + }}; + // If no context is specified, default to Debug. ($builder:expr, $ctx:expr, $exp:expr) => {{ - $builder.push_context($ctx); + $builder.push_context(log::Level::Debug, $ctx); let res = $exp; $builder.pop_context(); res 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 7592bc1f..0c256ae8 100644 --- a/src/fri/recursive_verifier.rs +++ b/src/fri/recursive_verifier.rs @@ -1,6 +1,5 @@ use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CommonCircuitData; -use crate::context; use crate::field::extension_field::target::{flatten_target, ExtensionTarget}; use crate::field::extension_field::Extendable; use crate::field::field::Field; @@ -11,8 +10,9 @@ 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}; +use crate::with_context; impl, const D: usize> CircuitBuilder { /// Computes P'(x^arity) from {P(x*g^i)}_(i=0..arity), where g is a `arity`-th root of unity @@ -25,18 +25,19 @@ impl, const D: usize> CircuitBuilder { last_evals: &[ExtensionTarget], beta: ExtensionTarget, ) -> ExtensionTarget { - debug_assert_eq!(last_evals.len(), 1 << arity_bits); + let arity = 1 << arity_bits; + debug_assert_eq!(last_evals.len(), arity); let g = F::primitive_root_of_unity(arity_bits); - let gt = self.constant(g); + let g_inv = g.exp((arity as u64) - 1); + let g_inv_t = self.constant(g_inv); // The evaluation vector needs to be reordered first. let mut evals = last_evals.to_vec(); reverse_index_bits_in_place(&mut evals); - // Want `g^(arity - rev_old_x_index)` as in the out-of-circuit version. - // Compute it as `g^(arity-1-rev_old_x_index) * g`, where the first term is gotten using two's complement. - let start = self.exp_from_complement_bits(gt, old_x_index_bits.iter().rev()); - let coset_start = self.mul_many(&[start, gt, x]); + // Want `g^(arity - rev_old_x_index)` as in the out-of-circuit version. Compute it as `(g^-1)^rev_old_x_index`. + let start = self.exp_from_bits(g_inv_t, old_x_index_bits.iter().rev()); + let coset_start = self.mul(start, x); // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. let points = g @@ -92,7 +93,7 @@ impl, const D: usize> CircuitBuilder { // Size of the LDE domain. let n = proof.final_poly.len() << (total_arities + config.rate_bits); - let betas = context!( + let betas = with_context!( self, "recover the random betas used in the FRI reductions.", proof @@ -106,7 +107,7 @@ impl, const D: usize> CircuitBuilder { ); challenger.observe_extension_elements(&proof.final_poly.0); - context!( + with_context!( self, "check PoW", self.fri_verify_proof_of_work(proof, challenger, &config.fri_config) @@ -123,12 +124,27 @@ impl, const D: usize> CircuitBuilder { "Number of reductions should be non-zero." ); - let precomputed_reduced_evals = - PrecomputedReducedEvalsTarget::from_os_and_alpha(os, alpha, self); + let precomputed_reduced_evals = with_context!( + self, + "precompute reduced evaluations", + PrecomputedReducedEvalsTarget::from_os_and_alpha(os, alpha, self) + ); + for (i, round_proof) in proof.query_round_proofs.iter().enumerate() { - context!( + // To minimize noise in our logs, we will only record a context for a single FRI query. + // The very first query will have some extra gates due to constants being registered, so + // the second query is a better representative. + let level = if i == 1 { + log::Level::Debug + } else { + log::Level::Trace + }; + + let num_queries = proof.query_round_proofs.len(); + with_context!( self, - &format!("verify {}'th FRI query", i), + level, + &format!("verify one (of {}) query rounds", num_queries), self.fri_verifier_query_round( zeta, alpha, @@ -157,7 +173,7 @@ impl, const D: usize> CircuitBuilder { .zip(initial_merkle_roots) .enumerate() { - context!( + with_context!( self, &format!("verify {}'th initial Merkle proof", i), self.verify_merkle_proof(evals.clone(), x_index_bits, root, merkle_proof) @@ -197,9 +213,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); @@ -212,9 +228,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); @@ -255,7 +271,7 @@ impl, const D: usize> CircuitBuilder { let x_index = challenger.get_challenge(self); let mut x_index_bits = self.low_bits(x_index, n_log, 64); let mut domain_size = n; - context!( + with_context!( self, "check FRI initial proof", self.fri_verify_initial_proof( @@ -267,7 +283,7 @@ impl, const D: usize> CircuitBuilder { let mut old_x_index_bits = Vec::new(); // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. - let mut subgroup_x = context!(self, "compute x from its index", { + let mut subgroup_x = with_context!(self, "compute x from its index", { let g = self.constant(F::MULTIPLICATIVE_GROUP_GENERATOR); let phi = self.constant(F::primitive_root_of_unity(n_log)); @@ -279,7 +295,7 @@ impl, const D: usize> CircuitBuilder { for (i, &arity_bits) in config.reduction_arity_bits.iter().enumerate() { let next_domain_size = domain_size >> arity_bits; let e_x = if i == 0 { - context!( + with_context!( self, "combine initial oracles", self.fri_combine_initial( @@ -294,7 +310,7 @@ impl, const D: usize> CircuitBuilder { } else { let last_evals = &evaluations[i - 1]; // Infer P(y) from {P(x)}_{x^arity=y}. - context!( + with_context!( self, "infer evaluation using interpolation", self.compute_evaluation( @@ -312,7 +328,7 @@ impl, const D: usize> CircuitBuilder { old_x_index_bits = x_index_bits; let low_x_index = self.le_sum(old_x_index_bits.iter()); evals = self.insert(low_x_index, e_x, evals); - context!( + with_context!( self, "verify FRI round Merkle proof.", self.verify_merkle_proof( @@ -334,7 +350,7 @@ impl, const D: usize> CircuitBuilder { let last_evals = evaluations.last().unwrap(); let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); - let purported_eval = context!( + let purported_eval = with_context!( self, "infer final evaluation using interpolation", self.compute_evaluation( @@ -349,7 +365,7 @@ impl, const D: usize> CircuitBuilder { // Final check of FRI. After all the reductions, we check that the final polynomial is equal // to the one sent by the prover. - let eval = context!( + let eval = with_context!( self, "evaluate final polynomial", proof.final_poly.eval_scalar(self, subgroup_x) 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/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs index cfeef82e..370f9900 100644 --- a/src/gadgets/arithmetic.rs +++ b/src/gadgets/arithmetic.rs @@ -188,27 +188,6 @@ impl, const D: usize> CircuitBuilder { product } - // TODO: Optimize this, maybe with a new gate. - // TODO: Test - /// Exponentiate `base` to the power of `2^bit_length-1-exponent`, given by its little-endian bits. - pub fn exp_from_complement_bits( - &mut self, - base: Target, - exponent_bits: impl Iterator>, - ) -> Target { - let mut current = base; - let one = self.one(); - let mut product = one; - - for bit in exponent_bits { - let multiplicand = self.select(*bit.borrow(), one, current); - product = self.mul(product, multiplicand); - current = self.mul(current, current); - } - - product - } - // TODO: Optimize this, maybe with a new gate. // TODO: Test /// Exponentiate `base` to the power of `exponent`, where `exponent < 2^num_bits`. diff --git a/src/gates/gate_testing.rs b/src/gates/gate_testing.rs index b71d1509..4a6c80df 100644 --- a/src/gates/gate_testing.rs +++ b/src/gates/gate_testing.rs @@ -36,6 +36,12 @@ pub(crate) fn test_low_degree, G: Gate, const D: usize>(g .map(|p| p.degree()) .collect::>(); + assert_eq!( + constraint_eval_degrees.len(), + gate.num_constraints(), + "eval should return num_constraints() constraints" + ); + let expected_eval_degree = WITNESS_DEGREE * gate.degree(); assert!( diff --git a/src/gates/mod.rs b/src/gates/mod.rs index a97dbd01..20680141 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -12,6 +12,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..0129a156 --- /dev/null +++ b/src/gates/reducing.rs @@ -0,0 +1,225 @@ +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 - 2 * 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 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 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 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 { + 2 * 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 + } +} + +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..a307ba2c 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -4,7 +4,6 @@ use serde::{Deserialize, Serialize}; use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CommonCircuitData; -use crate::context; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field::Field; @@ -15,8 +14,9 @@ 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}; +use crate::with_context; /// Two (~64 bit) field elements gives ~128 bit security. pub const SALT_SIZE: usize = 2; @@ -283,7 +283,7 @@ impl OpeningProofTarget { let alpha = challenger.get_extension_challenge(builder); - context!( + with_context!( builder, "verify FRI proof", builder.verify_fri_proof( diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index cc92ccdb..9e96be60 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -1,12 +1,12 @@ use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::{CircuitConfig, CommonCircuitData, VerifierCircuitTarget}; -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; +use crate::with_context; const MIN_WIRES: usize = 120; // TODO: Double check. const MIN_ROUTED_WIRES: usize = 28; // TODO: Double check. @@ -35,7 +35,7 @@ impl, const D: usize> CircuitBuilder { let mut challenger = RecursiveChallenger::new(self); let (betas, gammas, alphas, zeta) = - context!(self, "observe proof and generates challenges", { + with_context!(self, "observe proof and generates challenges", { // Observe the instance. let digest = HashTarget::from_vec( self.constants(&inner_common_data.circuit_digest.elements), @@ -69,7 +69,7 @@ impl, const D: usize> CircuitBuilder { let partial_products = &proof.openings.partial_products; let zeta_pow_deg = self.exp_power_of_2_extension(zeta, inner_common_data.degree_bits); - let vanishing_polys_zeta = context!( + let vanishing_polys_zeta = with_context!( self, "evaluate the vanishing polynomial at our challenge point, zeta.", eval_vanishing_poly_recursively( @@ -88,7 +88,7 @@ impl, const D: usize> CircuitBuilder { ) ); - context!(self, "check vanishing and quotient polynomials.", { + with_context!(self, "check vanishing and quotient polynomials.", { let quotient_polys_zeta = &proof.openings.quotient_polys; let mut scale = ReducingFactorTarget::new(zeta_pow_deg); let z_h_zeta = self.sub_extension(zeta_pow_deg, one); @@ -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..6101a9fb 100644 --- a/src/vanishing_poly.rs +++ b/src/vanishing_poly.rs @@ -1,6 +1,5 @@ use crate::circuit_builder::CircuitBuilder; use crate::circuit_data::CommonCircuitData; -use crate::context; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field::Field; @@ -9,8 +8,9 @@ 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}; +use crate::with_context; /// Evaluate the vanishing polynomial at `x`. In this context, the vanishing polynomial is a random /// linear combination of gate constraints, plus some other terms relating to the permutation @@ -236,7 +236,7 @@ pub fn evaluate_gate_constraints_recursively, const D: usize>( ) -> Vec> { let mut constraints = vec![builder.zero_extension(); num_gate_constraints]; for gate in gates { - let gate_constraints = context!( + let gate_constraints = with_context!( builder, &format!("evaluate {} constraints", gate.gate.0.id()), gate.gate @@ -270,7 +270,7 @@ pub(crate) fn eval_vanishing_poly_recursively, const D: usize>( let max_degree = common_data.quotient_degree_factor; let (num_prods, final_num_prod) = common_data.num_partial_products; - let constraint_terms = context!( + let constraint_terms = with_context!( builder, "evaluate gate constraints", evaluate_gate_constraints_recursively(