Skip to content

Commit

Permalink
[Feat] Universal verifier circuit (#26)
Browse files Browse the repository at this point in the history
* 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)

* fix: save break points when generating agg circuit for first time (#23)

* fix: save break points when generating agg circuit for first time

* chore: add circuit files to gitignore

* feat: halo2-lib universal verifier example

* chore: cargo fix

* feat: allow circuit size (number of rows) to be loaded as witness

* chore: clippy fix

* fix(n_as_witness): computation of shifts depends on `omega`

`omega` which changes when `k` changes, so all shift computations need
to be done as witness. Current implementation is likely not the most
optimal. Instead of storing `shift` as `omega^i`, we store just
`Rotation(i)`. We de-duplicate when possible using `BTreeMap` of
`Rotation`. Note you must use `Rotation` instead of `F` for `BTreeMap`
because the ordering of `omega^i` may change depending on `omega`.

* fix: temp remove pow_var

* add universal verifier range check test

* chore: do not serialize domain_as_witness if none

* Revert "fix: temp remove pow_var"

This reverts commit 69f648e.

* fix: halo2_lib example

* test: halo2_lib with variable lookup table passes

* Bump version to 0.1.3

---------

Co-authored-by: Roshan <[email protected]>
  • Loading branch information
jonathanpwang and rpalakkal committed Aug 18, 2023
1 parent 4eded17 commit f1d12e5
Show file tree
Hide file tree
Showing 24 changed files with 926 additions and 165 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
/target
testdata

Cargo.lock
params
agg.pk
break_points.json
2 changes: 1 addition & 1 deletion snark-verifier-sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "snark-verifier-sdk"
version = "0.1.2"
version = "0.1.3"
edition = "2021"

[dependencies]
Expand Down
3 changes: 2 additions & 1 deletion snark-verifier-sdk/benches/read_pk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG};
use pprof::criterion::{Output, PProfProfiler};
use rand::rngs::OsRng;

use snark_verifier_sdk::halo2::aggregation::AggregationConfigParams;
use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality};
use snark_verifier_sdk::{
gen_pk,
halo2::{aggregation::AggregationCircuit, gen_snark_shplonk},
Expand Down Expand Up @@ -190,6 +190,7 @@ fn bench(c: &mut Criterion) {
None,
&params,
snarks,
VerifierUniversality::None,
);

std::fs::remove_file("examples/agg.pk").ok();
Expand Down
5 changes: 4 additions & 1 deletion snark-verifier-sdk/benches/standard_plonk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use halo2_proofs::{
use pprof::criterion::{Output, PProfProfiler};
use rand::rngs::OsRng;
use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk};
use snark_verifier_sdk::halo2::aggregation::AggregationConfigParams;
use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality};
use snark_verifier_sdk::{
gen_pk,
halo2::{aggregation::AggregationCircuit, gen_proof_shplonk, gen_snark_shplonk},
Expand Down Expand Up @@ -193,6 +193,7 @@ fn bench(c: &mut Criterion) {
None,
&params,
snarks.clone(),
VerifierUniversality::None,
);

let start0 = start_timer!(|| "gen vk & pk");
Expand All @@ -213,6 +214,7 @@ fn bench(c: &mut Criterion) {
Some(break_points.clone()),
params,
snarks.clone(),
VerifierUniversality::None,
);
let instances = agg_circuit.instances();
gen_proof_shplonk(params, pk, agg_circuit, instances, None)
Expand All @@ -230,6 +232,7 @@ fn bench(c: &mut Criterion) {
Some(break_points),
&params,
snarks.clone(),
VerifierUniversality::None,
);
let num_instances = agg_circuit.num_instance();
let instances = agg_circuit.instances();
Expand Down
188 changes: 188 additions & 0 deletions snark-verifier-sdk/examples/n_as_witness.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
use halo2_base::gates::builder::CircuitBuilderStage;
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 rand::rngs::StdRng;
use rand::SeedableRng;
use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality};
use snark_verifier_sdk::SHPLONK;
use snark_verifier_sdk::{
gen_pk,
halo2::{aggregation::AggregationCircuit, gen_snark_shplonk},
Snark,
};

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 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)]
pub struct StandardPlonk(pub Fr, pub usize);

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)));
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()));
a.copy_advice(&mut region, config.b, 3);
a.copy_advice(&mut region, config.c, 4);

// assuming <= 10 blinding factors
// fill in most of circuit with a computation
let n = self.1;
for offset in 5..n - 10 {
region.assign_advice(config.a, offset, Value::known(-Fr::from(5u64)));
for (idx, column) in (1..).zip([
config.q_a,
config.q_b,
config.q_c,
config.q_ab,
config.constant,
]) {
region.assign_fixed(column, offset, Fr::from(idx as u64));
}
}

Ok(())
},
)
}
}
}

fn gen_application_snark(k: u32) -> Snark {
let rng = StdRng::seed_from_u64(0);
let params = gen_srs(k);
let circuit = application::StandardPlonk(Fr::random(rng), params.n() as usize);

let pk = gen_pk(&params, &circuit, None);
gen_snark_shplonk(&params, &pk, circuit, None::<&str>)
}

fn main() {
let dummy_snark = gen_application_snark(8);

let k = 15u32;
let params = gen_srs(k);
let lookup_bits = k as usize - 1;
let mut agg_circuit = AggregationCircuit::new::<SHPLONK>(
CircuitBuilderStage::Keygen,
AggregationConfigParams { degree: k, lookup_bits, ..Default::default() },
None,
&params,
vec![dummy_snark],
VerifierUniversality::Full,
);
let agg_config = agg_circuit.config(Some(10));

let pk = gen_pk(&params, &agg_circuit, None);
let break_points = agg_circuit.break_points();

let snarks = [8, 12, 15, 20].map(|k| (k, gen_application_snark(k)));
for (k, snark) in snarks {
let agg_circuit = AggregationCircuit::new::<SHPLONK>(
CircuitBuilderStage::Prover,
agg_config,
Some(break_points.clone()),
&params,
vec![snark],
VerifierUniversality::Full,
);
let _snark = gen_snark_shplonk(&params, &pk, agg_circuit, None::<&str>);
println!("snark with k = {k} success");
}
}
90 changes: 90 additions & 0 deletions snark-verifier-sdk/examples/range_check.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
use ark_std::{end_timer, start_timer};
use halo2_base::gates::builder::{
BaseConfigParams, CircuitBuilderStage, GateThreadBuilder, RangeWithInstanceCircuitBuilder,
};
use halo2_base::gates::flex_gate::GateStrategy;
use halo2_base::halo2_proofs::halo2curves::bn256::Fr;
use halo2_base::halo2_proofs::plonk::Circuit;
use halo2_base::safe_types::{GateInstructions, RangeChip, RangeInstructions};
use halo2_base::utils::fs::gen_srs;

use itertools::Itertools;
use snark_verifier_sdk::halo2::aggregation::{AggregationConfigParams, VerifierUniversality};
use snark_verifier_sdk::SHPLONK;
use snark_verifier_sdk::{
gen_pk,
halo2::{aggregation::AggregationCircuit, gen_snark_shplonk},
Snark,
};

fn generate_circuit(k: u32) -> Snark {
let mut builder = GateThreadBuilder::new(false);
let ctx = builder.main(0);
let lookup_bits = k as usize - 1;
let range = RangeChip::<Fr>::default(lookup_bits);

let x = ctx.load_witness(Fr::from(14));
range.range_check(ctx, x, 2 * lookup_bits + 1);
range.gate().add(ctx, x, x);

let circuit = RangeWithInstanceCircuitBuilder::<Fr>::keygen(
builder.clone(),
BaseConfigParams {
strategy: GateStrategy::Vertical,
k: k as usize,
num_advice_per_phase: vec![1],
num_lookup_advice_per_phase: vec![1],
num_fixed: 1,
lookup_bits: Some(lookup_bits),
},
vec![],
);
let params = gen_srs(k);

let pk = gen_pk(&params, &circuit, None);
let breakpoints = circuit.break_points();

let circuit = RangeWithInstanceCircuitBuilder::<Fr>::prover(
builder.clone(),
circuit.params(),
breakpoints,
vec![],
);
gen_snark_shplonk(&params, &pk, circuit, None::<&str>)
}

fn main() {
let dummy_snark = generate_circuit(13);

let k = 14u32;
let lookup_bits = k as usize - 1;
let params = gen_srs(k);
let mut agg_circuit = AggregationCircuit::new::<SHPLONK>(
CircuitBuilderStage::Keygen,
AggregationConfigParams { degree: k, lookup_bits, ..Default::default() },
None,
&params,
vec![dummy_snark],
VerifierUniversality::Full,
);
let agg_config = agg_circuit.config(Some(10));

let start0 = start_timer!(|| "gen vk & pk");
let pk = gen_pk(&params, &agg_circuit, None);
end_timer!(start0);
let break_points = agg_circuit.break_points();

let snarks = (14..17).map(generate_circuit).collect_vec();
for (i, snark) in snarks.into_iter().enumerate() {
let agg_circuit = AggregationCircuit::new::<SHPLONK>(
CircuitBuilderStage::Prover,
agg_config,
Some(break_points.clone()),
&params,
vec![snark],
VerifierUniversality::Full,
);
let _snark = gen_snark_shplonk(&params, &pk, agg_circuit, None::<&str>);
println!("snark {i} success");
}
}
Loading

0 comments on commit f1d12e5

Please sign in to comment.