├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .vscode └── settings.json ├── Cargo.toml ├── README.md ├── prints ├── inclusion-check-1-layout.png ├── merkle-sum-tree-layout.png ├── merkle-tree-1-layout.png ├── merkle-tree-2-layout.png ├── merkle-tree-3-layout.png └── poseidon-layout.png └── src ├── chips.rs ├── chips ├── add_carry_v1.rs ├── add_carry_v2.rs ├── hash_v1.rs ├── hash_v2.rs ├── inclusion_check.rs ├── inclusion_check_v2.rs ├── is_zero.rs ├── less_than.rs ├── lookup.rs ├── merkle_sum_tree.rs ├── merkle_v1.rs ├── merkle_v2.rs ├── merkle_v3.rs ├── overflow_check.rs ├── overflow_check_v2.rs ├── poseidon │ ├── hash.rs │ ├── hash_with_instance.rs │ ├── mod.rs │ └── spec.rs ├── safe_accumulator.rs ├── util.rs └── utils.rs ├── circuits.rs ├── circuits ├── add_carry_v1.rs ├── add_carry_v2.rs ├── hash_v1.rs ├── hash_v2.rs ├── inclusion_check.rs ├── inclusion_check_v2.rs ├── less_than.rs ├── less_than_v2.rs ├── less_than_v3.rs ├── merkle_sum_tree.rs ├── merkle_v1.rs ├── merkle_v2.rs ├── merkle_v3.rs ├── overflow_check.rs ├── overflow_check_v2.rs ├── poseidon.rs ├── safe_accumulator.rs └── utils.rs ├── decompose_bigInt.rs └── lib.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Format 22 | run: cargo fmt 23 | - name: Run tests 24 | run: cargo test --verbose -- --nocapture 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[rust]": { 3 | "editor.defaultFormatter": "rust-lang.rust-analyzer", 4 | "editor.formatOnSave": true, 5 | "editor.formatOnSaveMode": "file" 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "halo2-experiments" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [features] 7 | dev-graph = ["halo2_proofs/dev-graph", "plotters"] 8 | 9 | [dependencies] 10 | halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} 11 | halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} 12 | plotters = { version = "0.3.0", optional = true } 13 | tabbycat = { version = "0.1", features = ["attributes"], optional = true } 14 | eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits", rev= "37b8aca"} 15 | num-bigint = "0.4.3" 16 | hex = "0.4.3" 17 | arrayvec = "0.7.2" 18 | gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits", rev= "37b8aca"} 19 | rand = "0.8" 20 | -------------------------------------------------------------------------------- /prints/inclusion-check-1-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/inclusion-check-1-layout.png -------------------------------------------------------------------------------- /prints/merkle-sum-tree-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/merkle-sum-tree-layout.png -------------------------------------------------------------------------------- /prints/merkle-tree-1-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/merkle-tree-1-layout.png -------------------------------------------------------------------------------- /prints/merkle-tree-2-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/merkle-tree-2-layout.png -------------------------------------------------------------------------------- /prints/merkle-tree-3-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/merkle-tree-3-layout.png -------------------------------------------------------------------------------- /prints/poseidon-layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/summa-dev/halo2-experiments/9293898f3136f5b16b621f66c9787e437461dd75/prints/poseidon-layout.png -------------------------------------------------------------------------------- /src/chips.rs: -------------------------------------------------------------------------------- 1 | pub mod hash_v1; 2 | pub mod hash_v2; 3 | pub mod inclusion_check; 4 | pub mod inclusion_check_v2; 5 | pub mod merkle_sum_tree; 6 | pub mod merkle_v1; 7 | pub mod merkle_v2; 8 | pub mod merkle_v3; 9 | pub mod add_carry_v1; 10 | pub mod add_carry_v2; 11 | pub mod is_zero; 12 | pub mod overflow_check; 13 | pub mod overflow_check_v2; 14 | pub mod safe_accumulator; 15 | pub mod utils; 16 | pub mod poseidon; 17 | pub mod less_than; 18 | pub mod util; 19 | -------------------------------------------------------------------------------- /src/chips/add_carry_v1.rs: -------------------------------------------------------------------------------- 1 | use super::utils::f_to_nbits; 2 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 3 | use std::marker::PhantomData; 4 | use eth_types::Field; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct AddCarryConfig { 8 | pub advice: [Column; 3], 9 | pub constant: Column, 10 | pub instance: Column, 11 | pub selector: Selector, 12 | pub _marker: PhantomData 13 | } 14 | 15 | #[derive(Debug, Clone)] 16 | pub struct AddCarryChip { 17 | config: AddCarryConfig, 18 | } 19 | 20 | impl AddCarryChip { 21 | pub fn construct(config: AddCarryConfig) -> Self { 22 | Self { config } 23 | } 24 | 25 | pub fn configure( 26 | meta: &mut ConstraintSystem, 27 | advice: [Column; 3], 28 | constant: Column, 29 | selector: Selector, 30 | instance: Column, 31 | ) -> AddCarryConfig { 32 | let col_a = advice[0]; 33 | let col_b = advice[1]; 34 | let col_c = advice[2]; 35 | let add_carry_selector = selector; 36 | 37 | // Enable equality on the advice and instance column to enable permutation check 38 | meta.enable_equality(col_b); 39 | meta.enable_equality(col_c); 40 | meta.enable_equality(instance); 41 | 42 | // Enable constant column 43 | meta.enable_constant(constant); 44 | 45 | // enforce dummy hash function by creating a custom gate 46 | meta.create_gate("accumulate constraint", |meta| { 47 | let s = meta.query_selector(add_carry_selector); 48 | let prev_b = meta.query_advice(col_b, Rotation::prev()); 49 | let prev_c = meta.query_advice(col_c, Rotation::prev()); 50 | let a = meta.query_advice(col_a, Rotation::cur()); 51 | let b = meta.query_advice(col_b, Rotation::cur()); 52 | let c = meta.query_advice(col_c, Rotation::cur()); 53 | 54 | // Previous accumulator amount + new value from a_cell 55 | // using binary expression (x_n-4 * 2^16) + (x_n-3 * 2^8) + ... + (x_n * 2) 56 | vec![ 57 | s * ((a + (prev_b * Expression::Constant(F::from(1 << 16))) + prev_c) 58 | - ((b * Expression::Constant(F::from(1 << 16))) + c)), 59 | ] 60 | }); 61 | 62 | AddCarryConfig { 63 | advice: [col_a, col_b, col_c], 64 | constant, 65 | instance, 66 | selector: add_carry_selector, 67 | _marker: PhantomData, 68 | } 69 | } 70 | 71 | // Initial accumulator values from instance for expreiment 72 | pub fn assign_first_row( 73 | &self, 74 | mut layouter: impl Layouter, 75 | ) -> Result<(AssignedCell, AssignedCell), Error> { 76 | layouter.assign_region( 77 | || "Initialize first row as zero", 78 | |mut region| { 79 | let b_cell = region.assign_advice_from_constant( 80 | || "first acc[1]", 81 | self.config.advice[1], 82 | 0, 83 | F::zero(), 84 | )?; 85 | 86 | let c_cell = region.assign_advice_from_constant( 87 | || "first acc[2]", 88 | self.config.advice[2], 89 | 0, 90 | F::zero(), 91 | )?; 92 | 93 | Ok((b_cell, c_cell)) 94 | }, 95 | ) 96 | } 97 | 98 | pub fn assign_advice_row( 99 | &self, 100 | mut layouter: impl Layouter, 101 | a: Value, 102 | prev_b: AssignedCell, 103 | prev_c: AssignedCell, 104 | ) -> Result<(AssignedCell, AssignedCell), Error> { 105 | layouter.assign_region( 106 | || "adivce row for accumulating", 107 | |mut region| { 108 | // enable hash selector 109 | self.config.selector.enable(&mut region, 1)?; 110 | 111 | let _ = prev_b.copy_advice(|| "prev_b", &mut region, self.config.advice[1], 0); 112 | let _ = prev_c.copy_advice(|| "prev_c", &mut region, self.config.advice[2], 0); 113 | 114 | // Assign new amount to the cell inside the region 115 | region.assign_advice(|| "a", self.config.advice[0], 1, || a)?; 116 | 117 | // combine accumulated value and new 118 | let mut sum = F::zero(); 119 | 120 | prev_b 121 | .value() 122 | .map(|b| sum = sum.add(&b.mul(&F::from(1 << 16)))); 123 | prev_c.value().map(|c| sum = sum.add(c)); 124 | 125 | a.as_ref().map(|f| sum = sum.add(f)); 126 | 127 | // split by 16bits for two accumulator columns 128 | // Alternatives 129 | // option1. using additional advice column for calculation 130 | // option2. using lookup table for precalulated 131 | let (hi, lo) = f_to_nbits::<16, F>(&sum); 132 | 133 | // assigning two columns of accumulating value 134 | let b_cell = region.assign_advice( 135 | || "sum_hi", 136 | self.config.advice[1], 137 | 1, 138 | || Value::known(hi), 139 | )?; 140 | let c_cell = region.assign_advice( 141 | || "sum_lo", 142 | self.config.advice[2], 143 | 1, 144 | || Value::known(lo), 145 | )?; 146 | 147 | Ok((b_cell, c_cell)) 148 | }, 149 | ) 150 | } 151 | 152 | // Enforce permutation check between b & cell and instance column 153 | pub fn expose_public( 154 | &self, 155 | mut layouter: impl Layouter, 156 | cell: &AssignedCell, 157 | row: usize, 158 | ) -> Result<(), Error> { 159 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/chips/add_carry_v2.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use std::marker::PhantomData; 3 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct AddCarryV2Config { 7 | pub advice: [Column; 4], 8 | pub instance: Column, 9 | pub selector: Selector, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct AddCarryV2Chip { 14 | config: AddCarryV2Config, 15 | _marker: PhantomData 16 | } 17 | 18 | impl AddCarryV2Chip { 19 | pub fn construct(config: AddCarryV2Config) -> Self { 20 | Self { config, _marker: PhantomData } 21 | } 22 | 23 | pub fn configure( 24 | meta: &mut ConstraintSystem, 25 | advice: [Column; 4], 26 | selector: Selector, 27 | instance: Column, 28 | ) -> AddCarryV2Config { 29 | let col_a = advice[0]; 30 | let col_b_inv = advice[1]; 31 | let col_b = advice[2]; 32 | let col_c = advice[3]; 33 | let add_carry_selector = selector; 34 | 35 | // Enable equality on the advice and instance column to enable permutation check 36 | meta.enable_equality(col_b); 37 | meta.enable_equality(col_c); 38 | meta.enable_equality(instance); 39 | 40 | // This custom gate has two constraints: 41 | // 1. for each row, the previous accumulator amount + new value from a_cell 42 | // 2. left most accumulator bit is zero for checking overflow 43 | // Note that, if the value 'a' is more than 16bits, this chip could not get the correct result 44 | meta.create_gate("accumulate constraint", |meta| { 45 | let s = meta.query_selector(add_carry_selector); 46 | let prev_b = meta.query_advice(col_b, Rotation::prev()); 47 | let prev_c = meta.query_advice(col_c, Rotation::prev()); 48 | let a = meta.query_advice(col_a, Rotation::cur()); 49 | let b_inv = meta.query_advice(col_b_inv, Rotation::cur()); 50 | let b = meta.query_advice(col_b, Rotation::cur()); 51 | let c = meta.query_advice(col_c, Rotation::cur()); 52 | 53 | // Previous accumulator amount + new value from a_cell 54 | // using binary expression (x_n-4 * 2^16) + (x_n-3 * 2^8) + ... + (x_n * 2) 55 | vec![ 56 | s.clone() * ((a + (prev_b * Expression::Constant(F::from(1 << 16))) + prev_c) 57 | - ((b.clone() * Expression::Constant(F::from(1 << 16))) + c)), 58 | 59 | // check 'b' is zero 60 | s * b.clone() * (Expression::Constant(F::one()) - b.clone() * b_inv) 61 | ] 62 | }); 63 | 64 | AddCarryV2Config { 65 | advice: [col_a, col_b_inv, col_b, col_c], 66 | instance, 67 | selector: add_carry_selector, 68 | } 69 | } 70 | 71 | // Initial accumulator values from instance for expreiment 72 | pub fn assign_first_row( 73 | &self, 74 | mut layouter: impl Layouter, 75 | ) -> Result<(AssignedCell, AssignedCell), Error> { 76 | layouter.assign_region( 77 | || "first row", 78 | |mut region| { 79 | let b_cell = region.assign_advice_from_instance( 80 | || "first acc[1]", 81 | self.config.instance, 82 | 0, 83 | self.config.advice[2], 84 | 0, 85 | )?; 86 | 87 | let c_cell = region.assign_advice_from_instance( 88 | || "first acc[2]", 89 | self.config.instance, 90 | 1, 91 | self.config.advice[3], 92 | 0, 93 | )?; 94 | 95 | Ok((b_cell, c_cell)) 96 | }, 97 | ) 98 | } 99 | 100 | pub fn assign_advice_row( 101 | &self, 102 | mut layouter: impl Layouter, 103 | a: Value, 104 | prev_b: AssignedCell, 105 | prev_c: AssignedCell, 106 | ) -> Result<(AssignedCell, AssignedCell), Error> { 107 | layouter.assign_region( 108 | || "adivce row for accumulating", 109 | |mut region| { 110 | // enable hash selector 111 | self.config.selector.enable(&mut region, 1)?; 112 | 113 | let _ = prev_b.copy_advice(|| "prev_b", &mut region, self.config.advice[2], 0); 114 | let _ = prev_c.copy_advice(|| "prev_c", &mut region, self.config.advice[3], 0); 115 | 116 | // Assign new amount to the cell inside the region 117 | region.assign_advice(|| "a", self.config.advice[0], 1, || a)?; 118 | 119 | // combine accumulated value and new 120 | let mut sum = F::zero(); 121 | a.as_ref().map(|f| sum = sum.add(f)); 122 | prev_b 123 | .value() 124 | .map(|b| sum = sum.add(&b.mul(&F::from(1 << 16)))); 125 | prev_c.value().map(|c| sum = sum.add(c)); 126 | 127 | // split by 16bits for two accumulator columns 128 | // Alternatives 129 | // option1. using additional advice column for calculation 130 | // option2. using lookup table for precalulated 131 | let max_bits = F::from(1 << 16); 132 | let split_by_16bits = || { 133 | let mut remains = sum.clone(); 134 | let mut accumulator = F::zero(); 135 | while remains >= max_bits { 136 | remains = remains.sub(&max_bits); 137 | accumulator = accumulator.add(&F::one()); 138 | } 139 | (accumulator, remains) 140 | }; 141 | 142 | let (hi, lo) = split_by_16bits(); 143 | 144 | // assigning two columns of accumulating value 145 | let b_cell = region.assign_advice( 146 | || "sum_hi", 147 | self.config.advice[2], 148 | 1, 149 | || Value::known(hi), 150 | )?; 151 | let c_cell = region.assign_advice( 152 | || "sum_lo", 153 | self.config.advice[3], 154 | 1, 155 | || Value::known(lo), 156 | )?; 157 | 158 | let b_inv = Value::known(hi).map(|value| value.invert().unwrap_or(F::zero())); 159 | 160 | region.assign_advice(|| "b inv", self.config.advice[1], 1, || b_inv)?; 161 | 162 | Ok((b_cell, c_cell)) 163 | }, 164 | ) 165 | } 166 | 167 | // Enforce permutation check between b & cell and instance column 168 | pub fn expose_public( 169 | &self, 170 | mut layouter: impl Layouter, 171 | cell: &AssignedCell, 172 | row: usize, 173 | ) -> Result<(), Error> { 174 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/chips/hash_v1.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Hash1Config { 7 | pub advice: [Column; 2], 8 | pub instance: Column, 9 | pub selector: Selector, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Hash1Chip { 14 | config: Hash1Config, 15 | _marker: PhantomData, 16 | } 17 | 18 | impl Hash1Chip { 19 | pub fn construct(config: Hash1Config) -> Self { 20 | Self { 21 | config, 22 | _marker: PhantomData, 23 | } 24 | } 25 | 26 | pub fn configure( 27 | meta: &mut ConstraintSystem, 28 | advice: [Column; 2], 29 | instance: Column, 30 | ) -> Hash1Config { 31 | let col_a = advice[0]; 32 | let col_b = advice[1]; 33 | 34 | // create check selector 35 | let hash_selector = meta.selector(); 36 | 37 | // Enable equality on the advice and instance column to enable permutation check 38 | meta.enable_equality(col_b); 39 | meta.enable_equality(instance); 40 | 41 | // enforce dummy hash function by creating a custom gate 42 | meta.create_gate("hash constraint", |meta| { 43 | // enforce 2 * a = b, namely 2 * a - b = 0 44 | 45 | let s = meta.query_selector(hash_selector); 46 | let a = meta.query_advice(col_a, Rotation::cur()); 47 | let b = meta.query_advice(col_b, Rotation::cur()); 48 | 49 | vec![s * (Expression::Constant(F::from(2)) * a - b)] 50 | }); 51 | 52 | Hash1Config { 53 | advice: [col_a, col_b], 54 | instance, 55 | selector: hash_selector, 56 | } 57 | } 58 | 59 | pub fn assign_advice_row( 60 | &self, 61 | mut layouter: impl Layouter, 62 | a: Value, 63 | ) -> Result, Error> { 64 | layouter.assign_region( 65 | || "adivce row", 66 | |mut region| { 67 | // enable hash selector 68 | self.config.selector.enable(&mut region, 0)?; 69 | 70 | // Assign the value to username and balance to the cell inside the region 71 | region.assign_advice(|| "a", self.config.advice[0], 0, || a)?; 72 | 73 | let b_cell = region.assign_advice( 74 | || "b", 75 | self.config.advice[1], 76 | 0, 77 | || a * Value::known(F::from(2)), 78 | )?; 79 | 80 | Ok(b_cell) 81 | }, 82 | ) 83 | } 84 | 85 | // Enforce permutation check between b cell and instance column 86 | pub fn expose_public( 87 | &self, 88 | mut layouter: impl Layouter, 89 | b_cell: &AssignedCell, 90 | row: usize, 91 | ) -> Result<(), Error> { 92 | layouter.constrain_instance(b_cell.cell(), self.config.instance, row) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/chips/hash_v2.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Hash2Config { 7 | pub advice: [Column; 3], 8 | pub instance: Column, 9 | pub selector: Selector, 10 | } 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct Hash2Chip { 14 | config: Hash2Config, 15 | _marker: PhantomData, 16 | } 17 | 18 | impl Hash2Chip { 19 | pub fn construct(config: Hash2Config) -> Self { 20 | Self { 21 | config, 22 | _marker: PhantomData, 23 | } 24 | } 25 | 26 | pub fn configure( 27 | meta: &mut ConstraintSystem, 28 | advice: [Column; 3], 29 | instance: Column, 30 | ) -> Hash2Config { 31 | let col_a = advice[0]; 32 | let col_b = advice[1]; 33 | let col_c = advice[2]; 34 | 35 | // create check selector 36 | let hash_selector = meta.selector(); 37 | 38 | // Enable equality on the advice and instance column to enable permutation check 39 | meta.enable_equality(col_c); 40 | meta.enable_equality(instance); 41 | 42 | meta.enable_equality(col_a); 43 | meta.enable_equality(col_b); 44 | 45 | // enforce dummy hash function by creating a custom gate 46 | meta.create_gate("hash constraint", |meta| { 47 | // enforce a + b = c, namely a + b - c = 0 48 | let s = meta.query_selector(hash_selector); 49 | let a = meta.query_advice(col_a, Rotation::cur()); 50 | let b = meta.query_advice(col_b, Rotation::cur()); 51 | let c = meta.query_advice(col_c, Rotation::cur()); 52 | 53 | vec![s * (a + b - c)] 54 | }); 55 | 56 | Hash2Config { 57 | advice: [col_a, col_b, col_c], 58 | instance, 59 | selector: hash_selector, 60 | } 61 | } 62 | 63 | pub fn load_private( 64 | &self, 65 | mut layouter: impl Layouter, 66 | input: Value, 67 | ) -> Result, Error> { 68 | layouter.assign_region( 69 | || "load private", 70 | |mut region| { 71 | region.assign_advice(|| "private input", self.config.advice[0], 0, || input) 72 | }, 73 | ) 74 | } 75 | 76 | pub fn hash( 77 | &self, 78 | mut layouter: impl Layouter, 79 | a_cell: AssignedCell, 80 | b_cell: AssignedCell, 81 | ) -> Result, Error> { 82 | layouter.assign_region( 83 | || "hash row", 84 | |mut region| { 85 | // enable hash selector 86 | self.config.selector.enable(&mut region, 0)?; 87 | 88 | a_cell.copy_advice(|| "input_a", &mut region, self.config.advice[0], 0)?; 89 | b_cell.copy_advice(|| "input_b", &mut region, self.config.advice[1], 0)?; 90 | 91 | let c_cell = region.assign_advice( 92 | || "c", 93 | self.config.advice[2], 94 | 0, 95 | || a_cell.value().map(|x| x.to_owned()) + b_cell.value().map(|x| x.to_owned()), 96 | )?; 97 | 98 | Ok(c_cell) 99 | }, 100 | ) 101 | } 102 | 103 | // Enforce permutation check between b cell and instance column 104 | pub fn expose_public( 105 | &self, 106 | mut layouter: impl Layouter, 107 | c_cell: &AssignedCell, 108 | row: usize, 109 | ) -> Result<(), Error> { 110 | layouter.constrain_instance(c_cell.cell(), self.config.instance, row) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/chips/inclusion_check.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct InclusionCheckConfig { 7 | pub advice: [Column; 2], 8 | pub instance: Column, 9 | } 10 | #[derive(Debug, Clone)] 11 | pub struct InclusionCheckChip { 12 | config: InclusionCheckConfig, 13 | _marker: PhantomData, 14 | } 15 | 16 | impl InclusionCheckChip { 17 | pub fn construct(config: InclusionCheckConfig) -> Self { 18 | Self { 19 | config, 20 | _marker: PhantomData, 21 | } 22 | } 23 | 24 | pub fn configure( 25 | meta: &mut ConstraintSystem, 26 | advice: [Column; 2], 27 | instance: Column, 28 | ) -> InclusionCheckConfig { 29 | // decompose array to fetch 2 advice column 30 | let col_username = advice[0]; 31 | let col_balance = advice[1]; 32 | 33 | // enable equality for permutation check on the advice columns 34 | meta.enable_equality(col_username); 35 | meta.enable_equality(col_balance); 36 | // we also enable equality on the instance column as we need to execute permutation check on that 37 | meta.enable_equality(instance); 38 | 39 | InclusionCheckConfig { 40 | advice: [col_username, col_balance], 41 | instance, 42 | } 43 | } 44 | 45 | pub fn assign_generic_row( 46 | &self, 47 | mut layouter: impl Layouter, 48 | username: Value, 49 | balance: Value, 50 | ) -> Result<(), Error> { 51 | layouter.assign_region( 52 | || "generic row", 53 | |mut region| { 54 | // Assign the value to username and balance to the cell inside the region 55 | region.assign_advice(|| "username", self.config.advice[0], 0, || username)?; 56 | 57 | region.assign_advice(|| "balance", self.config.advice[1], 0, || balance)?; 58 | 59 | Ok(()) 60 | }, 61 | ) 62 | } 63 | 64 | pub fn assign_inclusion_check_row( 65 | &self, 66 | mut layouter: impl Layouter, 67 | username: Value, 68 | balance: Value, 69 | ) -> Result<(AssignedCell, AssignedCell), Error> { 70 | layouter.assign_region( 71 | || "inclusion row", 72 | |mut region| { 73 | // Assign the value to username and balance and return assigned cell 74 | let username_cell = region.assign_advice( 75 | || "username", // we are assigning to column a 76 | self.config.advice[0], 77 | 0, 78 | || username, 79 | )?; 80 | 81 | let balance_cell = 82 | region.assign_advice(|| "balance", self.config.advice[1], 0, || balance)?; 83 | 84 | Ok((username_cell, balance_cell)) 85 | }, 86 | ) 87 | } 88 | 89 | pub fn expose_public( 90 | &self, 91 | mut layouter: impl Layouter, 92 | public_username_cell: &AssignedCell, 93 | public_balance_cell: &AssignedCell, 94 | ) -> Result<(), Error> { 95 | // enforce equality between public_username_cell and instance column at row 0 96 | layouter.constrain_instance(public_username_cell.cell(), self.config.instance, 0)?; 97 | // enforce equality between balance_username_cell and instance column at row 1 98 | layouter.constrain_instance(public_balance_cell.cell(), self.config.instance, 1)?; 99 | 100 | Ok(()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/chips/inclusion_check_v2.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{ 4 | arithmetic::FieldExt, 5 | circuit::*, 6 | plonk::{Advice, Column, Fixed, ConstraintSystem, Error, Instance, Selector}, 7 | poly::Rotation, 8 | }; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct InclusionCheckV2Config { 12 | pub advice: [Column; 4], 13 | pub selector: Selector, 14 | pub instance: Column, 15 | pub constant: Column, 16 | } 17 | #[derive(Debug, Clone)] 18 | pub struct InclusionCheckV2Chip { 19 | config: InclusionCheckV2Config, 20 | _marker: PhantomData, 21 | } 22 | 23 | impl InclusionCheckV2Chip { 24 | pub fn construct(config: InclusionCheckV2Config) -> Self { 25 | Self { 26 | config, 27 | _marker: PhantomData, 28 | } 29 | } 30 | 31 | pub fn configure( 32 | meta: &mut ConstraintSystem, 33 | advice: [Column; 4], 34 | instance: Column, 35 | constant: Column, 36 | ) -> InclusionCheckV2Config { 37 | let username_column = advice[0]; 38 | let balance_column = advice[1]; 39 | let username_accumulator_column = advice[2]; 40 | let balance_accumulator_column = advice[3]; 41 | 42 | // create check selector 43 | let selector = meta.selector(); 44 | 45 | // Enable equality on the username_accumulator_column and balance_accumulator_column to enable permutation check 46 | meta.enable_equality(username_accumulator_column); 47 | meta.enable_equality(balance_accumulator_column); 48 | 49 | // Enable constant column. Api to enable constant column to be used for assignement 50 | meta.enable_constant(constant); 51 | 52 | // Enable equality on the instance column to enable permutation check 53 | meta.enable_equality(instance); 54 | 55 | meta.create_gate("accumulator constraint", |meta| { 56 | let s = meta.query_selector(selector); 57 | let username = meta.query_advice(username_column, Rotation::cur()); 58 | let username_accumulator = 59 | meta.query_advice(username_accumulator_column, Rotation::cur()); 60 | let prev_username_accumulator = 61 | meta.query_advice(username_accumulator_column, Rotation::prev()); 62 | 63 | let balance = meta.query_advice(balance_column, Rotation::cur()); 64 | let balance_accumulator = 65 | meta.query_advice(balance_accumulator_column, Rotation::cur()); 66 | let prev_balance_accumulator = 67 | meta.query_advice(balance_accumulator_column, Rotation::prev()); 68 | 69 | vec![ 70 | s.clone() * (username + prev_username_accumulator - username_accumulator), 71 | s * (balance + prev_balance_accumulator - balance_accumulator), 72 | ] 73 | }); 74 | 75 | InclusionCheckV2Config { 76 | advice: [ 77 | username_column, 78 | balance_column, 79 | username_accumulator_column, 80 | balance_accumulator_column, 81 | ], 82 | selector, 83 | instance, 84 | constant 85 | } 86 | } 87 | 88 | // Assign rows for instance column passing the entry of the users 89 | pub fn assign_rows( 90 | &self, 91 | mut layouter: impl Layouter, 92 | usernames: [Value; 10], 93 | balances: [Value; 10], 94 | constant: F, 95 | inclusion_index: u8, 96 | ) -> Result<(AssignedCell, AssignedCell), Error> { 97 | 98 | // For row 0, assign the zero value from constant to the accumulator 99 | layouter.assign_region( 100 | || "user and balance table", 101 | |mut region| { 102 | 103 | // for the first row, assign the zero value to the accumulator 104 | let mut username_acc_cell = region.assign_advice_from_constant( 105 | || "username accumulator init", 106 | self.config.advice[2], 107 | 0, 108 | constant, 109 | )?; 110 | 111 | let mut balance_acc_cell = region.assign_advice_from_constant( 112 | || "balance accumulator init", 113 | self.config.advice[3], 114 | 0, 115 | constant, 116 | )?; 117 | 118 | // for the other rows loop over the username and balance arrays and assign the values to the table 119 | // if the row is the inclusion index, enable the selector and assign the value to the accumulator 120 | // if the row is not the inclusion index, copy the accumulator from the previous row 121 | for _i in 0..usernames.len() { 122 | if (_i as u8) == inclusion_index { 123 | self.config.selector.enable(&mut region, _i + 1)?; 124 | 125 | region.assign_advice( 126 | || "username", 127 | self.config.advice[0], 128 | _i + 1, 129 | || usernames[_i], 130 | )?; 131 | 132 | region.assign_advice( 133 | || "balance", 134 | self.config.advice[1], 135 | _i + 1, 136 | || balances[_i], 137 | )?; 138 | 139 | username_acc_cell = region.assign_advice( 140 | || "username accumulator", 141 | self.config.advice[2], 142 | _i + 1, 143 | || usernames[_i], 144 | )?; 145 | 146 | balance_acc_cell = region.assign_advice( 147 | || "balance accumulator", 148 | self.config.advice[3], 149 | _i + 1, 150 | || balances[_i], 151 | )?; 152 | 153 | } else { 154 | region.assign_advice( 155 | || "username", 156 | self.config.advice[0], 157 | _i + 1, 158 | || usernames[_i], 159 | )?; 160 | 161 | region.assign_advice( 162 | || "balance", 163 | self.config.advice[1], 164 | _i + 1, 165 | || balances[_i], 166 | )?; 167 | 168 | username_acc_cell = username_acc_cell.copy_advice( 169 | || "copy username acc cell from prev row", 170 | &mut region, 171 | self.config.advice[2], 172 | _i + 1 173 | )?; 174 | 175 | balance_acc_cell = balance_acc_cell.copy_advice( 176 | || "copy balance acc cell from prev row", 177 | &mut region, 178 | self.config.advice[3], 179 | _i + 1 180 | )?; 181 | 182 | } 183 | } 184 | Ok((username_acc_cell, balance_acc_cell)) 185 | }, 186 | ) 187 | } 188 | 189 | pub fn expose_public( 190 | &self, 191 | mut layouter: impl Layouter, 192 | cell: &AssignedCell, 193 | row: usize, 194 | ) -> Result<(), Error> { 195 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/chips/is_zero.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | 3 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 4 | 5 | #[derive(Clone, Debug)] 6 | pub struct IsZeroConfig { 7 | pub value_inv: Column, 8 | pub is_zero_expr: Expression, 9 | } 10 | 11 | impl IsZeroConfig { 12 | pub fn expr(&self) -> Expression { 13 | self.is_zero_expr.clone() 14 | } 15 | } 16 | 17 | pub struct IsZeroChip { 18 | config: IsZeroConfig, 19 | } 20 | 21 | impl IsZeroChip { 22 | pub fn construct(config: IsZeroConfig) -> Self { 23 | IsZeroChip { config } 24 | } 25 | 26 | pub fn configure( 27 | meta: &mut ConstraintSystem, 28 | q_enable: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, 29 | value: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, 30 | value_inv: Column, 31 | ) -> IsZeroConfig { 32 | let mut is_zero_expr = Expression::Constant(F::zero()); 33 | 34 | meta.create_gate("is_zero", |meta| { 35 | // 36 | // valid | value | value_inv | 1 - value * value_inv | value * (1 - value* value_inv) 37 | // ------+-------+------------+------------------------+------------------------------- 38 | // yes | x | 1/x | 0 | 0 39 | // no | x | 0 | 1 | x 40 | // yes | 0 | 0 | 1 | 0 41 | // yes | 0 | y | 1 | 0 42 | // 43 | let value = value(meta); 44 | let q_enable = q_enable(meta); 45 | let value_inv = meta.query_advice(value_inv, Rotation::cur()); 46 | 47 | is_zero_expr = Expression::Constant(F::one()) - value.clone() * value_inv; 48 | vec![q_enable * value * is_zero_expr.clone()] 49 | }); 50 | 51 | IsZeroConfig { 52 | value_inv, 53 | is_zero_expr, 54 | } 55 | } 56 | 57 | pub fn assign( 58 | &self, 59 | region: &mut Region<'_, F>, 60 | offset: usize, 61 | value: Value, 62 | ) -> Result<(), Error> { 63 | let value_inv = value.map(|value| value.invert().unwrap_or(F::zero())); 64 | region.assign_advice(|| "value inv", self.config.value_inv, offset, || value_inv)?; 65 | Ok(()) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/chips/less_than.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 4 | 5 | // take an value in the `input` advice column 6 | // the goal is to check whether the value is less than target 7 | // table is the instance column that contains all the values from 0 to (instance-1) 8 | // advice_table gets dynamically filled with the values from table 9 | // The chip checks that the input value is less than the target value 10 | // This gets done by performing a lookup between the input value and the advice_table 11 | 12 | #[derive(Debug, Clone)] 13 | pub struct LessThanConfig { 14 | input: Column, 15 | table: Column, 16 | advice_table: Column, 17 | } 18 | 19 | #[derive(Debug, Clone)] 20 | pub struct LessThanChip { 21 | config: LessThanConfig, 22 | _marker: PhantomData, 23 | } 24 | 25 | impl LessThanChip { 26 | pub fn construct(config: LessThanConfig) -> Self { 27 | Self { 28 | config, 29 | _marker: PhantomData, 30 | } 31 | } 32 | 33 | pub fn configure( 34 | meta: &mut ConstraintSystem, 35 | input: Column, 36 | table: Column, 37 | ) -> LessThanConfig { 38 | 39 | let advice_table = meta.advice_column(); 40 | meta.enable_equality(table); 41 | meta.enable_equality(advice_table); 42 | meta.annotate_lookup_any_column(advice_table, || "Adv-table"); 43 | 44 | // Dynamic lookup check 45 | // TO DO: does it mean that we looking up input inside advice_table? 46 | meta.lookup_any( 47 | "dynamic lookup check", 48 | |meta| { 49 | let input = meta.query_advice(input, Rotation::cur()); 50 | let advice_table = meta.query_advice(advice_table, Rotation::cur()); 51 | vec![(input, advice_table)] 52 | } 53 | ); 54 | 55 | LessThanConfig { 56 | input, 57 | table, 58 | advice_table, 59 | } 60 | } 61 | 62 | pub fn assign( 63 | &self, 64 | mut layouter: impl Layouter, 65 | input: Value 66 | ) -> Result<(), Error> { 67 | layouter.assign_region( 68 | || "less than assignment", 69 | |mut region| { 70 | 71 | for i in 0..1000 { 72 | // Load Advice lookup table with Instance lookup table values. 73 | region.assign_advice_from_instance( 74 | || "Advice from instance tables", 75 | self.config.table, 76 | i, 77 | self.config.advice_table, 78 | i, 79 | )?; 80 | } 81 | 82 | // assign input value to input column 83 | region.assign_advice(|| "input", self.config.input, 0, || input)?; 84 | 85 | Ok(()) 86 | }, 87 | ) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/chips/lookup.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use halo2_proofs::{ 3 | circuit::{Layouter, Value}, 4 | plonk::{Column, ConstraintSystem, Error, Expression, Fixed, VirtualCells}, 5 | poly::Rotation, 6 | }; 7 | use std::marker::PhantomData; 8 | 9 | #[derive(Clone, Copy, Debug)] 10 | pub struct Config { 11 | u16: Column, 12 | } 13 | 14 | impl Config { 15 | pub fn range_check_u16( 16 | &self, 17 | meta: &mut ConstraintSystem, 18 | msg: &'static str, 19 | exp_fn: impl FnOnce(&mut VirtualCells<'_, F>) -> Expression, 20 | ) { 21 | meta.lookup_any(msg, |meta| { 22 | let exp = exp_fn(meta); 23 | vec![(exp, meta.query_fixed(self.u16, Rotation::cur()))] 24 | }); 25 | } 26 | } 27 | 28 | #[derive(Clone)] 29 | pub struct Queries { 30 | pub u16: Expression, 31 | } 32 | 33 | impl Queries { 34 | pub fn new(meta: &mut VirtualCells<'_, F>, c: Config) -> Self { 35 | Self { 36 | u16: meta.query_fixed(c.u16, Rotation::cur()), 37 | } 38 | } 39 | } 40 | 41 | pub struct Chip { 42 | config: Config, 43 | _marker: PhantomData, 44 | } 45 | 46 | impl Chip { 47 | pub fn construct(config: Config) -> Self { 48 | Self { 49 | config, 50 | _marker: PhantomData, 51 | } 52 | } 53 | 54 | pub fn configure(meta: &mut ConstraintSystem) -> Config { 55 | let config = Config { 56 | u16: meta.fixed_column(), 57 | }; 58 | meta.annotate_lookup_any_column(config.u16, || "LOOKUP_u16"); 59 | 60 | config 61 | } 62 | 63 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { 64 | for (column, exponent) in [ 65 | (self.config.u16, 16), 66 | ] { 67 | layouter.assign_region( 68 | || format!("assign u{} fixed column", exponent), 69 | |mut region| { 70 | for i in 0..(1 << exponent) { 71 | region.assign_fixed( 72 | || format!("assign {} in u{} fixed column", i, exponent), 73 | column, 74 | i, 75 | || Value::known(F::from(i as u64)), 76 | )?; 77 | } 78 | Ok(()) 79 | }, 80 | )?; 81 | } 82 | 83 | Ok(()) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/chips/merkle_sum_tree.rs: -------------------------------------------------------------------------------- 1 | use super::poseidon::hash::{PoseidonChip, PoseidonConfig}; 2 | use super::poseidon::spec::MySpec; 3 | use eth_types::Field; 4 | use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; 5 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 6 | 7 | const WIDTH: usize = 5; 8 | const RATE: usize = 4; 9 | const L: usize = 4; 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct MerkleSumTreeConfig { 13 | pub advice: [Column; 5], 14 | pub bool_selector: Selector, 15 | pub swap_selector: Selector, 16 | pub sum_selector: Selector, 17 | pub lt_selector: Selector, 18 | pub instance: Column, 19 | pub poseidon_config: PoseidonConfig, 20 | pub lt_config: LtConfig, 21 | } 22 | #[derive(Debug, Clone)] 23 | pub struct MerkleSumTreeChip { 24 | config: MerkleSumTreeConfig, 25 | } 26 | 27 | impl MerkleSumTreeChip { 28 | pub fn construct(config: MerkleSumTreeConfig) -> Self { 29 | Self { config } 30 | } 31 | 32 | pub fn configure( 33 | meta: &mut ConstraintSystem, 34 | advice: [Column; 5], 35 | instance: Column, 36 | ) -> MerkleSumTreeConfig { 37 | let col_a = advice[0]; 38 | let col_b = advice[1]; 39 | let col_c = advice[2]; 40 | let col_d = advice[3]; 41 | let col_e = advice[4]; 42 | 43 | // create selectors 44 | let bool_selector = meta.selector(); 45 | let swap_selector = meta.selector(); 46 | let sum_selector = meta.selector(); 47 | let lt_selector = meta.selector(); 48 | 49 | // enable equality for leaf_hash copy constraint with instance column (col_a) 50 | // enable equality for balance_hash copy constraint with instance column (col_b) 51 | // enable equality for copying left_hash, left_balance, right_hash, right_balance into poseidon_chip (col_a, col_b, col_c, col_d) 52 | // enable equality for computed_sum copy constraint with instance column (col_e) 53 | meta.enable_equality(col_a); 54 | meta.enable_equality(col_b); 55 | meta.enable_equality(col_c); 56 | meta.enable_equality(col_d); 57 | meta.enable_equality(col_e); 58 | meta.enable_equality(instance); 59 | 60 | // Enforces that e is either a 0 or 1 when the bool selector is enabled 61 | // s * e * (1 - e) = 0 62 | meta.create_gate("bool constraint", |meta| { 63 | let s = meta.query_selector(bool_selector); 64 | let e = meta.query_advice(col_e, Rotation::cur()); 65 | vec![s * e.clone() * (Expression::Constant(F::from(1)) - e)] 66 | }); 67 | 68 | // Enforces that if the swap bit (e) is on, l1=c, l2=d, r1=a, and r2=b. Otherwise, l1=a, l2=b, r1=c, and r2=d. 69 | // This applies only when the swap selector is enabled 70 | meta.create_gate("swap constraint", |meta| { 71 | let s = meta.query_selector(swap_selector); 72 | let a = meta.query_advice(col_a, Rotation::cur()); 73 | let b = meta.query_advice(col_b, Rotation::cur()); 74 | let c = meta.query_advice(col_c, Rotation::cur()); 75 | let d = meta.query_advice(col_d, Rotation::cur()); 76 | let e = meta.query_advice(col_e, Rotation::cur()); 77 | let l1 = meta.query_advice(col_a, Rotation::next()); 78 | let l2 = meta.query_advice(col_b, Rotation::next()); 79 | let r1 = meta.query_advice(col_c, Rotation::next()); 80 | let r2 = meta.query_advice(col_d, Rotation::next()); 81 | 82 | vec![ 83 | s.clone() 84 | * (e.clone() * Expression::Constant(F::from(2)) * (c.clone() - a.clone()) 85 | - (l1 - a) 86 | - (c - r1)), 87 | s * (e * Expression::Constant(F::from(2)) * (d.clone() - b.clone()) 88 | - (l2 - b) 89 | - (d - r2)), 90 | ] 91 | }); 92 | 93 | // Enforces that input_left_balance + input_right_balance = computed_sum 94 | meta.create_gate("sum constraint", |meta| { 95 | let s = meta.query_selector(sum_selector); 96 | let left_balance = meta.query_advice(col_b, Rotation::cur()); 97 | let right_balance = meta.query_advice(col_d, Rotation::cur()); 98 | let computed_sum = meta.query_advice(col_e, Rotation::cur()); 99 | vec![s * (left_balance + right_balance - computed_sum)] 100 | }); 101 | 102 | let hash_inputs = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); 103 | 104 | let poseidon_config = 105 | PoseidonChip::, WIDTH, RATE, L>::configure(meta, hash_inputs); 106 | 107 | // configure lt chip 108 | let lt_config = LtChip::configure( 109 | meta, 110 | |meta| meta.query_selector(lt_selector), 111 | |meta| meta.query_advice(col_a, Rotation::cur()), 112 | |meta| meta.query_advice(col_b, Rotation::cur()), 113 | ); 114 | 115 | let config = MerkleSumTreeConfig { 116 | advice: [col_a, col_b, col_c, col_d, col_e], 117 | bool_selector, 118 | swap_selector, 119 | sum_selector, 120 | lt_selector, 121 | instance, 122 | poseidon_config, 123 | lt_config, 124 | }; 125 | 126 | meta.create_gate( 127 | "verifies that `check` from current config equal to is_lt from LtChip ", 128 | |meta| { 129 | let q_enable = meta.query_selector(lt_selector); 130 | 131 | let check = meta.query_advice(col_c, Rotation::cur()); 132 | 133 | vec![q_enable * (config.lt_config.is_lt(meta, None) - check)] 134 | }, 135 | ); 136 | 137 | config 138 | } 139 | 140 | pub fn assing_leaf_hash_and_balance( 141 | &self, 142 | mut layouter: impl Layouter, 143 | leaf_hash: F, 144 | leaf_balance: F, 145 | ) -> Result<(AssignedCell, AssignedCell), Error> { 146 | let leaf_hash_cell = layouter.assign_region( 147 | || "assign leaf hash", 148 | |mut region| { 149 | region.assign_advice( 150 | || "leaf hash", 151 | self.config.advice[0], 152 | 0, 153 | || Value::known(leaf_hash), 154 | ) 155 | }, 156 | )?; 157 | 158 | let leaf_balance_cell = layouter.assign_region( 159 | || "assign leaf balance", 160 | |mut region| { 161 | region.assign_advice( 162 | || "leaf balance", 163 | self.config.advice[1], 164 | 0, 165 | || Value::known(leaf_balance), 166 | ) 167 | }, 168 | )?; 169 | 170 | Ok((leaf_hash_cell, leaf_balance_cell)) 171 | } 172 | 173 | pub fn merkle_prove_layer( 174 | &self, 175 | mut layouter: impl Layouter, 176 | prev_hash: &AssignedCell, 177 | prev_balance: &AssignedCell, 178 | element_hash: F, 179 | element_balance: F, 180 | index: F, 181 | ) -> Result<(AssignedCell, AssignedCell), Error> { 182 | let (left_hash, left_balance, right_hash, right_balance, computed_sum_cell) = layouter 183 | .assign_region( 184 | || "merkle prove layer", 185 | |mut region| { 186 | // Row 0 187 | self.config.bool_selector.enable(&mut region, 0)?; 188 | self.config.swap_selector.enable(&mut region, 0)?; 189 | let l1 = prev_hash.copy_advice( 190 | || "copy hash cell from previous level", 191 | &mut region, 192 | self.config.advice[0], 193 | 0, 194 | )?; 195 | let l2 = prev_balance.copy_advice( 196 | || "copy balance cell from previous level", 197 | &mut region, 198 | self.config.advice[1], 199 | 0, 200 | )?; 201 | let r1 = region.assign_advice( 202 | || "assign element_hash", 203 | self.config.advice[2], 204 | 0, 205 | || Value::known(element_hash), 206 | )?; 207 | let r2 = region.assign_advice( 208 | || "assign balance", 209 | self.config.advice[3], 210 | 0, 211 | || Value::known(element_balance), 212 | )?; 213 | let index = region.assign_advice( 214 | || "assign index", 215 | self.config.advice[4], 216 | 0, 217 | || Value::known(index), 218 | )?; 219 | 220 | let mut l1_val = l1.value().map(|x| x.to_owned()); 221 | let mut l2_val = l2.value().map(|x| x.to_owned()); 222 | let mut r1_val = r1.value().map(|x| x.to_owned()); 223 | let mut r2_val = r2.value().map(|x| x.to_owned()); 224 | 225 | self.config.sum_selector.enable(&mut region, 1)?; 226 | 227 | // if index is 0 return (l1, l2, r1, r2) else return (r1, r2, l1, l2) 228 | index.value().map(|x| x.to_owned()).map(|x| { 229 | (l1_val, l2_val, r1_val, r2_val) = if x == F::zero() { 230 | (l1_val, l2_val, r1_val, r2_val) 231 | } else { 232 | (r1_val, r2_val, l1_val, l2_val) 233 | }; 234 | }); 235 | 236 | // We need to perform the assignment of the row below according to the index 237 | let left_hash = region.assign_advice( 238 | || "assign left hash to be hashed", 239 | self.config.advice[0], 240 | 1, 241 | || l1_val, 242 | )?; 243 | 244 | let left_balance = region.assign_advice( 245 | || "assign left balance to be hashed", 246 | self.config.advice[1], 247 | 1, 248 | || l2_val, 249 | )?; 250 | 251 | let right_hash = region.assign_advice( 252 | || "assign right hash to be hashed", 253 | self.config.advice[2], 254 | 1, 255 | || r1_val, 256 | )?; 257 | 258 | let right_balance = region.assign_advice( 259 | || "assign right balance to be hashed", 260 | self.config.advice[3], 261 | 1, 262 | || r2_val, 263 | )?; 264 | 265 | let computed_sum = left_balance 266 | .value() 267 | .zip(right_balance.value()) 268 | .map(|(a, b)| *a + b); 269 | 270 | // Now we can assign the sum result to the computed_sum cell. 271 | let computed_sum_cell = region.assign_advice( 272 | || "assign sum of left and right balance", 273 | self.config.advice[4], 274 | 1, 275 | || computed_sum, 276 | )?; 277 | 278 | Ok(( 279 | left_hash, 280 | left_balance, 281 | right_hash, 282 | right_balance, 283 | computed_sum_cell, 284 | )) 285 | }, 286 | )?; 287 | 288 | // instantiate the poseidon_chip 289 | let poseidon_chip = PoseidonChip::, WIDTH, RATE, L>::construct( 290 | self.config.poseidon_config.clone(), 291 | ); 292 | 293 | // The hash function inside the poseidon_chip performs the following action 294 | // 1. Copy the left and right cells from the previous row 295 | // 2. Perform the hash function and assign the digest to the current row 296 | // 3. Constrain the digest to be equal to the hash of the left and right values 297 | let computed_hash = poseidon_chip.hash( 298 | layouter.namespace(|| "hash four child nodes"), 299 | [left_hash, left_balance, right_hash, right_balance], 300 | )?; 301 | 302 | Ok((computed_hash, computed_sum_cell)) 303 | } 304 | 305 | // Enforce computed sum to be less than total assets passed inside the instance column 306 | pub fn enforce_less_than( 307 | &self, 308 | mut layouter: impl Layouter, 309 | prev_computed_sum_cell: &AssignedCell, 310 | computed_sum: F, 311 | total_assets: F, 312 | ) -> Result<(), Error> { 313 | // Initiate chip config 314 | let chip = LtChip::construct(self.config.lt_config); 315 | chip.load(&mut layouter)?; 316 | 317 | layouter.assign_region( 318 | || "enforce sum to be less than total assets", 319 | |mut region| { 320 | // copy the computed sum to the cell in the first column 321 | prev_computed_sum_cell.copy_advice( 322 | || "copy computed sum", 323 | &mut region, 324 | self.config.advice[0], 325 | 0, 326 | )?; 327 | 328 | // copy the total assets from instance column to the cell in the second column 329 | region.assign_advice_from_instance( 330 | || "copy total assets", 331 | self.config.instance, 332 | 3, 333 | self.config.advice[1], 334 | 0, 335 | )?; 336 | 337 | // set check to be equal to 1 338 | region.assign_advice( 339 | || "check", 340 | self.config.advice[2], 341 | 0, 342 | || Value::known(F::from(1)), 343 | )?; 344 | 345 | // enable lt seletor 346 | self.config.lt_selector.enable(&mut region, 0)?; 347 | 348 | chip.assign(&mut region, 0, computed_sum, total_assets)?; 349 | 350 | Ok(()) 351 | }, 352 | )?; 353 | 354 | Ok(()) 355 | } 356 | 357 | // Enforce permutation check between input cell and instance column at row passed as input 358 | pub fn expose_public( 359 | &self, 360 | mut layouter: impl Layouter, 361 | cell: &AssignedCell, 362 | row: usize, 363 | ) -> Result<(), Error> { 364 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 365 | } 366 | } 367 | -------------------------------------------------------------------------------- /src/chips/merkle_v1.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct MerkleTreeV1Config { 7 | pub advice: [Column; 3], 8 | pub bool_selector: Selector, 9 | pub swap_selector: Selector, 10 | pub hash_selector: Selector, 11 | pub instance: Column, 12 | } 13 | #[derive(Debug, Clone)] 14 | pub struct MerkleTreeV1Chip { 15 | config: MerkleTreeV1Config, 16 | _marker: PhantomData, 17 | } 18 | 19 | impl MerkleTreeV1Chip { 20 | pub fn construct(config: MerkleTreeV1Config) -> Self { 21 | Self { 22 | config, 23 | _marker: PhantomData, 24 | } 25 | } 26 | 27 | pub fn configure( 28 | meta: &mut ConstraintSystem, 29 | advice: [Column; 3], 30 | instance: Column, 31 | ) -> MerkleTreeV1Config { 32 | let col_a = advice[0]; 33 | let col_b = advice[1]; 34 | let col_c = advice[2]; 35 | 36 | // create selectors 37 | let bool_selector = meta.selector(); 38 | let swap_selector = meta.selector(); 39 | let hash_selector = meta.selector(); 40 | 41 | // Enable equality on the advice column c and instance column to enable permutation check 42 | // between the last hash digest and the root hash passed inside the instance column 43 | meta.enable_equality(col_c); 44 | meta.enable_equality(instance); 45 | 46 | // Enable equality on the advice column a. This is need to carry digest from one level to the other 47 | // and perform copy_advice 48 | meta.enable_equality(col_a); 49 | 50 | // Enforces that c is either a 0 or 1 when the bool selector is enabled 51 | // s * c * (1 - c) = 0 52 | meta.create_gate("bool constraint", |meta| { 53 | let s = meta.query_selector(bool_selector); 54 | let c = meta.query_advice(col_c, Rotation::cur()); 55 | vec![s * c.clone() * (Expression::Constant(F::from(1)) - c)] 56 | }); 57 | 58 | // Enforces that if the swap bit (c) is on, l=b and r=a. Otherwise, l=a and r=b. 59 | // s * (c * 2 * (b - a) - (l - a) - (b - r)) = 0 60 | // This applies only when the swap selector is enabled 61 | meta.create_gate("swap constraint", |meta| { 62 | let s = meta.query_selector(swap_selector); 63 | let a = meta.query_advice(col_a, Rotation::cur()); 64 | let b = meta.query_advice(col_b, Rotation::cur()); 65 | let c = meta.query_advice(col_c, Rotation::cur()); 66 | let l = meta.query_advice(col_a, Rotation::next()); 67 | let r = meta.query_advice(col_b, Rotation::next()); 68 | vec![ 69 | s * (c * Expression::Constant(F::from(2)) * (b.clone() - a.clone()) 70 | - (l - a) 71 | - (b - r)), 72 | ] 73 | }); 74 | 75 | // enforce dummy hash function when hash selector is enabled 76 | // enforce a + b = c, namely a + b - c = 0 77 | meta.create_gate("hash constraint", |meta| { 78 | let s = meta.query_selector(hash_selector); 79 | let a = meta.query_advice(col_a, Rotation::cur()); 80 | let b = meta.query_advice(col_b, Rotation::cur()); 81 | let c = meta.query_advice(col_c, Rotation::cur()); 82 | 83 | vec![s * (a + b - c)] 84 | }); 85 | 86 | MerkleTreeV1Config { 87 | advice: [col_a, col_b, col_c], 88 | bool_selector, 89 | swap_selector, 90 | hash_selector, 91 | instance, 92 | } 93 | } 94 | 95 | pub fn assing_leaf( 96 | &self, 97 | mut layouter: impl Layouter, 98 | leaf: Value, 99 | ) -> Result, Error> { 100 | let node_cell = layouter.assign_region( 101 | || "assign leaf", 102 | |mut region| region.assign_advice(|| "assign leaf", self.config.advice[0], 0, || leaf), 103 | )?; 104 | Ok(node_cell) 105 | } 106 | 107 | pub fn merkle_prove_layer( 108 | &self, 109 | mut layouter: impl Layouter, 110 | node_cell: &AssignedCell, 111 | path_element: Value, 112 | index: Value, 113 | ) -> Result, Error> { 114 | layouter.assign_region( 115 | || "merkle prove layer", 116 | |mut region| { 117 | // Enabled Selectors at offset 0: Bool, Swap 118 | self.config.bool_selector.enable(&mut region, 0)?; 119 | self.config.swap_selector.enable(&mut region, 0)?; 120 | 121 | // Row 0: | node_cell | Path | Bit | 122 | // at tree_level 0, node_cell is the leaf 123 | // at next level, node_cell is the digest of the previous level 124 | node_cell.copy_advice( 125 | || "prev node_cell copy constraint", 126 | &mut region, 127 | self.config.advice[0], 128 | 0, 129 | )?; 130 | region.assign_advice( 131 | || "assign path element", 132 | self.config.advice[1], 133 | 0, 134 | || path_element, 135 | )?; 136 | region.assign_advice(|| "assign bit", self.config.advice[2], 0, || index)?; 137 | 138 | // Row 1: | InputLeft | InputRight | Digest | 139 | // Enabled Selectors: Hash 140 | self.config.hash_selector.enable(&mut region, 1)?; 141 | let mut input_l = node_cell.value().map(|x| x.to_owned()); 142 | let mut input_r = path_element; 143 | index.map(|index| { 144 | if index != F::zero() { 145 | (input_l, input_r) = 146 | (path_element, node_cell.value().map(|x| x.to_owned())); 147 | } 148 | }); 149 | 150 | region.assign_advice(|| "input left", self.config.advice[0], 1, || input_l)?; 151 | region.assign_advice(|| "input right", self.config.advice[1], 1, || input_r)?; 152 | 153 | let digest_cell = region.assign_advice( 154 | || "digest", 155 | self.config.advice[2], 156 | 1, 157 | || input_l + input_r, 158 | )?; 159 | 160 | Ok(digest_cell) 161 | }, 162 | ) 163 | } 164 | 165 | // Enforce permutation check between input cell and instance column 166 | pub fn expose_public( 167 | &self, 168 | mut layouter: impl Layouter, 169 | cell: &AssignedCell, 170 | row: usize, 171 | ) -> Result<(), Error> { 172 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /src/chips/merkle_v2.rs: -------------------------------------------------------------------------------- 1 | use super::hash_v2::{Hash2Chip, Hash2Config}; 2 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 3 | use std::marker::PhantomData; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct MerkleTreeV2Config { 7 | pub advice: [Column; 3], 8 | pub bool_selector: Selector, 9 | pub swap_selector: Selector, 10 | pub instance: Column, 11 | pub hash2_config: Hash2Config, 12 | } 13 | #[derive(Debug, Clone)] 14 | pub struct MerkleTreeV2Chip { 15 | config: MerkleTreeV2Config, 16 | _marker: PhantomData, 17 | } 18 | 19 | impl MerkleTreeV2Chip { 20 | pub fn construct(config: MerkleTreeV2Config) -> Self { 21 | Self { 22 | config, 23 | _marker: PhantomData, 24 | } 25 | } 26 | 27 | pub fn configure( 28 | meta: &mut ConstraintSystem, 29 | advice: [Column; 3], 30 | instance: Column, 31 | ) -> MerkleTreeV2Config { 32 | let col_a = advice[0]; 33 | let col_b = advice[1]; 34 | let col_c = advice[2]; 35 | 36 | let bool_selector = meta.selector(); 37 | let swap_selector = meta.selector(); 38 | 39 | // Enable equality on the advice column c and instance column to enable permutation check 40 | // between the last hash digest and the root hash passed inside the instance column 41 | meta.enable_equality(col_c); 42 | meta.enable_equality(instance); 43 | 44 | // Enable equality on the advice column a. This is need to carry digest from one level to the other 45 | // and perform copy_advice 46 | meta.enable_equality(col_a); 47 | 48 | // Enable equality on the advice column b. Need for permutation check when calling hash function 49 | meta.enable_equality(col_b); 50 | 51 | // Enforces that c is either a 0 or 1 when the bool selector is enabled 52 | // s * c * (1 - c) = 0 53 | meta.create_gate("bool constraint", |meta| { 54 | let s = meta.query_selector(bool_selector); 55 | let c = meta.query_advice(col_c, Rotation::cur()); 56 | vec![s * c.clone() * (Expression::Constant(F::from(1)) - c)] 57 | }); 58 | 59 | // Enforces that if the swap bit (c) is on, l=b and r=a. Otherwise, l=a and r=b. 60 | // s * (c * 2 * (b - a) - (l - a) - (b - r)) = 0 61 | // This applies only when the swap selector is enabled 62 | meta.create_gate("swap constraint", |meta| { 63 | let s = meta.query_selector(swap_selector); 64 | let a = meta.query_advice(col_a, Rotation::cur()); 65 | let b = meta.query_advice(col_b, Rotation::cur()); 66 | let c = meta.query_advice(col_c, Rotation::cur()); 67 | let l = meta.query_advice(col_a, Rotation::next()); 68 | let r = meta.query_advice(col_b, Rotation::next()); 69 | vec![ 70 | s * (c * Expression::Constant(F::from(2)) * (b.clone() - a.clone()) 71 | - (l - a) 72 | - (b - r)), 73 | ] 74 | }); 75 | 76 | let hash2_config = Hash2Chip::configure(meta, advice, instance); 77 | 78 | MerkleTreeV2Config { 79 | advice: [col_a, col_b, col_c], 80 | bool_selector, 81 | swap_selector, 82 | instance, 83 | hash2_config, 84 | } 85 | } 86 | 87 | pub fn assing_leaf( 88 | &self, 89 | mut layouter: impl Layouter, 90 | leaf: Value, 91 | ) -> Result, Error> { 92 | let node_cell = layouter.assign_region( 93 | || "assign leaf", 94 | |mut region| region.assign_advice(|| "assign leaf", self.config.advice[0], 0, || leaf), 95 | )?; 96 | 97 | Ok(node_cell) 98 | } 99 | 100 | pub fn merkle_prove_layer( 101 | &self, 102 | mut layouter: impl Layouter, 103 | node_cell: &AssignedCell, 104 | path_element: Value, 105 | index: Value, 106 | ) -> Result, Error> { 107 | let (left, right) = layouter.assign_region( 108 | || "merkle prove layer", 109 | |mut region| { 110 | // Row 0 111 | self.config.bool_selector.enable(&mut region, 0)?; 112 | self.config.swap_selector.enable(&mut region, 0)?; 113 | node_cell.copy_advice( 114 | || "copy node cell from previous prove layer", 115 | &mut region, 116 | self.config.advice[0], 117 | 0, 118 | )?; 119 | region.assign_advice( 120 | || "assign element", 121 | self.config.advice[1], 122 | 0, 123 | || path_element, 124 | )?; 125 | region.assign_advice(|| "assign index", self.config.advice[2], 0, || index)?; 126 | 127 | // Row 1 128 | // Here we just perform the assignment - no hashing is performed here! 129 | let node_cell_value = node_cell.value().map(|x| x.to_owned()); 130 | let (mut l, mut r) = (node_cell_value, path_element); 131 | index.map(|x| { 132 | (l, r) = if x == F::zero() { (l, r) } else { (r, l) }; 133 | }); 134 | 135 | // We need to perform the assignment of the row below in order to perform the swap check 136 | let left = region.assign_advice( 137 | || "assign left to be hashed", 138 | self.config.advice[0], 139 | 1, 140 | || l, 141 | )?; 142 | let right = region.assign_advice( 143 | || "assign right to be hashed", 144 | self.config.advice[1], 145 | 1, 146 | || r, 147 | )?; 148 | 149 | Ok((left, right)) 150 | }, 151 | )?; 152 | 153 | let hash_chip = Hash2Chip::construct(self.config.hash2_config.clone()); 154 | 155 | // The hash function performs the following action 156 | // 1. Copy the left and right values from the previous row 157 | // 2. Perform the hash function and assign the digest to the current row 158 | // 3. Constrain the digest to be equal to the hash of the left and right values 159 | let digest = hash_chip.hash(layouter.namespace(|| "hash row constaint"), left, right)?; 160 | Ok(digest) 161 | } 162 | 163 | // Enforce permutation check between input cell and instance column 164 | pub fn expose_public( 165 | &self, 166 | mut layouter: impl Layouter, 167 | cell: &AssignedCell, 168 | row: usize, 169 | ) -> Result<(), Error> { 170 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/chips/merkle_v3.rs: -------------------------------------------------------------------------------- 1 | use super::poseidon::hash::{PoseidonChip, PoseidonConfig}; 2 | use super::poseidon::spec::MySpec; 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*, poly::Rotation}; 4 | 5 | const WIDTH: usize = 3; 6 | const RATE: usize = 2; 7 | const L: usize = 2; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct MerkleTreeV3Config { 11 | pub advice: [Column; 3], 12 | pub bool_selector: Selector, 13 | pub swap_selector: Selector, 14 | pub instance: Column, 15 | pub poseidon_config: PoseidonConfig, 16 | } 17 | #[derive(Debug, Clone)] 18 | pub struct MerkleTreeV3Chip { 19 | config: MerkleTreeV3Config, 20 | } 21 | 22 | impl MerkleTreeV3Chip { 23 | pub fn construct(config: MerkleTreeV3Config) -> Self { 24 | Self { config } 25 | } 26 | 27 | pub fn configure( 28 | meta: &mut ConstraintSystem, 29 | advice: [Column; 3], 30 | instance: Column, 31 | ) -> MerkleTreeV3Config { 32 | let col_a = advice[0]; 33 | let col_b = advice[1]; 34 | let col_c = advice[2]; 35 | 36 | // create selectors 37 | let bool_selector = meta.selector(); 38 | let swap_selector = meta.selector(); 39 | 40 | meta.enable_equality(col_a); 41 | meta.enable_equality(col_b); 42 | meta.enable_equality(col_c); 43 | meta.enable_equality(instance); 44 | 45 | // Enforces that c is either a 0 or 1 when the bool selector is enabled 46 | // s * c * (1 - c) = 0 47 | meta.create_gate("bool constraint", |meta| { 48 | let s = meta.query_selector(bool_selector); 49 | let c = meta.query_advice(col_c, Rotation::cur()); 50 | vec![s * c.clone() * (Expression::Constant(F::from(1)) - c)] 51 | }); 52 | 53 | // Enforces that if the swap bit (c) is on, l=b and r=a. Otherwise, l=a and r=b. 54 | // s * (c * 2 * (b - a) - (l - a) - (b - r)) = 0 55 | // This applies only when the swap selector is enabled 56 | meta.create_gate("swap constraint", |meta| { 57 | let s = meta.query_selector(swap_selector); 58 | let a = meta.query_advice(col_a, Rotation::cur()); 59 | let b = meta.query_advice(col_b, Rotation::cur()); 60 | let c = meta.query_advice(col_c, Rotation::cur()); 61 | let l = meta.query_advice(col_a, Rotation::next()); 62 | let r = meta.query_advice(col_b, Rotation::next()); 63 | vec![ 64 | s * (c * Expression::Constant(F::from(2)) * (b.clone() - a.clone()) 65 | - (l - a) 66 | - (b - r)), 67 | ] 68 | }); 69 | 70 | let hash_inputs = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); 71 | 72 | let poseidon_config = 73 | PoseidonChip::, WIDTH, RATE, L>::configure(meta, hash_inputs); 74 | 75 | MerkleTreeV3Config { 76 | advice: [col_a, col_b, col_c], 77 | bool_selector, 78 | swap_selector, 79 | instance, 80 | poseidon_config, 81 | } 82 | } 83 | 84 | pub fn assing_leaf( 85 | &self, 86 | mut layouter: impl Layouter, 87 | leaf: Value, 88 | ) -> Result, Error> { 89 | let node_cell = layouter.assign_region( 90 | || "assign leaf", 91 | |mut region| region.assign_advice(|| "assign leaf", self.config.advice[0], 0, || leaf), 92 | )?; 93 | 94 | Ok(node_cell) 95 | } 96 | 97 | pub fn merkle_prove_layer( 98 | &self, 99 | mut layouter: impl Layouter, 100 | node_cell: &AssignedCell, 101 | path_element: Value, 102 | index: Value, 103 | ) -> Result, Error> { 104 | let (left, right) = layouter.assign_region( 105 | || "merkle prove layer", 106 | |mut region| { 107 | // Row 0 108 | self.config.bool_selector.enable(&mut region, 0)?; 109 | self.config.swap_selector.enable(&mut region, 0)?; 110 | node_cell.copy_advice( 111 | || "copy node cell from previous prove layer", 112 | &mut region, 113 | self.config.advice[0], 114 | 0, 115 | )?; 116 | region.assign_advice( 117 | || "assign element", 118 | self.config.advice[1], 119 | 0, 120 | || path_element, 121 | )?; 122 | region.assign_advice(|| "assign index", self.config.advice[2], 0, || index)?; 123 | 124 | // Row 1 125 | // Here we just perform the assignment - no hashing is performed here! 126 | let node_cell_value = node_cell.value().map(|x| x.to_owned()); 127 | let (mut l, mut r) = (node_cell_value, path_element); 128 | index.map(|x| { 129 | (l, r) = if x == F::zero() { (l, r) } else { (r, l) }; 130 | }); 131 | 132 | // We need to perform the assignment of the row below in order to perform the swap check 133 | let left = region.assign_advice( 134 | || "assign left to be hashed", 135 | self.config.advice[0], 136 | 1, 137 | || l, 138 | )?; 139 | let right = region.assign_advice( 140 | || "assign right to be hashed", 141 | self.config.advice[1], 142 | 1, 143 | || r, 144 | )?; 145 | 146 | Ok((left, right)) 147 | }, 148 | )?; 149 | 150 | // instantiate the poseidon_chip 151 | let poseidon_chip = PoseidonChip:: , WIDTH, RATE, L>::construct( 152 | self.config.poseidon_config.clone(), 153 | ); 154 | 155 | // The hash function inside the poseidon_chip performs the following action 156 | // 1. Copy the left and right cells from the previous row 157 | // 2. Perform the hash function and assign the digest to the current row 158 | // 3. Constrain the digest to be equal to the hash of the left and right values 159 | let digest = 160 | poseidon_chip.hash(layouter.namespace(|| "hash row constaint"), [left, right])?; 161 | Ok(digest) 162 | } 163 | 164 | // Enforce permutation check between input cell and instance column at row passed as input 165 | pub fn expose_public( 166 | &self, 167 | mut layouter: impl Layouter, 168 | cell: &AssignedCell, 169 | row: usize, 170 | ) -> Result<(), Error> { 171 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/chips/overflow_check.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use std::marker::PhantomData; 3 | 4 | use super::is_zero::{IsZeroChip, IsZeroConfig}; 5 | use super::utils::add_carry; 6 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct OverFlowCheckConfig { 10 | pub advice: [Column; 5], 11 | pub instance: Column, 12 | pub is_zero: IsZeroConfig, 13 | pub selector: [Selector; 2], 14 | } 15 | 16 | #[derive(Debug, Clone)] 17 | pub struct OverFlowChip { 18 | config: OverFlowCheckConfig, 19 | _marker: PhantomData, 20 | } 21 | 22 | impl OverFlowChip { 23 | pub fn construct(config: OverFlowCheckConfig) -> Self { 24 | Self { 25 | config, 26 | _marker: PhantomData, 27 | } 28 | } 29 | 30 | pub fn configure( 31 | meta: &mut ConstraintSystem, 32 | advice: [Column; 5], 33 | selector: [Selector; 2], 34 | instance: Column, 35 | ) -> OverFlowCheckConfig { 36 | let col_a = advice[0]; 37 | let col_b_inv = advice[1]; 38 | let col_b = advice[2]; 39 | let col_c = advice[3]; 40 | let col_d = advice[4]; 41 | let add_carry_selector = selector[0]; 42 | let overflow_check_selector = selector[1]; 43 | let is_zero = IsZeroChip::configure( 44 | meta, 45 | |meta| meta.query_selector(overflow_check_selector), 46 | |meta| meta.query_advice(col_b, Rotation::cur()), 47 | // |meta| meta.query_advice(col_b_inv, Rotation::cur()) 48 | col_b_inv, 49 | ); 50 | 51 | // Enable equality on the advice and instance column to enable permutation check 52 | meta.enable_equality(col_b); 53 | meta.enable_equality(col_c); 54 | meta.enable_equality(col_d); 55 | meta.enable_equality(instance); 56 | 57 | // enforce dummy hash function by creating a custom gate 58 | meta.create_gate("accumulate constraint", |meta| { 59 | let s_add = meta.query_selector(add_carry_selector); 60 | let s_over = meta.query_selector(overflow_check_selector); 61 | let prev_b = meta.query_advice(col_b, Rotation::prev()); 62 | let prev_c = meta.query_advice(col_c, Rotation::prev()); 63 | let prev_d = meta.query_advice(col_d, Rotation::prev()); 64 | let a = meta.query_advice(col_a, Rotation::cur()); 65 | let b = meta.query_advice(col_b, Rotation::cur()); 66 | let c = meta.query_advice(col_c, Rotation::cur()); 67 | let d = meta.query_advice(col_d, Rotation::cur()); 68 | 69 | // Previous accumulator amount + new value from a_cell 70 | // using binary expression (x_n-4 * 2^16) + (x_n-3 * 2^8) + ... + (x_n * 2) 71 | vec![ 72 | s_add 73 | * ((a 74 | + (prev_b * Expression::Constant(F::from(1 << 32))) 75 | + (prev_c * Expression::Constant(F::from(1 << 16))) 76 | + prev_d) 77 | - ((b.clone() * Expression::Constant(F::from(1 << 32))) 78 | + (c * Expression::Constant(F::from(1 << 16))) 79 | + d)), 80 | // check 'b' is zero 81 | // s_over.clone() * (a_equals_b.expr() * (output.clone() - c)), 82 | s_over * (Expression::Constant(F::one()) - is_zero.expr()), 83 | ] 84 | }); 85 | 86 | OverFlowCheckConfig { 87 | advice: [col_a, col_b_inv, col_b, col_c, col_d], 88 | instance, 89 | selector: [add_carry_selector, overflow_check_selector], 90 | is_zero, 91 | } 92 | } 93 | 94 | // Initial accumulator values from instance for expreiment 95 | pub fn assign_first_row( 96 | &self, 97 | mut layouter: impl Layouter, 98 | ) -> Result< 99 | ( 100 | AssignedCell, 101 | AssignedCell, 102 | AssignedCell, 103 | ), 104 | Error, 105 | > { 106 | layouter.assign_region( 107 | || "first row", 108 | |mut region| { 109 | let b_cell = region.assign_advice_from_instance( 110 | || "first acc[2]", 111 | self.config.instance, 112 | 0, 113 | self.config.advice[2], 114 | 0, 115 | )?; 116 | 117 | let c_cell = region.assign_advice_from_instance( 118 | || "first acc[4]", 119 | self.config.instance, 120 | 0, 121 | self.config.advice[3], 122 | 0, 123 | )?; 124 | 125 | let d_cell = region.assign_advice_from_instance( 126 | || "first acc[4]", 127 | self.config.instance, 128 | 1, 129 | self.config.advice[4], 130 | 0, 131 | )?; 132 | 133 | Ok((b_cell, c_cell, d_cell)) 134 | }, 135 | ) 136 | } 137 | 138 | pub fn assign_advice_row( 139 | &self, 140 | mut layouter: impl Layouter, 141 | a: Value, 142 | prev_b: AssignedCell, 143 | prev_c: AssignedCell, 144 | prev_d: AssignedCell, 145 | ) -> Result< 146 | ( 147 | AssignedCell, 148 | AssignedCell, 149 | AssignedCell, 150 | ), 151 | Error, 152 | > { 153 | let is_zero_chip = IsZeroChip::construct(self.config.is_zero.clone()); 154 | layouter.assign_region( 155 | || "adivce row for accumulating", 156 | |mut region| { 157 | // enable hash selector 158 | self.config.selector[0].enable(&mut region, 1)?; 159 | self.config.selector[1].enable(&mut region, 1)?; 160 | 161 | let _ = prev_b.copy_advice(|| "prev_b", &mut region, self.config.advice[2], 0); 162 | let _ = prev_c.copy_advice(|| "prev_c", &mut region, self.config.advice[3], 0); 163 | let _ = prev_d.copy_advice(|| "prev_d", &mut region, self.config.advice[4], 0); 164 | 165 | // Assign new value to the cell inside the region 166 | region.assign_advice(|| "a", self.config.advice[0], 1, || a)?; 167 | 168 | let (hi, lo) = add_carry::<16, F>(a, prev_c.clone(), prev_d.clone()); 169 | 170 | // assigning two columns of accumulating value 171 | let mut c_cell = region.assign_advice( 172 | || "sum_hi", 173 | self.config.advice[3], 174 | 1, 175 | || Value::known(hi), 176 | )?; 177 | let d_cell = region.assign_advice( 178 | || "sum_lo", 179 | self.config.advice[4], 180 | 1, 181 | || Value::known(lo), 182 | )?; 183 | 184 | let mut sum_overflow = F::zero(); 185 | if hi >= F::from(1 << 16) { 186 | let (ov, hi) = 187 | add_carry::<16, F>(Value::known(F::zero()), prev_b.clone(), c_cell.clone()); 188 | sum_overflow = ov; 189 | c_cell = region.assign_advice( 190 | || "sum_hi", 191 | self.config.advice[3], 192 | 1, 193 | || Value::known(hi), 194 | )?; 195 | } 196 | 197 | let b_cell = region.assign_advice( 198 | || "sum_overflow", 199 | self.config.advice[2], 200 | 1, 201 | || Value::known(sum_overflow), 202 | )?; 203 | 204 | // apply is_zero chip in here 205 | let _is_overflow = is_zero_chip.assign(&mut region, 1, Value::known(hi)); 206 | 207 | Ok((b_cell, c_cell, d_cell)) 208 | }, 209 | ) 210 | } 211 | 212 | // Enforce permutation check between b & cell and instance column 213 | pub fn expose_public( 214 | &self, 215 | mut layouter: impl Layouter, 216 | cell: &AssignedCell, 217 | row: usize, 218 | ) -> Result<(), Error> { 219 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/chips/overflow_check_v2.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use std::fmt::Debug; 3 | use std::marker::PhantomData; 4 | 5 | use super::utils::{decompose_bigInt_to_ubits, value_f_to_big_uint}; 6 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct OverflowCheckV2Config { 10 | pub value: Column, 11 | pub decomposed_values: [Column; ACC_COLS], 12 | pub range: Column, 13 | pub instance: Column, 14 | pub selector: Selector, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct OverflowChipV2 { 19 | config: OverflowCheckV2Config, 20 | _marker: PhantomData, 21 | } 22 | 23 | impl OverflowChipV2 { 24 | pub fn construct(config: OverflowCheckV2Config) -> Self { 25 | Self { 26 | config, 27 | _marker: PhantomData, 28 | } 29 | } 30 | 31 | pub fn configure( 32 | meta: &mut ConstraintSystem, 33 | value: Column, 34 | decomposed_values: [Column; ACC_COLS], 35 | range: Column, 36 | instance: Column, 37 | selector: Selector, 38 | ) -> OverflowCheckV2Config { 39 | decomposed_values.map(|col| meta.enable_equality(col)); 40 | 41 | meta.create_gate("equality check between decomposed value and value", |meta| { 42 | let s_doc = meta.query_selector(selector); 43 | 44 | let value = meta.query_advice(value, Rotation::cur()); 45 | 46 | let decomposed_value_vec = (0..ACC_COLS) 47 | .map(|i: usize| meta.query_advice(decomposed_values[i], Rotation::cur())) 48 | .collect::>(); 49 | 50 | let decomposed_value_sum = 51 | (0..=ACC_COLS - 2).fold(decomposed_value_vec[ACC_COLS - 1].clone(), |acc, i| { 52 | acc + (decomposed_value_vec[i].clone() 53 | * Expression::Constant(F::from( 54 | 1 << (MAX_BITS as usize * ((ACC_COLS - 1) - i)), 55 | ))) 56 | }); 57 | 58 | vec![s_doc.clone() * (decomposed_value_sum - value)] 59 | }); 60 | 61 | meta.annotate_lookup_any_column(range, || "LOOKUP_MAXBITS_RANGE"); 62 | 63 | decomposed_values[0..ACC_COLS].iter().for_each(|column| { 64 | meta.lookup_any("range check for MAXBITS", |meta| { 65 | let cell = meta.query_advice(*column, Rotation::cur()); 66 | let range = meta.query_fixed(range, Rotation::cur()); 67 | vec![(cell, range)] 68 | }); 69 | }); 70 | 71 | OverflowCheckV2Config { 72 | value, 73 | decomposed_values, 74 | range, 75 | instance, 76 | selector, 77 | } 78 | } 79 | 80 | pub fn assign( 81 | &self, 82 | mut layouter: impl Layouter, 83 | update_value: Value, 84 | ) -> Result<(), Error> { 85 | layouter.assign_region( 86 | || "assign decomposed values", 87 | |mut region| { 88 | // enable selector 89 | self.config.selector.enable(&mut region, 0)?; 90 | 91 | // Assign input value to the cell inside the region 92 | region.assign_advice(|| "assign value", self.config.value, 0, || update_value)?; 93 | 94 | // Just used helper function for decomposing. In other halo2 application used functions based on Field. 95 | let decomposed_values = decompose_bigInt_to_ubits( 96 | &value_f_to_big_uint(update_value), 97 | MAX_BITS as usize, 98 | ACC_COLS, 99 | ) as Vec; 100 | 101 | // Note that, decomposed result is little edian. So, we need to reverse it. 102 | for (idx, val) in decomposed_values.iter().rev().enumerate() { 103 | let _cell = region.assign_advice( 104 | || format!("assign decomposed[{}] col", idx), 105 | self.config.decomposed_values[idx], 106 | 0, 107 | || Value::known(*val), 108 | )?; 109 | } 110 | 111 | Ok(()) 112 | }, 113 | ) 114 | } 115 | 116 | pub fn load(&self, layouter: &mut impl Layouter) -> Result<(), Error> { 117 | let range = 1 << (MAX_BITS as usize); 118 | 119 | layouter.assign_region( 120 | || format!("load range check table of {} bits", MAX_BITS), 121 | |mut region| { 122 | for i in 0..range { 123 | region.assign_fixed( 124 | || "assign cell in fixed column", 125 | self.config.range, 126 | i, 127 | || Value::known(F::from(i as u64)), 128 | )?; 129 | } 130 | Ok(()) 131 | }, 132 | ) 133 | } 134 | 135 | // Enforce permutation check between b & cell and instance column 136 | pub fn expose_public( 137 | &self, 138 | mut layouter: impl Layouter, 139 | cell: &AssignedCell, 140 | row: usize, 141 | ) -> Result<(), Error> { 142 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/chips/poseidon/hash.rs: -------------------------------------------------------------------------------- 1 | /* 2 | An easy-to-use implementation of the Poseidon Hash in the form of a Halo2 Chip. While the Poseidon Hash function 3 | is already implemented in halo2_gadgets, there is no wrapper chip that makes it easy to use in other circuits. 4 | */ 5 | 6 | // This chip adds a set of advice columns to the gadget Chip to store the inputs of the hash 7 | // compared to `hash_with_instance` this version doesn't use any instance column. 8 | 9 | use halo2_gadgets::poseidon::{primitives::*, Hash, Pow5Chip, Pow5Config}; 10 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 11 | use std::marker::PhantomData; 12 | 13 | #[derive(Debug, Clone)] 14 | 15 | // WIDTH, RATE and L are const generics for the struct, which represent the width, rate, and number of inputs for the Poseidon hash function, respectively. 16 | // This means they are values that are known at compile time and can be used to specialize the implementation of the struct. 17 | // The actual chip provided by halo2_gadgets is added to the parent Chip. 18 | pub struct PoseidonConfig { 19 | pow5_config: Pow5Config, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | 24 | pub struct PoseidonChip< 25 | F: FieldExt, 26 | S: Spec, 27 | const WIDTH: usize, 28 | const RATE: usize, 29 | const L: usize, 30 | > { 31 | config: PoseidonConfig, 32 | _marker: PhantomData, 33 | } 34 | 35 | impl, const WIDTH: usize, const RATE: usize, const L: usize> 36 | PoseidonChip 37 | { 38 | pub fn construct(config: PoseidonConfig) -> Self { 39 | Self { 40 | config, 41 | _marker: PhantomData, 42 | } 43 | } 44 | 45 | // Configuration of the PoseidonChip 46 | pub fn configure( 47 | meta: &mut ConstraintSystem, 48 | hash_inputs: Vec>, 49 | ) -> PoseidonConfig { 50 | let partial_sbox = meta.advice_column(); 51 | let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); 52 | let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); 53 | 54 | for i in 0..WIDTH { 55 | meta.enable_equality(hash_inputs[i]); 56 | } 57 | meta.enable_constant(rc_b[0]); 58 | 59 | let pow5_config = Pow5Chip::configure::( 60 | meta, 61 | hash_inputs.try_into().unwrap(), 62 | partial_sbox, 63 | rc_a.try_into().unwrap(), 64 | rc_b.try_into().unwrap(), 65 | ); 66 | 67 | PoseidonConfig { 68 | pow5_config, 69 | } 70 | } 71 | 72 | // L is the number of inputs to the hash function 73 | // Takes the cells containing the input values of the hash function and return the cell containing the hash output 74 | // It uses the pow5_chip to compute the hash 75 | pub fn hash( 76 | &self, 77 | mut layouter: impl Layouter, 78 | input_cells: [AssignedCell; L], 79 | ) -> Result, Error> { 80 | 81 | let pow5_chip = Pow5Chip::construct(self.config.pow5_config.clone()); 82 | 83 | // initialize the hasher 84 | let hasher = Hash::<_, _, S, ConstantLength, WIDTH, RATE>::init( 85 | pow5_chip, 86 | layouter.namespace(|| "hasher"), 87 | )?; 88 | hasher.hash(layouter.namespace(|| "hash"), input_cells) 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /src/chips/poseidon/hash_with_instance.rs: -------------------------------------------------------------------------------- 1 | /* 2 | An easy-to-use implementation of the Poseidon Hash in the form of a Halo2 Chip. While the Poseidon Hash function 3 | is already implemented in halo2_gadgets, there is no wrapper chip that makes it easy to use in other circuits. 4 | */ 5 | 6 | // This chip adds a set of advice columns to the gadget Chip to store the inputs of the hash 7 | // Furthermore it adds an instance column to store the public expected output of the hash 8 | 9 | use halo2_gadgets::poseidon::{primitives::*, Hash, Pow5Chip, Pow5Config}; 10 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 11 | use std::marker::PhantomData; 12 | 13 | #[derive(Debug, Clone)] 14 | 15 | // WIDTH, RATE and L are const generics for the struct, which represent the width, rate, and number of inputs for the Poseidon hash function, respectively. 16 | // This means they are values that are known at compile time and can be used to specialize the implementation of the struct. 17 | // The actual chip provided by halo2_gadgets is added to the parent Chip. 18 | pub struct PoseidonConfig { 19 | hash_inputs: Vec>, 20 | instance: Column, 21 | pow5_config: Pow5Config, 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | 26 | pub struct PoseidonChip< 27 | F: FieldExt, 28 | S: Spec, 29 | const WIDTH: usize, 30 | const RATE: usize, 31 | const L: usize, 32 | > { 33 | config: PoseidonConfig, 34 | _marker: PhantomData, 35 | } 36 | 37 | impl, const WIDTH: usize, const RATE: usize, const L: usize> 38 | PoseidonChip 39 | { 40 | pub fn construct(config: PoseidonConfig) -> Self { 41 | Self { 42 | config, 43 | _marker: PhantomData, 44 | } 45 | } 46 | 47 | // Configuration of the PoseidonChip 48 | pub fn configure( 49 | meta: &mut ConstraintSystem, 50 | hash_inputs: Vec>, 51 | instance: Column, 52 | ) -> PoseidonConfig { 53 | let partial_sbox = meta.advice_column(); 54 | let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); 55 | let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); 56 | 57 | for i in 0..WIDTH { 58 | meta.enable_equality(hash_inputs[i]); 59 | } 60 | meta.enable_equality(instance); 61 | meta.enable_constant(rc_b[0]); 62 | 63 | let pow5_config = Pow5Chip::configure::( 64 | meta, 65 | hash_inputs.clone().try_into().unwrap(), 66 | partial_sbox, 67 | rc_a.try_into().unwrap(), 68 | rc_b.try_into().unwrap(), 69 | ); 70 | 71 | PoseidonConfig { 72 | hash_inputs, 73 | instance, 74 | pow5_config, 75 | } 76 | } 77 | 78 | pub fn load_private_inputs( 79 | &self, 80 | mut layouter: impl Layouter, 81 | inputs: [Value; L], 82 | ) -> Result<[AssignedCell; L], Error> { 83 | layouter.assign_region( 84 | || "load private inputs", 85 | |mut region| -> Result<[AssignedCell; L], Error> { 86 | let result = inputs 87 | .iter() 88 | .enumerate() 89 | .map(|(i, x)| { 90 | region.assign_advice( 91 | || "private input", 92 | self.config.hash_inputs[i], 93 | 0, 94 | || x.to_owned(), 95 | ) 96 | }) 97 | .collect::>, Error>>(); 98 | Ok(result?.try_into().unwrap()) 99 | }, 100 | ) 101 | } 102 | 103 | // L is the number of inputs to the hash function 104 | // Takes the cells containing the input values of the hash function and return the cell containing the hash output 105 | // It uses the pow5_chip to compute the hash 106 | pub fn hash( 107 | &self, 108 | mut layouter: impl Layouter, 109 | input_cells: &[AssignedCell; L], 110 | ) -> Result, Error> { 111 | // Assign values to word_cells by copying it from the cells passed as input 112 | let hash_input_cells = layouter.assign_region( 113 | || "copy input cells to hash input cells", 114 | |mut region| -> Result<[AssignedCell; L], Error> { 115 | let result = input_cells 116 | .iter() 117 | .enumerate() 118 | .map(|(i, input_cell)| { 119 | input_cell.copy_advice( 120 | || format!("word {}", i), 121 | &mut region, 122 | self.config.hash_inputs[i], 123 | 0, 124 | ) 125 | }) 126 | .collect::>, Error>>(); 127 | Ok(result?.try_into().unwrap()) 128 | }, 129 | )?; 130 | 131 | let pow5_chip = Pow5Chip::construct(self.config.pow5_config.clone()); 132 | 133 | // initialize the hasher 134 | let hasher = Hash::<_, _, S, ConstantLength, WIDTH, RATE>::init( 135 | pow5_chip, 136 | layouter.namespace(|| "hasher"), 137 | )?; 138 | hasher.hash(layouter.namespace(|| "hash"), hash_input_cells) 139 | } 140 | 141 | pub fn expose_public( 142 | &self, 143 | mut layouter: impl Layouter, 144 | cell: &AssignedCell, 145 | row: usize, 146 | ) -> Result<(), Error> { 147 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/chips/poseidon/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod hash_with_instance; 2 | pub mod hash; 3 | pub mod spec; 4 | -------------------------------------------------------------------------------- /src/chips/poseidon/spec.rs: -------------------------------------------------------------------------------- 1 | use halo2_gadgets::poseidon::primitives::*; 2 | use halo2_proofs::{arithmetic::FieldExt}; 3 | use std::marker::PhantomData; 4 | 5 | // P128Pow5T3 is the default Spec provided by the Halo2 Gadget => https://github.com/privacy-scaling-explorations/halo2/blob/main/halo2_gadgets/src/poseidon/primitives/p128pow5t3.rs#L13 6 | // This spec hardcodes the WIDTH and RATE parameters of the hash function to 3 and 2 respectively 7 | // This is problematic because to perform an hash of a input array of length 4, we need the WIDTH parameter to be higher than 3 8 | // Since the WIDTH parameter is used to define the number of hash_inputs column in the PoseidonChip. 9 | // Because of that we need to define a new Spec 10 | // MySpec struct allows us to define the parameters of the Poseidon hash function WIDTH and RATE 11 | #[derive(Debug, Clone, Copy)] 12 | pub struct MySpec{ 13 | _marker: PhantomData 14 | } 15 | 16 | impl Spec for MySpec { 17 | fn full_rounds() -> usize { 18 | 8 19 | } 20 | 21 | fn partial_rounds() -> usize { 22 | 56 23 | } 24 | 25 | fn sbox(val: F) -> F { 26 | val.pow_vartime(&[5]) 27 | } 28 | 29 | fn secure_mds() -> usize { 30 | 0 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/chips/safe_accumulator.rs: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use eth_types::Field; 3 | use num_bigint::BigUint; 4 | use std::char::MAX; 5 | use std::fmt::Debug; 6 | 7 | use super::is_zero::{IsZeroChip, IsZeroConfig}; 8 | use super::utils::{ 9 | decompose_bigInt_to_ubits, f_to_big_uint, range_check, range_check_vec, value_f_to_big_uint, 10 | }; 11 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct SafeAccumulatorConfig { 15 | pub update_value: Column, 16 | pub left_most_inv: Column, 17 | pub add_carries: [Column; ACC_COLS], 18 | pub accumulate: [Column; ACC_COLS], 19 | pub instance: Column, 20 | pub is_zero: IsZeroConfig, 21 | pub selector: [Selector; 2], 22 | } 23 | 24 | #[derive(Debug, Clone)] 25 | pub struct SafeACcumulatorChip { 26 | config: SafeAccumulatorConfig, 27 | } 28 | 29 | impl 30 | SafeACcumulatorChip 31 | { 32 | pub fn construct(config: SafeAccumulatorConfig) -> Self { 33 | Self { config } 34 | } 35 | 36 | pub fn configure( 37 | meta: &mut ConstraintSystem, 38 | update_value: Column, 39 | left_most_inv: Column, 40 | add_carries: [Column; ACC_COLS], 41 | accumulate: [Column; ACC_COLS], 42 | selector: [Selector; 3], 43 | instance: Column, 44 | ) -> SafeAccumulatorConfig { 45 | let bool_selector = selector[0]; 46 | let add_carry_selector = selector[1]; 47 | let overflow_check_selector = selector[2]; 48 | 49 | let is_zero = IsZeroChip::configure( 50 | meta, 51 | |meta| meta.query_selector(overflow_check_selector), 52 | |meta| meta.query_advice(accumulate[0], Rotation::cur()), 53 | left_most_inv, 54 | ); 55 | 56 | // Enable equality on the advice and instance column to enable permutation check 57 | accumulate.map(|col| meta.enable_equality(col)); 58 | add_carries.map(|col| meta.enable_equality(col)); 59 | 60 | meta.enable_equality(instance); 61 | 62 | meta.create_gate("bool constraint", |meta| { 63 | let mut exprs: Vec> = vec![]; 64 | 65 | let s = meta.query_selector(bool_selector); 66 | 67 | for carries in add_carries { 68 | let a = meta.query_advice(carries, Rotation::cur()); 69 | exprs.push(s.clone() * a.clone() * (Expression::Constant(F::from(1)) - a)); 70 | } 71 | 72 | exprs 73 | }); 74 | 75 | meta.create_gate("accumulation constraint", |meta| { 76 | let s_add = meta.query_selector(add_carry_selector); 77 | let s_over = meta.query_selector(overflow_check_selector); 78 | 79 | // value to be added 80 | let value = meta.query_advice(update_value, Rotation::cur()); 81 | 82 | let previous_acc = (0..ACC_COLS) 83 | .map(|i| meta.query_advice(accumulate[i], Rotation::prev())) 84 | .collect::>>(); 85 | let carries_acc = (0..ACC_COLS) 86 | .map(|i| meta.query_advice(add_carries[i], Rotation::cur())) 87 | .collect::>>(); 88 | let updated_acc = (0..ACC_COLS) 89 | .map(|i| meta.query_advice(accumulate[i], Rotation::cur())) 90 | .collect::>>(); 91 | 92 | let shift_next_chunk = Expression::Constant(F::from(1 << MAX_BITS)); 93 | 94 | // Add the value to the rightmost accumulation column. 95 | // 96 | // For example, let's assume that each accumulate column has a maximum value of 0xf. 97 | // For the purpose of explanation, we'll mark the target accumulate column with a cursor represented by ↓↓↓↓↓↓↓↓↓↓↓↓. 98 | // 99 | // If we add the value 0x5 to the decomposition of `previous_acc` which has the values `0xed` in the `accumulate_1` and `accumulate_0` columns. 100 | // ↓↓↓↓↓↓↓↓↓↓↓↓ 101 | // | - | new_value | left_most_inv | add_carries_2 | add_carries_1 | add_carries_0 | accumulate_2 | accumulate_1 | accumulate_0 | add_selector | 102 | // | -- | -- | -- | -- | -- | -- | -- | -- | - | - | 103 | // | previous_acc | | | | | | 0 | 0xe | → 0xd | 0 | 104 | // | updated_acc | → 0x5 | 0 | 0 | 0 | 1 ← | 0 | 0xf | 2 ← | 1 | 105 | // 106 | // In this case, the result is that `0xd + 0x5 = 0x12`. 107 | // The first digit of the result, '0x12', is 0x1, which is assigned to the add_carries_0 column, and the rest of the result, 0x2, is placed in the accumulate_0 column. 108 | // In other words, the sum of the value between the rightmost value of the accumulate columns and the value of new_value is divided into the carry and the remainder. 109 | // 110 | // Note that `←` and `→` are used to distinguish between addition and subtraction in the constraints. 111 | // 112 | let check_add_value_exprs = vec![ 113 | s_add.clone() 114 | * ((value.clone() + previous_acc[ACC_COLS - 1].clone()) 115 | - ((carries_acc[ACC_COLS - 1].clone() * shift_next_chunk.clone()) 116 | + updated_acc[ACC_COLS - 1].clone())), 117 | ]; 118 | let check_range_add_value = vec![s_add.clone() * range_check(value, 1 << MAX_BITS)]; 119 | 120 | // Check with other accumulation columns with carries 121 | // 122 | // In same circuit configuration above, starting with left most of accumulate columns, `accumulate_2`. 123 | // ↓↓↓↓↓↓↓↓↓↓↓↓ 124 | // | - | new_value | left_most_inv | add_carries_2 | add_carries_1 | add_carries_0 | accumulate_2 | accumulate_1 | accumulate_0 | add_selector | 125 | // | -- | -- | -- | -- | -- | -- | -- | -- | - | - | 126 | // | previous_acc | | | | | | 0 ← | 0xe | 0xd | 0 | 127 | // | updated_acc | 0x5 | 0 | → 0 | 0 ← | 1 | → 0 | 0xf | 1 | 1 | 128 | // 129 | // In here, the constraints easliy staisfy like this `(0x0 + 0x0) - (0x0 + (0 * (1 << 4))) = 0`. 130 | // 131 | // In the next iteration, move the cursor to the next accumulate column, `accumulate_1`. 132 | // add `add_accries_0` to previou accumulate number `0xe` then check equality with updated accumulate number `0xf` at `updated_acc` accumulate_1[1] 133 | // ↓↓↓↓↓↓↓↓↓↓↓↓ 134 | // | - | new_value | left_most_inv | add_carries_2 | add_carries_1 | add_carries_0 | accumulate_2 | accumulate_1 | accumulate_0 | add_selector | 135 | // | -- | -- | -- | -- | -- | -- | -- | -- | - | - | 136 | // | previous_acc | | | | | | 0 | 0xe ← | 0xd | 0 | 137 | // | updated_acc | 0x5 | 0 | 0 | → 0 | 1 ← | 0 | → 0xf | 1 | 1 | 138 | // 139 | // In here, the constraint satisfy that like this `(0xe + 0x1) - (0xf + (0 * (1 << 4))) = 0` 140 | // 141 | let check_accumulates_with_carries_expr = (0..ACC_COLS - 1) 142 | .map(|i| { 143 | s_add.clone() 144 | * ((updated_acc[i].clone() 145 | + (carries_acc[i].clone() * shift_next_chunk.clone())) 146 | - (previous_acc[i].clone() + carries_acc[i + 1].clone())) 147 | }) 148 | .collect::>>(); 149 | 150 | let check_overflow_expr = 151 | vec![s_over.clone() * (Expression::Constant(F::one()) - is_zero.expr())]; 152 | 153 | [ 154 | check_add_value_exprs, 155 | check_range_add_value, 156 | check_accumulates_with_carries_expr, 157 | check_overflow_expr, 158 | range_check_vec(&s_over, previous_acc, 1 << MAX_BITS), 159 | range_check_vec(&s_over, updated_acc, 1 << MAX_BITS), 160 | ] 161 | .concat() 162 | }); 163 | 164 | SafeAccumulatorConfig { 165 | update_value, 166 | left_most_inv, 167 | add_carries, 168 | accumulate, 169 | instance, 170 | selector: [add_carry_selector, overflow_check_selector], 171 | is_zero, 172 | } 173 | } 174 | 175 | pub fn assign( 176 | &self, 177 | mut layouter: impl Layouter, 178 | offset: usize, 179 | update_value: Value, 180 | accumulated_values: [Value; ACC_COLS], 181 | ) -> Result<(ArrayVec, ACC_COLS>, [Value; ACC_COLS]), Error> { 182 | let mut sum = F::zero(); 183 | update_value.as_ref().map(|f| sum = sum.add(f)); 184 | 185 | let is_zero_chip = IsZeroChip::construct(self.config.is_zero.clone()); 186 | layouter.assign_region( 187 | || "calculate accumulates", 188 | |mut region| { 189 | // enable selector 190 | self.config.selector[0].enable(&mut region, offset + 1)?; 191 | self.config.selector[1].enable(&mut region, offset + 1)?; 192 | 193 | let mut sum_big_uint = f_to_big_uint(&sum); 194 | 195 | // Assign new value to the cell inside the region 196 | region.assign_advice( 197 | || "assign value for adding", 198 | self.config.update_value, 199 | 1, 200 | || update_value, 201 | )?; 202 | 203 | // Assign previous accumulation 204 | for (idx, val) in accumulated_values.iter().enumerate() { 205 | let _ = region.assign_advice( 206 | || format!("assign previous accumulate[{}] col", idx), 207 | self.config.accumulate[idx], 208 | 0, 209 | || *val, 210 | )?; 211 | } 212 | 213 | // Calculates updated accumulate value 214 | for (idx, acc_val) in accumulated_values.iter().enumerate().rev() { 215 | let shift_bits = MAX_BITS as usize * ((ACC_COLS - 1) - idx); 216 | sum_big_uint += value_f_to_big_uint(*acc_val) << shift_bits; 217 | 218 | // calculate carried sum and assign 219 | // if `sum_big_uint` is higher than `1 << shift_bits` assign carried value 1 220 | let mut carry_flag = F::zero(); 221 | let shift_mask = BigUint::new(vec![1 << (MAX_BITS as usize + shift_bits)]); 222 | if sum_big_uint >= shift_mask && idx > 0 { 223 | carry_flag = F::one(); 224 | } 225 | 226 | let _ = region.assign_advice( 227 | || format!("assign carried value at [{}]", idx), 228 | self.config.add_carries[idx], 229 | offset + 1, 230 | || Value::known(carry_flag.clone()), 231 | ); 232 | } 233 | 234 | // decomposed result is little-endian, so the vector is opposite to the order of the columns 235 | let decomposed_sum_big_uint: Vec = 236 | decompose_bigInt_to_ubits(&sum_big_uint, ACC_COLS, MAX_BITS as usize); 237 | 238 | let mut updated_accumulates: [Value; ACC_COLS] = 239 | [Value::known(F::zero()); ACC_COLS]; 240 | let mut assigned_cells: ArrayVec, ACC_COLS> = ArrayVec::new(); 241 | let left_most_idx = ACC_COLS - 1; 242 | for (i, v) in decomposed_sum_big_uint.iter().enumerate() { 243 | // a value in left most columns is overflow 244 | if i == left_most_idx { 245 | is_zero_chip.assign(&mut region, 1, Value::known(v.clone()))?; 246 | } 247 | let cell = region.assign_advice( 248 | || format!("assign updated value to accumulated[{}]", i), 249 | self.config.accumulate[left_most_idx - i], 250 | offset + 1, 251 | || Value::known(v.clone()), 252 | ); 253 | assigned_cells.push(cell.unwrap()); 254 | updated_accumulates[left_most_idx - i] = Value::known(v.clone()); 255 | } 256 | // query assgiend cells via region 257 | Ok((assigned_cells, updated_accumulates)) 258 | }, 259 | ) 260 | } 261 | 262 | // Enforce permutation check between b & cell and instance column 263 | pub fn expose_public( 264 | &self, 265 | mut layouter: impl Layouter, 266 | cell: &AssignedCell, 267 | row: usize, 268 | ) -> Result<(), Error> { 269 | layouter.constrain_instance(cell.cell(), self.config.instance, row) 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/chips/util.rs: -------------------------------------------------------------------------------- 1 | //! Utility traits, functions used in the crate. 2 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 3 | 4 | /// Returns the sum of the passed in cells 5 | pub mod sum { 6 | use crate::chips::util::Expr; 7 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 8 | 9 | /// Returns an expression for the sum of the list of expressions. 10 | pub fn expr, I: IntoIterator>(inputs: I) -> Expression { 11 | inputs 12 | .into_iter() 13 | .fold(0.expr(), |acc, input| acc + input.expr()) 14 | } 15 | 16 | /// Returns the sum of the given list of values within the field. 17 | pub fn value(values: &[u8]) -> F { 18 | values 19 | .iter() 20 | .fold(F::zero(), |acc, value| acc + F::from(*value as u64)) 21 | } 22 | } 23 | 24 | /// Returns `1` when `expr[0] && expr[1] && ... == 1`, and returns `0` 25 | /// otherwise. Inputs need to be boolean 26 | pub mod and { 27 | use crate::chips::util::Expr; 28 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 29 | 30 | /// Returns an expression that evaluates to 1 only if all the expressions in 31 | /// the given list are 1, else returns 0. 32 | pub fn expr, I: IntoIterator>(inputs: I) -> Expression { 33 | inputs 34 | .into_iter() 35 | .fold(1.expr(), |acc, input| acc * input.expr()) 36 | } 37 | 38 | /// Returns the product of all given values. 39 | pub fn value(inputs: Vec) -> F { 40 | inputs.iter().fold(F::one(), |acc, input| acc * input) 41 | } 42 | } 43 | 44 | /// Returns `1` when `expr[0] || expr[1] || ... == 1`, and returns `0` 45 | /// otherwise. Inputs need to be boolean 46 | pub mod or { 47 | use super::{and, not}; 48 | use crate::chips::util::Expr; 49 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 50 | 51 | /// Returns an expression that evaluates to 1 if any expression in the given 52 | /// list is 1. Returns 0 if all the expressions were 0. 53 | pub fn expr, I: IntoIterator>(inputs: I) -> Expression { 54 | not::expr(and::expr(inputs.into_iter().map(not::expr))) 55 | } 56 | 57 | /// Returns the value after passing all given values through the OR gate. 58 | pub fn value(inputs: Vec) -> F { 59 | not::value(and::value(inputs.into_iter().map(not::value).collect())) 60 | } 61 | } 62 | 63 | /// Returns `1` when `b == 0`, and returns `0` otherwise. 64 | /// `b` needs to be boolean 65 | pub mod not { 66 | use crate::chips::util::Expr; 67 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 68 | 69 | /// Returns an expression that represents the NOT of the given expression. 70 | pub fn expr>(b: E) -> Expression { 71 | 1.expr() - b.expr() 72 | } 73 | 74 | /// Returns a value that represents the NOT of the given value. 75 | pub fn value(b: F) -> F { 76 | F::one() - b 77 | } 78 | } 79 | 80 | /// Returns `a ^ b`. 81 | /// `a` and `b` needs to be boolean 82 | pub mod xor { 83 | use crate::chips::util::Expr; 84 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 85 | 86 | /// Returns an expression that represents the XOR of the given expression. 87 | pub fn expr>(a: E, b: E) -> Expression { 88 | a.expr() + b.expr() - 2.expr() * a.expr() * b.expr() 89 | } 90 | 91 | /// Returns a value that represents the XOR of the given value. 92 | pub fn value(a: F, b: F) -> F { 93 | a + b - F::from(2u64) * a * b 94 | } 95 | } 96 | 97 | /// Returns `when_true` when `selector == 1`, and returns `when_false` when 98 | /// `selector == 0`. `selector` needs to be boolean. 99 | pub mod select { 100 | use crate::chips::util::Expr; 101 | use halo2_proofs::{arithmetic::FieldExt, plonk::Expression}; 102 | 103 | /// Returns the `when_true` expression when the selector is true, else 104 | /// returns the `when_false` expression. 105 | pub fn expr( 106 | selector: Expression, 107 | when_true: Expression, 108 | when_false: Expression, 109 | ) -> Expression { 110 | selector.clone() * when_true + (1.expr() - selector) * when_false 111 | } 112 | 113 | /// Returns the `when_true` value when the selector is true, else returns 114 | /// the `when_false` value. 115 | pub fn value(selector: F, when_true: F, when_false: F) -> F { 116 | selector * when_true + (F::one() - selector) * when_false 117 | } 118 | 119 | /// Returns the `when_true` word when selector is true, else returns the 120 | /// `when_false` word. 121 | pub fn value_word( 122 | selector: F, 123 | when_true: [u8; 32], 124 | when_false: [u8; 32], 125 | ) -> [u8; 32] { 126 | if selector == F::one() { 127 | when_true 128 | } else { 129 | when_false 130 | } 131 | } 132 | } 133 | 134 | /// Trait that implements functionality to get a constant expression from 135 | /// commonly used types. 136 | pub trait Expr { 137 | /// Returns an expression for the type. 138 | fn expr(&self) -> Expression; 139 | } 140 | 141 | /// Implementation trait `Expr` for type able to be casted to u64 142 | #[macro_export] 143 | macro_rules! impl_expr { 144 | ($type:ty) => { 145 | impl $crate::chips::util::Expr for $type { 146 | #[inline] 147 | fn expr(&self) -> Expression { 148 | Expression::Constant(F::from(*self as u64)) 149 | } 150 | } 151 | }; 152 | ($type:ty, $method:path) => { 153 | impl $crate::chips::util::Expr for $type { 154 | #[inline] 155 | fn expr(&self) -> Expression { 156 | Expression::Constant(F::from($method(self) as u64)) 157 | } 158 | } 159 | }; 160 | } 161 | 162 | impl Expr for Expression { 163 | #[inline] 164 | fn expr(&self) -> Expression { 165 | self.clone() 166 | } 167 | } 168 | 169 | impl Expr for &Expression { 170 | #[inline] 171 | fn expr(&self) -> Expression { 172 | (*self).clone() 173 | } 174 | } 175 | 176 | impl Expr for i32 { 177 | #[inline] 178 | fn expr(&self) -> Expression { 179 | Expression::Constant( 180 | F::from(self.unsigned_abs() as u64) 181 | * if self.is_negative() { 182 | -F::one() 183 | } else { 184 | F::one() 185 | }, 186 | ) 187 | } 188 | } 189 | 190 | /// Given a bytes-representation of an expression, it computes and returns the 191 | /// single expression. 192 | pub fn expr_from_bytes>(bytes: &[E]) -> Expression { 193 | let mut value = 0.expr(); 194 | let mut multiplier = F::one(); 195 | for byte in bytes.iter() { 196 | value = value + byte.expr() * multiplier; 197 | multiplier *= F::from(256); 198 | } 199 | value 200 | } 201 | 202 | /// Returns 2**by as FieldExt 203 | pub fn pow_of_two(by: usize) -> F { 204 | F::from(2).pow(&[by as u64, 0, 0, 0]) 205 | } -------------------------------------------------------------------------------- /src/chips/utils.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | 3 | use halo2_proofs::circuit::*; 4 | use halo2_proofs::plonk::Expression; 5 | use num_bigint::BigUint; 6 | 7 | fn parse_hex(hex_asm: &str) -> Vec { 8 | let mut hex_bytes = hex_asm 9 | .as_bytes() 10 | .iter() 11 | .filter_map(|b| match b { 12 | b'0'..=b'9' => Some(b - b'0'), 13 | b'a'..=b'f' => Some(b - b'a' + 10), 14 | b'A'..=b'F' => Some(b - b'A' + 10), 15 | _ => None, 16 | }) 17 | .fuse(); 18 | 19 | let mut bytes = Vec::new(); 20 | while let (Some(h), Some(l)) = (hex_bytes.next(), hex_bytes.next()) { 21 | bytes.push(h << 4 | l) 22 | } 23 | bytes 24 | } 25 | 26 | pub fn value_f_to_big_uint(v: Value) -> BigUint { 27 | let mut sum = F::zero(); 28 | v.as_ref().map(|f| sum = sum.add(f)); 29 | to_uint(sum) 30 | } 31 | 32 | pub fn f_to_big_uint(value: &F) -> BigUint { 33 | let mut sum = F::zero(); 34 | sum = sum.add(value); 35 | to_uint(sum) 36 | } 37 | 38 | pub fn f_to_nbits(value: &F) -> (F, F) { 39 | let max_bits = F::from(1 << N); 40 | let mut remains = value.clone(); 41 | let mut accumulator = F::zero(); 42 | while remains >= max_bits { 43 | remains = remains.sub(&max_bits); 44 | accumulator = accumulator.add(&F::one()); 45 | } 46 | (accumulator, remains) 47 | } 48 | 49 | pub fn add_carry( 50 | value: Value, 51 | hi: AssignedCell, 52 | lo: AssignedCell, 53 | ) -> (F, F) { 54 | let mut sum = F::zero(); 55 | 56 | // sum of all values 57 | value.as_ref().map(|f| sum = sum.add(f)); 58 | hi.value() 59 | .map(|f| sum = sum.add(&f.mul(&F::from(1 << MAX_BITS)))); 60 | lo.value().map(|f| sum = sum.add(f)); 61 | 62 | // Iterate sum of all 63 | f_to_nbits::(&sum) 64 | } 65 | 66 | fn to_uint(sum: F) -> BigUint { 67 | let sum_str = format!("{:?}", sum); 68 | let (_, splited_sum_str) = sum_str.split_at(2); // remove '0x' 69 | 70 | BigUint::from_bytes_be(parse_hex(splited_sum_str).as_slice()) 71 | } 72 | 73 | pub fn range_check(value: Expression, range: usize) -> Expression { 74 | (1..range).fold(value.clone(), |acc, i| { 75 | acc * (Expression::Constant(F::from(i as u64)) - value.clone()) 76 | }) 77 | } 78 | 79 | pub fn range_check_vec( 80 | selector: &Expression, 81 | value_vec: Vec>, 82 | range: usize, 83 | ) -> Vec> { 84 | let mut exprs: Vec> = vec![]; 85 | for w in value_vec { 86 | let w_expr = range_check(w, range); 87 | exprs.push(selector.clone() * w_expr); 88 | } 89 | exprs 90 | } 91 | 92 | pub fn decompose_bigInt_to_ubits( 93 | e: &BigUint, 94 | number_of_limbs: usize, 95 | bit_len: usize, 96 | ) -> Vec { 97 | debug_assert!(bit_len <= 64); 98 | 99 | let mut e = e.iter_u64_digits(); 100 | let mask: u64 = (1u64 << bit_len) - 1u64; 101 | let mut u64_digit = e.next().unwrap_or(0); 102 | let mut rem = 64; 103 | (0..number_of_limbs) 104 | .map(|_| match rem.cmp(&bit_len) { 105 | core::cmp::Ordering::Greater => { 106 | let limb = u64_digit & mask; 107 | u64_digit >>= bit_len; 108 | rem -= bit_len; 109 | F::from(limb) 110 | } 111 | core::cmp::Ordering::Equal => { 112 | let limb = u64_digit & mask; 113 | u64_digit = e.next().unwrap_or(0); 114 | rem = 64; 115 | F::from(limb) 116 | } 117 | core::cmp::Ordering::Less => { 118 | let mut limb = u64_digit; 119 | u64_digit = e.next().unwrap_or(0); 120 | limb |= (u64_digit & ((1 << (bit_len - rem)) - 1)) << rem; // * 121 | u64_digit >>= bit_len - rem; 122 | rem += 64 - bit_len; 123 | F::from(limb) 124 | } 125 | }) 126 | .collect() 127 | } 128 | -------------------------------------------------------------------------------- /src/circuits.rs: -------------------------------------------------------------------------------- 1 | pub mod hash_v1; 2 | pub mod hash_v2; 3 | pub mod inclusion_check; 4 | pub mod inclusion_check_v2; 5 | pub mod merkle_v1; 6 | pub mod merkle_v2; 7 | pub mod merkle_v3; 8 | pub mod poseidon; 9 | pub mod merkle_sum_tree; 10 | pub mod utils; 11 | pub mod less_than; 12 | pub mod less_than_v2; 13 | pub mod less_than_v3; 14 | pub mod add_carry_v1; 15 | pub mod add_carry_v2; 16 | pub mod overflow_check; 17 | pub mod overflow_check_v2; 18 | pub mod safe_accumulator; 19 | -------------------------------------------------------------------------------- /src/circuits/add_carry_v1.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use halo2_proofs::{circuit::*, plonk::*}; 3 | 4 | use super::super::chips::add_carry_v1::{AddCarryChip, AddCarryConfig}; 5 | 6 | #[derive(Default)] 7 | struct AddCarryCircuit { 8 | pub a: Vec>, 9 | } 10 | 11 | impl Circuit for AddCarryCircuit { 12 | type Config = AddCarryConfig; 13 | type FloorPlanner = SimpleFloorPlanner; 14 | 15 | fn without_witnesses(&self) -> Self { 16 | Self::default() 17 | } 18 | 19 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 20 | let col_a = meta.advice_column(); 21 | let col_b = meta.advice_column(); 22 | let col_c = meta.advice_column(); 23 | let constant = meta.fixed_column(); 24 | let carry_selector = meta.complex_selector(); 25 | let instance = meta.instance_column(); 26 | 27 | AddCarryChip::configure( 28 | meta, 29 | [col_a, col_b, col_c], 30 | constant, 31 | carry_selector, 32 | instance, 33 | ) 34 | } 35 | 36 | fn synthesize( 37 | &self, 38 | config: Self::Config, 39 | mut layouter: impl Layouter, 40 | ) -> Result<(), Error> { 41 | let chip = AddCarryChip::construct(config); 42 | 43 | let (mut prev_b, mut prev_c) = 44 | chip.assign_first_row(layouter.namespace(|| "load first row"))?; 45 | 46 | for (i, a) in self.a.iter().enumerate() { 47 | let (b, c) = chip.assign_advice_row( 48 | layouter.namespace(|| format!("load row {}", i)), 49 | *a, 50 | prev_b, 51 | prev_c, 52 | )?; 53 | prev_b = b; 54 | prev_c = c; 55 | } 56 | 57 | // check computation result 58 | chip.expose_public(layouter.namespace(|| "carry check"), &prev_b, 0)?; 59 | chip.expose_public(layouter.namespace(|| "remain check"), &prev_c, 1)?; 60 | Ok(()) 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::AddCarryCircuit; 67 | use halo2_proofs::{ 68 | circuit::Value, 69 | dev::{FailureLocation, MockProver, VerifyFailure}, 70 | halo2curves::bn256::Fr as Fp, 71 | plonk::Any, 72 | }; 73 | 74 | #[test] 75 | fn test_carry_1() { 76 | let k = 4; 77 | 78 | // a: new value 79 | let a = vec![ 80 | Value::known(Fp::from((1 << 16) - 1)), 81 | Value::known(Fp::from(1)), 82 | ]; 83 | let public_inputs = vec![Fp::from(1), Fp::from(0)]; // initial accumulated values 84 | 85 | let circuit = AddCarryCircuit:: { a }; 86 | let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); 87 | prover.assert_satisfied(); 88 | assert_eq!(prover.verify(), Ok(())); 89 | } 90 | 91 | #[test] 92 | fn test_carry_2() { 93 | let k = 4; 94 | 95 | // now a[1] is 2, which will cause carry lo 96 | let a = vec![ 97 | Value::known(Fp::from((1 << 16) - 1)), 98 | Value::known(Fp::from(2)), 99 | ]; 100 | let mut public_inputs = vec![Fp::from(1), Fp::from(0)]; // initial accumulated values 101 | 102 | let circuit = AddCarryCircuit { a }; 103 | let invalid_prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); 104 | assert_eq!( 105 | invalid_prover.verify(), 106 | Err(vec![ 107 | VerifyFailure::Permutation { 108 | column: (Any::advice(), 2).into(), 109 | location: FailureLocation::InRegion { 110 | region: (2, "adivce row for accumulating").into(), 111 | offset: 1 112 | } 113 | }, 114 | VerifyFailure::Permutation { 115 | column: (Any::Instance, 0).into(), 116 | location: FailureLocation::OutsideRegion { row: 1 } 117 | }, 118 | ]) 119 | ); 120 | 121 | // Result should be 1, 1 122 | public_inputs = vec![Fp::from(1), Fp::from(1)]; 123 | let valid_prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); 124 | valid_prover.assert_satisfied(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/circuits/add_carry_v2.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | 3 | use halo2_proofs::{circuit::*, plonk::*}; 4 | 5 | use super::super::chips::add_carry_v2::{AddCarryV2Chip, AddCarryV2Config}; 6 | 7 | #[derive(Default)] 8 | struct AddCarryCircuit { 9 | pub a: Value, 10 | } 11 | 12 | impl Circuit for AddCarryCircuit { 13 | type Config = AddCarryV2Config; 14 | type FloorPlanner = SimpleFloorPlanner; 15 | 16 | fn without_witnesses(&self) -> Self { 17 | Self::default() 18 | } 19 | 20 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 21 | let col_a = meta.advice_column(); 22 | let col_b_inv = meta.advice_column(); 23 | let col_b = meta.advice_column(); 24 | let col_c = meta.advice_column(); 25 | let carry_selector = meta.complex_selector(); 26 | let instance = meta.instance_column(); 27 | 28 | AddCarryV2Chip::configure(meta, [col_a, col_b_inv, col_b, col_c], carry_selector, instance) 29 | } 30 | 31 | fn synthesize( 32 | &self, 33 | config: Self::Config, 34 | mut layouter: impl Layouter, 35 | ) -> Result<(), Error> { 36 | let chip = AddCarryV2Chip::construct(config); 37 | 38 | let (prev_b, prev_c) = chip.assign_first_row(layouter.namespace(|| "load first row"))?; 39 | let (b, c) = 40 | chip.assign_advice_row(layouter.namespace(|| "load row"), self.a, prev_b.clone(), prev_c.clone())?; 41 | 42 | // check computation result 43 | chip.expose_public(layouter.namespace(|| "carry check"), &b, 2)?; 44 | chip.expose_public(layouter.namespace(|| "remain check"), &c, 3)?; 45 | Ok(()) 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use super::AddCarryCircuit; 52 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::bn256::Fr as Fp}; 53 | #[test] 54 | fn test_carry_2() { 55 | let k = 4; 56 | 57 | // a: new value 58 | // public_input[0]: x * 2^16 59 | // public_input[1]: x * 2^0 60 | // 61 | let a = Value::known(Fp::from(1)); 62 | let public_inputs = vec![Fp::from(0), Fp::from((1 << 16) - 2), Fp::from(0), Fp::from((1 << 16) - 1)]; // initial accumulated values 63 | 64 | let circuit = AddCarryCircuit { a }; 65 | let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); 66 | prover.assert_satisfied(); 67 | assert_eq!(prover.verify(), Ok(())); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/circuits/hash_v1.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::hash_v1::{Hash1Chip, Hash1Config}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | struct Hash1Circuit { 7 | pub a: Value, 8 | } 9 | 10 | impl Circuit for Hash1Circuit { 11 | type Config = Hash1Config; 12 | type FloorPlanner = SimpleFloorPlanner; 13 | 14 | fn without_witnesses(&self) -> Self { 15 | Self::default() 16 | } 17 | 18 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 19 | let col_a = meta.advice_column(); 20 | let col_b = meta.advice_column(); 21 | let instance = meta.instance_column(); 22 | 23 | Hash1Chip::configure(meta, [col_a, col_b], instance) 24 | } 25 | 26 | fn synthesize( 27 | &self, 28 | config: Self::Config, 29 | mut layouter: impl Layouter, 30 | ) -> Result<(), Error> { 31 | let chip = Hash1Chip::construct(config); 32 | let b = chip.assign_advice_row(layouter.namespace(|| "load row"), self.a)?; 33 | chip.expose_public(layouter.namespace(|| "hash output check"), &b, 0)?; 34 | Ok(()) 35 | } 36 | } 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use super::Hash1Circuit; 41 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 42 | #[test] 43 | fn test_hash_1() { 44 | let k = 4; 45 | let a = Value::known(Fp::from(2)); 46 | let public_inputs = vec![Fp::from(4)]; 47 | let circuit = Hash1Circuit { a }; 48 | let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); 49 | assert_eq!(prover.verify(), Ok(())); 50 | 51 | let public_inputs = vec![Fp::from(8)]; 52 | let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); 53 | assert!(prover.verify().is_err()); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/circuits/hash_v2.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::hash_v2::{Hash2Chip, Hash2Config}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | struct Hash2Circuit { 7 | pub a: Value, 8 | pub b: Value, 9 | } 10 | 11 | impl Circuit for Hash2Circuit { 12 | type Config = Hash2Config; 13 | type FloorPlanner = SimpleFloorPlanner; 14 | 15 | fn without_witnesses(&self) -> Self { 16 | Self::default() 17 | } 18 | 19 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 20 | let col_a = meta.advice_column(); 21 | let col_b = meta.advice_column(); 22 | let col_c = meta.advice_column(); 23 | let instance = meta.instance_column(); 24 | 25 | Hash2Chip::configure(meta, [col_a, col_b, col_c], instance) 26 | } 27 | 28 | fn synthesize( 29 | &self, 30 | config: Self::Config, 31 | mut layouter: impl Layouter, 32 | ) -> Result<(), Error> { 33 | let chip = Hash2Chip::construct(config); 34 | let a = chip.load_private(layouter.namespace(|| "load a"), self.a)?; 35 | let b = chip.load_private(layouter.namespace(|| "load b"), self.b)?; 36 | let c = chip.hash(layouter.namespace(|| "load row"), a, b)?; 37 | chip.expose_public(layouter.namespace(|| "hash output check"), &c, 0)?; 38 | Ok(()) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | use super::Hash2Circuit; 45 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 46 | 47 | #[test] 48 | fn test_hash_2() { 49 | let k = 4; 50 | 51 | // successful case 52 | let a = Value::known(Fp::from(2)); 53 | let b = Value::known(Fp::from(7)); 54 | let public_inputs = vec![Fp::from(9)]; 55 | let circuit = Hash2Circuit { a, b }; 56 | let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); 57 | assert_eq!(prover.verify(), Ok(())); 58 | 59 | // failure case 60 | let public_inputs = vec![Fp::from(8)]; 61 | let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); 62 | assert!(prover.verify().is_err()); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/circuits/inclusion_check.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::inclusion_check::{InclusionCheckChip, InclusionCheckConfig}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | 7 | // define circuit struct using array of usernames and balances 8 | struct MyCircuit { 9 | pub usernames: [Value; 10], 10 | pub balances: [Value; 10], 11 | pub inclusion_index: u8, 12 | } 13 | 14 | impl Circuit for MyCircuit { 15 | type Config = InclusionCheckConfig; 16 | type FloorPlanner = SimpleFloorPlanner; 17 | 18 | fn without_witnesses(&self) -> Self { 19 | Self::default() 20 | } 21 | 22 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 23 | let col_username = meta.advice_column(); 24 | let col_balance = meta.advice_column(); 25 | let instance = meta.instance_column(); 26 | 27 | InclusionCheckChip::configure(meta, [col_username, col_balance], instance) 28 | } 29 | 30 | fn synthesize( 31 | &self, 32 | config: Self::Config, 33 | mut layouter: impl Layouter, 34 | ) -> Result<(), Error> { 35 | // We create a new instance of chip using the config passed as input 36 | let chip = InclusionCheckChip::::construct(config); 37 | 38 | // loop over the usernames array and assign the rows 39 | for _i in 0..self.usernames.len() { 40 | // if row is equal to the inclusion index, assign the value using the assign_inclusion_check_row function 41 | // else assign the value using the assign_generic_row function 42 | if (_i as u8) == self.inclusion_index { 43 | // extract username and balances cell from here! 44 | let (username_cell, balance_cell) = chip.assign_inclusion_check_row( 45 | layouter.namespace(|| "inclusion row"), 46 | self.usernames[_i], 47 | self.balances[_i], 48 | )?; 49 | 50 | // expose the public values 51 | chip.expose_public( 52 | layouter.namespace(|| "expose public"), 53 | &username_cell, 54 | &balance_cell, 55 | )?; 56 | } else { 57 | chip.assign_generic_row( 58 | layouter.namespace(|| "generic row"), 59 | self.usernames[_i], 60 | self.balances[_i], 61 | )?; 62 | } 63 | } 64 | Ok(()) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | 71 | use super::MyCircuit; 72 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 73 | #[test] 74 | fn test_inclusion_check_1() { 75 | let k = 4; 76 | 77 | // initate usernames and balances array 78 | let mut usernames: [Value; 10] = [Value::default(); 10]; 79 | let mut balances: [Value; 10] = [Value::default(); 10]; 80 | 81 | // add 10 values to the username array and balances array 82 | for i in 0..10 { 83 | usernames[i] = Value::known(Fp::from(i as u64)); 84 | balances[i] = Value::known(Fp::from(i as u64) * Fp::from(2)); 85 | } 86 | 87 | // Table is 88 | // username | balance 89 | // 0 | 0 90 | // 1 | 2 91 | // 2 | 4 92 | // 3 | 6 93 | // 4 | 8 94 | // 5 | 10 95 | // 6 | 12 96 | // 7 | 14 97 | // 8 | 16 98 | // 9 | 18 99 | 100 | let circuit = MyCircuit:: { 101 | usernames, 102 | balances, 103 | inclusion_index: 7, 104 | }; 105 | 106 | // Test 1 - Inclusion check on a existing entry for the corresponding inclusion_index 107 | let public_input_valid = vec![Fp::from(7), Fp::from(14)]; 108 | let prover = MockProver::run(k, &circuit, vec![public_input_valid]).unwrap(); 109 | prover.assert_satisfied(); 110 | 111 | // Test 2 - Inclusion check on a existing entry but not for the corresponding inclusion_index 112 | let public_input_invalid = vec![Fp::from(8), Fp::from(16)]; 113 | let prover = MockProver::run(k, &circuit, vec![public_input_invalid]).unwrap(); 114 | assert!(prover.verify().is_err()); 115 | 116 | // Test 3 - Inclusion check on a non-existing entry 117 | let public_input_invalid2 = vec![Fp::from(10), Fp::from(20)]; 118 | let prover = MockProver::run(k, &circuit, vec![public_input_invalid2]).unwrap(); 119 | assert!(prover.verify().is_err()); 120 | } 121 | } 122 | 123 | #[cfg(feature = "dev-graph")] 124 | #[test] 125 | fn print_inclusion_check() { 126 | use halo2_proofs::halo2curves::pasta::Fp; 127 | use plotters::prelude::*; 128 | 129 | let root = 130 | BitMapBackend::new("prints/inclusion-check-1-layout.png", (1024, 3096)).into_drawing_area(); 131 | root.fill(&WHITE).unwrap(); 132 | let root = root 133 | .titled("Inclusion Check 1 Layout", ("sans-serif", 60)) 134 | .unwrap(); 135 | 136 | let mut usernames: [Value; 10] = [Value::known(Fp::from(0)); 10]; 137 | let mut balances: [Value; 10] = [Value::known(Fp::from(0)); 10]; 138 | 139 | let circuit = MyCircuit:: { 140 | usernames, 141 | balances, 142 | inclusion_index: 2, 143 | }; 144 | 145 | halo2_proofs::dev::CircuitLayout::default() 146 | .render(3, &circuit, &root) 147 | .unwrap(); 148 | } 149 | -------------------------------------------------------------------------------- /src/circuits/inclusion_check_v2.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::inclusion_check_v2::{InclusionCheckV2Chip, InclusionCheckV2Config}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | // define circuit struct using array of usernames and balances 7 | struct MyCircuit { 8 | pub usernames: [Value; 10], 9 | pub balances: [Value; 10], 10 | pub inclusion_index: u8, 11 | pub constant: F, 12 | } 13 | 14 | impl Circuit for MyCircuit { 15 | type Config = InclusionCheckV2Config; 16 | type FloorPlanner = SimpleFloorPlanner; 17 | 18 | fn without_witnesses(&self) -> Self { 19 | Self::default() 20 | } 21 | 22 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 23 | let col_username = meta.advice_column(); 24 | let col_balance = meta.advice_column(); 25 | let col_username_accumulator = meta.advice_column(); 26 | let col_balance_accumulator = meta.advice_column(); 27 | let instance = meta.instance_column(); 28 | 29 | // Create a fixed column to load constants. 30 | let constant = meta.fixed_column(); 31 | 32 | 33 | InclusionCheckV2Chip::configure( 34 | meta, 35 | [ 36 | col_username, 37 | col_balance, 38 | col_username_accumulator, 39 | col_balance_accumulator, 40 | ], 41 | instance, 42 | constant 43 | ) 44 | } 45 | 46 | fn synthesize( 47 | &self, 48 | config: Self::Config, 49 | mut layouter: impl Layouter, 50 | ) -> Result<(), Error> { 51 | // We create a new instance of chip using the config passed as input 52 | let chip = InclusionCheckV2Chip::::construct(config); 53 | 54 | let (user_acc_last_row_cell, balance_acc_last_row_cell) = chip.assign_rows( 55 | layouter.namespace(|| "init table"), 56 | self.usernames, 57 | self.balances, 58 | self.constant, 59 | self.inclusion_index, 60 | )?; 61 | 62 | chip.expose_public( 63 | layouter.namespace(|| "expose public"), 64 | &user_acc_last_row_cell, 65 | 0, 66 | )?; 67 | chip.expose_public( 68 | layouter.namespace(|| "expose public"), 69 | &balance_acc_last_row_cell, 70 | 1, 71 | )?; 72 | 73 | Ok(()) 74 | } 75 | } 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | 80 | use super::MyCircuit; 81 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 82 | 83 | #[test] 84 | fn test_inclusion_check_2() { 85 | let k = 5; 86 | 87 | // initate usernames and balances array 88 | let mut usernames: [Value; 10] = [Value::default(); 10]; 89 | let mut balances: [Value; 10] = [Value::default(); 10]; 90 | 91 | // add 10 values to the username array and balances array 92 | for i in 0..10 { 93 | usernames[i] = Value::known(Fp::from(i as u64)); 94 | balances[i] = Value::known(Fp::from(i as u64) * Fp::from(2)); 95 | } 96 | 97 | // Table is 98 | // username | balance 99 | // 0 | 0 100 | // 1 | 2 101 | // 2 | 4 102 | // 3 | 6 103 | // 4 | 8 104 | // 5 | 10 105 | // 6 | 12 106 | // 7 | 14 107 | // 8 | 16 108 | // 9 | 18 109 | 110 | let constant = Fp::from(0); 111 | 112 | let circuit = MyCircuit:: { 113 | usernames, 114 | balances, 115 | inclusion_index: 7, 116 | constant, 117 | }; 118 | 119 | // Test 1 - Inclusion check on a existing entry for the corresponding inclusion_index 120 | let public_input_valid = vec![Fp::from(7), Fp::from(14)]; 121 | let prover = MockProver::run(k, &circuit, vec![public_input_valid]).unwrap(); 122 | prover.assert_satisfied(); 123 | 124 | // Test 2 - Inclusion check on a existing entry but not for the corresponding inclusion_index 125 | let public_input_invalid = vec![Fp::from(8), Fp::from(16)]; 126 | let prover = MockProver::run(k, &circuit, vec![public_input_invalid]).unwrap(); 127 | assert!(prover.verify().is_err()); 128 | 129 | // Test 3 - Inclusion check on a non-existing entry 130 | let public_input_invalid2 = vec![Fp::from(10), Fp::from(20)]; 131 | let prover = MockProver::run(k, &circuit, vec![public_input_invalid2]).unwrap(); 132 | assert!(prover.verify().is_err()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/circuits/less_than.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::less_than::{LessThanChip, LessThanConfig}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | 7 | // define circuit struct using array of usernames and balances 8 | struct MyCircuit { 9 | pub input: Value, 10 | } 11 | 12 | impl Circuit for MyCircuit { 13 | type Config = LessThanConfig; 14 | type FloorPlanner = SimpleFloorPlanner; 15 | 16 | fn without_witnesses(&self) -> Self { 17 | Self::default() 18 | } 19 | 20 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 21 | let input = meta.advice_column(); 22 | let table = meta.instance_column(); 23 | 24 | LessThanChip::configure(meta, input, table) 25 | } 26 | 27 | fn synthesize( 28 | &self, 29 | config: Self::Config, 30 | mut layouter: impl Layouter, 31 | ) -> Result<(), Error> { 32 | // We create a new instance of chip using the config passed as input 33 | let chip = LessThanChip::::construct(config); 34 | 35 | // assign value to the chip 36 | let _ = chip.assign(layouter.namespace(|| "init table"), self.input); 37 | 38 | Ok(()) 39 | } 40 | } 41 | 42 | #[cfg(test)] 43 | mod tests { 44 | 45 | use super::MyCircuit; 46 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 47 | #[test] 48 | fn test_less_than_2() { 49 | let k = 10; 50 | 51 | // initate value 52 | let value = Value::known(Fp::from(755)); 53 | 54 | let circuit = MyCircuit:: { 55 | input: value 56 | }; 57 | 58 | let target = 800; 59 | 60 | // define public inputs looping from target to 0 and adding each value to pub_inputs vector 61 | let mut pub_inputs = vec![]; 62 | for i in 0..target { 63 | pub_inputs.push(Fp::from(i)); 64 | } 65 | 66 | // should verify as value is less than target 67 | let prover = MockProver::run(k, &circuit, vec![pub_inputs]).unwrap(); 68 | prover.assert_satisfied(); 69 | 70 | // shouldn't verify as value is greater than target 71 | let target_2 = 754; 72 | 73 | let mut pub_inputs_2 = vec![]; 74 | for i in 0..target_2 { 75 | pub_inputs_2.push(Fp::from(i)); 76 | } 77 | 78 | let invalid_prover = MockProver::run(k, &circuit, vec![pub_inputs_2]).unwrap(); 79 | 80 | assert!(invalid_prover.verify().is_err()); 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/circuits/less_than_v2.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; 3 | use std::marker::PhantomData; 4 | 5 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 6 | 7 | #[derive(Default)] 8 | // define circuit struct using array of usernames and balances 9 | struct MyCircuit { 10 | pub value_l: u64, 11 | pub value_r: u64, 12 | pub check: bool, 13 | _marker: PhantomData, 14 | } 15 | #[derive(Clone, Debug)] 16 | struct TestCircuitConfig { 17 | q_enable: Selector, 18 | value_l: Column, 19 | value_r: Column, 20 | check: Column, 21 | lt: LtConfig, 22 | } 23 | 24 | impl Circuit for MyCircuit { 25 | type Config = TestCircuitConfig; 26 | type FloorPlanner = SimpleFloorPlanner; 27 | 28 | fn without_witnesses(&self) -> Self { 29 | Self::default() 30 | } 31 | 32 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 33 | let q_enable = meta.complex_selector(); 34 | let value_l = meta.advice_column(); 35 | let value_r = meta.advice_column(); 36 | let check = meta.advice_column(); 37 | 38 | let lt = LtChip::configure( 39 | meta, 40 | |meta| meta.query_selector(q_enable), 41 | |meta| meta.query_advice(value_l, Rotation::cur()), 42 | |meta| meta.query_advice(value_r, Rotation::cur()), 43 | ); 44 | 45 | let config = Self::Config { 46 | q_enable, 47 | value_l, 48 | value_r, 49 | check, 50 | lt, 51 | }; 52 | 53 | meta.create_gate( 54 | "verifies that `check` current confif = is_lt from LtChip ", 55 | |meta| { 56 | let q_enable = meta.query_selector(q_enable); 57 | 58 | // This verifies lt(value_l::cur, value_r::cur) is calculated correctly 59 | let check = meta.query_advice(config.check, Rotation::cur()); 60 | 61 | vec![q_enable * (config.lt.is_lt(meta, None) - check)] 62 | }, 63 | ); 64 | 65 | config 66 | } 67 | 68 | fn synthesize( 69 | &self, 70 | config: Self::Config, 71 | mut layouter: impl Layouter, 72 | ) -> Result<(), Error> { 73 | let chip = LtChip::construct(config.lt); 74 | 75 | chip.load(&mut layouter)?; 76 | 77 | layouter.assign_region( 78 | || "witness", 79 | |mut region| { 80 | region.assign_advice( 81 | || "value left", 82 | config.value_l, 83 | 0, 84 | || Value::known(F::from(self.value_l)), 85 | )?; 86 | 87 | region.assign_advice( 88 | || "value right", 89 | config.value_r, 90 | 0, 91 | || Value::known(F::from(self.value_r)), 92 | )?; 93 | 94 | region.assign_advice( 95 | || "check", 96 | config.check, 97 | 0, 98 | || Value::known(F::from(self.check as u64)), 99 | )?; 100 | 101 | config.q_enable.enable(&mut region, 0)?; 102 | 103 | chip.assign(&mut region, 0, F::from(self.value_l), F::from(self.value_r))?; 104 | 105 | Ok(()) 106 | }, 107 | ) 108 | } 109 | } 110 | 111 | #[cfg(test)] 112 | mod tests { 113 | 114 | use super::MyCircuit; 115 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr as Fp}; 116 | use std::marker::PhantomData; 117 | 118 | #[test] 119 | fn test_less_than_2() { 120 | let k = 9; 121 | 122 | // initate usernames and balances array 123 | let value_l: u64 = 5; 124 | let value_r: u64 = 10; 125 | let check = true; 126 | 127 | let mut circuit = MyCircuit:: { 128 | value_l, 129 | value_r, 130 | check, 131 | _marker: PhantomData, 132 | }; 133 | 134 | // Test 1 - should be valid 135 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 136 | prover.assert_satisfied(); 137 | 138 | // switch value_l and value_r 139 | circuit.value_l = 10; 140 | circuit.value_r = 5; 141 | 142 | // Test 2 - should be invalid 143 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 144 | assert!(prover.verify().is_err()); 145 | 146 | // let check to be false 147 | circuit.check = false; 148 | 149 | // Test 3 - should be valid 150 | let prover = MockProver::run(k, &circuit, vec![]).unwrap(); 151 | prover.assert_satisfied(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/circuits/less_than_v3.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::hash_v1::{Hash1Chip, Hash1Config}; 2 | use eth_types::Field; 3 | use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; 4 | use std::marker::PhantomData; 5 | 6 | use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; 7 | 8 | #[derive(Default)] 9 | // define circuit struct using array of usernames and balances 10 | struct MyCircuit { 11 | pub value_l: u64, 12 | pub value_r: u64, 13 | pub check: bool, 14 | _marker: PhantomData, 15 | } 16 | 17 | #[derive(Clone, Debug)] 18 | struct TestCircuitConfig { 19 | q_enable: Selector, 20 | value_l: Column, 21 | value_r: Column, 22 | check: Column, 23 | lt: LtConfig, 24 | hash_config: Hash1Config, 25 | } 26 | 27 | impl Circuit for MyCircuit { 28 | type Config = TestCircuitConfig; 29 | type FloorPlanner = SimpleFloorPlanner; 30 | 31 | fn without_witnesses(&self) -> Self { 32 | Self::default() 33 | } 34 | 35 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 36 | let q_enable = meta.complex_selector(); 37 | let value_l = meta.advice_column(); 38 | let value_r = meta.advice_column(); 39 | let check = meta.advice_column(); 40 | let instance = meta.instance_column(); 41 | 42 | let lt = LtChip::configure( 43 | meta, 44 | |meta| meta.query_selector(q_enable), 45 | |meta| meta.query_advice(value_l, Rotation::cur()), 46 | |meta| meta.query_advice(value_r, Rotation::cur()), 47 | ); 48 | 49 | let hash_config = Hash1Chip::configure(meta, [value_l, value_r], instance); 50 | 51 | let config = Self::Config { 52 | q_enable, 53 | value_l, 54 | value_r, 55 | check, 56 | lt, 57 | hash_config, 58 | }; 59 | 60 | meta.create_gate( 61 | "verifies that `check` current confif = is_lt from LtChip ", 62 | |meta| { 63 | let q_enable = meta.query_selector(q_enable); 64 | 65 | // This verifies lt(value_l::cur, value_r::cur) is calculated correctly 66 | let check = meta.query_advice(config.check, Rotation::cur()); 67 | 68 | // verifies that check is equal to lt in the child chip 69 | vec![q_enable * (config.lt.is_lt(meta, None) - check)] 70 | }, 71 | ); 72 | 73 | config 74 | } 75 | 76 | fn synthesize( 77 | &self, 78 | config: Self::Config, 79 | mut layouter: impl Layouter, 80 | ) -> Result<(), Error> { 81 | let lt_chip = LtChip::construct(config.lt); 82 | lt_chip.load(&mut layouter)?; 83 | let hash_chip: Hash1Chip = Hash1Chip::construct(config.hash_config); 84 | 85 | let _ = layouter.assign_region( 86 | || "witness", 87 | |mut region| { 88 | region.assign_advice( 89 | || "value left", 90 | config.value_l, 91 | 0, 92 | || Value::known(F::from(self.value_l)), 93 | )?; 94 | 95 | region.assign_advice( 96 | || "value right", 97 | config.value_r, 98 | 0, 99 | || Value::known(F::from(self.value_r)), 100 | )?; 101 | 102 | region.assign_advice(|| "check", config.check, 0, || Value::known(F::from(1)))?; 103 | 104 | config.q_enable.enable(&mut region, 0)?; 105 | 106 | lt_chip.assign(&mut region, 0, F::from(self.value_l), F::from(self.value_r))?; 107 | 108 | Ok(()) 109 | }, 110 | ); 111 | 112 | let b = hash_chip.assign_advice_row( 113 | layouter.namespace(|| "load row"), 114 | Value::known(F::from(self.value_l)), 115 | )?; 116 | hash_chip.expose_public(layouter.namespace(|| "hash output check"), &b, 0)?; 117 | 118 | Ok(()) 119 | } 120 | } 121 | 122 | #[cfg(test)] 123 | mod tests { 124 | 125 | use super::MyCircuit; 126 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr as Fp}; 127 | use std::marker::PhantomData; 128 | 129 | #[test] 130 | fn test_less_than_3() { 131 | let k = 9; 132 | 133 | // initate usernames and balances array 134 | let value_l: u64 = 5; 135 | let value_r: u64 = 10; 136 | let check = true; 137 | 138 | let mut circuit = MyCircuit:: { 139 | value_l, 140 | value_r, 141 | check, 142 | _marker: PhantomData, 143 | }; 144 | 145 | let public_input_1 = vec![Fp::from(10)]; 146 | 147 | // Test 1 - should be valid 148 | let prover = MockProver::run(k, &circuit, vec![public_input_1.clone()]).unwrap(); 149 | prover.assert_satisfied(); 150 | 151 | // switch value_l and value_r 152 | circuit.value_l = 10; 153 | circuit.value_r = 5; 154 | 155 | // Test 2 - should be invalid 156 | let prover = MockProver::run(k, &circuit, vec![public_input_1.clone()]).unwrap(); 157 | assert!(prover.verify().is_err()); 158 | 159 | // let check to be false 160 | circuit.check = false; 161 | 162 | // Test 3 - should be invalid! as we are now forcing the check to be true 163 | let prover = MockProver::run(k, &circuit, vec![public_input_1]).unwrap(); 164 | assert!(prover.verify().is_err()); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/circuits/merkle_sum_tree.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::merkle_sum_tree::{MerkleSumTreeChip, MerkleSumTreeConfig}; 2 | use eth_types::Field; 3 | use halo2_proofs::{circuit::*, plonk::*}; 4 | use std::marker::PhantomData; 5 | 6 | #[derive(Default)] 7 | struct MerkleSumTreeCircuit { 8 | pub leaf_hash: F, 9 | pub leaf_balance: F, 10 | pub path_element_hashes: Vec, 11 | pub path_element_balances: Vec, 12 | pub path_indices: Vec, 13 | pub assets_sum: F, 14 | _marker: PhantomData, 15 | } 16 | 17 | impl Circuit for MerkleSumTreeCircuit { 18 | type Config = MerkleSumTreeConfig; 19 | type FloorPlanner = SimpleFloorPlanner; 20 | 21 | fn without_witnesses(&self) -> Self { 22 | Self::default() 23 | } 24 | 25 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 26 | // config columns for the merkle tree chip 27 | let col_a = meta.advice_column(); 28 | let col_b = meta.advice_column(); 29 | let col_c = meta.advice_column(); 30 | let col_d = meta.advice_column(); 31 | let col_e = meta.advice_column(); 32 | 33 | let instance = meta.instance_column(); 34 | 35 | MerkleSumTreeChip::configure(meta, [col_a, col_b, col_c, col_d, col_e], instance) 36 | } 37 | 38 | fn synthesize( 39 | &self, 40 | config: Self::Config, 41 | mut layouter: impl Layouter, 42 | ) -> Result<(), Error> { 43 | let chip = MerkleSumTreeChip::construct(config); 44 | let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance( 45 | layouter.namespace(|| "assign leaf"), 46 | F::from(self.leaf_hash), 47 | F::from(self.leaf_balance), 48 | )?; 49 | 50 | chip.expose_public(layouter.namespace(|| "public leaf hash"), &leaf_hash, 0)?; 51 | chip.expose_public( 52 | layouter.namespace(|| "public leaf balance"), 53 | &leaf_balance, 54 | 1, 55 | )?; 56 | 57 | // apply it for level 0 of the merkle tree 58 | // node cells passed as inputs are the leaf_hash cell and the leaf_balance cell 59 | let (mut next_hash, mut next_sum) = chip.merkle_prove_layer( 60 | layouter.namespace(|| format!("level {} merkle proof", 0)), 61 | &leaf_hash, 62 | &leaf_balance, 63 | self.path_element_hashes[0], 64 | self.path_element_balances[0], 65 | self.path_indices[0], 66 | )?; 67 | 68 | // apply it for the remaining levels of the merkle tree 69 | // node cells passed as inputs are the computed_hash_prev_level cell and the computed_balance_prev_level cell 70 | for i in 1..self.path_element_balances.len() { 71 | (next_hash, next_sum) = chip.merkle_prove_layer( 72 | layouter.namespace(|| format!("level {} merkle proof", i)), 73 | &next_hash, 74 | &next_sum, 75 | self.path_element_hashes[i], 76 | self.path_element_balances[i], 77 | self.path_indices[i], 78 | )?; 79 | } 80 | 81 | // compute the sum of the merkle sum tree as sum of the leaf balance and the sum of the path elements balances 82 | let computed_sum = self.leaf_balance 83 | + self 84 | .path_element_balances 85 | .iter() 86 | .fold(F::zero(), |acc, x| acc + x); 87 | 88 | // enforce computed sum to be less than the assets sum 89 | chip.enforce_less_than( 90 | layouter.namespace(|| "enforce less than"), 91 | &next_sum, 92 | computed_sum, 93 | self.assets_sum, 94 | )?; 95 | 96 | chip.expose_public(layouter.namespace(|| "public root"), &next_hash, 2)?; 97 | Ok(()) 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use crate::circuits::utils::full_prover; 104 | 105 | use super::super::super::chips::poseidon::spec::MySpec; 106 | use super::MerkleSumTreeCircuit; 107 | use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength}; 108 | use halo2_proofs::{dev::MockProver, halo2curves::bn256::Fr as Fp}; 109 | use std::marker::PhantomData; 110 | 111 | const WIDTH: usize = 5; 112 | const RATE: usize = 4; 113 | const L: usize = 4; 114 | 115 | #[derive(Debug, Clone)] 116 | struct Node { 117 | pub hash: Fp, 118 | pub balance: Fp, 119 | } 120 | 121 | fn compute_merkle_sum_root(node: &Node, elements: &Vec, indices: &Vec) -> Node { 122 | let k = elements.len(); 123 | let mut digest = node.clone(); 124 | let mut message: [Fp; 4]; 125 | for i in 0..k { 126 | if indices[i] == 0.into() { 127 | message = [ 128 | digest.hash, 129 | digest.balance, 130 | elements[i].hash, 131 | elements[i].balance, 132 | ]; 133 | } else { 134 | message = [ 135 | elements[i].hash, 136 | elements[i].balance, 137 | digest.hash, 138 | digest.balance, 139 | ]; 140 | } 141 | 142 | digest.hash = 143 | poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init( 144 | ) 145 | .hash(message); 146 | 147 | digest.balance = digest.balance + elements[i].balance; 148 | } 149 | digest 150 | } 151 | 152 | fn instantiate_circuit( 153 | leaf: Node, 154 | elements: Vec, 155 | indices: Vec, 156 | assets_sum: Fp, 157 | ) -> MerkleSumTreeCircuit { 158 | let element_hashes: Vec = elements.iter().map(|node| node.hash).collect(); 159 | let element_balances: Vec = elements.iter().map(|node| node.balance).collect(); 160 | 161 | MerkleSumTreeCircuit { 162 | leaf_hash: leaf.hash, 163 | leaf_balance: leaf.balance, 164 | path_element_hashes: element_hashes, 165 | path_element_balances: element_balances, 166 | path_indices: indices, 167 | assets_sum, 168 | _marker: PhantomData, 169 | } 170 | } 171 | 172 | fn build_merkle_tree() -> (Node, Vec, Vec, Node) { 173 | let leaf = Node { 174 | hash: Fp::from(10u64), 175 | balance: Fp::from(100u64), 176 | }; 177 | 178 | let elements = vec![ 179 | Node { 180 | hash: Fp::from(1u64), 181 | balance: Fp::from(10u64), 182 | }, 183 | Node { 184 | hash: Fp::from(5u64), 185 | balance: Fp::from(50u64), 186 | }, 187 | Node { 188 | hash: Fp::from(6u64), 189 | balance: Fp::from(60u64), 190 | }, 191 | Node { 192 | hash: Fp::from(9u64), 193 | balance: Fp::from(90u64), 194 | }, 195 | Node { 196 | hash: Fp::from(9u64), 197 | balance: Fp::from(90u64), 198 | }, 199 | ]; 200 | 201 | let indices = vec![ 202 | Fp::from(0u64), 203 | Fp::from(0u64), 204 | Fp::from(0u64), 205 | Fp::from(0u64), 206 | Fp::from(0u64), 207 | ]; 208 | 209 | let root = compute_merkle_sum_root(&leaf, &elements, &indices); 210 | 211 | (leaf, elements, indices, root) 212 | } 213 | 214 | #[test] 215 | fn test_valid_merkle_sum_tree() { 216 | let (leaf, elements, indices, root) = build_merkle_tree(); 217 | 218 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 219 | 220 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 221 | 222 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 223 | 224 | let valid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 225 | 226 | valid_prover.assert_satisfied(); 227 | } 228 | 229 | #[test] 230 | fn test_invalid_root_hash() { 231 | let (leaf, elements, indices, root) = build_merkle_tree(); 232 | 233 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 234 | 235 | let public_input = vec![leaf.hash, leaf.balance, Fp::from(1000u64), assets_sum]; 236 | 237 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 238 | 239 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 240 | 241 | // error => Err([Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 2), Equality constraint not satisfied by cell (Column('Advice', 5 - ), in Region 26 ('permute state') at offset 36)]) 242 | // computed_hash (advice column[5]) != root.hash (instance column row 2) 243 | assert!(invalid_prover.verify().is_err()); 244 | } 245 | 246 | #[test] 247 | fn test_invalid_leaf_hash() { 248 | let (leaf, elements, indices, root) = build_merkle_tree(); 249 | 250 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 251 | 252 | let public_input = vec![Fp::from(1000u64), leaf.balance, root.hash, assets_sum]; 253 | 254 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 255 | 256 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 257 | 258 | // error => Equality constraint not satisfied by cell (Column('Advice', 0 - ), in Region 2 ('merkle prove layer') at offset 0). Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 0) 259 | // leaf_hash (advice column[0]) != leaf.hash (instance column row 0) 260 | assert!(invalid_prover.verify().is_err()); 261 | } 262 | 263 | #[test] 264 | fn test_invalid_leaf_balance() { 265 | let (leaf, elements, indices, root) = build_merkle_tree(); 266 | 267 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 268 | 269 | let public_input = vec![leaf.hash, Fp::from(1000u64), root.hash, assets_sum]; 270 | 271 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 272 | 273 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 274 | 275 | // error => Equality constraint not satisfied by cell (Column('Advice', 1 - ), in Region 2 ('merkle prove layer') at offset 0) Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 1) 276 | // leaf_balance (advice column[1]) != leaf.balance (instance column row 1) 277 | assert!(invalid_prover.verify().is_err()); 278 | } 279 | 280 | #[test] 281 | fn test_non_binary_index() { 282 | let (leaf, elements, mut indices, root) = build_merkle_tree(); 283 | 284 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 285 | 286 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 287 | 288 | indices[0] = Fp::from(2); 289 | 290 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 291 | 292 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 293 | 294 | // error: constraint not satisfied 'bool constraint' 295 | // error: constraint not satisfied 'swap constraint' 296 | assert!(invalid_prover.verify().is_err()); 297 | } 298 | 299 | #[test] 300 | fn test_swapping_index() { 301 | let (leaf, elements, mut indices, root) = build_merkle_tree(); 302 | 303 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 304 | 305 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 306 | 307 | indices[0] = Fp::from(1); 308 | 309 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 310 | 311 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 312 | 313 | // error => Err([Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 2), Equality constraint not satisfied by cell (Column('Advice', 5 - ), in Region 26 ('permute state') at offset 36)]) 314 | // computed_hash (advice column[5]) != root.hash (instance column row 2) 315 | assert!(invalid_prover.verify().is_err()); 316 | } 317 | 318 | #[test] 319 | fn test_is_not_less_than() { 320 | let (leaf, elements, indices, root) = build_merkle_tree(); 321 | 322 | let assets_sum = Fp::from(200u64); // less than liabilities sum (400) 323 | 324 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 325 | 326 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 327 | 328 | let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 329 | 330 | // error: constraint not satisfied 331 | // Cell layout in region 'enforce sum to be less than total assets': 332 | // | Offset | A2 | A11| 333 | // +--------+----+----+ 334 | // | 0 | x0 | x1 | <--{ Gate 'verifies that `check` from current config equal to is_lt from LtChip ' applied here 335 | 336 | // Constraint '': 337 | // ((S10 * (1 - S10)) * (0x2 - S10)) * (x1 - x0) = 0 338 | 339 | // Assigned cell values: 340 | // x0 = 1 341 | // x1 = 0 342 | assert!(invalid_prover.verify().is_err()); 343 | } 344 | 345 | #[test] 346 | fn test_full_prover() { 347 | let k = 9; 348 | 349 | let (leaf, elements, indices, root) = build_merkle_tree(); 350 | 351 | let assets_sum = Fp::from(500u64); // greater than liabilities sum (400) 352 | 353 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 354 | 355 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 356 | 357 | full_prover(circuit, k, &public_input); 358 | } 359 | 360 | #[cfg(feature = "dev-graph")] 361 | #[test] 362 | fn print_merkle_sum_tree() { 363 | use plotters::prelude::*; 364 | 365 | let (leaf, elements, indices, root) = build_merkle_tree(); 366 | 367 | let assets_sum = Fp::from(200u64); // less than liabilities sum (400) 368 | 369 | let public_input = vec![leaf.hash, leaf.balance, root.hash, assets_sum]; 370 | 371 | let circuit = instantiate_circuit(leaf, elements, indices, assets_sum); 372 | 373 | let root = BitMapBackend::new("prints/merkle-sum-tree-layout.png", (1024, 3096)) 374 | .into_drawing_area(); 375 | root.fill(&WHITE).unwrap(); 376 | let root = root 377 | .titled("Merkle Sum Tree Layout", ("sans-serif", 60)) 378 | .unwrap(); 379 | 380 | halo2_proofs::dev::CircuitLayout::default() 381 | .render(8, &circuit, &root) 382 | .unwrap(); 383 | } 384 | } 385 | -------------------------------------------------------------------------------- /src/circuits/merkle_v1.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::merkle_v1::{MerkleTreeV1Chip, MerkleTreeV1Config}; 2 | 3 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 4 | 5 | #[derive(Default)] 6 | struct MerkleTreeV1Circuit { 7 | pub leaf: Value, 8 | pub path_elements: Vec>, 9 | pub path_indices: Vec>, 10 | } 11 | 12 | impl Circuit for MerkleTreeV1Circuit { 13 | type Config = MerkleTreeV1Config; 14 | type FloorPlanner = SimpleFloorPlanner; 15 | 16 | fn without_witnesses(&self) -> Self { 17 | Self::default() 18 | } 19 | 20 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 21 | let col_a = meta.advice_column(); 22 | let col_b = meta.advice_column(); 23 | let col_c = meta.advice_column(); 24 | let instance = meta.instance_column(); 25 | 26 | MerkleTreeV1Chip::configure(meta, [col_a, col_b, col_c], instance) 27 | } 28 | 29 | fn synthesize( 30 | &self, 31 | config: Self::Config, 32 | mut layouter: impl Layouter, 33 | ) -> Result<(), Error> { 34 | // We create a new instance of chip using the config passed as input 35 | let chip = MerkleTreeV1Chip::::construct(config); 36 | 37 | let leaf_cell = chip.assing_leaf(layouter.namespace(|| "load leaf"), self.leaf)?; 38 | 39 | // Verify that the leaf matches the public input 40 | chip.expose_public(layouter.namespace(|| "leaf"), &leaf_cell, 0)?; 41 | 42 | // apply it for level 0 of the merkle tree 43 | let mut digest = chip.merkle_prove_layer( 44 | layouter.namespace(|| "level 0"), 45 | &leaf_cell, 46 | self.path_elements[0], 47 | self.path_indices[0], 48 | )?; 49 | 50 | // apply it for the remaining levels of the merkle tree 51 | for i in 1..self.path_elements.len() { 52 | digest = chip.merkle_prove_layer( 53 | layouter.namespace(|| "next level"), 54 | &digest, 55 | self.path_elements[i], 56 | self.path_indices[i], 57 | )?; 58 | } 59 | 60 | chip.expose_public(layouter.namespace(|| "root"), &digest, 1)?; 61 | 62 | Ok(()) 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::MerkleTreeV1Circuit; 69 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 70 | 71 | #[test] 72 | fn test_merkle_tree_1() { 73 | let leaf = 99u64; 74 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 75 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 76 | let digest: u64 = leaf + elements.iter().sum::(); 77 | 78 | let leaf_fp = Value::known(Fp::from(leaf)); 79 | let elements_fp: Vec> = elements 80 | .iter() 81 | .map(|x| Value::known(Fp::from(x.to_owned()))) 82 | .collect(); 83 | let indices_fp: Vec> = indices 84 | .iter() 85 | .map(|x| Value::known(Fp::from(x.to_owned()))) 86 | .collect(); 87 | 88 | let circuit = MerkleTreeV1Circuit { 89 | leaf: leaf_fp, 90 | path_elements: elements_fp, 91 | path_indices: indices_fp, 92 | }; 93 | 94 | let public_input = vec![Fp::from(leaf), Fp::from(digest)]; 95 | let prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 96 | prover.assert_satisfied(); 97 | } 98 | } 99 | 100 | #[cfg(feature = "dev-graph")] 101 | #[test] 102 | fn print_merkle_tree_1() { 103 | use halo2_proofs::halo2curves::pasta::Fp; 104 | use plotters::prelude::*; 105 | 106 | let root = 107 | BitMapBackend::new("prints/merkle-tree-1-layout.png", (1024, 3096)).into_drawing_area(); 108 | root.fill(&WHITE).unwrap(); 109 | let root = root 110 | .titled("Merkle Tree 1 Layout", ("sans-serif", 60)) 111 | .unwrap(); 112 | 113 | let leaf = 99u64; 114 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 115 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 116 | let digest: u64 = leaf + elements.iter().sum::(); 117 | 118 | let leaf_fp = Value::known(Fp::from(leaf)); 119 | let elements_fp: Vec> = elements 120 | .iter() 121 | .map(|x| Value::known(Fp::from(x.to_owned()))) 122 | .collect(); 123 | let indices_fp: Vec> = indices 124 | .iter() 125 | .map(|x| Value::known(Fp::from(x.to_owned()))) 126 | .collect(); 127 | 128 | let circuit = MerkleTreeV1Circuit { 129 | leaf: leaf_fp, 130 | path_elements: elements_fp, 131 | path_indices: indices_fp, 132 | }; 133 | 134 | halo2_proofs::dev::CircuitLayout::default() 135 | .render(4, &circuit, &root) 136 | .unwrap(); 137 | } 138 | -------------------------------------------------------------------------------- /src/circuits/merkle_v2.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::merkle_v2::{MerkleTreeV2Chip, MerkleTreeV2Config}; 2 | use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; 3 | 4 | #[derive(Default)] 5 | struct MerkleTreeV2Circuit { 6 | pub leaf: Value, 7 | pub path_elements: Vec>, 8 | pub path_indices: Vec>, 9 | } 10 | 11 | impl Circuit for MerkleTreeV2Circuit { 12 | type Config = MerkleTreeV2Config; 13 | type FloorPlanner = SimpleFloorPlanner; 14 | 15 | fn without_witnesses(&self) -> Self { 16 | Self::default() 17 | } 18 | 19 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 20 | let col_a = meta.advice_column(); 21 | let col_b = meta.advice_column(); 22 | let col_c = meta.advice_column(); 23 | let instance = meta.instance_column(); 24 | MerkleTreeV2Chip::configure(meta, [col_a, col_b, col_c], instance) 25 | } 26 | 27 | fn synthesize( 28 | &self, 29 | config: Self::Config, 30 | mut layouter: impl Layouter, 31 | ) -> Result<(), Error> { 32 | let chip = MerkleTreeV2Chip::construct(config); 33 | let leaf_cell = chip.assing_leaf(layouter.namespace(|| "assign leaf"), self.leaf)?; 34 | chip.expose_public(layouter.namespace(|| "public leaf"), &leaf_cell, 0); 35 | 36 | // apply it for level 0 of the merkle tree 37 | // node cell passed as input is the leaf cell 38 | let mut digest = chip.merkle_prove_layer( 39 | layouter.namespace(|| "merkle_prove"), 40 | &leaf_cell, 41 | self.path_elements[0], 42 | self.path_indices[0], 43 | )?; 44 | 45 | // apply it for the remaining levels of the merkle tree 46 | // node cell passed as input is the digest cell 47 | for i in 1..self.path_elements.len() { 48 | digest = chip.merkle_prove_layer( 49 | layouter.namespace(|| "next level"), 50 | &digest, 51 | self.path_elements[i], 52 | self.path_indices[i], 53 | )?; 54 | } 55 | chip.expose_public(layouter.namespace(|| "public root"), &digest, 1)?; 56 | Ok(()) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::MerkleTreeV2Circuit; 63 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 64 | 65 | #[test] 66 | fn test_merkle_tree_2() { 67 | let leaf = 99u64; 68 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 69 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 70 | let digest: u64 = leaf + elements.iter().sum::(); 71 | 72 | let leaf_fp = Value::known(Fp::from(leaf)); 73 | let elements_fp: Vec> = elements 74 | .iter() 75 | .map(|x| Value::known(Fp::from(x.to_owned()))) 76 | .collect(); 77 | let indices_fp: Vec> = indices 78 | .iter() 79 | .map(|x| Value::known(Fp::from(x.to_owned()))) 80 | .collect(); 81 | 82 | let circuit = MerkleTreeV2Circuit { 83 | leaf: leaf_fp, 84 | path_elements: elements_fp, 85 | path_indices: indices_fp, 86 | }; 87 | 88 | let public_input = vec![Fp::from(leaf), Fp::from(digest)]; 89 | let prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); 90 | prover.assert_satisfied(); 91 | } 92 | } 93 | 94 | #[cfg(feature = "dev-graph")] 95 | #[test] 96 | fn print_merkle_tree_2() { 97 | use halo2_proofs::halo2curves::pasta::Fp; 98 | use plotters::prelude::*; 99 | 100 | let root = 101 | BitMapBackend::new("prints/merkle-tree-2-layout.png", (1024, 3096)).into_drawing_area(); 102 | root.fill(&WHITE).unwrap(); 103 | let root = root 104 | .titled("Merkle Tree 2 Layout", ("sans-serif", 60)) 105 | .unwrap(); 106 | 107 | let leaf = 99u64; 108 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 109 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 110 | let digest: u64 = leaf + elements.iter().sum::(); 111 | 112 | let leaf_fp = Value::known(Fp::from(leaf)); 113 | let elements_fp: Vec> = elements 114 | .iter() 115 | .map(|x| Value::known(Fp::from(x.to_owned()))) 116 | .collect(); 117 | let indices_fp: Vec> = indices 118 | .iter() 119 | .map(|x| Value::known(Fp::from(x.to_owned()))) 120 | .collect(); 121 | 122 | let circuit = MerkleTreeV2Circuit { 123 | leaf: leaf_fp, 124 | path_elements: elements_fp, 125 | path_indices: indices_fp, 126 | }; 127 | 128 | halo2_proofs::dev::CircuitLayout::default() 129 | .render(4, &circuit, &root) 130 | .unwrap(); 131 | } 132 | -------------------------------------------------------------------------------- /src/circuits/merkle_v3.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::merkle_v3::{MerkleTreeV3Chip, MerkleTreeV3Config}; 2 | use halo2_proofs::{circuit::*, arithmetic::FieldExt, plonk::*}; 3 | 4 | #[derive(Default)] 5 | struct MerkleTreeV3Circuit { 6 | pub leaf: Value, 7 | pub path_elements: Vec>, 8 | pub path_indices: Vec>, 9 | } 10 | 11 | impl Circuit for MerkleTreeV3Circuit { 12 | type Config = MerkleTreeV3Config; 13 | type FloorPlanner = SimpleFloorPlanner; 14 | 15 | fn without_witnesses(&self) -> Self { 16 | Self::default() 17 | } 18 | 19 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 20 | // config for the merkle tree chip 21 | let col_a = meta.advice_column(); 22 | let col_b = meta.advice_column(); 23 | let col_c = meta.advice_column(); 24 | let instance = meta.instance_column(); 25 | 26 | MerkleTreeV3Chip::configure(meta, [col_a, col_b, col_c], instance) 27 | } 28 | 29 | fn synthesize( 30 | &self, 31 | config: Self::Config, 32 | mut layouter: impl Layouter, 33 | ) -> Result<(), Error> { 34 | let chip = MerkleTreeV3Chip::construct(config); 35 | let leaf_cell = chip.assing_leaf(layouter.namespace(|| "assign leaf"), self.leaf)?; 36 | chip.expose_public(layouter.namespace(|| "public leaf"), &leaf_cell, 0)?; 37 | 38 | // apply it for level 0 of the merkle tree 39 | // node cell passed as input is the leaf cell 40 | let mut digest = chip.merkle_prove_layer( 41 | layouter.namespace(|| "merkle_prove"), 42 | &leaf_cell, 43 | self.path_elements[0], 44 | self.path_indices[0], 45 | )?; 46 | 47 | // apply it for the remaining levels of the merkle tree 48 | // node cell passed as input is the digest cell 49 | for i in 1..self.path_elements.len() { 50 | digest = chip.merkle_prove_layer( 51 | layouter.namespace(|| "next level"), 52 | &digest, 53 | self.path_elements[i], 54 | self.path_indices[i], 55 | )?; 56 | } 57 | chip.expose_public(layouter.namespace(|| "public root"), &digest, 1)?; 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::MerkleTreeV3Circuit; 65 | use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength, P128Pow5T3}; 66 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 67 | 68 | const WIDTH: usize = 3; 69 | const RATE: usize = 2; 70 | const L: usize = 2; 71 | 72 | fn compute_merkle_root(leaf: &u64, elements: &Vec, indices: &Vec) -> Fp { 73 | let k = elements.len(); 74 | let mut digest = Fp::from(leaf.clone()); 75 | let mut message: [Fp; 2]; 76 | for i in 0..k { 77 | if indices[i] == 0 { 78 | message = [digest, Fp::from(elements[i])]; 79 | } else { 80 | message = [Fp::from(elements[i]), digest]; 81 | } 82 | 83 | digest = poseidon::Hash::<_, P128Pow5T3, ConstantLength, WIDTH, RATE>::init() 84 | .hash(message); 85 | } 86 | return digest; 87 | } 88 | 89 | #[test] 90 | fn test_merkle_tree_3() { 91 | let leaf = 99u64; 92 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 93 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 94 | 95 | let root = compute_merkle_root(&leaf, &elements, &indices); 96 | 97 | let leaf_fp = Value::known(Fp::from(leaf)); 98 | let elements_fp: Vec> = elements 99 | .iter() 100 | .map(|x| Value::known(Fp::from(x.to_owned()))) 101 | .collect(); 102 | let indices_fp: Vec> = indices 103 | .iter() 104 | .map(|x| Value::known(Fp::from(x.to_owned()))) 105 | .collect(); 106 | 107 | let circuit = MerkleTreeV3Circuit { 108 | leaf: leaf_fp, 109 | path_elements: elements_fp, 110 | path_indices: indices_fp, 111 | }; 112 | 113 | let correct_public_input = vec![Fp::from(leaf), root]; 114 | let valid_prover = MockProver::run(10, &circuit, vec![correct_public_input]).unwrap(); 115 | valid_prover.assert_satisfied(); 116 | 117 | let wrong_public_input = vec![Fp::from(leaf), Fp::from(0)]; 118 | let invalid_prover = MockProver::run(10, &circuit, vec![wrong_public_input]).unwrap(); 119 | assert!(invalid_prover.verify().is_err()); 120 | } 121 | } 122 | 123 | #[cfg(feature = "dev-graph")] 124 | #[test] 125 | fn print_merkle_tree_3() { 126 | use halo2_proofs::halo2curves::pasta::Fp; 127 | use plotters::prelude::*; 128 | 129 | let root = 130 | BitMapBackend::new("prints/merkle-tree-3-layout.png", (1024, 3096)).into_drawing_area(); 131 | root.fill(&WHITE).unwrap(); 132 | let root = root 133 | .titled("Merkle Tree 3 Layout", ("sans-serif", 60)) 134 | .unwrap(); 135 | 136 | let leaf = 99u64; 137 | let elements = vec![1u64, 5u64, 6u64, 9u64, 9u64]; 138 | let indices = vec![0u64, 0u64, 0u64, 0u64, 0u64]; 139 | let digest: u64 = leaf + elements.iter().sum::(); 140 | 141 | let leaf_fp = Value::known(Fp::from(leaf)); 142 | let elements_fp: Vec> = elements 143 | .iter() 144 | .map(|x| Value::known(Fp::from(x.to_owned()))) 145 | .collect(); 146 | let indices_fp: Vec> = indices 147 | .iter() 148 | .map(|x| Value::known(Fp::from(x.to_owned()))) 149 | .collect(); 150 | 151 | let circuit = MerkleTreeV3Circuit { 152 | leaf: leaf_fp, 153 | path_elements: elements_fp, 154 | path_indices: indices_fp, 155 | }; 156 | 157 | halo2_proofs::dev::CircuitLayout::default() 158 | .render(8, &circuit, &root) 159 | .unwrap(); 160 | } 161 | -------------------------------------------------------------------------------- /src/circuits/overflow_check.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use halo2_proofs::{circuit::*, plonk::*}; 3 | 4 | use super::super::chips::overflow_check::{OverFlowCheckConfig, OverFlowChip}; 5 | 6 | #[derive(Default)] 7 | struct OverflowCheckCircuit { 8 | pub a: Value, 9 | } 10 | 11 | impl Circuit for OverflowCheckCircuit { 12 | type Config = OverFlowCheckConfig; 13 | type FloorPlanner = SimpleFloorPlanner; 14 | 15 | fn without_witnesses(&self) -> Self { 16 | Self::default() 17 | } 18 | 19 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 20 | let col_a = meta.advice_column(); 21 | let col_b_inv = meta.advice_column(); 22 | let col_b = meta.advice_column(); 23 | let col_c = meta.advice_column(); 24 | let col_d = meta.advice_column(); 25 | let carry_selector = meta.selector(); 26 | let overflow_selector = meta.selector(); 27 | let instance = meta.instance_column(); 28 | 29 | OverFlowChip::configure( 30 | meta, 31 | [col_a, col_b_inv, col_b, col_c, col_d], 32 | [carry_selector, overflow_selector], 33 | instance, 34 | ) 35 | } 36 | 37 | fn synthesize( 38 | &self, 39 | config: Self::Config, 40 | mut layouter: impl Layouter, 41 | ) -> Result<(), Error> { 42 | let chip = OverFlowChip::construct(config); 43 | 44 | let (prev_b, prev_c, prev_d) = 45 | chip.assign_first_row(layouter.namespace(|| "load first row"))?; 46 | 47 | let (b, c, d) = chip.assign_advice_row( 48 | layouter.namespace(|| "load row"), 49 | self.a, 50 | prev_b.clone(), 51 | prev_c.clone(), 52 | prev_d.clone(), 53 | )?; 54 | 55 | // check computation result 56 | chip.expose_public(layouter.namespace(|| "overflow check"), &b, 2)?; 57 | chip.expose_public(layouter.namespace(|| "sum_high check"), &c, 3)?; 58 | chip.expose_public(layouter.namespace(|| "sum_low check"), &d, 4)?; 59 | Ok(()) 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use std::panic; 66 | use super::OverflowCheckCircuit; 67 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::bn256::Fr as Fp}; 68 | #[test] 69 | fn test_none_overflow_case() { 70 | let k = 4; 71 | 72 | // a: new value 73 | let a = Value::known(Fp::from((1 << 16) + 3)); 74 | let public_inputs = vec![ 75 | // initial values for A[3], A[4], last two columns 76 | Fp::from(0), 77 | Fp::from((1 << 16) - 2), 78 | // 79 | // checking value 80 | Fp::from(0), // 2^32 <- 0 means not overflowed 81 | Fp::from(2), // 2^16 82 | Fp::from(1), // 2^0 83 | ]; 84 | 85 | let circuit = OverflowCheckCircuit { a }; 86 | let prover = MockProver::run(k, &circuit, vec![public_inputs.clone()]).unwrap(); 87 | prover.assert_satisfied(); 88 | assert_eq!(prover.verify(), Ok(())); 89 | } 90 | 91 | #[test] 92 | fn test_overflow_case() { 93 | let k = 4; 94 | 95 | // a: new value 96 | let a = Value::known(Fp::from((1 << 32) + 2)); 97 | let public_inputs = vec![ 98 | // initial values for A[3], A[4], last two columns 99 | Fp::from(0), // 0 * 2^16 100 | Fp::from((1 << 16) - 1), // only for testing, over 2^16 is not allowed on accumulated columns 101 | // 102 | // checking value 103 | Fp::from(1), // 2^32 <- not 0 means overflowed 104 | Fp::from(1), // 2^16 105 | Fp::from(1), // 2^0 106 | ]; 107 | 108 | let circuit = OverflowCheckCircuit { a }; 109 | let prover = MockProver::run(k, &circuit, vec![public_inputs]).unwrap(); 110 | 111 | // TODO: should check panic message 112 | let panic_result = panic::catch_unwind(|| prover.assert_satisfied()); 113 | assert!(panic_result.is_err()); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/circuits/overflow_check_v2.rs: -------------------------------------------------------------------------------- 1 | use eth_types::Field; 2 | use halo2_proofs::{circuit::*, plonk::*}; 3 | 4 | use super::super::chips::overflow_check_v2::{OverflowCheckV2Config, OverflowChipV2}; 5 | // use crate::chips::utils::{decompose_bigInt_to_ubits, value_f_to_big_uint}; 6 | 7 | #[derive(Default)] 8 | struct OverflowCheckCircuitV2 { 9 | pub a: Value, 10 | pub b: Value, 11 | } 12 | 13 | impl Circuit for OverflowCheckCircuitV2 { 14 | type Config = OverflowCheckV2Config<4, 4>; 15 | type FloorPlanner = SimpleFloorPlanner; 16 | 17 | fn without_witnesses(&self) -> Self { 18 | Self::default() 19 | } 20 | 21 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 22 | let col_a = meta.advice_column(); 23 | let col_b = meta.advice_column(); 24 | let col_c = meta.advice_column(); 25 | let col_d = meta.advice_column(); 26 | let col_e = meta.advice_column(); 27 | let u8 = meta.fixed_column(); 28 | let selector = meta.selector(); 29 | let instance = meta.instance_column(); 30 | 31 | OverflowChipV2::configure( 32 | meta, 33 | col_a, 34 | [col_b, col_c, col_d, col_e], 35 | u8, 36 | instance, 37 | selector, 38 | ) 39 | } 40 | 41 | fn synthesize( 42 | &self, 43 | config: Self::Config, 44 | mut layouter: impl Layouter, 45 | ) -> Result<(), Error> { 46 | let chip = OverflowChipV2::construct(config); 47 | 48 | chip.load(&mut layouter)?; 49 | 50 | // check overflow 51 | chip.assign(layouter.namespace(|| "checking overflow value a"), self.a)?; 52 | chip.assign(layouter.namespace(|| "checking overflow value b"), self.b)?; 53 | chip.assign( 54 | layouter.namespace(|| "checking overflow value a + b"), 55 | self.a + self.b, 56 | )?; 57 | 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::OverflowCheckCircuitV2; 65 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::bn256::Fr as Fp}; 66 | #[test] 67 | fn test_none_overflow_case() { 68 | let k = 5; 69 | 70 | // a: new value 71 | let a = Value::known(Fp::from((1 << 16) - 2)); 72 | let b = Value::known(Fp::from(1)); 73 | 74 | let circuit = OverflowCheckCircuitV2:: { a, b }; 75 | let prover = MockProver::run(k, &circuit, vec![vec![]]).unwrap(); 76 | prover.assert_satisfied(); 77 | } 78 | 79 | #[test] 80 | fn test_overflow_case() { 81 | let k = 5; 82 | 83 | // a: new value 84 | let a = Value::known(Fp::from((1 << 16) - 2)); 85 | let b = Value::known(Fp::from(3)); 86 | 87 | let circuit = OverflowCheckCircuitV2 { a, b }; 88 | let invalid_prover = MockProver::run(k, &circuit, vec![vec![]]).unwrap(); 89 | assert!(invalid_prover.verify().is_err()); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/circuits/poseidon.rs: -------------------------------------------------------------------------------- 1 | use super::super::chips::poseidon::hash_with_instance::{PoseidonChip, PoseidonConfig}; 2 | use halo2_gadgets::poseidon::primitives::*; 3 | use halo2_proofs::{circuit::*, arithmetic::FieldExt, plonk::*}; 4 | use std::marker::PhantomData; 5 | 6 | struct PoseidonCircuit< 7 | F: FieldExt, 8 | S: Spec, 9 | const WIDTH: usize, 10 | const RATE: usize, 11 | const L: usize, 12 | > { 13 | hash_input: [Value; L], 14 | digest: Value, 15 | _spec: PhantomData, 16 | } 17 | 18 | impl, const WIDTH: usize, const RATE: usize, const L: usize> Circuit 19 | for PoseidonCircuit 20 | { 21 | type Config = PoseidonConfig; 22 | type FloorPlanner = SimpleFloorPlanner; 23 | 24 | fn without_witnesses(&self) -> Self { 25 | Self { 26 | hash_input: (0..L) 27 | .map(|_i| Value::unknown()) 28 | .collect::>>() 29 | .try_into() 30 | .unwrap(), 31 | digest: Value::unknown(), 32 | _spec: PhantomData, 33 | } 34 | } 35 | 36 | fn configure(meta: &mut ConstraintSystem) -> PoseidonConfig { 37 | let instance = meta.instance_column(); 38 | let hash_inputs = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); 39 | 40 | PoseidonChip::::configure(meta, hash_inputs, instance) 41 | } 42 | 43 | fn synthesize( 44 | &self, 45 | config: PoseidonConfig, 46 | mut layouter: impl Layouter, 47 | ) -> Result<(), Error> { 48 | let poseidon_chip = PoseidonChip::::construct(config); 49 | let assigned_input_cells = poseidon_chip.load_private_inputs( 50 | layouter.namespace(|| "load private inputs"), 51 | self.hash_input, 52 | )?; 53 | let digest = poseidon_chip.hash( 54 | layouter.namespace(|| "poseidon chip"), 55 | &assigned_input_cells, 56 | )?; 57 | poseidon_chip.expose_public(layouter.namespace(|| "expose result"), &digest, 0)?; 58 | Ok(()) 59 | } 60 | } 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::super::super::chips::poseidon::spec::MySpec; 65 | use super::PoseidonCircuit; 66 | use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength}; 67 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::pasta::Fp}; 68 | use std::marker::PhantomData; 69 | #[test] 70 | fn test_poseidon() { 71 | let input = 99u64; 72 | let hash_input = [ 73 | Fp::from(input), 74 | Fp::from(input), 75 | Fp::from(input), 76 | Fp::from(input), 77 | ]; 78 | 79 | const WIDTH: usize = 5; 80 | const RATE: usize = 4; 81 | const L: usize = 4; 82 | 83 | assert_eq!(hash_input.len(), L); 84 | assert_eq!(WIDTH, hash_input.len() + 1); 85 | assert_eq!(RATE, hash_input.len()); 86 | 87 | // compute the hash outside of the circuit 88 | let digest = 89 | poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init() 90 | .hash(hash_input); 91 | 92 | let circuit = PoseidonCircuit::, WIDTH, RATE, L> { 93 | hash_input: hash_input.map(Value::known), 94 | digest: Value::known(digest), 95 | _spec: PhantomData, 96 | }; 97 | let public_input = vec![digest]; 98 | let prover = MockProver::run(7, &circuit, vec![public_input]).unwrap(); 99 | prover.assert_satisfied(); 100 | } 101 | 102 | #[cfg(feature = "dev-graph")] 103 | #[test] 104 | fn print_poseidon() { 105 | use super::super::super::chips::poseidon::spec::MySpec; 106 | use halo2_proofs::halo2curves::pasta::Fp; 107 | use plotters::prelude::*; 108 | 109 | let root = 110 | BitMapBackend::new("prints/poseidon-layout.png", (1024, 3096)).into_drawing_area(); 111 | root.fill(&WHITE).unwrap(); 112 | let root = root.titled("Posiedon Layout", ("sans-serif", 60)).unwrap(); 113 | 114 | let input = 99u64; 115 | let hash_input = [ 116 | Fp::from(input), 117 | Fp::from(input), 118 | Fp::from(input), 119 | Fp::from(input), 120 | ]; 121 | 122 | const WIDTH: usize = 5; 123 | const RATE: usize = 4; 124 | const L: usize = 4; 125 | 126 | let digest = 127 | poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init() 128 | .hash(hash_input); 129 | 130 | let circuit = PoseidonCircuit::, WIDTH, RATE, L> { 131 | hash_input: hash_input.map(|x| Value::known(x)), 132 | digest: Value::known(digest), 133 | _spec: PhantomData, 134 | }; 135 | 136 | halo2_proofs::dev::CircuitLayout::default() 137 | .render(7, &circuit, &root) 138 | .unwrap(); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/circuits/safe_accumulator.rs: -------------------------------------------------------------------------------- 1 | use arrayvec::ArrayVec; 2 | use eth_types::Field; 3 | use halo2_proofs::{circuit::*, plonk::*}; 4 | 5 | use super::super::chips::safe_accumulator::{SafeACcumulatorChip, SafeAccumulatorConfig}; 6 | 7 | #[derive(Default)] 8 | struct SafeAccumulatorCircuit { 9 | pub values: Vec>, 10 | pub accumulated_value: [Value; 4], 11 | } 12 | 13 | impl Circuit for SafeAccumulatorCircuit { 14 | type Config = SafeAccumulatorConfig<4, 4, F>; // 4 bits for each column and 4 columns 15 | type FloorPlanner = SimpleFloorPlanner; 16 | 17 | fn without_witnesses(&self) -> Self { 18 | Self::default() 19 | } 20 | 21 | fn configure(meta: &mut ConstraintSystem) -> Self::Config { 22 | let new_value = meta.advice_column(); 23 | let left_most_acc_inv = meta.advice_column(); 24 | let carry_cols = [ 25 | meta.advice_column(), 26 | meta.advice_column(), 27 | meta.advice_column(), 28 | meta.advice_column(), 29 | ]; 30 | let acc_cols = [ 31 | meta.advice_column(), 32 | meta.advice_column(), 33 | meta.advice_column(), 34 | meta.advice_column(), 35 | ]; 36 | let add_selector = meta.selector(); 37 | let overflow_selector = meta.selector(); 38 | let boolean_selector = meta.selector(); 39 | let instance = meta.instance_column(); 40 | 41 | SafeACcumulatorChip::<4, 4, F>::configure( 42 | meta, 43 | new_value, 44 | left_most_acc_inv, 45 | carry_cols, 46 | acc_cols, 47 | [boolean_selector, add_selector, overflow_selector], 48 | instance, 49 | ) 50 | } 51 | 52 | fn synthesize( 53 | &self, 54 | config: Self::Config, 55 | mut layouter: impl Layouter, 56 | ) -> Result<(), Error> { 57 | let chip = SafeACcumulatorChip::construct(config); 58 | 59 | let (mut assigned_cells, mut previous_accumulates) = chip 60 | .assign( 61 | layouter.namespace(|| "initial rows"), 62 | 0, 63 | self.values[0], 64 | self.accumulated_value, 65 | ) 66 | .unwrap(); 67 | 68 | // Actually, there is no need to multiple values for a single user. 69 | // It may need multiple values who has multiple accounts in same identity 70 | // so, I just keep this code for now. 71 | let mut latest_accumulates: [Value; 4]; 72 | for (i, v) in self.values.iter().skip(1).enumerate() { 73 | (assigned_cells, latest_accumulates) = chip 74 | .assign( 75 | layouter.namespace(|| "additional rows"), 76 | i, 77 | *v, 78 | previous_accumulates, 79 | ) 80 | .unwrap(); 81 | previous_accumulates = latest_accumulates; 82 | } 83 | 84 | // check assigned cells values are correct with instance 85 | for (i, cell) in assigned_cells.iter().rev().enumerate() { 86 | chip.expose_public(layouter.namespace(|| format!("accumulate_{}", i)), cell, i)?; 87 | } 88 | 89 | Ok(()) 90 | } 91 | } 92 | 93 | #[cfg(test)] 94 | mod tests { 95 | use super::SafeAccumulatorCircuit; 96 | use halo2_proofs::{circuit::Value, dev::MockProver, halo2curves::bn256::Fr as Fp}; 97 | 98 | #[test] 99 | fn test_none_overflow_case() { 100 | let k = 8; 101 | 102 | let values = vec![Value::known(Fp::from(4))]; 103 | let accumulated_value = [ 104 | Value::known(Fp::from(0)), 105 | Value::known(Fp::from(0)), 106 | Value::known(Fp::from((1 << 4) - 2)), // 0xe 107 | Value::known(Fp::from((1 << 4) - 3)), // 0xd 108 | ]; 109 | 110 | let result_accumulated = vec![ 111 | Fp::from(0), 112 | Fp::from(0), 113 | Fp::from((1 << 4) - 1), // 0xf 114 | Fp::from(1), // 0x1 115 | ]; 116 | 117 | let circuit = SafeAccumulatorCircuit:: { 118 | values, 119 | accumulated_value, 120 | }; 121 | let prover = MockProver::run(k, &circuit, vec![result_accumulated]).unwrap(); 122 | prover.assert_satisfied(); 123 | } 124 | 125 | #[test] 126 | fn test_none_overflow_case_with_multiple_values() { 127 | let k = 8; 128 | 129 | let values = vec![Value::known(Fp::from(1)), Value::known(Fp::from(3))]; 130 | let accumulated_value = [ 131 | Value::known(Fp::from(0)), 132 | Value::known(Fp::from(0)), 133 | Value::known(Fp::from((1 << 4) - 2)), // 0xe 134 | Value::known(Fp::from((1 << 4) - 3)), // 0xd 135 | ]; 136 | 137 | let result_accumulated = vec![ 138 | Fp::from(0), 139 | Fp::from(0), 140 | Fp::from((1 << 4) - 1), // 0xf 141 | Fp::from(1), // 0x1 142 | ]; 143 | 144 | let circuit = SafeAccumulatorCircuit { 145 | values, 146 | accumulated_value, 147 | }; 148 | let prover = MockProver::run(k, &circuit, vec![result_accumulated]).unwrap(); 149 | prover.assert_satisfied(); 150 | } 151 | 152 | #[test] 153 | fn test_overflow_case() { 154 | let k = 8; 155 | 156 | let values = vec![Value::known(Fp::from(4))]; 157 | let accumulated_value = [ 158 | Value::known(Fp::from(0)), 159 | Value::known(Fp::from((1 << 4) - 1)), // 0xf 160 | Value::known(Fp::from((1 << 4) - 1)), // 0xf 161 | Value::known(Fp::from((1 << 4) - 3)), // 0xd 162 | ]; 163 | 164 | let circuit = SafeAccumulatorCircuit { 165 | values, 166 | accumulated_value, 167 | }; 168 | let invalid_prover = MockProver::run(k, &circuit, vec![vec![]]).unwrap(); 169 | assert!(invalid_prover.verify().is_err()); 170 | } 171 | 172 | #[test] 173 | fn test_adding_over_range_value() { 174 | let k = 8; 175 | 176 | let invalid_values = vec![Value::known(Fp::from(16))]; 177 | let accumulated_value = [ 178 | Value::known(Fp::from(0)), 179 | Value::known(Fp::from(0)), 180 | Value::known(Fp::from((1 << 4) - 2)), // 0xe 181 | Value::known(Fp::from((1 << 4) - 1)), // 0xf 182 | ]; 183 | 184 | let circuit = SafeAccumulatorCircuit { 185 | values: invalid_values, 186 | accumulated_value, 187 | }; 188 | let invalid_prover = MockProver::run(k, &circuit, vec![vec![]]).unwrap(); 189 | assert!(invalid_prover.verify().is_err()); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/circuits/utils.rs: -------------------------------------------------------------------------------- 1 | use halo2_proofs::{ 2 | halo2curves::bn256::{Fr as Fp, Bn256, G1Affine}, 3 | poly::{ 4 | commitment::ParamsProver, 5 | kzg::{ 6 | commitment::{ 7 | ParamsKZG, 8 | KZGCommitmentScheme, 9 | }, 10 | strategy::SingleStrategy, 11 | multiopen::{ProverSHPLONK, VerifierSHPLONK} 12 | }, 13 | }, 14 | plonk::{ 15 | create_proof, verify_proof, keygen_pk, keygen_vk, Circuit 16 | }, 17 | transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer}, 18 | }; 19 | use std::time::Instant; 20 | use rand::rngs::OsRng; 21 | 22 | pub fn full_prover > ( 23 | circuit: C, 24 | k: u32, 25 | public_input: &[Fp] 26 | ) { 27 | 28 | let params = ParamsKZG::::setup(k, OsRng); 29 | 30 | let vk_time_start = Instant::now(); 31 | let vk = keygen_vk(¶ms, &circuit).unwrap(); 32 | let vk_time = vk_time_start.elapsed(); 33 | 34 | let pk_time_start = Instant::now(); 35 | let pk = keygen_pk(¶ms, vk, &circuit).unwrap(); 36 | let pk_time = pk_time_start.elapsed(); 37 | 38 | let proof_time_start = Instant::now(); 39 | let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); 40 | create_proof::< 41 | KZGCommitmentScheme, 42 | ProverSHPLONK<'_, Bn256>, 43 | Challenge255, 44 | _, 45 | Blake2bWrite, G1Affine, Challenge255>, 46 | _, 47 | >(¶ms, &pk, &[circuit], &[&[public_input]], OsRng, &mut transcript) 48 | .expect("prover should not fail"); 49 | let proof = transcript.finalize(); 50 | let proof_time = proof_time_start.elapsed(); 51 | 52 | let verifier_params = params.verifier_params(); 53 | let verify_time_start = Instant::now(); 54 | let strategy = SingleStrategy::new(¶ms); 55 | let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); 56 | assert!(verify_proof::< 57 | KZGCommitmentScheme, 58 | VerifierSHPLONK<'_, Bn256>, 59 | Challenge255, 60 | Blake2bRead<&[u8], G1Affine, Challenge255>, 61 | SingleStrategy<'_, Bn256>, 62 | >(verifier_params, pk.get_vk(), strategy, &[&[public_input]], &mut transcript) 63 | .is_ok()); 64 | let verify_time = verify_time_start.elapsed(); 65 | 66 | println!("Time to generate vk {:?}", vk_time); 67 | println!("Time to generate pk {:?}", pk_time); 68 | println!("Prover Time {:?}", proof_time); 69 | println!("Verifier Time {:?}", verify_time); 70 | } -------------------------------------------------------------------------------- /src/decompose_bigInt.rs: -------------------------------------------------------------------------------- 1 | use halo2curves::{ff::PrimeField, pasta::*}; 2 | use num_bigint::BigUint; 3 | 4 | pub fn decompose_bigInt_to_ubits( 5 | e: &BigUint, 6 | number_of_limbs: usize, 7 | bit_len: usize, 8 | ) -> Vec { 9 | debug_assert!(bit_len <= 64); 10 | 11 | let mut e = e.iter_u64_digits(); 12 | let mask: u64 = (1u64 << bit_len) - 1u64; 13 | let mut u64_digit = e.next().unwrap_or(0); 14 | let mut rem = 64; 15 | (0..number_of_limbs) 16 | .map(|_| match rem.cmp(&bit_len) { 17 | core::cmp::Ordering::Greater => { 18 | let limb = u64_digit & mask; 19 | u64_digit >>= bit_len; 20 | rem -= bit_len; 21 | Fp::from(limb) 22 | } 23 | core::cmp::Ordering::Equal => { 24 | let limb = u64_digit & mask; 25 | u64_digit = e.next().unwrap_or(0); 26 | rem = 64; 27 | Fp::from(limb) 28 | } 29 | core::cmp::Ordering::Less => { 30 | let mut limb = u64_digit; 31 | u64_digit = e.next().unwrap_or(0); 32 | limb |= (u64_digit & ((1 << (bit_len - rem)) - 1)) << rem; // * 33 | u64_digit >>= bit_len - rem; 34 | rem += 64 - bit_len; 35 | Fp::from(limb) 36 | } 37 | }) 38 | .collect() 39 | } 40 | 41 | pub fn decompose_biguint_u64(e: &BigUint, num_limbs: usize, bit_len: usize) -> Vec { 42 | debug_assert!(bit_len > 64 && bit_len <= 128); 43 | let mut e = e.iter_u64_digits(); 44 | 45 | let mut limb0 = e.next().unwrap_or(0) as u128; 46 | let mut rem = bit_len - 64; 47 | let mut u64_digit = e.next().unwrap_or(0); 48 | limb0 |= ((u64_digit & ((1 << rem) - 1)) as u128) << 64; 49 | u64_digit >>= rem; 50 | rem = 64 - rem; 51 | 52 | core::iter::once(Fp::from_u128(limb0)) 53 | .chain((1..num_limbs).map(|_| { 54 | let mut limb: u128 = u64_digit.into(); 55 | let mut bits = rem; 56 | u64_digit = e.next().unwrap_or(0); 57 | if bit_len - bits >= 64 { 58 | limb |= (u64_digit as u128) << bits; 59 | u64_digit = e.next().unwrap_or(0); 60 | bits += 64; 61 | } 62 | rem = bit_len - bits; 63 | limb |= ((u64_digit & ((1 << rem) - 1)) as u128) << bits; 64 | u64_digit >>= rem; 65 | rem = 64 - rem; 66 | Fp::from_u128(limb) 67 | })) 68 | .collect() 69 | } 70 | 71 | mod tests { 72 | use super::*; 73 | use halo2curves::pasta::Fp; 74 | use num_bigint::BigUint; 75 | 76 | #[test] 77 | fn test_decompose_bigInt_toU64() { 78 | let a = BigUint::new(vec![u32::MAX, u32::MAX - 1]); 79 | let decom = decompose_biguint_u64(&a, 3, 65); 80 | // println!("decomposed a: {:?}", decom); 81 | assert_eq!(decom[0], Fp::from(u64::MAX - 1)); 82 | } 83 | 84 | #[test] 85 | fn test_decompose_bigInt_to_u16() { 86 | let b = BigUint::new(vec![u32::MAX, u32::MAX]); 87 | let decom = decompose_bigInt_to_ubits(&b, 12, 16); 88 | // println!("decompose_Fps: {:?}", decom); 89 | assert_eq!(decom[0], Fp::from(65535)); 90 | assert_eq!(decom[1], Fp::from(65535)); 91 | assert_eq!(decom[2], Fp::from(65535)); 92 | assert_eq!(decom[3], Fp::from(65535)); 93 | assert_eq!(decom[4], Fp::zero()); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod chips; 2 | pub mod circuits; --------------------------------------------------------------------------------