diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 1916eba6..02d34fba 100644 --- a/src/circuit_builder.rs +++ b/src/circuit_builder.rs @@ -8,6 +8,7 @@ use crate::circuit_data::{ VerifierCircuitData, VerifierOnlyCircuitData, }; use crate::field::cosets::get_unique_coset_shifts; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::gates::constant::ConstantGate; use crate::gates::gate::{GateInstance, GateRef}; @@ -129,6 +130,12 @@ impl, const D: usize> CircuitBuilder { self.assert_equal(src, dst); } + pub fn route_extension(&mut self, src: ExtensionTarget, dst: ExtensionTarget) { + for i in 0..D { + self.route(src.0[i], dst.0[i]); + } + } + /// Adds a generator which will copy `src` to `dst`. pub fn generate_copy(&mut self, src: Target, dst: Target) { self.add_generator(CopyGenerator { src, dst }); @@ -148,6 +155,17 @@ impl, const D: usize> CircuitBuilder { self.copy_constraints.push((x, y)); } + pub fn assert_zero(&mut self, x: Target) { + let zero = self.zero(); + self.assert_equal(x, zero); + } + + pub fn assert_equal_extension(&mut self, x: ExtensionTarget, y: ExtensionTarget) { + for i in 0..D { + self.assert_equal(x.0[i], y.0[i]); + } + } + pub fn add_generators(&mut self, generators: Vec>>) { self.generators.extend(generators); } diff --git a/src/circuit_data.rs b/src/circuit_data.rs index 6cc38522..28dfbfac 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -7,7 +7,7 @@ use crate::gates::gate::GateRef; use crate::generator::WitnessGenerator; use crate::polynomial::commitment::ListPolynomialCommitment; use crate::proof::{Hash, HashTarget, Proof}; -use crate::prover::prove; +use crate::prover::{prove, PLONK_BLINDING}; use crate::verifier::verify; use crate::witness::PartialWitness; @@ -48,6 +48,23 @@ impl CircuitConfig { pub fn num_advice_wires(&self) -> usize { self.num_wires - self.num_routed_wires } + + pub(crate) fn large_config() -> Self { + Self { + num_wires: 134, + num_routed_wires: 12, + security_bits: 128, + rate_bits: 3, + num_challenges: 3, + fri_config: FriConfig { + proof_of_work_bits: 1, + rate_bits: 3, + reduction_arity_bits: vec![1], + num_query_rounds: 1, + blinding: PLONK_BLINDING.to_vec(), + }, + } + } } /// Circuit data required by the prover or the verifier. diff --git a/src/field/crandall_field.rs b/src/field/crandall_field.rs index 0cee8860..dbd29cb2 100644 --- a/src/field/crandall_field.rs +++ b/src/field/crandall_field.rs @@ -8,7 +8,7 @@ use num::Integer; use crate::field::extension_field::quadratic::QuadraticCrandallField; use crate::field::extension_field::quartic::QuarticCrandallField; -use crate::field::extension_field::Extendable; +use crate::field::extension_field::{Extendable, Frobenius}; use crate::field::field::Field; /// EPSILON = 9 * 2**28 - 1 @@ -444,6 +444,8 @@ fn split(x: u128) -> (u64, u64) { (x as u64, (x >> 64) as u64) } +impl Frobenius<1> for CrandallField {} + #[cfg(test)] mod tests { use crate::test_arithmetic; diff --git a/src/field/extension_field/algebra.rs b/src/field/extension_field/algebra.rs index 996053bf..fcd60185 100644 --- a/src/field/extension_field/algebra.rs +++ b/src/field/extension_field/algebra.rs @@ -220,7 +220,6 @@ mod tests { let y = ExtensionAlgebra::from_basefield_array(arr1); let z = x * y; - dbg!(z.0, mul_mle(ts.clone())); assert_eq!(z.0, mul_mle(ts)); } diff --git a/src/field/extension_field/mod.rs b/src/field/extension_field/mod.rs index 60d2b2e1..9caa7dc8 100644 --- a/src/field/extension_field/mod.rs +++ b/src/field/extension_field/mod.rs @@ -1,4 +1,5 @@ use crate::field::field::Field; +use std::convert::TryInto; pub mod algebra; pub mod quadratic; @@ -12,32 +13,42 @@ pub mod target; pub trait OEF: FieldExtension { // Element W of BaseField, such that `X^d - W` is irreducible over BaseField. const W: Self::BaseField; - - /// Frobenius automorphisms: x -> x^p, where p is the order of BaseField. - fn frobenius(&self) -> Self { - let arr = self.to_basefield_array(); - let k = (Self::BaseField::ORDER - 1) / (D as u64); - let z0 = Self::W.exp(k); - let mut z = Self::BaseField::ONE; - let mut res = [Self::BaseField::ZERO; D]; - for i in 0..D { - res[i] = arr[i] * z; - z *= z0; - } - - Self::from_basefield_array(res) - } } impl OEF<1> for F { const W: Self::BaseField = F::ZERO; } -pub trait Extendable: Field + Sized { - type Extension: Field + OEF + From; +pub trait Frobenius: OEF { + /// FrobeniusField automorphisms: x -> x^p, where p is the order of BaseField. + fn frobenius(&self) -> Self { + self.repeated_frobenius(1) + } + + /// Repeated Frobenius automorphisms: x -> x^(p^k). + fn repeated_frobenius(&self, count: usize) -> Self { + if count == 0 { + return *self; + } else if count >= D { + return self.repeated_frobenius(count % D); + } + let arr = self.to_basefield_array(); + let k = (Self::BaseField::ORDER - 1) / (D as u64); + let z0 = Self::W.exp(k * count as u64); + let mut res = [Self::BaseField::ZERO; D]; + for (i, z) in z0.powers().take(D).enumerate() { + res[i] = arr[i] * z; + } + + Self::from_basefield_array(res) + } } -impl Extendable<1> for F { +pub trait Extendable: Field + Sized { + type Extension: Field + OEF + Frobenius + From; +} + +impl + FieldExtension<1, BaseField = F>> Extendable<1> for F { type Extension = F; } @@ -88,10 +99,6 @@ where { debug_assert_eq!(l.len() % D, 0); l.chunks_exact(D) - .map(|c| { - let mut arr = [F::ZERO; D]; - arr.copy_from_slice(c); - F::Extension::from_basefield_array(arr) - }) + .map(|c| F::Extension::from_basefield_array(c.to_vec().try_into().unwrap())) .collect() } diff --git a/src/field/extension_field/quadratic.rs b/src/field/extension_field/quadratic.rs index b61ccbaa..4dc712af 100644 --- a/src/field/extension_field/quadratic.rs +++ b/src/field/extension_field/quadratic.rs @@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use rand::Rng; use crate::field::crandall_field::CrandallField; -use crate::field::extension_field::{FieldExtension, OEF}; +use crate::field::extension_field::{FieldExtension, Frobenius, OEF}; use crate::field::field::Field; #[derive(Copy, Clone, Eq, PartialEq, Hash)] @@ -18,6 +18,8 @@ impl OEF<2> for QuadraticCrandallField { const W: CrandallField = CrandallField(3); } +impl Frobenius<2> for QuadraticCrandallField {} + impl FieldExtension<2> for QuadraticCrandallField { type BaseField = CrandallField; @@ -65,7 +67,7 @@ impl Field for QuadraticCrandallField { return None; } - let a_pow_r_minus_1 = OEF::<2>::frobenius(self); + let a_pow_r_minus_1 = self.frobenius(); let a_pow_r = a_pow_r_minus_1 * *self; debug_assert!(FieldExtension::<2>::is_in_basefield(&a_pow_r)); @@ -192,7 +194,7 @@ impl DivAssign for QuadraticCrandallField { #[cfg(test)] mod tests { use crate::field::extension_field::quadratic::QuadraticCrandallField; - use crate::field::extension_field::{FieldExtension, OEF}; + use crate::field::extension_field::{FieldExtension, Frobenius, OEF}; use crate::field::field::Field; #[test] @@ -233,7 +235,7 @@ mod tests { let x = F::rand(); assert_eq!( x.exp(>::BaseField::ORDER), - OEF::<2>::frobenius(&x) + x.frobenius() ); } diff --git a/src/field/extension_field/quartic.rs b/src/field/extension_field/quartic.rs index 2acac183..b93cbb56 100644 --- a/src/field/extension_field/quartic.rs +++ b/src/field/extension_field/quartic.rs @@ -6,7 +6,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use rand::Rng; use crate::field::crandall_field::CrandallField; -use crate::field::extension_field::{FieldExtension, OEF}; +use crate::field::extension_field::{FieldExtension, Frobenius, OEF}; use crate::field::field::Field; /// A quartic extension of `CrandallField`. @@ -20,6 +20,8 @@ impl OEF<4> for QuarticCrandallField { const W: CrandallField = CrandallField(3); } +impl Frobenius<4> for QuarticCrandallField {} + impl FieldExtension<4> for QuarticCrandallField { type BaseField = CrandallField; @@ -93,9 +95,9 @@ impl Field for QuarticCrandallField { return None; } - let a_pow_p = OEF::<4>::frobenius(self); + let a_pow_p = self.frobenius(); let a_pow_p_plus_1 = a_pow_p * *self; - let a_pow_p3_plus_p2 = OEF::<4>::frobenius(&OEF::<4>::frobenius(&a_pow_p_plus_1)); + let a_pow_p3_plus_p2 = a_pow_p_plus_1.repeated_frobenius(2); let a_pow_r_minus_1 = a_pow_p3_plus_p2 * a_pow_p; let a_pow_r = a_pow_r_minus_1 * *self; debug_assert!(FieldExtension::<4>::is_in_basefield(&a_pow_r)); @@ -241,7 +243,7 @@ impl DivAssign for QuarticCrandallField { #[cfg(test)] mod tests { use crate::field::extension_field::quartic::QuarticCrandallField; - use crate::field::extension_field::{FieldExtension, OEF}; + use crate::field::extension_field::{FieldExtension, Frobenius, OEF}; use crate::field::field::Field; fn exp_naive(x: F, power: u128) -> F { @@ -292,11 +294,18 @@ mod tests { #[test] fn test_frobenius() { type F = QuarticCrandallField; + const D: usize = 4; let x = F::rand(); assert_eq!( - exp_naive(x, >::BaseField::ORDER as u128), - OEF::<4>::frobenius(&x) + exp_naive(x, >::BaseField::ORDER as u128), + x.frobenius() ); + for count in 2..D { + assert_eq!( + x.repeated_frobenius(count), + (0..count).fold(x, |acc, _| acc.frobenius()) + ); + } } #[test] diff --git a/src/field/extension_field/target.rs b/src/field/extension_field/target.rs index 71e04360..0316b04f 100644 --- a/src/field/extension_field/target.rs +++ b/src/field/extension_field/target.rs @@ -1,7 +1,11 @@ +use std::convert::{TryFrom, TryInto}; +use std::ops::Range; + use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::algebra::ExtensionAlgebra; use crate::field::extension_field::{Extendable, FieldExtension, OEF}; use crate::field::field::Field; +use crate::gates::mul_extension::MulExtensionGate; use crate::target::Target; /// `Target`s representing an element of an extension field. @@ -12,6 +16,50 @@ impl ExtensionTarget { pub fn to_target_array(&self) -> [Target; D] { self.0 } + + pub fn frobenius>(&self, builder: &mut CircuitBuilder) -> Self { + self.repeated_frobenius(1, builder) + } + + pub fn repeated_frobenius>( + &self, + count: usize, + builder: &mut CircuitBuilder, + ) -> Self { + if count == 0 { + return *self; + } else if count >= D { + return self.repeated_frobenius(count % D, builder); + } + let arr = self.to_target_array(); + let k = (F::ORDER - 1) / (D as u64); + let z0 = F::W.exp(k * count as u64); + let zs = z0 + .powers() + .take(D) + .map(|z| builder.constant(z)) + .collect::>(); + + let mut res = Vec::with_capacity(D); + for (z, a) in zs.into_iter().zip(arr) { + res.push(builder.mul(z, a)); + } + + res.try_into().unwrap() + } + + pub fn from_range(gate: usize, range: Range) -> Self { + debug_assert_eq!(range.end - range.start, D); + Target::wires_from_range(gate, range).try_into().unwrap() + } +} + +impl TryFrom> for ExtensionTarget { + type Error = Vec; + + fn try_from(value: Vec) -> Result { + Ok(Self(value.try_into()?)) + } } /// `Target`s representing an element of an extension of an extension field. @@ -92,6 +140,7 @@ impl, const D: usize> CircuitBuilder { sum } + /// TODO: Change this to using an `arithmetic_extension` function once `MulExtensionGate` supports addend. pub fn sub_extension( &mut self, mut a: ExtensionTarget, @@ -114,23 +163,31 @@ impl, const D: usize> CircuitBuilder { a } + pub fn mul_extension_with_const( + &mut self, + const_0: F, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, + ) -> ExtensionTarget { + let gate = self.add_gate(MulExtensionGate::new(), vec![const_0]); + + let wire_multiplicand_0 = + ExtensionTarget::from_range(gate, MulExtensionGate::::wires_multiplicand_0()); + let wire_multiplicand_1 = + ExtensionTarget::from_range(gate, MulExtensionGate::::wires_multiplicand_1()); + let wire_output = ExtensionTarget::from_range(gate, MulExtensionGate::::wires_output()); + + self.route_extension(multiplicand_0, wire_multiplicand_0); + self.route_extension(multiplicand_1, wire_multiplicand_1); + wire_output + } + pub fn mul_extension( &mut self, - a: ExtensionTarget, - b: ExtensionTarget, + multiplicand_0: ExtensionTarget, + multiplicand_1: ExtensionTarget, ) -> ExtensionTarget { - let mut res = [self.zero(); D]; - for i in 0..D { - for j in 0..D { - res[(i + j) % D] = if i + j < D { - self.mul_add(a.0[i], b.0[j], res[(i + j) % D]) - } else { - // W * a[i] * b[i] + res[(i + j) % D] - self.arithmetic(F::Extension::W, a.0[i], b.0[i], F::ONE, res[(i + j) % D]) - } - } - } - ExtensionTarget(res) + self.mul_extension_with_const(F::ONE, multiplicand_0, multiplicand_1) } pub fn mul_ext_algebra( @@ -164,6 +221,7 @@ impl, const D: usize> CircuitBuilder { /// Like `mul_add`, but for `ExtensionTarget`s. Note that, unlike `mul_add`, this has no /// performance benefit over separate muls and adds. + /// TODO: Change this to using an `arithmetic_extension` function once `MulExtensionGate` supports addend. pub fn mul_add_extension( &mut self, a: ExtensionTarget, @@ -174,12 +232,23 @@ impl, const D: usize> CircuitBuilder { self.add_extension(product, c) } + /// Like `mul_sub`, but for `ExtensionTarget`s. Note that, unlike `mul_sub`, this has no + /// performance benefit over separate muls and subs. + /// TODO: Change this to using an `arithmetic_extension` function once `MulExtensionGate` supports addend. + pub fn scalar_mul_sub_extension( + &mut self, + a: Target, + b: ExtensionTarget, + c: ExtensionTarget, + ) -> ExtensionTarget { + let product = self.scalar_mul_ext(a, b); + self.sub_extension(product, c) + } + /// Returns `a * b`, where `b` is in the extension field and `a` is in the base field. - pub fn scalar_mul_ext(&mut self, a: Target, mut b: ExtensionTarget) -> ExtensionTarget { - for i in 0..D { - b.0[i] = self.mul(a, b.0[i]); - } - b + pub fn scalar_mul_ext(&mut self, a: Target, b: ExtensionTarget) -> ExtensionTarget { + let a_ext = self.convert_to_ext(a); + self.mul_extension(a_ext, b) } /// Returns `a * b`, where `b` is in the extension of the extension field, and `a` is in the @@ -194,4 +263,26 @@ impl, const D: usize> CircuitBuilder { } b } + + pub fn convert_to_ext(&mut self, t: Target) -> ExtensionTarget { + let zero = self.zero(); + let mut arr = [zero; D]; + arr[0] = t; + ExtensionTarget(arr) + } +} + +/// Flatten the slice by sending every extension target to its D-sized canonical representation. +pub fn flatten_target(l: &[ExtensionTarget]) -> Vec { + l.iter() + .flat_map(|x| x.to_target_array().to_vec()) + .collect() +} + +/// Batch every D-sized chunks into extension targets. +pub fn unflatten_target, const D: usize>(l: &[Target]) -> Vec> { + debug_assert_eq!(l.len() % D, 0); + l.chunks_exact(D) + .map(|c| c.to_vec().try_into().unwrap()) + .collect() } diff --git a/src/field/field.rs b/src/field/field.rs index b637c72b..b19f175e 100644 --- a/src/field/field.rs +++ b/src/field/field.rs @@ -7,6 +7,7 @@ use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssi use num::Integer; use rand::Rng; +use crate::field::extension_field::Frobenius; use crate::util::bits_u64; /// A finite field with prime order less than 2^64. @@ -283,3 +284,17 @@ impl Iterator for Powers { Some(result) } } + +impl Powers { + /// Apply the Frobenius automorphism `k` times. + pub fn repeated_frobenius(self, k: usize) -> Self + where + F: Frobenius, + { + let Self { base, current } = self; + Self { + base: base.repeated_frobenius(k), + current: current.repeated_frobenius(k), + } + } +} diff --git a/src/fri/mod.rs b/src/fri/mod.rs index c147e8c6..6351a2af 100644 --- a/src/fri/mod.rs +++ b/src/fri/mod.rs @@ -1,6 +1,7 @@ use crate::polynomial::commitment::SALT_SIZE; pub mod prover; +mod recursive_verifier; pub mod verifier; /// Somewhat arbitrary. Smaller values will increase delta, but with diminishing returns, diff --git a/src/fri/prover.rs b/src/fri/prover.rs index d1eeadcf..f937ee55 100644 --- a/src/fri/prover.rs +++ b/src/fri/prover.rs @@ -107,7 +107,7 @@ fn fri_proof_of_work(current_hash: Hash, config: &FriConfig) -> F { ) .to_canonical_u64() .leading_zeros() - >= config.proof_of_work_bits + >= config.proof_of_work_bits + F::ORDER.leading_zeros() }) .map(F::from_canonical_u64) .expect("Proof of work failed.") diff --git a/src/fri/recursive_verifier.rs b/src/fri/recursive_verifier.rs new file mode 100644 index 00000000..b35098e1 --- /dev/null +++ b/src/fri/recursive_verifier.rs @@ -0,0 +1,354 @@ +use itertools::izip; + +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::{flatten_target, ExtensionTarget}; +use crate::field::extension_field::Extendable; +use crate::field::field::Field; +use crate::fri::FriConfig; +use crate::plonk_challenger::RecursiveChallenger; +use crate::proof::{ + FriInitialTreeProofTarget, FriProofTarget, FriQueryRoundTarget, HashTarget, OpeningSetTarget, +}; +use crate::target::Target; +use crate::util::{log2_strict, reverse_index_bits_in_place}; + +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 + /// and P' is the FRI reduced polynomial. + fn compute_evaluation( + &mut self, + x: Target, + old_x_index: Target, + arity_bits: usize, + last_evals: &[ExtensionTarget], + beta: ExtensionTarget, + ) -> ExtensionTarget { + debug_assert_eq!(last_evals.len(), 1 << arity_bits); + + let g = F::primitive_root_of_unity(arity_bits); + + // The evaluation vector needs to be reordered first. + let mut evals = last_evals.to_vec(); + reverse_index_bits_in_place(&mut evals); + let mut old_x_index_bits = self.split_le(old_x_index, arity_bits); + old_x_index_bits.reverse(); + let evals = self.rotate_left_from_bits(&old_x_index_bits, &evals); + + // The answer is gotten by interpolating {(x*g^i, P(x*g^i))} and evaluating at beta. + let points = g + .powers() + .map(|y| { + let yt = self.constant(y); + self.mul(x, yt) + }) + .zip(evals) + .collect::>(); + + self.interpolate(&points, beta) + } + + fn fri_verify_proof_of_work( + &mut self, + proof: &FriProofTarget, + challenger: &mut RecursiveChallenger, + config: &FriConfig, + ) { + let mut inputs = challenger.get_hash(self).elements.to_vec(); + inputs.push(proof.pow_witness); + + let hash = self.hash_n_to_m(inputs, 1, false)[0]; + self.assert_leading_zeros(hash, config.proof_of_work_bits + F::ORDER.leading_zeros()); + } + + pub fn verify_fri_proof( + &mut self, + purported_degree_log: usize, + // Openings of the PLONK polynomials. + os: &OpeningSetTarget, + // Point at which the PLONK polynomials are opened. + zeta: ExtensionTarget, + // Scaling factor to combine polynomials. + alpha: ExtensionTarget, + initial_merkle_roots: &[HashTarget], + proof: &FriProofTarget, + challenger: &mut RecursiveChallenger, + config: &FriConfig, + ) { + let total_arities = config.reduction_arity_bits.iter().sum::(); + debug_assert_eq!( + purported_degree_log, + log2_strict(proof.final_poly.len()) + total_arities - config.rate_bits, + "Final polynomial has wrong degree." + ); + + // Size of the LDE domain. + let n = proof.final_poly.len() << total_arities; + + // Recover the random betas used in the FRI reductions. + let betas = proof + .commit_phase_merkle_roots + .iter() + .map(|root| { + challenger.observe_hash(root); + challenger.get_extension_challenge(self) + }) + .collect::>(); + challenger.observe_extension_elements(&proof.final_poly.0); + + // Check PoW. + self.fri_verify_proof_of_work(proof, challenger, config); + + // Check that parameters are coherent. + debug_assert_eq!( + config.num_query_rounds, + proof.query_round_proofs.len(), + "Number of query rounds does not match config." + ); + debug_assert!( + !config.reduction_arity_bits.is_empty(), + "Number of reductions should be non-zero." + ); + + for round_proof in &proof.query_round_proofs { + self.fri_verifier_query_round( + os, + zeta, + alpha, + initial_merkle_roots, + &proof, + challenger, + n, + &betas, + round_proof, + config, + ); + } + } + + fn fri_verify_initial_proof( + &mut self, + x_index: Target, + proof: &FriInitialTreeProofTarget, + initial_merkle_roots: &[HashTarget], + ) { + for ((evals, merkle_proof), &root) in proof.evals_proofs.iter().zip(initial_merkle_roots) { + self.verify_merkle_proof(evals.clone(), x_index, root, merkle_proof); + } + } + + fn fri_combine_initial( + &mut self, + proof: &FriInitialTreeProofTarget, + alpha: ExtensionTarget, + os: &OpeningSetTarget, + zeta: ExtensionTarget, + subgroup_x: Target, + ) -> ExtensionTarget { + assert!(D > 1, "Not implemented for D=1."); + let config = &self.config.fri_config.clone(); + let degree_log = proof.evals_proofs[0].1.siblings.len() - config.rate_bits; + let subgroup_x = self.convert_to_ext(subgroup_x); + let mut alpha_powers = self.powers(alpha); + let mut sum = self.zero_extension(); + + // We will add three terms to `sum`: + // - one for polynomials opened at `x` only + // - one for polynomials opened at `x` and `g x` + // - one for polynomials opened at `x` and `x.frobenius()` + + // Polynomials opened at `x`, i.e., the constants, sigmas and quotient polynomials. + let single_evals = [0, 1, 4] + .iter() + .flat_map(|&i| proof.unsalted_evals(i, config)) + .map(|&e| self.convert_to_ext(e)) + .collect::>(); + let single_openings = os + .constants + .iter() + .chain(&os.plonk_sigmas) + .chain(&os.quotient_polys); + let mut single_numerator = self.zero_extension(); + for (e, &o) in izip!(single_evals, single_openings) { + let a = alpha_powers.next(self); + let diff = self.sub_extension(e, o); + single_numerator = self.mul_add_extension(a, diff, single_numerator); + } + let single_denominator = self.sub_extension(subgroup_x, zeta); + let quotient = self.div_unsafe_extension(single_numerator, single_denominator); + sum = self.add_extension(sum, quotient); + + // Polynomials opened at `x` and `g x`, i.e., the Zs polynomials. + let zs_evals = proof + .unsalted_evals(3, config) + .iter() + .map(|&e| self.convert_to_ext(e)) + .collect::>(); + // TODO: Would probably be more efficient using `CircuitBuilder::reduce_with_powers_recursive` + let mut zs_composition_eval = self.zero_extension(); + let mut alpha_powers_cloned = alpha_powers.clone(); + for &e in &zs_evals { + let a = alpha_powers_cloned.next(self); + zs_composition_eval = self.mul_add_extension(a, e, zs_composition_eval); + } + + let g = self.constant_extension(F::Extension::primitive_root_of_unity(degree_log)); + let zeta_right = self.mul_extension(g, zeta); + let mut zs_ev_zeta = self.zero_extension(); + let mut alpha_powers_cloned = alpha_powers.clone(); + for &t in &os.plonk_zs { + let a = alpha_powers_cloned.next(self); + zs_ev_zeta = self.mul_add_extension(a, t, zs_ev_zeta); + } + let mut zs_ev_zeta_right = self.zero_extension(); + for &t in &os.plonk_zs_right { + let a = alpha_powers.next(self); + zs_ev_zeta_right = self.mul_add_extension(a, t, zs_ev_zeta); + } + let interpol_val = self.interpolate2( + [(zeta, zs_ev_zeta), (zeta_right, zs_ev_zeta_right)], + subgroup_x, + ); + let zs_numerator = self.sub_extension(zs_composition_eval, interpol_val); + let vanish_zeta = self.sub_extension(subgroup_x, zeta); + let vanish_zeta_right = self.sub_extension(subgroup_x, zeta_right); + let zs_denominator = self.mul_extension(vanish_zeta, vanish_zeta_right); + let zs_quotient = self.div_unsafe_extension(zs_numerator, zs_denominator); + sum = self.add_extension(sum, zs_quotient); + + // Polynomials opened at `x` and `x.frobenius()`, i.e., the wires polynomials. + let wire_evals = proof + .unsalted_evals(2, config) + .iter() + .map(|&e| self.convert_to_ext(e)) + .collect::>(); + let mut wire_composition_eval = self.zero_extension(); + let mut alpha_powers_cloned = alpha_powers.clone(); + for &e in &wire_evals { + let a = alpha_powers_cloned.next(self); + wire_composition_eval = self.mul_add_extension(a, e, wire_composition_eval); + } + let mut alpha_powers_cloned = alpha_powers.clone(); + let wire_eval = os.wires.iter().fold(self.zero_extension(), |acc, &w| { + let a = alpha_powers_cloned.next(self); + self.mul_add_extension(a, w, acc) + }); + let mut alpha_powers_frob = alpha_powers.repeated_frobenius(D - 1, self); + let wire_eval_frob = os + .wires + .iter() + .fold(self.zero_extension(), |acc, &w| { + let a = alpha_powers_frob.next(self); + self.mul_add_extension(a, w, acc) + }) + .frobenius(self); + let zeta_frob = zeta.frobenius(self); + let wire_interpol_val = + self.interpolate2([(zeta, wire_eval), (zeta_frob, wire_eval_frob)], subgroup_x); + let wire_numerator = self.sub_extension(wire_composition_eval, wire_interpol_val); + let vanish_zeta_frob = self.sub_extension(subgroup_x, zeta_frob); + let wire_denominator = self.mul_extension(vanish_zeta, vanish_zeta_frob); + let wire_quotient = self.div_unsafe_extension(wire_numerator, wire_denominator); + sum = self.add_extension(sum, wire_quotient); + + sum + } + + fn fri_verifier_query_round( + &mut self, + os: &OpeningSetTarget, + zeta: ExtensionTarget, + alpha: ExtensionTarget, + initial_merkle_roots: &[HashTarget], + proof: &FriProofTarget, + challenger: &mut RecursiveChallenger, + n: usize, + betas: &[ExtensionTarget], + round_proof: &FriQueryRoundTarget, + config: &FriConfig, + ) { + let n_log = log2_strict(n); + let mut evaluations: Vec>> = Vec::new(); + // TODO: Do we need to range check `x_index` to a target smaller than `p`? + let mut x_index = challenger.get_challenge(self); + x_index = self.split_low_high(x_index, n_log, 64).0; + let mut x_index_num_bits = n_log; + let mut domain_size = n; + self.fri_verify_initial_proof( + x_index, + &round_proof.initial_trees_proof, + initial_merkle_roots, + ); + let mut old_x_index = self.zero(); + // `subgroup_x` is `subgroup[x_index]`, i.e., the actual field element in the domain. + let g = self.constant(F::MULTIPLICATIVE_GROUP_GENERATOR); + let phi = self.constant(F::primitive_root_of_unity(n_log)); + + let reversed_x = self.reverse_limbs::<2>(x_index, n_log); + let phi = self.exp(phi, reversed_x, n_log); + let mut subgroup_x = self.mul(g, phi); + + 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 { + self.fri_combine_initial( + &round_proof.initial_trees_proof, + alpha, + os, + zeta, + subgroup_x, + ) + } else { + let last_evals = &evaluations[i - 1]; + // Infer P(y) from {P(x)}_{x^arity=y}. + self.compute_evaluation( + subgroup_x, + old_x_index, + config.reduction_arity_bits[i - 1], + last_evals, + betas[i - 1], + ) + }; + let mut evals = round_proof.steps[i].evals.clone(); + // Insert P(y) into the evaluation vector, since it wasn't included by the prover. + let (low_x_index, high_x_index) = + self.split_low_high(x_index, arity_bits, x_index_num_bits); + evals = self.insert(low_x_index, e_x, evals); + evaluations.push(evals); + self.verify_merkle_proof( + flatten_target(&evaluations[i]), + high_x_index, + proof.commit_phase_merkle_roots[i], + &round_proof.steps[i].merkle_proof, + ); + + if i > 0 { + // Update the point x to x^arity. + for _ in 0..config.reduction_arity_bits[i - 1] { + subgroup_x = self.square(subgroup_x); + } + } + domain_size = next_domain_size; + old_x_index = low_x_index; + x_index = high_x_index; + x_index_num_bits -= arity_bits; + } + + let last_evals = evaluations.last().unwrap(); + let final_arity_bits = *config.reduction_arity_bits.last().unwrap(); + let purported_eval = self.compute_evaluation( + subgroup_x, + old_x_index, + final_arity_bits, + last_evals, + *betas.last().unwrap(), + ); + for _ in 0..final_arity_bits { + subgroup_x = self.square(subgroup_x); + } + + // 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 = proof.final_poly.eval_scalar(self, subgroup_x); + self.assert_equal_extension(eval, purported_eval); + } +} diff --git a/src/fri/verifier.rs b/src/fri/verifier.rs index 68f4ee15..b5a650bb 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -1,6 +1,6 @@ use anyhow::{ensure, Result}; -use crate::field::extension_field::{flatten, Extendable, FieldExtension, OEF}; +use crate::field::extension_field::{flatten, Extendable, FieldExtension, Frobenius}; use crate::field::field::Field; use crate::field::lagrange::{barycentric_weights, interpolant, interpolate}; use crate::fri::FriConfig; @@ -159,6 +159,7 @@ fn fri_combine_initial, const D: usize>( // - one for Zs, which are opened at `x` and `g x` // - one for wire polynomials, which are opened at `x` and its conjugate + // Polynomials opened at `x`, i.e., the constants, sigmas and quotient polynomials. let single_evals = [0, 1, 4] .iter() .flat_map(|&i| proof.unsalted_evals(i, config)) @@ -173,6 +174,7 @@ fn fri_combine_initial, const D: usize>( let single_denominator = subgroup_x - zeta; sum += single_numerator / single_denominator; + // Polynomials opened at `x` and `g x`, i.e., the Zs polynomials. let zs_evals = proof .unsalted_evals(3, config) .iter() @@ -190,20 +192,25 @@ fn fri_combine_initial, const D: usize>( let zs_denominator = (subgroup_x - zeta) * (subgroup_x - zeta_right); sum += zs_numerator / zs_denominator; + // Polynomials opened at `x` and `x.frobenius()`, i.e., the wires polynomials. let wire_evals = proof .unsalted_evals(2, config) .iter() .map(|&e| F::Extension::from_basefield(e)); let wire_composition_eval = reduce_with_iter(wire_evals, alpha_powers.clone()); let zeta_frob = zeta.frobenius(); - let wire_evals_frob = os.wires.iter().map(|e| e.frobenius()); - let wires_interpol = interpolant(&[ - (zeta, reduce_with_iter(&os.wires, alpha_powers.clone())), - (zeta_frob, reduce_with_iter(wire_evals_frob, alpha_powers)), - ]); - let wires_numerator = wire_composition_eval - wires_interpol.eval(subgroup_x); - let wires_denominator = (subgroup_x - zeta) * (subgroup_x - zeta_frob); - sum += wires_numerator / wires_denominator; + let wire_eval = reduce_with_iter(&os.wires, alpha_powers.clone()); + // We want to compute `sum a^i*phi(w_i)`, where `phi` denotes the Frobenius automorphism. + // Since `phi^D=id` and `phi` is a field automorphism, we have the following equalities: + // `sum a^i*phi(w_i) = sum phi(phi^(D-1)(a^i)*w_i) = phi(sum phi^(D-1)(a)^i*w_i)` + // So we can compute the original sum using only one call to the `D-1`-repeated Frobenius of alpha, + // and one call at the end of the sum. + let alpha_powers_frob = alpha_powers.repeated_frobenius(D - 1); + let wire_eval_frob = reduce_with_iter(&os.wires, alpha_powers_frob).frobenius(); + let wire_interpol = interpolant(&[(zeta, wire_eval), (zeta_frob, wire_eval_frob)]); + let wire_numerator = wire_composition_eval - wire_interpol.eval(subgroup_x); + let wire_denominator = (subgroup_x - zeta) * (subgroup_x - zeta_frob); + sum += wire_numerator / wire_denominator; sum } @@ -276,7 +283,7 @@ fn fri_verifier_query_round, const D: usize>( } } domain_size = next_domain_size; - old_x_index = x_index; + old_x_index = x_index & (arity - 1); x_index >>= arity_bits; } diff --git a/src/gadgets/arithmetic.rs b/src/gadgets/arithmetic.rs index a214df3f..6250ea81 100644 --- a/src/gadgets/arithmetic.rs +++ b/src/gadgets/arithmetic.rs @@ -1,7 +1,11 @@ +use std::convert::TryInto; + use crate::circuit_builder::CircuitBuilder; -use crate::field::extension_field::Extendable; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::gates::arithmetic::ArithmeticGate; +use crate::gates::mul_extension::MulExtensionGate; use crate::generator::SimpleGenerator; use crate::target::Target; use crate::wire::Wire; @@ -169,6 +173,22 @@ impl, const D: usize> CircuitBuilder { product } + // TODO: Optimize this, maybe with a new gate. + /// Exponentiate `base` to the power of `exponent`, where `exponent < 2^num_bits`. + pub fn exp(&mut self, base: Target, exponent: Target, num_bits: usize) -> Target { + let mut current = base; + let one = self.one(); + let mut product = one; + let exponent_bits = self.split_le(exponent, num_bits); + + for bit in exponent_bits.into_iter() { + product = self.mul_many(&[bit, current, product]); + current = self.mul(current, current); + } + + product + } + /// Computes `q = x / y` by witnessing `q` and requiring that `q * y = x`. This can be unsafe in /// some cases, as it allows `0 / 0 = `. pub fn div_unsafe(&mut self, x: Target, y: Target) -> Target { @@ -224,6 +244,38 @@ impl, const D: usize> CircuitBuilder { q } + + /// Computes `q = x / y` by witnessing `q` and requiring that `q * y = x`. This can be unsafe in + /// some cases, as it allows `0 / 0 = `. + pub fn div_unsafe_extension( + &mut self, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + // Add an `ArithmeticGate` to compute `q * y`. + let gate = self.add_gate(MulExtensionGate::new(), vec![F::ONE]); + + let multiplicand_0 = + Target::wires_from_range(gate, MulExtensionGate::::wires_multiplicand_0()); + let multiplicand_0 = ExtensionTarget(multiplicand_0.try_into().unwrap()); + let multiplicand_1 = + Target::wires_from_range(gate, MulExtensionGate::::wires_multiplicand_1()); + let multiplicand_1 = ExtensionTarget(multiplicand_1.try_into().unwrap()); + let output = Target::wires_from_range(gate, MulExtensionGate::::wires_output()); + let output = ExtensionTarget(output.try_into().unwrap()); + + self.add_generator(QuotientGeneratorExtension { + numerator: x, + denominator: y, + quotient: multiplicand_0, + }); + + self.route_extension(y, multiplicand_1); + + self.assert_equal_extension(output, x); + + multiplicand_0 + } } struct QuotientGenerator { @@ -243,3 +295,107 @@ impl SimpleGenerator for QuotientGenerator { PartialWitness::singleton_target(self.quotient, num / den) } } + +struct QuotientGeneratorExtension { + numerator: ExtensionTarget, + denominator: ExtensionTarget, + quotient: ExtensionTarget, +} + +impl, const D: usize> SimpleGenerator for QuotientGeneratorExtension { + fn dependencies(&self) -> Vec { + let mut deps = self.numerator.to_target_array().to_vec(); + deps.extend(&self.denominator.to_target_array()); + deps + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let num = witness.get_extension_target(self.numerator); + let dem = witness.get_extension_target(self.denominator); + let quotient = num / dem; + let mut pw = PartialWitness::new(); + for i in 0..D { + pw.set_target( + self.quotient.to_target_array()[i], + quotient.to_basefield_array()[i], + ); + } + pw + } +} + +/// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, ...`. +#[derive(Clone)] +pub struct PowersTarget { + base: ExtensionTarget, + current: ExtensionTarget, +} + +impl PowersTarget { + pub fn next>( + &mut self, + builder: &mut CircuitBuilder, + ) -> ExtensionTarget { + let result = self.current; + self.current = builder.mul_extension(self.base, self.current); + result + } + + pub fn repeated_frobenius>( + self, + k: usize, + builder: &mut CircuitBuilder, + ) -> Self { + let Self { base, current } = self; + Self { + base: base.repeated_frobenius(k, builder), + current: current.repeated_frobenius(k, builder), + } + } +} + +impl, const D: usize> CircuitBuilder { + pub fn powers(&mut self, base: ExtensionTarget) -> PowersTarget { + PowersTarget { + base, + current: self.one_extension(), + } + } +} + +#[cfg(test)] +mod tests { + use crate::circuit_builder::CircuitBuilder; + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::field::Field; + use crate::fri::FriConfig; + use crate::prover::PLONK_BLINDING; + use crate::witness::PartialWitness; + + #[test] + fn test_div_extension() { + type F = CrandallField; + type FF = QuarticCrandallField; + const D: usize = 4; + + let config = CircuitConfig::large_config(); + + let mut builder = CircuitBuilder::::new(config); + + let x = FF::rand(); + let y = FF::rand(); + let x = FF::TWO; + let y = FF::ONE; + let z = x / y; + let xt = builder.constant_extension(x); + let yt = builder.constant_extension(y); + let zt = builder.constant_extension(z); + let comp_zt = builder.div_unsafe_extension(xt, yt); + builder.assert_equal_extension(zt, comp_zt); + + let data = builder.build(); + let proof = data.prove(PartialWitness::new()); + } +} diff --git a/src/gadgets/insert.rs b/src/gadgets/insert.rs new file mode 100644 index 00000000..64cf7299 --- /dev/null +++ b/src/gadgets/insert.rs @@ -0,0 +1,71 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::target::Target; + +impl, const D: usize> CircuitBuilder { + /// Inserts a `Target` in a vector at a non-deterministic index. This is done by rotating to the + /// left, inserting at 0 and then rotating to the right. + /// Note: `index` is not range-checked. + pub fn insert( + &mut self, + index: Target, + element: ExtensionTarget, + v: Vec>, + ) -> Vec> { + let mut v = self.rotate_left(index, &v); + v.insert(0, element); + self.rotate_right(index, &v) + } +} +#[cfg(test)] +mod tests { + use super::*; + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::field::Field; + use crate::witness::PartialWitness; + + fn real_insert( + index: usize, + element: ExtensionTarget, + v: &[ExtensionTarget], + ) -> Vec> { + let mut res = v.to_vec(); + res.insert(index, element); + res + } + + fn test_insert_given_len(len_log: usize) { + type F = CrandallField; + type FF = QuarticCrandallField; + let len = 1 << len_log; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + let v = (0..len - 1) + .map(|_| builder.constant_extension(FF::rand())) + .collect::>(); + + for i in 0..len { + let it = builder.constant(F::from_canonical_usize(i)); + let elem = builder.constant_extension(FF::rand()); + let inserted = real_insert(i, elem, &v); + let purported_inserted = builder.insert(it, elem, v.clone()); + + for (x, y) in inserted.into_iter().zip(purported_inserted) { + builder.route_extension(x, y); + } + } + + let data = builder.build(); + let proof = data.prove(PartialWitness::new()); + } + + #[test] + fn test_insert() { + for len_log in 1..3 { + test_insert_given_len(len_log); + } + } +} diff --git a/src/gadgets/interpolation.rs b/src/gadgets/interpolation.rs new file mode 100644 index 00000000..a94eb582 --- /dev/null +++ b/src/gadgets/interpolation.rs @@ -0,0 +1,140 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::gates::interpolation::InterpolationGate; +use crate::target::Target; +use std::marker::PhantomData; + +impl, const D: usize> CircuitBuilder { + /// Interpolate two points. No need for an `InterpolationGate` since the coefficients + /// of the linear interpolation polynomial can be easily computed with arithmetic operations. + pub fn interpolate2( + &mut self, + interpolation_points: [(ExtensionTarget, ExtensionTarget); 2], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + // a0 -> a1 + // b0 -> b1 + // x -> a1 + (x-a0)*(b1-a1)/(b0-a0) + + let x_m_a0 = self.sub_extension(evaluation_point, interpolation_points[0].0); + let b1_m_a1 = self.sub_extension(interpolation_points[1].1, interpolation_points[0].1); + let b0_m_a0 = self.sub_extension(interpolation_points[1].0, interpolation_points[0].0); + let quotient = self.div_unsafe_extension(b1_m_a1, b0_m_a0); + + self.mul_add_extension(x_m_a0, quotient, interpolation_points[0].1) + } + + /// Interpolate a list of point/evaluation pairs at a given point. + /// Returns the evaluation of the interpolated polynomial at `evaluation_point`. + pub fn interpolate( + &mut self, + interpolation_points: &[(Target, ExtensionTarget)], + evaluation_point: ExtensionTarget, + ) -> ExtensionTarget { + let gate = InterpolationGate:: { + num_points: interpolation_points.len(), + _phantom: PhantomData, + }; + let gate_index = + self.add_gate_no_constants(InterpolationGate::new(interpolation_points.len())); + for (i, &(p, v)) in interpolation_points.iter().enumerate() { + self.route(p, Target::wire(gate_index, gate.wire_point(i))); + self.route_extension( + v, + ExtensionTarget::from_range(gate_index, gate.wires_value(i)), + ); + } + self.route_extension( + evaluation_point, + ExtensionTarget::from_range(gate_index, gate.wires_evaluation_point()), + ); + + ExtensionTarget::from_range(gate_index, gate.wires_evaluation_value()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + 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::field::field::Field; + use crate::field::lagrange::{interpolant, interpolate}; + use crate::witness::PartialWitness; + use std::convert::TryInto; + + #[test] + fn test_interpolate() { + type F = CrandallField; + type FF = QuarticCrandallField; + let config = CircuitConfig { + num_routed_wires: 18, + ..CircuitConfig::large_config() + }; + let mut builder = CircuitBuilder::::new(config); + + let len = 2; + let points = (0..len) + .map(|_| (F::rand(), FF::rand())) + .collect::>(); + + let homogeneous_points = points + .iter() + .map(|&(a, b)| (>::from_basefield(a), b)) + .collect::>(); + + let true_interpolant = interpolant(&homogeneous_points); + + let z = FF::rand(); + let true_eval = true_interpolant.eval(z); + + let points_target = points + .iter() + .map(|&(p, v)| (builder.constant(p), builder.constant_extension(v))) + .collect::>(); + + let zt = builder.constant_extension(z); + + let eval = builder.interpolate(&points_target, zt); + let true_eval_target = builder.constant_extension(true_eval); + builder.assert_equal_extension(eval, true_eval_target); + + let data = builder.build(); + let proof = data.prove(PartialWitness::new()); + } + + #[test] + fn test_interpolate2() { + type F = CrandallField; + type FF = QuarticCrandallField; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + + let len = 2; + let points = (0..len) + .map(|_| (FF::rand(), FF::rand())) + .collect::>(); + + let true_interpolant = interpolant(&points); + + let z = FF::rand(); + let true_eval = true_interpolant.eval(z); + + let points_target = points + .iter() + .map(|&(p, v)| (builder.constant_extension(p), builder.constant_extension(v))) + .collect::>(); + + let zt = builder.constant_extension(z); + + let eval = builder.interpolate2(points_target.try_into().unwrap(), zt); + let true_eval_target = builder.constant_extension(true_eval); + builder.assert_equal_extension(eval, true_eval_target); + + let data = builder.build(); + let proof = data.prove(PartialWitness::new()); + } +} diff --git a/src/gadgets/mod.rs b/src/gadgets/mod.rs index 9a6a728e..a1e041fc 100644 --- a/src/gadgets/mod.rs +++ b/src/gadgets/mod.rs @@ -1,4 +1,9 @@ pub mod arithmetic; pub mod hash; +pub mod insert; +pub mod interpolation; pub mod polynomial; +pub mod range_check; +pub mod rotate; +pub mod split_base; pub(crate) mod split_join; diff --git a/src/gadgets/polynomial.rs b/src/gadgets/polynomial.rs index 543be834..07bc1952 100644 --- a/src/gadgets/polynomial.rs +++ b/src/gadgets/polynomial.rs @@ -6,6 +6,10 @@ use crate::target::Target; pub struct PolynomialCoeffsExtTarget(pub Vec>); impl PolynomialCoeffsExtTarget { + pub fn len(&self) -> usize { + self.0.len() + } + pub fn eval_scalar>( &self, builder: &mut CircuitBuilder, diff --git a/src/gadgets/range_check.rs b/src/gadgets/range_check.rs new file mode 100644 index 00000000..7fd35efc --- /dev/null +++ b/src/gadgets/range_check.rs @@ -0,0 +1,63 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::Extendable; +use crate::field::field::Field; +use crate::gates::base_sum::BaseSumGate; +use crate::generator::SimpleGenerator; +use crate::target::Target; +use crate::witness::PartialWitness; + +impl, const D: usize> CircuitBuilder { + /// Checks that `x < 2^n_log` using a `BaseSumGate`. + pub fn range_check(&mut self, x: Target, n_log: usize) { + let gate = self.add_gate(BaseSumGate::<2>::new(n_log), vec![]); + let sum = Target::wire(gate, BaseSumGate::<2>::WIRE_SUM); + self.route(x, sum); + } + + /// Returns `(a,b)` such that `x = a + 2^n_log * b` with `a < 2^n_log`. + /// `x` is assumed to be range-checked for having `num_bits` bits. + pub fn split_low_high(&mut self, x: Target, n_log: usize, num_bits: usize) -> (Target, Target) { + let low_gate = self.add_gate(BaseSumGate::<2>::new(n_log), vec![]); + let high_gate = self.add_gate(BaseSumGate::<2>::new(num_bits - n_log), vec![]); + let low = Target::wire(low_gate, BaseSumGate::<2>::WIRE_SUM); + let high = Target::wire(high_gate, BaseSumGate::<2>::WIRE_SUM); + self.add_generator(LowHighGenerator { + integer: x, + n_log, + low, + high, + }); + + let pow2 = self.constant(F::from_canonical_u64(1 << n_log)); + let comp_x = self.mul_add(high, pow2, low); + self.assert_equal(x, comp_x); + + (low, high) + } +} + +#[derive(Debug)] +struct LowHighGenerator { + integer: Target, + n_log: usize, + low: Target, + high: Target, +} + +impl SimpleGenerator for LowHighGenerator { + fn dependencies(&self) -> Vec { + vec![self.integer] + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let integer_value = witness.get_target(self.integer).to_canonical_u64(); + let low = integer_value & ((1 << self.n_log) - 1); + let high = integer_value >> self.n_log; + + let mut result = PartialWitness::new(); + result.set_target(self.low, F::from_canonical_u64(low)); + result.set_target(self.high, F::from_canonical_u64(high)); + + result + } +} diff --git a/src/gadgets/rotate.rs b/src/gadgets/rotate.rs new file mode 100644 index 00000000..bd39a36b --- /dev/null +++ b/src/gadgets/rotate.rs @@ -0,0 +1,161 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::Extendable; +use crate::target::Target; +use crate::util::log2_ceil; + +impl, const D: usize> CircuitBuilder { + /// Selects `x` or `y` based on `b`, which is assumed to be binary. + /// In particular, this returns `if b { x } else { y }`. + /// Note: This does not range-check `b`. + // TODO: This uses 10 gates per call. If addends are added to `MulExtensionGate`, this will be + // reduced to 2 gates. We could also use a new degree 2 `SelectGate` for this. + // If `num_routed_wire` is larger than 26, we could batch two `select` in one gate. + pub fn select( + &mut self, + b: Target, + x: ExtensionTarget, + y: ExtensionTarget, + ) -> ExtensionTarget { + let b_y_minus_y = self.scalar_mul_sub_extension(b, y, y); + self.scalar_mul_sub_extension(b, x, b_y_minus_y) + } + + /// Left-rotates an array `k` times if `b=1` else return the same array. + pub fn rotate_left_fixed( + &mut self, + b: Target, + k: usize, + v: &[ExtensionTarget], + ) -> Vec> { + let len = v.len(); + debug_assert!(k < len, "Trying to rotate by more than the vector length."); + let mut res = Vec::new(); + + for i in 0..len { + res.push(self.select(b, v[(i + k) % len], v[i])); + } + + res + } + + /// Left-rotates an array `k` times if `b=1` else return the same array. + pub fn rotate_right_fixed( + &mut self, + b: Target, + k: usize, + v: &[ExtensionTarget], + ) -> Vec> { + let len = v.len(); + debug_assert!(k < len, "Trying to rotate by more than the vector length."); + let mut res = Vec::new(); + + for i in 0..len { + res.push(self.select(b, v[(len + i - k) % len], v[i])); + } + + res + } + + /// Left-rotates an vector by the `Target` having bits given in little-endian by `num_rotation_bits`. + pub fn rotate_left_from_bits( + &mut self, + num_rotation_bits: &[Target], + v: &[ExtensionTarget], + ) -> Vec> { + let mut v = v.to_vec(); + + for i in 0..num_rotation_bits.len() { + v = self.rotate_left_fixed(num_rotation_bits[i], 1 << i, &v); + } + + v + } + + pub fn rotate_right_from_bits( + &mut self, + num_rotation_bits: &[Target], + v: &[ExtensionTarget], + ) -> Vec> { + let mut v = v.to_vec(); + + for i in 0..num_rotation_bits.len() { + v = self.rotate_right_fixed(num_rotation_bits[i], 1 << i, &v); + } + + v + } + + /// Left-rotates an array by `num_rotation`. Assumes that `num_rotation` is range-checked to be + /// less than `2^len_bits`. + pub fn rotate_left( + &mut self, + num_rotation: Target, + v: &[ExtensionTarget], + ) -> Vec> { + let len_bits = log2_ceil(v.len()); + let bits = self.split_le(num_rotation, len_bits); + + self.rotate_left_from_bits(&bits, v) + } + + pub fn rotate_right( + &mut self, + num_rotation: Target, + v: &[ExtensionTarget], + ) -> Vec> { + let len_bits = log2_ceil(v.len()); + let bits = self.split_le(num_rotation, len_bits); + + self.rotate_right_from_bits(&bits, v) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; + use crate::field::field::Field; + use crate::witness::PartialWitness; + + fn real_rotate( + num_rotation: usize, + v: &[ExtensionTarget], + ) -> Vec> { + let mut res = v.to_vec(); + res.rotate_left(num_rotation); + res + } + + fn test_rotate_given_len(len: usize) { + type F = CrandallField; + type FF = QuarticCrandallField; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + let v = (0..len) + .map(|_| builder.constant_extension(FF::rand())) + .collect::>(); + + for i in 0..len { + let it = builder.constant(F::from_canonical_usize(i)); + let rotated = real_rotate(i, &v); + let purported_rotated = builder.rotate_left(it, &v); + + for (x, y) in rotated.into_iter().zip(purported_rotated) { + builder.assert_equal_extension(x, y); + } + } + + let data = builder.build(); + let proof = data.prove(PartialWitness::new()); + } + + #[test] + fn test_rotate() { + for len in 1..5 { + test_rotate_given_len(len); + } + } +} diff --git a/src/gadgets/split_base.rs b/src/gadgets/split_base.rs new file mode 100644 index 00000000..37f88409 --- /dev/null +++ b/src/gadgets/split_base.rs @@ -0,0 +1,71 @@ +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::Extendable; +use crate::gates::base_sum::BaseSumGate; +use crate::target::Target; + +impl, const D: usize> CircuitBuilder { + /// Split the given element into a list of targets, where each one represents a + /// base-B limb of the element, with little-endian ordering. + pub(crate) fn split_le_base( + &mut self, + x: Target, + num_limbs: usize, + ) -> Vec { + let gate = self.add_gate(BaseSumGate::::new(num_limbs), vec![]); + let sum = Target::wire(gate, BaseSumGate::::WIRE_SUM); + self.route(x, sum); + + Target::wires_from_range( + gate, + BaseSumGate::::START_LIMBS..BaseSumGate::::START_LIMBS + num_limbs, + ) + } + + /// Asserts that `x`'s big-endian bit representation has at least `leading_zeros` leading zeros. + pub(crate) fn assert_leading_zeros(&mut self, x: Target, leading_zeros: u32) { + self.range_check(x, (64 - leading_zeros) as usize); + } + + pub(crate) fn reverse_limbs(&mut self, x: Target, num_limbs: usize) -> Target { + let gate = self.add_gate(BaseSumGate::::new(num_limbs), vec![]); + let sum = Target::wire(gate, BaseSumGate::::WIRE_SUM); + self.route(x, sum); + + Target::wire(gate, BaseSumGate::::WIRE_REVERSED_SUM) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::field::field::Field; + use crate::witness::PartialWitness; + + #[test] + fn test_split_base() { + type F = CrandallField; + let config = CircuitConfig::large_config(); + let mut builder = CircuitBuilder::::new(config); + let x = F::from_canonical_usize(0b110100000); // 416 = 1532 in base 6. + let xt = builder.constant(x); + let limbs = builder.split_le_base::<6>(xt, 24); + let one = builder.one(); + let two = builder.two(); + let three = builder.constant(F::from_canonical_u64(3)); + let five = builder.constant(F::from_canonical_u64(5)); + builder.route(limbs[0], two); + builder.route(limbs[1], three); + builder.route(limbs[2], five); + builder.route(limbs[3], one); + let rev = builder.constant(F::from_canonical_u64(11)); + let revt = builder.reverse_limbs::<2>(xt, 9); + builder.route(revt, rev); + + builder.assert_leading_zeros(xt, 64 - 9); + let data = builder.build(); + + let proof = data.prove(PartialWitness::new()); + } +} diff --git a/src/gadgets/split_join.rs b/src/gadgets/split_join.rs index e65198ec..5eb60148 100644 --- a/src/gadgets/split_join.rs +++ b/src/gadgets/split_join.rs @@ -1,8 +1,10 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::Extendable; use crate::field::field::Field; +use crate::gates::base_sum::BaseSumGate; use crate::generator::{SimpleGenerator, WitnessGenerator}; use crate::target::Target; +use crate::util::ceil_div_usize; use crate::wire::Wire; use crate::witness::PartialWitness; @@ -21,6 +23,53 @@ impl, const D: usize> CircuitBuilder { }); bit_targets } + + /// Split the given integer into a list of wires, where each one represents a + /// bit of the integer, with little-endian ordering. + /// Verifies that the decomposition is correct by using `k` `BaseSum<2>` gates + /// with `k` such that `k*num_routed_wires>=num_bits`. + pub(crate) fn split_le(&mut self, integer: Target, num_bits: usize) -> Vec { + if num_bits == 0 { + return Vec::new(); + } + let bits_per_gate = self.config.num_routed_wires - BaseSumGate::<2>::START_LIMBS; + let k = ceil_div_usize(num_bits, bits_per_gate); + let gates = (0..k) + .map(|_| self.add_gate_no_constants(BaseSumGate::<2>::new(bits_per_gate))) + .collect::>(); + + let mut bits = Vec::with_capacity(num_bits); + for &gate in &gates { + bits.extend(Target::wires_from_range( + gate, + BaseSumGate::<2>::START_LIMBS..BaseSumGate::<2>::START_LIMBS + bits_per_gate, + )); + } + bits.drain(num_bits..); + + let zero = self.zero(); + let one = self.one(); + let mut acc = zero; + for &gate in gates.iter().rev() { + let sum = Target::wire(gate, BaseSumGate::<2>::WIRE_SUM); + acc = self.arithmetic( + F::from_canonical_usize(1 << bits_per_gate), + acc, + one, + F::ONE, + sum, + ); + } + self.assert_equal(acc, integer); + + self.add_generator(WireSplitGenerator { + integer, + gates, + num_limbs: bits_per_gate, + }); + + bits + } } /// Generator for a little-endian split. @@ -79,3 +128,39 @@ impl SimpleGenerator for SplitGenerator { result } } + +#[derive(Debug)] +struct WireSplitGenerator { + integer: Target, + gates: Vec, + num_limbs: usize, +} + +impl SimpleGenerator for WireSplitGenerator { + fn dependencies(&self) -> Vec { + vec![self.integer] + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let mut integer_value = witness.get_target(self.integer).to_canonical_u64(); + + let mut result = PartialWitness::new(); + for &gate in &self.gates { + let sum = Target::wire(gate, BaseSumGate::<2>::WIRE_SUM); + result.set_target( + sum, + F::from_canonical_u64(integer_value & ((1 << self.num_limbs) - 1)), + ); + integer_value >>= self.num_limbs; + } + + debug_assert_eq!( + integer_value, + 0, + "Integer too large to fit in {} many `BaseSumGate`s", + self.gates.len() + ); + + result + } +} diff --git a/src/gates/base_sum.rs b/src/gates/base_sum.rs new file mode 100644 index 00000000..e34aa6c1 --- /dev/null +++ b/src/gates/base_sum.rs @@ -0,0 +1,182 @@ +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::field::Field; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::plonk_common::{reduce_with_powers, reduce_with_powers_recursive}; +use crate::target::Target; +use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::witness::PartialWitness; + +/// A gate which can decompose a number into base B little-endian limbs, +/// and compute the limb-reversed (i.e. big-endian) sum. +#[derive(Debug)] +pub struct BaseSumGate { + num_limbs: usize, +} + +impl BaseSumGate { + pub fn new, const D: usize>(num_limbs: usize) -> GateRef { + GateRef::new(BaseSumGate:: { num_limbs }) + } + + pub const WIRE_SUM: usize = 0; + pub const WIRE_REVERSED_SUM: usize = 1; + pub const START_LIMBS: usize = 2; + + /// Returns the index of the `i`th limb wire. + pub fn limbs(&self) -> Range { + Self::START_LIMBS..Self::START_LIMBS + self.num_limbs + } +} + +impl, const D: usize, const B: usize> Gate for BaseSumGate { + fn id(&self) -> String { + format!("{:?} + Base: {}", self, B) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let sum = vars.local_wires[Self::WIRE_SUM]; + let reversed_sum = vars.local_wires[Self::WIRE_REVERSED_SUM]; + let mut limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B)); + limbs.reverse(); + let computed_reversed_sum = + reduce_with_powers(&limbs, F::Extension::from_canonical_usize(B)); + let mut constraints = vec![computed_sum - sum, computed_reversed_sum - reversed_sum]; + for limb in limbs { + constraints.push( + (0..B) + .map(|i| limb - F::Extension::from_canonical_usize(i)) + .product(), + ); + } + constraints + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let base = builder.constant(F::from_canonical_usize(B)); + let sum = vars.local_wires[Self::WIRE_SUM]; + let reversed_sum = vars.local_wires[Self::WIRE_REVERSED_SUM]; + let mut limbs = vars.local_wires[self.limbs()].to_vec(); + let computed_sum = reduce_with_powers_recursive(builder, &limbs, base); + limbs.reverse(); + let computed_reversed_sum = reduce_with_powers_recursive(builder, &limbs, base); + let mut constraints = vec![ + builder.sub_extension(computed_sum, sum), + builder.sub_extension(computed_reversed_sum, reversed_sum), + ]; + for limb in limbs { + constraints.push({ + let mut acc = builder.one_extension(); + (0..B).for_each(|i| { + let it = builder.constant_extension(F::from_canonical_usize(i).into()); + let diff = builder.sub_extension(limb, it); + acc = builder.mul_extension(acc, diff); + }); + acc + }); + } + constraints + } + + fn generators( + &self, + gate_index: usize, + _local_constants: &[F], + ) -> Vec>> { + let gen = BaseSplitGenerator:: { + gate_index, + num_limbs: self.num_limbs, + }; + vec![Box::new(gen)] + } + + // 2 for the sum and reversed sum, then `num_limbs` for the limbs. + fn num_wires(&self) -> usize { + self.num_limbs + 2 + } + + fn num_constants(&self) -> usize { + 0 + } + + // Bounded by the range-check (x-0)*(x-1)*...*(x-B+1). + fn degree(&self) -> usize { + B + } + + // 2 for checking the sum and reversed sum, then `num_limbs` for range-checking the limbs. + fn num_constraints(&self) -> usize { + 2 + self.num_limbs + } +} + +#[derive(Debug)] +pub struct BaseSplitGenerator { + gate_index: usize, + num_limbs: usize, +} + +impl SimpleGenerator for BaseSplitGenerator { + fn dependencies(&self) -> Vec { + vec![Target::wire(self.gate_index, BaseSumGate::::WIRE_SUM)] + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let sum_value = witness + .get_target(Target::wire(self.gate_index, BaseSumGate::::WIRE_SUM)) + .to_canonical_u64() as usize; + debug_assert_eq!( + (0..self.num_limbs).fold(sum_value, |acc, _| acc / B), + 0, + "Integer too large to fit in given number of limbs" + ); + + let limbs = (BaseSumGate::::START_LIMBS..BaseSumGate::::START_LIMBS + self.num_limbs) + .map(|i| Target::wire(self.gate_index, i)); + let limbs_value = (0..self.num_limbs) + .scan(sum_value, |acc, _| { + let tmp = *acc % B; + *acc /= B; + Some(F::from_canonical_usize(tmp)) + }) + .collect::>(); + + let b_field = F::from_canonical_usize(B); + let reversed_sum = limbs_value + .iter() + .fold(F::ZERO, |acc, &x| acc * b_field + x); + + let mut result = PartialWitness::new(); + result.set_target( + Target::wire(self.gate_index, BaseSumGate::::WIRE_REVERSED_SUM), + reversed_sum, + ); + for (b, b_value) in limbs.zip(limbs_value) { + result.set_target(b, b_value); + } + + result + } +} + +#[cfg(test)] +mod tests { + use crate::circuit_data::CircuitConfig; + use crate::field::crandall_field::CrandallField; + use crate::gates::base_sum::BaseSumGate; + use crate::gates::gate_testing::test_low_degree; + + #[test] + fn low_degree() { + test_low_degree(BaseSumGate::<6>::new::(11)) + } +} diff --git a/src/gates/interpolation.rs b/src/gates/interpolation.rs index c17be3e0..ac2ca49f 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -22,8 +22,8 @@ use crate::witness::PartialWitness; /// given point. #[derive(Clone, Debug)] pub(crate) struct InterpolationGate, const D: usize> { - num_points: usize, - _phantom: PhantomData, + pub num_points: usize, + pub _phantom: PhantomData, } impl, const D: usize> InterpolationGate { @@ -355,9 +355,7 @@ mod tests { }; assert!( - gate.eval_unfiltered(vars.clone()) - .iter() - .all(|x| x.is_zero()), + 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 ebcf6e3f..d013c7cd 100644 --- a/src/gates/mod.rs +++ b/src/gates/mod.rs @@ -1,8 +1,10 @@ pub(crate) mod arithmetic; +pub mod base_sum; pub mod constant; pub(crate) mod gate; pub mod gmimc; -mod interpolation; +pub mod interpolation; +pub mod mul_extension; pub(crate) mod noop; #[cfg(test)] diff --git a/src/gates/mul_extension.rs b/src/gates/mul_extension.rs new file mode 100644 index 00000000..e378e2b1 --- /dev/null +++ b/src/gates/mul_extension.rs @@ -0,0 +1,145 @@ +use std::ops::Range; + +use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; +use crate::field::extension_field::{Extendable, FieldExtension}; +use crate::gates::gate::{Gate, GateRef}; +use crate::generator::{SimpleGenerator, WitnessGenerator}; +use crate::target::Target; +use crate::vars::{EvaluationTargets, EvaluationVars}; +use crate::wire::Wire; +use crate::witness::PartialWitness; + +/// A gate which can multiply two field extension elements. +/// TODO: Add an addend if `NUM_ROUTED_WIRES` is large enough. +#[derive(Debug)] +pub struct MulExtensionGate; + +impl MulExtensionGate { + pub fn new>() -> GateRef { + GateRef::new(MulExtensionGate) + } + + pub fn wires_multiplicand_0() -> Range { + 0..D + } + pub fn wires_multiplicand_1() -> Range { + D..2 * D + } + pub fn wires_output() -> Range { + 2 * D..3 * D + } +} + +impl, const D: usize> Gate for MulExtensionGate { + fn id(&self) -> String { + format!("{:?}", self) + } + + fn eval_unfiltered(&self, vars: EvaluationVars) -> Vec { + let const_0 = vars.local_constants[0]; + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_multiplicand_0()); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_multiplicand_1()); + let output = vars.get_local_ext_algebra(Self::wires_output()); + let computed_output = multiplicand_0 * multiplicand_1 * const_0.into(); + (output - computed_output).to_basefield_array().to_vec() + } + + fn eval_unfiltered_recursively( + &self, + builder: &mut CircuitBuilder, + vars: EvaluationTargets, + ) -> Vec> { + let const_0 = vars.local_constants[0]; + let multiplicand_0 = vars.get_local_ext_algebra(Self::wires_multiplicand_0()); + let multiplicand_1 = vars.get_local_ext_algebra(Self::wires_multiplicand_1()); + let output = vars.get_local_ext_algebra(Self::wires_output()); + let computed_output = builder.mul_ext_algebra(multiplicand_0, multiplicand_1); + let computed_output = builder.scalar_mul_ext_algebra(const_0, computed_output); + let diff = builder.sub_ext_algebra(output, computed_output); + diff.to_ext_target_array().to_vec() + } + + fn generators( + &self, + gate_index: usize, + local_constants: &[F], + ) -> Vec>> { + let gen = MulExtensionGenerator { + gate_index, + const_0: local_constants[0], + }; + vec![Box::new(gen)] + } + + fn num_wires(&self) -> usize { + 12 + } + + fn num_constants(&self) -> usize { + 1 + } + + fn degree(&self) -> usize { + 3 + } + + fn num_constraints(&self) -> usize { + D + } +} + +struct MulExtensionGenerator, const D: usize> { + gate_index: usize, + const_0: F, +} + +impl, const D: usize> SimpleGenerator for MulExtensionGenerator { + fn dependencies(&self) -> Vec { + MulExtensionGate::::wires_multiplicand_0() + .chain(MulExtensionGate::::wires_multiplicand_1()) + .map(|i| { + Target::Wire(Wire { + gate: self.gate_index, + input: i, + }) + }) + .collect() + } + + fn run_once(&self, witness: &PartialWitness) -> PartialWitness { + let multiplicand_0_target = ExtensionTarget::from_range( + self.gate_index, + MulExtensionGate::::wires_multiplicand_0(), + ); + let multiplicand_0 = witness.get_extension_target(multiplicand_0_target); + + let multiplicand_1_target = ExtensionTarget::from_range( + self.gate_index, + MulExtensionGate::::wires_multiplicand_1(), + ); + let multiplicand_1 = witness.get_extension_target(multiplicand_1_target); + + let output_target = + ExtensionTarget::from_range(self.gate_index, MulExtensionGate::::wires_output()); + + let computed_output = + F::Extension::from_basefield(self.const_0) * multiplicand_0 * multiplicand_1; + + let mut pw = PartialWitness::new(); + pw.set_extension_target(output_target, computed_output); + pw + } +} + +#[cfg(test)] +mod tests { + use crate::field::crandall_field::CrandallField; + use crate::gates::gate_testing::test_low_degree; + use crate::gates::mul_extension::MulExtensionGate; + + #[test] + fn low_degree() { + test_low_degree(MulExtensionGate::<4>::new::()) + } +} diff --git a/src/gates/noop.rs b/src/gates/noop.rs index eddd0361..a12df932 100644 --- a/src/gates/noop.rs +++ b/src/gates/noop.rs @@ -5,7 +5,7 @@ use crate::gates::gate::{Gate, GateRef}; use crate::generator::WitnessGenerator; use crate::vars::{EvaluationTargets, EvaluationVars}; -/// A gate which takes a single constant parameter and outputs that value. +/// A gate which does nothing. pub struct NoopGate; impl NoopGate { diff --git a/src/merkle_proofs.rs b/src/merkle_proofs.rs index d5ab8a78..7d50f466 100644 --- a/src/merkle_proofs.rs +++ b/src/merkle_proofs.rs @@ -62,7 +62,7 @@ impl, const D: usize> CircuitBuilder { leaf_data: Vec, leaf_index: Target, merkle_root: HashTarget, - proof: MerkleProofTarget, + proof: &MerkleProofTarget, ) { let zero = self.zero(); let height = proof.siblings.len(); @@ -71,7 +71,7 @@ impl, const D: usize> CircuitBuilder { let mut state: HashTarget = self.hash_or_noop(leaf_data); let mut acc_leaf_index = zero; - for (bit, sibling) in purported_index_bits.into_iter().zip(proof.siblings) { + for (bit, &sibling) in purported_index_bits.into_iter().zip(&proof.siblings) { let gate = self .add_gate_no_constants(GMiMCGate::::with_automatic_constants()); diff --git a/src/plonk_challenger.rs b/src/plonk_challenger.rs index fd21ee0d..b24bbde8 100644 --- a/src/plonk_challenger.rs +++ b/src/plonk_challenger.rs @@ -1,9 +1,11 @@ use crate::circuit_builder::CircuitBuilder; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::hash::{permute, SPONGE_RATE, SPONGE_WIDTH}; use crate::proof::{Hash, HashTarget, OpeningSet}; use crate::target::Target; +use std::convert::TryInto; /// Observes prover messages, and generates challenges by hashing the transcript. #[derive(Clone)] @@ -41,9 +43,7 @@ impl Challenger { where F: Extendable, { - for &e in &element.to_basefield_array() { - self.observe_element(e); - } + self.observe_elements(&element.to_basefield_array()); } pub fn observe_elements(&mut self, elements: &[F]) { @@ -177,7 +177,7 @@ impl Default for Challenger { } /// A recursive version of `Challenger`. -pub(crate) struct RecursiveChallenger { +pub struct RecursiveChallenger { sponge_state: [Target; SPONGE_WIDTH], input_buffer: Vec, output_buffer: Vec, @@ -212,6 +212,16 @@ impl RecursiveChallenger { self.observe_elements(&hash.elements) } + pub fn observe_extension_element(&mut self, element: ExtensionTarget) { + self.observe_elements(&element.0); + } + + pub fn observe_extension_elements(&mut self, elements: &[ExtensionTarget]) { + for &element in elements { + self.observe_extension_element(element); + } + } + pub(crate) fn get_challenge, const D: usize>( &mut self, builder: &mut CircuitBuilder, @@ -255,6 +265,27 @@ impl RecursiveChallenger { (0..n).map(|_| self.get_challenge(builder)).collect() } + pub fn get_hash, const D: usize>( + &mut self, + builder: &mut CircuitBuilder, + ) -> HashTarget { + HashTarget { + elements: [ + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + self.get_challenge(builder), + ], + } + } + + pub fn get_extension_challenge, const D: usize>( + &mut self, + builder: &mut CircuitBuilder, + ) -> ExtensionTarget { + self.get_n_challenges(builder, D).try_into().unwrap() + } + /// Absorb any buffered inputs. After calling this, the input buffer will be empty. fn absorb_buffered_inputs, const D: usize>( &mut self, diff --git a/src/plonk_common.rs b/src/plonk_common.rs index e8c3c995..5c881fb1 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -206,10 +206,15 @@ pub(crate) fn reduce_with_powers(terms: &[F], alpha: F) -> F { pub(crate) fn reduce_with_powers_recursive, const D: usize>( builder: &mut CircuitBuilder, - terms: Vec, + terms: &[ExtensionTarget], alpha: Target, -) -> Target { - todo!() +) -> ExtensionTarget { + let mut sum = builder.zero_extension(); + for &term in terms.iter().rev() { + sum = builder.scalar_mul_ext(alpha, sum); + sum = builder.add_extension(sum, term); + } + sum } /// Reduce a sequence of field elements by the given coefficients. diff --git a/src/polynomial/commitment.rs b/src/polynomial/commitment.rs index 355a56e4..c1fd08eb 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -1,8 +1,8 @@ use anyhow::Result; use rayon::prelude::*; -use crate::field::extension_field::FieldExtension; -use crate::field::extension_field::{Extendable, OEF}; +use crate::field::extension_field::Extendable; +use crate::field::extension_field::{FieldExtension, Frobenius}; use crate::field::field::Field; use crate::field::lagrange::interpolant; use crate::fri::{prover::fri_proof, verifier::verify_fri_proof, FriConfig}; @@ -10,7 +10,7 @@ use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; use crate::plonk_common::{reduce_polys_with_iter, reduce_with_iter}; use crate::polynomial::polynomial::PolynomialCoeffs; -use crate::proof::{FriProof, Hash, OpeningSet}; +use crate::proof::{FriProof, FriProofTarget, Hash, OpeningSet}; use crate::timed; use crate::util::{log2_strict, reverse_index_bits_in_place, transpose}; @@ -253,6 +253,10 @@ impl, const D: usize> OpeningProof { } } +pub struct OpeningProofTarget { + fri_proof: FriProofTarget, +} + #[cfg(test)] mod tests { use anyhow::Result; diff --git a/src/proof.rs b/src/proof.rs index b1772c45..e05e4093 100644 --- a/src/proof.rs +++ b/src/proof.rs @@ -1,10 +1,12 @@ use std::convert::TryInto; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field::Field; use crate::fri::FriConfig; +use crate::gadgets::polynomial::PolynomialCoeffsExtTarget; use crate::merkle_proofs::{MerkleProof, MerkleProofTarget}; -use crate::polynomial::commitment::{ListPolynomialCommitment, OpeningProof}; +use crate::polynomial::commitment::{ListPolynomialCommitment, OpeningProof, OpeningProofTarget}; use crate::polynomial::polynomial::PolynomialCoeffs; use crate::target::Target; @@ -34,6 +36,7 @@ impl Hash { } /// Represents a ~256 bit hash output. +#[derive(Copy, Clone, Debug)] pub struct HashTarget { pub(crate) elements: [Target; 4], } @@ -64,39 +67,33 @@ pub struct Proof, const D: usize> { pub plonk_zs_root: Hash, /// Merkle root of LDEs of the quotient polynomial components. pub quotient_polys_root: Hash, - /// Purported values of each polynomial at the challenge point. pub openings: OpeningSet, - /// A FRI argument for each FRI query. pub opening_proof: OpeningProof, } -pub struct ProofTarget { - /// Merkle root of LDEs of wire values. +pub struct ProofTarget { pub wires_root: HashTarget, - /// Merkle root of LDEs of Z, in the context of Plonk's permutation argument. pub plonk_zs_root: HashTarget, - /// Merkle root of LDEs of the quotient polynomial components. pub quotient_polys_root: HashTarget, - - /// Purported values of each polynomial at each challenge point. - pub openings: Vec, - - /// A FRI argument for each FRI query. - pub fri_proofs: Vec, + pub openings: Vec>, + pub opening_proof: Vec>, } /// Evaluations and Merkle proof produced by the prover in a FRI query step. -// TODO: Implement FriQueryStepTarget pub struct FriQueryStep, const D: usize> { pub evals: Vec, pub merkle_proof: MerkleProof, } +pub struct FriQueryStepTarget { + pub evals: Vec>, + pub merkle_proof: MerkleProofTarget, +} + /// Evaluations and Merkle proofs of the original set of polynomials, /// before they are combined into a composition polynomial. -// TODO: Implement FriInitialTreeProofTarget pub struct FriInitialTreeProof { pub evals_proofs: Vec<(Vec, MerkleProof)>, } @@ -108,13 +105,28 @@ impl FriInitialTreeProof { } } +pub struct FriInitialTreeProofTarget { + pub evals_proofs: Vec<(Vec, MerkleProofTarget)>, +} + +impl FriInitialTreeProofTarget { + pub(crate) fn unsalted_evals(&self, i: usize, config: &FriConfig) -> &[Target] { + let evals = &self.evals_proofs[i].0; + &evals[..evals.len() - config.salt_size(i)] + } +} + /// Proof for a FRI query round. -// TODO: Implement FriQueryRoundTarget pub struct FriQueryRound, const D: usize> { pub initial_trees_proof: FriInitialTreeProof, pub steps: Vec>, } +pub struct FriQueryRoundTarget { + pub initial_trees_proof: FriInitialTreeProofTarget, + pub steps: Vec>, +} + pub struct FriProof, const D: usize> { /// A Merkle root for each reduced polynomial in the commit phase. pub commit_phase_merkle_roots: Vec>, @@ -126,18 +138,14 @@ pub struct FriProof, const D: usize> { pub pow_witness: F, } -/// Represents a single FRI query, i.e. a path through the reduction tree. -pub struct FriProofTarget { - /// A Merkle root for each reduced polynomial in the commit phase. +pub struct FriProofTarget { pub commit_phase_merkle_roots: Vec, - /// Merkle proofs for the original purported codewords, i.e. the subject of the LDT. - pub initial_merkle_proofs: Vec, - /// Merkle proofs for the reduced polynomials that were sent in the commit phase. - pub intermediate_merkle_proofs: Vec, - /// The final polynomial in coefficient form. - pub final_poly: Vec, + pub query_round_proofs: Vec>, + pub final_poly: PolynomialCoeffsExtTarget, + pub pow_witness: Target, } +#[derive(Clone, Debug)] /// The purported values of each polynomial at a single point. pub struct OpeningSet, const D: usize> { pub constants: Vec, @@ -176,10 +184,11 @@ impl, const D: usize> OpeningSet { } /// The purported values of each polynomial at a single point. -pub struct OpeningSetTarget { - pub constants: Vec, - pub plonk_sigmas: Vec, - pub wires: Vec, - pub plonk_zs: Vec, - pub quotient_polys: Vec, +pub struct OpeningSetTarget { + pub constants: Vec>, + pub plonk_sigmas: Vec>, + pub wires: Vec>, + pub plonk_zs: Vec>, + pub plonk_zs_right: Vec>, + pub quotient_polys: Vec>, } diff --git a/src/prover.rs b/src/prover.rs index 0731d2b4..116a6b24 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -115,7 +115,7 @@ pub(crate) fn prove, const D: usize>( let zeta = challenger.get_extension_challenge(); - let (opening_proof, mut openings) = timed!( + let (opening_proof, openings) = timed!( ListPolynomialCommitment::open_plonk( &[ &prover_data.constants_commitment, diff --git a/src/recursive_verifier.rs b/src/recursive_verifier.rs index a9d37553..c1005a67 100644 --- a/src/recursive_verifier.rs +++ b/src/recursive_verifier.rs @@ -13,7 +13,7 @@ pub fn add_recursive_verifier, const D: usize>( inner_config: CircuitConfig, inner_circuit: VerifierCircuitTarget, inner_gates: Vec>, - inner_proof: ProofTarget, + inner_proof: ProofTarget, ) { assert!(builder.config.num_wires >= MIN_WIRES); assert!(builder.config.num_wires >= MIN_ROUTED_WIRES); diff --git a/src/target.rs b/src/target.rs index b5736564..423865fa 100644 --- a/src/target.rs +++ b/src/target.rs @@ -1,5 +1,6 @@ use crate::circuit_data::CircuitConfig; use crate::wire::Wire; +use std::ops::Range; /// A location in the witness. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -21,4 +22,8 @@ impl Target { Target::VirtualAdviceTarget { .. } => false, } } + + pub fn wires_from_range(gate: usize, range: Range) -> Vec { + range.map(|i| Self::wire(gate, i)).collect() + } } diff --git a/src/util/mod.rs b/src/util/mod.rs index 7ab9bc5b..09bd4e72 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -7,7 +7,7 @@ pub(crate) fn bits_u64(n: u64) -> usize { (64 - n.leading_zeros()) as usize } -pub(crate) fn ceil_div_usize(a: usize, b: usize) -> usize { +pub(crate) const fn ceil_div_usize(a: usize, b: usize) -> usize { (a + b - 1) / b } diff --git a/src/verifier.rs b/src/verifier.rs index 396f23c0..a2750421 100644 --- a/src/verifier.rs +++ b/src/verifier.rs @@ -55,13 +55,13 @@ pub(crate) fn verify, const D: usize>( ); // Check each polynomial identity, of the form `vanishing(x) = Z_H(x) quotient(x)`, at zeta. - let quotient_polys_zeta = proof.openings.quotient_polys; + let quotient_polys_zeta = &proof.openings.quotient_polys; let z_h_zeta = eval_zero_poly(common_data.degree(), zeta); for i in 0..num_challenges { ensure!(vanishing_polys_zeta[i] == z_h_zeta * quotient_polys_zeta[i]); } - let evaluations = todo!(); + let evaluations = proof.openings.clone(); let merkle_roots = &[ verifier_data.constants_root, @@ -71,9 +71,13 @@ pub(crate) fn verify, const D: usize>( proof.quotient_polys_root, ]; - proof - .opening_proof - .verify(zeta, evaluations, merkle_roots, &mut challenger, fri_config)?; + proof.opening_proof.verify( + zeta, + &evaluations, + merkle_roots, + &mut challenger, + fri_config, + )?; Ok(()) } diff --git a/src/wire.rs b/src/wire.rs index 61b7f5be..02d43029 100644 --- a/src/wire.rs +++ b/src/wire.rs @@ -1,4 +1,5 @@ use crate::circuit_data::CircuitConfig; +use std::ops::Range; /// Represents a wire in the circuit. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] @@ -13,4 +14,8 @@ impl Wire { pub fn is_routable(&self, config: &CircuitConfig) -> bool { self.input < config.num_routed_wires } + + pub fn from_range(gate: usize, range: Range) -> Vec { + range.map(|i| Wire { gate, input: i }).collect() + } } diff --git a/src/witness.rs b/src/witness.rs index a0b4b2a4..e71b1cfb 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -1,9 +1,11 @@ use std::collections::HashMap; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::target::Target; use crate::wire::Wire; +use std::convert::TryInto; #[derive(Clone, Debug)] pub struct PartialWitness { @@ -39,6 +41,15 @@ impl PartialWitness { targets.iter().map(|&t| self.get_target(t)).collect() } + pub fn get_extension_target(&self, et: ExtensionTarget) -> F::Extension + where + F: Extendable, + { + F::Extension::from_basefield_array( + self.get_targets(&et.to_target_array()).try_into().unwrap(), + ) + } + pub fn try_get_target(&self, target: Target) -> Option { self.target_values.get(&target).cloned() } @@ -70,6 +81,19 @@ impl PartialWitness { } } + pub fn set_extension_target( + &mut self, + et: ExtensionTarget, + value: F::Extension, + ) where + F: Extendable, + { + let limbs = value.to_basefield_array(); + (0..D).for_each(|i| { + self.set_target(et.0[i], limbs[i]); + }); + } + pub fn set_wire(&mut self, wire: Wire, value: F) { self.set_target(Target::Wire(wire), value) }