Skip to content

Commit

Permalink
Add verifying key and proving key serialization (privacy-scaling-expl…
Browse files Browse the repository at this point in the history
…orations#103)

* VerifyingKey Serialization: merge Nalin's PR
zcash#661 that allows for vkey
serialization/deserialization and fixes the previous selector
optimization issue

* feat: add serialization & deserialization of pkey and vkey using serde
derive

* fix: add `num_fixed_commitments` to vkey serialization so correct number
of fixed columns, post selector compression, are read

* fix: serialize/deserialize pkey directly to/from file

* Update: implement suggestions for PR

* add `plonk::Error::Serde` to pass through serialization errors

* update: remove serialization from evaluation as we now recalculate directly

* fix: clippy warnings

* feat: implement `ProvingKey` serialization without using external crates
`serde` or `bincode`

* examples: add `serialization` example to test `ProvingKey` read and
write on "simple-example"

* feat: added `to/from_bytes` for `ProvingKey` and `VerifyingKey`

* add `pack`/`unpack` helper functions between `&[bool]` and `u8`
* made serialization example use smaller example of standard plonk for
  less code bloat

Co-authored-by: Nalin Bhardwaj <[email protected]>
  • Loading branch information
jonathanpwang and nalinbhardwaj authored Dec 2, 2022
1 parent 5fc8ce8 commit 246ce4e
Show file tree
Hide file tree
Showing 6 changed files with 500 additions and 6 deletions.
182 changes: 182 additions & 0 deletions halo2_proofs/examples/serialization.rs
Original file line number Diff line number Diff line change
@@ -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<Advice>,
b: Column<Advice>,
c: Column<Advice>,
q_a: Column<Fixed>,
q_b: Column<Fixed>,
q_c: Column<Fixed>,
q_ab: Column<Fixed>,
constant: Column<Fixed>,
#[allow(dead_code)]
instance: Column<Instance>,
}

impl StandardPlonkConfig {
fn configure(meta: &mut ConstraintSystem<Fr>) -> 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<Fr> for StandardPlonk {
type Config = StandardPlonkConfig;
type FloorPlanner = SimpleFloorPlanner;

fn without_witnesses(&self) -> Self {
Self::default()
}

fn configure(meta: &mut ConstraintSystem<Fr>) -> Self::Config {
StandardPlonkConfig::configure(meta)
}

fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Fr>,
) -> 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::<Bn256>::setup(k, OsRng);
let vk = keygen_vk(&params, &circuit).expect("vk should not fail");
let pk = keygen_pk(&params, 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::<G1Affine>::read::<_, StandardPlonk>(&mut reader, &params).unwrap();

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

let instances: &[&[Fr]] = &[&[circuit.0]];
let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]);
create_proof::<
KZGCommitmentScheme<Bn256>,
ProverGWC<'_, Bn256>,
Challenge255<G1Affine>,
_,
Blake2bWrite<Vec<u8>, G1Affine, Challenge255<_>>,
_,
>(
&params,
&pk,
&[circuit],
&[instances],
OsRng,
&mut transcript,
)
.expect("prover should not fail");
let proof = transcript.finalize();

let strategy = SingleStrategy::new(&params);
let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]);
assert!(verify_proof::<
KZGCommitmentScheme<Bn256>,
VerifierGWC<'_, Bn256>,
Challenge255<G1Affine>,
Blake2bRead<&[u8], G1Affine, Challenge255<G1Affine>>,
SingleStrategy<'_, Bn256>,
>(
&params,
pk.get_vk(),
strategy,
&[instances],
&mut transcript
)
.is_ok());
}
77 changes: 74 additions & 3 deletions halo2_proofs/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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<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")
})
}

/// 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())
}
}

impl<F: PrimeField> 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<R: io::Read, F: PrimeField, B>(
reader: &mut R,
) -> 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);

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

/// Writes a slice of polynomials to buffer
pub(crate) fn write_polynomial_slice<W: io::Write, F: PrimeField, B>(
slice: &[Polynomial<F, B>],
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<F: PrimeField, B>(slice: &[Polynomial<F, B>]) -> 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))
}
Loading

0 comments on commit 246ce4e

Please sign in to comment.