From 00877358cbedd2d9b567764329dce33a44cfe1fb Mon Sep 17 00:00:00 2001 From: Miguel Ambrona Date: Fri, 26 Apr 2024 17:39:44 +0200 Subject: [PATCH] Reduce size of proving keys and verifying keys (#6) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * plonk/keygen: add function to compute l polys * plonk/permutation/keygen: add function to compute polys and cosets * plonk: improve size of serialized prover keys Co-authored-by: Iñigo Querejeta Azurmendi <31273774+iquerejeta@users.noreply.github.com> * plonk/keygen: disable complex selectors --------- Co-authored-by: Iñigo Querejeta Azurmendi <31273774+iquerejeta@users.noreply.github.com> --- halo2_proofs/src/plonk.rs | 27 +++++----- halo2_proofs/src/plonk/keygen.rs | 57 +++++++++++++------- halo2_proofs/src/plonk/permutation.rs | 18 ++++--- halo2_proofs/src/plonk/permutation/keygen.rs | 57 ++++++++++++-------- 4 files changed, 96 insertions(+), 63 deletions(-) diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index 875f09436..678500af0 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -346,13 +346,9 @@ where where C: SerdeCurveAffine, { - let scalar_len = C::Scalar::default().to_repr().as_ref().len(); self.vk.bytes_length(format) + 12 - + scalar_len * (self.l0.len() + self.l_last.len() + self.l_active_row.len()) + polynomial_slice_byte_length(&self.fixed_values) - + polynomial_slice_byte_length(&self.fixed_polys) - + polynomial_slice_byte_length(&self.fixed_cosets) + self.permutation.bytes_length() } } @@ -373,12 +369,7 @@ where /// Does so by first writing the verifying key and then serializing the rest of the data (in the form of field polynomials) pub fn write(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> { self.vk.write(writer, format)?; - self.l0.write(writer, format)?; - self.l_last.write(writer, format)?; - self.l_active_row.write(writer, format)?; write_polynomial_slice(&self.fixed_values, writer, format)?; - write_polynomial_slice(&self.fixed_polys, writer, format)?; - write_polynomial_slice(&self.fixed_cosets, writer, format)?; self.permutation.write(writer, format)?; Ok(()) } @@ -405,13 +396,19 @@ where #[cfg(feature = "circuit-params")] params, )?; - let l0 = Polynomial::read(reader, format)?; - let l_last = Polynomial::read(reader, format)?; - let l_active_row = Polynomial::read(reader, format)?; + let [l0, l_last, l_active_row] = compute_lagrange_polys(&vk, &vk.cs); let fixed_values = read_polynomial_vec(reader, format)?; - let fixed_polys = read_polynomial_vec(reader, format)?; - let fixed_cosets = read_polynomial_vec(reader, format)?; - let permutation = permutation::ProvingKey::read(reader, format)?; + let fixed_polys: Vec<_> = fixed_values + .iter() + .map(|poly| vk.domain.lagrange_to_coeff(poly.clone())) + .collect(); + + let fixed_cosets = fixed_polys + .iter() + .map(|poly| vk.domain.coeff_to_extended(poly.clone())) + .collect(); + let permutation = + permutation::ProvingKey::read(reader, format, &vk.domain, &vk.cs.permutation)?; let ev = Evaluator::new(vk.cs()); Ok(Self { vk, diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index fc70574b6..9e18e0784 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -20,7 +20,7 @@ use crate::{ poly::{ batch_invert_assigned, commitment::{Blind, Params, MSM}, - EvaluationDomain, + EvaluationDomain, ExtendedLagrangeCoeff, }, }; @@ -207,7 +207,16 @@ where ConcreteCircuit: Circuit, C::Scalar: FromUniformBytes<64>, { - keygen_vk_custom(params, circuit, true) + // We disable "complex selectors" for now, because they are not properly + // serialized. At the moment, they are stored as boolean vectors containing + // their value on every single row. This makes the verification key size + // linear when they are enabled. + // Furthermore, having complex selectors makes the cs structure variable + // depending on the selector instantiation. This is undesirable. + // In the future, if we want to enjoy the benefits of complex selectors + // (about a 10-20% improvement in proof size and verifying key payload size) + // we need to improve their serialization. + keygen_vk_custom(params, circuit, false) } /// Generate a `VerifyingKey` from an instance of `Circuit`. @@ -350,6 +359,31 @@ where .permutation .build_pk(params, &vk.domain, &cs.permutation); + let [l0, l_last, l_active_row] = compute_lagrange_polys(&vk, &cs); + + // Compute the optimized evaluation data structure + let ev = Evaluator::new(&vk.cs); + + Ok(ProvingKey { + vk, + l0, + l_last, + l_active_row, + fixed_values: fixed, + fixed_polys, + fixed_cosets, + permutation: permutation_pk, + ev, + }) +} + +pub(crate) fn compute_lagrange_polys( + vk: &VerifyingKey, + cs: &ConstraintSystem, +) -> [Polynomial; 3] +where + C: CurveAffine, +{ // Compute l_0(X) // TODO: this can be done more efficiently let mut l0 = vk.domain.empty_lagrange(); @@ -369,7 +403,8 @@ where // Compute l_last(X) which evaluates to 1 on the first inactive row (just // before the blinding factors) and 0 otherwise over the domain let mut l_last = vk.domain.empty_lagrange(); - l_last[params.n() as usize - cs.blinding_factors() - 1] = C::Scalar::ONE; + let n = l_last.len(); + l_last[n - cs.blinding_factors() - 1] = C::Scalar::ONE; let l_last = vk.domain.lagrange_to_coeff(l_last); let l_last = vk.domain.coeff_to_extended(l_last); @@ -382,19 +417,5 @@ where *value = one - (l_last[idx] + l_blind[idx]); } }); - - // Compute the optimized evaluation data structure - let ev = Evaluator::new(&vk.cs); - - Ok(ProvingKey { - vk, - l0, - l_last, - l_active_row, - fixed_values: fixed, - fixed_polys, - fixed_cosets, - permutation: permutation_pk, - ev, - }) + [l0, l_last, l_active_row] } diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 29a4a323a..a6789b752 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -1,5 +1,7 @@ //! Implementation of permutation argument. +use self::keygen::compute_polys_and_cosets; + use super::circuit::{Any, Column}; use crate::{ arithmetic::CurveAffine, @@ -7,7 +9,7 @@ use crate::{ polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, SerdeCurveAffine, SerdePrimeField, }, - poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, + poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, SerdeFormat, }; use ff::PrimeField; @@ -139,10 +141,14 @@ where C::Scalar: SerdePrimeField, { /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. - pub(super) fn read(reader: &mut R, format: SerdeFormat) -> io::Result { + pub(super) fn read( + reader: &mut R, + format: SerdeFormat, + domain: &EvaluationDomain, + p: &Argument, + ) -> io::Result { let permutations = read_polynomial_vec(reader, format)?; - let polys = read_polynomial_vec(reader, format)?; - let cosets = read_polynomial_vec(reader, format)?; + let (polys, cosets) = compute_polys_and_cosets::(domain, p, &permutations); Ok(ProvingKey { permutations, polys, @@ -157,8 +163,6 @@ where format: SerdeFormat, ) -> io::Result<()> { write_polynomial_slice(&self.permutations, writer, format)?; - write_polynomial_slice(&self.polys, writer, format)?; - write_polynomial_slice(&self.cosets, writer, format)?; Ok(()) } } @@ -167,7 +171,5 @@ impl ProvingKey { /// Gets the total number of bytes in the serialization of `self` pub(super) fn bytes_length(&self) -> usize { polynomial_slice_byte_length(&self.permutations) - + polynomial_slice_byte_length(&self.polys) - + polynomial_slice_byte_length(&self.cosets) } } diff --git a/halo2_proofs/src/plonk/permutation/keygen.rs b/halo2_proofs/src/plonk/permutation/keygen.rs index 33b81332a..e02cd9b29 100644 --- a/halo2_proofs/src/plonk/permutation/keygen.rs +++ b/halo2_proofs/src/plonk/permutation/keygen.rs @@ -7,7 +7,7 @@ use crate::{ plonk::{Any, Column, Error}, poly::{ commitment::{Blind, CommitmentScheme, Params}, - EvaluationDomain, + Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial, }, }; @@ -211,27 +211,7 @@ impl Assembly { }); } - let mut polys = vec![domain.empty_coeff(); p.columns.len()]; - { - parallelize(&mut polys, |o, start| { - for (x, poly) in o.iter_mut().enumerate() { - let i = start + x; - let permutation_poly = permutations[i].clone(); - *poly = domain.lagrange_to_coeff(permutation_poly); - } - }); - } - - let mut cosets = vec![domain.empty_extended(); p.columns.len()]; - { - parallelize(&mut cosets, |o, start| { - for (x, coset) in o.iter_mut().enumerate() { - let i = start + x; - let poly = polys[i].clone(); - *coset = domain.coeff_to_extended(poly); - } - }); - } + let (polys, cosets) = compute_polys_and_cosets::(domain, p, &permutations); ProvingKey { permutations, @@ -250,3 +230,36 @@ impl Assembly { &self.mapping } } + +#[allow(clippy::type_complexity)] +pub(crate) fn compute_polys_and_cosets( + domain: &EvaluationDomain, + p: &Argument, + permutations: &[Polynomial], +) -> ( + Vec>, + Vec>, +) { + let mut polys = vec![domain.empty_coeff(); p.columns.len()]; + { + parallelize(&mut polys, |o, start| { + for (x, poly) in o.iter_mut().enumerate() { + let i = start + x; + let permutation_poly = permutations[i].clone(); + *poly = domain.lagrange_to_coeff(permutation_poly); + } + }); + } + + let mut cosets = vec![domain.empty_extended(); p.columns.len()]; + { + parallelize(&mut cosets, |o, start| { + for (x, coset) in o.iter_mut().enumerate() { + let i = start + x; + let poly = polys[i].clone(); + *coset = domain.coeff_to_extended(poly); + } + }); + } + (polys, cosets) +}