From dfd07cf54b010a633a4c5251a5f19dc713ee0c9b Mon Sep 17 00:00:00 2001 From: Keyvan Kambakhsh Date: Sun, 19 Nov 2023 01:30:08 +0330 Subject: [PATCH 1/3] Update phase2 to compile with latest bellman --- Cargo.toml | 13 +- examples/mimc.rs | 163 ++-- src/lib.rs | 1906 ++++++++++++++++++++++------------------------ 3 files changed, 1016 insertions(+), 1066 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 034af7a..e7319ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "phase2" -version = "0.2.2" +version = "0.3.0" +edition = "2021" authors = ["Sean Bowe "] description = "Library for performing MPCs for creating zk-SNARK public parameters" documentation = "https://docs.rs/phase2" @@ -9,9 +10,13 @@ license = "MIT/Apache-2.0" repository = "https://github.com/ebfull/phase2" [dependencies] -pairing = "0.14" -rand = "0.4" -bellman = "0.1" +pairing = "0.23.0" +rand = "0.8.5" +bellman = "0.14.0" +bls12_381 = "0.8.0" +ff = { version = "0.13" } +group = "0.13.0" +rand_chacha = "0.3.1" byteorder = "1" num_cpus = "1" crossbeam = "0.3" diff --git a/examples/mimc.rs b/examples/mimc.rs index 7000118..4b260c5 100644 --- a/examples/mimc.rs +++ b/examples/mimc.rs @@ -1,45 +1,34 @@ extern crate bellman; extern crate pairing; -extern crate rand; extern crate phase2; +extern crate rand; // For randomness (during paramgen and proof generation) -use rand::{thread_rng, Rng}; +use rand::thread_rng; // For benchmarking use std::time::{Duration, Instant}; // Bring in some tools for using pairing-friendly curves -use pairing::{ - Engine, - Field, -}; +use ff::{Field, PrimeField}; +use pairing::Engine; // We're going to use the BLS12-381 pairing-friendly elliptic curve. -use pairing::bls12_381::{ - Bls12 -}; +use bls12_381::Bls12; // We'll use these interfaces to construct our circuit. -use bellman::{ - Circuit, - ConstraintSystem, - SynthesisError -}; +use bellman::{Circuit, ConstraintSystem, SynthesisError}; // We're going to use the Groth16 proving system. -use bellman::groth16::{ - Proof, - prepare_verifying_key, - create_random_proof, - verify_proof, -}; +use bellman::groth16::{create_random_proof, prepare_verifying_key, verify_proof, Proof}; + +use std::ops::{AddAssign, MulAssign}; const MIMC_ROUNDS: usize = 322; /// This is an implementation of MiMC, specifically a /// variant named `LongsightF322p3` for BLS12-381. -/// See http://eprint.iacr.org/2016/492 for more +/// See http://eprint.iacr.org/2016/492 for more /// information about this construction. /// /// ``` @@ -50,19 +39,14 @@ const MIMC_ROUNDS: usize = 322; /// return xL /// } /// ``` -fn mimc( - mut xl: E::Fr, - mut xr: E::Fr, - constants: &[E::Fr] -) -> E::Fr -{ +fn mimc(mut xl: E::Fr, mut xr: E::Fr, constants: &[E::Fr]) -> E::Fr { assert_eq!(constants.len(), MIMC_ROUNDS); for i in 0..MIMC_ROUNDS { let mut tmp1 = xl; tmp1.add_assign(&constants[i]); let mut tmp2 = tmp1; - tmp2.square(); + tmp2 = tmp2.square(); tmp2.mul_assign(&tmp1); tmp2.add_assign(&xr); xr = xl; @@ -74,83 +58,84 @@ fn mimc( /// This is our demo circuit for proving knowledge of the /// preimage of a MiMC hash invocation. -struct MiMCDemo<'a, E: Engine> { - xl: Option, - xr: Option, - constants: &'a [E::Fr] +struct MiMCDemo<'a, S: PrimeField> { + xl: Option, + xr: Option, + constants: &'a [S], } /// Our demo circuit implements this `Circuit` trait which /// is used during paramgen and proving in order to /// synthesize the constraint system. -impl<'a, E: Engine> Circuit for MiMCDemo<'a, E> { - fn synthesize>( - self, - cs: &mut CS - ) -> Result<(), SynthesisError> - { +impl<'a, S: PrimeField> Circuit for MiMCDemo<'a, S> { + fn synthesize>(self, cs: &mut CS) -> Result<(), SynthesisError> { assert_eq!(self.constants.len(), MIMC_ROUNDS); // Allocate the first component of the preimage. let mut xl_value = self.xl; - let mut xl = cs.alloc(|| "preimage xl", || { - xl_value.ok_or(SynthesisError::AssignmentMissing) - })?; + let mut xl = cs.alloc( + || "preimage xl", + || xl_value.ok_or(SynthesisError::AssignmentMissing), + )?; // Allocate the second component of the preimage. let mut xr_value = self.xr; - let mut xr = cs.alloc(|| "preimage xr", || { - xr_value.ok_or(SynthesisError::AssignmentMissing) - })?; + let mut xr = cs.alloc( + || "preimage xr", + || xr_value.ok_or(SynthesisError::AssignmentMissing), + )?; for i in 0..MIMC_ROUNDS { // xL, xR := xR + (xL + Ci)^3, xL let cs = &mut cs.namespace(|| format!("round {}", i)); // tmp = (xL + Ci)^2 - let mut tmp_value = xl_value.map(|mut e| { + let tmp_value = xl_value.map(|mut e| { e.add_assign(&self.constants[i]); - e.square(); + e = e.square(); e }); - let mut tmp = cs.alloc(|| "tmp", || { - tmp_value.ok_or(SynthesisError::AssignmentMissing) - })?; + let tmp = cs.alloc( + || "tmp", + || tmp_value.ok_or(SynthesisError::AssignmentMissing), + )?; cs.enforce( || "tmp = (xL + Ci)^2", |lc| lc + xl + (self.constants[i], CS::one()), |lc| lc + xl + (self.constants[i], CS::one()), - |lc| lc + tmp + |lc| lc + tmp, ); // new_xL = xR + (xL + Ci)^3 // new_xL = xR + tmp * (xL + Ci) // new_xL - xR = tmp * (xL + Ci) - let mut new_xl_value = xl_value.map(|mut e| { + let new_xl_value = xl_value.map(|mut e| { e.add_assign(&self.constants[i]); e.mul_assign(&tmp_value.unwrap()); e.add_assign(&xr_value.unwrap()); e }); - let mut new_xl = if i == (MIMC_ROUNDS-1) { + let new_xl = if i == (MIMC_ROUNDS - 1) { // This is the last round, xL is our image and so // we allocate a public input. - cs.alloc_input(|| "image", || { - new_xl_value.ok_or(SynthesisError::AssignmentMissing) - })? + cs.alloc_input( + || "image", + || new_xl_value.ok_or(SynthesisError::AssignmentMissing), + )? } else { - cs.alloc(|| "new_xl", || { - new_xl_value.ok_or(SynthesisError::AssignmentMissing) - })? + cs.alloc( + || "new_xl", + || new_xl_value.ok_or(SynthesisError::AssignmentMissing), + )? }; cs.enforce( || "new_xL = xR + (xL + Ci)^3", |lc| lc + tmp, |lc| lc + xl + (self.constants[i], CS::one()), - |lc| lc + new_xl - xr + |lc| lc + new_xl - xr, ); // xR = xL @@ -169,42 +154,52 @@ impl<'a, E: Engine> Circuit for MiMCDemo<'a, E> { fn main() { // This may not be cryptographically safe, use // `OsRng` (for example) in production software. - let rng = &mut thread_rng(); + let mut rng = thread_rng(); // Generate the MiMC round constants - let constants = (0..MIMC_ROUNDS).map(|_| rng.gen()).collect::>(); + let constants = (0..MIMC_ROUNDS) + .map(|_| bls12_381::Scalar::random(&mut rng)) + .collect::>(); println!("Creating parameters..."); // Create parameters for our circuit let mut params = { - let c = MiMCDemo:: { + let c = MiMCDemo:: { xl: None, xr: None, - constants: &constants + constants: &constants, }; phase2::MPCParameters::new(c).unwrap() }; let old_params = params.clone(); - params.contribute(rng); + params.contribute(&mut rng); let first_contrib = phase2::verify_contribution(&old_params, ¶ms).expect("should verify"); let old_params = params.clone(); - params.contribute(rng); + params.contribute(&mut rng); let second_contrib = phase2::verify_contribution(&old_params, ¶ms).expect("should verify"); - let verification_result = params.verify(MiMCDemo:: { - xl: None, - xr: None, - constants: &constants - }).unwrap(); - - assert!(phase2::contains_contribution(&verification_result, &first_contrib)); - assert!(phase2::contains_contribution(&verification_result, &second_contrib)); + let verification_result = params + .verify(MiMCDemo:: { + xl: None, + xr: None, + constants: &constants, + }) + .unwrap(); + + assert!(phase2::contains_contribution( + &verification_result, + &first_contrib + )); + assert!(phase2::contains_contribution( + &verification_result, + &second_contrib + )); let params = params.get_params(); @@ -224,8 +219,8 @@ fn main() { for _ in 0..SAMPLES { // Generate a random preimage and compute the image - let xl = rng.gen(); - let xr = rng.gen(); + let xl = bls12_381::Scalar::random(&mut rng); + let xr = bls12_381::Scalar::random(&mut rng); let image = mimc::(xl, xr, &constants); proof_vec.truncate(0); @@ -237,11 +232,11 @@ fn main() { let c = MiMCDemo { xl: Some(xl), xr: Some(xr), - constants: &constants + constants: &constants, }; // Create a groth16 proof with our parameters. - let proof = create_random_proof(c, params, rng).unwrap(); + let proof = create_random_proof(c, params, &mut rng).unwrap(); proof.write(&mut proof_vec).unwrap(); } @@ -251,20 +246,16 @@ fn main() { let start = Instant::now(); let proof = Proof::read(&proof_vec[..]).unwrap(); // Check the proof - assert!(verify_proof( - &pvk, - &proof, - &[image] - ).unwrap()); + assert!(verify_proof(&pvk, &proof, &[image]).is_ok()); total_verifying += start.elapsed(); } let proving_avg = total_proving / SAMPLES; - let proving_avg = proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 - + (proving_avg.as_secs() as f64); + let proving_avg = + proving_avg.subsec_nanos() as f64 / 1_000_000_000f64 + (proving_avg.as_secs() as f64); let verifying_avg = total_verifying / SAMPLES; - let verifying_avg = verifying_avg.subsec_nanos() as f64 / 1_000_000_000f64 - + (verifying_avg.as_secs() as f64); + let verifying_avg = + verifying_avg.subsec_nanos() as f64 / 1_000_000_000f64 + (verifying_avg.as_secs() as f64); println!("Average proving time: {:?} seconds", proving_avg); println!("Average verifying time: {:?} seconds", verifying_avg); diff --git a/src/lib.rs b/src/lib.rs index e091603..8357855 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,20 +13,20 @@ //! ```rust //! extern crate pairing; //! extern crate bellman; -//! -//! use pairing::{Engine, Field}; +//! +//! use ff::PrimeField; //! use bellman::{ //! Circuit, //! ConstraintSystem, //! SynthesisError, //! }; -//! -//! struct CubeRoot { -//! cube_root: Option +//! +//! struct CubeRoot { +//! cube_root: Option //! } -//! -//! impl Circuit for CubeRoot { -//! fn synthesize>( +//! +//! impl Circuit for CubeRoot { +//! fn synthesize>( //! self, //! cs: &mut CS //! ) -> Result<(), SynthesisError> @@ -35,34 +35,34 @@ //! 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 }) +//! .map(|mut root| { root.square() }) //! })?; -//! +//! //! // Enforce that `square` is root^2 //! cs.enforce( //! || "squaring", //! |lc| lc + root, //! |lc| lc + root, -//! |lc| lc + square +//! |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 = tmp.square(); //! tmp.mul_assign(&root); //! tmp //! }) //! })?; -//! +//! //! // Enforce that `cube` is root^3 //! // i.e. that `cube` is `root` * `square` //! cs.enforce( @@ -83,53 +83,52 @@ //! let's create some parameters and make some proofs. //! //! ```rust,ignore -//! extern crate rand; -//! -//! use pairing::bls12_381::{Bls12, Fr}; +//! use bls12_381::{Bls12, Scalar}; +//! use ff::Field; +//! use std::ops::MulAssign; //! use bellman::groth16::{ //! generate_random_parameters, //! create_random_proof, //! prepare_verifying_key, //! verify_proof //! }; -//! use rand::{OsRng, Rand}; -//! -//! let rng = &mut OsRng::new(); -//! +//! +//! let rng = &mut rand::thread_rng(); +//! //! // Create public parameters for our circuit //! let params = { -//! let circuit = CubeRoot:: { +//! let circuit = CubeRoot:: { //! cube_root: None //! }; -//! +//! //! generate_random_parameters::( //! 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 root = Scalar::random(rng); //! let mut cube = root; -//! cube.square(); +//! cube = 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:: { +//! CubeRoot:: { //! cube_root: Some(root) //! }, ¶ms, rng //! ).unwrap(); -//! +//! //! // Verifier checks the proof against the cube -//! assert!(verify_proof(&pvk, &proof, &[cube]).unwrap()); +//! assert!(verify_proof(&pvk, &proof, &[cube]).is_ok()); //! } //! ``` //! ## Creating parameters @@ -160,7 +159,7 @@ //! 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](https://lists.z.cash.foundation/pipermail/zapps-wg/2018/000362.html). -//! +//! //! These parameters are not safe to use; false proofs can be //! created for them. Let's contribute some randomness to these //! parameters. @@ -195,101 +194,43 @@ //! `params.params()`, so that you can interact with the bellman APIs //! just as before. -extern crate pairing; -extern crate bellman; -extern crate rand; -extern crate byteorder; -extern crate blake2_rfc; -extern crate num_cpus; -extern crate crossbeam; - +use bellman::groth16::{Parameters, VerifyingKey}; +use bellman::multicore::Worker; +use bellman::{Circuit, ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; use blake2_rfc::blake2b::Blake2b; - -use byteorder::{ - BigEndian, - ReadBytesExt, - WriteBytesExt -}; - -use std::{ - io::{ - self, - Read, - Write, - BufReader - }, - fs::{ - File - }, - sync::{ - Arc - } -}; - -use pairing::{ - Engine, - PrimeField, - Field, - EncodedPoint, - CurveAffine, - CurveProjective, - Wnaf, - bls12_381::{ - Bls12, - Fr, - G1, - G2, - G1Affine, - G1Uncompressed, - G2Affine, - G2Uncompressed - } -}; - -use bellman::{ - Circuit, - SynthesisError, - Variable, - Index, - ConstraintSystem, - LinearCombination, - groth16::{ - Parameters, - VerifyingKey - }, - multicore::Worker -}; - -use rand::{ - Rng, - Rand, - ChaChaRng, - SeedableRng -}; - -/// This is our assembly structure that we'll use to synthesize the -/// circuit into a QAP. -struct KeypairAssembly { +use bls12_381::Bls12; +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use ff::{Field, PrimeField}; +use group::{prime::PrimeCurveAffine, Wnaf, WnafGroup}; +use pairing::group::{Curve, Group, UncompressedEncoding}; +use rand::{Rng, SeedableRng}; +use rand_chacha::ChaChaRng; +use std::fs::File; +use std::io; +use std::io::{BufReader, Read, Write}; +use std::ops::{AddAssign, Mul}; +use std::sync::Arc; + +struct KeypairAssembly { num_inputs: usize, num_aux: usize, num_constraints: usize, - at_inputs: Vec>, - bt_inputs: Vec>, - ct_inputs: Vec>, - at_aux: Vec>, - bt_aux: Vec>, - ct_aux: Vec> + at_inputs: Vec>, + bt_inputs: Vec>, + ct_inputs: Vec>, + at_aux: Vec>, + bt_aux: Vec>, + ct_aux: Vec>, } -impl ConstraintSystem for KeypairAssembly { +impl ConstraintSystem for KeypairAssembly { type Root = Self; - fn alloc( - &mut self, - _: A, - _: F - ) -> Result - where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + fn alloc(&mut self, _: A, _: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, { // There is no assignment, so we don't even invoke the // function for obtaining one. @@ -303,13 +244,11 @@ impl ConstraintSystem for KeypairAssembly { Ok(Variable::new_unchecked(Index::Aux(index))) } - - fn alloc_input( - &mut self, - _: A, - _: F - ) -> Result - where F: FnOnce() -> Result, A: FnOnce() -> AR, AR: Into + fn alloc_input(&mut self, _: A, _: F) -> Result + where + F: FnOnce() -> Result, + A: FnOnce() -> AR, + AR: Into, { // There is no assignment, so we don't even invoke the // function for obtaining one. @@ -324,48 +263,59 @@ impl ConstraintSystem for KeypairAssembly { Ok(Variable::new_unchecked(Index::Input(index))) } - fn enforce( - &mut self, - _: A, - a: LA, - b: LB, - c: LC - ) - where A: FnOnce() -> AR, AR: Into, - LA: FnOnce(LinearCombination) -> LinearCombination, - LB: FnOnce(LinearCombination) -> LinearCombination, - LC: FnOnce(LinearCombination) -> LinearCombination + fn enforce(&mut self, _: A, a: LA, b: LB, c: LC) + where + A: FnOnce() -> AR, + AR: Into, + LA: FnOnce(LinearCombination) -> LinearCombination, + LB: FnOnce(LinearCombination) -> LinearCombination, + LC: FnOnce(LinearCombination) -> LinearCombination, { - fn eval( - l: LinearCombination, - inputs: &mut [Vec<(E::Fr, usize)>], - aux: &mut [Vec<(E::Fr, usize)>], - this_constraint: usize - ) - { + fn eval( + l: LinearCombination, + inputs: &mut [Vec<(Fr, usize)>], + aux: &mut [Vec<(Fr, usize)>], + this_constraint: usize, + ) { for &(var, coeff) in l.as_ref() { match var.get_unchecked() { Index::Input(id) => inputs[id].push((coeff, this_constraint)), - Index::Aux(id) => aux[id].push((coeff, this_constraint)) + Index::Aux(id) => aux[id].push((coeff, this_constraint)), } } } - eval(a(LinearCombination::zero()), &mut self.at_inputs, &mut self.at_aux, self.num_constraints); - eval(b(LinearCombination::zero()), &mut self.bt_inputs, &mut self.bt_aux, self.num_constraints); - eval(c(LinearCombination::zero()), &mut self.ct_inputs, &mut self.ct_aux, self.num_constraints); + eval( + a(LinearCombination::zero()), + &mut self.at_inputs, + &mut self.at_aux, + self.num_constraints, + ); + eval( + b(LinearCombination::zero()), + &mut self.bt_inputs, + &mut self.bt_aux, + self.num_constraints, + ); + eval( + c(LinearCombination::zero()), + &mut self.ct_inputs, + &mut self.ct_aux, + self.num_constraints, + ); self.num_constraints += 1; } fn push_namespace(&mut self, _: N) - where NR: Into, N: FnOnce() -> NR + where + NR: Into, + N: FnOnce() -> NR, { // Do nothing; we don't care about namespaces in this context. } - fn pop_namespace(&mut self) - { + fn pop_namespace(&mut self) { // Do nothing; we don't care about namespaces in this context. } @@ -374,1013 +324,1017 @@ impl ConstraintSystem for KeypairAssembly { } } -/// MPC parameters are just like bellman `Parameters` except, when serialized, -/// they contain a transcript of contributions at the end, which can be verified. +/// This allows others to verify that you contributed. The hash produced +/// by `MPCParameters::contribute` is just a BLAKE2b hash of this object. +#[derive(Clone)] +struct PublicKey { + /// This is the delta (in G1) after the transformation, kept so that we + /// can check correctness of the public keys without having the entire + /// interstitial parameters for each contribution. + delta_after: bls12_381::G1Affine, + + /// Random element chosen by the contributor. + s: bls12_381::G1Affine, + + /// That element, taken to the contributor's secret delta. + s_delta: bls12_381::G1Affine, + + /// r is H(last_pubkey | s | s_delta), r_delta proves knowledge of delta + r_delta: bls12_381::G2Affine, + + /// Hash of the transcript (used for mapping to r) + transcript: [u8; 64], +} + +impl PartialEq for PublicKey { + fn eq(&self, other: &PublicKey) -> bool { + self.delta_after == other.delta_after + && self.s == other.s + && self.s_delta == other.s_delta + && self.r_delta == other.r_delta + && &self.transcript[..] == &other.transcript[..] + } +} + #[derive(Clone)] pub struct MPCParameters { params: Parameters, cs_hash: [u8; 64], - contributions: Vec + contributions: Vec, } impl PartialEq for MPCParameters { fn eq(&self, other: &MPCParameters) -> bool { - self.params == other.params && - &self.cs_hash[..] == &other.cs_hash[..] && - self.contributions == other.contributions + 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 `contribute()`). - pub fn new( - circuit: C, - ) -> Result - where C: Circuit - { - let mut assembly = KeypairAssembly { - num_inputs: 0, - num_aux: 0, - num_constraints: 0, - at_inputs: vec![], - bt_inputs: vec![], - ct_inputs: vec![], - at_aux: vec![], - bt_aux: vec![], - ct_aux: vec![] - }; +impl PublicKey { + fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(self.delta_after.to_uncompressed().as_ref())?; + writer.write_all(self.s.to_uncompressed().as_ref())?; + writer.write_all(self.s_delta.to_uncompressed().as_ref())?; + writer.write_all(self.r_delta.to_uncompressed().as_ref())?; + writer.write_all(&self.transcript)?; - // Allocate the "one" input variable - assembly.alloc_input(|| "", || Ok(Fr::one()))?; + Ok(()) + } - // Synthesize the circuit. - circuit.synthesize(&mut assembly)?; + fn read(mut reader: R) -> io::Result { + let mut g1_repr = ::Uncompressed::default(); + let mut g2_repr = ::Uncompressed::default(); - // Input constraints to ensure full density of IC query - // x * 0 = 0 - for i in 0..assembly.num_inputs { - assembly.enforce(|| "", - |lc| lc + Variable::new_unchecked(Index::Input(i)), - |lc| lc, - |lc| lc, - ); + reader.read_exact(g1_repr.as_mut())?; + let delta_after: bls12_381::G1Affine = Option::from( + ::from_uncompressed(&g1_repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid Data!"))?; + + if delta_after.is_identity().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); } - // Compute the size of our evaluation domain - let mut m = 1; - let mut exp = 0; - while m < assembly.num_constraints { - m *= 2; - exp += 1; + reader.read_exact(g1_repr.as_mut())?; + let s: bls12_381::G1Affine = Option::from( + ::from_uncompressed(&g1_repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid Data!"))?; - // Powers of Tau ceremony can't support more than 2^21 - if exp > 21 { - return Err(SynthesisError::PolynomialDegreeTooLarge) - } + if s.is_identity().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); } - // Try to load "phase1radix2m{}" - let f = match File::open(format!("phase1radix2m{}", exp)) { - Ok(f) => f, - Err(e) => { - panic!("Couldn't load phase1radix2m{}: {:?}", exp, e); - } - }; - let f = &mut BufReader::with_capacity(1024 * 1024, f); + reader.read_exact(g1_repr.as_mut())?; + let s_delta: bls12_381::G1Affine = Option::from( + ::from_uncompressed(&g1_repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid Data!"))?; - let read_g1 = |reader: &mut BufReader| -> io::Result { - let mut repr = G1Uncompressed::empty(); - reader.read_exact(repr.as_mut())?; + if s_delta.is_identity().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); + } - repr.into_affine_unchecked() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - .and_then(|e| if e.is_zero() { - Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")) - } else { - Ok(e) - }) - }; + reader.read_exact(g2_repr.as_mut())?; + let r_delta: bls12_381::G2Affine = Option::from( + ::from_uncompressed(&g2_repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid Data!"))?; - let read_g2 = |reader: &mut BufReader| -> io::Result { - let mut repr = G2Uncompressed::empty(); - reader.read_exact(repr.as_mut())?; + if r_delta.is_identity().into() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); + } - repr.into_affine_unchecked() - .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e)) - .and_then(|e| if e.is_zero() { - Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")) - } else { - Ok(e) - }) - }; + let mut transcript = [0u8; 64]; + reader.read_exact(&mut transcript)?; - let alpha = read_g1(f)?; - let beta_g1 = read_g1(f)?; - let beta_g2 = read_g2(f)?; + Ok(PublicKey { + delta_after, + s, + s_delta, + r_delta, + transcript, + }) + } +} - let mut coeffs_g1 = Vec::with_capacity(m); - for _ in 0..m { - coeffs_g1.push(read_g1(f)?); - } +/// Abstraction over a writer which hashes the data being written. +struct HashWriter { + writer: W, + hasher: Blake2b, +} - let mut coeffs_g2 = Vec::with_capacity(m); - for _ in 0..m { - coeffs_g2.push(read_g2(f)?); +impl Clone for HashWriter { + fn clone(&self) -> HashWriter { + HashWriter { + writer: io::sink(), + hasher: self.hasher.clone(), } + } +} - let mut alpha_coeffs_g1 = Vec::with_capacity(m); - for _ in 0..m { - alpha_coeffs_g1.push(read_g1(f)?); +impl HashWriter { + /// Construct a new `HashWriter` given an existing `writer` by value. + pub fn new(writer: W) -> Self { + HashWriter { + writer: writer, + hasher: Blake2b::new(64), } + } - let mut beta_coeffs_g1 = Vec::with_capacity(m); - for _ in 0..m { - beta_coeffs_g1.push(read_g1(f)?); - } + /// Destroy this writer and return the hash of what was written. + pub fn into_hash(self) -> [u8; 64] { + let mut tmp = [0u8; 64]; + tmp.copy_from_slice(self.hasher.finalize().as_ref()); + tmp + } +} - // These are `Arc` so that later it'll be easier - // to use multiexp during QAP evaluation (which - // requires a futures-based API) - let coeffs_g1 = Arc::new(coeffs_g1); - let coeffs_g2 = Arc::new(coeffs_g2); - let alpha_coeffs_g1 = Arc::new(alpha_coeffs_g1); - let beta_coeffs_g1 = Arc::new(beta_coeffs_g1); +impl Write for HashWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let bytes = self.writer.write(buf)?; - let mut h = Vec::with_capacity(m - 1); - for _ in 0..(m - 1) { - h.push(read_g1(f)?); + if bytes > 0 { + self.hasher.update(&buf[0..bytes]); } - let mut ic = vec![G1::zero(); assembly.num_inputs]; - let mut l = vec![G1::zero(); assembly.num_aux]; - let mut a_g1 = vec![G1::zero(); assembly.num_inputs + assembly.num_aux]; - let mut b_g1 = vec![G1::zero(); assembly.num_inputs + assembly.num_aux]; - let mut b_g2 = vec![G2::zero(); assembly.num_inputs + assembly.num_aux]; + Ok(bytes) + } - fn eval( - // Lagrange coefficients for tau - coeffs_g1: Arc>, - coeffs_g2: Arc>, - alpha_coeffs_g1: Arc>, - beta_coeffs_g1: Arc>, + fn flush(&mut self) -> io::Result<()> { + self.writer.flush() + } +} - // QAP polynomials - at: &[Vec<(Fr, usize)>], - bt: &[Vec<(Fr, usize)>], - ct: &[Vec<(Fr, usize)>], +fn hash_to_g2(digest: &[u8]) -> bls12_381::G2Projective { + assert!(digest.len() >= 32); + let mut seed = [0u8; 32]; + seed.copy_from_slice(&digest[..32]); + bls12_381::G2Projective::random(&mut ChaChaRng::from_seed(seed)) +} - // Resulting evaluated QAP polynomials - a_g1: &mut [G1], - b_g1: &mut [G1], - b_g2: &mut [G2], - ext: &mut [G1], +/// Verify a contribution, given the old parameters and +/// the new parameters. Returns the hash of the contribution. +pub fn verify_contribution(before: &MPCParameters, after: &MPCParameters) -> Result<[u8; 64], ()> { + // Transformation involves a single new object + if after.contributions.len() != (before.contributions.len() + 1) { + return Err(()); + } - // Worker - worker: &Worker - ) - { - // Sanity check - assert_eq!(a_g1.len(), at.len()); - assert_eq!(a_g1.len(), bt.len()); - assert_eq!(a_g1.len(), ct.len()); - assert_eq!(a_g1.len(), b_g1.len()); - assert_eq!(a_g1.len(), b_g2.len()); - assert_eq!(a_g1.len(), ext.len()); + // None of the previous transformations should change + if &before.contributions[..] != &after.contributions[0..before.contributions.len()] { + return Err(()); + } - // Evaluate polynomials in multiple threads - worker.scope(a_g1.len(), |scope, chunk| { - for ((((((a_g1, b_g1), b_g2), ext), at), bt), ct) in - a_g1.chunks_mut(chunk) - .zip(b_g1.chunks_mut(chunk)) - .zip(b_g2.chunks_mut(chunk)) - .zip(ext.chunks_mut(chunk)) - .zip(at.chunks(chunk)) - .zip(bt.chunks(chunk)) - .zip(ct.chunks(chunk)) - { - let coeffs_g1 = coeffs_g1.clone(); - let coeffs_g2 = coeffs_g2.clone(); - let alpha_coeffs_g1 = alpha_coeffs_g1.clone(); - let beta_coeffs_g1 = beta_coeffs_g1.clone(); + // H/L will change, but should have same length + if before.params.h.len() != after.params.h.len() { + return Err(()); + } + if before.params.l.len() != after.params.l.len() { + return Err(()); + } - scope.spawn(move || { - for ((((((a_g1, b_g1), b_g2), ext), at), bt), ct) in - a_g1.iter_mut() - .zip(b_g1.iter_mut()) - .zip(b_g2.iter_mut()) - .zip(ext.iter_mut()) - .zip(at.iter()) - .zip(bt.iter()) - .zip(ct.iter()) - { - for &(coeff, lag) in at { - a_g1.add_assign(&coeffs_g1[lag].mul(coeff)); - ext.add_assign(&beta_coeffs_g1[lag].mul(coeff)); - } + // A/B_G1/B_G2 doesn't change at all + if before.params.a != after.params.a { + return Err(()); + } + if before.params.b_g1 != after.params.b_g1 { + return Err(()); + } + if before.params.b_g2 != after.params.b_g2 { + return Err(()); + } - for &(coeff, lag) in bt { - b_g1.add_assign(&coeffs_g1[lag].mul(coeff)); - b_g2.add_assign(&coeffs_g2[lag].mul(coeff)); - ext.add_assign(&alpha_coeffs_g1[lag].mul(coeff)); - } - - for &(coeff, lag) in ct { - ext.add_assign(&coeffs_g1[lag].mul(coeff)); - } - } + // alpha/beta/gamma don't change + if before.params.vk.alpha_g1 != after.params.vk.alpha_g1 { + return Err(()); + } + if before.params.vk.beta_g1 != after.params.vk.beta_g1 { + return Err(()); + } + if before.params.vk.beta_g2 != after.params.vk.beta_g2 { + return Err(()); + } + if before.params.vk.gamma_g2 != after.params.vk.gamma_g2 { + return Err(()); + } - // Batch normalize - G1::batch_normalization(a_g1); - G1::batch_normalization(b_g1); - G2::batch_normalization(b_g2); - G1::batch_normalization(ext); - }); - } - }); - } + // IC shouldn't change, as gamma doesn't change + if before.params.vk.ic != after.params.vk.ic { + return Err(()); + } - let worker = Worker::new(); + // cs_hash should be the same + if &before.cs_hash[..] != &after.cs_hash[..] { + return Err(()); + } - // Evaluate for inputs. - eval( - coeffs_g1.clone(), - coeffs_g2.clone(), - alpha_coeffs_g1.clone(), - beta_coeffs_g1.clone(), - &assembly.at_inputs, - &assembly.bt_inputs, - &assembly.ct_inputs, - &mut a_g1[0..assembly.num_inputs], - &mut b_g1[0..assembly.num_inputs], - &mut b_g2[0..assembly.num_inputs], - &mut ic, - &worker - ); + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + sink.write_all(&before.cs_hash[..]).unwrap(); - // Evaluate for auxillary variables. - eval( - coeffs_g1.clone(), - coeffs_g2.clone(), - alpha_coeffs_g1.clone(), - beta_coeffs_g1.clone(), - &assembly.at_aux, - &assembly.bt_aux, - &assembly.ct_aux, - &mut a_g1[assembly.num_inputs..], - &mut b_g1[assembly.num_inputs..], - &mut b_g2[assembly.num_inputs..], - &mut l, - &worker - ); + for pubkey in &before.contributions { + pubkey.write(&mut sink).unwrap(); + } - // Don't allow any elements be unconstrained, so that - // the L query is always fully dense. - for e in l.iter() { - if e.is_zero() { - return Err(SynthesisError::UnconstrainedVariable); - } - } + let pubkey = after.contributions.last().unwrap(); + sink.write_all(pubkey.s.to_uncompressed().as_ref()).unwrap(); + sink.write_all(pubkey.s_delta.to_uncompressed().as_ref()) + .unwrap(); - let vk = VerifyingKey { - alpha_g1: alpha, - beta_g1: beta_g1, - beta_g2: beta_g2, - gamma_g2: G2Affine::one(), - delta_g1: G1Affine::one(), - delta_g2: G2Affine::one(), - ic: ic.into_iter().map(|e| e.into_affine()).collect() - }; + let h = sink.into_hash(); - let params = Parameters { - vk: vk, - h: Arc::new(h), - l: Arc::new(l.into_iter().map(|e| e.into_affine()).collect()), + // The transcript must be consistent + if &pubkey.transcript[..] != h.as_ref() { + return Err(()); + } - // Filter points at infinity away from A/B queries - a: Arc::new(a_g1.into_iter().filter(|e| !e.is_zero()).map(|e| e.into_affine()).collect()), - b_g1: Arc::new(b_g1.into_iter().filter(|e| !e.is_zero()).map(|e| e.into_affine()).collect()), - b_g2: Arc::new(b_g2.into_iter().filter(|e| !e.is_zero()).map(|e| e.into_affine()).collect()) - }; + let r = hash_to_g2(h.as_ref()).to_affine(); - let h = { - let sink = io::sink(); - let mut sink = HashWriter::new(sink); + // Check the signature of knowledge + if !same_ratio((r, pubkey.r_delta), (pubkey.s, pubkey.s_delta)) { + return Err(()); + } - params.write(&mut sink).unwrap(); + // Check the change from the old delta is consistent + if !same_ratio( + (before.params.vk.delta_g1, pubkey.delta_after), + (r, pubkey.r_delta), + ) { + return Err(()); + } - sink.into_hash() - }; + // Current parameters should have consistent delta in G1 + if pubkey.delta_after != after.params.vk.delta_g1 { + return Err(()); + } - let mut cs_hash = [0; 64]; - cs_hash.copy_from_slice(h.as_ref()); + // Current parameters should have consistent delta in G2 + if !same_ratio( + (bls12_381::G1Affine::generator(), pubkey.delta_after), + (bls12_381::G2Affine::generator(), after.params.vk.delta_g2), + ) { + return Err(()); + } - Ok(MPCParameters { - params: params, - cs_hash: cs_hash, - contributions: vec![] - }) + // H and L queries should be updated with delta^-1 + if !same_ratio( + merge_pairs(&before.params.h, &after.params.h), + (after.params.vk.delta_g2, before.params.vk.delta_g2), // reversed for inverse + ) { + return Err(()); } - /// Get the underlying Groth16 `Parameters` - pub fn get_params(&self) -> &Parameters { - &self.params + if !same_ratio( + merge_pairs(&before.params.l, &after.params.l), + (after.params.vk.delta_g2, before.params.vk.delta_g2), // reversed for inverse + ) { + return Err(()); } - /// Contributes some randomness to the parameters. Only one - /// contributor needs to be honest for the parameters to be - /// secure. - /// - /// 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, - rng: &mut R - ) -> [u8; 64] - { - // Generate a keypair - let (pubkey, privkey) = keypair(rng, self); + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + pubkey.write(&mut sink).unwrap(); + let h = sink.into_hash(); + let mut response = [0u8; 64]; + response.copy_from_slice(h.as_ref()); - fn batch_exp(bases: &mut [C], coeff: C::Scalar) { - let coeff = coeff.into_repr(); + Ok(response) +} - let mut projective = vec![C::Projective::zero(); bases.len()]; - let cpus = num_cpus::get(); - let chunk_size = if bases.len() < cpus { - 1 - } else { - bases.len() / cpus - }; +fn same_ratio(g1: (G1, G1), g2: (G1::Pair, G1::Pair)) -> bool { + g1.0.pairing_with(&g2.1) == g1.1.pairing_with(&g2.0) +} - // Perform wNAF over multiple cores, placing results into `projective`. - crossbeam::scope(|scope| { - for (bases, projective) in bases.chunks_mut(chunk_size) - .zip(projective.chunks_mut(chunk_size)) - { - scope.spawn(move || { - let mut wnaf = Wnaf::new(); +fn merge_pairs(v1: &[G], v2: &[G]) -> (G, G) +where + G::Curve: WnafGroup, +{ + use rand::thread_rng; + use std::sync::Mutex; - for (base, projective) in bases.iter_mut() - .zip(projective.iter_mut()) - { - *projective = wnaf.base(base.into_projective(), 1).scalar(coeff); - } - }); - } - }); + assert_eq!(v1.len(), v2.len()); - // Perform batch normalization - crossbeam::scope(|scope| { - for projective in projective.chunks_mut(chunk_size) - { - scope.spawn(move || { - C::Projective::batch_normalization(projective); - }); - } - }); + let chunk = (v1.len() / num_cpus::get()) + 1; - // Turn it all back into affine points - for (projective, affine) in projective.iter().zip(bases.iter_mut()) { - *affine = projective.into_affine(); - } - } + let s = Arc::new(Mutex::new(G::Curve::identity())); + let sx = Arc::new(Mutex::new(G::Curve::identity())); - let delta_inv = privkey.delta.inverse().expect("nonzero"); - let mut l = (&self.params.l[..]).to_vec(); - let mut h = (&self.params.h[..]).to_vec(); - batch_exp(&mut l, delta_inv); - batch_exp(&mut h, delta_inv); - self.params.l = Arc::new(l); - self.params.h = Arc::new(h); + crossbeam::scope(|scope| { + for (v1, v2) in v1.chunks(chunk).zip(v2.chunks(chunk)) { + let s = s.clone(); + let sx = sx.clone(); - self.params.vk.delta_g1 = self.params.vk.delta_g1.mul(privkey.delta).into_affine(); - self.params.vk.delta_g2 = self.params.vk.delta_g2.mul(privkey.delta).into_affine(); + scope.spawn(move || { + // We do not need to be overly cautious of the RNG + // used for this check. + let rng = &mut thread_rng(); - self.contributions.push(pubkey.clone()); + let mut wnaf = Wnaf::new(); + let mut local_s = G::Curve::identity(); + let mut local_sx = G::Curve::identity(); - // Calculate the hash of the public key and return it - { - let sink = io::sink(); - let mut sink = HashWriter::new(sink); - pubkey.write(&mut sink).unwrap(); - let h = sink.into_hash(); - let mut response = [0u8; 64]; - response.copy_from_slice(h.as_ref()); - response - } - } + for (v1, v2) in v1.iter().zip(v2.iter()) { + let rho = G::Scalar::random(&mut *rng); + let mut wnaf = wnaf.scalar(&rho); + let v1 = wnaf.base(v1.to_curve()); + let v2 = wnaf.base(v2.to_curve()); - /// 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>( - &self, - circuit: C - ) -> Result, ()> - { - let initial_params = MPCParameters::new(circuit).map_err(|_| ())?; + local_s.add_assign(&v1); + local_sx.add_assign(&v2); + } - // H/L will change, but should have same length - if initial_params.params.h.len() != self.params.h.len() { - return Err(()); - } - if initial_params.params.l.len() != self.params.l.len() { - return Err(()); + s.lock().unwrap().add_assign(&local_s); + sx.lock().unwrap().add_assign(&local_sx); + }); } + }); - // A/B_G1/B_G2 doesn't change at all - if initial_params.params.a != self.params.a { - return Err(()); - } - if initial_params.params.b_g1 != self.params.b_g1 { - return Err(()); - } - if initial_params.params.b_g2 != self.params.b_g2 { - return Err(()); - } + let s = s.lock().unwrap().to_affine(); + let sx = sx.lock().unwrap().to_affine(); - // alpha/beta/gamma don't change - if initial_params.params.vk.alpha_g1 != self.params.vk.alpha_g1 { - return Err(()); - } - if initial_params.params.vk.beta_g1 != self.params.vk.beta_g1 { - return Err(()); - } - if initial_params.params.vk.beta_g2 != self.params.vk.beta_g2 { - return Err(()); - } - if initial_params.params.vk.gamma_g2 != self.params.vk.gamma_g2 { - return Err(()); - } + (s, sx) +} - // IC shouldn't change, as gamma doesn't change - if initial_params.params.vk.ic != self.params.vk.ic { - return Err(()); - } +/// This needs to be destroyed by at least one participant +/// for the final parameters to be secure. +struct PrivateKey { + delta: bls12_381::Scalar, +} - // cs_hash should be the same - if &initial_params.cs_hash[..] != &self.cs_hash[..] { - return Err(()); - } +/// Compute a keypair, given the current parameters. Keypairs +/// cannot be reused for multiple contributions or contributions +/// in different parameters. +fn keypair(rng: &mut R, current: &MPCParameters) -> (PublicKey, PrivateKey) { + // Sample random delta + let delta: bls12_381::Scalar = bls12_381::Scalar::random(&mut *rng); + // Compute delta s-pair in G1 + let s = bls12_381::G1Projective::random(rng).to_affine(); + let s_delta = s.mul(delta).to_affine(); + + // H(cs_hash | | s | s_delta) + let h = { let sink = io::sink(); let mut sink = HashWriter::new(sink); - sink.write_all(&initial_params.cs_hash[..]).unwrap(); - - let mut current_delta = G1Affine::one(); - let mut result = vec![]; - - for pubkey in &self.contributions { - let mut our_sink = sink.clone(); - our_sink.write_all(pubkey.s.into_uncompressed().as_ref()).unwrap(); - our_sink.write_all(pubkey.s_delta.into_uncompressed().as_ref()).unwrap(); + sink.write_all(¤t.cs_hash[..]).unwrap(); + for pubkey in ¤t.contributions { pubkey.write(&mut sink).unwrap(); - - let h = our_sink.into_hash(); - - // The transcript must be consistent - if &pubkey.transcript[..] != h.as_ref() { - return Err(()); - } - - let r = hash_to_g2(h.as_ref()).into_affine(); - - // Check the signature of knowledge - if !same_ratio((r, pubkey.r_delta), (pubkey.s, pubkey.s_delta)) { - return Err(()); - } - - // Check the change from the old delta is consistent - if !same_ratio( - (current_delta, pubkey.delta_after), - (r, pubkey.r_delta) - ) { - return Err(()); - } - - current_delta = pubkey.delta_after; - - { - let sink = io::sink(); - let mut sink = HashWriter::new(sink); - pubkey.write(&mut sink).unwrap(); - let h = sink.into_hash(); - let mut response = [0u8; 64]; - response.copy_from_slice(h.as_ref()); - result.push(response); - } - } - - // Current parameters should have consistent delta in G1 - if current_delta != self.params.vk.delta_g1 { - return Err(()); - } - - // Current parameters should have consistent delta in G2 - if !same_ratio( - (G1Affine::one(), current_delta), - (G2Affine::one(), self.params.vk.delta_g2) - ) { - return Err(()); - } - - // H and L queries should be updated with delta^-1 - if !same_ratio( - merge_pairs(&initial_params.params.h, &self.params.h), - (self.params.vk.delta_g2, G2Affine::one()) // reversed for inverse - ) { - return Err(()); } + sink.write_all(s.to_uncompressed().as_ref()).unwrap(); + sink.write_all(s_delta.to_uncompressed().as_ref()).unwrap(); - if !same_ratio( - merge_pairs(&initial_params.params.l, &self.params.l), - (self.params.vk.delta_g2, G2Affine::one()) // reversed for inverse - ) { - return Err(()); - } + sink.into_hash() + }; - Ok(result) - } + // This avoids making a weird assumption about the hash into the + // group. + let mut transcript = [0; 64]; + transcript.copy_from_slice(h.as_ref()); - /// Serialize these parameters. The serialized parameters - /// can be read by bellman as Groth16 `Parameters`. - pub fn write( - &self, - mut writer: W - ) -> io::Result<()> - { - self.params.write(&mut writer)?; - writer.write_all(&self.cs_hash)?; + // Compute delta s-pair in G2 + let r = hash_to_g2(h.as_ref()).to_affine(); + let r_delta = r.mul(delta).to_affine(); - writer.write_u32::(self.contributions.len() as u32)?; - for pubkey in &self.contributions { - pubkey.write(&mut writer)?; - } + ( + PublicKey { + delta_after: current.params.vk.delta_g1.mul(delta).to_affine(), + s: s, + s_delta: s_delta, + r_delta: r_delta, + transcript: transcript, + }, + PrivateKey { delta: delta }, + ) +} - Ok(()) - } +fn batch_normalization(proj: &mut [C]) +where + C::AffineRepr: Clone + Into, +{ + let mut affines = vec![C::identity().to_affine(); proj.len()]; + C::batch_normalize(&*proj, &mut affines); + proj.iter_mut().zip(affines.iter()).for_each(|(a, b)| { + *a = b.clone().into(); + }); +} - /// Deserialize these parameters. If `checked` is false, - /// we won't perform curve validity and group order - /// checks. - pub fn read( - mut reader: R, - checked: bool - ) -> io::Result +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 `contribute()`). + pub fn new(circuit: C) -> Result + where + C: Circuit, { - let params = Parameters::read(&mut reader, checked)?; + let mut assembly = KeypairAssembly { + num_inputs: 0, + num_aux: 0, + num_constraints: 0, + at_inputs: vec![], + bt_inputs: vec![], + ct_inputs: vec![], + at_aux: vec![], + bt_aux: vec![], + ct_aux: vec![], + }; - let mut cs_hash = [0u8; 64]; - reader.read_exact(&mut cs_hash)?; + // Allocate the "one" input variable + assembly.alloc_input(|| "", || Ok(bls12_381::Scalar::ONE))?; - let contributions_len = reader.read_u32::()? as usize; + // Synthesize the circuit. + circuit.synthesize(&mut assembly)?; - let mut contributions = vec![]; - for _ in 0..contributions_len { - contributions.push(PublicKey::read(&mut reader)?); + // Input constraints to ensure full density of IC query + // x * 0 = 0 + for i in 0..assembly.num_inputs { + assembly.enforce( + || "", + |lc| lc + Variable::new_unchecked(Index::Input(i)), + |lc| lc, + |lc| lc, + ); } - 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)] -struct PublicKey { - /// This is the delta (in G1) after the transformation, kept so that we - /// can check correctness of the public keys without having the entire - /// interstitial parameters for each contribution. - delta_after: G1Affine, - - /// Random element chosen by the contributor. - s: G1Affine, + // Compute the size of our evaluation domain + let mut m = 1; + let mut exp = 0; + while m < assembly.num_constraints { + m *= 2; + exp += 1; - /// That element, taken to the contributor's secret delta. - s_delta: G1Affine, + // Powers of Tau ceremony can't support more than 2^21 + if exp > 21 { + return Err(SynthesisError::PolynomialDegreeTooLarge); + } + } - /// r is H(last_pubkey | s | s_delta), r_delta proves knowledge of delta - r_delta: G2Affine, + // Try to load "phase1radix2m{}" + let f = match File::open(format!("phase1radix2m{}", exp)) { + Ok(f) => f, + Err(e) => { + panic!("Couldn't load phase1radix2m{}: {:?}", exp, e); + } + }; + let f = &mut BufReader::with_capacity(1024 * 1024, f); - /// Hash of the transcript (used for mapping to r) - transcript: [u8; 64], -} + let read_g1 = |reader: &mut BufReader| -> io::Result { + let mut repr = ::Uncompressed::default(); + reader.read_exact(repr.as_mut())?; -impl PublicKey { - fn write( - &self, - mut writer: W - ) -> io::Result<()> - { - writer.write_all(self.delta_after.into_uncompressed().as_ref())?; - writer.write_all(self.s.into_uncompressed().as_ref())?; - writer.write_all(self.s_delta.into_uncompressed().as_ref())?; - writer.write_all(self.r_delta.into_uncompressed().as_ref())?; - writer.write_all(&self.transcript)?; + Option::from( + ::from_uncompressed_unchecked(&repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid data")) + .and_then(|e: bls12_381::G1Affine| { + if e.is_identity().into() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )) + } else { + Ok(e) + } + }) + }; - Ok(()) - } + let read_g2 = |reader: &mut BufReader| -> io::Result { + let mut repr = ::Uncompressed::default(); + reader.read_exact(repr.as_mut())?; - fn read( - mut reader: R - ) -> io::Result - { - let mut g1_repr = G1Uncompressed::empty(); - let mut g2_repr = G2Uncompressed::empty(); + Option::from( + ::from_uncompressed_unchecked(&repr), + ) + .ok_or(io::Error::new(io::ErrorKind::InvalidData, "Invalid data")) + .and_then(|e: bls12_381::G2Affine| { + if e.is_identity().into() { + Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )) + } else { + Ok(e) + } + }) + }; - reader.read_exact(g1_repr.as_mut())?; - let delta_after = g1_repr.into_affine().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let alpha = read_g1(f)?; + let beta_g1 = read_g1(f)?; + let beta_g2 = read_g2(f)?; - if delta_after.is_zero() { - return Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")); + let mut coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + coeffs_g1.push(read_g1(f)?); } - reader.read_exact(g1_repr.as_mut())?; - let s = g1_repr.into_affine().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; - - if s.is_zero() { - return Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")); + let mut coeffs_g2 = Vec::with_capacity(m); + for _ in 0..m { + coeffs_g2.push(read_g2(f)?); } - reader.read_exact(g1_repr.as_mut())?; - let s_delta = g1_repr.into_affine().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + let mut alpha_coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + alpha_coeffs_g1.push(read_g1(f)?); + } - if s_delta.is_zero() { - return Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")); + let mut beta_coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + beta_coeffs_g1.push(read_g1(f)?); } - reader.read_exact(g2_repr.as_mut())?; - let r_delta = g2_repr.into_affine().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + // These are `Arc` so that later it'll be easier + // to use multiexp during QAP evaluation (which + // requires a futures-based API) + let coeffs_g1 = Arc::new(coeffs_g1); + let coeffs_g2 = Arc::new(coeffs_g2); + let alpha_coeffs_g1 = Arc::new(alpha_coeffs_g1); + let beta_coeffs_g1 = Arc::new(beta_coeffs_g1); - if r_delta.is_zero() { - return Err(io::Error::new(io::ErrorKind::InvalidData, "point at infinity")); + let mut h = Vec::with_capacity(m - 1); + for _ in 0..(m - 1) { + h.push(read_g1(f)?); } - let mut transcript = [0u8; 64]; - reader.read_exact(&mut transcript)?; + let mut ic = vec![bls12_381::G1Projective::identity(); assembly.num_inputs]; + let mut l = vec![bls12_381::G1Projective::identity(); assembly.num_aux]; + let mut a_g1 = + vec![bls12_381::G1Projective::identity(); assembly.num_inputs + assembly.num_aux]; + let mut b_g1 = + vec![bls12_381::G1Projective::identity(); assembly.num_inputs + assembly.num_aux]; + let mut b_g2 = + vec![bls12_381::G2Projective::identity(); assembly.num_inputs + assembly.num_aux]; - Ok(PublicKey { - delta_after, s, s_delta, r_delta, transcript - }) - } -} + fn eval( + // Lagrange coefficients for tau + coeffs_g1: Arc>, + coeffs_g2: Arc>, + alpha_coeffs_g1: Arc>, + beta_coeffs_g1: Arc>, -impl PartialEq for PublicKey { - fn eq(&self, other: &PublicKey) -> bool { - self.delta_after == other.delta_after && - self.s == other.s && - self.s_delta == other.s_delta && - self.r_delta == other.r_delta && - &self.transcript[..] == &other.transcript[..] - } -} + // QAP polynomials + at: &[Vec<(bls12_381::Scalar, usize)>], + bt: &[Vec<(bls12_381::Scalar, usize)>], + ct: &[Vec<(bls12_381::Scalar, usize)>], -/// Verify a contribution, given the old parameters and -/// the new parameters. Returns the hash of the contribution. -pub fn verify_contribution( - before: &MPCParameters, - after: &MPCParameters -) -> Result<[u8; 64], ()> -{ - // Transformation involves a single new object - if after.contributions.len() != (before.contributions.len() + 1) { - return Err(()); - } + // Resulting evaluated QAP polynomials + a_g1: &mut [bls12_381::G1Projective], + b_g1: &mut [bls12_381::G1Projective], + b_g2: &mut [bls12_381::G2Projective], + ext: &mut [bls12_381::G1Projective], - // None of the previous transformations should change - if &before.contributions[..] != &after.contributions[0..before.contributions.len()] { - return Err(()); - } + // Worker + worker: &Worker, + ) { + // Sanity check + assert_eq!(a_g1.len(), at.len()); + assert_eq!(a_g1.len(), bt.len()); + assert_eq!(a_g1.len(), ct.len()); + assert_eq!(a_g1.len(), b_g1.len()); + assert_eq!(a_g1.len(), b_g2.len()); + assert_eq!(a_g1.len(), ext.len()); - // H/L will change, but should have same length - if before.params.h.len() != after.params.h.len() { - return Err(()); - } - if before.params.l.len() != after.params.l.len() { - return Err(()); - } + // Evaluate polynomials in multiple threads + worker.scope(a_g1.len(), |scope, chunk| { + for ((((((a_g1, b_g1), b_g2), ext), at), bt), ct) in a_g1 + .chunks_mut(chunk) + .zip(b_g1.chunks_mut(chunk)) + .zip(b_g2.chunks_mut(chunk)) + .zip(ext.chunks_mut(chunk)) + .zip(at.chunks(chunk)) + .zip(bt.chunks(chunk)) + .zip(ct.chunks(chunk)) + { + let coeffs_g1 = coeffs_g1.clone(); + let coeffs_g2 = coeffs_g2.clone(); + let alpha_coeffs_g1 = alpha_coeffs_g1.clone(); + let beta_coeffs_g1 = beta_coeffs_g1.clone(); - // A/B_G1/B_G2 doesn't change at all - if before.params.a != after.params.a { - return Err(()); - } - if before.params.b_g1 != after.params.b_g1 { - return Err(()); - } - if before.params.b_g2 != after.params.b_g2 { - return Err(()); - } + scope.spawn(move |_| { + for ((((((a_g1, b_g1), b_g2), ext), at), bt), ct) in a_g1 + .iter_mut() + .zip(b_g1.iter_mut()) + .zip(b_g2.iter_mut()) + .zip(ext.iter_mut()) + .zip(at.iter()) + .zip(bt.iter()) + .zip(ct.iter()) + { + for &(coeff, lag) in at { + a_g1.add_assign(&coeffs_g1[lag].mul(coeff)); + ext.add_assign(&beta_coeffs_g1[lag].mul(coeff)); + } - // alpha/beta/gamma don't change - if before.params.vk.alpha_g1 != after.params.vk.alpha_g1 { - return Err(()); - } - if before.params.vk.beta_g1 != after.params.vk.beta_g1 { - return Err(()); - } - if before.params.vk.beta_g2 != after.params.vk.beta_g2 { - return Err(()); - } - if before.params.vk.gamma_g2 != after.params.vk.gamma_g2 { - return Err(()); - } + for &(coeff, lag) in bt { + b_g1.add_assign(&coeffs_g1[lag].mul(coeff)); + b_g2.add_assign(&coeffs_g2[lag].mul(coeff)); + ext.add_assign(&alpha_coeffs_g1[lag].mul(coeff)); + } - // IC shouldn't change, as gamma doesn't change - if before.params.vk.ic != after.params.vk.ic { - return Err(()); - } + for &(coeff, lag) in ct { + ext.add_assign(&coeffs_g1[lag].mul(coeff)); + } + } - // cs_hash should be the same - if &before.cs_hash[..] != &after.cs_hash[..] { - return Err(()); - } + // Batch normalize + batch_normalization(a_g1); + batch_normalization(b_g1); + batch_normalization(b_g2); + batch_normalization(ext); + }); + } + }); + } - let sink = io::sink(); - let mut sink = HashWriter::new(sink); - sink.write_all(&before.cs_hash[..]).unwrap(); + let worker = Worker::new(); - for pubkey in &before.contributions { - pubkey.write(&mut sink).unwrap(); - } + // Evaluate for inputs. + eval( + coeffs_g1.clone(), + coeffs_g2.clone(), + alpha_coeffs_g1.clone(), + beta_coeffs_g1.clone(), + &assembly.at_inputs, + &assembly.bt_inputs, + &assembly.ct_inputs, + &mut a_g1[0..assembly.num_inputs], + &mut b_g1[0..assembly.num_inputs], + &mut b_g2[0..assembly.num_inputs], + &mut ic, + &worker, + ); - let pubkey = after.contributions.last().unwrap(); - sink.write_all(pubkey.s.into_uncompressed().as_ref()).unwrap(); - sink.write_all(pubkey.s_delta.into_uncompressed().as_ref()).unwrap(); + // Evaluate for auxillary variables. + eval( + coeffs_g1.clone(), + coeffs_g2.clone(), + alpha_coeffs_g1.clone(), + beta_coeffs_g1.clone(), + &assembly.at_aux, + &assembly.bt_aux, + &assembly.ct_aux, + &mut a_g1[assembly.num_inputs..], + &mut b_g1[assembly.num_inputs..], + &mut b_g2[assembly.num_inputs..], + &mut l, + &worker, + ); - let h = sink.into_hash(); + // Don't allow any elements be unconstrained, so that + // the L query is always fully dense. + for e in l.iter() { + if Into::::into(e.is_identity()) { + return Err(SynthesisError::UnconstrainedVariable); + } + } - // The transcript must be consistent - if &pubkey.transcript[..] != h.as_ref() { - return Err(()); - } + let vk = VerifyingKey { + alpha_g1: alpha, + beta_g1: beta_g1, + beta_g2: beta_g2, + gamma_g2: bls12_381::G2Affine::generator(), + delta_g1: bls12_381::G1Affine::generator(), + delta_g2: bls12_381::G2Affine::generator(), + ic: ic.into_iter().map(|e| e.to_affine()).collect(), + }; - let r = hash_to_g2(h.as_ref()).into_affine(); + let params = Parameters { + vk: vk, + h: Arc::new(h), + l: Arc::new(l.into_iter().map(|e| e.to_affine()).collect()), - // Check the signature of knowledge - if !same_ratio((r, pubkey.r_delta), (pubkey.s, pubkey.s_delta)) { - return Err(()); - } + // Filter points at infinity away from A/B queries + a: Arc::new( + a_g1.into_iter() + .filter(|e| !Into::::into(e.is_identity())) + .map(|e| e.to_affine()) + .collect(), + ), + b_g1: Arc::new( + b_g1.into_iter() + .filter(|e| !Into::::into(e.is_identity())) + .map(|e| e.to_affine()) + .collect(), + ), + b_g2: Arc::new( + b_g2.into_iter() + .filter(|e| !Into::::into(e.is_identity())) + .map(|e| e.to_affine()) + .collect(), + ), + }; - // Check the change from the old delta is consistent - if !same_ratio( - (before.params.vk.delta_g1, pubkey.delta_after), - (r, pubkey.r_delta) - ) { - return Err(()); - } + let h = { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); - // Current parameters should have consistent delta in G1 - if pubkey.delta_after != after.params.vk.delta_g1 { - return Err(()); - } + params.write(&mut sink).unwrap(); - // Current parameters should have consistent delta in G2 - if !same_ratio( - (G1Affine::one(), pubkey.delta_after), - (G2Affine::one(), after.params.vk.delta_g2) - ) { - return Err(()); - } + sink.into_hash() + }; - // H and L queries should be updated with delta^-1 - if !same_ratio( - merge_pairs(&before.params.h, &after.params.h), - (after.params.vk.delta_g2, before.params.vk.delta_g2) // reversed for inverse - ) { - return Err(()); - } + let mut cs_hash = [0; 64]; + cs_hash.copy_from_slice(h.as_ref()); - if !same_ratio( - merge_pairs(&before.params.l, &after.params.l), - (after.params.vk.delta_g2, before.params.vk.delta_g2) // reversed for inverse - ) { - return Err(()); + Ok(MPCParameters { + params: params, + cs_hash: cs_hash, + contributions: vec![], + }) } - let sink = io::sink(); - let mut sink = HashWriter::new(sink); - pubkey.write(&mut sink).unwrap(); - let h = sink.into_hash(); - let mut response = [0u8; 64]; - response.copy_from_slice(h.as_ref()); + /// Get the underlying Groth16 `Parameters` + pub fn get_params(&self) -> &Parameters { + &self.params + } - Ok(response) -} + /// Contributes some randomness to the parameters. Only one + /// contributor needs to be honest for the parameters to be + /// secure. + /// + /// 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, rng: &mut R) -> [u8; 64] { + // Generate a keypair + let (pubkey, privkey) = keypair(rng, self); -/// Checks if pairs have the same ratio. -fn same_ratio( - g1: (G1, G1), - g2: (G1::Pair, G1::Pair) -) -> bool -{ - g1.0.pairing_with(&g2.1) == g1.1.pairing_with(&g2.0) -} + fn batch_exp(bases: &mut [bls12_381::G1Affine], coeff: bls12_381::Scalar) { + let mut projective = vec![bls12_381::G1Projective::identity(); bases.len()]; + let cpus = num_cpus::get(); + let chunk_size = if bases.len() < cpus { + 1 + } else { + bases.len() / cpus + }; -/// Computes a random linear combination over v1/v2. -/// -/// Checking that many pairs of elements are exponentiated by -/// the same `x` can be achieved (with high probability) with -/// the following technique: -/// -/// Given v1 = [a, b, c] and v2 = [as, bs, cs], compute -/// (a*r1 + b*r2 + c*r3, (as)*r1 + (bs)*r2 + (cs)*r3) for some -/// random r1, r2, r3. Given (g, g^s)... -/// -/// e(g, (as)*r1 + (bs)*r2 + (cs)*r3) = e(g^s, a*r1 + b*r2 + c*r3) -/// -/// ... with high probability. -fn merge_pairs(v1: &[G], v2: &[G]) -> (G, G) -{ - use std::sync::{Arc, Mutex}; - use rand::{thread_rng}; + // Perform wNAF over multiple cores, placing results into `projective`. + crossbeam::scope(|scope| { + for (bases, projective) in bases + .chunks_mut(chunk_size) + .zip(projective.chunks_mut(chunk_size)) + { + scope.spawn(move || { + let mut wnaf = Wnaf::new(); - assert_eq!(v1.len(), v2.len()); + for (base, projective) in bases.iter_mut().zip(projective.iter_mut()) { + *projective = wnaf.base(base.to_curve(), 1).scalar(&coeff); + } + }); + } + }); - let chunk = (v1.len() / num_cpus::get()) + 1; + // Perform batch normalization + crossbeam::scope(|scope| { + for projective in projective.chunks_mut(chunk_size) { + scope.spawn(move || { + batch_normalization(projective); + }); + } + }); - let s = Arc::new(Mutex::new(G::Projective::zero())); - let sx = Arc::new(Mutex::new(G::Projective::zero())); + // Turn it all back into affine points + for (projective, affine) in projective.iter().zip(bases.iter_mut()) { + *affine = projective.to_affine(); + } + } - crossbeam::scope(|scope| { - for (v1, v2) in v1.chunks(chunk).zip(v2.chunks(chunk)) { - let s = s.clone(); - let sx = sx.clone(); + let delta_inv = privkey.delta.invert().expect("nonzero"); + let mut l = (&self.params.l[..]).to_vec(); + let mut h = (&self.params.h[..]).to_vec(); + batch_exp(&mut l, delta_inv); + batch_exp(&mut h, delta_inv); + self.params.l = Arc::new(l); + self.params.h = Arc::new(h); - scope.spawn(move || { - // We do not need to be overly cautious of the RNG - // used for this check. - let rng = &mut thread_rng(); + self.params.vk.delta_g1 = self.params.vk.delta_g1.mul(privkey.delta).to_affine(); + self.params.vk.delta_g2 = self.params.vk.delta_g2.mul(privkey.delta).to_affine(); - let mut wnaf = Wnaf::new(); - let mut local_s = G::Projective::zero(); - let mut local_sx = G::Projective::zero(); + self.contributions.push(pubkey.clone()); - for (v1, v2) in v1.iter().zip(v2.iter()) { - let rho = G::Scalar::rand(rng); - let mut wnaf = wnaf.scalar(rho.into_repr()); - let v1 = wnaf.base(v1.into_projective()); - let v2 = wnaf.base(v2.into_projective()); + // Calculate the hash of the public key and return it + { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + pubkey.write(&mut sink).unwrap(); + let h = sink.into_hash(); + let mut response = [0u8; 64]; + response.copy_from_slice(h.as_ref()); + response + } + } - local_s.add_assign(&v1); - local_sx.add_assign(&v2); - } + /// 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>(&self, circuit: C) -> Result, ()> { + let initial_params = MPCParameters::new(circuit).map_err(|_| ())?; - s.lock().unwrap().add_assign(&local_s); - sx.lock().unwrap().add_assign(&local_sx); - }); + // H/L will change, but should have same length + if initial_params.params.h.len() != self.params.h.len() { + return Err(()); + } + if initial_params.params.l.len() != self.params.l.len() { + return Err(()); } - }); - - let s = s.lock().unwrap().into_affine(); - let sx = sx.lock().unwrap().into_affine(); - (s, sx) -} + // A/B_G1/B_G2 doesn't change at all + if initial_params.params.a != self.params.a { + return Err(()); + } + if initial_params.params.b_g1 != self.params.b_g1 { + return Err(()); + } + if initial_params.params.b_g2 != self.params.b_g2 { + return Err(()); + } -/// This needs to be destroyed by at least one participant -/// for the final parameters to be secure. -struct PrivateKey { - delta: Fr -} + // alpha/beta/gamma don't change + if initial_params.params.vk.alpha_g1 != self.params.vk.alpha_g1 { + return Err(()); + } + if initial_params.params.vk.beta_g1 != self.params.vk.beta_g1 { + return Err(()); + } + if initial_params.params.vk.beta_g2 != self.params.vk.beta_g2 { + return Err(()); + } + if initial_params.params.vk.gamma_g2 != self.params.vk.gamma_g2 { + return Err(()); + } -/// Compute a keypair, given the current parameters. Keypairs -/// cannot be reused for multiple contributions or contributions -/// in different parameters. -fn keypair( - rng: &mut R, - current: &MPCParameters, -) -> (PublicKey, PrivateKey) -{ - // Sample random delta - let delta: Fr = rng.gen(); + // IC shouldn't change, as gamma doesn't change + if initial_params.params.vk.ic != self.params.vk.ic { + return Err(()); + } - // Compute delta s-pair in G1 - let s = G1::rand(rng).into_affine(); - let s_delta = s.mul(delta).into_affine(); + // cs_hash should be the same + if &initial_params.cs_hash[..] != &self.cs_hash[..] { + return Err(()); + } - // H(cs_hash | | s | s_delta) - let h = { let sink = io::sink(); let mut sink = HashWriter::new(sink); + sink.write_all(&initial_params.cs_hash[..]).unwrap(); + + let mut current_delta = bls12_381::G1Affine::generator(); + let mut result = vec![]; + + for pubkey in &self.contributions { + let mut our_sink = sink.clone(); + our_sink + .write_all(pubkey.s.to_uncompressed().as_ref()) + .unwrap(); + our_sink + .write_all(pubkey.s_delta.to_uncompressed().as_ref()) + .unwrap(); - sink.write_all(¤t.cs_hash[..]).unwrap(); - for pubkey in ¤t.contributions { pubkey.write(&mut sink).unwrap(); - } - sink.write_all(s.into_uncompressed().as_ref()).unwrap(); - sink.write_all(s_delta.into_uncompressed().as_ref()).unwrap(); - sink.into_hash() - }; + let h = our_sink.into_hash(); - // This avoids making a weird assumption about the hash into the - // group. - let mut transcript = [0; 64]; - transcript.copy_from_slice(h.as_ref()); + // The transcript must be consistent + if &pubkey.transcript[..] != h.as_ref() { + return Err(()); + } - // Compute delta s-pair in G2 - let r = hash_to_g2(h.as_ref()).into_affine(); - let r_delta = r.mul(delta).into_affine(); + let r = hash_to_g2(h.as_ref()).to_affine(); - ( - PublicKey { - delta_after: current.params.vk.delta_g1.mul(delta).into_affine(), - s: s, - s_delta: s_delta, - r_delta: r_delta, - transcript: transcript - }, - PrivateKey { - delta: delta - } - ) -} + // Check the signature of knowledge + if !same_ratio((r, pubkey.r_delta), (pubkey.s, pubkey.s_delta)) { + return Err(()); + } -/// Hashes to G2 using the first 32 bytes of `digest`. Panics if `digest` is less -/// than 32 bytes. -fn hash_to_g2(mut digest: &[u8]) -> G2 -{ - assert!(digest.len() >= 32); + // Check the change from the old delta is consistent + if !same_ratio((current_delta, pubkey.delta_after), (r, pubkey.r_delta)) { + return Err(()); + } - let mut seed = Vec::with_capacity(8); + current_delta = pubkey.delta_after; - for _ in 0..8 { - seed.push(digest.read_u32::().expect("assertion above guarantees this to work")); - } + { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + pubkey.write(&mut sink).unwrap(); + let h = sink.into_hash(); + let mut response = [0u8; 64]; + response.copy_from_slice(h.as_ref()); + result.push(response); + } + } - ChaChaRng::from_seed(&seed).gen() -} + // Current parameters should have consistent delta in G1 + if current_delta != self.params.vk.delta_g1 { + return Err(()); + } -/// Abstraction over a writer which hashes the data being written. -struct HashWriter { - writer: W, - hasher: Blake2b -} + // Current parameters should have consistent delta in G2 + if !same_ratio( + (bls12_381::G1Affine::generator(), current_delta), + (bls12_381::G2Affine::generator(), self.params.vk.delta_g2), + ) { + return Err(()); + } -impl Clone for HashWriter { - fn clone(&self) -> HashWriter { - HashWriter { - writer: io::sink(), - hasher: self.hasher.clone() + // H and L queries should be updated with delta^-1 + if !same_ratio( + merge_pairs(&initial_params.params.h, &self.params.h), + (self.params.vk.delta_g2, bls12_381::G2Affine::generator()), // reversed for inverse + ) { + return Err(()); } - } -} -impl HashWriter { - /// Construct a new `HashWriter` given an existing `writer` by value. - pub fn new(writer: W) -> Self { - HashWriter { - writer: writer, - hasher: Blake2b::new(64) + if !same_ratio( + merge_pairs(&initial_params.params.l, &self.params.l), + (self.params.vk.delta_g2, bls12_381::G2Affine::generator()), // reversed for inverse + ) { + return Err(()); } - } - /// Destroy this writer and return the hash of what was written. - pub fn into_hash(self) -> [u8; 64] { - let mut tmp = [0u8; 64]; - tmp.copy_from_slice(self.hasher.finalize().as_ref()); - tmp + Ok(result) } -} -impl Write for HashWriter { - fn write(&mut self, buf: &[u8]) -> io::Result { - let bytes = self.writer.write(buf)?; + /// Serialize these parameters. The serialized parameters + /// can be read by bellman as Groth16 `Parameters`. + pub fn write(&self, mut writer: W) -> io::Result<()> { + self.params.write(&mut writer)?; + writer.write_all(&self.cs_hash)?; - if bytes > 0 { - self.hasher.update(&buf[0..bytes]); + writer.write_u32::(self.contributions.len() as u32)?; + for pubkey in &self.contributions { + pubkey.write(&mut writer)?; } - Ok(bytes) + Ok(()) } - fn flush(&mut self) -> io::Result<()> { - self.writer.flush() + /// Deserialize these parameters. If `checked` is false, + /// we won't perform curve validity and group order + /// checks. + pub fn read(mut reader: R, checked: bool) -> io::Result { + 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::()? as usize; + + let mut contributions = vec![]; + for _ in 0..contributions_len { + contributions.push(PublicKey::read(&mut reader)?); + } + + Ok(MPCParameters { + params, + cs_hash, + contributions, + }) } } -/// 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 -{ +pub fn contains_contribution(contributions: &[[u8; 64]], my_contribution: &[u8; 64]) -> bool { for contrib in contributions { if &contrib[..] == &my_contribution[..] { - return true + return true; } } - return false + return false; } From c1c15aa096ea4c45302772a0450f10a3aad9b44b Mon Sep 17 00:00:00 2001 From: Keyvan Kambakhsh Date: Sun, 19 Nov 2023 14:49:32 +0330 Subject: [PATCH 2/3] Bring the comments back --- src/lib.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 8357855..12cfe3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,6 +211,8 @@ use std::io::{BufReader, Read, Write}; use std::ops::{AddAssign, Mul}; use std::sync::Arc; +/// This is our assembly structure that we'll use to synthesize the +/// circuit into a QAP. struct KeypairAssembly { num_inputs: usize, num_aux: usize, @@ -356,6 +358,8 @@ impl PartialEq for PublicKey { } } +/// 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, @@ -499,6 +503,8 @@ impl Write for HashWriter { } } +/// Hashes to G2 using the first 32 bytes of `digest`. Panics if `digest` is less +/// than 32 bytes. fn hash_to_g2(digest: &[u8]) -> bls12_381::G2Projective { assert!(digest.len() >= 32); let mut seed = [0u8; 32]; @@ -635,10 +641,24 @@ pub fn verify_contribution(before: &MPCParameters, after: &MPCParameters) -> Res Ok(response) } +/// Checks if pairs have the same ratio. fn same_ratio(g1: (G1, G1), g2: (G1::Pair, G1::Pair)) -> bool { g1.0.pairing_with(&g2.1) == g1.1.pairing_with(&g2.0) } +/// Computes a random linear combination over v1/v2. +/// +/// Checking that many pairs of elements are exponentiated by +/// the same `x` can be achieved (with high probability) with +/// the following technique: +/// +/// Given v1 = [a, b, c] and v2 = [as, bs, cs], compute +/// (a*r1 + b*r2 + c*r3, (as)*r1 + (bs)*r2 + (cs)*r3) for some +/// random r1, r2, r3. Given (g, g^s)... +/// +/// e(g, (as)*r1 + (bs)*r2 + (cs)*r3) = e(g^s, a*r1 + b*r2 + c*r3) +/// +/// ... with high probability. fn merge_pairs(v1: &[G], v2: &[G]) -> (G, G) where G::Curve: WnafGroup, @@ -1329,6 +1349,9 @@ impl MPCParameters { } } +/// 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[..] { From 02cc446d4305b670a35ab8b158f003acfc07ae82 Mon Sep 17 00:00:00 2001 From: Keyvan Kambakhsh Date: Sun, 19 Nov 2023 14:58:17 +0330 Subject: [PATCH 3/3] Make sure the code examples work --- src/lib.rs | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 12cfe3f..b3cb355 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,9 +11,6 @@ //! a field element. //! //! ```rust -//! extern crate pairing; -//! extern crate bellman; -//! //! use ff::PrimeField; //! use bellman::{ //! Circuit, @@ -40,7 +37,7 @@ //! let square = cs.alloc(|| "square", || { //! self.cube_root //! .ok_or(SynthesisError::AssignmentMissing) -//! .map(|mut root| { root.square() }) +//! .map(|root| { root.square() }) //! })?; //! //! // Enforce that `square` is root^2 @@ -93,7 +90,7 @@ //! verify_proof //! }; //! -//! let rng = &mut rand::thread_rng(); +//! let mut rng = rand::thread_rng(); //! //! // Create public parameters for our circuit //! let params = { @@ -103,7 +100,7 @@ //! //! generate_random_parameters::( //! circuit, -//! rng +//! &mut rng //! ).unwrap() //! }; //! @@ -114,7 +111,7 @@ //! for _ in 0..50 { //! // Verifier picks a cube in the field. //! // Let's just make a random one. -//! let root = Scalar::random(rng); +//! let root = Scalar::random(&mut rng); //! let mut cube = root; //! cube = cube.square(); //! cube.mul_assign(&root); @@ -124,11 +121,11 @@ //! let proof = create_random_proof( //! CubeRoot:: { //! cube_root: Some(root) -//! }, ¶ms, rng +//! }, ¶ms, &mut rng //! ).unwrap(); //! //! // Verifier checks the proof against the cube -//! assert!(verify_proof(&pvk, &proof, &[cube]).is_ok()); +//! assert!(verify_proof(&pvk, &proof, &[cube.clone()]).is_ok()); //! } //! ``` //! ## Creating parameters @@ -149,8 +146,6 @@ //! for our circuit: //! //! ```rust,ignore -//! extern crate phase2; -//! //! let mut params = phase2::MPCParameters::new(CubeRoot { //! cube_root: None //! }).unwrap(); @@ -167,7 +162,7 @@ //! ```rust,ignore //! // Contribute randomness to the parameters. Remember this hash, //! // it's how we know our contribution is in the parameters! -//! let hash = params.contribute(rng); +//! let hash = params.contribute(&mut rng); //! ``` //! //! These parameters are now secure to use, so long as you weren't