From 707b1ad538b0d389cd07eb947963577d26370c95 Mon Sep 17 00:00:00 2001 From: Jonathan Wang <31040440+jonathanpwang@users.noreply.github.com> Date: Wed, 13 Dec 2023 01:23:36 -0500 Subject: [PATCH] [feat] update `gen_dummy_snark` to help with keygen (#48) * chore: update `gen_dummy_snark` with circuit-params * chore: better doc comment * chore: remove dbg * feat: refactor into `gen_dummy_snark_from_vk` This function does not need to know the `ConcreteCircuit` type * feat: add `KeygenAggregationCircuitIntent` for keygen Trait to help keygen of aggregation circuits. * chore: fix clippy * chore: rename function * chore: add `AggregationDependencyIntentOwned` * chore: From impl * chore: add `gen_dummy_snark_from_protocol` Also added `NativeKzgAccumulationScheme` trait * chore: remove redundancy --- Cargo.lock | 63 +++++------ snark-verifier-sdk/src/halo2.rs | 111 ++++++++++++++++---- snark-verifier-sdk/src/halo2/aggregation.rs | 10 +- snark-verifier-sdk/src/halo2/utils.rs | 91 ++++++++++++++++ 4 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 snark-verifier-sdk/src/halo2/utils.rs diff --git a/Cargo.lock b/Cargo.lock index f2a6bd84..e1dcfeaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1134,21 +1134,39 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "halo2-axiom" +version = "0.4.1" +dependencies = [ + "blake2b_simd", + "crossbeam", + "ff", + "group", + "halo2curves-axiom", + "itertools 0.11.0", + "maybe-rayon", + "pairing", + "rand", + "rand_core", + "rustc-hash", + "sha3 0.10.8", + "tracing", +] + [[package]] name = "halo2-base" -version = "0.4.0" -source = "git+https://github.com/axiom-crypto/halo2-lib.git?branch=release-0.4.1-rc#a30e3b18d285c8b0d7145c1c34297edd9433df60" +version = "0.4.1" dependencies = [ "getset", - "halo2_proofs 0.2.0", - "halo2_proofs 0.2.0 (git+https://github.com/privacy-scaling-explorations/halo2.git?rev=7a21656)", + "halo2-axiom", + "halo2_proofs", "itertools 0.11.0", "jemallocator", "log", "num-bigint", "num-integer", "num-traits", - "poseidon-rs", + "poseidon-primitives", "rand_chacha", "rayon", "rustc-hash", @@ -1158,8 +1176,7 @@ dependencies = [ [[package]] name = "halo2-ecc" -version = "0.4.0" -source = "git+https://github.com/axiom-crypto/halo2-lib.git?branch=release-0.4.1-rc#a30e3b18d285c8b0d7145c1c34297edd9433df60" +version = "0.4.1" dependencies = [ "halo2-base", "itertools 0.10.5", @@ -1175,24 +1192,6 @@ dependencies = [ "test-case", ] -[[package]] -name = "halo2_proofs" -version = "0.2.0" -dependencies = [ - "blake2b_simd", - "crossbeam", - "ff", - "group", - "halo2curves 0.4.0", - "maybe-rayon", - "pairing", - "rand", - "rand_core", - "rustc-hash", - "sha3 0.10.8", - "tracing", -] - [[package]] name = "halo2_proofs" version = "0.2.0" @@ -1201,7 +1200,7 @@ dependencies = [ "blake2b_simd", "ff", "group", - "halo2curves 0.1.0", + "halo2curves", "maybe-rayon", "rand_chacha", "rand_core", @@ -1230,9 +1229,10 @@ dependencies = [ ] [[package]] -name = "halo2curves" -version = "0.4.0" -source = "git+https://github.com/axiom-crypto/halo2curves.git?branch=main#e185711b6ba8f3e22f2af8bf24a5fc84b781ca46" +name = "halo2curves-axiom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d47eaec6a3040c2fbaa020716bf571b0c55025126242510f774183aff84b92e" dependencies = [ "blake2b_simd", "ff", @@ -1910,9 +1910,10 @@ dependencies = [ ] [[package]] -name = "poseidon-rs" +name = "poseidon-primitives" version = "0.1.1" -source = "git+https://github.com/axiom-crypto/poseidon-circuit.git?rev=1aee4a1#1aee4a1bf6220578924079aac4f2eee3874116a1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd95570f7ea849b4187298b5bb229643e44e1d47ddf3979d0db8a1c28be26a8" dependencies = [ "bitvec", "ff", diff --git a/snark-verifier-sdk/src/halo2.rs b/snark-verifier-sdk/src/halo2.rs index aa5f8810..ee400913 100644 --- a/snark-verifier-sdk/src/halo2.rs +++ b/snark-verifier-sdk/src/halo2.rs @@ -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}, @@ -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 @@ -274,38 +275,67 @@ pub fn read_snark(path: impl AsRef) -> Result { bincode::deserialize_from(f) } +pub trait NativeKzgAccumulationScheme = PolynomialCommitmentScheme< + G1Affine, + NativeLoader, + VerifyingKey = KzgSuccinctVerifyingKey, + Output = KzgAccumulator, + > + AccumulationScheme< + G1Affine, + NativeLoader, + Accumulator = KzgAccumulator, + VerifyingKey = KzgAsVerifyingKey, + > + CostEstimation>>; + // copied from snark_verifier --example recursion pub fn gen_dummy_snark( params: &ParamsKZG, vk: Option<&VerifyingKey>, num_instance: Vec, + circuit_params: ConcreteCircuit::Params, ) -> Snark where ConcreteCircuit: CircuitExt, - AS: PolynomialCommitmentScheme< - G1Affine, - NativeLoader, - VerifyingKey = KzgSuccinctVerifyingKey, - Output = KzgAccumulator, - > + AccumulationScheme< - G1Affine, - NativeLoader, - Accumulator = KzgAccumulator, - VerifyingKey = KzgAsVerifyingKey, - > + CostEstimation>>, + ConcreteCircuit::Params: Clone, + AS: NativeKzgAccumulationScheme, { - struct CsProxy(PhantomData<(F, C)>); + #[derive(Clone)] + struct CsProxy> { + params: C::Params, + _marker: PhantomData, + } - impl> Circuit for CsProxy { + impl> CsProxy { + pub fn new(params: C::Params) -> Self { + Self { params, _marker: PhantomData } + } + } + + impl> Circuit for CsProxy + 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) -> Self::Config { - C::configure(meta) + fn params(&self) -> Self::Params { + self.params.clone() + } + + fn configure_with_params( + meta: &mut ConstraintSystem, + params: Self::Params, + ) -> Self::Config { + C::configure_with_params(meta, params) + } + + fn configure(_: &mut ConstraintSystem) -> Self::Config { + unreachable!("must use configure_with_params") } fn synthesize( @@ -330,15 +360,50 @@ where let dummy_vk = vk .is_none() - .then(|| keygen_vk(params, &CsProxy::(PhantomData)).unwrap()); - let protocol = compile( + .then(|| keygen_vk(params, &CsProxy::::new(circuit_params)).unwrap()); + + gen_dummy_snark_from_vk::( 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( + params: &ParamsKZG, + vk: &VerifyingKey, + num_instance: Vec, + accumulator_indices: Option>, +) -> 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::(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(protocol: PlonkProtocol) -> Snark +where + AS: NativeKzgAccumulationScheme, +{ + let instances = protocol.num_instance.iter().map(|&n| vec![Fr::default(); n]).collect(); let proof = { let mut transcript = PoseidonTranscript::::new::(Vec::new()); for _ in 0..protocol diff --git a/snark-verifier-sdk/src/halo2/aggregation.rs b/snark-verifier-sdk/src/halo2/aggregation.rs index ed940729..60c4b3db 100644 --- a/snark-verifier-sdk/src/halo2/aggregation.rs +++ b/snark-verifier-sdk/src/halo2/aggregation.rs @@ -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>>, pub accumulator: KzgAccumulator>>, /// This returns the assigned `preprocessed` and `transcript_initial_state` values as a vector of assigned values, one for each aggregated snark. @@ -295,8 +299,10 @@ impl TryFrom for AggregationConfigParams { pub struct AggregationCircuit { /// Circuit builder consisting of virtual region managers pub builder: BaseCircuitBuilder, - // 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>>, /// 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. diff --git a/snark-verifier-sdk/src/halo2/utils.rs b/snark-verifier-sdk/src/halo2/utils.rs new file mode 100644 index 00000000..a7faa64e --- /dev/null +++ b/snark-verifier-sdk/src/halo2/utils.rs @@ -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, + pub num_instance: &'a [usize], + pub is_aggregation: bool, +} + +#[derive(Clone, Debug)] +pub struct AggregationDependencyIntentOwned { + pub vk: VerifyingKey, + pub num_instance: Vec, + 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 = AggregationCircuit; + + /// The **ordered** list of [`VerifyingKey`]s of the circuits to be aggregated. + fn intent_of_dependencies(&self) -> Vec; + + /// 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) -> 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::( + ¶ms, + 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, + } + } +}