Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] update gen_dummy_snark to help with keygen #48

Merged
merged 11 commits into from
Dec 13, 2023
63 changes: 32 additions & 31 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

111 changes: 88 additions & 23 deletions snark-verifier-sdk/src/halo2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use snark_verifier::{
system::halo2::{compile, Config},
util::arithmetic::Rotation,
util::transcript::TranscriptWrite,
verifier::plonk::PlonkProof,
verifier::plonk::{PlonkProof, PlonkProtocol},
};
use std::{
fs::{self, File},
Expand All @@ -46,6 +46,7 @@ use std::{
};

pub mod aggregation;
pub mod utils;

// Poseidon parameters
// We use the same ones Scroll uses for security: https://github.com/scroll-tech/poseidon-circuit/blob/714f50c7572a4ff6f2b1fa51a9604a99cd7b6c71/src/poseidon/primitives/bn256/fp.rs
Expand Down Expand Up @@ -274,38 +275,67 @@ pub fn read_snark(path: impl AsRef<Path>) -> Result<Snark, bincode::Error> {
bincode::deserialize_from(f)
}

pub trait NativeKzgAccumulationScheme = PolynomialCommitmentScheme<
G1Affine,
NativeLoader,
VerifyingKey = KzgSuccinctVerifyingKey<G1Affine>,
Output = KzgAccumulator<G1Affine, NativeLoader>,
> + AccumulationScheme<
G1Affine,
NativeLoader,
Accumulator = KzgAccumulator<G1Affine, NativeLoader>,
VerifyingKey = KzgAsVerifyingKey,
> + CostEstimation<G1Affine, Input = Vec<Query<Rotation>>>;

// copied from snark_verifier --example recursion
pub fn gen_dummy_snark<ConcreteCircuit, AS>(
params: &ParamsKZG<Bn256>,
vk: Option<&VerifyingKey<G1Affine>>,
num_instance: Vec<usize>,
circuit_params: ConcreteCircuit::Params,
) -> Snark
where
ConcreteCircuit: CircuitExt<Fr>,
AS: PolynomialCommitmentScheme<
G1Affine,
NativeLoader,
VerifyingKey = KzgSuccinctVerifyingKey<G1Affine>,
Output = KzgAccumulator<G1Affine, NativeLoader>,
> + AccumulationScheme<
G1Affine,
NativeLoader,
Accumulator = KzgAccumulator<G1Affine, NativeLoader>,
VerifyingKey = KzgAsVerifyingKey,
> + CostEstimation<G1Affine, Input = Vec<Query<Rotation>>>,
ConcreteCircuit::Params: Clone,
AS: NativeKzgAccumulationScheme,
{
struct CsProxy<F, C>(PhantomData<(F, C)>);
#[derive(Clone)]
struct CsProxy<F: Field, C: Circuit<F>> {
params: C::Params,
_marker: PhantomData<F>,
}

impl<F: Field, C: CircuitExt<F>> Circuit<F> for CsProxy<F, C> {
impl<F: Field, C: Circuit<F>> CsProxy<F, C> {
pub fn new(params: C::Params) -> Self {
Self { params, _marker: PhantomData }
}
}

impl<F: Field, C: CircuitExt<F>> Circuit<F> for CsProxy<F, C>
where
C::Params: Clone,
{
type Config = C::Config;
type FloorPlanner = C::FloorPlanner;
type Params = C::Params;

fn without_witnesses(&self) -> Self {
CsProxy(PhantomData)
Self::new(self.params.clone())
}

fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
C::configure(meta)
fn params(&self) -> Self::Params {
self.params.clone()
}

fn configure_with_params(
meta: &mut ConstraintSystem<F>,
params: Self::Params,
) -> Self::Config {
C::configure_with_params(meta, params)
}

fn configure(_: &mut ConstraintSystem<F>) -> Self::Config {
unreachable!("must use configure_with_params")
}

fn synthesize(
Expand All @@ -330,15 +360,50 @@ where

let dummy_vk = vk
.is_none()
.then(|| keygen_vk(params, &CsProxy::<Fr, ConcreteCircuit>(PhantomData)).unwrap());
let protocol = compile(
.then(|| keygen_vk(params, &CsProxy::<Fr, ConcreteCircuit>::new(circuit_params)).unwrap());

gen_dummy_snark_from_vk::<AS>(
params,
vk.or(dummy_vk.as_ref()).unwrap(),
Config::kzg()
.with_num_instance(num_instance.clone())
.with_accumulator_indices(ConcreteCircuit::accumulator_indices()),
num_instance,
ConcreteCircuit::accumulator_indices(),
)
}

/// Creates a dummy snark in the correct shape corresponding to the given verifying key.
/// This dummy snark will **not** verify.
/// This snark can be used as a placeholder input into an aggregation circuit expecting a snark
/// with this verifying key.
///
/// Note that this function does not need to know the concrete `Circuit` type.
pub fn gen_dummy_snark_from_vk<AS>(
params: &ParamsKZG<Bn256>,
vk: &VerifyingKey<G1Affine>,
num_instance: Vec<usize>,
accumulator_indices: Option<Vec<(usize, usize)>>,
) -> Snark
where
AS: NativeKzgAccumulationScheme,
{
let protocol = compile(
params,
vk,
Config::kzg().with_num_instance(num_instance).with_accumulator_indices(accumulator_indices),
);
let instances = num_instance.into_iter().map(|n| vec![Fr::default(); n]).collect();
gen_dummy_snark_from_protocol::<AS>(protocol)
}

/// Creates a dummy snark in the correct shape corresponding to the given Plonk protocol.
/// This dummy snark will **not** verify.
/// This snark can be used as a placeholder input into an aggregation circuit expecting a snark
/// with this protocol.
///
/// Note that this function does not need to know the concrete `Circuit` type.
pub fn gen_dummy_snark_from_protocol<AS>(protocol: PlonkProtocol<G1Affine>) -> Snark
where
AS: NativeKzgAccumulationScheme,
{
let instances = protocol.num_instance.iter().map(|&n| vec![Fr::default(); n]).collect();
let proof = {
let mut transcript = PoseidonTranscript::<NativeLoader, _>::new::<SECURE_MDS>(Vec::new());
for _ in 0..protocol
Expand Down
10 changes: 8 additions & 2 deletions snark-verifier-sdk/src/halo2/aggregation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ pub struct PreprocessedAndDomainAsWitness {

#[derive(Clone, Debug)]
pub struct SnarkAggregationWitness<'a> {
/// The (flattened) public instances from previous snarks that were aggregated, now collected as PRIVATE assigned values.
/// * If previous snark was from aggregation circuit, the previous instances will still contain the old KZG accumulator.
///
/// The user can optionally append these private witnesses to `inner.assigned_instances` to expose them.
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.
Expand Down Expand Up @@ -295,8 +299,10 @@ impl TryFrom<BaseCircuitParams> for AggregationConfigParams {
pub struct AggregationCircuit {
/// Circuit builder consisting of virtual region managers
pub builder: BaseCircuitBuilder<Fr>,
// the public instances from previous snarks that were aggregated, now collected as PRIVATE assigned values
// the user can optionally append these to `inner.assigned_instances` to expose them
/// The (flattened) public instances from previous snarks that were aggregated, now collected as PRIVATE assigned values.
/// * If previous snark was from aggregation circuit, the previous instances will still contain the old KZG accumulator.
///
/// The user can optionally append these private witnesses to `inner.assigned_instances` to expose them.
#[getset(get = "pub")]
previous_instances: Vec<Vec<AssignedValue<Fr>>>,
/// This returns the assigned `preprocessed_digest` (vkey), optional `transcript_initial_state`, `domain.n` (optional), and `omega` (optional) values as a vector of assigned values, one for each aggregated snark.
Expand Down
91 changes: 91 additions & 0 deletions snark-verifier-sdk/src/halo2/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
use halo2_base::{
halo2_proofs::{
halo2curves::bn256::{Fr, G1Affine},
plonk::{Circuit, VerifyingKey},
},
utils::fs::read_params,
};

use crate::{CircuitExt, Snark, SHPLONK};

use super::{aggregation::AggregationCircuit, gen_dummy_snark_from_vk};

#[derive(Clone, Copy, Debug)]
pub struct AggregationDependencyIntent<'a> {
pub vk: &'a VerifyingKey<G1Affine>,
pub num_instance: &'a [usize],
pub is_aggregation: bool,
}

#[derive(Clone, Debug)]
pub struct AggregationDependencyIntentOwned {
pub vk: VerifyingKey<G1Affine>,
pub num_instance: Vec<usize>,
pub is_aggregation: bool,
}

/// This trait should be implemented on the minimal circuit configuration data necessary to
/// completely determine an aggregation circuit
/// (independent of circuit inputs or specific snarks to be aggregated).
/// This is used to generate a _dummy_ instantiation of a concrete `Circuit` type for the purposes of key generation.
/// This dummy instantiation just needs to have the correct arithmetization format, but the witnesses do not need to
/// satisfy constraints.
///
/// This trait is specialized for aggregation circuits, which need to aggregate **dependency** snarks.
/// The aggregation circuit should only depend on the verifying key of each dependency snark.
pub trait KeygenAggregationCircuitIntent {
/// Concrete circuit type. Defaults to [`AggregationCircuit`].
type AggregationCircuit: Circuit<Fr> = AggregationCircuit;

/// The **ordered** list of [`VerifyingKey`]s of the circuits to be aggregated.
fn intent_of_dependencies(&self) -> Vec<AggregationDependencyIntent>;

/// Builds a _dummy_ instantiation of `Self::AggregationCircuit` for the purposes of key generation.
/// Assumes that `snarks` is an ordered list of [`Snark`]s, where the `i`th snark corresponds to the `i`th [`VerifyingKey`] in `vk_of_dependencies`.
/// The `snarks` only need to have the correct witness sizes (e.g., proof length) but the
/// snarks do _not_ need to verify.
///
/// May specify additional custom logic for building the aggregation circuit from the snarks.
fn build_keygen_circuit_from_snarks(self, snarks: Vec<Snark>) -> Self::AggregationCircuit;

/// Builds a _dummy_ instantiation of `Self::AggregationCircuit` for the purposes of key generation.
///
/// Generates dummy snarks from the verifying keys in `vk_of_dependencies`, **assuming** that SHPLONK is
/// used for the multi-open scheme.
/// To do so, it will try to read KZG trusted setup files from the directory set by environmental variable
/// `PARAMS_DIR` or `./params/`.
// The `params` is not actually used, so this requirement should be removed in the future:
// requires refactoring `compile`.
fn build_keygen_circuit_shplonk(self) -> Self::AggregationCircuit
where
Self: Sized,
{
let snarks = self
.intent_of_dependencies()
.into_iter()
.map(|AggregationDependencyIntent { vk, num_instance, is_aggregation }| {
let k = vk.get_domain().k();
let params = read_params(k);
let accumulator_indices =
is_aggregation.then_some(AggregationCircuit::accumulator_indices().unwrap());
gen_dummy_snark_from_vk::<SHPLONK>(
&params,
vk,
num_instance.to_vec(),
accumulator_indices,
)
})
.collect();
self.build_keygen_circuit_from_snarks(snarks)
}
}

impl<'a> From<&'a AggregationDependencyIntentOwned> for AggregationDependencyIntent<'a> {
fn from(intent: &'a AggregationDependencyIntentOwned) -> Self {
Self {
vk: &intent.vk,
num_instance: &intent.num_instance,
is_aggregation: intent.is_aggregation,
}
}
}
Loading