Skip to content

Instantly share code, notes, and snippets.

@DrPeterVanNostrand
Last active September 13, 2022 18:58
Show Gist options
  • Save DrPeterVanNostrand/9ace9895cab7f4b59070da9349e59ec5 to your computer and use it in GitHub Desktop.
Save DrPeterVanNostrand/9ace9895cab7f4b59070da9349e59ec5 to your computer and use it in GitHub Desktop.
Halo2 verification key serialization without constraint system
// halo2_proofs/src/poly/domain.rs
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EvaluationDomain<G: Group> {
pub(crate) n: u64,
pub(crate) k: u32,
pub(crate) extended_k: u32,
pub(crate) omega: G::Scalar,
pub(crate) omega_inv: G::Scalar,
pub(crate) extended_omega: G::Scalar,
pub(crate) extended_omega_inv: G::Scalar,
pub(crate) g_coset: G::Scalar,
pub(crate) g_coset_inv: G::Scalar,
pub(crate) quotient_poly_degree: u64,
pub(crate) ifft_divisor: G::Scalar,
pub(crate) extended_ifft_divisor: G::Scalar,
pub(crate) t_evaluations: Vec<G::Scalar>,
pub(crate) barycentric_weight: G::Scalar,
}
// halo2_proofs/src/plonk/keygen.rs
pub(crate) struct Assembly<F: Field> {
pub(crate) k: u32,
pub(crate) fixed: Vec<Polynomial<Assigned<F>, LagrangeCoeff>>,
pub(crate) permutation: permutation::keygen::Assembly,
pub(crate) selectors: Vec<Vec<bool>>,
// A range of available rows for assignment and copies.
pub(crate) usable_rows: Range<usize>,
pub(crate) _marker: std::marker::PhantomData<F>,
}
// halo2_proofs/src/plonk/permutation.rs
pub(crate) struct Argument {
/// A sequence of columns involved in the argument.
pub(crate) columns: Vec<Column<Any>>,
}
// halo2_proofs/src/plonk.rs
use std::io::{self, Read, Write};
const FIELD_SIZE: usize = 32;
const AFFINE_SIZE: usize = 2 * FIELD_SIZE;
impl<C: CurveAffine> PartialEq for VerifyingKey<C> {
fn eq(&self, other: &Self) -> bool {
format!("{:?}", self) == format!("{:?}", other)
}
}
impl<C: CurveAffine> Eq for VerifyingKey<C> {}
impl<C: CurveAffine> VerifyingKey<C> {
...
/// Serialize evaluation domain, fixed commitments, and permutation verification key.
#[allow(unsafe_code)]
pub fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
assert_eq!(std::mem::size_of::<C::Scalar>(), FIELD_SIZE);
assert_eq!(std::mem::size_of::<C>(), AFFINE_SIZE);
writer.write_all(&self.domain.n.to_le_bytes())?;
writer.write_all(&self.domain.k.to_le_bytes())?;
writer.write_all(&self.domain.extended_k.to_le_bytes())?;
{
let bytes_ptr = (&self.domain.omega as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.omega_inv as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.extended_omega as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.extended_omega_inv as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.g_coset as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.g_coset_inv as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
writer.write_all(&self.domain.quotient_poly_degree.to_le_bytes())?;
{
let bytes_ptr = (&self.domain.ifft_divisor as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let bytes_ptr = (&self.domain.extended_ifft_divisor as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let byte_len = self.domain.t_evaluations.len() * FIELD_SIZE;
let bytes_ptr = self.domain.t_evaluations.as_ptr() as *const u8;
let bytes = unsafe { std::slice::from_raw_parts(bytes_ptr, byte_len) };
writer.write_all(&bytes)?;
}
{
let bytes_ptr = (&self.domain.barycentric_weight as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
{
let byte_len = self.fixed_commitments.len() * AFFINE_SIZE;
let bytes_ptr = self.fixed_commitments.as_ptr() as *const u8;
let bytes = unsafe { std::slice::from_raw_parts(bytes_ptr, byte_len) };
writer.write_all(&bytes)?;
}
{
let byte_len = self.permutation.commitments.len() * AFFINE_SIZE;
let bytes_ptr = self.permutation.commitments.as_ptr() as *const u8;
let bytes = unsafe { std::slice::from_raw_parts(bytes_ptr, byte_len) };
writer.write_all(&bytes)?;
}
writer.write_all(&self.cs_degree.to_le_bytes())?;
{
let bytes_ptr = (&self.transcript_repr as *const C::Scalar) as *const [u8; FIELD_SIZE];
unsafe { writer.write_all(&*bytes_ptr)? };
}
Ok(())
}
/// Deserialize evaluation domain, fixed commitments, and permutation verification key.
#[allow(unsafe_code)]
pub fn read<R: Read, ConcreteCircuit: Circuit<C::Scalar>>(
reader: &mut R,
params: &Params<C>,
circuit: &ConcreteCircuit,
) -> io::Result<Self> {
assert_eq!(std::mem::size_of::<C::Scalar>(), FIELD_SIZE);
assert_eq!(std::mem::size_of::<C>(), AFFINE_SIZE);
let mut cs = ConstraintSystem::<C::Scalar>::default();
let config = ConcreteCircuit::configure(&mut cs);
let empty_lagrange = Polynomial::<Assigned<C::Scalar>, LagrangeCoeff> {
values: vec![C::Scalar::group_zero().into(); params.n as usize],
_marker: std::marker::PhantomData,
};
let mut assembly: Assembly<C::Scalar> = Assembly {
k: params.k,
fixed: vec![empty_lagrange; cs.num_fixed_columns],
permutation: permutation::keygen::Assembly::new(params.n as usize, &cs.permutation),
selectors: vec![vec![false; params.n as usize]; cs.num_selectors],
usable_rows: 0..params.n as usize - (cs.blinding_factors() + 1),
_marker: std::marker::PhantomData,
};
ConcreteCircuit::FloorPlanner::synthesize(
&mut assembly,
circuit,
config,
cs.constants.clone(),
)
.expect("failed to synthesize circuit");
let (cs, _selector_polys) = cs.compress_selectors(assembly.selectors);
let domain = {
let n = {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
u64::from_le_bytes(buf)
};
assert_eq!(n, params.n);
let k = {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
u32::from_le_bytes(buf)
};
assert_eq!(k, params.k);
let extended_k = {
let mut buf = [0u8; 4];
reader.read_exact(&mut buf)?;
u32::from_le_bytes(buf)
};
assert!(extended_k >= k);
let omega = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let omega_inv = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
assert_eq!(omega * &omega_inv, C::Scalar::one());
let extended_omega = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let extended_omega_inv = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
assert_eq!(extended_omega * &extended_omega_inv, C::Scalar::one());
let g_coset = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let g_coset_inv = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
assert_eq!(g_coset * &g_coset_inv, C::Scalar::one());
let quotient_poly_degree = {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
u64::from_le_bytes(buf)
};
assert_eq!(quotient_poly_degree, cs.degree() as u64 - 1);
let n_quotient_deg = n * quotient_poly_degree;
assert!(1 << extended_k >= n_quotient_deg);
assert!(1 << (extended_k - 1) < n_quotient_deg);
let ifft_divisor = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let extended_ifft_divisor = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let t_evaluations = {
let len = 1 << (extended_k - k);
let byte_len = len * FIELD_SIZE;
let mut buf = vec![0u8; byte_len];
reader.read_exact(&mut buf)?;
let mut buf_no_drop = std::mem::ManuallyDrop::new(buf);
unsafe { Vec::from_raw_parts(buf_no_drop.as_mut_ptr() as *mut C::Scalar, len, len) }
};
let barycentric_weight = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
EvaluationDomain::<C::Scalar> {
n,
k,
extended_k,
omega,
omega_inv,
extended_omega,
extended_omega_inv,
g_coset,
g_coset_inv,
quotient_poly_degree,
ifft_divisor,
extended_ifft_divisor,
t_evaluations,
barycentric_weight,
}
};
let fixed_commitments = {
let len = cs.num_fixed_columns;
let byte_len = len * AFFINE_SIZE;
let mut buf = vec![0u8; byte_len];
reader.read_exact(&mut buf)?;
let mut buf_no_drop = std::mem::ManuallyDrop::new(buf);
unsafe { Vec::from_raw_parts(buf_no_drop.as_mut_ptr() as *mut C, len, len) }
};
let permutation = {
let len = cs.permutation.columns.len();
let byte_len = len * AFFINE_SIZE;
let mut buf = vec![0u8; byte_len];
reader.read_exact(&mut buf)?;
let mut buf_no_drop = std::mem::ManuallyDrop::new(buf);
let commitments = unsafe { Vec::from_raw_parts(buf_no_drop.as_mut_ptr() as *mut C, len, len) };
permutation::VerifyingKey::<C> { commitments }
};
let cs_degree = {
let mut buf = [0u8; 8];
reader.read_exact(&mut buf)?;
usize::from_le_bytes(buf)
};
let transcript_repr = {
let mut buf = [0u8; FIELD_SIZE];
reader.read_exact(&mut buf)?;
unsafe { *(buf.as_ptr() as *const C::Scalar) }
};
let vk = Self::from_parts(domain, fixed_commitments, permutation, cs);
assert_eq!(cs_degree, vk.cs_degree);
assert_eq!(transcript_repr, vk.transcript_repr);
Ok(vk)
}
}
// halo2_proofs/tests/plonk_api.rs
#[test]
fn plonk_api() {
...
// Test verification key serializtion.
use halo2_proofs::plonk::VerifyingKey;
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let mut vk_bytes = Vec::<u8>::new();
vk.write(&mut vk_bytes).unwrap();
let mut vk_reader = std::io::Cursor::new(vk_bytes);
let vk2 = VerifyingKey::<EqAffine>::read(&mut vk_reader, &params, &empty_circuit).unwrap();
assert_eq!(vk, vk2);
}
// halo2_proofs/src/poly.rs
pub struct Polynomial<F, B> {
pub(crate) values: Vec<F>,
pub(crate) _marker: PhantomData<B>,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment