From 97c03d12be10290d8d0b7bbf0ada3fdab16c714b Mon Sep 17 00:00:00 2001 From: dark64 Date: Tue, 23 Nov 2021 15:35:54 +0100 Subject: [PATCH] add zokrates_mpc crate --- Cargo.lock | 56 +- Cargo.toml | 1 + zokrates_book/src/toolbox/trusted_setup.md | 4 +- zokrates_cli/Cargo.toml | 2 +- .../mpc_tutorial/{phase1 => }/phase1radix2m2 | Bin .../examples/book/mpc_tutorial/test.sh | 4 +- zokrates_cli/src/ops/mpc/beacon.rs | 11 +- zokrates_cli/src/ops/mpc/contribute.rs | 11 +- zokrates_cli/src/ops/mpc/export.rs | 9 +- zokrates_cli/src/ops/mpc/init.rs | 21 +- zokrates_cli/src/ops/mpc/verify.rs | 23 +- zokrates_cli/tests/integration.rs | 8 +- zokrates_mpc/Cargo.toml | 17 + zokrates_mpc/src/groth16/keypair.rs | 118 +++ zokrates_mpc/src/groth16/keypair_assembly.rs | 120 +++ zokrates_mpc/src/groth16/mod.rs | 3 + zokrates_mpc/src/groth16/parameters.rs | 937 ++++++++++++++++++ zokrates_mpc/src/hash_writer.rs | 53 + zokrates_mpc/src/lib.rs | 2 + 19 files changed, 1309 insertions(+), 91 deletions(-) rename zokrates_cli/examples/book/mpc_tutorial/{phase1 => }/phase1radix2m2 (100%) create mode 100644 zokrates_mpc/Cargo.toml create mode 100644 zokrates_mpc/src/groth16/keypair.rs create mode 100644 zokrates_mpc/src/groth16/keypair_assembly.rs create mode 100644 zokrates_mpc/src/groth16/mod.rs create mode 100644 zokrates_mpc/src/groth16/parameters.rs create mode 100644 zokrates_mpc/src/hash_writer.rs create mode 100644 zokrates_mpc/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 9360fb0e..41f90e8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -854,12 +854,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "exitcode" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de853764b47027c2e862a995c34978ffa63c1501f2e15f987ba11bd4f9bba193" - [[package]] name = "failure" version = "0.1.8" @@ -1174,15 +1168,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "0.4.8" @@ -1487,7 +1472,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fbf404899169771dd6a32c84248b83cd67a26cc7cc957aac87661490e1227e4" dependencies = [ - "itertools 0.7.11", + "itertools", "proc-macro2 0.4.30", "quote 0.6.13", "single", @@ -1528,29 +1513,6 @@ dependencies = [ "sha-1", ] -[[package]] -name = "phase2" -version = "0.2.2" -source = "git+https://github.com/dark64/phase2-bn254#e372bf41ba874af79bc8ed22a31229494e2a11f4" -dependencies = [ - "bellman_ce", - "blake2 0.8.1", - "blake2-rfc", - "byteorder", - "cfg-if 0.1.10", - "crossbeam", - "exitcode", - "hex 0.4.3", - "itertools 0.9.0", - "num-bigint 0.2.6", - "num-traits 0.2.14", - "num_cpus", - "rand 0.4.6", - "rust-crypto", - "serde", - "serde_json", -] - [[package]] name = "pin-project-lite" version = "0.2.7" @@ -2522,7 +2484,6 @@ dependencies = [ "hex 0.3.2", "lazy_static", "log", - "phase2", "rand 0.4.6", "regex 0.2.11", "rust-crypto", @@ -2532,6 +2493,7 @@ dependencies = [ "zokrates_core", "zokrates_field", "zokrates_fs_resolver", + "zokrates_mpc", ] [[package]] @@ -2641,6 +2603,20 @@ dependencies = [ "zokrates_common", ] +[[package]] +name = "zokrates_mpc" +version = "0.1.0" +dependencies = [ + "bellman_ce", + "blake2-rfc", + "byteorder", + "crossbeam", + "getrandom 0.2.3", + "num_cpus", + "pairing_ce", + "rand 0.4.6", +] + [[package]] name = "zokrates_parser" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 50d4c4ce..3671a9e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "zokrates_fs_resolver", "zokrates_stdlib", "zokrates_embed", + "zokrates_mpc", "zokrates_abi", "zokrates_test", "zokrates_core_test", diff --git a/zokrates_book/src/toolbox/trusted_setup.md b/zokrates_book/src/toolbox/trusted_setup.md index 4a246b8e..f3bbfc9f 100644 --- a/zokrates_book/src/toolbox/trusted_setup.md +++ b/zokrates_book/src/toolbox/trusted_setup.md @@ -36,8 +36,8 @@ Initializing MPC... Writing initial parameters to `mpc.params` ``` -Using the `-r` flag we pass a path to the directory that contains parameters for various `2^m` circuit depths (`phase1radix2m{0..=m}`). -These files can be computed using [phase2-bn254](https://github.com/kobigurk/phase2-bn254) utility by picking the latest response from [Perpetual Powers of Tau](https://github.com/weijiekoh/perpetualpowersoftau) and following procedures explained in the mentioned repositories. +Using the `-r` flag we pass a path of the file that contains parameters for our circuit with a depth of `2^n` (`phase1radix2m{n}`). +The paramaters with various circuit depths can be computed using [phase2-bn254](https://github.com/kobigurk/phase2-bn254) utility by picking the latest response from [Perpetual Powers of Tau](https://github.com/weijiekoh/perpetualpowersoftau) and following procedures explained in the mentioned repositories. ## Making a contribution diff --git a/zokrates_cli/Cargo.toml b/zokrates_cli/Cargo.toml index 6d0078df..2a0a3c72 100644 --- a/zokrates_cli/Cargo.toml +++ b/zokrates_cli/Cargo.toml @@ -20,12 +20,12 @@ bincode = "0.8.0" regex = "0.2" zokrates_field = { version = "0.4", path = "../zokrates_field", default-features = false } zokrates_abi = { version = "0.1", path = "../zokrates_abi" } +zokrates_mpc = { version = "0.1", path = "../zokrates_mpc" } zokrates_core = { version = "0.6", path = "../zokrates_core", default-features = false } zokrates_fs_resolver = { version = "0.5", path = "../zokrates_fs_resolver"} serde_json = "1.0" dirs = "3.0.1" lazy_static = "1.4.0" -phase2 = { git = "https://github.com/dark64/phase2-bn254" } byteorder = "1" rust-crypto = "0.2.36" rand = "0.4" diff --git a/zokrates_cli/examples/book/mpc_tutorial/phase1/phase1radix2m2 b/zokrates_cli/examples/book/mpc_tutorial/phase1radix2m2 similarity index 100% rename from zokrates_cli/examples/book/mpc_tutorial/phase1/phase1radix2m2 rename to zokrates_cli/examples/book/mpc_tutorial/phase1radix2m2 diff --git a/zokrates_cli/examples/book/mpc_tutorial/test.sh b/zokrates_cli/examples/book/mpc_tutorial/test.sh index a60a1b18..9c3728e8 100755 --- a/zokrates_cli/examples/book/mpc_tutorial/test.sh +++ b/zokrates_cli/examples/book/mpc_tutorial/test.sh @@ -12,7 +12,7 @@ zokrates compile -i circuit.zok -o circuit # initialize the ceremony # this step requires phase1 files eg. phase1radix2m2 for circuits of 2^2 constraints -zokrates mpc init -i circuit -o mpc.params -r ./phase1 +zokrates mpc init -i circuit -o mpc.params -r ./phase1radix2m2 # first contribution zokrates mpc contribute -i mpc.params -o alice.params -e "alice 1" @@ -27,7 +27,7 @@ zokrates mpc contribute -i bob.params -o charlie.params -e "charlie 3" zokrates mpc beacon -i charlie.params -o final.params -h b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9 -n 10 # verify contributions -zokrates mpc verify -i final.params -c circuit -r ./phase1 +zokrates mpc verify -i final.params -c circuit -r ./phase1radix2m2 # export keys from final parameters (proving and verification key) zokrates mpc export -i final.params diff --git a/zokrates_cli/src/ops/mpc/beacon.rs b/zokrates_cli/src/ops/mpc/beacon.rs index a32aabf1..c4a52968 100644 --- a/zokrates_cli/src/ops/mpc/beacon.rs +++ b/zokrates_cli/src/ops/mpc/beacon.rs @@ -1,9 +1,10 @@ use crate::constants::MPC_DEFAULT_PATH; use clap::{App, Arg, ArgMatches, SubCommand}; -use phase2::parameters::MPCParameters; use std::fs::File; use std::io::{BufReader, BufWriter}; use std::path::Path; +use zokrates_field::{BellmanFieldExtensions, Bn128Field}; +use zokrates_mpc::groth16::parameters::MPCParameters; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("beacon") @@ -52,8 +53,9 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { File::open(&path).map_err(|why| format!("Could not open `{}`: {}", path.display(), why))?; let reader = BufReader::new(file); - let mut params = MPCParameters::read(reader, true) - .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; + let mut params = + MPCParameters::<::BellmanEngine>::read(reader, true) + .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; let beacon_hash = sub_matches.value_of("hash").unwrap(); let num_iterations: usize = sub_matches.value_of("iterations").unwrap().parse().unwrap(); @@ -117,8 +119,7 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { }; println!("Contributing to `{}`...", path.display()); - let zero: u32 = 0; - let hash = params.contribute(&mut rng, &zero); + let hash = params.contribute(&mut rng); println!("The BLAKE2b hash of your contribution is:\n"); for line in hash.chunks(16) { diff --git a/zokrates_cli/src/ops/mpc/contribute.rs b/zokrates_cli/src/ops/mpc/contribute.rs index 28a3beae..5e933395 100644 --- a/zokrates_cli/src/ops/mpc/contribute.rs +++ b/zokrates_cli/src/ops/mpc/contribute.rs @@ -1,9 +1,10 @@ use crate::constants::MPC_DEFAULT_PATH; use clap::{App, Arg, ArgMatches, SubCommand}; -use phase2::parameters::MPCParameters; use std::fs::File; use std::io::{BufReader, BufWriter}; use std::path::Path; +use zokrates_field::{BellmanFieldExtensions, Bn128Field}; +use zokrates_mpc::groth16::parameters::MPCParameters; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("contribute") @@ -44,8 +45,9 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { File::open(&path).map_err(|why| format!("Could not open `{}`: {}", path.display(), why))?; let reader = BufReader::new(file); - let mut params = MPCParameters::read(reader, true) - .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; + let mut params = + MPCParameters::<::BellmanEngine>::read(reader, true) + .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; let entropy = sub_matches.value_of("entropy").unwrap(); @@ -83,8 +85,7 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { }; println!("Contributing to `{}`...", path.display()); - let zero: u32 = 0; - let hash = params.contribute(&mut rng, &zero); + let hash = params.contribute(&mut rng); println!("The BLAKE2b hash of your contribution is:\n"); for line in hash.chunks(16) { diff --git a/zokrates_cli/src/ops/mpc/export.rs b/zokrates_cli/src/ops/mpc/export.rs index 78e6d493..f8093081 100644 --- a/zokrates_cli/src/ops/mpc/export.rs +++ b/zokrates_cli/src/ops/mpc/export.rs @@ -1,12 +1,12 @@ use crate::constants::{MPC_DEFAULT_PATH, PROVING_KEY_DEFAULT_PATH, VERIFICATION_KEY_DEFAULT_PATH}; use clap::{App, Arg, ArgMatches, SubCommand}; -use phase2::parameters::MPCParameters; use std::fs::File; use std::io::{BufReader, Write}; use std::path::Path; use zokrates_core::proof_system::bellman::{parse_g1, parse_g2}; use zokrates_core::proof_system::groth16::VerificationKey; -use zokrates_field::Bn128Field; +use zokrates_field::{BellmanFieldExtensions, Bn128Field}; +use zokrates_mpc::groth16::parameters::MPCParameters; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("export") @@ -49,8 +49,9 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { File::open(&path).map_err(|why| format!("Could not open `{}`: {}", path.display(), why))?; let reader = BufReader::new(file); - let mpc_params = MPCParameters::read(reader, true) - .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; + let mpc_params = + MPCParameters::<::BellmanEngine>::read(reader, true) + .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; println!("Exporting keys from `{}`...", path.display()); diff --git a/zokrates_cli/src/ops/mpc/init.rs b/zokrates_cli/src/ops/mpc/init.rs index b3865620..d37b9c13 100644 --- a/zokrates_cli/src/ops/mpc/init.rs +++ b/zokrates_cli/src/ops/mpc/init.rs @@ -7,6 +7,7 @@ use zokrates_core::ir; use zokrates_core::ir::ProgEnum; use zokrates_core::proof_system::bellman::Computation; use zokrates_field::Bn128Field; +use zokrates_mpc::groth16::parameters::MPCParameters; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("init") @@ -22,7 +23,7 @@ pub fn subcommand() -> App<'static, 'static> { .default_value(FLATTENED_CODE_DEFAULT_PATH), ) .arg( - Arg::with_name("radix-dir") + Arg::with_name("radix-path") .short("r") .long("radix-dir") .help("Path of the directory containing parameters for various 2^m circuit depths (phase1radix2m{0..=m})") @@ -59,18 +60,14 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { fn cli_mpc_init(ir_prog: ir::Prog, sub_matches: &ArgMatches) -> Result<(), String> { println!("Initializing MPC..."); - let radix_dir = Path::new(sub_matches.value_of("radix-dir").unwrap()); + let radix_path = Path::new(sub_matches.value_of("radix-path").unwrap()); + let radix_file = File::open(radix_path) + .map_err(|why| format!("Could not open `{}`: {}", radix_path.display(), why))?; + + let mut radix_reader = BufReader::with_capacity(1024 * 1024, radix_file); + let circuit = Computation::without_witness(ir_prog); - let params = phase2::parameters::MPCParameters::new( - circuit, - true, - &radix_dir - .to_path_buf() - .into_os_string() - .into_string() - .unwrap(), - ) - .unwrap(); + let params = MPCParameters::new(circuit, true, &mut radix_reader).unwrap(); let output_path = Path::new(sub_matches.value_of("output").unwrap()); let output_file = File::create(&output_path) diff --git a/zokrates_cli/src/ops/mpc/verify.rs b/zokrates_cli/src/ops/mpc/verify.rs index 01d6dc98..c9390011 100644 --- a/zokrates_cli/src/ops/mpc/verify.rs +++ b/zokrates_cli/src/ops/mpc/verify.rs @@ -1,6 +1,5 @@ use crate::constants::{FLATTENED_CODE_DEFAULT_PATH, MPC_DEFAULT_PATH}; use clap::{App, Arg, ArgMatches, SubCommand}; -use phase2::parameters::MPCParameters; use std::fs::File; use std::io::BufReader; use std::path::Path; @@ -8,6 +7,7 @@ use zokrates_core::ir; use zokrates_core::ir::ProgEnum; use zokrates_core::proof_system::bellman::Computation; use zokrates_field::Bn128Field; +use zokrates_mpc::groth16::parameters::MPCParameters; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("verify") @@ -33,10 +33,10 @@ pub fn subcommand() -> App<'static, 'static> { .default_value(FLATTENED_CODE_DEFAULT_PATH), ) .arg( - Arg::with_name("radix-dir") + Arg::with_name("radix-path") .short("r") .long("radix-dir") - .help("Path of the directory containing parameters for various 2^m circuit depths (phase1radix2m{0..=m})") + .help("Path to the radix file containing parameters for 2^n circuit depth (phase1radix2m{n})") .value_name("PATH") .takes_value(true) .required(true), @@ -68,19 +68,16 @@ fn cli_mpc_verify(ir_prog: ir::Prog, sub_matches: &ArgMatches) -> Re let params = MPCParameters::read(reader, true) .map_err(|why| format!("Could not read `{}`: {}", path.display(), why))?; - let radix_dir = Path::new(sub_matches.value_of("radix-dir").unwrap()); + let radix_path = Path::new(sub_matches.value_of("radix-path").unwrap()); + let radix_file = File::open(radix_path) + .map_err(|why| format!("Could not open `{}`: {}", radix_path.display(), why))?; + + let mut radix_reader = BufReader::with_capacity(1024 * 1024, radix_file); + let circuit = Computation::without_witness(ir_prog); let result = params - .verify( - circuit, - true, - &radix_dir - .to_path_buf() - .into_os_string() - .into_string() - .unwrap(), - ) + .verify(circuit, true, &mut radix_reader) .map_err(|_| "Verification failed".to_string())?; let contribution_count = result.len(); diff --git a/zokrates_cli/tests/integration.rs b/zokrates_cli/tests/integration.rs index c5223fe7..3c67f8cd 100644 --- a/zokrates_cli/tests/integration.rs +++ b/zokrates_cli/tests/integration.rs @@ -458,13 +458,7 @@ mod integration { for g in glob("./examples/book/mpc_tutorial/**/*").expect("Failed to read glob pattern") { let path = g.unwrap(); - let canonical_path = - tmp_base.join(path.strip_prefix("examples/book/mpc_tutorial/").unwrap()); - if path.is_dir() { - std::fs::create_dir(canonical_path.clone()).expect("Failed to create directory"); - continue; - } - std::fs::hard_link(path, canonical_path).unwrap(); + std::fs::hard_link(path.clone(), tmp_base.join(path.file_name().unwrap())).unwrap(); } let stdlib = std::fs::canonicalize("../zokrates_stdlib/stdlib").unwrap(); diff --git a/zokrates_mpc/Cargo.toml b/zokrates_mpc/Cargo.toml new file mode 100644 index 00000000..b75f3554 --- /dev/null +++ b/zokrates_mpc/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "zokrates_mpc" +version = "0.1.0" +authors = ["dark64 "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +num_cpus = "1" +rand = "0.4" +byteorder = "1" +crossbeam = "0.7.3" +blake2-rfc = "0.2" +bellman_ce = { version = "^0.3", default-features = false } +pairing_ce = { version = "^0.21" } +getrandom = { version = "0.2", features = ["js"] } \ No newline at end of file diff --git a/zokrates_mpc/src/groth16/keypair.rs b/zokrates_mpc/src/groth16/keypair.rs new file mode 100644 index 00000000..906d216a --- /dev/null +++ b/zokrates_mpc/src/groth16/keypair.rs @@ -0,0 +1,118 @@ +extern crate bellman_ce; + +use bellman_ce::pairing::{CurveAffine, EncodedPoint, Engine}; +use std::io::{self, Read, Write}; + +/// This needs to be destroyed by at least one participant +/// for the final parameters to be secure. +pub struct PrivateKey { + pub delta: E::Fr, +} + +/// 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 + /// can check correctness of the public keys without having the entire + /// interstitial parameters for each contribution. + pub delta_after: E::G1Affine, + + /// Random element chosen by the contributor. + pub s: E::G1Affine, + + /// That element, taken to the contributor's secret delta. + pub s_delta: E::G1Affine, + + /// r is H(last_pubkey | s | s_delta), r_delta proves knowledge of delta + pub r_delta: E::G2Affine, + + /// Hash of the transcript (used for mapping to r) + pub transcript: [u8; 64], +} + +impl PublicKey { + pub 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)?; + + Ok(()) + } + + pub fn read(mut reader: R) -> io::Result> { + let mut g1_repr = <::G1Affine as CurveAffine>::Uncompressed::empty(); + let mut g2_repr = <::G2Affine as CurveAffine>::Uncompressed::empty(); + + reader.read_exact(g1_repr.as_mut())?; + let delta_after = g1_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + if delta_after.is_zero() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); + } + + 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", + )); + } + + reader.read_exact(g1_repr.as_mut())?; + let s_delta = g1_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + if s_delta.is_zero() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); + } + + reader.read_exact(g2_repr.as_mut())?; + let r_delta = g2_repr + .into_affine() + .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; + + if r_delta.is_zero() { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "point at infinity", + )); + } + + let mut transcript = [0u8; 64]; + reader.read_exact(&mut transcript)?; + + Ok(PublicKey { + delta_after, + s, + s_delta, + r_delta, + transcript, + }) + } +} + +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[..] + } +} diff --git a/zokrates_mpc/src/groth16/keypair_assembly.rs b/zokrates_mpc/src/groth16/keypair_assembly.rs new file mode 100644 index 00000000..7fd1a2b9 --- /dev/null +++ b/zokrates_mpc/src/groth16/keypair_assembly.rs @@ -0,0 +1,120 @@ +extern crate bellman_ce; + +use bellman_ce::pairing::Engine; +use bellman_ce::{ConstraintSystem, Index, LinearCombination, SynthesisError, Variable}; + +/// This is our assembly structure that we'll use to synthesize the +/// circuit into a QAP. +pub struct KeypairAssembly { + pub num_inputs: usize, + pub num_aux: usize, + pub num_constraints: usize, + pub at_inputs: Vec>, + pub bt_inputs: Vec>, + pub ct_inputs: Vec>, + pub at_aux: Vec>, + pub bt_aux: Vec>, + pub ct_aux: Vec>, +} + +impl ConstraintSystem for KeypairAssembly { + type Root = Self; + + 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. + + let index = self.num_aux; + self.num_aux += 1; + + self.at_aux.push(vec![]); + self.bt_aux.push(vec![]); + self.ct_aux.push(vec![]); + + Ok(Variable::new_unchecked(Index::Aux(index))) + } + + 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. + + let index = self.num_inputs; + self.num_inputs += 1; + + self.at_inputs.push(vec![]); + self.bt_inputs.push(vec![]); + self.ct_inputs.push(vec![]); + + 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 eval( + l: LinearCombination, + inputs: &mut [Vec<(E::Fr, usize)>], + aux: &mut [Vec<(E::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)), + } + } + } + + 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, + { + // Do nothing; we don't care about namespaces in this context. + } + + fn pop_namespace(&mut self) { + // Do nothing; we don't care about namespaces in this context. + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } +} diff --git a/zokrates_mpc/src/groth16/mod.rs b/zokrates_mpc/src/groth16/mod.rs new file mode 100644 index 00000000..9c0ae86f --- /dev/null +++ b/zokrates_mpc/src/groth16/mod.rs @@ -0,0 +1,3 @@ +mod keypair; +mod keypair_assembly; +pub mod parameters; diff --git a/zokrates_mpc/src/groth16/parameters.rs b/zokrates_mpc/src/groth16/parameters.rs new file mode 100644 index 00000000..352035c4 --- /dev/null +++ b/zokrates_mpc/src/groth16/parameters.rs @@ -0,0 +1,937 @@ +#![allow(clippy::redundant_clone)] + +extern crate bellman_ce; +extern crate byteorder; +extern crate crossbeam; +extern crate num_cpus; +extern crate rand; + +use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; + +use std::{ + io::{self, Read, Write}, + sync::Arc, +}; + +use bellman_ce::{ + groth16::{Parameters, VerifyingKey}, + pairing::{ + ff::{Field, PrimeField}, + CurveAffine, CurveProjective, EncodedPoint, Engine, Wnaf, + }, + worker::Worker, + Circuit, ConstraintSystem, Index, SynthesisError, Variable, +}; + +use rand::{ChaChaRng, Rand, Rng, SeedableRng}; + +use super::keypair::*; +use super::keypair_assembly::*; +use crate::hash_writer::HashWriter; + +/// 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, + cs_hash: [u8; 64], + 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 + } +} + +impl MPCParameters { + /// Create new Groth16 parameters for a given circuit. The resulting parameters are unsafe to + /// use until there are contributions (see `contribute()`). + #[cfg(not(target_arch = "wasm32"))] + pub fn new( + circuit: C, + should_filter_points_at_infinity: bool, + phase1_radix: &mut R, + ) -> Result, SynthesisError> + 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![], + }; + + // Allocate the "one" input variable + assembly.alloc_input(|| "", || Ok(E::Fr::one()))?; + + // Synthesize the circuit. + circuit.synthesize(&mut assembly)?; + + // 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, + ); + } + + // Compute the size of our evaluation domain + let mut m = 1; + let mut exp = 0; + while m < assembly.num_constraints { + m *= 2; + exp += 1; + + // Powers of Tau ceremony can't support more than 2^28 + if exp > 28 { + return Err(SynthesisError::PolynomialDegreeTooLarge); + } + } + + let read_g1 = |reader: &mut R| -> io::Result { + let mut repr = <::G1Affine as CurveAffine>::Uncompressed::empty(); + reader.read_exact(repr.as_mut())?; + + 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 read_g2 = |reader: &mut R| -> io::Result { + let mut repr = <::G2Affine as CurveAffine>::Uncompressed::empty(); + reader.read_exact(repr.as_mut())?; + + 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 alpha = read_g1(phase1_radix)?; + let beta_g1 = read_g1(phase1_radix)?; + let beta_g2 = read_g2(phase1_radix)?; + + let mut coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + coeffs_g1.push(read_g1(phase1_radix)?); + } + + let mut coeffs_g2 = Vec::with_capacity(m); + for _ in 0..m { + coeffs_g2.push(read_g2(phase1_radix)?); + } + + let mut alpha_coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + alpha_coeffs_g1.push(read_g1(phase1_radix)?); + } + + let mut beta_coeffs_g1 = Vec::with_capacity(m); + for _ in 0..m { + beta_coeffs_g1.push(read_g1(phase1_radix)?); + } + + // 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); + + let mut h = Vec::with_capacity(m - 1); + for _ in 0..m - 1 { + h.push(read_g1(phase1_radix)?); + } + + let mut ic = vec![E::G1::zero(); assembly.num_inputs]; + let mut l = vec![E::G1::zero(); assembly.num_aux]; + let mut a_g1 = vec![E::G1::zero(); assembly.num_inputs + assembly.num_aux]; + let mut b_g1 = vec![E::G1::zero(); assembly.num_inputs + assembly.num_aux]; + let mut b_g2 = vec![E::G2::zero(); assembly.num_inputs + assembly.num_aux]; + + #[allow(clippy::too_many_arguments)] + fn eval( + // Lagrange coefficients for tau + coeffs_g1: Arc>, + coeffs_g2: Arc>, + alpha_coeffs_g1: Arc>, + beta_coeffs_g1: Arc>, + + // QAP polynomials + at: &[Vec<(E::Fr, usize)>], + bt: &[Vec<(E::Fr, usize)>], + ct: &[Vec<(E::Fr, usize)>], + + // Resulting evaluated QAP polynomials + a_g1: &mut [E::G1], + b_g1: &mut [E::G1], + b_g2: &mut [E::G2], + ext: &mut [E::G1], + + // 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()); + + // 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(); + + 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)); + } + + 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)); + } + } + + // Batch normalize + E::G1::batch_normalization(a_g1); + E::G1::batch_normalization(b_g1); + E::G2::batch_normalization(b_g2); + E::G1::batch_normalization(ext); + }); + } + }); + } + + let worker = Worker::new(); + + // 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, + ); + + // Evaluate for auxiliary 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, + ); + + // 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 vk = VerifyingKey { + alpha_g1: alpha, + beta_g1, + beta_g2, + gamma_g2: E::G2Affine::one(), + delta_g1: E::G1Affine::one(), + delta_g2: E::G2Affine::one(), + ic: ic.into_iter().map(|e| e.into_affine()).collect(), + }; + + let params = if should_filter_points_at_infinity { + Parameters { + vk, + h: Arc::new(h), + l: Arc::new(l.into_iter().map(|e| e.into_affine()).collect()), + + // 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(), + ), + } + } else { + Parameters { + vk, + h: Arc::new(h), + l: Arc::new(l.into_iter().map(|e| e.into_affine()).collect()), + a: Arc::new(a_g1.into_iter().map(|e| e.into_affine()).collect()), + b_g1: Arc::new(b_g1.into_iter().map(|e| e.into_affine()).collect()), + b_g2: Arc::new(b_g2.into_iter().map(|e| e.into_affine()).collect()), + } + }; + + let h = { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + + params.write(&mut sink).unwrap(); + sink.into_hash() + }; + + let mut cs_hash = [0; 64]; + cs_hash.copy_from_slice(h.as_ref()); + + Ok(MPCParameters { + params, + cs_hash, + contributions: vec![], + }) + } + + // Generates random keypair + fn generate_keypair(&self, rng: &mut R) -> (PublicKey, PrivateKey) { + // Sample random delta + let delta: E::Fr = rng.gen(); + + // Compute delta s-pair in G1 + let s = E::G1::rand(rng).into_affine(); + let s_delta = s.mul(delta).into_affine(); + + // H(cs_hash | | s | s_delta) + let h = { + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + + sink.write_all(&self.cs_hash[..]).unwrap(); + for pubkey in &self.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() + }; + + // This avoids making a weird assumption about the hash into the group. + let mut transcript = [0; 64]; + transcript.copy_from_slice(h.as_ref()); + + // Compute delta s-pair in G2 + let r = hash_to_g2::(h.as_ref()).into_affine(); + let r_delta = r.mul(delta).into_affine(); + + ( + PublicKey { + delta_after: self.params.vk.delta_g1.mul(delta).into_affine(), + s, + s_delta, + r_delta, + transcript, + }, + PrivateKey { delta }, + ) + } + + /// 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) = self.generate_keypair(rng); + + #[cfg(not(target_arch = "wasm32"))] + fn batch_exp(bases: &mut [C], coeff: C::Scalar) { + let coeff = coeff.into_repr(); + + 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 + }; + + // 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(); + for (base, projective) in bases.iter_mut().zip(projective.iter_mut()) { + *projective = wnaf.base(base.into_projective(), 1).scalar(coeff); + } + }); + } + }) + .unwrap(); + + // Perform batch normalization + crossbeam::scope(|scope| { + for projective in projective.chunks_mut(chunk_size) { + scope.spawn(move |_| { + C::Projective::batch_normalization(projective); + }); + } + }) + .unwrap(); + + // Turn it all back into affine points + for (projective, affine) in projective.iter().zip(bases.iter_mut()) { + *affine = projective.into_affine(); + } + } + + #[cfg(target_arch = "wasm32")] + fn batch_exp(bases: &mut [C], coeff: C::Scalar) { + let coeff = coeff.into_repr(); + + let mut projective = vec![C::Projective::zero(); bases.len()]; + + // Perform wNAF, placing results into `projective`. + let mut wnaf = Wnaf::new(); + for (base, projective) in bases.iter_mut().zip(projective.iter_mut()) { + *projective = wnaf.base(base.into_projective(), 1).scalar(coeff); + } + + // Perform batch normalization + C::Projective::batch_normalization(&mut projective); + + // Turn it all back into affine points + for (projective, affine) in projective.iter().zip(bases.iter_mut()) { + *affine = projective.into_affine(); + } + } + + 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); + 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(); + + 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); + pubkey.write(&mut sink).unwrap(); + let h = sink.into_hash(); + let mut response = [0u8; 64]; + response.copy_from_slice(h.as_ref()); + response + } + } + + /// 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. + #[cfg(not(target_arch = "wasm32"))] + #[allow(clippy::result_unit_err)] + pub fn verify, R: Read>( + &self, + circuit: C, + should_filter_points_at_infinity: bool, + phase1_radix: &mut R, + ) -> Result, ()> { + let initial_params = + MPCParameters::new(circuit, should_filter_points_at_infinity, phase1_radix) + .map_err(|_| ())?; + + // 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(()); + } + + // 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(()); + } + + // 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(()); + } + + // IC shouldn't change, as gamma doesn't change + if initial_params.params.vk.ic != self.params.vk.ic { + return Err(()); + } + + // cs_hash should be the same + if initial_params.cs_hash[..] != self.cs_hash[..] { + return Err(()); + } + + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + sink.write_all(&initial_params.cs_hash[..]).unwrap(); + + let mut current_delta = E::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(); + + 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( + (E::G1Affine::one(), current_delta), + (E::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, E::G2Affine::one()), // reversed for inverse + ) { + return Err(()); + } + + if !same_ratio( + merge_pairs(&initial_params.params.l, &self.params.l), + (self.params.vk.delta_g2, E::G2Affine::one()), // reversed for inverse + ) { + return Err(()); + } + + Ok(result) + } + + /// 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)?; + + writer.write_u32::(self.contributions.len() as u32)?; + for pubkey in &self.contributions { + pubkey.write(&mut writer)?; + } + + Ok(()) + } + + /// Deserialize these parameters. If `checked` is false, curve validity and + /// group order checks are not performed. + 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, + }) + } + + /// Get the underlying Groth16 `Parameters` + pub fn get_params(&self) -> &Parameters { + &self.params + } +} + +/// Verify a contribution, given the old parameters and +/// the new parameters. Returns the hash of the contribution. +#[allow(clippy::result_unit_err)] +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(()); + } + + // None of the previous transformations should change + if before.contributions[..] != after.contributions[0..before.contributions.len()] { + return Err(()); + } + + // 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(()); + } + + // 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(()); + } + + // 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(()); + } + + // IC shouldn't change, as gamma doesn't change + if before.params.vk.ic != after.params.vk.ic { + return Err(()); + } + + // cs_hash should be the same + if before.cs_hash[..] != after.cs_hash[..] { + return Err(()); + } + + let sink = io::sink(); + let mut sink = HashWriter::new(sink); + sink.write_all(&before.cs_hash[..]).unwrap(); + + for pubkey in &before.contributions { + pubkey.write(&mut sink).unwrap(); + } + + 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(); + + let h = 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( + (before.params.vk.delta_g1, pubkey.delta_after), + (r, pubkey.r_delta), + ) { + return Err(()); + } + + // Current parameters should have consistent delta in G1 + if pubkey.delta_after != after.params.vk.delta_g1 { + return Err(()); + } + + // Current parameters should have consistent delta in G2 + if !same_ratio( + (E::G1Affine::one(), pubkey.delta_after), + (E::G2Affine::one(), after.params.vk.delta_g2), + ) { + return Err(()); + } + + // 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(()); + } + + 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(()); + } + + 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()); + + Ok(response) +} + +/// Checks if pairs have the same ratio. +fn same_ratio(g1: (G1, G1), g2: (G1::Pair, G1::Pair)) -> bool { + if g1.0.is_zero() || g1.1.is_zero() || g2.0.is_zero() || g2.1.is_zero() { + return false; + } + g1.0.pairing_with(&g2.1) == g1.1.pairing_with(&g2.0) +} + +#[cfg(not(target_arch = "wasm32"))] +fn merge_pairs(v1: &[G], v2: &[G]) -> (G, G) { + use rand::thread_rng; + use std::sync::Mutex; + assert_eq!(v1.len(), v2.len()); + + let chunk = (v1.len() / num_cpus::get()) + 1; + + let s = Arc::new(Mutex::new(G::Projective::zero())); + let sx = Arc::new(Mutex::new(G::Projective::zero())); + + crossbeam::scope(|scope| { + for (v1, v2) in v1.chunks(chunk).zip(v2.chunks(chunk)) { + let s = s.clone(); + let sx = sx.clone(); + + scope.spawn(move |_| { + // We do not need to be overly cautious of the RNG + // used for this check. + let rng = &mut thread_rng(); + + let mut wnaf = Wnaf::new(); + let mut local_s = G::Projective::zero(); + let mut local_sx = G::Projective::zero(); + + 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()); + + local_s.add_assign(&v1); + local_sx.add_assign(&v2); + } + + s.lock().unwrap().add_assign(&local_s); + sx.lock().unwrap().add_assign(&local_sx); + }); + } + }) + .unwrap(); + + let s = s.lock().unwrap().into_affine(); + let sx = sx.lock().unwrap().into_affine(); + + (s, sx) +} + +#[cfg(target_arch = "wasm32")] +fn merge_pairs(v1: &[G], v2: &[G]) -> (G, G) { + use std::mem::transmute; + assert_eq!(v1.len(), v2.len()); + + let mut wnaf = Wnaf::new(); + let mut s = G::Projective::zero(); + let mut sx = G::Projective::zero(); + + for (v1, v2) in v1.iter().zip(v2.iter()) { + // We do not need to be overly cautious of the RNG + // used for this check. + let mut seed = [0u8; 32]; + getrandom::getrandom(&mut seed).expect("could not get random seed"); + + let seed: [u32; 8] = unsafe { transmute(seed) }; + let mut rng = ChaChaRng::from_seed(&seed); + + let rho = G::Scalar::rand(&mut rng); + let mut wnaf = wnaf.scalar(rho.into_repr()); + let v1 = wnaf.base(v1.into_projective()); + let v2 = wnaf.base(v2.into_projective()); + + s.add_assign(&v1); + sx.add_assign(&v2); + } + + (s.into_affine(), sx.into_affine()) +} + +/// Hashes to G2 using the first 32 bytes of `digest`. Panics if `digest` is less +/// than 32 bytes. The input must be random. +fn hash_to_g2(mut digest: &[u8]) -> E::G2 { + assert!(digest.len() >= 32); + + let mut seed = Vec::with_capacity(8); + + for _ in 0..8 { + seed.push( + digest + .read_u32::() + .expect("assertion above guarantees this to work"), + ); + } + + ChaChaRng::from_seed(&seed).gen() +} diff --git a/zokrates_mpc/src/hash_writer.rs b/zokrates_mpc/src/hash_writer.rs new file mode 100644 index 00000000..e268811a --- /dev/null +++ b/zokrates_mpc/src/hash_writer.rs @@ -0,0 +1,53 @@ +extern crate blake2_rfc; + +use blake2_rfc::blake2b::Blake2b; +use std::io; +use std::io::Write; + +/// Abstraction over a writer which hashes the data being written. +pub struct HashWriter { + writer: W, + hasher: Blake2b, +} + +impl Clone for HashWriter { + fn clone(&self) -> HashWriter { + HashWriter { + writer: io::sink(), + hasher: self.hasher.clone(), + } + } +} + +impl HashWriter { + /// Construct a new `HashWriter` given an existing `writer` by value. + pub fn new(writer: W) -> Self { + HashWriter { + writer, + hasher: Blake2b::new(64), + } + } + + /// 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 + } +} + +impl Write for HashWriter { + fn write(&mut self, buf: &[u8]) -> io::Result { + let bytes = self.writer.write(buf)?; + + if bytes > 0 { + self.hasher.update(&buf[0..bytes]); + } + + Ok(bytes) + } + + fn flush(&mut self) -> io::Result<()> { + self.writer.flush() + } +} diff --git a/zokrates_mpc/src/lib.rs b/zokrates_mpc/src/lib.rs new file mode 100644 index 00000000..b1880d79 --- /dev/null +++ b/zokrates_mpc/src/lib.rs @@ -0,0 +1,2 @@ +pub mod groth16; +mod hash_writer;