diff --git a/Cargo.toml b/Cargo.toml index 7a52d3bb..a25b90a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ sha3 = { version = "0.10.1", optional = true } foundry_evm = { git = "https://github.com/jonathanpwang/foundry", package = "foundry-evm", branch = "fix/pin-revm-to-rev", optional = true } # loader_halo2 -halo2_ecc = { git = "https://github.com/jonathanpwang/halo2-ecc", branch = "value", default-features = false, optional = true } +halo2_ecc = { git = "https://github.com/jonathanpwang/halo2-ecc", tag = "v2022_09_29", default-features = false, optional = true } poseidon = { git = "https://github.com/privacy-scaling-explorations/poseidon", branch = "padding", optional = true } # test @@ -52,7 +52,8 @@ system_halo2 = ["dep:halo2_proofs"] sanity_check = [] [patch."https://github.com/privacy-scaling-explorations/halo2"] -halo2_proofs = { git = "https://github.com/han0110/halo2", branch = "feature/configurable-instance-query", package = "halo2_proofs" } +# halo2_proofs = { path = "../halo2/halo2_proofs" } +halo2_proofs = { git = "https://github.com/zk-attestor/halo2.git", branch = "serialize", package = "halo2_proofs" } [[example]] name = "evm-verifier" diff --git a/examples/README.md b/examples/README.md index 55370999..27996ca3 100644 --- a/examples/README.md +++ b/examples/README.md @@ -1,6 +1,6 @@ In `plonk-verifier` root directory: -1. Create `./src/configs/verify_circuit_for_evm.config` +1. Create `./src/configs/verify_circuit.config` 2. Run diff --git a/examples/evm-verifier-with-accumulator.rs b/examples/evm-verifier-with-accumulator.rs index 6715e5a0..a8bf3c2f 100644 --- a/examples/evm-verifier-with-accumulator.rs +++ b/examples/evm-verifier-with-accumulator.rs @@ -1,10 +1,10 @@ +use application::StandardPlonk; use ark_std::{end_timer, start_timer}; use ethereum_types::Address; use foundry_evm::executor::{fork::MultiFork, Backend, ExecutorBuilder}; use halo2_curves::bn256::{Bn256, Fq, Fr, G1Affine}; use halo2_proofs::{ - dev::MockProver, - plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ProvingKey, VerifyingKey}, + plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, poly::{ commitment::{Params, ParamsProver}, kzg::{ @@ -22,8 +22,16 @@ use plonk_verifier::{ evm::{encode_calldata, EvmLoader}, native::NativeLoader, }, - pcs::kzg::{Gwc19, Kzg, KzgAs, LimbsEncoding}, - system::halo2::{compile, read_or_create_srs, transcript::evm::EvmTranscript, Config}, + pcs::kzg::{Gwc19, Kzg, LimbsEncoding}, + system::halo2::{ + aggregation::{ + self, create_snark_shplonk, gen_pk, gen_srs, write_bytes, AggregationCircuit, Snark, + TargetCircuit, + }, + compile, + transcript::evm::EvmTranscript, + Config, + }, verifier::{self, PlonkVerifier}, }; use rand::rngs::OsRng; @@ -33,7 +41,7 @@ const LIMBS: usize = 3; const BITS: usize = 88; type Pcs = Kzg; -type As = KzgAs; +// type As = KzgAs; type Plonk = verifier::Plonk>; mod application { @@ -91,7 +99,7 @@ mod application { } #[derive(Clone, Default)] - pub struct StandardPlonk(Fr); + pub struct StandardPlonk(pub Fr); impl StandardPlonk { pub fn rand(mut rng: R) -> Self { @@ -153,313 +161,6 @@ mod application { } } -mod aggregation { - use super::{As, Plonk, BITS, LIMBS}; - use halo2_curves::bn256::{Bn256, Fq, Fr, G1Affine}; - use halo2_ecc::{ - fields::fp::FpConfig, - gates::{Context, ContextParams}, - }; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{self, Circuit}, - poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}, - }; - use itertools::Itertools; - use plonk_verifier::{ - loader::{self, native::NativeLoader}, - pcs::{ - kzg::{KzgAccumulator, KzgSuccinctVerifyingKey}, - AccumulationScheme, AccumulationSchemeProver, - }, - system::{ - self, - halo2::{Halo2VerifierCircuitConfig, Halo2VerifierCircuitConfigParams}, - }, - util::arithmetic::fe_to_limbs, - verifier::PlonkVerifier, - Protocol, - }; - use rand::rngs::OsRng; - use std::rc::Rc; - - const T: usize = 5; - const RATE: usize = 4; - const R_F: usize = 8; - const R_P: usize = 60; - - type Svk = KzgSuccinctVerifyingKey; - - type Halo2Loader<'a, 'b> = loader::halo2::Halo2Loader<'a, 'b, G1Affine>; - pub type PoseidonTranscript = - system::halo2::transcript::halo2::PoseidonTranscript; - - pub struct Snark { - protocol: Protocol, - instances: Vec>, - proof: Vec, - } - - impl Snark { - pub fn new(protocol: Protocol, instances: Vec>, proof: Vec) -> Self { - Self { protocol, instances, proof } - } - } - - impl From for SnarkWitness { - fn from(snark: Snark) -> Self { - Self { - protocol: snark.protocol, - instances: snark - .instances - .into_iter() - .map(|instances| instances.into_iter().map(Value::known).collect_vec()) - .collect(), - proof: Value::known(snark.proof), - } - } - } - - #[derive(Clone)] - pub struct SnarkWitness { - protocol: Protocol, - instances: Vec>>, - proof: Value>, - } - - impl SnarkWitness { - fn without_witnesses(&self) -> Self { - SnarkWitness { - protocol: self.protocol.clone(), - instances: self - .instances - .iter() - .map(|instances| vec![Value::unknown(); instances.len()]) - .collect(), - proof: Value::unknown(), - } - } - - fn proof(&self) -> Value<&[u8]> { - self.proof.as_ref().map(Vec::as_slice) - } - } - - pub fn aggregate<'a, 'b>( - svk: &Svk, - loader: &Rc>, - snarks: &[SnarkWitness], - as_proof: Value<&'_ [u8]>, - ) -> KzgAccumulator>> { - let assign_instances = |instances: &[Vec>]| { - instances - .iter() - .map(|instances| { - instances.iter().map(|instance| loader.assign_scalar(*instance)).collect_vec() - }) - .collect_vec() - }; - - let accumulators = snarks - .iter() - .flat_map(|snark| { - let instances = assign_instances(&snark.instances); - let mut transcript = - PoseidonTranscript::, _, _>::new(loader, snark.proof()); - let proof = - Plonk::read_proof(svk, &snark.protocol, &instances, &mut transcript).unwrap(); - Plonk::succinct_verify(svk, &snark.protocol, &instances, &proof).unwrap() - }) - .collect_vec(); - - let acccumulator = { - let mut transcript = PoseidonTranscript::, _, _>::new(loader, as_proof); - let proof = - As::read_proof(&Default::default(), &accumulators, &mut transcript).unwrap(); - As::verify(&Default::default(), &accumulators, &proof).unwrap() - }; - - acccumulator - } - - #[derive(Clone)] - pub struct AggregationCircuit { - svk: Svk, - snarks: Vec, - instances: Vec, - as_proof: Value>, - } - - impl AggregationCircuit { - pub fn new(params: &ParamsKZG, snarks: impl IntoIterator) -> Self { - let svk = params.get_g()[0].into(); - let snarks = snarks.into_iter().collect_vec(); - - let accumulators = snarks - .iter() - .flat_map(|snark| { - let mut transcript = - PoseidonTranscript::::new(snark.proof.as_slice()); - let proof = - Plonk::read_proof(&svk, &snark.protocol, &snark.instances, &mut transcript) - .unwrap(); - Plonk::succinct_verify(&svk, &snark.protocol, &snark.instances, &proof).unwrap() - }) - .collect_vec(); - - let (accumulator, as_proof) = { - let mut transcript = PoseidonTranscript::::new(Vec::new()); - let accumulator = - As::create_proof(&Default::default(), &accumulators, &mut transcript, OsRng) - .unwrap(); - (accumulator, transcript.finalize()) - }; - - let KzgAccumulator { lhs, rhs } = accumulator; - let instances = - [lhs.x, lhs.y, rhs.x, rhs.y].map(fe_to_limbs::<_, _, LIMBS, BITS>).concat(); - - Self { - svk, - snarks: snarks.into_iter().map_into().collect(), - instances, - as_proof: Value::known(as_proof), - } - } - - pub fn accumulator_indices() -> Vec<(usize, usize)> { - (0..4 * LIMBS).map(|idx| (0, idx)).collect() - } - - pub fn num_instance() -> Vec { - vec![4 * LIMBS] - } - - pub fn instances(&self) -> Vec> { - vec![self.instances.clone()] - } - - pub fn as_proof(&self) -> Value<&[u8]> { - self.as_proof.as_ref().map(Vec::as_slice) - } - } - - impl Circuit for AggregationCircuit { - type Config = Halo2VerifierCircuitConfig; - type FloorPlanner = SimpleFloorPlanner; - - fn without_witnesses(&self) -> Self { - Self { - svk: self.svk, - snarks: self.snarks.iter().map(SnarkWitness::without_witnesses).collect(), - instances: Vec::new(), - as_proof: Value::unknown(), - } - } - - fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { - let path = "./src/configs/verify_circuit_for_evm.config"; - let params_str = - std::fs::read_to_string(path).expect(format!("{} should exist", path).as_str()); - let params: Halo2VerifierCircuitConfigParams = - serde_json::from_str(params_str.as_str()).unwrap(); - - assert!( - params.limb_bits == BITS && params.num_limbs == LIMBS, - "For now we fix limb_bits = {}, otherwise change code", - BITS - ); - let base_field_config = FpConfig::configure( - meta, - params.strategy, - params.num_advice, - params.num_lookup_advice, - params.num_fixed, - params.lookup_bits, - params.limb_bits, - params.num_limbs, - halo2_ecc::utils::modulus::(), - ); - - let instance = meta.instance_column(); - meta.enable_equality(instance); - - Self::Config { base_field_config, instance } - } - - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), plonk::Error> { - let mut layouter = layouter.namespace(|| "aggregation"); - config.base_field_config.load_lookup_table(&mut layouter)?; - - // Need to trick layouter to skip first pass in get shape mode - let using_simple_floor_planner = true; - let mut first_pass = true; - let mut final_pair = None; - layouter.assign_region( - || "", - |region| { - if using_simple_floor_planner && first_pass { - first_pass = false; - return Ok(()); - } - let ctx = Context::new( - region, - ContextParams { - num_advice: config.base_field_config.range.gate.num_advice, - using_simple_floor_planner, - first_pass, - }, - ); - - let loader = Halo2Loader::new(&config.base_field_config, ctx); - let KzgAccumulator { lhs, rhs } = - aggregate(&self.svk, &loader, &self.snarks, self.as_proof()); - - // REQUIRED STEP - loader.finalize(); - final_pair = Some((lhs.assigned(), rhs.assigned())); - - Ok(()) - }, - )?; - let (lhs, rhs) = final_pair.unwrap(); - Ok({ - // TODO: use less instances by following Scroll's strategy of keeping only last bit of y coordinate - let mut layouter = layouter.namespace(|| "expose"); - for (i, assigned_instance) in lhs - .x - .truncation - .limbs - .iter() - .chain(lhs.y.truncation.limbs.iter()) - .chain(rhs.x.truncation.limbs.iter()) - .chain(rhs.y.truncation.limbs.iter()) - .enumerate() - { - layouter.constrain_instance( - assigned_instance.cell().clone(), - config.instance, - i, - )?; - } - }) - } - } -} - -fn gen_srs(k: u32) -> ParamsKZG { - read_or_create_srs::(k, |k| ParamsKZG::::setup(k, OsRng)) -} - -fn gen_pk>(params: &ParamsKZG, circuit: &C) -> ProvingKey { - let vk = keygen_vk(params, circuit).unwrap(); - keygen_pk(params, vk, circuit).unwrap() -} - fn gen_proof< C: Circuit, E: EncodedChallenge, @@ -506,25 +207,6 @@ fn gen_proof< proof } -fn gen_application_snark(params: &ParamsKZG) -> aggregation::Snark { - let circuit = application::StandardPlonk::rand(OsRng); - - let pk = gen_pk(params, &circuit); - let protocol = compile( - params, - pk.get_vk(), - Config::kzg().with_num_instance(application::StandardPlonk::num_instance()), - ); - - let proof = gen_proof::< - _, - _, - aggregation::PoseidonTranscript, - aggregation::PoseidonTranscript, - >(params, &pk, circuit.clone(), circuit.instances()); - aggregation::Snark::new(protocol, circuit.instances(), proof) -} - fn gen_aggregation_evm_verifier( params: &ParamsKZG, vk: &VerifyingKey, @@ -553,13 +235,16 @@ fn gen_aggregation_evm_verifier( fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) { let calldata = encode_calldata(&instances, &proof); + write_bytes("./data/verifier_calldata.dat", &calldata); let success = { let mut evm = ExecutorBuilder::default() .with_gas_limit(u64::MAX.into()) .build(Backend::new(MultiFork::new().0, None)); let caller = Address::from_low_u64_be(0xfe); - let verifier = evm.deploy(caller, deployment_code.into(), 0.into(), None).unwrap().address; + let verifier = evm.deploy(caller, deployment_code.into(), 0.into(), None).unwrap(); + dbg!(verifier.gas); + let verifier = verifier.address; let result = evm.call_raw(caller, verifier, calldata.into(), 0.into()).unwrap(); dbg!(result.gas); @@ -570,7 +255,7 @@ fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) } pub fn load_verify_circuit_degree() -> u32 { - let path = "./src/configs/verify_circuit_for_evm.config"; + let path = "./src/configs/verify_circuit.config"; let params_str = std::fs::read_to_string(path).expect(format!("{} file should exist", path).as_str()); let params: plonk_verifier::system::halo2::Halo2VerifierCircuitConfigParams = @@ -578,31 +263,55 @@ pub fn load_verify_circuit_degree() -> u32 { params.degree } +impl TargetCircuit for StandardPlonk { + const TARGET_CIRCUIT_K: u32 = 8; + const PUBLIC_INPUT_SIZE: usize = 1; + const N_PROOFS: usize = 1; + const NAME: &'static str = "standard_plonk"; + + type Circuit = Self; + fn default_circuit() -> Self::Circuit { + StandardPlonk::rand(OsRng) + } + fn instances() -> Vec> { + unimplemented!() + } +} + fn main() { + let app_circuit = StandardPlonk::rand(OsRng); + let (_, snark) = create_snark_shplonk::( + vec![app_circuit.clone()], + vec![vec![vec![app_circuit.0]]], + None, + ); + let snarks = vec![snark]; + let k = load_verify_circuit_degree(); let params = gen_srs(k); - let params_app = { - let mut params = params.clone(); - params.downsize(8); - params - }; - let snarks = [(); 3].map(|_| gen_application_snark(¶ms_app)); - let agg_circuit = aggregation::AggregationCircuit::new(¶ms, snarks); - println!("finished creating agg_circuit"); - let pk_time = start_timer!(|| "agg_circuit vk & pk time"); - let pk = gen_pk(¶ms, &agg_circuit); - end_timer!(pk_time); + let agg_circuit = AggregationCircuit::new(¶ms, snarks, true); + let pk = gen_pk(¶ms, &agg_circuit, "standard_plonk_agg_circuit"); let deploy_time = start_timer!(|| "generate aggregation evm verifier code"); let deployment_code = gen_aggregation_evm_verifier( ¶ms, pk.get_vk(), - aggregation::AggregationCircuit::num_instance(), - aggregation::AggregationCircuit::accumulator_indices(), + agg_circuit.num_instance(), + AggregationCircuit::accumulator_indices(), ); end_timer!(deploy_time); - + write_bytes("./data/verifier_bytecode.dat", &deployment_code); + + // use different input snarks to test instances etc + let app_circuit = StandardPlonk::rand(OsRng); + let (_, snark) = create_snark_shplonk::( + vec![app_circuit.clone()], + vec![vec![vec![app_circuit.0]]], + None, + ); + let snarks = vec![snark]; + let agg_circuit = AggregationCircuit::new(¶ms, snarks, true); let proof_time = start_timer!(|| "create agg_circuit proof"); let proof = gen_proof::<_, _, EvmTranscript, EvmTranscript>( ¶ms, diff --git a/src/bin/README.md b/src/bin/README.md index fa866e71..30743d9f 100644 --- a/src/bin/README.md +++ b/src/bin/README.md @@ -1,3 +1,18 @@ ``` cargo run --bin evm-verifier-with-aggregation --release -- --nocapture ``` + +Workflow: + +1. User supplies a list of `Circuit` instances and a list of public inputs for these circuits. + +2. Program generates vkey and pkey for each circuit. + + - [Todo] currently there is no way to write vkey or pkey: https://github.com/zcash/halo2/issues/443 + +3. Program generates proofs based on given instances, retrieving cached proof when it exists and cached instances match. Caches proofs and instances. + + - Verifies proofs just for safety. + - The data of params, vkey, proof, instances is compiled into a `Snark` object. + +4. `Snark` objects are use to construct aggregation circuit. We expose the target circuit public inputs as public inputs to aggregation circuit so that the evm circuit has access to them as private inputs (passed in calldata). diff --git a/src/bin/evm-verifier-with-aggregation.rs b/src/bin/evm-verifier-with-aggregation.rs index bb8c7dbe..90f72953 100644 --- a/src/bin/evm-verifier-with-aggregation.rs +++ b/src/bin/evm-verifier-with-aggregation.rs @@ -23,7 +23,7 @@ use plonk_verifier::{ loader::evm::{encode_calldata, EvmLoader}, pcs::kzg::{Gwc19, Kzg, LimbsEncoding}, system::halo2::{ - aggregation::{self, create_snark_shplonk, gen_pk, gen_srs}, + aggregation::{self, create_snark_shplonk, gen_pk, gen_srs, TargetCircuit}, compile, transcript::evm::EvmTranscript, Config, BITS, LIMBS, @@ -143,6 +143,7 @@ impl aggregation::TargetCircuit for EthBlockHeaderCircuit { const PUBLIC_INPUT_SIZE: usize = 0; //(Self::TARGET_CIRCUIT_K * 2) as usize; const N_PROOFS: usize = 1; const NAME: &'static str = "eth"; + const READABLE_VKEY: bool = true; type Circuit = EthBlockHeaderTestCircuit; fn default_circuit() -> Self::Circuit { @@ -159,24 +160,35 @@ impl aggregation::TargetCircuit for EthBlockHeaderCircuit { } } +fn default_circuits() -> Vec { + (0..T::N_PROOFS).map(|_| T::default_circuit()).collect_vec() +} +fn default_instances() -> Vec>> { + (0..T::N_PROOFS).map(|_| T::instances()).collect_vec() +} + fn main() { - let (params_app, snark) = create_snark_shplonk::(None); + let (params_app, snark) = create_snark_shplonk::( + default_circuits::(), + default_instances::(), + None, + ); let snarks = vec![snark]; - let agg_circuit = aggregation::AggregationCircuit::new(¶ms_app, snarks); + let agg_circuit = aggregation::AggregationCircuit::new(¶ms_app, snarks, true); println!("finished creating agg_circuit"); let k = load_aggregation_circuit_degree(); let params = gen_srs(k); let pk_time = start_timer!(|| "agg_circuit vk & pk time"); - let pk = gen_pk(¶ms, &agg_circuit); + let pk = gen_pk(¶ms, &agg_circuit, "bin_agg_circuit"); end_timer!(pk_time); let deploy_time = start_timer!(|| "generate aggregation evm verifier code"); let deployment_code = gen_aggregation_evm_verifier( ¶ms, pk.get_vk(), - aggregation::AggregationCircuit::num_instance(), + agg_circuit.num_instance(), aggregation::AggregationCircuit::accumulator_indices(), ); end_timer!(deploy_time); diff --git a/src/configs/verify_circuit.config b/src/configs/verify_circuit.config index 898bde16..37e349a8 100644 --- a/src/configs/verify_circuit.config +++ b/src/configs/verify_circuit.config @@ -1 +1 @@ -{"strategy":"Simple","degree":19,"num_advice":296,"num_lookup_advice":33,"num_fixed":1,"lookup_bits":18,"limb_bits":88,"num_limbs":3} \ No newline at end of file +{"strategy":"Simple","degree":19,"num_advice":6,"num_lookup_advice":1,"num_fixed":1,"lookup_bits":18,"limb_bits":88,"num_limbs":3} \ No newline at end of file diff --git a/src/configs/verify_circuit_for_evm.config b/src/configs/verify_circuit_for_evm.config deleted file mode 100644 index 59fa5f5a..00000000 --- a/src/configs/verify_circuit_for_evm.config +++ /dev/null @@ -1 +0,0 @@ -{"strategy":"Simple","degree":24,"num_advice":1,"num_lookup_advice":0,"num_fixed":1,"lookup_bits":22,"limb_bits":88,"num_limbs":3} \ No newline at end of file diff --git a/src/system/halo2/aggregation.rs b/src/system/halo2/aggregation.rs index 2e9b75de..760e5ed6 100644 --- a/src/system/halo2/aggregation.rs +++ b/src/system/halo2/aggregation.rs @@ -20,30 +20,36 @@ use crate::{ Protocol, }; use ark_std::{end_timer, start_timer}; -use halo2_curves::bn256::{Bn256, Fq, Fr, G1Affine}; +use halo2_curves::{ + bn256::{Bn256, Fq, Fr, G1Affine}, + group::ff::PrimeField, +}; use halo2_ecc::{ fields::fp::FpConfig, gates::{Context, ContextParams}, }; use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{self, create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ProvingKey}, + circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}, + plonk::{ + self, create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ProvingKey, VerifyingKey, + }, poly::{ commitment::ParamsProver, kzg::{ commitment::{KZGCommitmentScheme, ParamsKZG}, multiopen::{ProverSHPLONK, VerifierSHPLONK}, - strategy::{AccumulatorStrategy, SingleStrategy}, + strategy::AccumulatorStrategy, }, VerificationStrategy, }, transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, }; use itertools::Itertools; -use rand::rngs::OsRng; use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; use std::{ - io::{Cursor, Read, Write}, + fs::{self, File}, + io::{BufReader, Cursor, Read, Write}, + path::Path, rc::Rc, }; @@ -53,7 +59,7 @@ const R_F: usize = 8; const R_P: usize = 60; type Halo2Loader<'a, 'b> = loader::halo2::Halo2Loader<'a, 'b, G1Affine>; -type PoseidonTranscript = +pub type PoseidonTranscript = system::halo2::transcript::halo2::PoseidonTranscript; type Pcs = Kzg; @@ -120,7 +126,8 @@ pub fn aggregate<'a, 'b>( snarks: &[SnarkWitness], as_vk: &AsVk, as_proof: Value<&'_ [u8]>, -) -> KzgAccumulator>> { + expose_instances: bool, +) -> Vec> { let assign_instances = |instances: &[Vec>]| { instances .iter() @@ -130,10 +137,18 @@ pub fn aggregate<'a, 'b>( .collect_vec() }; + let mut instances_to_expose = vec![]; let mut accumulators = snarks .iter() .flat_map(|snark| { let instances = assign_instances(&snark.instances); + if expose_instances { + instances_to_expose.extend( + instances + .iter() + .flat_map(|instance| instance.iter().map(|scalar| scalar.assigned())), + ); + } let mut transcript = PoseidonTranscript::, _, _>::new(loader, snark.proof()); let proof = @@ -142,7 +157,7 @@ pub fn aggregate<'a, 'b>( }) .collect_vec(); - let acccumulator = if accumulators.len() > 1 { + let KzgAccumulator { lhs, rhs } = if accumulators.len() > 1 { let mut transcript = PoseidonTranscript::, _, _>::new(loader, as_proof); let proof = As::read_proof(as_vk, &accumulators, &mut transcript).unwrap(); As::verify(as_vk, &accumulators, &proof).unwrap() @@ -150,7 +165,19 @@ pub fn aggregate<'a, 'b>( accumulators.pop().unwrap() }; - acccumulator + let lhs = lhs.assigned(); + let rhs = rhs.assigned(); + + lhs.x + .truncation + .limbs + .iter() + .chain(lhs.y.truncation.limbs.iter()) + .chain(rhs.x.truncation.limbs.iter()) + .chain(rhs.y.truncation.limbs.iter()) + .chain(instances_to_expose.iter()) + .cloned() + .collect_vec() } #[derive(Clone)] @@ -160,10 +187,15 @@ pub struct AggregationCircuit { instances: Vec, as_vk: AsVk, as_proof: Value>, + expose_target_instances: bool, } impl AggregationCircuit { - pub fn new(params: &ParamsKZG, snarks: impl IntoIterator) -> Self { + pub fn new( + params: &ParamsKZG, + snarks: impl IntoIterator, + expose_target_instances: bool, + ) -> Self { let svk = params.get_g()[0].into(); let snarks = snarks.into_iter().collect_vec(); @@ -195,7 +227,11 @@ impl AggregationCircuit { }; let KzgAccumulator { lhs, rhs } = accumulator; - let instances = [lhs.x, lhs.y, rhs.x, rhs.y].map(fe_to_limbs::<_, _, LIMBS, BITS>).concat(); + let mut instances = + [lhs.x, lhs.y, rhs.x, rhs.y].map(fe_to_limbs::<_, _, LIMBS, BITS>).concat(); + if expose_target_instances { + instances.extend(snarks.iter().flat_map(|snark| snark.instances.iter().flatten())); + } Self { svk, @@ -203,6 +239,7 @@ impl AggregationCircuit { instances, as_vk: as_pk.vk(), as_proof, + expose_target_instances, } } @@ -210,8 +247,9 @@ impl AggregationCircuit { (0..4 * LIMBS).map(|idx| (0, idx)).collect() } - pub fn num_instance() -> Vec { - vec![4 * LIMBS] + pub fn num_instance(&self) -> Vec { + dbg!(self.instances.len()); + vec![self.instances.len()] } pub fn instances(&self) -> Vec> { @@ -234,13 +272,13 @@ impl Circuit for AggregationCircuit { instances: Vec::new(), as_vk: self.as_vk, as_proof: Value::unknown(), + expose_target_instances: self.expose_target_instances, } } fn configure(meta: &mut plonk::ConstraintSystem) -> Self::Config { let path = "./src/configs/verify_circuit.config"; - let params_str = - std::fs::read_to_string(path).expect(format!("{} should exist", path).as_str()); + let params_str = fs::read_to_string(path).expect(format!("{} should exist", path).as_str()); let params: Halo2VerifierCircuitConfigParams = serde_json::from_str(params_str.as_str()).unwrap(); @@ -278,7 +316,7 @@ impl Circuit for AggregationCircuit { // Need to trick layouter to skip first pass in get shape mode let using_simple_floor_planner = true; let mut first_pass = true; - let mut final_pair = None; + let mut assigned_instances = None; layouter.assign_region( || "", |region| { @@ -296,30 +334,26 @@ impl Circuit for AggregationCircuit { ); let loader = Halo2Loader::new(&config.base_field_config, ctx); - let KzgAccumulator { lhs, rhs } = - aggregate(&self.svk, &loader, &self.snarks, &self.as_vk, self.as_proof()); + let instances = aggregate( + &self.svk, + &loader, + &self.snarks, + &self.as_vk, + self.as_proof(), + self.expose_target_instances, + ); // REQUIRED STEP loader.finalize(); - final_pair = Some((lhs.assigned(), rhs.assigned())); + assigned_instances = Some(instances); Ok(()) }, )?; - let (lhs, rhs) = final_pair.unwrap(); Ok({ // TODO: use less instances by following Scroll's strategy of keeping only last bit of y coordinate let mut layouter = layouter.namespace(|| "expose"); - for (i, assigned_instance) in lhs - .x - .truncation - .limbs - .iter() - .chain(lhs.y.truncation.limbs.iter()) - .chain(rhs.x.truncation.limbs.iter()) - .chain(rhs.y.truncation.limbs.iter()) - .enumerate() - { + for (i, assigned_instance) in assigned_instances.unwrap().iter().enumerate() { layouter.constrain_instance( assigned_instance.cell().clone(), config.instance, @@ -336,14 +370,110 @@ pub fn gen_srs(k: u32) -> ParamsKZG { }) } -pub fn gen_pk>(params: &ParamsKZG, circuit: &C) -> ProvingKey { - let vk_time = start_timer!(|| "vkey"); - let vk = keygen_vk(params, circuit).unwrap(); - end_timer!(vk_time); - let pk_time = start_timer!(|| "pkey"); - let pk = keygen_pk(params, vk, circuit).unwrap(); - end_timer!(pk_time); - pk +pub fn gen_vk>( + params: &ParamsKZG, + circuit: &ConcreteCircuit, + name: &str, +) -> VerifyingKey { + let path = format!("./data/{}.vkey", name); + match File::open(path.as_str()) { + Ok(f) => { + println!("Reading vkey from {}", path); + let mut bufreader = BufReader::new(f); + let vk = VerifyingKey::read::<_, ConcreteCircuit>(&mut bufreader, params) + .expect("Reading vkey should not fail"); + vk + } + Err(_) => { + let vk_time = start_timer!(|| "vkey"); + let vk = keygen_vk(params, circuit).unwrap(); + end_timer!(vk_time); + let mut f = File::create(path.as_str()).unwrap(); + vk.write(&mut f).unwrap(); + vk + } + } +} + +pub fn gen_pk>( + params: &ParamsKZG, + circuit: &ConcreteCircuit, + name: &str, +) -> ProvingKey { + let path = format!("./data/{}.pkey", name); + match File::open(path.as_str()) { + Ok(f) => { + println!("Reading pkey from {}", path); + let mut bufreader = BufReader::new(f); + let pk = ProvingKey::read::<_, ConcreteCircuit>(&mut bufreader, params) + .expect("Reading pkey should not fail"); + pk + } + Err(_) => { + let vk = gen_vk::(params, circuit, name); + let pk_time = start_timer!(|| "pkey"); + let pk = keygen_pk(params, vk, circuit).unwrap(); + end_timer!(pk_time); + let mut f = File::create(path.as_str()).unwrap(); + pk.write(&mut f).unwrap(); + pk + } + } +} + +pub fn read_bytes(path: &str) -> Vec { + let mut buf = vec![]; + let mut f = File::open(path).unwrap(); + f.read_to_end(&mut buf).unwrap(); + buf +} + +pub fn write_bytes(path: &str, buf: &Vec) { + let mut f = File::create(path).unwrap(); + f.write(buf).unwrap(); +} + +/// reads the instances for T::N_PROOFS circuits from file +pub fn read_instances(path: &str) -> Option>>> { + let f = File::open(path); + if let Err(_) = f { + return None; + } + let f = f.unwrap(); + let reader = BufReader::new(f); + let instances_bytes: Vec>> = serde_json::from_reader(reader).unwrap(); + let mut ret = vec![]; + for circuit_instances in instances_bytes.into_iter() { + let mut ret1 = vec![]; + for instance_column in circuit_instances.into_iter() { + let mut ret2 = vec![]; + assert_eq!(instance_column.len() % 32, 0); + for id in (0..instance_column.len()).step_by(32) { + let mut repr = [0u8; 32]; + repr.clone_from_slice(&instance_column[id..id + 32]); + ret2.push(Fr::from_repr(repr).unwrap()); + } + ret1.push(ret2); + } + ret.push(ret1); + } + Some(ret) +} + +pub fn write_instances(instances: &Vec>>, path: &str) { + let mut bytes = vec![]; + for circuit_instances in instances.iter() { + bytes.push( + circuit_instances + .iter() + .map(|instance_column| { + instance_column.iter().flat_map(|x| x.to_repr()).collect_vec() + }) + .collect_vec(), + ); + } + let f = File::create(path).unwrap(); + serde_json::to_writer(f, &bytes).unwrap(); } pub trait TargetCircuit { @@ -359,10 +489,11 @@ pub trait TargetCircuit { } pub fn create_snark_shplonk( + circuits: Vec, + instances: Vec>>, // instances[i][j][..] is the i-th circuit's j-th instance column accumulator_indices: Option>, ) -> (ParamsKZG, Snark) { println!("CREATING SNARK FOR: {}", T::NAME); - let circuits = (0..T::N_PROOFS).map(|_| T::default_circuit()).collect_vec(); let config = if let Some(accumulator_indices) = accumulator_indices { Config::kzg() .set_zk(true) @@ -373,14 +504,13 @@ pub fn create_snark_shplonk( }; let params = gen_srs(T::TARGET_CIRCUIT_K); - let pk = gen_pk(¶ms, &circuits[0]); - let num_instance = T::instances().iter().map(|instances| instances.len()).collect(); + let pk = gen_pk(¶ms, &T::default_circuit(), T::NAME); + // num_instance[i] is number of instance columns in i-th circuit + let num_instance = instances.iter().map(|instances| instances.len()).collect(); let protocol = compile(¶ms, pk.get_vk(), config.with_num_instance(num_instance)); - let proof_time = start_timer!(|| "create proof"); // usual shenanigans to turn nested Vec into nested slice - let instances0: Vec>> = circuits.iter().map(|_| T::instances()).collect_vec(); - let instances1: Vec> = instances0 + let instances1: Vec> = instances .iter() .map(|instances| instances.iter().map(Vec::as_slice).collect_vec()) .collect_vec(); @@ -388,33 +518,34 @@ pub fn create_snark_shplonk( // TODO: need to cache the instances as well! let proof = { - let path = format!("./data/proof_{}.data", T::NAME); - match std::fs::File::open(path.as_str()) { - Ok(mut file) => { - let mut buf = vec![]; - file.read_to_end(&mut buf).unwrap(); - buf - } - Err(_) => { - let mut transcript = - PoseidonTranscript::, _>::init(Vec::new()); - create_proof::, ProverSHPLONK<_>, ChallengeScalar<_>, _, _, _>( - ¶ms, - &pk, - &circuits, - instances2.as_slice(), - &mut ChaCha20Rng::from_seed(Default::default()), - &mut transcript, - ) - .unwrap(); - let proof = transcript.finalize(); - let mut file = std::fs::File::create(path.as_str()).unwrap(); - file.write_all(&proof).unwrap(); - proof - } + let path = format!("./data/proof_{}.dat", T::NAME); + let instance_path = format!("./data/instances_{}.dat", T::NAME); + if let Some(cached_instances) = read_instances::(instance_path.as_str()) && Path::new(path.as_str()).exists() && cached_instances == instances { + let mut file = File::open(path.as_str()).unwrap(); + let mut buf = vec![]; + file.read_to_end(&mut buf).unwrap(); + buf + } else { + let proof_time = start_timer!(|| "create proof"); + let mut transcript = + PoseidonTranscript::, _>::init(Vec::new()); + create_proof::, ProverSHPLONK<_>, ChallengeScalar<_>, _, _, _>( + ¶ms, + &pk, + &circuits, + instances2.as_slice(), + &mut ChaCha20Rng::from_seed(Default::default()), + &mut transcript, + ) + .unwrap(); + let proof = transcript.finalize(); + let mut file = File::create(path.as_str()).unwrap(); + file.write_all(&proof).unwrap(); + write_instances(&instances, instance_path.as_str()); + end_timer!(proof_time); + proof } }; - end_timer!(proof_time); let verify_time = start_timer!(|| "verify proof"); { @@ -439,5 +570,5 @@ pub fn create_snark_shplonk( } end_timer!(verify_time); - (params, Snark::new(protocol.clone(), instances0.into_iter().flatten().collect_vec(), proof)) + (params, Snark::new(protocol.clone(), instances.into_iter().flatten().collect_vec(), proof)) }