From 84da93cd39f5beec17321e12de59f774bd50b589 Mon Sep 17 00:00:00 2001 From: Fan Date: Thu, 21 Oct 2021 16:31:19 +0800 Subject: [PATCH] Verify subproof part in aggregate proof (#119) * update dependencies from bellman_ce to franklin_crypto * induce verification of aggregation part in recursive proof Co-authored-by: Ho --- Cargo.lock | 10 ++-- Cargo.toml | 8 +-- src/bin/main.rs | 3 +- src/circom_circuit.rs | 3 +- src/lib.rs | 4 +- src/plonk.rs | 16 +++-- src/r1cs_file.rs | 6 +- src/reader.rs | 2 +- src/recursive/mod.rs | 80 +++++++++++++++++++------ src/tests.rs | 2 +- src/transpile.rs | 1 + src/utils.rs | 135 +++++++++++++++++++++++++++++++++++++++++- 12 files changed, 224 insertions(+), 46 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f3987de..f5faf5a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,7 +656,7 @@ dependencies = [ "hex", "hmac", "itertools 0.9.0", - "num-bigint 0.3.2", + "num-bigint 0.3.3", "num-integer", "num-traits", "poseidon_hash", @@ -1130,9 +1130,9 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d0a3d5e207573f948a9e5376662aa743a2ea13f7c50a554d7af443a73fbfeba" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" dependencies = [ "autocfg 1.0.1", "num-integer", @@ -1349,7 +1349,6 @@ name = "plonkit" version = "0.1.4" dependencies = [ "anyhow", - "bellman_ce", "bellman_vk_codegen", "byteorder", "clap-v3", @@ -1358,10 +1357,9 @@ dependencies = [ "hex-literal", "itertools 0.8.2", "log", - "num-bigint 0.2.6", + "num-bigint 0.3.3", "num-traits", "rand 0.4.6", - "recursive_aggregation_circuit", "serde", "serde_json", "solidity_recursive_plonk_verifier", diff --git a/Cargo.toml b/Cargo.toml index 9a4288b..67502e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ path = "src/bin/main.rs" [dependencies] anyhow = "1.0.34" -bellman_ce = { git = "https://github.com/matter-labs/bellman", branch = "beta", default-features = false, features = [ "plonk" ] } # active features depend on build type +# bellman_ce = { git = "https://github.com/matter-labs/bellman", branch = "beta", default-features = false, features = [ "plonk" ] } # active features depend on build type bellman_vk_codegen = { git = "https://github.com/fluidex/solidity_plonk_verifier.git" } byteorder = "1" clap = { package = "clap-v3", version = "3.0.0-beta.1" } # todo: replace with official v3 when it's released to crates.io @@ -23,13 +23,13 @@ franklin-crypto = { git = "https://github.com/matter-labs/franklin-crypto", bran hex-literal = "0.2.1" itertools = "0.8.1" log = "0.4.11" -num-bigint = "0.2.3" +num-bigint = "0.3.3" num-traits = "0.2.8" rand = "0.4" -recursive_aggregation_circuit = { package = "recursive_aggregation_circuit", version = "1.0.0", git = "https://github.com/matter-labs/recursive_aggregation_circuit.git", branch = "master" } +# recursive_aggregation_circuit = { package = "recursive_aggregation_circuit", version = "1.0.0", git = "https://github.com/matter-labs/recursive_aggregation_circuit.git", branch = "master" } serde = { version = "1.0", features = [ "derive" ] } serde_json = "1.0" recurisive_vk_codegen = { package = "solidity_recursive_plonk_verifier", git = "https://github.com/fluidex/solidity_recursive_plonk_verifier.git" } [features] -default = [ "bellman_ce/multicore" ] +default = [ ] diff --git a/src/bin/main.rs b/src/bin/main.rs index 918f027..f706c99 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,6 +1,5 @@ #![cfg(not(tarpaulin_include))] -extern crate bellman_ce; extern crate bellman_vk_codegen; extern crate clap; extern crate plonkit; @@ -10,7 +9,7 @@ use std::fs::File; use std::path::Path; use std::str; -use bellman_ce::pairing::bn256::Bn256; +use plonkit::bellman_ce::pairing::bn256::Bn256; use plonkit::circom_circuit::CircomCircuit; use plonkit::plonk; diff --git a/src/circom_circuit.rs b/src/circom_circuit.rs index 7415f37..35aad7f 100644 --- a/src/circom_circuit.rs +++ b/src/circom_circuit.rs @@ -1,12 +1,11 @@ #![allow(clippy::needless_range_loop)] -extern crate bellman_ce; extern crate rand; use itertools::Itertools; use std::collections::BTreeMap; use std::str; -use bellman_ce::{ +use crate::bellman_ce::{ pairing::{ff::PrimeField, ff::ScalarEngine, Engine}, Circuit, ConstraintSystem, Index, LinearCombination, SynthesisError, Variable, }; diff --git a/src/lib.rs b/src/lib.rs index 9cf04ea..7d602ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,9 +4,9 @@ extern crate serde; #[macro_use] extern crate hex_literal; -extern crate bellman_ce; extern crate bellman_vk_codegen; extern crate byteorder; +extern crate franklin_crypto; extern crate itertools; extern crate num_bigint; extern crate num_traits; @@ -20,5 +20,7 @@ pub mod recursive; pub mod transpile; pub mod utils; +pub use franklin_crypto::bellman as bellman_ce; + #[cfg(test)] mod tests; diff --git a/src/plonk.rs b/src/plonk.rs index fd9f008..1d5ebe8 100644 --- a/src/plonk.rs +++ b/src/plonk.rs @@ -1,8 +1,6 @@ // Most of this file is forked from source codes of [Matter Labs's zkSync](https://github.com/matter-labs/zksync) -use crate::circom_circuit::CircomCircuit; -use crate::transpile::{transpile_with_gates_count, ConstraintStat, TranspilerWrapper}; -use bellman_ce::bn256::Bn256; -use bellman_ce::{ +use crate::bellman_ce::bn256::Bn256; +use crate::bellman_ce::{ kate_commitment::{Crs, CrsForLagrangeForm, CrsForMonomialForm}, pairing::Engine, plonk::{ @@ -15,6 +13,8 @@ use bellman_ce::{ worker::Worker, Circuit, ScalarEngine, SynthesisError, }; +use crate::circom_circuit::CircomCircuit; +use crate::transpile::{transpile_with_gates_count, ConstraintStat, TranspilerWrapper}; type E = Bn256; use franklin_crypto::plonk::circuit::bigint::field::RnsParameters; @@ -193,11 +193,15 @@ pub fn verify( ) -> Result { match transcript { "keccak" => { - bellman_ce::plonk::better_cs::verifier::verify::<_, _, RollingKeccakTranscript<::Fr>>(proof, vk, None) + crate::bellman_ce::plonk::better_cs::verifier::verify::<_, _, RollingKeccakTranscript<::Fr>>(proof, vk, None) } "rescue" => { let (bn256_param, rns_param) = get_default_rescue_transcript_params(); - bellman_ce::plonk::better_cs::verifier::verify::<_, _, RescueTranscriptForRNS>(proof, vk, Some((&bn256_param, &rns_param))) + crate::bellman_ce::plonk::better_cs::verifier::verify::<_, _, RescueTranscriptForRNS>( + proof, + vk, + Some((&bn256_param, &rns_param)), + ) } _ => { unimplemented!("invalid transcript. use 'keccak' or 'rescue'"); diff --git a/src/r1cs_file.rs b/src/r1cs_file.rs index bab25b8..42b2d1e 100644 --- a/src/r1cs_file.rs +++ b/src/r1cs_file.rs @@ -1,11 +1,11 @@ // some codes borrowed from https://github.com/poma/zkutil/blob/master/src/r1cs_reader.rs #![allow(unused_variables, dead_code)] -use crate::circom_circuit::Constraint; -use bellman_ce::pairing::{ +use crate::bellman_ce::pairing::{ bn256::Bn256, ff::{Field, PrimeField, PrimeFieldRepr}, Engine, }; +use crate::circom_circuit::Constraint; use byteorder::{LittleEndian, ReadBytesExt}; use std::io::{Error, ErrorKind, Read, Result}; @@ -192,7 +192,7 @@ mod tests { " ); - use bellman_ce::pairing::ff; + use crate::bellman_ce::pairing::ff; let file = from_reader(&data[..]).unwrap(); assert_eq!(file.version, 1); diff --git a/src/reader.rs b/src/reader.rs index 548160b..71ee4c7 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -6,7 +6,7 @@ use std::fs::{File, OpenOptions}; use std::io::{BufRead, BufReader, Read}; use std::str; -use bellman_ce::{ +use crate::bellman_ce::{ kate_commitment::{Crs, CrsForLagrangeForm, CrsForMonomialForm}, pairing::{bn256::Bn256, ff::PrimeField, Engine}, plonk::{ diff --git a/src/recursive/mod.rs b/src/recursive/mod.rs index 657c217..1a483b1 100644 --- a/src/recursive/mod.rs +++ b/src/recursive/mod.rs @@ -1,35 +1,35 @@ #![allow(clippy::needless_range_loop)] - +use crate::{bellman_ce, utils}; use bellman_ce::kate_commitment::{Crs, CrsForMonomialForm}; +use bellman_ce::pairing::bn256; +use bellman_ce::pairing::bn256::Bn256; +use bellman_ce::pairing::ff::ScalarEngine; +use bellman_ce::pairing::{CurveAffine, Engine}; +use bellman_ce::plonk::better_better_cs::cs::PlonkCsWidth4WithNextStepAndCustomGatesParams; +use bellman_ce::plonk::better_better_cs::cs::ProvingAssembly; +use bellman_ce::plonk::better_better_cs::cs::TrivialAssembly; +use bellman_ce::plonk::better_better_cs::cs::Width4MainGateWithDNext; +use bellman_ce::plonk::better_better_cs::cs::{Circuit, Setup}; +use bellman_ce::plonk::better_better_cs::setup::VerificationKey; +use bellman_ce::plonk::better_better_cs::verifier::verify as core_verify; +use bellman_ce::plonk::commitments::transcript::keccak_transcript::RollingKeccakTranscript; use bellman_ce::plonk::{ better_cs::cs::PlonkCsWidth4WithNextStepParams, better_cs::keys::{Proof as OldProof, VerificationKey as OldVerificationKey}, }; -use bellman_ce::SynthesisError; -use franklin_crypto::bellman::pairing::bn256; -use franklin_crypto::bellman::pairing::bn256::Bn256; -use franklin_crypto::bellman::pairing::ff::ScalarEngine; -use franklin_crypto::bellman::pairing::{CurveAffine, Engine}; -use franklin_crypto::bellman::plonk::better_better_cs::cs::PlonkCsWidth4WithNextStepAndCustomGatesParams; -use franklin_crypto::bellman::plonk::better_better_cs::cs::ProvingAssembly; -use franklin_crypto::bellman::plonk::better_better_cs::cs::TrivialAssembly; -use franklin_crypto::bellman::plonk::better_better_cs::cs::Width4MainGateWithDNext; -use franklin_crypto::bellman::plonk::better_better_cs::cs::{Circuit, Setup}; -use franklin_crypto::bellman::plonk::better_better_cs::setup::VerificationKey; -use franklin_crypto::bellman::plonk::better_better_cs::verifier::verify as core_verify; -use franklin_crypto::bellman::plonk::commitments::transcript::keccak_transcript::RollingKeccakTranscript; -use franklin_crypto::bellman::worker::Worker; +use bellman_ce::worker::Worker; +use bellman_ce::{Field, SynthesisError}; use franklin_crypto::plonk::circuit::bigint::field::RnsParameters; use franklin_crypto::plonk::circuit::verifier_circuit::affine_point_wrapper::aux_data::{AuxData, BN256AuxData}; use franklin_crypto::plonk::circuit::verifier_circuit::data_structs::IntoLimbedWitness; use franklin_crypto::plonk::circuit::Width4WithCustomGates; use franklin_crypto::rescue::bn256::Bn256RescueParams; use itertools::Itertools; -pub use recurisive_vk_codegen::types::{AggregatedProof, RecursiveVerificationKey}; -use recursive_aggregation_circuit::circuit::{ +use recurisive_vk_codegen::circuit::{ create_recursive_circuit_setup, create_recursive_circuit_vk_and_setup, create_vks_tree, make_aggregate, make_public_input_and_limbed_aggregate, RecursiveAggregationCircuitBn256, }; +pub use recurisive_vk_codegen::types::{AggregatedProof, RecursiveVerificationKey}; // only support depth<8. different depths don't really make performance different const VK_TREE_DEPTH: usize = 7; @@ -135,6 +135,42 @@ pub fn prove( }) } +fn verify_subproof_limbs( + proof: &AggregatedProof, + vk: &VerificationKey, +) -> Result { + let mut rns_params = RnsParameters::::Fq>::new_for_field(68, 110, 4); + + //keep the behavior same as recursive_aggregation_circuit + rns_params.set_prefer_single_limb_allocation(true); + + let aggr_limbs_nums: Vec = proof.aggr_limbs.iter().map(utils::fe_to_biguint).collect(); + //we need 4 Fr to build 2 G1Affine ... + let num_consume = rns_params.num_limbs_for_in_field_representation; + assert_eq!(num_consume * 4, aggr_limbs_nums.len()); + + let mut start = 0; + let pg_x = utils::witness_to_field(&aggr_limbs_nums[start..start + num_consume], &rns_params); + start += num_consume; + let pg_y = utils::witness_to_field(&aggr_limbs_nums[start..start + num_consume], &rns_params); + start += num_consume; + let px_x = utils::witness_to_field(&aggr_limbs_nums[start..start + num_consume], &rns_params); + start += num_consume; + let px_y = utils::witness_to_field(&aggr_limbs_nums[start..start + num_consume], &rns_params); + + let pair_with_generator = bn256::G1Affine::from_xy_checked(pg_x, pg_y).map_err(|_| SynthesisError::Unsatisfiable)?; + let pair_with_x = bn256::G1Affine::from_xy_checked(px_x, px_y).map_err(|_| SynthesisError::Unsatisfiable)?; + + let valid = Bn256::final_exponentiation(&Bn256::miller_loop(&[ + (&pair_with_generator.prepare(), &vk.g2_elements[0].prepare()), + (&pair_with_x.prepare(), &vk.g2_elements[1].prepare()), + ])) + .ok_or(SynthesisError::Unsatisfiable)? + == ::Fqk::one(); + + Ok(valid) +} + // verify a recursive proof by using a corresponding verification key pub fn verify( vk: VerificationKey, @@ -145,7 +181,15 @@ pub fn verify( inputs.push(chunk); } log::info!("individual_inputs: {:#?}", inputs); - core_verify::<_, _, RollingKeccakTranscript<::Fr>>(&vk, &aggregated_proof.proof, None) + //notice in PlonkCore.sol the aggregate pairs from subproofs and recursive proofs are combined: 1 * inner + challenge * outer + //and only one verify on pairing has been run to save some gas + //here we just verify them respectively + let valid = core_verify::<_, _, RollingKeccakTranscript<::Fr>>(&vk, &aggregated_proof.proof, None)?; + if !valid { + return Ok(valid); + } + log::info!("aggregated proof is valid"); + verify_subproof_limbs(&aggregated_proof, &vk) } // export a verification key for a recursion circuit diff --git a/src/tests.rs b/src/tests.rs index 2372f41..50f4aa7 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,8 +1,8 @@ use std::fs; +use crate::bellman_ce::bn256::Bn256; use crate::circom_circuit::CircomCircuit; use crate::{plonk, reader}; -use bellman_ce::bn256::Bn256; const CIRCUIT_FILE: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/test/circuits/simple/circuit.r1cs.json"); const WITNESS_FILE: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/test/circuits/simple/witness.json"); diff --git a/src/transpile.rs b/src/transpile.rs index ba0015d..d4c3d02 100644 --- a/src/transpile.rs +++ b/src/transpile.rs @@ -1,3 +1,4 @@ +use crate::bellman_ce; use bellman_ce::pairing::Engine; use bellman_ce::plonk::better_cs::adaptor::{TranspilationVariant, Transpiler}; use bellman_ce::plonk::better_cs::cs::{ diff --git a/src/utils.rs b/src/utils.rs index 80f3dfd..2506ef0 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,15 +1,127 @@ -use num_bigint::BigUint; +use crate::bellman_ce::pairing::{ff::PrimeField, Engine}; +use franklin_crypto::plonk::circuit::bigint::field::RnsParameters; +pub use num_bigint::BigUint; use num_traits::Num; use std::fmt::Display; +//export some more funcs +pub use franklin_crypto::plonk::circuit::bigint::bigint::{biguint_to_fe, fe_to_biguint}; + /// convert a hex integer representation ("0x...") to decimal representation pub fn repr_to_big(r: T) -> String { BigUint::from_str_radix(&format!("{}", r)[2..], 16).unwrap().to_str_radix(10) } +fn from_single_size_limb_witnesses(witnesses: &[BigUint], params: &RnsParameters) -> F { + assert_eq!(params.num_limbs_for_in_field_representation, witnesses.len()); + assert!( + params.binary_limbs_params.limb_size_bits % params.range_check_info.minimal_multiple == 0, + "limb size must be divisible by range constraint strategy granularity" + ); + + let mut this_value = BigUint::from(0u64); + for (witness_idx, w) in witnesses.iter().enumerate() { + this_value += w.clone() << (witness_idx * params.binary_limbs_params.limb_size_bits); + + //checking, we have omitted some more sophisticated like 'match over strategy' + let (expected_width, expected_max_value) = ( + params.binary_limbs_bit_widths[witness_idx], + params.binary_limbs_max_values[witness_idx].clone(), + ); + assert!(expected_width > 0); + assert!( + w <= &expected_max_value, + "limb is {}, max value is {}", + w.to_str_radix(16), + expected_max_value.to_str_radix(16) + ); + } + + biguint_to_fe(this_value) +} + +fn from_double_size_limb_witnesses( + witnesses: &[BigUint], + top_limb_may_overflow: bool, + params: &RnsParameters, +) -> F { + assert!(params.num_binary_limbs == 2 * witnesses.len()); + // until we make better handling of a case that top limb should be zero + // we make sure that + assert!(params.num_limbs_for_in_field_representation & 1 == 0); + + let mut this_value = BigUint::from(0u64); + for (witness_idx, w) in witnesses.iter().enumerate() { + this_value += w.clone() << (witness_idx * 2 * params.binary_limbs_params.limb_size_bits); + + //checking, we have omitted some more sophisticated like 'match over strategy' + let low_idx = witness_idx * 2; + let high_idx = witness_idx * 2 + 1; + + if low_idx < params.num_limbs_for_in_field_representation { + assert!(high_idx < params.num_limbs_for_in_field_representation) + } + // if the element must fit into the field than pad with zeroes + if !top_limb_may_overflow + && low_idx >= params.num_limbs_for_in_field_representation + && high_idx >= params.num_limbs_for_in_field_representation + { + unreachable!("should not try to allocate a value in a field with non-constant high limbs"); + } + + let (expected_low_width, expected_low_max_value) = if top_limb_may_overflow { + ( + params.binary_limbs_params.limb_size_bits, + params.binary_limbs_params.limb_max_value.clone(), + ) + } else { + ( + params.binary_limbs_bit_widths[low_idx], + params.binary_limbs_max_values[low_idx].clone(), + ) + }; + + let (expected_high_width, _expected_high_max_value) = if top_limb_may_overflow { + ( + params.binary_limbs_params.limb_size_bits, + params.binary_limbs_params.limb_max_value.clone(), + ) + } else { + ( + params.binary_limbs_bit_widths[high_idx], + params.binary_limbs_max_values[high_idx].clone(), + ) + }; + + assert!(expected_low_width > 0); + assert!(expected_high_width > 0); + if top_limb_may_overflow { + assert_eq!(expected_low_width, expected_high_width); + } + + assert_eq!(params.binary_limbs_params.limb_max_value.clone(), expected_low_max_value); + + assert!(expected_high_width & 1 == 0); + } + + biguint_to_fe(this_value) +} + +// refer to plonk/circuit/bigint/field, merge the limbs into prime field without allocting +// inside a cs +pub fn witness_to_field(limbs: &[BigUint], params: &RnsParameters) -> F { + if params.can_allocate_from_double_limb_witness() { + from_double_size_limb_witnesses(limbs, true, params) + } else { + from_single_size_limb_witnesses(limbs, params) + } +} + #[cfg(test)] mod tests { - use super::repr_to_big; + use super::*; + use crate::bellman_ce::pairing::bn256::Bn256; + use franklin_crypto::plonk::circuit::verifier_circuit::utils::field_to_witness; #[test] fn test_repr_to_big() { @@ -18,4 +130,23 @@ mod tests { "42107805128296840955128475693973618460424912398453449171839298387937674312611" ) } + + #[test] + fn test_witness_to_field() { + type Fq = ::Fq; + let mut rns_params = RnsParameters::::Fq>::new_for_field(68, 110, 4); + rns_params.set_prefer_single_limb_allocation(true); + + let fq: Fq = + biguint_to_fe(BigUint::from_str_radix(&"0x115cc0f5e7d690413df64c6b9662e9cf2a3617f2743245519e19607a4417189a"[2..], 16).unwrap()); + + let wts: Vec = field_to_witness(&fq, &rns_params).iter().map(fe_to_biguint).collect(); + let fq_restored = witness_to_field(&wts[..], &rns_params); + assert_eq!(fq, fq_restored); + + rns_params.set_prefer_single_limb_allocation(false); + let wts: Vec = field_to_witness(&fq, &rns_params).iter().map(fe_to_biguint).collect(); + let fq_restored = witness_to_field(&wts[..], &rns_params); + assert_eq!(fq, fq_restored); + } }