Merge pull request #135 from zcash/serialize-params

Serialize params
This commit is contained in:
ebfull 2021-01-30 11:43:55 -07:00 committed by GitHub
commit 5f89227cdd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 318 additions and 95 deletions

View file

@ -226,7 +226,8 @@ fn bench_with_k(name: &str, k: u32, c: &mut Criterion) {
let empty_circuit: MyCircuit<Fp> = MyCircuit { a: None, k };
// Initialize the proving key
let pk = keygen(&params, &empty_circuit).expect("keygen should not fail");
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
let prover_name = name.to_string() + "-prover";
let verifier_name = name.to_string() + "-verifier";

View file

@ -257,7 +257,8 @@ fn main() {
let empty_circuit: MyCircuit<Fp> = MyCircuit { a: None, k };
// Initialize the proving key
let pk = keygen(&params, &empty_circuit).expect("keygen should not fail");
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
println!("[Keygen] {}", recorder);
recorder.clear();

View file

@ -8,6 +8,8 @@ use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};
use super::{FieldExt, Group};
use std::io::{self, Read, Write};
/// This trait is a common interface for dealing with elements of an elliptic
/// curve group in the "projective" form, where that arithmetic is usually more
/// efficient.
@ -153,10 +155,25 @@ pub trait CurveAffine:
/// endian representation.
fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self>;
/// Reads a compressed element from the buffer and attempts to parse it
/// using `from_bytes`.
fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut compressed = [0u8; 32];
reader.read_exact(&mut compressed[..])?;
Option::from(Self::from_bytes(&compressed))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof"))
}
/// Obtains the compressed, 32-byte little endian representation of this
/// element.
fn to_bytes(&self) -> [u8; 32];
/// Writes an element in compressed form to the buffer.
fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
let compressed = self.to_bytes();
writer.write_all(&compressed[..])
}
/// Attempts to obtain a group element from its uncompressed 64-byte little
/// endian representation.
fn from_bytes_wide(bytes: &[u8; 64]) -> CtOption<Self>;

View file

@ -10,6 +10,8 @@ use subtle::{Choice, ConstantTimeEq, CtOption};
use super::Group;
use std::io::{self, Read, Write};
const_assert!(size_of::<usize>() >= 4);
/// This trait is a common interface for dealing with elements of a finite
@ -77,10 +79,25 @@ pub trait FieldExt:
/// representation.
fn to_bytes(&self) -> [u8; 32];
/// Writes this element in its normalized, little endian form into a buffer.
fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
let compressed = self.to_bytes();
writer.write_all(&compressed[..])
}
/// Attempts to obtain a field element from its normalized, little endian
/// byte representation.
fn from_bytes(bytes: &[u8; 32]) -> CtOption<Self>;
/// Reads a normalized, little endian represented field element from a
/// buffer.
fn read<R: Read>(reader: &mut R) -> io::Result<Self> {
let mut compressed = [0u8; 32];
reader.read_exact(&mut compressed[..])?;
Option::from(Self::from_bytes(&compressed))
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "invalid point encoding in proof"))
}
/// Obtains a field element that is congruent to the provided little endian
/// byte representation of an integer.
fn from_bytes_wide(bytes: &[u8; 64]) -> Self;

View file

@ -6,7 +6,9 @@
//! [plonk]: https://eprint.iacr.org/2019/953
use crate::arithmetic::CurveAffine;
use crate::poly::{Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial};
use crate::poly::{
commitment::Params, Coeff, EvaluationDomain, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
};
use crate::transcript::ChallengeScalar;
mod circuit;
@ -23,6 +25,8 @@ pub use keygen::*;
pub use prover::*;
pub use verifier::*;
use std::io;
/// This is a verifying key which allows for the verification of proofs for a
/// particular circuit.
#[derive(Debug)]
@ -33,6 +37,45 @@ pub struct VerifyingKey<C: CurveAffine> {
cs: ConstraintSystem<C::Scalar>,
}
impl<C: CurveAffine> VerifyingKey<C> {
/// Writes a verifying key to a buffer.
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
for commitment in &self.fixed_commitments {
writer.write_all(&commitment.to_bytes())?;
}
for permutation in &self.permutations {
permutation.write(writer)?;
}
Ok(())
}
/// Reads a verification key from a buffer.
pub fn read<R: io::Read, ConcreteCircuit: Circuit<C::Scalar>>(
reader: &mut R,
params: &Params<C>,
) -> io::Result<Self> {
let (domain, cs, _) = keygen::create_domain::<C, ConcreteCircuit>(params);
let fixed_commitments: Vec<_> = (0..cs.num_fixed_columns)
.map(|_| C::read(reader))
.collect::<Result<_, _>>()?;
let permutations: Vec<_> = cs
.permutations
.iter()
.map(|argument| permutation::VerifyingKey::read(reader, argument))
.collect::<Result<_, _>>()?;
Ok(VerifyingKey {
domain,
fixed_commitments,
permutations,
cs,
})
}
}
/// This is a proving key which allows for the creation of proofs for a
/// particular circuit.
#[derive(Debug)]
@ -442,7 +485,8 @@ fn test_proving() {
};
// Initialize the proving key
let pk = keygen(&params, &empty_circuit).expect("keygen should not fail");
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
let mut pubinputs = pk.get_vk().get_domain().empty_lagrange();
pubinputs[0] = aux;
@ -487,8 +531,11 @@ fn test_proving() {
let msm = guard.clone().use_challenges();
assert!(msm.clone().eval());
let mut transcript = DummyHashRead::init(&proof[..], Fq::one());
let guard =
verify_proof(&params, pk.get_vk(), msm, pubinput_slice, &mut transcript).unwrap();
let mut vk_buffer = vec![];
pk.get_vk().write(&mut vk_buffer).unwrap();
let vk = VerifyingKey::<EqAffine>::read::<_, MyCircuit<Fp>>(&mut &vk_buffer[..], &params)
.unwrap();
let guard = verify_proof(&params, &vk, msm, pubinput_slice, &mut transcript).unwrap();
{
let msm = guard.clone().use_challenges();
assert!(msm.eval());

View file

@ -2,72 +2,25 @@ use ff::Field;
use super::{
circuit::{Advice, Assignment, Circuit, Column, ConstraintSystem, Fixed},
permutation, Error, ProvingKey, VerifyingKey,
permutation, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey,
};
use crate::arithmetic::{Curve, CurveAffine};
use crate::poly::{
commitment::{Blind, Params},
EvaluationDomain, LagrangeCoeff, Polynomial, Rotation,
EvaluationDomain, Rotation,
};
/// Generate a `ProvingKey` from an instance of `Circuit`.
pub fn keygen<C, ConcreteCircuit>(
pub(crate) fn create_domain<C, ConcreteCircuit>(
params: &Params<C>,
circuit: &ConcreteCircuit,
) -> Result<ProvingKey<C>, Error>
) -> (
EvaluationDomain<C::Scalar>,
ConstraintSystem<C::Scalar>,
ConcreteCircuit::Config,
)
where
C: CurveAffine,
ConcreteCircuit: Circuit<C::Scalar>,
{
struct Assembly<F: Field> {
fixed: Vec<Polynomial<F, LagrangeCoeff>>,
permutations: Vec<permutation::keygen::Assembly>,
_marker: std::marker::PhantomData<F>,
}
impl<F: Field> Assignment<F> for Assembly<F> {
fn assign_advice(
&mut self,
_: Column<Advice>,
_: usize,
_: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
// We only care about fixed columns here
Ok(())
}
fn assign_fixed(
&mut self,
column: Column<Fixed>,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
*self
.fixed
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?;
Ok(())
}
fn copy(
&mut self,
permutation: usize,
left_column: usize,
left_row: usize,
right_column: usize,
right_row: usize,
) -> Result<(), Error> {
// Check bounds first
if permutation >= self.permutations.len() {
return Err(Error::BoundsFailure);
}
self.permutations[permutation].copy(left_column, left_row, right_column, right_row)
}
}
let mut cs = ConstraintSystem::default();
let config = ConcreteCircuit::configure(&mut cs);
@ -99,6 +52,71 @@ where
let domain = EvaluationDomain::new(degree as u32, params.k);
(domain, cs, config)
}
/// Assembly to be used in circuit synthesis.
#[derive(Debug)]
pub struct Assembly<F: Field> {
fixed: Vec<Polynomial<F, LagrangeCoeff>>,
permutations: Vec<permutation::keygen::Assembly>,
_marker: std::marker::PhantomData<F>,
}
impl<F: Field> Assignment<F> for Assembly<F> {
fn assign_advice(
&mut self,
_: Column<Advice>,
_: usize,
_: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
// We only care about fixed columns here
Ok(())
}
fn assign_fixed(
&mut self,
column: Column<Fixed>,
row: usize,
to: impl FnOnce() -> Result<F, Error>,
) -> Result<(), Error> {
*self
.fixed
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?;
Ok(())
}
fn copy(
&mut self,
permutation: usize,
left_column: usize,
left_row: usize,
right_column: usize,
right_row: usize,
) -> Result<(), Error> {
// Check bounds first
if permutation >= self.permutations.len() {
return Err(Error::BoundsFailure);
}
self.permutations[permutation].copy(left_column, left_row, right_column, right_row)
}
}
/// Generate a `VerifyingKey` from an instance of `Circuit`.
pub fn keygen_vk<C, ConcreteCircuit>(
params: &Params<C>,
circuit: &ConcreteCircuit,
) -> Result<VerifyingKey<C>, Error>
where
C: CurveAffine,
ConcreteCircuit: Circuit<C::Scalar>,
{
let (domain, cs, config) = create_domain::<C, ConcreteCircuit>(params);
let mut assembly: Assembly<C::Scalar> = Assembly {
fixed: vec![domain.empty_lagrange(); cs.num_fixed_columns],
permutations: cs
@ -109,17 +127,17 @@ where
_marker: std::marker::PhantomData,
};
// Synthesize the circuit to obtain SRS
// Synthesize the circuit to obtain URS
circuit.synthesize(&mut assembly, config)?;
let permutation_helper = permutation::keygen::Assembly::build_helper(params, &cs, &domain);
let (permutation_pks, permutation_vks) = cs
let permutation_vks = cs
.permutations
.iter()
.zip(assembly.permutations.into_iter())
.map(|(p, assembly)| assembly.build_keys(params, &domain, &permutation_helper, p))
.unzip();
.map(|(p, assembly)| assembly.build_vk(params, &domain, &permutation_helper, p))
.collect();
let fixed_commitments = assembly
.fixed
@ -127,35 +145,77 @@ where
.map(|poly| params.commit_lagrange(poly, Blind::default()).to_affine())
.collect();
Ok(VerifyingKey {
domain,
fixed_commitments,
permutations: permutation_vks,
cs,
})
}
/// Generate a `ProvingKey` from a `VerifyingKey` and an instance of `Circuit`.
pub fn keygen_pk<C, ConcreteCircuit>(
params: &Params<C>,
vk: VerifyingKey<C>,
circuit: &ConcreteCircuit,
) -> Result<ProvingKey<C>, Error>
where
C: CurveAffine,
ConcreteCircuit: Circuit<C::Scalar>,
{
let mut cs = ConstraintSystem::default();
let config = ConcreteCircuit::configure(&mut cs);
let mut assembly: Assembly<C::Scalar> = Assembly {
fixed: vec![vk.domain.empty_lagrange(); vk.cs.num_fixed_columns],
permutations: vk
.cs
.permutations
.iter()
.map(|p| permutation::keygen::Assembly::new(params.n as usize, p))
.collect(),
_marker: std::marker::PhantomData,
};
// Synthesize the circuit to obtain URS
circuit.synthesize(&mut assembly, config)?;
let fixed_polys: Vec<_> = assembly
.fixed
.iter()
.map(|poly| domain.lagrange_to_coeff(poly.clone()))
.map(|poly| vk.domain.lagrange_to_coeff(poly.clone()))
.collect();
let fixed_cosets = cs
let fixed_cosets = vk
.cs
.fixed_queries
.iter()
.map(|&(column, at)| {
let poly = fixed_polys[column.index()].clone();
domain.coeff_to_extended(poly, at)
vk.domain.coeff_to_extended(poly, at)
})
.collect();
let permutation_helper =
permutation::keygen::Assembly::build_helper(params, &vk.cs, &vk.domain);
let permutation_pks = vk
.cs
.permutations
.iter()
.zip(assembly.permutations.into_iter())
.map(|(p, assembly)| assembly.build_pk(&vk.domain, &permutation_helper, p))
.collect();
// Compute l_0(X)
// TODO: this can be done more efficiently
let mut l0 = domain.empty_lagrange();
let mut l0 = vk.domain.empty_lagrange();
l0[0] = C::Scalar::one();
let l0 = domain.lagrange_to_coeff(l0);
let l0 = domain.coeff_to_extended(l0, Rotation::cur());
let l0 = vk.domain.lagrange_to_coeff(l0);
let l0 = vk.domain.coeff_to_extended(l0, Rotation::cur());
Ok(ProvingKey {
vk: VerifyingKey {
domain,
fixed_commitments,
permutations: permutation_vks,
cs,
},
vk,
l0,
fixed_values: assembly.fixed,
fixed_polys,

View file

@ -10,6 +10,8 @@ pub(crate) mod keygen;
mod prover;
mod verifier;
use std::io;
/// A permutation argument.
#[derive(Debug, Clone)]
pub(crate) struct Argument {
@ -49,6 +51,23 @@ pub(crate) struct VerifyingKey<C: CurveAffine> {
commitments: Vec<C>,
}
impl<C: CurveAffine> VerifyingKey<C> {
pub(crate) fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
for commitment in &self.commitments {
commitment.write(writer)?;
}
Ok(())
}
pub(crate) fn read<R: io::Read>(reader: &mut R, argument: &Argument) -> io::Result<Self> {
let commitments = (0..argument.columns.len())
.map(|_| C::read(reader))
.collect::<Result<Vec<_>, _>>()?;
Ok(VerifyingKey { commitments })
}
}
/// The proving key for a single permutation argument.
#[derive(Debug)]
pub(crate) struct ProvingKey<C: CurveAffine> {

View file

@ -132,19 +132,15 @@ impl Assembly {
AssemblyHelper { deltaomega }
}
pub(crate) fn build_keys<C: CurveAffine>(
pub(crate) fn build_vk<C: CurveAffine>(
self,
params: &Params<C>,
domain: &EvaluationDomain<C::Scalar>,
helper: &AssemblyHelper<C>,
p: &Argument,
) -> (ProvingKey<C>, VerifyingKey<C>) {
// Compute permutation polynomials, convert to coset form and
// pre-compute commitments for the SRS.
) -> VerifyingKey<C> {
// Pre-compute commitments for the URS.
let mut commitments = vec![];
let mut permutations = vec![];
let mut polys = vec![];
let mut cosets = vec![];
for i in 0..p.columns.len() {
// Computes the permutation polynomial based on the permutation
// description in the assembly.
@ -160,19 +156,39 @@ impl Assembly {
.commit_lagrange(&permutation_poly, Blind::default())
.to_affine(),
);
}
VerifyingKey { commitments }
}
pub(crate) fn build_pk<C: CurveAffine>(
self,
domain: &EvaluationDomain<C::Scalar>,
helper: &AssemblyHelper<C>,
p: &Argument,
) -> ProvingKey<C> {
// Compute permutation polynomials, convert to coset form.
let mut permutations = vec![];
let mut polys = vec![];
let mut cosets = vec![];
for i in 0..p.columns.len() {
// Computes the permutation polynomial based on the permutation
// description in the assembly.
let mut permutation_poly = domain.empty_lagrange();
for (j, p) in permutation_poly.iter_mut().enumerate() {
let (permuted_i, permuted_j) = self.mapping[i][j];
*p = helper.deltaomega[permuted_i][permuted_j];
}
// Store permutation polynomial and precompute its coset evaluation
permutations.push(permutation_poly.clone());
let poly = domain.lagrange_to_coeff(permutation_poly);
polys.push(poly.clone());
cosets.push(domain.coeff_to_extended(poly, Rotation::cur()));
}
(
ProvingKey {
permutations,
polys,
cosets,
},
VerifyingKey { commitments },
)
ProvingKey {
permutations,
polys,
cosets,
}
}
}

View file

@ -20,6 +20,8 @@ pub use msm::MSM;
pub use prover::create_proof;
pub use verifier::{verify_proof, Accumulator, Guard};
use std::io;
/// These are the public parameters for the polynomial commitment scheme.
#[derive(Debug)]
pub struct Params<C: CurveAffine> {
@ -188,6 +190,45 @@ impl<C: CurveAffine> Params<C> {
pub fn get_g(&self) -> Vec<C> {
self.g.clone()
}
/// Writes params to a buffer.
pub fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
writer.write_all(&self.k.to_le_bytes())?;
for g_element in &self.g {
writer.write_all(&g_element.to_bytes())?;
}
for g_lagrange_element in &self.g_lagrange {
writer.write_all(&g_lagrange_element.to_bytes())?;
}
writer.write_all(&self.h.to_bytes())?;
writer.write_all(&self.u.to_bytes())?;
Ok(())
}
/// Reads params from a buffer.
pub fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
let mut k = [0u8; 4];
reader.read_exact(&mut k[..])?;
let k = u32::from_le_bytes(k);
let n: u64 = 1 << k;
let g: Vec<_> = (0..n).map(|_| C::read(reader)).collect::<Result<_, _>>()?;
let g_lagrange: Vec<_> = (0..n).map(|_| C::read(reader)).collect::<Result<_, _>>()?;
let h = C::read(reader)?;
let u = C::read(reader)?;
Ok(Params {
k,
n,
g,
g_lagrange,
h,
u,
})
}
}
/// Wrapper type around a blinding factor.
@ -292,13 +333,17 @@ fn test_opening_proof() {
commitment::{Blind, Params},
EvaluationDomain,
};
use crate::arithmetic::{eval_polynomial, Curve, FieldExt};
use crate::arithmetic::{eval_polynomial, FieldExt};
use crate::pasta::{EpAffine, Fq};
use crate::transcript::{
ChallengeScalar, DummyHashRead, DummyHashWrite, Transcript, TranscriptRead, TranscriptWrite,
};
let params = Params::<EpAffine>::new(K);
let mut params_buffer = vec![];
params.write(&mut params_buffer).unwrap();
let params: Params<EpAffine> = Params::read::<_>(&mut &params_buffer[..]).unwrap();
let domain = EvaluationDomain::new(1, K);
let mut px = domain.empty_coeff();

View file

@ -20,7 +20,7 @@ use std::io;
/// **Important:** This function assumes that the provided `transcript` has
/// already seen the common inputs: the polynomial commitment P, the claimed
/// opening v, and the point x. It's probably also nice for the transcript
/// to have seen the elliptic curve description and the SRS, if you want to
/// to have seen the elliptic curve description and the URS, if you want to
/// be rigorous.
pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
params: &Params<C>,
@ -82,7 +82,7 @@ pub fn create_proof<C: CurveAffine, T: TranscriptWrite<C>>(
}
}
// Initialize the vector `G` from the SRS. We'll be progressively collapsing
// Initialize the vector `G` from the URS. We'll be progressively collapsing
// this vector into smaller and smaller vectors until it is of length 1.
let mut g = params.g.clone();