diff --git a/Cargo.lock b/Cargo.lock index 49e009185..c6dff4f88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -446,7 +446,7 @@ dependencies = [ "cfg-if 1.0.0", "crossbeam", "futures", - "hex 0.3.2", + "hex 0.4.3", "lazy_static", "num_cpus", "pairing_ce 0.28.2", @@ -466,7 +466,7 @@ dependencies = [ "byteorder", "cfg-if 1.0.0", "futures", - "hex 0.3.2", + "hex 0.4.3", "num_cpus", "pairing_ce 0.28.2", "rand 0.4.6", @@ -1399,6 +1399,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 +2119,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" @@ -3384,11 +3404,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/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/src/groth16.rs b/zokrates_bellman/src/groth16.rs index 126fbf18f..6718ec019 100644 --- a/zokrates_bellman/src/groth16.rs +++ b/zokrates_bellman/src/groth16.rs @@ -7,15 +7,12 @@ use bellman::pairing::{ff::to_hex, CurveAffine, Engine}; use zokrates_field::BellmanFieldExtensions; use zokrates_field::Field; use zokrates_proof_systems::{ - Backend, G1Affine, G2Affine, MpcBackend, NonUniversalBackend, Proof, SetupKeypair, + Backend, G1Affine, G2Affine, NonUniversalBackend, Proof, SetupKeypair, }; use crate::Bellman; use crate::Computation; use crate::{parse_g1, parse_g2, serialization}; -use phase2::MPCParameters; -use rand_0_4::Rng; -use std::io::{Read, Write}; use zokrates_ast::ir::{ProgIterator, Statement, Witness}; use zokrates_proof_systems::groth16::{ProofPoints, VerificationKey, G16}; use zokrates_proof_systems::Scheme; diff --git a/zokrates_bellman/src/plonk.rs b/zokrates_bellman/src/plonk.rs index ce4942356..d14e05afb 100644 --- a/zokrates_bellman/src/plonk.rs +++ b/zokrates_bellman/src/plonk.rs @@ -172,6 +172,12 @@ fn deserialize_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,6 +201,7 @@ fn serialize_vk( .try_into() .map_err(|_| ()) .unwrap(), + omega: omega, } } 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/mpc/beacon.rs b/zokrates_cli/src/ops/mpc/beacon.rs index c585f5631..5458b8753 100644 --- a/zokrates_cli/src/ops/mpc/beacon.rs +++ b/zokrates_cli/src/ops/mpc/beacon.rs @@ -3,10 +3,9 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use std::fs::File; use std::io::{BufReader, BufWriter}; use std::path::Path; -use zokrates_bellman::Bellman; use zokrates_common::constants::{BLS12_381, BN128}; -use zokrates_field::{BellmanFieldExtensions, Bls12_381Field, Bn128Field, Field}; -use zokrates_proof_systems::{MpcBackend, MpcScheme, G16}; +use zokrates_field::{BellmanFieldExtensions, Field}; +use zokrates_proof_systems::{MpcBackend, MpcScheme}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("beacon") diff --git a/zokrates_cli/src/ops/mpc/contribute.rs b/zokrates_cli/src/ops/mpc/contribute.rs index e2b668871..9ffd85d95 100644 --- a/zokrates_cli/src/ops/mpc/contribute.rs +++ b/zokrates_cli/src/ops/mpc/contribute.rs @@ -3,10 +3,9 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use std::fs::File; use std::io::{BufReader, BufWriter}; use std::path::Path; -use zokrates_bellman::Bellman; use zokrates_common::constants::{BLS12_381, BN128}; -use zokrates_field::{BellmanFieldExtensions, Bls12_381Field, Bn128Field, Field}; -use zokrates_proof_systems::{MpcBackend, MpcScheme, G16}; +use zokrates_field::{BellmanFieldExtensions, Field}; +use zokrates_proof_systems::{MpcBackend, MpcScheme}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("contribute") diff --git a/zokrates_cli/src/ops/mpc/export.rs b/zokrates_cli/src/ops/mpc/export.rs index dce308def..2f223ba1d 100644 --- a/zokrates_cli/src/ops/mpc/export.rs +++ b/zokrates_cli/src/ops/mpc/export.rs @@ -3,10 +3,9 @@ use clap::{App, Arg, ArgMatches, SubCommand}; use std::fs::File; use std::io::{BufReader, Write}; use std::path::Path; -use zokrates_bellman::Bellman; use zokrates_common::constants::{BLS12_381, BN128}; -use zokrates_field::{BellmanFieldExtensions, Bls12_381Field, Bn128Field, Field}; -use zokrates_proof_systems::{MpcBackend, MpcScheme, TaggedVerificationKey, G16}; +use zokrates_field::{BellmanFieldExtensions, Field}; +use zokrates_proof_systems::{MpcBackend, MpcScheme, TaggedVerificationKey}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("export") diff --git a/zokrates_cli/src/ops/mpc/init.rs b/zokrates_cli/src/ops/mpc/init.rs index 0fa6a523a..dcd83fe9f 100644 --- a/zokrates_cli/src/ops/mpc/init.rs +++ b/zokrates_cli/src/ops/mpc/init.rs @@ -4,9 +4,8 @@ use std::fs::File; use std::io::{BufReader, BufWriter}; use std::path::Path; use zokrates_ast::ir::{self, ProgEnum}; -use zokrates_bellman::Bellman; use zokrates_field::{BellmanFieldExtensions, Field}; -use zokrates_proof_systems::{MpcBackend, MpcScheme, G16}; +use zokrates_proof_systems::{MpcBackend, MpcScheme}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("init") diff --git a/zokrates_cli/src/ops/mpc/verify.rs b/zokrates_cli/src/ops/mpc/verify.rs index 968be2692..84d1a5172 100644 --- a/zokrates_cli/src/ops/mpc/verify.rs +++ b/zokrates_cli/src/ops/mpc/verify.rs @@ -4,9 +4,8 @@ use std::fs::File; use std::io::BufReader; use std::path::Path; use zokrates_ast::ir::{self, ProgEnum}; -use zokrates_bellman::Bellman; use zokrates_field::{BellmanFieldExtensions, Field}; -use zokrates_proof_systems::{MpcBackend, MpcScheme, G16}; +use zokrates_proof_systems::{MpcBackend, MpcScheme}; pub fn subcommand() -> App<'static, 'static> { SubCommand::with_name("verify") 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_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..d5b002b20 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::*; diff --git a/zokrates_proof_systems/src/scheme/plonk.rs b/zokrates_proof_systems/src/scheme/plonk.rs index 5a8060cff..52d19ad0c 100644 --- a/zokrates_proof_systems/src/scheme/plonk.rs +++ b/zokrates_proof_systems/src/scheme/plonk.rs @@ -1,9 +1,13 @@ -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)] pub struct Plonk; @@ -17,6 +21,9 @@ 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)] @@ -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(|x| encode_g1_element(x)) + .collect(), + ); + + let grand_product_commitment = encode_g1_element(&proof.grand_product_commitment); + + let quotient_poly_commitments = Token::FixedArray( + proof + .quotient_poly_commitments + .iter() + .map(|x| encode_g1_element(x)) + .collect(), + ); + + let wire_values_at_z = Token::FixedArray( + proof + .wire_values_at_z + .iter() + .map(|x| encode_fr_element_as_tuple(x)) + .collect(), + ); + + let wire_values_at_z_omega = Token::FixedArray( + proof + .wire_values_at_z_omega + .iter() + .map(|x| encode_fr_element_as_tuple(x)) + .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(|x| encode_fr_element_as_tuple(x)) + .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..1c77c0f56 --- /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 in 0..2 { + map.insert( + format!("selector_commitment_{}_{}", i, j), + to_json(&rendered[j]), + ); + } + } + + for (i, c) in vk.next_step_selector_commitments.iter().enumerate() { + let rendered = render_g1_affine_to_hex(&c); + + for j in 0..2 { + map.insert( + format!("next_step_selector_commitment_{}_{}", i, j), + to_json(&rendered[j]), + ); + } + } + + for (i, c) in vk.permutation_commitments.iter().enumerate() { + let rendered = render_g1_affine_to_hex(&c); + + for j in 0..2 { + map.insert( + format!("permutation_commitment_{}_{}", i, j), + to_json(&rendered[j]), + ); + } + } + + 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,