diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 771d231..61435e0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -27,8 +27,7 @@ jobs: run: (hash svm 2>/dev/null || cargo install --version 0.3.0 svm-rs) && svm install 0.8.21 && solc --version - name: Run test - run: cargo test --all --all-features -- --nocapture - + run: cargo test --workspace --all-features --all-targets -- --nocapture lint: name: Lint @@ -51,4 +50,4 @@ jobs: run: cargo fmt --all -- --check - name: Run clippy - run: cargo clippy --all --all-features --all-targets -- -D warnings + run: cargo clippy --workspace --all-features --all-targets -- -D warnings diff --git a/.gitignore b/.gitignore index c32ca19..7e85a55 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ Cargo.lock # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -.vscode \ No newline at end of file +.vscode +generated/ \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index e56c4f8..0cd9fdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,9 @@ ruint = "1.10.1" sha3 = "0.10" itertools = "0.11.0" +# evm +revm = { version = "3.3.0", optional = true } + [dev-dependencies] rand = "0.8.5" revm = "3.3.0" @@ -18,3 +21,11 @@ halo2_maingate = { git = "https://github.com/privacy-scaling-explorations/halo2w [patch."https://github.com/privacy-scaling-explorations/halo2.git"] halo2_proofs = { git = "https://github.com/han0110/halo2", branch = "tmp/expose-transcript-repr-for-0420" } + +[features] +default = [] +evm = ["dep:revm"] + +[[example]] +name = "separately" +required-features = ["evm"] diff --git a/README.md b/README.md index cfc8c99..877bddc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,44 @@ # Halo2 Solidity Verifier -A set of tooling related to halo2 circuits verification inside solidity contracts. +Solidity verifier generator for [`halo2`](http://github.com/privacy-scaling-explorations/halo2) proof with KZG polynomial commitment scheme on BN254 + +## Usage + +### Generate verifier and verifying key separately as 2 solidity contracts + +```rust +let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, num_instances); +let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); +``` + +Check [`examples/separately.rs`](./examples/separately.rs) for more details. + +### Generate verifier and verifying key in a single solidity contract + +```rust +let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, num_instances); +let verifier_solidity = generator.render().unwrap(); +``` + +### Encode proof into calldata to invoke `verifyProof` + +```rust +let calldata = encode_calldata(vk_address, &proof, &instances); +``` + +Note that function selector is already included. + +## Limitation + +- It only allows circuit with **exact 1 instance column** and **no rotated query to this instance column**. +- Option `--via-ir` seems necessary when compiling the generated contract, otherwise it'd cause stack too deep error. However, `--via-ir` is not allowed to be used with `--standard-json`, not sure how to work around this yet. +- Even the `configure` is same, the [selector compression](https://github.com/privacy-scaling-explorations/halo2/blob/7a2165617195d8baa422ca7b2b364cef02380390/halo2_proofs/src/plonk/circuit/compress_selectors.rs#L51) might lead to different configuration when selector assignments are different. To avoid this we might need to update halo2 to support disabling selector compression. +- Now it only supports BDFG21 batch open scheme (aka SHPLONK), GWC19 is not yet implemented. + +## Compatibility + +The [`Keccak256Transcript`](./src/transcript.rs#L19) behaves exactly same as the `EvmTranscript` in `snark-verifier`. + +## Acknowledgement + +The template is heavily inspired by Aztec's [`BaseUltraVerifier.sol`](https://github.com/AztecProtocol/barretenberg/blob/4c456a2b196282160fd69bead6a1cea85289af37/sol/src/ultra/BaseUltraVerifier.sol). diff --git a/examples/separately.rs b/examples/separately.rs new file mode 100644 index 0000000..5a4f9a5 --- /dev/null +++ b/examples/separately.rs @@ -0,0 +1,242 @@ +use application::StandardPlonk; +use prelude::*; + +use halo2_solidity_verifier::{ + compile_solidity, encode_calldata, BatchOpenScheme::Bdfg21, Evm, Keccak256Transcript, + SolidityGenerator, +}; + +const K_RANGE: Range = 10..17; + +fn main() { + let mut rng = seeded_std_rng(); + + let params = setup(K_RANGE, &mut rng); + + let vk = keygen_vk(¶ms[&K_RANGE.start], &StandardPlonk::default()).unwrap(); + let generator = SolidityGenerator::new(¶ms[&K_RANGE.start], &vk, Bdfg21, 0); + let (verifier_solidity, _) = generator.render_separately().unwrap(); + save_solidity("Halo2Verifier.sol", &verifier_solidity); + + let verifier_creation_code = compile_solidity(&verifier_solidity); + let verifier_creation_code_size = verifier_creation_code.len(); + println!("Verifier creation code size: {verifier_creation_code_size}"); + + let mut evm = Evm::default(); + let verifier_address = evm.create(verifier_creation_code); + + let deployed_verifier_solidity = verifier_solidity; + + for k in K_RANGE { + let num_instances = k as usize; + let circuit = StandardPlonk::rand(num_instances, &mut rng); + + let vk = keygen_vk(¶ms[&k], &circuit).unwrap(); + let pk = keygen_pk(¶ms[&k], vk, &circuit).unwrap(); + let generator = SolidityGenerator::new(¶ms[&k], pk.get_vk(), Bdfg21, num_instances); + let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); + save_solidity(format!("Halo2VerifyingKey-{k}.sol"), &vk_solidity); + + assert_eq!(deployed_verifier_solidity, verifier_solidity); + + let vk_creation_code = compile_solidity(&vk_solidity); + let vk_address = evm.create(vk_creation_code); + + let calldata = { + let instances = circuit.instances(); + let proof = create_proof_checked(¶ms[&k], &pk, circuit, &instances, &mut rng); + encode_calldata(vk_address.0.into(), &proof, &instances) + }; + let (gas_cost, output) = evm.call(verifier_address, calldata); + assert_eq!(output, [vec![0; 31], vec![1]].concat()); + println!("Gas cost of verifying standard Plonk with 2^{k} rows: {gas_cost}"); + } +} + +fn save_solidity(name: impl AsRef, solidity: &str) { + const DIR_GENERATED: &str = "./generated"; + + create_dir_all(DIR_GENERATED).unwrap(); + File::create(format!("{DIR_GENERATED}/{}", name.as_ref())) + .unwrap() + .write_all(solidity.as_bytes()) + .unwrap(); +} + +fn setup(k_range: Range, mut rng: impl RngCore) -> HashMap> { + k_range + .clone() + .zip(k_range.map(|k| ParamsKZG::::setup(k, &mut rng))) + .collect() +} + +fn create_proof_checked( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: impl Circuit, + instances: &[Fr], + mut rng: impl RngCore, +) -> Vec { + use halo2_proofs::{ + poly::kzg::{ + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, + }, + transcript::TranscriptWriterBuffer, + }; + + let proof = { + let mut transcript = Keccak256Transcript::new(Vec::new()); + create_proof::<_, ProverSHPLONK<_>, _, _, _, _>( + params, + pk, + &[circuit], + &[&[instances]], + &mut rng, + &mut transcript, + ) + .unwrap(); + transcript.finalize() + }; + + let result = { + let mut transcript = Keccak256Transcript::new(proof.as_slice()); + verify_proof::<_, VerifierSHPLONK<_>, _, _, SingleStrategy<_>>( + params, + pk.get_vk(), + SingleStrategy::new(params), + &[&[instances]], + &mut transcript, + ) + }; + assert!(result.is_ok()); + + proof +} + +mod application { + use crate::prelude::*; + + #[derive(Clone)] + pub struct StandardPlonkConfig { + selectors: [Column; 5], + wires: [Column; 3], + } + + impl StandardPlonkConfig { + fn configure(meta: &mut ConstraintSystem) -> Self { + let [w_l, w_r, w_o] = [(); 3].map(|_| meta.advice_column()); + let [q_l, q_r, q_o, q_m, q_c] = [(); 5].map(|_| meta.fixed_column()); + let pi = meta.instance_column(); + [w_l, w_r, w_o].map(|column| meta.enable_equality(column)); + meta.create_gate( + "q_l·w_l + q_r·w_r + q_o·w_o + q_m·w_l·w_r + q_c + pi = 0", + |meta| { + let [w_l, w_r, w_o] = + [w_l, w_r, w_o].map(|column| meta.query_advice(column, Rotation::cur())); + let [q_l, q_r, q_o, q_m, q_c] = [q_l, q_r, q_o, q_m, q_c] + .map(|column| meta.query_fixed(column, Rotation::cur())); + let pi = meta.query_instance(pi, Rotation::cur()); + Some( + q_l * w_l.clone() + + q_r * w_r.clone() + + q_o * w_o + + q_m * w_l * w_r + + q_c + + pi, + ) + }, + ); + StandardPlonkConfig { + selectors: [q_l, q_r, q_o, q_m, q_c], + wires: [w_l, w_r, w_o], + } + } + } + + #[derive(Clone, Debug, Default)] + pub struct StandardPlonk(Vec); + + impl StandardPlonk { + pub fn rand(num_instances: usize, mut rng: R) -> Self { + Self((0..num_instances).map(|_| F::random(&mut rng)).collect()) + } + + pub fn instances(&self) -> Vec { + self.0.clone() + } + } + + impl Circuit for StandardPlonk { + type Config = StandardPlonkConfig; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); + StandardPlonkConfig::configure(meta) + } + + fn synthesize( + &self, + config: Self::Config, + mut layouter: impl Layouter, + ) -> Result<(), Error> { + let [q_l, q_r, q_o, q_m, q_c] = config.selectors; + let [w_l, w_r, w_o] = config.wires; + layouter.assign_region( + || "", + |mut region| { + for (offset, instance) in self.0.iter().enumerate() { + region.assign_advice(|| "", w_l, offset, || Value::known(*instance))?; + region.assign_fixed(|| "", q_l, offset, || Value::known(-F::ONE))?; + } + let offset = self.0.len(); + let a = region.assign_advice(|| "", w_l, offset, || Value::known(F::ONE))?; + a.copy_advice(|| "", &mut region, w_r, offset)?; + a.copy_advice(|| "", &mut region, w_o, offset)?; + let offset = offset + 1; + region.assign_advice(|| "", w_l, offset, || Value::known(-F::from(5)))?; + for (column, idx) in [q_l, q_r, q_o, q_m, q_c].iter().zip(1..) { + region.assign_fixed( + || "", + *column, + offset, + || Value::known(F::from(idx)), + )?; + } + Ok(()) + }, + ) + } + } +} + +mod prelude { + pub use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner, Value}, + halo2curves::{ + bn256::{Bn256, Fr, G1Affine}, + ff::PrimeField, + }, + plonk::*, + poly::{commitment::Params, kzg::commitment::ParamsKZG, Rotation}, + }; + pub use rand::{ + rngs::{OsRng, StdRng}, + RngCore, SeedableRng, + }; + pub use std::{ + collections::HashMap, + fs::{create_dir_all, File}, + io::Write, + ops::Range, + }; + + pub fn seeded_std_rng() -> impl RngCore { + StdRng::seed_from_u64(OsRng.next_u64()) + } +} diff --git a/src/codegen.rs b/src/codegen.rs index e79acfb..67213a3 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -1,18 +1,14 @@ -use crate::{ - codegen::{ - evaluator::Evaluator, - pcs::{ - bdfg21_computations, queries, rotation_sets, - BatchOpenScheme::{Bdfg21, Gwc19}, - }, - template::{Halo2Verifier, Halo2VerifyingKey}, - util::{ConstraintSystemMeta, Data, Ptr}, +use crate::codegen::{ + evaluator::Evaluator, + pcs::{ + bdfg21_computations, queries, rotation_sets, + BatchOpenScheme::{Bdfg21, Gwc19}, }, - fe_to_u256, g1_to_u256s, g2_to_u256s, + template::{Halo2Verifier, Halo2VerifyingKey}, + util::{fe_to_u256, g1_to_u256s, g2_to_u256s, ConstraintSystemMeta, Data, Ptr}, }; use halo2_proofs::{ - arithmetic::Field, - halo2curves::bn256, + halo2curves::{bn256, ff::Field}, plonk::VerifyingKey, poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG, Rotation}, }; @@ -23,34 +19,82 @@ use std::fmt::{self, Debug}; mod evaluator; mod pcs; mod template; -mod util; +pub(crate) mod util; pub use pcs::BatchOpenScheme; +/// Solidity verifier generator for [`halo2`] proof with KZG polynomial commitment scheme on BN254. #[derive(Debug)] pub struct SolidityGenerator<'a> { params: &'a ParamsKZG, vk: &'a VerifyingKey, - meta: ConstraintSystemMeta, + scheme: BatchOpenScheme, num_instances: usize, acc_encoding: Option, - scheme: BatchOpenScheme, + meta: ConstraintSystemMeta, } +/// KZG accumulator encoding information. +/// Limbs of each field element are assumed to be least significant limb first. +/// +/// Given instances and `AccumulatorEncoding`, the accumulator will be interpreted as below: +/// ```rust +/// use halo2_proofs::halo2curves::{bn256, ff::{Field, PrimeField}, CurveAffine}; +/// +/// fn accumulator_from_limbs( +/// instances: &[bn256::Fr], +/// offset: usize, +/// num_limbs: usize, +/// num_limb_bits: usize, +/// ) -> (bn256::G1Affine, bn256::G1Affine) { +/// let limbs = |offset| &instances[offset..offset + num_limbs]; +/// let acc_lhs_x = fe_from_limbs(limbs(offset), num_limb_bits); +/// let acc_lhs_y = fe_from_limbs(limbs(offset + num_limbs), num_limb_bits); +/// let acc_rhs_x = fe_from_limbs(limbs(offset + 2 * num_limbs), num_limb_bits); +/// let acc_rhs_y = fe_from_limbs(limbs(offset + 3 * num_limbs), num_limb_bits); +/// let acc_lhs = bn256::G1Affine::from_xy(acc_lhs_x, acc_lhs_y).unwrap(); +/// let acc_rhs = bn256::G1Affine::from_xy(acc_rhs_x, acc_rhs_y).unwrap(); +/// (acc_lhs, acc_rhs) +/// } +/// +/// fn fe_from_limbs(limbs: &[bn256::Fr], num_limb_bits: usize) -> bn256::Fq { +/// limbs.iter().rev().fold(bn256::Fq::ZERO, |acc, limb| { +/// acc * bn256::Fq::from(2).pow_vartime([num_limb_bits as u64]) +/// + bn256::Fq::from_repr_vartime(limb.to_repr()).unwrap() +/// }) +/// } +/// ``` +/// +/// In the end of `verifyProof`, the accumulator will be used to do batched pairing with the +/// pairing input of incoming proof. #[derive(Clone, Copy, Debug)] pub struct AccumulatorEncoding { + /// Offset of accumulator limbs in instances. pub offset: usize, + /// Number of limbs per base field element. pub num_limbs: usize, + /// Number of bits per limb. pub num_limb_bits: usize, } +impl AccumulatorEncoding { + /// Return a new `AccumulatorEncoding`. + pub fn new(offset: usize, num_limbs: usize, num_limb_bits: usize) -> Self { + Self { + offset, + num_limbs, + num_limb_bits, + } + } +} + impl<'a> SolidityGenerator<'a> { + /// Return a new `SolidityGenerator`. pub fn new( params: &'a ParamsKZG, vk: &'a VerifyingKey, - num_instances: usize, - acc_encoding: Option, scheme: BatchOpenScheme, + num_instances: usize, ) -> Self { assert_ne!(vk.cs().num_advice_columns(), 0); assert_eq!( @@ -74,26 +118,35 @@ impl<'a> SolidityGenerator<'a> { Self { params, vk, - meta: ConstraintSystemMeta::new(vk.cs()), - num_instances, - acc_encoding, scheme, + num_instances, + acc_encoding: None, + meta: ConstraintSystemMeta::new(vk.cs()), } } + + /// Set `AccumulatorEncoding`. + pub fn set_acc_encoding(mut self, acc_encoding: Option) -> Self { + self.acc_encoding = acc_encoding; + self + } } impl<'a> SolidityGenerator<'a> { + /// Render `Halo2Verifier.sol` with verifying key embedded into writer. pub fn render_into(&self, verifier_writer: &mut impl fmt::Write) -> Result<(), fmt::Error> { self.generate_verifier(false).render(verifier_writer) } + /// Render `Halo2Verifier.sol` with verifying key embedded and return it as `String`. pub fn render(&self) -> Result { let mut verifier_output = String::new(); self.render_into(&mut verifier_output)?; Ok(verifier_output) } - pub fn render_into_separately( + /// Render `Halo2Verifier.sol` and `Halo2VerifyingKey.sol` into writers. + pub fn render_separately_into( &self, verifier_writer: &mut impl fmt::Write, vk_writer: &mut impl fmt::Write, @@ -103,10 +156,11 @@ impl<'a> SolidityGenerator<'a> { Ok(()) } + /// Render `Halo2Verifier.sol` and `Halo2VerifyingKey.sol` and return them as `String`. pub fn render_separately(&self) -> Result<(String, String), fmt::Error> { let mut verifier_output = String::new(); let mut vk_output = String::new(); - self.render_into_separately(&mut verifier_output, &mut vk_output)?; + self.render_separately_into(&mut verifier_output, &mut vk_output)?; Ok((verifier_output, vk_output)) } @@ -184,8 +238,8 @@ impl<'a> SolidityGenerator<'a> { let vk = self.generate_vk(); let vk_len = vk.len(); - let vk_mptr = Ptr::memory(self.estimate_working_memory_size(&vk, proof_cptr)); - let data = Data::new(&self.meta, self.scheme, &vk, vk_mptr, proof_cptr); + let vk_mptr = Ptr::memory(self.estimate_static_working_memory_size(&vk, proof_cptr)); + let data = Data::new(&self.meta, &vk, vk_mptr, proof_cptr); let evaluator = Evaluator::new(self.vk.cs(), &self.meta, &data); let quotient_eval_numer_computations = chain![ @@ -227,32 +281,38 @@ impl<'a> SolidityGenerator<'a> { proof_len: self.meta.proof_len(self.scheme), challenge_mptr: data.challenge_mptr, theta_mptr: data.theta_mptr, - instance_eval_mptr: data.instance_eval_mptr, quotient_eval_numer_computations, pcs_computations, } } - fn estimate_working_memory_size(&self, vk: &Halo2VerifyingKey, proof_cptr: Ptr) -> usize { - match self.scheme { + fn estimate_static_working_memory_size( + &self, + vk: &Halo2VerifyingKey, + proof_cptr: Ptr, + ) -> usize { + let pcs_computation = match self.scheme { Bdfg21 => { let mock_vk_mptr = Ptr::memory(0x100000); - let mock = Data::new(&self.meta, self.scheme, vk, mock_vk_mptr, proof_cptr); + let mock = Data::new(&self.meta, vk, mock_vk_mptr, proof_cptr); let (superset, sets) = rotation_sets(&queries(&self.meta, &mock)); let num_coeffs = sets.iter().map(|set| set.rots().len()).sum::(); - itertools::max(chain![ - self.meta - .num_advices() - .into_iter() - .map(|n| (n * 0x40) + 0x20), - [ - (self.meta.num_evals + 1) * 0x20, - (2 * (1 + num_coeffs) + 6 + 2 * superset.len() + 2 * sets.len()) * 0x20 - ], - ]) - .unwrap() + 2 * (1 + num_coeffs) + 6 + 2 * superset.len() + 1 + 3 * sets.len() } Gwc19 => unimplemented!(), - } + }; + + itertools::max(chain![ + // Hashing advice commitments + chain![self.meta.num_advices().into_iter()].map(|n| n * 2 + 1), + // Hashing evaluations + [self.meta.num_evals + 1], + // PCS computation + [pcs_computation], + // Pairing + [12], + ]) + .unwrap() + * 0x20 } } diff --git a/src/codegen/evaluator.rs b/src/codegen/evaluator.rs index b68dd8b..f558987 100644 --- a/src/codegen/evaluator.rs +++ b/src/codegen/evaluator.rs @@ -1,9 +1,6 @@ #![allow(clippy::useless_format)] -use crate::{ - codegen::util::{code_block, ConstraintSystemMeta, Data}, - fe_to_u256, -}; +use crate::codegen::util::{code_block, fe_to_u256, ConstraintSystemMeta, Data}; use halo2_proofs::{ halo2curves::ff::PrimeField, plonk::{ diff --git a/src/codegen/pcs.rs b/src/codegen/pcs.rs index 2576fe4..ee96f85 100644 --- a/src/codegen/pcs.rs +++ b/src/codegen/pcs.rs @@ -4,9 +4,18 @@ use crate::codegen::util::{for_loop, ConstraintSystemMeta, Data, EcPoint, Locati use itertools::{chain, izip, Itertools}; use std::collections::{BTreeMap, BTreeSet}; +/// KZG batch open schemes in `halo2`. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BatchOpenScheme { + /// Batch open scheme in [Plonk] paper. + /// Corresponding to `halo2_proofs::poly::kzg::multiopen::ProverGWC` + /// + /// [Plonk]: https://eprint.iacr.org/2019/953.pdf Gwc19, + /// Batch open scheme in [BDFG21] paper. + /// Corresponding to `halo2_proofs::poly::kzg::multiopen::ProverSHPLONK` + /// + /// [BDFG21]: https://eprint.iacr.org/2020/081.pdf Bdfg21, } @@ -184,7 +193,7 @@ pub(crate) fn bdfg21_computations(meta: &ConstraintSystemMeta, data: &Data) -> V let points = izip!(&superset, Word::range(point_mptr)).collect::>(); let mu_minus_points = izip!(&superset, Word::range(mu_minus_point_mptr)).collect::>(); - let vanishing_0 = Word::from(free_mptr + 2 * superset.len()); + let vanishing_0 = Word::from(vanishing_0_mptr); let diffs = Word::range(diff_mptr).take(sets.len()).collect_vec(); let r_evals = Word::range(r_eval_mptr).take(sets.len()).collect_vec(); let sums = Word::range(sum_mptr).take(sets.len()).collect_vec(); @@ -256,10 +265,10 @@ pub(crate) fn bdfg21_computations(meta: &ConstraintSystemMeta, data: &Data) -> V ["let diff".to_string()], izip!(0.., &sets, &diffs).flat_map(|(set_idx, set, diff)| { chain![ - [format!( - "diff := {}", - mu_minus_points[set.diffs().first().unwrap()] - )], + [set.diffs() + .first() + .map(|rot| format!("diff := {}", mu_minus_points[rot])) + .unwrap_or_else(|| "diff := 1".to_string())], chain![set.diffs().iter().skip(1)] .map(|rot| { format!("diff := mulmod(diff, {}, r)", mu_minus_points[rot]) }), [format!("mstore({}, diff)", diff.ptr())], @@ -315,7 +324,7 @@ pub(crate) fn bdfg21_computations(meta: &ConstraintSystemMeta, data: &Data) -> V let normalized_coeff_computations = chain![ [ - format!("success := batch_invert(success, {first_batch_invert_end}, r)"), + format!("success := batch_invert(success, 0, {first_batch_invert_end}, r)"), format!("let diff_0_inv := {diff_0}"), format!("mstore({}, diff_0_inv)", diffs[0].ptr()), ], @@ -432,7 +441,7 @@ pub(crate) fn bdfg21_computations(meta: &ConstraintSystemMeta, data: &Data) -> V ["mstore(mptr, mload(sum_mptr))".to_string()], ), [ - format!("success := batch_invert(success, {second_batch_invert_end}, r)"), + format!("success := batch_invert(success, 0, {second_batch_invert_end}, r)"), format!( "let r_eval := mulmod(mload({}), {}, r)", second_batch_invert_end - 1, diff --git a/src/codegen/template.rs b/src/codegen/template.rs index 8c2b4bf..218ac63 100644 --- a/src/codegen/template.rs +++ b/src/codegen/template.rs @@ -31,7 +31,6 @@ pub(crate) struct Halo2Verifier { pub(crate) vk_mptr: Ptr, pub(crate) challenge_mptr: Ptr, pub(crate) theta_mptr: Ptr, - pub(crate) instance_eval_mptr: Ptr, pub(crate) proof_cptr: Ptr, pub(crate) quotient_comm_cptr: Ptr, pub(crate) num_neg_lagranges: usize, diff --git a/src/codegen/util.rs b/src/codegen/util.rs index 34f5a83..146249d 100644 --- a/src/codegen/util.rs +++ b/src/codegen/util.rs @@ -3,11 +3,13 @@ use crate::codegen::{ BatchOpenScheme::{self, Bdfg21, Gwc19}, }; use halo2_proofs::{ - halo2curves::ff::PrimeField, + halo2curves::{bn256, ff::PrimeField, CurveAffine}, plonk::{Any, Column, ConstraintSystem}, }; use itertools::{chain, izip, Itertools}; +use ruint::aliases::U256; use std::{ + borrow::Borrow, collections::HashMap, fmt::{self, Display, Formatter}, ops::{Add, Sub}, @@ -109,8 +111,8 @@ impl ConstraintSystemMeta { pub(crate) fn num_advices(&self) -> Vec { chain![ self.num_user_advices.iter().cloned(), + (self.num_lookup_permuteds != 0).then_some(self.num_lookup_permuteds), // lookup permuted [ - self.num_lookup_permuteds, // lookup permuted self.num_permutation_zs + self.num_lookup_zs + 1, // permutation and lookup grand products, random self.num_quotients, // quotients ], @@ -120,12 +122,20 @@ impl ConstraintSystemMeta { pub(crate) fn num_challenges(&self) -> Vec { let mut num_challenges = self.num_user_challenges.clone(); - *num_challenges.last_mut().unwrap() += 1; // theta - num_challenges.extend([ - 2, // beta, gamma - 1, // y - 1, // x - ]); + if self.num_lookup_permuteds == 0 { + *num_challenges.last_mut().unwrap() += 3; // theta, beta, gamma + num_challenges.extend([ + 1, // y + 1, // x + ]); + } else { + *num_challenges.last_mut().unwrap() += 1; // theta + num_challenges.extend([ + 2, // beta, gamma + 1, // y + 1, // x + ]); + } num_challenges } @@ -151,22 +161,12 @@ impl ConstraintSystemMeta { } } } - - pub(crate) fn num_batch_open_challenges(&self, scheme: BatchOpenScheme) -> usize { - match scheme { - Bdfg21 => 3, - Gwc19 => { - unimplemented!() - } - } - } } #[derive(Debug)] pub(crate) struct Data { pub(crate) challenge_mptr: Ptr, pub(crate) theta_mptr: Ptr, - pub(crate) instance_eval_mptr: Ptr, pub(crate) quotient_comm_cptr: Ptr, pub(crate) w_cptr: Ptr, @@ -196,7 +196,6 @@ pub(crate) struct Data { impl Data { pub(crate) fn new( meta: &ConstraintSystemMeta, - scheme: BatchOpenScheme, vk: &Halo2VerifyingKey, vk_mptr: Ptr, proof_cptr: Ptr, @@ -205,7 +204,6 @@ impl Data { let permutation_comm_mptr = fixed_comm_mptr + 2 * vk.fixed_comms.len(); let challenge_mptr = permutation_comm_mptr + 2 * vk.permutation_comms.len(); let theta_mptr = challenge_mptr + meta.num_user_challenges.iter().sum::(); - let instance_eval_mptr = theta_mptr + 5 + meta.num_batch_open_challenges(scheme); let advice_comm_start = proof_cptr; let lookup_permuted_comm_start = advice_comm_start + 2 * meta.advice_index.len(); @@ -289,7 +287,6 @@ impl Data { Self { challenge_mptr, theta_mptr, - instance_eval_mptr, quotient_comm_cptr: quotient_comm_start, w_cptr, @@ -563,3 +560,27 @@ pub(crate) fn for_loop( ] .collect() } + +pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 2] { + let coords = ec_point.borrow().coordinates().unwrap(); + [coords.x(), coords.y()].map(fe_to_u256::) +} + +pub(crate) fn g2_to_u256s(ec_point: impl Borrow) -> [U256; 4] { + let coords = ec_point.borrow().coordinates().unwrap(); + let x = coords.x().to_repr(); + let y = coords.y().to_repr(); + [ + U256::try_from_le_slice(&x.as_ref()[0x20..]).unwrap(), + U256::try_from_le_slice(&x.as_ref()[..0x20]).unwrap(), + U256::try_from_le_slice(&y.as_ref()[0x20..]).unwrap(), + U256::try_from_le_slice(&y.as_ref()[..0x20]).unwrap(), + ] +} + +pub(crate) fn fe_to_u256(fe: impl Borrow) -> U256 +where + F: PrimeField, +{ + U256::from_le_bytes(fe.borrow().to_repr()) +} diff --git a/src/evm.rs b/src/evm.rs new file mode 100644 index 0000000..a89d1f3 --- /dev/null +++ b/src/evm.rs @@ -0,0 +1,210 @@ +use crate::codegen::util::fe_to_u256; +use halo2_proofs::halo2curves::bn256; +use itertools::chain; +use ruint::aliases::U256; + +/// Function signature of `verifyProof(bytes,uint256[])`. +pub const FN_SIG_VERIFY_PROOF: [u8; 4] = [0x1e, 0x8e, 0x1e, 0x13]; + +/// Function signature of `verifyProof(address,bytes,uint256[])`. +pub const FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS: [u8; 4] = [0xaf, 0x83, 0xa1, 0x8d]; + +/// Encode proof into calldata to invoke `Halo2Verifier.verifyProof`. +/// +/// For `vk_address`: +/// - Pass `None` if verifying key is embedded in `Halo2Verifier` +/// - Pass `Some(vk_address)` if verifying key is separated and deployed at `vk_address` +pub fn encode_calldata( + vk_address: Option<[u8; 20]>, + proof: &[u8], + instances: &[bn256::Fr], +) -> Vec { + let (fn_sig, offset) = if vk_address.is_some() { + (FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, 0x60) + } else { + (FN_SIG_VERIFY_PROOF, 0x40) + }; + let vk_address = if let Some(vk_address) = vk_address { + U256::try_from_be_slice(&vk_address) + .unwrap() + .to_be_bytes::<0x20>() + .to_vec() + } else { + Vec::new() + }; + let num_instances = instances.len(); + chain![ + fn_sig, + vk_address, + U256::from(offset).to_be_bytes::<0x20>(), + U256::from(offset + 0x20 + proof.len()).to_be_bytes::<0x20>(), + U256::from(proof.len()).to_be_bytes::<0x20>(), + proof.iter().cloned(), + U256::from(num_instances).to_be_bytes::<0x20>(), + instances + .iter() + .flat_map(|instance| fe_to_u256::(instance).to_be_bytes::<0x20>()), + ] + .collect() +} + +#[cfg(any(test, feature = "evm"))] +pub(crate) mod test { + use revm::{ + primitives::{Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, + InMemoryDB, EVM, + }; + use std::{ + fmt::{self, Debug, Formatter}, + io::Write, + process::{Command, Stdio}, + str, + }; + + /// Compile solidity with `--via-ir` flag, then return creation bytecode. + /// + /// # Panics + /// Panics if executable `solc` can not be found, or compilation fails. + pub fn compile_solidity(solidity: impl AsRef<[u8]>) -> Vec { + let mut cmd = Command::new("solc") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .arg("--bin") + .arg("--via-ir") + .arg("-") + .spawn() + .unwrap(); + cmd.stdin + .take() + .unwrap() + .write_all(solidity.as_ref()) + .unwrap(); + let output = cmd.wait_with_output().unwrap(); + let stdout = str::from_utf8(&output.stdout).unwrap(); + if let Some(binary) = find_binary(stdout) { + binary + } else { + panic!( + "Compilation fails:\n{}", + str::from_utf8(&output.stderr).unwrap() + ) + } + } + + fn find_binary(stdout: &str) -> Option> { + let start = stdout.find("Binary:")? + 8; + Some(hex::decode(&stdout[start..stdout.len() - 1]).unwrap()) + } + + /// Evm runner. + pub struct Evm { + evm: EVM, + } + + impl Debug for Evm { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let mut debug_struct = f.debug_struct("Evm"); + debug_struct + .field("env", &self.evm.env) + .field("db", &self.evm.db.as_ref().unwrap()) + .finish() + } + } + + impl Default for Evm { + fn default() -> Self { + Self { + evm: EVM { + env: Default::default(), + db: Some(Default::default()), + }, + } + } + } + + impl Evm { + /// Return code_size of given address. + /// + /// # Panics + /// Panics if given address doesn't have bytecode. + pub fn code_size(&mut self, address: Address) -> usize { + self.evm.db.as_ref().unwrap().accounts[&address] + .info + .code + .as_ref() + .unwrap() + .len() + } + + /// Apply create transaction with given `bytecode` as creation bytecode. + /// Return created `address`. + /// + /// # Panics + /// Panics if execution reverts or halts unexpectedly. + pub fn create(&mut self, bytecode: Vec) -> Address { + let (_, output) = self.transact_success_or_panic(TxEnv { + gas_limit: u64::MAX, + transact_to: TransactTo::Create(CreateScheme::Create), + data: bytecode.into(), + ..Default::default() + }); + match output { + Output::Create(_, Some(address)) => address, + _ => unreachable!(), + } + } + + /// Apply call transaction to given `address` with `calldata`. + /// Returns `gas_used` and `return_data`. + /// + /// # Panics + /// Panics if execution reverts or halts unexpectedly. + pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { + let (gas_used, output) = self.transact_success_or_panic(TxEnv { + gas_limit: u64::MAX, + transact_to: TransactTo::Call(address), + data: calldata.into(), + ..Default::default() + }); + match output { + Output::Call(output) => (gas_used, output.into()), + _ => unreachable!(), + } + } + + fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) { + self.evm.env.tx = tx; + let result = self.evm.transact_commit().unwrap(); + self.evm.env.tx = Default::default(); + match result { + ExecutionResult::Success { + gas_used, + output, + logs, + .. + } => { + if !logs.is_empty() { + println!("--- logs from {} ---", logs[0].address); + for (log_idx, log) in logs.iter().enumerate() { + println!("log#{log_idx}"); + for (topic_idx, topic) in log.topics.iter().enumerate() { + println!(" topic{topic_idx}: {:?}", topic); + } + } + println!("--- end ---"); + } + (gas_used, output) + } + ExecutionResult::Revert { gas_used, output } => panic!( + "Transaction reverts with gas_used {gas_used} and output {:#x}", + output + ), + ExecutionResult::Halt { reason, gas_used } => panic!( + "Transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", + reason + ), + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs index 8ee4f2b..47d1d29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,74 +1,21 @@ -use halo2_proofs::halo2curves::{bn256, ff::PrimeField, CurveAffine}; -use itertools::chain; -use ruint::aliases::U256; -use std::borrow::Borrow; +//! Solidity verifier generator for [`halo2`] proof with KZG polynomial commitment scheme on BN254. +//! +//! [`halo2`]: http://github.com/privacy-scaling-explorations/halo2 + +#![deny(missing_docs)] +#![deny(missing_debug_implementations)] +#![deny(rustdoc::broken_intra_doc_links)] mod codegen; +mod evm; mod transcript; -pub use codegen::{BatchOpenScheme, SolidityGenerator}; -pub use transcript::{ChallengeEvm, Keccak256Transcript}; - -pub const FN_SIG_VERIFY_PROOF: [u8; 4] = [0x1e, 0x8e, 0x1e, 0x13]; -pub const FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS: [u8; 4] = [0xaf, 0x83, 0xa1, 0x8d]; - #[cfg(test)] mod test; -pub fn encode_calldata( - vk_address: Option<[u8; 20]>, - proof: &[u8], - instances: &[bn256::Fr], -) -> Vec { - let (fn_sig, offset) = if vk_address.is_some() { - (FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, 0x60) - } else { - (FN_SIG_VERIFY_PROOF, 0x40) - }; - let vk_address = if let Some(vk_address) = vk_address { - U256::try_from_be_slice(&vk_address) - .unwrap() - .to_be_bytes::<0x20>() - .to_vec() - } else { - Vec::new() - }; - let num_instances = instances.len(); - chain![ - fn_sig, - vk_address, - U256::from(offset).to_be_bytes::<0x20>(), - U256::from(offset + 0x20 + proof.len()).to_be_bytes::<0x20>(), - U256::from(proof.len()).to_be_bytes::<0x20>(), - proof.iter().cloned(), - U256::from(num_instances).to_be_bytes::<0x20>(), - instances - .iter() - .flat_map(|instance| fe_to_u256::(instance).to_be_bytes::<0x20>()), - ] - .collect() -} - -fn fe_to_u256(fe: impl Borrow) -> U256 -where - F: PrimeField, -{ - U256::from_le_bytes(fe.borrow().to_repr()) -} - -fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 2] { - let coords = ec_point.borrow().coordinates().unwrap(); - [coords.x(), coords.y()].map(fe_to_u256::) -} +pub use codegen::{AccumulatorEncoding, BatchOpenScheme, SolidityGenerator}; +pub use evm::{encode_calldata, FN_SIG_VERIFY_PROOF, FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS}; +pub use transcript::Keccak256Transcript; -fn g2_to_u256s(ec_point: impl Borrow) -> [U256; 4] { - let coords = ec_point.borrow().coordinates().unwrap(); - let x = coords.x().to_repr(); - let y = coords.y().to_repr(); - [ - U256::try_from_le_slice(&x.as_ref()[0x20..]).unwrap(), - U256::try_from_le_slice(&x.as_ref()[..0x20]).unwrap(), - U256::try_from_le_slice(&y.as_ref()[0x20..]).unwrap(), - U256::try_from_le_slice(&y.as_ref()[..0x20]).unwrap(), - ] -} +#[cfg(feature = "evm")] +pub use evm::test::{compile_solidity, Evm}; diff --git a/src/test.rs b/src/test.rs index 229a550..416664a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,10 +1,11 @@ use crate::{ codegen::{AccumulatorEncoding, BatchOpenScheme::Bdfg21, SolidityGenerator}, - encode_calldata, FN_SIG_VERIFY_PROOF, FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, + encode_calldata, + evm::test::{compile_solidity, Evm}, + FN_SIG_VERIFY_PROOF, FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, }; use halo2_proofs::halo2curves::bn256::{Bn256, Fr}; use rand::{rngs::StdRng, RngCore, SeedableRng}; -use ruint::aliases::U256; use sha3::Digest; use std::{fs::File, io::Write}; @@ -45,78 +46,65 @@ fn render_separately_maingate() { } fn run_render>() { - let acc_encoding = AccumulatorEncoding { - offset: 0, - num_limbs: 4, - num_limb_bits: 68, - } - .into(); - let (param, vk, instances, proof) = + let acc_encoding = AccumulatorEncoding::new(0, 4, 68).into(); + let (params, vk, instances, proof) = halo2::create_testdata_bdfg21::(C::min_k(), acc_encoding, std_rng()); - let generator = SolidityGenerator::new(¶m, &vk, instances.len(), acc_encoding, Bdfg21); + let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, instances.len()) + .set_acc_encoding(acc_encoding); let verifier_solidity = generator.render().unwrap(); - let verifier_bytecode = ethereum::compile_solidity(verifier_solidity); - let verifier_deployment_codesize = verifier_bytecode.len(); + let verifier_creation_code = compile_solidity(verifier_solidity); + let verifier_creation_code_size = verifier_creation_code.len(); - let mut evm = ethereum::Evm::default(); - let verifier_address = evm.create(verifier_bytecode); - let verifier_runtime_codesize = evm.codesize(verifier_address); + let mut evm = Evm::default(); + let verifier_address = evm.create(verifier_creation_code); + let verifier_runtime_code_size = evm.code_size(verifier_address); - println!("Verifier deployment code size: {verifier_deployment_codesize}"); - println!("Verifier runtime code size: {verifier_runtime_codesize}"); + println!("Verifier creation code size: {verifier_creation_code_size}"); + println!("Verifier runtime code size: {verifier_runtime_code_size}"); let (gas_cost, output) = evm.call(verifier_address, encode_calldata(None, &proof, &instances)); - assert_eq!( - U256::from_be_bytes::<0x20>(output.try_into().unwrap()), - U256::from(1) - ); + assert_eq!(output, [vec![0; 31], vec![1]].concat()); println!("Gas cost: {gas_cost}"); } fn run_render_separately>() { - let acc_encoding = AccumulatorEncoding { - offset: 0, - num_limbs: 4, - num_limb_bits: 68, - } - .into(); - let (param, vk, instances, _) = + let acc_encoding = AccumulatorEncoding::new(0, 4, 68).into(); + let (params, vk, instances, _) = halo2::create_testdata_bdfg21::(C::min_k(), acc_encoding, std_rng()); - let generator = SolidityGenerator::new(¶m, &vk, instances.len(), acc_encoding, Bdfg21); + let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, instances.len()) + .set_acc_encoding(acc_encoding); let (verifier_solidity, _vk_solidity) = generator.render_separately().unwrap(); - let verifier_bytecode = ethereum::compile_solidity(&verifier_solidity); - let verifier_deployment_codesize = verifier_bytecode.len(); + let verifier_creation_code = compile_solidity(&verifier_solidity); + let verifier_creation_code_size = verifier_creation_code.len(); - let mut evm = ethereum::Evm::default(); - let verifier_address = evm.create(verifier_bytecode); - let verifier_runtime_codesize = evm.codesize(verifier_address); + let mut evm = Evm::default(); + let verifier_address = evm.create(verifier_creation_code); + let verifier_runtime_code_size = evm.code_size(verifier_address); - println!("Verifier deployment code size: {verifier_deployment_codesize}"); - println!("Verifier runtime code size: {verifier_runtime_codesize}"); + println!("Verifier creation code size: {verifier_creation_code_size}"); + println!("Verifier runtime code size: {verifier_runtime_code_size}"); let deployed_verifier_solidity = verifier_solidity; for k in C::min_k()..C::min_k() + 4 { - let (param, vk, instances, proof) = + let (params, vk, instances, proof) = halo2::create_testdata_bdfg21::(k, acc_encoding, std_rng()); - let generator = SolidityGenerator::new(¶m, &vk, instances.len(), acc_encoding, Bdfg21); + let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, instances.len()) + .set_acc_encoding(acc_encoding); let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); assert_eq!(deployed_verifier_solidity, verifier_solidity); - let vk_bytecode = ethereum::compile_solidity(&vk_solidity); - let vk_address = evm.create(vk_bytecode); + let vk_creation_code = compile_solidity(&vk_solidity); + let vk_address = evm.create(vk_creation_code); let (gas_cost, output) = evm.call( verifier_address, encode_calldata(vk_address.0.into(), &proof, &instances), ); - assert_eq!( - U256::from_be_bytes::<0x20>(output.try_into().unwrap()), - U256::from(1) - ); + assert_eq!(output, [vec![0; 31], vec![1]].concat()); println!("Gas cost: {gas_cost}"); } } @@ -142,130 +130,6 @@ fn save_generated(verifier: &str, vk: Option<&str>) { } } -mod ethereum { - use revm::{ - primitives::{Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, - InMemoryDB, EVM, - }; - use std::{ - io::Write, - process::{Command, Stdio}, - str, - }; - - pub fn compile_solidity(solidity: impl AsRef<[u8]>) -> Vec { - let mut cmd = Command::new("solc") - .stdin(Stdio::piped()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .arg("--bin") - .arg("--via-ir") - .arg("-") - .spawn() - .unwrap(); - cmd.stdin - .take() - .unwrap() - .write_all(solidity.as_ref()) - .unwrap(); - let output = cmd.wait_with_output().unwrap(); - let stdout = str::from_utf8(&output.stdout).unwrap(); - if let Some(binary) = find_binary(stdout) { - binary - } else { - panic!("{}", str::from_utf8(&output.stderr).unwrap()); - } - } - - fn find_binary(stdout: &str) -> Option> { - let start = stdout.find("Binary:")? + 8; - Some(hex::decode(&stdout[start..stdout.len() - 1]).unwrap()) - } - - pub struct Evm { - evm: EVM, - } - - impl Default for Evm { - fn default() -> Self { - Self { - evm: EVM { - env: Default::default(), - db: Some(Default::default()), - }, - } - } - } - - impl Evm { - pub fn codesize(&mut self, address: Address) -> usize { - self.evm.db.as_ref().unwrap().accounts[&address] - .info - .code - .as_ref() - .unwrap() - .len() - } - - pub fn create(&mut self, bytecode: Vec) -> Address { - let (_, output) = self.transact_success_or_panic(TxEnv { - gas_limit: u64::MAX, - transact_to: TransactTo::Create(CreateScheme::Create), - data: bytecode.into(), - ..Default::default() - }); - match output { - Output::Create(_, Some(address)) => address, - _ => unreachable!(), - } - } - - pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { - let (gas_used, output) = self.transact_success_or_panic(TxEnv { - gas_limit: u64::MAX, - transact_to: TransactTo::Call(address), - data: calldata.into(), - ..Default::default() - }); - match output { - Output::Call(output) => (gas_used, output.into()), - _ => unreachable!(), - } - } - - fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) { - self.evm.env.tx = tx; - let result = self.evm.transact_commit().unwrap(); - self.evm.env.tx = Default::default(); - match result { - ExecutionResult::Success { - gas_used, - output, - logs, - .. - } => { - if !logs.is_empty() { - println!("--- logs from {} ---", logs[0].address); - for log in logs { - println!("topic1: {:?}", log.topics[0]); - } - println!("--- end ---"); - } - (gas_used, output) - } - ExecutionResult::Revert { gas_used, output } => panic!( - "Transaction reverts with gas_used {gas_used} and output {:#x}", - output - ), - ExecutionResult::Halt { reason, gas_used } => panic!( - "Transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", - reason - ), - } - } - } -} - mod halo2 { use crate::{codegen::AccumulatorEncoding, transcript::Keccak256Transcript}; use halo2_proofs::{ @@ -309,19 +173,19 @@ mod halo2 { Vec, ) { let circuit = C::new(acc_encoding, rng.clone()); - let instnaces = circuit.instances(); + let instances = circuit.instances(); - let param = ParamsKZG::::setup(k, &mut rng); - let vk = keygen_vk(¶m, &circuit).unwrap(); - let pk = keygen_pk(¶m, vk.clone(), &circuit).unwrap(); + let params = ParamsKZG::::setup(k, &mut rng); + let vk = keygen_vk(¶ms, &circuit).unwrap(); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).unwrap(); let proof = { let mut transcript = Keccak256Transcript::new(Vec::new()); create_proof::<_, ProverSHPLONK<_>, _, _, _, _>( - ¶m, + ¶ms, &pk, &[circuit], - &[&[&instnaces]], + &[&[&instances]], &mut rng, &mut transcript, ) @@ -332,16 +196,16 @@ mod halo2 { let result = { let mut transcript = Keccak256Transcript::new(proof.as_slice()); verify_proof::<_, VerifierSHPLONK<_>, _, _, SingleStrategy<_>>( - ¶m, + ¶ms, pk.get_vk(), - SingleStrategy::new(¶m), - &[&[&instnaces]], + SingleStrategy::new(¶ms), + &[&[&instances]], &mut transcript, ) }; assert!(result.is_ok()); - (param, vk, instnaces, proof) + (params, vk, instances, proof) } fn random_accumulator_limbs( diff --git a/src/transcript.rs b/src/transcript.rs index 8ce79f8..f125329 100644 --- a/src/transcript.rs +++ b/src/transcript.rs @@ -14,6 +14,7 @@ use std::{ mem, }; +/// Transcript using Keccak256 as hash function in Fiat-Shamir transformation. #[derive(Debug, Default)] pub struct Keccak256Transcript { stream: S, @@ -22,6 +23,7 @@ pub struct Keccak256Transcript { } impl Keccak256Transcript { + /// Return a `Keccak256Transcript` with empty buffer. pub fn new(stream: S) -> Self { Self { stream, diff --git a/templates/Halo2Verifier.sol b/templates/Halo2Verifier.sol index 2493025..bd917bb 100644 --- a/templates/Halo2Verifier.sol +++ b/templates/Halo2Verifier.sol @@ -1,13 +1,15 @@ -pragma solidity ^0.8.21; +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; contract Halo2Verifier { - uint256 internal constant PROOF_LEN_CPTR = {{ (proof_cptr - 1) }}; + uint256 internal constant PROOF_LEN_CPTR = {{ proof_cptr - 1 }}; uint256 internal constant PROOF_CPTR = {{ proof_cptr }}; - uint256 internal constant NUM_INSTANCE_CPTR = {{ (proof_cptr + (proof_len / 32)) }}; - uint256 internal constant INSTANCE_CPTR = {{ (proof_cptr + (proof_len / 32) + 1) }}; + uint256 internal constant NUM_INSTANCE_CPTR = {{ proof_cptr + (proof_len / 32) }}; + uint256 internal constant INSTANCE_CPTR = {{ proof_cptr + (proof_len / 32) + 1 }}; uint256 internal constant FIRST_QUOTIENT_X_CPTR = {{ quotient_comm_cptr }}; - uint256 internal constant LAST_QUOTIENT_X_CPTR = {{ (quotient_comm_cptr + 2 * (num_quotients - 1)) }}; + uint256 internal constant LAST_QUOTIENT_X_CPTR = {{ quotient_comm_cptr + 2 * (num_quotients - 1) }}; uint256 internal constant VK_MPTR = {{ vk_mptr }}; uint256 internal constant VK_DIGEST_MPTR = {{ vk_mptr }}; @@ -39,28 +41,33 @@ contract Halo2Verifier { uint256 internal constant GAMMA_MPTR = {{ theta_mptr + 2 }}; uint256 internal constant Y_MPTR = {{ theta_mptr + 3 }}; uint256 internal constant X_MPTR = {{ theta_mptr + 4 }}; + {%- match scheme %} + {%- when Bdfg21 %} uint256 internal constant ZETA_MPTR = {{ theta_mptr + 5 }}; uint256 internal constant NU_MPTR = {{ theta_mptr + 6 }}; uint256 internal constant MU_MPTR = {{ theta_mptr + 7 }}; - - uint256 internal constant INSTANCE_EVAL_MPTR = {{ instance_eval_mptr }}; - uint256 internal constant X_N_MPTR = {{ instance_eval_mptr + 1 }}; - uint256 internal constant X_N_MINUS_1_INV_MPTR = {{ instance_eval_mptr + 2 }}; - uint256 internal constant L_LAST_MPTR = {{ instance_eval_mptr + 3 }}; - uint256 internal constant L_BLIND_MPTR = {{ instance_eval_mptr + 4 }}; - uint256 internal constant L_0_MPTR = {{ instance_eval_mptr + 5 }}; - uint256 internal constant QUOTIENT_EVAL_MPTR = {{ instance_eval_mptr + 6 }}; - uint256 internal constant QUOTIENT_X_MPTR = {{ instance_eval_mptr + 7 }}; - uint256 internal constant QUOTIENT_Y_MPTR = {{ instance_eval_mptr + 8 }}; - uint256 internal constant R_EVAL_MPTR = {{ instance_eval_mptr + 9 }}; - uint256 internal constant PAIRING_LHS_X_MPTR = {{ instance_eval_mptr + 10 }}; - uint256 internal constant PAIRING_LHS_Y_MPTR = {{ instance_eval_mptr + 11 }}; - uint256 internal constant PAIRING_RHS_X_MPTR = {{ instance_eval_mptr + 12 }}; - uint256 internal constant PAIRING_RHS_Y_MPTR = {{ instance_eval_mptr + 13 }}; - uint256 internal constant ACC_LHS_X_MPTR = {{ instance_eval_mptr + 14 }}; - uint256 internal constant ACC_LHS_Y_MPTR = {{ instance_eval_mptr + 15 }}; - uint256 internal constant ACC_RHS_X_MPTR = {{ instance_eval_mptr + 16 }}; - uint256 internal constant ACC_RHS_Y_MPTR = {{ instance_eval_mptr + 17 }}; + {%- when Gwc19 %} + // TODO + {%- endmatch %} + + uint256 internal constant ACC_LHS_X_MPTR = {{ theta_mptr + 8 }}; + uint256 internal constant ACC_LHS_Y_MPTR = {{ theta_mptr + 9 }}; + uint256 internal constant ACC_RHS_X_MPTR = {{ theta_mptr + 10 }}; + uint256 internal constant ACC_RHS_Y_MPTR = {{ theta_mptr + 11 }}; + uint256 internal constant X_N_MPTR = {{ theta_mptr + 12 }}; + uint256 internal constant X_N_MINUS_1_INV_MPTR = {{ theta_mptr + 13 }}; + uint256 internal constant L_LAST_MPTR = {{ theta_mptr + 14 }}; + uint256 internal constant L_BLIND_MPTR = {{ theta_mptr + 15 }}; + uint256 internal constant L_0_MPTR = {{ theta_mptr + 16 }}; + uint256 internal constant INSTANCE_EVAL_MPTR = {{ theta_mptr + 17 }}; + uint256 internal constant QUOTIENT_EVAL_MPTR = {{ theta_mptr + 18 }}; + uint256 internal constant QUOTIENT_X_MPTR = {{ theta_mptr + 19 }}; + uint256 internal constant QUOTIENT_Y_MPTR = {{ theta_mptr + 20 }}; + uint256 internal constant R_EVAL_MPTR = {{ theta_mptr + 21 }}; + uint256 internal constant PAIRING_LHS_X_MPTR = {{ theta_mptr + 22 }}; + uint256 internal constant PAIRING_LHS_Y_MPTR = {{ theta_mptr + 23 }}; + uint256 internal constant PAIRING_RHS_X_MPTR = {{ theta_mptr + 24 }}; + uint256 internal constant PAIRING_RHS_Y_MPTR = {{ theta_mptr + 25 }}; function verifyProof( {%- match vk %} @@ -113,12 +120,12 @@ contract Halo2Verifier { ret := add(challenge_mptr, 0x20) } - // Batch invert values in memory[0..mptr_end] in place. + // Batch invert values in memory[mptr_start..mptr_end] in place. // Return updated (success). - function batch_invert(success, mptr_end, r) -> ret { - let mptr := 0x20 + function batch_invert(success, mptr_start, mptr_end, r) -> ret { let gp_mptr := mptr_end - let gp := mload(0x00) + let gp := mload(mptr_start) + let mptr := add(mptr_start, 0x20) for {} lt(mptr, sub(mptr_end, 0x20)) @@ -140,10 +147,12 @@ contract Halo2Verifier { ret := and(success, staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20)) let all_inv := mload(gp_mptr) + let first_mptr := mptr_start + let second_mptr := add(first_mptr, 0x20) gp_mptr := sub(gp_mptr, 0x20) for {} - lt(0x20, mptr) + lt(second_mptr, mptr) {} { let inv := mulmod(all_inv, mload(gp_mptr), r) @@ -152,10 +161,10 @@ contract Halo2Verifier { mptr := sub(mptr, 0x20) gp_mptr := sub(gp_mptr, 0x20) } - let inv_first := mulmod(all_inv, mload(0x20), r) - let inv_second := mulmod(all_inv, mload(0x00), r) - mstore(0x00, inv_first) - mstore(0x20, inv_second) + let inv_first := mulmod(all_inv, mload(second_mptr), r) + let inv_second := mulmod(all_inv, mload(first_mptr), r) + mstore(first_mptr, inv_first) + mstore(second_mptr, inv_second) } // Add (x, y) into point at (0x00, 0x20). @@ -267,11 +276,7 @@ contract Halo2Verifier { // Phase {{ loop.index }} for - {%- if loop.first %} - { let proof_cptr_end := add(proof_cptr, {{ (2 * 32 * num_advices)|hex() }}) } - {%- else %} { let proof_cptr_end := add(proof_cptr, {{ (2 * 32 * num_advices)|hex() }}) } - {%- endif %} lt(proof_cptr, proof_cptr_end) {} { @@ -309,6 +314,7 @@ contract Halo2Verifier { success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W' {%- when Gwc19 %} + // TODO {%- endmatch %} // Read accumulator from instances @@ -369,12 +375,11 @@ contract Halo2Verifier { { x_n := mulmod(x_n, x_n, r) } - mstore(X_N_MPTR, x_n) let omega := mload(OMEGA_MPTR) - let mptr_end := mul(0x20, add(mload(NUM_INSTANCES_MPTR), {{ num_neg_lagranges }})) - let mptr := 0x00 + let mptr := X_N_MPTR + let mptr_end := add(mptr, mul(0x20, add(mload(NUM_INSTANCES_MPTR), {{ num_neg_lagranges }}))) for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } lt(mptr, mptr_end) @@ -384,11 +389,10 @@ contract Halo2Verifier { pow_of_omega := mulmod(pow_of_omega, omega, r) } let x_n_minus_1 := addmod(x_n, sub(r, 1), r) - mstore(mptr, x_n_minus_1) - success := batch_invert(success, add(mptr, 0x20), r) - mstore(X_N_MINUS_1_INV_MPTR, mload(mptr)) + mstore(mptr_end, x_n_minus_1) + success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), r) - mptr := 0x00 + mptr := X_N_MPTR let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) for { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } @@ -399,34 +403,40 @@ contract Halo2Verifier { pow_of_omega := mulmod(pow_of_omega, omega, r) } - let l_i_cptr := {{ ((num_neg_lagranges + 1) * 32)|hex() }} + let l_blind := mload(add(X_N_MPTR, 0x20)) + let l_i_cptr := add(X_N_MPTR, 0x40) + for + { let l_i_cptr_end := add(X_N_MPTR, {{ (num_neg_lagranges * 32)|hex() }}) } + lt(l_i_cptr, l_i_cptr_end) + { l_i_cptr := add(l_i_cptr, 0x20) } + { + l_blind := addmod(l_blind, mload(l_i_cptr), r) + } + + let instance_eval := mulmod(mload(l_i_cptr), calldataload(INSTANCE_CPTR), r) let instance_cptr := add(INSTANCE_CPTR, 0x20) - let instance_eval := mulmod(mload({{ (num_neg_lagranges * 32)|hex() }}), calldataload(INSTANCE_CPTR), r) + l_i_cptr := add(l_i_cptr, 0x20) for { let instance_cptr_end := add(INSTANCE_CPTR, mul(0x20, mload(NUM_INSTANCES_MPTR))) } lt(instance_cptr, instance_cptr_end) - {} + { + instance_cptr := add(instance_cptr, 0x20) + l_i_cptr := add(l_i_cptr, 0x20) + } { instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) - l_i_cptr := add(l_i_cptr, 0x20) - instance_cptr := add(instance_cptr, 0x20) } - l_i_cptr := 0x40 - let l_blind := mload(0x20) - for - { let l_i_cptr_end := {{ 64 + 32 * (num_neg_lagranges - 2) }} } - lt(l_i_cptr, l_i_cptr_end) - {} - { - l_blind := addmod(l_blind, mload(l_i_cptr), r) - l_i_cptr := add(l_i_cptr, 0x20) - } + let x_n_minus_1_inv := mload(mptr_end) + let l_last := mload(X_N_MPTR) + let l_0 := mload(add(X_N_MPTR, {{ (num_neg_lagranges * 32)|hex() }})) - mstore(INSTANCE_EVAL_MPTR, instance_eval) - mstore(L_LAST_MPTR, mload(0x00)) + mstore(X_N_MPTR, x_n) + mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) + mstore(L_LAST_MPTR, l_last) mstore(L_BLIND_MPTR, l_blind) - mstore(L_0_MPTR, mload({{ (num_neg_lagranges * 32)|hex() }})) + mstore(L_0_MPTR, l_0) + mstore(INSTANCE_EVAL_MPTR, instance_eval) } // Compute quotient evavluation diff --git a/templates/Halo2VerifyingKey.sol b/templates/Halo2VerifyingKey.sol index c7b221b..c9edfce 100644 --- a/templates/Halo2VerifyingKey.sol +++ b/templates/Halo2VerifyingKey.sol @@ -1,4 +1,6 @@ -pragma solidity ^0.8.21; +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; contract Halo2VerifyingKey { constructor() {