diff --git a/halo2_proofs/examples/serialization.rs b/halo2_proofs/examples/serialization.rs new file mode 100644 index 0000000000..fbd19a89b4 --- /dev/null +++ b/halo2_proofs/examples/serialization.rs @@ -0,0 +1,182 @@ +use std::{ + fs::File, + io::{BufReader, BufWriter, Write}, +}; + +use ff::Field; +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{ + create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, + ConstraintSystem, Error, Fixed, Instance, ProvingKey, + }, + poly::{ + kzg::{ + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverGWC, VerifierGWC}, + strategy::SingleStrategy, + }, + Rotation, + }, + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, + }, +}; +use halo2curves::bn256::{Bn256, Fr, G1Affine}; +use rand_core::OsRng; + +#[derive(Clone, Copy)] +struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, +} + +impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { + a, + b, + c, + q_a, + q_b, + q_c, + q_ab, + constant, + instance, + } + } +} + +#[derive(Clone, Default)] +struct StandardPlonk(Fr); + +impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self::default() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; + region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; + + region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx as u64)))?; + } + + let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; + a.copy_advice(|| "", &mut region, config.b, 3)?; + a.copy_advice(|| "", &mut region, config.c, 4)?; + Ok(()) + }, + ) + } +} + +fn main() { + let k = 4; + let circuit = StandardPlonk(Fr::random(OsRng)); + let params = ParamsKZG::::setup(k, OsRng); + let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk, &circuit).expect("pk should not fail"); + + let f = File::create("serialization-test.pk").unwrap(); + let mut writer = BufWriter::new(f); + pk.write(&mut writer).unwrap(); + writer.flush().unwrap(); + + let f = File::open("serialization-test.pk").unwrap(); + let mut reader = BufReader::new(f); + let pk = ProvingKey::::read::<_, StandardPlonk>(&mut reader, ¶ms).unwrap(); + + std::fs::remove_file("serialization-test.pk").unwrap(); + + let instances: &[&[Fr]] = &[&[circuit.0]]; + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverGWC<'_, Bn256>, + Challenge255, + _, + Blake2bWrite, G1Affine, Challenge255<_>>, + _, + >( + ¶ms, + &pk, + &[circuit], + &[instances], + OsRng, + &mut transcript, + ) + .expect("prover should not fail"); + let proof = transcript.finalize(); + + let strategy = SingleStrategy::new(¶ms); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + assert!(verify_proof::< + KZGCommitmentScheme, + VerifierGWC<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >( + ¶ms, + pk.get_vk(), + strategy, + &[instances], + &mut transcript + ) + .is_ok()); +} diff --git a/halo2_proofs/src/helpers.rs b/halo2_proofs/src/helpers.rs index 297fd7b9ca..f601eab984 100644 --- a/halo2_proofs/src/helpers.rs +++ b/halo2_proofs/src/helpers.rs @@ -1,6 +1,7 @@ -use std::io; - +use crate::poly::Polynomial; +use ff::PrimeField; use halo2curves::CurveAffine; +use std::io; pub(crate) trait CurveRead: CurveAffine { /// Reads a compressed element from the buffer and attempts to parse it @@ -9,8 +10,78 @@ pub(crate) trait CurveRead: CurveAffine { let mut compressed = Self::Repr::default(); reader.read_exact(compressed.as_mut())?; Option::from(Self::from_bytes(&compressed)) - .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof")) + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof")) } } impl CurveRead for C {} + +pub(crate) trait SerdePrimeField: PrimeField { + /// Reads a field element as bytes from the buffer using `from_repr`. + /// Endianness is specified by `PrimeField` implementation. + fn read(reader: &mut R) -> io::Result { + let mut compressed = Self::Repr::default(); + reader.read_exact(compressed.as_mut())?; + Option::from(Self::from_repr(compressed)).ok_or_else(|| { + io::Error::new(io::ErrorKind::Other, "Invalid prime field point encoding") + }) + } + + /// Writes a field element as bytes to the buffer using `to_repr`. + /// Endianness is specified by `PrimeField` implementation. + fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(self.to_repr().as_ref()) + } +} + +impl SerdePrimeField for F {} + +/// Convert a slice of `bool` into a `u8`. +/// +/// Panics if the slice has length greater than 8. +pub fn pack(bits: &[bool]) -> u8 { + let mut value = 0u8; + assert!(bits.len() <= 8); + for (bit_index, bit) in bits.iter().enumerate() { + value |= (*bit as u8) << bit_index; + } + value +} + +/// Writes the first `bits.len()` bits of a `u8` into `bits`. +pub fn unpack(byte: u8, bits: &mut [bool]) { + for (bit_index, bit) in bits.iter_mut().enumerate() { + *bit = (byte >> bit_index) & 1 == 1; + } +} + +/// Reads a vector of polynomials from buffer +pub(crate) fn read_polynomial_vec( + reader: &mut R, +) -> io::Result>> { + let mut len_be_bytes = [0u8; 4]; + reader.read_exact(&mut len_be_bytes)?; + let len = u32::from_be_bytes(len_be_bytes); + + (0..len) + .map(|_| Polynomial::::read(reader)) + .collect::>>() +} + +/// Writes a slice of polynomials to buffer +pub(crate) fn write_polynomial_slice( + slice: &[Polynomial], + writer: &mut W, +) -> io::Result<()> { + writer.write_all(&(slice.len() as u32).to_be_bytes())?; + for poly in slice.iter() { + poly.write(writer)?; + } + Ok(()) +} + +/// Gets the total number of bytes of a slice of polynomials, assuming all polynomials are the same length +pub(crate) fn polynomial_slice_byte_length(slice: &[Polynomial]) -> usize { + let field_len = F::default().to_repr().as_ref().len(); + 4 + slice.len() * (4 + field_len * slice.get(0).map(|poly| poly.len()).unwrap_or(0)) +} diff --git a/halo2_proofs/src/plonk.rs b/halo2_proofs/src/plonk.rs index f9a6587af6..70ae557eed 100644 --- a/halo2_proofs/src/plonk.rs +++ b/halo2_proofs/src/plonk.rs @@ -6,10 +6,14 @@ //! [plonk]: https://eprint.iacr.org/2019/953 use blake2b_simd::Params as Blake2bParams; +use ff::PrimeField; use group::ff::Field; use crate::arithmetic::{CurveAffine, FieldExt}; -use crate::helpers::CurveRead; +use crate::helpers::{ + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, CurveRead, + SerdePrimeField, +}; use crate::poly::{ commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, PinnedEvaluationDomain, Polynomial, @@ -50,14 +54,100 @@ pub struct VerifyingKey { cs_degree: usize, /// The representative of this `VerifyingKey` in transcripts. transcript_repr: C::Scalar, + selectors: Vec>, } impl VerifyingKey { + /// Writes a verifying key to a buffer. + pub fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&(self.fixed_commitments.len() as u32).to_be_bytes())?; + for commitment in &self.fixed_commitments { + writer.write_all(commitment.to_bytes().as_ref())?; + } + self.permutation.write(writer)?; + + // write self.selectors + for selector in &self.selectors { + // since `selector` is filled with `bool`, we pack them 8 at a time into bytes and then write + for bits in selector.chunks(8) { + writer.write_all(&[crate::helpers::pack(bits)])?; + } + } + Ok(()) + } + + /// Reads a verification key from a buffer. + pub fn read<'params, R: io::Read, ConcreteCircuit: Circuit>( + reader: &mut R, + params: &impl Params<'params, C>, + ) -> io::Result { + let (domain, cs, _) = keygen::create_domain::(params.k()); + let mut num_fixed_columns_be_bytes = [0u8; 4]; + reader.read_exact(&mut num_fixed_columns_be_bytes)?; + let num_fixed_columns = u32::from_be_bytes(num_fixed_columns_be_bytes); + + let fixed_commitments: Vec<_> = (0..num_fixed_columns) + .map(|_| C::read(reader)) + .collect::>()?; + + let permutation = permutation::VerifyingKey::read(reader, &cs.permutation)?; + + // read selectors + let selectors: Vec> = vec![vec![false; params.n() as usize]; cs.num_selectors] + .into_iter() + .map(|mut selector| { + let mut selector_bytes = vec![0u8; (selector.len() + 7) / 8]; + reader.read_exact(&mut selector_bytes)?; + for (bits, byte) in selector.chunks_mut(8).into_iter().zip(selector_bytes) { + crate::helpers::unpack(byte, bits); + } + Ok(selector) + }) + .collect::>>>() + .unwrap(); + let (cs, _) = cs.compress_selectors(selectors.clone()); + + Ok(Self::from_parts( + domain, + fixed_commitments, + permutation, + cs, + selectors, + )) + } + + /// Writes a verifying key to a vector of bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length()); + Self::write(self, &mut bytes).expect("Writing to vector should not fail"); + bytes + } + + /// Reads a verification key from a slice of bytes. + pub fn from_bytes<'params, ConcreteCircuit: Circuit>( + mut bytes: &[u8], + params: &impl Params<'params, C>, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>(&mut bytes, params) + } + + fn bytes_length(&self) -> usize { + 4 + (self.fixed_commitments.len() * C::default().to_bytes().as_ref().len()) + + self.permutation.bytes_length() + + self.selectors.len() + * (self + .selectors + .get(0) + .map(|selector| selector.len() / 8 + 1) + .unwrap_or(0)) + } + fn from_parts( domain: EvaluationDomain, fixed_commitments: Vec, permutation: permutation::VerifyingKey, cs: ConstraintSystem, + selectors: Vec>, ) -> Self { // Compute cached values. let cs_degree = cs.degree(); @@ -70,6 +160,7 @@ impl VerifyingKey { cs_degree, // Temporary, this is not pinned. transcript_repr: C::Scalar::zero(), + selectors, }; let mut hasher = Blake2bParams::new() @@ -159,6 +250,75 @@ impl ProvingKey { pub fn get_vk(&self) -> &VerifyingKey { &self.vk } + + /// Writes a proving key to a buffer. + /// 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) -> io::Result<()> { + self.vk.write(writer)?; + self.l0.write(writer)?; + self.l_last.write(writer)?; + self.l_active_row.write(writer)?; + write_polynomial_slice(&self.fixed_values, writer)?; + write_polynomial_slice(&self.fixed_polys, writer)?; + write_polynomial_slice(&self.fixed_cosets, writer)?; + self.permutation.write(writer)?; + Ok(()) + } + + /// Reads a proving key from a buffer. + /// Does so by reading verification key first, and then deserializing the rest of the file into the remaining proving key data. + pub fn read<'params, R: io::Read, ConcreteCircuit: Circuit>( + reader: &mut R, + params: &impl Params<'params, C>, + ) -> io::Result { + let vk = VerifyingKey::::read::(reader, params)?; + let l0 = Polynomial::read(reader)?; + let l_last = Polynomial::read(reader)?; + let l_active_row = Polynomial::read(reader)?; + let fixed_values = read_polynomial_vec(reader)?; + let fixed_polys = read_polynomial_vec(reader)?; + let fixed_cosets = read_polynomial_vec(reader)?; + let permutation = permutation::ProvingKey::read(reader)?; + let ev = Evaluator::new(vk.cs()); + Ok(Self { + vk, + l0, + l_last, + l_active_row, + fixed_values, + fixed_polys, + fixed_cosets, + permutation, + ev, + }) + } + + /// Writes a proving key to a vector of bytes. + pub fn to_bytes(&self) -> Vec { + let mut bytes = Vec::::with_capacity(self.bytes_length()); + Self::write(self, &mut bytes).expect("Writing to vector should not fail"); + bytes + } + + /// Reads a proving key from a slice of bytes. + pub fn from_bytes<'params, ConcreteCircuit: Circuit>( + mut bytes: &[u8], + params: &impl Params<'params, C>, + ) -> io::Result { + Self::read::<_, ConcreteCircuit>(&mut bytes, params) + } + + /// Gets the total number of bytes in the serialization of `self` + fn bytes_length(&self) -> usize { + let scalar_len = C::Scalar::default().to_repr().as_ref().len(); + self.vk.bytes_length() + + 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() + } } impl VerifyingKey { diff --git a/halo2_proofs/src/plonk/keygen.rs b/halo2_proofs/src/plonk/keygen.rs index 39cef34c0f..a3fe3dc3d7 100644 --- a/halo2_proofs/src/plonk/keygen.rs +++ b/halo2_proofs/src/plonk/keygen.rs @@ -225,7 +225,7 @@ where )?; let mut fixed = batch_invert_assigned(assembly.fixed); - let (cs, selector_polys) = cs.compress_selectors(assembly.selectors); + let (cs, selector_polys) = cs.compress_selectors(assembly.selectors.clone()); fixed.extend( selector_polys .into_iter() @@ -246,6 +246,7 @@ where fixed_commitments, permutation_vk, cs, + assembly.selectors, )) } diff --git a/halo2_proofs/src/plonk/permutation.rs b/halo2_proofs/src/plonk/permutation.rs index 26a4d805d6..7d0d8cc774 100644 --- a/halo2_proofs/src/plonk/permutation.rs +++ b/halo2_proofs/src/plonk/permutation.rs @@ -1,9 +1,12 @@ use super::circuit::{Any, Column}; use crate::{ arithmetic::CurveAffine, - helpers::CurveRead, + helpers::{ + polynomial_slice_byte_length, read_polynomial_vec, write_polynomial_slice, CurveRead, + }, poly::{Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial}, }; +use ff::PrimeField; pub(crate) mod keygen; pub(crate) mod prover; @@ -83,6 +86,25 @@ impl VerifyingKey { pub fn commitments(&self) -> &Vec { &self.commitments } + + pub(crate) fn write(&self, writer: &mut W) -> io::Result<()> { + for commitment in &self.commitments { + writer.write_all(commitment.to_bytes().as_ref())?; + } + + Ok(()) + } + + pub(crate) fn read(reader: &mut R, argument: &Argument) -> io::Result { + let commitments = (0..argument.columns.len()) + .map(|_| C::read(reader)) + .collect::, _>>()?; + Ok(VerifyingKey { commitments }) + } + + pub(crate) fn bytes_length(&self) -> usize { + self.commitments.len() * C::default().to_bytes().as_ref().len() + } } /// The proving key for a single permutation argument. @@ -92,3 +114,32 @@ pub(crate) struct ProvingKey { polys: Vec>, pub(super) cosets: Vec>, } + +impl ProvingKey { + /// Reads proving key for a single permutation argument from buffer using `Polynomial::read`. + pub(super) fn read(reader: &mut R) -> io::Result { + let permutations = read_polynomial_vec(reader)?; + let polys = read_polynomial_vec(reader)?; + let cosets = read_polynomial_vec(reader)?; + Ok(ProvingKey { + permutations, + polys, + cosets, + }) + } + + /// Writes proving key for a single permutation argument to buffer using `Polynomial::write`. + pub(super) fn write(&self, writer: &mut W) -> io::Result<()> { + write_polynomial_slice(&self.permutations, writer)?; + write_polynomial_slice(&self.polys, writer)?; + write_polynomial_slice(&self.cosets, writer)?; + Ok(()) + } + + /// 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/poly.rs b/halo2_proofs/src/poly.rs index ecf66a6e0b..1e2ac6366c 100644 --- a/halo2_proofs/src/poly.rs +++ b/halo2_proofs/src/poly.rs @@ -3,11 +3,14 @@ //! the committed polynomials at arbitrary points. use crate::arithmetic::parallelize; +use crate::helpers::SerdePrimeField; use crate::plonk::Assigned; +use ff::PrimeField; use group::ff::{BatchInvert, Field}; use halo2curves::FieldExt; use std::fmt::Debug; +use std::io; use std::marker::PhantomData; use std::ops::{Add, Deref, DerefMut, Index, IndexMut, Mul, RangeFrom, RangeFull, Sub}; @@ -143,6 +146,32 @@ impl Polynomial { } } +impl Polynomial { + /// Reads polynomial from buffer using `SerdePrimeField::read`. + pub(crate) fn read(reader: &mut R) -> io::Result { + let mut poly_len_be_bytes = [0u8; 4]; + reader.read_exact(&mut poly_len_be_bytes)?; + let poly_len = u32::from_be_bytes(poly_len_be_bytes); + + (0..poly_len) + .map(|_| F::read(reader)) + .collect::>>() + .map(|values| Self { + values, + _marker: PhantomData, + }) + } + + /// Writes polynomial to buffer using `SerdePrimeField::write`. + pub(crate) fn write(&self, writer: &mut W) -> io::Result<()> { + writer.write_all(&(self.values.len() as u32).to_be_bytes())?; + for value in self.values.iter() { + value.write(writer)?; + } + Ok(()) + } +} + pub(crate) fn batch_invert_assigned( assigned: Vec, LagrangeCoeff>>, ) -> Vec> {