Skip to content

Commit

Permalink
feat: add example with different vkey as private witness
Browse files Browse the repository at this point in the history
Same aggregation circuit, verifies different snarks with different vkeys
(same standard plonk gate, but different selectors / copy constraints)
  • Loading branch information
jonathanpwang committed Jul 24, 2023
1 parent cbf7219 commit df44945
Show file tree
Hide file tree
Showing 3 changed files with 255 additions and 7 deletions.
2 changes: 2 additions & 0 deletions snark-verifier-sdk/benches/standard_plonk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -226,6 +227,7 @@ fn bench(c: &mut Criterion) {
lookup_bits,
&params,
snarks.clone(),
false,
);
let num_instances = agg_circuit.num_instance();
let instances = agg_circuit.instances();
Expand Down
210 changes: 210 additions & 0 deletions snark-verifier-sdk/examples/standard_plonk.rs
Original file line number Diff line number Diff line change
@@ -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<Advice>,
b: Column<Advice>,
c: Column<Advice>,
q_a: Column<Fixed>,
q_b: Column<Fixed>,
q_c: Column<Fixed>,
q_ab: Column<Fixed>,
constant: Column<Fixed>,
#[allow(dead_code)]
instance: Column<Instance>,
}

impl StandardPlonkConfig {
fn configure(meta: &mut ConstraintSystem<Fr>) -> 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<Fr> for StandardPlonk {
fn num_instance(&self) -> Vec<usize> {
vec![1]
}

fn instances(&self) -> Vec<Vec<Fr>> {
vec![vec![self.0]]
}
}

impl Circuit<Fr> for StandardPlonk {
type Config = StandardPlonkConfig;
type FloorPlanner = SimpleFloorPlanner;

fn without_witnesses(&self) -> Self {
Self(Fr::zero(), self.1)
}

fn configure(meta: &mut ConstraintSystem<Fr>) -> Self::Config {
meta.set_minimum_degree(4);
StandardPlonkConfig::configure(meta)
}

fn synthesize(
&self,
config: Self::Config,
mut layouter: impl Layouter<Fr>,
) -> 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<Bn256>, 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(&params_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::<SHPLONK>(
CircuitBuilderStage::Keygen,
None,
lookup_bits,
&params,
vec![dummy_snark],
true,
);
agg_circuit.config(k, Some(10));

let start0 = start_timer!(|| "gen vk & pk");
let pk = gen_pk(&params, &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(&params_app, flag));
for (i, snark) in snarks.into_iter().enumerate() {
let agg_circuit = AggregationCircuit::new::<SHPLONK>(
CircuitBuilderStage::Prover,
Some(break_points.clone()),
lookup_bits,
&params,
vec![snark],
true,
);
let _snark = gen_snark_shplonk(&params, &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(&params, &pk, agg_circuit, instances.clone());
let deployment_code = gen_evm_verifier_shplonk::<AggregationCircuit<SHPLONK>>(
&params,
pk.get_vk(),
num_instances,
Some(Path::new("./examples/standard_plonk.yul")),
);
evm_verify(deployment_code, instances, proof_calldata);
}
*/
}
50 changes: 43 additions & 7 deletions snark-verifier-sdk/src/halo2/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,34 @@ pub type Svk = KzgSuccinctVerifyingKey<G1Affine>;
pub type BaseFieldEccChip<'chip> = halo2_ecc::ecc::BaseFieldEccChip<'chip, G1Affine>;
pub type Halo2Loader<'chip> = loader::halo2::Halo2Loader<G1Affine, BaseFieldEccChip<'chip>>;

pub struct SnarkAggregationWitness<'a> {
pub previous_instances: Vec<Vec<AssignedValue<Fr>>>,
pub accumulator: KzgAccumulator<G1Affine, Rc<Halo2Loader<'a>>>,
/// 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<Vec<Vec<AssignedValue<Fr>>>>,
}

#[allow(clippy::type_complexity)]
/// Core function used in `synthesize` to aggregate multiple `snarks`.
///
/// Returns the assigned instances of previous snarks and the new final pair that needs to be verified in a pairing check.
/// For each previous snark, we concatenate all instances into a single vector. We return a vector of vectors,
/// one vector per snark, for convenience.
///
/// - `preprocessed_as_witness`: flag for whether preprocessed digest (i.e., verifying key) should be loaded as witness (if false, loaded as constant)
///
/// # Assumptions
/// * `snarks` is not empty
pub fn aggregate<'a, AS>(
svk: &Svk,
loader: &Rc<Halo2Loader<'a>>,
snarks: &[Snark],
as_proof: &[u8],
) -> (Vec<Vec<AssignedValue<Fr>>>, KzgAccumulator<G1Affine, Rc<Halo2Loader<'a>>>)
preprocessed_as_witness: bool,
) -> SnarkAggregationWitness<'a>
where
AS: PolynomialCommitmentScheme<
G1Affine,
Expand All @@ -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::<Rc<Halo2Loader<'a>>, &[u8]>::from_spec(
loader,
Expand All @@ -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
Expand All @@ -115,6 +147,7 @@ where
previous_instances.push(
instances.into_iter().flatten().map(|scalar| scalar.into_assigned()).collect(),
);
preprocessed_digest.push(inputs);

accumulator
})
Expand All @@ -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.
Expand Down Expand Up @@ -215,6 +249,7 @@ impl AggregationCircuit {
lookup_bits: usize,
params: &ParamsKZG<Bn256>,
snarks: impl IntoIterator<Item = Snark>,
preprocessed_as_witness: bool,
) -> Self
where
AS: for<'a> Halo2KzgAccumulationScheme<'a>,
Expand Down Expand Up @@ -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::<AS>(&svk, &loader, &snarks, as_proof.as_slice());
let SnarkAggregationWitness { previous_instances, accumulator, preprocessed_digest } =
aggregate::<AS>(&svk, &loader, &snarks, as_proof.as_slice(), preprocessed_as_witness);
let lhs = accumulator.lhs.assigned();
let rhs = accumulator.rhs.assigned();
let assigned_instances = lhs
Expand Down Expand Up @@ -312,7 +347,7 @@ impl AggregationCircuit {
where
AS: for<'a> Halo2KzgAccumulationScheme<'a>,
{
let mut private = Self::new::<AS>(stage, break_points, lookup_bits, params, snarks);
let mut private = Self::new::<AS>(stage, break_points, lookup_bits, params, snarks, false);
private.expose_previous_instances(has_prev_accumulator);
private
}
Expand All @@ -327,7 +362,7 @@ impl AggregationCircuit {
.with(|conf| conf.borrow().lookup_bits)
.unwrap_or(params.k() as usize - 1);
let circuit =
Self::new::<AS>(CircuitBuilderStage::Keygen, None, lookup_bits, params, snarks);
Self::new::<AS>(CircuitBuilderStage::Keygen, None, lookup_bits, params, snarks, false);
circuit.config(params.k(), Some(10));
circuit
}
Expand All @@ -350,6 +385,7 @@ impl AggregationCircuit {
lookup_bits,
params,
snarks,
false,
)
}

Expand Down

0 comments on commit df44945

Please sign in to comment.