diff --git a/.circleci/config.yml b/.circleci/config.yml index 0bfef41ee..655d5b522 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -45,7 +45,7 @@ jobs: - run: name: Run tests no_output_timeout: 1h - command: RUSTFLAGS="-D warnings" ./test.sh + command: RUSTFLAGS="-D warnings" RUST_BACKTRACE=1 ./test.sh - save-sccache-cache wasm_test: docker: @@ -79,7 +79,7 @@ jobs: - run: name: Run integration tests no_output_timeout: "30m" - command: RUSTFLAGS="-D warnings" ./integration_test.sh + command: RUSTFLAGS="-D warnings" RUST_BACKTRACE=1 ./integration_test.sh - save-sccache-cache deploy: docker: diff --git a/Cargo.lock b/Cargo.lock index 49e009185..cb6ac7d6b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -437,8 +437,9 @@ dependencies = [ [[package]] name = "bellman_ce" version = "0.3.2" -source = "git+https://github.com/georgwiese/bellman?rev=b356c7001f30da23bfad2b43eb0b7ca9804c8252#b356c7001f30da23bfad2b43eb0b7ca9804c8252" +source = "git+https://github.com/georgwiese/bellman?rev=dbed83d1971c29e8fdb07d341e8593fef8ba5eab#dbed83d1971c29e8fdb07d341e8593fef8ba5eab" dependencies = [ + "arrayvec 0.7.2", "bit-vec", "blake2s_const", "blake2s_simd", @@ -452,27 +453,11 @@ dependencies = [ "pairing_ce 0.28.2", "rand 0.4.6", "serde", + "smallvec", "tiny-keccak 1.5.0", "web-sys", ] -[[package]] -name = "bellman_ce" -version = "0.3.2" -source = "git+https://github.com/matter-labs/bellman#09474aa6fcb200a7da96f46495452fae92503ed8" -dependencies = [ - "bit-vec", - "blake2s_simd", - "byteorder", - "cfg-if 1.0.0", - "futures", - "hex 0.3.2", - "num_cpus", - "pairing_ce 0.28.2", - "rand 0.4.6", - "serde", -] - [[package]] name = "bellman_ce" version = "0.3.5" @@ -572,7 +557,7 @@ dependencies = [ [[package]] name = "blake2s_const" version = "0.6.0" -source = "git+https://github.com/georgwiese/bellman?rev=b356c7001f30da23bfad2b43eb0b7ca9804c8252#b356c7001f30da23bfad2b43eb0b7ca9804c8252" +source = "git+https://github.com/georgwiese/bellman?rev=dbed83d1971c29e8fdb07d341e8593fef8ba5eab#dbed83d1971c29e8fdb07d341e8593fef8ba5eab" dependencies = [ "arrayref", "arrayvec 0.5.2", @@ -1399,6 +1384,20 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "handlebars" +version = "3.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4498fc115fa7d34de968184e473529abb40eeb6be8bc5f7faba3d08c316cb3e3" +dependencies = [ + "log", + "pest", + "pest_derive", + "quick-error", + "serde", + "serde_json", +] + [[package]] name = "hashbrown" version = "0.9.1" @@ -2105,6 +2104,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + [[package]] name = "quote" version = "0.6.13" @@ -2567,6 +2572,12 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + [[package]] name = "solc" version = "0.1.0" @@ -3162,7 +3173,23 @@ dependencies = [ name = "zokrates_bellman" version = "0.1.0" dependencies = [ - "bellman_ce 0.3.2 (git+https://github.com/georgwiese/bellman?rev=b356c7001f30da23bfad2b43eb0b7ca9804c8252)", + "bellman_ce 0.3.5", + "getrandom", + "hex 0.4.3", + "pairing_ce 0.21.1", + "phase2", + "rand 0.4.6", + "zokrates_ast", + "zokrates_field", + "zokrates_interpreter", + "zokrates_proof_systems", +] + +[[package]] +name = "zokrates_bellman_plonk" +version = "0.1.0" +dependencies = [ + "bellman_ce 0.3.2", "getrandom", "hex 0.4.3", "phase2", @@ -3218,6 +3245,7 @@ dependencies = [ "zokrates_ark", "zokrates_ast", "zokrates_bellman", + "zokrates_bellman_plonk", "zokrates_circom", "zokrates_common", "zokrates_core", @@ -3293,7 +3321,8 @@ dependencies = [ "ark-ec", "ark-ff", "ark-serialize", - "bellman_ce 0.3.2 (git+https://github.com/matter-labs/bellman)", + "bellman_ce 0.3.2", + "bellman_ce 0.3.5", "bincode", "lazy_static", "num-bigint 0.2.6", @@ -3384,11 +3413,13 @@ dependencies = [ "cfg-if 0.1.10", "ethabi", "getrandom", + "handlebars", "hex 0.4.3", "primitive-types", "rand 0.4.6", "regex 0.2.11", "serde", + "serde_json", "zokrates_ast", "zokrates_field", ] diff --git a/Cargo.toml b/Cargo.toml index 92c7358e2..9ac09a8be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ members = [ "zokrates_interpreter", "zokrates_embed", "zokrates_bellman", + "zokrates_bellman_plonk", "zokrates_proof_systems", "zokrates_js", "zokrates_circom" diff --git a/zokrates_ark/src/lib.rs b/zokrates_ark/src/lib.rs index 48e6b5078..33683b160 100644 --- a/zokrates_ark/src/lib.rs +++ b/zokrates_ark/src/lib.rs @@ -157,7 +157,7 @@ mod parse { use super::*; use ark_ff::ToBytes; use zokrates_field::G2Type; - use zokrates_proof_systems::{Fq2, Fr, G1Affine, G2Affine, G2AffineFq, G2AffineFq2, GAffine}; + use zokrates_proof_systems::{Fq2, Fr, G1Affine, G2Affine, G2AffineFq, GAffine}; pub fn parse_g1( e: &::G1Affine, diff --git a/zokrates_bellman/Cargo.toml b/zokrates_bellman/Cargo.toml index 12cb4a9bc..852b1fbed 100644 --- a/zokrates_bellman/Cargo.toml +++ b/zokrates_bellman/Cargo.toml @@ -4,16 +4,16 @@ version = "0.1.0" edition = "2021" [features] -wasm = ["bellman_ce/nolog", "bellman_ce/wasm"] -multicore = ["bellman_ce/multicore", "phase2/multicore"] +wasm = ["bellman/nolog", "bellman/wasm"] +multicore = ["bellman/multicore", "phase2/multicore"] [dependencies] zokrates_field = { version = "0.5", path = "../zokrates_field", default-features = false } zokrates_ast = { version = "0.1", path = "../zokrates_ast", default-features = false } zokrates_proof_systems = { version = "0.1", path = "../zokrates_proof_systems", default-features = false } -bellman_ce = { git = "https://github.com/georgwiese/bellman", rev="b356c7001f30da23bfad2b43eb0b7ca9804c8252", default-features = false, features = ["plonk", "multicore"] } -# pairing = { package = "pairing_ce", version = "^0.21" } +bellman = { package = "bellman_ce", version = "^0.3", default-features = false } +pairing = { package = "pairing_ce", version = "^0.21" } phase2 = { git = "https://github.com/Zokrates/phase2", default-features = false } rand_0_4 = { version = "0.4", package = "rand" }# getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } diff --git a/zokrates_bellman/src/groth16.rs b/zokrates_bellman/src/groth16.rs index 126fbf18f..d8afaf50d 100644 --- a/zokrates_bellman/src/groth16.rs +++ b/zokrates_bellman/src/groth16.rs @@ -2,7 +2,7 @@ use bellman::groth16::{ prepare_verifying_key, verify_proof, Parameters, PreparedVerifyingKey, Proof as BellmanProof, VerifyingKey, }; -use bellman::pairing::{ff::to_hex, CurveAffine, Engine}; +use pairing::{ff::to_hex, CurveAffine, Engine}; use zokrates_field::BellmanFieldExtensions; use zokrates_field::Field; @@ -100,60 +100,60 @@ impl NonUniversalBackend for Bellman } } -// impl MpcBackend for Bellman { -// fn initialize>>( -// program: ProgIterator, -// phase1_radix: &mut R, -// output: &mut W, -// ) -> Result<(), String> { -// let circuit = Computation::without_witness(program); -// let params = MPCParameters::new(circuit, phase1_radix).map_err(|e| e.to_string())?; -// params.write(output).map_err(|e| e.to_string())?; -// Ok(()) -// } - -// fn contribute( -// params: &mut R, -// rng: &mut G, -// output: &mut W, -// ) -> Result<[u8; 64], String> { -// let mut params = -// MPCParameters::::read(params, true).map_err(|e| e.to_string())?; - -// let hash = params.contribute(rng); -// params.write(output).map_err(|e| e.to_string())?; - -// Ok(hash) -// } - -// fn verify>>( -// params: &mut P, -// program: ProgIterator, -// phase1_radix: &mut R, -// ) -> Result, String> { -// let params = -// MPCParameters::::read(params, true).map_err(|e| e.to_string())?; - -// let circuit = Computation::without_witness(program); -// let hashes = params -// .verify(circuit, phase1_radix) -// .map_err(|_| "parameters malformed".to_string())?; - -// Ok(hashes) -// } - -// fn export_keypair(params: &mut R) -> Result, String> { -// let params = -// MPCParameters::::read(params, true).map_err(|e| e.to_string())?; - -// let params = params.get_params(); -// let mut pk: Vec = Vec::new(); -// params.write(&mut pk).map_err(|e| e.to_string())?; - -// let vk = serialization::parameters_to_verification_key::(params); -// Ok(SetupKeypair::new(vk, pk)) -// } -// } +impl MpcBackend for Bellman { + fn initialize>>( + program: ProgIterator, + phase1_radix: &mut R, + output: &mut W, + ) -> Result<(), String> { + let circuit = Computation::without_witness(program); + let params = MPCParameters::new(circuit, phase1_radix).map_err(|e| e.to_string())?; + params.write(output).map_err(|e| e.to_string())?; + Ok(()) + } + + fn contribute( + params: &mut R, + rng: &mut G, + output: &mut W, + ) -> Result<[u8; 64], String> { + let mut params = + MPCParameters::::read(params, true).map_err(|e| e.to_string())?; + + let hash = params.contribute(rng); + params.write(output).map_err(|e| e.to_string())?; + + Ok(hash) + } + + fn verify>>( + params: &mut P, + program: ProgIterator, + phase1_radix: &mut R, + ) -> Result, String> { + let params = + MPCParameters::::read(params, true).map_err(|e| e.to_string())?; + + let circuit = Computation::without_witness(program); + let hashes = params + .verify(circuit, phase1_radix) + .map_err(|_| "parameters malformed".to_string())?; + + Ok(hashes) + } + + fn export_keypair(params: &mut R) -> Result, String> { + let params = + MPCParameters::::read(params, true).map_err(|e| e.to_string())?; + + let params = params.get_params(); + let mut pk: Vec = Vec::new(); + params.write(&mut pk).map_err(|e| e.to_string())?; + + let vk = parameters_to_verification_key::(params); + Ok(SetupKeypair::new(vk, pk)) + } +} pub fn parameters_to_verification_key( parameters: &Parameters, diff --git a/zokrates_bellman/src/lib.rs b/zokrates_bellman/src/lib.rs index 04022152e..6ceee8157 100644 --- a/zokrates_bellman/src/lib.rs +++ b/zokrates_bellman/src/lib.rs @@ -1,7 +1,4 @@ pub mod groth16; -pub mod plonk; - -extern crate bellman_ce as bellman; use bellman::groth16::Proof; use bellman::groth16::{ @@ -200,7 +197,7 @@ impl>> Co pub mod serialization { use super::*; - use bellman::{pairing::from_hex, CurveAffine, Engine}; + use bellman::{pairing::from_hex, pairing::CurveAffine, pairing::Engine}; use zokrates_proof_systems::{G1Affine, G2Affine}; pub fn to_g1( @@ -243,7 +240,7 @@ pub mod serialization { mod parse { use super::*; - use bellman::{pairing::CurveAffine, PrimeField}; + use bellman::{pairing::ff::PrimeField, pairing::CurveAffine}; use zokrates_proof_systems::{Fq2, Fr, G1Affine, G2Affine, GAffine}; fn to_hex(bytes: &[u8]) -> String { @@ -277,7 +274,7 @@ mod parse { pub fn parse_fr( e: &::Fr, ) -> Fr { - use crate::bellman::PrimeFieldRepr; + use bellman::pairing::ff::PrimeFieldRepr; let mut bytes: Vec = Vec::new(); e.into_repr().write_le(&mut bytes).unwrap(); bytes.reverse(); diff --git a/zokrates_bellman_plonk/Cargo.toml b/zokrates_bellman_plonk/Cargo.toml new file mode 100644 index 000000000..a2c28eee3 --- /dev/null +++ b/zokrates_bellman_plonk/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "zokrates_bellman_plonk" +version = "0.1.0" +edition = "2021" + +[features] +wasm = ["bellman_ce/nolog", "bellman_ce/wasm"] +multicore = ["bellman_ce/multicore", "phase2/multicore"] + +[dependencies] +zokrates_field = { version = "0.5", path = "../zokrates_field", default-features = false } +zokrates_ast = { version = "0.1", path = "../zokrates_ast", default-features = false } +zokrates_proof_systems = { version = "0.1", path = "../zokrates_proof_systems", default-features = false } + +bellman_ce = { git = "https://github.com/georgwiese/bellman", rev="dbed83d1971c29e8fdb07d341e8593fef8ba5eab", default-features = false, features = ["plonk", "multicore"] } +phase2 = { git = "https://github.com/Zokrates/phase2", default-features = false } +rand_0_4 = { version = "0.4", package = "rand" }# +getrandom = { version = "0.2", features = ["js", "wasm-bindgen"] } +hex = "0.4.2" + +[dev-dependencies] +zokrates_interpreter = { version = "0.1", path = "../zokrates_interpreter", features = ["bellman"] } + + + + + diff --git a/zokrates_bellman_plonk/src/lib.rs b/zokrates_bellman_plonk/src/lib.rs new file mode 100644 index 000000000..f7f5ca9bc --- /dev/null +++ b/zokrates_bellman_plonk/src/lib.rs @@ -0,0 +1,472 @@ +pub mod plonk; + +extern crate bellman_ce as bellman; + +use bellman::groth16::Proof; +use bellman::groth16::{ + create_random_proof, generate_random_parameters, prepare_verifying_key, verify_proof, + Parameters, +}; +use bellman::pairing::ff::ScalarEngine; +use bellman::{ + Circuit, ConstraintSystem, LinearCombination, SynthesisError, Variable as BellmanVariable, +}; +use std::collections::BTreeMap; +use zokrates_ast::common::Variable; +use zokrates_ast::ir::{CanonicalLinComb, ProgIterator, Statement, Witness}; +use zokrates_field::BellmanPlonkFieldExtensions; +use zokrates_field::Field; + +use rand_0_4::ChaChaRng; + +pub use self::parse::*; + +pub struct Bellman; + +#[derive(Clone)] +pub struct Computation>> { + program: ProgIterator, + witness: Option>, +} + +impl>> Computation { + pub fn with_witness(program: ProgIterator, witness: Witness) -> Self { + Computation { + program, + witness: Some(witness), + } + } + + pub fn without_witness(program: ProgIterator) -> Self { + Computation { + program, + witness: None, + } + } +} + +fn bellman_combination>( + l: CanonicalLinComb, + cs: &mut CS, + symbols: &mut BTreeMap, + witness: &mut Witness, +) -> LinearCombination { + l.0.into_iter() + .map(|(k, v)| { + ( + v.into_bellman(), + *symbols.entry(k).or_insert_with(|| { + match k.is_output() { + true => cs.alloc_input( + || format!("{}", k), + || { + Ok(witness + .0 + .remove(&k) + .ok_or(SynthesisError::AssignmentMissing)? + .into_bellman()) + }, + ), + false => cs.alloc( + || format!("{}", k), + || { + Ok(witness + .0 + .remove(&k) + .ok_or(SynthesisError::AssignmentMissing)? + .into_bellman()) + }, + ), + } + .unwrap() + }), + ) + }) + .fold(LinearCombination::zero(), |acc, e| acc + e) +} + +impl>> + Circuit for Computation +{ + fn synthesize>( + self, + cs: &mut CS, + ) -> Result<(), SynthesisError> { + // mapping from IR variables + let mut symbols = BTreeMap::new(); + + let mut witness = self.witness.unwrap_or_else(Witness::empty); + + assert!(symbols.insert(Variable::one(), CS::one()).is_none()); + + symbols.extend(self.program.arguments.iter().enumerate().map(|(index, p)| { + let wire = match p.private { + true => cs.alloc( + || format!("PRIVATE_INPUT_{}", index), + || { + Ok(witness + .0 + .remove(&p.id) + .ok_or(SynthesisError::AssignmentMissing)? + .into_bellman()) + }, + ), + false => cs.alloc_input( + || format!("PUBLIC_INPUT_{}", index), + || { + Ok(witness + .0 + .remove(&p.id) + .ok_or(SynthesisError::AssignmentMissing)? + .into_bellman()) + }, + ), + } + .unwrap(); + (p.id, wire) + })); + + for statement in self.program.statements { + if let Statement::Constraint(quad, lin, _) = statement { + let a = &bellman_combination( + quad.left.into_canonical(), + cs, + &mut symbols, + &mut witness, + ); + let b = &bellman_combination( + quad.right.into_canonical(), + cs, + &mut symbols, + &mut witness, + ); + let c = &bellman_combination(lin.into_canonical(), cs, &mut symbols, &mut witness); + + cs.enforce(|| "Constraint", |lc| lc + a, |lc| lc + b, |lc| lc + c); + } + } + + Ok(()) + } +} + +impl>> + Computation +{ + fn get_random_seed(&self) -> Result<[u32; 8], getrandom::Error> { + let mut seed = [0u8; 32]; + getrandom::getrandom(&mut seed)?; + + use std::mem::transmute; + // This is safe because we are just reinterpreting the bytes (u8[32] -> u32[8]), + // byte order or the actual content does not matter here as this is used + // as a random seed for the rng. + let seed: [u32; 8] = unsafe { transmute(seed) }; + Ok(seed) + } + + pub fn prove(self, params: &Parameters) -> Proof { + use rand_0_4::SeedableRng; + let seed = self.get_random_seed().unwrap(); + let rng = &mut ChaChaRng::from_seed(seed.as_ref()); + + // extract public inputs + let public_inputs = self.public_inputs_values(); + + let proof = create_random_proof(self, params, rng).unwrap(); + + let pvk = prepare_verifying_key(¶ms.vk); + + assert!(verify_proof(&pvk, &proof, &public_inputs).unwrap()); + + proof + } + + pub fn public_inputs_values(&self) -> Vec<::Fr> { + self.program + .public_inputs_values(self.witness.as_ref().unwrap()) + .iter() + .map(|v| v.clone().into_bellman()) + .collect() + } + + pub fn setup(self) -> Parameters { + use rand_0_4::SeedableRng; + let seed = self.get_random_seed().unwrap(); + let rng = &mut ChaChaRng::from_seed(seed.as_ref()); + // run setup phase + generate_random_parameters(self, rng).unwrap() + } +} + +pub mod serialization { + use super::*; + use bellman::{pairing::from_hex, pairing::CurveAffine, pairing::Engine}; + use zokrates_proof_systems::{G1Affine, G2Affine}; + + pub fn to_g1( + g1: G1Affine, + ) -> ::G1Affine { + if g1.is_infinity { + return ::G1Affine::zero(); + } + + ::G1Affine::from_xy_unchecked( + from_hex(&g1.x).unwrap(), + from_hex(&g1.y).unwrap(), + ) + } + pub fn to_g2( + g2: G2Affine, + ) -> ::G2Affine { + match g2 { + G2Affine::Fq2(g2) => { + if g2.is_infinity { + return ::G2Affine::zero(); + } + + let x = T::new_fq2(&(g2.x).0, &(g2.x).1); + let y = T::new_fq2(&(g2.y).0, &(g2.y).1); + ::G2Affine::from_xy_unchecked(x, y) + } + _ => unreachable!(), + } + } + + pub fn to_fr( + e: String, + ) -> ::Fr { + T::try_from_str(e.trim_start_matches("0x"), 16) + .unwrap() + .into_bellman() + } +} + +mod parse { + use super::*; + use bellman::{pairing::ff::PrimeField, pairing::CurveAffine}; + use zokrates_proof_systems::{Fq2, Fr, G1Affine, G2Affine, GAffine}; + + fn to_hex(bytes: &[u8]) -> String { + let mut hex = hex::encode(bytes); + hex.insert_str(0, "0x"); + hex + } + + pub fn parse_g1( + e: &::G1Affine, + ) -> G1Affine { + if e.is_zero() { + return G1Affine::infinity(); + } + + let uncompressed = e.into_uncompressed(); + let bytes: &[u8] = uncompressed.as_ref(); + + let mut iter = bytes.chunks(bytes.len() / 2); + + let x = to_hex(iter.next().unwrap()); + let y = to_hex(iter.next().unwrap()); + + G1Affine { + x, + y, + is_infinity: false, + } + } + + pub fn parse_fr( + e: &::Fr, + ) -> Fr { + use bellman::pairing::ff::PrimeFieldRepr; + let mut bytes: Vec = Vec::new(); + e.into_repr().write_le(&mut bytes).unwrap(); + bytes.reverse(); + + format!("0x{}", hex::encode(&bytes)) + } + + pub fn parse_g2( + e: &::G2Affine, + ) -> G2Affine { + if e.is_zero() { + return G2Affine::Fq2(GAffine::infinity()); + } + + let uncompressed = e.into_uncompressed(); + let bytes: &[u8] = uncompressed.as_ref(); + + let mut iter = bytes.chunks(bytes.len() / 4); + let x1 = to_hex(iter.next().unwrap()); + let x0 = to_hex(iter.next().unwrap()); + let y1 = to_hex(iter.next().unwrap()); + let y0 = to_hex(iter.next().unwrap()); + + G2Affine::Fq2(GAffine::new(Fq2(x0, x1), Fq2(y0, y1))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use zokrates_ast::ir::LinComb; + use zokrates_field::Bn128Field; + use zokrates_interpreter::Interpreter; + + mod prove { + use super::*; + use zokrates_ast::flat::Parameter; + use zokrates_ast::ir::Prog; + + #[test] + fn empty() { + let program: Prog = Prog::default(); + + let interpreter = Interpreter::default(); + + let witness = interpreter.execute(program.clone(), &[]).unwrap(); + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn identity() { + let program: Prog = Prog { + arguments: vec![Parameter::private(Variable::new(0))], + return_count: 1, + statements: vec![Statement::constraint(Variable::new(0), Variable::public(0))], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(0)]) + .unwrap(); + + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn public_identity() { + let program: Prog = Prog { + arguments: vec![Parameter::public(Variable::new(0))], + return_count: 1, + statements: vec![Statement::constraint(Variable::new(0), Variable::public(0))], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(0)]) + .unwrap(); + + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn no_arguments() { + let program: Prog = Prog { + arguments: vec![], + return_count: 1, + statements: vec![Statement::constraint(Variable::one(), Variable::public(0))], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter.execute(program.clone(), &[]).unwrap(); + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn unordered_variables() { + // public variables must be ordered from 0 + // private variables can be unordered + let program: Prog = Prog { + arguments: vec![ + Parameter::private(Variable::new(42)), + Parameter::public(Variable::new(51)), + ], + return_count: 2, + statements: vec![ + Statement::constraint( + LinComb::from(Variable::new(42)) + LinComb::from(Variable::new(51)), + Variable::public(0), + ), + Statement::constraint( + LinComb::from(Variable::one()) + LinComb::from(Variable::new(42)), + Variable::public(1), + ), + ], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(3), Bn128Field::from(4)]) + .unwrap(); + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn one() { + let program: Prog = Prog { + arguments: vec![Parameter::public(Variable::new(42))], + return_count: 1, + statements: vec![Statement::constraint( + LinComb::from(Variable::new(42)) + LinComb::one(), + Variable::public(0), + )], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(3)]) + .unwrap(); + + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + + #[test] + fn with_directives() { + let program: Prog = Prog { + arguments: vec![ + Parameter::private(Variable::new(42)), + Parameter::public(Variable::new(51)), + ], + return_count: 1, + statements: vec![Statement::constraint( + LinComb::from(Variable::new(42)) + LinComb::from(Variable::new(51)), + Variable::public(0), + )], + }; + + let interpreter = Interpreter::default(); + + let witness = interpreter + .execute(program.clone(), &[Bn128Field::from(3), Bn128Field::from(4)]) + .unwrap(); + let computation = Computation::with_witness(program, witness); + + let params = computation.clone().setup(); + let _proof = computation.prove(¶ms); + } + } +} diff --git a/zokrates_bellman/src/plonk.rs b/zokrates_bellman_plonk/src/plonk.rs similarity index 89% rename from zokrates_bellman/src/plonk.rs rename to zokrates_bellman_plonk/src/plonk.rs index ce4942356..db99b48de 100644 --- a/zokrates_bellman/src/plonk.rs +++ b/zokrates_bellman_plonk/src/plonk.rs @@ -6,7 +6,7 @@ use bellman::plonk::{ Proof as BellmanProof, SetupPolynomials, VerificationKey as BellmanVerificationKey, }; -use zokrates_field::BellmanFieldExtensions; +use zokrates_field::BellmanPlonkFieldExtensions; use zokrates_field::Field; use zokrates_proof_systems::{Backend, Proof, SetupKeypair, UniversalBackend}; @@ -19,7 +19,7 @@ use zokrates_ast::ir::{ProgIterator, Statement, Witness}; use zokrates_proof_systems::plonk::{Plonk, ProofPoints, VerificationKey}; use zokrates_proof_systems::Scheme; -impl UniversalBackend for Bellman { +impl UniversalBackend for Bellman { fn universal_setup(size: u32) -> Vec { let crs: Crs = Crs::::dummy_crs(2usize.pow(size) as usize); @@ -69,7 +69,7 @@ impl UniversalBackend for Bellman { } } -impl Backend for Bellman { +impl Backend for Bellman { fn generate_proof>>( program: ProgIterator, witness: Witness, @@ -94,6 +94,9 @@ impl Backend for Bellman { None, &Crs::::dummy_crs(2usize.pow(10)), None, + // The multithreaded bellman_ce implementation does not work properly with any number of CPUs, + // so we hard-code 4 threads here... + Some(4), ) .unwrap(); @@ -131,7 +134,7 @@ impl Backend for Bellman { } } -fn deserialize_vk( +fn deserialize_vk( vk: >::VerificationKey, ) -> BellmanVerificationKey { BellmanVerificationKey { @@ -169,9 +172,15 @@ fn deserialize_vk( } } -fn serialize_vk( +fn serialize_vk( vk: BellmanVerificationKey, ) -> >::VerificationKey { + let domain = bellman::plonk::domains::Domain::< + ::Fr, + >::new_for_size(vk.n.next_power_of_two() as u64) + .unwrap(); + let omega = parse_fr::(&domain.generator); + VerificationKey { n: vk.n as u32, num_inputs: vk.num_inputs as u32, @@ -195,10 +204,11 @@ fn serialize_vk( .try_into() .map_err(|_| ()) .unwrap(), + omega, } } -fn deserialize_proof( +fn deserialize_proof( proof: Proof, ) -> BellmanProof { let inputs = proof.inputs; @@ -245,7 +255,7 @@ fn deserialize_proof( } } -fn serialize_proof( +fn serialize_proof( proof: BellmanProof, ) -> Proof { let public_inputs = proof.input_values.iter().map(parse_fr::).collect(); @@ -311,8 +321,8 @@ mod tests { println!("{}", &program); // generate a dummy universal setup of size 2**10 - let crs: Crs<::BellmanEngine, CrsForMonomialForm> = - Crs::<::BellmanEngine, CrsForMonomialForm>::dummy_crs(2usize.pow(10) as usize); + let crs: Crs<::BellmanEngine, CrsForMonomialForm> = + Crs::<::BellmanEngine, CrsForMonomialForm>::dummy_crs(2usize.pow(10) as usize); // transpile let hints = transpile(Computation::without_witness(program.clone())).unwrap(); @@ -341,7 +351,7 @@ mod tests { // generate a proof with no setup precomputations and no init params for the transcript, using Blake2s let proof: BellmanProof< - ::BellmanEngine, + ::BellmanEngine, PlonkCsWidth4WithNextStepParams, > = prove_by_steps::<_, _, Blake2sTranscript<_>>( computation, @@ -350,6 +360,9 @@ mod tests { None, &crs, None, + // The multithreaded bellman_ce implementation does not work properly with any number of CPUs, + // so we hard-code 4 threads here... + Some(4), ) .unwrap(); diff --git a/zokrates_cli/Cargo.toml b/zokrates_cli/Cargo.toml index 81e9a7472..2da8b226a 100644 --- a/zokrates_cli/Cargo.toml +++ b/zokrates_cli/Cargo.toml @@ -7,7 +7,7 @@ edition = "2018" [features] default = ["bellman", "ark"] -bellman = ["zokrates_bellman", "zokrates_core/bellman", "zokrates_common/bellman"] +bellman = ["zokrates_bellman", "zokrates_bellman_plonk", "zokrates_core/bellman", "zokrates_common/bellman"] ark = ["zokrates_ark", "zokrates_core/ark", "zokrates_common/ark"] [dependencies] @@ -41,6 +41,7 @@ sha2 = "0.10.0" zokrates_proof_systems = { version = "0.1", path = "../zokrates_proof_systems", default-features = false } zokrates_ark = { version = "0.1", path = "../zokrates_ark", default-features = false, optional = true } zokrates_bellman = { version = "0.1", path = "../zokrates_bellman", default-features = false, optional = true } +zokrates_bellman_plonk = { version = "0.1", path = "../zokrates_bellman_plonk", default-features = false, optional = true } [dev-dependencies] glob = "0.2.11" diff --git a/zokrates_cli/src/ops/export_verifier.rs b/zokrates_cli/src/ops/export_verifier.rs index 08b35a44a..993214d5e 100644 --- a/zokrates_cli/src/ops/export_verifier.rs +++ b/zokrates_cli/src/ops/export_verifier.rs @@ -68,6 +68,9 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { (CurveParameter::Bn128, SchemeParameter::MARLIN) => { cli_export_verifier::(sub_matches, vk) } + (CurveParameter::Bn128, SchemeParameter::PLONK) => { + cli_export_verifier::(sub_matches, vk) + } (curve_parameter, scheme_parameter) => Err(format!("Could not export verifier with given parameters (curve: {}, scheme: {}): not supported", curve_parameter, scheme_parameter)) } } diff --git a/zokrates_cli/src/ops/generate_proof.rs b/zokrates_cli/src/ops/generate_proof.rs index a3685b575..cc30c5369 100644 --- a/zokrates_cli/src/ops/generate_proof.rs +++ b/zokrates_cli/src/ops/generate_proof.rs @@ -9,6 +9,8 @@ use zokrates_ark::Ark; use zokrates_ast::ir::{self, ProgEnum}; #[cfg(feature = "bellman")] use zokrates_bellman::Bellman; +#[cfg(feature = "bellman")] +use zokrates_bellman_plonk::Bellman as BellmanPlonk; use zokrates_common::constants; use zokrates_common::helpers::*; use zokrates_field::Field; @@ -108,9 +110,11 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { }, #[cfg(feature = "bellman")] Parameters(BackendParameter::Bellman, _, SchemeParameter::PLONK) => match prog { - ProgEnum::Bn128Program(p) => cli_generate_proof::<_, _, Plonk, Bellman>(p, sub_matches), + ProgEnum::Bn128Program(p) => { + cli_generate_proof::<_, _, Plonk, BellmanPlonk>(p, sub_matches) + } ProgEnum::Bls12_381Program(p) => { - cli_generate_proof::<_, _, Plonk, Bellman>(p, sub_matches) + cli_generate_proof::<_, _, Plonk, BellmanPlonk>(p, sub_matches) } _ => unreachable!(), }, diff --git a/zokrates_cli/src/ops/mpc/beacon.rs b/zokrates_cli/src/ops/mpc/beacon.rs index c585f5631..b5af669e7 100644 --- a/zokrates_cli/src/ops/mpc/beacon.rs +++ b/zokrates_cli/src/ops/mpc/beacon.rs @@ -61,8 +61,8 @@ pub fn subcommand() -> App<'static, 'static> { pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { match sub_matches.value_of("curve").unwrap() { - // BN128 => cli_mpc_beacon::(sub_matches), - // BLS12_381 => cli_mpc_beacon::(sub_matches), + BN128 => cli_mpc_beacon::(sub_matches), + BLS12_381 => cli_mpc_beacon::(sub_matches), _ => unreachable!(), } } diff --git a/zokrates_cli/src/ops/mpc/contribute.rs b/zokrates_cli/src/ops/mpc/contribute.rs index e2b668871..3490f381b 100644 --- a/zokrates_cli/src/ops/mpc/contribute.rs +++ b/zokrates_cli/src/ops/mpc/contribute.rs @@ -53,8 +53,8 @@ pub fn subcommand() -> App<'static, 'static> { pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { match sub_matches.value_of("curve").unwrap() { - // BN128 => cli_mpc_contribute::(sub_matches), - // BLS12_381 => cli_mpc_contribute::(sub_matches), + BN128 => cli_mpc_contribute::(sub_matches), + BLS12_381 => cli_mpc_contribute::(sub_matches), _ => unreachable!(), } } diff --git a/zokrates_cli/src/ops/mpc/export.rs b/zokrates_cli/src/ops/mpc/export.rs index dce308def..cf640fa1c 100644 --- a/zokrates_cli/src/ops/mpc/export.rs +++ b/zokrates_cli/src/ops/mpc/export.rs @@ -55,8 +55,8 @@ pub fn subcommand() -> App<'static, 'static> { pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { match sub_matches.value_of("curve").unwrap() { - // BN128 => cli_mpc_export::(sub_matches), - // BLS12_381 => cli_mpc_export::(sub_matches), + BN128 => cli_mpc_export::(sub_matches), + BLS12_381 => cli_mpc_export::(sub_matches), _ => unreachable!(), } } diff --git a/zokrates_cli/src/ops/mpc/init.rs b/zokrates_cli/src/ops/mpc/init.rs index 0fa6a523a..71136366c 100644 --- a/zokrates_cli/src/ops/mpc/init.rs +++ b/zokrates_cli/src/ops/mpc/init.rs @@ -51,8 +51,8 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { let mut reader = BufReader::new(file); match ProgEnum::deserialize(&mut reader)? { - // ProgEnum::Bn128Program(p) => cli_mpc_init::<_, _, G16, Bellman>(p, sub_matches), - // ProgEnum::Bls12_381Program(p) => cli_mpc_init::<_, _, G16, Bellman>(p, sub_matches), + ProgEnum::Bn128Program(p) => cli_mpc_init::<_, _, G16, Bellman>(p, sub_matches), + ProgEnum::Bls12_381Program(p) => cli_mpc_init::<_, _, G16, Bellman>(p, sub_matches), _ => Err("Current protocol only supports bn128/bls12_381 programs".into()), } } diff --git a/zokrates_cli/src/ops/mpc/verify.rs b/zokrates_cli/src/ops/mpc/verify.rs index 968be2692..6beca03db 100644 --- a/zokrates_cli/src/ops/mpc/verify.rs +++ b/zokrates_cli/src/ops/mpc/verify.rs @@ -51,8 +51,8 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { let mut reader = BufReader::new(file); match ProgEnum::deserialize(&mut reader)? { - // ProgEnum::Bn128Program(p) => cli_mpc_verify::<_, _, G16, Bellman>(p, sub_matches), - // ProgEnum::Bls12_381Program(p) => cli_mpc_verify::<_, _, G16, Bellman>(p, sub_matches), + ProgEnum::Bn128Program(p) => cli_mpc_verify::<_, _, G16, Bellman>(p, sub_matches), + ProgEnum::Bls12_381Program(p) => cli_mpc_verify::<_, _, G16, Bellman>(p, sub_matches), _ => Err("Current protocol only supports bn128/bls12_381 programs".into()), } } diff --git a/zokrates_cli/src/ops/setup.rs b/zokrates_cli/src/ops/setup.rs index c4f80415e..f5cfa7346 100644 --- a/zokrates_cli/src/ops/setup.rs +++ b/zokrates_cli/src/ops/setup.rs @@ -9,6 +9,8 @@ use zokrates_ark::Ark; use zokrates_ast::ir::{self, ProgEnum}; #[cfg(feature = "bellman")] use zokrates_bellman::Bellman; +#[cfg(feature = "bellman")] +use zokrates_bellman_plonk::Bellman as BellmanPlonk; use zokrates_common::constants; use zokrates_common::helpers::*; use zokrates_field::Field; @@ -179,10 +181,10 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { match prog { ProgEnum::Bn128Program(p) => { - cli_setup_universal::<_, _, Plonk, Bellman>(p, setup, sub_matches) + cli_setup_universal::<_, _, Plonk, BellmanPlonk>(p, setup, sub_matches) } ProgEnum::Bls12_381Program(p) => { - cli_setup_universal::<_, _, Plonk, Bellman>(p, setup, sub_matches) + cli_setup_universal::<_, _, Plonk, BellmanPlonk>(p, setup, sub_matches) } _ => unreachable!(), } diff --git a/zokrates_cli/src/ops/universal_setup.rs b/zokrates_cli/src/ops/universal_setup.rs index 3713e8ee8..d43ea5747 100644 --- a/zokrates_cli/src/ops/universal_setup.rs +++ b/zokrates_cli/src/ops/universal_setup.rs @@ -7,7 +7,7 @@ use std::path::Path; #[cfg(feature = "ark")] use zokrates_ark::Ark; #[cfg(feature = "bellman")] -use zokrates_bellman::Bellman; +use zokrates_bellman_plonk::Bellman as BellmanPlonk; use zokrates_common::constants; use zokrates_common::helpers::*; use zokrates_field::{Bls12_377Field, Bls12_381Field, Bn128Field, Bw6_761Field, Field}; @@ -78,14 +78,14 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { match parameters { #[cfg(feature = "bellman")] Parameters(BackendParameter::Bellman, CurveParameter::Bn128, SchemeParameter::PLONK) => { - cli_universal_setup::(sub_matches) + cli_universal_setup::(sub_matches) } #[cfg(feature = "bellman")] Parameters( BackendParameter::Bellman, CurveParameter::Bls12_381, SchemeParameter::PLONK, - ) => cli_universal_setup::(sub_matches), + ) => cli_universal_setup::(sub_matches), #[cfg(feature = "ark")] Parameters(BackendParameter::Ark, CurveParameter::Bn128, SchemeParameter::MARLIN) => { cli_universal_setup::(sub_matches) diff --git a/zokrates_cli/src/ops/verify.rs b/zokrates_cli/src/ops/verify.rs index 2b5fe631c..78ea52b13 100644 --- a/zokrates_cli/src/ops/verify.rs +++ b/zokrates_cli/src/ops/verify.rs @@ -8,6 +8,8 @@ use std::path::Path; use zokrates_ark::Ark; #[cfg(feature = "bellman")] use zokrates_bellman::Bellman; +#[cfg(feature = "bellman")] +use zokrates_bellman_plonk::Bellman as BellmanPlonk; use zokrates_common::constants; use zokrates_common::helpers::*; use zokrates_field::{Bls12_377Field, Bls12_381Field, Bn128Field, Bw6_761Field, Field}; @@ -122,14 +124,14 @@ pub fn exec(sub_matches: &ArgMatches) -> Result<(), String> { } #[cfg(feature = "bellman")] Parameters(BackendParameter::Bellman, CurveParameter::Bn128, SchemeParameter::PLONK) => { - cli_verify::(vk, proof) + cli_verify::(vk, proof) } #[cfg(feature = "bellman")] Parameters( BackendParameter::Bellman, CurveParameter::Bls12_381, SchemeParameter::PLONK, - ) => cli_verify::(vk, proof), + ) => cli_verify::(vk, proof), #[cfg(feature = "ark")] Parameters(BackendParameter::Ark, CurveParameter::Bn128, SchemeParameter::G16) => { cli_verify::(vk, proof) diff --git a/zokrates_cli/tests/integration.rs b/zokrates_cli/tests/integration.rs index 6ee737663..216c04666 100644 --- a/zokrates_cli/tests/integration.rs +++ b/zokrates_cli/tests/integration.rs @@ -23,7 +23,7 @@ mod integration { use zokrates_ast::typed::abi::Abi; use zokrates_field::Bn128Field; use zokrates_proof_systems::{ - to_token::ToToken, Marlin, Proof, SolidityCompatibleScheme, G16, GM17, + to_token::ToToken, Marlin, Plonk, Proof, SolidityCompatibleScheme, G16, GM17, SOLIDITY_G2_ADDITION_LIB, }; @@ -43,7 +43,6 @@ mod integration { fn test_compile_and_witness_dir() { let global_dir = TempDir::new("global").unwrap(); let global_base = global_dir.path(); - let universal_setup_path = global_base.join("universal_setup.dat"); // GENERATE A UNIVERSAL SETUP assert_cli::Assert::main_binary() @@ -54,7 +53,27 @@ mod integration { "--proving-scheme", "marlin", "--universal-setup-path", - universal_setup_path.to_str().unwrap(), + global_base + .join("universal_setup_marlin.dat") + .to_str() + .unwrap(), + ]) + .succeeds() + .unwrap(); + assert_cli::Assert::main_binary() + .with_args(&[ + "universal-setup", + "--backend", + "bellman", + "--size", + "10", + "--proving-scheme", + "plonk", + "--universal-setup-path", + global_base + .join("universal_setup_plonk.dat") + .to_str() + .unwrap(), ]) .succeeds() .unwrap(); @@ -89,6 +108,8 @@ mod integration { expected_witness_path: &Path, global_path: &Path, ) { + println!("Running test for program: {:?}", program_name); + let tmp_dir = TempDir::new(program_name).unwrap(); let tmp_base = tmp_dir.path(); let test_case_path = tmp_base.join(program_name); @@ -97,7 +118,6 @@ mod integration { let witness_path = tmp_base.join(program_name).join("witness"); let inline_witness_path = tmp_base.join(program_name).join("inline_witness"); let proof_path = tmp_base.join(program_name).join("proof.json"); - let universal_setup_path = global_path.join("universal_setup.dat"); let verification_key_path = tmp_base .join(program_name) .join("verification") @@ -235,7 +255,7 @@ mod integration { } let backends = map! { - "bellman" => vec!["g16"], + "bellman" => vec!["g16", "plonk"], "ark" => vec!["g16", "gm17", "marlin"] }; @@ -243,6 +263,8 @@ mod integration { for scheme in &schemes { println!("test with {}, {}", backend, scheme); // SETUP + let universal_setup_path = + global_path.join(format!("universal_setup_{}.dat", scheme)); let setup = assert_cli::Assert::main_binary() .with_args(&[ "setup", @@ -264,6 +286,10 @@ mod integration { .doesnt_contain("This program is too small to generate a setup with Marlin") .execute(); + if let Err(e) = &setup { + eprint!("{}", e); + } + if setup.is_ok() { // GENERATE-PROOF assert_cli::Assert::main_binary() @@ -318,7 +344,6 @@ mod integration { .unwrap(); match *scheme { "marlin" => { - // Get the proof let proof: Proof = serde_json::from_reader( File::open(proof_path.to_str().unwrap()).unwrap(), ) @@ -326,8 +351,15 @@ mod integration { test_solidity_verifier(contract_str, proof); } + "plonk" => { + let proof: Proof = serde_json::from_reader( + File::open(proof_path.to_str().unwrap()).unwrap(), + ) + .unwrap(); + + test_solidity_verifier(contract_str, proof); + } "g16" => { - // Get the proof let proof: Proof = serde_json::from_reader( File::open(proof_path.to_str().unwrap()).unwrap(), ) @@ -336,7 +368,6 @@ mod integration { test_solidity_verifier(contract_str, proof); } "gm17" => { - // Get the proof let proof: Proof = serde_json::from_reader( File::open(proof_path.to_str().unwrap()).unwrap(), ) @@ -411,7 +442,6 @@ mod integration { }) .collect::>(), ); - let inputs = [proof_token, input_token.clone()]; // Call verify function on contract @@ -445,7 +475,7 @@ mod integration { ) .unwrap(); - assert_eq!(result.op_out, Return::InvalidOpcode); + assert!(result.op_out == Return::InvalidOpcode || result.op_out == Return::Revert); } fn test_compile_and_smtlib2( diff --git a/zokrates_field/Cargo.toml b/zokrates_field/Cargo.toml index 14ce21586..3fc0a91b7 100644 --- a/zokrates_field/Cargo.toml +++ b/zokrates_field/Cargo.toml @@ -6,7 +6,7 @@ edition = "2018" [features] default = ["bellman"] -bellman = ["bellman_ce"] +bellman = ["bellman_ce", "bellman_ce_plonk"] [dependencies] serde = "1.0" @@ -19,7 +19,8 @@ num-traits = { version = "0.2", default-features = false } num-integer = { version = "0.1", default-features = false } # bellman -bellman_ce = { git = "https://github.com/matter-labs/bellman", version = "^0.3", default-features = false, optional = true } +bellman_ce = { version = "^0.3", default-features = false, optional = true } +bellman_ce_plonk = { package = "bellman_ce", git = "https://github.com/georgwiese/bellman", rev="dbed83d1971c29e8fdb07d341e8593fef8ba5eab", default-features = false, features = ["plonk", "multicore"], optional = true } # ark ark-ff = { version = "^0.3.0", default-features = false } diff --git a/zokrates_field/src/bls12_381.rs b/zokrates_field/src/bls12_381.rs index 403d0b947..7867689ee 100644 --- a/zokrates_field/src/bls12_381.rs +++ b/zokrates_field/src/bls12_381.rs @@ -7,6 +7,16 @@ ark_extensions!(Bls12_381); #[cfg(feature = "bellman")] use bellman_ce::pairing::bls12_381::{Bls12, Fq2}; +#[cfg(feature = "bellman")] +use bellman_ce_plonk::pairing::bls12_381::{Bls12 as Bls12Plonk, Fq2 as Fq2Plonk}; + use crate::G2Type; #[cfg(feature = "bellman")] -bellman_extensions!(Bls12, Fq2); +bellman_extensions!(bellman_ce, BellmanFieldExtensions, Bls12, Fq2); +#[cfg(feature = "bellman")] +bellman_extensions!( + bellman_ce_plonk, + BellmanPlonkFieldExtensions, + Bls12Plonk, + Fq2Plonk +); diff --git a/zokrates_field/src/bn128.rs b/zokrates_field/src/bn128.rs index 4b6c9cbb3..946d09165 100644 --- a/zokrates_field/src/bn128.rs +++ b/zokrates_field/src/bn128.rs @@ -7,9 +7,19 @@ ark_extensions!(Bn254); #[cfg(feature = "bellman")] use bellman_ce::pairing::bn256::{Bn256, Fq2}; +#[cfg(feature = "bellman")] +use bellman_ce_plonk::pairing::bn256::{Bn256 as Bn256Plonk, Fq2 as Fq2Plonk}; + use crate::G2Type; #[cfg(feature = "bellman")] -bellman_extensions!(Bn256, Fq2); +bellman_extensions!(bellman_ce, BellmanFieldExtensions, Bn256, Fq2); +#[cfg(feature = "bellman")] +bellman_extensions!( + bellman_ce_plonk, + BellmanPlonkFieldExtensions, + Bn256Plonk, + Fq2Plonk +); #[cfg(test)] mod tests { @@ -306,7 +316,12 @@ mod tests { let rng = &mut thread_rng(); for _ in 0..1000 { let a: Fr = rng.gen(); - assert_eq!(FieldPrime::from_bellman(a).into_bellman(), a); + assert_eq!( + BellmanFieldExtensions::into_bellman( + ::from_bellman(a) + ), + a + ); } } @@ -317,8 +332,13 @@ mod tests { for _ in 0..1000 { let a: Fr = rng.gen(); // now test idempotence - let a = FieldPrime::from_bellman(a); - assert_eq!(FieldPrime::from_bellman(a.clone().into_bellman()), a); + let a = ::from_bellman(a); + assert_eq!( + ::from_bellman( + BellmanFieldExtensions::into_bellman(a.clone()) + ), + a + ); } } @@ -326,21 +346,24 @@ mod tests { fn one() { let a = FieldPrime::from(1); - assert_eq!(a.into_bellman(), Fr::one()); + assert_eq!(BellmanFieldExtensions::into_bellman(a), Fr::one()); } #[test] fn zero() { let a = FieldPrime::from(0); - assert_eq!(a.into_bellman(), Fr::zero()); + assert_eq!(BellmanFieldExtensions::into_bellman(a), Fr::zero()); } #[test] fn minus_one() { let mut a: Fr = Fr::one(); a.negate(); - assert_eq!(FieldPrime::from_bellman(a), FieldPrime::from(-1)); + assert_eq!( + ::from_bellman(a), + FieldPrime::from(-1) + ); } #[test] @@ -350,13 +373,13 @@ mod tests { let mut a: Fr = rng.gen(); let b: Fr = rng.gen(); - let aa = FieldPrime::from_bellman(a); - let bb = FieldPrime::from_bellman(b); + let aa = ::from_bellman(a); + let bb = ::from_bellman(b); let cc = aa + bb; a.add_assign(&b); - assert_eq!(FieldPrime::from_bellman(a), cc); + assert_eq!(::from_bellman(a), cc); } } } diff --git a/zokrates_field/src/lib.rs b/zokrates_field/src/lib.rs index dc1e6b907..7c45c5b58 100644 --- a/zokrates_field/src/lib.rs +++ b/zokrates_field/src/lib.rs @@ -8,6 +8,7 @@ extern crate num_bigint; #[cfg(feature = "bellman")] use bellman_ce::pairing::{ff::ScalarEngine, Engine}; +use bellman_ce_plonk::pairing::{ff::ScalarEngine as ScalarEnginePlonk, Engine as EnginePlonk}; use num_bigint::BigUint; use num_traits::{CheckedDiv, One, Zero}; @@ -33,6 +34,16 @@ pub trait BellmanFieldExtensions { fn new_fq2(c0: &str, c1: &str) -> ::Fqe; } +#[cfg(feature = "bellman")] +pub trait BellmanPlonkFieldExtensions { + /// An associated type to be able to operate with Bellman ff traits + type BellmanEngine: EnginePlonk; + + fn from_bellman(e: ::Fr) -> Self; + fn into_bellman(self) -> ::Fr; + fn new_fq2(c0: &str, c1: &str) -> ::Fqe; +} + pub trait ArkFieldExtensions { /// An associated type to be able to operate with ark ff traits type ArkEngine: ark_ec::PairingEngine; @@ -576,33 +587,32 @@ mod prime_field { #[cfg(feature = "bellman")] macro_rules! bellman_extensions { - ($bellman_type:ty, $fq2_type:ident) => { - use crate::BellmanFieldExtensions; - use bellman_ce::pairing::ff::ScalarEngine; + ($bellman_crate:ident, $trait:ident, $bellman_type:ty, $fq2_type:ident) => { + use crate::$trait; - impl BellmanFieldExtensions for FieldPrime { + impl $trait for FieldPrime { type BellmanEngine = $bellman_type; - fn from_bellman(e: ::Fr) -> Self { - use bellman_ce::pairing::ff::{PrimeField, PrimeFieldRepr}; + fn from_bellman(e: ::Fr) -> Self { + use $bellman_crate::pairing::ff::{PrimeField, PrimeFieldRepr}; let mut res: Vec = vec![]; e.into_repr().write_le(&mut res).unwrap(); Self::from_byte_vector(res) } - fn into_bellman(self) -> ::Fr { - use bellman_ce::pairing::ff::PrimeField; + fn into_bellman(self) -> ::Fr { + use $bellman_crate::pairing::ff::PrimeField; let s = self.to_dec_string(); - ::Fr::from_str(&s).unwrap() + ::Fr::from_str(&s).unwrap() } fn new_fq2( c0: &str, c1: &str, - ) -> ::Fqe { + ) -> ::Fqe { $fq2_type { - c0: bellman_ce::pairing::from_hex(c0).unwrap(), - c1: bellman_ce::pairing::from_hex(c1).unwrap(), + c0: $bellman_crate::pairing::from_hex(c0).unwrap(), + c1: $bellman_crate::pairing::from_hex(c1).unwrap(), } } } diff --git a/zokrates_proof_systems/Cargo.toml b/zokrates_proof_systems/Cargo.toml index 5fc9ee2c3..56308cea0 100644 --- a/zokrates_proof_systems/Cargo.toml +++ b/zokrates_proof_systems/Cargo.toml @@ -13,4 +13,8 @@ cfg-if = "0.1" ethabi = "17.0.0" primitive-types = { version = "0.11", features = ["rlp"] } rand_0_4 = { version = "0.4", package = "rand" } -getrandom = { version = "0.2", features = ["js"] } \ No newline at end of file +getrandom = { version = "0.2", features = ["js"] } + +# Used by solidity renderer +handlebars = "3.*" +serde_json = "1.*" \ No newline at end of file diff --git a/zokrates_proof_systems/solidity_templates/PlonkVerifier.sol b/zokrates_proof_systems/solidity_templates/PlonkVerifier.sol new file mode 100644 index 000000000..99fc6dd9d --- /dev/null +++ b/zokrates_proof_systems/solidity_templates/PlonkVerifier.sol @@ -0,0 +1,873 @@ +pragma solidity ^0.8.0; + +// Copied and adjusted from: +// https://github.com/matter-labs/solidity_plonk_verifier/blob/16c1c8114c008c71e90265d33e1dfcff0a1ee1af/bellman_vk_codegen/template.sol + +library PairingsBn254 { + uint256 constant q_mod = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + uint256 constant r_mod = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 constant bn254_b_coeff = 3; + + struct G1Point { + uint256 X; + uint256 Y; + } + + struct Fr { + uint256 value; + } + + function new_fr(uint256 fr) internal pure returns (Fr memory) { + require(fr < r_mod); + return Fr({value: fr}); + } + + function copy(Fr memory self) internal pure returns (Fr memory n) { + n.value = self.value; + } + + function assign(Fr memory self, Fr memory other) internal pure { + self.value = other.value; + } + + function inverse(Fr memory fr) internal view returns (Fr memory) { + require(fr.value != 0); + return pow(fr, r_mod-2); + } + + function add_assign(Fr memory self, Fr memory other) internal pure { + self.value = addmod(self.value, other.value, r_mod); + } + + function sub_assign(Fr memory self, Fr memory other) internal pure { + self.value = addmod(self.value, r_mod - other.value, r_mod); + } + + function mul_assign(Fr memory self, Fr memory other) internal pure { + self.value = mulmod(self.value, other.value, r_mod); + } + + function pow(Fr memory self, uint256 power) internal view returns (Fr memory) { + uint256[6] memory input = [32, 32, 32, self.value, power, r_mod]; + uint256[1] memory result; + bool success; + assembly { + success := staticcall(gas(), 0x05, input, 0xc0, result, 0x20) + } + require(success); + return Fr({value: result[0]}); + } + + // Encoding of field elements is: X[0] * z + X[1] + struct G2Point { + uint[2] X; + uint[2] Y; + } + + function P1() internal pure returns (G1Point memory) { + return G1Point(1, 2); + } + + function new_g1(uint256 x, uint256 y) internal pure returns (G1Point memory) { + return G1Point(x, y); + } + + function new_g1_checked(uint256 x, uint256 y) internal pure returns (G1Point memory) { + if (x == 0 && y == 0) { + // point of infinity is (0,0) + return G1Point(x, y); + } + + // check encoding + require(x < q_mod); + require(y < q_mod); + // check on curve + uint256 lhs = mulmod(y, y, q_mod); // y^2 + uint256 rhs = mulmod(x, x, q_mod); // x^2 + rhs = mulmod(rhs, x, q_mod); // x^3 + rhs = addmod(rhs, bn254_b_coeff, q_mod); // x^3 + b + require(lhs == rhs); + + return G1Point(x, y); + } + + function new_g2(uint256[2] memory x, uint256[2] memory y) internal pure returns (G2Point memory) { + return G2Point(x, y); + } + + function copy_g1(G1Point memory self) internal pure returns (G1Point memory result) { + result.X = self.X; + result.Y = self.Y; + } + + function P2() internal pure returns (G2Point memory) { + // for some reason ethereum expects to have c1*v + c0 form + + return G2Point( + [0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2, + 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed], + [0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b, + 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa] + ); + } + + function negate(G1Point memory self) internal pure { + // The prime q in the base field F_q for G1 + if (self.Y == 0) { + require(self.X == 0); + return; + } + + self.Y = q_mod - self.Y; + } + + function point_add(G1Point memory p1, G1Point memory p2) + internal view returns (G1Point memory r) + { + point_add_into_dest(p1, p2, r); + return r; + } + + function point_add_assign(G1Point memory p1, G1Point memory p2) + internal view + { + point_add_into_dest(p1, p2, p1); + } + + function point_add_into_dest(G1Point memory p1, G1Point memory p2, G1Point memory dest) + internal view + { + if (p2.X == 0 && p2.Y == 0) { + // we add zero, nothing happens + dest.X = p1.X; + dest.Y = p1.Y; + return; + } else if (p1.X == 0 && p1.Y == 0) { + // we add into zero, and we add non-zero point + dest.X = p2.X; + dest.Y = p2.Y; + return; + } else { + uint256[4] memory input; + + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = p2.Y; + + bool success = false; + assembly { + success := staticcall(gas(), 6, input, 0x80, dest, 0x40) + } + require(success); + } + } + + function point_sub_assign(G1Point memory p1, G1Point memory p2) + internal view + { + point_sub_into_dest(p1, p2, p1); + } + + function point_sub_into_dest(G1Point memory p1, G1Point memory p2, G1Point memory dest) + internal view + { + if (p2.X == 0 && p2.Y == 0) { + // we subtracted zero, nothing happens + dest.X = p1.X; + dest.Y = p1.Y; + return; + } else if (p1.X == 0 && p1.Y == 0) { + // we subtract from zero, and we subtract non-zero point + dest.X = p2.X; + dest.Y = q_mod - p2.Y; + return; + } else { + uint256[4] memory input; + + input[0] = p1.X; + input[1] = p1.Y; + input[2] = p2.X; + input[3] = q_mod - p2.Y; + + bool success = false; + assembly { + success := staticcall(gas(), 6, input, 0x80, dest, 0x40) + } + require(success); + } + } + + function point_mul(G1Point memory p, Fr memory s) + internal view returns (G1Point memory r) + { + point_mul_into_dest(p, s, r); + return r; + } + + function point_mul_assign(G1Point memory p, Fr memory s) + internal view + { + point_mul_into_dest(p, s, p); + } + + function point_mul_into_dest(G1Point memory p, Fr memory s, G1Point memory dest) + internal view + { + uint[3] memory input; + input[0] = p.X; + input[1] = p.Y; + input[2] = s.value; + bool success; + assembly { + success := staticcall(gas(), 7, input, 0x60, dest, 0x40) + } + require(success); + } + + function pairing(G1Point[] memory p1, G2Point[] memory p2) + internal view returns (bool) + { + require(p1.length == p2.length); + uint elements = p1.length; + uint inputSize = elements * 6; + uint[] memory input = new uint[](inputSize); + for (uint i = 0; i < elements; i++) + { + input[i * 6 + 0] = p1[i].X; + input[i * 6 + 1] = p1[i].Y; + input[i * 6 + 2] = p2[i].X[0]; + input[i * 6 + 3] = p2[i].X[1]; + input[i * 6 + 4] = p2[i].Y[0]; + input[i * 6 + 5] = p2[i].Y[1]; + } + uint[1] memory out; + bool success; + assembly { + success := staticcall(gas(), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + } + require(success); + return out[0] != 0; + } + + /// Convenience method for a pairing check for two pairs. + function pairingProd2(G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2) + internal view returns (bool) + { + G1Point[] memory p1 = new G1Point[](2); + G2Point[] memory p2 = new G2Point[](2); + p1[0] = a1; + p1[1] = b1; + p2[0] = a2; + p2[1] = b2; + return pairing(p1, p2); + } +} + +library TranscriptLibrary { + // flip 0xe000000000000000000000000000000000000000000000000000000000000000; + uint256 constant FR_MASK = 0x1fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + uint32 constant DST_0 = 0; + uint32 constant DST_1 = 1; + uint32 constant DST_CHALLENGE = 2; + + struct Transcript { + bytes32 state_0; + bytes32 state_1; + uint32 challenge_counter; + } + + function new_transcript() internal pure returns (Transcript memory t) { + t.state_0 = bytes32(0); + t.state_1 = bytes32(0); + t.challenge_counter = 0; + } + + function update_with_u256(Transcript memory self, uint256 value) internal pure { + bytes32 old_state_0 = self.state_0; + self.state_0 = keccak256(abi.encodePacked(DST_0, old_state_0, self.state_1, value)); + self.state_1 = keccak256(abi.encodePacked(DST_1, old_state_0, self.state_1, value)); + } + + function update_with_fr(Transcript memory self, PairingsBn254.Fr memory value) internal pure { + update_with_u256(self, value.value); + } + + function update_with_g1(Transcript memory self, PairingsBn254.G1Point memory p) internal pure { + update_with_u256(self, p.X); + update_with_u256(self, p.Y); + } + + function get_challenge(Transcript memory self) internal pure returns(PairingsBn254.Fr memory challenge) { + bytes32 query = keccak256(abi.encodePacked(DST_CHALLENGE, self.state_0, self.state_1, self.challenge_counter)); + self.challenge_counter += 1; + challenge = PairingsBn254.Fr({value: uint256(query) & FR_MASK}); + } +} + +contract Plonk4VerifierWithAccessToDNext { + using PairingsBn254 for PairingsBn254.G1Point; + using PairingsBn254 for PairingsBn254.G2Point; + using PairingsBn254 for PairingsBn254.Fr; + + using TranscriptLibrary for TranscriptLibrary.Transcript; + + uint256 constant STATE_WIDTH = 4; + uint256 constant ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP = 1; + + struct VerificationKey { + uint256 domain_size; + uint256 num_inputs; + PairingsBn254.Fr omega; + PairingsBn254.G1Point[STATE_WIDTH+2] selector_commitments; // STATE_WIDTH for witness + multiplication + constant + PairingsBn254.G1Point[ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP] next_step_selector_commitments; + PairingsBn254.G1Point[STATE_WIDTH] permutation_commitments; + PairingsBn254.Fr[STATE_WIDTH-1] permutation_non_residues; + PairingsBn254.G2Point g2_x; + } + + struct Proof { + PairingsBn254.G1Point[STATE_WIDTH] wire_commitments; + PairingsBn254.G1Point grand_product_commitment; + PairingsBn254.G1Point[STATE_WIDTH] quotient_poly_commitments; + PairingsBn254.Fr[STATE_WIDTH] wire_values_at_z; + PairingsBn254.Fr[ACCESSIBLE_STATE_POLYS_ON_NEXT_STEP] wire_values_at_z_omega; + PairingsBn254.Fr grand_product_at_z_omega; + PairingsBn254.Fr quotient_polynomial_at_z; + PairingsBn254.Fr linearization_polynomial_at_z; + PairingsBn254.Fr[STATE_WIDTH-1] permutation_polynomials_at_z; + + PairingsBn254.G1Point opening_at_z_proof; + PairingsBn254.G1Point opening_at_z_omega_proof; + } + + struct PartialVerifierState { + PairingsBn254.Fr alpha; + PairingsBn254.Fr beta; + PairingsBn254.Fr gamma; + PairingsBn254.Fr v; + PairingsBn254.Fr u; + PairingsBn254.Fr z; + PairingsBn254.Fr[] cached_lagrange_evals; + } + + function evaluate_lagrange_poly_out_of_domain( + uint256 poly_num, + uint256 domain_size, + PairingsBn254.Fr memory omega, + PairingsBn254.Fr memory at + ) internal view returns (PairingsBn254.Fr memory res) { + require(poly_num < domain_size); + PairingsBn254.Fr memory one = PairingsBn254.new_fr(1); + PairingsBn254.Fr memory omega_power = omega.pow(poly_num); + res = at.pow(domain_size); + res.sub_assign(one); + require(res.value != 0); // Vanishing polynomial can not be zero at point `at` + res.mul_assign(omega_power); + + PairingsBn254.Fr memory den = PairingsBn254.copy(at); + den.sub_assign(omega_power); + den.mul_assign(PairingsBn254.new_fr(domain_size)); + + den = den.inverse(); + + res.mul_assign(den); + } + + function batch_evaluate_lagrange_poly_out_of_domain( + uint256[] memory poly_nums, + uint256 domain_size, + PairingsBn254.Fr memory omega, + PairingsBn254.Fr memory at + ) internal view returns (PairingsBn254.Fr[] memory res) { + PairingsBn254.Fr memory one = PairingsBn254.new_fr(1); + PairingsBn254.Fr memory tmp_1 = PairingsBn254.new_fr(0); + PairingsBn254.Fr memory tmp_2 = PairingsBn254.new_fr(domain_size); + PairingsBn254.Fr memory vanishing_at_z = at.pow(domain_size); + vanishing_at_z.sub_assign(one); + // we can not have random point z be in domain + require(vanishing_at_z.value != 0); + PairingsBn254.Fr[] memory nums = new PairingsBn254.Fr[](poly_nums.length); + PairingsBn254.Fr[] memory dens = new PairingsBn254.Fr[](poly_nums.length); + // numerators in a form omega^i * (z^n - 1) + // denoms in a form (z - omega^i) * N + for (uint i = 0; i < poly_nums.length; i++) { + tmp_1 = omega.pow(poly_nums[i]); // power of omega + nums[i].assign(vanishing_at_z); + nums[i].mul_assign(tmp_1); + + dens[i].assign(at); // (X - omega^i) * N + dens[i].sub_assign(tmp_1); + dens[i].mul_assign(tmp_2); // mul by domain size + } + + PairingsBn254.Fr[] memory partial_products = new PairingsBn254.Fr[](poly_nums.length); + partial_products[0].assign(PairingsBn254.new_fr(1)); + for (uint i = 1; i < dens.length; i++) { + partial_products[i].assign(dens[i-1]); + partial_products[i].mul_assign(partial_products[i-1]); + } + + tmp_2.assign(partial_products[partial_products.length - 1]); + tmp_2.mul_assign(dens[dens.length - 1]); + tmp_2 = tmp_2.inverse(); // tmp_2 contains a^-1 * b^-1 (with! the last one) + + for (uint i = dens.length - 1; true; i--) { + tmp_1.assign(tmp_2); // all inversed + tmp_1.mul_assign(partial_products[i]); // clear lowest terms + tmp_2.mul_assign(dens[i]); + dens[i].assign(tmp_1); + + if (i == 0) { + break; + } + } + + for (uint i = 0; i < nums.length; i++) { + nums[i].mul_assign(dens[i]); + } + + return nums; + } + + function evaluate_vanishing( + uint256 domain_size, + PairingsBn254.Fr memory at + ) internal view returns (PairingsBn254.Fr memory res) { + res = at.pow(domain_size); + res.sub_assign(PairingsBn254.new_fr(1)); + } + + function verify_at_z( + PartialVerifierState memory state, + Proof memory proof, + uint256[] memory input_values, + VerificationKey memory vk + ) internal view returns (bool) { + PairingsBn254.Fr memory lhs = evaluate_vanishing(vk.domain_size, state.z); + require(lhs.value != 0); // we can not check a polynomial relationship if point `z` is in the domain + lhs.mul_assign(proof.quotient_polynomial_at_z); + + PairingsBn254.Fr memory quotient_challenge = PairingsBn254.new_fr(1); + PairingsBn254.Fr memory rhs = PairingsBn254.copy(proof.linearization_polynomial_at_z); + + // public inputs + PairingsBn254.Fr memory tmp = PairingsBn254.new_fr(0); + for (uint256 i = 0; i < input_values.length; i++) { + tmp.assign(state.cached_lagrange_evals[i]); + tmp.mul_assign(PairingsBn254.new_fr(input_values[i])); + rhs.add_assign(tmp); + } + + quotient_challenge.mul_assign(state.alpha); + + PairingsBn254.Fr memory z_part = PairingsBn254.copy(proof.grand_product_at_z_omega); + for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) { + tmp.assign(proof.permutation_polynomials_at_z[i]); + tmp.mul_assign(state.beta); + tmp.add_assign(state.gamma); + tmp.add_assign(proof.wire_values_at_z[i]); + + z_part.mul_assign(tmp); + } + + tmp.assign(state.gamma); + // we need a wire value of the last polynomial in enumeration + tmp.add_assign(proof.wire_values_at_z[STATE_WIDTH - 1]); + + z_part.mul_assign(tmp); + z_part.mul_assign(quotient_challenge); + + rhs.sub_assign(z_part); + + quotient_challenge.mul_assign(state.alpha); + + tmp.assign(state.cached_lagrange_evals[0]); + tmp.mul_assign(quotient_challenge); + + rhs.sub_assign(tmp); + + return lhs.value == rhs.value; + } + + function reconstruct_d( + PartialVerifierState memory state, + Proof memory proof, + VerificationKey memory vk + ) internal view returns (PairingsBn254.G1Point memory res) { + // we compute what power of v is used as a delinearization factor in batch opening of + // commitments. Let's label W(x) = 1 / (x - z) * + // [ + // t_0(x) + z^n * t_1(x) + z^2n * t_2(x) + z^3n * t_3(x) - t(z) + // + v (r(x) - r(z)) + // + v^{2..5} * (witness(x) - witness(z)) + // + v^(6..8) * (permutation(x) - permutation(z)) + // ] + // W'(x) = 1 / (x - z*omega) * + // [ + // + v^9 (z(x) - z(z*omega)) <- we need this power + // + v^10 * (d(x) - d(z*omega)) + // ] + // + // we pay a little for a few arithmetic operations to not introduce another constant + uint256 power_for_z_omega_opening = 1 + 1 + STATE_WIDTH + STATE_WIDTH - 1; + res = PairingsBn254.copy_g1(vk.selector_commitments[STATE_WIDTH + 1]); + + PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1(); + PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(0); + + // addition gates + for (uint256 i = 0; i < STATE_WIDTH; i++) { + tmp_g1 = vk.selector_commitments[i].point_mul(proof.wire_values_at_z[i]); + res.point_add_assign(tmp_g1); + } + + // multiplication gate + tmp_fr.assign(proof.wire_values_at_z[0]); + tmp_fr.mul_assign(proof.wire_values_at_z[1]); + tmp_g1 = vk.selector_commitments[STATE_WIDTH].point_mul(tmp_fr); + res.point_add_assign(tmp_g1); + + // d_next + tmp_g1 = vk.next_step_selector_commitments[0].point_mul(proof.wire_values_at_z_omega[0]); + res.point_add_assign(tmp_g1); + + // z * non_res * beta + gamma + a + PairingsBn254.Fr memory grand_product_part_at_z = PairingsBn254.copy(state.z); + grand_product_part_at_z.mul_assign(state.beta); + grand_product_part_at_z.add_assign(proof.wire_values_at_z[0]); + grand_product_part_at_z.add_assign(state.gamma); + for (uint256 i = 0; i < vk.permutation_non_residues.length; i++) { + tmp_fr.assign(state.z); + tmp_fr.mul_assign(vk.permutation_non_residues[i]); + tmp_fr.mul_assign(state.beta); + tmp_fr.add_assign(state.gamma); + tmp_fr.add_assign(proof.wire_values_at_z[i+1]); + + grand_product_part_at_z.mul_assign(tmp_fr); + } + + grand_product_part_at_z.mul_assign(state.alpha); + + tmp_fr.assign(state.cached_lagrange_evals[0]); + tmp_fr.mul_assign(state.alpha); + tmp_fr.mul_assign(state.alpha); + + grand_product_part_at_z.add_assign(tmp_fr); + + PairingsBn254.Fr memory grand_product_part_at_z_omega = state.v.pow(power_for_z_omega_opening); + grand_product_part_at_z_omega.mul_assign(state.u); + + PairingsBn254.Fr memory last_permutation_part_at_z = PairingsBn254.new_fr(1); + for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) { + tmp_fr.assign(state.beta); + tmp_fr.mul_assign(proof.permutation_polynomials_at_z[i]); + tmp_fr.add_assign(state.gamma); + tmp_fr.add_assign(proof.wire_values_at_z[i]); + + last_permutation_part_at_z.mul_assign(tmp_fr); + } + + last_permutation_part_at_z.mul_assign(state.beta); + last_permutation_part_at_z.mul_assign(proof.grand_product_at_z_omega); + last_permutation_part_at_z.mul_assign(state.alpha); + + // add to the linearization + tmp_g1 = proof.grand_product_commitment.point_mul(grand_product_part_at_z); + tmp_g1.point_sub_assign(vk.permutation_commitments[STATE_WIDTH - 1].point_mul(last_permutation_part_at_z)); + + res.point_add_assign(tmp_g1); + res.point_mul_assign(state.v); + + res.point_add_assign(proof.grand_product_commitment.point_mul(grand_product_part_at_z_omega)); + } + + function verify_commitments( + PartialVerifierState memory state, + Proof memory proof, + VerificationKey memory vk + ) internal view returns (bool) { + PairingsBn254.G1Point memory d = reconstruct_d(state, proof, vk); + + PairingsBn254.Fr memory z_in_domain_size = state.z.pow(vk.domain_size); + + PairingsBn254.G1Point memory tmp_g1 = PairingsBn254.P1(); + + PairingsBn254.Fr memory aggregation_challenge = PairingsBn254.new_fr(1); + + PairingsBn254.G1Point memory commitment_aggregation = PairingsBn254.copy_g1(proof.quotient_poly_commitments[0]); + PairingsBn254.Fr memory tmp_fr = PairingsBn254.new_fr(1); + for (uint i = 1; i < proof.quotient_poly_commitments.length; i++) { + tmp_fr.mul_assign(z_in_domain_size); + tmp_g1 = proof.quotient_poly_commitments[i].point_mul(tmp_fr); + commitment_aggregation.point_add_assign(tmp_g1); + } + + aggregation_challenge.mul_assign(state.v); + commitment_aggregation.point_add_assign(d); + + for (uint i = 0; i < proof.wire_commitments.length; i++) { + aggregation_challenge.mul_assign(state.v); + tmp_g1 = proof.wire_commitments[i].point_mul(aggregation_challenge); + commitment_aggregation.point_add_assign(tmp_g1); + } + + for (uint i = 0; i < vk.permutation_commitments.length - 1; i++) { + aggregation_challenge.mul_assign(state.v); + tmp_g1 = vk.permutation_commitments[i].point_mul(aggregation_challenge); + commitment_aggregation.point_add_assign(tmp_g1); + } + + aggregation_challenge.mul_assign(state.v); + + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(aggregation_challenge); + tmp_fr.mul_assign(state.u); + tmp_g1 = proof.wire_commitments[STATE_WIDTH - 1].point_mul(tmp_fr); + commitment_aggregation.point_add_assign(tmp_g1); + + // collect opening values + aggregation_challenge = PairingsBn254.new_fr(1); + + PairingsBn254.Fr memory aggregated_value = PairingsBn254.copy(proof.quotient_polynomial_at_z); + + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(proof.linearization_polynomial_at_z); + tmp_fr.mul_assign(aggregation_challenge); + aggregated_value.add_assign(tmp_fr); + + for (uint i = 0; i < proof.wire_values_at_z.length; i++) { + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(proof.wire_values_at_z[i]); + tmp_fr.mul_assign(aggregation_challenge); + aggregated_value.add_assign(tmp_fr); + } + + for (uint i = 0; i < proof.permutation_polynomials_at_z.length; i++) { + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(proof.permutation_polynomials_at_z[i]); + tmp_fr.mul_assign(aggregation_challenge); + aggregated_value.add_assign(tmp_fr); + } + + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(proof.grand_product_at_z_omega); + tmp_fr.mul_assign(aggregation_challenge); + tmp_fr.mul_assign(state.u); + aggregated_value.add_assign(tmp_fr); + + aggregation_challenge.mul_assign(state.v); + + tmp_fr.assign(proof.wire_values_at_z_omega[0]); + tmp_fr.mul_assign(aggregation_challenge); + tmp_fr.mul_assign(state.u); + aggregated_value.add_assign(tmp_fr); + + commitment_aggregation.point_sub_assign(PairingsBn254.P1().point_mul(aggregated_value)); + + PairingsBn254.G1Point memory pair_with_generator = commitment_aggregation; + pair_with_generator.point_add_assign(proof.opening_at_z_proof.point_mul(state.z)); + + tmp_fr.assign(state.z); + tmp_fr.mul_assign(vk.omega); + tmp_fr.mul_assign(state.u); + pair_with_generator.point_add_assign(proof.opening_at_z_omega_proof.point_mul(tmp_fr)); + + PairingsBn254.G1Point memory pair_with_x = proof.opening_at_z_omega_proof.point_mul(state.u); + pair_with_x.point_add_assign(proof.opening_at_z_proof); + pair_with_x.negate(); + + return PairingsBn254.pairingProd2(pair_with_generator, PairingsBn254.P2(), pair_with_x, vk.g2_x); + } + + function verify_initial( + PartialVerifierState memory state, + Proof memory proof, + uint256[] memory input_values, + VerificationKey memory vk + ) internal view returns (bool) { + require(input_values.length == vk.num_inputs); + require(vk.num_inputs >= 1); + TranscriptLibrary.Transcript memory transcript = TranscriptLibrary.new_transcript(); + for (uint256 i = 0; i < vk.num_inputs; i++) { + transcript.update_with_u256(input_values[i]); + } + + for (uint256 i = 0; i < proof.wire_commitments.length; i++) { + transcript.update_with_g1(proof.wire_commitments[i]); + } + + state.beta = transcript.get_challenge(); + state.gamma = transcript.get_challenge(); + + transcript.update_with_g1(proof.grand_product_commitment); + state.alpha = transcript.get_challenge(); + + for (uint256 i = 0; i < proof.quotient_poly_commitments.length; i++) { + transcript.update_with_g1(proof.quotient_poly_commitments[i]); + } + + state.z = transcript.get_challenge(); + + uint256[] memory lagrange_poly_numbers = new uint256[](vk.num_inputs); + for (uint256 i = 0; i < lagrange_poly_numbers.length; i++) { + lagrange_poly_numbers[i] = i; + } + + state.cached_lagrange_evals = batch_evaluate_lagrange_poly_out_of_domain( + lagrange_poly_numbers, + vk.domain_size, + vk.omega, state.z + ); + + bool valid = verify_at_z(state, proof, input_values, vk); + + if (valid == false) { + return false; + } + + for (uint256 i = 0; i < proof.wire_values_at_z.length; i++) { + transcript.update_with_fr(proof.wire_values_at_z[i]); + } + + for (uint256 i = 0; i < proof.wire_values_at_z_omega.length; i++) { + transcript.update_with_fr(proof.wire_values_at_z_omega[i]); + } + + for (uint256 i = 0; i < proof.permutation_polynomials_at_z.length; i++) { + transcript.update_with_fr(proof.permutation_polynomials_at_z[i]); + } + + transcript.update_with_fr(proof.quotient_polynomial_at_z); + transcript.update_with_fr(proof.linearization_polynomial_at_z); + + transcript.update_with_fr(proof.grand_product_at_z_omega); + + state.v = transcript.get_challenge(); + transcript.update_with_g1(proof.opening_at_z_proof); + transcript.update_with_g1(proof.opening_at_z_omega_proof); + state.u = transcript.get_challenge(); + + return true; + } + + // This verifier is for a PLONK with a state width 4 + // and main gate equation + // q_a(X) * a(X) + + // q_b(X) * b(X) + + // q_c(X) * c(X) + + // q_d(X) * d(X) + + // q_m(X) * a(X) * b(X) + + // q_constants(X)+ + // q_d_next(X) * d(X*omega) + // where q_{}(X) are selectors a, b, c, d - state (witness) polynomials + // q_d_next(X) "peeks" into the next row of the trace, so it takes + // the same d(X) polynomial, but shifted + + function verify(Proof memory proof, uint256[] memory input_values, VerificationKey memory vk) internal view returns (bool) { + PartialVerifierState memory state; + + bool valid = verify_initial(state, proof, input_values, vk); + + if (valid == false) { + return false; + } + + valid = verify_commitments(state, proof, vk); + + return valid; + } +} + +contract Verifier is Plonk4VerifierWithAccessToDNext { + uint256 constant SERIALIZED_PROOF_LENGTH = 33; + + function get_verification_key() internal pure returns(VerificationKey memory vk) { + vk.domain_size = {{domain_size}}; + vk.num_inputs = {{num_inputs}}; + vk.omega = PairingsBn254.new_fr({{omega}}); + vk.selector_commitments[0] = PairingsBn254.new_g1( + {{selector_commitment_0_0}}, + {{selector_commitment_0_1}} + ); + vk.selector_commitments[1] = PairingsBn254.new_g1( + {{selector_commitment_1_0}}, + {{selector_commitment_1_1}} + ); + vk.selector_commitments[2] = PairingsBn254.new_g1( + {{selector_commitment_2_0}}, + {{selector_commitment_2_1}} + ); + vk.selector_commitments[3] = PairingsBn254.new_g1( + {{selector_commitment_3_0}}, + {{selector_commitment_3_1}} + ); + vk.selector_commitments[4] = PairingsBn254.new_g1( + {{selector_commitment_4_0}}, + {{selector_commitment_4_1}} + ); + vk.selector_commitments[5] = PairingsBn254.new_g1( + {{selector_commitment_5_0}}, + {{selector_commitment_5_1}} + ); + + // we only have access to value of the d(x) witness polynomial on the next + // trace step, so we only need one element here and deal with it in other places + // by having this in mind + vk.next_step_selector_commitments[0] = PairingsBn254.new_g1( + {{next_step_selector_commitment_0_0}}, + {{next_step_selector_commitment_0_1}} + ); + + vk.permutation_commitments[0] = PairingsBn254.new_g1( + {{permutation_commitment_0_0}}, + {{permutation_commitment_0_1}} + ); + vk.permutation_commitments[1] = PairingsBn254.new_g1( + {{permutation_commitment_1_0}}, + {{permutation_commitment_1_1}} + ); + vk.permutation_commitments[2] = PairingsBn254.new_g1( + {{permutation_commitment_2_0}}, + {{permutation_commitment_2_1}} + ); + vk.permutation_commitments[3] = PairingsBn254.new_g1( + {{permutation_commitment_3_0}}, + {{permutation_commitment_3_1}} + ); + + vk.permutation_non_residues[0] = PairingsBn254.new_fr( + {{permutation_non_residue_0}} + ); + vk.permutation_non_residues[1] = PairingsBn254.new_fr( + {{permutation_non_residue_1}} + ); + vk.permutation_non_residues[2] = PairingsBn254.new_fr( + {{permutation_non_residue_2}} + ); + + vk.g2_x = PairingsBn254.new_g2( + [{{g2_x_x_c1}}, + {{g2_x_x_c0}}], + [{{g2_x_y_c1}}, + {{g2_x_y_c0}}] + ); + } + + function verifyTx(Proof memory proof, uint256[{{num_inputs}}] memory input) public view returns (bool r) + { + uint256[] memory inputs_dynamic_array = new uint256[](input.length); + for (uint256 i = 0; i < input.length; i++) { + inputs_dynamic_array[i] = input[i]; + } + VerificationKey memory vk = get_verification_key(); + return verify(proof, inputs_dynamic_array, vk); + } +} + diff --git a/zokrates_proof_systems/src/lib.rs b/zokrates_proof_systems/src/lib.rs index 4d8111877..2af0aa4ad 100644 --- a/zokrates_proof_systems/src/lib.rs +++ b/zokrates_proof_systems/src/lib.rs @@ -2,6 +2,7 @@ pub mod to_token; mod scheme; mod solidity; +mod solidity_renderers; mod tagged; pub use self::scheme::*; @@ -44,16 +45,16 @@ impl> Proof { pub type Fr = String; pub type Fq = String; -#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq)] pub struct Fq2(pub String, pub String); impl fmt::Display for Fq2 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "({}, {})", self.0, self.1) + write!(f, "[{}, {}]", self.0, self.1) } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct GAffine { pub x: F, pub y: F, @@ -83,14 +84,14 @@ impl fmt::Display for GAffine { if self.is_infinity { write!(f, "Infinity") } else { - write!(f, "({}, {})", self.x, self.y) + write!(f, "[{}, {}]", self.x, self.y) } } } pub type G1Affine = GAffine; -#[derive(Serialize, Deserialize, Clone, PartialEq, Debug)] +#[derive(Serialize, Deserialize, Clone, PartialEq, Debug, Eq)] #[serde(untagged)] pub enum G2Affine { Fq2(G2AffineFq2), diff --git a/zokrates_proof_systems/src/scheme/gm17.rs b/zokrates_proof_systems/src/scheme/gm17.rs index 3c1538689..5720be63e 100644 --- a/zokrates_proof_systems/src/scheme/gm17.rs +++ b/zokrates_proof_systems/src/scheme/gm17.rs @@ -6,17 +6,17 @@ use serde::{Deserialize, Serialize}; use zokrates_field::Field; #[allow(clippy::upper_case_acronyms)] -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct GM17; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ProofPoints { pub a: G1, pub b: G2, pub c: G1, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct VerificationKey { pub h: G2, pub g_alpha: G1, @@ -50,24 +50,35 @@ impl SolidityCompatibleScheme for GM17 { let input_loop = Regex::new(r#"(<%input_loop%>)"#).unwrap(); let input_argument = Regex::new(r#"(<%input_argument%>)"#).unwrap(); + let trim = |s: String| String::from(&s[1..s.len() - 1]); + template_text = vk_regex - .replace(template_text.as_str(), vk.h.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.h.to_string()).as_str()) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.g_alpha.to_string().as_str()) + .replace( + template_text.as_str(), + trim(vk.g_alpha.to_string()).as_str(), + ) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.h_beta.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.h_beta.to_string()).as_str()) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.g_gamma.to_string().as_str()) + .replace( + template_text.as_str(), + trim(vk.g_gamma.to_string()).as_str(), + ) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.h_gamma.to_string().as_str()) + .replace( + template_text.as_str(), + trim(vk.h_gamma.to_string()).as_str(), + ) .into_owned(); let query_count: usize = vk.query.len(); @@ -113,7 +124,7 @@ impl SolidityCompatibleScheme for GM17 { format!( "vk.query[{}] = Pairing.G1Point({});", i, - g1.to_string().as_str() + trim(g1.to_string()).as_str() ) .as_str(), ); diff --git a/zokrates_proof_systems/src/scheme/groth16.rs b/zokrates_proof_systems/src/scheme/groth16.rs index 548a49818..84377f1ba 100644 --- a/zokrates_proof_systems/src/scheme/groth16.rs +++ b/zokrates_proof_systems/src/scheme/groth16.rs @@ -5,17 +5,17 @@ use regex::Regex; use serde::{Deserialize, Serialize}; use zokrates_field::Field; -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct G16; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ProofPoints { pub a: G1, pub b: G2, pub c: G1, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct VerificationKey { pub alpha: G1, pub beta: G2, @@ -48,20 +48,22 @@ impl SolidityCompatibleScheme for G16 { let input_loop = Regex::new(r#"(<%input_loop%>)"#).unwrap(); let input_argument = Regex::new(r#"(<%input_argument%>)"#).unwrap(); + let trim = |s: String| String::from(&s[1..s.len() - 1]); + template_text = vk_regex - .replace(template_text.as_str(), vk.alpha.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.alpha.to_string()).as_str()) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.beta.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.beta.to_string()).as_str()) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.gamma.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.gamma.to_string()).as_str()) .into_owned(); template_text = vk_regex - .replace(template_text.as_str(), vk.delta.to_string().as_str()) + .replace(template_text.as_str(), trim(vk.delta.to_string()).as_str()) .into_owned(); let gamma_abc_count: usize = vk.gamma_abc.len(); @@ -110,7 +112,7 @@ impl SolidityCompatibleScheme for G16 { format!( "vk.gamma_abc[{}] = Pairing.G1Point({});", i, - g1.to_string().as_str() + trim(g1.to_string()).as_str() ) .as_str(), ); diff --git a/zokrates_proof_systems/src/scheme/marlin.rs b/zokrates_proof_systems/src/scheme/marlin.rs index 25c071010..0054014f2 100644 --- a/zokrates_proof_systems/src/scheme/marlin.rs +++ b/zokrates_proof_systems/src/scheme/marlin.rs @@ -7,7 +7,7 @@ use zokrates_field::Field; #[derive(Serialize, Debug, Clone)] pub struct Marlin; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ProofPoints { pub commitments: Vec)>>, pub evaluations: Vec, @@ -46,7 +46,7 @@ impl From> for SolidityProof { } } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct KZGVerifierKey { /// The generator of G1. pub g: G1, @@ -58,7 +58,7 @@ pub struct KZGVerifierKey { pub beta_h: G2, } -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct VerificationKey { // Useful values to precompute for solidity contract pub fs_seed: Vec, @@ -96,6 +96,8 @@ impl SolidityCompatibleScheme for Marlin { let (template, solidity_pairing_lib) = (String::from(CONTRACT_TEMPLATE), solidity_pairing_lib(false)); + let trim = |s: String| String::from(&s[1..s.len() - 1]); + // Replace public parameters in template let src = template .replace( @@ -109,7 +111,7 @@ impl SolidityCompatibleScheme for Marlin { populate_index_comms, "vk.index_comms[{}] = Pairing.G1Point({});", i, - &g.to_string() + &trim(g.to_string()) ) .unwrap(); if i < vk.index_comms.len() - 1 { @@ -118,10 +120,10 @@ impl SolidityCompatibleScheme for Marlin { } populate_index_comms }) - .replace("<%vk_kzg_g%>", &vk.vk.g.to_string()) - .replace("<%vk_kzg_gamma_g%>", &vk.vk.gamma_g.to_string()) - .replace("<%vk_kzg_h%>", &vk.vk.h.to_string()) - .replace("<%vk_kzg_beta_h%>", &vk.vk.beta_h.to_string()) + .replace("<%vk_kzg_g%>", &trim(vk.vk.g.to_string())) + .replace("<%vk_kzg_gamma_g%>", &trim(vk.vk.gamma_g.to_string())) + .replace("<%vk_kzg_h%>", &trim(vk.vk.h.to_string())) + .replace("<%vk_kzg_beta_h%>", &trim(vk.vk.beta_h.to_string())) .replace( "<%vk_degree_bounds_length%>", &vk.degree_bounds_and_shift_powers @@ -136,14 +138,16 @@ impl SolidityCompatibleScheme for Marlin { } else { vk.num_constraints.next_power_of_two() }; - vk.degree_bounds_and_shift_powers - .as_ref() - .unwrap() - .iter() - .find(|(b, _)| *b == h_domain_size - 2) - .unwrap() - .1 - .to_string() + trim( + vk.degree_bounds_and_shift_powers + .as_ref() + .unwrap() + .iter() + .find(|(b, _)| *b == h_domain_size - 2) + .unwrap() + .1 + .to_string(), + ) }) .replace("<%vk_g2_shift%>", &{ let k_domain_size = if vk.num_non_zero.is_power_of_two() { @@ -151,14 +155,16 @@ impl SolidityCompatibleScheme for Marlin { } else { vk.num_non_zero.next_power_of_two() }; - vk.degree_bounds_and_shift_powers - .as_ref() - .unwrap() - .iter() - .find(|(b, _)| *b == k_domain_size - 2) - .unwrap() - .1 - .to_string() + trim( + vk.degree_bounds_and_shift_powers + .as_ref() + .unwrap() + .iter() + .find(|(b, _)| *b == k_domain_size - 2) + .unwrap() + .1 + .to_string(), + ) }) .replace("<%fs_init_seed_len%>", &(vk.fs_seed.len() / 32).to_string()) .replace("<%fs_init_seed_overflow_len%>", &{ diff --git a/zokrates_proof_systems/src/scheme/plonk.rs b/zokrates_proof_systems/src/scheme/plonk.rs index 5a8060cff..8230e2171 100644 --- a/zokrates_proof_systems/src/scheme/plonk.rs +++ b/zokrates_proof_systems/src/scheme/plonk.rs @@ -1,14 +1,18 @@ -use crate::scheme::{NonUniversalScheme, Scheme}; -use crate::solidity::solidity_pairing_lib; -use crate::{Fr, G1Affine, G2Affine, UniversalScheme}; -use regex::Regex; +use ethabi::Token; + +use crate::scheme::Scheme; +use crate::to_token::{encode_fr_element_as_tuple, encode_g1_element, ToToken}; +use crate::{Fr, G1Affine, G2Affine, SolidityCompatibleScheme, UniversalScheme}; +// use regex::Regex; use serde::{Deserialize, Serialize}; -use zokrates_field::Field; +use zokrates_field::{Bn128Field, Field}; + +use crate::solidity_renderers::plonk_solidity_renderer::render_verification_key; -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, Eq)] pub struct Plonk; -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct VerificationKey { pub n: u32, pub num_inputs: u32, @@ -17,9 +21,12 @@ pub struct VerificationKey { pub permutation_commitments: Vec, pub non_residues: Vec, pub g2_elements: [G2; 2], + + // The omega can be computed from n and the generator of Fr and is redundant. + pub omega: Fr, } -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub struct ProofPoints { pub num_inputs: u32, pub n: u32, @@ -44,3 +51,90 @@ impl Scheme for Plonk { } impl UniversalScheme for Plonk {} + +impl SolidityCompatibleScheme for Plonk { + type Proof = Self::ProofPoints; + + fn export_solidity_verifier(vk: Self::VerificationKey) -> String { + render_verification_key::(&vk) + } +} + +impl ToToken for Plonk { + fn to_token(proof: Self::Proof) -> ethabi::Token { + let wire_commitments = Token::FixedArray( + proof + .wire_commitments + .iter() + .map(encode_g1_element) + .collect(), + ); + + let grand_product_commitment = encode_g1_element(&proof.grand_product_commitment); + + let quotient_poly_commitments = Token::FixedArray( + proof + .quotient_poly_commitments + .iter() + .map(encode_g1_element) + .collect(), + ); + + let wire_values_at_z = Token::FixedArray( + proof + .wire_values_at_z + .iter() + .map(encode_fr_element_as_tuple) + .collect(), + ); + + let wire_values_at_z_omega = Token::FixedArray( + proof + .wire_values_at_z_omega + .iter() + .map(encode_fr_element_as_tuple) + .collect(), + ); + + let grand_product_at_z_omega = encode_fr_element_as_tuple(&proof.grand_product_at_z_omega); + + let quotient_polynomial_at_z = encode_fr_element_as_tuple(&proof.quotient_polynomial_at_z); + + let linearization_polynomial_at_z = + encode_fr_element_as_tuple(&proof.linearization_polynomial_at_z); + + let permutation_polynomials_at_z = Token::FixedArray( + proof + .permutation_polynomials_at_z + .iter() + .map(encode_fr_element_as_tuple) + .collect(), + ); + + let opening_at_z_proof = encode_g1_element(&proof.opening_at_z_proof); + + let opening_at_z_omega_proof = encode_g1_element(&proof.opening_at_z_omega_proof); + + let proof_tokens = vec![ + wire_commitments, + grand_product_commitment, + quotient_poly_commitments, + wire_values_at_z, + wire_values_at_z_omega, + grand_product_at_z_omega, + quotient_polynomial_at_z, + linearization_polynomial_at_z, + permutation_polynomials_at_z, + opening_at_z_proof, + opening_at_z_omega_proof, + ]; + + Token::Tuple(proof_tokens) + } + + fn modify(mut proof: Self::Proof) -> Self::Proof { + proof.opening_at_z_omega_proof.x = + "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".into(); + proof + } +} diff --git a/zokrates_proof_systems/src/solidity_renderers/mod.rs b/zokrates_proof_systems/src/solidity_renderers/mod.rs new file mode 100644 index 000000000..206466295 --- /dev/null +++ b/zokrates_proof_systems/src/solidity_renderers/mod.rs @@ -0,0 +1 @@ +pub mod plonk_solidity_renderer; diff --git a/zokrates_proof_systems/src/solidity_renderers/plonk_solidity_renderer.rs b/zokrates_proof_systems/src/solidity_renderers/plonk_solidity_renderer.rs new file mode 100644 index 000000000..d3894510b --- /dev/null +++ b/zokrates_proof_systems/src/solidity_renderers/plonk_solidity_renderer.rs @@ -0,0 +1,106 @@ +// Copied and adjusted from: +// https://github.com/matter-labs/solidity_plonk_verifier/blob/3c458aff9005c62119113653bba17e0881c41675/bellman_vk_codegen/src/lib.rs + +use handlebars::*; +use zokrates_field::Field; + +use crate::{G1Affine, G2Affine, Scheme}; +use serde_json::value::Map; + +use crate::Plonk; + +pub fn render_verification_key(vk: &>::VerificationKey) -> String { + let mut map = Map::new(); + + let domain_size = vk.n.next_power_of_two().to_string(); + map.insert("domain_size".to_owned(), to_json(domain_size)); + + let num_inputs = vk.num_inputs.to_string(); + map.insert("num_inputs".to_owned(), to_json(num_inputs)); + + map.insert("omega".to_owned(), to_json(vk.omega.clone())); + + for (i, c) in vk.selector_commitments.iter().enumerate() { + let rendered = render_g1_affine_to_hex(c); + + for (j, rendered_item) in rendered.iter().enumerate() { + map.insert( + format!("selector_commitment_{}_{}", i, j), + to_json(rendered_item), + ); + } + } + + for (i, c) in vk.next_step_selector_commitments.iter().enumerate() { + let rendered = render_g1_affine_to_hex(c); + + for (j, rendered_item) in rendered.iter().enumerate() { + map.insert( + format!("next_step_selector_commitment_{}_{}", i, j), + to_json(rendered_item), + ); + } + } + + for (i, c) in vk.permutation_commitments.iter().enumerate() { + let rendered = render_g1_affine_to_hex(c); + + for (j, rendered_item) in rendered.iter().enumerate() { + map.insert( + format!("permutation_commitment_{}_{}", i, j), + to_json(rendered_item), + ); + } + } + + for (i, c) in vk.non_residues.iter().enumerate() { + map.insert(format!("permutation_non_residue_{}", i), to_json(&c)); + } + + let rendered = render_g2_affine_to_hex(&vk.g2_elements[1]); + + map.insert("g2_x_x_c0".to_owned(), to_json(&rendered[0])); + map.insert("g2_x_x_c1".to_owned(), to_json(&rendered[1])); + map.insert("g2_x_y_c0".to_owned(), to_json(&rendered[2])); + map.insert("g2_x_y_c1".to_owned(), to_json(&rendered[3])); + + let mut handlebars = Handlebars::new(); + + // register template from a file and assign a name to it + let template_str = include_str!("../../solidity_templates/PlonkVerifier.sol"); + handlebars + .register_template_string("contract", &template_str) + .expect("must read the template"); + + handlebars.render("contract", &map).unwrap() +} + +fn render_g1_affine_to_hex(point: &G1Affine) -> [String; 2] { + if point.is_infinity { + return ["0x0".to_owned(), "0x0".to_owned()]; + } + + let point = point.clone(); + + [point.x, point.y] +} + +fn render_g2_affine_to_hex(point: &G2Affine) -> [String; 4] { + match point { + G2Affine::Fq2(point) => { + if point.is_infinity { + return [ + "0x0".to_owned(), + "0x0".to_owned(), + "0x0".to_owned(), + "0x0".to_owned(), + ]; + } + + let point = point.clone(); + + [point.x.0, point.x.1, point.y.0, point.y.1] + } + _ => unreachable!(), + } +} diff --git a/zokrates_proof_systems/src/to_token.rs b/zokrates_proof_systems/src/to_token.rs index 7d52d1241..0e9081c1a 100644 --- a/zokrates_proof_systems/src/to_token.rs +++ b/zokrates_proof_systems/src/to_token.rs @@ -6,15 +6,15 @@ use super::{ }; /// Helper methods for parsing group structure -pub fn encode_g1_element(g: &G1Affine) -> (U256, U256) { - ( - U256::from(&hex::decode(&g.x.trim_start_matches("0x")).unwrap()[..]), - U256::from(&hex::decode(&g.y.trim_start_matches("0x")).unwrap()[..]), - ) +pub fn encode_g1_element(g: &G1Affine) -> Token { + let x = U256::from(&hex::decode(&g.x.trim_start_matches("0x")).unwrap()[..]); + let y = U256::from(&hex::decode(&g.y.trim_start_matches("0x")).unwrap()[..]); + + Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) } -pub fn encode_g2_element(g: &G2Affine) -> ((U256, U256), (U256, U256)) { - match g { +pub fn encode_g2_element(g: &G2Affine) -> Token { + let ((x0, y0), (x1, y1)) = match g { G2Affine::Fq2(g) => ( ( U256::from(&hex::decode(&g.x.0.trim_start_matches("0x")).unwrap()[..]), @@ -26,11 +26,24 @@ pub fn encode_g2_element(g: &G2Affine) -> ((U256, U256), (U256, U256)) { ), ), _ => unreachable!(), - } + }; + + Token::Tuple(vec![ + Token::FixedArray(vec![Token::Uint(x0), Token::Uint(y0)]), + Token::FixedArray(vec![Token::Uint(x1), Token::Uint(y1)]), + ]) +} + +pub fn encode_fr_element(f: &Fr) -> Token { + Token::Uint(U256::from( + &hex::decode(&f.trim_start_matches("0x")).unwrap()[..], + )) } -pub fn encode_fr_element(f: &Fr) -> U256 { - U256::from(&hex::decode(&f.trim_start_matches("0x")).unwrap()[..]) +pub fn encode_fr_element_as_tuple(f: &Fr) -> Token { + Token::Tuple(vec![Token::Uint(U256::from( + &hex::decode(&f.trim_start_matches("0x")).unwrap()[..], + ))]) } pub trait ToToken: SolidityCompatibleScheme { @@ -41,23 +54,11 @@ pub trait ToToken: SolidityCompatibleScheme { impl ToToken for G16 { fn to_token(proof: Self::Proof) -> Token { - let a = { - let (x, y) = encode_g1_element(&proof.a); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; - - let b = { - let ((x0, y0), (x1, y1)) = encode_g2_element(&proof.b); - Token::Tuple(vec![ - Token::FixedArray(vec![Token::Uint(x0), Token::Uint(y0)]), - Token::FixedArray(vec![Token::Uint(x1), Token::Uint(y1)]), - ]) - }; - - let c = { - let (x, y) = encode_g1_element(&proof.c); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let a = encode_g1_element(&proof.a); + + let b = encode_g2_element(&proof.b); + + let c = encode_g1_element(&proof.c); let proof_tokens = vec![a, b, c]; @@ -72,23 +73,11 @@ impl ToToken for G16 { impl ToToken for GM17 { fn to_token(proof: Self::Proof) -> Token { - let a = { - let (x, y) = encode_g1_element(&proof.a); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; - - let b = { - let ((x0, y0), (x1, y1)) = encode_g2_element(&proof.b); - Token::Tuple(vec![ - Token::FixedArray(vec![Token::Uint(x0), Token::Uint(y0)]), - Token::FixedArray(vec![Token::Uint(x1), Token::Uint(y1)]), - ]) - }; - - let c = { - let (x, y) = encode_g1_element(&proof.c); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let a = encode_g1_element(&proof.a); + + let b = encode_g2_element(&proof.b); + + let c = encode_g1_element(&proof.c); let proof_tokens = vec![a, b, c]; @@ -103,64 +92,29 @@ impl ToToken for GM17 { impl ToToken for Marlin { fn to_token(proof: Self::Proof) -> Token { - let comms_1_token = Token::Array( - proof - .comms_1 - .iter() - .map(encode_g1_element) - .map(|(x, y)| Token::Tuple(vec![Token::Uint(x), Token::Uint(y)])) - .collect(), - ); + let comms_1_token = Token::Array(proof.comms_1.iter().map(encode_g1_element).collect()); - let comms_2_token = Token::Array( - proof - .comms_2 - .iter() - .map(encode_g1_element) - .map(|(x, y)| Token::Tuple(vec![Token::Uint(x), Token::Uint(y)])) - .collect(), - ); + let comms_2_token = Token::Array(proof.comms_2.iter().map(encode_g1_element).collect()); - let degree_bound_comms_2_g1_token = { - let (x, y) = encode_g1_element(&proof.degree_bound_comms_2_g1); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let degree_bound_comms_2_g1_token = encode_g1_element(&proof.degree_bound_comms_2_g1); - let comms_3_token = Token::Array( - proof - .comms_3 - .iter() - .map(encode_g1_element) - .map(|(x, y)| Token::Tuple(vec![Token::Uint(x), Token::Uint(y)])) - .collect(), - ); + let comms_3_token = Token::Array(proof.comms_3.iter().map(encode_g1_element).collect()); - let degree_bound_comms_3_g2_token = { - let (x, y) = encode_g1_element(&proof.degree_bound_comms_3_g2); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let degree_bound_comms_3_g2_token = encode_g1_element(&proof.degree_bound_comms_3_g2); let evals_token = Token::Array( proof .evals .into_iter() .map(|f| encode_fr_element(&f)) - .map(Token::Uint) .collect::>(), ); - let pc_lc_opening_1_token = { - let (x, y) = encode_g1_element(&proof.batch_lc_proof_1); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let pc_lc_opening_1_token = encode_g1_element(&proof.batch_lc_proof_1); - let degree_bound_pc_lc_opening_1_token = - Token::Uint(encode_fr_element(&proof.batch_lc_proof_1_r)); + let degree_bound_pc_lc_opening_1_token = encode_fr_element(&proof.batch_lc_proof_1_r); - let pc_lc_opening_2_token = { - let (x, y) = encode_g1_element(&proof.batch_lc_proof_2); - Token::Tuple(vec![Token::Uint(x), Token::Uint(y)]) - }; + let pc_lc_opening_2_token = encode_g1_element(&proof.batch_lc_proof_2); let proof_tokens = vec![ comms_1_token,