diff --git a/src/bin/bench_recursion.rs b/src/bin/bench_recursion.rs index 19f7716c..9fab2456 100644 --- a/src/bin/bench_recursion.rs +++ b/src/bin/bench_recursion.rs @@ -8,7 +8,6 @@ use plonky2::fri::FriConfig; use plonky2::gates::constant::ConstantGate; use plonky2::gates::gmimc::GMiMCGate; use plonky2::hash::GMIMC_ROUNDS; -use plonky2::prover::PLONK_BLINDING; use plonky2::witness::PartialWitness; fn main() { @@ -41,7 +40,6 @@ fn bench_prove, const D: usize>() { rate_bits: 3, reduction_arity_bits: vec![1], num_query_rounds: 1, - blinding: PLONK_BLINDING.to_vec(), }, }; diff --git a/src/circuit_builder.rs b/src/circuit_builder.rs index 9be203bb..640e17a5 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 37925c0c..aa4cc242 100644 --- a/src/circuit_data.rs +++ b/src/circuit_data.rs @@ -39,7 +39,6 @@ impl Default for CircuitConfig { rate_bits: 1, reduction_arity_bits: vec![1], num_query_rounds: 1, - blinding: vec![true], }, } } @@ -49,6 +48,22 @@ 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, + }, + } + } } /// 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 3c6f0e47..95d2f6e0 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/field/lagrange.rs b/src/field/interpolation.rs similarity index 81% rename from src/field/lagrange.rs rename to src/field/interpolation.rs index 8911feb0..968eeca2 100644 --- a/src/field/lagrange.rs +++ b/src/field/interpolation.rs @@ -65,11 +65,23 @@ pub fn barycentric_weights(points: &[(F, F)]) -> Vec { ) } +/// Interpolate the linear polynomial passing through `points` on `x`. +pub fn interpolate2(points: [(F, F); 2], x: F) -> F { + // a0 -> a1 + // b0 -> b1 + // x -> a1 + (x-a0)*(b1-a1)/(b0-a0) + let (a0, a1) = points[0]; + let (b0, b1) = points[1]; + assert_ne!(a0, b0); + a1 + (x - a0) * (b1 - a1) / (b0 - a0) +} + #[cfg(test)] mod tests { + use super::*; use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; - use crate::field::lagrange::interpolant; use crate::polynomial::polynomial::PolynomialCoeffs; #[test] @@ -120,4 +132,18 @@ mod tests { fn eval_naive(coeffs: &PolynomialCoeffs, domain: &[F]) -> Vec<(F, F)> { domain.iter().map(|&x| (x, coeffs.eval(x))).collect() } + + #[test] + fn test_interpolate2() { + type F = QuarticCrandallField; + let points = [(F::rand(), F::rand()), (F::rand(), F::rand())]; + let x = F::rand(); + + let ev0 = interpolant(&points).eval(x); + let ev1 = interpolate(&points, x, &barycentric_weights(&points)); + let ev2 = interpolate2(points, x); + + assert_eq!(ev0, ev1); + assert_eq!(ev0, ev2); + } } diff --git a/src/field/mod.rs b/src/field/mod.rs index 179fb10d..15efe280 100644 --- a/src/field/mod.rs +++ b/src/field/mod.rs @@ -3,7 +3,7 @@ pub mod crandall_field; pub mod extension_field; pub mod fft; pub mod field; -pub(crate) mod lagrange; +pub(crate) mod interpolation; #[cfg(test)] mod field_testing; diff --git a/src/fri/mod.rs b/src/fri/mod.rs index c147e8c6..87fe3db5 100644 --- a/src/fri/mod.rs +++ b/src/fri/mod.rs @@ -1,6 +1,5 @@ -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, @@ -21,20 +20,6 @@ pub struct FriConfig { /// Number of query rounds to perform. pub num_query_rounds: usize, - - /// Vector of the same length as the number of initial Merkle trees. - /// `blinding[i]==true` iff the i-th tree is salted. - pub blinding: Vec, -} - -impl FriConfig { - pub(crate) fn salt_size(&self, i: usize) -> usize { - if self.blinding[i] { - SALT_SIZE - } else { - 0 - } - } } fn fri_delta(rate_log: usize, conjecture: bool) -> f64 { 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..7c04d4a2 --- /dev/null +++ b/src/fri/recursive_verifier.rs @@ -0,0 +1,359 @@ +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::plonk_common::PlonkPolynomials; +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 = [ + PlonkPolynomials::CONSTANTS, + PlonkPolynomials::SIGMAS, + PlonkPolynomials::QUOTIENT, + ] + .iter() + .flat_map(|&p| proof.unsalted_evals(p)) + .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(PlonkPolynomials::ZS) + .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(PlonkPolynomials::WIRES) + .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..d88e8e0b 100644 --- a/src/fri/verifier.rs +++ b/src/fri/verifier.rs @@ -1,14 +1,15 @@ 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::field::interpolation::{barycentric_weights, interpolate, interpolate2}; use crate::fri::FriConfig; use crate::hash::hash_n_to_1; use crate::merkle_proofs::verify_merkle_proof; use crate::plonk_challenger::Challenger; -use crate::plonk_common::reduce_with_iter; +use crate::plonk_common::PlonkPolynomials; use crate::proof::{FriInitialTreeProof, FriProof, FriQueryRound, Hash, OpeningSet}; +use crate::util::scaling::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 @@ -151,59 +152,77 @@ fn fri_combine_initial, const D: usize>( assert!(D > 1, "Not implemented for D=1."); let degree_log = proof.evals_proofs[0].1.siblings.len() - config.rate_bits; let subgroup_x = F::Extension::from_basefield(subgroup_x); - let mut alpha_powers = alpha.powers(); + let mut alpha = ReducingFactor::new(alpha); let mut sum = F::Extension::ZERO; // We will add three terms to `sum`: // - one for various polynomials which are opened at a single point `x` // - one for Zs, which are opened at `x` and `g x` - // - one for wire polynomials, which are opened at `x` and its conjugate + // - one for wire polynomials, which are opened at `x` and `x.frobenius()` - let single_evals = [0, 1, 4] - .iter() - .flat_map(|&i| proof.unsalted_evals(i, config)) - .map(|&e| F::Extension::from_basefield(e)); + // Polynomials opened at `x`, i.e., the constants, sigmas and quotient polynomials. + let single_evals = [ + PlonkPolynomials::CONSTANTS, + PlonkPolynomials::SIGMAS, + PlonkPolynomials::QUOTIENT, + ] + .iter() + .flat_map(|&p| proof.unsalted_evals(p)) + .map(|&e| F::Extension::from_basefield(e)); let single_openings = os .constants .iter() .chain(&os.plonk_s_sigmas) .chain(&os.quotient_polys); - let single_diffs = single_evals.zip(single_openings).map(|(e, &o)| e - o); - let single_numerator = reduce_with_iter(single_diffs, &mut alpha_powers); + let single_diffs = single_evals + .into_iter() + .zip(single_openings) + .map(|(e, &o)| e - o) + .collect::>(); + let single_numerator = alpha.reduce(single_diffs.iter()); let single_denominator = subgroup_x - zeta; sum += single_numerator / single_denominator; + alpha.reset(); + // Polynomials opened at `x` and `g x`, i.e., the Zs polynomials. let zs_evals = proof - .unsalted_evals(3, config) + .unsalted_evals(PlonkPolynomials::ZS) .iter() .map(|&e| F::Extension::from_basefield(e)); - let zs_composition_eval = reduce_with_iter(zs_evals, alpha_powers.clone()); + let zs_composition_eval = alpha.clone().reduce(zs_evals); let zeta_right = F::Extension::primitive_root_of_unity(degree_log) * zeta; - let zs_interpol = interpolant(&[ - (zeta, reduce_with_iter(&os.plonk_zs, alpha_powers.clone())), - ( - zeta_right, - reduce_with_iter(&os.plonk_zs_right, &mut alpha_powers), - ), - ]); - let zs_numerator = zs_composition_eval - zs_interpol.eval(subgroup_x); + let zs_interpol = interpolate2( + [ + (zeta, alpha.clone().reduce(os.plonk_zs.iter())), + (zeta_right, alpha.reduce(os.plonk_zs_right.iter())), + ], + subgroup_x, + ); + let zs_numerator = zs_composition_eval - zs_interpol; let zs_denominator = (subgroup_x - zeta) * (subgroup_x - zeta_right); + sum = alpha.shift(sum); 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) + .unsalted_evals(PlonkPolynomials::WIRES) .iter() .map(|&e| F::Extension::from_basefield(e)); - let wire_composition_eval = reduce_with_iter(wire_evals, alpha_powers.clone()); + let wire_composition_eval = alpha.clone().reduce(wire_evals); 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 mut alpha_frob = alpha.repeated_frobenius(D - 1); + let wire_eval = alpha.reduce(os.wires.iter()); + // 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 wire_eval_frob = alpha_frob.reduce(os.wires.iter()).frobenius(); + let wire_interpol = interpolate2([(zeta, wire_eval), (zeta_frob, wire_eval_frob)], subgroup_x); + let wire_numerator = wire_composition_eval - wire_interpol; + let wire_denominator = (subgroup_x - zeta) * (subgroup_x - zeta_frob); + sum = alpha.shift(sum); + sum += wire_numerator / wire_denominator; sum } @@ -276,7 +295,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..520f8cd9 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,106 @@ 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::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..40b91c1e --- /dev/null +++ b/src/gadgets/interpolation.rs @@ -0,0 +1,142 @@ +use std::marker::PhantomData; + +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; + +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 std::convert::TryInto; + + 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::interpolation::{interpolant, interpolate}; + use crate::witness::PartialWitness; + + #[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..ccf8d57d 100644 --- a/src/gates/interpolation.rs +++ b/src/gates/interpolation.rs @@ -6,7 +6,7 @@ use crate::circuit_builder::CircuitBuilder; use crate::field::extension_field::algebra::PolynomialCoeffsAlgebra; use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; -use crate::field::lagrange::interpolant; +use crate::field::interpolation::interpolant; use crate::gadgets::polynomial::PolynomialCoeffsExtAlgebraTarget; use crate::gates::gate::{Gate, GateRef}; use crate::generator::{SimpleGenerator, WitnessGenerator}; @@ -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 290b5231..d1141672 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..c9f11b74 100644 --- a/src/plonk_common.rs +++ b/src/plonk_common.rs @@ -6,10 +6,62 @@ use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::Extendable; use crate::field::field::Field; use crate::gates::gate::GateRef; +use crate::polynomial::commitment::SALT_SIZE; use crate::polynomial::polynomial::PolynomialCoeffs; use crate::target::Target; use crate::vars::{EvaluationTargets, EvaluationVars, EvaluationVarsBase}; +/// Holds the Merkle tree index and blinding flag of a set of polynomials used in FRI. +#[derive(Debug, Copy, Clone)] +pub struct PolynomialsIndexBlinding { + pub(crate) index: usize, + pub(crate) blinding: bool, +} +impl PolynomialsIndexBlinding { + pub fn salt_size(&self) -> usize { + if self.blinding { + SALT_SIZE + } else { + 0 + } + } +} +/// Holds the indices and blinding flags of the Plonk polynomials. +pub struct PlonkPolynomials; +impl PlonkPolynomials { + pub const CONSTANTS: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + index: 0, + blinding: false, + }; + pub const SIGMAS: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + index: 1, + blinding: false, + }; + pub const WIRES: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + index: 2, + blinding: true, + }; + pub const ZS: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + index: 3, + blinding: true, + }; + pub const QUOTIENT: PolynomialsIndexBlinding = PolynomialsIndexBlinding { + index: 4, + blinding: true, + }; + + pub fn polynomials(i: usize) -> PolynomialsIndexBlinding { + match i { + 0 => Self::CONSTANTS, + 1 => Self::SIGMAS, + 2 => Self::WIRES, + 3 => Self::ZS, + 4 => Self::QUOTIENT, + _ => panic!("There are only 5 sets of polynomials in Plonk."), + } + } +} + /// 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 /// argument. All such terms should vanish on `H`. @@ -206,10 +258,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..4b2bf872 100644 --- a/src/polynomial/commitment.rs +++ b/src/polynomial/commitment.rs @@ -1,17 +1,17 @@ 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}; use crate::merkle_tree::MerkleTree; use crate::plonk_challenger::Challenger; -use crate::plonk_common::{reduce_polys_with_iter, reduce_with_iter}; +use crate::plonk_common::PlonkPolynomials; use crate::polynomial::polynomial::PolynomialCoeffs; -use crate::proof::{FriProof, Hash, OpeningSet}; +use crate::proof::{FriProof, FriProofTarget, Hash, OpeningSet}; use crate::timed; +use crate::util::scaling::ReducingFactor; use crate::util::{log2_strict, reverse_index_bits_in_place, transpose}; pub const SALT_SIZE: usize = 2; @@ -110,60 +110,50 @@ impl ListPolynomialCommitment { challenger.observe_opening_set(&os); let alpha = challenger.get_extension_challenge(); - let mut alpha_powers = alpha.powers(); + let mut alpha = ReducingFactor::new(alpha); // Final low-degree polynomial that goes into FRI. let mut final_poly = PolynomialCoeffs::empty(); // Polynomials opened at a single point. - let single_polys = [0, 1, 4] - .iter() - .flat_map(|&i| &commitments[i].polynomials) - .map(|p| p.to_extension()); - let single_os = [&os.constants, &os.plonk_s_sigmas, &os.quotient_polys]; - let single_evals = single_os.iter().flat_map(|v| v.iter()); - let single_composition_poly = reduce_polys_with_iter(single_polys, alpha_powers.clone()); - let single_composition_eval = reduce_with_iter(single_evals, &mut alpha_powers); + let single_polys = [ + PlonkPolynomials::CONSTANTS, + PlonkPolynomials::SIGMAS, + PlonkPolynomials::QUOTIENT, + ] + .iter() + .flat_map(|&p| &commitments[p.index].polynomials) + .map(|p| p.to_extension()); + let single_composition_poly = alpha.reduce_polys(single_polys); - let single_quotient = Self::compute_quotient( - &[zeta], - &[single_composition_eval], - &single_composition_poly, - ); - final_poly = &final_poly + &single_quotient; + let single_quotient = Self::compute_quotient([zeta], single_composition_poly); + final_poly += single_quotient; + alpha.reset(); // Zs polynomials are opened at `zeta` and `g*zeta`. - let zs_polys = commitments[3].polynomials.iter().map(|p| p.to_extension()); - let zs_composition_poly = reduce_polys_with_iter(zs_polys, alpha_powers.clone()); - let zs_composition_evals = [ - reduce_with_iter(&os.plonk_zs, alpha_powers.clone()), - reduce_with_iter(&os.plonk_zs_right, &mut alpha_powers), - ]; + let zs_polys = commitments[PlonkPolynomials::ZS.index] + .polynomials + .iter() + .map(|p| p.to_extension()); + let zs_composition_poly = alpha.reduce_polys(zs_polys); - let zs_quotient = Self::compute_quotient( - &[zeta, g * zeta], - &zs_composition_evals, - &zs_composition_poly, - ); - final_poly = &final_poly + &zs_quotient; + let zs_quotient = Self::compute_quotient([zeta, g * zeta], zs_composition_poly); + alpha.shift_poly(&mut final_poly); + final_poly += zs_quotient; // When working in an extension field, need to check that wires are in the base field. // Check this by opening the wires polynomials at `zeta` and `zeta.frobenius()` and using the fact that // a polynomial `f` is over the base field iff `f(z).frobenius()=f(z.frobenius())` with high probability. - let wire_polys = commitments[2].polynomials.iter().map(|p| p.to_extension()); - let wire_composition_poly = reduce_polys_with_iter(wire_polys, alpha_powers.clone()); - let wire_evals_frob = os.wires.iter().map(|e| e.frobenius()).collect::>(); - let wire_composition_evals = [ - reduce_with_iter(&os.wires, alpha_powers.clone()), - reduce_with_iter(&wire_evals_frob, alpha_powers), - ]; + let wire_polys = commitments[PlonkPolynomials::WIRES.index] + .polynomials + .iter() + .map(|p| p.to_extension()); + let wire_composition_poly = alpha.reduce_polys(wire_polys); - let wires_quotient = Self::compute_quotient( - &[zeta, zeta.frobenius()], - &wire_composition_evals, - &wire_composition_poly, - ); - final_poly = &final_poly + &wires_quotient; + let wires_quotient = + Self::compute_quotient([zeta, zeta.frobenius()], wire_composition_poly); + alpha.shift_poly(&mut final_poly); + final_poly += wires_quotient; let lde_final_poly = final_poly.lde(config.rate_bits); let lde_final_values = lde_final_poly @@ -194,28 +184,27 @@ impl ListPolynomialCommitment { /// Given `points=(x_i)`, `evals=(y_i)` and `poly=P` with `P(x_i)=y_i`, computes the polynomial /// `Q=(P-I)/Z` where `I` interpolates `(x_i, y_i)` and `Z` is the vanishing polynomial on `(x_i)`. - fn compute_quotient( - points: &[F::Extension], - evals: &[F::Extension], - poly: &PolynomialCoeffs, + fn compute_quotient( + points: [F::Extension; N], + poly: PolynomialCoeffs, ) -> PolynomialCoeffs where F: Extendable, { - let pairs = points - .iter() - .zip(evals) - .map(|(&x, &e)| (x, e)) - .collect::>(); - debug_assert!(pairs.iter().all(|&(x, e)| poly.eval(x) == e)); - - let interpolant = interpolant(&pairs); - let denominator = points.iter().fold(PolynomialCoeffs::one(), |acc, &x| { - &acc * &PolynomialCoeffs::new(vec![-x, F::Extension::ONE]) - }); - let numerator = poly - &interpolant; - let (quotient, rem) = numerator.div_rem(&denominator); - debug_assert!(rem.is_zero()); + let quotient = if N == 1 { + poly.divide_by_linear(points[0]).0 + } else if N == 2 { + // The denominator is `(X - p0)(X - p1) = p0 p1 - (p0 + p1) X + X^2`. + let denominator = vec![ + points[0] * points[1], + -points[0] - points[1], + F::Extension::ONE, + ] + .into(); + poly.div_rem_long_division(&denominator).0 // Could also use `divide_by_linear` twice. + } else { + unreachable!("This shouldn't happen. Plonk should open polynomials at 1 or 2 points.") + }; quotient.padded(quotient.degree_plus_one().next_power_of_two()) } @@ -253,12 +242,16 @@ impl, const D: usize> OpeningProof { } } +pub struct OpeningProofTarget { + fri_proof: FriProofTarget, +} + #[cfg(test)] mod tests { use anyhow::Result; - use rand::Rng; use super::*; + use crate::plonk_common::PlonkPolynomials; fn gen_random_test_case, const D: usize>( k: usize, @@ -284,17 +277,6 @@ mod tests { point } - fn gen_random_blindings() -> Vec { - let mut rng = rand::thread_rng(); - vec![ - rng.gen_bool(0.5), - rng.gen_bool(0.5), - rng.gen_bool(0.5), - rng.gen_bool(0.5), - rng.gen_bool(0.5), - ] - } - fn check_batch_polynomial_commitment, const D: usize>() -> Result<()> { let ks = [1, 2, 3, 5, 8]; let degree_log = 11; @@ -303,7 +285,6 @@ mod tests { rate_bits: 2, reduction_arity_bits: vec![2, 3, 1, 2], num_query_rounds: 3, - blinding: gen_random_blindings(), }; let lpcs = (0..5) @@ -311,7 +292,7 @@ mod tests { ListPolynomialCommitment::::new( gen_random_test_case(ks[i], degree_log), fri_config.rate_bits, - fri_config.blinding[i], + PlonkPolynomials::polynomials(i).blinding, ) }) .collect::>(); diff --git a/src/polynomial/division.rs b/src/polynomial/division.rs index b74fd00f..50e1f8a6 100644 --- a/src/polynomial/division.rs +++ b/src/polynomial/division.rs @@ -26,7 +26,7 @@ impl PolynomialCoeffs { .to_vec() .into(); let mut q = rev_q.rev(); - let mut qb = &q * b; + let qb = &q * b; let mut r = self - &qb; q.trim(); r.trim(); @@ -59,8 +59,7 @@ impl PolynomialCoeffs { quotient.coeffs[cur_q_degree] = cur_q_coeff; for (i, &div_coeff) in b.coeffs.iter().enumerate() { - remainder.coeffs[cur_q_degree + i] = - remainder.coeffs[cur_q_degree + i] - (cur_q_coeff * div_coeff); + remainder.coeffs[cur_q_degree + i] -= cur_q_coeff * div_coeff; } remainder.trim(); } @@ -97,7 +96,7 @@ impl PolynomialCoeffs { let denominators = (0..a_eval.len()) .map(|i| { if i != 0 { - root_pow = root_pow * root_n; + root_pow *= root_n; } denominator_g * root_pow - F::ONE }) @@ -125,8 +124,25 @@ impl PolynomialCoeffs { p } + /// Let `self=p(X)`, this returns `(p(X)-p(z))/(X-z)` and `p(z)`. + /// See https://en.wikipedia.org/wiki/Horner%27s_method + pub(crate) fn divide_by_linear(&self, z: F) -> (PolynomialCoeffs, F) { + let mut bs = self + .coeffs + .iter() + .rev() + .scan(F::ZERO, |acc, &c| { + *acc = *acc * z + c; + Some(*acc) + }) + .collect::>(); + let ev = bs.pop().unwrap_or(F::ZERO); + bs.reverse(); + (Self { coeffs: bs }, ev) + } + /// Computes the inverse of `self` modulo `x^n`. - pub(crate) fn inv_mod_xn(&self, n: usize) -> Self { + pub fn inv_mod_xn(&self, n: usize) -> Self { assert!(self.coeffs[0].is_nonzero(), "Inverse doesn't exist."); let h = if self.len() < n { @@ -166,7 +182,10 @@ impl PolynomialCoeffs { #[cfg(test)] mod tests { + use std::time::Instant; + use crate::field::crandall_field::CrandallField; + use crate::field::extension_field::quartic::QuarticCrandallField; use crate::field::field::Field; use crate::polynomial::polynomial::PolynomialCoeffs; @@ -199,4 +218,49 @@ mod tests { let computed_q = a.divide_by_z_h(4); assert_eq!(computed_q, q); } + + #[test] + #[ignore] + fn test_division_by_linear() { + type F = QuarticCrandallField; + let n = 1_000_000; + let poly = PolynomialCoeffs::new(F::rand_vec(n)); + let z = F::rand(); + let ev = poly.eval(z); + + let timer = Instant::now(); + let (quotient, ev2) = poly.div_rem(&PolynomialCoeffs::new(vec![-z, F::ONE])); + println!("{:.3}s for usual", timer.elapsed().as_secs_f32()); + assert_eq!(ev2.trimmed().coeffs, vec![ev]); + + let timer = Instant::now(); + let (quotient, ev3) = poly.div_rem_long_division(&PolynomialCoeffs::new(vec![-z, F::ONE])); + println!("{:.3}s for long division", timer.elapsed().as_secs_f32()); + assert_eq!(ev3.trimmed().coeffs, vec![ev]); + + let timer = Instant::now(); + let horn = poly.divide_by_linear(z); + println!("{:.3}s for Horner", timer.elapsed().as_secs_f32()); + assert_eq!((quotient, ev), horn); + } + + #[test] + #[ignore] + fn test_division_by_quadratic() { + type F = QuarticCrandallField; + let n = 1_000_000; + let poly = PolynomialCoeffs::new(F::rand_vec(n)); + let quad = PolynomialCoeffs::new(F::rand_vec(2)); + + let timer = Instant::now(); + let (quotient0, rem0) = poly.div_rem(&quad); + println!("{:.3}s for usual", timer.elapsed().as_secs_f32()); + + let timer = Instant::now(); + let (quotient1, rem1) = poly.div_rem_long_division(&quad); + println!("{:.3}s for long division", timer.elapsed().as_secs_f32()); + + assert_eq!(quotient0.trimmed(), quotient1.trimmed()); + assert_eq!(rem0.trimmed(), rem1.trimmed()); + } } diff --git a/src/polynomial/polynomial.rs b/src/polynomial/polynomial.rs index 9f605051..02f66684 100644 --- a/src/polynomial/polynomial.rs +++ b/src/polynomial/polynomial.rs @@ -1,6 +1,6 @@ use std::cmp::max; use std::iter::Sum; -use std::ops::{Add, Mul, Sub}; +use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign}; use crate::field::extension_field::Extendable; use crate::field::fft::{fft, ifft}; @@ -243,6 +243,46 @@ impl Sub for &PolynomialCoeffs { } } +impl AddAssign for PolynomialCoeffs { + fn add_assign(&mut self, rhs: Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, r) in self.coeffs.iter_mut().zip(rhs.coeffs) { + *l += r; + } + } +} + +impl AddAssign<&Self> for PolynomialCoeffs { + fn add_assign(&mut self, rhs: &Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, &r) in self.coeffs.iter_mut().zip(&rhs.coeffs) { + *l += r; + } + } +} + +impl SubAssign for PolynomialCoeffs { + fn sub_assign(&mut self, rhs: Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, r) in self.coeffs.iter_mut().zip(rhs.coeffs) { + *l -= r; + } + } +} + +impl SubAssign<&Self> for PolynomialCoeffs { + fn sub_assign(&mut self, rhs: &Self) { + let len = max(self.len(), rhs.len()); + self.coeffs.resize(len, F::ZERO); + for (l, &r) in self.coeffs.iter_mut().zip(&rhs.coeffs) { + *l -= r; + } + } +} + impl Mul for &PolynomialCoeffs { type Output = PolynomialCoeffs; @@ -252,6 +292,12 @@ impl Mul for &PolynomialCoeffs { } } +impl MulAssign for PolynomialCoeffs { + fn mul_assign(&mut self, rhs: F) { + self.coeffs.iter_mut().for_each(|x| *x *= rhs); + } +} + impl Mul for &PolynomialCoeffs { type Output = PolynomialCoeffs; diff --git a/src/proof.rs b/src/proof.rs index b1772c45..7536f3ee 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::plonk_common::PolynomialsIndexBlinding; +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,57 +67,66 @@ 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)>, } impl FriInitialTreeProof { - pub(crate) fn unsalted_evals(&self, i: usize, config: &FriConfig) -> &[F] { - let evals = &self.evals_proofs[i].0; - &evals[..evals.len() - config.salt_size(i)] + pub(crate) fn unsalted_evals(&self, polynomials: PolynomialsIndexBlinding) -> &[F] { + let evals = &self.evals_proofs[polynomials.index].0; + &evals[..evals.len() - polynomials.salt_size()] + } +} + +pub struct FriInitialTreeProofTarget { + pub evals_proofs: Vec<(Vec, MerkleProofTarget)>, +} + +impl FriInitialTreeProofTarget { + pub(crate) fn unsalted_evals(&self, polynomials: PolynomialsIndexBlinding) -> &[Target] { + let evals = &self.evals_proofs[polynomials.index].0; + &evals[..evals.len() - polynomials.salt_size()] } } /// 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 0215aa13..0d3f1be1 100644 --- a/src/prover.rs +++ b/src/prover.rs @@ -19,9 +19,6 @@ use crate::vars::EvaluationVarsBase; use crate::wire::Wire; use crate::witness::PartialWitness; -/// Corresponds to constants - sigmas - wires - zs - quotient — polynomial commitments. -pub const PLONK_BLINDING: [bool; 5] = [false, false, true, true, true]; - pub(crate) fn prove, const D: usize>( prover_data: &ProverOnlyCircuitData, common_data: &CommonCircuitData, @@ -122,7 +119,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..f901b0af 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1,3 +1,4 @@ +pub mod scaling; pub(crate) mod timing; use crate::field::field::Field; @@ -7,7 +8,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/util/scaling.rs b/src/util/scaling.rs new file mode 100644 index 00000000..cea86195 --- /dev/null +++ b/src/util/scaling.rs @@ -0,0 +1,75 @@ +use std::borrow::Borrow; + +use crate::field::extension_field::Frobenius; +use crate::field::field::Field; +use crate::polynomial::polynomial::PolynomialCoeffs; + +/// 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` +/// The most efficient way to do this is to compute both quotient separately using Horner's method, +/// scale the second one by `a^(r-1-k)`, and add them up. +/// This struct abstract away these operations by implementing Horner's method and keeping track +/// of the number of multiplications by `a` to compute the scaling factor. +/// See https://github.com/mir-protocol/plonky2/pull/69 for more details and discussions. +#[derive(Debug, Copy, Clone)] +pub struct ReducingFactor { + base: F, + count: u64, +} + +impl ReducingFactor { + pub fn new(base: F) -> Self { + Self { base, count: 0 } + } + + fn mul(&mut self, x: F) -> F { + self.count += 1; + self.base * x + } + + fn mul_poly(&mut self, p: &mut PolynomialCoeffs) { + self.count += 1; + *p *= self.base; + } + + pub fn reduce(&mut self, iter: impl DoubleEndedIterator>) -> F { + iter.rev() + .fold(F::ZERO, |acc, x| self.mul(acc) + *x.borrow()) + } + + pub fn reduce_polys( + &mut self, + polys: impl DoubleEndedIterator>>, + ) -> PolynomialCoeffs { + polys.rev().fold(PolynomialCoeffs::empty(), |mut acc, x| { + self.mul_poly(&mut acc); + acc += x.borrow(); + acc + }) + } + + pub fn shift(&mut self, x: F) -> F { + let tmp = self.base.exp(self.count) * x; + self.count = 0; + tmp + } + + pub fn shift_poly(&mut self, p: &mut PolynomialCoeffs) { + *p *= self.base.exp(self.count); + self.count = 0; + } + + pub fn reset(&mut self) { + self.count = 0; + } + + pub fn repeated_frobenius(&self, count: usize) -> Self + where + F: Frobenius, + { + Self { + base: self.base.repeated_frobenius(count), + count: self.count, + } + } +} 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 ff6a8a50..f55d4297 100644 --- a/src/witness.rs +++ b/src/witness.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use anyhow::{ensure, Result}; +use crate::field::extension_field::target::ExtensionTarget; use crate::field::extension_field::{Extendable, FieldExtension}; use crate::field::field::Field; use crate::gates::gate::GateInstance; @@ -42,6 +43,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() } @@ -73,6 +83,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) }