├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── benchmarking ├── Cargo.toml └── src │ ├── blake2f_circuit_bench.rs │ ├── constants.rs │ ├── lib.rs │ ├── ripemd160_circuit_bench.rs │ └── sha2_256_circuit_bench.rs ├── blake2f-circuit ├── Cargo.toml └── src │ └── lib.rs ├── examples ├── Cargo.toml └── src │ ├── lib.rs │ └── residue_pattern.rs ├── ripemd160-circuit ├── Cargo.toml └── src │ └── lib.rs ├── sha2-256-circuit ├── Cargo.toml └── src │ └── lib.rs └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "zkp-mooc-halo2" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [workspace] 7 | members = [ 8 | "benchmarking", 9 | "blake2f-circuit", 10 | "ripemd160-circuit", 11 | "sha2-256-circuit", 12 | "examples", 13 | ] 14 | 15 | [dependencies] 16 | blake2f-circuit = { version = "^0.1.0", path = "./blake2f-circuit" } 17 | ripemd160-circuit = { version = "^0.1.0", path = "./ripemd160-circuit" } 18 | sha2-256-circuit = { version = "^0.1.0", path = "./sha2-256-circuit" } 19 | examples = { version = "^0.1.0", path = "./examples" } 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Scroll 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zk-mooc-halo2 2 | 3 | This repository provides skeleton code to build circuits for the "Category 4: Circuit Development in Halo2-ce" 4 | track under https://zk-hacking.org. 5 | 6 | The repository consists of 3 member crates, specifically `blake2f-circuit`, `ripemd160-circuit` and `sha2-256-circuit`, 7 | to configure constraints for verifying the input-output relationships for each of the hash functions. These hash 8 | functions are [precompiled contracts](https://www.evm.codes/precompiled) in the Ethereum Virtual Machine, and Scroll's 9 | zkEVM architecture relies on these circuits and their tables to check the input-output relationship via lookup arguments. 10 | 11 | The repository also contains a `benchmarking` crate to benchmark and further optimise the layout of each of the circuits. 12 | To run the benchmarks and see the output run the following commands: 13 | ``` 14 | cd benchmarking 15 | DEGREE=17 cargo test -- --nocapture 16 | ``` 17 | 18 | ## Workshop Video & Additional Resources 19 | 20 | - [Workshop Recording](https://www.youtube.com/watch?v=60lkR8DZKUA) 21 | - [Workshop Slides](https://drive.google.com/file/d/1SfY_kBWs2S-23gw6hKvpKwHHjqQn_Ak8/view?usp=sharing) 22 | - [ZKP / Web3 Hackathon](https://zk-hacking.org/) hosted by Berkeley [RDI](https://rdi.berkeley.edu/) and in partnership with [ZKP MOOC](https://zk-learning.org/) 23 | - [ZK Circuit Track](https://zk-hacking.org/tracks/zk_circuit_track/) (Category 4 sponsored by Scroll) 24 | - [Installing Rust](https://doc.rust-lang.org/book/ch01-01-installation.html) 25 | -------------------------------------------------------------------------------- /benchmarking/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmarking" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dev-dependencies] 7 | ark-std = { version = "0.3", features = ["print-trace"] } 8 | ethers-core = "^1.0.0" 9 | halo2_proofs = { git = "https://github.com/halo2-ce/halo2.git" } 10 | rand = "0.8" 11 | rand_chacha = "0.3" 12 | rand_xorshift = "0.3" 13 | 14 | # circuits 15 | blake2f-circuit = { version = "^0.1.0", path = "../blake2f-circuit", features = [ "test" ] } 16 | ripemd160-circuit = { version = "^0.1.0", path = "../ripemd160-circuit", features = [ "test" ] } 17 | sha2-256-circuit = { version = "^0.1.0", path = "../sha2-256-circuit", features = [ "test" ] } 18 | -------------------------------------------------------------------------------- /benchmarking/src/blake2f_circuit_bench.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use ark_std::{end_timer, start_timer}; 4 | use blake2f_circuit::dev::{Blake2fTestCircuit, INPUTS_OUTPUTS}; 5 | use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}; 6 | use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG, ParamsVerifierKZG}; 7 | use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; 8 | use halo2_proofs::poly::kzg::strategy::SingleStrategy; 9 | use halo2_proofs::{ 10 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 11 | poly::commitment::ParamsProver, 12 | transcript::{ 13 | Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, 14 | }, 15 | }; 16 | use rand::SeedableRng; 17 | use rand_xorshift::XorShiftRng; 18 | use std::{env::var, marker::PhantomData}; 19 | 20 | use crate::constants::{PROOFGEN_PREFIX, PROOFVER_PREFIX, SETUP_PREFIX}; 21 | 22 | #[test] 23 | fn bench_blake2f_circuit() { 24 | // Unique string used by bench results module for parsing the result. 25 | const BENCHMARK_ID: &str = "BLAKE2 Compression Function Circuit"; 26 | 27 | let degree: u32 = var("DEGREE") 28 | .expect("No DEGREE env var was provided") 29 | .parse() 30 | .expect("Cannot parse DEGREE env var as u32"); 31 | 32 | // Create BLAKE2F circuit with some test vectors. 33 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 34 | let circuit: Blake2fTestCircuit = Blake2fTestCircuit { 35 | inputs, 36 | outputs, 37 | _marker: PhantomData, 38 | }; 39 | 40 | // Initialize the polynomial commitment parameters. 41 | let mut rng = XorShiftRng::from_seed([ 42 | 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 43 | 0xbc, 0xe5, 44 | ]); 45 | 46 | // Bench setup generation. 47 | let setup_message = format!("{} {} with degree = {}", BENCHMARK_ID, SETUP_PREFIX, degree); 48 | let start1 = start_timer!(|| setup_message); 49 | let general_params = ParamsKZG::::setup(degree, &mut rng); 50 | let verifier_params: ParamsVerifierKZG = general_params.verifier_params().clone(); 51 | end_timer!(start1); 52 | 53 | // Initialize the proving/verifying key. 54 | let vk = keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); 55 | let pk = keygen_pk(&general_params, vk, &circuit).expect("keygen_pk should not fail"); 56 | let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); 57 | 58 | // Bench proof generation time. 59 | let proof_message = format!( 60 | "{} {} with degree = {}", 61 | BENCHMARK_ID, PROOFGEN_PREFIX, degree 62 | ); 63 | let start2 = start_timer!(|| proof_message); 64 | create_proof::< 65 | KZGCommitmentScheme, 66 | ProverSHPLONK<'_, Bn256>, 67 | Challenge255, 68 | XorShiftRng, 69 | Blake2bWrite, G1Affine, Challenge255>, 70 | Blake2fTestCircuit, 71 | >( 72 | &general_params, 73 | &pk, 74 | &[circuit], 75 | &[&[]], 76 | rng, 77 | &mut transcript, 78 | ) 79 | .expect("proof generation should not fail"); 80 | let proof = transcript.finalize(); 81 | end_timer!(start2); 82 | 83 | // Bench verification time. 84 | let start3 = start_timer!(|| format!("{} {}", BENCHMARK_ID, PROOFVER_PREFIX)); 85 | let mut verifier_transcript = Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&proof[..]); 86 | let strategy = SingleStrategy::new(&general_params); 87 | verify_proof::< 88 | KZGCommitmentScheme, 89 | VerifierSHPLONK<'_, Bn256>, 90 | Challenge255, 91 | Blake2bRead<&[u8], G1Affine, Challenge255>, 92 | SingleStrategy<'_, Bn256>, 93 | >( 94 | &verifier_params, 95 | pk.get_vk(), 96 | strategy, 97 | &[&[]], 98 | &mut verifier_transcript, 99 | ) 100 | .expect("failed to verify bench circuit"); 101 | end_timer!(start3); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /benchmarking/src/constants.rs: -------------------------------------------------------------------------------- 1 | pub const SETUP_PREFIX: &str = "[Setup generation]"; 2 | pub const PROOFGEN_PREFIX: &str = "[Proof generation]"; 3 | pub const PROOFVER_PREFIX: &str = "[Proof verification]"; 4 | -------------------------------------------------------------------------------- /benchmarking/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | pub mod blake2f_circuit_bench; 3 | 4 | #[cfg(test)] 5 | pub mod ripemd160_circuit_bench; 6 | 7 | #[cfg(test)] 8 | pub mod sha2_256_circuit_bench; 9 | 10 | #[cfg(test)] 11 | mod constants; 12 | -------------------------------------------------------------------------------- /benchmarking/src/ripemd160_circuit_bench.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use ark_std::{end_timer, start_timer}; 4 | use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}; 5 | use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG, ParamsVerifierKZG}; 6 | use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; 7 | use halo2_proofs::poly::kzg::strategy::SingleStrategy; 8 | use halo2_proofs::{ 9 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 10 | poly::commitment::ParamsProver, 11 | transcript::{ 12 | Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, 13 | }, 14 | }; 15 | use rand::SeedableRng; 16 | use rand_xorshift::XorShiftRng; 17 | use ripemd160_circuit::dev::{Ripemd160TestCircuit, INPUTS_OUTPUTS}; 18 | use std::{env::var, marker::PhantomData}; 19 | 20 | use crate::constants::{PROOFGEN_PREFIX, PROOFVER_PREFIX, SETUP_PREFIX}; 21 | 22 | #[test] 23 | fn bench_ripemd160_circuit() { 24 | // Unique string used by bench results module for parsing the result. 25 | const BENCHMARK_ID: &str = "RIPEMD-160 Circuit"; 26 | 27 | let degree: u32 = var("DEGREE") 28 | .expect("No DEGREE env var was provided") 29 | .parse() 30 | .expect("Cannot parse DEGREE env var as u32"); 31 | 32 | // Create RIPEMD-160 circuit with some test vectors. 33 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 34 | let circuit: Ripemd160TestCircuit = Ripemd160TestCircuit { 35 | inputs, 36 | outputs, 37 | _marker: PhantomData, 38 | }; 39 | 40 | // Initialize the polynomial commitment parameters. 41 | let mut rng = XorShiftRng::from_seed([ 42 | 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 43 | 0xbc, 0xe5, 44 | ]); 45 | 46 | // Bench setup generation. 47 | let setup_message = format!("{} {} with degree = {}", BENCHMARK_ID, SETUP_PREFIX, degree); 48 | let start1 = start_timer!(|| setup_message); 49 | let general_params = ParamsKZG::::setup(degree, &mut rng); 50 | let verifier_params: ParamsVerifierKZG = general_params.verifier_params().clone(); 51 | end_timer!(start1); 52 | 53 | // Initialize the proving/verifying key. 54 | let vk = keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); 55 | let pk = keygen_pk(&general_params, vk, &circuit).expect("keygen_pk should not fail"); 56 | let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); 57 | 58 | // Bench proof generation time. 59 | let proof_message = format!( 60 | "{} {} with degree = {}", 61 | BENCHMARK_ID, PROOFGEN_PREFIX, degree 62 | ); 63 | let start2 = start_timer!(|| proof_message); 64 | create_proof::< 65 | KZGCommitmentScheme, 66 | ProverSHPLONK<'_, Bn256>, 67 | Challenge255, 68 | XorShiftRng, 69 | Blake2bWrite, G1Affine, Challenge255>, 70 | Ripemd160TestCircuit, 71 | >( 72 | &general_params, 73 | &pk, 74 | &[circuit], 75 | &[&[]], 76 | rng, 77 | &mut transcript, 78 | ) 79 | .expect("proof generation should not fail"); 80 | let proof = transcript.finalize(); 81 | end_timer!(start2); 82 | 83 | // Bench verification time. 84 | let start3 = start_timer!(|| format!("{} {}", BENCHMARK_ID, PROOFVER_PREFIX)); 85 | let mut verifier_transcript = Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&proof[..]); 86 | let strategy = SingleStrategy::new(&general_params); 87 | verify_proof::< 88 | KZGCommitmentScheme, 89 | VerifierSHPLONK<'_, Bn256>, 90 | Challenge255, 91 | Blake2bRead<&[u8], G1Affine, Challenge255>, 92 | SingleStrategy<'_, Bn256>, 93 | >( 94 | &verifier_params, 95 | pk.get_vk(), 96 | strategy, 97 | &[&[]], 98 | &mut verifier_transcript, 99 | ) 100 | .expect("failed to verify bench circuit"); 101 | end_timer!(start3); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /benchmarking/src/sha2_256_circuit_bench.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod tests { 3 | use ark_std::{end_timer, start_timer}; 4 | use halo2_proofs::plonk::{create_proof, keygen_pk, keygen_vk, verify_proof}; 5 | use halo2_proofs::poly::kzg::commitment::{KZGCommitmentScheme, ParamsKZG, ParamsVerifierKZG}; 6 | use halo2_proofs::poly::kzg::multiopen::{ProverSHPLONK, VerifierSHPLONK}; 7 | use halo2_proofs::poly::kzg::strategy::SingleStrategy; 8 | use halo2_proofs::{ 9 | halo2curves::bn256::{Bn256, Fr, G1Affine}, 10 | poly::commitment::ParamsProver, 11 | transcript::{ 12 | Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, 13 | }, 14 | }; 15 | use rand::SeedableRng; 16 | use rand_xorshift::XorShiftRng; 17 | use sha2_256_circuit::dev::{Sha2TestCircuit, INPUTS_OUTPUTS}; 18 | use std::{env::var, marker::PhantomData}; 19 | 20 | use crate::constants::{PROOFGEN_PREFIX, PROOFVER_PREFIX, SETUP_PREFIX}; 21 | 22 | #[test] 23 | fn bench_sha2_256_circuit() { 24 | // Unique string used by bench results module for parsing the result. 25 | const BENCHMARK_ID: &str = "SHA2-256 Circuit"; 26 | 27 | let degree: u32 = var("DEGREE") 28 | .expect("No DEGREE env var was provided") 29 | .parse() 30 | .expect("Cannot parse DEGREE env var as u32"); 31 | 32 | // Create SHA2-256 circuit with some test vectors. 33 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 34 | let circuit: Sha2TestCircuit = Sha2TestCircuit { 35 | inputs, 36 | outputs, 37 | _marker: PhantomData, 38 | }; 39 | 40 | // Initialize the polynomial commitment parameters. 41 | let mut rng = XorShiftRng::from_seed([ 42 | 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 43 | 0xbc, 0xe5, 44 | ]); 45 | 46 | // Bench setup generation. 47 | let setup_message = format!("{} {} with degree = {}", BENCHMARK_ID, SETUP_PREFIX, degree); 48 | let start1 = start_timer!(|| setup_message); 49 | let general_params = ParamsKZG::::setup(degree, &mut rng); 50 | let verifier_params: ParamsVerifierKZG = general_params.verifier_params().clone(); 51 | end_timer!(start1); 52 | 53 | // Initialize the proving/verifying key. 54 | let vk = keygen_vk(&general_params, &circuit).expect("keygen_vk should not fail"); 55 | let pk = keygen_pk(&general_params, vk, &circuit).expect("keygen_pk should not fail"); 56 | let mut transcript = Blake2bWrite::<_, G1Affine, Challenge255<_>>::init(vec![]); 57 | 58 | // Bench proof generation time. 59 | let proof_message = format!( 60 | "{} {} with degree = {}", 61 | BENCHMARK_ID, PROOFGEN_PREFIX, degree 62 | ); 63 | let start2 = start_timer!(|| proof_message); 64 | create_proof::< 65 | KZGCommitmentScheme, 66 | ProverSHPLONK<'_, Bn256>, 67 | Challenge255, 68 | XorShiftRng, 69 | Blake2bWrite, G1Affine, Challenge255>, 70 | Sha2TestCircuit, 71 | >( 72 | &general_params, 73 | &pk, 74 | &[circuit], 75 | &[&[]], 76 | rng, 77 | &mut transcript, 78 | ) 79 | .expect("proof generation should not fail"); 80 | let proof = transcript.finalize(); 81 | end_timer!(start2); 82 | 83 | // Bench verification time. 84 | let start3 = start_timer!(|| format!("{} {}", BENCHMARK_ID, PROOFVER_PREFIX)); 85 | let mut verifier_transcript = Blake2bRead::<_, G1Affine, Challenge255<_>>::init(&proof[..]); 86 | let strategy = SingleStrategy::new(&general_params); 87 | verify_proof::< 88 | KZGCommitmentScheme, 89 | VerifierSHPLONK<'_, Bn256>, 90 | Challenge255, 91 | Blake2bRead<&[u8], G1Affine, Challenge255>, 92 | SingleStrategy<'_, Bn256>, 93 | >( 94 | &verifier_params, 95 | pk.get_vk(), 96 | strategy, 97 | &[&[]], 98 | &mut verifier_transcript, 99 | ) 100 | .expect("failed to verify bench circuit"); 101 | end_timer!(start3); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /blake2f-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "blake2f-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ethers-core = "^1.0.0" 8 | halo2_proofs = { git = "https://github.com/halo2-ce/halo2.git" } 9 | lazy_static = "1.4.0" 10 | 11 | [features] 12 | default = ["test"] 13 | test = [] 14 | -------------------------------------------------------------------------------- /blake2f-circuit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | #![allow(unreachable_code)] 4 | 5 | use std::marker::PhantomData; 6 | 7 | use halo2_proofs::{ 8 | arithmetic::FieldExt, 9 | circuit::Layouter, 10 | plonk::{Advice, Any, Column, ConstraintSystem, Error}, 11 | }; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct Blake2fTable { 15 | id: Column, 16 | } 17 | 18 | impl Blake2fTable { 19 | pub fn construct(meta: &mut ConstraintSystem) -> Self { 20 | Self { 21 | id: meta.advice_column(), 22 | } 23 | } 24 | 25 | pub fn columns(&self) -> Vec> { 26 | vec![self.id.into()] 27 | } 28 | 29 | pub fn annotations(&self) -> Vec { 30 | vec![String::from("id")] 31 | } 32 | } 33 | 34 | #[derive(Clone, Debug)] 35 | pub struct Blake2fConfig { 36 | table: Blake2fTable, 37 | _marker: PhantomData, 38 | } 39 | 40 | impl Blake2fConfig { 41 | pub fn configure(meta: &mut ConstraintSystem, table: Blake2fTable) -> Self { 42 | Self { 43 | table, 44 | _marker: PhantomData, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Clone, Debug, Default)] 50 | pub struct Blake2fWitness { 51 | pub rounds: u32, 52 | pub h: [u64; 8], 53 | pub m: [u64; 16], 54 | pub t: [u64; 2], 55 | pub f: bool, 56 | } 57 | 58 | #[derive(Clone, Debug)] 59 | pub struct Blake2fChip { 60 | config: Blake2fConfig, 61 | data: Vec, 62 | } 63 | 64 | impl Blake2fChip { 65 | pub fn construct(config: Blake2fConfig, data: Vec) -> Self { 66 | Self { config, data } 67 | } 68 | 69 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { 70 | Ok(()) 71 | } 72 | } 73 | 74 | #[cfg(any(feature = "test", test))] 75 | pub mod dev { 76 | use super::*; 77 | 78 | use ethers_core::{types::H512, utils::hex::FromHex}; 79 | use halo2_proofs::{arithmetic::FieldExt, circuit::SimpleFloorPlanner, plonk::Circuit}; 80 | use std::{marker::PhantomData, str::FromStr}; 81 | 82 | lazy_static::lazy_static! { 83 | // https://eips.ethereum.org/EIPS/eip-152#example-usage-in-solidity 84 | pub static ref INPUTS_OUTPUTS: (Vec, Vec) = { 85 | let (h1, h2) = ( 86 | <[u8; 32]>::from_hex("48c9bdf267e6096a3ba7ca8485ae67bb2bf894fe72f36e3cf1361d5f3af54fa5").expect(""), 87 | <[u8; 32]>::from_hex("d182e6ad7f520e511f6c3e2b8c68059b6bbd41fbabd9831f79217e1319cde05b").expect(""), 88 | ); 89 | let (m1, m2, m3, m4) = ( 90 | <[u8; 32]>::from_hex("6162630000000000000000000000000000000000000000000000000000000000").expect(""), 91 | <[u8; 32]>::from_hex("0000000000000000000000000000000000000000000000000000000000000000").expect(""), 92 | <[u8; 32]>::from_hex("0000000000000000000000000000000000000000000000000000000000000000").expect(""), 93 | <[u8; 32]>::from_hex("0000000000000000000000000000000000000000000000000000000000000000").expect(""), 94 | ); 95 | ( 96 | vec![ 97 | Blake2fWitness { 98 | rounds: 12, 99 | h: [ 100 | u64::from_le_bytes(h1[0x00..0x08].try_into().expect("")), 101 | u64::from_le_bytes(h1[0x08..0x10].try_into().expect("")), 102 | u64::from_le_bytes(h1[0x10..0x18].try_into().expect("")), 103 | u64::from_le_bytes(h1[0x18..0x20].try_into().expect("")), 104 | u64::from_le_bytes(h2[0x00..0x08].try_into().expect("")), 105 | u64::from_le_bytes(h2[0x08..0x10].try_into().expect("")), 106 | u64::from_le_bytes(h2[0x10..0x18].try_into().expect("")), 107 | u64::from_le_bytes(h2[0x18..0x20].try_into().expect("")), 108 | ], 109 | m: [ 110 | u64::from_le_bytes(m1[0x00..0x08].try_into().expect("")), 111 | u64::from_le_bytes(m1[0x08..0x10].try_into().expect("")), 112 | u64::from_le_bytes(m1[0x10..0x18].try_into().expect("")), 113 | u64::from_le_bytes(m1[0x18..0x20].try_into().expect("")), 114 | u64::from_le_bytes(m2[0x00..0x08].try_into().expect("")), 115 | u64::from_le_bytes(m2[0x08..0x10].try_into().expect("")), 116 | u64::from_le_bytes(m2[0x10..0x18].try_into().expect("")), 117 | u64::from_le_bytes(m2[0x18..0x20].try_into().expect("")), 118 | u64::from_le_bytes(m3[0x00..0x08].try_into().expect("")), 119 | u64::from_le_bytes(m3[0x08..0x10].try_into().expect("")), 120 | u64::from_le_bytes(m3[0x10..0x18].try_into().expect("")), 121 | u64::from_le_bytes(m3[0x18..0x20].try_into().expect("")), 122 | u64::from_le_bytes(m4[0x00..0x08].try_into().expect("")), 123 | u64::from_le_bytes(m4[0x08..0x10].try_into().expect("")), 124 | u64::from_le_bytes(m4[0x10..0x18].try_into().expect("")), 125 | u64::from_le_bytes(m4[0x18..0x20].try_into().expect("")), 126 | ], 127 | t: [3, 0], 128 | f: true, 129 | } 130 | ], 131 | vec![ 132 | H512::from_str("ba80a53f981c4d0d6a2797b69f12f6e94c212f14685ac4b74b12bb6fdbffa2d17d87c5392aab792dc252d5de4533cc9518d38aa8dbf1925ab92386edd4009923") 133 | .expect("BLAKE2F compression function output is 64-bytes") 134 | ], 135 | ) 136 | }; 137 | } 138 | 139 | #[derive(Default)] 140 | pub struct Blake2fTestCircuit { 141 | pub inputs: Vec, 142 | pub outputs: Vec, 143 | pub _marker: PhantomData, 144 | } 145 | 146 | impl Circuit for Blake2fTestCircuit { 147 | type Config = Blake2fConfig; 148 | type FloorPlanner = SimpleFloorPlanner; 149 | 150 | fn without_witnesses(&self) -> Self { 151 | Self::default() 152 | } 153 | 154 | fn configure(meta: &mut halo2_proofs::plonk::ConstraintSystem) -> Self::Config { 155 | let blake2f_table = Blake2fTable::construct(meta); 156 | Blake2fConfig::configure(meta, blake2f_table) 157 | } 158 | 159 | fn synthesize( 160 | &self, 161 | config: Self::Config, 162 | mut layouter: impl Layouter, 163 | ) -> Result<(), Error> { 164 | let chip = Blake2fChip::construct(config, self.inputs.clone()); 165 | chip.load(&mut layouter) 166 | } 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod tests { 172 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; 173 | use std::marker::PhantomData; 174 | 175 | use crate::dev::{Blake2fTestCircuit, INPUTS_OUTPUTS}; 176 | 177 | #[test] 178 | fn test_blake2f_circuit() { 179 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 180 | 181 | let circuit: Blake2fTestCircuit = Blake2fTestCircuit { 182 | inputs, 183 | outputs, 184 | _marker: PhantomData, 185 | }; 186 | 187 | let k = 8; 188 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 189 | assert_eq!(prover.verify(), Ok(())); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ethers-core = "^1.0.0" 8 | halo2_proofs = { git = "https://github.com/halo2-ce/halo2.git" } 9 | lazy_static = "1.4.0" 10 | 11 | [features] 12 | default = ["test"] 13 | test = [] 14 | -------------------------------------------------------------------------------- /examples/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod residue_pattern; 2 | 3 | pub use residue_pattern::{residue_pattern, ResiduePatternChip, ResiduePatternConfig}; 4 | -------------------------------------------------------------------------------- /examples/src/residue_pattern.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | halo2curves::bn256::Fr, 3 | arithmetic::{FieldExt, Field}, 4 | circuit::{Layouter, Region, Value}, 5 | plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector}, 6 | poly::Rotation, 7 | }; 8 | 9 | #[derive(Clone, Copy)] 10 | pub struct ResiduePatternConfig { 11 | always_enabled: Selector, // This selector is always enabled to avoid ConstraintPoisoned errors. 12 | index_is_nonzero: Selector, // enabled iff index column is not zero. 13 | index: Column, // repeats [0..length) 14 | 15 | value: Column, // value we're computing residue pattern for 16 | is_residue: Column, // binary column that is 1 iff value + index is a quadratic residue 17 | pattern: Column, // built up bit by bit from is_residue 18 | square_root: Column, // square root of value + index if its a residue or nonresidue * (value + index) otherwise. 19 | } 20 | 21 | pub struct ResiduePatternChip { 22 | length: usize, 23 | nonresidue: F, 24 | config: ResiduePatternConfig, 25 | } 26 | 27 | pub fn residue_pattern(x: Fr) -> u64 { 28 | (0u64..64) 29 | .map(|i| Option::::from((x + Fr::from(i)).sqrt()).is_some()) 30 | .fold(0, |pattern, is_residue| 2 * pattern + u64::from(is_residue)) 31 | } 32 | 33 | impl ResiduePatternConfig { 34 | pub fn configure(meta: &mut ConstraintSystem, nonresidue: F) -> Self { 35 | let [always_enabled, index_is_nonzero] = [0; 2].map(|_| meta.selector()); 36 | let index = meta.fixed_column(); 37 | let [value, is_residue, pattern, square_root] = [0; 4].map(|_| meta.advice_column()); 38 | 39 | meta.create_gate("value does not change if index is non-zero", |meta| { 40 | let index = meta.query_fixed(index, Rotation::cur()); 41 | let value_current = meta.query_advice(value, Rotation::cur()); 42 | let value_previous = meta.query_advice(value, Rotation::prev()); 43 | vec![index * (value_current - value_previous)] 44 | }); 45 | 46 | meta.create_gate("is_residue is binary", |meta| { 47 | let always_enabled = meta.query_selector(always_enabled); 48 | let is_residue = meta.query_advice(is_residue, Rotation::cur()); 49 | vec![ 50 | always_enabled * is_residue.clone() * (Expression::Constant(F::one()) - is_residue), 51 | ] 52 | }); 53 | 54 | meta.create_gate("square_root^2 = value + index if is_residue", |meta| { 55 | let always_enabled = meta.query_selector(always_enabled); 56 | let is_residue = meta.query_advice(is_residue, Rotation::cur()); 57 | let square_root = meta.query_advice(square_root, Rotation::cur()); 58 | let square = meta.query_advice(value, Rotation::cur()) 59 | + meta.query_fixed(index, Rotation::cur()); 60 | vec![always_enabled * is_residue * (square_root.square() - square)] 61 | }); 62 | 63 | meta.create_gate( 64 | "square_root^2 = nonresidue * (value + index) if not is_residue", 65 | |meta| { 66 | let always_enabled = meta.query_selector(always_enabled); 67 | let is_nonresidue = 68 | Expression::Constant(F::one()) - meta.query_advice(is_residue, Rotation::cur()); 69 | let fixed_nonresidue = Expression::Constant(nonresidue); 70 | let square_root = meta.query_advice(square_root, Rotation::cur()); 71 | let nonresidue = meta.query_advice(value, Rotation::cur()) 72 | + meta.query_fixed(index, Rotation::cur()); 73 | vec![ 74 | always_enabled 75 | * is_nonresidue 76 | * (square_root.square() - fixed_nonresidue * nonresidue), 77 | ] 78 | }, 79 | ); 80 | 81 | meta.create_gate( 82 | "current pattern = is_residue + 2 * previous pattern", 83 | |meta| { 84 | let index_is_nonzero = meta.query_selector(index_is_nonzero); 85 | let is_residue = meta.query_advice(is_residue, Rotation::cur()); 86 | let pattern_current = meta.query_advice(pattern, Rotation::cur()); 87 | let pattern_previous = meta.query_advice(pattern, Rotation::prev()); 88 | vec![ 89 | index_is_nonzero 90 | * (pattern_current 91 | - Expression::Constant(F::from(2)) * pattern_previous 92 | - is_residue), 93 | ] 94 | }, 95 | ); 96 | 97 | Self { 98 | index, 99 | value, 100 | is_residue, 101 | pattern, 102 | square_root, 103 | index_is_nonzero, 104 | always_enabled, 105 | } 106 | } 107 | } 108 | 109 | impl ResiduePatternChip { 110 | pub fn assign(&self, layouter: &mut impl Layouter, values: &[F]) -> Result, Error> { 111 | layouter.assign_region( 112 | || "residue_pattern", 113 | |mut region| { 114 | let mut patterns = vec![]; 115 | let mut offset = 0; 116 | for value in values.iter() { 117 | patterns.push(self.assign_value(&mut region, offset, *value)?); 118 | offset += self.length; 119 | } 120 | Ok(patterns) 121 | }, 122 | ) 123 | } 124 | 125 | fn assign_value( 126 | &self, 127 | region: &mut Region<'_, F>, 128 | offset: usize, 129 | value: F, 130 | ) -> Result { 131 | let config = self.config; 132 | let mut pattern = 0; 133 | let mut offset = offset; 134 | for index in 0u64..self.length.try_into().unwrap() { 135 | config.always_enabled.enable(region, offset)?; 136 | if index != 0 { 137 | config.index_is_nonzero.enable(region, offset)?; 138 | } 139 | 140 | let index = F::from(index); 141 | region.assign_fixed(|| "index", config.index, offset, || Value::known(index))?; 142 | 143 | region.assign_advice(|| "value", config.value, offset, || Value::known(value))?; 144 | 145 | let (is_residue, square_root) = 146 | if let Some(square_root) = Option::::from((value + index).sqrt()) { 147 | (true, square_root) 148 | } else { 149 | ( 150 | false, 151 | Option::::from((self.nonresidue * (value + index)).sqrt()).unwrap(), 152 | ) 153 | }; 154 | 155 | region.assign_advice( 156 | || "is_residue", 157 | config.is_residue, 158 | offset, 159 | || Value::known(if is_residue { F::one() } else { F::zero() }), 160 | )?; 161 | 162 | pattern = 2 * pattern + u64::from(is_residue); 163 | region.assign_advice( 164 | || "pattern", 165 | config.pattern, 166 | offset, 167 | || Value::known(F::from(pattern)), 168 | )?; 169 | 170 | region.assign_advice( 171 | || "square_root", 172 | config.square_root, 173 | offset, 174 | || Value::known(square_root), 175 | )?; 176 | 177 | offset += 1; 178 | } 179 | Ok(pattern) 180 | } 181 | } 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use super::*; 186 | use halo2_proofs::{ 187 | arithmetic::Field, circuit::SimpleFloorPlanner, dev::MockProver, 188 | plonk::Circuit, 189 | }; 190 | 191 | #[derive(Default)] 192 | struct TestCircuit { 193 | values: Vec, 194 | length: usize, 195 | nonresidue: F, 196 | } 197 | 198 | impl TestCircuit { 199 | fn nonresidue() -> F { 200 | F::from(5) 201 | } 202 | } 203 | 204 | impl Circuit for TestCircuit { 205 | type Config = ResiduePatternConfig; 206 | type FloorPlanner = SimpleFloorPlanner; 207 | 208 | fn without_witnesses(&self) -> Self { 209 | Self::default() 210 | } 211 | 212 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 213 | ResiduePatternConfig::configure(meta, Self::nonresidue()) 214 | } 215 | 216 | fn synthesize( 217 | &self, 218 | config: Self::Config, 219 | mut layouter: impl Layouter, 220 | ) -> Result<(), Error> { 221 | let chip = ResiduePatternChip { 222 | config, 223 | length: self.length, 224 | nonresidue: self.nonresidue, 225 | }; 226 | chip.assign(&mut layouter, &self.values)?; 227 | Ok(()) 228 | } 229 | } 230 | 231 | #[test] 232 | fn test_vectors() { 233 | assert_eq!( 234 | residue_pattern(Fr::zero()), 235 | 0b1111101011001100101000001111010010011101000100001110111100110000 236 | ); 237 | assert_eq!( 238 | residue_pattern(Fr::one()), 239 | 0b1111010110011001010000011110100100111010001000011101111001100001 240 | ); 241 | assert_eq!( 242 | residue_pattern(Fr::from(0x5234234)), 243 | 0b110011011100010010000111110001011101000000000010111000101011110 244 | ); 245 | } 246 | 247 | #[test] 248 | fn test_nonresidue() { 249 | assert_eq!( 250 | Option::::from(TestCircuit::::nonresidue().sqrt()), 251 | None 252 | ); 253 | } 254 | 255 | #[test] 256 | fn test_residue_pattern_circuit() { 257 | let circuit = TestCircuit { 258 | values: vec![0.into(), 2323.into(), 124123123.into(), 3.into()], 259 | length: 64, 260 | nonresidue: TestCircuit::::nonresidue(), 261 | }; 262 | 263 | let k = 10; 264 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 265 | assert_eq!(prover.verify(), Ok(())); 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /ripemd160-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ripemd160-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ethers-core = "^1.0.0" 8 | halo2_proofs = { git = "https://github.com/halo2-ce/halo2.git" } 9 | lazy_static = "1.4.0" 10 | 11 | [features] 12 | default = ["test"] 13 | test = [] 14 | -------------------------------------------------------------------------------- /ripemd160-circuit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | #![allow(unreachable_code)] 4 | 5 | use std::marker::PhantomData; 6 | 7 | use halo2_proofs::{ 8 | arithmetic::FieldExt, 9 | circuit::Layouter, 10 | plonk::{Advice, Any, Column, ConstraintSystem, Error}, 11 | }; 12 | 13 | #[derive(Clone, Debug)] 14 | pub struct Ripemd160Table { 15 | id: Column, 16 | } 17 | 18 | impl Ripemd160Table { 19 | pub fn construct(meta: &mut ConstraintSystem) -> Self { 20 | Self { 21 | id: meta.advice_column(), 22 | } 23 | } 24 | 25 | pub fn columns(&self) -> Vec> { 26 | vec![self.id.into()] 27 | } 28 | 29 | pub fn annotations(&self) -> Vec { 30 | vec![String::from("id")] 31 | } 32 | } 33 | 34 | #[derive(Clone, Debug)] 35 | pub struct Ripemd160Config { 36 | table: Ripemd160Table, 37 | _marker: PhantomData, 38 | } 39 | 40 | impl Ripemd160Config { 41 | pub fn configure(meta: &mut ConstraintSystem, table: Ripemd160Table) -> Self { 42 | Self { 43 | table, 44 | _marker: PhantomData, 45 | } 46 | } 47 | } 48 | 49 | #[derive(Clone, Debug)] 50 | pub struct Ripemd160Witness { 51 | pub inputs: Vec>, 52 | pub _marker: PhantomData, 53 | } 54 | 55 | #[derive(Clone, Debug)] 56 | pub struct Ripemd160Chip { 57 | config: Ripemd160Config, 58 | data: Ripemd160Witness, 59 | } 60 | 61 | impl Ripemd160Chip { 62 | pub fn construct(config: Ripemd160Config, data: Ripemd160Witness) -> Self { 63 | Self { config, data } 64 | } 65 | 66 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { 67 | Ok(()) 68 | } 69 | } 70 | 71 | #[cfg(any(feature = "test", test))] 72 | pub mod dev { 73 | use super::*; 74 | 75 | use ethers_core::types::H160; 76 | use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; 77 | use std::str::FromStr; 78 | 79 | lazy_static::lazy_static! { 80 | pub static ref INPUTS_OUTPUTS: (Vec>, Vec) = { 81 | [ 82 | ("", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), 83 | ("abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), 84 | ( 85 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 86 | "12a053384a9c0c88e405a06c27dcf49ada62eb2b", 87 | ), 88 | ( 89 | "abcdefghijklmnopqrstuvwxyz", 90 | "f71c27109c692c1b56bbdceb5b9d2865b3708dbc", 91 | ), 92 | ] 93 | .iter() 94 | .map(|(input, output)| { 95 | ( 96 | input.as_bytes().to_vec(), 97 | H160::from_str(output).expect("ripemd-160 hash is 20-bytes"), 98 | ) 99 | }) 100 | .unzip() 101 | }; 102 | } 103 | 104 | #[derive(Default)] 105 | pub struct Ripemd160TestCircuit { 106 | pub inputs: Vec>, 107 | pub outputs: Vec, 108 | pub _marker: PhantomData, 109 | } 110 | 111 | impl Circuit for Ripemd160TestCircuit { 112 | type Config = Ripemd160Config; 113 | type FloorPlanner = SimpleFloorPlanner; 114 | 115 | fn without_witnesses(&self) -> Self { 116 | Self::default() 117 | } 118 | 119 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 120 | let ripemd160_table = Ripemd160Table::construct(meta); 121 | Ripemd160Config::configure(meta, ripemd160_table) 122 | } 123 | 124 | fn synthesize( 125 | &self, 126 | config: Self::Config, 127 | mut layouter: impl Layouter, 128 | ) -> Result<(), Error> { 129 | let chip = Ripemd160Chip::construct( 130 | config, 131 | Ripemd160Witness { 132 | inputs: self.inputs.clone(), 133 | _marker: PhantomData, 134 | }, 135 | ); 136 | chip.load(&mut layouter) 137 | } 138 | } 139 | } 140 | 141 | #[cfg(test)] 142 | mod tests { 143 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; 144 | use std::marker::PhantomData; 145 | 146 | use crate::dev::{Ripemd160TestCircuit, INPUTS_OUTPUTS}; 147 | 148 | #[test] 149 | fn test_ripemd160_circuit() { 150 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 151 | 152 | let circuit: Ripemd160TestCircuit = Ripemd160TestCircuit { 153 | inputs, 154 | outputs, 155 | _marker: PhantomData, 156 | }; 157 | 158 | let k = 8; 159 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 160 | assert_eq!(prover.verify(), Ok(())); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /sha2-256-circuit/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sha2-256-circuit" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | ethers-core = "^1.0.0" 8 | halo2_proofs = { git = "https://github.com/halo2-ce/halo2.git" } 9 | lazy_static = "1.4" 10 | 11 | [features] 12 | default = ["test"] 13 | test = [] 14 | -------------------------------------------------------------------------------- /sha2-256-circuit/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | #![allow(unreachable_code)] 4 | 5 | //! A circuit is a layout of columns over multiple rows, capable of building or 6 | //! defining their own custom constraints. In the [`zkEVM`] architecture, many 7 | //! such circuits (individually termed as sub-circuits) are placed within a 8 | //! super-circuit. When a circuit encounters an expensive operation, it can 9 | //! outsource the verification effort to another circuit through the usage of 10 | //! lookup arguments. 11 | //! 12 | //! For instance, the [`EVM-circuit`] would like to verify the output of a call 13 | //! to the precompiled contract [`SHA2-256`], which in itself is an expensive 14 | //! operation to verify. So in order to separate out the verification logic and 15 | //! build a more developer-friendly approach, the EVM circuit would use the 16 | //! SHA2-256 circuit's table via lookups to communicate simply the input-output 17 | //! relationship, outsourcing the effort of verifying the relationship itself 18 | //! to the SHA2-256 circuit. 19 | //! 20 | //! In the sha2-256-circuit crate, we export the SHA2-256 circuit's config `Sha2Config`, 21 | //! and the table within it (that other circuits can use as a lookup argument) 22 | //! `Sha2Table`. The config type defines the layout of the circuit, the various 23 | //! named columns in the circuit's layout, and the `configure` method is meant 24 | //! to define the relationship between those columns over its neighbouring rows. 25 | //! 26 | //! For instance, for the `id` field to be an incremental field, one may specify 27 | //! the following relationship: 28 | //! ``` 29 | //! # impl Sha2Config { 30 | //! pub fn configure(meta: &mut ConstraintSystem, table: Sha2Table) -> Self { 31 | //! meta.create_gate("validity check over all rows", |meta| { 32 | //! let mut cb = BaseConstraintBuilder::default(); 33 | //! cb.require_equal( 34 | //! "id field is incremental, i.e. id::cur + 1 == id::next", 35 | //! meta.query_advice(table.id, Rotation::cur()) + 1.expr(), 36 | //! meta.query_advice(table.id, Rotation::next()), 37 | //! ); 38 | //! cb.gate(1.expr()) // enable this gate over all rows. 39 | //! }); 40 | //! 41 | //! Self { 42 | //! table, 43 | //! _marker: PhantomData, 44 | //! } 45 | //! } 46 | //! # } 47 | //! ``` 48 | //! 49 | //! We also describe how the EVM circuit would lookup to the SHA2 circuit via lookup 50 | //! arguments [`here`]. Currently, the table is a dummy column named `id`. 51 | //! 52 | //! The following tasks are expected to be done: 53 | //! - Define the layout of the SHA2-256 circuit through columns in `Sha2Config`. 54 | //! - Define the lookup argument exposed by SHA2-256 circuit via `Sha2Table`. 55 | //! - Define verification logic over rows of the circuit by constraining the relationship 56 | //! between the columns. 57 | //! - Assign witness data to the circuit via the `load` method. 58 | //! - Test the verification logic in the circuit. 59 | //! 60 | //! [`zkEVM`]: https://privacy-scaling-explorations.github.io/zkevm-docs/introduction.html 61 | //! [`EVM-circuit`]: https://github.com/scroll-tech/zkevm-circuits/blob/scroll-stable/zkevm-circuits/src/evm_circuit.rs 62 | //! [`SHA2-256`]: https://en.wikipedia.org/wiki/SHA-2#Pseudocode 63 | //! [`here`]: https://github.com/scroll-tech/zkevm-circuits/pull/398 64 | 65 | use std::marker::PhantomData; 66 | 67 | use halo2_proofs::{ 68 | arithmetic::FieldExt, 69 | circuit::Layouter, 70 | plonk::{Advice, Any, Column, ConstraintSystem, Error}, 71 | }; 72 | 73 | #[derive(Clone, Debug)] 74 | pub struct Sha2Table { 75 | id: Column, 76 | } 77 | 78 | impl Sha2Table { 79 | pub fn construct(meta: &mut ConstraintSystem) -> Self { 80 | Self { 81 | id: meta.advice_column(), 82 | } 83 | } 84 | 85 | pub fn columns(&self) -> Vec> { 86 | vec![self.id.into()] 87 | } 88 | 89 | pub fn annotations(&self) -> Vec { 90 | vec![String::from("id")] 91 | } 92 | } 93 | 94 | #[derive(Clone, Debug)] 95 | pub struct Sha2Config { 96 | table: Sha2Table, 97 | _marker: PhantomData, 98 | } 99 | 100 | impl Sha2Config { 101 | pub fn configure(meta: &mut ConstraintSystem, table: Sha2Table) -> Self { 102 | Self { 103 | table, 104 | _marker: PhantomData, 105 | } 106 | } 107 | } 108 | 109 | #[derive(Clone, Debug)] 110 | pub struct Sha2Witness { 111 | pub inputs: Vec>, 112 | pub _marker: PhantomData, 113 | } 114 | 115 | #[derive(Clone, Debug)] 116 | pub struct Sha2Chip { 117 | config: Sha2Config, 118 | data: Sha2Witness, 119 | } 120 | 121 | impl Sha2Chip { 122 | pub fn construct(config: Sha2Config, data: Sha2Witness) -> Self { 123 | Self { data, config } 124 | } 125 | 126 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { 127 | Ok(()) 128 | } 129 | } 130 | 131 | #[cfg(any(feature = "test", test))] 132 | pub mod dev { 133 | use super::*; 134 | 135 | use ethers_core::types::H256; 136 | use halo2_proofs::{circuit::SimpleFloorPlanner, plonk::Circuit}; 137 | use std::str::FromStr; 138 | 139 | lazy_static::lazy_static! { 140 | pub static ref INPUTS_OUTPUTS: (Vec>, Vec) = { 141 | [ 142 | ( 143 | "", 144 | "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", 145 | ), 146 | ( 147 | "abc", 148 | "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad", 149 | ), 150 | ( 151 | "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", 152 | "248d6a61d20638b8e5c026930c3e6039a33ce45964ff2167f6ecedd419db06c1", 153 | ), 154 | ( 155 | "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu", 156 | "cf5b16a778af8380036ce59e7b0492370b249b11e8f07a51afac45037afee9d1", 157 | ), 158 | ] 159 | .iter() 160 | .map(|(input, output)| { 161 | ( 162 | input.as_bytes().to_vec(), 163 | H256::from_str(output).expect("SHA-256 hash is 32-bytes"), 164 | ) 165 | }) 166 | .unzip() 167 | }; 168 | } 169 | 170 | #[derive(Default)] 171 | pub struct Sha2TestCircuit { 172 | pub inputs: Vec>, 173 | pub outputs: Vec, 174 | pub _marker: PhantomData, 175 | } 176 | 177 | impl Circuit for Sha2TestCircuit { 178 | type Config = Sha2Config; 179 | type FloorPlanner = SimpleFloorPlanner; 180 | 181 | fn without_witnesses(&self) -> Self { 182 | Self::default() 183 | } 184 | 185 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 186 | let sha2_table = Sha2Table::construct(meta); 187 | Sha2Config::configure(meta, sha2_table) 188 | } 189 | 190 | fn synthesize( 191 | &self, 192 | config: Self::Config, 193 | mut layouter: impl Layouter, 194 | ) -> Result<(), Error> { 195 | let chip = Sha2Chip::construct( 196 | config, 197 | Sha2Witness { 198 | inputs: self.inputs.clone(), 199 | _marker: PhantomData, 200 | }, 201 | ); 202 | chip.load(&mut layouter) 203 | } 204 | } 205 | } 206 | 207 | #[cfg(test)] 208 | mod tests { 209 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr}; 210 | use std::marker::PhantomData; 211 | 212 | use crate::dev::{Sha2TestCircuit, INPUTS_OUTPUTS}; 213 | 214 | #[test] 215 | fn test_sha2_circuit() { 216 | let (inputs, outputs) = INPUTS_OUTPUTS.clone(); 217 | 218 | let circuit: Sha2TestCircuit = Sha2TestCircuit { 219 | inputs, 220 | outputs, 221 | _marker: PhantomData, 222 | }; 223 | 224 | let k = 8; 225 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 226 | assert_eq!(prover.verify(), Ok(())); 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use blake2f_circuit; 2 | 3 | pub use ripemd160_circuit; 4 | 5 | pub use sha2_256_circuit; 6 | --------------------------------------------------------------------------------