Skip to content

Commit

Permalink
Feature to serialize/deserialize KZG params, verifying key, and provi…
Browse files Browse the repository at this point in the history
…ng key into uncompressed Montgomery form (#111)

* feat: read `VerifyingKey` and `ProvingKey` does not require `params` as
long as we serialize `params.k()`

* feat: add features "serde-raw" and "raw-unchecked" to
serialize/deserialize KZG params, verifying key, and proving key
directly into raw bytes in internal memory format.
So field elements are stored in Montgomery form `a * R (mod p)` and
curve points are stored without compression.

* chore: switch to halo2curves 0.3.1 tag

* feat: add enum `SerdeFormat` for user to select
serialization/deserialization format of curve and field elements

Co-authored-by: Jonathan Wang <jonathanpwang@users.noreply.github.com>
  • Loading branch information
jonathanpwang and jonathanpwang authored Jan 16, 2023
1 parent 789790f commit 4ad07d4
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 191 deletions.
2 changes: 1 addition & 1 deletion halo2_proofs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ backtrace = { version = "0.3", optional = true }
rayon = "1.5.1"
ff = "0.12"
group = "0.12"
halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves', tag = '0.3.0' }
halo2curves = { git = 'https://github.com/privacy-scaling-explorations/halo2curves.git', tag = "0.3.1" }
rand_core = { version = "0.6", default-features = false }
tracing = "0.1"
blake2b_simd = "1"
Expand Down
6 changes: 4 additions & 2 deletions halo2_proofs/examples/serialization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ use halo2_proofs::{
transcript::{
Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer,
},
SerdeFormat,
};
use halo2curves::bn256::{Bn256, Fr, G1Affine};
use rand_core::OsRng;
Expand Down Expand Up @@ -134,12 +135,13 @@ fn main() {

let f = File::create("serialization-test.pk").unwrap();
let mut writer = BufWriter::new(f);
pk.write(&mut writer).unwrap();
pk.write(&mut writer, SerdeFormat::RawBytes).unwrap();
writer.flush().unwrap();

let f = File::open("serialization-test.pk").unwrap();
let mut reader = BufReader::new(f);
let pk = ProvingKey::<G1Affine>::read::<_, StandardPlonk>(&mut reader, &params).unwrap();
let pk = ProvingKey::<G1Affine>::read::<_, StandardPlonk>(&mut reader, SerdeFormat::RawBytes)
.unwrap();

std::fs::remove_file("serialization-test.pk").unwrap();

Expand Down
107 changes: 83 additions & 24 deletions halo2_proofs/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,25 @@
use crate::poly::Polynomial;
use ff::PrimeField;
use halo2curves::CurveAffine;
use halo2curves::{pairing::Engine, serde::SerdeObject, CurveAffine};
use std::io;

/// This enum specifies how various types are serialized and deserialized.
#[derive(Clone, Copy, Debug)]
pub enum SerdeFormat {
/// Curve elements are serialized in compressed form.
/// Field elements are serialized in standard form, with endianness specified by the
/// `PrimeField` implementation.
Processed,
/// Curve elements are serialized in uncompressed form. Field elements are serialized
/// in their internal Montgomery representation.
/// When deserializing, checks are performed to ensure curve elements indeed lie on the curve and field elements
/// are less than modulus.
RawBytes,
/// Serialization is the same as `RawBytes`, but no checks are performed.
RawBytesUnchecked,
}

// Keep this trait for compatibility with IPA serialization
pub(crate) trait CurveRead: CurveAffine {
/// Reads a compressed element from the buffer and attempts to parse it
/// using `from_bytes`.
Expand All @@ -13,28 +30,68 @@ pub(crate) trait CurveRead: CurveAffine {
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid point encoding in proof"))
}
}

impl<C: CurveAffine> 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<R: io::Read>(reader: &mut R) -> io::Result<Self> {
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")
})
pub trait SerdeCurveAffine: CurveAffine + SerdeObject {
/// Reads an element from the buffer and parses it according to the `format`:
/// - `Processed`: Reads a compressed curve element and decompress it
/// - `RawBytes`: Reads an uncompressed curve element with coordinates in Montgomery form.
/// Checks that field elements are less than modulus, and then checks that the point is on the curve.
/// - `RawBytesUnchecked`: Reads an uncompressed curve element with coordinates in Montgomery form;
/// does not perform any checks
fn read<R: io::Read>(reader: &mut R, format: SerdeFormat) -> io::Result<Self> {
match format {
SerdeFormat::Processed => <Self as CurveRead>::read(reader),
SerdeFormat::RawBytes => <Self as SerdeObject>::read_raw(reader),
SerdeFormat::RawBytesUnchecked => Ok(<Self as SerdeObject>::read_raw_unchecked(reader)),
}
}

/// Writes a field element as bytes to the buffer using `to_repr`.
/// Endianness is specified by `PrimeField` implementation.
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(self.to_repr().as_ref())
/// Writes a curve element according to `format`:
/// - `Processed`: Writes a compressed curve element
/// - Otherwise: Writes an uncompressed curve element with coordinates in Montgomery form
fn write<W: io::Write>(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> {
match format {
SerdeFormat::Processed => writer.write_all(self.to_bytes().as_ref()),
_ => self.write_raw(writer),
}
}
}
impl<C: CurveAffine + SerdeObject> SerdeCurveAffine for C {}

pub trait SerdePrimeField: PrimeField + SerdeObject {
/// Reads a field element as bytes from the buffer according to the `format`:
/// - `Processed`: Reads a field element in standard form, with endianness specified by the
/// `PrimeField` implementation, and checks that the element is less than the modulus.
/// - `RawBytes`: Reads a field element from raw bytes in its internal Montgomery representations,
/// and checks that the element is less than the modulus.
/// - `RawBytesUnchecked`: Reads a field element in Montgomery form and performs no checks.
fn read<R: io::Read>(reader: &mut R, format: SerdeFormat) -> io::Result<Self> {
match format {
SerdeFormat::Processed => {
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")
})
}
SerdeFormat::RawBytes => <Self as SerdeObject>::read_raw(reader),
SerdeFormat::RawBytesUnchecked => Ok(<Self as SerdeObject>::read_raw_unchecked(reader)),
}
}

impl<F: PrimeField> SerdePrimeField for F {}
/// Writes a field element as bytes to the buffer according to the `format`:
/// - `Processed`: Writes a field element in standard form, with endianness specified by the
/// `PrimeField` implementation.
/// - Otherwise: Writes a field element into raw bytes in its internal Montgomery representation,
/// WITHOUT performing the expensive Montgomery reduction.
fn write<W: io::Write>(&self, writer: &mut W, format: SerdeFormat) -> io::Result<()> {
match format {
SerdeFormat::Processed => writer.write_all(self.to_repr().as_ref()),
_ => self.write_raw(writer),
}
}
}
impl<F: PrimeField + SerdeObject> SerdePrimeField for F {}

/// Convert a slice of `bool` into a `u8`.
///
Expand All @@ -56,26 +113,28 @@ pub fn unpack(byte: u8, bits: &mut [bool]) {
}

/// Reads a vector of polynomials from buffer
pub(crate) fn read_polynomial_vec<R: io::Read, F: PrimeField, B>(
pub(crate) fn read_polynomial_vec<R: io::Read, F: SerdePrimeField, B>(
reader: &mut R,
format: SerdeFormat,
) -> io::Result<Vec<Polynomial<F, B>>> {
let mut len_be_bytes = [0u8; 4];
reader.read_exact(&mut len_be_bytes)?;
let len = u32::from_be_bytes(len_be_bytes);
let mut len = [0u8; 4];
reader.read_exact(&mut len)?;
let len = u32::from_be_bytes(len);

(0..len)
.map(|_| Polynomial::<F, B>::read(reader))
.map(|_| Polynomial::<F, B>::read(reader, format))
.collect::<io::Result<Vec<_>>>()
}

/// Writes a slice of polynomials to buffer
pub(crate) fn write_polynomial_slice<W: io::Write, F: PrimeField, B>(
pub(crate) fn write_polynomial_slice<W: io::Write, F: SerdePrimeField, B>(
slice: &[Polynomial<F, B>],
writer: &mut W,
format: SerdeFormat,
) -> io::Result<()> {
writer.write_all(&(slice.len() as u32).to_be_bytes())?;
for poly in slice.iter() {
poly.write(writer)?;
poly.write(writer, format)?;
}
Ok(())
}
Expand Down
1 change: 1 addition & 0 deletions halo2_proofs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ pub mod transcript;

pub mod dev;
mod helpers;
pub use helpers::SerdeFormat;
Loading

0 comments on commit 4ad07d4

Please sign in to comment.