From df44945cf531155e864f7b1f608bf623f34e55ad Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Sun, 23 Jul 2023 23:50:28 -0600 Subject: [PATCH] feat: add example with different vkey as private witness Same aggregation circuit, verifies different snarks with different vkeys (same standard plonk gate, but different selectors / copy constraints) --- snark-verifier-sdk/benches/standard_plonk.rs | 2 + snark-verifier-sdk/examples/standard_plonk.rs | 210 ++++++++++++++++++ snark-verifier-sdk/src/halo2/aggregation.rs | 50 ++++- 3 files changed, 255 insertions(+), 7 deletions(-) create mode 100644 snark-verifier-sdk/examples/standard_plonk.rs diff --git a/snark-verifier-sdk/benches/standard_plonk.rs b/snark-verifier-sdk/benches/standard_plonk.rs index f696f822..24e3ae73 100644 --- a/snark-verifier-sdk/benches/standard_plonk.rs +++ b/snark-verifier-sdk/benches/standard_plonk.rs @@ -209,6 +209,7 @@ fn bench(c: &mut Criterion) { lookup_bits, params, snarks.clone(), + false, ); let instances = agg_circuit.instances(); gen_proof_shplonk(params, pk, agg_circuit, instances, None) @@ -226,6 +227,7 @@ fn bench(c: &mut Criterion) { lookup_bits, ¶ms, snarks.clone(), + false, ); let num_instances = agg_circuit.num_instance(); let instances = agg_circuit.instances(); diff --git a/snark-verifier-sdk/examples/standard_plonk.rs b/snark-verifier-sdk/examples/standard_plonk.rs new file mode 100644 index 00000000..35bb573b --- /dev/null +++ b/snark-verifier-sdk/examples/standard_plonk.rs @@ -0,0 +1,210 @@ +use application::ComputeFlag; +use ark_std::{end_timer, start_timer}; +use halo2_base::gates::builder::{set_lookup_bits, CircuitBuilderStage, BASE_CONFIG_PARAMS}; +use halo2_base::halo2_proofs; +use halo2_base::halo2_proofs::arithmetic::Field; +use halo2_base::halo2_proofs::halo2curves::bn256::Fr; +use halo2_base::halo2_proofs::poly::commitment::Params; +use halo2_base::utils::fs::gen_srs; +use halo2_proofs::halo2curves as halo2_curves; +use halo2_proofs::plonk::Circuit; +use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG}; +use rand::rngs::OsRng; +use snark_verifier_sdk::{ + evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}, + gen_pk, + halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, + Snark, +}; +use snark_verifier_sdk::{CircuitExt, SHPLONK}; +use std::path::Path; + +mod application { + use super::halo2_curves::bn256::Fr; + use super::halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, + poly::Rotation, + }; + use rand::RngCore; + use snark_verifier_sdk::CircuitExt; + + #[derive(Clone, Copy)] + pub struct StandardPlonkConfig { + a: Column, + b: Column, + c: Column, + q_a: Column, + q_b: Column, + q_c: Column, + q_ab: Column, + constant: Column, + #[allow(dead_code)] + instance: Column, + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [a, b, c] = [(); 3].map(|_| meta.advice_column()); + let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); + let instance = meta.instance_column(); + + [a, b, c].map(|column| meta.enable_equality(column)); + + meta.create_gate( + "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", + |meta| { + let [a, b, c] = + [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let instance = meta.query_instance(instance, Rotation::cur()); + Some( + q_a * a.clone() + + q_b * b.clone() + + q_c * c + + q_ab * a * b + + constant + + instance, + ) + }, + ); + + StandardPlonkConfig { a, b, c, q_a, q_b, q_c, q_ab, constant, instance } + } + } + + #[derive(Clone, Copy, PartialEq, Eq)] + pub enum ComputeFlag { + All, + SkipFixed, + SkipCopy, + } + + #[derive(Clone)] + pub struct StandardPlonk(pub Fr, pub ComputeFlag); + + impl CircuitExt for StandardPlonk { + fn num_instance(&self) -> Vec { + vec![1] + } + + fn instances(&self) -> Vec> { + vec![vec![self.0]] + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + Self(Fr::zero(), self.1) + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + layouter.assign_region( + || "", + |mut region| { + region.assign_advice(config.a, 0, Value::known(self.0)); + region.assign_fixed(config.q_a, 0, -Fr::one()); + region.assign_advice(config.a, 1, Value::known(-Fr::from(5u64))); + if self.1 != ComputeFlag::SkipFixed { + for (idx, column) in (1..).zip([ + config.q_a, + config.q_b, + config.q_c, + config.q_ab, + config.constant, + ]) { + region.assign_fixed(column, 1, Fr::from(idx as u64)); + } + } + let a = region.assign_advice(config.a, 2, Value::known(Fr::one())); + if self.1 != ComputeFlag::SkipCopy { + a.copy_advice(&mut region, config.b, 3); + a.copy_advice(&mut region, config.c, 4); + } + + Ok(()) + }, + ) + } + } +} + +fn gen_application_snark(params: &ParamsKZG, flag: ComputeFlag) -> Snark { + let circuit = application::StandardPlonk(Fr::random(OsRng), flag); + + let pk = gen_pk(params, &circuit, None); + gen_snark_shplonk(params, &pk, circuit, None::<&str>) +} + +fn main() { + let params_app = gen_srs(8); + let dummy_snark = gen_application_snark(¶ms_app, ComputeFlag::All); + + let k = 22u32; + let params = gen_srs(k); + let lookup_bits = k as usize - 1; + BASE_CONFIG_PARAMS.with(|config| { + config.borrow_mut().lookup_bits = Some(lookup_bits); + config.borrow_mut().k = k as usize; + }); + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Keygen, + None, + lookup_bits, + ¶ms, + vec![dummy_snark], + true, + ); + agg_circuit.config(k, Some(10)); + + let start0 = start_timer!(|| "gen vk & pk"); + let pk = gen_pk(¶ms, &agg_circuit, Some(Path::new("./examples/agg.pk"))); + end_timer!(start0); + let break_points = agg_circuit.break_points(); + + let snarks = [ComputeFlag::All, ComputeFlag::SkipFixed, ComputeFlag::SkipCopy] + .map(|flag| gen_application_snark(¶ms_app, flag)); + for (i, snark) in snarks.into_iter().enumerate() { + let agg_circuit = AggregationCircuit::new::( + CircuitBuilderStage::Prover, + Some(break_points.clone()), + lookup_bits, + ¶ms, + vec![snark], + true, + ); + let _snark = gen_snark_shplonk(¶ms, &pk, agg_circuit, None::<&str>); + println!("snark {i} success"); + } + + /* + #[cfg(feature = "loader_evm")] + { + // do one more time to verify + let num_instances = agg_circuit.num_instance(); + let instances = agg_circuit.instances(); + let proof_calldata = gen_evm_proof_shplonk(¶ms, &pk, agg_circuit, instances.clone()); + + let deployment_code = gen_evm_verifier_shplonk::>( + ¶ms, + pk.get_vk(), + num_instances, + Some(Path::new("./examples/standard_plonk.yul")), + ); + evm_verify(deployment_code, instances, proof_calldata); + } + */ +} diff --git a/snark-verifier-sdk/src/halo2/aggregation.rs b/snark-verifier-sdk/src/halo2/aggregation.rs index 9f9ac64a..b6bbabf1 100644 --- a/snark-verifier-sdk/src/halo2/aggregation.rs +++ b/snark-verifier-sdk/src/halo2/aggregation.rs @@ -47,6 +47,16 @@ pub type Svk = KzgSuccinctVerifyingKey; pub type BaseFieldEccChip<'chip> = halo2_ecc::ecc::BaseFieldEccChip<'chip, G1Affine>; pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader>; +pub struct SnarkAggregationWitness<'a> { + pub previous_instances: Vec>>, + pub accumulator: KzgAccumulator>>, + /// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark. + /// These can then be exposed as public instances. + /// + /// This is only useful if preprocessed digest is loaded as witness (i.e., `preprocessed_as_witness` is true in `aggregate`), so we set it to `None` otherwise. + pub preprocessed_digest: Option>>>, +} + #[allow(clippy::type_complexity)] /// Core function used in `synthesize` to aggregate multiple `snarks`. /// @@ -54,6 +64,8 @@ pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader( @@ -61,7 +73,8 @@ pub fn aggregate<'a, AS>( loader: &Rc>, snarks: &[Snark], as_proof: &[u8], -) -> (Vec>>, KzgAccumulator>>) + preprocessed_as_witness: bool, +) -> SnarkAggregationWitness<'a> where AS: PolynomialCommitmentScheme< G1Affine, @@ -86,6 +99,7 @@ where }; let mut previous_instances = Vec::with_capacity(snarks.len()); + let mut preprocessed_digest = Vec::with_capacity(snarks.len()); // to avoid re-loading the spec each time, we create one transcript and clear the stream let mut transcript = PoseidonTranscript::>, &[u8]>::from_spec( loader, @@ -96,7 +110,25 @@ where let mut accumulators = snarks .iter() .flat_map(|snark| { - let protocol = snark.protocol.loaded(loader); + let protocol = if preprocessed_as_witness { + snark.protocol.loaded_preprocessed_as_witness(loader) + } else { + snark.protocol.loaded(loader) + }; + let inputs = protocol + .preprocessed + .iter() + .flat_map(|preprocessed| { + let assigned = preprocessed.assigned(); + [assigned.x(), assigned.y()] + .into_iter() + .flat_map(|coordinate| coordinate.limbs().to_vec()) + .collect_vec() + }) + .chain( + protocol.transcript_initial_state.clone().map(|scalar| scalar.into_assigned()), + ) + .collect_vec(); let instances = assign_instances(&snark.instances); // read the transcript and perform Fiat-Shamir @@ -115,6 +147,7 @@ where previous_instances.push( instances.into_iter().flatten().map(|scalar| scalar.into_assigned()).collect(), ); + preprocessed_digest.push(inputs); accumulator }) @@ -133,8 +166,9 @@ where } else { accumulators.pop().unwrap() }; + let preprocessed_digest = preprocessed_as_witness.then(|| preprocessed_digest); - (previous_instances, accumulator) + SnarkAggregationWitness { previous_instances, accumulator, preprocessed_digest } } /// Same as `FlexGateConfigParams` except we assume a single Phase and default 'Vertical' strategy. @@ -215,6 +249,7 @@ impl AggregationCircuit { lookup_bits: usize, params: &ParamsKZG, snarks: impl IntoIterator, + preprocessed_as_witness: bool, ) -> Self where AS: for<'a> Halo2KzgAccumulationScheme<'a>, @@ -265,8 +300,8 @@ impl AggregationCircuit { let ecc_chip = BaseFieldEccChip::new(&fp_chip); let loader = Halo2Loader::new(ecc_chip, builder); - let (previous_instances, accumulator) = - aggregate::(&svk, &loader, &snarks, as_proof.as_slice()); + let SnarkAggregationWitness { previous_instances, accumulator, preprocessed_digest } = + aggregate::(&svk, &loader, &snarks, as_proof.as_slice(), preprocessed_as_witness); let lhs = accumulator.lhs.assigned(); let rhs = accumulator.rhs.assigned(); let assigned_instances = lhs @@ -312,7 +347,7 @@ impl AggregationCircuit { where AS: for<'a> Halo2KzgAccumulationScheme<'a>, { - let mut private = Self::new::(stage, break_points, lookup_bits, params, snarks); + let mut private = Self::new::(stage, break_points, lookup_bits, params, snarks, false); private.expose_previous_instances(has_prev_accumulator); private } @@ -327,7 +362,7 @@ impl AggregationCircuit { .with(|conf| conf.borrow().lookup_bits) .unwrap_or(params.k() as usize - 1); let circuit = - Self::new::(CircuitBuilderStage::Keygen, None, lookup_bits, params, snarks); + Self::new::(CircuitBuilderStage::Keygen, None, lookup_bits, params, snarks, false); circuit.config(params.k(), Some(10)); circuit } @@ -350,6 +385,7 @@ impl AggregationCircuit { lookup_bits, params, snarks, + false, ) }