Clean up API and add comments
This commit is contained in:
parent
462a681a1d
commit
2196f97fb7
@ -184,46 +184,20 @@ fn main() {
|
||||
constants: &constants
|
||||
};
|
||||
|
||||
phase2::new_parameters(c).unwrap()
|
||||
phase2::MPCParameters::new(c).unwrap()
|
||||
};
|
||||
|
||||
let old_params = params.clone();
|
||||
let (pubkey, privkey) = phase2::keypair(rng, ¶ms);
|
||||
params.transform(&pubkey, &privkey);
|
||||
params.contribute(&pubkey, &privkey);
|
||||
|
||||
{
|
||||
let mut w = vec![];
|
||||
pubkey.write(&mut w).unwrap();
|
||||
|
||||
let deser = phase2::PublicKey::read(&w[..]).unwrap();
|
||||
assert!(pubkey == deser);
|
||||
}
|
||||
|
||||
phase2::verify_transform(MiMCDemo::<Bls12> {
|
||||
phase2::verify_contribution(MiMCDemo::<Bls12> {
|
||||
xl: None,
|
||||
xr: None,
|
||||
constants: &constants
|
||||
}, &old_params, ¶ms).unwrap();
|
||||
|
||||
let old_params = params.clone();
|
||||
let (pubkey, privkey) = phase2::keypair(rng, ¶ms);
|
||||
params.transform(&pubkey, &privkey);
|
||||
|
||||
phase2::verify_transform(MiMCDemo::<Bls12> {
|
||||
xl: None,
|
||||
xr: None,
|
||||
constants: &constants
|
||||
}, &old_params, ¶ms).unwrap();
|
||||
|
||||
{
|
||||
let mut w = vec![];
|
||||
params.write(&mut w).unwrap();
|
||||
|
||||
let deser = phase2::MPCParameters::read(&w[..], true).unwrap();
|
||||
assert!(params == deser);
|
||||
}
|
||||
|
||||
let params = params.params();
|
||||
let params = params.get_params();
|
||||
|
||||
// Prepare the verification key (for proof verification)
|
||||
let pvk = prepare_verifying_key(¶ms.vk);
|
||||
|
387
src/lib.rs
387
src/lib.rs
@ -1,3 +1,206 @@
|
||||
//! # zk-SNARK MPCs, made easy.
|
||||
//!
|
||||
//! ## Make your circuit
|
||||
//!
|
||||
//! Grab the [`bellman`](https://github.com/ebfull/bellman) and
|
||||
//! [`pairing`](https://github.com/ebfull/bellman) crates. Bellman
|
||||
//! provides a trait called `Circuit`, which you must implement
|
||||
//! for your computation.
|
||||
//!
|
||||
//! Here's a silly example: proving you know the cube root of
|
||||
//! a field element.
|
||||
//!
|
||||
//! ```rust
|
||||
//! extern crate pairing;
|
||||
//! extern crate bellman;
|
||||
//!
|
||||
//! use pairing::{Engine, Field};
|
||||
//! use bellman::{
|
||||
//! Circuit,
|
||||
//! ConstraintSystem,
|
||||
//! SynthesisError,
|
||||
//! };
|
||||
//!
|
||||
//! struct CubeRoot<E: Engine> {
|
||||
//! cube_root: Option<E::Fr>
|
||||
//! }
|
||||
//!
|
||||
//! impl<E: Engine> Circuit<E> for CubeRoot<E> {
|
||||
//! fn synthesize<CS: ConstraintSystem<E>>(
|
||||
//! self,
|
||||
//! cs: &mut CS
|
||||
//! ) -> Result<(), SynthesisError>
|
||||
//! {
|
||||
//! // Witness the cube root
|
||||
//! let root = cs.alloc(|| "root", || {
|
||||
//! self.cube_root.ok_or(SynthesisError::AssignmentMissing)
|
||||
//! })?;
|
||||
//!
|
||||
//! // Witness the square of the cube root
|
||||
//! let square = cs.alloc(|| "square", || {
|
||||
//! self.cube_root
|
||||
//! .ok_or(SynthesisError::AssignmentMissing)
|
||||
//! .map(|mut root| {root.square(); root })
|
||||
//! })?;
|
||||
//!
|
||||
//! // Enforce that `square` is root^2
|
||||
//! cs.enforce(
|
||||
//! || "squaring",
|
||||
//! |lc| lc + root,
|
||||
//! |lc| lc + root,
|
||||
//! |lc| lc + square
|
||||
//! );
|
||||
//!
|
||||
//! // Witness the cube, as a public input
|
||||
//! let cube = cs.alloc_input(|| "cube", || {
|
||||
//! self.cube_root
|
||||
//! .ok_or(SynthesisError::AssignmentMissing)
|
||||
//! .map(|root| {
|
||||
//! let mut tmp = root;
|
||||
//! tmp.square();
|
||||
//! tmp.mul_assign(&root);
|
||||
//! tmp
|
||||
//! })
|
||||
//! })?;
|
||||
//!
|
||||
//! // Enforce that `cube` is root^3
|
||||
//! // i.e. that `cube` is `root` * `square`
|
||||
//! cs.enforce(
|
||||
//! || "cubing",
|
||||
//! |lc| lc + root,
|
||||
//! |lc| lc + square,
|
||||
//! |lc| lc + cube
|
||||
//! );
|
||||
//!
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Create some proofs
|
||||
//!
|
||||
//! Now that we have `CubeRoot<E>` implementing `Circuit`,
|
||||
//! let's create some parameters and make some proofs.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! extern crate rand;
|
||||
//!
|
||||
//! use pairing::bls12_381::{Bls12, Fr};
|
||||
//! use bellman::groth16::{
|
||||
//! generate_random_parameters,
|
||||
//! create_random_proof,
|
||||
//! prepare_verifying_key,
|
||||
//! verify_proof
|
||||
//! };
|
||||
//! use rand::{OsRng, Rand};
|
||||
//!
|
||||
//! let rng = &mut OsRng::new();
|
||||
//!
|
||||
//! // Create public parameters for our circuit
|
||||
//! let params = {
|
||||
//! let circuit = CubeRoot::<Bls12> {
|
||||
//! cube_root: None
|
||||
//! };
|
||||
//!
|
||||
//! generate_random_parameters::<Bls12, _, _>(
|
||||
//! circuit,
|
||||
//! rng
|
||||
//! ).unwrap()
|
||||
//! };
|
||||
//!
|
||||
//! // Prepare the verifying key for verification
|
||||
//! let pvk = prepare_verifying_key(¶ms.vk);
|
||||
//!
|
||||
//! // Let's start making proofs!
|
||||
//! for _ in 0..50 {
|
||||
//! // Verifier picks a cube in the field.
|
||||
//! // Let's just make a random one.
|
||||
//! let root = Fr::rand(rng);
|
||||
//! let mut cube = root;
|
||||
//! cube.square();
|
||||
//! cube.mul_assign(&root);
|
||||
//!
|
||||
//! // Prover gets the cube, figures out the cube
|
||||
//! // root, and makes the proof:
|
||||
//! let proof = create_random_proof(
|
||||
//! CubeRoot::<Bls12> {
|
||||
//! cube_root: Some(root)
|
||||
//! }, ¶ms, rng
|
||||
//! ).unwrap();
|
||||
//!
|
||||
//! // Verifier checks the proof against the cube
|
||||
//! assert!(verify_proof(&pvk, &proof, &[cube]).unwrap());
|
||||
//! }
|
||||
//! ```
|
||||
//! ## Creating parameters
|
||||
//!
|
||||
//! Notice in the previous example that we created our zk-SNARK
|
||||
//! parameters by calling `generate_random_parameters`. However,
|
||||
//! if you wanted you could have called `generate_parameters`
|
||||
//! with some secret numbers you chose, and kept them for
|
||||
//! yourself. Given those numbers, you can create false proofs.
|
||||
//!
|
||||
//! In order to convince others you didn't, a multi-party
|
||||
//! computation (MPC) can be used. The MPC has the property that
|
||||
//! only one participant needs to be honest for the parameters to
|
||||
//! be secure. This crate (`phase2`) is about creating parameters
|
||||
//! securely using such an MPC.
|
||||
//!
|
||||
//! Let's start by using `phase2` to create some base parameters
|
||||
//! for our circuit:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! extern crate phase2;
|
||||
//!
|
||||
//! let mut params = phase2::MPCParameters::new(CubeRoot {
|
||||
//! cube_root: None
|
||||
//! }).unwrap();
|
||||
//! ```
|
||||
//!
|
||||
//! The first time you try this, it will try to read a file like
|
||||
//! `phase1radix2m2` from the current directory. You need to grab
|
||||
//! that from the Powers of Tau.
|
||||
//!
|
||||
//! These parameters are not safe to use; false proofs can be
|
||||
//! created for them. Let's contribute some randomness to these
|
||||
//! parameters.
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! // Create a random keypair for our parameters
|
||||
//! let (pubkey, privkey) = phase2::keypair(rng, ¶ms);
|
||||
//!
|
||||
//! // Contribute to the parameters. Remember this hash, it's
|
||||
//! // how we know our contribution is in the parameters!
|
||||
//! let hash = params.contribute(&pubkey, &privkey);
|
||||
//!
|
||||
//! // Throw away the private key!
|
||||
//! drop(privkey);
|
||||
//! ```
|
||||
//!
|
||||
//! These parameters are now secure to use, so long as you destroyed
|
||||
//! the privkey. That may not be convincing to others, so let them
|
||||
//! contribute randomness too! `params` can be serialized and sent
|
||||
//! elsewhere, where they can do the same thing and send new
|
||||
//! parameters back to you. Only one person needs to destroy the
|
||||
//! `privkey` for the final parameters to be secure.
|
||||
//!
|
||||
//! Once you're done setting up the parameters, you can verify the
|
||||
//! parameters:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let contributions = params.verify(CubeRoot {
|
||||
//! cube_root: None
|
||||
//! }).expect("parameters should be valid!");
|
||||
//!
|
||||
//! // We need to check the `contributions` to see if our `hash`
|
||||
//! // is in it (see above, when we first contributed)
|
||||
//! assert!(phase2::contains_contribution(&contributions, &hash));
|
||||
//! ```
|
||||
//!
|
||||
//! Great, now if you're happy, grab the Groth16 `Parameters` with
|
||||
//! `params.params()`, so that you can interact with the bellman APIs
|
||||
//! just as before.
|
||||
|
||||
extern crate pairing;
|
||||
extern crate bellman;
|
||||
extern crate rand;
|
||||
@ -181,7 +384,28 @@ impl<E: Engine> ConstraintSystem<E> for KeypairAssembly<E> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_parameters<C>(
|
||||
/// MPC parameters are just like bellman `Parameters` except, when serialized,
|
||||
/// they contain a transcript of contributions at the end, which can be verified.
|
||||
#[derive(Clone)]
|
||||
pub struct MPCParameters {
|
||||
params: Parameters<Bls12>,
|
||||
cs_hash: [u8; 64],
|
||||
contributions: Vec<PublicKey>
|
||||
}
|
||||
|
||||
impl PartialEq for MPCParameters {
|
||||
fn eq(&self, other: &MPCParameters) -> bool {
|
||||
self.params == other.params &&
|
||||
&self.cs_hash[..] == &other.cs_hash[..] &&
|
||||
self.contributions == other.contributions
|
||||
}
|
||||
}
|
||||
|
||||
impl MPCParameters {
|
||||
/// Create new Groth16 parameters (compatible with bellman) for a
|
||||
/// given circuit. The resulting parameters are unsafe to use
|
||||
/// until there are contributions (see `transform`).
|
||||
pub fn new<C>(
|
||||
circuit: C,
|
||||
) -> Result<MPCParameters, SynthesisError>
|
||||
where C: Circuit<Bls12>
|
||||
@ -469,67 +693,22 @@ pub fn new_parameters<C>(
|
||||
})
|
||||
}
|
||||
|
||||
/// MPC parameters are just like bellman `Parameters` except, when serialized,
|
||||
/// they contain a transcript of contributions at the end, which can be verified.
|
||||
#[derive(Clone)]
|
||||
pub struct MPCParameters {
|
||||
params: Parameters<Bls12>,
|
||||
cs_hash: [u8; 64],
|
||||
contributions: Vec<PublicKey>
|
||||
}
|
||||
|
||||
impl PartialEq for MPCParameters {
|
||||
fn eq(&self, other: &MPCParameters) -> bool {
|
||||
self.params == other.params &&
|
||||
&self.cs_hash[..] == &other.cs_hash[..] &&
|
||||
self.contributions == other.contributions
|
||||
}
|
||||
}
|
||||
|
||||
impl MPCParameters {
|
||||
pub fn write<W: Write>(
|
||||
&self,
|
||||
mut writer: W
|
||||
) -> io::Result<()>
|
||||
{
|
||||
self.params.write(&mut writer)?;
|
||||
writer.write_all(&self.cs_hash)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.contributions.len() as u32)?;
|
||||
for pubkey in &self.contributions {
|
||||
pubkey.write(&mut writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(
|
||||
mut reader: R,
|
||||
checked: bool
|
||||
) -> io::Result<MPCParameters>
|
||||
{
|
||||
let params = Parameters::read(&mut reader, checked)?;
|
||||
|
||||
let mut cs_hash = [0u8; 64];
|
||||
reader.read_exact(&mut cs_hash)?;
|
||||
|
||||
let contributions_len = reader.read_u32::<BigEndian>()? as usize;
|
||||
|
||||
let mut contributions = vec![];
|
||||
for _ in 0..contributions_len {
|
||||
contributions.push(PublicKey::read(&mut reader)?);
|
||||
}
|
||||
|
||||
Ok(MPCParameters {
|
||||
params, cs_hash, contributions
|
||||
})
|
||||
}
|
||||
|
||||
pub fn params(&self) -> &Parameters<Bls12> {
|
||||
/// Get the underlying Groth16 `Parameters`
|
||||
pub fn get_params(&self) -> &Parameters<Bls12> {
|
||||
&self.params
|
||||
}
|
||||
|
||||
pub fn transform(
|
||||
/// Contributes some randomness to the parameters. Only one
|
||||
/// contributor needs to destroy their `PrivateKey` to keep
|
||||
/// the parameters secure. See `keypair()` for creating
|
||||
/// keypairs.
|
||||
///
|
||||
/// This function returns a "hash" that is bound to the
|
||||
/// contribution. Contributors can use this hash to make
|
||||
/// sure their contribution is in the final parameters, by
|
||||
/// checking to see if it appears in the output of
|
||||
/// `MPCParameters::verify`.
|
||||
pub fn contribute(
|
||||
&mut self,
|
||||
pubkey: &PublicKey,
|
||||
privkey: &PrivateKey
|
||||
@ -592,6 +771,7 @@ impl MPCParameters {
|
||||
|
||||
self.contributions.push(pubkey.clone());
|
||||
|
||||
// Calculate the hash of the public key and return it
|
||||
{
|
||||
let sink = io::sink();
|
||||
let mut sink = HashWriter::new(sink);
|
||||
@ -603,12 +783,17 @@ impl MPCParameters {
|
||||
}
|
||||
}
|
||||
|
||||
/// Verify the correctness of the parameters, given a circuit
|
||||
/// instance. This will return all of the hashes that
|
||||
/// contributors obtained when they ran
|
||||
/// `MPCParameters::contribute`, for ensuring that contributions
|
||||
/// exist in the final parameters.
|
||||
pub fn verify<C: Circuit<Bls12>>(
|
||||
&self,
|
||||
circuit: C
|
||||
) -> Result<Vec<[u8; 64]>, ()>
|
||||
{
|
||||
let initial_params = new_parameters(circuit).map_err(|_| ())?;
|
||||
let initial_params = MPCParameters::new(circuit).map_err(|_| ())?;
|
||||
|
||||
// H/L will change, but should have same length
|
||||
if initial_params.params.h.len() != self.params.h.len() {
|
||||
@ -732,8 +917,51 @@ impl MPCParameters {
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Serialize these parameters. The serialized parameters
|
||||
/// can be read by bellman as Groth16 `Parameters`.
|
||||
pub fn write<W: Write>(
|
||||
&self,
|
||||
mut writer: W
|
||||
) -> io::Result<()>
|
||||
{
|
||||
self.params.write(&mut writer)?;
|
||||
writer.write_all(&self.cs_hash)?;
|
||||
|
||||
writer.write_u32::<BigEndian>(self.contributions.len() as u32)?;
|
||||
for pubkey in &self.contributions {
|
||||
pubkey.write(&mut writer)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserialize these parameters.
|
||||
pub fn read<R: Read>(
|
||||
mut reader: R,
|
||||
checked: bool
|
||||
) -> io::Result<MPCParameters>
|
||||
{
|
||||
let params = Parameters::read(&mut reader, checked)?;
|
||||
|
||||
let mut cs_hash = [0u8; 64];
|
||||
reader.read_exact(&mut cs_hash)?;
|
||||
|
||||
let contributions_len = reader.read_u32::<BigEndian>()? as usize;
|
||||
|
||||
let mut contributions = vec![];
|
||||
for _ in 0..contributions_len {
|
||||
contributions.push(PublicKey::read(&mut reader)?);
|
||||
}
|
||||
|
||||
Ok(MPCParameters {
|
||||
params, cs_hash, contributions
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// This allows others to verify that you contributed. The hash produced
|
||||
/// by `MPCParameters::contribute` is just a BLAKE2b hash of this object.
|
||||
#[derive(Clone)]
|
||||
pub struct PublicKey {
|
||||
/// This is the delta (in G1) after the transformation, kept so that we
|
||||
@ -755,7 +983,7 @@ pub struct PublicKey {
|
||||
}
|
||||
|
||||
impl PublicKey {
|
||||
pub fn write<W: Write>(
|
||||
fn write<W: Write>(
|
||||
&self,
|
||||
mut writer: W
|
||||
) -> io::Result<()>
|
||||
@ -769,7 +997,7 @@ impl PublicKey {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn read<R: Read>(
|
||||
fn read<R: Read>(
|
||||
mut reader: R
|
||||
) -> io::Result<PublicKey>
|
||||
{
|
||||
@ -823,11 +1051,16 @@ impl PartialEq for PublicKey {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn verify_transform<C: Circuit<Bls12>>(
|
||||
/// Verify a contribution, given the old parameters and
|
||||
/// the new parameters. This is basically a wrapper around
|
||||
/// `MPCParameters::verify` which just checks that a new
|
||||
/// contribution was added and none of the existing
|
||||
/// contributions were changed.
|
||||
pub fn verify_contribution<C: Circuit<Bls12>>(
|
||||
circuit: C,
|
||||
before: &MPCParameters,
|
||||
after: &MPCParameters
|
||||
) -> Result<Vec<[u8; 64]>, ()>
|
||||
) -> Result<[u8; 64], ()>
|
||||
{
|
||||
// Transformation involves a single new object
|
||||
if after.contributions.len() != (before.contributions.len() + 1) {
|
||||
@ -839,7 +1072,7 @@ pub fn verify_transform<C: Circuit<Bls12>>(
|
||||
return Err(());
|
||||
}
|
||||
|
||||
after.verify(circuit)
|
||||
after.verify(circuit).map(|v| *v.last().unwrap())
|
||||
}
|
||||
|
||||
/// Checks if pairs have the same ratio.
|
||||
@ -912,10 +1145,15 @@ fn merge_pairs<G: CurveAffine>(v1: &[G], v2: &[G]) -> (G, G)
|
||||
(s, sx)
|
||||
}
|
||||
|
||||
/// This needs to be destroyed by at least one participant
|
||||
/// for the final parameters to be secure.
|
||||
pub struct PrivateKey {
|
||||
delta: Fr
|
||||
}
|
||||
|
||||
/// Compute a keypair, given the current parameters. Keypairs
|
||||
/// cannot be reused for multiple contributions or contributions
|
||||
/// in different parameters.
|
||||
pub fn keypair<R: Rng>(
|
||||
rng: &mut R,
|
||||
current: &MPCParameters,
|
||||
@ -982,7 +1220,7 @@ fn hash_to_g2(mut digest: &[u8]) -> G2
|
||||
}
|
||||
|
||||
/// Abstraction over a writer which hashes the data being written.
|
||||
pub struct HashWriter<W: Write> {
|
||||
struct HashWriter<W: Write> {
|
||||
writer: W,
|
||||
hasher: Blake2b
|
||||
}
|
||||
@ -1026,3 +1264,20 @@ impl<W: Write> Write for HashWriter<W> {
|
||||
self.writer.flush()
|
||||
}
|
||||
}
|
||||
|
||||
/// This is a cheap helper utility that exists purely
|
||||
/// because Rust still doesn't have type-level integers
|
||||
/// and so doesn't implement `PartialEq` for `[T; 64]`
|
||||
pub fn contains_contribution(
|
||||
contributions: &[[u8; 64]],
|
||||
my_contribution: &[u8; 64]
|
||||
) -> bool
|
||||
{
|
||||
for contrib in contributions {
|
||||
if &contrib[..] == &my_contribution[..] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user