├── .github └── workflows │ └── ci.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── askama.toml ├── examples └── separately.rs ├── rust-toolchain.toml ├── src ├── codegen.rs ├── codegen │ ├── evaluator.rs │ ├── pcs.rs │ ├── pcs │ │ ├── bdfg21.rs │ │ └── gwc19.rs │ ├── template.rs │ └── util.rs ├── evm.rs ├── lib.rs ├── test.rs └── transcript.rs └── templates ├── Halo2Verifier.sol └── Halo2VerifyingKey.sol /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | test: 11 | name: Test 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Install toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | toolchain: stable 20 | profile: minimal 21 | 22 | - uses: Swatinem/rust-cache@v1 23 | with: 24 | cache-on-failure: true 25 | 26 | - name: Install solc 27 | run: (hash svm 2>/dev/null || cargo install svm-rs@0.5.4) && svm install 0.8.21 && solc --version 28 | 29 | - name: Run test 30 | run: cargo test --workspace --all-features --all-targets -- --nocapture 31 | 32 | lint: 33 | name: Lint 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v2 37 | 38 | - name: Install toolchain 39 | uses: actions-rs/toolchain@v1 40 | with: 41 | toolchain: stable 42 | profile: minimal 43 | components: rustfmt, clippy 44 | 45 | - uses: Swatinem/rust-cache@v1 46 | with: 47 | cache-on-failure: true 48 | 49 | - name: Run fmt 50 | run: cargo fmt --all -- --check 51 | 52 | - name: Run clippy 53 | run: cargo clippy --workspace --all-features --all-targets -- -D warnings 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb 15 | 16 | .vscode 17 | generated/ -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "halo2_solidity_verifier" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v0.3.0" } 8 | askama = { version = "0.12.0", features = ["config"], default-features = false } 9 | hex = "0.4.3" 10 | ruint = "1" 11 | sha3 = "0.10" 12 | itertools = "0.11.0" 13 | 14 | # For feature = "evm" 15 | revm = { version = "3.3.0", default-features = false, optional = true } 16 | 17 | [dev-dependencies] 18 | rand = "0.8.5" 19 | revm = { version = "3.3.0", default-features = false } 20 | halo2_maingate = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2024_01_31", package = "maingate" } 21 | 22 | [features] 23 | default = [] 24 | evm = ["dep:revm"] 25 | 26 | [[example]] 27 | name = "separately" 28 | required-features = ["evm"] 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Privacy & Scaling Explorations (formerly known as appliedzkp) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Halo2 Solidity Verifier 2 | 3 | > ⚠️ This repo has NOT been audited and is NOT intended for a production environment yet. 4 | 5 | Solidity verifier generator for [`halo2`](http://github.com/privacy-scaling-explorations/halo2) proof with KZG polynomial commitment scheme on BN254. 6 | 7 | For audited solidity verifier generator and proof aggregation toolkits, please refer to [`snark-verifier`](http://github.com/axiom-crypto/snark-verifier). 8 | 9 | ## Usage 10 | 11 | ### Generate verifier and verifying key separately as 2 solidity contracts 12 | 13 | ```rust 14 | let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, num_instances); 15 | let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); 16 | ``` 17 | 18 | Check [`examples/separately.rs`](./examples/separately.rs) for more details. 19 | 20 | > [!NOTE] 21 | > Currently example run only with rust `1.77.0` version due to a `cargo` update ([rust-lang/rust#123285](https://github.com/rust-lang/rust/issues/123285)). 22 | > 23 | > The `rust` toolchain version is specified in [rust-toolchain.toml](./rust-toolchain.toml) file. 24 | 25 | Run example with the following command: 26 | 27 | ```bash 28 | cargo run --all-features --example separately 29 | ``` 30 | 31 | ### Generate verifier and verifying key in a single solidity contract 32 | 33 | ```rust 34 | let generator = SolidityGenerator::new(¶ms, &vk, Bdfg21, num_instances); 35 | let verifier_solidity = generator.render().unwrap(); 36 | ``` 37 | 38 | ### Encode proof into calldata to invoke `verifyProof` 39 | 40 | ```rust 41 | let calldata = encode_calldata(vk_address, &proof, &instances); 42 | ``` 43 | 44 | Note that function selector is already included. 45 | 46 | ## Test 47 | 48 | To run tests, use the following command: 49 | 50 | ```bash 51 | cargo test --workspace --all-features --all-targets -- --nocapture 52 | ``` 53 | 54 | > [!NOTE] 55 | > Currently tests run only with rust `1.77.0` version due to a `cargo` update ([rust-lang/rust#123285](https://github.com/rust-lang/rust/issues/123285)). 56 | > 57 | > The `rust` toolchain version is specified in [rust-toolchain.toml](./rust-toolchain.toml) file. 58 | 59 | 60 | ## Limitations & Caveats 61 | 62 | - It only allows circuit with **less or equal than 1 instance column** and **no rotated query to this instance column**. 63 | - Currently 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, please use [`keygen_vk_custom`](https://github.com/privacy-scaling-explorations/halo2/blob/6fc6d7ca018f3899b030618cb18580249b1e7c82/halo2_proofs/src/plonk/keygen.rs#L223) with `compress_selectors: false` to do key generation without selector compression. 64 | 65 | ## Compatibility 66 | 67 | The [`Keccak256Transcript`](./src/transcript.rs#L19) behaves exactly same as the `EvmTranscript` in `snark-verifier`. 68 | 69 | ## Design Rationale 70 | 71 | The current solidity verifier generator within `snark-verifier` faces a couple of issues: 72 | 73 | - The generator receives only unoptimized, low-level operations, such as add or mul. As a result, it currently unrolls all assembly codes, making it susceptible to exceeding the contract size limit, even with a moderately sized circuit. 74 | - The existing solution involves complex abstractions and APIs for consumers. 75 | 76 | This repository is a ground-up rebuild, addressing these concerns while maintaining a focus on code size and readability. Remarkably, the gas cost is comparable, if not slightly lower, than the one generated by `snark-verifier`. 77 | 78 | ## Acknowledgement 79 | 80 | The template is heavily inspired by Aztec's [`BaseUltraVerifier.sol`](https://github.com/AztecProtocol/barretenberg/blob/4c456a2b196282160fd69bead6a1cea85289af37/sol/src/ultra/BaseUltraVerifier.sol). 81 | -------------------------------------------------------------------------------- /askama.toml: -------------------------------------------------------------------------------- 1 | [[escaper]] 2 | path = "askama::Text" 3 | extensions = ["sol"] 4 | -------------------------------------------------------------------------------- /examples/separately.rs: -------------------------------------------------------------------------------- 1 | use application::StandardPlonk; 2 | use prelude::*; 3 | 4 | use halo2_solidity_verifier::{ 5 | compile_solidity, encode_calldata, BatchOpenScheme::Bdfg21, Evm, Keccak256Transcript, 6 | SolidityGenerator, 7 | }; 8 | 9 | const K_RANGE: Range = 10..17; 10 | 11 | fn main() { 12 | let mut rng = seeded_std_rng(); 13 | 14 | let params = setup(K_RANGE, &mut rng); 15 | 16 | let vk = keygen_vk(¶ms[&K_RANGE.start], &StandardPlonk::default()).unwrap(); 17 | let generator = SolidityGenerator::new(¶ms[&K_RANGE.start], &vk, Bdfg21, 0); 18 | let (verifier_solidity, _) = generator.render_separately().unwrap(); 19 | save_solidity("Halo2Verifier.sol", &verifier_solidity); 20 | 21 | let verifier_creation_code = compile_solidity(&verifier_solidity); 22 | let verifier_creation_code_size = verifier_creation_code.len(); 23 | println!("Verifier creation code size: {verifier_creation_code_size}"); 24 | 25 | let mut evm = Evm::default(); 26 | let verifier_address = evm.create(verifier_creation_code); 27 | 28 | let deployed_verifier_solidity = verifier_solidity; 29 | 30 | for k in K_RANGE { 31 | let num_instances = k as usize; 32 | let circuit = StandardPlonk::rand(num_instances, &mut rng); 33 | 34 | let vk = keygen_vk(¶ms[&k], &circuit).unwrap(); 35 | let pk = keygen_pk(¶ms[&k], vk, &circuit).unwrap(); 36 | let generator = SolidityGenerator::new(¶ms[&k], pk.get_vk(), Bdfg21, num_instances); 37 | let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); 38 | save_solidity(format!("Halo2VerifyingKey-{k}.sol"), &vk_solidity); 39 | 40 | assert_eq!(deployed_verifier_solidity, verifier_solidity); 41 | 42 | let vk_creation_code = compile_solidity(&vk_solidity); 43 | let vk_address = evm.create(vk_creation_code); 44 | 45 | let calldata = { 46 | let instances = circuit.instances(); 47 | let proof = create_proof_checked(¶ms[&k], &pk, circuit, &instances, &mut rng); 48 | encode_calldata(Some(vk_address.into()), &proof, &instances) 49 | }; 50 | let (gas_cost, output) = evm.call(verifier_address, calldata); 51 | assert_eq!(output, [vec![0; 31], vec![1]].concat()); 52 | println!("Gas cost of verifying standard Plonk with 2^{k} rows: {gas_cost}"); 53 | } 54 | } 55 | 56 | fn save_solidity(name: impl AsRef, solidity: &str) { 57 | const DIR_GENERATED: &str = "./generated"; 58 | 59 | create_dir_all(DIR_GENERATED).unwrap(); 60 | File::create(format!("{DIR_GENERATED}/{}", name.as_ref())) 61 | .unwrap() 62 | .write_all(solidity.as_bytes()) 63 | .unwrap(); 64 | } 65 | 66 | fn setup(k_range: Range, mut rng: impl RngCore) -> HashMap> { 67 | k_range 68 | .clone() 69 | .zip(k_range.map(|k| ParamsKZG::::setup(k, &mut rng))) 70 | .collect() 71 | } 72 | 73 | fn create_proof_checked( 74 | params: &ParamsKZG, 75 | pk: &ProvingKey, 76 | circuit: impl Circuit, 77 | instances: &[Fr], 78 | mut rng: impl RngCore, 79 | ) -> Vec { 80 | use halo2_proofs::{ 81 | poly::kzg::{ 82 | multiopen::{ProverSHPLONK, VerifierSHPLONK}, 83 | strategy::SingleStrategy, 84 | }, 85 | transcript::TranscriptWriterBuffer, 86 | }; 87 | 88 | let proof = { 89 | let mut transcript = Keccak256Transcript::new(Vec::new()); 90 | create_proof::<_, ProverSHPLONK<_>, _, _, _, _>( 91 | params, 92 | pk, 93 | &[circuit], 94 | &[&[instances]], 95 | &mut rng, 96 | &mut transcript, 97 | ) 98 | .unwrap(); 99 | transcript.finalize() 100 | }; 101 | 102 | let result = { 103 | let mut transcript = Keccak256Transcript::new(proof.as_slice()); 104 | verify_proof::<_, VerifierSHPLONK<_>, _, _, SingleStrategy<_>>( 105 | params, 106 | pk.get_vk(), 107 | SingleStrategy::new(params), 108 | &[&[instances]], 109 | &mut transcript, 110 | ) 111 | }; 112 | assert!(result.is_ok()); 113 | 114 | proof 115 | } 116 | 117 | mod application { 118 | use crate::prelude::*; 119 | 120 | #[derive(Clone)] 121 | pub struct StandardPlonkConfig { 122 | selectors: [Column; 5], 123 | wires: [Column; 3], 124 | } 125 | 126 | impl StandardPlonkConfig { 127 | fn configure(meta: &mut ConstraintSystem) -> Self { 128 | let [w_l, w_r, w_o] = [(); 3].map(|_| meta.advice_column()); 129 | let [q_l, q_r, q_o, q_m, q_c] = [(); 5].map(|_| meta.fixed_column()); 130 | let pi = meta.instance_column(); 131 | [w_l, w_r, w_o].map(|column| meta.enable_equality(column)); 132 | meta.create_gate( 133 | "q_l·w_l + q_r·w_r + q_o·w_o + q_m·w_l·w_r + q_c + pi = 0", 134 | |meta| { 135 | let [w_l, w_r, w_o] = 136 | [w_l, w_r, w_o].map(|column| meta.query_advice(column, Rotation::cur())); 137 | let [q_l, q_r, q_o, q_m, q_c] = [q_l, q_r, q_o, q_m, q_c] 138 | .map(|column| meta.query_fixed(column, Rotation::cur())); 139 | let pi = meta.query_instance(pi, Rotation::cur()); 140 | Some( 141 | q_l * w_l.clone() 142 | + q_r * w_r.clone() 143 | + q_o * w_o 144 | + q_m * w_l * w_r 145 | + q_c 146 | + pi, 147 | ) 148 | }, 149 | ); 150 | StandardPlonkConfig { 151 | selectors: [q_l, q_r, q_o, q_m, q_c], 152 | wires: [w_l, w_r, w_o], 153 | } 154 | } 155 | } 156 | 157 | #[derive(Clone, Debug, Default)] 158 | pub struct StandardPlonk(Vec); 159 | 160 | impl StandardPlonk { 161 | pub fn rand(num_instances: usize, mut rng: R) -> Self { 162 | Self((0..num_instances).map(|_| F::random(&mut rng)).collect()) 163 | } 164 | 165 | pub fn instances(&self) -> Vec { 166 | self.0.clone() 167 | } 168 | } 169 | 170 | impl Circuit for StandardPlonk { 171 | type Config = StandardPlonkConfig; 172 | type FloorPlanner = SimpleFloorPlanner; 173 | 174 | fn without_witnesses(&self) -> Self { 175 | unimplemented!() 176 | } 177 | 178 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 179 | meta.set_minimum_degree(5); 180 | StandardPlonkConfig::configure(meta) 181 | } 182 | 183 | fn synthesize( 184 | &self, 185 | config: Self::Config, 186 | mut layouter: impl Layouter, 187 | ) -> Result<(), Error> { 188 | let [q_l, q_r, q_o, q_m, q_c] = config.selectors; 189 | let [w_l, w_r, w_o] = config.wires; 190 | layouter.assign_region( 191 | || "", 192 | |mut region| { 193 | for (offset, instance) in self.0.iter().enumerate() { 194 | region.assign_advice(|| "", w_l, offset, || Value::known(*instance))?; 195 | region.assign_fixed(|| "", q_l, offset, || Value::known(-F::ONE))?; 196 | } 197 | let offset = self.0.len(); 198 | let a = region.assign_advice(|| "", w_l, offset, || Value::known(F::ONE))?; 199 | a.copy_advice(|| "", &mut region, w_r, offset)?; 200 | a.copy_advice(|| "", &mut region, w_o, offset)?; 201 | let offset = offset + 1; 202 | region.assign_advice(|| "", w_l, offset, || Value::known(-F::from(5)))?; 203 | for (column, idx) in [q_l, q_r, q_o, q_m, q_c].iter().zip(1..) { 204 | region.assign_fixed( 205 | || "", 206 | *column, 207 | offset, 208 | || Value::known(F::from(idx)), 209 | )?; 210 | } 211 | Ok(()) 212 | }, 213 | ) 214 | } 215 | } 216 | } 217 | 218 | mod prelude { 219 | pub use halo2_proofs::{ 220 | circuit::{Layouter, SimpleFloorPlanner, Value}, 221 | halo2curves::{ 222 | bn256::{Bn256, Fr, G1Affine}, 223 | ff::PrimeField, 224 | }, 225 | plonk::*, 226 | poly::{kzg::commitment::ParamsKZG, Rotation}, 227 | }; 228 | pub use rand::{ 229 | rngs::{OsRng, StdRng}, 230 | RngCore, SeedableRng, 231 | }; 232 | pub use std::{ 233 | collections::HashMap, 234 | fs::{create_dir_all, File}, 235 | io::Write, 236 | ops::Range, 237 | }; 238 | 239 | pub fn seeded_std_rng() -> impl RngCore { 240 | StdRng::seed_from_u64(OsRng.next_u64()) 241 | } 242 | } 243 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.77.0" -------------------------------------------------------------------------------- /src/codegen.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{ 2 | evaluator::Evaluator, 3 | template::{Halo2Verifier, Halo2VerifyingKey}, 4 | util::{fr_to_u256, g1_to_u256s, g2_to_u256s, ConstraintSystemMeta, Data, Ptr}, 5 | }; 6 | use halo2_proofs::{ 7 | halo2curves::{bn256, ff::Field}, 8 | plonk::VerifyingKey, 9 | poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG, Rotation}, 10 | }; 11 | use itertools::{chain, Itertools}; 12 | use ruint::aliases::U256; 13 | use std::fmt::{self, Debug}; 14 | 15 | mod evaluator; 16 | mod pcs; 17 | mod template; 18 | pub(crate) mod util; 19 | 20 | pub use pcs::BatchOpenScheme; 21 | 22 | /// Solidity verifier generator for [`halo2`] proof with KZG polynomial commitment scheme on BN254. 23 | #[derive(Debug)] 24 | pub struct SolidityGenerator<'a> { 25 | params: &'a ParamsKZG, 26 | vk: &'a VerifyingKey, 27 | scheme: BatchOpenScheme, 28 | num_instances: usize, 29 | acc_encoding: Option, 30 | meta: ConstraintSystemMeta, 31 | } 32 | 33 | /// KZG accumulator encoding information. 34 | /// Limbs of each field element are assumed to be least significant limb first. 35 | /// 36 | /// Given instances and `AccumulatorEncoding`, the accumulator will be interpreted as below: 37 | /// ```rust 38 | /// use halo2_proofs::halo2curves::{bn256, ff::{Field, PrimeField}, CurveAffine}; 39 | /// 40 | /// fn accumulator_from_limbs( 41 | /// instances: &[bn256::Fr], 42 | /// offset: usize, 43 | /// num_limbs: usize, 44 | /// num_limb_bits: usize, 45 | /// ) -> (bn256::G1Affine, bn256::G1Affine) { 46 | /// let limbs = |offset| &instances[offset..offset + num_limbs]; 47 | /// let acc_lhs_x = fe_from_limbs(limbs(offset), num_limb_bits); 48 | /// let acc_lhs_y = fe_from_limbs(limbs(offset + num_limbs), num_limb_bits); 49 | /// let acc_rhs_x = fe_from_limbs(limbs(offset + 2 * num_limbs), num_limb_bits); 50 | /// let acc_rhs_y = fe_from_limbs(limbs(offset + 3 * num_limbs), num_limb_bits); 51 | /// let acc_lhs = bn256::G1Affine::from_xy(acc_lhs_x, acc_lhs_y).unwrap(); 52 | /// let acc_rhs = bn256::G1Affine::from_xy(acc_rhs_x, acc_rhs_y).unwrap(); 53 | /// (acc_lhs, acc_rhs) 54 | /// } 55 | /// 56 | /// fn fe_from_limbs(limbs: &[bn256::Fr], num_limb_bits: usize) -> bn256::Fq { 57 | /// limbs.iter().rev().fold(bn256::Fq::ZERO, |acc, limb| { 58 | /// acc * bn256::Fq::from(2).pow_vartime([num_limb_bits as u64]) 59 | /// + bn256::Fq::from_repr_vartime(limb.to_repr()).unwrap() 60 | /// }) 61 | /// } 62 | /// ``` 63 | /// 64 | /// In the end of `verifyProof`, the accumulator will be used to do batched pairing with the 65 | /// pairing input of incoming proof. 66 | #[derive(Clone, Copy, Debug)] 67 | pub struct AccumulatorEncoding { 68 | /// Offset of accumulator limbs in instances. 69 | pub offset: usize, 70 | /// Number of limbs per base field element. 71 | pub num_limbs: usize, 72 | /// Number of bits per limb. 73 | pub num_limb_bits: usize, 74 | } 75 | 76 | impl AccumulatorEncoding { 77 | /// Return a new `AccumulatorEncoding`. 78 | pub fn new(offset: usize, num_limbs: usize, num_limb_bits: usize) -> Self { 79 | Self { 80 | offset, 81 | num_limbs, 82 | num_limb_bits, 83 | } 84 | } 85 | } 86 | 87 | impl<'a> SolidityGenerator<'a> { 88 | /// Return a new `SolidityGenerator`. 89 | pub fn new( 90 | params: &'a ParamsKZG, 91 | vk: &'a VerifyingKey, 92 | scheme: BatchOpenScheme, 93 | num_instances: usize, 94 | ) -> Self { 95 | assert_ne!(vk.cs().num_advice_columns(), 0); 96 | assert!( 97 | vk.cs().num_instance_columns() <= 1, 98 | "Multiple instance columns is not yet implemented" 99 | ); 100 | assert!( 101 | !vk.cs() 102 | .instance_queries() 103 | .iter() 104 | .any(|(_, rotation)| *rotation != Rotation::cur()), 105 | "Rotated query to instance column is not yet implemented" 106 | ); 107 | 108 | Self { 109 | params, 110 | vk, 111 | scheme, 112 | num_instances, 113 | acc_encoding: None, 114 | meta: ConstraintSystemMeta::new(vk.cs()), 115 | } 116 | } 117 | 118 | /// Set `AccumulatorEncoding`. 119 | pub fn set_acc_encoding(mut self, acc_encoding: Option) -> Self { 120 | self.acc_encoding = acc_encoding; 121 | self 122 | } 123 | } 124 | 125 | impl<'a> SolidityGenerator<'a> { 126 | /// Render `Halo2Verifier.sol` with verifying key embedded into writer. 127 | pub fn render_into(&self, verifier_writer: &mut impl fmt::Write) -> Result<(), fmt::Error> { 128 | self.generate_verifier(false).render(verifier_writer) 129 | } 130 | 131 | /// Render `Halo2Verifier.sol` with verifying key embedded and return it as `String`. 132 | pub fn render(&self) -> Result { 133 | let mut verifier_output = String::new(); 134 | self.render_into(&mut verifier_output)?; 135 | Ok(verifier_output) 136 | } 137 | 138 | /// Render `Halo2Verifier.sol` and `Halo2VerifyingKey.sol` into writers. 139 | pub fn render_separately_into( 140 | &self, 141 | verifier_writer: &mut impl fmt::Write, 142 | vk_writer: &mut impl fmt::Write, 143 | ) -> Result<(), fmt::Error> { 144 | self.generate_verifier(true).render(verifier_writer)?; 145 | self.generate_vk().render(vk_writer)?; 146 | Ok(()) 147 | } 148 | 149 | /// Render `Halo2Verifier.sol` and `Halo2VerifyingKey.sol` and return them as `String`. 150 | pub fn render_separately(&self) -> Result<(String, String), fmt::Error> { 151 | let mut verifier_output = String::new(); 152 | let mut vk_output = String::new(); 153 | self.render_separately_into(&mut verifier_output, &mut vk_output)?; 154 | Ok((verifier_output, vk_output)) 155 | } 156 | 157 | fn generate_vk(&self) -> Halo2VerifyingKey { 158 | let constants = { 159 | let domain = self.vk.get_domain(); 160 | let vk_digest = fr_to_u256(self.vk.transcript_repr()); 161 | let num_instances = U256::from(self.num_instances); 162 | let k = U256::from(domain.k()); 163 | let n_inv = fr_to_u256(bn256::Fr::from(1 << domain.k()).invert().unwrap()); 164 | let omega = fr_to_u256(domain.get_omega()); 165 | let omega_inv = fr_to_u256(domain.get_omega_inv()); 166 | let omega_inv_to_l = { 167 | let l = self.meta.rotation_last.unsigned_abs() as u64; 168 | fr_to_u256(domain.get_omega_inv().pow_vartime([l])) 169 | }; 170 | let has_accumulator = U256::from(self.acc_encoding.is_some() as usize); 171 | let acc_offset = self 172 | .acc_encoding 173 | .map(|acc_encoding| U256::from(acc_encoding.offset)) 174 | .unwrap_or_default(); 175 | let num_acc_limbs = self 176 | .acc_encoding 177 | .map(|acc_encoding| U256::from(acc_encoding.num_limbs)) 178 | .unwrap_or_default(); 179 | let num_acc_limb_bits = self 180 | .acc_encoding 181 | .map(|acc_encoding| U256::from(acc_encoding.num_limb_bits)) 182 | .unwrap_or_default(); 183 | let g1 = self.params.get_g()[0]; 184 | let g1 = g1_to_u256s(g1); 185 | let g2 = g2_to_u256s(self.params.g2()); 186 | let neg_s_g2 = g2_to_u256s(-self.params.s_g2()); 187 | vec![ 188 | ("vk_digest", vk_digest), 189 | ("num_instances", num_instances), 190 | ("k", k), 191 | ("n_inv", n_inv), 192 | ("omega", omega), 193 | ("omega_inv", omega_inv), 194 | ("omega_inv_to_l", omega_inv_to_l), 195 | ("has_accumulator", has_accumulator), 196 | ("acc_offset", acc_offset), 197 | ("num_acc_limbs", num_acc_limbs), 198 | ("num_acc_limb_bits", num_acc_limb_bits), 199 | ("g1_x", g1[0]), 200 | ("g1_y", g1[1]), 201 | ("g2_x_1", g2[0]), 202 | ("g2_x_2", g2[1]), 203 | ("g2_y_1", g2[2]), 204 | ("g2_y_2", g2[3]), 205 | ("neg_s_g2_x_1", neg_s_g2[0]), 206 | ("neg_s_g2_x_2", neg_s_g2[1]), 207 | ("neg_s_g2_y_1", neg_s_g2[2]), 208 | ("neg_s_g2_y_2", neg_s_g2[3]), 209 | ] 210 | }; 211 | let fixed_comms = chain![self.vk.fixed_commitments()] 212 | .flat_map(g1_to_u256s) 213 | .tuples() 214 | .collect(); 215 | let permutation_comms = chain![self.vk.permutation().commitments()] 216 | .flat_map(g1_to_u256s) 217 | .tuples() 218 | .collect(); 219 | Halo2VerifyingKey { 220 | constants, 221 | fixed_comms, 222 | permutation_comms, 223 | } 224 | } 225 | 226 | fn generate_verifier(&self, separate: bool) -> Halo2Verifier { 227 | let proof_cptr = Ptr::calldata(if separate { 0x84 } else { 0x64 }); 228 | 229 | let vk = self.generate_vk(); 230 | let vk_len = vk.len(); 231 | let vk_mptr = Ptr::memory(self.static_working_memory_size(&vk, proof_cptr)); 232 | let data = Data::new(&self.meta, &vk, vk_mptr, proof_cptr); 233 | 234 | let evaluator = Evaluator::new(self.vk.cs(), &self.meta, &data); 235 | let quotient_eval_numer_computations = chain![ 236 | evaluator.gate_computations(), 237 | evaluator.permutation_computations(), 238 | evaluator.lookup_computations() 239 | ] 240 | .enumerate() 241 | .map(|(idx, (mut lines, var))| { 242 | let line = if idx == 0 { 243 | format!("quotient_eval_numer := {var}") 244 | } else { 245 | format!( 246 | "quotient_eval_numer := addmod(mulmod(quotient_eval_numer, y, r), {var}, r)" 247 | ) 248 | }; 249 | lines.push(line); 250 | lines 251 | }) 252 | .collect(); 253 | 254 | let pcs_computations = self.scheme.computations(&self.meta, &data); 255 | 256 | Halo2Verifier { 257 | scheme: self.scheme, 258 | embedded_vk: (!separate).then_some(vk), 259 | vk_len, 260 | vk_mptr, 261 | num_neg_lagranges: self.meta.rotation_last.unsigned_abs() as usize, 262 | num_advices: self.meta.num_advices(), 263 | num_challenges: self.meta.num_challenges(), 264 | num_rotations: self.meta.num_rotations, 265 | num_evals: self.meta.num_evals, 266 | num_quotients: self.meta.num_quotients, 267 | proof_cptr, 268 | quotient_comm_cptr: data.quotient_comm_cptr, 269 | proof_len: self.meta.proof_len(self.scheme), 270 | challenge_mptr: data.challenge_mptr, 271 | theta_mptr: data.theta_mptr, 272 | quotient_eval_numer_computations, 273 | pcs_computations, 274 | } 275 | } 276 | 277 | fn static_working_memory_size(&self, vk: &Halo2VerifyingKey, proof_cptr: Ptr) -> usize { 278 | let pcs_computation = { 279 | let mock_vk_mptr = Ptr::memory(0x100000); 280 | let mock = Data::new(&self.meta, vk, mock_vk_mptr, proof_cptr); 281 | self.scheme.static_working_memory_size(&self.meta, &mock) 282 | }; 283 | 284 | itertools::max([ 285 | // Keccak256 input (can overwrite vk) 286 | itertools::max(chain![ 287 | self.meta.num_advices().into_iter().map(|n| n * 2 + 1), 288 | [self.meta.num_evals + 1], 289 | ]) 290 | .unwrap() 291 | .saturating_sub(vk.len() / 0x20), 292 | // PCS computation 293 | pcs_computation, 294 | // Pairing 295 | 12, 296 | ]) 297 | .unwrap() 298 | * 0x20 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/codegen/evaluator.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::useless_format)] 2 | 3 | use crate::codegen::util::{code_block, fe_to_u256, ConstraintSystemMeta, Data}; 4 | use halo2_proofs::{ 5 | halo2curves::ff::PrimeField, 6 | plonk::{ 7 | Advice, AdviceQuery, Any, Challenge, ConstraintSystem, Expression, Fixed, FixedQuery, Gate, 8 | InstanceQuery, 9 | }, 10 | }; 11 | use itertools::{chain, izip, Itertools}; 12 | use ruint::aliases::U256; 13 | use std::{cell::RefCell, cmp::Ordering, collections::HashMap, iter}; 14 | 15 | #[derive(Debug)] 16 | pub(crate) struct Evaluator<'a, F: PrimeField> { 17 | cs: &'a ConstraintSystem, 18 | meta: &'a ConstraintSystemMeta, 19 | data: &'a Data, 20 | var_counter: RefCell, 21 | var_cache: RefCell>, 22 | } 23 | 24 | impl<'a, F> Evaluator<'a, F> 25 | where 26 | F: PrimeField, 27 | { 28 | pub(crate) fn new( 29 | cs: &'a ConstraintSystem, 30 | meta: &'a ConstraintSystemMeta, 31 | data: &'a Data, 32 | ) -> Self { 33 | Self { 34 | cs, 35 | meta, 36 | data, 37 | var_counter: Default::default(), 38 | var_cache: Default::default(), 39 | } 40 | } 41 | 42 | pub fn gate_computations(&self) -> Vec<(Vec, String)> { 43 | self.cs 44 | .gates() 45 | .iter() 46 | .flat_map(Gate::polynomials) 47 | .map(|expression| self.evaluate_and_reset(expression)) 48 | .collect() 49 | } 50 | 51 | pub fn permutation_computations(&self) -> Vec<(Vec, String)> { 52 | let Self { meta, data, .. } = self; 53 | let last_chunk_idx = meta.num_permutation_zs - 1; 54 | chain![ 55 | data.permutation_z_evals.first().map(|(z, _, _)| { 56 | vec![ 57 | format!("let l_0 := mload(L_0_MPTR)"), 58 | format!("let eval := addmod(l_0, sub(r, mulmod(l_0, {z}, r)), r)"), 59 | ] 60 | }), 61 | data.permutation_z_evals.last().map(|(z, _, _)| { 62 | let item = "addmod(mulmod(perm_z_last, perm_z_last, r), sub(r, perm_z_last), r)"; 63 | vec![ 64 | format!("let perm_z_last := {z}"), 65 | format!("let eval := mulmod(mload(L_LAST_MPTR), {item}, r)"), 66 | ] 67 | }), 68 | data.permutation_z_evals.iter().tuple_windows().map( 69 | |((_, _, z_i_last), (z_j, _, _))| { 70 | let item = format!("addmod({z_j}, sub(r, {z_i_last}), r)"); 71 | vec![format!("let eval := mulmod(mload(L_0_MPTR), {item}, r)")] 72 | } 73 | ), 74 | izip!( 75 | meta.permutation_columns.chunks(meta.permutation_chunk_len), 76 | &data.permutation_z_evals, 77 | ) 78 | .enumerate() 79 | .map(|(chunk_idx, (columns, evals))| { 80 | let last_column_idx = columns.len() - 1; 81 | chain![ 82 | [ 83 | format!("let gamma := mload(GAMMA_MPTR)"), 84 | format!("let beta := mload(BETA_MPTR)"), 85 | format!("let lhs := {}", evals.1), 86 | format!("let rhs := {}", evals.0), 87 | ], 88 | columns.iter().flat_map(|column| { 89 | let perm_eval = &data.permutation_evals[column]; 90 | let eval = self.eval(*column.column_type(), column.index(), 0); 91 | let item = format!("mulmod(beta, {perm_eval}, r)"); 92 | [format!( 93 | "lhs := mulmod(lhs, addmod(addmod({eval}, {item}, r), gamma, r), r)" 94 | )] 95 | }), 96 | (chunk_idx == 0) 97 | .then(|| "mstore(0x00, mulmod(beta, mload(X_MPTR), r))".to_string()), 98 | columns.iter().enumerate().flat_map(|(idx, column)| { 99 | let eval = self.eval(*column.column_type(), column.index(), 0); 100 | let item = format!("addmod(addmod({eval}, mload(0x00), r), gamma, r)"); 101 | chain![ 102 | [format!("rhs := mulmod(rhs, {item}, r)")], 103 | (!(chunk_idx == last_chunk_idx && idx == last_column_idx)) 104 | .then(|| "mstore(0x00, mulmod(mload(0x00), delta, r))".to_string()), 105 | ] 106 | }), 107 | { 108 | let item = format!("addmod(mload(L_LAST_MPTR), mload(L_BLIND_MPTR), r)"); 109 | let item = format!("sub(r, mulmod(left_sub_right, {item}, r))"); 110 | [ 111 | format!("let left_sub_right := addmod(lhs, sub(r, rhs), r)"), 112 | format!("let eval := addmod(left_sub_right, {item}, r)"), 113 | ] 114 | } 115 | ] 116 | .collect_vec() 117 | }) 118 | ] 119 | .zip(iter::repeat("eval".to_string())) 120 | .collect() 121 | } 122 | 123 | pub fn lookup_computations(&self) -> Vec<(Vec, String)> { 124 | let input_tables = self 125 | .cs 126 | .lookups() 127 | .iter() 128 | .map(|lookup| { 129 | let [(input_lines, inputs), (table_lines, tables)] = 130 | [lookup.input_expressions(), lookup.table_expressions()].map(|expressions| { 131 | let (lines, inputs) = expressions 132 | .iter() 133 | .map(|expression| self.evaluate(expression)) 134 | .fold((Vec::new(), Vec::new()), |mut acc, result| { 135 | acc.0.extend(result.0); 136 | acc.1.push(result.1); 137 | acc 138 | }); 139 | self.reset(); 140 | (lines, inputs) 141 | }); 142 | (input_lines, inputs, table_lines, tables) 143 | }) 144 | .collect_vec(); 145 | izip!(input_tables, &self.data.lookup_evals) 146 | .flat_map(|(input_table, evals)| { 147 | let (input_lines, inputs, table_lines, tables) = input_table; 148 | let (input_0, rest_inputs) = inputs.split_first().unwrap(); 149 | let (table_0, rest_tables) = tables.split_first().unwrap(); 150 | let (z, z_next, p_input, p_input_prev, p_table) = evals; 151 | [ 152 | vec![ 153 | format!("let l_0 := mload(L_0_MPTR)"), 154 | format!("let eval := addmod(l_0, mulmod(l_0, sub(r, {z}), r), r)"), 155 | ], 156 | { 157 | let item = format!("addmod(mulmod({z}, {z}, r), sub(r, {z}), r)"); 158 | vec![ 159 | format!("let l_last := mload(L_LAST_MPTR)"), 160 | format!("let eval := mulmod(l_last, {item}, r)"), 161 | ] 162 | }, 163 | chain![ 164 | ["let theta := mload(THETA_MPTR)", "let input"].map(str::to_string), 165 | code_block::<1, false>(chain![ 166 | input_lines, 167 | [format!("input := {input_0}")], 168 | rest_inputs.iter().map(|input| format!( 169 | "input := addmod(mulmod(input, theta, r), {input}, r)" 170 | )) 171 | ]), 172 | ["let table"].map(str::to_string), 173 | code_block::<1, false>(chain![ 174 | table_lines, 175 | [format!("table := {table_0}")], 176 | rest_tables.iter().map(|table| format!( 177 | "table := addmod(mulmod(table, theta, r), {table}, r)" 178 | )) 179 | ]), 180 | { 181 | let lhs = format!("addmod({p_input}, beta, r)"); 182 | let rhs = format!("addmod({p_table}, gamma, r)"); 183 | let permuted = format!("mulmod({lhs}, {rhs}, r)"); 184 | let input = 185 | "mulmod(addmod(input, beta, r), addmod(table, gamma, r), r)"; 186 | [ 187 | format!("let beta := mload(BETA_MPTR)"), 188 | format!("let gamma := mload(GAMMA_MPTR)"), 189 | format!("let lhs := mulmod({z_next}, {permuted}, r)"), 190 | format!("let rhs := mulmod({z}, {input}, r)"), 191 | ] 192 | }, 193 | { 194 | let l_inactive = "addmod(mload(L_BLIND_MPTR), mload(L_LAST_MPTR), r)"; 195 | let l_active = format!("addmod(1, sub(r, {l_inactive}), r)"); 196 | [format!( 197 | "let eval := mulmod({l_active}, addmod(lhs, sub(r, rhs), r), r)" 198 | )] 199 | }, 200 | ] 201 | .collect_vec(), 202 | { 203 | let l_0 = "mload(L_0_MPTR)"; 204 | let item = format!("addmod({p_input}, sub(r, {p_table}), r)"); 205 | vec![format!("let eval := mulmod({l_0}, {item}, r)")] 206 | }, 207 | { 208 | let l_inactive = "addmod(mload(L_BLIND_MPTR), mload(L_LAST_MPTR), r)"; 209 | let l_active = format!("addmod(1, sub(r, {l_inactive}), r)"); 210 | let lhs = format!("addmod({p_input}, sub(r, {p_table}), r)"); 211 | let rhs = format!("addmod({p_input}, sub(r, {p_input_prev}), r)"); 212 | vec![format!( 213 | "let eval := mulmod({l_active}, mulmod({lhs}, {rhs}, r), r)" 214 | )] 215 | }, 216 | ] 217 | }) 218 | .zip(iter::repeat("eval".to_string())) 219 | .collect_vec() 220 | } 221 | 222 | fn eval(&self, column_type: impl Into, column_index: usize, rotation: i32) -> String { 223 | match column_type.into() { 224 | Any::Advice(_) => self.data.advice_evals[&(column_index, rotation)].to_string(), 225 | Any::Fixed => self.data.fixed_evals[&(column_index, rotation)].to_string(), 226 | Any::Instance => self.data.instance_eval.to_string(), 227 | } 228 | } 229 | 230 | fn reset(&self) { 231 | *self.var_counter.borrow_mut() = Default::default(); 232 | *self.var_cache.borrow_mut() = Default::default(); 233 | } 234 | 235 | fn evaluate_and_reset(&self, expression: &Expression) -> (Vec, String) { 236 | let result = self.evaluate(expression); 237 | self.reset(); 238 | result 239 | } 240 | 241 | fn evaluate(&self, expression: &Expression) -> (Vec, String) { 242 | evaluate( 243 | expression, 244 | &|constant| { 245 | let constant = u256_string(constant); 246 | self.init_var(constant, None) 247 | }, 248 | &|query| { 249 | self.init_var( 250 | self.eval(Fixed, query.column_index(), query.rotation().0), 251 | Some(fixed_eval_var(query)), 252 | ) 253 | }, 254 | &|query| { 255 | self.init_var( 256 | self.eval(Advice::default(), query.column_index(), query.rotation().0), 257 | Some(advice_eval_var(query)), 258 | ) 259 | }, 260 | &|_| self.init_var(self.data.instance_eval, Some("i_eval".to_string())), 261 | &|challenge| { 262 | self.init_var( 263 | self.data.challenges[challenge.index()], 264 | Some(format!("c_{}", challenge.index())), 265 | ) 266 | }, 267 | &|(mut acc, var)| { 268 | let (lines, var) = self.init_var(format!("sub(r, {var})"), None); 269 | acc.extend(lines); 270 | (acc, var) 271 | }, 272 | &|(mut lhs_acc, lhs_var), (rhs_acc, rhs_var)| { 273 | let (lines, var) = self.init_var(format!("addmod({lhs_var}, {rhs_var}, r)"), None); 274 | lhs_acc.extend(rhs_acc); 275 | lhs_acc.extend(lines); 276 | (lhs_acc, var) 277 | }, 278 | &|(mut lhs_acc, lhs_var), (rhs_acc, rhs_var)| { 279 | let (lines, var) = self.init_var(format!("mulmod({lhs_var}, {rhs_var}, r)"), None); 280 | lhs_acc.extend(rhs_acc); 281 | lhs_acc.extend(lines); 282 | (lhs_acc, var) 283 | }, 284 | &|(mut acc, var), scalar| { 285 | let scalar = u256_string(scalar); 286 | let (lines, var) = self.init_var(format!("mulmod({var}, {scalar}, r)"), None); 287 | acc.extend(lines); 288 | (acc, var) 289 | }, 290 | ) 291 | } 292 | 293 | fn init_var(&self, value: impl ToString, var: Option) -> (Vec, String) { 294 | let value = value.to_string(); 295 | if self.var_cache.borrow().contains_key(&value) { 296 | (vec![], self.var_cache.borrow()[&value].clone()) 297 | } else { 298 | let var = var.unwrap_or_else(|| self.next_var()); 299 | self.var_cache 300 | .borrow_mut() 301 | .insert(value.clone(), var.clone()); 302 | (vec![format!("let {var} := {value}")], var) 303 | } 304 | } 305 | 306 | fn next_var(&self) -> String { 307 | let count = *self.var_counter.borrow(); 308 | *self.var_counter.borrow_mut() += 1; 309 | format!("var{count}") 310 | } 311 | } 312 | 313 | fn u256_string(value: U256) -> String { 314 | if value.bit_len() < 64 { 315 | format!("0x{:x}", value.as_limbs()[0]) 316 | } else { 317 | format!("0x{value:x}") 318 | } 319 | } 320 | 321 | fn fixed_eval_var(fixed_query: FixedQuery) -> String { 322 | column_eval_var("f", fixed_query.column_index(), fixed_query.rotation().0) 323 | } 324 | 325 | fn advice_eval_var(advice_query: AdviceQuery) -> String { 326 | column_eval_var("a", advice_query.column_index(), advice_query.rotation().0) 327 | } 328 | 329 | fn column_eval_var(prefix: &'static str, column_index: usize, rotation: i32) -> String { 330 | match rotation.cmp(&0) { 331 | Ordering::Less => format!("{prefix}_{column_index}_prev_{}", rotation.abs()), 332 | Ordering::Equal => format!("{prefix}_{column_index}"), 333 | Ordering::Greater => format!("{prefix}_{column_index}_next_{rotation}"), 334 | } 335 | } 336 | 337 | #[allow(clippy::too_many_arguments)] 338 | fn evaluate( 339 | expression: &Expression, 340 | constant: &impl Fn(U256) -> T, 341 | fixed: &impl Fn(FixedQuery) -> T, 342 | advice: &impl Fn(AdviceQuery) -> T, 343 | instance: &impl Fn(InstanceQuery) -> T, 344 | challenge: &impl Fn(Challenge) -> T, 345 | negated: &impl Fn(T) -> T, 346 | sum: &impl Fn(T, T) -> T, 347 | product: &impl Fn(T, T) -> T, 348 | scaled: &impl Fn(T, U256) -> T, 349 | ) -> T 350 | where 351 | F: PrimeField, 352 | { 353 | let evaluate = |expr| { 354 | evaluate( 355 | expr, constant, fixed, advice, instance, challenge, negated, sum, product, scaled, 356 | ) 357 | }; 358 | match expression { 359 | Expression::Constant(scalar) => constant(fe_to_u256(*scalar)), 360 | Expression::Selector(_) => unreachable!(), 361 | Expression::Fixed(query) => fixed(*query), 362 | Expression::Advice(query) => advice(*query), 363 | Expression::Instance(query) => instance(*query), 364 | Expression::Challenge(value) => challenge(*value), 365 | Expression::Negated(value) => negated(evaluate(value)), 366 | Expression::Sum(lhs, rhs) => sum(evaluate(lhs), evaluate(rhs)), 367 | Expression::Product(lhs, rhs) => product(evaluate(lhs), evaluate(rhs)), 368 | Expression::Scaled(value, scalar) => scaled(evaluate(value), fe_to_u256(*scalar)), 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /src/codegen/pcs.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::util::{ConstraintSystemMeta, Data, EcPoint, Word}; 2 | use itertools::{chain, izip}; 3 | 4 | mod bdfg21; 5 | mod gwc19; 6 | 7 | /// KZG batch open schemes in `halo2`. 8 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 9 | pub enum BatchOpenScheme { 10 | /// Batch open scheme in [Plonk] paper. 11 | /// Corresponding to `halo2_proofs::poly::kzg::multiopen::ProverGWC` 12 | /// 13 | /// [Plonk]: https://eprint.iacr.org/2019/953.pdf 14 | Gwc19, 15 | /// Batch open scheme in [BDFG21] paper. 16 | /// Corresponding to `halo2_proofs::poly::kzg::multiopen::ProverSHPLONK` 17 | /// 18 | /// [BDFG21]: https://eprint.iacr.org/2020/081.pdf 19 | Bdfg21, 20 | } 21 | 22 | impl BatchOpenScheme { 23 | pub(crate) fn static_working_memory_size( 24 | &self, 25 | meta: &ConstraintSystemMeta, 26 | data: &Data, 27 | ) -> usize { 28 | match self { 29 | Self::Bdfg21 => bdfg21::static_working_memory_size(meta, data), 30 | Self::Gwc19 => gwc19::static_working_memory_size(meta, data), 31 | } 32 | } 33 | 34 | pub(crate) fn computations( 35 | &self, 36 | meta: &ConstraintSystemMeta, 37 | data: &Data, 38 | ) -> Vec> { 39 | match self { 40 | Self::Bdfg21 => bdfg21::computations(meta, data), 41 | Self::Gwc19 => gwc19::computations(meta, data), 42 | } 43 | } 44 | } 45 | 46 | #[derive(Debug)] 47 | pub(crate) struct Query { 48 | comm: EcPoint, 49 | rot: i32, 50 | eval: Word, 51 | } 52 | 53 | impl Query { 54 | fn new(comm: EcPoint, rot: i32, eval: Word) -> Self { 55 | Self { comm, rot, eval } 56 | } 57 | } 58 | 59 | pub(crate) fn queries(meta: &ConstraintSystemMeta, data: &Data) -> Vec { 60 | chain![ 61 | meta.advice_queries.iter().map(|query| { 62 | let comm = data.advice_comms[query.0]; 63 | let eval = data.advice_evals[query]; 64 | Query::new(comm, query.1, eval) 65 | }), 66 | izip!(&data.permutation_z_comms, &data.permutation_z_evals).flat_map(|(&comm, evals)| { 67 | [Query::new(comm, 0, evals.0), Query::new(comm, 1, evals.1)] 68 | }), 69 | izip!(&data.permutation_z_comms, &data.permutation_z_evals) 70 | .rev() 71 | .skip(1) 72 | .map(|(&comm, evals)| Query::new(comm, meta.rotation_last, evals.2)), 73 | izip!( 74 | &data.lookup_permuted_comms, 75 | &data.lookup_z_comms, 76 | &data.lookup_evals 77 | ) 78 | .flat_map(|(permuted_comms, &z_comm, evals)| { 79 | [ 80 | Query::new(z_comm, 0, evals.0), 81 | Query::new(permuted_comms.0, 0, evals.2), 82 | Query::new(permuted_comms.1, 0, evals.4), 83 | Query::new(permuted_comms.0, -1, evals.3), 84 | Query::new(z_comm, 1, evals.1), 85 | ] 86 | }), 87 | meta.fixed_queries.iter().map(|query| { 88 | let comm = data.fixed_comms[query.0]; 89 | let eval = data.fixed_evals[query]; 90 | Query::new(comm, query.1, eval) 91 | }), 92 | meta.permutation_columns.iter().map(|column| { 93 | let comm = data.permutation_comms[column]; 94 | let eval = data.permutation_evals[column]; 95 | Query::new(comm, 0, eval) 96 | }), 97 | [ 98 | Query::new(data.computed_quotient_comm, 0, data.computed_quotient_eval), 99 | Query::new(data.random_comm, 0, data.random_eval), 100 | ] 101 | ] 102 | .collect() 103 | } 104 | -------------------------------------------------------------------------------- /src/codegen/pcs/bdfg21.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::useless_format)] 2 | 3 | use crate::codegen::{ 4 | pcs::{queries, Query}, 5 | util::{ 6 | for_loop, group_backward_adjacent_ec_points, group_backward_adjacent_words, 7 | ConstraintSystemMeta, Data, EcPoint, Location, Ptr, Word, 8 | }, 9 | }; 10 | use itertools::{chain, izip, Itertools}; 11 | use std::collections::{BTreeMap, BTreeSet}; 12 | 13 | pub(super) fn static_working_memory_size(meta: &ConstraintSystemMeta, data: &Data) -> usize { 14 | let (superset, sets) = rotation_sets(&queries(meta, data)); 15 | let num_coeffs = sets.iter().map(|set| set.rots().len()).sum::(); 16 | 2 * (1 + num_coeffs) + 6 + 2 * superset.len() + 1 + 3 * sets.len() 17 | } 18 | 19 | pub(super) fn computations(meta: &ConstraintSystemMeta, data: &Data) -> Vec> { 20 | let (superset, sets) = rotation_sets(&queries(meta, data)); 21 | let min_rot = *superset.first().unwrap(); 22 | let max_rot = *superset.last().unwrap(); 23 | let num_coeffs = sets.iter().map(|set| set.rots().len()).sum::(); 24 | 25 | let w = EcPoint::from(data.w_cptr); 26 | let w_prime = EcPoint::from(data.w_cptr + 2); 27 | 28 | let diff_0 = Word::from(Ptr::memory(0x00)); 29 | let coeffs = sets 30 | .iter() 31 | .scan(diff_0.ptr() + 1, |state, set| { 32 | let ptrs = Word::range(*state).take(set.rots().len()).collect_vec(); 33 | *state = *state + set.rots().len(); 34 | Some(ptrs) 35 | }) 36 | .collect_vec(); 37 | 38 | let first_batch_invert_end = diff_0.ptr() + 1 + num_coeffs; 39 | let second_batch_invert_end = diff_0.ptr() + sets.len(); 40 | let free_mptr = diff_0.ptr() + 2 * (1 + num_coeffs) + 6; 41 | 42 | let point_mptr = free_mptr; 43 | let mu_minus_point_mptr = point_mptr + superset.len(); 44 | let vanishing_0_mptr = mu_minus_point_mptr + superset.len(); 45 | let diff_mptr = vanishing_0_mptr + 1; 46 | let r_eval_mptr = diff_mptr + sets.len(); 47 | let sum_mptr = r_eval_mptr + sets.len(); 48 | 49 | let point_vars = 50 | izip!(&superset, (0..).map(|idx| format!("point_{idx}"))).collect::>(); 51 | let points = izip!(&superset, Word::range(point_mptr)).collect::>(); 52 | let mu_minus_points = 53 | izip!(&superset, Word::range(mu_minus_point_mptr)).collect::>(); 54 | let vanishing_0 = Word::from(vanishing_0_mptr); 55 | let diffs = Word::range(diff_mptr).take(sets.len()).collect_vec(); 56 | let r_evals = Word::range(r_eval_mptr).take(sets.len()).collect_vec(); 57 | let sums = Word::range(sum_mptr).take(sets.len()).collect_vec(); 58 | 59 | let point_computations = chain![ 60 | [ 61 | "let x := mload(X_MPTR)", 62 | "let omega := mload(OMEGA_MPTR)", 63 | "let omega_inv := mload(OMEGA_INV_MPTR)", 64 | "let x_pow_of_omega := mulmod(x, omega, r)" 65 | ] 66 | .map(str::to_string), 67 | (1..=max_rot).flat_map(|rot| { 68 | chain![ 69 | points 70 | .get(&rot) 71 | .map(|point| format!("mstore({}, x_pow_of_omega)", point.ptr())), 72 | (rot != max_rot) 73 | .then(|| "x_pow_of_omega := mulmod(x_pow_of_omega, omega, r)".to_string()) 74 | ] 75 | }), 76 | [ 77 | format!("mstore({}, x)", points[&0].ptr()), 78 | format!("x_pow_of_omega := mulmod(x, omega_inv, r)") 79 | ], 80 | (min_rot..0).rev().flat_map(|rot| { 81 | chain![ 82 | points 83 | .get(&rot) 84 | .map(|point| format!("mstore({}, x_pow_of_omega)", point.ptr())), 85 | (rot != min_rot).then(|| { 86 | "x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r)".to_string() 87 | }) 88 | ] 89 | }) 90 | ] 91 | .collect_vec(); 92 | 93 | let vanishing_computations = chain![ 94 | ["let mu := mload(MU_MPTR)".to_string()], 95 | { 96 | let mptr = mu_minus_points.first_key_value().unwrap().1.ptr(); 97 | let mptr_end = mptr + mu_minus_points.len(); 98 | for_loop( 99 | [ 100 | format!("let mptr := {mptr}"), 101 | format!("let mptr_end := {mptr_end}"), 102 | format!("let point_mptr := {free_mptr}"), 103 | ], 104 | "lt(mptr, mptr_end)", 105 | [ 106 | "mptr := add(mptr, 0x20)", 107 | "point_mptr := add(point_mptr, 0x20)", 108 | ], 109 | ["mstore(mptr, addmod(mu, sub(r, mload(point_mptr)), r))"], 110 | ) 111 | }, 112 | ["let s".to_string()], 113 | chain![ 114 | [format!( 115 | "s := {}", 116 | mu_minus_points[sets[0].rots().first().unwrap()] 117 | )], 118 | chain![sets[0].rots().iter().skip(1)] 119 | .map(|rot| { format!("s := mulmod(s, {}, r)", mu_minus_points[rot]) }), 120 | [format!("mstore({}, s)", vanishing_0.ptr())], 121 | ], 122 | ["let diff".to_string()], 123 | izip!(0.., &sets, &diffs).flat_map(|(set_idx, set, diff)| { 124 | chain![ 125 | [set.diffs() 126 | .first() 127 | .map(|rot| format!("diff := {}", mu_minus_points[rot])) 128 | .unwrap_or_else(|| "diff := 1".to_string())], 129 | chain![set.diffs().iter().skip(1)] 130 | .map(|rot| { format!("diff := mulmod(diff, {}, r)", mu_minus_points[rot]) }), 131 | [format!("mstore({}, diff)", diff.ptr())], 132 | (set_idx == 0).then(|| format!("mstore({}, diff)", diff_0.ptr())), 133 | ] 134 | }) 135 | ] 136 | .collect_vec(); 137 | 138 | let coeff_computations = izip!(&sets, &coeffs) 139 | .map(|(set, coeffs)| { 140 | let coeff_points = set 141 | .rots() 142 | .iter() 143 | .map(|rot| &point_vars[rot]) 144 | .enumerate() 145 | .map(|(i, rot_i)| { 146 | set.rots() 147 | .iter() 148 | .map(|rot| &point_vars[rot]) 149 | .enumerate() 150 | .filter_map(|(j, rot_j)| (i != j).then_some((rot_i, rot_j))) 151 | .collect_vec() 152 | }) 153 | .collect_vec(); 154 | chain![ 155 | set.rots() 156 | .iter() 157 | .map(|rot| format!("let {} := {}", &point_vars[rot], points[rot])), 158 | ["let coeff".to_string()], 159 | izip!(set.rots(), &coeff_points, coeffs).flat_map( 160 | |(rot_i, coeff_points, coeff)| chain![ 161 | [coeff_points 162 | .first() 163 | .map(|(point_i, point_j)| { 164 | format!("coeff := addmod({point_i}, sub(r, {point_j}), r)") 165 | }) 166 | .unwrap_or_else(|| "coeff := 1".to_string())], 167 | coeff_points.iter().skip(1).map(|(point_i, point_j)| { 168 | let item = format!("addmod({point_i}, sub(r, {point_j}), r)"); 169 | format!("coeff := mulmod(coeff, {item}, r)") 170 | }), 171 | [ 172 | format!("coeff := mulmod(coeff, {}, r)", mu_minus_points[rot_i]), 173 | format!("mstore({}, coeff)", coeff.ptr()) 174 | ], 175 | ] 176 | ) 177 | ] 178 | .collect_vec() 179 | }) 180 | .collect_vec(); 181 | 182 | let normalized_coeff_computations = chain![ 183 | [ 184 | format!("success := batch_invert(success, 0, {first_batch_invert_end}, r)"), 185 | format!("let diff_0_inv := {diff_0}"), 186 | format!("mstore({}, diff_0_inv)", diffs[0].ptr()), 187 | ], 188 | for_loop( 189 | [ 190 | format!("let mptr := {}", diffs[0].ptr() + 1), 191 | format!("let mptr_end := {}", diffs[0].ptr() + sets.len()), 192 | ], 193 | "lt(mptr, mptr_end)", 194 | ["mptr := add(mptr, 0x20)"], 195 | ["mstore(mptr, mulmod(mload(mptr), diff_0_inv, r))"], 196 | ), 197 | ] 198 | .collect_vec(); 199 | 200 | let r_evals_computations = izip!(0.., &sets, &coeffs, &diffs, &r_evals).map( 201 | |(set_idx, set, coeffs, set_coeff, r_eval)| { 202 | let is_single_rot_set = set.rots().len() == 1; 203 | chain![ 204 | is_single_rot_set.then(|| format!("let coeff := {}", coeffs[0])), 205 | ["let zeta := mload(ZETA_MPTR)", "let r_eval"].map(str::to_string), 206 | if is_single_rot_set { 207 | let evals = set.evals().iter().map(|evals| evals[0]).collect_vec(); 208 | let eval_groups = group_backward_adjacent_words(evals.iter().rev().skip(1)); 209 | chain![ 210 | evals 211 | .last() 212 | .map(|eval| format!("r_eval := mulmod(coeff, {eval}, r)")), 213 | eval_groups.iter().flat_map(|(loc, evals)| { 214 | if evals.len() < 3 { 215 | evals 216 | .iter() 217 | .flat_map(|eval| { 218 | let item = format!("mulmod(coeff, {eval}, r)"); 219 | [ 220 | format!("r_eval := mulmod(r_eval, zeta, r)"), 221 | format!("r_eval := addmod(r_eval, {item}, r)"), 222 | ] 223 | }) 224 | .collect_vec() 225 | } else { 226 | assert_eq!(*loc, Location::Calldata); 227 | let item = "mulmod(coeff, calldataload(cptr), r)"; 228 | for_loop( 229 | [ 230 | format!("let cptr := {}", evals[0].ptr()), 231 | format!("let cptr_end := {}", evals[0].ptr() - evals.len()), 232 | ], 233 | "lt(cptr_end, cptr)", 234 | ["cptr := sub(cptr, 0x20)"], 235 | [format!( 236 | "r_eval := addmod(mulmod(r_eval, zeta, r), {item}, r)" 237 | )], 238 | ) 239 | } 240 | }) 241 | ] 242 | .collect_vec() 243 | } else { 244 | chain![set.evals().iter().enumerate().rev()] 245 | .flat_map(|(idx, evals)| { 246 | chain![ 247 | izip!(evals, coeffs).map(|(eval, coeff)| { 248 | let item = format!("mulmod({coeff}, {eval}, r)"); 249 | format!("r_eval := addmod(r_eval, {item}, r)") 250 | }), 251 | (idx != 0).then(|| format!("r_eval := mulmod(r_eval, zeta, r)")), 252 | ] 253 | }) 254 | .collect_vec() 255 | }, 256 | (set_idx != 0).then(|| format!("r_eval := mulmod(r_eval, {set_coeff}, r)")), 257 | [format!("mstore({}, r_eval)", r_eval.ptr())], 258 | ] 259 | .collect_vec() 260 | }, 261 | ); 262 | 263 | let coeff_sums_computation = izip!(&coeffs, &sums).map(|(coeffs, sum)| { 264 | let (coeff_0, rest_coeffs) = coeffs.split_first().unwrap(); 265 | chain![ 266 | [format!("let sum := {coeff_0}")], 267 | rest_coeffs 268 | .iter() 269 | .map(|coeff_mptr| format!("sum := addmod(sum, {coeff_mptr}, r)")), 270 | [format!("mstore({}, sum)", sum.ptr())], 271 | ] 272 | .collect_vec() 273 | }); 274 | 275 | let r_eval_computations = chain![ 276 | for_loop( 277 | [ 278 | format!("let mptr := 0x00"), 279 | format!("let mptr_end := {second_batch_invert_end}"), 280 | format!("let sum_mptr := {}", sums[0].ptr()), 281 | ], 282 | "lt(mptr, mptr_end)", 283 | ["mptr := add(mptr, 0x20)", "sum_mptr := add(sum_mptr, 0x20)"], 284 | ["mstore(mptr, mload(sum_mptr))"], 285 | ), 286 | [ 287 | format!("success := batch_invert(success, 0, {second_batch_invert_end}, r)"), 288 | format!( 289 | "let r_eval := mulmod(mload({}), {}, r)", 290 | second_batch_invert_end - 1, 291 | r_evals.last().unwrap() 292 | ) 293 | ], 294 | for_loop( 295 | [ 296 | format!("let sum_inv_mptr := {}", second_batch_invert_end - 2), 297 | format!("let sum_inv_mptr_end := {second_batch_invert_end}"), 298 | format!("let r_eval_mptr := {}", r_evals[r_evals.len() - 2].ptr()), 299 | ], 300 | "lt(sum_inv_mptr, sum_inv_mptr_end)", 301 | [ 302 | "sum_inv_mptr := sub(sum_inv_mptr, 0x20)", 303 | "r_eval_mptr := sub(r_eval_mptr, 0x20)" 304 | ], 305 | [ 306 | "r_eval := mulmod(r_eval, mload(NU_MPTR), r)", 307 | "r_eval := addmod(r_eval, mulmod(mload(sum_inv_mptr), mload(r_eval_mptr), r), r)" 308 | ], 309 | ), 310 | ["mstore(G1_SCALAR_MPTR, sub(r, r_eval))".to_string()], 311 | ] 312 | .collect_vec(); 313 | 314 | let pairing_input_computations = chain![ 315 | ["let zeta := mload(ZETA_MPTR)", "let nu := mload(NU_MPTR)"].map(str::to_string), 316 | izip!(0.., &sets, &diffs).flat_map(|(set_idx, set, set_coeff)| { 317 | let is_first_set = set_idx == 0; 318 | let is_last_set = set_idx == sets.len() - 1; 319 | let ec_add = &format!("ec_add_{}", if is_first_set { "acc" } else { "tmp" }); 320 | let ec_mul = &format!("ec_mul_{}", if is_first_set { "acc" } else { "tmp" }); 321 | let acc_x = Ptr::memory(0x00) + if is_first_set { 0 } else { 4 }; 322 | let acc_y = acc_x + 1; 323 | let comm_groups = group_backward_adjacent_ec_points(set.comms().iter().rev().skip(1)); 324 | 325 | chain![ 326 | set.comms() 327 | .last() 328 | .map(|comm| { 329 | [ 330 | format!("mstore({acc_x}, {})", comm.x()), 331 | format!("mstore({acc_y}, {})", comm.y()), 332 | ] 333 | }) 334 | .into_iter() 335 | .flatten(), 336 | comm_groups.into_iter().flat_map(move |(loc, comms)| { 337 | if comms.len() < 3 { 338 | comms 339 | .iter() 340 | .flat_map(|comm| { 341 | let (x, y) = (comm.x(), comm.y()); 342 | [ 343 | format!("success := {ec_mul}(success, zeta)"), 344 | format!("success := {ec_add}(success, {x}, {y})"), 345 | ] 346 | }) 347 | .collect_vec() 348 | } else { 349 | let ptr = comms.first().unwrap().x().ptr(); 350 | let ptr_end = ptr - 2 * comms.len(); 351 | let x = Word::from(Ptr::new(loc, "ptr")); 352 | let y = Word::from(Ptr::new(loc, "add(ptr, 0x20)")); 353 | for_loop( 354 | [ 355 | format!("let ptr := {ptr}"), 356 | format!("let ptr_end := {ptr_end}"), 357 | ], 358 | "lt(ptr_end, ptr)", 359 | ["ptr := sub(ptr, 0x40)"], 360 | [ 361 | format!("success := {ec_mul}(success, zeta)"), 362 | format!("success := {ec_add}(success, {x}, {y})"), 363 | ], 364 | ) 365 | } 366 | }), 367 | (!is_first_set) 368 | .then(|| { 369 | let scalar = format!("mulmod(nu, {set_coeff}, r)"); 370 | chain![ 371 | [ 372 | format!("success := ec_mul_tmp(success, {scalar})"), 373 | format!("success := ec_add_acc(success, mload(0x80), mload(0xa0))"), 374 | ], 375 | (!is_last_set).then(|| format!("nu := mulmod(nu, mload(NU_MPTR), r)")) 376 | ] 377 | }) 378 | .into_iter() 379 | .flatten(), 380 | ] 381 | .collect_vec() 382 | }), 383 | [ 384 | format!("mstore(0x80, mload(G1_X_MPTR))"), 385 | format!("mstore(0xa0, mload(G1_Y_MPTR))"), 386 | format!("success := ec_mul_tmp(success, mload(G1_SCALAR_MPTR))"), 387 | format!("success := ec_add_acc(success, mload(0x80), mload(0xa0))"), 388 | format!("mstore(0x80, {})", w.x()), 389 | format!("mstore(0xa0, {})", w.y()), 390 | format!("success := ec_mul_tmp(success, sub(r, {vanishing_0}))"), 391 | format!("success := ec_add_acc(success, mload(0x80), mload(0xa0))"), 392 | format!("mstore(0x80, {})", w_prime.x()), 393 | format!("mstore(0xa0, {})", w_prime.y()), 394 | format!("success := ec_mul_tmp(success, mload(MU_MPTR))"), 395 | format!("success := ec_add_acc(success, mload(0x80), mload(0xa0))"), 396 | format!("mstore(PAIRING_LHS_X_MPTR, mload(0x00))"), 397 | format!("mstore(PAIRING_LHS_Y_MPTR, mload(0x20))"), 398 | format!("mstore(PAIRING_RHS_X_MPTR, {})", w_prime.x()), 399 | format!("mstore(PAIRING_RHS_Y_MPTR, {})", w_prime.y()), 400 | ], 401 | ] 402 | .collect_vec(); 403 | 404 | chain![ 405 | [point_computations, vanishing_computations], 406 | coeff_computations, 407 | [normalized_coeff_computations], 408 | r_evals_computations, 409 | coeff_sums_computation, 410 | [r_eval_computations, pairing_input_computations], 411 | ] 412 | .collect_vec() 413 | } 414 | 415 | #[derive(Debug)] 416 | struct RotationSet { 417 | rots: BTreeSet, 418 | diffs: BTreeSet, 419 | comms: Vec, 420 | evals: Vec>, 421 | } 422 | 423 | impl RotationSet { 424 | fn rots(&self) -> &BTreeSet { 425 | &self.rots 426 | } 427 | 428 | fn diffs(&self) -> &BTreeSet { 429 | &self.diffs 430 | } 431 | 432 | fn comms(&self) -> &[EcPoint] { 433 | &self.comms 434 | } 435 | 436 | fn evals(&self) -> &[Vec] { 437 | &self.evals 438 | } 439 | } 440 | 441 | fn rotation_sets(queries: &[Query]) -> (BTreeSet, Vec) { 442 | let mut superset = BTreeSet::new(); 443 | let comm_queries = queries.iter().fold( 444 | Vec::<(EcPoint, BTreeMap)>::new(), 445 | |mut comm_queries, query| { 446 | superset.insert(query.rot); 447 | if let Some(pos) = comm_queries 448 | .iter() 449 | .position(|(comm, _)| comm == &query.comm) 450 | { 451 | let (_, queries) = &mut comm_queries[pos]; 452 | assert!(!queries.contains_key(&query.rot)); 453 | queries.insert(query.rot, query.eval); 454 | } else { 455 | comm_queries.push((query.comm, BTreeMap::from_iter([(query.rot, query.eval)]))); 456 | } 457 | comm_queries 458 | }, 459 | ); 460 | let superset = superset; 461 | let sets = 462 | comm_queries 463 | .into_iter() 464 | .fold(Vec::::new(), |mut sets, (comm, queries)| { 465 | if let Some(pos) = sets 466 | .iter() 467 | .position(|set| itertools::equal(&set.rots, queries.keys())) 468 | { 469 | let set = &mut sets[pos]; 470 | if !set.comms.contains(&comm) { 471 | set.comms.push(comm); 472 | set.evals.push(queries.into_values().collect_vec()); 473 | } 474 | } else { 475 | let diffs = BTreeSet::from_iter( 476 | superset 477 | .iter() 478 | .filter(|rot| !queries.contains_key(rot)) 479 | .copied(), 480 | ); 481 | let set = RotationSet { 482 | rots: BTreeSet::from_iter(queries.keys().copied()), 483 | diffs, 484 | comms: vec![comm], 485 | evals: vec![queries.into_values().collect()], 486 | }; 487 | sets.push(set); 488 | } 489 | sets 490 | }); 491 | (superset, sets) 492 | } 493 | -------------------------------------------------------------------------------- /src/codegen/pcs/gwc19.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::useless_format)] 2 | 3 | use crate::codegen::{ 4 | pcs::{queries, Query}, 5 | util::{ 6 | for_loop, group_backward_adjacent_ec_points, group_backward_adjacent_words, 7 | ConstraintSystemMeta, Data, EcPoint, Location, Ptr, Word, 8 | }, 9 | }; 10 | use itertools::{chain, izip, Itertools}; 11 | use std::collections::BTreeMap; 12 | 13 | pub(super) fn static_working_memory_size(meta: &ConstraintSystemMeta, _: &Data) -> usize { 14 | 0x100 + meta.num_rotations * 0x40 15 | } 16 | 17 | pub(super) fn computations(meta: &ConstraintSystemMeta, data: &Data) -> Vec> { 18 | let sets = rotation_sets(&queries(meta, data)); 19 | let rots = sets.iter().map(|set| set.rot).collect_vec(); 20 | let (min_rot, max_rot) = rots 21 | .iter() 22 | .copied() 23 | .minmax() 24 | .into_option() 25 | .unwrap_or_default(); 26 | 27 | let ws = EcPoint::range(data.w_cptr).take(sets.len()).collect_vec(); 28 | 29 | let point_w_mptr = Ptr::memory(0x100); 30 | let point_ws = izip!(rots, EcPoint::range(point_w_mptr)).collect::>(); 31 | 32 | let eval_computations = { 33 | chain![ 34 | [ 35 | "let nu := mload(NU_MPTR)", 36 | "let mu := mload(MU_MPTR)", 37 | "let eval_acc", 38 | "let eval_tmp", 39 | ] 40 | .map(str::to_string), 41 | sets.iter().enumerate().rev().flat_map(|(set_idx, set)| { 42 | let is_last_set = set_idx == sets.len() - 1; 43 | let eval_acc = &format!("eval_{}", if is_last_set { "acc" } else { "tmp" }); 44 | let eval_groups = group_backward_adjacent_words(set.evals().iter().rev().skip(1)); 45 | 46 | chain![ 47 | set.evals() 48 | .last() 49 | .map(|eval| format!("{eval_acc} := {}", eval)), 50 | eval_groups.iter().flat_map(|(loc, evals)| { 51 | if evals.len() < 3 { 52 | evals 53 | .iter() 54 | .map(|eval| { 55 | format!( 56 | "{eval_acc} := addmod(mulmod({eval_acc}, nu, r), {eval}, r)" 57 | ) 58 | }) 59 | .collect_vec() 60 | } else { 61 | assert_eq!(*loc, Location::Calldata); 62 | let eval = "calldataload(cptr)"; 63 | for_loop( 64 | [ 65 | format!("let cptr := {}", evals[0].ptr()), 66 | format!("let cptr_end := {}", evals[0].ptr() - evals.len()), 67 | ], 68 | "lt(cptr_end, cptr)", 69 | ["cptr := sub(cptr, 0x20)"], 70 | [format!( 71 | "{eval_acc} := addmod(mulmod({eval_acc}, nu, r), {eval}, r)" 72 | )], 73 | ) 74 | } 75 | }), 76 | (!is_last_set) 77 | .then_some([ 78 | "eval_acc := mulmod(eval_acc, mu, r)", 79 | "eval_acc := addmod(eval_acc, eval_tmp, r)", 80 | ]) 81 | .into_iter() 82 | .flatten() 83 | .map(str::to_string), 84 | ] 85 | .collect_vec() 86 | }), 87 | ["mstore(G1_SCALAR_MPTR, sub(r, eval_acc))".to_string()], 88 | ] 89 | .collect_vec() 90 | }; 91 | 92 | let point_computations = chain![ 93 | [ 94 | "let x := mload(X_MPTR)", 95 | "let omega := mload(OMEGA_MPTR)", 96 | "let omega_inv := mload(OMEGA_INV_MPTR)", 97 | "let x_pow_of_omega := mulmod(x, omega, r)" 98 | ] 99 | .map(str::to_string), 100 | (1..=max_rot).flat_map(|rot| { 101 | chain![ 102 | point_ws 103 | .get(&rot) 104 | .map(|point| format!("mstore({}, x_pow_of_omega)", point.x().ptr())), 105 | (rot != max_rot) 106 | .then(|| "x_pow_of_omega := mulmod(x_pow_of_omega, omega, r)".to_string()) 107 | ] 108 | }), 109 | [ 110 | format!("mstore({}, x)", point_ws[&0].x().ptr()), 111 | format!("x_pow_of_omega := mulmod(x, omega_inv, r)") 112 | ], 113 | (min_rot..0).rev().flat_map(|rot| { 114 | chain![ 115 | point_ws 116 | .get(&rot) 117 | .map(|point| format!("mstore({}, x_pow_of_omega)", point.x().ptr())), 118 | (rot != min_rot).then(|| { 119 | "x_pow_of_omega := mulmod(x_pow_of_omega, omega_inv, r)".to_string() 120 | }) 121 | ] 122 | }) 123 | ] 124 | .collect_vec(); 125 | 126 | let point_w_computations = for_loop( 127 | [ 128 | format!("let cptr := {}", data.w_cptr), 129 | format!("let mptr := {point_w_mptr}"), 130 | format!("let mptr_end := {}", point_w_mptr + 2 * sets.len()), 131 | ], 132 | "lt(mptr, mptr_end)".to_string(), 133 | ["mptr := add(mptr, 0x40)", "cptr := add(cptr, 0x40)"].map(str::to_string), 134 | [ 135 | "mstore(0x00, calldataload(cptr))", 136 | "mstore(0x20, calldataload(add(cptr, 0x20)))", 137 | "success := ec_mul_acc(success, mload(mptr))", 138 | "mstore(mptr, mload(0x00))", 139 | "mstore(add(mptr, 0x20), mload(0x20))", 140 | ] 141 | .map(str::to_string), 142 | ); 143 | 144 | let pairing_lhs_computations = chain![ 145 | ["let nu := mload(NU_MPTR)", "let mu := mload(MU_MPTR)"].map(str::to_string), 146 | sets.iter().enumerate().rev().flat_map(|(set_idx, set)| { 147 | let is_last_set = set_idx == sets.len() - 1; 148 | let ec_add = &format!("ec_add_{}", if is_last_set { "acc" } else { "tmp" }); 149 | let ec_mul = &format!("ec_mul_{}", if is_last_set { "acc" } else { "tmp" }); 150 | let acc_x = Ptr::memory(0x00) + if is_last_set { 0 } else { 4 }; 151 | let acc_y = acc_x + 1; 152 | let point_w = &point_ws[&set.rot]; 153 | let comm_groups = group_backward_adjacent_ec_points(set.comms().iter().rev().skip(1)); 154 | 155 | chain![ 156 | set.comms() 157 | .last() 158 | .map(|comm| { 159 | [ 160 | format!("mstore({acc_x}, {})", comm.x()), 161 | format!("mstore({acc_y}, {})", comm.y()), 162 | ] 163 | }) 164 | .into_iter() 165 | .flatten(), 166 | comm_groups.into_iter().flat_map(move |(loc, comms)| { 167 | if comms.len() < 3 { 168 | comms 169 | .iter() 170 | .flat_map(|comm| { 171 | let (x, y) = (comm.x(), comm.y()); 172 | [ 173 | format!("success := {ec_mul}(success, nu)"), 174 | format!("success := {ec_add}(success, {x}, {y})"), 175 | ] 176 | }) 177 | .collect_vec() 178 | } else { 179 | let ptr = comms.first().unwrap().x().ptr(); 180 | let ptr_end = ptr - 2 * comms.len(); 181 | let x = Word::from(Ptr::new(loc, "ptr")); 182 | let y = Word::from(Ptr::new(loc, "add(ptr, 0x20)")); 183 | for_loop( 184 | [ 185 | format!("let ptr := {ptr}"), 186 | format!("let ptr_end := {ptr_end}"), 187 | ], 188 | "lt(ptr_end, ptr)", 189 | ["ptr := sub(ptr, 0x40)".to_string()], 190 | [ 191 | format!("success := {ec_mul}(success, nu)"), 192 | format!("success := {ec_add}(success, {x}, {y})"), 193 | ], 194 | ) 195 | } 196 | }), 197 | [format!( 198 | "success := {ec_add}(success, {}, {})", 199 | point_w.x(), 200 | point_w.y() 201 | )], 202 | (!is_last_set) 203 | .then_some([ 204 | "success := ec_mul_acc(success, mu)", 205 | "success := ec_add_acc(success, mload(0x80), mload(0xa0))", 206 | ]) 207 | .into_iter() 208 | .flatten() 209 | .map(str::to_string), 210 | ] 211 | .collect_vec() 212 | }), 213 | [ 214 | "mstore(0x80, mload(G1_X_MPTR))", 215 | "mstore(0xa0, mload(G1_Y_MPTR))", 216 | "success := ec_mul_tmp(success, mload(G1_SCALAR_MPTR))", 217 | "success := ec_add_acc(success, mload(0x80), mload(0xa0))", 218 | "mstore(PAIRING_LHS_X_MPTR, mload(0x00))", 219 | "mstore(PAIRING_LHS_Y_MPTR, mload(0x20))", 220 | ] 221 | .map(str::to_string), 222 | ] 223 | .collect_vec(); 224 | 225 | let pairing_rhs_computations = chain![ 226 | [ 227 | format!("let mu := mload(MU_MPTR)"), 228 | format!("mstore(0x00, {})", ws.last().unwrap().x()), 229 | format!("mstore(0x20, {})", ws.last().unwrap().y()), 230 | ], 231 | ws.iter() 232 | .nth_back(1) 233 | .map(|w_second_last| { 234 | let x = "calldataload(cptr)"; 235 | let y = "calldataload(add(cptr, 0x20))"; 236 | for_loop( 237 | [ 238 | format!("let cptr := {}", w_second_last.x().ptr()), 239 | format!("let cptr_end := {}", ws[0].x().ptr() - 1), 240 | ], 241 | "lt(cptr_end, cptr)", 242 | ["cptr := sub(cptr, 0x40)"], 243 | [ 244 | format!("success := ec_mul_acc(success, mu)"), 245 | format!("success := ec_add_acc(success, {x}, {y})"), 246 | ], 247 | ) 248 | }) 249 | .into_iter() 250 | .flatten(), 251 | [ 252 | format!("mstore(PAIRING_RHS_X_MPTR, mload(0x00))"), 253 | format!("mstore(PAIRING_RHS_Y_MPTR, mload(0x20))"), 254 | ], 255 | ] 256 | .collect_vec(); 257 | 258 | vec![ 259 | eval_computations, 260 | point_computations, 261 | point_w_computations, 262 | pairing_lhs_computations, 263 | pairing_rhs_computations, 264 | ] 265 | } 266 | 267 | #[derive(Debug)] 268 | struct RotationSet { 269 | rot: i32, 270 | comms: Vec, 271 | evals: Vec, 272 | } 273 | 274 | impl RotationSet { 275 | fn comms(&self) -> &[EcPoint] { 276 | &self.comms 277 | } 278 | 279 | fn evals(&self) -> &[Word] { 280 | &self.evals 281 | } 282 | } 283 | 284 | fn rotation_sets(queries: &[Query]) -> Vec { 285 | queries.iter().fold(Vec::new(), |mut sets, query| { 286 | if let Some(pos) = sets.iter().position(|set| set.rot == query.rot) { 287 | sets[pos].comms.push(query.comm); 288 | sets[pos].evals.push(query.eval); 289 | } else { 290 | sets.push(RotationSet { 291 | rot: query.rot, 292 | comms: vec![query.comm], 293 | evals: vec![query.eval], 294 | }); 295 | } 296 | sets 297 | }) 298 | } 299 | -------------------------------------------------------------------------------- /src/codegen/template.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{ 2 | pcs::BatchOpenScheme::{self, Bdfg21, Gwc19}, 3 | util::Ptr, 4 | }; 5 | use askama::{Error, Template}; 6 | use ruint::aliases::U256; 7 | use std::fmt; 8 | 9 | #[derive(Template)] 10 | #[template(path = "Halo2VerifyingKey.sol")] 11 | pub(crate) struct Halo2VerifyingKey { 12 | pub(crate) constants: Vec<(&'static str, U256)>, 13 | pub(crate) fixed_comms: Vec<(U256, U256)>, 14 | pub(crate) permutation_comms: Vec<(U256, U256)>, 15 | } 16 | 17 | impl Halo2VerifyingKey { 18 | pub(crate) fn len(&self) -> usize { 19 | (self.constants.len() * 0x20) 20 | + (self.fixed_comms.len() + self.permutation_comms.len()) * 0x40 21 | } 22 | } 23 | 24 | #[derive(Template)] 25 | #[template(path = "Halo2Verifier.sol")] 26 | pub(crate) struct Halo2Verifier { 27 | pub(crate) scheme: BatchOpenScheme, 28 | pub(crate) embedded_vk: Option, 29 | pub(crate) vk_len: usize, 30 | pub(crate) proof_len: usize, 31 | pub(crate) vk_mptr: Ptr, 32 | pub(crate) challenge_mptr: Ptr, 33 | pub(crate) theta_mptr: Ptr, 34 | pub(crate) proof_cptr: Ptr, 35 | pub(crate) quotient_comm_cptr: Ptr, 36 | pub(crate) num_neg_lagranges: usize, 37 | pub(crate) num_advices: Vec, 38 | pub(crate) num_challenges: Vec, 39 | pub(crate) num_rotations: usize, 40 | pub(crate) num_evals: usize, 41 | pub(crate) num_quotients: usize, 42 | pub(crate) quotient_eval_numer_computations: Vec>, 43 | pub(crate) pcs_computations: Vec>, 44 | } 45 | 46 | impl Halo2VerifyingKey { 47 | pub(crate) fn render(&self, writer: &mut impl fmt::Write) -> Result<(), fmt::Error> { 48 | self.render_into(writer).map_err(|err| match err { 49 | Error::Fmt(err) => err, 50 | _ => unreachable!(), 51 | }) 52 | } 53 | } 54 | 55 | impl Halo2Verifier { 56 | pub(crate) fn render(&self, writer: &mut impl fmt::Write) -> Result<(), fmt::Error> { 57 | self.render_into(writer).map_err(|err| match err { 58 | Error::Fmt(err) => err, 59 | _ => unreachable!(), 60 | }) 61 | } 62 | } 63 | 64 | mod filters { 65 | use std::fmt::LowerHex; 66 | 67 | pub fn hex(value: impl LowerHex) -> ::askama::Result { 68 | let value = format!("{value:x}"); 69 | Ok(if value.len() % 2 == 1 { 70 | format!("0x0{value}") 71 | } else { 72 | format!("0x{value}") 73 | }) 74 | } 75 | 76 | pub fn hex_padded(value: impl LowerHex, pad: usize) -> ::askama::Result { 77 | let string = format!("0x{value:0pad$x}"); 78 | if string == "0x0" { 79 | Ok(format!("0x{}", "0".repeat(pad))) 80 | } else { 81 | Ok(string) 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/codegen/util.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::{ 2 | template::Halo2VerifyingKey, 3 | BatchOpenScheme::{self, Bdfg21, Gwc19}, 4 | }; 5 | use halo2_proofs::{ 6 | halo2curves::{bn256, ff::PrimeField, CurveAffine}, 7 | plonk::{Any, Column, ConstraintSystem}, 8 | }; 9 | use itertools::{chain, izip, Itertools}; 10 | use ruint::{aliases::U256, UintTryFrom}; 11 | use std::{ 12 | borrow::Borrow, 13 | collections::HashMap, 14 | fmt::{self, Display, Formatter}, 15 | ops::{Add, Sub}, 16 | }; 17 | 18 | #[derive(Debug)] 19 | pub(crate) struct ConstraintSystemMeta { 20 | pub(crate) num_fixeds: usize, 21 | pub(crate) permutation_columns: Vec>, 22 | pub(crate) permutation_chunk_len: usize, 23 | pub(crate) num_lookup_permuteds: usize, 24 | pub(crate) num_permutation_zs: usize, 25 | pub(crate) num_lookup_zs: usize, 26 | pub(crate) num_quotients: usize, 27 | pub(crate) advice_queries: Vec<(usize, i32)>, 28 | pub(crate) fixed_queries: Vec<(usize, i32)>, 29 | pub(crate) num_rotations: usize, 30 | pub(crate) num_evals: usize, 31 | pub(crate) num_user_advices: Vec, 32 | pub(crate) num_user_challenges: Vec, 33 | pub(crate) advice_indices: Vec, 34 | pub(crate) challenge_indices: Vec, 35 | pub(crate) rotation_last: i32, 36 | } 37 | 38 | impl ConstraintSystemMeta { 39 | pub(crate) fn new(cs: &ConstraintSystem) -> Self { 40 | let num_fixeds = cs.num_fixed_columns(); 41 | let permutation_columns = cs.permutation().get_columns(); 42 | let permutation_chunk_len = cs.degree() - 2; 43 | let num_lookup_permuteds = 2 * cs.lookups().len(); 44 | let num_permutation_zs = cs 45 | .permutation() 46 | .get_columns() 47 | .chunks(cs.degree() - 2) 48 | .count(); 49 | let num_lookup_zs = cs.lookups().len(); 50 | let num_quotients = cs.degree() - 1; 51 | let advice_queries = cs 52 | .advice_queries() 53 | .iter() 54 | .map(|(column, rotation)| (column.index(), rotation.0)) 55 | .collect_vec(); 56 | let fixed_queries = cs 57 | .fixed_queries() 58 | .iter() 59 | .map(|(column, rotation)| (column.index(), rotation.0)) 60 | .collect_vec(); 61 | let num_evals = advice_queries.len() 62 | + fixed_queries.len() 63 | + 1 64 | + cs.permutation().get_columns().len() 65 | + (3 * num_permutation_zs - 1) 66 | + 5 * cs.lookups().len(); 67 | let num_phase = *cs.advice_column_phase().iter().max().unwrap_or(&0) as usize + 1; 68 | // Indices of advice and challenge are not same as their position in calldata/memory, 69 | // because we support multiple phases, we need to remap them and find their actual indices. 70 | let remapping = |phase: Vec| { 71 | let nums = phase.iter().fold(vec![0; num_phase], |mut nums, phase| { 72 | nums[*phase as usize] += 1; 73 | nums 74 | }); 75 | let offsets = nums 76 | .iter() 77 | .take(num_phase - 1) 78 | .fold(vec![0], |mut offsets, n| { 79 | offsets.push(offsets.last().unwrap() + n); 80 | offsets 81 | }); 82 | let index = phase 83 | .iter() 84 | .scan(offsets, |state, phase| { 85 | let index = state[*phase as usize]; 86 | state[*phase as usize] += 1; 87 | Some(index) 88 | }) 89 | .collect::>(); 90 | (nums, index) 91 | }; 92 | let (num_user_advices, advice_indices) = remapping(cs.advice_column_phase()); 93 | let (num_user_challenges, challenge_indices) = remapping(cs.challenge_phase()); 94 | let rotation_last = -(cs.blinding_factors() as i32 + 1); 95 | let num_rotations = chain![ 96 | advice_queries.iter().map(|query| query.1), 97 | fixed_queries.iter().map(|query| query.1), 98 | (num_permutation_zs > 0) 99 | .then_some([0, 1]) 100 | .into_iter() 101 | .flatten(), 102 | (num_permutation_zs > 1).then_some(rotation_last), 103 | (num_lookup_zs > 0) 104 | .then_some([-1, 0, 1]) 105 | .into_iter() 106 | .flatten(), 107 | ] 108 | .unique() 109 | .count(); 110 | Self { 111 | num_fixeds, 112 | permutation_columns, 113 | permutation_chunk_len, 114 | num_lookup_permuteds, 115 | num_permutation_zs, 116 | num_lookup_zs, 117 | num_quotients, 118 | advice_queries, 119 | fixed_queries, 120 | num_evals, 121 | num_rotations, 122 | num_user_advices, 123 | num_user_challenges, 124 | advice_indices, 125 | challenge_indices, 126 | rotation_last, 127 | } 128 | } 129 | 130 | pub(crate) fn num_advices(&self) -> Vec { 131 | chain![ 132 | self.num_user_advices.iter().cloned(), 133 | (self.num_lookup_permuteds != 0).then_some(self.num_lookup_permuteds), // lookup permuted 134 | [ 135 | self.num_permutation_zs + self.num_lookup_zs + 1, // permutation and lookup grand products, random 136 | self.num_quotients, // quotients 137 | ], 138 | ] 139 | .collect() 140 | } 141 | 142 | pub(crate) fn num_challenges(&self) -> Vec { 143 | let mut num_challenges = self.num_user_challenges.clone(); 144 | // If there is no lookup used, merge also beta and gamma into the last user phase, to avoid 145 | // squeezing challenge from nothing. 146 | // Otherwise, merge theta into last user phase since they are originally adjacent. 147 | if self.num_lookup_permuteds == 0 { 148 | *num_challenges.last_mut().unwrap() += 3; // theta, beta, gamma 149 | num_challenges.extend([ 150 | 1, // y 151 | 1, // x 152 | ]); 153 | } else { 154 | *num_challenges.last_mut().unwrap() += 1; // theta 155 | num_challenges.extend([ 156 | 2, // beta, gamma 157 | 1, // y 158 | 1, // x 159 | ]); 160 | } 161 | num_challenges 162 | } 163 | 164 | pub(crate) fn num_permutations(&self) -> usize { 165 | self.permutation_columns.len() 166 | } 167 | 168 | pub(crate) fn num_lookups(&self) -> usize { 169 | self.num_lookup_zs 170 | } 171 | 172 | pub(crate) fn proof_len(&self, scheme: BatchOpenScheme) -> usize { 173 | self.num_advices().iter().sum::() * 0x40 174 | + self.num_evals * 0x20 175 | + self.batch_open_proof_len(scheme) 176 | } 177 | 178 | pub(crate) fn batch_open_proof_len(&self, scheme: BatchOpenScheme) -> usize { 179 | (match scheme { 180 | Bdfg21 => 2, 181 | Gwc19 => self.num_rotations, 182 | }) * 0x40 183 | } 184 | } 185 | 186 | #[derive(Debug)] 187 | pub(crate) struct Data { 188 | pub(crate) challenge_mptr: Ptr, 189 | pub(crate) theta_mptr: Ptr, 190 | 191 | pub(crate) quotient_comm_cptr: Ptr, 192 | pub(crate) w_cptr: Ptr, 193 | 194 | pub(crate) fixed_comms: Vec, 195 | pub(crate) permutation_comms: HashMap, EcPoint>, 196 | pub(crate) advice_comms: Vec, 197 | pub(crate) lookup_permuted_comms: Vec<(EcPoint, EcPoint)>, 198 | pub(crate) permutation_z_comms: Vec, 199 | pub(crate) lookup_z_comms: Vec, 200 | pub(crate) random_comm: EcPoint, 201 | 202 | pub(crate) challenges: Vec, 203 | 204 | pub(crate) instance_eval: Word, 205 | pub(crate) advice_evals: HashMap<(usize, i32), Word>, 206 | pub(crate) fixed_evals: HashMap<(usize, i32), Word>, 207 | pub(crate) random_eval: Word, 208 | pub(crate) permutation_evals: HashMap, Word>, 209 | pub(crate) permutation_z_evals: Vec<(Word, Word, Word)>, 210 | pub(crate) lookup_evals: Vec<(Word, Word, Word, Word, Word)>, 211 | 212 | pub(crate) computed_quotient_comm: EcPoint, 213 | pub(crate) computed_quotient_eval: Word, 214 | } 215 | 216 | impl Data { 217 | pub(crate) fn new( 218 | meta: &ConstraintSystemMeta, 219 | vk: &Halo2VerifyingKey, 220 | vk_mptr: Ptr, 221 | proof_cptr: Ptr, 222 | ) -> Self { 223 | let fixed_comm_mptr = vk_mptr + vk.constants.len(); 224 | let permutation_comm_mptr = fixed_comm_mptr + 2 * vk.fixed_comms.len(); 225 | let challenge_mptr = permutation_comm_mptr + 2 * vk.permutation_comms.len(); 226 | let theta_mptr = challenge_mptr + meta.challenge_indices.len(); 227 | 228 | let advice_comm_start = proof_cptr; 229 | let lookup_permuted_comm_start = advice_comm_start + 2 * meta.advice_indices.len(); 230 | let permutation_z_comm_start = lookup_permuted_comm_start + 2 * meta.num_lookup_permuteds; 231 | let lookup_z_comm_start = permutation_z_comm_start + 2 * meta.num_permutation_zs; 232 | let random_comm_start = lookup_z_comm_start + 2 * meta.num_lookup_zs; 233 | let quotient_comm_start = random_comm_start + 2; 234 | 235 | let eval_cptr = quotient_comm_start + 2 * meta.num_quotients; 236 | let advice_eval_cptr = eval_cptr; 237 | let fixed_eval_cptr = advice_eval_cptr + meta.advice_queries.len(); 238 | let random_eval_cptr = fixed_eval_cptr + meta.fixed_queries.len(); 239 | let permutation_eval_cptr = random_eval_cptr + 1; 240 | let permutation_z_eval_cptr = permutation_eval_cptr + meta.num_permutations(); 241 | let lookup_eval_cptr = permutation_z_eval_cptr + 3 * meta.num_permutation_zs - 1; 242 | let w_cptr = lookup_eval_cptr + 5 * meta.num_lookups(); 243 | 244 | let fixed_comms = EcPoint::range(fixed_comm_mptr) 245 | .take(meta.num_fixeds) 246 | .collect(); 247 | let permutation_comms = izip!( 248 | meta.permutation_columns.iter().cloned(), 249 | EcPoint::range(permutation_comm_mptr) 250 | ) 251 | .collect(); 252 | let advice_comms = meta 253 | .advice_indices 254 | .iter() 255 | .map(|idx| advice_comm_start + 2 * idx) 256 | .map_into() 257 | .collect(); 258 | let lookup_permuted_comms = EcPoint::range(lookup_permuted_comm_start) 259 | .take(meta.num_lookup_permuteds) 260 | .tuples() 261 | .collect(); 262 | let permutation_z_comms = EcPoint::range(permutation_z_comm_start) 263 | .take(meta.num_permutation_zs) 264 | .collect(); 265 | let lookup_z_comms = EcPoint::range(lookup_z_comm_start) 266 | .take(meta.num_lookup_zs) 267 | .collect(); 268 | let random_comm = random_comm_start.into(); 269 | let computed_quotient_comm = EcPoint::new( 270 | Ptr::memory("QUOTIENT_X_MPTR"), 271 | Ptr::memory("QUOTIENT_Y_MPTR"), 272 | ); 273 | 274 | let challenges = meta 275 | .challenge_indices 276 | .iter() 277 | .map(|idx| challenge_mptr + *idx) 278 | .map_into() 279 | .collect_vec(); 280 | let instance_eval = Ptr::memory("INSTANCE_EVAL_MPTR").into(); 281 | let advice_evals = izip!( 282 | meta.advice_queries.iter().cloned(), 283 | Word::range(advice_eval_cptr) 284 | ) 285 | .collect(); 286 | let fixed_evals = izip!( 287 | meta.fixed_queries.iter().cloned(), 288 | Word::range(fixed_eval_cptr) 289 | ) 290 | .collect(); 291 | let random_eval = random_eval_cptr.into(); 292 | let permutation_evals = izip!( 293 | meta.permutation_columns.iter().cloned(), 294 | Word::range(permutation_eval_cptr) 295 | ) 296 | .collect(); 297 | let permutation_z_evals = Word::range(permutation_z_eval_cptr) 298 | .take(3 * meta.num_permutation_zs) 299 | .tuples() 300 | .collect_vec(); 301 | let lookup_evals = Word::range(lookup_eval_cptr) 302 | .take(5 * meta.num_lookup_zs) 303 | .tuples() 304 | .collect_vec(); 305 | let computed_quotient_eval = Ptr::memory("QUOTIENT_EVAL_MPTR").into(); 306 | 307 | Self { 308 | challenge_mptr, 309 | theta_mptr, 310 | quotient_comm_cptr: quotient_comm_start, 311 | w_cptr, 312 | 313 | fixed_comms, 314 | permutation_comms, 315 | advice_comms, 316 | lookup_permuted_comms, 317 | permutation_z_comms, 318 | lookup_z_comms, 319 | random_comm, 320 | computed_quotient_comm, 321 | 322 | challenges, 323 | 324 | instance_eval, 325 | advice_evals, 326 | fixed_evals, 327 | permutation_evals, 328 | permutation_z_evals, 329 | lookup_evals, 330 | random_eval, 331 | computed_quotient_eval, 332 | } 333 | } 334 | } 335 | 336 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 337 | pub(crate) enum Location { 338 | Calldata, 339 | Memory, 340 | } 341 | 342 | impl Location { 343 | fn opcode(&self) -> &'static str { 344 | match self { 345 | Location::Calldata => "calldataload", 346 | Location::Memory => "mload", 347 | } 348 | } 349 | } 350 | 351 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 352 | pub(crate) enum Value { 353 | Integer(usize), 354 | Identifier(&'static str), 355 | } 356 | 357 | impl Value { 358 | pub(crate) fn is_integer(&self) -> bool { 359 | match self { 360 | Value::Integer(_) => true, 361 | Value::Identifier(_) => false, 362 | } 363 | } 364 | 365 | pub(crate) fn as_usize(&self) -> usize { 366 | match self { 367 | Value::Integer(int) => *int, 368 | Value::Identifier(_) => unreachable!(), 369 | } 370 | } 371 | } 372 | 373 | impl Default for Value { 374 | fn default() -> Self { 375 | Self::Integer(0) 376 | } 377 | } 378 | 379 | impl From<&'static str> for Value { 380 | fn from(ident: &'static str) -> Self { 381 | Value::Identifier(ident) 382 | } 383 | } 384 | 385 | impl From for Value { 386 | fn from(int: usize) -> Self { 387 | Value::Integer(int) 388 | } 389 | } 390 | 391 | impl Display for Value { 392 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 393 | match self { 394 | Value::Integer(int) => { 395 | let hex = format!("{int:x}"); 396 | if hex.len() % 2 == 1 { 397 | write!(f, "0x0{hex}") 398 | } else { 399 | write!(f, "0x{hex}") 400 | } 401 | } 402 | Value::Identifier(ident) => { 403 | write!(f, "{ident}") 404 | } 405 | } 406 | } 407 | } 408 | 409 | impl Add for Value { 410 | type Output = Value; 411 | 412 | fn add(self, rhs: usize) -> Self::Output { 413 | (self.as_usize() + rhs * 0x20).into() 414 | } 415 | } 416 | 417 | impl Sub for Value { 418 | type Output = Value; 419 | 420 | fn sub(self, rhs: usize) -> Self::Output { 421 | (self.as_usize() - rhs * 0x20).into() 422 | } 423 | } 424 | 425 | /// `Ptr` points to a EVM word at either calldata or memory. 426 | /// 427 | /// When adding or subtracting it by 1, its value moves by 32 and points to next/previous EVM word. 428 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 429 | pub(crate) struct Ptr { 430 | loc: Location, 431 | value: Value, 432 | } 433 | 434 | impl Ptr { 435 | pub(crate) fn new(loc: Location, value: impl Into) -> Self { 436 | Self { 437 | loc, 438 | value: value.into(), 439 | } 440 | } 441 | 442 | pub(crate) fn memory(value: impl Into) -> Self { 443 | Self::new(Location::Memory, value.into()) 444 | } 445 | 446 | pub(crate) fn calldata(value: impl Into) -> Self { 447 | Self::new(Location::Calldata, value.into()) 448 | } 449 | 450 | pub(crate) fn loc(&self) -> Location { 451 | self.loc 452 | } 453 | 454 | pub(crate) fn value(&self) -> Value { 455 | self.value 456 | } 457 | } 458 | 459 | impl Display for Ptr { 460 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 461 | write!(f, "{}", self.value) 462 | } 463 | } 464 | 465 | impl Add for Ptr { 466 | type Output = Ptr; 467 | 468 | fn add(mut self, rhs: usize) -> Self::Output { 469 | self.value = self.value + rhs; 470 | self 471 | } 472 | } 473 | 474 | impl Sub for Ptr { 475 | type Output = Ptr; 476 | 477 | fn sub(mut self, rhs: usize) -> Self::Output { 478 | self.value = self.value - rhs; 479 | self 480 | } 481 | } 482 | 483 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 484 | pub(crate) struct Word(Ptr); 485 | 486 | impl Word { 487 | pub(crate) fn range(word: impl Into) -> impl Iterator { 488 | let ptr = word.into().ptr(); 489 | (0..).map(move |idx| ptr + idx).map_into() 490 | } 491 | 492 | pub(crate) fn ptr(&self) -> Ptr { 493 | self.0 494 | } 495 | 496 | pub(crate) fn loc(&self) -> Location { 497 | self.0.loc() 498 | } 499 | } 500 | 501 | impl Display for Word { 502 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 503 | write!(f, "{}({})", self.0.loc.opcode(), self.0.value) 504 | } 505 | } 506 | 507 | impl From for Word { 508 | fn from(ptr: Ptr) -> Self { 509 | Self(ptr) 510 | } 511 | } 512 | 513 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 514 | pub(crate) struct EcPoint { 515 | x: Word, 516 | y: Word, 517 | } 518 | 519 | impl EcPoint { 520 | pub(crate) fn new(x: impl Into, y: impl Into) -> Self { 521 | Self { 522 | x: x.into(), 523 | y: y.into(), 524 | } 525 | } 526 | 527 | pub(crate) fn range(ec_point: impl Into) -> impl Iterator { 528 | let ptr = ec_point.into().x.ptr(); 529 | (0..).map(move |idx| ptr + 2 * idx).map_into() 530 | } 531 | 532 | pub(crate) fn loc(&self) -> Location { 533 | self.x.ptr().loc() 534 | } 535 | 536 | pub(crate) fn x(&self) -> Word { 537 | self.x 538 | } 539 | 540 | pub(crate) fn y(&self) -> Word { 541 | self.y 542 | } 543 | } 544 | 545 | impl From for EcPoint { 546 | fn from(ptr: Ptr) -> Self { 547 | Self::new(ptr, ptr + 1) 548 | } 549 | } 550 | 551 | /// Add indention to given lines by `4 * N` spaces. 552 | pub(crate) fn indent( 553 | lines: impl IntoIterator>, 554 | ) -> Vec { 555 | lines 556 | .into_iter() 557 | .map(|line| format!("{}{}", " ".repeat(N * 4), line.into())) 558 | .collect() 559 | } 560 | 561 | /// Create a code block for given lines with indention. 562 | /// 563 | /// If `PACKED` is true, single line code block will be packed into single line. 564 | pub(crate) fn code_block( 565 | lines: impl IntoIterator>, 566 | ) -> Vec { 567 | let lines = lines.into_iter().map_into().collect_vec(); 568 | let bracket_indent = " ".repeat((N - 1) * 4); 569 | match lines.len() { 570 | 0 => vec![format!("{bracket_indent}{{}}")], 571 | 1 if PACKED => vec![format!("{bracket_indent}{{ {} }}", lines[0])], 572 | _ => chain![ 573 | [format!("{bracket_indent}{{")], 574 | indent::(lines), 575 | [format!("{bracket_indent}}}")], 576 | ] 577 | .collect(), 578 | } 579 | } 580 | 581 | /// Create a for loop with proper indention. 582 | pub(crate) fn for_loop( 583 | initialization: impl IntoIterator>, 584 | condition: impl Into, 585 | advancement: impl IntoIterator>, 586 | body: impl IntoIterator>, 587 | ) -> Vec { 588 | chain![ 589 | ["for".to_string()], 590 | code_block::<2, true>(initialization), 591 | indent::<1>([condition.into()]), 592 | code_block::<2, true>(advancement), 593 | code_block::<1, false>(body), 594 | ] 595 | .collect() 596 | } 597 | 598 | pub(crate) fn group_backward_adjacent_words<'a>( 599 | words: impl IntoIterator, 600 | ) -> Vec<(Location, Vec<&'a Word>)> { 601 | words.into_iter().fold(Vec::new(), |mut word_groups, word| { 602 | if let Some(last_group) = word_groups.last_mut() { 603 | let last_word = **last_group.1.last().unwrap(); 604 | if last_group.0 == word.loc() 605 | && last_word.ptr().value().is_integer() 606 | && last_word.ptr() - 1 == word.ptr() 607 | { 608 | last_group.1.push(word) 609 | } else { 610 | word_groups.push((word.loc(), vec![word])) 611 | } 612 | word_groups 613 | } else { 614 | vec![(word.loc(), vec![word])] 615 | } 616 | }) 617 | } 618 | 619 | pub(crate) fn group_backward_adjacent_ec_points<'a>( 620 | ec_point: impl IntoIterator, 621 | ) -> Vec<(Location, Vec<&'a EcPoint>)> { 622 | ec_point 623 | .into_iter() 624 | .fold(Vec::new(), |mut ec_point_groups, ec_point| { 625 | if let Some(last_group) = ec_point_groups.last_mut() { 626 | let last_ec_point = **last_group.1.last().unwrap(); 627 | if last_group.0 == ec_point.loc() 628 | && last_ec_point.x().ptr().value().is_integer() 629 | && last_ec_point.x().ptr() - 2 == ec_point.x().ptr() 630 | { 631 | last_group.1.push(ec_point) 632 | } else { 633 | ec_point_groups.push((ec_point.loc(), vec![ec_point])) 634 | } 635 | ec_point_groups 636 | } else { 637 | vec![(ec_point.loc(), vec![ec_point])] 638 | } 639 | }) 640 | } 641 | 642 | pub(crate) fn g1_to_u256s(ec_point: impl Borrow) -> [U256; 2] { 643 | let coords = ec_point.borrow().coordinates().unwrap(); 644 | [coords.x(), coords.y()].map(fq_to_u256) 645 | } 646 | 647 | pub(crate) fn g2_to_u256s(ec_point: impl Borrow) -> [U256; 4] { 648 | let coords = ec_point.borrow().coordinates().unwrap(); 649 | let x = coords.x().to_repr(); 650 | let y = coords.y().to_repr(); 651 | [ 652 | U256::try_from_le_slice(&x.as_ref()[0x20..]).unwrap(), 653 | U256::try_from_le_slice(&x.as_ref()[..0x20]).unwrap(), 654 | U256::try_from_le_slice(&y.as_ref()[0x20..]).unwrap(), 655 | U256::try_from_le_slice(&y.as_ref()[..0x20]).unwrap(), 656 | ] 657 | } 658 | 659 | pub(crate) fn fq_to_u256(fe: impl Borrow) -> U256 { 660 | fe_to_u256(fe) 661 | } 662 | 663 | pub(crate) fn fr_to_u256(fe: impl Borrow) -> U256 { 664 | fe_to_u256(fe) 665 | } 666 | 667 | pub(crate) fn fe_to_u256(fe: impl Borrow) -> U256 668 | where 669 | F: PrimeField, 670 | { 671 | U256::from_le_bytes(fe.borrow().to_repr()) 672 | } 673 | 674 | pub(crate) fn to_u256_be_bytes(value: T) -> [u8; 32] 675 | where 676 | U256: UintTryFrom, 677 | { 678 | U256::from(value).to_be_bytes() 679 | } 680 | -------------------------------------------------------------------------------- /src/evm.rs: -------------------------------------------------------------------------------- 1 | use crate::codegen::util::{fr_to_u256, to_u256_be_bytes}; 2 | use halo2_proofs::halo2curves::bn256; 3 | use itertools::chain; 4 | use ruint::aliases::U256; 5 | 6 | /// Function signature of `verifyProof(bytes,uint256[])`. 7 | pub const FN_SIG_VERIFY_PROOF: [u8; 4] = [0x1e, 0x8e, 0x1e, 0x13]; 8 | 9 | /// Function signature of `verifyProof(address,bytes,uint256[])`. 10 | pub const FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS: [u8; 4] = [0xaf, 0x83, 0xa1, 0x8d]; 11 | 12 | /// Encode proof into calldata to invoke `Halo2Verifier.verifyProof`. 13 | /// 14 | /// For `vk_address`: 15 | /// - Pass `None` if verifying key is embedded in `Halo2Verifier` 16 | /// - Pass `Some(vk_address)` if verifying key is separated and deployed at `vk_address` 17 | pub fn encode_calldata( 18 | vk_address: Option<[u8; 20]>, 19 | proof: &[u8], 20 | instances: &[bn256::Fr], 21 | ) -> Vec { 22 | let (fn_sig, offset) = if vk_address.is_some() { 23 | (FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, 0x60) 24 | } else { 25 | (FN_SIG_VERIFY_PROOF, 0x40) 26 | }; 27 | let vk_address = if let Some(vk_address) = vk_address { 28 | U256::try_from_be_slice(&vk_address) 29 | .unwrap() 30 | .to_be_bytes::<0x20>() 31 | .to_vec() 32 | } else { 33 | Vec::new() 34 | }; 35 | let num_instances = instances.len(); 36 | chain![ 37 | fn_sig, // function signature 38 | vk_address, // verifying key address 39 | to_u256_be_bytes(offset), // offset of proof 40 | to_u256_be_bytes(offset + 0x20 + proof.len()), // offset of instances 41 | to_u256_be_bytes(proof.len()), // length of proof 42 | proof.iter().cloned(), // proof 43 | to_u256_be_bytes(num_instances), // length of instances 44 | instances.iter().map(fr_to_u256).flat_map(to_u256_be_bytes), // instances 45 | ] 46 | .collect() 47 | } 48 | 49 | #[cfg(any(test, feature = "evm"))] 50 | pub(crate) mod test { 51 | pub use revm; 52 | use revm::{ 53 | primitives::{Address, CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, 54 | InMemoryDB, EVM, 55 | }; 56 | use std::{ 57 | fmt::{self, Debug, Formatter}, 58 | io::{self, Write}, 59 | process::{Command, Stdio}, 60 | str, 61 | }; 62 | 63 | /// Compile solidity with `--via-ir` flag, then return creation bytecode. 64 | /// 65 | /// # Panics 66 | /// Panics if executable `solc` can not be found, or compilation fails. 67 | pub fn compile_solidity(solidity: impl AsRef<[u8]>) -> Vec { 68 | let mut process = match Command::new("solc") 69 | .stdin(Stdio::piped()) 70 | .stdout(Stdio::piped()) 71 | .stderr(Stdio::piped()) 72 | .arg("--bin") 73 | .arg("--optimize") 74 | .arg("-") 75 | .spawn() 76 | { 77 | Ok(process) => process, 78 | Err(err) if err.kind() == io::ErrorKind::NotFound => { 79 | panic!("Command 'solc' not found"); 80 | } 81 | Err(err) => { 82 | panic!("Failed to spwan process with command 'solc':\n{err}"); 83 | } 84 | }; 85 | process 86 | .stdin 87 | .take() 88 | .unwrap() 89 | .write_all(solidity.as_ref()) 90 | .unwrap(); 91 | let output = process.wait_with_output().unwrap(); 92 | let stdout = str::from_utf8(&output.stdout).unwrap(); 93 | if let Some(binary) = find_binary(stdout) { 94 | binary 95 | } else { 96 | panic!( 97 | "Compilation fails:\n{}", 98 | str::from_utf8(&output.stderr).unwrap() 99 | ) 100 | } 101 | } 102 | 103 | fn find_binary(stdout: &str) -> Option> { 104 | let start = stdout.find("Binary:")? + 8; 105 | Some(hex::decode(&stdout[start..stdout.len() - 1]).unwrap()) 106 | } 107 | 108 | /// Evm runner. 109 | pub struct Evm { 110 | evm: EVM, 111 | } 112 | 113 | impl Debug for Evm { 114 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { 115 | let mut debug_struct = f.debug_struct("Evm"); 116 | debug_struct 117 | .field("env", &self.evm.env) 118 | .field("db", &self.evm.db.as_ref().unwrap()) 119 | .finish() 120 | } 121 | } 122 | 123 | impl Default for Evm { 124 | fn default() -> Self { 125 | Self { 126 | evm: EVM { 127 | env: Default::default(), 128 | db: Some(Default::default()), 129 | }, 130 | } 131 | } 132 | } 133 | 134 | impl Evm { 135 | /// Return code_size of given address. 136 | /// 137 | /// # Panics 138 | /// Panics if given address doesn't have bytecode. 139 | pub fn code_size(&mut self, address: Address) -> usize { 140 | self.evm.db.as_ref().unwrap().accounts[&address] 141 | .info 142 | .code 143 | .as_ref() 144 | .unwrap() 145 | .len() 146 | } 147 | 148 | /// Apply create transaction with given `bytecode` as creation bytecode. 149 | /// Return created `address`. 150 | /// 151 | /// # Panics 152 | /// Panics if execution reverts or halts unexpectedly. 153 | pub fn create(&mut self, bytecode: Vec) -> Address { 154 | let (_, output) = self.transact_success_or_panic(TxEnv { 155 | gas_limit: u64::MAX, 156 | transact_to: TransactTo::Create(CreateScheme::Create), 157 | data: bytecode.into(), 158 | ..Default::default() 159 | }); 160 | match output { 161 | Output::Create(_, Some(address)) => address, 162 | _ => unreachable!(), 163 | } 164 | } 165 | 166 | /// Apply call transaction to given `address` with `calldata`. 167 | /// Returns `gas_used` and `return_data`. 168 | /// 169 | /// # Panics 170 | /// Panics if execution reverts or halts unexpectedly. 171 | pub fn call(&mut self, address: Address, calldata: Vec) -> (u64, Vec) { 172 | let (gas_used, output) = self.transact_success_or_panic(TxEnv { 173 | gas_limit: u64::MAX, 174 | transact_to: TransactTo::Call(address), 175 | data: calldata.into(), 176 | ..Default::default() 177 | }); 178 | match output { 179 | Output::Call(output) => (gas_used, output.into()), 180 | _ => unreachable!(), 181 | } 182 | } 183 | 184 | fn transact_success_or_panic(&mut self, tx: TxEnv) -> (u64, Output) { 185 | self.evm.env.tx = tx; 186 | let result = self.evm.transact_commit().unwrap(); 187 | self.evm.env.tx = Default::default(); 188 | match result { 189 | ExecutionResult::Success { 190 | gas_used, 191 | output, 192 | logs, 193 | .. 194 | } => { 195 | if !logs.is_empty() { 196 | println!("--- logs from {} ---", logs[0].address); 197 | for (log_idx, log) in logs.iter().enumerate() { 198 | println!("log#{log_idx}"); 199 | for (topic_idx, topic) in log.topics.iter().enumerate() { 200 | println!(" topic{topic_idx}: {topic:?}"); 201 | } 202 | } 203 | println!("--- end ---"); 204 | } 205 | (gas_used, output) 206 | } 207 | ExecutionResult::Revert { gas_used, output } => { 208 | panic!("Transaction reverts with gas_used {gas_used} and output {output:#x}") 209 | } 210 | ExecutionResult::Halt { reason, gas_used } => panic!( 211 | "Transaction halts unexpectedly with gas_used {gas_used} and reason {reason:?}" 212 | ), 213 | } 214 | } 215 | } 216 | } 217 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Solidity verifier generator for [`halo2`] proof with KZG polynomial commitment scheme on BN254. 2 | //! 3 | //! [`halo2`]: http://github.com/privacy-scaling-explorations/halo2 4 | 5 | #![deny(missing_docs)] 6 | #![deny(missing_debug_implementations)] 7 | #![deny(rustdoc::broken_intra_doc_links)] 8 | 9 | mod codegen; 10 | mod evm; 11 | mod transcript; 12 | 13 | #[cfg(test)] 14 | mod test; 15 | 16 | pub use codegen::{AccumulatorEncoding, BatchOpenScheme, SolidityGenerator}; 17 | pub use evm::{encode_calldata, FN_SIG_VERIFY_PROOF, FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS}; 18 | pub use transcript::Keccak256Transcript; 19 | 20 | #[cfg(feature = "evm")] 21 | pub use evm::test::{compile_solidity, revm, Evm}; 22 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codegen::{ 3 | AccumulatorEncoding, 4 | BatchOpenScheme::{self, Bdfg21, Gwc19}, 5 | SolidityGenerator, 6 | }, 7 | encode_calldata, 8 | evm::test::{compile_solidity, Evm}, 9 | FN_SIG_VERIFY_PROOF, FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, 10 | }; 11 | use halo2_proofs::halo2curves::bn256::{Bn256, Fr}; 12 | use rand::{rngs::StdRng, RngCore, SeedableRng}; 13 | use sha3::Digest; 14 | use std::{fs::File, io::Write}; 15 | 16 | #[test] 17 | fn function_signature() { 18 | for (fn_name, fn_sig) in [ 19 | ("verifyProof(bytes,uint256[])", FN_SIG_VERIFY_PROOF), 20 | ( 21 | "verifyProof(address,bytes,uint256[])", 22 | FN_SIG_VERIFY_PROOF_WITH_VK_ADDRESS, 23 | ), 24 | ] { 25 | assert_eq!( 26 | <[u8; 32]>::from(sha3::Keccak256::digest(fn_name))[..4], 27 | fn_sig, 28 | ); 29 | } 30 | } 31 | 32 | #[test] 33 | fn render_bdfg21_huge() { 34 | run_render::>(Bdfg21) 35 | } 36 | 37 | #[test] 38 | fn render_bdfg21_maingate() { 39 | run_render::>(Bdfg21) 40 | } 41 | 42 | #[test] 43 | fn render_gwc19_huge() { 44 | run_render::>(Gwc19) 45 | } 46 | 47 | #[test] 48 | fn render_gwc19_maingate() { 49 | run_render::>(Gwc19) 50 | } 51 | 52 | #[test] 53 | fn render_separately_bdfg21_huge() { 54 | run_render_separately::>(Bdfg21) 55 | } 56 | 57 | #[test] 58 | fn render_separately_bdfg21_maingate() { 59 | run_render_separately::>(Bdfg21) 60 | } 61 | 62 | #[test] 63 | fn render_separately_gwc19_huge() { 64 | run_render_separately::>(Gwc19) 65 | } 66 | 67 | #[test] 68 | fn render_separately_gwc19_maingate() { 69 | run_render_separately::>(Gwc19) 70 | } 71 | 72 | fn run_render>(scheme: BatchOpenScheme) { 73 | let acc_encoding = AccumulatorEncoding::new(0, 4, 68).into(); 74 | let (params, vk, instances, proof) = 75 | halo2::create_testdata::(C::min_k(), scheme, acc_encoding, std_rng()); 76 | 77 | let generator = SolidityGenerator::new(¶ms, &vk, scheme, instances.len()) 78 | .set_acc_encoding(acc_encoding); 79 | let verifier_solidity = generator.render().unwrap(); 80 | let verifier_creation_code = compile_solidity(verifier_solidity); 81 | let verifier_creation_code_size = verifier_creation_code.len(); 82 | 83 | let mut evm = Evm::default(); 84 | let verifier_address = evm.create(verifier_creation_code); 85 | let verifier_runtime_code_size = evm.code_size(verifier_address); 86 | 87 | println!("Verifier creation code size: {verifier_creation_code_size}"); 88 | println!("Verifier runtime code size: {verifier_runtime_code_size}"); 89 | 90 | let (gas_cost, output) = evm.call(verifier_address, encode_calldata(None, &proof, &instances)); 91 | assert_eq!(output, [vec![0; 31], vec![1]].concat()); 92 | println!("Gas cost: {gas_cost}"); 93 | } 94 | 95 | fn run_render_separately>(scheme: BatchOpenScheme) { 96 | let acc_encoding = AccumulatorEncoding::new(0, 4, 68).into(); 97 | let (params, vk, instances, _) = 98 | halo2::create_testdata::(C::min_k(), scheme, acc_encoding, std_rng()); 99 | 100 | let generator = SolidityGenerator::new(¶ms, &vk, scheme, instances.len()) 101 | .set_acc_encoding(acc_encoding); 102 | let (verifier_solidity, _vk_solidity) = generator.render_separately().unwrap(); 103 | let verifier_creation_code = compile_solidity(&verifier_solidity); 104 | let verifier_creation_code_size = verifier_creation_code.len(); 105 | 106 | let mut evm = Evm::default(); 107 | let verifier_address = evm.create(verifier_creation_code); 108 | let verifier_runtime_code_size = evm.code_size(verifier_address); 109 | 110 | println!("Verifier creation code size: {verifier_creation_code_size}"); 111 | println!("Verifier runtime code size: {verifier_runtime_code_size}"); 112 | 113 | let deployed_verifier_solidity = verifier_solidity; 114 | 115 | for k in C::min_k()..C::min_k() + 4 { 116 | let (params, vk, instances, proof) = 117 | halo2::create_testdata::(k, scheme, acc_encoding, std_rng()); 118 | let generator = SolidityGenerator::new(¶ms, &vk, scheme, instances.len()) 119 | .set_acc_encoding(acc_encoding); 120 | 121 | let (verifier_solidity, vk_solidity) = generator.render_separately().unwrap(); 122 | assert_eq!(deployed_verifier_solidity, verifier_solidity); 123 | 124 | let vk_creation_code = compile_solidity(&vk_solidity); 125 | let vk_address = evm.create(vk_creation_code); 126 | 127 | let (gas_cost, output) = evm.call( 128 | verifier_address, 129 | encode_calldata(Some(vk_address.into()), &proof, &instances), 130 | ); 131 | assert_eq!(output, [vec![0; 31], vec![1]].concat()); 132 | println!("Gas cost: {gas_cost}"); 133 | } 134 | } 135 | 136 | fn std_rng() -> impl RngCore + Clone { 137 | StdRng::seed_from_u64(0) 138 | } 139 | 140 | #[allow(dead_code)] 141 | fn save_generated(verifier: &str, vk: Option<&str>) { 142 | const DIR_GENERATED: &str = "./target/generated"; 143 | 144 | std::fs::create_dir_all(DIR_GENERATED).unwrap(); 145 | File::create(format!("{DIR_GENERATED}/Halo2Verifier.sol")) 146 | .unwrap() 147 | .write_all(verifier.as_bytes()) 148 | .unwrap(); 149 | if let Some(vk) = vk { 150 | File::create(format!("{DIR_GENERATED}/Halo2VerifyingKey.sol")) 151 | .unwrap() 152 | .write_all(vk.as_bytes()) 153 | .unwrap(); 154 | } 155 | } 156 | 157 | mod halo2 { 158 | use crate::{ 159 | codegen::AccumulatorEncoding, 160 | transcript::Keccak256Transcript, 161 | BatchOpenScheme::{self, Bdfg21, Gwc19}, 162 | }; 163 | use halo2_proofs::{ 164 | arithmetic::CurveAffine, 165 | halo2curves::{ 166 | bn256, 167 | ff::{Field, PrimeField}, 168 | group::{prime::PrimeCurveAffine, Curve, Group}, 169 | pairing::{MillerLoopResult, MultiMillerLoop}, 170 | }, 171 | plonk::{create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, VerifyingKey}, 172 | poly::kzg::{ 173 | commitment::ParamsKZG, 174 | multiopen::{ProverGWC, ProverSHPLONK, VerifierGWC, VerifierSHPLONK}, 175 | strategy::SingleStrategy, 176 | }, 177 | transcript::TranscriptWriterBuffer, 178 | }; 179 | use itertools::Itertools; 180 | use rand::RngCore; 181 | use ruint::aliases::U256; 182 | use std::borrow::Borrow; 183 | 184 | pub trait TestCircuit: Circuit { 185 | fn min_k() -> u32; 186 | 187 | fn new(acc_encoding: Option, rng: impl RngCore) -> Self; 188 | 189 | fn instances(&self) -> Vec; 190 | } 191 | 192 | #[allow(clippy::type_complexity)] 193 | pub fn create_testdata>( 194 | k: u32, 195 | scheme: BatchOpenScheme, 196 | acc_encoding: Option, 197 | mut rng: impl RngCore + Clone, 198 | ) -> ( 199 | ParamsKZG, 200 | VerifyingKey, 201 | Vec, 202 | Vec, 203 | ) { 204 | match scheme { 205 | Bdfg21 => { 206 | create_testdata_inner!(ProverSHPLONK<_>, VerifierSHPLONK<_>, k, acc_encoding, rng) 207 | } 208 | Gwc19 => create_testdata_inner!(ProverGWC<_>, VerifierGWC<_>, k, acc_encoding, rng), 209 | } 210 | } 211 | 212 | macro_rules! create_testdata_inner { 213 | ($p:ty, $v:ty, $k:ident, $acc_encoding:ident, $rng:ident) => {{ 214 | let circuit = C::new($acc_encoding, $rng.clone()); 215 | let instances = circuit.instances(); 216 | 217 | let params = ParamsKZG::::setup($k, &mut $rng); 218 | let vk = keygen_vk(¶ms, &circuit).unwrap(); 219 | let pk = keygen_pk(¶ms, vk.clone(), &circuit).unwrap(); 220 | 221 | let proof = { 222 | let mut transcript = Keccak256Transcript::new(Vec::new()); 223 | create_proof::<_, $p, _, _, _, _>( 224 | ¶ms, 225 | &pk, 226 | &[circuit], 227 | &[&[&instances]], 228 | &mut $rng, 229 | &mut transcript, 230 | ) 231 | .unwrap(); 232 | transcript.finalize() 233 | }; 234 | 235 | let result = { 236 | let mut transcript = Keccak256Transcript::new(proof.as_slice()); 237 | verify_proof::<_, $v, _, _, SingleStrategy<_>>( 238 | ¶ms, 239 | pk.get_vk(), 240 | SingleStrategy::new(¶ms), 241 | &[&[&instances]], 242 | &mut transcript, 243 | ) 244 | }; 245 | assert!(result.is_ok()); 246 | 247 | (params, vk, instances, proof) 248 | }}; 249 | } 250 | 251 | use create_testdata_inner; 252 | 253 | fn random_accumulator_limbs( 254 | acc_encoding: AccumulatorEncoding, 255 | mut rng: impl RngCore, 256 | ) -> Vec 257 | where 258 | M: MultiMillerLoop, 259 | M::G1Affine: CurveAffine, 260 | ::Base: PrimeField, 261 | ::ScalarExt: PrimeField, 262 | { 263 | let s = M::Fr::random(&mut rng); 264 | let g1 = M::G1Affine::generator(); 265 | let g2 = M::G2Affine::generator(); 266 | let neg_s_g2 = (g2 * -s).to_affine(); 267 | let lhs_scalar = M::Fr::random(&mut rng); 268 | let rhs_scalar = lhs_scalar * s.invert().unwrap(); 269 | let [lhs, rhs] = [lhs_scalar, rhs_scalar].map(|scalar| (g1 * scalar).to_affine()); 270 | 271 | assert!(bool::from( 272 | M::multi_miller_loop(&[(&lhs, &g2.into()), (&rhs, &neg_s_g2.into())]) 273 | .final_exponentiation() 274 | .is_identity() 275 | )); 276 | 277 | [lhs, rhs] 278 | .into_iter() 279 | .flat_map(|ec_point| ec_point_to_limbs(ec_point, acc_encoding.num_limb_bits)) 280 | .collect() 281 | } 282 | 283 | fn ec_point_to_limbs(ec_point: impl Borrow, num_limb_bits: usize) -> Vec 284 | where 285 | C: CurveAffine, 286 | C::Base: PrimeField, 287 | C::Scalar: PrimeField, 288 | { 289 | let coords = ec_point.borrow().coordinates().unwrap(); 290 | [*coords.x(), *coords.y()] 291 | .into_iter() 292 | .flat_map(|coord| fe_to_limbs(coord, num_limb_bits)) 293 | .collect() 294 | } 295 | 296 | fn fe_to_limbs(fe: impl Borrow, num_limb_bits: usize) -> Vec 297 | where 298 | F1: PrimeField, 299 | F2: PrimeField, 300 | { 301 | let big = U256::from_le_bytes(fe.borrow().to_repr()); 302 | let mask = &((U256::from(1) << num_limb_bits) - U256::from(1)); 303 | (0usize..) 304 | .step_by(num_limb_bits) 305 | .map(|shift| fe_from_u256((big >> shift) & mask)) 306 | .take((F1::NUM_BITS as usize + num_limb_bits - 1) / num_limb_bits) 307 | .collect_vec() 308 | } 309 | 310 | fn fe_from_u256(u256: impl Borrow) -> F 311 | where 312 | F: PrimeField, 313 | { 314 | let bytes = u256.borrow().to_le_bytes::<32>(); 315 | F::from_repr_vartime(bytes).unwrap() 316 | } 317 | 318 | pub mod huge { 319 | use crate::{ 320 | codegen::AccumulatorEncoding, 321 | test::halo2::{random_accumulator_limbs, TestCircuit}, 322 | }; 323 | use halo2_proofs::{ 324 | arithmetic::CurveAffine, 325 | circuit::{Layouter, SimpleFloorPlanner, Value}, 326 | halo2curves::{ 327 | ff::{Field, PrimeField}, 328 | pairing::MultiMillerLoop, 329 | }, 330 | plonk::{ 331 | self, Advice, Circuit, Column, ConstraintSystem, Expression, FirstPhase, Fixed, 332 | Instance, SecondPhase, Selector, ThirdPhase, 333 | }, 334 | poly::Rotation, 335 | }; 336 | use itertools::{izip, Itertools}; 337 | use rand::RngCore; 338 | use std::{array, fmt::Debug, iter, mem}; 339 | 340 | #[derive(Clone, Debug, Default)] 341 | pub struct HugeCircuit(Vec); 342 | 343 | impl TestCircuit for HugeCircuit 344 | where 345 | M: MultiMillerLoop, 346 | M::G1Affine: CurveAffine, 347 | ::Base: PrimeField, 348 | ::ScalarExt: PrimeField, 349 | { 350 | fn min_k() -> u32 { 351 | 6 352 | } 353 | 354 | fn new(acc_encoding: Option, mut rng: impl RngCore) -> Self { 355 | let instances = if let Some(acc_encoding) = acc_encoding { 356 | random_accumulator_limbs::(acc_encoding, rng) 357 | } else { 358 | iter::repeat_with(|| M::Fr::random(&mut rng)) 359 | .take(10) 360 | .collect() 361 | }; 362 | Self(instances) 363 | } 364 | 365 | fn instances(&self) -> Vec { 366 | self.0.clone() 367 | } 368 | } 369 | 370 | impl Circuit for HugeCircuit { 371 | type Config = ( 372 | [Selector; 10], 373 | [Selector; 10], 374 | [Column; 10], 375 | [Column; 10], 376 | Column, 377 | ); 378 | type FloorPlanner = SimpleFloorPlanner; 379 | #[cfg(feature = "halo2_circuit_params")] 380 | type Params = (); 381 | 382 | fn without_witnesses(&self) -> Self { 383 | unimplemented!() 384 | } 385 | 386 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 387 | let selectors = [(); 10].map(|_| meta.selector()); 388 | let complex_selectors = [(); 10].map(|_| meta.complex_selector()); 389 | let fixeds = [(); 10].map(|_| meta.fixed_column()); 390 | let (advices, challenges) = (0..10) 391 | .map(|idx| match idx % 3 { 392 | 0 => ( 393 | meta.advice_column_in(FirstPhase), 394 | meta.challenge_usable_after(FirstPhase), 395 | ), 396 | 1 => ( 397 | meta.advice_column_in(SecondPhase), 398 | meta.challenge_usable_after(SecondPhase), 399 | ), 400 | 2 => ( 401 | meta.advice_column_in(ThirdPhase), 402 | meta.challenge_usable_after(ThirdPhase), 403 | ), 404 | _ => unreachable!(), 405 | }) 406 | .unzip::<_, _, Vec<_>, Vec<_>>(); 407 | let advices: [_; 10] = advices.try_into().unwrap(); 408 | let challenges: [_; 10] = challenges.try_into().unwrap(); 409 | let instance = meta.instance_column(); 410 | 411 | meta.create_gate("", |meta| { 412 | let selectors = selectors.map(|selector| meta.query_selector(selector)); 413 | let advices: [Expression; 10] = array::from_fn(|idx| { 414 | let rotation = Rotation((idx as i32 - advices.len() as i32) / 2); 415 | meta.query_advice(advices[idx], rotation) 416 | }); 417 | let challenges = challenges.map(|challenge| meta.query_challenge(challenge)); 418 | 419 | izip!( 420 | selectors, 421 | advices.iter().cloned(), 422 | advices[1..].iter().cloned(), 423 | advices[2..].iter().cloned(), 424 | challenges.iter().cloned(), 425 | challenges[1..].iter().cloned(), 426 | challenges[2..].iter().cloned(), 427 | ) 428 | .map(|(q, a1, a2, a3, c1, c2, c3)| q * a1 * a2 * a3 * c1 * c2 * c3) 429 | .collect_vec() 430 | }); 431 | 432 | for ((q1, q2, q3), (f1, f2, f3), (a1, a2, a3)) in izip!( 433 | complex_selectors.iter().tuple_windows(), 434 | fixeds.iter().tuple_windows(), 435 | advices.iter().tuple_windows() 436 | ) { 437 | meta.lookup_any("", |meta| { 438 | izip!([q1, q2, q3], [f1, f2, f3], [a1, a2, a3]) 439 | .map(|(q, f, a)| { 440 | let q = meta.query_selector(*q); 441 | let f = meta.query_fixed(*f, Rotation::cur()); 442 | let a = meta.query_advice(*a, Rotation::cur()); 443 | (q * a, f) 444 | }) 445 | .collect_vec() 446 | }); 447 | } 448 | 449 | fixeds.map(|column| meta.enable_equality(column)); 450 | advices.map(|column| meta.enable_equality(column)); 451 | meta.enable_equality(instance); 452 | 453 | (selectors, complex_selectors, fixeds, advices, instance) 454 | } 455 | 456 | fn synthesize( 457 | &self, 458 | (selectors, complex_selectors, fixeds, advices, instance): Self::Config, 459 | mut layouter: impl Layouter, 460 | ) -> Result<(), plonk::Error> { 461 | let assigneds = layouter.assign_region( 462 | || "", 463 | |mut region| { 464 | let offset = &mut 10; 465 | let mut next_offset = || mem::replace(offset, *offset + 1); 466 | 467 | for q in selectors { 468 | q.enable(&mut region, next_offset())?; 469 | } 470 | for q in complex_selectors { 471 | q.enable(&mut region, next_offset())?; 472 | } 473 | for (idx, column) in izip!(1.., fixeds) { 474 | let value = Value::known(M::Fr::from(idx)); 475 | region.assign_fixed(|| "", column, next_offset(), || value)?; 476 | } 477 | izip!(advices, &self.0) 478 | .map(|(column, value)| { 479 | let value = Value::known(*value); 480 | region.assign_advice(|| "", column, next_offset(), || value) 481 | }) 482 | .try_collect::<_, Vec<_>, _>() 483 | }, 484 | )?; 485 | for (idx, assigned) in izip!(0.., assigneds) { 486 | layouter.constrain_instance(assigned.cell(), instance, idx)?; 487 | } 488 | Ok(()) 489 | } 490 | } 491 | } 492 | 493 | pub mod maingate { 494 | use crate::{ 495 | codegen::AccumulatorEncoding, 496 | test::halo2::{random_accumulator_limbs, TestCircuit}, 497 | }; 498 | use halo2_maingate::{ 499 | MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, 500 | RangeInstructions, RegionCtx, 501 | }; 502 | use halo2_proofs::{ 503 | arithmetic::CurveAffine, 504 | circuit::{Layouter, SimpleFloorPlanner, Value}, 505 | halo2curves::{ 506 | ff::{Field, PrimeField}, 507 | pairing::MultiMillerLoop, 508 | }, 509 | plonk::{Circuit, ConstraintSystem, Error}, 510 | }; 511 | use itertools::Itertools; 512 | use rand::RngCore; 513 | use std::iter; 514 | 515 | #[derive(Clone)] 516 | pub struct MainGateWithRangeConfig { 517 | main_gate_config: MainGateConfig, 518 | range_config: RangeConfig, 519 | } 520 | 521 | impl MainGateWithRangeConfig { 522 | fn configure( 523 | meta: &mut ConstraintSystem, 524 | composition_bits: Vec, 525 | overflow_bits: Vec, 526 | ) -> Self { 527 | let main_gate_config = MainGate::::configure(meta); 528 | let range_config = RangeChip::::configure( 529 | meta, 530 | &main_gate_config, 531 | composition_bits, 532 | overflow_bits, 533 | ); 534 | MainGateWithRangeConfig { 535 | main_gate_config, 536 | range_config, 537 | } 538 | } 539 | 540 | fn main_gate(&self) -> MainGate { 541 | MainGate::new(self.main_gate_config.clone()) 542 | } 543 | 544 | fn range_chip(&self) -> RangeChip { 545 | RangeChip::new(self.range_config.clone()) 546 | } 547 | } 548 | 549 | #[derive(Clone, Default)] 550 | pub struct MainGateWithRange { 551 | instances: Vec, 552 | } 553 | 554 | impl TestCircuit for MainGateWithRange 555 | where 556 | M: MultiMillerLoop, 557 | M::G1Affine: CurveAffine, 558 | ::Base: PrimeField, 559 | ::ScalarExt: PrimeField, 560 | { 561 | fn min_k() -> u32 { 562 | 9 563 | } 564 | 565 | fn new(acc_encoding: Option, mut rng: impl RngCore) -> Self { 566 | let instances = if let Some(acc_encoding) = acc_encoding { 567 | random_accumulator_limbs::(acc_encoding, rng) 568 | } else { 569 | iter::repeat_with(|| M::Fr::random(&mut rng)) 570 | .take(10) 571 | .collect() 572 | }; 573 | Self { instances } 574 | } 575 | 576 | fn instances(&self) -> Vec { 577 | self.instances.clone() 578 | } 579 | } 580 | 581 | impl Circuit for MainGateWithRange { 582 | type Config = MainGateWithRangeConfig; 583 | type FloorPlanner = SimpleFloorPlanner; 584 | #[cfg(feature = "halo2_circuit_params")] 585 | type Params = (); 586 | 587 | fn without_witnesses(&self) -> Self { 588 | unimplemented!() 589 | } 590 | 591 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 592 | MainGateWithRangeConfig::configure(meta, vec![8], vec![4, 7]) 593 | } 594 | 595 | fn synthesize( 596 | &self, 597 | config: Self::Config, 598 | mut layouter: impl Layouter, 599 | ) -> Result<(), Error> { 600 | let main_gate = config.main_gate(); 601 | let range_chip = config.range_chip(); 602 | range_chip.load_table(&mut layouter)?; 603 | 604 | let advices = layouter.assign_region( 605 | || "", 606 | |region| { 607 | let mut ctx = RegionCtx::new(region, 0); 608 | 609 | let advices = self 610 | .instances 611 | .iter() 612 | .map(|value| main_gate.assign_value(&mut ctx, Value::known(*value))) 613 | .try_collect::<_, Vec<_>, _>()?; 614 | 615 | // Dummy gates to make all fixed column with values 616 | range_chip.decompose( 617 | &mut ctx, 618 | Value::known(M::Fr::from(u64::MAX)), 619 | 8, 620 | 64, 621 | )?; 622 | range_chip.decompose( 623 | &mut ctx, 624 | Value::known(M::Fr::from(u32::MAX as u64)), 625 | 8, 626 | 39, 627 | )?; 628 | let a = &advices[0]; 629 | let b = 630 | main_gate.sub_sub_with_constant(&mut ctx, a, a, a, M::Fr::from(2))?; 631 | let cond = main_gate.assign_bit(&mut ctx, Value::known(M::Fr::ONE))?; 632 | main_gate.select(&mut ctx, a, &b, &cond)?; 633 | 634 | Ok(advices) 635 | }, 636 | )?; 637 | 638 | for (offset, advice) in advices.into_iter().enumerate() { 639 | main_gate.expose_public(layouter.namespace(|| ""), advice, offset)? 640 | } 641 | 642 | Ok(()) 643 | } 644 | } 645 | } 646 | } 647 | -------------------------------------------------------------------------------- /src/transcript.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | halo2curves::{ff::PrimeField, Coordinates, CurveAffine}, 3 | transcript::{ 4 | EncodedChallenge, Transcript, TranscriptRead, TranscriptReadBuffer, TranscriptWrite, 5 | TranscriptWriterBuffer, 6 | }, 7 | }; 8 | use itertools::{chain, Itertools}; 9 | use ruint::aliases::U256; 10 | use sha3::{Digest, Keccak256}; 11 | use std::{ 12 | io::{self, Read, Write}, 13 | marker::PhantomData, 14 | mem, 15 | }; 16 | 17 | /// Transcript using Keccak256 as hash function in Fiat-Shamir transformation. 18 | #[derive(Debug, Default)] 19 | pub struct Keccak256Transcript { 20 | stream: S, 21 | buf: Vec, 22 | _marker: PhantomData, 23 | } 24 | 25 | impl Keccak256Transcript { 26 | /// Return a `Keccak256Transcript` with empty buffer. 27 | pub fn new(stream: S) -> Self { 28 | Self { 29 | stream, 30 | buf: Vec::new(), 31 | _marker: PhantomData, 32 | } 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct ChallengeEvm(C::Scalar) 38 | where 39 | C: CurveAffine, 40 | C::Scalar: PrimeField; 41 | 42 | impl EncodedChallenge for ChallengeEvm 43 | where 44 | C: CurveAffine, 45 | C::Scalar: PrimeField, 46 | { 47 | type Input = [u8; 0x20]; 48 | 49 | fn new(challenge_input: &[u8; 0x20]) -> Self { 50 | ChallengeEvm(u256_to_fe(U256::from_be_bytes(*challenge_input))) 51 | } 52 | 53 | fn get_scalar(&self) -> C::Scalar { 54 | self.0 55 | } 56 | } 57 | 58 | impl Transcript> for Keccak256Transcript 59 | where 60 | C: CurveAffine, 61 | C::Scalar: PrimeField, 62 | { 63 | fn squeeze_challenge(&mut self) -> ChallengeEvm { 64 | let buf_len = self.buf.len(); 65 | let data = chain![ 66 | mem::take(&mut self.buf), 67 | if buf_len == 0x20 { Some(1) } else { None } 68 | ] 69 | .collect_vec(); 70 | let hash: [u8; 0x20] = Keccak256::digest(data).into(); 71 | self.buf = hash.to_vec(); 72 | ChallengeEvm::new(&hash) 73 | } 74 | 75 | fn common_point(&mut self, ec_point: C) -> io::Result<()> { 76 | let coords: Coordinates = Option::from(ec_point.coordinates()).ok_or_else(|| { 77 | io::Error::new( 78 | io::ErrorKind::Other, 79 | "Invalid elliptic curve point".to_string(), 80 | ) 81 | })?; 82 | [coords.x(), coords.y()].map(|coordinate| { 83 | self.buf 84 | .extend(coordinate.to_repr().as_ref().iter().rev().cloned()); 85 | }); 86 | Ok(()) 87 | } 88 | 89 | fn common_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { 90 | self.buf.extend(scalar.to_repr().as_ref().iter().rev()); 91 | Ok(()) 92 | } 93 | } 94 | 95 | impl TranscriptRead> for Keccak256Transcript 96 | where 97 | C: CurveAffine, 98 | C::Scalar: PrimeField, 99 | { 100 | fn read_point(&mut self) -> io::Result { 101 | let mut reprs = [::Repr::default(); 2]; 102 | for repr in &mut reprs { 103 | self.stream.read_exact(repr.as_mut())?; 104 | repr.as_mut().reverse(); 105 | } 106 | let [x, y] = reprs.map(|repr| Option::from(C::Base::from_repr(repr))); 107 | let ec_point = x 108 | .zip(y) 109 | .and_then(|(x, y)| Option::from(C::from_xy(x, y))) 110 | .ok_or_else(|| { 111 | io::Error::new( 112 | io::ErrorKind::Other, 113 | "Invalid elliptic curve point".to_string(), 114 | ) 115 | })?; 116 | self.common_point(ec_point)?; 117 | Ok(ec_point) 118 | } 119 | 120 | fn read_scalar(&mut self) -> io::Result { 121 | let mut data = [0; 0x20]; 122 | self.stream.read_exact(data.as_mut())?; 123 | data.reverse(); 124 | let scalar = C::Scalar::from_repr_vartime(data) 125 | .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid scalar".to_string()))?; 126 | Transcript::>::common_scalar(self, scalar)?; 127 | Ok(scalar) 128 | } 129 | } 130 | 131 | impl TranscriptReadBuffer> for Keccak256Transcript 132 | where 133 | C: CurveAffine, 134 | C::Scalar: PrimeField, 135 | { 136 | fn init(reader: R) -> Self { 137 | Keccak256Transcript::new(reader) 138 | } 139 | } 140 | 141 | impl TranscriptWrite> for Keccak256Transcript 142 | where 143 | C: CurveAffine, 144 | C::Scalar: PrimeField, 145 | { 146 | fn write_point(&mut self, ec_point: C) -> io::Result<()> { 147 | self.common_point(ec_point)?; 148 | let coords = ec_point.coordinates().unwrap(); 149 | for coord in [coords.x(), coords.y()] { 150 | let mut repr = coord.to_repr(); 151 | repr.as_mut().reverse(); 152 | self.stream.write_all(repr.as_ref())?; 153 | } 154 | Ok(()) 155 | } 156 | 157 | fn write_scalar(&mut self, scalar: C::Scalar) -> io::Result<()> { 158 | Transcript::>::common_scalar(self, scalar)?; 159 | let mut data = scalar.to_repr(); 160 | data.as_mut().reverse(); 161 | self.stream.write_all(data.as_ref()) 162 | } 163 | } 164 | 165 | impl TranscriptWriterBuffer> for Keccak256Transcript 166 | where 167 | C: CurveAffine, 168 | C::Scalar: PrimeField, 169 | { 170 | fn init(writer: W) -> Self { 171 | Keccak256Transcript::new(writer) 172 | } 173 | 174 | fn finalize(self) -> W { 175 | self.stream 176 | } 177 | } 178 | 179 | fn u256_to_fe(value: U256) -> F 180 | where 181 | F: PrimeField, 182 | { 183 | let value = value % modulus::(); 184 | F::from_repr(value.to_le_bytes::<0x20>()).unwrap() 185 | } 186 | 187 | fn modulus() -> U256 188 | where 189 | F: PrimeField, 190 | { 191 | U256::from_le_bytes((-F::ONE).to_repr()) + U256::from(1) 192 | } 193 | -------------------------------------------------------------------------------- /templates/Halo2Verifier.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Halo2Verifier { 6 | uint256 internal constant PROOF_LEN_CPTR = {{ proof_cptr - 1 }}; 7 | uint256 internal constant PROOF_CPTR = {{ proof_cptr }}; 8 | uint256 internal constant NUM_INSTANCE_CPTR = {{ proof_cptr + (proof_len / 32) }}; 9 | uint256 internal constant INSTANCE_CPTR = {{ proof_cptr + (proof_len / 32) + 1 }}; 10 | 11 | uint256 internal constant FIRST_QUOTIENT_X_CPTR = {{ quotient_comm_cptr }}; 12 | uint256 internal constant LAST_QUOTIENT_X_CPTR = {{ quotient_comm_cptr + 2 * (num_quotients - 1) }}; 13 | 14 | uint256 internal constant VK_MPTR = {{ vk_mptr }}; 15 | uint256 internal constant VK_DIGEST_MPTR = {{ vk_mptr }}; 16 | uint256 internal constant NUM_INSTANCES_MPTR = {{ vk_mptr + 1 }}; 17 | uint256 internal constant K_MPTR = {{ vk_mptr + 2 }}; 18 | uint256 internal constant N_INV_MPTR = {{ vk_mptr + 3 }}; 19 | uint256 internal constant OMEGA_MPTR = {{ vk_mptr + 4 }}; 20 | uint256 internal constant OMEGA_INV_MPTR = {{ vk_mptr + 5 }}; 21 | uint256 internal constant OMEGA_INV_TO_L_MPTR = {{ vk_mptr + 6 }}; 22 | uint256 internal constant HAS_ACCUMULATOR_MPTR = {{ vk_mptr + 7 }}; 23 | uint256 internal constant ACC_OFFSET_MPTR = {{ vk_mptr + 8 }}; 24 | uint256 internal constant NUM_ACC_LIMBS_MPTR = {{ vk_mptr + 9 }}; 25 | uint256 internal constant NUM_ACC_LIMB_BITS_MPTR = {{ vk_mptr + 10 }}; 26 | uint256 internal constant G1_X_MPTR = {{ vk_mptr + 11 }}; 27 | uint256 internal constant G1_Y_MPTR = {{ vk_mptr + 12 }}; 28 | uint256 internal constant G2_X_1_MPTR = {{ vk_mptr + 13 }}; 29 | uint256 internal constant G2_X_2_MPTR = {{ vk_mptr + 14 }}; 30 | uint256 internal constant G2_Y_1_MPTR = {{ vk_mptr + 15 }}; 31 | uint256 internal constant G2_Y_2_MPTR = {{ vk_mptr + 16 }}; 32 | uint256 internal constant NEG_S_G2_X_1_MPTR = {{ vk_mptr + 17 }}; 33 | uint256 internal constant NEG_S_G2_X_2_MPTR = {{ vk_mptr + 18 }}; 34 | uint256 internal constant NEG_S_G2_Y_1_MPTR = {{ vk_mptr + 19 }}; 35 | uint256 internal constant NEG_S_G2_Y_2_MPTR = {{ vk_mptr + 20 }}; 36 | 37 | uint256 internal constant CHALLENGE_MPTR = {{ challenge_mptr }}; 38 | 39 | uint256 internal constant THETA_MPTR = {{ theta_mptr }}; 40 | uint256 internal constant BETA_MPTR = {{ theta_mptr + 1 }}; 41 | uint256 internal constant GAMMA_MPTR = {{ theta_mptr + 2 }}; 42 | uint256 internal constant Y_MPTR = {{ theta_mptr + 3 }}; 43 | uint256 internal constant X_MPTR = {{ theta_mptr + 4 }}; 44 | {%- match scheme %} 45 | {%- when Bdfg21 %} 46 | uint256 internal constant ZETA_MPTR = {{ theta_mptr + 5 }}; 47 | uint256 internal constant NU_MPTR = {{ theta_mptr + 6 }}; 48 | uint256 internal constant MU_MPTR = {{ theta_mptr + 7 }}; 49 | {%- when Gwc19 %} 50 | uint256 internal constant NU_MPTR = {{ theta_mptr + 5 }}; 51 | uint256 internal constant MU_MPTR = {{ theta_mptr + 6 }}; 52 | {%- endmatch %} 53 | 54 | uint256 internal constant ACC_LHS_X_MPTR = {{ theta_mptr + 8 }}; 55 | uint256 internal constant ACC_LHS_Y_MPTR = {{ theta_mptr + 9 }}; 56 | uint256 internal constant ACC_RHS_X_MPTR = {{ theta_mptr + 10 }}; 57 | uint256 internal constant ACC_RHS_Y_MPTR = {{ theta_mptr + 11 }}; 58 | uint256 internal constant X_N_MPTR = {{ theta_mptr + 12 }}; 59 | uint256 internal constant X_N_MINUS_1_INV_MPTR = {{ theta_mptr + 13 }}; 60 | uint256 internal constant L_LAST_MPTR = {{ theta_mptr + 14 }}; 61 | uint256 internal constant L_BLIND_MPTR = {{ theta_mptr + 15 }}; 62 | uint256 internal constant L_0_MPTR = {{ theta_mptr + 16 }}; 63 | uint256 internal constant INSTANCE_EVAL_MPTR = {{ theta_mptr + 17 }}; 64 | uint256 internal constant QUOTIENT_EVAL_MPTR = {{ theta_mptr + 18 }}; 65 | uint256 internal constant QUOTIENT_X_MPTR = {{ theta_mptr + 19 }}; 66 | uint256 internal constant QUOTIENT_Y_MPTR = {{ theta_mptr + 20 }}; 67 | uint256 internal constant G1_SCALAR_MPTR = {{ theta_mptr + 21 }}; 68 | uint256 internal constant PAIRING_LHS_X_MPTR = {{ theta_mptr + 22 }}; 69 | uint256 internal constant PAIRING_LHS_Y_MPTR = {{ theta_mptr + 23 }}; 70 | uint256 internal constant PAIRING_RHS_X_MPTR = {{ theta_mptr + 24 }}; 71 | uint256 internal constant PAIRING_RHS_Y_MPTR = {{ theta_mptr + 25 }}; 72 | 73 | function verifyProof( 74 | {%- match self.embedded_vk %} 75 | {%- when None %} 76 | address vk, 77 | {%- else %} 78 | {%- endmatch %} 79 | bytes calldata proof, 80 | uint256[] calldata instances 81 | ) public view returns (bool) { 82 | assembly { 83 | // Read EC point (x, y) at (proof_cptr, proof_cptr + 0x20), 84 | // and check if the point is on affine plane, 85 | // and store them in (hash_mptr, hash_mptr + 0x20). 86 | // Return updated (success, proof_cptr, hash_mptr). 87 | function read_ec_point(success, proof_cptr, hash_mptr, q) -> ret0, ret1, ret2 { 88 | let x := calldataload(proof_cptr) 89 | let y := calldataload(add(proof_cptr, 0x20)) 90 | ret0 := and(success, lt(x, q)) 91 | ret0 := and(ret0, lt(y, q)) 92 | ret0 := and(ret0, eq(mulmod(y, y, q), addmod(mulmod(x, mulmod(x, x, q), q), 3, q))) 93 | mstore(hash_mptr, x) 94 | mstore(add(hash_mptr, 0x20), y) 95 | ret1 := add(proof_cptr, 0x40) 96 | ret2 := add(hash_mptr, 0x40) 97 | } 98 | 99 | // Squeeze challenge by keccak256(memory[0..hash_mptr]), 100 | // and store hash mod r as challenge in challenge_mptr, 101 | // and push back hash in 0x00 as the first input for next squeeze. 102 | // Return updated (challenge_mptr, hash_mptr). 103 | function squeeze_challenge(challenge_mptr, hash_mptr, r) -> ret0, ret1 { 104 | let hash := keccak256(0x00, hash_mptr) 105 | mstore(challenge_mptr, mod(hash, r)) 106 | mstore(0x00, hash) 107 | ret0 := add(challenge_mptr, 0x20) 108 | ret1 := 0x20 109 | } 110 | 111 | // Squeeze challenge without absorbing new input from calldata, 112 | // by putting an extra 0x01 in memory[0x20] and squeeze by keccak256(memory[0..21]), 113 | // and store hash mod r as challenge in challenge_mptr, 114 | // and push back hash in 0x00 as the first input for next squeeze. 115 | // Return updated (challenge_mptr). 116 | function squeeze_challenge_cont(challenge_mptr, r) -> ret { 117 | mstore8(0x20, 0x01) 118 | let hash := keccak256(0x00, 0x21) 119 | mstore(challenge_mptr, mod(hash, r)) 120 | mstore(0x00, hash) 121 | ret := add(challenge_mptr, 0x20) 122 | } 123 | 124 | // Batch invert values in memory[mptr_start..mptr_end] in place. 125 | // Return updated (success). 126 | function batch_invert(success, mptr_start, mptr_end, r) -> ret { 127 | let gp_mptr := mptr_end 128 | let gp := mload(mptr_start) 129 | let mptr := add(mptr_start, 0x20) 130 | for 131 | {} 132 | lt(mptr, sub(mptr_end, 0x20)) 133 | {} 134 | { 135 | gp := mulmod(gp, mload(mptr), r) 136 | mstore(gp_mptr, gp) 137 | mptr := add(mptr, 0x20) 138 | gp_mptr := add(gp_mptr, 0x20) 139 | } 140 | gp := mulmod(gp, mload(mptr), r) 141 | 142 | mstore(gp_mptr, 0x20) 143 | mstore(add(gp_mptr, 0x20), 0x20) 144 | mstore(add(gp_mptr, 0x40), 0x20) 145 | mstore(add(gp_mptr, 0x60), gp) 146 | mstore(add(gp_mptr, 0x80), sub(r, 2)) 147 | mstore(add(gp_mptr, 0xa0), r) 148 | ret := and(success, staticcall(gas(), 0x05, gp_mptr, 0xc0, gp_mptr, 0x20)) 149 | let all_inv := mload(gp_mptr) 150 | 151 | let first_mptr := mptr_start 152 | let second_mptr := add(first_mptr, 0x20) 153 | gp_mptr := sub(gp_mptr, 0x20) 154 | for 155 | {} 156 | lt(second_mptr, mptr) 157 | {} 158 | { 159 | let inv := mulmod(all_inv, mload(gp_mptr), r) 160 | all_inv := mulmod(all_inv, mload(mptr), r) 161 | mstore(mptr, inv) 162 | mptr := sub(mptr, 0x20) 163 | gp_mptr := sub(gp_mptr, 0x20) 164 | } 165 | let inv_first := mulmod(all_inv, mload(second_mptr), r) 166 | let inv_second := mulmod(all_inv, mload(first_mptr), r) 167 | mstore(first_mptr, inv_first) 168 | mstore(second_mptr, inv_second) 169 | } 170 | 171 | // Add (x, y) into point at (0x00, 0x20). 172 | // Return updated (success). 173 | function ec_add_acc(success, x, y) -> ret { 174 | mstore(0x40, x) 175 | mstore(0x60, y) 176 | ret := and(success, staticcall(gas(), 0x06, 0x00, 0x80, 0x00, 0x40)) 177 | } 178 | 179 | // Scale point at (0x00, 0x20) by scalar. 180 | function ec_mul_acc(success, scalar) -> ret { 181 | mstore(0x40, scalar) 182 | ret := and(success, staticcall(gas(), 0x07, 0x00, 0x60, 0x00, 0x40)) 183 | } 184 | 185 | // Add (x, y) into point at (0x80, 0xa0). 186 | // Return updated (success). 187 | function ec_add_tmp(success, x, y) -> ret { 188 | mstore(0xc0, x) 189 | mstore(0xe0, y) 190 | ret := and(success, staticcall(gas(), 0x06, 0x80, 0x80, 0x80, 0x40)) 191 | } 192 | 193 | // Scale point at (0x80, 0xa0) by scalar. 194 | // Return updated (success). 195 | function ec_mul_tmp(success, scalar) -> ret { 196 | mstore(0xc0, scalar) 197 | ret := and(success, staticcall(gas(), 0x07, 0x80, 0x60, 0x80, 0x40)) 198 | } 199 | 200 | // Perform pairing check. 201 | // Return updated (success). 202 | function ec_pairing(success, lhs_x, lhs_y, rhs_x, rhs_y) -> ret { 203 | mstore(0x00, lhs_x) 204 | mstore(0x20, lhs_y) 205 | mstore(0x40, mload(G2_X_1_MPTR)) 206 | mstore(0x60, mload(G2_X_2_MPTR)) 207 | mstore(0x80, mload(G2_Y_1_MPTR)) 208 | mstore(0xa0, mload(G2_Y_2_MPTR)) 209 | mstore(0xc0, rhs_x) 210 | mstore(0xe0, rhs_y) 211 | mstore(0x100, mload(NEG_S_G2_X_1_MPTR)) 212 | mstore(0x120, mload(NEG_S_G2_X_2_MPTR)) 213 | mstore(0x140, mload(NEG_S_G2_Y_1_MPTR)) 214 | mstore(0x160, mload(NEG_S_G2_Y_2_MPTR)) 215 | ret := and(success, staticcall(gas(), 0x08, 0x00, 0x180, 0x00, 0x20)) 216 | ret := and(ret, mload(0x00)) 217 | } 218 | 219 | // Modulus 220 | let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 // BN254 base field 221 | let r := 21888242871839275222246405745257275088548364400416034343698204186575808495617 // BN254 scalar field 222 | 223 | // Initialize success as true 224 | let success := true 225 | 226 | { 227 | {%- match self.embedded_vk %} 228 | {%- when Some with (embedded_vk) %} 229 | // Load vk_digest and num_instances of vk into memory 230 | {%- for (name, chunk) in embedded_vk.constants[..2] %} 231 | mstore({{ vk_mptr + loop.index0 }}, {{ chunk|hex_padded(64) }}) // {{ name }} 232 | {%- endfor %} 233 | {%- when None %} 234 | // Copy vk_digest and num_instances of vk into memory 235 | extcodecopy(vk, VK_MPTR, 0x00, 0x40) 236 | {%- endmatch %} 237 | 238 | // Check valid length of proof 239 | success := and(success, eq({{ proof_len|hex() }}, calldataload(PROOF_LEN_CPTR))) 240 | 241 | // Check valid length of instances 242 | let num_instances := mload(NUM_INSTANCES_MPTR) 243 | success := and(success, eq(num_instances, calldataload(NUM_INSTANCE_CPTR))) 244 | 245 | // Absorb vk diegst 246 | mstore(0x00, mload(VK_DIGEST_MPTR)) 247 | 248 | // Read instances and witness commitments and generate challenges 249 | let hash_mptr := 0x20 250 | let instance_cptr := INSTANCE_CPTR 251 | for 252 | { let instance_cptr_end := add(instance_cptr, mul(0x20, num_instances)) } 253 | lt(instance_cptr, instance_cptr_end) 254 | {} 255 | { 256 | let instance := calldataload(instance_cptr) 257 | success := and(success, lt(instance, r)) 258 | mstore(hash_mptr, instance) 259 | instance_cptr := add(instance_cptr, 0x20) 260 | hash_mptr := add(hash_mptr, 0x20) 261 | } 262 | 263 | let proof_cptr := PROOF_CPTR 264 | let challenge_mptr := CHALLENGE_MPTR 265 | {%- for num_advices in num_advices %} 266 | 267 | // Phase {{ loop.index }} 268 | for 269 | { let proof_cptr_end := add(proof_cptr, {{ (2 * 32 * num_advices)|hex() }}) } 270 | lt(proof_cptr, proof_cptr_end) 271 | {} 272 | { 273 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 274 | } 275 | 276 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) 277 | {%- for _ in 0..num_challenges[loop.index0] - 1 %} 278 | challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) 279 | {%- endfor %} 280 | {%- endfor %} 281 | 282 | // Read evaluations 283 | for 284 | { let proof_cptr_end := add(proof_cptr, {{ (32 * num_evals)|hex() }}) } 285 | lt(proof_cptr, proof_cptr_end) 286 | {} 287 | { 288 | let eval := calldataload(proof_cptr) 289 | success := and(success, lt(eval, r)) 290 | mstore(hash_mptr, eval) 291 | proof_cptr := add(proof_cptr, 0x20) 292 | hash_mptr := add(hash_mptr, 0x20) 293 | } 294 | 295 | // Read batch opening proof and generate challenges 296 | {%- match scheme %} 297 | {%- when Bdfg21 %} 298 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // zeta 299 | challenge_mptr := squeeze_challenge_cont(challenge_mptr, r) // nu 300 | 301 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W 302 | 303 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // mu 304 | 305 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) // W' 306 | {%- when Gwc19 %} 307 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // nu 308 | 309 | for 310 | { let proof_cptr_end := add(proof_cptr, {{ (2 * 32 * num_rotations)|hex() }}) } 311 | lt(proof_cptr, proof_cptr_end) 312 | {} 313 | { 314 | success, proof_cptr, hash_mptr := read_ec_point(success, proof_cptr, hash_mptr, q) 315 | } 316 | 317 | challenge_mptr, hash_mptr := squeeze_challenge(challenge_mptr, hash_mptr, r) // mu 318 | {%- endmatch %} 319 | 320 | {%~ match self.embedded_vk %} 321 | {%- when Some with (embedded_vk) %} 322 | // Load full vk into memory 323 | {%- for (name, chunk) in embedded_vk.constants %} 324 | mstore({{ vk_mptr + loop.index0 }}, {{ chunk|hex_padded(64) }}) // {{ name }} 325 | {%- endfor %} 326 | {%- for (x, y) in embedded_vk.fixed_comms %} 327 | {%- let offset = embedded_vk.constants.len() %} 328 | mstore({{ vk_mptr + offset + 2 * loop.index0 }}, {{ x|hex_padded(64) }}) // fixed_comms[{{ loop.index0 }}].x 329 | mstore({{ vk_mptr + offset + 2 * loop.index0 + 1 }}, {{ y|hex_padded(64) }}) // fixed_comms[{{ loop.index0 }}].y 330 | {%- endfor %} 331 | {%- for (x, y) in embedded_vk.permutation_comms %} 332 | {%- let offset = embedded_vk.constants.len() + 2 * embedded_vk.fixed_comms.len() %} 333 | mstore({{ vk_mptr + offset + 2 * loop.index0 }}, {{ x|hex_padded(64) }}) // permutation_comms[{{ loop.index0 }}].x 334 | mstore({{ vk_mptr + offset + 2 * loop.index0 + 1 }}, {{ y|hex_padded(64) }}) // permutation_comms[{{ loop.index0 }}].y 335 | {%- endfor %} 336 | {%- when None %} 337 | // Copy full vk into memory 338 | extcodecopy(vk, VK_MPTR, 0x00, {{ vk_len|hex() }}) 339 | {%- endmatch %} 340 | 341 | // Read accumulator from instances 342 | if mload(HAS_ACCUMULATOR_MPTR) { 343 | let num_limbs := mload(NUM_ACC_LIMBS_MPTR) 344 | let num_limb_bits := mload(NUM_ACC_LIMB_BITS_MPTR) 345 | 346 | let cptr := add(INSTANCE_CPTR, mul(mload(ACC_OFFSET_MPTR), 0x20)) 347 | let lhs_y_off := mul(num_limbs, 0x20) 348 | let rhs_x_off := mul(lhs_y_off, 2) 349 | let rhs_y_off := mul(lhs_y_off, 3) 350 | let lhs_x := calldataload(cptr) 351 | let lhs_y := calldataload(add(cptr, lhs_y_off)) 352 | let rhs_x := calldataload(add(cptr, rhs_x_off)) 353 | let rhs_y := calldataload(add(cptr, rhs_y_off)) 354 | for 355 | { 356 | let cptr_end := add(cptr, mul(0x20, num_limbs)) 357 | let shift := num_limb_bits 358 | } 359 | lt(cptr, cptr_end) 360 | {} 361 | { 362 | cptr := add(cptr, 0x20) 363 | lhs_x := add(lhs_x, shl(shift, calldataload(cptr))) 364 | lhs_y := add(lhs_y, shl(shift, calldataload(add(cptr, lhs_y_off)))) 365 | rhs_x := add(rhs_x, shl(shift, calldataload(add(cptr, rhs_x_off)))) 366 | rhs_y := add(rhs_y, shl(shift, calldataload(add(cptr, rhs_y_off)))) 367 | shift := add(shift, num_limb_bits) 368 | } 369 | 370 | success := and(success, and(lt(lhs_x, q), lt(lhs_y, q))) 371 | success := and(success, eq(mulmod(lhs_y, lhs_y, q), addmod(mulmod(lhs_x, mulmod(lhs_x, lhs_x, q), q), 3, q))) 372 | success := and(success, and(lt(rhs_x, q), lt(rhs_y, q))) 373 | success := and(success, eq(mulmod(rhs_y, rhs_y, q), addmod(mulmod(rhs_x, mulmod(rhs_x, rhs_x, q), q), 3, q))) 374 | 375 | mstore(ACC_LHS_X_MPTR, lhs_x) 376 | mstore(ACC_LHS_Y_MPTR, lhs_y) 377 | mstore(ACC_RHS_X_MPTR, rhs_x) 378 | mstore(ACC_RHS_Y_MPTR, rhs_y) 379 | } 380 | 381 | pop(q) 382 | } 383 | 384 | // Revert earlier if anything from calldata is invalid 385 | if iszero(success) { 386 | revert(0, 0) 387 | } 388 | 389 | // Compute lagrange evaluations and instance evaluation 390 | { 391 | let k := mload(K_MPTR) 392 | let x := mload(X_MPTR) 393 | let x_n := x 394 | for 395 | { let idx := 0 } 396 | lt(idx, k) 397 | { idx := add(idx, 1) } 398 | { 399 | x_n := mulmod(x_n, x_n, r) 400 | } 401 | 402 | let omega := mload(OMEGA_MPTR) 403 | 404 | let mptr := X_N_MPTR 405 | let mptr_end := add(mptr, mul(0x20, add(mload(NUM_INSTANCES_MPTR), {{ num_neg_lagranges }}))) 406 | if iszero(mload(NUM_INSTANCES_MPTR)) { 407 | mptr_end := add(mptr_end, 0x20) 408 | } 409 | for 410 | { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } 411 | lt(mptr, mptr_end) 412 | { mptr := add(mptr, 0x20) } 413 | { 414 | mstore(mptr, addmod(x, sub(r, pow_of_omega), r)) 415 | pow_of_omega := mulmod(pow_of_omega, omega, r) 416 | } 417 | let x_n_minus_1 := addmod(x_n, sub(r, 1), r) 418 | mstore(mptr_end, x_n_minus_1) 419 | success := batch_invert(success, X_N_MPTR, add(mptr_end, 0x20), r) 420 | 421 | mptr := X_N_MPTR 422 | let l_i_common := mulmod(x_n_minus_1, mload(N_INV_MPTR), r) 423 | for 424 | { let pow_of_omega := mload(OMEGA_INV_TO_L_MPTR) } 425 | lt(mptr, mptr_end) 426 | { mptr := add(mptr, 0x20) } 427 | { 428 | mstore(mptr, mulmod(l_i_common, mulmod(mload(mptr), pow_of_omega, r), r)) 429 | pow_of_omega := mulmod(pow_of_omega, omega, r) 430 | } 431 | 432 | let l_blind := mload(add(X_N_MPTR, 0x20)) 433 | let l_i_cptr := add(X_N_MPTR, 0x40) 434 | for 435 | { let l_i_cptr_end := add(X_N_MPTR, {{ (num_neg_lagranges * 32)|hex() }}) } 436 | lt(l_i_cptr, l_i_cptr_end) 437 | { l_i_cptr := add(l_i_cptr, 0x20) } 438 | { 439 | l_blind := addmod(l_blind, mload(l_i_cptr), r) 440 | } 441 | 442 | let instance_eval := 0 443 | for 444 | { 445 | let instance_cptr := INSTANCE_CPTR 446 | let instance_cptr_end := add(instance_cptr, mul(0x20, mload(NUM_INSTANCES_MPTR))) 447 | } 448 | lt(instance_cptr, instance_cptr_end) 449 | { 450 | instance_cptr := add(instance_cptr, 0x20) 451 | l_i_cptr := add(l_i_cptr, 0x20) 452 | } 453 | { 454 | instance_eval := addmod(instance_eval, mulmod(mload(l_i_cptr), calldataload(instance_cptr), r), r) 455 | } 456 | 457 | let x_n_minus_1_inv := mload(mptr_end) 458 | let l_last := mload(X_N_MPTR) 459 | let l_0 := mload(add(X_N_MPTR, {{ (num_neg_lagranges * 32)|hex() }})) 460 | 461 | mstore(X_N_MPTR, x_n) 462 | mstore(X_N_MINUS_1_INV_MPTR, x_n_minus_1_inv) 463 | mstore(L_LAST_MPTR, l_last) 464 | mstore(L_BLIND_MPTR, l_blind) 465 | mstore(L_0_MPTR, l_0) 466 | mstore(INSTANCE_EVAL_MPTR, instance_eval) 467 | } 468 | 469 | // Compute quotient evavluation 470 | { 471 | let quotient_eval_numer 472 | let delta := 4131629893567559867359510883348571134090853742863529169391034518566172092834 473 | let y := mload(Y_MPTR) 474 | 475 | {%- for code_block in quotient_eval_numer_computations %} 476 | { 477 | {%- for line in code_block %} 478 | {{ line }} 479 | {%- endfor %} 480 | } 481 | {%- endfor %} 482 | 483 | pop(y) 484 | pop(delta) 485 | 486 | let quotient_eval := mulmod(quotient_eval_numer, mload(X_N_MINUS_1_INV_MPTR), r) 487 | mstore(QUOTIENT_EVAL_MPTR, quotient_eval) 488 | } 489 | 490 | // Compute quotient commitment 491 | { 492 | mstore(0x00, calldataload(LAST_QUOTIENT_X_CPTR)) 493 | mstore(0x20, calldataload(add(LAST_QUOTIENT_X_CPTR, 0x20))) 494 | let x_n := mload(X_N_MPTR) 495 | for 496 | { 497 | let cptr := sub(LAST_QUOTIENT_X_CPTR, 0x40) 498 | let cptr_end := sub(FIRST_QUOTIENT_X_CPTR, 0x40) 499 | } 500 | lt(cptr_end, cptr) 501 | {} 502 | { 503 | success := ec_mul_acc(success, x_n) 504 | success := ec_add_acc(success, calldataload(cptr), calldataload(add(cptr, 0x20))) 505 | cptr := sub(cptr, 0x40) 506 | } 507 | mstore(QUOTIENT_X_MPTR, mload(0x00)) 508 | mstore(QUOTIENT_Y_MPTR, mload(0x20)) 509 | } 510 | 511 | // Compute pairing lhs and rhs 512 | { 513 | {%- for code_block in pcs_computations %} 514 | { 515 | {%- for line in code_block %} 516 | {{ line }} 517 | {%- endfor %} 518 | } 519 | {%- endfor %} 520 | } 521 | 522 | // Random linear combine with accumulator 523 | if mload(HAS_ACCUMULATOR_MPTR) { 524 | mstore(0x00, mload(ACC_LHS_X_MPTR)) 525 | mstore(0x20, mload(ACC_LHS_Y_MPTR)) 526 | mstore(0x40, mload(ACC_RHS_X_MPTR)) 527 | mstore(0x60, mload(ACC_RHS_Y_MPTR)) 528 | mstore(0x80, mload(PAIRING_LHS_X_MPTR)) 529 | mstore(0xa0, mload(PAIRING_LHS_Y_MPTR)) 530 | mstore(0xc0, mload(PAIRING_RHS_X_MPTR)) 531 | mstore(0xe0, mload(PAIRING_RHS_Y_MPTR)) 532 | let challenge := mod(keccak256(0x00, 0x100), r) 533 | 534 | // [pairing_lhs] += challenge * [acc_lhs] 535 | success := ec_mul_acc(success, challenge) 536 | success := ec_add_acc(success, mload(PAIRING_LHS_X_MPTR), mload(PAIRING_LHS_Y_MPTR)) 537 | mstore(PAIRING_LHS_X_MPTR, mload(0x00)) 538 | mstore(PAIRING_LHS_Y_MPTR, mload(0x20)) 539 | 540 | // [pairing_rhs] += challenge * [acc_rhs] 541 | mstore(0x00, mload(ACC_RHS_X_MPTR)) 542 | mstore(0x20, mload(ACC_RHS_Y_MPTR)) 543 | success := ec_mul_acc(success, challenge) 544 | success := ec_add_acc(success, mload(PAIRING_RHS_X_MPTR), mload(PAIRING_RHS_Y_MPTR)) 545 | mstore(PAIRING_RHS_X_MPTR, mload(0x00)) 546 | mstore(PAIRING_RHS_Y_MPTR, mload(0x20)) 547 | } 548 | 549 | // Perform pairing 550 | success := ec_pairing( 551 | success, 552 | mload(PAIRING_LHS_X_MPTR), 553 | mload(PAIRING_LHS_Y_MPTR), 554 | mload(PAIRING_RHS_X_MPTR), 555 | mload(PAIRING_RHS_Y_MPTR) 556 | ) 557 | 558 | // Revert if anything fails 559 | if iszero(success) { 560 | revert(0x00, 0x00) 561 | } 562 | 563 | // Return 1 as result if everything succeeds 564 | mstore(0x00, 1) 565 | return(0x00, 0x20) 566 | } 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /templates/Halo2VerifyingKey.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | 3 | pragma solidity ^0.8.0; 4 | 5 | contract Halo2VerifyingKey { 6 | constructor() { 7 | assembly { 8 | {%- for (name, chunk) in constants %} 9 | mstore({{ (32 * loop.index0)|hex_padded(4) }}, {{ chunk|hex_padded(64) }}) // {{ name }} 10 | {%- endfor %} 11 | {%- for (x, y) in fixed_comms %} 12 | {%- let offset = constants.len() %} 13 | mstore({{ (32 * (offset + 2 * loop.index0))|hex_padded(4) }}, {{ x|hex_padded(64) }}) // fixed_comms[{{ loop.index0 }}].x 14 | mstore({{ (32 * (offset + 2 * loop.index0 + 1))|hex_padded(4) }}, {{ y|hex_padded(64) }}) // fixed_comms[{{ loop.index0 }}].y 15 | {%- endfor %} 16 | {%- for (x, y) in permutation_comms %} 17 | {%- let offset = constants.len() + 2 * fixed_comms.len() %} 18 | mstore({{ (32 * (offset + 2 * loop.index0))|hex_padded(4) }}, {{ x|hex_padded(64) }}) // permutation_comms[{{ loop.index0 }}].x 19 | mstore({{ (32 * (offset + 2 * loop.index0 + 1))|hex_padded(4) }}, {{ y|hex_padded(64) }}) // permutation_comms[{{ loop.index0 }}].y 20 | {%- endfor %} 21 | 22 | return(0, {{ (32 * (constants.len() + 2 * fixed_comms.len() + 2 * permutation_comms.len()))|hex() }}) 23 | } 24 | } 25 | } 26 | --------------------------------------------------------------------------------