├── rust-toolchain ├── .gitignore ├── snark-verifier ├── src │ ├── system │ │ └── halo2 │ │ │ ├── test │ │ │ ├── circuit.rs │ │ │ ├── ipa │ │ │ │ └── native.rs │ │ │ ├── kzg │ │ │ │ ├── native.rs │ │ │ │ └── evm.rs │ │ │ ├── circuit │ │ │ │ ├── maingate.rs │ │ │ │ └── standard.rs │ │ │ ├── kzg.rs │ │ │ └── ipa.rs │ │ │ ├── strategy.rs │ │ │ ├── transcript.rs │ │ │ └── test.rs │ ├── pcs │ │ ├── ipa │ │ │ ├── multiopen.rs │ │ │ ├── accumulator.rs │ │ │ ├── decider.rs │ │ │ └── accumulation.rs │ │ ├── kzg │ │ │ ├── multiopen.rs │ │ │ ├── multiopen │ │ │ │ └── gwc19.rs │ │ │ ├── accumulation.rs │ │ │ ├── decider.rs │ │ │ └── accumulator.rs │ │ └── kzg.rs │ ├── system.rs │ ├── util │ │ ├── hash.rs │ │ ├── transcript.rs │ │ ├── poly.rs │ │ ├── hash │ │ │ └── poseidon.rs │ │ ├── arithmetic.rs │ │ └── msm.rs │ ├── loader │ │ ├── evm.rs │ │ ├── halo2.rs │ │ ├── evm │ │ │ ├── code.rs │ │ │ ├── util │ │ │ │ └── executor.rs │ │ │ └── util.rs │ │ ├── halo2 │ │ │ └── test.rs │ │ └── native.rs │ ├── lib.rs │ ├── cost.rs │ ├── verifier.rs │ ├── util.rs │ ├── pcs.rs │ ├── verifier │ │ └── plonk.rs │ └── loader.rs ├── Cargo.toml └── examples │ └── evm-verifier.rs ├── Cargo.toml ├── snark-verifier-sdk ├── README.md ├── Cargo.toml ├── examples │ └── standard_plonk.rs ├── src │ ├── evm.rs │ └── lib.rs └── benches │ └── standard_plonk.rs ├── LICENSE-MIT ├── README.md ├── .github └── workflows │ └── ci.yaml └── LICENSE-APACHE /rust-toolchain: -------------------------------------------------------------------------------- 1 | 1.75.0 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | .DS_Store 3 | 4 | /target 5 | testdata 6 | 7 | Cargo.lock 8 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/circuit.rs: -------------------------------------------------------------------------------- 1 | pub mod maingate; 2 | pub mod standard; 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "snark-verifier", 4 | "snark-verifier-sdk" 5 | ] 6 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/ipa/multiopen.rs: -------------------------------------------------------------------------------- 1 | mod bgh19; 2 | 3 | pub use bgh19::{Bgh19, Bgh19Proof}; 4 | -------------------------------------------------------------------------------- /snark-verifier/src/system.rs: -------------------------------------------------------------------------------- 1 | //! Proof systems `snark-verifier` supports 2 | 3 | #[cfg(feature = "system_halo2")] 4 | pub mod halo2; 5 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg/multiopen.rs: -------------------------------------------------------------------------------- 1 | mod bdfg21; 2 | mod gwc19; 3 | 4 | pub use bdfg21::{Bdfg21, Bdfg21Proof}; 5 | pub use gwc19::{Gwc19, Gwc19Proof}; 6 | -------------------------------------------------------------------------------- /snark-verifier/src/util/hash.rs: -------------------------------------------------------------------------------- 1 | //! Hash algorithms. 2 | 3 | #[cfg(feature = "loader_halo2")] 4 | mod poseidon; 5 | 6 | #[cfg(feature = "loader_halo2")] 7 | pub use crate::util::hash::poseidon::Poseidon; 8 | 9 | #[cfg(feature = "loader_evm")] 10 | pub use sha3::{Digest, Keccak256}; 11 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/evm.rs: -------------------------------------------------------------------------------- 1 | //! `Loader` implementation for generating yul code as EVM verifier. 2 | 3 | mod code; 4 | pub(crate) mod loader; 5 | pub(crate) mod util; 6 | 7 | pub use loader::{EcPoint, EvmLoader, Scalar}; 8 | pub use util::{ 9 | compile_solidity, deploy_and_call, encode_calldata, estimate_gas, fe_to_u256, modulus, 10 | u256_to_fe, Address, B256, U256, U512, 11 | }; 12 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/ipa/accumulator.rs: -------------------------------------------------------------------------------- 1 | use crate::{loader::Loader, util::arithmetic::CurveAffine}; 2 | 3 | /// Inner product argument accumulator. 4 | #[derive(Clone, Debug)] 5 | pub struct IpaAccumulator 6 | where 7 | C: CurveAffine, 8 | L: Loader, 9 | { 10 | /// $\xi$. 11 | pub xi: Vec, 12 | /// $U$. 13 | pub u: L::LoadedEcPoint, 14 | } 15 | 16 | impl IpaAccumulator 17 | where 18 | C: CurveAffine, 19 | L: Loader, 20 | { 21 | /// Initialize a [`IpaAccumulator`]. 22 | pub fn new(xi: Vec, u: L::LoadedEcPoint) -> Self { 23 | Self { xi, u } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /snark-verifier/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Generic (S)NARK verifier. 2 | 3 | #![allow( 4 | clippy::type_complexity, 5 | clippy::too_many_arguments, 6 | clippy::upper_case_acronyms 7 | )] 8 | #![deny(missing_debug_implementations, missing_docs, unsafe_code, rustdoc::all)] 9 | 10 | pub mod cost; 11 | pub mod loader; 12 | pub mod pcs; 13 | pub mod system; 14 | pub mod util; 15 | pub mod verifier; 16 | 17 | /// Error that could happen while verification. 18 | #[derive(Clone, Debug)] 19 | pub enum Error { 20 | /// Instances that don't match the amount specified in protocol. 21 | InvalidInstances, 22 | /// Protocol that is unreasonable for a verifier. 23 | InvalidProtocol(String), 24 | /// Assertion failure while verification. 25 | AssertionFailure(String), 26 | /// Transcript error. 27 | Transcript(std::io::ErrorKind, String), 28 | } 29 | -------------------------------------------------------------------------------- /snark-verifier-sdk/README.md: -------------------------------------------------------------------------------- 1 | # Snark Verifier SDK 2 | 3 | To make file storage go in the correct places, 4 | 5 | ```bash 6 | cd snark-verifier-sdk 7 | ``` 8 | 9 | To run standard plonk example: 10 | 11 | ```bash 12 | cargo run --example standard_plonk --release 13 | ``` 14 | 15 | If feature "loader_evm" is on, this will generate yul code for the verifier contract and simulate a transaction call to that contract with generated proof calldata using revm. 16 | 17 | This example is essentially the same as [`evm-verifier-with-accumulator`](../snark-verifier/examples/evm-verifier-with-accumulator.rs) except that it uses this SDK and uses SHPLONK as the polynomial multi-open scheme instead of GWC (multi-open scheme from original PLONK paper). 18 | 19 | To run standard Plonk benchmark: 20 | 21 | ```bash 22 | cargo bench --bench standard_plonk 23 | ``` 24 | 25 | These examples/benches will generate unsafe trusted setups in `./params` folder. It will also cache proving keys and certain snarks. 26 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg.rs: -------------------------------------------------------------------------------- 1 | //! [KZG]() 2 | //! polynomial commitment scheme and accumulation scheme. 3 | 4 | mod accumulation; 5 | mod accumulator; 6 | mod decider; 7 | mod multiopen; 8 | 9 | pub use accumulation::{KzgAs, KzgAsProvingKey, KzgAsVerifyingKey}; 10 | pub use accumulator::{KzgAccumulator, LimbsEncoding}; 11 | pub use decider::KzgDecidingKey; 12 | pub use multiopen::{Bdfg21, Bdfg21Proof, Gwc19, Gwc19Proof}; 13 | 14 | #[cfg(feature = "loader_halo2")] 15 | pub use accumulator::LimbsEncodingInstructions; 16 | 17 | /// KZG succinct verifying key. 18 | #[derive(Clone, Copy, Debug)] 19 | pub struct KzgSuccinctVerifyingKey { 20 | /// Generator. 21 | pub g: C, 22 | } 23 | 24 | impl KzgSuccinctVerifyingKey { 25 | /// Initialize a [`KzgSuccinctVerifyingKey`]. 26 | pub fn new(g: C) -> Self { 27 | Self { g } 28 | } 29 | } 30 | 31 | impl From for KzgSuccinctVerifyingKey { 32 | fn from(g: C) -> KzgSuccinctVerifyingKey { 33 | KzgSuccinctVerifyingKey::new(g) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /snark-verifier/src/loader/halo2.rs: -------------------------------------------------------------------------------- 1 | //! `Loader` implementation for generating verifier in [`halo2_proofs`] circuit. 2 | 3 | pub(crate) mod loader; 4 | mod shim; 5 | 6 | #[cfg(test)] 7 | pub(crate) mod test; 8 | 9 | pub use loader::{EcPoint, Halo2Loader, Scalar}; 10 | pub use shim::{Context, EccInstructions, IntegerInstructions}; 11 | pub use util::Valuetools; 12 | 13 | pub use halo2_wrong_ecc; 14 | 15 | mod util { 16 | use halo2_proofs::circuit::Value; 17 | 18 | /// Helper methods when dealing with iterator of [`Value`]. 19 | pub trait Valuetools: Iterator> { 20 | /// Fold zipped values into accumulator, returns `Value::unknown()` if 21 | /// any is `Value::unknown()`. 22 | fn fold_zipped(self, init: B, mut f: F) -> Value 23 | where 24 | Self: Sized, 25 | F: FnMut(B, V) -> B, 26 | { 27 | self.fold(Value::known(init), |acc, value| { 28 | acc.zip(value).map(|(acc, value)| f(acc, value)) 29 | }) 30 | } 31 | } 32 | 33 | impl>> Valuetools for I {} 34 | } 35 | -------------------------------------------------------------------------------- /snark-verifier/src/cost.rs: -------------------------------------------------------------------------------- 1 | //! Cost estimation. 2 | 3 | use std::ops::Add; 4 | 5 | /// Cost of verification. 6 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 7 | pub struct Cost { 8 | /// Number of instances. 9 | pub num_instance: usize, 10 | /// Number of commitments in proof. 11 | pub num_commitment: usize, 12 | /// Number of evaluations in proof. 13 | pub num_evaluation: usize, 14 | /// Number of scalar multiplications to perform. 15 | pub num_msm: usize, 16 | /// Number of pairings to perform. 17 | pub num_pairing: usize, 18 | } 19 | 20 | impl Add for Cost { 21 | type Output = Cost; 22 | 23 | fn add(mut self, rhs: Cost) -> Self::Output { 24 | self.num_instance += rhs.num_instance; 25 | self.num_commitment += rhs.num_commitment; 26 | self.num_evaluation += rhs.num_evaluation; 27 | self.num_msm += rhs.num_msm; 28 | self.num_pairing += rhs.num_pairing; 29 | self 30 | } 31 | } 32 | 33 | /// For estimating cost of a verifier. 34 | pub trait CostEstimation { 35 | /// Input for [`CostEstimation::estimate_cost`]. 36 | type Input; 37 | 38 | /// Estimate cost of verifier given the input. 39 | fn estimate_cost(input: &Self::Input) -> Cost; 40 | } 41 | -------------------------------------------------------------------------------- /snark-verifier/src/verifier.rs: -------------------------------------------------------------------------------- 1 | //! Verifiers for (S)NARK. 2 | 3 | use crate::{ 4 | loader::Loader, 5 | util::{arithmetic::CurveAffine, transcript::TranscriptRead}, 6 | Error, 7 | }; 8 | use std::fmt::Debug; 9 | 10 | pub mod plonk; 11 | 12 | /// (S)NARK verifier for verifying a (S)NARK. 13 | pub trait SnarkVerifier 14 | where 15 | C: CurveAffine, 16 | L: Loader, 17 | { 18 | /// Verifying key for subroutines if any. 19 | type VerifyingKey: Clone + Debug; 20 | /// Protocol specifying configuration of a (S)NARK. 21 | type Protocol: Clone + Debug; 22 | /// Structured proof read from transcript. 23 | type Proof: Clone + Debug; 24 | /// Output of verification. 25 | type Output: Clone + Debug; 26 | 27 | /// Read [`SnarkVerifier::Proof`] from transcript. 28 | fn read_proof( 29 | vk: &Self::VerifyingKey, 30 | protocol: &Self::Protocol, 31 | instances: &[Vec], 32 | transcript: &mut T, 33 | ) -> Result 34 | where 35 | T: TranscriptRead; 36 | 37 | /// Verify [`SnarkVerifier::Proof`] and output [`SnarkVerifier::Output`]. 38 | fn verify( 39 | vk: &Self::VerifyingKey, 40 | protocol: &Self::Protocol, 41 | instances: &[Vec], 42 | proof: &Self::Proof, 43 | ) -> Result; 44 | } 45 | -------------------------------------------------------------------------------- /snark-verifier/src/util.rs: -------------------------------------------------------------------------------- 1 | //! Utilities. 2 | 3 | pub mod arithmetic; 4 | pub mod hash; 5 | pub mod msm; 6 | pub mod poly; 7 | pub mod transcript; 8 | 9 | pub(crate) use itertools::Itertools; 10 | 11 | #[cfg(feature = "parallel")] 12 | pub(crate) use rayon::current_num_threads; 13 | 14 | /// Parallelly executing the function on the items of the given iterator. 15 | pub fn parallelize_iter(iter: I, f: F) 16 | where 17 | I: Send + Iterator, 18 | T: Send, 19 | F: Fn(T) + Send + Sync + Clone, 20 | { 21 | #[cfg(feature = "parallel")] 22 | rayon::scope(|scope| { 23 | for item in iter { 24 | let f = f.clone(); 25 | scope.spawn(move |_| f(item)); 26 | } 27 | }); 28 | #[cfg(not(feature = "parallel"))] 29 | iter.for_each(f); 30 | } 31 | 32 | /// Parallelly executing the function on the given mutable slice. 33 | pub fn parallelize(v: &mut [T], f: F) 34 | where 35 | T: Send, 36 | F: Fn((&mut [T], usize)) + Send + Sync + Clone, 37 | { 38 | #[cfg(feature = "parallel")] 39 | { 40 | let num_threads = current_num_threads(); 41 | let chunk_size = v.len() / num_threads; 42 | if chunk_size < num_threads { 43 | f((v, 0)); 44 | } else { 45 | parallelize_iter(v.chunks_mut(chunk_size).zip((0..).step_by(chunk_size)), f); 46 | } 47 | } 48 | #[cfg(not(feature = "parallel"))] 49 | f((v, 0)); 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SNARK Verifier 2 | 3 | Generic (S)NARK verifier. 4 | 5 | ## User Guide 6 | 7 | This project is tailored for verifying [Halo2](https://github.com/privacy-scaling-explorations/halo2)-generated proofs on Ethereum. 8 | 9 | Additionally, within this repository, there is an example provided for verifying an aggregated proof, which serves as proof for multiple other proofs. You can test it by executing `cargo run --example evm-verifier-with-accumulator`. 10 | 11 | As this effort is continuously evolving, there are multiple repository variants to choose from. For optimal results: 12 | 13 | - Use [Axiom’s fork](https://github.com/axiom-crypto/snark-verifier) of snark-verifier when 14 | - You require production-grade quality; Axiom's fork has been audited. 15 | - Proof aggregation is necessary. 16 | - In the future, consider using [halo2-solidity-verifier](https://github.com/privacy-scaling-explorations/halo2-solidity-verifier) for solidity verifier generation. However, please note the following: 17 | - The plan is to phase out snark-verifier and shift focus to halo2-solidity-verifier 18 | - Keep in mind halo2-solidity-verifier is currently unaudited and has certain limitations, refer to the project README for details. 19 | - snark-verifier may encounter issues generating Solidity code due to hitting the contract size limit with large circuits, a problem addressed by halo2-solidity-verifier. 20 | - Proof aggregation is not supported in halo2-solidity-verifier at this time. 21 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/strategy.rs: -------------------------------------------------------------------------------- 1 | //! Verifier strategy 2 | 3 | pub mod ipa { 4 | //! IPA verifier strategy 5 | 6 | use crate::util::arithmetic::CurveAffine; 7 | use halo2_proofs::{ 8 | plonk::Error, 9 | poly::{ 10 | commitment::MSM, 11 | ipa::{ 12 | commitment::{IPACommitmentScheme, ParamsIPA}, 13 | msm::MSMIPA, 14 | multiopen::VerifierIPA, 15 | strategy::GuardIPA, 16 | }, 17 | VerificationStrategy, 18 | }, 19 | }; 20 | 21 | /// Strategy that handles single proof and decide immediately, but also 22 | /// returns `g` if the proof is valid. 23 | #[derive(Clone, Debug)] 24 | pub struct SingleStrategy<'a, C: CurveAffine> { 25 | msm: MSMIPA<'a, C>, 26 | } 27 | 28 | impl<'a, C: CurveAffine> VerificationStrategy<'a, IPACommitmentScheme, VerifierIPA<'a, C>> 29 | for SingleStrategy<'a, C> 30 | { 31 | type Output = C; 32 | 33 | fn new(params: &'a ParamsIPA) -> Self { 34 | SingleStrategy { 35 | msm: MSMIPA::new(params), 36 | } 37 | } 38 | 39 | fn process( 40 | self, 41 | f: impl FnOnce(MSMIPA<'a, C>) -> Result, Error>, 42 | ) -> Result { 43 | let guard = f(self.msm)?; 44 | 45 | let g = guard.compute_g(); 46 | let (msm, _) = guard.use_g(g); 47 | 48 | if msm.check() { 49 | Ok(g) 50 | } else { 51 | Err(Error::ConstraintSystemFailure) 52 | } 53 | } 54 | 55 | fn finalize(self) -> bool { 56 | unreachable!() 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /snark-verifier/src/util/transcript.rs: -------------------------------------------------------------------------------- 1 | //! Transcript traits. 2 | 3 | use crate::{ 4 | loader::{native::NativeLoader, Loader}, 5 | {util::arithmetic::CurveAffine, Error}, 6 | }; 7 | 8 | /// Common methods for prover and verifier. 9 | pub trait Transcript 10 | where 11 | C: CurveAffine, 12 | L: Loader, 13 | { 14 | /// Returns [`Loader`]. 15 | fn loader(&self) -> &L; 16 | 17 | /// Squeeze a challenge. 18 | fn squeeze_challenge(&mut self) -> L::LoadedScalar; 19 | 20 | /// Squeeze `n` challenges. 21 | fn squeeze_n_challenges(&mut self, n: usize) -> Vec { 22 | (0..n).map(|_| self.squeeze_challenge()).collect() 23 | } 24 | 25 | /// Update with an elliptic curve point. 26 | fn common_ec_point(&mut self, ec_point: &L::LoadedEcPoint) -> Result<(), Error>; 27 | 28 | /// Update with a scalar. 29 | fn common_scalar(&mut self, scalar: &L::LoadedScalar) -> Result<(), Error>; 30 | } 31 | 32 | /// Transcript for verifier. 33 | pub trait TranscriptRead: Transcript 34 | where 35 | C: CurveAffine, 36 | L: Loader, 37 | { 38 | /// Read a scalar. 39 | fn read_scalar(&mut self) -> Result; 40 | 41 | /// Read `n` scalar. 42 | fn read_n_scalars(&mut self, n: usize) -> Result, Error> { 43 | (0..n).map(|_| self.read_scalar()).collect() 44 | } 45 | 46 | /// Read a elliptic curve point. 47 | fn read_ec_point(&mut self) -> Result; 48 | 49 | /// Read `n` elliptic curve point. 50 | fn read_n_ec_points(&mut self, n: usize) -> Result, Error> { 51 | (0..n).map(|_| self.read_ec_point()).collect() 52 | } 53 | } 54 | 55 | /// Transcript for prover. 56 | pub trait TranscriptWrite: Transcript { 57 | /// Write a scalar. 58 | fn write_scalar(&mut self, scalar: C::Scalar) -> Result<(), Error>; 59 | 60 | /// Write a elliptic curve point. 61 | fn write_ec_point(&mut self, ec_point: C) -> Result<(), Error>; 62 | } 63 | -------------------------------------------------------------------------------- /snark-verifier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snark-verifier" 3 | version = "0.1.1" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | itertools = "0.10.3" 8 | lazy_static = "1.4.0" 9 | num-bigint = "0.4.3" 10 | num-integer = "0.1.45" 11 | num-traits = "0.2.15" 12 | rand = "0.8" 13 | hex = "0.4" 14 | halo2_curves = { version = "0.6.0", package = "halo2curves" } 15 | 16 | # parallel 17 | rayon = { version = "1.5.3", optional = true } 18 | 19 | # system_halo2 20 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v0.3.0", optional = true } 21 | 22 | # loader_evm 23 | sha3 = { version = "0.10", optional = true } 24 | revm = { version = "3.5.0", optional = true, default-features = false } 25 | 26 | # loader_halo2 27 | halo2_wrong_ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2024_01_31", package = "ecc", optional = true } 28 | poseidon = { git = "https://github.com/privacy-scaling-explorations/poseidon", tag = "v2024_01_31", optional = true } 29 | 30 | # derive_serde 31 | serde = { version = "1.0", features = ["derive"], optional = true } 32 | 33 | [dev-dependencies] 34 | rand_chacha = "0.3.1" 35 | paste = "1.0.7" 36 | 37 | # system_halo2 38 | halo2_wrong_ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2024_01_31", package = "ecc" } 39 | 40 | [features] 41 | default = ["loader_evm", "loader_halo2", "system_halo2"] 42 | 43 | parallel = ["dep:rayon"] 44 | 45 | # loaders 46 | loader_evm = ["dep:sha3", "dep:revm"] 47 | loader_halo2 = ["dep:halo2_proofs", "dep:halo2_wrong_ecc", "dep:poseidon"] 48 | 49 | # systems 50 | system_halo2 = ["dep:halo2_proofs"] 51 | 52 | # features of halo2 53 | halo2_circuit_params = [ 54 | "halo2_proofs?/circuit-params", 55 | "halo2_wrong_ecc?/circuit-params", 56 | ] 57 | derive_serde = ["dep:serde"] 58 | 59 | [[example]] 60 | name = "evm-verifier" 61 | required-features = ["loader_evm", "system_halo2"] 62 | 63 | [[example]] 64 | name = "evm-verifier-with-accumulator" 65 | required-features = ["loader_halo2", "loader_evm", "system_halo2"] 66 | -------------------------------------------------------------------------------- /snark-verifier-sdk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snark-verifier-sdk" 3 | version = "0.1.2" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | itertools = "0.10.3" 8 | lazy_static = "1.4.0" 9 | num-bigint = "0.4.3" 10 | num-integer = "0.1.45" 11 | num-traits = "0.2.15" 12 | rand = "0.8" 13 | rand_chacha = "0.3.1" 14 | hex = "0.4" 15 | serde = { version = "1.0", features = ["derive"] } 16 | serde_json = "1.0" 17 | bincode = "1.3.3" 18 | ark-std = { version = "0.3.0", features = ["print-trace"], optional = true } 19 | 20 | snark-verifier = { path = "../snark-verifier", default-features = false } 21 | 22 | # system_halo2 23 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v0.3.0" } # not optional for now 24 | halo2curves = "0.6.0" 25 | 26 | # loader_halo2 27 | halo2_wrong_ecc = { git = "https://github.com/privacy-scaling-explorations/halo2wrong", tag = "v2024_01_31", package = "ecc", optional = true } 28 | poseidon = { git = "https://github.com/privacy-scaling-explorations/poseidon", tag = "v2024_01_31", optional = true } 29 | 30 | # loader_evm 31 | ethereum-types = { version = "0.14", default-features = false, features = [ 32 | "std", 33 | ], optional = true } 34 | 35 | [dev-dependencies] 36 | ark-std = { version = "0.3.0", features = ["print-trace"] } 37 | paste = "1.0.7" 38 | pprof = { version = "0.11", features = ["criterion", "flamegraph"] } 39 | criterion = "0.4" 40 | criterion-macro = "0.4" 41 | 42 | [features] 43 | default = ["loader_halo2", "loader_evm", "derive_serde", "display"] 44 | display = ["dep:ark-std"] 45 | loader_evm = ["snark-verifier/loader_evm", "dep:ethereum-types"] 46 | loader_halo2 = [ 47 | "snark-verifier/system_halo2", 48 | "snark-verifier/loader_halo2", 49 | "dep:halo2_wrong_ecc", 50 | "dep:poseidon", 51 | ] 52 | parallel = ["snark-verifier/parallel"] 53 | derive_serde = ["snark-verifier/derive_serde", "halo2curves/derive_serde"] 54 | halo2_circuit_params = ["snark-verifier/halo2_circuit_params"] 55 | 56 | [[bench]] 57 | 58 | name = "standard_plonk" 59 | required-features = ["loader_halo2", "loader_evm"] 60 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 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 | profile: minimal 20 | override: false 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 --locked --git https://github.com/alloy-rs/svm-rs) && svm install 0.8.21 && solc --version 28 | 29 | - name: Run test 30 | run: cargo test --all --all-features -- --nocapture 31 | 32 | 33 | lint: 34 | name: Lint 35 | runs-on: ubuntu-latest 36 | steps: 37 | - uses: actions/checkout@v2 38 | 39 | - name: Install toolchain 40 | uses: actions-rs/toolchain@v1 41 | with: 42 | profile: minimal 43 | override: false 44 | components: rustfmt, clippy 45 | 46 | - uses: Swatinem/rust-cache@v1 47 | with: 48 | cache-on-failure: true 49 | 50 | - name: Run fmt 51 | run: cargo fmt --all -- --check 52 | 53 | - name: Run clippy 54 | run: cargo clippy --all --all-features --all-targets -- -D warnings 55 | 56 | build: 57 | name: Build target ${{ matrix.target }} 58 | runs-on: ubuntu-latest 59 | strategy: 60 | matrix: 61 | target: 62 | - wasm32-wasi 63 | steps: 64 | - uses: actions/checkout@v2 65 | 66 | - name: Install toolchain 67 | uses: actions-rs/toolchain@v1 68 | with: 69 | profile: minimal 70 | override: false 71 | 72 | - name: Add target 73 | run: rustup target add ${{ matrix.target }} 74 | 75 | - uses: Swatinem/rust-cache@v1 76 | with: 77 | cache-on-failure: true 78 | 79 | - name: Run build 80 | run: cargo build --target ${{ matrix.target }} 81 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/evm/code.rs: -------------------------------------------------------------------------------- 1 | pub enum Precompiled { 2 | BigModExp = 0x05, 3 | Bn254Add = 0x6, 4 | Bn254ScalarMul = 0x7, 5 | Bn254Pairing = 0x8, 6 | } 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct SolidityAssemblyCode { 10 | // runtime code area 11 | runtime: String, 12 | } 13 | 14 | impl SolidityAssemblyCode { 15 | pub fn new() -> Self { 16 | Self { 17 | runtime: String::new(), 18 | } 19 | } 20 | 21 | pub fn code(&self, base_modulus: String, scalar_modulus: String) -> String { 22 | format!( 23 | " 24 | // SPDX-License-Identifier: MIT 25 | 26 | pragma solidity ^0.8.0; 27 | 28 | contract Halo2Verifier {{ 29 | fallback(bytes calldata) external returns (bytes memory) {{ 30 | assembly (\"memory-safe\") {{ 31 | // Enforce that Solidity memory layout is respected 32 | let data := mload(0x40) 33 | if iszero(eq(data, 0x80)) {{ 34 | revert(0, 0) 35 | }} 36 | 37 | let success := true 38 | let f_p := {base_modulus} 39 | let f_q := {scalar_modulus} 40 | function validate_ec_point(x, y) -> valid {{ 41 | {{ 42 | let x_lt_p := lt(x, {base_modulus}) 43 | let y_lt_p := lt(y, {base_modulus}) 44 | valid := and(x_lt_p, y_lt_p) 45 | }} 46 | {{ 47 | let y_square := mulmod(y, y, {base_modulus}) 48 | let x_square := mulmod(x, x, {base_modulus}) 49 | let x_cube := mulmod(x_square, x, {base_modulus}) 50 | let x_cube_plus_3 := addmod(x_cube, 3, {base_modulus}) 51 | let is_affine := eq(x_cube_plus_3, y_square) 52 | valid := and(valid, is_affine) 53 | }} 54 | }} 55 | {} 56 | }} 57 | }} 58 | }} 59 | ", 60 | self.runtime 61 | ) 62 | } 63 | 64 | pub fn runtime_append(&mut self, mut code: String) { 65 | code.push('\n'); 66 | self.runtime.push_str(&code); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/ipa/decider.rs: -------------------------------------------------------------------------------- 1 | use crate::{pcs::ipa::IpaSuccinctVerifyingKey, util::arithmetic::CurveAffine}; 2 | 3 | /// Inner product argument deciding key. 4 | #[derive(Clone, Debug)] 5 | pub struct IpaDecidingKey { 6 | svk: IpaSuccinctVerifyingKey, 7 | /// Committing key. 8 | g: Vec, 9 | } 10 | 11 | impl IpaDecidingKey { 12 | /// Initialize an [`IpaDecidingKey`]. 13 | pub fn new(svk: IpaSuccinctVerifyingKey, g: Vec) -> Self { 14 | Self { svk, g } 15 | } 16 | } 17 | 18 | impl AsRef> for IpaDecidingKey { 19 | fn as_ref(&self) -> &IpaSuccinctVerifyingKey { 20 | &self.svk 21 | } 22 | } 23 | 24 | mod native { 25 | use crate::{ 26 | loader::native::NativeLoader, 27 | pcs::{ 28 | ipa::{h_coeffs, IpaAccumulator, IpaAs, IpaDecidingKey}, 29 | AccumulationDecider, 30 | }, 31 | util::{ 32 | arithmetic::{Curve, CurveAffine, Field}, 33 | msm::multi_scalar_multiplication, 34 | Itertools, 35 | }, 36 | Error, 37 | }; 38 | use std::fmt::Debug; 39 | 40 | impl AccumulationDecider for IpaAs 41 | where 42 | C: CurveAffine, 43 | MOS: Clone + Debug, 44 | { 45 | type DecidingKey = IpaDecidingKey; 46 | 47 | fn decide( 48 | dk: &Self::DecidingKey, 49 | IpaAccumulator { u, xi }: IpaAccumulator, 50 | ) -> Result<(), Error> { 51 | let h = h_coeffs(&xi, C::Scalar::ONE); 52 | (u == multi_scalar_multiplication(&h, &dk.g).to_affine()) 53 | .then_some(()) 54 | .ok_or_else(|| Error::AssertionFailure("U == commit(G, h)".to_string())) 55 | } 56 | 57 | fn decide_all( 58 | dk: &Self::DecidingKey, 59 | accumulators: Vec>, 60 | ) -> Result<(), Error> { 61 | accumulators 62 | .into_iter() 63 | .map(|accumulator| Self::decide(dk, accumulator)) 64 | .try_collect::<_, Vec<_>, _>()?; 65 | Ok(()) 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/halo2/test.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | util::{arithmetic::CurveAffine, Itertools}, 3 | verifier::plonk::PlonkProtocol, 4 | }; 5 | use halo2_proofs::circuit::Value; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct Snark { 9 | pub protocol: PlonkProtocol, 10 | pub instances: Vec>, 11 | pub proof: Vec, 12 | } 13 | 14 | impl Snark { 15 | pub fn new(protocol: PlonkProtocol, instances: Vec>, proof: Vec) -> Self { 16 | assert_eq!( 17 | protocol.num_instance, 18 | instances 19 | .iter() 20 | .map(|instances| instances.len()) 21 | .collect_vec() 22 | ); 23 | Snark { 24 | protocol, 25 | instances, 26 | proof, 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug)] 32 | pub struct SnarkWitness { 33 | pub protocol: PlonkProtocol, 34 | pub instances: Vec>>, 35 | pub proof: Value>, 36 | } 37 | 38 | impl From> for SnarkWitness { 39 | fn from(snark: Snark) -> Self { 40 | Self { 41 | protocol: snark.protocol, 42 | instances: snark 43 | .instances 44 | .into_iter() 45 | .map(|instances| instances.into_iter().map(Value::known).collect_vec()) 46 | .collect(), 47 | proof: Value::known(snark.proof), 48 | } 49 | } 50 | } 51 | 52 | impl SnarkWitness { 53 | pub fn new_without_witness(protocol: PlonkProtocol) -> Self { 54 | let instances = protocol 55 | .num_instance 56 | .iter() 57 | .map(|num_instance| vec![Value::unknown(); *num_instance]) 58 | .collect(); 59 | SnarkWitness { 60 | protocol, 61 | instances, 62 | proof: Value::unknown(), 63 | } 64 | } 65 | 66 | pub fn without_witnesses(&self) -> Self { 67 | SnarkWitness::new_without_witness(self.protocol.clone()) 68 | } 69 | 70 | pub fn proof(&self) -> Value<&[u8]> { 71 | self.proof.as_ref().map(Vec::as_slice) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/ipa/native.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | pcs::ipa::{Bgh19, IpaAs}, 3 | system::halo2::test::ipa::{ 4 | halo2_ipa_config, halo2_ipa_create_snark, halo2_ipa_native_verify, halo2_ipa_prepare, 5 | }, 6 | system::halo2::test::StandardPlonk, 7 | verifier::plonk::PlonkVerifier, 8 | }; 9 | use halo2_curves::pasta::pallas; 10 | use halo2_proofs::{ 11 | poly::ipa::multiopen::{ProverIPA, VerifierIPA}, 12 | transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer}, 13 | }; 14 | use paste::paste; 15 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 16 | 17 | macro_rules! test { 18 | (@ $name:ident, $k:expr, $config:expr, $create_circuit:expr, $prover:ty, $verifier:ty, $plonk_verifier:ty) => { 19 | paste! { 20 | #[test] 21 | fn []() { 22 | let (params, pk, protocol, circuits) = halo2_ipa_prepare!( 23 | pallas::Affine, 24 | $k, 25 | $config, 26 | $create_circuit 27 | ); 28 | let snark = halo2_ipa_create_snark!( 29 | $prover, 30 | $verifier, 31 | Blake2bWrite<_, _, _>, 32 | Blake2bRead<_, _, _>, 33 | Challenge255<_>, 34 | ¶ms, 35 | &pk, 36 | &protocol, 37 | &circuits 38 | ); 39 | halo2_ipa_native_verify!( 40 | $plonk_verifier, 41 | params, 42 | &snark.protocol, 43 | &snark.instances, 44 | &mut Blake2bRead::<_, pallas::Affine, _>::init(snark.proof.as_slice()) 45 | ); 46 | } 47 | } 48 | }; 49 | ($name:ident, $k:expr, $config:expr, $create_circuit:expr) => { 50 | test!(@ $name, $k, $config, $create_circuit, ProverIPA, VerifierIPA, PlonkVerifier::>); 51 | } 52 | } 53 | 54 | test!( 55 | zk_standard_plonk_rand, 56 | 9, 57 | halo2_ipa_config!(true, 1), 58 | StandardPlonk::rand(ChaCha20Rng::from_seed(Default::default())) 59 | ); 60 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/evm/util/executor.rs: -------------------------------------------------------------------------------- 1 | use revm::{ 2 | primitives::{CreateScheme, ExecutionResult, Output, TransactTo, TxEnv}, 3 | InMemoryDB, EVM, 4 | }; 5 | 6 | /// Deploy contract and then call with calldata. 7 | /// Returns gas_used of call to deployed contract if both transactions are successful. 8 | pub fn deploy_and_call(deployment_code: Vec, calldata: Vec) -> Result { 9 | let mut evm = EVM { 10 | env: Default::default(), 11 | db: Some(InMemoryDB::default()), 12 | }; 13 | 14 | evm.env.tx = TxEnv { 15 | gas_limit: u64::MAX, 16 | transact_to: TransactTo::Create(CreateScheme::Create), 17 | data: deployment_code.into(), 18 | ..Default::default() 19 | }; 20 | 21 | let result = evm.transact_commit().unwrap(); 22 | let contract = match result { 23 | ExecutionResult::Success { 24 | output: Output::Create(_, Some(contract)), 25 | .. 26 | } => contract, 27 | ExecutionResult::Revert { gas_used, output } => { 28 | return Err(format!( 29 | "Contract deployment transaction reverts with gas_used {gas_used} and output {:#x}", 30 | output 31 | )) 32 | } 33 | ExecutionResult::Halt { reason, gas_used } => return Err(format!( 34 | "Contract deployment transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", 35 | reason 36 | )), 37 | _ => unreachable!(), 38 | }; 39 | 40 | evm.env.tx = TxEnv { 41 | gas_limit: u64::MAX, 42 | transact_to: TransactTo::Call(contract), 43 | data: calldata.into(), 44 | ..Default::default() 45 | }; 46 | 47 | let result = evm.transact_commit().unwrap(); 48 | match result { 49 | ExecutionResult::Success { gas_used, .. } => Ok(gas_used), 50 | ExecutionResult::Revert { gas_used, output } => Err(format!( 51 | "Contract call transaction reverts with gas_used {gas_used} and output {:#x}", 52 | output 53 | )), 54 | ExecutionResult::Halt { reason, gas_used } => Err(format!( 55 | "Contract call transaction halts unexpectedly with gas_used {gas_used} and reason {:?}", 56 | reason 57 | )), 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/native.rs: -------------------------------------------------------------------------------- 1 | //! `Loader` implementation in native rust. 2 | 3 | use crate::{ 4 | loader::{EcPointLoader, LoadedEcPoint, LoadedScalar, Loader, ScalarLoader}, 5 | util::arithmetic::{Curve, CurveAffine, FieldOps, PrimeField}, 6 | Error, 7 | }; 8 | use lazy_static::lazy_static; 9 | use std::fmt::Debug; 10 | 11 | lazy_static! { 12 | /// NativeLoader instance for [`LoadedEcPoint::loader`] and 13 | /// [`LoadedScalar::loader`] referencing. 14 | pub static ref LOADER: NativeLoader = NativeLoader; 15 | } 16 | 17 | /// `Loader` implementation in native rust. 18 | #[derive(Clone, Debug)] 19 | pub struct NativeLoader; 20 | 21 | impl LoadedEcPoint for C { 22 | type Loader = NativeLoader; 23 | 24 | fn loader(&self) -> &NativeLoader { 25 | &LOADER 26 | } 27 | } 28 | 29 | impl FieldOps for F { 30 | fn invert(&self) -> Option { 31 | self.invert().into() 32 | } 33 | } 34 | 35 | impl LoadedScalar for F { 36 | type Loader = NativeLoader; 37 | 38 | fn loader(&self) -> &NativeLoader { 39 | &LOADER 40 | } 41 | } 42 | 43 | impl EcPointLoader for NativeLoader { 44 | type LoadedEcPoint = C; 45 | 46 | fn ec_point_load_const(&self, value: &C) -> Self::LoadedEcPoint { 47 | *value 48 | } 49 | 50 | fn ec_point_assert_eq( 51 | &self, 52 | annotation: &str, 53 | lhs: &Self::LoadedEcPoint, 54 | rhs: &Self::LoadedEcPoint, 55 | ) -> Result<(), Error> { 56 | lhs.eq(rhs) 57 | .then_some(()) 58 | .ok_or_else(|| Error::AssertionFailure(annotation.to_string())) 59 | } 60 | 61 | fn multi_scalar_multiplication( 62 | pairs: &[(&>::LoadedScalar, &C)], 63 | ) -> C { 64 | pairs 65 | .iter() 66 | .cloned() 67 | .map(|(scalar, base)| *base * scalar) 68 | .reduce(|acc, value| acc + value) 69 | .unwrap() 70 | .to_affine() 71 | } 72 | } 73 | 74 | impl ScalarLoader for NativeLoader { 75 | type LoadedScalar = F; 76 | 77 | fn load_const(&self, value: &F) -> Self::LoadedScalar { 78 | *value 79 | } 80 | 81 | fn assert_eq( 82 | &self, 83 | annotation: &str, 84 | lhs: &Self::LoadedScalar, 85 | rhs: &Self::LoadedScalar, 86 | ) -> Result<(), Error> { 87 | lhs.eq(rhs) 88 | .then_some(()) 89 | .ok_or_else(|| Error::AssertionFailure(annotation.to_string())) 90 | } 91 | } 92 | 93 | impl Loader for NativeLoader {} 94 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/kzg/native.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | pcs::kzg::{Bdfg21, Gwc19, KzgAs, LimbsEncoding}, 3 | system::halo2::test::{ 4 | kzg::{ 5 | halo2_kzg_config, halo2_kzg_create_snark, halo2_kzg_native_verify, halo2_kzg_prepare, 6 | main_gate_with_range_with_mock_kzg_accumulator, BITS, LIMBS, 7 | }, 8 | StandardPlonk, 9 | }, 10 | verifier::plonk::PlonkVerifier, 11 | }; 12 | use halo2_curves::bn256::{Bn256, G1Affine}; 13 | use halo2_proofs::{ 14 | poly::kzg::multiopen::{ProverGWC, ProverSHPLONK, VerifierGWC, VerifierSHPLONK}, 15 | transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer}, 16 | }; 17 | use paste::paste; 18 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 19 | 20 | macro_rules! test { 21 | (@ $prefix:ident, $name:ident, $k:expr, $config:expr, $create_circuit:expr, $prover:ty, $verifier:ty, $plonk_verifier:ty) => { 22 | paste! { 23 | #[test] 24 | fn []() { 25 | let (params, pk, protocol, circuits) = halo2_kzg_prepare!( 26 | $k, 27 | $config, 28 | $create_circuit 29 | ); 30 | let snark = halo2_kzg_create_snark!( 31 | $prover, 32 | $verifier, 33 | Blake2bWrite<_, _, _>, 34 | Blake2bRead<_, _, _>, 35 | Challenge255<_>, 36 | ¶ms, 37 | &pk, 38 | &protocol, 39 | &circuits 40 | ); 41 | halo2_kzg_native_verify!( 42 | $plonk_verifier, 43 | params, 44 | &snark.protocol, 45 | &snark.instances, 46 | &mut Blake2bRead::<_, G1Affine, _>::init(snark.proof.as_slice()) 47 | ); 48 | } 49 | } 50 | }; 51 | ($name:ident, $k:expr, $config:expr, $create_circuit:expr) => { 52 | test!(@ shplonk, $name, $k, $config, $create_circuit, ProverSHPLONK<_>, VerifierSHPLONK<_>, PlonkVerifier, LimbsEncoding>); 53 | test!(@ plonk, $name, $k, $config, $create_circuit, ProverGWC<_>, VerifierGWC<_>, PlonkVerifier, LimbsEncoding>); 54 | } 55 | } 56 | 57 | test!( 58 | zk_standard_plonk_rand, 59 | 9, 60 | halo2_kzg_config!(true, 2), 61 | StandardPlonk::rand(ChaCha20Rng::from_seed(Default::default())) 62 | ); 63 | test!( 64 | zk_main_gate_with_range_with_mock_kzg_accumulator, 65 | 9, 66 | halo2_kzg_config!(true, 2, (0..4 * LIMBS).map(|idx| (0, idx)).collect()), 67 | main_gate_with_range_with_mock_kzg_accumulator::() 68 | ); 69 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/transcript.rs: -------------------------------------------------------------------------------- 1 | //! Transcripts implemented with both `halo2_proofs::transcript` and 2 | //! `crate::util::transcript`. 3 | use crate::{ 4 | loader::native::{self, NativeLoader}, 5 | util::{ 6 | arithmetic::{CurveAffine, FromUniformBytes}, 7 | transcript::{Transcript, TranscriptRead, TranscriptWrite}, 8 | }, 9 | Error, 10 | }; 11 | use halo2_proofs::transcript::{Blake2bRead, Blake2bWrite, Challenge255}; 12 | use std::io::{Read, Write}; 13 | 14 | #[cfg(feature = "loader_evm")] 15 | pub mod evm; 16 | 17 | #[cfg(feature = "loader_halo2")] 18 | pub mod halo2; 19 | 20 | impl Transcript for Blake2bRead> 21 | where 22 | C::Scalar: FromUniformBytes<64>, 23 | { 24 | fn loader(&self) -> &NativeLoader { 25 | &native::LOADER 26 | } 27 | 28 | fn squeeze_challenge(&mut self) -> C::Scalar { 29 | *halo2_proofs::transcript::Transcript::squeeze_challenge_scalar::(self) 30 | } 31 | 32 | fn common_ec_point(&mut self, ec_point: &C) -> Result<(), Error> { 33 | halo2_proofs::transcript::Transcript::common_point(self, *ec_point) 34 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 35 | } 36 | 37 | fn common_scalar(&mut self, scalar: &C::Scalar) -> Result<(), Error> { 38 | halo2_proofs::transcript::Transcript::common_scalar(self, *scalar) 39 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 40 | } 41 | } 42 | 43 | impl TranscriptRead for Blake2bRead> 44 | where 45 | C::Scalar: FromUniformBytes<64>, 46 | { 47 | fn read_scalar(&mut self) -> Result { 48 | halo2_proofs::transcript::TranscriptRead::read_scalar(self) 49 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 50 | } 51 | 52 | fn read_ec_point(&mut self) -> Result { 53 | halo2_proofs::transcript::TranscriptRead::read_point(self) 54 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 55 | } 56 | } 57 | 58 | impl Transcript for Blake2bWrite> 59 | where 60 | C::Scalar: FromUniformBytes<64>, 61 | { 62 | fn loader(&self) -> &NativeLoader { 63 | &native::LOADER 64 | } 65 | 66 | fn squeeze_challenge(&mut self) -> C::Scalar { 67 | *halo2_proofs::transcript::Transcript::squeeze_challenge_scalar::(self) 68 | } 69 | 70 | fn common_ec_point(&mut self, ec_point: &C) -> Result<(), Error> { 71 | halo2_proofs::transcript::Transcript::common_point(self, *ec_point) 72 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 73 | } 74 | 75 | fn common_scalar(&mut self, scalar: &C::Scalar) -> Result<(), Error> { 76 | halo2_proofs::transcript::Transcript::common_scalar(self, *scalar) 77 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 78 | } 79 | } 80 | 81 | impl TranscriptWrite for Blake2bWrite, C, Challenge255> 82 | where 83 | C::Scalar: FromUniformBytes<64>, 84 | { 85 | fn write_scalar(&mut self, scalar: C::Scalar) -> Result<(), Error> { 86 | halo2_proofs::transcript::TranscriptWrite::write_scalar(self, scalar) 87 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 88 | } 89 | 90 | fn write_ec_point(&mut self, ec_point: C) -> Result<(), Error> 91 | where 92 | C::Scalar: FromUniformBytes<64>, 93 | { 94 | halo2_proofs::transcript::TranscriptWrite::write_point(self, ec_point) 95 | .map_err(|err| Error::Transcript(err.kind(), err.to_string())) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/circuit/maingate.rs: -------------------------------------------------------------------------------- 1 | use crate::util::arithmetic::{CurveAffine, PrimeField}; 2 | use halo2_proofs::{ 3 | circuit::{floor_planner::V1, Layouter, Value}, 4 | plonk::{Circuit, ConstraintSystem, Error}, 5 | }; 6 | use halo2_wrong_ecc::{ 7 | maingate::{ 8 | MainGate, MainGateConfig, MainGateInstructions, RangeChip, RangeConfig, RangeInstructions, 9 | RegionCtx, 10 | }, 11 | BaseFieldEccChip, EccConfig, 12 | }; 13 | use rand::RngCore; 14 | 15 | #[derive(Clone)] 16 | pub struct MainGateWithRangeConfig { 17 | main_gate_config: MainGateConfig, 18 | range_config: RangeConfig, 19 | } 20 | 21 | impl MainGateWithRangeConfig { 22 | pub fn configure( 23 | meta: &mut ConstraintSystem, 24 | composition_bits: Vec, 25 | overflow_bits: Vec, 26 | ) -> Self { 27 | let main_gate_config = MainGate::::configure(meta); 28 | let range_config = 29 | RangeChip::::configure(meta, &main_gate_config, composition_bits, overflow_bits); 30 | MainGateWithRangeConfig { 31 | main_gate_config, 32 | range_config, 33 | } 34 | } 35 | 36 | pub fn main_gate(&self) -> MainGate { 37 | MainGate::new(self.main_gate_config.clone()) 38 | } 39 | 40 | pub fn range_chip(&self) -> RangeChip { 41 | RangeChip::new(self.range_config.clone()) 42 | } 43 | 44 | pub fn ecc_chip( 45 | &self, 46 | ) -> BaseFieldEccChip { 47 | BaseFieldEccChip::new(EccConfig::new( 48 | self.range_config.clone(), 49 | self.main_gate_config.clone(), 50 | )) 51 | } 52 | } 53 | 54 | #[derive(Clone, Default)] 55 | pub struct MainGateWithRange(Vec); 56 | 57 | impl MainGateWithRange { 58 | pub fn new(inner: Vec) -> Self { 59 | Self(inner) 60 | } 61 | 62 | pub fn rand(mut rng: R) -> Self { 63 | Self::new(vec![F::from(rng.next_u32() as u64)]) 64 | } 65 | 66 | pub fn instances(&self) -> Vec> { 67 | vec![self.0.clone()] 68 | } 69 | } 70 | 71 | impl Circuit for MainGateWithRange { 72 | type Config = MainGateWithRangeConfig; 73 | type FloorPlanner = V1; 74 | #[cfg(feature = "halo2_circuit_params")] 75 | type Params = (); 76 | 77 | fn without_witnesses(&self) -> Self { 78 | Self(vec![F::ZERO]) 79 | } 80 | 81 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 82 | MainGateWithRangeConfig::configure(meta, vec![8], vec![4, 7]) 83 | } 84 | 85 | fn synthesize( 86 | &self, 87 | config: Self::Config, 88 | mut layouter: impl Layouter, 89 | ) -> Result<(), Error> { 90 | let main_gate = config.main_gate(); 91 | let range_chip = config.range_chip(); 92 | range_chip.load_table(&mut layouter)?; 93 | 94 | let a = layouter.assign_region( 95 | || "", 96 | |region| { 97 | let mut ctx = RegionCtx::new(region, 0); 98 | range_chip.decompose(&mut ctx, Value::known(F::from(u64::MAX)), 8, 64)?; 99 | range_chip.decompose(&mut ctx, Value::known(F::from(u32::MAX as u64)), 8, 39)?; 100 | let a = range_chip.assign(&mut ctx, Value::known(self.0[0]), 8, 68)?; 101 | let b = main_gate.sub_sub_with_constant(&mut ctx, &a, &a, &a, F::from(2))?; 102 | let cond = main_gate.assign_bit(&mut ctx, Value::known(F::ONE))?; 103 | main_gate.select(&mut ctx, &a, &b, &cond)?; 104 | 105 | Ok(a) 106 | }, 107 | )?; 108 | 109 | main_gate.expose_public(layouter, a, 0)?; 110 | 111 | Ok(()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/kzg.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | system::halo2::test::{read_or_create_srs, MainGateWithRange}, 3 | util::arithmetic::{fe_to_limbs, CurveAffine, MultiMillerLoop}, 4 | }; 5 | use halo2_curves::{serde::SerdeObject, CurveExt}; 6 | use halo2_proofs::poly::{commitment::ParamsProver, kzg::commitment::ParamsKZG}; 7 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 8 | 9 | mod native; 10 | 11 | #[cfg(feature = "loader_evm")] 12 | mod evm; 13 | 14 | #[cfg(feature = "loader_halo2")] 15 | mod halo2; 16 | 17 | pub const TESTDATA_DIR: &str = "./src/system/halo2/test/kzg/testdata"; 18 | 19 | pub const LIMBS: usize = 4; 20 | pub const BITS: usize = 68; 21 | 22 | pub fn setup(k: u32) -> ParamsKZG 23 | where 24 | M::G1Affine: SerdeObject + CurveAffine, 25 | M::G1: CurveExt, 26 | { 27 | ParamsKZG::::setup(k, ChaCha20Rng::from_seed(Default::default())) 28 | } 29 | 30 | pub fn main_gate_with_range_with_mock_kzg_accumulator( 31 | ) -> MainGateWithRange 32 | where 33 | M::G2Affine: CurveAffine + SerdeObject, 34 | M::G1Affine: CurveAffine + SerdeObject, 35 | M::G1: CurveExt, 36 | { 37 | let srs = read_or_create_srs(TESTDATA_DIR, 1, setup::); 38 | let [g1, s_g1] = [srs.get_g()[0], srs.get_g()[1]].map(|point| point.coordinates().unwrap()); 39 | MainGateWithRange::new( 40 | [s_g1.x(), s_g1.y(), g1.x(), g1.y()] 41 | .into_iter() 42 | .cloned() 43 | .flat_map(fe_to_limbs::<_, _, LIMBS, BITS>) 44 | .collect(), 45 | ) 46 | } 47 | 48 | macro_rules! halo2_kzg_config { 49 | ($zk:expr, $num_proof:expr) => { 50 | $crate::system::halo2::Config::kzg() 51 | .set_zk($zk) 52 | .with_num_proof($num_proof) 53 | }; 54 | ($zk:expr, $num_proof:expr, $accumulator_indices:expr) => { 55 | $crate::system::halo2::Config::kzg() 56 | .set_zk($zk) 57 | .with_num_proof($num_proof) 58 | .with_accumulator_indices(Some($accumulator_indices)) 59 | }; 60 | } 61 | 62 | macro_rules! halo2_kzg_prepare { 63 | ($k:expr, $config:expr, $create_circuit:expr) => {{ 64 | use halo2_curves::bn256::Bn256; 65 | use $crate::system::halo2::test::{ 66 | halo2_prepare, 67 | kzg::{setup, TESTDATA_DIR}, 68 | }; 69 | 70 | halo2_prepare!(TESTDATA_DIR, $k, setup::, $config, $create_circuit) 71 | }}; 72 | } 73 | 74 | macro_rules! halo2_kzg_create_snark { 75 | ( 76 | $prover:ty, 77 | $verifier:ty, 78 | $transcript_read:ty, 79 | $transcript_write:ty, 80 | $encoded_challenge:ty, 81 | $params:expr, 82 | $pk:expr, 83 | $protocol:expr, 84 | $circuits:expr 85 | ) => {{ 86 | use halo2_proofs::poly::kzg::{commitment::KZGCommitmentScheme, strategy::SingleStrategy}; 87 | use $crate::system::halo2::test::halo2_create_snark; 88 | 89 | halo2_create_snark!( 90 | KZGCommitmentScheme<_>, 91 | $prover, 92 | $verifier, 93 | SingleStrategy<_>, 94 | $transcript_read, 95 | $transcript_write, 96 | $encoded_challenge, 97 | |proof, _| proof, 98 | $params, 99 | $pk, 100 | $protocol, 101 | $circuits 102 | ) 103 | }}; 104 | } 105 | 106 | macro_rules! halo2_kzg_native_verify { 107 | ( 108 | $plonk_verifier:ty, 109 | $params:expr, 110 | $protocol:expr, 111 | $instances:expr, 112 | $transcript:expr 113 | ) => {{ 114 | use $crate::system::halo2::test::halo2_native_verify; 115 | 116 | halo2_native_verify!( 117 | $plonk_verifier, 118 | $params, 119 | $protocol, 120 | $instances, 121 | $transcript, 122 | &($params.get_g()[0], $params.g2(), $params.s_g2()).into() 123 | ) 124 | }}; 125 | } 126 | 127 | pub(crate) use { 128 | halo2_kzg_config, halo2_kzg_create_snark, halo2_kzg_native_verify, halo2_kzg_prepare, 129 | }; 130 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/circuit/standard.rs: -------------------------------------------------------------------------------- 1 | use crate::util::arithmetic::PrimeField; 2 | use halo2_proofs::{ 3 | circuit::{floor_planner::V1, Layouter, Value}, 4 | plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, 5 | poly::Rotation, 6 | }; 7 | use rand::RngCore; 8 | 9 | #[allow(dead_code)] 10 | #[derive(Clone)] 11 | pub struct StandardPlonkConfig { 12 | a: Column, 13 | b: Column, 14 | c: Column, 15 | q_a: Column, 16 | q_b: Column, 17 | q_c: Column, 18 | q_ab: Column, 19 | constant: Column, 20 | instance: Column, 21 | } 22 | 23 | impl StandardPlonkConfig { 24 | pub fn configure(meta: &mut ConstraintSystem) -> Self { 25 | let [a, b, c] = [(); 3].map(|_| meta.advice_column()); 26 | let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); 27 | let instance = meta.instance_column(); 28 | 29 | [a, b, c].map(|column| meta.enable_equality(column)); 30 | 31 | meta.create_gate( 32 | "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", 33 | |meta| { 34 | let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); 35 | let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] 36 | .map(|column| meta.query_fixed(column, Rotation::cur())); 37 | let instance = meta.query_instance(instance, Rotation::cur()); 38 | Some( 39 | q_a * a.clone() 40 | + q_b * b.clone() 41 | + q_c * c 42 | + q_ab * a * b 43 | + constant 44 | + instance, 45 | ) 46 | }, 47 | ); 48 | 49 | StandardPlonkConfig { 50 | a, 51 | b, 52 | c, 53 | q_a, 54 | q_b, 55 | q_c, 56 | q_ab, 57 | constant, 58 | instance, 59 | } 60 | } 61 | } 62 | 63 | #[derive(Clone, Default)] 64 | pub struct StandardPlonk(F); 65 | 66 | impl StandardPlonk { 67 | pub fn rand(mut rng: R) -> Self { 68 | Self(F::from(rng.next_u32() as u64)) 69 | } 70 | 71 | pub fn instances(&self) -> Vec> { 72 | vec![vec![self.0]] 73 | } 74 | } 75 | 76 | impl Circuit for StandardPlonk { 77 | type Config = StandardPlonkConfig; 78 | type FloorPlanner = V1; 79 | #[cfg(feature = "halo2_circuit_params")] 80 | type Params = (); 81 | 82 | fn without_witnesses(&self) -> Self { 83 | Self::default() 84 | } 85 | 86 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 87 | meta.set_minimum_degree(4); 88 | StandardPlonkConfig::configure(meta) 89 | } 90 | 91 | fn synthesize( 92 | &self, 93 | config: Self::Config, 94 | mut layouter: impl Layouter, 95 | ) -> Result<(), Error> { 96 | layouter.assign_region( 97 | || "", 98 | |mut region| { 99 | region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; 100 | region.assign_fixed(|| "", config.q_a, 0, || Value::known(-F::ONE))?; 101 | 102 | region.assign_advice(|| "", config.a, 1, || Value::known(-F::from(5)))?; 103 | for (column, idx) in [ 104 | config.q_a, 105 | config.q_b, 106 | config.q_c, 107 | config.q_ab, 108 | config.constant, 109 | ] 110 | .iter() 111 | .zip(1..) 112 | { 113 | region.assign_fixed(|| "", *column, 1, || Value::known(F::from(idx)))?; 114 | } 115 | 116 | let a = region.assign_advice(|| "", config.a, 2, || Value::known(F::ONE))?; 117 | a.copy_advice(|| "", &mut region, config.b, 3)?; 118 | a.copy_advice(|| "", &mut region, config.c, 4)?; 119 | 120 | Ok(()) 121 | }, 122 | ) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /snark-verifier/src/loader/evm/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cost::Cost, 3 | util::{arithmetic::PrimeField, Itertools}, 4 | }; 5 | use std::{ 6 | io::{self, Write}, 7 | iter, 8 | process::{Command, Stdio}, 9 | }; 10 | 11 | pub use executor::deploy_and_call; 12 | pub use revm::primitives::ruint::aliases::{B160 as Address, B256, U256, U512}; 13 | 14 | pub(crate) mod executor; 15 | 16 | /// Memory chunk in EVM. 17 | #[derive(Debug)] 18 | pub struct MemoryChunk { 19 | ptr: usize, 20 | len: usize, 21 | } 22 | 23 | impl MemoryChunk { 24 | pub(crate) fn new(ptr: usize) -> Self { 25 | Self { ptr, len: 0 } 26 | } 27 | 28 | pub(crate) fn ptr(&self) -> usize { 29 | self.ptr 30 | } 31 | 32 | pub(crate) fn len(&self) -> usize { 33 | self.len 34 | } 35 | 36 | pub(crate) fn end(&self) -> usize { 37 | self.ptr + self.len 38 | } 39 | 40 | pub(crate) fn reset(&mut self, ptr: usize) { 41 | self.ptr = ptr; 42 | self.len = 0; 43 | } 44 | 45 | pub(crate) fn extend(&mut self, size: usize) { 46 | self.len += size; 47 | } 48 | } 49 | 50 | /// Convert a [`PrimeField`] into a [`U256`]. 51 | /// Assuming fields that implement traits in crate `ff` always have 52 | /// little-endian representation. 53 | pub fn fe_to_u256(f: F) -> U256 54 | where 55 | F: PrimeField, 56 | { 57 | U256::from_le_bytes(f.to_repr()) 58 | } 59 | 60 | /// Convert a [`U256`] into a [`PrimeField`]. 61 | pub fn u256_to_fe(value: U256) -> F 62 | where 63 | F: PrimeField, 64 | { 65 | let value = value % modulus::(); 66 | F::from_repr(value.to_le_bytes::<32>()).unwrap() 67 | } 68 | 69 | /// Returns modulus of [`PrimeField`] as [`U256`]. 70 | pub fn modulus() -> U256 71 | where 72 | F: PrimeField, 73 | { 74 | U256::from_le_bytes((-F::ONE).to_repr()) + U256::from(1) 75 | } 76 | 77 | /// Encode instances and proof into calldata. 78 | pub fn encode_calldata(instances: &[Vec], proof: &[u8]) -> Vec 79 | where 80 | F: PrimeField, 81 | { 82 | iter::empty() 83 | .chain( 84 | instances 85 | .iter() 86 | .flatten() 87 | .flat_map(|value| value.to_repr().as_ref().iter().rev().cloned().collect_vec()), 88 | ) 89 | .chain(proof.iter().cloned()) 90 | .collect() 91 | } 92 | 93 | /// Estimate gas cost with given [`Cost`]. 94 | pub fn estimate_gas(cost: Cost) -> usize { 95 | let proof_size = cost.num_commitment * 64 + (cost.num_evaluation + cost.num_instance) * 32; 96 | 97 | let intrinsic_cost = 21000; 98 | let calldata_cost = (proof_size as f64 * 15.25).ceil() as usize; 99 | let ec_operation_cost = (45100 + cost.num_pairing * 34000) + (cost.num_msm - 2) * 6350; 100 | 101 | intrinsic_cost + calldata_cost + ec_operation_cost 102 | } 103 | 104 | /// Compile given Solidity `code` into deployment bytecode. 105 | pub fn compile_solidity(code: &str) -> Vec { 106 | let mut cmd = match Command::new("solc") 107 | .stdin(Stdio::piped()) 108 | .stdout(Stdio::piped()) 109 | .arg("--bin") 110 | .arg("-") 111 | .spawn() 112 | { 113 | Ok(cmd) => cmd, 114 | Err(err) if err.kind() == io::ErrorKind::NotFound => { 115 | panic!("Command 'solc' not found"); 116 | } 117 | Err(err) => { 118 | panic!("Failed to spawn cmd with command 'solc':\n{err}"); 119 | } 120 | }; 121 | 122 | cmd.stdin 123 | .take() 124 | .unwrap() 125 | .write_all(code.as_bytes()) 126 | .unwrap(); 127 | let output = cmd.wait_with_output().unwrap().stdout; 128 | let binary = *split_by_ascii_whitespace(&output).last().unwrap(); 129 | hex::decode(binary).unwrap() 130 | } 131 | 132 | fn split_by_ascii_whitespace(bytes: &[u8]) -> Vec<&[u8]> { 133 | let mut split = Vec::new(); 134 | let mut start = None; 135 | for (idx, byte) in bytes.iter().enumerate() { 136 | if byte.is_ascii_whitespace() { 137 | if let Some(start) = start.take() { 138 | split.push(&bytes[start..idx]); 139 | } 140 | } else if start.is_none() { 141 | start = Some(idx); 142 | } 143 | } 144 | split 145 | } 146 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/ipa.rs: -------------------------------------------------------------------------------- 1 | use crate::util::arithmetic::CurveAffine; 2 | use halo2_proofs::poly::{ 3 | commitment::{Params, ParamsProver}, 4 | ipa::commitment::ParamsIPA, 5 | }; 6 | use std::mem::size_of; 7 | 8 | mod native; 9 | 10 | pub const TESTDATA_DIR: &str = "./src/system/halo2/test/ipa/testdata"; 11 | 12 | pub fn setup(k: u32) -> ParamsIPA { 13 | ParamsIPA::new(k) 14 | } 15 | 16 | pub fn w_u() -> (C, C) { 17 | let mut buf = Vec::new(); 18 | setup::(1).write(&mut buf).unwrap(); 19 | 20 | let repr = C::Repr::default(); 21 | let repr_len = repr.as_ref().len(); 22 | let offset = size_of::() + 4 * repr_len; 23 | 24 | let [w, u] = [offset, offset + repr_len].map(|offset| { 25 | let mut repr = C::Repr::default(); 26 | repr.as_mut() 27 | .copy_from_slice(&buf[offset..offset + repr_len]); 28 | C::from_bytes(&repr).unwrap() 29 | }); 30 | 31 | (w, u) 32 | } 33 | 34 | macro_rules! halo2_ipa_config { 35 | ($zk:expr, $num_proof:expr) => { 36 | $crate::system::halo2::Config::ipa() 37 | .set_zk($zk) 38 | .with_num_proof($num_proof) 39 | }; 40 | ($zk:expr, $num_proof:expr, $accumulator_indices:expr) => { 41 | $crate::system::halo2::Config::ipa() 42 | .set_zk($zk) 43 | .with_num_proof($num_proof) 44 | .with_accumulator_indices($accumulator_indices) 45 | }; 46 | } 47 | 48 | macro_rules! halo2_ipa_prepare { 49 | ($dir:expr, $curve:path, $k:expr, $config:expr, $create_circuit:expr) => {{ 50 | use $crate::system::halo2::test::{halo2_prepare, ipa::setup}; 51 | 52 | halo2_prepare!($dir, $k, setup::<$curve>, $config, $create_circuit) 53 | }}; 54 | (pallas::Affine, $k:expr, $config:expr, $create_circuit:expr) => {{ 55 | use halo2_curves::pasta::pallas; 56 | use $crate::system::halo2::test::ipa::TESTDATA_DIR; 57 | 58 | halo2_ipa_prepare!( 59 | &format!("{TESTDATA_DIR}/pallas"), 60 | pallas::Affine, 61 | $k, 62 | $config, 63 | $create_circuit 64 | ) 65 | }}; 66 | (vesta::Affine, $k:expr, $config:expr, $create_circuit:expr) => {{ 67 | use halo2_curves::pasta::vesta; 68 | use $crate::system::halo2::test::ipa::TESTDATA_DIR; 69 | 70 | halo2_ipa_prepare!( 71 | &format!("{TESTDATA_DIR}/vesta"), 72 | vesta::Affine, 73 | $k, 74 | $config, 75 | $create_circuit 76 | ) 77 | }}; 78 | } 79 | 80 | macro_rules! halo2_ipa_create_snark { 81 | ( 82 | $prover:ty, 83 | $verifier:ty, 84 | $transcript_read:ty, 85 | $transcript_write:ty, 86 | $encoded_challenge:ty, 87 | $params:expr, 88 | $pk:expr, 89 | $protocol:expr, 90 | $circuits:expr 91 | ) => {{ 92 | use halo2_proofs::poly::ipa::commitment::IPACommitmentScheme; 93 | use $crate::{ 94 | system::halo2::{strategy::ipa::SingleStrategy, test::halo2_create_snark}, 95 | util::arithmetic::GroupEncoding, 96 | }; 97 | 98 | halo2_create_snark!( 99 | IPACommitmentScheme<_>, 100 | $prover, 101 | $verifier, 102 | SingleStrategy<_>, 103 | $transcript_read, 104 | $transcript_write, 105 | $encoded_challenge, 106 | |proof, g| { [proof, g.to_bytes().as_ref().to_vec()].concat() }, 107 | $params, 108 | $pk, 109 | $protocol, 110 | $circuits 111 | ) 112 | }}; 113 | } 114 | 115 | macro_rules! halo2_ipa_native_verify { 116 | ( 117 | $plonk_verifier:ty, 118 | $params:expr, 119 | $protocol:expr, 120 | $instances:expr, 121 | $transcript:expr 122 | ) => {{ 123 | use $crate::{ 124 | pcs::ipa::{IpaDecidingKey, IpaSuccinctVerifyingKey}, 125 | system::halo2::test::{halo2_native_verify, ipa::w_u}, 126 | }; 127 | 128 | let (w, u) = w_u(); 129 | halo2_native_verify!( 130 | $plonk_verifier, 131 | $params, 132 | $protocol, 133 | $instances, 134 | $transcript, 135 | &IpaDecidingKey::new( 136 | IpaSuccinctVerifyingKey::new( 137 | $protocol.domain.clone(), 138 | $params.get_g()[0], 139 | u, 140 | Some(w) 141 | ), 142 | $params.get_g().to_vec() 143 | ) 144 | ) 145 | }}; 146 | } 147 | 148 | pub(crate) use { 149 | halo2_ipa_config, halo2_ipa_create_snark, halo2_ipa_native_verify, halo2_ipa_prepare, 150 | }; 151 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg/multiopen/gwc19.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | cost::{Cost, CostEstimation}, 3 | loader::{LoadedScalar, Loader}, 4 | pcs::{ 5 | kzg::{KzgAccumulator, KzgAs, KzgSuccinctVerifyingKey}, 6 | PolynomialCommitmentScheme, Query, 7 | }, 8 | util::{ 9 | arithmetic::{CurveAffine, MultiMillerLoop, PrimeField}, 10 | msm::Msm, 11 | transcript::TranscriptRead, 12 | Itertools, 13 | }, 14 | Error, 15 | }; 16 | 17 | /// Verifier of multi-open KZG. It is for the GWC implementation 18 | /// in [`halo2_proofs`]. 19 | /// Notations are following . 20 | #[derive(Clone, Debug)] 21 | pub struct Gwc19; 22 | 23 | impl PolynomialCommitmentScheme for KzgAs 24 | where 25 | M: MultiMillerLoop, 26 | M::Fr: Ord, 27 | M::G1Affine: CurveAffine, 28 | L: Loader, 29 | { 30 | type VerifyingKey = KzgSuccinctVerifyingKey; 31 | type Proof = Gwc19Proof; 32 | type Output = KzgAccumulator; 33 | 34 | fn read_proof( 35 | _: &Self::VerifyingKey, 36 | queries: &[Query], 37 | transcript: &mut T, 38 | ) -> Result 39 | where 40 | T: TranscriptRead, 41 | { 42 | Gwc19Proof::read(queries, transcript) 43 | } 44 | 45 | fn verify( 46 | svk: &Self::VerifyingKey, 47 | commitments: &[Msm], 48 | z: &L::LoadedScalar, 49 | queries: &[Query], 50 | proof: &Self::Proof, 51 | ) -> Result { 52 | let sets = query_sets(queries); 53 | let powers_of_u = &proof.u.powers(sets.len()); 54 | let f = { 55 | let powers_of_v = proof 56 | .v 57 | .powers(sets.iter().map(|set| set.polys.len()).max().unwrap()); 58 | sets.iter() 59 | .map(|set| set.msm(commitments, &powers_of_v)) 60 | .zip(powers_of_u.iter()) 61 | .map(|(msm, power_of_u)| msm * power_of_u) 62 | .sum::>() 63 | }; 64 | let z_omegas = sets.iter().map(|set| z.loader().load_const(&set.shift) * z); 65 | 66 | let rhs = proof 67 | .ws 68 | .iter() 69 | .zip(powers_of_u.iter()) 70 | .map(|(w, power_of_u)| Msm::base(w) * power_of_u) 71 | .collect_vec(); 72 | let lhs = f + rhs 73 | .iter() 74 | .zip(z_omegas) 75 | .map(|(uw, z_omega)| uw.clone() * &z_omega) 76 | .sum(); 77 | 78 | Ok(KzgAccumulator::new( 79 | lhs.evaluate(Some(svk.g)), 80 | rhs.into_iter().sum::>().evaluate(Some(svk.g)), 81 | )) 82 | } 83 | } 84 | 85 | /// Structured proof of [`Gwc19`]. 86 | #[derive(Clone, Debug)] 87 | pub struct Gwc19Proof 88 | where 89 | C: CurveAffine, 90 | L: Loader, 91 | { 92 | v: L::LoadedScalar, 93 | ws: Vec, 94 | u: L::LoadedScalar, 95 | } 96 | 97 | impl Gwc19Proof 98 | where 99 | C: CurveAffine, 100 | L: Loader, 101 | { 102 | fn read(queries: &[Query], transcript: &mut T) -> Result 103 | where 104 | T: TranscriptRead, 105 | { 106 | let v = transcript.squeeze_challenge(); 107 | let ws = transcript.read_n_ec_points(query_sets(queries).len())?; 108 | let u = transcript.squeeze_challenge(); 109 | Ok(Gwc19Proof { v, ws, u }) 110 | } 111 | } 112 | 113 | struct QuerySet<'a, F, T> { 114 | shift: F, 115 | polys: Vec, 116 | evals: Vec<&'a T>, 117 | } 118 | 119 | impl<'a, F, T> QuerySet<'a, F, T> 120 | where 121 | F: PrimeField, 122 | T: Clone, 123 | { 124 | fn msm>( 125 | &self, 126 | commitments: &[Msm<'a, C, L>], 127 | powers_of_v: &[L::LoadedScalar], 128 | ) -> Msm { 129 | self.polys 130 | .iter() 131 | .zip(self.evals.iter().cloned()) 132 | .map(|(poly, eval)| { 133 | let commitment = commitments[*poly].clone(); 134 | commitment - Msm::constant(eval.clone()) 135 | }) 136 | .zip(powers_of_v.iter()) 137 | .map(|(msm, power_of_v)| msm * power_of_v) 138 | .sum() 139 | } 140 | } 141 | 142 | fn query_sets(queries: &[Query]) -> Vec> 143 | where 144 | F: PrimeField, 145 | T: Clone + PartialEq, 146 | { 147 | queries.iter().fold(Vec::new(), |mut sets, query| { 148 | if let Some(pos) = sets.iter().position(|set| set.shift == query.shift) { 149 | sets[pos].polys.push(query.poly); 150 | sets[pos].evals.push(&query.eval); 151 | } else { 152 | sets.push(QuerySet { 153 | shift: query.shift, 154 | polys: vec![query.poly], 155 | evals: vec![&query.eval], 156 | }); 157 | } 158 | sets 159 | }) 160 | } 161 | 162 | impl CostEstimation for KzgAs 163 | where 164 | M: MultiMillerLoop, 165 | { 166 | type Input = Vec>; 167 | 168 | fn estimate_cost(queries: &Vec>) -> Cost { 169 | let num_w = query_sets(queries).len(); 170 | Cost { 171 | num_commitment: num_w, 172 | num_msm: num_w, 173 | ..Default::default() 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /snark-verifier/src/util/poly.rs: -------------------------------------------------------------------------------- 1 | //! Polynomial. 2 | 3 | use crate::util::{arithmetic::Field, parallelize}; 4 | use rand::Rng; 5 | use std::{ 6 | iter::{self, Sum}, 7 | ops::{ 8 | Add, Index, IndexMut, Mul, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo, 9 | RangeToInclusive, Sub, 10 | }, 11 | }; 12 | 13 | #[derive(Clone, Debug)] 14 | /// Univariate polynomial. 15 | pub struct Polynomial(Vec); 16 | 17 | impl Polynomial { 18 | /// Initialize an univariate polynomial. 19 | pub fn new(inner: Vec) -> Self { 20 | Self(inner) 21 | } 22 | 23 | /// Returns `true` if the `Polynomial` contains no elements. 24 | pub fn is_empty(&self) -> bool { 25 | self.0.is_empty() 26 | } 27 | 28 | /// Returns the length of the `Polynomial`. 29 | pub fn len(&self) -> usize { 30 | self.0.len() 31 | } 32 | 33 | /// Returns an iterator of the `Polynomial`. 34 | pub fn iter(&self) -> impl Iterator { 35 | self.0.iter() 36 | } 37 | 38 | /// Returns a mutable iterator of the `Polynomial`. 39 | pub fn iter_mut(&mut self) -> impl Iterator { 40 | self.0.iter_mut() 41 | } 42 | 43 | /// Into vector of coefficients. 44 | pub fn to_vec(self) -> Vec { 45 | self.0 46 | } 47 | } 48 | 49 | impl Polynomial { 50 | pub(crate) fn rand(n: usize, mut rng: R) -> Self { 51 | Self::new(iter::repeat_with(|| F::random(&mut rng)).take(n).collect()) 52 | } 53 | 54 | /// Returns evaluation at given `x`. 55 | pub fn evaluate(&self, x: F) -> F { 56 | let evaluate_serial = |coeffs: &[F]| { 57 | coeffs 58 | .iter() 59 | .rev() 60 | .fold(F::ZERO, |acc, coeff| acc * x + coeff) 61 | }; 62 | 63 | #[cfg(feature = "parallel")] 64 | { 65 | use crate::util::{arithmetic::powers, current_num_threads, parallelize_iter}; 66 | use num_integer::Integer; 67 | 68 | let num_threads = current_num_threads(); 69 | if self.len() * 2 < num_threads { 70 | return evaluate_serial(&self.0); 71 | } 72 | 73 | let chunk_size = Integer::div_ceil(&self.len(), &num_threads); 74 | let mut results = vec![F::ZERO; num_threads]; 75 | parallelize_iter( 76 | results 77 | .iter_mut() 78 | .zip(self.0.chunks(chunk_size)) 79 | .zip(powers(x.pow_vartime([chunk_size as u64]))), 80 | |((result, coeffs), scalar)| *result = evaluate_serial(coeffs) * scalar, 81 | ); 82 | results.iter().fold(F::ZERO, |acc, result| acc + result) 83 | } 84 | #[cfg(not(feature = "parallel"))] 85 | evaluate_serial(&self.0) 86 | } 87 | } 88 | 89 | impl<'a, F: Field> Add<&'a Polynomial> for Polynomial { 90 | type Output = Polynomial; 91 | 92 | fn add(mut self, rhs: &'a Polynomial) -> Polynomial { 93 | parallelize(&mut self.0, |(lhs, start)| { 94 | for (lhs, rhs) in lhs.iter_mut().zip(rhs.0[start..].iter()) { 95 | *lhs += *rhs; 96 | } 97 | }); 98 | self 99 | } 100 | } 101 | 102 | impl<'a, F: Field> Sub<&'a Polynomial> for Polynomial { 103 | type Output = Polynomial; 104 | 105 | fn sub(mut self, rhs: &'a Polynomial) -> Polynomial { 106 | parallelize(&mut self.0, |(lhs, start)| { 107 | for (lhs, rhs) in lhs.iter_mut().zip(rhs.0[start..].iter()) { 108 | *lhs -= *rhs; 109 | } 110 | }); 111 | self 112 | } 113 | } 114 | 115 | impl Sub for Polynomial { 116 | type Output = Polynomial; 117 | 118 | fn sub(mut self, rhs: F) -> Polynomial { 119 | self.0[0] -= rhs; 120 | self 121 | } 122 | } 123 | 124 | impl Add for Polynomial { 125 | type Output = Polynomial; 126 | 127 | fn add(mut self, rhs: F) -> Polynomial { 128 | self.0[0] += rhs; 129 | self 130 | } 131 | } 132 | 133 | impl Mul for Polynomial { 134 | type Output = Polynomial; 135 | 136 | fn mul(mut self, rhs: F) -> Polynomial { 137 | if rhs == F::ZERO { 138 | return Polynomial::new(vec![F::ZERO; self.len()]); 139 | } 140 | if rhs == F::ONE { 141 | return self; 142 | } 143 | parallelize(&mut self.0, |(lhs, _)| { 144 | for lhs in lhs.iter_mut() { 145 | *lhs *= rhs; 146 | } 147 | }); 148 | self 149 | } 150 | } 151 | 152 | impl Sum for Polynomial { 153 | fn sum>(iter: I) -> Self { 154 | iter.reduce(|acc, item| acc + &item).unwrap() 155 | } 156 | } 157 | 158 | macro_rules! impl_index { 159 | ($($range:ty => $output:ty,)*) => { 160 | $( 161 | impl Index<$range> for Polynomial { 162 | type Output = $output; 163 | 164 | fn index(&self, index: $range) -> &$output { 165 | self.0.index(index) 166 | } 167 | } 168 | impl IndexMut<$range> for Polynomial { 169 | fn index_mut(&mut self, index: $range) -> &mut $output { 170 | self.0.index_mut(index) 171 | } 172 | } 173 | )* 174 | }; 175 | } 176 | 177 | impl_index!( 178 | usize => F, 179 | Range => [F], 180 | RangeFrom => [F], 181 | RangeFull => [F], 182 | RangeInclusive => [F], 183 | RangeTo => [F], 184 | RangeToInclusive => [F], 185 | ); 186 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs.rs: -------------------------------------------------------------------------------- 1 | //! Verfieirs for polynomial commitment schemes. 2 | 3 | use crate::{ 4 | loader::{native::NativeLoader, Loader}, 5 | util::{ 6 | arithmetic::{CurveAffine, PrimeField}, 7 | msm::Msm, 8 | transcript::{TranscriptRead, TranscriptWrite}, 9 | }, 10 | Error, 11 | }; 12 | use rand::Rng; 13 | use std::{fmt::Debug, marker::PhantomData}; 14 | 15 | pub mod ipa; 16 | pub mod kzg; 17 | 18 | /// Query to an oracle. 19 | /// It assumes all queries are based on the same point, but with some `shift`. 20 | #[derive(Clone, Debug)] 21 | pub struct Query { 22 | /// Index of polynomial to query 23 | pub poly: usize, 24 | /// Shift of the query point. 25 | pub shift: F, 26 | /// Evaluation read from transcript. 27 | pub eval: T, 28 | } 29 | 30 | impl Query { 31 | /// Initialize [`Query`] without evaluation. 32 | pub fn new(poly: usize, shift: F) -> Self { 33 | Self { 34 | poly, 35 | shift, 36 | eval: (), 37 | } 38 | } 39 | 40 | /// Returns [`Query`] with evaluation. 41 | pub fn with_evaluation(self, eval: T) -> Query { 42 | Query { 43 | poly: self.poly, 44 | shift: self.shift, 45 | eval, 46 | } 47 | } 48 | } 49 | 50 | /// Polynomial commitment scheme verifier. 51 | pub trait PolynomialCommitmentScheme: Clone + Debug 52 | where 53 | C: CurveAffine, 54 | L: Loader, 55 | { 56 | /// Verifying key. 57 | type VerifyingKey: Clone + Debug; 58 | /// Structured proof read from transcript. 59 | type Proof: Clone + Debug; 60 | /// Output of verification. 61 | type Output: Clone + Debug; 62 | 63 | /// Read [`PolynomialCommitmentScheme::Proof`] from transcript. 64 | fn read_proof( 65 | vk: &Self::VerifyingKey, 66 | queries: &[Query], 67 | transcript: &mut T, 68 | ) -> Result 69 | where 70 | T: TranscriptRead; 71 | 72 | /// Verify [`PolynomialCommitmentScheme::Proof`] and output [`PolynomialCommitmentScheme::Output`]. 73 | fn verify( 74 | vk: &Self::VerifyingKey, 75 | commitments: &[Msm], 76 | point: &L::LoadedScalar, 77 | queries: &[Query], 78 | proof: &Self::Proof, 79 | ) -> Result; 80 | } 81 | 82 | /// Accumulation scheme verifier. 83 | pub trait AccumulationScheme 84 | where 85 | C: CurveAffine, 86 | L: Loader, 87 | { 88 | /// Accumulator to be accumulated. 89 | type Accumulator: Clone + Debug; 90 | /// Verifying key. 91 | type VerifyingKey: Clone + Debug; 92 | /// Structured proof read from transcript. 93 | type Proof: Clone + Debug; 94 | 95 | /// Read a [`AccumulationScheme::Proof`] from transcript. 96 | fn read_proof( 97 | vk: &Self::VerifyingKey, 98 | instances: &[Self::Accumulator], 99 | transcript: &mut T, 100 | ) -> Result 101 | where 102 | T: TranscriptRead; 103 | 104 | /// Verify old [`AccumulationScheme::Accumulator`]s are accumulated properly 105 | /// into a new one with the [`AccumulationScheme::Proof`], and returns the 106 | /// new one as output. 107 | fn verify( 108 | vk: &Self::VerifyingKey, 109 | instances: &[Self::Accumulator], 110 | proof: &Self::Proof, 111 | ) -> Result; 112 | } 113 | 114 | /// Accumulation scheme decider. 115 | /// When accumulation is going to end, the decider will perform the check if the 116 | /// final accumulator is valid or not, where the check is usually much more 117 | /// expensive than accumulation verification. 118 | pub trait AccumulationDecider: AccumulationScheme 119 | where 120 | C: CurveAffine, 121 | L: Loader, 122 | { 123 | /// Deciding key. The key for decider for perform the final accumulator 124 | /// check. 125 | type DecidingKey: Clone + Debug; 126 | 127 | /// Decide if a [`AccumulationScheme::Accumulator`] is valid. 128 | fn decide(dk: &Self::DecidingKey, accumulator: Self::Accumulator) -> Result<(), Error>; 129 | 130 | /// Decide if all [`AccumulationScheme::Accumulator`]s are valid. 131 | fn decide_all( 132 | dk: &Self::DecidingKey, 133 | accumulators: Vec, 134 | ) -> Result<(), Error>; 135 | } 136 | 137 | /// Accumulation scheme prover. 138 | pub trait AccumulationSchemeProver: AccumulationScheme 139 | where 140 | C: CurveAffine, 141 | { 142 | /// Proving key. 143 | type ProvingKey: Clone + Debug; 144 | 145 | /// Create a proof that argues if old [`AccumulationScheme::Accumulator`]s 146 | /// are properly accumulated into the new one, and returns the new one as 147 | /// output. 148 | fn create_proof( 149 | pk: &Self::ProvingKey, 150 | instances: &[Self::Accumulator], 151 | transcript: &mut T, 152 | rng: R, 153 | ) -> Result 154 | where 155 | T: TranscriptWrite, 156 | R: Rng; 157 | } 158 | 159 | /// Accumulator encoding. 160 | pub trait AccumulatorEncoding: Clone + Debug 161 | where 162 | C: CurveAffine, 163 | L: Loader, 164 | { 165 | /// Accumulator to be encoded. 166 | type Accumulator: Clone + Debug; 167 | 168 | /// Decode an [`AccumulatorEncoding::Accumulator`] from several 169 | /// [`crate::loader::ScalarLoader::LoadedScalar`]s. 170 | fn from_repr(repr: &[&L::LoadedScalar]) -> Result; 171 | } 172 | 173 | impl AccumulatorEncoding for PhantomData 174 | where 175 | C: CurveAffine, 176 | L: Loader, 177 | PCS: PolynomialCommitmentScheme, 178 | { 179 | type Accumulator = PCS::Output; 180 | 181 | fn from_repr(_: &[&L::LoadedScalar]) -> Result { 182 | unimplemented!() 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test/kzg/evm.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | loader::native::NativeLoader, 3 | pcs::kzg::{Bdfg21, Gwc19, KzgAs, LimbsEncoding}, 4 | system::halo2::{ 5 | test::{ 6 | kzg::{ 7 | self, halo2_kzg_config, halo2_kzg_create_snark, halo2_kzg_native_verify, 8 | halo2_kzg_prepare, main_gate_with_range_with_mock_kzg_accumulator, BITS, LIMBS, 9 | }, 10 | StandardPlonk, 11 | }, 12 | transcript::evm::{ChallengeEvm, EvmTranscript}, 13 | }, 14 | verifier::plonk::PlonkVerifier, 15 | }; 16 | use halo2_curves::bn256::{Bn256, G1Affine}; 17 | use halo2_proofs::poly::kzg::multiopen::{ProverGWC, ProverSHPLONK, VerifierGWC, VerifierSHPLONK}; 18 | use paste::paste; 19 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 20 | 21 | macro_rules! halo2_kzg_evm_verify { 22 | ($plonk_verifier:ty, $params:expr, $protocol:expr, $instances:expr, $proof:expr) => {{ 23 | use halo2_curves::bn256::{Bn256, Fq, Fr}; 24 | use halo2_proofs::poly::commitment::ParamsProver; 25 | use std::rc::Rc; 26 | use $crate::{ 27 | loader::evm::{compile_solidity, deploy_and_call, encode_calldata, EvmLoader}, 28 | system::halo2::{ 29 | test::kzg::{BITS, LIMBS}, 30 | transcript::evm::EvmTranscript, 31 | }, 32 | util::Itertools, 33 | verifier::SnarkVerifier, 34 | }; 35 | 36 | let loader = EvmLoader::new::(); 37 | let deployment_code = { 38 | let vk = ($params.get_g()[0].into(), $params.g2(), $params.s_g2()).into(); 39 | let protocol = $protocol.loaded(&loader); 40 | let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 41 | let instances = transcript.load_instances( 42 | $instances 43 | .iter() 44 | .map(|instances| instances.len()) 45 | .collect_vec(), 46 | ); 47 | let proof = 48 | <$plonk_verifier>::read_proof(&vk, &protocol, &instances, &mut transcript).unwrap(); 49 | <$plonk_verifier>::verify(&vk, &protocol, &instances, &proof).unwrap(); 50 | 51 | compile_solidity(&loader.solidity_code()) 52 | }; 53 | 54 | let calldata = encode_calldata($instances, &$proof); 55 | let gas_cost = deploy_and_call(deployment_code.clone(), calldata.clone()).unwrap(); 56 | println!("Total gas cost: {}", gas_cost); 57 | 58 | let mut calldata = calldata; 59 | calldata[0] = calldata[0].wrapping_add(1); 60 | assert!(deploy_and_call(deployment_code, calldata) 61 | .unwrap_err() 62 | .starts_with("Contract call transaction reverts")) 63 | }}; 64 | } 65 | 66 | macro_rules! test { 67 | (@ $(#[$attr:meta],)* $prefix:ident, $name:ident, $k:expr, $config:expr, $create_circuit:expr, $prover:ty, $verifier:ty, $plonk_verifier:ty) => { 68 | paste! { 69 | $(#[$attr])* 70 | fn []() { 71 | let (params, pk, protocol, circuits) = halo2_kzg_prepare!( 72 | $k, 73 | $config, 74 | $create_circuit 75 | ); 76 | let snark = halo2_kzg_create_snark!( 77 | $prover, 78 | $verifier, 79 | EvmTranscript, 80 | EvmTranscript, 81 | ChallengeEvm<_>, 82 | ¶ms, 83 | &pk, 84 | &protocol, 85 | &circuits 86 | ); 87 | halo2_kzg_native_verify!( 88 | $plonk_verifier, 89 | params, 90 | &snark.protocol, 91 | &snark.instances, 92 | &mut EvmTranscript::<_, NativeLoader, _, _>::new(snark.proof.as_slice()) 93 | ); 94 | halo2_kzg_evm_verify!( 95 | $plonk_verifier, 96 | params, 97 | &snark.protocol, 98 | &snark.instances, 99 | snark.proof 100 | ); 101 | } 102 | } 103 | }; 104 | ($name:ident, $k:expr, $config:expr, $create_circuit:expr) => { 105 | test!(@ #[test], shplonk, $name, $k, $config, $create_circuit, ProverSHPLONK<_>, VerifierSHPLONK<_>, PlonkVerifier, LimbsEncoding>); 106 | test!(@ #[test], plonk, $name, $k, $config, $create_circuit, ProverGWC<_>, VerifierGWC<_>, PlonkVerifier, LimbsEncoding>); 107 | }; 108 | ($(#[$attr:meta],)* $name:ident, $k:expr, $config:expr, $create_circuit:expr) => { 109 | test!(@ #[test] $(,#[$attr])*, plonk, $name, $k, $config, $create_circuit, ProverGWC<_>, VerifierGWC<_>, PlonkVerifier, LimbsEncoding>); 110 | }; 111 | } 112 | 113 | test!( 114 | zk_standard_plonk_rand, 115 | 9, 116 | halo2_kzg_config!(true, 1), 117 | StandardPlonk::rand(ChaCha20Rng::from_seed(Default::default())) 118 | ); 119 | test!( 120 | zk_main_gate_with_range_with_mock_kzg_accumulator, 121 | 9, 122 | halo2_kzg_config!(true, 1, (0..4 * LIMBS).map(|idx| (0, idx)).collect()), 123 | main_gate_with_range_with_mock_kzg_accumulator::() 124 | ); 125 | test!( 126 | #[cfg(feature = "loader_halo2")], 127 | #[ignore = "cause it requires 32GB memory to run"], 128 | zk_accumulation_two_snark, 129 | 22, 130 | halo2_kzg_config!(true, 1, (0..4 * LIMBS).map(|idx| (0, idx)).collect()), 131 | kzg::halo2::Accumulation::two_snark() 132 | ); 133 | test!( 134 | #[cfg(feature = "loader_halo2")], 135 | #[ignore = "cause it requires 32GB memory to run"], 136 | zk_accumulation_two_snark_with_accumulator, 137 | 22, 138 | halo2_kzg_config!(true, 1, (0..4 * LIMBS).map(|idx| (0, idx)).collect()), 139 | kzg::halo2::Accumulation::two_snark_with_accumulator() 140 | ); 141 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg/accumulation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | loader::{native::NativeLoader, LoadedScalar, Loader}, 3 | pcs::{kzg::KzgAccumulator, AccumulationScheme, AccumulationSchemeProver}, 4 | util::{ 5 | arithmetic::{Curve, CurveAffine, Field, MultiMillerLoop}, 6 | msm::Msm, 7 | transcript::{TranscriptRead, TranscriptWrite}, 8 | }, 9 | Error, 10 | }; 11 | use rand::Rng; 12 | use std::{fmt::Debug, marker::PhantomData}; 13 | 14 | /// KZG accumulation scheme. The second generic `MOS` stands for different kind 15 | /// of multi-open scheme. 16 | #[derive(Clone, Debug)] 17 | pub struct KzgAs(PhantomData<(M, MOS)>); 18 | 19 | impl AccumulationScheme for KzgAs 20 | where 21 | M: MultiMillerLoop, 22 | M::G1Affine: CurveAffine, 23 | L: Loader, 24 | MOS: Clone + Debug, 25 | { 26 | type Accumulator = KzgAccumulator; 27 | type VerifyingKey = KzgAsVerifyingKey; 28 | type Proof = KzgAsProof; 29 | 30 | fn read_proof( 31 | vk: &Self::VerifyingKey, 32 | instances: &[Self::Accumulator], 33 | transcript: &mut T, 34 | ) -> Result 35 | where 36 | T: TranscriptRead, 37 | { 38 | KzgAsProof::read(vk, instances, transcript) 39 | } 40 | 41 | fn verify( 42 | _: &Self::VerifyingKey, 43 | instances: &[Self::Accumulator], 44 | proof: &Self::Proof, 45 | ) -> Result { 46 | let (lhs, rhs) = instances 47 | .iter() 48 | .map(|accumulator| (&accumulator.lhs, &accumulator.rhs)) 49 | .chain(proof.blind.as_ref().map(|tup| (&tup.0, &tup.1))) 50 | .unzip::<_, _, Vec<_>, Vec<_>>(); 51 | 52 | let powers_of_r = proof.r.powers(lhs.len()); 53 | let [lhs, rhs] = [lhs, rhs].map(|bases| { 54 | bases 55 | .into_iter() 56 | .zip(powers_of_r.iter()) 57 | .map(|(base, r)| Msm::::base(base) * r) 58 | .sum::>() 59 | .evaluate(None) 60 | }); 61 | 62 | Ok(KzgAccumulator::new(lhs, rhs)) 63 | } 64 | } 65 | 66 | /// KZG accumulation scheme proving key. 67 | #[derive(Clone, Copy, Debug, Default)] 68 | pub struct KzgAsProvingKey(pub Option<(C, C)>); 69 | 70 | impl KzgAsProvingKey { 71 | /// Initialize a [`KzgAsProvingKey`]. 72 | pub fn new(g: Option<(C, C)>) -> Self { 73 | Self(g) 74 | } 75 | 76 | /// Returns if it supports zero-knowledge or not. 77 | pub fn zk(&self) -> bool { 78 | self.0.is_some() 79 | } 80 | 81 | /// Returns [`KzgAsVerifyingKey`]. 82 | pub fn vk(&self) -> KzgAsVerifyingKey { 83 | KzgAsVerifyingKey(self.zk()) 84 | } 85 | } 86 | 87 | /// KZG accumulation scheme verifying key. 88 | #[derive(Clone, Copy, Debug, Default)] 89 | pub struct KzgAsVerifyingKey(bool); 90 | 91 | impl KzgAsVerifyingKey { 92 | /// Returns if it supports zero-knowledge or not. 93 | pub fn zk(&self) -> bool { 94 | self.0 95 | } 96 | } 97 | 98 | /// KZG accumulation scheme proof. 99 | #[derive(Clone, Debug)] 100 | pub struct KzgAsProof 101 | where 102 | C: CurveAffine, 103 | L: Loader, 104 | { 105 | blind: Option<(L::LoadedEcPoint, L::LoadedEcPoint)>, 106 | r: L::LoadedScalar, 107 | } 108 | 109 | impl KzgAsProof 110 | where 111 | C: CurveAffine, 112 | L: Loader, 113 | { 114 | fn read( 115 | vk: &KzgAsVerifyingKey, 116 | instances: &[KzgAccumulator], 117 | transcript: &mut T, 118 | ) -> Result 119 | where 120 | T: TranscriptRead, 121 | { 122 | assert!(!instances.is_empty()); 123 | 124 | for accumulator in instances { 125 | transcript.common_ec_point(&accumulator.lhs)?; 126 | transcript.common_ec_point(&accumulator.rhs)?; 127 | } 128 | 129 | let blind = vk 130 | .zk() 131 | .then(|| Ok((transcript.read_ec_point()?, transcript.read_ec_point()?))) 132 | .transpose()?; 133 | 134 | let r = transcript.squeeze_challenge(); 135 | 136 | Ok(Self { blind, r }) 137 | } 138 | } 139 | 140 | impl AccumulationSchemeProver for KzgAs 141 | where 142 | M: MultiMillerLoop, 143 | M::G1Affine: CurveAffine, 144 | MOS: Clone + Debug, 145 | { 146 | type ProvingKey = KzgAsProvingKey; 147 | 148 | fn create_proof( 149 | pk: &Self::ProvingKey, 150 | instances: &[KzgAccumulator], 151 | transcript: &mut T, 152 | rng: R, 153 | ) -> Result, Error> 154 | where 155 | T: TranscriptWrite, 156 | R: Rng, 157 | { 158 | assert!(!instances.is_empty()); 159 | 160 | for accumulator in instances { 161 | transcript.common_ec_point(&accumulator.lhs)?; 162 | transcript.common_ec_point(&accumulator.rhs)?; 163 | } 164 | 165 | let blind = pk 166 | .zk() 167 | .then(|| { 168 | let s = M::Fr::random(rng); 169 | let (g, s_g) = pk.0.unwrap(); 170 | let lhs = (s_g * s).to_affine(); 171 | let rhs = (g * s).to_affine(); 172 | transcript.write_ec_point(lhs)?; 173 | transcript.write_ec_point(rhs)?; 174 | Ok((lhs, rhs)) 175 | }) 176 | .transpose()?; 177 | 178 | let r = transcript.squeeze_challenge(); 179 | 180 | let (lhs, rhs) = instances 181 | .iter() 182 | .cloned() 183 | .map(|accumulator| (accumulator.lhs, accumulator.rhs)) 184 | .chain(blind) 185 | .unzip::<_, _, Vec<_>, Vec<_>>(); 186 | 187 | let powers_of_r = r.powers(lhs.len()); 188 | let [lhs, rhs] = [lhs, rhs].map(|msms| { 189 | msms.iter() 190 | .zip(powers_of_r.iter()) 191 | .map(|(msm, power_of_r)| Msm::::base(msm) * power_of_r) 192 | .sum::>() 193 | .evaluate(None) 194 | }); 195 | 196 | Ok(KzgAccumulator::new(lhs, rhs)) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /snark-verifier-sdk/examples/standard_plonk.rs: -------------------------------------------------------------------------------- 1 | use ark_std::{end_timer, start_timer}; 2 | use halo2_proofs::halo2curves as halo2_curves; 3 | use halo2_proofs::plonk::Circuit; 4 | use halo2_proofs::{halo2curves::bn256::Bn256, poly::kzg::commitment::ParamsKZG}; 5 | use rand::rngs::OsRng; 6 | use snark_verifier_sdk::evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}; 7 | use snark_verifier_sdk::halo2::gen_srs; 8 | use snark_verifier_sdk::{ 9 | gen_pk, 10 | halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, 11 | Snark, 12 | }; 13 | use snark_verifier_sdk::{CircuitExt, SHPLONK}; 14 | use std::path::Path; 15 | 16 | mod application { 17 | use super::halo2_curves::bn256::Fr; 18 | use halo2_proofs::{ 19 | circuit::{Layouter, SimpleFloorPlanner, Value}, 20 | plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, 21 | poly::Rotation, 22 | }; 23 | use rand::RngCore; 24 | use snark_verifier_sdk::CircuitExt; 25 | 26 | #[derive(Clone, Copy)] 27 | pub struct StandardPlonkConfig { 28 | a: Column, 29 | b: Column, 30 | c: Column, 31 | q_a: Column, 32 | q_b: Column, 33 | q_c: Column, 34 | q_ab: Column, 35 | constant: Column, 36 | #[allow(dead_code)] 37 | instance: Column, 38 | } 39 | 40 | impl StandardPlonkConfig { 41 | fn configure(meta: &mut ConstraintSystem) -> Self { 42 | let [a, b, c] = [(); 3].map(|_| meta.advice_column()); 43 | let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); 44 | let instance = meta.instance_column(); 45 | 46 | [a, b, c].map(|column| meta.enable_equality(column)); 47 | 48 | meta.create_gate( 49 | "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", 50 | |meta| { 51 | let [a, b, c] = 52 | [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); 53 | let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] 54 | .map(|column| meta.query_fixed(column, Rotation::cur())); 55 | let instance = meta.query_instance(instance, Rotation::cur()); 56 | Some( 57 | q_a * a.clone() 58 | + q_b * b.clone() 59 | + q_c * c 60 | + q_ab * a * b 61 | + constant 62 | + instance, 63 | ) 64 | }, 65 | ); 66 | 67 | StandardPlonkConfig { 68 | a, 69 | b, 70 | c, 71 | q_a, 72 | q_b, 73 | q_c, 74 | q_ab, 75 | constant, 76 | instance, 77 | } 78 | } 79 | } 80 | 81 | #[derive(Clone, Default)] 82 | pub struct StandardPlonk(Fr); 83 | 84 | impl StandardPlonk { 85 | pub fn rand(mut rng: R) -> Self { 86 | Self(Fr::from(rng.next_u32() as u64)) 87 | } 88 | } 89 | 90 | impl CircuitExt for StandardPlonk { 91 | fn num_instance(&self) -> Vec { 92 | vec![1] 93 | } 94 | 95 | fn instances(&self) -> Vec> { 96 | vec![vec![self.0]] 97 | } 98 | } 99 | 100 | impl Circuit for StandardPlonk { 101 | type Config = StandardPlonkConfig; 102 | type FloorPlanner = SimpleFloorPlanner; 103 | #[cfg(feature = "halo2_circuit_params")] 104 | type Params = (); 105 | 106 | fn without_witnesses(&self) -> Self { 107 | Self::default() 108 | } 109 | 110 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 111 | meta.set_minimum_degree(4); 112 | StandardPlonkConfig::configure(meta) 113 | } 114 | 115 | fn synthesize( 116 | &self, 117 | config: Self::Config, 118 | mut layouter: impl Layouter, 119 | ) -> Result<(), Error> { 120 | layouter.assign_region( 121 | || "", 122 | |mut region| { 123 | region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; 124 | region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; 125 | region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; 126 | for (idx, column) in (1..).zip([ 127 | config.q_a, 128 | config.q_b, 129 | config.q_c, 130 | config.q_ab, 131 | config.constant, 132 | ]) { 133 | region.assign_fixed( 134 | || "", 135 | column, 136 | 1, 137 | || Value::known(Fr::from(idx as u64)), 138 | )?; 139 | } 140 | let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; 141 | a.copy_advice(|| "", &mut region, config.b, 3)?; 142 | a.copy_advice(|| "", &mut region, config.c, 4)?; 143 | 144 | Ok(()) 145 | }, 146 | ) 147 | } 148 | } 149 | } 150 | 151 | fn gen_application_snark(params: &ParamsKZG) -> Snark { 152 | let circuit = application::StandardPlonk::rand(OsRng); 153 | 154 | let pk = gen_pk(params, &circuit, Some(Path::new("./examples/app.pk"))); 155 | gen_snark_shplonk(params, &pk, circuit, None::<&str>) 156 | } 157 | 158 | fn main() { 159 | let params_app = gen_srs(8); 160 | let snarks = [(); 3].map(|_| gen_application_snark(¶ms_app)); 161 | 162 | let params = gen_srs(22); 163 | let agg_circuit = AggregationCircuit::::new(¶ms, snarks); 164 | 165 | let start0 = start_timer!(|| "gen vk & pk"); 166 | let pk = gen_pk( 167 | ¶ms, 168 | &agg_circuit.without_witnesses(), 169 | Some(Path::new("./examples/agg.pk")), 170 | ); 171 | end_timer!(start0); 172 | 173 | let num_instances = agg_circuit.num_instance(); 174 | let instances = agg_circuit.instances(); 175 | let proof_calldata = gen_evm_proof_shplonk(¶ms, &pk, agg_circuit, instances.clone()); 176 | 177 | let deployment_code = gen_evm_verifier_shplonk::>( 178 | ¶ms, 179 | pk.get_vk(), 180 | num_instances, 181 | Some(Path::new("./examples/StandardPlonkVerifierExample.sol")), 182 | ); 183 | evm_verify(deployment_code, instances, proof_calldata); 184 | } 185 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg/decider.rs: -------------------------------------------------------------------------------- 1 | use crate::{pcs::kzg::KzgSuccinctVerifyingKey, util::arithmetic::MultiMillerLoop}; 2 | use std::marker::PhantomData; 3 | 4 | /// KZG deciding key. 5 | #[derive(Debug, Clone, Copy)] 6 | pub struct KzgDecidingKey { 7 | /// KZG succinct verifying key. 8 | pub svk: KzgSuccinctVerifyingKey, 9 | /// Generator on G2. 10 | pub g2: M::G2Affine, 11 | /// Generator to the trusted-setup secret on G2. 12 | pub s_g2: M::G2Affine, 13 | _marker: PhantomData, 14 | } 15 | 16 | impl KzgDecidingKey { 17 | /// Initialize a [`KzgDecidingKey`] 18 | pub fn new( 19 | svk: impl Into>, 20 | g2: M::G2Affine, 21 | s_g2: M::G2Affine, 22 | ) -> Self { 23 | Self { 24 | svk: svk.into(), 25 | g2, 26 | s_g2, 27 | _marker: PhantomData, 28 | } 29 | } 30 | } 31 | 32 | impl From<(M::G1Affine, M::G2Affine, M::G2Affine)> for KzgDecidingKey { 33 | fn from((g1, g2, s_g2): (M::G1Affine, M::G2Affine, M::G2Affine)) -> KzgDecidingKey { 34 | KzgDecidingKey::new(g1, g2, s_g2) 35 | } 36 | } 37 | 38 | impl AsRef> for KzgDecidingKey { 39 | fn as_ref(&self) -> &KzgSuccinctVerifyingKey { 40 | &self.svk 41 | } 42 | } 43 | 44 | mod native { 45 | 46 | use halo2_curves::CurveAffine; 47 | 48 | use crate::{ 49 | loader::native::NativeLoader, 50 | pcs::{ 51 | kzg::{KzgAccumulator, KzgAs, KzgDecidingKey}, 52 | AccumulationDecider, 53 | }, 54 | util::{ 55 | arithmetic::{Group, MillerLoopResult, MultiMillerLoop}, 56 | Itertools, 57 | }, 58 | Error, 59 | }; 60 | use std::fmt::Debug; 61 | 62 | impl AccumulationDecider for KzgAs 63 | where 64 | M: MultiMillerLoop, 65 | M::G1Affine: CurveAffine, 66 | MOS: Clone + Debug, 67 | { 68 | type DecidingKey = KzgDecidingKey; 69 | 70 | fn decide( 71 | dk: &Self::DecidingKey, 72 | KzgAccumulator { lhs, rhs }: KzgAccumulator, 73 | ) -> Result<(), Error> { 74 | let terms = [(&lhs, &dk.g2.into()), (&rhs, &(-dk.s_g2).into())]; 75 | bool::from( 76 | M::multi_miller_loop(&terms) 77 | .final_exponentiation() 78 | .is_identity(), 79 | ) 80 | .then_some(()) 81 | .ok_or_else(|| Error::AssertionFailure("e(lhs, g2)·e(rhs, -s_g2) == O".to_string())) 82 | } 83 | 84 | fn decide_all( 85 | dk: &Self::DecidingKey, 86 | accumulators: Vec>, 87 | ) -> Result<(), Error> { 88 | accumulators 89 | .into_iter() 90 | .map(|accumulator| Self::decide(dk, accumulator)) 91 | .try_collect::<_, Vec<_>, _>()?; 92 | Ok(()) 93 | } 94 | } 95 | } 96 | 97 | #[cfg(feature = "loader_evm")] 98 | mod evm { 99 | use crate::{ 100 | loader::{ 101 | evm::{loader::Value, EvmLoader, U256}, 102 | LoadedScalar, 103 | }, 104 | pcs::{ 105 | kzg::{KzgAccumulator, KzgAs, KzgDecidingKey}, 106 | AccumulationDecider, 107 | }, 108 | util::{ 109 | arithmetic::{CurveAffine, MultiMillerLoop, PrimeField}, 110 | msm::Msm, 111 | }, 112 | Error, 113 | }; 114 | use std::{fmt::Debug, rc::Rc}; 115 | 116 | impl AccumulationDecider> for KzgAs 117 | where 118 | M: MultiMillerLoop, 119 | M::Fr: PrimeField, 120 | M::G1Affine: CurveAffine, 121 | M::G2Affine: CurveAffine, 122 | MOS: Clone + Debug, 123 | { 124 | type DecidingKey = KzgDecidingKey; 125 | 126 | fn decide( 127 | dk: &Self::DecidingKey, 128 | KzgAccumulator { lhs, rhs }: KzgAccumulator>, 129 | ) -> Result<(), Error> { 130 | let loader = lhs.loader(); 131 | let [g2, minus_s_g2] = [dk.g2, -dk.s_g2].map(|ec_point| { 132 | let coordinates = ec_point.coordinates().unwrap(); 133 | let x = coordinates.x().to_repr(); 134 | let y = coordinates.y().to_repr(); 135 | ( 136 | U256::try_from_le_slice(&x.as_ref()[32..]).unwrap(), 137 | U256::try_from_le_slice(&x.as_ref()[..32]).unwrap(), 138 | U256::try_from_le_slice(&y.as_ref()[32..]).unwrap(), 139 | U256::try_from_le_slice(&y.as_ref()[..32]).unwrap(), 140 | ) 141 | }); 142 | loader.pairing(&lhs, g2, &rhs, minus_s_g2); 143 | Ok(()) 144 | } 145 | 146 | fn decide_all( 147 | dk: &Self::DecidingKey, 148 | mut accumulators: Vec>>, 149 | ) -> Result<(), Error> { 150 | assert!(!accumulators.is_empty()); 151 | 152 | let accumulator = if accumulators.len() == 1 { 153 | accumulators.pop().unwrap() 154 | } else { 155 | let loader = accumulators[0].lhs.loader(); 156 | let (lhs, rhs) = accumulators 157 | .iter() 158 | .map(|KzgAccumulator { lhs, rhs }| { 159 | let [lhs, rhs] = [&lhs, &rhs].map(|ec_point| loader.dup_ec_point(ec_point)); 160 | (lhs, rhs) 161 | }) 162 | .unzip::<_, _, Vec<_>, Vec<_>>(); 163 | 164 | let hash_ptr = loader.keccak256(lhs[0].ptr(), lhs.len() * 0x80); 165 | let challenge_ptr = loader.allocate(0x20); 166 | let code = format!("mstore({challenge_ptr}, mod(mload({hash_ptr}), f_q))"); 167 | loader.code_mut().runtime_append(code); 168 | let challenge = loader.scalar(Value::Memory(challenge_ptr)); 169 | 170 | let powers_of_challenge = LoadedScalar::::powers(&challenge, lhs.len()); 171 | let [lhs, rhs] = [lhs, rhs].map(|msms| { 172 | msms.iter() 173 | .zip(powers_of_challenge.iter()) 174 | .map(|(msm, power_of_challenge)| { 175 | Msm::>::base(msm) * power_of_challenge 176 | }) 177 | .sum::>() 178 | .evaluate(None) 179 | }); 180 | 181 | KzgAccumulator::new(lhs, rhs) 182 | }; 183 | 184 | >>::decide(dk, accumulator) 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /snark-verifier-sdk/src/evm.rs: -------------------------------------------------------------------------------- 1 | use crate::{GWC, SHPLONK}; 2 | 3 | use super::{CircuitExt, PlonkVerifier}; 4 | #[cfg(feature = "display")] 5 | use ark_std::{end_timer, start_timer}; 6 | use halo2_proofs::{ 7 | halo2curves::bn256::{Bn256, Fq, Fr, G1Affine}, 8 | plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, 9 | poly::{ 10 | commitment::{ParamsProver, Prover, Verifier}, 11 | kzg::{ 12 | commitment::{KZGCommitmentScheme, ParamsKZG}, 13 | msm::DualMSM, 14 | multiopen::{ProverGWC, ProverSHPLONK, VerifierGWC, VerifierSHPLONK}, 15 | strategy::{AccumulatorStrategy, GuardKZG}, 16 | }, 17 | VerificationStrategy, 18 | }, 19 | transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, 20 | }; 21 | use itertools::Itertools; 22 | use rand::{rngs::StdRng, SeedableRng}; 23 | pub use snark_verifier::loader::evm::encode_calldata; 24 | use snark_verifier::{ 25 | loader::evm::{compile_solidity, deploy_and_call, EvmLoader}, 26 | pcs::{ 27 | kzg::{KzgAccumulator, KzgAsVerifyingKey, KzgDecidingKey, KzgSuccinctVerifyingKey}, 28 | AccumulationDecider, AccumulationScheme, PolynomialCommitmentScheme, 29 | }, 30 | system::halo2::{compile, transcript::evm::EvmTranscript, Config}, 31 | verifier::SnarkVerifier, 32 | }; 33 | use std::{fs, io, path::Path, rc::Rc}; 34 | 35 | /// Generates a proof for evm verification using either SHPLONK or GWC proving method. Uses Keccak for Fiat-Shamir. 36 | pub fn gen_evm_proof<'params, C, P, V>( 37 | params: &'params ParamsKZG, 38 | pk: &'params ProvingKey, 39 | circuit: C, 40 | instances: Vec>, 41 | ) -> Vec 42 | where 43 | C: Circuit, 44 | P: Prover<'params, KZGCommitmentScheme>, 45 | V: Verifier< 46 | 'params, 47 | KZGCommitmentScheme, 48 | Guard = GuardKZG<'params, Bn256>, 49 | MSMAccumulator = DualMSM<'params, Bn256>, 50 | >, 51 | { 52 | let instances = instances 53 | .iter() 54 | .map(|instances| instances.as_slice()) 55 | .collect_vec(); 56 | 57 | #[cfg(feature = "display")] 58 | let proof_time = start_timer!(|| "Create EVM proof"); 59 | let rng = StdRng::from_entropy(); 60 | let proof = { 61 | let mut transcript = TranscriptWriterBuffer::<_, G1Affine, _>::init(Vec::new()); 62 | create_proof::, P, _, _, EvmTranscript<_, _, _, _>, _>( 63 | params, 64 | pk, 65 | &[circuit], 66 | &[instances.as_slice()], 67 | rng, 68 | &mut transcript, 69 | ) 70 | .unwrap(); 71 | transcript.finalize() 72 | }; 73 | #[cfg(feature = "display")] 74 | end_timer!(proof_time); 75 | 76 | let accept = { 77 | let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); 78 | VerificationStrategy::<_, V>::finalize( 79 | verify_proof::<_, V, _, EvmTranscript<_, _, _, _>, _>( 80 | params.verifier_params(), 81 | pk.get_vk(), 82 | AccumulatorStrategy::new(params.verifier_params()), 83 | &[instances.as_slice()], 84 | &mut transcript, 85 | ) 86 | .unwrap(), 87 | ) 88 | }; 89 | assert!(accept); 90 | 91 | proof 92 | } 93 | 94 | pub fn gen_evm_proof_gwc<'params, C: Circuit>( 95 | params: &'params ParamsKZG, 96 | pk: &'params ProvingKey, 97 | circuit: C, 98 | instances: Vec>, 99 | ) -> Vec { 100 | gen_evm_proof::, VerifierGWC<_>>(params, pk, circuit, instances) 101 | } 102 | 103 | pub fn gen_evm_proof_shplonk<'params, C: Circuit>( 104 | params: &'params ParamsKZG, 105 | pk: &'params ProvingKey, 106 | circuit: C, 107 | instances: Vec>, 108 | ) -> Vec { 109 | gen_evm_proof::, VerifierSHPLONK<_>>(params, pk, circuit, instances) 110 | } 111 | 112 | pub fn gen_evm_verifier( 113 | params: &ParamsKZG, 114 | vk: &VerifyingKey, 115 | num_instance: Vec, 116 | path: Option<&Path>, 117 | ) -> Vec 118 | where 119 | C: CircuitExt, 120 | AS: PolynomialCommitmentScheme< 121 | G1Affine, 122 | Rc, 123 | VerifyingKey = KzgSuccinctVerifyingKey, 124 | Output = KzgAccumulator>, 125 | > + AccumulationScheme< 126 | G1Affine, 127 | Rc, 128 | VerifyingKey = KzgAsVerifyingKey, 129 | Accumulator = KzgAccumulator>, 130 | > + AccumulationDecider, DecidingKey = KzgDecidingKey>, 131 | { 132 | let protocol = compile( 133 | params, 134 | vk, 135 | Config::kzg() 136 | .with_num_instance(num_instance.clone()) 137 | .with_accumulator_indices(C::accumulator_indices()), 138 | ); 139 | // deciding key 140 | let dk = (params.get_g()[0], params.g2(), params.s_g2()).into(); 141 | 142 | let loader = EvmLoader::new::(); 143 | let protocol = protocol.loaded(&loader); 144 | let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 145 | 146 | let instances = transcript.load_instances(num_instance); 147 | let proof = 148 | PlonkVerifier::::read_proof(&dk, &protocol, &instances, &mut transcript).unwrap(); 149 | PlonkVerifier::::verify(&dk, &protocol, &instances, &proof).unwrap(); 150 | 151 | let sol_code = loader.solidity_code(); 152 | let byte_code = compile_solidity(&sol_code); 153 | if let Some(path) = path { 154 | path.parent() 155 | .and_then(|dir| fs::create_dir_all(dir).ok()) 156 | .unwrap(); 157 | fs::write(path, sol_code).unwrap(); 158 | } 159 | byte_code 160 | } 161 | 162 | pub fn gen_evm_verifier_gwc>( 163 | params: &ParamsKZG, 164 | vk: &VerifyingKey, 165 | num_instance: Vec, 166 | path: Option<&Path>, 167 | ) -> Vec { 168 | gen_evm_verifier::(params, vk, num_instance, path) 169 | } 170 | 171 | pub fn gen_evm_verifier_shplonk>( 172 | params: &ParamsKZG, 173 | vk: &VerifyingKey, 174 | num_instance: Vec, 175 | path: Option<&Path>, 176 | ) -> Vec { 177 | gen_evm_verifier::(params, vk, num_instance, path) 178 | } 179 | 180 | pub fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) -> u64 { 181 | let calldata = encode_calldata(&instances, &proof); 182 | let gas_cost = deploy_and_call(deployment_code, calldata).unwrap(); 183 | dbg!(gas_cost); 184 | gas_cost 185 | } 186 | 187 | pub fn write_calldata(instances: &[Vec], proof: &[u8], path: &Path) -> io::Result { 188 | let calldata = encode_calldata(instances, proof); 189 | let calldata = hex::encode(calldata); 190 | fs::write(path, &calldata)?; 191 | Ok(calldata) 192 | } 193 | -------------------------------------------------------------------------------- /snark-verifier/src/verifier/plonk.rs: -------------------------------------------------------------------------------- 1 | //! Verifiers for [PLONK], currently there are [`PlonkSuccinctVerifier`] and 2 | //! [`PlonkVerifier`] implemented and both are implemented assuming the used 3 | //! [`PolynomialCommitmentScheme`] has [atomic] or [split] accumulation scheme 4 | //! ([`PlonkVerifier`] is just [`PlonkSuccinctVerifier`] plus doing accumulator 5 | //! deciding then returns accept/reject as output). 6 | //! 7 | //! [PLONK]: https://eprint.iacr.org/2019/953 8 | //! [atomic]: https://eprint.iacr.org/2020/499 9 | //! [split]: https://eprint.iacr.org/2020/1618 10 | 11 | use crate::{ 12 | cost::{Cost, CostEstimation}, 13 | loader::Loader, 14 | pcs::{ 15 | AccumulationDecider, AccumulationScheme, AccumulatorEncoding, PolynomialCommitmentScheme, 16 | Query, 17 | }, 18 | util::{arithmetic::CurveAffine, transcript::TranscriptRead}, 19 | verifier::{plonk::protocol::CommonPolynomialEvaluation, SnarkVerifier}, 20 | Error, 21 | }; 22 | use std::{iter, marker::PhantomData}; 23 | 24 | mod proof; 25 | pub(crate) mod protocol; 26 | 27 | pub use proof::PlonkProof; 28 | pub use protocol::PlonkProtocol; 29 | 30 | /// Verifier that verifies the cheap part of PLONK and output the accumulator. 31 | #[derive(Debug)] 32 | pub struct PlonkSuccinctVerifier>(PhantomData<(AS, AE)>); 33 | 34 | impl SnarkVerifier for PlonkSuccinctVerifier 35 | where 36 | C: CurveAffine, 37 | L: Loader, 38 | AS: AccumulationScheme + PolynomialCommitmentScheme, 39 | AE: AccumulatorEncoding, 40 | { 41 | type VerifyingKey = >::VerifyingKey; 42 | type Protocol = PlonkProtocol; 43 | type Proof = PlonkProof; 44 | type Output = Vec; 45 | 46 | fn read_proof( 47 | svk: &Self::VerifyingKey, 48 | protocol: &Self::Protocol, 49 | instances: &[Vec], 50 | transcript: &mut T, 51 | ) -> Result 52 | where 53 | T: TranscriptRead, 54 | { 55 | PlonkProof::read::(svk, protocol, instances, transcript) 56 | } 57 | 58 | fn verify( 59 | svk: &Self::VerifyingKey, 60 | protocol: &Self::Protocol, 61 | instances: &[Vec], 62 | proof: &Self::Proof, 63 | ) -> Result { 64 | let common_poly_eval = { 65 | let mut common_poly_eval = 66 | CommonPolynomialEvaluation::new(&protocol.domain, protocol.langranges(), &proof.z); 67 | 68 | L::batch_invert(common_poly_eval.denoms()); 69 | common_poly_eval.evaluate(); 70 | 71 | common_poly_eval 72 | }; 73 | 74 | let mut evaluations = proof.evaluations(protocol, instances, &common_poly_eval)?; 75 | let commitments = proof.commitments(protocol, &common_poly_eval, &mut evaluations)?; 76 | let queries = proof.queries(protocol, evaluations); 77 | 78 | let accumulator = >::verify( 79 | svk, 80 | &commitments, 81 | &proof.z, 82 | &queries, 83 | &proof.pcs, 84 | )?; 85 | 86 | let accumulators = iter::empty() 87 | .chain(Some(accumulator)) 88 | .chain(proof.old_accumulators.iter().cloned()) 89 | .collect(); 90 | 91 | Ok(accumulators) 92 | } 93 | } 94 | 95 | /// Verifier that first verifies the cheap part of PLONK, then decides 96 | /// accumulator and returns accept/reject as output. 97 | #[derive(Debug)] 98 | pub struct PlonkVerifier>(PhantomData<(AS, AE)>); 99 | 100 | impl SnarkVerifier for PlonkVerifier 101 | where 102 | C: CurveAffine, 103 | L: Loader, 104 | AS: AccumulationDecider + PolynomialCommitmentScheme, 105 | AS::DecidingKey: AsRef<>::VerifyingKey>, 106 | AE: AccumulatorEncoding, 107 | { 108 | type VerifyingKey = AS::DecidingKey; 109 | type Protocol = PlonkProtocol; 110 | type Proof = PlonkProof; 111 | type Output = (); 112 | 113 | fn read_proof( 114 | vk: &Self::VerifyingKey, 115 | protocol: &Self::Protocol, 116 | instances: &[Vec], 117 | transcript: &mut T, 118 | ) -> Result 119 | where 120 | T: TranscriptRead, 121 | { 122 | PlonkProof::read::(vk.as_ref(), protocol, instances, transcript) 123 | } 124 | 125 | fn verify( 126 | vk: &Self::VerifyingKey, 127 | protocol: &Self::Protocol, 128 | instances: &[Vec], 129 | proof: &Self::Proof, 130 | ) -> Result { 131 | let accumulators = 132 | PlonkSuccinctVerifier::::verify(vk.as_ref(), protocol, instances, proof)?; 133 | AS::decide_all(vk, accumulators) 134 | } 135 | } 136 | 137 | impl CostEstimation<(C, L)> for PlonkSuccinctVerifier 138 | where 139 | C: CurveAffine, 140 | L: Loader, 141 | AS: AccumulationScheme 142 | + PolynomialCommitmentScheme 143 | + CostEstimation>>, 144 | { 145 | type Input = PlonkProtocol; 146 | 147 | fn estimate_cost(protocol: &PlonkProtocol) -> Cost { 148 | let plonk_cost = { 149 | let num_accumulator = protocol.accumulator_indices.len(); 150 | let num_instance = protocol.num_instance.iter().sum(); 151 | let num_commitment = 152 | protocol.num_witness.iter().sum::() + protocol.quotient.num_chunk(); 153 | let num_evaluation = protocol.evaluations.len(); 154 | let num_msm = protocol.preprocessed.len() + num_commitment + 1 + 2 * num_accumulator; 155 | Cost { 156 | num_instance, 157 | num_commitment, 158 | num_evaluation, 159 | num_msm, 160 | ..Default::default() 161 | } 162 | }; 163 | let pcs_cost = { 164 | let queries = PlonkProof::::empty_queries(protocol); 165 | AS::estimate_cost(&queries) 166 | }; 167 | plonk_cost + pcs_cost 168 | } 169 | } 170 | 171 | impl CostEstimation<(C, L)> for PlonkVerifier 172 | where 173 | C: CurveAffine, 174 | L: Loader, 175 | AS: AccumulationScheme 176 | + PolynomialCommitmentScheme 177 | + CostEstimation>>, 178 | { 179 | type Input = PlonkProtocol; 180 | 181 | fn estimate_cost(protocol: &PlonkProtocol) -> Cost { 182 | PlonkSuccinctVerifier::::estimate_cost(protocol) 183 | + Cost { 184 | num_pairing: 2, 185 | ..Default::default() 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /snark-verifier-sdk/benches/standard_plonk.rs: -------------------------------------------------------------------------------- 1 | use ark_std::{end_timer, start_timer}; 2 | use criterion::{criterion_group, criterion_main}; 3 | use criterion::{BenchmarkId, Criterion}; 4 | use halo2_proofs::halo2curves as halo2_curves; 5 | use halo2_proofs::{ 6 | halo2curves::bn256::Bn256, 7 | poly::{commitment::Params, kzg::commitment::ParamsKZG}, 8 | }; 9 | use pprof::criterion::{Output, PProfProfiler}; 10 | use rand::rngs::OsRng; 11 | use snark_verifier_sdk::halo2::gen_srs; 12 | use snark_verifier_sdk::SHPLONK; 13 | use snark_verifier_sdk::{ 14 | gen_pk, 15 | halo2::{aggregation::AggregationCircuit, gen_snark_shplonk}, 16 | Snark, 17 | }; 18 | use std::path::Path; 19 | 20 | mod application { 21 | use super::halo2_curves::bn256::Fr; 22 | use halo2_proofs::{ 23 | circuit::{Layouter, SimpleFloorPlanner, Value}, 24 | plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, 25 | poly::Rotation, 26 | }; 27 | use rand::RngCore; 28 | use snark_verifier_sdk::CircuitExt; 29 | 30 | #[derive(Clone, Copy)] 31 | pub struct StandardPlonkConfig { 32 | a: Column, 33 | b: Column, 34 | c: Column, 35 | q_a: Column, 36 | q_b: Column, 37 | q_c: Column, 38 | q_ab: Column, 39 | constant: Column, 40 | #[allow(dead_code)] 41 | instance: Column, 42 | } 43 | 44 | impl StandardPlonkConfig { 45 | fn configure(meta: &mut ConstraintSystem) -> Self { 46 | let [a, b, c] = [(); 3].map(|_| meta.advice_column()); 47 | let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); 48 | let instance = meta.instance_column(); 49 | 50 | [a, b, c].map(|column| meta.enable_equality(column)); 51 | 52 | meta.create_gate( 53 | "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", 54 | |meta| { 55 | let [a, b, c] = 56 | [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); 57 | let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] 58 | .map(|column| meta.query_fixed(column, Rotation::cur())); 59 | let instance = meta.query_instance(instance, Rotation::cur()); 60 | Some( 61 | q_a * a.clone() 62 | + q_b * b.clone() 63 | + q_c * c 64 | + q_ab * a * b 65 | + constant 66 | + instance, 67 | ) 68 | }, 69 | ); 70 | 71 | StandardPlonkConfig { 72 | a, 73 | b, 74 | c, 75 | q_a, 76 | q_b, 77 | q_c, 78 | q_ab, 79 | constant, 80 | instance, 81 | } 82 | } 83 | } 84 | 85 | #[derive(Clone, Default)] 86 | pub struct StandardPlonk(Fr); 87 | 88 | impl StandardPlonk { 89 | pub fn rand(mut rng: R) -> Self { 90 | Self(Fr::from(rng.next_u32() as u64)) 91 | } 92 | } 93 | 94 | impl CircuitExt for StandardPlonk { 95 | fn num_instance(&self) -> Vec { 96 | vec![1] 97 | } 98 | 99 | fn instances(&self) -> Vec> { 100 | vec![vec![self.0]] 101 | } 102 | } 103 | 104 | impl Circuit for StandardPlonk { 105 | type Config = StandardPlonkConfig; 106 | type FloorPlanner = SimpleFloorPlanner; 107 | #[cfg(feature = "halo2_circuit_params")] 108 | type Params = (); 109 | 110 | fn without_witnesses(&self) -> Self { 111 | Self::default() 112 | } 113 | 114 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 115 | meta.set_minimum_degree(4); 116 | StandardPlonkConfig::configure(meta) 117 | } 118 | 119 | fn synthesize( 120 | &self, 121 | config: Self::Config, 122 | mut layouter: impl Layouter, 123 | ) -> Result<(), Error> { 124 | layouter.assign_region( 125 | || "", 126 | |mut region| { 127 | region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; 128 | region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::one()))?; 129 | region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5u64)))?; 130 | for (idx, column) in (1..).zip([ 131 | config.q_a, 132 | config.q_b, 133 | config.q_c, 134 | config.q_ab, 135 | config.constant, 136 | ]) { 137 | region.assign_fixed( 138 | || "", 139 | column, 140 | 1, 141 | || Value::known(Fr::from(idx as u64)), 142 | )?; 143 | } 144 | let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::one()))?; 145 | a.copy_advice(|| "", &mut region, config.b, 3)?; 146 | a.copy_advice(|| "", &mut region, config.c, 4)?; 147 | 148 | Ok(()) 149 | }, 150 | ) 151 | } 152 | } 153 | } 154 | 155 | fn gen_application_snark(params: &ParamsKZG) -> Snark { 156 | let circuit = application::StandardPlonk::rand(OsRng); 157 | 158 | let pk = gen_pk(params, &circuit, Some(Path::new("./benches/app.pk"))); 159 | gen_snark_shplonk(params, &pk, circuit, None::<&str>) 160 | } 161 | 162 | fn bench(c: &mut Criterion) { 163 | let params_app = gen_srs(8); 164 | let snarks = [(); 3].map(|_| gen_application_snark(¶ms_app)); 165 | 166 | let params = gen_srs(22); 167 | let agg_circuit = AggregationCircuit::::new(¶ms, snarks.clone()); 168 | 169 | let start0 = start_timer!(|| "gen vk & pk"); 170 | let pk = gen_pk(¶ms, &agg_circuit, Some(Path::new("./benches/agg.pk"))); 171 | end_timer!(start0); 172 | 173 | let mut group = c.benchmark_group("plonk-prover"); 174 | group.sample_size(10); 175 | group.bench_with_input( 176 | BenchmarkId::new("standard-plonk-agg", params.k()), 177 | &(¶ms, &pk, &snarks), 178 | |b, &(params, pk, snarks)| { 179 | b.iter(|| { 180 | let agg_circuit = AggregationCircuit::::new(params, snarks.clone()); 181 | gen_snark_shplonk(params, pk, agg_circuit, None::<&str>) 182 | }) 183 | }, 184 | ); 185 | group.finish(); 186 | } 187 | 188 | criterion_group! { 189 | name = benches; 190 | config = Criterion::default().with_profiler(PProfProfiler::new(10, Output::Flamegraph(None))); 191 | targets = bench 192 | } 193 | criterion_main!(benches); 194 | -------------------------------------------------------------------------------- /snark-verifier/src/util/hash/poseidon.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | loader::{LoadedScalar, ScalarLoader}, 3 | util::{ 4 | arithmetic::{FromUniformBytes, PrimeField}, 5 | Itertools, 6 | }, 7 | }; 8 | use poseidon::{self, SparseMDSMatrix, Spec}; 9 | use std::{iter, marker::PhantomData, mem}; 10 | 11 | #[derive(Debug)] 12 | struct State { 13 | inner: [L; T], 14 | _marker: PhantomData, 15 | } 16 | 17 | impl, const T: usize, const RATE: usize> State { 18 | fn new(inner: [L; T]) -> Self { 19 | Self { 20 | inner, 21 | _marker: PhantomData, 22 | } 23 | } 24 | 25 | fn loader(&self) -> &L::Loader { 26 | self.inner[0].loader() 27 | } 28 | 29 | fn power5_with_constant(value: &L, constant: &F) -> L { 30 | value 31 | .loader() 32 | .sum_products_with_const(&[(value, &value.square().square())], *constant) 33 | } 34 | 35 | fn sbox_full(&mut self, constants: &[F; T]) { 36 | for (state, constant) in self.inner.iter_mut().zip(constants.iter()) { 37 | *state = Self::power5_with_constant(state, constant); 38 | } 39 | } 40 | 41 | fn sbox_part(&mut self, constant: &F) { 42 | self.inner[0] = Self::power5_with_constant(&self.inner[0], constant); 43 | } 44 | 45 | fn absorb_with_pre_constants(&mut self, inputs: &[L], pre_constants: &[F; T]) { 46 | assert!(inputs.len() < T); 47 | 48 | self.inner[0] = self 49 | .loader() 50 | .sum_with_const(&[&self.inner[0]], pre_constants[0]); 51 | self.inner 52 | .iter_mut() 53 | .zip(pre_constants.iter()) 54 | .skip(1) 55 | .zip(inputs) 56 | .for_each(|((state, constant), input)| { 57 | *state = state.loader().sum_with_const(&[state, input], *constant); 58 | }); 59 | self.inner 60 | .iter_mut() 61 | .zip(pre_constants.iter()) 62 | .skip(1 + inputs.len()) 63 | .enumerate() 64 | .for_each(|(idx, (state, constant))| { 65 | *state = state.loader().sum_with_const( 66 | &[state], 67 | if idx == 0 { 68 | F::ONE + constant 69 | } else { 70 | *constant 71 | }, 72 | ); 73 | }); 74 | } 75 | 76 | fn apply_mds(&mut self, mds: &[[F; T]; T]) { 77 | self.inner = mds 78 | .iter() 79 | .map(|row| { 80 | self.loader() 81 | .sum_with_coeff(&row.iter().cloned().zip(self.inner.iter()).collect_vec()) 82 | }) 83 | .collect_vec() 84 | .try_into() 85 | .unwrap(); 86 | } 87 | 88 | fn apply_sparse_mds(&mut self, mds: &SparseMDSMatrix) { 89 | self.inner = iter::once( 90 | self.loader().sum_with_coeff( 91 | &mds.row() 92 | .iter() 93 | .cloned() 94 | .zip(self.inner.iter()) 95 | .collect_vec(), 96 | ), 97 | ) 98 | .chain( 99 | mds.col_hat() 100 | .iter() 101 | .zip(self.inner.iter().skip(1)) 102 | .map(|(coeff, state)| { 103 | self.loader() 104 | .sum_with_coeff(&[(*coeff, &self.inner[0]), (F::ONE, state)]) 105 | }), 106 | ) 107 | .collect_vec() 108 | .try_into() 109 | .unwrap(); 110 | } 111 | } 112 | 113 | /// Poseidon hasher with configurable `RATE`. 114 | #[derive(Debug)] 115 | pub struct Poseidon { 116 | spec: Spec, 117 | state: State, 118 | buf: Vec, 119 | } 120 | 121 | impl, L: LoadedScalar, const T: usize, const RATE: usize> 122 | Poseidon 123 | { 124 | /// Initialize a poseidon hasher. 125 | pub fn new(loader: &L::Loader, r_f: usize, r_p: usize) -> Self { 126 | Self { 127 | spec: Spec::new(r_f, r_p), 128 | state: State::new( 129 | poseidon::State::default() 130 | .words() 131 | .map(|state| loader.load_const(&state)), 132 | ), 133 | buf: Vec::new(), 134 | } 135 | } 136 | 137 | /// Same as `new`, but uses the given `spec` instead of creating a new one. 138 | pub fn from_spec(loader: &L::Loader, spec: Spec) -> Self { 139 | Self { 140 | spec, 141 | state: State::new( 142 | poseidon::State::default() 143 | .words() 144 | .map(|state| loader.load_const(&state)), 145 | ), 146 | buf: Vec::new(), 147 | } 148 | } 149 | 150 | /// Store given `elements` into buffer. 151 | pub fn update(&mut self, elements: &[L]) { 152 | self.buf.extend_from_slice(elements); 153 | } 154 | 155 | /// Consume buffer and perform permutation, then output second element of 156 | /// state. 157 | pub fn squeeze(&mut self) -> L { 158 | let buf = mem::take(&mut self.buf); 159 | let exact = buf.len() % RATE == 0; 160 | 161 | for chunk in buf.chunks(RATE) { 162 | self.permutation(chunk); 163 | } 164 | if exact { 165 | self.permutation(&[]); 166 | } 167 | 168 | self.state.inner[1].clone() 169 | } 170 | 171 | fn permutation(&mut self, inputs: &[L]) { 172 | let r_f = self.spec.r_f() / 2; 173 | let mds = self.spec.mds_matrices().mds().rows(); 174 | let pre_sparse_mds = self.spec.mds_matrices().pre_sparse_mds().rows(); 175 | let sparse_matrices = self.spec.mds_matrices().sparse_matrices(); 176 | 177 | // First half of the full rounds 178 | let constants = self.spec.constants().start(); 179 | self.state.absorb_with_pre_constants(inputs, &constants[0]); 180 | for constants in constants.iter().skip(1).take(r_f - 1) { 181 | self.state.sbox_full(constants); 182 | self.state.apply_mds(&mds); 183 | } 184 | self.state.sbox_full(constants.last().unwrap()); 185 | self.state.apply_mds(&pre_sparse_mds); 186 | 187 | // Partial rounds 188 | let constants = self.spec.constants().partial(); 189 | for (constant, sparse_mds) in constants.iter().zip(sparse_matrices.iter()) { 190 | self.state.sbox_part(constant); 191 | self.state.apply_sparse_mds(sparse_mds); 192 | } 193 | 194 | // Second half of the full rounds 195 | let constants = self.spec.constants().end(); 196 | for constants in constants.iter() { 197 | self.state.sbox_full(constants); 198 | self.state.apply_mds(&mds); 199 | } 200 | self.state.sbox_full(&[F::ZERO; T]); 201 | self.state.apply_mds(&mds); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /snark-verifier/src/system/halo2/test.rs: -------------------------------------------------------------------------------- 1 | use crate::util::arithmetic::{CurveAffine, FromUniformBytes, WithSmallOrderMulGroup}; 2 | use halo2_proofs::{ 3 | dev::MockProver, 4 | plonk::{create_proof, verify_proof, Circuit, ProvingKey}, 5 | poly::{ 6 | commitment::{CommitmentScheme, Params, ParamsProver, Prover, Verifier}, 7 | VerificationStrategy, 8 | }, 9 | transcript::{EncodedChallenge, TranscriptReadBuffer, TranscriptWriterBuffer}, 10 | }; 11 | use rand_chacha::rand_core::RngCore; 12 | use std::{fs, io::Cursor}; 13 | 14 | mod circuit; 15 | mod ipa; 16 | mod kzg; 17 | 18 | pub use circuit::{ 19 | maingate::{MainGateWithRange, MainGateWithRangeConfig}, 20 | standard::StandardPlonk, 21 | }; 22 | 23 | pub fn read_or_create_srs<'a, C: CurveAffine, P: ParamsProver<'a, C>>( 24 | dir: &str, 25 | k: u32, 26 | setup: impl Fn(u32) -> P, 27 | ) -> P { 28 | let path = format!("{}/k-{}.srs", dir, k); 29 | match fs::File::open(path.as_str()) { 30 | Ok(mut file) => P::read(&mut file).unwrap(), 31 | Err(_) => { 32 | fs::create_dir_all(dir).unwrap(); 33 | let params = setup(k); 34 | params.write(&mut fs::File::create(path).unwrap()).unwrap(); 35 | params 36 | } 37 | } 38 | } 39 | 40 | pub fn create_proof_checked<'a, S, C, P, V, VS, TW, TR, EC, R>( 41 | params: &'a S::ParamsProver, 42 | pk: &ProvingKey, 43 | circuits: &[C], 44 | instances: &[&[&[S::Scalar]]], 45 | mut rng: R, 46 | finalize: impl Fn(Vec, VS::Output) -> Vec, 47 | ) -> Vec 48 | where 49 | S: CommitmentScheme, 50 | S::Scalar: WithSmallOrderMulGroup<3> + FromUniformBytes<64> + Ord, 51 | S::ParamsVerifier: 'a, 52 | C: Circuit, 53 | P: Prover<'a, S>, 54 | V: Verifier<'a, S>, 55 | VS: VerificationStrategy<'a, S, V>, 56 | TW: TranscriptWriterBuffer, S::Curve, EC>, 57 | TR: TranscriptReadBuffer>, S::Curve, EC>, 58 | EC: EncodedChallenge, 59 | R: RngCore, 60 | { 61 | for (circuit, instances) in circuits.iter().zip(instances.iter()) { 62 | MockProver::run( 63 | params.k(), 64 | circuit, 65 | instances.iter().map(|instance| instance.to_vec()).collect(), 66 | ) 67 | .unwrap() 68 | .assert_satisfied(); 69 | } 70 | 71 | let proof = { 72 | let mut transcript = TW::init(Vec::new()); 73 | create_proof::( 74 | params, 75 | pk, 76 | circuits, 77 | instances, 78 | &mut rng, 79 | &mut transcript, 80 | ) 81 | .unwrap(); 82 | transcript.finalize() 83 | }; 84 | 85 | let output = { 86 | let params = params.verifier_params(); 87 | let strategy = VS::new(params); 88 | let mut transcript = TR::init(Cursor::new(proof.clone())); 89 | verify_proof(params, pk.get_vk(), strategy, instances, &mut transcript).unwrap() 90 | }; 91 | 92 | finalize(proof, output) 93 | } 94 | 95 | macro_rules! halo2_prepare { 96 | ($dir:expr, $k:expr, $setup:expr, $config:expr, $create_circuit:expr) => {{ 97 | use halo2_proofs::plonk::{keygen_pk, keygen_vk}; 98 | use std::iter; 99 | use $crate::{ 100 | system::halo2::{compile, test::read_or_create_srs}, 101 | util::{arithmetic::GroupEncoding, Itertools}, 102 | }; 103 | 104 | let params = read_or_create_srs($dir, $k, $setup); 105 | 106 | let circuits = iter::repeat_with(|| $create_circuit) 107 | .take($config.num_proof) 108 | .collect_vec(); 109 | 110 | let pk = if $config.zk { 111 | let vk = keygen_vk(¶ms, &circuits[0]).unwrap(); 112 | let pk = keygen_pk(¶ms, vk, &circuits[0]).unwrap(); 113 | pk 114 | } else { 115 | // TODO: Re-enable optional-zk when it's merged in pse/halo2. 116 | unimplemented!() 117 | }; 118 | 119 | let num_instance = circuits[0] 120 | .instances() 121 | .iter() 122 | .map(|instances| instances.len()) 123 | .collect(); 124 | let protocol = compile( 125 | ¶ms, 126 | pk.get_vk(), 127 | $config.with_num_instance(num_instance), 128 | ); 129 | assert_eq!( 130 | protocol.preprocessed.len(), 131 | protocol 132 | .preprocessed 133 | .iter() 134 | .map( 135 | |ec_point| <[u8; 32]>::try_from(ec_point.to_bytes().as_ref().to_vec()).unwrap() 136 | ) 137 | .unique() 138 | .count() 139 | ); 140 | 141 | (params, pk, protocol, circuits) 142 | }}; 143 | } 144 | 145 | macro_rules! halo2_create_snark { 146 | ( 147 | $commitment_scheme:ty, 148 | $prover:ty, 149 | $verifier:ty, 150 | $verification_strategy:ty, 151 | $transcript_read:ty, 152 | $transcript_write:ty, 153 | $encoded_challenge:ty, 154 | $finalize:expr, 155 | $params:expr, 156 | $pk:expr, 157 | $protocol:expr, 158 | $circuits:expr 159 | ) => {{ 160 | use rand_chacha::{rand_core::SeedableRng, ChaCha20Rng}; 161 | use $crate::{ 162 | loader::halo2::test::Snark, system::halo2::test::create_proof_checked, util::Itertools, 163 | }; 164 | 165 | let instances = $circuits 166 | .iter() 167 | .map(|circuit| circuit.instances()) 168 | .collect_vec(); 169 | let proof = { 170 | #[allow(clippy::needless_borrow)] 171 | let instances = instances 172 | .iter() 173 | .map(|instances| instances.iter().map(Vec::as_slice).collect_vec()) 174 | .collect_vec(); 175 | let instances = instances.iter().map(Vec::as_slice).collect_vec(); 176 | create_proof_checked::< 177 | $commitment_scheme, 178 | _, 179 | $prover, 180 | $verifier, 181 | $verification_strategy, 182 | $transcript_read, 183 | $transcript_write, 184 | $encoded_challenge, 185 | _, 186 | >( 187 | $params, 188 | $pk, 189 | $circuits, 190 | &instances, 191 | &mut ChaCha20Rng::from_seed(Default::default()), 192 | $finalize, 193 | ) 194 | }; 195 | 196 | Snark::new( 197 | $protocol.clone(), 198 | instances.into_iter().flatten().collect_vec(), 199 | proof, 200 | ) 201 | }}; 202 | } 203 | 204 | macro_rules! halo2_native_verify { 205 | ( 206 | $plonk_verifier:ty, 207 | $params:expr, 208 | $protocol:expr, 209 | $instances:expr, 210 | $transcript:expr, 211 | $vk:expr 212 | ) => {{ 213 | use halo2_proofs::poly::commitment::ParamsProver; 214 | use $crate::verifier::SnarkVerifier; 215 | 216 | let proof = <$plonk_verifier>::read_proof($vk, $protocol, $instances, $transcript).unwrap(); 217 | assert!(<$plonk_verifier>::verify($vk, $protocol, $instances, &proof).is_ok()) 218 | }}; 219 | } 220 | 221 | pub(crate) use {halo2_create_snark, halo2_native_verify, halo2_prepare}; 222 | -------------------------------------------------------------------------------- /snark-verifier-sdk/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "display")] 2 | use ark_std::{end_timer, start_timer}; 3 | use halo2::aggregation::AccumulationSchemeSDK; 4 | use halo2_proofs::{ 5 | circuit::Value, 6 | halo2curves::{ 7 | bn256::{Bn256, Fr, G1Affine}, 8 | group::ff::Field, 9 | }, 10 | plonk::{keygen_pk, keygen_vk, Circuit, ProvingKey, Selector}, 11 | poly::kzg::commitment::ParamsKZG, 12 | SerdeFormat, 13 | }; 14 | use itertools::Itertools; 15 | #[cfg(feature = "derive_serde")] 16 | use serde::{Deserialize, Serialize}; 17 | pub use snark_verifier::loader::native::NativeLoader; 18 | use snark_verifier::{ 19 | pcs::kzg::{Bdfg21, Gwc19, KzgAs, LimbsEncoding}, 20 | verifier::{self, plonk::PlonkProtocol}, 21 | }; 22 | use std::{ 23 | fs::{self, File}, 24 | io::{self, BufReader, BufWriter}, 25 | path::Path, 26 | }; 27 | 28 | #[cfg(feature = "loader_evm")] 29 | pub mod evm; 30 | #[cfg(feature = "loader_halo2")] 31 | pub mod halo2; 32 | 33 | pub const LIMBS: usize = 4; 34 | pub const BITS: usize = 68; 35 | 36 | /// AS stands for accumulation scheme. 37 | /// AS can be either `Kzg` (the original PLONK KZG multi-open) or `Kzg` (SHPLONK) 38 | pub type PlonkVerifier = verifier::plonk::PlonkVerifier>; 39 | pub type PlonkSuccinctVerifier = 40 | verifier::plonk::PlonkSuccinctVerifier>; 41 | pub type SHPLONK = KzgAs; 42 | pub type GWC = KzgAs; 43 | 44 | impl AccumulationSchemeSDK for GWC {} 45 | impl AccumulationSchemeSDK for SHPLONK {} 46 | 47 | #[derive(Clone, Debug)] 48 | #[cfg_attr(feature = "derive_serde", derive(Serialize, Deserialize))] 49 | pub struct Snark { 50 | pub protocol: PlonkProtocol, 51 | pub instances: Vec>, 52 | pub proof: Vec, 53 | } 54 | 55 | impl Snark { 56 | pub fn new(protocol: PlonkProtocol, instances: Vec>, proof: Vec) -> Self { 57 | Self { 58 | protocol, 59 | instances, 60 | proof, 61 | } 62 | } 63 | 64 | pub fn proof(&self) -> &[u8] { 65 | &self.proof[..] 66 | } 67 | } 68 | 69 | impl From for SnarkWitness { 70 | fn from(snark: Snark) -> Self { 71 | Self { 72 | protocol: snark.protocol, 73 | instances: snark 74 | .instances 75 | .into_iter() 76 | .map(|instances| instances.into_iter().map(Value::known).collect_vec()) 77 | .collect(), 78 | proof: Value::known(snark.proof), 79 | } 80 | } 81 | } 82 | 83 | #[derive(Clone)] 84 | pub struct SnarkWitness { 85 | protocol: PlonkProtocol, 86 | instances: Vec>>, 87 | proof: Value>, 88 | } 89 | 90 | impl SnarkWitness { 91 | fn without_witnesses(&self) -> Self { 92 | SnarkWitness { 93 | protocol: self.protocol.clone(), 94 | instances: self 95 | .instances 96 | .iter() 97 | .map(|instances| vec![Value::unknown(); instances.len()]) 98 | .collect(), 99 | proof: Value::unknown(), 100 | } 101 | } 102 | 103 | fn proof(&self) -> Value<&[u8]> { 104 | self.proof.as_ref().map(Vec::as_slice) 105 | } 106 | } 107 | 108 | pub trait CircuitExt: Circuit { 109 | /// Return the instances of the circuit. 110 | /// This may depend on extra circuit parameters but NOT on private witnesses. 111 | fn instances(&self) -> Vec>; 112 | 113 | fn num_instance(&self) -> Vec { 114 | self.instances().iter().map(Vec::len).collect() 115 | } 116 | 117 | fn accumulator_indices() -> Option> { 118 | None 119 | } 120 | 121 | /// Output the simple selector columns (before selector compression) of the circuit 122 | fn selectors(_: &Self::Config) -> Vec { 123 | vec![] 124 | } 125 | } 126 | 127 | pub fn read_pk>( 128 | path: &Path, 129 | #[cfg(feature = "halo2_circuit_params")] param: C::Params, 130 | ) -> io::Result> { 131 | let f = File::open(path)?; 132 | #[cfg(feature = "display")] 133 | let read_time = start_timer!(|| format!("Reading pkey from {path:?}")); 134 | 135 | // BufReader is indeed MUCH faster than Read 136 | let mut bufreader = BufReader::new(f); 137 | // But it's even faster to load the whole file into memory first and then process, 138 | // HOWEVER this requires twice as much memory to initialize 139 | // let initial_buffer_size = f.metadata().map(|m| m.len() as usize + 1).unwrap_or(0); 140 | // let mut bufreader = Vec::with_capacity(initial_buffer_size); 141 | // f.read_to_end(&mut bufreader)?; 142 | let pk = ProvingKey::read::<_, C>( 143 | &mut bufreader, 144 | SerdeFormat::RawBytes, 145 | #[cfg(feature = "halo2_circuit_params")] 146 | param, 147 | ) 148 | .unwrap(); 149 | 150 | #[cfg(feature = "display")] 151 | end_timer!(read_time); 152 | 153 | Ok(pk) 154 | } 155 | 156 | #[allow(clippy::let_and_return)] 157 | pub fn gen_pk>( 158 | params: &ParamsKZG, // TODO: read pk without params 159 | circuit: &C, 160 | path: Option<&Path>, 161 | ) -> ProvingKey { 162 | if let Some(path) = path { 163 | if let Ok(pk) = read_pk::( 164 | path, 165 | #[cfg(feature = "halo2_circuit_params")] 166 | circuit.params(), 167 | ) { 168 | return pk; 169 | } 170 | } 171 | #[cfg(feature = "display")] 172 | let pk_time = start_timer!(|| "Generating vkey & pkey"); 173 | 174 | let vk = keygen_vk(params, circuit).unwrap(); 175 | let pk = keygen_pk(params, vk, circuit).unwrap(); 176 | 177 | #[cfg(feature = "display")] 178 | end_timer!(pk_time); 179 | 180 | if let Some(path) = path { 181 | #[cfg(feature = "display")] 182 | let write_time = start_timer!(|| format!("Writing pkey to {path:?}")); 183 | 184 | path.parent() 185 | .and_then(|dir| fs::create_dir_all(dir).ok()) 186 | .unwrap(); 187 | let mut f = BufWriter::new(File::create(path).unwrap()); 188 | pk.write(&mut f, SerdeFormat::RawBytesUnchecked).unwrap(); 189 | 190 | #[cfg(feature = "display")] 191 | end_timer!(write_time); 192 | } 193 | pk 194 | } 195 | 196 | pub fn read_instances(path: impl AsRef) -> Result>, bincode::Error> { 197 | let f = File::open(path)?; 198 | let reader = BufReader::new(f); 199 | let instances: Vec> = bincode::deserialize_from(reader)?; 200 | instances 201 | .into_iter() 202 | .map(|instance_column| { 203 | instance_column 204 | .iter() 205 | .map(|bytes| { 206 | Option::from(Fr::from_bytes(bytes)).ok_or_else(|| { 207 | Box::new(bincode::ErrorKind::Custom( 208 | "Invalid finite field point".to_owned(), 209 | )) 210 | }) 211 | }) 212 | .collect::, _>>() 213 | }) 214 | .collect() 215 | } 216 | 217 | pub fn write_instances(instances: &[&[Fr]], path: impl AsRef) { 218 | let instances: Vec> = instances 219 | .iter() 220 | .map(|instance_column| instance_column.iter().map(|x| x.to_bytes()).collect_vec()) 221 | .collect_vec(); 222 | let f = BufWriter::new(File::create(path).unwrap()); 223 | bincode::serialize_into(f, &instances).unwrap(); 224 | } 225 | 226 | #[cfg(feature = "zkevm")] 227 | mod zkevm { 228 | use super::CircuitExt; 229 | use eth_types::Field; 230 | use zkevm_circuits::{evm_circuit::EvmCircuit, state_circuit::StateCircuit}; 231 | 232 | impl CircuitExt for EvmCircuit { 233 | fn instances(&self) -> Vec> { 234 | vec![] 235 | } 236 | fn num_instance(&self) -> Vec { 237 | vec![] 238 | } 239 | } 240 | 241 | impl CircuitExt for StateCircuit { 242 | fn instances(&self) -> Vec> { 243 | vec![] 244 | } 245 | fn num_instance(&self) -> Vec { 246 | vec![] 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /snark-verifier/examples/evm-verifier.rs: -------------------------------------------------------------------------------- 1 | use halo2_curves::{ 2 | bn256::{Bn256, Fq, Fr, G1Affine}, 3 | ff::Field, 4 | }; 5 | use halo2_proofs::{ 6 | circuit::{Layouter, SimpleFloorPlanner, Value}, 7 | dev::MockProver, 8 | plonk::{ 9 | create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column, 10 | ConstraintSystem, Error, Fixed, Instance, ProvingKey, VerifyingKey, 11 | }, 12 | poly::{ 13 | commitment::{Params, ParamsProver}, 14 | kzg::{ 15 | commitment::{KZGCommitmentScheme, ParamsKZG}, 16 | multiopen::{ProverGWC, VerifierGWC}, 17 | strategy::AccumulatorStrategy, 18 | }, 19 | Rotation, VerificationStrategy, 20 | }, 21 | transcript::{TranscriptReadBuffer, TranscriptWriterBuffer}, 22 | }; 23 | use itertools::Itertools; 24 | use rand::{rngs::OsRng, RngCore}; 25 | use snark_verifier::{ 26 | loader::evm::{self, deploy_and_call, encode_calldata, EvmLoader}, 27 | pcs::kzg::{Gwc19, KzgAs}, 28 | system::halo2::{compile, transcript::evm::EvmTranscript, Config}, 29 | verifier::{self, SnarkVerifier}, 30 | }; 31 | use std::rc::Rc; 32 | 33 | type PlonkVerifier = verifier::plonk::PlonkVerifier>; 34 | 35 | #[derive(Clone, Copy)] 36 | struct StandardPlonkConfig { 37 | a: Column, 38 | b: Column, 39 | c: Column, 40 | q_a: Column, 41 | q_b: Column, 42 | q_c: Column, 43 | q_ab: Column, 44 | constant: Column, 45 | #[allow(dead_code)] 46 | instance: Column, 47 | } 48 | 49 | impl StandardPlonkConfig { 50 | fn configure(meta: &mut ConstraintSystem) -> Self { 51 | let [a, b, c] = [(); 3].map(|_| meta.advice_column()); 52 | let [q_a, q_b, q_c, q_ab, constant] = [(); 5].map(|_| meta.fixed_column()); 53 | let instance = meta.instance_column(); 54 | 55 | [a, b, c].map(|column| meta.enable_equality(column)); 56 | 57 | meta.create_gate( 58 | "q_a·a + q_b·b + q_c·c + q_ab·a·b + constant + instance = 0", 59 | |meta| { 60 | let [a, b, c] = [a, b, c].map(|column| meta.query_advice(column, Rotation::cur())); 61 | let [q_a, q_b, q_c, q_ab, constant] = [q_a, q_b, q_c, q_ab, constant] 62 | .map(|column| meta.query_fixed(column, Rotation::cur())); 63 | let instance = meta.query_instance(instance, Rotation::cur()); 64 | Some( 65 | q_a * a.clone() 66 | + q_b * b.clone() 67 | + q_c * c 68 | + q_ab * a * b 69 | + constant 70 | + instance, 71 | ) 72 | }, 73 | ); 74 | 75 | StandardPlonkConfig { 76 | a, 77 | b, 78 | c, 79 | q_a, 80 | q_b, 81 | q_c, 82 | q_ab, 83 | constant, 84 | instance, 85 | } 86 | } 87 | } 88 | 89 | #[derive(Clone, Default)] 90 | struct StandardPlonk(Fr); 91 | 92 | impl StandardPlonk { 93 | fn rand(mut rng: R) -> Self { 94 | Self(Fr::from(rng.next_u32() as u64)) 95 | } 96 | 97 | fn num_instance() -> Vec { 98 | vec![1] 99 | } 100 | 101 | fn instances(&self) -> Vec> { 102 | vec![vec![self.0]] 103 | } 104 | } 105 | 106 | impl Circuit for StandardPlonk { 107 | type Config = StandardPlonkConfig; 108 | type FloorPlanner = SimpleFloorPlanner; 109 | #[cfg(feature = "halo2_circuit_params")] 110 | type Params = (); 111 | 112 | fn without_witnesses(&self) -> Self { 113 | Self::default() 114 | } 115 | 116 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 117 | meta.set_minimum_degree(4); 118 | StandardPlonkConfig::configure(meta) 119 | } 120 | 121 | fn synthesize( 122 | &self, 123 | config: Self::Config, 124 | mut layouter: impl Layouter, 125 | ) -> Result<(), Error> { 126 | layouter.assign_region( 127 | || "", 128 | |mut region| { 129 | region.assign_advice(|| "", config.a, 0, || Value::known(self.0))?; 130 | region.assign_fixed(|| "", config.q_a, 0, || Value::known(-Fr::ONE))?; 131 | 132 | region.assign_advice(|| "", config.a, 1, || Value::known(-Fr::from(5)))?; 133 | for (idx, column) in (1..).zip([ 134 | config.q_a, 135 | config.q_b, 136 | config.q_c, 137 | config.q_ab, 138 | config.constant, 139 | ]) { 140 | region.assign_fixed(|| "", column, 1, || Value::known(Fr::from(idx)))?; 141 | } 142 | 143 | let a = region.assign_advice(|| "", config.a, 2, || Value::known(Fr::ONE))?; 144 | a.copy_advice(|| "", &mut region, config.b, 3)?; 145 | a.copy_advice(|| "", &mut region, config.c, 4)?; 146 | 147 | Ok(()) 148 | }, 149 | ) 150 | } 151 | } 152 | 153 | fn gen_srs(k: u32) -> ParamsKZG { 154 | ParamsKZG::::setup(k, OsRng) 155 | } 156 | 157 | fn gen_pk>(params: &ParamsKZG, circuit: &C) -> ProvingKey { 158 | let vk = keygen_vk(params, circuit).unwrap(); 159 | keygen_pk(params, vk, circuit).unwrap() 160 | } 161 | 162 | fn gen_proof>( 163 | params: &ParamsKZG, 164 | pk: &ProvingKey, 165 | circuit: C, 166 | instances: Vec>, 167 | ) -> Vec { 168 | MockProver::run(params.k(), &circuit, instances.clone()) 169 | .unwrap() 170 | .assert_satisfied(); 171 | 172 | let instances = instances 173 | .iter() 174 | .map(|instances| instances.as_slice()) 175 | .collect_vec(); 176 | let proof = { 177 | let mut transcript = TranscriptWriterBuffer::<_, G1Affine, _>::init(Vec::new()); 178 | create_proof::, ProverGWC<_>, _, _, EvmTranscript<_, _, _, _>, _>( 179 | params, 180 | pk, 181 | &[circuit], 182 | &[instances.as_slice()], 183 | OsRng, 184 | &mut transcript, 185 | ) 186 | .unwrap(); 187 | transcript.finalize() 188 | }; 189 | 190 | let accept = { 191 | let mut transcript = TranscriptReadBuffer::<_, G1Affine, _>::init(proof.as_slice()); 192 | VerificationStrategy::<_, VerifierGWC<_>>::finalize( 193 | verify_proof::<_, VerifierGWC<_>, _, EvmTranscript<_, _, _, _>, _>( 194 | params.verifier_params(), 195 | pk.get_vk(), 196 | AccumulatorStrategy::new(params.verifier_params()), 197 | &[instances.as_slice()], 198 | &mut transcript, 199 | ) 200 | .unwrap(), 201 | ) 202 | }; 203 | assert!(accept); 204 | 205 | proof 206 | } 207 | 208 | fn gen_evm_verifier( 209 | params: &ParamsKZG, 210 | vk: &VerifyingKey, 211 | num_instance: Vec, 212 | ) -> Vec { 213 | let protocol = compile( 214 | params, 215 | vk, 216 | Config::kzg().with_num_instance(num_instance.clone()), 217 | ); 218 | let vk = (params.get_g()[0], params.g2(), params.s_g2()).into(); 219 | 220 | let loader = EvmLoader::new::(); 221 | let protocol = protocol.loaded(&loader); 222 | let mut transcript = EvmTranscript::<_, Rc, _, _>::new(&loader); 223 | 224 | let instances = transcript.load_instances(num_instance); 225 | let proof = PlonkVerifier::read_proof(&vk, &protocol, &instances, &mut transcript).unwrap(); 226 | PlonkVerifier::verify(&vk, &protocol, &instances, &proof).unwrap(); 227 | 228 | evm::compile_solidity(&loader.solidity_code()) 229 | } 230 | 231 | fn evm_verify(deployment_code: Vec, instances: Vec>, proof: Vec) { 232 | let calldata = encode_calldata(&instances, &proof); 233 | let gas_cost = deploy_and_call(deployment_code, calldata).unwrap(); 234 | dbg!(gas_cost); 235 | } 236 | 237 | fn main() { 238 | let params = gen_srs(8); 239 | 240 | let circuit = StandardPlonk::rand(OsRng); 241 | let pk = gen_pk(¶ms, &circuit); 242 | let deployment_code = gen_evm_verifier(¶ms, pk.get_vk(), StandardPlonk::num_instance()); 243 | 244 | let proof = gen_proof(¶ms, &pk, circuit.clone(), circuit.instances()); 245 | evm_verify(deployment_code, circuit.instances(), proof); 246 | } 247 | -------------------------------------------------------------------------------- /snark-verifier/src/loader.rs: -------------------------------------------------------------------------------- 1 | //! Abstraction of field element and elliptic curve point for generic verifier 2 | //! implementation. 3 | 4 | use crate::{ 5 | util::{ 6 | arithmetic::{CurveAffine, FieldOps, PrimeField}, 7 | Itertools, 8 | }, 9 | Error, 10 | }; 11 | use std::{borrow::Cow, fmt::Debug, iter, ops::Deref}; 12 | 13 | pub mod native; 14 | 15 | #[cfg(feature = "loader_evm")] 16 | pub mod evm; 17 | 18 | #[cfg(feature = "loader_halo2")] 19 | pub mod halo2; 20 | 21 | /// Loaded elliptic curve point. 22 | pub trait LoadedEcPoint: Clone + Debug + PartialEq { 23 | /// [`Loader`]. 24 | type Loader: Loader; 25 | 26 | /// Returns [`Loader`]. 27 | fn loader(&self) -> &Self::Loader; 28 | } 29 | 30 | /// Loaded field element. 31 | pub trait LoadedScalar: Clone + Debug + PartialEq + FieldOps { 32 | /// [`Loader`]. 33 | type Loader: ScalarLoader; 34 | 35 | /// Returns [`Loader`]. 36 | fn loader(&self) -> &Self::Loader; 37 | 38 | /// Returns square. 39 | fn square(&self) -> Self { 40 | self.clone() * self 41 | } 42 | 43 | /// Returns inverse if any. 44 | fn invert(&self) -> Option { 45 | FieldOps::invert(self) 46 | } 47 | 48 | /// Returns power to exponent. 49 | fn pow_const(&self, mut exp: u64) -> Self { 50 | assert!(exp > 0); 51 | 52 | let mut base = self.clone(); 53 | 54 | while exp & 1 == 0 { 55 | base = base.square(); 56 | exp >>= 1; 57 | } 58 | 59 | let mut acc = base.clone(); 60 | while exp > 1 { 61 | exp >>= 1; 62 | base = base.square(); 63 | if exp & 1 == 1 { 64 | acc *= &base; 65 | } 66 | } 67 | acc 68 | } 69 | 70 | /// Returns powers up to exponent `n-1`. 71 | fn powers(&self, n: usize) -> Vec { 72 | iter::once(self.loader().load_one()) 73 | .chain( 74 | iter::successors(Some(self.clone()), |power| Some(power.clone() * self)) 75 | .take(n - 1), 76 | ) 77 | .collect_vec() 78 | } 79 | } 80 | 81 | /// Elliptic curve point loader. 82 | pub trait EcPointLoader { 83 | /// [`LoadedEcPoint`]. 84 | type LoadedEcPoint: LoadedEcPoint; 85 | 86 | /// Load a constant elliptic curve point. 87 | fn ec_point_load_const(&self, value: &C) -> Self::LoadedEcPoint; 88 | 89 | /// Load `identity` as constant. 90 | fn ec_point_load_zero(&self) -> Self::LoadedEcPoint { 91 | self.ec_point_load_const(&C::identity()) 92 | } 93 | 94 | /// Load `generator` as constant. 95 | fn ec_point_load_one(&self) -> Self::LoadedEcPoint { 96 | self.ec_point_load_const(&C::generator()) 97 | } 98 | 99 | /// Assert lhs and rhs elliptic curve points are equal. 100 | fn ec_point_assert_eq( 101 | &self, 102 | annotation: &str, 103 | lhs: &Self::LoadedEcPoint, 104 | rhs: &Self::LoadedEcPoint, 105 | ) -> Result<(), Error>; 106 | 107 | /// Perform multi-scalar multiplication. 108 | fn multi_scalar_multiplication( 109 | pairs: &[(&Self::LoadedScalar, &Self::LoadedEcPoint)], 110 | ) -> Self::LoadedEcPoint 111 | where 112 | Self: ScalarLoader; 113 | } 114 | 115 | /// Field element loader. 116 | pub trait ScalarLoader { 117 | /// [`LoadedScalar`]. 118 | type LoadedScalar: LoadedScalar; 119 | 120 | /// Load a constant field element. 121 | fn load_const(&self, value: &F) -> Self::LoadedScalar; 122 | 123 | /// Load `zero` as constant. 124 | fn load_zero(&self) -> Self::LoadedScalar { 125 | self.load_const(&F::ZERO) 126 | } 127 | 128 | /// Load `one` as constant. 129 | fn load_one(&self) -> Self::LoadedScalar { 130 | self.load_const(&F::ONE) 131 | } 132 | 133 | /// Assert lhs and rhs field elements are equal. 134 | fn assert_eq( 135 | &self, 136 | annotation: &str, 137 | lhs: &Self::LoadedScalar, 138 | rhs: &Self::LoadedScalar, 139 | ) -> Result<(), Error>; 140 | 141 | /// Sum field elements with coefficients and constant. 142 | fn sum_with_coeff_and_const( 143 | &self, 144 | values: &[(F, &Self::LoadedScalar)], 145 | constant: F, 146 | ) -> Self::LoadedScalar { 147 | if values.is_empty() { 148 | return self.load_const(&constant); 149 | } 150 | 151 | let loader = values.first().unwrap().1.loader(); 152 | iter::empty() 153 | .chain(if constant == F::ZERO { 154 | None 155 | } else { 156 | Some(Cow::Owned(loader.load_const(&constant))) 157 | }) 158 | .chain(values.iter().map(|&(coeff, value)| { 159 | if coeff == F::ONE { 160 | Cow::Borrowed(value) 161 | } else { 162 | Cow::Owned(loader.load_const(&coeff) * value) 163 | } 164 | })) 165 | .reduce(|acc, term| Cow::Owned(acc.into_owned() + term.deref())) 166 | .unwrap() 167 | .into_owned() 168 | } 169 | 170 | /// Sum product of field elements with coefficients and constant. 171 | fn sum_products_with_coeff_and_const( 172 | &self, 173 | values: &[(F, &Self::LoadedScalar, &Self::LoadedScalar)], 174 | constant: F, 175 | ) -> Self::LoadedScalar { 176 | if values.is_empty() { 177 | return self.load_const(&constant); 178 | } 179 | 180 | let loader = values.first().unwrap().1.loader(); 181 | iter::empty() 182 | .chain(if constant == F::ZERO { 183 | None 184 | } else { 185 | Some(loader.load_const(&constant)) 186 | }) 187 | .chain(values.iter().map(|&(coeff, lhs, rhs)| { 188 | if coeff == F::ONE { 189 | lhs.clone() * rhs 190 | } else { 191 | loader.load_const(&coeff) * lhs * rhs 192 | } 193 | })) 194 | .reduce(|acc, term| acc + term) 195 | .unwrap() 196 | } 197 | 198 | /// Sum field elements with coefficients. 199 | fn sum_with_coeff(&self, values: &[(F, &Self::LoadedScalar)]) -> Self::LoadedScalar { 200 | self.sum_with_coeff_and_const(values, F::ZERO) 201 | } 202 | 203 | /// Sum field elements and constant. 204 | fn sum_with_const(&self, values: &[&Self::LoadedScalar], constant: F) -> Self::LoadedScalar { 205 | self.sum_with_coeff_and_const( 206 | &values.iter().map(|&value| (F::ONE, value)).collect_vec(), 207 | constant, 208 | ) 209 | } 210 | 211 | /// Sum field elements. 212 | fn sum(&self, values: &[&Self::LoadedScalar]) -> Self::LoadedScalar { 213 | self.sum_with_const(values, F::ZERO) 214 | } 215 | 216 | /// Sum product of field elements with coefficients. 217 | fn sum_products_with_coeff( 218 | &self, 219 | values: &[(F, &Self::LoadedScalar, &Self::LoadedScalar)], 220 | ) -> Self::LoadedScalar { 221 | self.sum_products_with_coeff_and_const(values, F::ZERO) 222 | } 223 | 224 | /// Sum product of field elements and constant. 225 | fn sum_products_with_const( 226 | &self, 227 | values: &[(&Self::LoadedScalar, &Self::LoadedScalar)], 228 | constant: F, 229 | ) -> Self::LoadedScalar { 230 | self.sum_products_with_coeff_and_const( 231 | &values 232 | .iter() 233 | .map(|&(lhs, rhs)| (F::ONE, lhs, rhs)) 234 | .collect_vec(), 235 | constant, 236 | ) 237 | } 238 | 239 | /// Sum product of field elements. 240 | fn sum_products( 241 | &self, 242 | values: &[(&Self::LoadedScalar, &Self::LoadedScalar)], 243 | ) -> Self::LoadedScalar { 244 | self.sum_products_with_const(values, F::ZERO) 245 | } 246 | 247 | /// Product of field elements. 248 | fn product(&self, values: &[&Self::LoadedScalar]) -> Self::LoadedScalar { 249 | values 250 | .iter() 251 | .fold(self.load_one(), |acc, value| acc * *value) 252 | } 253 | 254 | /// Batch invert field elements. 255 | fn batch_invert<'a>(values: impl IntoIterator) 256 | where 257 | Self::LoadedScalar: 'a, 258 | { 259 | values 260 | .into_iter() 261 | .for_each(|value| *value = LoadedScalar::invert(value).unwrap_or_else(|| value.clone())) 262 | } 263 | } 264 | 265 | /// [`EcPointLoader`] and [`ScalarLoader`] with some helper methods. 266 | pub trait Loader: 267 | EcPointLoader + ScalarLoader + Clone + Debug 268 | { 269 | /// Start cost metering with an `identifier`. 270 | fn start_cost_metering(&self, _identifier: &str) {} 271 | 272 | /// End latest started cost metering. 273 | fn end_cost_metering(&self) {} 274 | } 275 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/kzg/accumulator.rs: -------------------------------------------------------------------------------- 1 | use crate::{loader::Loader, util::arithmetic::CurveAffine}; 2 | use std::fmt::Debug; 3 | 4 | /// KZG accumulator, containing lhs G1 and rhs G1 of pairing. 5 | #[derive(Clone, Debug)] 6 | pub struct KzgAccumulator 7 | where 8 | C: CurveAffine, 9 | L: Loader, 10 | { 11 | /// Left-hand side G1 of pairing. 12 | pub lhs: L::LoadedEcPoint, 13 | /// Right-hand side G1 of pairing. 14 | pub rhs: L::LoadedEcPoint, 15 | } 16 | 17 | impl KzgAccumulator 18 | where 19 | C: CurveAffine, 20 | L: Loader, 21 | { 22 | /// Initialize a [`KzgAccumulator`]. 23 | pub fn new(lhs: L::LoadedEcPoint, rhs: L::LoadedEcPoint) -> Self { 24 | Self { lhs, rhs } 25 | } 26 | } 27 | 28 | /// `AccumulatorEncoding` that encodes `Accumulator` into limbs. 29 | /// 30 | /// Since in circuit everything are in scalar field, but `Accumulator` might contain base field elements, so we split them into limbs. 31 | /// The const generic `LIMBS` and `BITS` respectively represents how many limbs 32 | /// a base field element are split into and how many bits each limbs could have. 33 | #[derive(Clone, Debug)] 34 | pub struct LimbsEncoding; 35 | 36 | mod native { 37 | use crate::{ 38 | loader::native::NativeLoader, 39 | pcs::{ 40 | kzg::{KzgAccumulator, LimbsEncoding}, 41 | AccumulatorEncoding, 42 | }, 43 | util::{ 44 | arithmetic::{fe_from_limbs, CurveAffine}, 45 | Itertools, 46 | }, 47 | Error, 48 | }; 49 | 50 | impl AccumulatorEncoding 51 | for LimbsEncoding 52 | where 53 | C: CurveAffine, 54 | { 55 | type Accumulator = KzgAccumulator; 56 | 57 | fn from_repr(limbs: &[&C::Scalar]) -> Result { 58 | assert_eq!(limbs.len(), 4 * LIMBS); 59 | 60 | let [lhs_x, lhs_y, rhs_x, rhs_y]: [_; 4] = limbs 61 | .chunks(LIMBS) 62 | .map(|limbs| { 63 | fe_from_limbs::<_, _, LIMBS, BITS>( 64 | limbs 65 | .iter() 66 | .map(|limb| **limb) 67 | .collect_vec() 68 | .try_into() 69 | .unwrap(), 70 | ) 71 | }) 72 | .collect_vec() 73 | .try_into() 74 | .unwrap(); 75 | let accumulator = KzgAccumulator::new( 76 | C::from_xy(lhs_x, lhs_y).unwrap(), 77 | C::from_xy(rhs_x, rhs_y).unwrap(), 78 | ); 79 | 80 | Ok(accumulator) 81 | } 82 | } 83 | } 84 | 85 | #[cfg(feature = "loader_evm")] 86 | mod evm { 87 | use crate::{ 88 | loader::evm::{EvmLoader, Scalar}, 89 | pcs::{ 90 | kzg::{KzgAccumulator, LimbsEncoding}, 91 | AccumulatorEncoding, 92 | }, 93 | util::{ 94 | arithmetic::{CurveAffine, PrimeField}, 95 | Itertools, 96 | }, 97 | Error, 98 | }; 99 | use std::rc::Rc; 100 | 101 | impl AccumulatorEncoding> 102 | for LimbsEncoding 103 | where 104 | C: CurveAffine, 105 | C::Scalar: PrimeField, 106 | { 107 | type Accumulator = KzgAccumulator>; 108 | 109 | fn from_repr(limbs: &[&Scalar]) -> Result { 110 | assert_eq!(limbs.len(), 4 * LIMBS); 111 | 112 | let loader = limbs[0].loader(); 113 | 114 | let [lhs_x, lhs_y, rhs_x, rhs_y]: [[_; LIMBS]; 4] = limbs 115 | .chunks(LIMBS) 116 | .map(|limbs| limbs.to_vec().try_into().unwrap()) 117 | .collect_vec() 118 | .try_into() 119 | .unwrap(); 120 | let accumulator = KzgAccumulator::new( 121 | loader.ec_point_from_limbs::(lhs_x, lhs_y), 122 | loader.ec_point_from_limbs::(rhs_x, rhs_y), 123 | ); 124 | 125 | Ok(accumulator) 126 | } 127 | } 128 | } 129 | 130 | #[cfg(feature = "loader_halo2")] 131 | pub use halo2::LimbsEncodingInstructions; 132 | 133 | #[cfg(feature = "loader_halo2")] 134 | mod halo2 { 135 | use crate::{ 136 | loader::halo2::{EccInstructions, Halo2Loader, Scalar, Valuetools}, 137 | pcs::{ 138 | kzg::{KzgAccumulator, LimbsEncoding}, 139 | AccumulatorEncoding, 140 | }, 141 | util::{ 142 | arithmetic::{fe_from_limbs, CurveAffine}, 143 | Itertools, 144 | }, 145 | Error, 146 | }; 147 | use halo2_proofs::{circuit::Value, plonk}; 148 | use std::{iter, ops::Deref, rc::Rc}; 149 | 150 | fn ec_point_from_limbs( 151 | limbs: &[Value<&C::Scalar>], 152 | ) -> Value { 153 | assert_eq!(limbs.len(), 2 * LIMBS); 154 | 155 | let [x, y] = [&limbs[..LIMBS], &limbs[LIMBS..]].map(|limbs| { 156 | limbs 157 | .iter() 158 | .cloned() 159 | .fold_zipped(Vec::new(), |mut acc, limb| { 160 | acc.push(*limb); 161 | acc 162 | }) 163 | .map(|limbs| fe_from_limbs::<_, _, LIMBS, BITS>(limbs.try_into().unwrap())) 164 | }); 165 | 166 | x.zip(y).map(|(x, y)| C::from_xy(x, y).unwrap()) 167 | } 168 | 169 | /// Instructions to encode/decode a elliptic curve point into/from limbs. 170 | pub trait LimbsEncodingInstructions<'a, C: CurveAffine, const LIMBS: usize, const BITS: usize>: 171 | EccInstructions<'a, C> 172 | { 173 | /// Decode and assign an elliptic curve point from limbs. 174 | fn assign_ec_point_from_limbs( 175 | &self, 176 | ctx: &mut Self::Context, 177 | limbs: &[impl Deref], 178 | ) -> Result; 179 | 180 | /// Encode an elliptic curve point into limbs. 181 | fn assign_ec_point_to_limbs( 182 | &self, 183 | ctx: &mut Self::Context, 184 | ec_point: impl Deref, 185 | ) -> Result, plonk::Error>; 186 | } 187 | 188 | impl<'a, C, EccChip, const LIMBS: usize, const BITS: usize> 189 | AccumulatorEncoding>> for LimbsEncoding 190 | where 191 | C: CurveAffine, 192 | EccChip: LimbsEncodingInstructions<'a, C, LIMBS, BITS>, 193 | { 194 | type Accumulator = KzgAccumulator>>; 195 | 196 | fn from_repr(limbs: &[&Scalar<'a, C, EccChip>]) -> Result { 197 | assert_eq!(limbs.len(), 4 * LIMBS); 198 | 199 | let loader = limbs[0].loader(); 200 | 201 | let [lhs, rhs] = [&limbs[..2 * LIMBS], &limbs[2 * LIMBS..]].map(|limbs| { 202 | let assigned = loader 203 | .ecc_chip() 204 | .assign_ec_point_from_limbs( 205 | &mut loader.ctx_mut(), 206 | &limbs.iter().map(|limb| limb.assigned()).collect_vec(), 207 | ) 208 | .unwrap(); 209 | loader.ec_point_from_assigned(assigned) 210 | }); 211 | 212 | Ok(KzgAccumulator::new(lhs, rhs)) 213 | } 214 | } 215 | 216 | mod halo2_wrong { 217 | use super::*; 218 | use halo2_wrong_ecc::BaseFieldEccChip; 219 | 220 | impl<'a, C: CurveAffine, const LIMBS: usize, const BITS: usize> 221 | LimbsEncodingInstructions<'a, C, LIMBS, BITS> for BaseFieldEccChip 222 | { 223 | fn assign_ec_point_from_limbs( 224 | &self, 225 | ctx: &mut Self::Context, 226 | limbs: &[impl Deref], 227 | ) -> Result { 228 | assert_eq!(limbs.len(), 2 * LIMBS); 229 | 230 | let ec_point = self.assign_point( 231 | ctx, 232 | ec_point_from_limbs::<_, LIMBS, BITS>( 233 | &limbs.iter().map(|limb| limb.value()).collect_vec(), 234 | ), 235 | )?; 236 | 237 | for (src, dst) in limbs.iter().zip_eq( 238 | iter::empty() 239 | .chain(ec_point.x().limbs()) 240 | .chain(ec_point.y().limbs()), 241 | ) { 242 | ctx.constrain_equal(src.cell(), dst.as_ref().cell())?; 243 | } 244 | 245 | Ok(ec_point) 246 | } 247 | 248 | fn assign_ec_point_to_limbs( 249 | &self, 250 | _: &mut Self::Context, 251 | ec_point: impl Deref, 252 | ) -> Result, plonk::Error> { 253 | Ok(iter::empty() 254 | .chain(ec_point.x().limbs()) 255 | .chain(ec_point.y().limbs()) 256 | .map(|limb| limb.as_ref()) 257 | .cloned() 258 | .collect()) 259 | } 260 | } 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /snark-verifier/src/util/arithmetic.rs: -------------------------------------------------------------------------------- 1 | //! Arithmetic related re-exported traits and utilities. 2 | 3 | use crate::util::Itertools; 4 | use num_bigint::BigUint; 5 | use num_traits::One; 6 | use std::{ 7 | cmp::Ordering, 8 | fmt::Debug, 9 | iter, mem, 10 | ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, 11 | }; 12 | 13 | pub use halo2_curves::{ 14 | ff::{BatchInvert, Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}, 15 | group::{prime::PrimeCurveAffine, Curve, Group, GroupEncoding}, 16 | pairing::MillerLoopResult, 17 | Coordinates, CurveAffine, CurveExt, 18 | }; 19 | 20 | /// [`halo2_curves::pairing::MultiMillerLoop`] with [`std::fmt::Debug`]. 21 | pub trait MultiMillerLoop: halo2_curves::pairing::MultiMillerLoop + Debug {} 22 | 23 | impl MultiMillerLoop for M {} 24 | 25 | /// Operations that could be done with field elements. 26 | pub trait FieldOps: 27 | Sized 28 | + Neg 29 | + Add 30 | + Sub 31 | + Mul 32 | + for<'a> Add<&'a Self, Output = Self> 33 | + for<'a> Sub<&'a Self, Output = Self> 34 | + for<'a> Mul<&'a Self, Output = Self> 35 | + AddAssign 36 | + SubAssign 37 | + MulAssign 38 | + for<'a> AddAssign<&'a Self> 39 | + for<'a> SubAssign<&'a Self> 40 | + for<'a> MulAssign<&'a Self> 41 | { 42 | /// Returns multiplicative inversion if any. 43 | fn invert(&self) -> Option; 44 | } 45 | 46 | /// Batch invert [`PrimeField`] elements and multiply all with given coefficient. 47 | pub fn batch_invert_and_mul(values: &mut [F], coeff: &F) { 48 | let products = values 49 | .iter() 50 | .filter(|value| !value.is_zero_vartime()) 51 | .scan(F::ONE, |acc, value| { 52 | *acc *= value; 53 | Some(*acc) 54 | }) 55 | .collect_vec(); 56 | 57 | let mut all_product_inv = products.last().unwrap().invert().unwrap() * coeff; 58 | 59 | for (value, product) in values 60 | .iter_mut() 61 | .rev() 62 | .filter(|value| !value.is_zero_vartime()) 63 | .zip(products.into_iter().rev().skip(1).chain(Some(F::ONE))) 64 | { 65 | let mut inv = all_product_inv * product; 66 | mem::swap(value, &mut inv); 67 | all_product_inv *= inv; 68 | } 69 | } 70 | 71 | /// Batch invert [`PrimeField`] elements. 72 | pub fn batch_invert(values: &mut [F]) { 73 | batch_invert_and_mul(values, &F::ONE) 74 | } 75 | 76 | /// Root of unity of 2^k-sized multiplicative subgroup of [`PrimeField`] by 77 | /// repeatedly squaring the root of unity of the largest multiplicative 78 | /// subgroup. 79 | /// 80 | /// # Panic 81 | /// 82 | /// If given `k` is greater than [`PrimeField::S`]. 83 | pub fn root_of_unity(k: usize) -> F { 84 | assert!(k <= F::S as usize); 85 | 86 | iter::successors(Some(F::ROOT_OF_UNITY), |acc| Some(acc.square())) 87 | .take(F::S as usize - k + 1) 88 | .last() 89 | .unwrap() 90 | } 91 | 92 | /// Rotation on a group. 93 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 94 | #[cfg_attr(feature = "derive_serde", derive(serde::Serialize, serde::Deserialize))] 95 | pub struct Rotation(pub i32); 96 | 97 | impl Rotation { 98 | /// No rotation 99 | pub fn cur() -> Self { 100 | Rotation(0) 101 | } 102 | 103 | /// To previous element 104 | pub fn prev() -> Self { 105 | Rotation(-1) 106 | } 107 | 108 | /// To next element 109 | pub fn next() -> Self { 110 | Rotation(1) 111 | } 112 | } 113 | 114 | impl From for Rotation { 115 | fn from(rotation: i32) -> Self { 116 | Self(rotation) 117 | } 118 | } 119 | 120 | /// 2-adicity multiplicative domain 121 | #[derive(Clone, Debug)] 122 | #[cfg_attr(feature = "derive_serde", derive(serde::Serialize, serde::Deserialize))] 123 | pub struct Domain { 124 | /// Log size of the domain. 125 | pub k: usize, 126 | /// Size of the domain. 127 | pub n: usize, 128 | /// Inverse of `n`. 129 | pub n_inv: F, 130 | /// Generator of the domain. 131 | pub gen: F, 132 | /// Inverse of `gen`. 133 | pub gen_inv: F, 134 | } 135 | 136 | impl Domain { 137 | /// Initialize a domain with specified generator. 138 | pub fn new(k: usize, gen: F) -> Self { 139 | let n = 1 << k; 140 | let n_inv = F::from(n as u64).invert().unwrap(); 141 | let gen_inv = gen.invert().unwrap(); 142 | 143 | Self { 144 | k, 145 | n, 146 | n_inv, 147 | gen, 148 | gen_inv, 149 | } 150 | } 151 | 152 | /// Rotate an element to given `rotation`. 153 | pub fn rotate_scalar(&self, scalar: F, rotation: Rotation) -> F { 154 | match rotation.0.cmp(&0) { 155 | Ordering::Equal => scalar, 156 | Ordering::Greater => scalar * self.gen.pow_vartime([rotation.0 as u64]), 157 | Ordering::Less => scalar * self.gen_inv.pow_vartime([(-rotation.0) as u64]), 158 | } 159 | } 160 | } 161 | 162 | /// Contains numerator and denominator for deferred evaluation. 163 | #[derive(Clone, Debug)] 164 | pub struct Fraction { 165 | numer: Option, 166 | denom: T, 167 | eval: Option, 168 | inv: bool, 169 | } 170 | 171 | impl Fraction { 172 | /// Initialize an unevaluated fraction. 173 | pub fn new(numer: T, denom: T) -> Self { 174 | Self { 175 | numer: Some(numer), 176 | denom, 177 | eval: None, 178 | inv: false, 179 | } 180 | } 181 | 182 | /// Initialize an unevaluated fraction without numerator. 183 | pub fn one_over(denom: T) -> Self { 184 | Self { 185 | numer: None, 186 | denom, 187 | eval: None, 188 | inv: false, 189 | } 190 | } 191 | 192 | /// Returns denominator. 193 | pub fn denom(&self) -> Option<&T> { 194 | if !self.inv { 195 | Some(&self.denom) 196 | } else { 197 | None 198 | } 199 | } 200 | 201 | #[must_use = "To be inverted"] 202 | /// Returns mutable denominator for doing inversion. 203 | pub fn denom_mut(&mut self) -> Option<&mut T> { 204 | if !self.inv { 205 | self.inv = true; 206 | Some(&mut self.denom) 207 | } else { 208 | None 209 | } 210 | } 211 | } 212 | 213 | impl Fraction { 214 | /// Evaluate the fraction and cache the result. 215 | /// 216 | /// # Panic 217 | /// 218 | /// If `denom_mut` is not called before. 219 | pub fn evaluate(&mut self) { 220 | assert!(self.inv); 221 | 222 | if self.eval.is_none() { 223 | self.eval = Some( 224 | self.numer 225 | .take() 226 | .map(|numer| numer * &self.denom) 227 | .unwrap_or_else(|| self.denom.clone()), 228 | ); 229 | } 230 | } 231 | 232 | /// Returns cached fraction evaluation. 233 | /// 234 | /// # Panic 235 | /// 236 | /// If `evaluate` is not called before. 237 | pub fn evaluated(&self) -> &T { 238 | assert!(self.eval.is_some()); 239 | 240 | self.eval.as_ref().unwrap() 241 | } 242 | } 243 | 244 | /// Modulus of a [`PrimeField`] 245 | pub fn modulus() -> BigUint { 246 | fe_to_big(-F::ONE) + 1usize 247 | } 248 | 249 | /// Convert a [`BigUint`] into a [`PrimeField`] . 250 | pub fn fe_from_big(big: BigUint) -> F { 251 | let bytes = big.to_bytes_le(); 252 | let mut repr = F::Repr::default(); 253 | assert!(bytes.len() <= repr.as_ref().len()); 254 | repr.as_mut()[..bytes.len()].clone_from_slice(bytes.as_slice()); 255 | F::from_repr(repr).unwrap() 256 | } 257 | 258 | /// Convert a [`PrimeField`] into a [`BigUint`]. 259 | pub fn fe_to_big(fe: F) -> BigUint { 260 | BigUint::from_bytes_le(fe.to_repr().as_ref()) 261 | } 262 | 263 | /// Convert a [`PrimeField`] into another [`PrimeField`]. 264 | pub fn fe_to_fe(fe: F1) -> F2 { 265 | fe_from_big(fe_to_big(fe) % modulus::()) 266 | } 267 | 268 | /// Convert `LIMBS` limbs into a [`PrimeField`], assuming each limb contains at 269 | /// most `BITS`. 270 | pub fn fe_from_limbs( 271 | limbs: [F1; LIMBS], 272 | ) -> F2 { 273 | fe_from_big( 274 | limbs 275 | .iter() 276 | .map(|limb| BigUint::from_bytes_le(limb.to_repr().as_ref())) 277 | .zip((0usize..).step_by(BITS)) 278 | .map(|(limb, shift)| limb << shift) 279 | .reduce(|acc, shifted| acc + shifted) 280 | .unwrap(), 281 | ) 282 | } 283 | 284 | /// Convert a [`PrimeField`] into `LIMBS` limbs where each limb contains at 285 | /// most `BITS`. 286 | pub fn fe_to_limbs( 287 | fe: F1, 288 | ) -> [F2; LIMBS] { 289 | let big = BigUint::from_bytes_le(fe.to_repr().as_ref()); 290 | let mask = &((BigUint::one() << BITS) - 1usize); 291 | (0usize..) 292 | .step_by(BITS) 293 | .take(LIMBS) 294 | .map(|shift| fe_from_big((&big >> shift) & mask)) 295 | .collect_vec() 296 | .try_into() 297 | .unwrap() 298 | } 299 | 300 | /// Returns iterator that yields scalar^0, scalar^1, scalar^2... 301 | pub fn powers(scalar: F) -> impl Iterator { 302 | iter::successors(Some(F::ONE), move |power| Some(scalar * power)) 303 | } 304 | 305 | /// Compute inner product of 2 slice of [`Field`]. 306 | pub fn inner_product(lhs: &[F], rhs: &[F]) -> F { 307 | lhs.iter() 308 | .zip_eq(rhs.iter()) 309 | .map(|(lhs, rhs)| *lhs * rhs) 310 | .reduce(|acc, product| acc + product) 311 | .unwrap_or_default() 312 | } 313 | -------------------------------------------------------------------------------- /snark-verifier/src/pcs/ipa/accumulation.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | loader::{native::NativeLoader, LoadedScalar, Loader}, 3 | pcs::{ 4 | ipa::{ 5 | h_coeffs, h_eval, Ipa, IpaAccumulator, IpaProof, IpaProvingKey, IpaSuccinctVerifyingKey, 6 | }, 7 | AccumulationScheme, AccumulationSchemeProver, 8 | }, 9 | util::{ 10 | arithmetic::{Curve, CurveAffine, Field}, 11 | msm::Msm, 12 | poly::Polynomial, 13 | transcript::{TranscriptRead, TranscriptWrite}, 14 | Itertools, 15 | }, 16 | Error, 17 | }; 18 | use rand::Rng; 19 | use std::{array, fmt::Debug, iter, marker::PhantomData}; 20 | 21 | /// Inner product argument accumulation scheme. The second generic `MOS` stands 22 | /// for different kind of multi-open scheme. 23 | #[derive(Clone, Debug)] 24 | pub struct IpaAs(PhantomData<(C, MOS)>); 25 | 26 | impl AccumulationScheme for IpaAs 27 | where 28 | C: CurveAffine, 29 | L: Loader, 30 | MOS: Clone + Debug, 31 | { 32 | type Accumulator = IpaAccumulator; 33 | type VerifyingKey = IpaSuccinctVerifyingKey; 34 | type Proof = IpaAsProof; 35 | 36 | fn read_proof( 37 | vk: &Self::VerifyingKey, 38 | instances: &[Self::Accumulator], 39 | transcript: &mut T, 40 | ) -> Result 41 | where 42 | T: TranscriptRead, 43 | { 44 | IpaAsProof::read(vk, instances, transcript) 45 | } 46 | 47 | fn verify( 48 | vk: &Self::VerifyingKey, 49 | instances: &[Self::Accumulator], 50 | proof: &Self::Proof, 51 | ) -> Result { 52 | let loader = proof.z.loader(); 53 | let s = vk.s.as_ref().map(|s| loader.ec_point_load_const(s)); 54 | 55 | let (u, h) = instances 56 | .iter() 57 | .map(|IpaAccumulator { u, xi }| (u.clone(), h_eval(xi, &proof.z))) 58 | .chain( 59 | proof 60 | .a_b_u 61 | .as_ref() 62 | .map(|(a, b, u)| (u.clone(), a.clone() * &proof.z + b)), 63 | ) 64 | .unzip::<_, _, Vec<_>, Vec<_>>(); 65 | let powers_of_alpha = proof.alpha.powers(u.len()); 66 | 67 | let mut c = powers_of_alpha 68 | .iter() 69 | .zip(u.iter()) 70 | .map(|(power_of_alpha, u)| Msm::::base(u) * power_of_alpha) 71 | .sum::>(); 72 | if let Some(omega) = proof.omega.as_ref() { 73 | c += Msm::base(s.as_ref().unwrap()) * omega; 74 | } 75 | let v = loader.sum_products(&powers_of_alpha.iter().zip(h.iter()).collect_vec()); 76 | 77 | Ipa::succinct_verify(vk, &c, &proof.z, &v, &proof.ipa) 78 | } 79 | } 80 | 81 | /// Inner product argument accumulation scheme proof. 82 | #[derive(Clone, Debug)] 83 | pub struct IpaAsProof 84 | where 85 | C: CurveAffine, 86 | L: Loader, 87 | { 88 | a_b_u: Option<(L::LoadedScalar, L::LoadedScalar, L::LoadedEcPoint)>, 89 | omega: Option, 90 | alpha: L::LoadedScalar, 91 | z: L::LoadedScalar, 92 | ipa: IpaProof, 93 | } 94 | 95 | impl IpaAsProof 96 | where 97 | C: CurveAffine, 98 | L: Loader, 99 | { 100 | fn read( 101 | vk: &IpaSuccinctVerifyingKey, 102 | instances: &[IpaAccumulator], 103 | transcript: &mut T, 104 | ) -> Result 105 | where 106 | T: TranscriptRead, 107 | { 108 | assert!(instances.len() > 1); 109 | 110 | let a_b_u = vk 111 | .zk() 112 | .then(|| { 113 | let a = transcript.read_scalar()?; 114 | let b = transcript.read_scalar()?; 115 | let u = transcript.read_ec_point()?; 116 | Ok((a, b, u)) 117 | }) 118 | .transpose()?; 119 | let omega = vk 120 | .zk() 121 | .then(|| { 122 | let omega = transcript.read_scalar()?; 123 | Ok(omega) 124 | }) 125 | .transpose()?; 126 | 127 | for accumulator in instances { 128 | for xi in accumulator.xi.iter() { 129 | transcript.common_scalar(xi)?; 130 | } 131 | transcript.common_ec_point(&accumulator.u)?; 132 | } 133 | 134 | let alpha = transcript.squeeze_challenge(); 135 | let z = transcript.squeeze_challenge(); 136 | 137 | let ipa = IpaProof::read(vk, transcript)?; 138 | 139 | Ok(Self { 140 | a_b_u, 141 | omega, 142 | alpha, 143 | z, 144 | ipa, 145 | }) 146 | } 147 | } 148 | 149 | impl AccumulationSchemeProver for IpaAs 150 | where 151 | C: CurveAffine, 152 | MOS: Clone + Debug, 153 | { 154 | type ProvingKey = IpaProvingKey; 155 | 156 | fn create_proof( 157 | pk: &Self::ProvingKey, 158 | instances: &[IpaAccumulator], 159 | transcript: &mut T, 160 | mut rng: R, 161 | ) -> Result, Error> 162 | where 163 | T: TranscriptWrite, 164 | R: Rng, 165 | { 166 | assert!(instances.len() > 1); 167 | 168 | let a_b_u = pk 169 | .zk() 170 | .then(|| { 171 | let [a, b] = array::from_fn(|_| C::Scalar::random(&mut rng)); 172 | let u = (pk.g[1] * a + pk.g[0] * b).to_affine(); 173 | transcript.write_scalar(a)?; 174 | transcript.write_scalar(b)?; 175 | transcript.write_ec_point(u)?; 176 | Ok((a, b, u)) 177 | }) 178 | .transpose()?; 179 | let omega = pk 180 | .zk() 181 | .then(|| { 182 | let omega = C::Scalar::random(&mut rng); 183 | transcript.write_scalar(omega)?; 184 | Ok(omega) 185 | }) 186 | .transpose()?; 187 | 188 | for accumulator in instances { 189 | for xi in accumulator.xi.iter() { 190 | transcript.common_scalar(xi)?; 191 | } 192 | transcript.common_ec_point(&accumulator.u)?; 193 | } 194 | 195 | let alpha = transcript.squeeze_challenge(); 196 | let z = transcript.squeeze_challenge(); 197 | 198 | let (u, h) = instances 199 | .iter() 200 | .map(|IpaAccumulator { u, xi }| (*u, h_coeffs(xi, C::Scalar::ONE))) 201 | .chain(a_b_u.map(|(a, b, u)| { 202 | ( 203 | u, 204 | iter::empty() 205 | .chain([b, a]) 206 | .chain(iter::repeat(C::Scalar::ZERO).take(pk.domain.n - 2)) 207 | .collect(), 208 | ) 209 | })) 210 | .unzip::<_, _, Vec<_>, Vec<_>>(); 211 | let powers_of_alpha = alpha.powers(u.len()); 212 | 213 | let h = powers_of_alpha 214 | .into_iter() 215 | .zip(h.into_iter().map(Polynomial::new)) 216 | .map(|(power_of_alpha, h)| h * power_of_alpha) 217 | .sum::>(); 218 | 219 | Ipa::create_proof(pk, &h.to_vec(), &z, omega.as_ref(), transcript, &mut rng) 220 | } 221 | } 222 | 223 | #[cfg(test)] 224 | mod test { 225 | use crate::{ 226 | pcs::{ 227 | ipa::{self, IpaProvingKey}, 228 | AccumulationDecider, AccumulationScheme, AccumulationSchemeProver, 229 | }, 230 | util::{arithmetic::Field, msm::Msm, poly::Polynomial, Itertools}, 231 | }; 232 | use halo2_curves::pasta::pallas; 233 | use halo2_proofs::transcript::{ 234 | Blake2bRead, Blake2bWrite, TranscriptReadBuffer, TranscriptWriterBuffer, 235 | }; 236 | use rand::rngs::OsRng; 237 | use std::iter; 238 | 239 | #[test] 240 | fn test_ipa_as() { 241 | type Ipa = ipa::Ipa; 242 | type IpaAs = ipa::IpaAs; 243 | 244 | let k = 10; 245 | let zk = true; 246 | let mut rng = OsRng; 247 | 248 | let pk = IpaProvingKey::::rand(k, zk, &mut rng); 249 | let accumulators = iter::repeat_with(|| { 250 | let (c, z, v, proof) = { 251 | let p = Polynomial::::rand(pk.domain.n, &mut rng); 252 | let omega = pk.zk().then(|| pallas::Scalar::random(&mut rng)); 253 | let c = pk.commit(&p, omega); 254 | let z = pallas::Scalar::random(&mut rng); 255 | let v = p.evaluate(z); 256 | let mut transcript = Blake2bWrite::init(Vec::new()); 257 | Ipa::create_proof(&pk, &p[..], &z, omega.as_ref(), &mut transcript, &mut rng) 258 | .unwrap(); 259 | (c, z, v, transcript.finalize()) 260 | }; 261 | 262 | let svk = pk.svk(); 263 | let accumulator = { 264 | let mut transcript = Blake2bRead::init(proof.as_slice()); 265 | let proof = Ipa::read_proof(&svk, &mut transcript).unwrap(); 266 | Ipa::succinct_verify(&svk, &Msm::base(&c), &z, &v, &proof).unwrap() 267 | }; 268 | 269 | accumulator 270 | }) 271 | .take(10) 272 | .collect_vec(); 273 | 274 | let proof = { 275 | let apk = pk.clone(); 276 | let mut transcript = Blake2bWrite::init(Vec::new()); 277 | IpaAs::create_proof(&apk, &accumulators, &mut transcript, &mut rng).unwrap(); 278 | transcript.finalize() 279 | }; 280 | 281 | let accumulator = { 282 | let avk = pk.svk(); 283 | let mut transcript = Blake2bRead::init(proof.as_slice()); 284 | let proof = IpaAs::read_proof(&avk, &accumulators, &mut transcript).unwrap(); 285 | IpaAs::verify(&avk, &accumulators, &proof).unwrap() 286 | }; 287 | 288 | let dk = pk.dk(); 289 | assert!(IpaAs::decide(&dk, accumulator).is_ok()); 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /snark-verifier/src/util/msm.rs: -------------------------------------------------------------------------------- 1 | //! Multi-scalar multiplication algorithm. 2 | 3 | use crate::{ 4 | loader::{LoadedEcPoint, Loader}, 5 | util::{ 6 | arithmetic::{CurveAffine, Group, PrimeField}, 7 | Itertools, 8 | }, 9 | }; 10 | use num_integer::Integer; 11 | use std::{ 12 | default::Default, 13 | iter::{self, Sum}, 14 | mem::size_of, 15 | ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, 16 | }; 17 | 18 | #[derive(Clone, Debug)] 19 | /// Contains unevaluated multi-scalar multiplication. 20 | pub struct Msm<'a, C: CurveAffine, L: Loader> { 21 | constant: Option, 22 | scalars: Vec, 23 | bases: Vec<&'a L::LoadedEcPoint>, 24 | } 25 | 26 | impl<'a, C, L> Default for Msm<'a, C, L> 27 | where 28 | C: CurveAffine, 29 | L: Loader, 30 | { 31 | fn default() -> Self { 32 | Self { 33 | constant: None, 34 | scalars: Vec::new(), 35 | bases: Vec::new(), 36 | } 37 | } 38 | } 39 | 40 | impl<'a, C, L> Msm<'a, C, L> 41 | where 42 | C: CurveAffine, 43 | L: Loader, 44 | { 45 | /// Initialize with a constant. 46 | pub fn constant(constant: L::LoadedScalar) -> Self { 47 | Msm { 48 | constant: Some(constant), 49 | ..Default::default() 50 | } 51 | } 52 | 53 | /// Initialize with a base. 54 | pub fn base<'b: 'a>(base: &'b L::LoadedEcPoint) -> Self { 55 | let one = base.loader().load_one(); 56 | Msm { 57 | scalars: vec![one], 58 | bases: vec![base], 59 | ..Default::default() 60 | } 61 | } 62 | 63 | pub(crate) fn size(&self) -> usize { 64 | self.bases.len() 65 | } 66 | 67 | pub(crate) fn split(mut self) -> (Self, Option) { 68 | let constant = self.constant.take(); 69 | (self, constant) 70 | } 71 | 72 | pub(crate) fn try_into_constant(self) -> Option { 73 | self.bases.is_empty().then(|| self.constant.unwrap()) 74 | } 75 | 76 | /// Evaluate multi-scalar multiplication. 77 | /// 78 | /// # Panic 79 | /// 80 | /// If given `gen` is `None` but there `constant` has some value. 81 | pub fn evaluate(self, gen: Option) -> L::LoadedEcPoint { 82 | let gen = gen.map(|gen| { 83 | self.bases 84 | .first() 85 | .unwrap() 86 | .loader() 87 | .ec_point_load_const(&gen) 88 | }); 89 | let pairs = iter::empty() 90 | .chain( 91 | self.constant 92 | .as_ref() 93 | .map(|constant| (constant, gen.as_ref().unwrap())), 94 | ) 95 | .chain(self.scalars.iter().zip(self.bases)) 96 | .collect_vec(); 97 | L::multi_scalar_multiplication(&pairs) 98 | } 99 | 100 | fn scale(&mut self, factor: &L::LoadedScalar) { 101 | if let Some(constant) = self.constant.as_mut() { 102 | *constant *= factor; 103 | } 104 | for scalar in self.scalars.iter_mut() { 105 | *scalar *= factor 106 | } 107 | } 108 | 109 | fn push<'b: 'a>(&mut self, scalar: L::LoadedScalar, base: &'b L::LoadedEcPoint) { 110 | if let Some(pos) = self.bases.iter().position(|exist| exist.eq(&base)) { 111 | self.scalars[pos] += &scalar; 112 | } else { 113 | self.scalars.push(scalar); 114 | self.bases.push(base); 115 | } 116 | } 117 | 118 | fn extend<'b: 'a>(&mut self, mut other: Msm<'b, C, L>) { 119 | match (self.constant.as_mut(), other.constant.as_ref()) { 120 | (Some(lhs), Some(rhs)) => *lhs += rhs, 121 | (None, Some(_)) => self.constant = other.constant.take(), 122 | _ => {} 123 | }; 124 | for (scalar, base) in other.scalars.into_iter().zip(other.bases) { 125 | self.push(scalar, base); 126 | } 127 | } 128 | } 129 | 130 | impl<'a, 'b, C, L> Add> for Msm<'a, C, L> 131 | where 132 | 'b: 'a, 133 | C: CurveAffine, 134 | L: Loader, 135 | { 136 | type Output = Msm<'a, C, L>; 137 | 138 | fn add(mut self, rhs: Msm<'b, C, L>) -> Self::Output { 139 | self.extend(rhs); 140 | self 141 | } 142 | } 143 | 144 | impl<'a, 'b, C, L> AddAssign> for Msm<'a, C, L> 145 | where 146 | 'b: 'a, 147 | C: CurveAffine, 148 | L: Loader, 149 | { 150 | fn add_assign(&mut self, rhs: Msm<'b, C, L>) { 151 | self.extend(rhs); 152 | } 153 | } 154 | 155 | impl<'a, 'b, C, L> Sub> for Msm<'a, C, L> 156 | where 157 | 'b: 'a, 158 | C: CurveAffine, 159 | L: Loader, 160 | { 161 | type Output = Msm<'a, C, L>; 162 | 163 | fn sub(mut self, rhs: Msm<'b, C, L>) -> Self::Output { 164 | self.extend(-rhs); 165 | self 166 | } 167 | } 168 | 169 | impl<'a, 'b, C, L> SubAssign> for Msm<'a, C, L> 170 | where 171 | 'b: 'a, 172 | C: CurveAffine, 173 | L: Loader, 174 | { 175 | fn sub_assign(&mut self, rhs: Msm<'b, C, L>) { 176 | self.extend(-rhs); 177 | } 178 | } 179 | 180 | impl<'a, C, L> Mul<&L::LoadedScalar> for Msm<'a, C, L> 181 | where 182 | C: CurveAffine, 183 | L: Loader, 184 | { 185 | type Output = Msm<'a, C, L>; 186 | 187 | fn mul(mut self, rhs: &L::LoadedScalar) -> Self::Output { 188 | self.scale(rhs); 189 | self 190 | } 191 | } 192 | 193 | impl<'a, C, L> MulAssign<&L::LoadedScalar> for Msm<'a, C, L> 194 | where 195 | C: CurveAffine, 196 | L: Loader, 197 | { 198 | fn mul_assign(&mut self, rhs: &L::LoadedScalar) { 199 | self.scale(rhs); 200 | } 201 | } 202 | 203 | impl<'a, C, L> Neg for Msm<'a, C, L> 204 | where 205 | C: CurveAffine, 206 | L: Loader, 207 | { 208 | type Output = Msm<'a, C, L>; 209 | fn neg(mut self) -> Msm<'a, C, L> { 210 | self.constant = self.constant.map(|constant| -constant); 211 | for scalar in self.scalars.iter_mut() { 212 | *scalar = -scalar.clone(); 213 | } 214 | self 215 | } 216 | } 217 | 218 | impl<'a, C, L> Sum for Msm<'a, C, L> 219 | where 220 | C: CurveAffine, 221 | L: Loader, 222 | { 223 | fn sum>(iter: I) -> Self { 224 | iter.reduce(|acc, item| acc + item).unwrap_or_default() 225 | } 226 | } 227 | 228 | #[derive(Clone, Copy)] 229 | enum Bucket { 230 | None, 231 | Affine(C), 232 | Projective(C::Curve), 233 | } 234 | 235 | impl Bucket { 236 | fn add_assign(&mut self, rhs: &C) { 237 | *self = match *self { 238 | Bucket::None => Bucket::Affine(*rhs), 239 | Bucket::Affine(lhs) => Bucket::Projective(lhs + *rhs), 240 | Bucket::Projective(mut lhs) => { 241 | lhs += *rhs; 242 | Bucket::Projective(lhs) 243 | } 244 | } 245 | } 246 | 247 | fn add(self, mut rhs: C::Curve) -> C::Curve { 248 | match self { 249 | Bucket::None => rhs, 250 | Bucket::Affine(lhs) => { 251 | rhs += lhs; 252 | rhs 253 | } 254 | Bucket::Projective(lhs) => lhs + rhs, 255 | } 256 | } 257 | } 258 | 259 | fn multi_scalar_multiplication_serial( 260 | scalars: &[C::Scalar], 261 | bases: &[C], 262 | result: &mut C::Curve, 263 | ) { 264 | let scalars = scalars.iter().map(|scalar| scalar.to_repr()).collect_vec(); 265 | let num_bytes = scalars[0].as_ref().len(); 266 | let num_bits = 8 * num_bytes; 267 | 268 | let window_size = (scalars.len() as f64).ln().ceil() as usize + 2; 269 | let num_buckets = (1 << window_size) - 1; 270 | 271 | let windowed_scalar = |idx: usize, bytes: &::Repr| { 272 | let skip_bits = idx * window_size; 273 | let skip_bytes = skip_bits / 8; 274 | 275 | let mut value = [0; size_of::()]; 276 | for (dst, src) in value.iter_mut().zip(bytes.as_ref()[skip_bytes..].iter()) { 277 | *dst = *src; 278 | } 279 | 280 | (usize::from_le_bytes(value) >> (skip_bits - (skip_bytes * 8))) & num_buckets 281 | }; 282 | 283 | let num_window = Integer::div_ceil(&num_bits, &window_size); 284 | for idx in (0..num_window).rev() { 285 | for _ in 0..window_size { 286 | *result = result.double(); 287 | } 288 | 289 | let mut buckets = vec![Bucket::None; num_buckets]; 290 | 291 | for (scalar, base) in scalars.iter().zip(bases.iter()) { 292 | let scalar = windowed_scalar(idx, scalar); 293 | if scalar != 0 { 294 | buckets[scalar - 1].add_assign(base); 295 | } 296 | } 297 | 298 | let mut running_sum = C::Curve::identity(); 299 | for bucket in buckets.into_iter().rev() { 300 | running_sum = bucket.add(running_sum); 301 | *result += &running_sum; 302 | } 303 | } 304 | } 305 | 306 | /// Multi-scalar multiplication algorithm copied from 307 | /// . 308 | pub fn multi_scalar_multiplication(scalars: &[C::Scalar], bases: &[C]) -> C::Curve { 309 | assert_eq!(scalars.len(), bases.len()); 310 | 311 | #[cfg(feature = "parallel")] 312 | { 313 | use crate::util::{current_num_threads, parallelize_iter}; 314 | 315 | let num_threads = current_num_threads(); 316 | if scalars.len() < num_threads { 317 | let mut result = C::Curve::identity(); 318 | multi_scalar_multiplication_serial(scalars, bases, &mut result); 319 | return result; 320 | } 321 | 322 | let chunk_size = Integer::div_ceil(&scalars.len(), &num_threads); 323 | let mut results = vec![C::Curve::identity(); num_threads]; 324 | parallelize_iter( 325 | scalars 326 | .chunks(chunk_size) 327 | .zip(bases.chunks(chunk_size)) 328 | .zip(results.iter_mut()), 329 | |((scalars, bases), result)| { 330 | multi_scalar_multiplication_serial(scalars, bases, result); 331 | }, 332 | ); 333 | results 334 | .iter() 335 | .fold(C::Curve::identity(), |acc, result| acc + result) 336 | } 337 | #[cfg(not(feature = "parallel"))] 338 | { 339 | let mut result = C::Curve::identity(); 340 | multi_scalar_multiplication_serial(scalars, bases, &mut result); 341 | result 342 | } 343 | } 344 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. --------------------------------------------------------------------------------