From 897eb04f9ed2bfe78384fbca3cb04f1e18a28dfa Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 12 Apr 2023 15:36:52 +0200 Subject: [PATCH 01/25] refactor `assing_leaf_hash_and_balance` function --- README.md | 2 +- src/chips/merkle_sum_tree.rs | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0eb6ae4b..2e8cb13c 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ The LessThan Chip Configuration contains: - 1 advice column `lt` that denotes the result of the comparison: 1 if `lhs < rhs` and 0 otherwise - An array of `diff` advice columns of length N_BYTES. It is basically the difference between `lhs` and `rhs` expressed in 8-bit chunks. -- An field element `range` that denotes the range in which both `lhs` and `rhs` are expected to be. This is calculated as `2^N_BYTES * 8` where `N_BYTES` is the number of bytes that we want to use to represent the values `lhs` and `rhs`. +- An field element `range` that denotes the range, expressed in bits, in which both `lhs` and `rhs` are expected to be. This is calculated as `2^N_BYTES * 8` where `N_BYTES` is the number of bytes that we want to use to represent the values `lhs` and `rhs`. The configure function takes as input the lhs and rhs virtual cells from a higher level chip and enforces the following gate: diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index 2f904d60..c3a70f22 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -142,21 +142,30 @@ impl MerkleSumTreeChip { leaf_hash: F, leaf_balance: F, ) -> Result<(AssignedCell, AssignedCell), Error> { - let leaf_hash_cell = layouter.assign_region( + let (leaf_hash_cell, leaf_balance_cell) = layouter.assign_region( || "assign leaf hash", |mut region| { - region.assign_advice(|| "leaf hash", self.config.advice[0], 0, || Value::known(leaf_hash)) - }, - )?; - let leaf_balance_cell = layouter.assign_region( - || "assign leaf balance", - |mut region| { - region.assign_advice(|| "leaf balance", self.config.advice[1], 0, || Value::known(leaf_balance)) + let l = region.assign_advice( + || "leaf hash", + self.config.advice[0], + 0, + || Value::known(leaf_hash) + )?; + + let r = region.assign_advice( + || "leaf balance", + self.config.advice[1], + 0, + || Value::known(leaf_balance) + )?; + + Ok((l, r)) }, )?; Ok((leaf_hash_cell, leaf_balance_cell)) + } pub fn merkle_prove_layer( @@ -342,7 +351,7 @@ impl MerkleSumTreeChip { Ok(()) } - // Enforce permutation check between input cell and instance column at row passed as input + // Enforce copy constraint check between input cell and instance column at row passed as input pub fn expose_public( &self, mut layouter: impl Layouter, From 1d3bf85405f86b872d7f183743baa70a580d8bd9 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:35:08 +0200 Subject: [PATCH 02/25] minor fixing --- src/chips/merkle_sum_tree.rs | 1 - src/circuits/merkle_sum_tree.rs | 31 +------------------------------ 2 files changed, 1 insertion(+), 31 deletions(-) diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index c3a70f22..9c75ca6c 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -220,7 +220,6 @@ impl MerkleSumTreeChip { let mut r1_val = r1.value().map(|x| x.to_owned()); let mut r2_val = r2.value().map(|x| x.to_owned()); - self.config.sum_selector.enable(&mut region, 1)?; // if index is 0 return (l1, l2, r1, r2) else return (r1, r2, l1, l2) diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index a0a5a1ee..d2930d7b 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -49,7 +49,7 @@ impl Circuit for MerkleSumTreeCircuit { ) -> Result<(), Error> { let chip = MerkleSumTreeChip::construct(config); - let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance(layouter.namespace(|| "assign leaf"), F::from(self.leaf_hash), F::from(self.leaf_balance))?; + let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance(layouter.namespace(|| "assign leaf"), self.leaf_hash, self.leaf_balance)?; chip.expose_public(layouter.namespace(|| "public leaf hash"), &leaf_hash, 0)?; chip.expose_public(layouter.namespace(|| "public leaf balance"), &leaf_balance, 1)?; @@ -99,35 +99,6 @@ mod tests { }; use std::marker::PhantomData; use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; - - // const WIDTH: usize = 5; - // const RATE: usize = 4; - // const L: usize = 4; - - // #[derive(Debug, Clone)] - // struct Node { - // pub hash: Fp, - // pub balance: Fp, - // } - - // fn compute_merkle_sum_root(node: &Node, elements: &Vec, indices: &Vec) -> Node { - // let k = elements.len(); - // let mut digest = node.clone(); - // let mut message: [Fp; 4]; - // for i in 0..k { - // if indices[i] == 0.into() { - // message = [digest.hash, digest.balance, elements[i].hash, elements[i].balance]; - // } else { - // message = [elements[i].hash, elements[i].balance, digest.hash, digest.balance]; - // } - - // digest.hash = poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init() - // .hash(message); - - // digest.balance = digest.balance + elements[i].balance; - // } - // digest - // } fn instantiate_circuit(assets_sum: Fp) -> MerkleSumTreeCircuit{ From 635622b15988e58e92ca9aeff139d91c823b8a4c Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:29:45 +0200 Subject: [PATCH 03/25] add better error handling in test --- src/circuits/merkle_sum_tree.rs | 36 +++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index d2930d7b..94eeaa16 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -94,8 +94,9 @@ mod tests { use super::MerkleSumTreeCircuit; use halo2_proofs::{ - dev::MockProver, + dev::{MockProver, VerifyFailure, FailureLocation}, halo2curves::bn256::{Fr as Fp}, + plonk::{Any}, }; use std::marker::PhantomData; use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; @@ -149,11 +150,13 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // invalid_prover.assert_satisfied(); + let result = invalid_prover.verify(); - // 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 17 ('permute state') at offset 36)]) - // computed_hash (advice column[5]) != root.hash (instance column row 2) - assert!(invalid_prover.verify().is_err()); + let error = result.unwrap_err(); + + let expected_error = "[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 16 ('permute state') at offset 36)]"; + + assert_eq!(format!("{:?}", error), expected_error); } #[test] @@ -169,9 +172,12 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // 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) - // leaf_hash (advice column[0]) != leaf.hash (instance column row 0) - assert!(invalid_prover.verify().is_err()); + let result = invalid_prover.verify(); + + let error = result.unwrap_err(); + let expected_error = "[Equality constraint not satisfied by cell (Column('Advice', 0 - ), in Region 1 ('merkle prove layer') at offset 0), Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 0)]"; + + assert_eq!(format!("{:?}", error), expected_error); } @@ -188,10 +194,12 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // 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) - // leaf_balance (advice column[1]) != leaf.balance (instance column row 1) - - assert!(invalid_prover.verify().is_err()); + let result = invalid_prover.verify(); + + let error = result.unwrap_err(); + let expected_error = "[Equality constraint not satisfied by cell (Column('Advice', 1 - ), in Region 1 ('merkle prove layer') at offset 0), Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 1)]"; + + assert_eq!(format!("{:?}", error), expected_error); } #[test] @@ -286,6 +294,4 @@ mod tests { .render(8, &circuit, &root) .unwrap(); } -} - - +} \ No newline at end of file From 247f7fd433345847ff18c2aae76d88ba9ae1b67b Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:42:13 +0200 Subject: [PATCH 04/25] added deps to support circuit printing --- Cargo.lock | 891 +++++++++++++++++++++++++++++- Cargo.toml | 7 +- prints/merkle-sum-tree-layout.png | Bin 0 -> 66203 bytes src/circuits/merkle_sum_tree.rs | 8 +- 4 files changed, 871 insertions(+), 35 deletions(-) create mode 100644 prints/merkle-sum-tree-layout.png diff --git a/Cargo.lock b/Cargo.lock index 2085a230..bf90b7fe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + [[package]] name = "aes" version = "0.7.5" @@ -45,6 +51,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" + [[package]] name = "arrayref" version = "0.3.7" @@ -65,7 +86,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -141,6 +162,12 @@ dependencies = [ "serde", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitvec" version = "0.17.4" @@ -336,6 +363,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + [[package]] name = "byteorder" version = "1.4.3" @@ -351,6 +384,12 @@ dependencies = [ "serde", ] +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + [[package]] name = "cfg-if" version = "1.0.0" @@ -363,8 +402,13 @@ version = "0.4.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" dependencies = [ + "iana-time-zone", + "js-sys", "num-integer", "num-traits", + "time", + "wasm-bindgen", + "winapi", ] [[package]] @@ -385,6 +429,27 @@ dependencies = [ "halo2_gadgets", "halo2_proofs", "merkle-sum-tree-rust", + "plotters", + "tabbycat", +] + +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", ] [[package]] @@ -440,10 +505,22 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.6", - "sha3 0.10.6", + "sha3 0.10.7", "thiserror", ] +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "const-cstr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" + [[package]] name = "const-oid" version = "0.9.2" @@ -462,6 +539,59 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb4a24b1aaf0fd0ce8b45161144d6f42cd91677fd5940fd431183eb023b3a2b8" +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + [[package]] name = "cpufeatures" version = "0.2.6" @@ -471,11 +601,20 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -582,14 +721,82 @@ dependencies = [ "cipher", ] +[[package]] +name = "cxx" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn 2.0.14", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.14", +] + +[[package]] +name = "darling" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858" +dependencies = [ + "darling_core 0.10.2", + "darling_macro 0.10.2", +] + [[package]] name = "darling" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.13.4", + "darling_macro 0.13.4", +] + +[[package]] +name = "darling_core" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.9.3", + "syn 1.0.109", ] [[package]] @@ -602,7 +809,18 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", + "syn 1.0.109", +] + +[[package]] +name = "darling_macro" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72" +dependencies = [ + "darling_core 0.10.2", + "quote", "syn 1.0.109", ] @@ -612,7 +830,7 @@ version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" dependencies = [ - "darling_core", + "darling_core 0.13.4", "quote", "syn 1.0.109", ] @@ -627,6 +845,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derive_builder" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0" +dependencies = [ + "darling 0.10.2", + "derive_builder_core", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_builder_core" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef" +dependencies = [ + "darling 0.10.2", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "digest" version = "0.7.6" @@ -665,6 +908,48 @@ dependencies = [ "subtle", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dlib" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac1b7517328c04c2aa68422fc60a41b92208182142ed04a25879c26c8f878794" +dependencies = [ + "libloading", +] + +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "winapi", + "wio", +] + [[package]] name = "ecdsa" version = "0.14.8" @@ -720,7 +1005,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.6", - "sha3 0.10.6", + "sha3 0.10.7", "thiserror", "uuid", ] @@ -728,7 +1013,7 @@ dependencies = [ [[package]] name = "eth-types" version = "0.1.0" -source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#d4b4b81c8b57afcde8f24adee2abcef1cd10cbad" +source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#fa320ea0435320a8312a383dfa0eb9e8bcd61dc8" dependencies = [ "ethers-core", "ethers-signers", @@ -743,7 +1028,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "sha3 0.10.6", + "sha3 0.10.7", "strum", "strum_macros", "subtle", @@ -762,7 +1047,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.6", + "sha3 0.10.7", "thiserror", "uint", ] @@ -873,6 +1158,15 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + [[package]] name = "ff" version = "0.12.1" @@ -896,12 +1190,89 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide 0.6.2", +] + +[[package]] +name = "float-ord" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "font-kit" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" +dependencies = [ + "bitflags", + "byteorder", + "core-foundation", + "core-graphics", + "core-text", + "dirs-next", + "dwrote", + "float-ord", + "freetype", + "lazy_static", + "libc", + "log", + "pathfinder_geometry", + "pathfinder_simd", + "walkdir", + "winapi", + "yeslogic-fontconfig-sys", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "freetype" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee38378a9e3db1cc693b4f88d166ae375338a0ff75cb8263e1c601d51f35dc6" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "funty" version = "2.0.0" @@ -911,7 +1282,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gadgets" version = "0.1.0" -source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#d4b4b81c8b57afcde8f24adee2abcef1cd10cbad" +source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#fa320ea0435320a8312a383dfa0eb9e8bcd61dc8" dependencies = [ "digest 0.7.6", "eth-types", @@ -950,17 +1321,27 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "gif" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3edd93c6756b4dfaf2709eafcc345ba2636565295c198a9cfbf75fa5e3e00b06" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "group" version = "0.12.1" @@ -1010,9 +1391,11 @@ dependencies = [ "ff", "group", "halo2curves 0.3.1", + "plotters", "rand_core", "rayon", "sha3 0.9.1", + "tabbycat", "tracing", ] @@ -1119,12 +1502,51 @@ dependencies = [ "hmac 0.8.1", ] +[[package]] +name = "iana-time-zone" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "image" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -1188,6 +1610,12 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + [[package]] name = "js-sys" version = "0.3.61" @@ -1207,7 +1635,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", - "sha3 0.10.6", + "sha3 0.10.7", ] [[package]] @@ -1231,6 +1659,16 @@ version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "libsecp256k1" version = "0.7.1" @@ -1279,6 +1717,15 @@ dependencies = [ "libsecp256k1-core", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "log" version = "0.4.17" @@ -1306,7 +1753,7 @@ dependencies = [ [[package]] name = "merkle-sum-tree-rust" version = "0.1.0" -source = "git+https://github.com/summa-dev/merkle-sum-tree-rust#8aeb7d7f30d1c6aac4d3809c4b482baeef0c05d3" +source = "git+https://github.com/summa-dev/merkle-sum-tree-rust#2e3914ad3441aeaa6d323f2c14600cdd52b04aee" dependencies = [ "csv", "halo2-experiments", @@ -1316,6 +1763,25 @@ dependencies = [ "serde", ] +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + [[package]] name = "num" version = "0.4.0" @@ -1483,6 +1949,25 @@ dependencies = [ "subtle", ] +[[package]] +name = "pathfinder_geometry" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b7e7b4ea703700ce73ebf128e1450eb69c3a8329199ffbfb9b2a0418e5ad3" +dependencies = [ + "log", + "pathfinder_simd", +] + +[[package]] +name = "pathfinder_simd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39fe46acc5503595e5949c17b818714d26fdf9b4920eacf3b2947f0199f4a6ff" +dependencies = [ + "rustc_version", +] + [[package]] name = "pbkdf2" version = "0.10.1" @@ -1504,6 +1989,16 @@ dependencies = [ "sha2 0.10.6", ] +[[package]] +name = "pest" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +dependencies = [ + "thiserror", + "ucd-trie", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1520,6 +2015,71 @@ dependencies = [ "spki", ] +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "plotters" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2538b639e642295546c50fcd545198c9d64ee2a38620a628724a3b266d5fbf97" +dependencies = [ + "chrono", + "font-kit", + "image", + "lazy_static", + "num-traits", + "pathfinder_geometry", + "plotters-backend", + "plotters-bitmap", + "plotters-svg", + "ttf-parser", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142" + +[[package]] +name = "plotters-bitmap" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4a1f21490a6cf4a84c272ad20bd7844ed99a3178187a4c5ab7f2051295beef" +dependencies = [ + "gif", + "image", + "plotters-backend", +] + +[[package]] +name = "plotters-svg" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "png" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.1", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1684,6 +2244,26 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + [[package]] name = "regex" version = "1.7.3" @@ -1800,6 +2380,15 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver", +] + [[package]] name = "rustversion" version = "1.0.12" @@ -1821,12 +2410,27 @@ dependencies = [ "cipher", ] +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" + [[package]] name = "scrypt" version = "0.8.1" @@ -1860,24 +2464,42 @@ dependencies = [ "zeroize", ] +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", ] [[package]] @@ -1907,7 +2529,7 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" dependencies = [ - "darling", + "darling 0.13.4", "proc-macro2", "quote", "syn 1.0.109", @@ -1975,9 +2597,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" dependencies = [ "digest 0.10.6", "keccak", @@ -1993,6 +2615,12 @@ dependencies = [ "rand_core", ] +[[package]] +name = "simd-adler32" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238abfbb77c1915110ad968465608b68e869e0772622c9656714e73e5a1a522f" + [[package]] name = "simdutf8" version = "0.1.4" @@ -2015,6 +2643,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "strsim" version = "0.10.0" @@ -2062,21 +2696,41 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.13" +version = "2.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tabbycat" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c45590f0f859197b4545be1b17b2bc3cc7bb075f7d1cc0ea1dc6521c0bf256a3" +dependencies = [ + "anyhow", + "derive_builder", + "regex", +] + [[package]] name = "tap" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -2094,7 +2748,18 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.13", + "syn 2.0.14", +] + +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", ] [[package]] @@ -2164,12 +2829,24 @@ dependencies = [ "once_cell", ] +[[package]] +name = "ttf-parser" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd" + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + [[package]] name = "uint" version = "0.9.5" @@ -2188,6 +2865,12 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" version = "0.2.4" @@ -2210,6 +2893,22 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "walkdir" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2270,6 +2969,119 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winnow" version = "0.4.1" @@ -2279,6 +3091,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "wio" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5" +dependencies = [ + "winapi", +] + [[package]] name = "wyz" version = "0.5.1" @@ -2288,6 +3109,18 @@ dependencies = [ "tap", ] +[[package]] +name = "yeslogic-fontconfig-sys" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" +dependencies = [ + "const-cstr", + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index 2b601e5c..5e21ef24 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,9 +5,14 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +dev-graph = ["halo2_proofs/dev-graph", "plotters"] + [dependencies] halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} -merkle-sum-tree-rust = { git = "https://github.com/summa-dev/merkle-sum-tree-rust"} \ No newline at end of file +merkle-sum-tree-rust = { git = "https://github.com/summa-dev/merkle-sum-tree-rust"} +plotters = { version = "0.3.0", optional = true } +tabbycat = { version = "0.1", features = ["attributes"], optional = true } \ No newline at end of file diff --git a/prints/merkle-sum-tree-layout.png b/prints/merkle-sum-tree-layout.png new file mode 100644 index 0000000000000000000000000000000000000000..c0e62fc4e99c7db6417e982b8f2c4109ec4aeb71 GIT binary patch literal 66203 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 Date: Thu, 13 Apr 2023 08:41:45 +0200 Subject: [PATCH 05/25] modify `enforce_less_than` function signature --- Cargo.toml | 2 +- src/chips/merkle_sum_tree.rs | 7 +++---- src/circuits/merkle_sum_tree.rs | 7 +++---- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 5e21ef24..e5c84844 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,5 +14,5 @@ halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} merkle-sum-tree-rust = { git = "https://github.com/summa-dev/merkle-sum-tree-rust"} -plotters = { version = "0.3.0", optional = true } +plotters = { version = "0.3.4", optional = true } tabbycat = { version = "0.1", features = ["attributes"], optional = true } \ No newline at end of file diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index 9c75ca6c..1d729c8d 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -302,8 +302,7 @@ impl MerkleSumTreeChip { &self, mut layouter: impl Layouter, prev_computed_sum_cell: &AssignedCell, - computed_sum: F, - total_assets: F, + computed_sum: F ) -> Result<(), Error> { // Initiate chip config @@ -322,7 +321,7 @@ impl MerkleSumTreeChip { )?; // copy the total assets from instance column to the cell in the second column - region.assign_advice_from_instance( + let total_assets_cell = region.assign_advice_from_instance( || "copy total assets", self.config.instance, 3, @@ -341,7 +340,7 @@ impl MerkleSumTreeChip { // enable lt seletor self.config.lt_selector.enable(&mut region, 0)?; - chip.assign(&mut region, 0, computed_sum, total_assets)?; + total_assets_cell.value().map(|total_assets| chip.assign(&mut region, 0, computed_sum, total_assets.to_owned())); Ok(()) }, diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 23257d65..4e83c67d 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -82,7 +82,7 @@ impl Circuit for MerkleSumTreeCircuit { let computed_sum = self.leaf_balance + self.path_element_balances.iter().fold(F::zero(), |acc, x| acc + x); // enforce computed sum to be less than the assets sum - chip.enforce_less_than(layouter.namespace(|| "enforce less than"), &next_sum, computed_sum, self.assets_sum)?; + chip.enforce_less_than(layouter.namespace(|| "enforce less than"), &next_sum, computed_sum)?; chip.expose_public(layouter.namespace(|| "public root"), &next_hash, 2)?; Ok(()) @@ -94,9 +94,8 @@ mod tests { use super::MerkleSumTreeCircuit; use halo2_proofs::{ - dev::{MockProver, VerifyFailure, FailureLocation}, + dev::{MockProver}, halo2curves::bn256::{Fr as Fp}, - plonk::{Any}, }; use std::marker::PhantomData; use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; @@ -289,7 +288,7 @@ mod tests { .unwrap(); halo2_proofs::dev::CircuitLayout::default() - .render(8, &circuit, &root) + .render(10, &circuit, &root) .unwrap(); } } \ No newline at end of file From 0bd026019b27acd6ca79cf9b60d5ee370c0b7ba3 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Thu, 13 Apr 2023 08:58:12 +0200 Subject: [PATCH 06/25] further modification of `enforce_less_than` function signature --- src/chips/merkle_sum_tree.rs | 17 ++++++++++++++--- src/circuits/merkle_sum_tree.rs | 5 +---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index 1d729c8d..09d41cce 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -302,7 +302,6 @@ impl MerkleSumTreeChip { &self, mut layouter: impl Layouter, prev_computed_sum_cell: &AssignedCell, - computed_sum: F ) -> Result<(), Error> { // Initiate chip config @@ -313,7 +312,7 @@ impl MerkleSumTreeChip { |mut region| { // copy the computed sum to the cell in the first column - prev_computed_sum_cell.copy_advice( + let computed_sum_cell = prev_computed_sum_cell.copy_advice( || "copy computed sum", &mut region, self.config.advice[0], @@ -340,7 +339,19 @@ impl MerkleSumTreeChip { // enable lt seletor self.config.lt_selector.enable(&mut region, 0)?; - total_assets_cell.value().map(|total_assets| chip.assign(&mut region, 0, computed_sum, total_assets.to_owned())); + let mut total_assets = F::from(0); + let mut computed_sum = F::from(0); + + total_assets_cell.value().map(|x| { + total_assets = x.to_owned(); + }); + + computed_sum_cell.value().map(|x| { + computed_sum = x.to_owned(); + }); + + chip.assign(&mut region, 0, computed_sum, total_assets)?; + Ok(()) }, diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 4e83c67d..11e84fc1 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -78,11 +78,8 @@ impl Circuit for MerkleSumTreeCircuit { )?; } - // compute the sum of the merkle sum tree as sum of the leaf balance and the sum of the path elements balances - let computed_sum = self.leaf_balance + self.path_element_balances.iter().fold(F::zero(), |acc, x| acc + x); - // enforce computed sum to be less than the assets sum - chip.enforce_less_than(layouter.namespace(|| "enforce less than"), &next_sum, computed_sum)?; + chip.enforce_less_than(layouter.namespace(|| "enforce less than"), &next_sum)?; chip.expose_public(layouter.namespace(|| "public root"), &next_hash, 2)?; Ok(()) From 19127d5055fd1a4f77b5ffd6dde22a5cc2dd6303 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Thu, 13 Apr 2023 09:27:40 +0200 Subject: [PATCH 07/25] reduce verbosity in `enforce_less_than` function --- src/chips/merkle_sum_tree.rs | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index 09d41cce..d711b377 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -219,7 +219,8 @@ impl MerkleSumTreeChip { let mut l2_val = l2.value().map(|x| x.to_owned()); let mut r1_val = r1.value().map(|x| x.to_owned()); let mut r2_val = r2.value().map(|x| x.to_owned()); - + + // Row 1 self.config.sum_selector.enable(&mut region, 1)?; // if index is 0 return (l1, l2, r1, r2) else return (r1, r2, l1, l2) @@ -339,20 +340,12 @@ impl MerkleSumTreeChip { // enable lt seletor self.config.lt_selector.enable(&mut region, 0)?; - let mut total_assets = F::from(0); - let mut computed_sum = F::from(0); - - total_assets_cell.value().map(|x| { - total_assets = x.to_owned(); - }); - - computed_sum_cell.value().map(|x| { - computed_sum = x.to_owned(); + total_assets_cell.value().zip(computed_sum_cell.value()).map(|(total_assets, computed_sum)| { + if let Err(e) = chip.assign(&mut region, 0, computed_sum.to_owned(), total_assets.to_owned()){ + println!("Error: {:?}", e); + }; }); - chip.assign(&mut region, 0, computed_sum, total_assets)?; - - Ok(()) }, )?; From f75a691531517579ec281350626c4d8ecd51f38d Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Thu, 13 Apr 2023 11:59:33 +0200 Subject: [PATCH 08/25] update testing --- Cargo.lock | 4 +- prints/merkle-sum-tree-layout.png | Bin 66203 -> 650564 bytes src/chips/merkle_sum_tree.rs | 2 +- src/circuits/merkle_sum_tree.rs | 137 ++++++++++++++++++++++-------- 4 files changed, 106 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bf90b7fe..2f069209 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2504,9 +2504,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", diff --git a/prints/merkle-sum-tree-layout.png b/prints/merkle-sum-tree-layout.png index c0e62fc4e99c7db6417e982b8f2c4109ec4aeb71..70b12220b4f6b822883b2d440f0eaa8509926280 100644 GIT binary patch literal 650564 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%0AT!MoG_CEo+J#fCprc4OE8J_COz)f<8*1dX00R7 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 MerkleSumTreeChip { lt_config }; - meta.create_gate("verifies that `check` from current config equal to is_lt from LtChip ", |meta| { + meta.create_gate("verifies that `check` from current config equal to is_lt from LtChip", |meta| { let q_enable = meta.query_selector(lt_selector); let check = meta.query_advice(col_c, Rotation::cur()); diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 11e84fc1..fe393b4a 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -91,8 +91,9 @@ mod tests { use super::MerkleSumTreeCircuit; use halo2_proofs::{ - dev::{MockProver}, + dev::{MockProver, FailureLocation, VerifyFailure}, halo2curves::bn256::{Fr as Fp}, + plonk::{Any} }; use std::marker::PhantomData; use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; @@ -146,13 +147,19 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - let result = invalid_prover.verify(); - - let error = result.unwrap_err(); - let expected_error = "[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 16 ('permute state') at offset 36)]"; + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); - assert_eq!(format!("{:?}", error), expected_error); } #[test] @@ -168,13 +175,17 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - let result = invalid_prover.verify(); - - let error = result.unwrap_err(); - let expected_error = "[Equality constraint not satisfied by cell (Column('Advice', 0 - ), in Region 1 ('merkle prove layer') at offset 0), Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 0)]"; - - assert_eq!(format!("{:?}", error), expected_error); - + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + } + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 0 } }, + ]) + ); } #[test] @@ -213,9 +224,55 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // error: constraint not satisfied 'bool constraint' - // error: constraint not satisfied 'swap constraint' - assert!(invalid_prover.verify().is_err()); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "bool constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 0).into(), 0).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), + (((Any::advice(), 0).into(), 1).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), + (((Any::advice(), 2).into(), 0).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), + (((Any::advice(), 2).into(), 1).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 1, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), + (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); } #[test] @@ -233,9 +290,18 @@ mod tests { let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // 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)]) - // computed_hash (advice column[5]) != root.hash (instance column row 2) - assert!(invalid_prover.verify().is_err()); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); } #[test] @@ -251,18 +317,23 @@ mod tests { let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); - // error: constraint not satisfied - // Cell layout in region 'enforce sum to be less than total assets': - // | Offset | A2 | A11| - // +--------+----+----+ - // | 0 | x0 | x1 | <--{ Gate 'verifies that `check` from current config equal to is_lt from LtChip ' applied here - - // Constraint '': - // ((S10 * (1 - S10)) * (0x2 - S10)) * (x1 - x0) = 0 + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((7, "verifies that `check` from current config equal to is_lt from LtChip").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (17, "enforce sum to be less than total assets").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 2).into(), 0).into(), "1".to_string()), + (((Any::advice(), 11).into(), 0).into(), "0".to_string()) + ] + } + ]) + ); - // Assigned cell values: - // x0 = 1 - // x1 = 0 assert!(invalid_prover.verify().is_err()); } @@ -273,19 +344,17 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let circuit = instantiate_circuit(assets_sum); let root = - BitMapBackend::new("prints/merkle-sum-tree-layout.png", (1024, 3096)).into_drawing_area(); + BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)).into_drawing_area(); root.fill(&WHITE).unwrap(); let root = root .titled("Merkle Sum Tree Layout", ("sans-serif", 60)) .unwrap(); halo2_proofs::dev::CircuitLayout::default() - .render(10, &circuit, &root) + .render(8, &circuit, &root) .unwrap(); } } \ No newline at end of file From 343b31107d8d5d8bab7a139e1bb25530df4448c3 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Thu, 13 Apr 2023 15:02:04 +0200 Subject: [PATCH 09/25] update testeing in merkle sum tre --- src/circuits/merkle_sum_tree.rs | 129 ++++++++++++++++++++++++-------- 1 file changed, 96 insertions(+), 33 deletions(-) diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index fe393b4a..7daba5fe 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -122,31 +122,29 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let circuit = instantiate_circuit(assets_sum); - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - let valid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); valid_prover.assert_satisfied(); } + // Passing an invalid root hash in the instance column should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_root_hash() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let circuit = instantiate_circuit(assets_sum); - let public_input = vec![circuit.leaf_hash, user_balance, Fp::from(1000u64), assets_sum]; + let invalid_root_hash = Fp::from(1000u64); - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -162,18 +160,47 @@ mod tests { } + // Passing an invalid leaf hash as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash #[test] - fn test_invalid_leaf_hash() { + fn test_invalid_leaf_hash_as_witness() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); + let mut circuit = instantiate_circuit(assets_sum); + + // invalidate leaf hash + circuit.leaf_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + // Passing an invalid leaf hash in the instance column should fail the permutation check between the (valid) leaf hash added as part of the witness and the instance column leaf hash + #[test] + fn test_invalid_leaf_hash_as_instance() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_circuit(assets_sum); - let public_input = vec![Fp::from(1000u64), user_balance, circuit.root_hash, assets_sum]; + // add invalid leaf hash in the instance column + let invalid_leaf_hash = Fp::from(1000u64); - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let public_input = vec![invalid_leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -188,41 +215,78 @@ mod tests { ); } + // Passing an invalid leaf balance as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash #[test] - fn test_invalid_leaf_balance() { + fn test_invalid_leaf_balance_as_witness() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let invalid_user_balance = Fp::from(11887u64); + let user_balance = Fp::from(11888u64); - let circuit = instantiate_circuit(assets_sum); + let mut circuit = instantiate_circuit(assets_sum); + + // invalid leaf balance + circuit.leaf_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + + // Passing an invalid leaf balance in the instance column should fail the permutation check between the (valid) leaf balance added as part of the witness and the instance column leaf balance + #[test] + fn test_invalid_leaf_balance_as_instance() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let public_input = vec![circuit.leaf_hash, invalid_user_balance, circuit.root_hash, assets_sum]; + let circuit = instantiate_circuit(assets_sum); - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + // add invalid leaf balance in the instance column + let invalid_leaf_balance = Fp::from(1000u64); - let result = invalid_prover.verify(); + let public_input = vec![circuit.leaf_hash, invalid_leaf_balance, circuit.root_hash, circuit.assets_sum]; - let error = result.unwrap_err(); - let expected_error = "[Equality constraint not satisfied by cell (Column('Advice', 1 - ), in Region 1 ('merkle prove layer') at offset 0), Equality constraint not satisfied by cell (Column('Instance', 0 - ), outside any region, on row 1)]"; + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - assert_eq!(format!("{:?}", error), expected_error); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::advice(), 1).into(), location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + } + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 1 } }, + ]) + ); } + // Passing a non binary index should fail the bool constraint check, the two swap constraints and the permutation check between the computed root hash and the instance column root hash #[test] fn test_non_binary_index() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let mut circuit = instantiate_circuit(assets_sum); + // invalidate path index inside the circuit circuit.path_indices[0] = Fp::from(2); - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -275,21 +339,20 @@ mod tests { ); } + // Swapping the indices should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_swapping_index() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let mut circuit = instantiate_circuit(assets_sum); // swap indices circuit.path_indices[0] = Fp::from(1); - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -304,18 +367,17 @@ mod tests { ); } + // Passing an assets sum that is less than the liabilities sum should fail the lessThan constraint check #[test] fn test_is_not_less_than() { let less_than_assets_sum = Fp::from(556861u64); // less than liabilities sum (556862) - let user_balance = Fp::from(11888u64); - let circuit = instantiate_circuit(less_than_assets_sum); - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, less_than_assets_sum]; + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - let invalid_prover = MockProver::run(10, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -328,6 +390,7 @@ mod tests { }, cell_values: vec![ (((Any::advice(), 2).into(), 0).into(), "1".to_string()), + // The zero means that is not less than (((Any::advice(), 11).into(), 0).into(), "0".to_string()) ] } From 09dc0b92c81a2150306ae8a91ae100f15e048907 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:00:45 +0200 Subject: [PATCH 10/25] add support and testing for `full_prover` Full prover is a util to generate an actual proof. While for the rest of the tests the proofs were generated using the `MockProver` --- Cargo.lock | 46 +++++++++++++++++- Cargo.toml | 2 + src/circuits.rs | 3 +- src/circuits/merkle_sum_tree.rs | 85 +++++++++++++++++++++++++++++++-- src/circuits/utils.rs | 64 +++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 src/circuits/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 2f069209..c582a3a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -66,6 +66,17 @@ version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "colored", + "num-traits", + "rand", +] + [[package]] name = "arrayref" version = "0.3.7" @@ -89,6 +100,17 @@ dependencies = [ "syn 2.0.14", ] +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "auto_impl" version = "1.0.1" @@ -424,12 +446,14 @@ dependencies = [ name = "circuits-halo2" version = "0.1.0" dependencies = [ + "ark-std", "eth-types", "gadgets", "halo2_gadgets", "halo2_proofs", "merkle-sum-tree-rust", "plotters", + "rand", "tabbycat", ] @@ -515,6 +539,17 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", +] + [[package]] name = "const-cstr" version = "0.3.0" @@ -1457,6 +1492,15 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "hermit-abi" version = "0.2.6" @@ -1864,7 +1908,7 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" dependencies = [ - "hermit-abi", + "hermit-abi 0.2.6", "libc", ] diff --git a/Cargo.toml b/Cargo.toml index e5c84844..daedfdd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,4 +15,6 @@ eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circu gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} merkle-sum-tree-rust = { git = "https://github.com/summa-dev/merkle-sum-tree-rust"} plotters = { version = "0.3.4", optional = true } +rand = "0.8" +ark-std = { version = "0.3.0", features = ["print-trace"] } tabbycat = { version = "0.1", features = ["attributes"], optional = true } \ No newline at end of file diff --git a/src/circuits.rs b/src/circuits.rs index eb993211..8a7c758b 100644 --- a/src/circuits.rs +++ b/src/circuits.rs @@ -1 +1,2 @@ -pub mod merkle_sum_tree; \ No newline at end of file +pub mod merkle_sum_tree; +pub mod utils; \ No newline at end of file diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 7daba5fe..609cbd34 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -92,11 +92,18 @@ mod tests { use super::MerkleSumTreeCircuit; use halo2_proofs::{ dev::{MockProver, FailureLocation, VerifyFailure}, - halo2curves::bn256::{Fr as Fp}, - plonk::{Any} + halo2curves::bn256::{Fr as Fp, Bn256}, + plonk::{Any, keygen_pk, keygen_vk}, + poly::{ + kzg::{ + commitment::{ParamsKZG}, + }, + }, }; use std::marker::PhantomData; use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; + use super::super::utils::{full_prover, full_verifier}; + use rand::rngs::OsRng; fn instantiate_circuit(assets_sum: Fp) -> MerkleSumTreeCircuit{ @@ -117,6 +124,19 @@ mod tests { } + fn instantiate_empty_circuit() -> MerkleSumTreeCircuit{ + MerkleSumTreeCircuit { + leaf_hash: Fp::zero(), + leaf_balance: Fp::zero(), + path_element_hashes: vec![Fp::zero(); 4], + path_element_balances: vec![Fp::zero(); 4], + path_indices: vec![Fp::zero(); 4], + assets_sum : Fp::zero(), + root_hash: Fp::zero(), + _marker: PhantomData, + } + } + #[test] fn test_valid_merkle_sum_tree() { @@ -132,6 +152,35 @@ mod tests { } + #[test] + fn test_valid_merkle_sum_tree_with_full_prover() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_empty_circuit(); + + // we generate a universal trusted setup of our own for testing + let params = ParamsKZG::::setup(8, OsRng); + + // we generate the verification key and the proving key + // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys + // Note: the dimension of the circuit used to generate the keys must be the same as the dimension of the circuit used to generate the proof + // In this case, the dimension are represented by the heigth of the merkle tree + let vk = keygen_vk(¶ms, &circuit).expect("vk generation should not fail"); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk generation should not fail"); + + // Only now we can instantiate the circuit with the actual inputs + let circuit = instantiate_circuit(assets_sum); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + // Generate the proof + let proof = full_prover(¶ms, &pk, circuit, &public_input); + + // verify the proof to be true + assert!(full_verifier(¶ms, &vk, proof, &public_input)); + } + // Passing an invalid root hash in the instance column should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_root_hash() { @@ -144,7 +193,7 @@ mod tests { let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -160,6 +209,36 @@ mod tests { } + #[test] + fn test_invalid_root_hash_with_full_prover() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_empty_circuit(); + + // we generate a universal trusted setup of our own for testing + let params = ParamsKZG::::setup(8, OsRng); + + // we generate the verification key and the proving key + // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys + let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk should not fail"); + + // Only now we can instantiate the circuit with the actual inputs + let circuit = instantiate_circuit(assets_sum); + + let invalid_root_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + + // Generate the proof + let proof = full_prover(¶ms, &pk, circuit, &public_input); + + // verify the proof to be false + assert!(!full_verifier(¶ms, &vk, proof, &public_input)); + + } + // Passing an invalid leaf hash as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_leaf_hash_as_witness() { diff --git a/src/circuits/utils.rs b/src/circuits/utils.rs new file mode 100644 index 00000000..56596878 --- /dev/null +++ b/src/circuits/utils.rs @@ -0,0 +1,64 @@ +use halo2_proofs::{ + halo2curves::bn256::{Fr as Fp, Bn256, G1Affine}, + poly::{ + commitment::ParamsProver, + kzg::{ + commitment::{ + ParamsKZG, + KZGCommitmentScheme, + }, + strategy::SingleStrategy, + multiopen::{ProverSHPLONK, VerifierSHPLONK} + }, + }, + plonk::{ + create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey + }, + transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer}, +}; +use rand::rngs::OsRng; +use ark_std::{end_timer, start_timer}; + +pub fn full_prover > ( + params: &ParamsKZG, + pk: &ProvingKey, + circuit: C, + public_input: &[Fp], +) -> Vec { + + let pf_time = start_timer!(|| "Creating proof"); + + let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); + create_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255, + _, + Blake2bWrite, G1Affine, Challenge255>, + _, + >(params, pk, &[circuit], &[&[public_input]], OsRng, &mut transcript) + .expect("prover should not fail"); + let proof = transcript.finalize(); + end_timer!(pf_time); + proof +} + +pub fn full_verifier( + params: &ParamsKZG, + vk: &VerifyingKey, + proof: Vec, + public_input: &[Fp], +) -> bool { + let verifier_params = params.verifier_params(); + let strategy = SingleStrategy::new(params); + let mut transcript = Blake2bRead::<_, _, Challenge255<_>>::init(&proof[..]); + + verify_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<&[u8], G1Affine, Challenge255>, + SingleStrategy<'_, Bn256>, + >(verifier_params, vk, strategy, &[&[public_input]], &mut transcript) + .is_ok() +} \ No newline at end of file From ae326b6ac6a0dcc653560e6b0ee47d26015718c8 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 2 May 2023 12:41:32 +0200 Subject: [PATCH 11/25] module restructuring --- src/chips.rs | 2 -- src/chips/merkle_sum_tree.rs | 4 ++-- src/chips/mod.rs | 2 ++ src/circuits/merkle_sum_tree.rs | 2 +- src/{circuits.rs => circuits/mod.rs} | 0 5 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 src/chips.rs create mode 100644 src/chips/mod.rs rename src/{circuits.rs => circuits/mod.rs} (100%) diff --git a/src/chips.rs b/src/chips.rs deleted file mode 100644 index 37fe16ff..00000000 --- a/src/chips.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod poseidon; -pub mod merkle_sum_tree; \ No newline at end of file diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index be2bdf6b..be977db3 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -1,5 +1,5 @@ -use super::poseidon::hash::{PoseidonChip, PoseidonConfig}; -use super::poseidon::spec::MySpec; +use crate::chips::poseidon::hash::{PoseidonChip, PoseidonConfig}; +use crate::chips::poseidon::spec::MySpec; use halo2_proofs::{circuit::*,plonk::*, poly::Rotation}; use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; use eth_types::Field; diff --git a/src/chips/mod.rs b/src/chips/mod.rs new file mode 100644 index 00000000..7fa813f0 --- /dev/null +++ b/src/chips/mod.rs @@ -0,0 +1,2 @@ +pub mod merkle_sum_tree; +pub mod poseidon; \ No newline at end of file diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 609cbd34..5d3b57ca 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -1,4 +1,4 @@ -use super::super::chips::merkle_sum_tree::{MerkleSumTreeChip, MerkleSumTreeConfig}; +use crate::chips::merkle_sum_tree::{MerkleSumTreeChip, MerkleSumTreeConfig}; use halo2_proofs::{circuit::*, plonk::*}; use std::marker::PhantomData; use eth_types::Field; diff --git a/src/circuits.rs b/src/circuits/mod.rs similarity index 100% rename from src/circuits.rs rename to src/circuits/mod.rs From a6cccb932fff4dd7f4c2f02df8ed06dc9d4730fd Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 2 May 2023 12:56:48 +0200 Subject: [PATCH 12/25] add 'merkle_sum_tree` as an internal module --- Cargo.lock | 29 +------ Cargo.toml | 6 +- src/circuits/merkle_sum_tree.rs | 2 +- src/lib.rs | 2 +- src/merkle_sum_tree/csv/entry_16.csv | 17 ++++ .../csv/entry_16_switched_order.csv | 17 ++++ src/merkle_sum_tree/entry.rs | 56 ++++++++++++ src/merkle_sum_tree/merkle_sum_tree_lib.rs | 65 ++++++++++++++ src/merkle_sum_tree/mod.rs | 85 +++++++++++++++++++ src/merkle_sum_tree/utils/build_tree.rs | 43 ++++++++++ .../utils/create_middle_node.rs | 9 ++ src/merkle_sum_tree/utils/create_proof.rs | 43 ++++++++++ src/merkle_sum_tree/utils/csv_parser.rs | 26 ++++++ src/merkle_sum_tree/utils/hash.rs | 12 +++ src/merkle_sum_tree/utils/index_of.rs | 9 ++ src/merkle_sum_tree/utils/mod.rs | 22 +++++ .../utils/proof_verification.rs | 27 ++++++ 17 files changed, 440 insertions(+), 30 deletions(-) create mode 100644 src/merkle_sum_tree/csv/entry_16.csv create mode 100644 src/merkle_sum_tree/csv/entry_16_switched_order.csv create mode 100644 src/merkle_sum_tree/entry.rs create mode 100644 src/merkle_sum_tree/merkle_sum_tree_lib.rs create mode 100644 src/merkle_sum_tree/mod.rs create mode 100644 src/merkle_sum_tree/utils/build_tree.rs create mode 100644 src/merkle_sum_tree/utils/create_middle_node.rs create mode 100644 src/merkle_sum_tree/utils/create_proof.rs create mode 100644 src/merkle_sum_tree/utils/csv_parser.rs create mode 100644 src/merkle_sum_tree/utils/hash.rs create mode 100644 src/merkle_sum_tree/utils/index_of.rs create mode 100644 src/merkle_sum_tree/utils/mod.rs create mode 100644 src/merkle_sum_tree/utils/proof_verification.rs diff --git a/Cargo.lock b/Cargo.lock index c582a3a2..db0c3e5f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -447,13 +447,15 @@ name = "circuits-halo2" version = "0.1.0" dependencies = [ "ark-std", + "csv", "eth-types", "gadgets", "halo2_gadgets", "halo2_proofs", - "merkle-sum-tree-rust", + "hex", "plotters", "rand", + "serde", "tabbycat", ] @@ -1388,18 +1390,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "halo2-experiments" -version = "0.1.0" -source = "git+https://github.com/summa-dev/halo2-experiments?branch=merkle-sum-tree#6986429861b2d9abc1357ebe10189b782a9f53f0" -dependencies = [ - "eth-types", - "gadgets", - "halo2_gadgets", - "halo2_proofs", - "rand", -] - [[package]] name = "halo2_gadgets" version = "0.2.0" @@ -1794,19 +1784,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "merkle-sum-tree-rust" -version = "0.1.0" -source = "git+https://github.com/summa-dev/merkle-sum-tree-rust#2e3914ad3441aeaa6d323f2c14600cdd52b04aee" -dependencies = [ - "csv", - "halo2-experiments", - "halo2_gadgets", - "halo2_proofs", - "hex", - "serde", -] - [[package]] name = "miniz_oxide" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index daedfdd9..625cefd8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,8 +13,10 @@ halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} -merkle-sum-tree-rust = { git = "https://github.com/summa-dev/merkle-sum-tree-rust"} plotters = { version = "0.3.4", optional = true } rand = "0.8" ark-std = { version = "0.3.0", features = ["print-trace"] } -tabbycat = { version = "0.1", features = ["attributes"], optional = true } \ No newline at end of file +tabbycat = { version = "0.1", features = ["attributes"], optional = true } +csv = "1.1" +serde = { version = "1.0", features = ["derive"] } +hex = "0.4.3" diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 5d3b57ca..c9a18457 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -101,7 +101,7 @@ mod tests { }, }; use std::marker::PhantomData; - use merkle_sum_tree_rust::{MerkleSumTree, MerkleProof}; + use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof}; use super::super::utils::{full_prover, full_verifier}; use rand::rngs::OsRng; diff --git a/src/lib.rs b/src/lib.rs index cae2cab0..0ffbd878 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod chips; pub mod circuits; - +pub mod merkle_sum_tree; \ No newline at end of file diff --git a/src/merkle_sum_tree/csv/entry_16.csv b/src/merkle_sum_tree/csv/entry_16.csv new file mode 100644 index 00000000..206ffec5 --- /dev/null +++ b/src/merkle_sum_tree/csv/entry_16.csv @@ -0,0 +1,17 @@ +username,balance +dxGaEAii,11888 +MBlfbBGI,67823 +lAhWlEWZ,18651 +nuZweYtO,22073 +gbdSwiuY,34897 +RZNneNuP,83296 +YsscHXkp,31699 +RkLzkDun,2087 +HlQlnEYI,30605 +RqkZOFYe,16881 +NjCSRAfD,41163 +pHniJMQY,14874 +dOGIMzKR,10032 +HfMDmNLp,55683 +xPLKzCBl,79731 +AtwIxZHo,35479 diff --git a/src/merkle_sum_tree/csv/entry_16_switched_order.csv b/src/merkle_sum_tree/csv/entry_16_switched_order.csv new file mode 100644 index 00000000..25f9c6b4 --- /dev/null +++ b/src/merkle_sum_tree/csv/entry_16_switched_order.csv @@ -0,0 +1,17 @@ +username,balance +dxGaEAii,11888 +MBlfbBGI,67823 +lAhWlEWZ,18651 +nuZweYtO,22073 +gbdSwiuY,34897 +RZNneNuP,83296 +YsscHXkp,31699 +RkLzkDun,2087 +HlQlnEYI,30605 +RqkZOFYe,16881 +NjCSRAfD,41163 +pHniJMQY,14874 +dOGIMzKR,10032 +HfMDmNLp,55683 +AtwIxZHo,35479 +xPLKzCBl,79731 diff --git a/src/merkle_sum_tree/entry.rs b/src/merkle_sum_tree/entry.rs new file mode 100644 index 00000000..91b737dc --- /dev/null +++ b/src/merkle_sum_tree/entry.rs @@ -0,0 +1,56 @@ +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +use crate::merkle_sum_tree::utils::{big_intify_username, poseidon}; +use crate::merkle_sum_tree::Node; + +#[derive(Default, Clone, Debug)] +pub struct Entry { + username_to_big_int: u64, + balance: u64, + username: String, +} + +impl Entry { + pub const ZERO_ENTRY: Entry = Entry { + username_to_big_int: 0, + balance: 0, + username: String::new(), + }; + + pub fn new(username: String, balance: u64) -> Result { + // if balance < 0 { + // return Err("entry balance can't be negative"); + // } + + Ok(Entry { + username_to_big_int: big_intify_username(&username), + balance, + username, + }) + } + + pub fn compute_leaf(&self) -> Node { + Node { + hash: poseidon( + Fp::from(self.username_to_big_int), + Fp::from(self.balance), + Fp::from(0), + Fp::from(0), + ), + balance: Fp::from(self.balance), + } + } + + // Getters + pub fn balance(&self) -> u64 { + self.balance + } + + pub fn username_to_big_int(&self) -> u64 { + self.username_to_big_int + } + + pub fn username(&self) -> &str { + &self.username + } +} diff --git a/src/merkle_sum_tree/merkle_sum_tree_lib.rs b/src/merkle_sum_tree/merkle_sum_tree_lib.rs new file mode 100644 index 00000000..a009e1c3 --- /dev/null +++ b/src/merkle_sum_tree/merkle_sum_tree_lib.rs @@ -0,0 +1,65 @@ +use crate::merkle_sum_tree::utils::{ + build_merkle_tree_from_entries, create_proof, index_of, parse_csv_to_entries, verify_proof, +}; +use crate::merkle_sum_tree::{Entry, MerkleProof, Node}; + +pub struct MerkleSumTree { + root: Node, + nodes: Vec>, + depth: usize, + entries: Vec, +} + +impl MerkleSumTree { + pub const MAX_DEPTH: usize = 32; + + pub fn new(path: &str) -> Result> { + let entries = parse_csv_to_entries(path)?; + let depth = (entries.len() as f64).log2().ceil() as usize; + + if !(1..=Self::MAX_DEPTH).contains(&depth) { + return Err("The tree depth must be between 1 and 32".into()); + } + + let mut nodes = vec![]; + let root = build_merkle_tree_from_entries(&entries, depth, &mut nodes)?; + + Ok(MerkleSumTree { + root, + nodes, + depth, + entries, + }) + } + + // Getter methods + pub fn root(&self) -> &Node { + &self.root + } + + pub fn depth(&self) -> &usize { + &self.depth + } + + pub fn leaves(&self) -> &[Node] { + &self.nodes[0] + } + + pub fn entries(&self) -> &[Entry] { + &self.entries + } + + pub fn index_of(&self, username: &str, balance: u64) -> Option { + index_of(username, balance, &self.nodes) + } + + pub fn generate_proof(&self, index: usize) -> Result { + create_proof(index, &self.entries, self.depth, &self.nodes, &self.root) + } + + pub fn verify_proof(&self, proof: &MerkleProof) -> bool { + verify_proof(proof) + } + + // Implement the rest of the methods +} diff --git a/src/merkle_sum_tree/mod.rs b/src/merkle_sum_tree/mod.rs new file mode 100644 index 00000000..b9efa460 --- /dev/null +++ b/src/merkle_sum_tree/mod.rs @@ -0,0 +1,85 @@ +mod entry; +mod merkle_sum_tree_lib; +mod utils; +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +#[derive(Default, Clone, Debug)] +pub struct MerkleProof { + pub root_hash: Fp, + pub entry: Entry, + pub sibling_hashes: Vec, + pub sibling_sums: Vec, + pub path_indices: Vec, +} + +#[derive(Default, Clone, Debug)] +pub struct Node { + pub hash: Fp, + pub balance: Fp, +} + +pub use entry::Entry; +pub use merkle_sum_tree_lib::MerkleSumTree; +pub use utils::big_intify_username; + +#[cfg(test)] +mod tests { + + use super::{Entry, MerkleSumTree}; + + #[test] + fn init_mst() { + // create new merkle tree + let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); + + // get root + let root = merkle_tree.root(); + + // expect root hash to be different than 0 + assert!(root.hash != 0.into()); + assert!(root.balance == 556862.into()); + assert!(*merkle_tree.depth() == 4_usize); + + // get proof + let proof = merkle_tree.generate_proof(0).unwrap(); + + // verify proof + assert!(merkle_tree.verify_proof(&proof)); + + // Should generate different root hashes when changing the entry order + let merkle_tree_2 = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); + assert_ne!(root.hash, merkle_tree_2.root().hash); + + // the balance should be the same + assert_eq!(root.balance, merkle_tree_2.root().balance); + + // should retrun the index of an entry that exist in the tree + assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479), Some(15)); + + // shouldn't retrun the index of an entry that doesn't exist in the tree + assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478), None); + + // should create valid proof for each entry in the tree and verify it + for i in 0..15 { + let proof = merkle_tree.generate_proof(i).unwrap(); + assert!(merkle_tree.verify_proof(&proof)); + } + + // shouldn't create a proof for an entry that doesn't exist in the tree + assert!(merkle_tree.generate_proof(16).is_err()); + + // shouldn't verify a proof with a wrong entry + let mut proof_invalid_1 = proof.clone(); + proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479).unwrap(); + assert!(!merkle_tree.verify_proof(&proof_invalid_1)); + + // shouldn't verify a proof with a wrong root hash + let mut proof_invalid_2 = proof.clone(); + proof_invalid_2.root_hash = 0.into(); + assert!(!merkle_tree.verify_proof(&proof_invalid_2)); + + // shouldn't verify a proof with a wrong computed balance + let mut proof_invalid_3 = proof.clone(); + proof_invalid_3.sibling_sums[0] = 0.into(); + } +} diff --git a/src/merkle_sum_tree/utils/build_tree.rs b/src/merkle_sum_tree/utils/build_tree.rs new file mode 100644 index 00000000..c84ba922 --- /dev/null +++ b/src/merkle_sum_tree/utils/build_tree.rs @@ -0,0 +1,43 @@ +use super::create_middle_node::create_middle_node; +use crate::merkle_sum_tree::{Entry, Node}; +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +pub fn build_merkle_tree_from_entries( + entries: &[Entry], + depth: usize, + nodes: &mut Vec>, +) -> Result> { + let n = entries.len(); + let mut tree = vec![ + vec![ + Node { + hash: Fp::from(0), + balance: Fp::from(0) + }; + n + ]; + depth + 1 + ]; + + // Compute the leaves + for (i, entry) in entries.iter().enumerate() { + tree[0][i] = entry.compute_leaf(); + } + + // Compute the inner nodes + for level in 1..=depth { + let nodes_in_level = (n + (1 << level) - 1) / (1 << level); + for i in 0..nodes_in_level { + tree[level][i] = + create_middle_node(&tree[level - 1][2 * i], &tree[level - 1][2 * i + 1]); + + // let left_child = tree[level - 1][2 * i].hash; + // let right_child = tree[level - 1][2 * i + 1].hash; + // tree[level][i].hash = poseidon(left_child, right_child); + } + } + + let root = tree[depth][0].clone(); + *nodes = tree; + Ok(root) +} diff --git a/src/merkle_sum_tree/utils/create_middle_node.rs b/src/merkle_sum_tree/utils/create_middle_node.rs new file mode 100644 index 00000000..1f605b7c --- /dev/null +++ b/src/merkle_sum_tree/utils/create_middle_node.rs @@ -0,0 +1,9 @@ +use super::hash::poseidon; +use crate::merkle_sum_tree::Node; + +pub fn create_middle_node(child_l: &Node, child_r: &Node) -> Node { + Node { + hash: poseidon(child_l.hash, child_l.balance, child_r.hash, child_r.balance), + balance: child_l.balance + child_r.balance, + } +} diff --git a/src/merkle_sum_tree/utils/create_proof.rs b/src/merkle_sum_tree/utils/create_proof.rs new file mode 100644 index 00000000..07537c0b --- /dev/null +++ b/src/merkle_sum_tree/utils/create_proof.rs @@ -0,0 +1,43 @@ +use crate::merkle_sum_tree::{Entry, MerkleProof, Node}; +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +pub fn create_proof( + index: usize, + entries: &[Entry], + depth: usize, + nodes: &[Vec], + root: &Node, +) -> Result { + if index >= nodes[0].len() { + return Err("The leaf does not exist in this tree"); + } + + let mut sibling_hashes = vec![Fp::from(0); depth]; + let mut sibling_sums = vec![Fp::from(0); depth]; + let mut path_indices = vec![Fp::from(0); depth]; + let mut current_index = index; + + for level in 0..depth { + let position = current_index % 2; + let level_start_index = current_index - position; + let level_end_index = level_start_index + 2; + + path_indices[level] = Fp::from(position as u64); + + for i in level_start_index..level_end_index { + if i != current_index { + sibling_hashes[level] = nodes[level][i].hash; + sibling_sums[level] = nodes[level][i].balance; + } + } + current_index /= 2; + } + + Ok(MerkleProof { + root_hash: root.hash, + entry: entries[index].clone(), + sibling_hashes, + sibling_sums, + path_indices, + }) +} diff --git a/src/merkle_sum_tree/utils/csv_parser.rs b/src/merkle_sum_tree/utils/csv_parser.rs new file mode 100644 index 00000000..d14bf70a --- /dev/null +++ b/src/merkle_sum_tree/utils/csv_parser.rs @@ -0,0 +1,26 @@ +use crate::merkle_sum_tree::Entry; +use serde::Deserialize; +use std::error::Error; +use std::fs::File; +use std::path::Path; + +#[derive(Debug, Deserialize)] +struct CsvEntry { + username: String, + balance: u64, +} + +pub fn parse_csv_to_entries>(path: P) -> Result, Box> { + let mut entries = Vec::new(); + let file = File::open(path)?; + let mut rdr = csv::Reader::from_reader(file); + + for result in rdr.deserialize() { + let record: CsvEntry = result?; + let entry = Entry::new(record.username, record.balance)?; + + entries.push(entry); + } + + Ok(entries) +} diff --git a/src/merkle_sum_tree/utils/hash.rs b/src/merkle_sum_tree/utils/hash.rs new file mode 100644 index 00000000..7d745a61 --- /dev/null +++ b/src/merkle_sum_tree/utils/hash.rs @@ -0,0 +1,12 @@ +use crate::chips::poseidon::spec::MySpec; +use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength}; +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +pub fn poseidon(l1: Fp, l2: Fp, r1: Fp, r2: Fp) -> Fp { + const WIDTH: usize = 5; + const RATE: usize = 4; + const L: usize = 4; + + poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init() + .hash([l1, l2, r1, r2]) +} diff --git a/src/merkle_sum_tree/utils/index_of.rs b/src/merkle_sum_tree/utils/index_of.rs new file mode 100644 index 00000000..94f9bf14 --- /dev/null +++ b/src/merkle_sum_tree/utils/index_of.rs @@ -0,0 +1,9 @@ +use crate::merkle_sum_tree::{Entry, Node}; + +pub fn index_of(username: &str, balance: u64, nodes: &[Vec]) -> Option { + let entry = Entry::new(username.to_string(), balance).unwrap(); + let leaf = entry.compute_leaf(); + let leaf_hash = leaf.hash; + + nodes[0].iter().position(|node| node.hash == leaf_hash) +} diff --git a/src/merkle_sum_tree/utils/mod.rs b/src/merkle_sum_tree/utils/mod.rs new file mode 100644 index 00000000..9be6f937 --- /dev/null +++ b/src/merkle_sum_tree/utils/mod.rs @@ -0,0 +1,22 @@ +mod build_tree; +mod create_middle_node; +mod create_proof; +mod csv_parser; +mod hash; +mod index_of; +mod proof_verification; + +pub use build_tree::build_merkle_tree_from_entries; +pub use create_middle_node::create_middle_node; +pub use create_proof::create_proof; +pub use csv_parser::parse_csv_to_entries; +pub use hash::poseidon; +pub use index_of::index_of; +pub use proof_verification::verify_proof; + +// Add the big_intify_username function +pub fn big_intify_username(username: &str) -> u64 { + let utf8_bytes = username.as_bytes(); + let hex_string = hex::encode(utf8_bytes); + u64::from_str_radix(&hex_string, 16).unwrap_or(0) +} diff --git a/src/merkle_sum_tree/utils/proof_verification.rs b/src/merkle_sum_tree/utils/proof_verification.rs new file mode 100644 index 00000000..e7427b8e --- /dev/null +++ b/src/merkle_sum_tree/utils/proof_verification.rs @@ -0,0 +1,27 @@ +// proof_verification.rs + +use super::create_middle_node::create_middle_node; +use crate::merkle_sum_tree::{MerkleProof, Node}; +use halo2_proofs::halo2curves::bn256::{Fr as Fp}; + +pub fn verify_proof(proof: &MerkleProof) -> bool { + let mut node = proof.entry.compute_leaf(); + let mut balance = Fp::from(proof.entry.balance()); + + for i in 0..proof.sibling_hashes.len() { + let sibling_node = Node { + hash: proof.sibling_hashes[i], + balance: proof.sibling_sums[i], + }; + + if proof.path_indices[i] == 0.into() { + node = create_middle_node(&node, &sibling_node); + } else { + node = create_middle_node(&sibling_node, &node); + } + + balance += sibling_node.balance; + } + + proof.root_hash == node.hash && balance == node.balance +} From 22a111f38bafe436e3f961a1a78190feca7a8778 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 2 May 2023 16:40:52 +0200 Subject: [PATCH 13/25] move `csv_entries` to circuits folder --- {csv_entries => src/circuits/csv_entries}/entry_16.csv | 0 src/circuits/merkle_sum_tree.rs | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename {csv_entries => src/circuits/csv_entries}/entry_16.csv (100%) diff --git a/csv_entries/entry_16.csv b/src/circuits/csv_entries/entry_16.csv similarity index 100% rename from csv_entries/entry_16.csv rename to src/circuits/csv_entries/entry_16.csv diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index c9a18457..aa3ad011 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -107,7 +107,7 @@ mod tests { fn instantiate_circuit(assets_sum: Fp) -> MerkleSumTreeCircuit{ - let merkle_sum_tree= MerkleSumTree::new("csv_entries/entry_16.csv").unwrap(); + let merkle_sum_tree= MerkleSumTree::new("src/circuits/csv_entries/entry_16.csv").unwrap(); let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); From fe1854e9f34df3116ffb6818fb181054ad388092 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 09:11:48 +0200 Subject: [PATCH 14/25] feat: added support for bigint for userbalance --- Cargo.lock | 1 + Cargo.toml | 1 + src/circuits/merkle_sum_tree.rs | 4 +- .../csv/entry_16_bigints.csv} | 4 +- src/merkle_sum_tree/entry.rs | 32 +++++------ src/merkle_sum_tree/merkle_sum_tree_lib.rs | 5 +- src/merkle_sum_tree/mod.rs | 53 ++++++++++++++++--- src/merkle_sum_tree/utils/csv_parser.rs | 6 ++- src/merkle_sum_tree/utils/index_of.rs | 4 +- src/merkle_sum_tree/utils/mod.rs | 9 ++-- .../utils/operation_helpers.rs | 42 +++++++++++++++ .../utils/proof_verification.rs | 6 +-- 12 files changed, 120 insertions(+), 47 deletions(-) rename src/{circuits/csv_entries/entry_16.csv => merkle_sum_tree/csv/entry_16_bigints.csv} (83%) create mode 100644 src/merkle_sum_tree/utils/operation_helpers.rs diff --git a/Cargo.lock b/Cargo.lock index db0c3e5f..7c3baaf7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -453,6 +453,7 @@ dependencies = [ "halo2_gadgets", "halo2_proofs", "hex", + "num-bigint", "plotters", "rand", "serde", diff --git a/Cargo.toml b/Cargo.toml index 625cefd8..1e9e1627 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,3 +20,4 @@ tabbycat = { version = "0.1", features = ["attributes"], optional = true } csv = "1.1" serde = { version = "1.0", features = ["derive"] } hex = "0.4.3" +num-bigint = "0.4" diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index aa3ad011..6a177746 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -101,7 +101,7 @@ mod tests { }, }; use std::marker::PhantomData; - use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof}; + use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof, big_int_to_fp}; use super::super::utils::{full_prover, full_verifier}; use rand::rngs::OsRng; @@ -113,7 +113,7 @@ mod tests { MerkleSumTreeCircuit { leaf_hash: proof.entry.compute_leaf().hash, - leaf_balance: Fp::from(proof.entry.balance()), + leaf_balance: big_int_to_fp(proof.entry.balance()), path_element_hashes: proof.sibling_hashes, path_element_balances: proof.sibling_sums, path_indices: proof.path_indices, diff --git a/src/circuits/csv_entries/entry_16.csv b/src/merkle_sum_tree/csv/entry_16_bigints.csv similarity index 83% rename from src/circuits/csv_entries/entry_16.csv rename to src/merkle_sum_tree/csv/entry_16_bigints.csv index 206ffec5..0240e192 100644 --- a/src/circuits/csv_entries/entry_16.csv +++ b/src/merkle_sum_tree/csv/entry_16_bigints.csv @@ -1,5 +1,5 @@ username,balance -dxGaEAii,11888 +dxGaEAii,18446744073709551616 MBlfbBGI,67823 lAhWlEWZ,18651 nuZweYtO,22073 @@ -14,4 +14,4 @@ pHniJMQY,14874 dOGIMzKR,10032 HfMDmNLp,55683 xPLKzCBl,79731 -AtwIxZHo,35479 +AtwIxZHo,35479 \ No newline at end of file diff --git a/src/merkle_sum_tree/entry.rs b/src/merkle_sum_tree/entry.rs index 91b737dc..19317d28 100644 --- a/src/merkle_sum_tree/entry.rs +++ b/src/merkle_sum_tree/entry.rs @@ -1,26 +1,19 @@ use halo2_proofs::halo2curves::bn256::{Fr as Fp}; -use crate::merkle_sum_tree::utils::{big_intify_username, poseidon}; +use crate::merkle_sum_tree::utils::{big_intify_username, poseidon, big_int_to_fp}; use crate::merkle_sum_tree::Node; +use num_bigint::{BigInt}; #[derive(Default, Clone, Debug)] pub struct Entry { - username_to_big_int: u64, - balance: u64, + username_to_big_int: BigInt, + balance: BigInt, username: String, } impl Entry { - pub const ZERO_ENTRY: Entry = Entry { - username_to_big_int: 0, - balance: 0, - username: String::new(), - }; - pub fn new(username: String, balance: u64) -> Result { - // if balance < 0 { - // return Err("entry balance can't be negative"); - // } + pub fn new(username: String, balance: BigInt) -> Result { Ok(Entry { username_to_big_int: big_intify_username(&username), @@ -32,22 +25,23 @@ impl Entry { pub fn compute_leaf(&self) -> Node { Node { hash: poseidon( - Fp::from(self.username_to_big_int), - Fp::from(self.balance), + big_int_to_fp(&self.username_to_big_int), + big_int_to_fp(&self.balance), Fp::from(0), Fp::from(0), + ), - balance: Fp::from(self.balance), + balance: big_int_to_fp(&self.balance), } } // Getters - pub fn balance(&self) -> u64 { - self.balance + pub fn balance(&self) -> &BigInt { + &self.balance } - pub fn username_to_big_int(&self) -> u64 { - self.username_to_big_int + pub fn username_to_big_int(&self) -> &BigInt { + &self.username_to_big_int } pub fn username(&self) -> &str { diff --git a/src/merkle_sum_tree/merkle_sum_tree_lib.rs b/src/merkle_sum_tree/merkle_sum_tree_lib.rs index a009e1c3..351b0fbd 100644 --- a/src/merkle_sum_tree/merkle_sum_tree_lib.rs +++ b/src/merkle_sum_tree/merkle_sum_tree_lib.rs @@ -1,3 +1,4 @@ +use num_bigint::BigInt; use crate::merkle_sum_tree::utils::{ build_merkle_tree_from_entries, create_proof, index_of, parse_csv_to_entries, verify_proof, }; @@ -32,7 +33,6 @@ impl MerkleSumTree { }) } - // Getter methods pub fn root(&self) -> &Node { &self.root } @@ -49,7 +49,7 @@ impl MerkleSumTree { &self.entries } - pub fn index_of(&self, username: &str, balance: u64) -> Option { + pub fn index_of(&self, username: &str, balance: BigInt) -> Option { index_of(username, balance, &self.nodes) } @@ -61,5 +61,4 @@ impl MerkleSumTree { verify_proof(proof) } - // Implement the rest of the methods } diff --git a/src/merkle_sum_tree/mod.rs b/src/merkle_sum_tree/mod.rs index b9efa460..24b611aa 100644 --- a/src/merkle_sum_tree/mod.rs +++ b/src/merkle_sum_tree/mod.rs @@ -20,15 +20,17 @@ pub struct Node { pub use entry::Entry; pub use merkle_sum_tree_lib::MerkleSumTree; -pub use utils::big_intify_username; +pub use utils::{big_intify_username, big_int_to_fp}; #[cfg(test)] mod tests { + use num_bigint::{ToBigInt, BigInt}; + use super::{Entry, MerkleSumTree}; #[test] - fn init_mst() { + fn test_mst() { // create new merkle tree let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); @@ -37,10 +39,12 @@ mod tests { // expect root hash to be different than 0 assert!(root.hash != 0.into()); + // expect balance to match the sum of all entries assert!(root.balance == 556862.into()); + // expect depth to be 4 assert!(*merkle_tree.depth() == 4_usize); - // get proof + // get proof for entry 0 let proof = merkle_tree.generate_proof(0).unwrap(); // verify proof @@ -50,14 +54,14 @@ mod tests { let merkle_tree_2 = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); assert_ne!(root.hash, merkle_tree_2.root().hash); - // the balance should be the same + // the balance total should be the same assert_eq!(root.balance, merkle_tree_2.root().balance); // should retrun the index of an entry that exist in the tree - assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479), Some(15)); + assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479.to_bigint().unwrap()), Some(15)); // shouldn't retrun the index of an entry that doesn't exist in the tree - assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478), None); + assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478.to_bigint().unwrap()), None); // should create valid proof for each entry in the tree and verify it for i in 0..15 { @@ -70,7 +74,7 @@ mod tests { // shouldn't verify a proof with a wrong entry let mut proof_invalid_1 = proof.clone(); - proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479).unwrap(); + proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479.to_bigint().unwrap()).unwrap(); assert!(!merkle_tree.verify_proof(&proof_invalid_1)); // shouldn't verify a proof with a wrong root hash @@ -79,7 +83,40 @@ mod tests { assert!(!merkle_tree.verify_proof(&proof_invalid_2)); // shouldn't verify a proof with a wrong computed balance - let mut proof_invalid_3 = proof.clone(); + let mut proof_invalid_3 = proof; proof_invalid_3.sibling_sums[0] = 0.into(); } + + #[test] + fn test_mst_with_bigint () { + // create new merkle tree with entries that have balances greater than 2^64 + let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_bigints.csv").unwrap(); + + // get root + let root = merkle_tree.root(); + + // expect root hash to be different than 0 + assert!(root.hash != 0.into()); + + // expect balance to match the sum of all entries + let exp_balance = { + let balance = 18446744073710096590_u128.to_bigint().unwrap(); + let (_, mut bytes) = BigInt::to_bytes_le(&balance); + bytes.resize(32, 0); + bytes + }; + + let root_balance = root.balance.to_bytes(); + + assert_eq!(root_balance.to_vec(), exp_balance); + + // expect depth to be 4 + assert!(*merkle_tree.depth() == 4_usize); + + // get proof for entry 0 + let proof = merkle_tree.generate_proof(0).unwrap(); + + // verify proof + assert!(merkle_tree.verify_proof(&proof)); + } } diff --git a/src/merkle_sum_tree/utils/csv_parser.rs b/src/merkle_sum_tree/utils/csv_parser.rs index d14bf70a..c86be2fa 100644 --- a/src/merkle_sum_tree/utils/csv_parser.rs +++ b/src/merkle_sum_tree/utils/csv_parser.rs @@ -1,4 +1,5 @@ use crate::merkle_sum_tree::Entry; +use num_bigint::BigInt; use serde::Deserialize; use std::error::Error; use std::fs::File; @@ -7,7 +8,7 @@ use std::path::Path; #[derive(Debug, Deserialize)] struct CsvEntry { username: String, - balance: u64, + balance: String, } pub fn parse_csv_to_entries>(path: P) -> Result, Box> { @@ -17,7 +18,8 @@ pub fn parse_csv_to_entries>(path: P) -> Result, Box]) -> Option { +pub fn index_of(username: &str, balance: BigInt, nodes: &[Vec]) -> Option { let entry = Entry::new(username.to_string(), balance).unwrap(); let leaf = entry.compute_leaf(); let leaf_hash = leaf.hash; diff --git a/src/merkle_sum_tree/utils/mod.rs b/src/merkle_sum_tree/utils/mod.rs index 9be6f937..7b56248d 100644 --- a/src/merkle_sum_tree/utils/mod.rs +++ b/src/merkle_sum_tree/utils/mod.rs @@ -5,6 +5,7 @@ mod csv_parser; mod hash; mod index_of; mod proof_verification; +mod operation_helpers; pub use build_tree::build_merkle_tree_from_entries; pub use create_middle_node::create_middle_node; @@ -13,10 +14,6 @@ pub use csv_parser::parse_csv_to_entries; pub use hash::poseidon; pub use index_of::index_of; pub use proof_verification::verify_proof; +pub use operation_helpers::{big_intify_username, big_int_to_fp}; + -// Add the big_intify_username function -pub fn big_intify_username(username: &str) -> u64 { - let utf8_bytes = username.as_bytes(); - let hex_string = hex::encode(utf8_bytes); - u64::from_str_radix(&hex_string, 16).unwrap_or(0) -} diff --git a/src/merkle_sum_tree/utils/operation_helpers.rs b/src/merkle_sum_tree/utils/operation_helpers.rs new file mode 100644 index 00000000..4d9a9640 --- /dev/null +++ b/src/merkle_sum_tree/utils/operation_helpers.rs @@ -0,0 +1,42 @@ +use num_bigint::{BigInt, Sign}; +use halo2_proofs::halo2curves::{bn256::{Fr as Fp}, group::ff::PrimeField}; + +// Return a BigUint representation of the username +pub fn big_intify_username(username: &str) -> BigInt { + let utf8_bytes = username.as_bytes(); + BigInt::from_bytes_be(Sign::Plus, utf8_bytes) +} + +pub fn big_int_to_fp(big_int: &BigInt) -> Fp { + Fp::from_str_vartime(&big_int.to_str_radix(10)[..]).unwrap() +} + +#[cfg(test)] +mod tests { + + use num_bigint::{ToBigInt, BigInt}; + use super::big_int_to_fp; + + #[test] + fn test_big_int_conversion() { + + let big_int = 3.to_bigint().unwrap(); + let fp = big_int_to_fp(&big_int); + + assert_eq!(fp, 3.into()); + + let big_int_over_64 = (18446744073709551616_i128).to_bigint().unwrap(); + let fp_2 = big_int_to_fp(&big_int_over_64); + + let big_int_to_bytes = { + let (_, mut bytes) = BigInt::to_bytes_le(&big_int_over_64); + bytes.resize(32, 0); + bytes + }; + + assert_eq!(fp_2.to_bytes().to_vec(), big_int_to_bytes); + + let fp_3 = fp_2 - fp; + assert_eq!(fp_3, 18446744073709551613.into()); + } +} diff --git a/src/merkle_sum_tree/utils/proof_verification.rs b/src/merkle_sum_tree/utils/proof_verification.rs index e7427b8e..0c60386a 100644 --- a/src/merkle_sum_tree/utils/proof_verification.rs +++ b/src/merkle_sum_tree/utils/proof_verification.rs @@ -1,12 +1,10 @@ -// proof_verification.rs - use super::create_middle_node::create_middle_node; +use crate::merkle_sum_tree::utils::{big_int_to_fp}; use crate::merkle_sum_tree::{MerkleProof, Node}; -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; pub fn verify_proof(proof: &MerkleProof) -> bool { let mut node = proof.entry.compute_leaf(); - let mut balance = Fp::from(proof.entry.balance()); + let mut balance = big_int_to_fp(proof.entry.balance()); for i in 0..proof.sibling_hashes.len() { let sibling_node = Node { From e0edd4380abee789676a159ee2e6c22ac0173929 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 09:40:44 +0200 Subject: [PATCH 15/25] test: circuit using input with bigint entries --- src/circuits/merkle_sum_tree.rs | 49 +++++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 6a177746..80416fa5 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -104,10 +104,11 @@ mod tests { use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof, big_int_to_fp}; use super::super::utils::{full_prover, full_verifier}; use rand::rngs::OsRng; + use num_bigint::{ToBigInt}; - fn instantiate_circuit(assets_sum: Fp) -> MerkleSumTreeCircuit{ + fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit{ - let merkle_sum_tree= MerkleSumTree::new("src/circuits/csv_entries/entry_16.csv").unwrap(); + let merkle_sum_tree= MerkleSumTree::new(path).unwrap(); let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); @@ -142,7 +143,27 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + valid_prover.assert_satisfied(); + + } + + #[test] + fn test_valid_merkle_sum_tree_2() { + + // Same as above but now the entries contain a balance that is greater than 64 bits + // liabilities sum is 18446744073710096590 + + let assets_sum_big_int = 18446744073710096591_u128.to_bigint().unwrap(); // greater than liabilities sum + + let assets_sum = big_int_to_fp(&assets_sum_big_int); + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; @@ -170,7 +191,7 @@ mod tests { let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk generation should not fail"); // Only now we can instantiate the circuit with the actual inputs - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; @@ -187,7 +208,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let invalid_root_hash = Fp::from(1000u64); @@ -225,7 +246,7 @@ mod tests { let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk should not fail"); // Only now we can instantiate the circuit with the actual inputs - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let invalid_root_hash = Fp::from(1000u64); @@ -245,7 +266,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let mut circuit = instantiate_circuit(assets_sum); + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // invalidate leaf hash circuit.leaf_hash = Fp::from(1000u64); @@ -272,7 +293,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // add invalid leaf hash in the instance column let invalid_leaf_hash = Fp::from(1000u64); @@ -302,7 +323,7 @@ mod tests { let user_balance = Fp::from(11888u64); - let mut circuit = instantiate_circuit(assets_sum); + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // invalid leaf balance circuit.leaf_hash = Fp::from(1000u64); @@ -330,7 +351,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // add invalid leaf balance in the instance column let invalid_leaf_balance = Fp::from(1000u64); @@ -358,7 +379,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let mut circuit = instantiate_circuit(assets_sum); + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // invalidate path index inside the circuit circuit.path_indices[0] = Fp::from(2); @@ -424,7 +445,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let mut circuit = instantiate_circuit(assets_sum); + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); // swap indices circuit.path_indices[0] = Fp::from(1); @@ -452,7 +473,7 @@ mod tests { let less_than_assets_sum = Fp::from(556861u64); // less than liabilities sum (556862) - let circuit = instantiate_circuit(less_than_assets_sum); + let circuit = instantiate_circuit(less_than_assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; @@ -486,7 +507,7 @@ mod tests { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - let circuit = instantiate_circuit(assets_sum); + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let root = BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)).into_drawing_area(); From 2dafd30a82c62b7d8d2d1994bd96dc71c1c4dd06 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 10:18:44 +0200 Subject: [PATCH 16/25] refactor: move `merkle_sum_tree` module testing to `tests.rs` --- src/merkle_sum_tree/mod.rs | 98 +------------- src/merkle_sum_tree/tests.rs | 120 ++++++++++++++++++ .../utils/operation_helpers.rs | 32 +---- 3 files changed, 122 insertions(+), 128 deletions(-) create mode 100644 src/merkle_sum_tree/tests.rs diff --git a/src/merkle_sum_tree/mod.rs b/src/merkle_sum_tree/mod.rs index 24b611aa..553f1847 100644 --- a/src/merkle_sum_tree/mod.rs +++ b/src/merkle_sum_tree/mod.rs @@ -1,6 +1,7 @@ mod entry; mod merkle_sum_tree_lib; mod utils; +mod tests; use halo2_proofs::halo2curves::bn256::{Fr as Fp}; #[derive(Default, Clone, Debug)] @@ -22,101 +23,4 @@ pub use entry::Entry; pub use merkle_sum_tree_lib::MerkleSumTree; pub use utils::{big_intify_username, big_int_to_fp}; -#[cfg(test)] -mod tests { - use num_bigint::{ToBigInt, BigInt}; - - use super::{Entry, MerkleSumTree}; - - #[test] - fn test_mst() { - // create new merkle tree - let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); - - // get root - let root = merkle_tree.root(); - - // expect root hash to be different than 0 - assert!(root.hash != 0.into()); - // expect balance to match the sum of all entries - assert!(root.balance == 556862.into()); - // expect depth to be 4 - assert!(*merkle_tree.depth() == 4_usize); - - // get proof for entry 0 - let proof = merkle_tree.generate_proof(0).unwrap(); - - // verify proof - assert!(merkle_tree.verify_proof(&proof)); - - // Should generate different root hashes when changing the entry order - let merkle_tree_2 = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); - assert_ne!(root.hash, merkle_tree_2.root().hash); - - // the balance total should be the same - assert_eq!(root.balance, merkle_tree_2.root().balance); - - // should retrun the index of an entry that exist in the tree - assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479.to_bigint().unwrap()), Some(15)); - - // shouldn't retrun the index of an entry that doesn't exist in the tree - assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478.to_bigint().unwrap()), None); - - // should create valid proof for each entry in the tree and verify it - for i in 0..15 { - let proof = merkle_tree.generate_proof(i).unwrap(); - assert!(merkle_tree.verify_proof(&proof)); - } - - // shouldn't create a proof for an entry that doesn't exist in the tree - assert!(merkle_tree.generate_proof(16).is_err()); - - // shouldn't verify a proof with a wrong entry - let mut proof_invalid_1 = proof.clone(); - proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479.to_bigint().unwrap()).unwrap(); - assert!(!merkle_tree.verify_proof(&proof_invalid_1)); - - // shouldn't verify a proof with a wrong root hash - let mut proof_invalid_2 = proof.clone(); - proof_invalid_2.root_hash = 0.into(); - assert!(!merkle_tree.verify_proof(&proof_invalid_2)); - - // shouldn't verify a proof with a wrong computed balance - let mut proof_invalid_3 = proof; - proof_invalid_3.sibling_sums[0] = 0.into(); - } - - #[test] - fn test_mst_with_bigint () { - // create new merkle tree with entries that have balances greater than 2^64 - let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_bigints.csv").unwrap(); - - // get root - let root = merkle_tree.root(); - - // expect root hash to be different than 0 - assert!(root.hash != 0.into()); - - // expect balance to match the sum of all entries - let exp_balance = { - let balance = 18446744073710096590_u128.to_bigint().unwrap(); - let (_, mut bytes) = BigInt::to_bytes_le(&balance); - bytes.resize(32, 0); - bytes - }; - - let root_balance = root.balance.to_bytes(); - - assert_eq!(root_balance.to_vec(), exp_balance); - - // expect depth to be 4 - assert!(*merkle_tree.depth() == 4_usize); - - // get proof for entry 0 - let proof = merkle_tree.generate_proof(0).unwrap(); - - // verify proof - assert!(merkle_tree.verify_proof(&proof)); - } -} diff --git a/src/merkle_sum_tree/tests.rs b/src/merkle_sum_tree/tests.rs new file mode 100644 index 00000000..7ce94fe0 --- /dev/null +++ b/src/merkle_sum_tree/tests.rs @@ -0,0 +1,120 @@ +#[cfg(test)] +mod test { + + use num_bigint::{ToBigInt, BigInt}; + use crate::merkle_sum_tree::{Entry, MerkleSumTree}; + use crate::merkle_sum_tree::utils::big_int_to_fp; + + #[test] + fn test_mst() { + // create new merkle tree + let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); + + // get root + let root = merkle_tree.root(); + + // expect root hash to be different than 0 + assert!(root.hash != 0.into()); + // expect balance to match the sum of all entries + assert!(root.balance == 556862.into()); + // expect depth to be 4 + assert!(*merkle_tree.depth() == 4_usize); + + // get proof for entry 0 + let proof = merkle_tree.generate_proof(0).unwrap(); + + // verify proof + assert!(merkle_tree.verify_proof(&proof)); + + // Should generate different root hashes when changing the entry order + let merkle_tree_2 = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); + assert_ne!(root.hash, merkle_tree_2.root().hash); + + // the balance total should be the same + assert_eq!(root.balance, merkle_tree_2.root().balance); + + // should retrun the index of an entry that exist in the tree + assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479.to_bigint().unwrap()), Some(15)); + + // shouldn't retrun the index of an entry that doesn't exist in the tree + assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478.to_bigint().unwrap()), None); + + // should create valid proof for each entry in the tree and verify it + for i in 0..15 { + let proof = merkle_tree.generate_proof(i).unwrap(); + assert!(merkle_tree.verify_proof(&proof)); + } + + // shouldn't create a proof for an entry that doesn't exist in the tree + assert!(merkle_tree.generate_proof(16).is_err()); + + // shouldn't verify a proof with a wrong entry + let mut proof_invalid_1 = proof.clone(); + proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479.to_bigint().unwrap()).unwrap(); + assert!(!merkle_tree.verify_proof(&proof_invalid_1)); + + // shouldn't verify a proof with a wrong root hash + let mut proof_invalid_2 = proof.clone(); + proof_invalid_2.root_hash = 0.into(); + assert!(!merkle_tree.verify_proof(&proof_invalid_2)); + + // shouldn't verify a proof with a wrong computed balance + let mut proof_invalid_3 = proof; + proof_invalid_3.sibling_sums[0] = 0.into(); + } + + #[test] + fn test_mst_with_bigint () { + // create new merkle tree with entries that have balances greater than 2^64 + let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_bigints.csv").unwrap(); + + // get root + let root = merkle_tree.root(); + + // expect root hash to be different than 0 + assert!(root.hash != 0.into()); + + // expect balance to match the sum of all entries + let exp_balance = { + let balance = 18446744073710096590_u128.to_bigint().unwrap(); + let (_, mut bytes) = BigInt::to_bytes_le(&balance); + bytes.resize(32, 0); + bytes + }; + + let root_balance = root.balance.to_bytes(); + + assert_eq!(root_balance.to_vec(), exp_balance); + + // expect depth to be 4 + assert!(*merkle_tree.depth() == 4_usize); + + // get proof for entry 0 + let proof = merkle_tree.generate_proof(0).unwrap(); + + // verify proof + assert!(merkle_tree.verify_proof(&proof)); + } + #[test] + fn test_big_int_conversion() { + + let big_int = 3.to_bigint().unwrap(); + let fp = big_int_to_fp(&big_int); + + assert_eq!(fp, 3.into()); + + let big_int_over_64 = (18446744073709551616_i128).to_bigint().unwrap(); + let fp_2 = big_int_to_fp(&big_int_over_64); + + let big_int_to_bytes = { + let (_, mut bytes) = BigInt::to_bytes_le(&big_int_over_64); + bytes.resize(32, 0); + bytes + }; + + assert_eq!(fp_2.to_bytes().to_vec(), big_int_to_bytes); + + let fp_3 = fp_2 - fp; + assert_eq!(fp_3, 18446744073709551613.into()); + } +} \ No newline at end of file diff --git a/src/merkle_sum_tree/utils/operation_helpers.rs b/src/merkle_sum_tree/utils/operation_helpers.rs index 4d9a9640..6b4960c3 100644 --- a/src/merkle_sum_tree/utils/operation_helpers.rs +++ b/src/merkle_sum_tree/utils/operation_helpers.rs @@ -9,34 +9,4 @@ pub fn big_intify_username(username: &str) -> BigInt { pub fn big_int_to_fp(big_int: &BigInt) -> Fp { Fp::from_str_vartime(&big_int.to_str_radix(10)[..]).unwrap() -} - -#[cfg(test)] -mod tests { - - use num_bigint::{ToBigInt, BigInt}; - use super::big_int_to_fp; - - #[test] - fn test_big_int_conversion() { - - let big_int = 3.to_bigint().unwrap(); - let fp = big_int_to_fp(&big_int); - - assert_eq!(fp, 3.into()); - - let big_int_over_64 = (18446744073709551616_i128).to_bigint().unwrap(); - let fp_2 = big_int_to_fp(&big_int_over_64); - - let big_int_to_bytes = { - let (_, mut bytes) = BigInt::to_bytes_le(&big_int_over_64); - bytes.resize(32, 0); - bytes - }; - - assert_eq!(fp_2.to_bytes().to_vec(), big_int_to_bytes); - - let fp_3 = fp_2 - fp; - assert_eq!(fp_3, 18446744073709551613.into()); - } -} +} \ No newline at end of file From 8f521ca996f017cc9a578dc3731b37eed9387dea Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 11:11:48 +0200 Subject: [PATCH 17/25] chore: renamed module to `mst` and modified relative paths to absolute --- src/merkle_sum_tree/entry.rs | 1 - src/merkle_sum_tree/mod.rs | 4 ++-- src/merkle_sum_tree/{merkle_sum_tree_lib.rs => mst.rs} | 0 src/merkle_sum_tree/utils/build_tree.rs | 2 +- src/merkle_sum_tree/utils/create_middle_node.rs | 2 +- src/merkle_sum_tree/utils/index_of.rs | 1 - src/merkle_sum_tree/utils/mod.rs | 3 +-- src/merkle_sum_tree/utils/proof_verification.rs | 6 ++++-- 8 files changed, 9 insertions(+), 10 deletions(-) rename src/merkle_sum_tree/{merkle_sum_tree_lib.rs => mst.rs} (100%) diff --git a/src/merkle_sum_tree/entry.rs b/src/merkle_sum_tree/entry.rs index 19317d28..22227ed3 100644 --- a/src/merkle_sum_tree/entry.rs +++ b/src/merkle_sum_tree/entry.rs @@ -1,5 +1,4 @@ use halo2_proofs::halo2curves::bn256::{Fr as Fp}; - use crate::merkle_sum_tree::utils::{big_intify_username, poseidon, big_int_to_fp}; use crate::merkle_sum_tree::Node; use num_bigint::{BigInt}; diff --git a/src/merkle_sum_tree/mod.rs b/src/merkle_sum_tree/mod.rs index 553f1847..96083ee4 100644 --- a/src/merkle_sum_tree/mod.rs +++ b/src/merkle_sum_tree/mod.rs @@ -1,5 +1,5 @@ mod entry; -mod merkle_sum_tree_lib; +mod mst; mod utils; mod tests; use halo2_proofs::halo2curves::bn256::{Fr as Fp}; @@ -20,7 +20,7 @@ pub struct Node { } pub use entry::Entry; -pub use merkle_sum_tree_lib::MerkleSumTree; +pub use mst::MerkleSumTree; pub use utils::{big_intify_username, big_int_to_fp}; diff --git a/src/merkle_sum_tree/merkle_sum_tree_lib.rs b/src/merkle_sum_tree/mst.rs similarity index 100% rename from src/merkle_sum_tree/merkle_sum_tree_lib.rs rename to src/merkle_sum_tree/mst.rs diff --git a/src/merkle_sum_tree/utils/build_tree.rs b/src/merkle_sum_tree/utils/build_tree.rs index c84ba922..cd1983c4 100644 --- a/src/merkle_sum_tree/utils/build_tree.rs +++ b/src/merkle_sum_tree/utils/build_tree.rs @@ -1,4 +1,4 @@ -use super::create_middle_node::create_middle_node; +use crate::merkle_sum_tree::utils::create_middle_node::create_middle_node; use crate::merkle_sum_tree::{Entry, Node}; use halo2_proofs::halo2curves::bn256::{Fr as Fp}; diff --git a/src/merkle_sum_tree/utils/create_middle_node.rs b/src/merkle_sum_tree/utils/create_middle_node.rs index 1f605b7c..30a1c3f5 100644 --- a/src/merkle_sum_tree/utils/create_middle_node.rs +++ b/src/merkle_sum_tree/utils/create_middle_node.rs @@ -1,4 +1,4 @@ -use super::hash::poseidon; +use crate::merkle_sum_tree::utils::hash::poseidon; use crate::merkle_sum_tree::Node; pub fn create_middle_node(child_l: &Node, child_r: &Node) -> Node { diff --git a/src/merkle_sum_tree/utils/index_of.rs b/src/merkle_sum_tree/utils/index_of.rs index e97f504d..110c8a5a 100644 --- a/src/merkle_sum_tree/utils/index_of.rs +++ b/src/merkle_sum_tree/utils/index_of.rs @@ -1,5 +1,4 @@ use num_bigint::BigInt; - use crate::merkle_sum_tree::{Entry, Node}; pub fn index_of(username: &str, balance: BigInt, nodes: &[Vec]) -> Option { diff --git a/src/merkle_sum_tree/utils/mod.rs b/src/merkle_sum_tree/utils/mod.rs index 7b56248d..253f96e7 100644 --- a/src/merkle_sum_tree/utils/mod.rs +++ b/src/merkle_sum_tree/utils/mod.rs @@ -8,12 +8,11 @@ mod proof_verification; mod operation_helpers; pub use build_tree::build_merkle_tree_from_entries; -pub use create_middle_node::create_middle_node; pub use create_proof::create_proof; pub use csv_parser::parse_csv_to_entries; pub use hash::poseidon; pub use index_of::index_of; pub use proof_verification::verify_proof; -pub use operation_helpers::{big_intify_username, big_int_to_fp}; +pub use operation_helpers::*; diff --git a/src/merkle_sum_tree/utils/proof_verification.rs b/src/merkle_sum_tree/utils/proof_verification.rs index 0c60386a..76d8ca55 100644 --- a/src/merkle_sum_tree/utils/proof_verification.rs +++ b/src/merkle_sum_tree/utils/proof_verification.rs @@ -1,5 +1,7 @@ -use super::create_middle_node::create_middle_node; -use crate::merkle_sum_tree::utils::{big_int_to_fp}; +use crate::merkle_sum_tree::utils::{ + create_middle_node::create_middle_node, + big_int_to_fp, +}; use crate::merkle_sum_tree::{MerkleProof, Node}; pub fn verify_proof(proof: &MerkleProof) -> bool { From 166588d793711798f6d1b44d47f254480f7dc8bc Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 11:48:44 +0200 Subject: [PATCH 18/25] refactor: move `circuits` module testing to `tests.rs` --- src/circuits/merkle_sum_tree.rs | 436 -------------------------------- src/circuits/mod.rs | 3 +- src/circuits/tests.rs | 435 +++++++++++++++++++++++++++++++ 3 files changed, 437 insertions(+), 437 deletions(-) create mode 100644 src/circuits/tests.rs diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 80416fa5..9e217a01 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -85,439 +85,3 @@ impl Circuit for MerkleSumTreeCircuit { Ok(()) } } - -#[cfg(test)] -mod tests { - - use super::MerkleSumTreeCircuit; - use halo2_proofs::{ - dev::{MockProver, FailureLocation, VerifyFailure}, - halo2curves::bn256::{Fr as Fp, Bn256}, - plonk::{Any, keygen_pk, keygen_vk}, - poly::{ - kzg::{ - commitment::{ParamsKZG}, - }, - }, - }; - use std::marker::PhantomData; - use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof, big_int_to_fp}; - use super::super::utils::{full_prover, full_verifier}; - use rand::rngs::OsRng; - use num_bigint::{ToBigInt}; - - fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit{ - - let merkle_sum_tree= MerkleSumTree::new(path).unwrap(); - - let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); - - MerkleSumTreeCircuit { - leaf_hash: proof.entry.compute_leaf().hash, - leaf_balance: big_int_to_fp(proof.entry.balance()), - path_element_hashes: proof.sibling_hashes, - path_element_balances: proof.sibling_sums, - path_indices: proof.path_indices, - assets_sum, - root_hash: proof.root_hash, - _marker: PhantomData, - } - - } - - fn instantiate_empty_circuit() -> MerkleSumTreeCircuit{ - MerkleSumTreeCircuit { - leaf_hash: Fp::zero(), - leaf_balance: Fp::zero(), - path_element_hashes: vec![Fp::zero(); 4], - path_element_balances: vec![Fp::zero(); 4], - path_indices: vec![Fp::zero(); 4], - assets_sum : Fp::zero(), - root_hash: Fp::zero(), - _marker: PhantomData, - } - } - - #[test] - fn test_valid_merkle_sum_tree() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - valid_prover.assert_satisfied(); - - } - - #[test] - fn test_valid_merkle_sum_tree_2() { - - // Same as above but now the entries contain a balance that is greater than 64 bits - // liabilities sum is 18446744073710096590 - - let assets_sum_big_int = 18446744073710096591_u128.to_bigint().unwrap(); // greater than liabilities sum - - let assets_sum = big_int_to_fp(&assets_sum_big_int); - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - valid_prover.assert_satisfied(); - - } - - #[test] - fn test_valid_merkle_sum_tree_with_full_prover() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_empty_circuit(); - - // we generate a universal trusted setup of our own for testing - let params = ParamsKZG::::setup(8, OsRng); - - // we generate the verification key and the proving key - // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys - // Note: the dimension of the circuit used to generate the keys must be the same as the dimension of the circuit used to generate the proof - // In this case, the dimension are represented by the heigth of the merkle tree - let vk = keygen_vk(¶ms, &circuit).expect("vk generation should not fail"); - let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk generation should not fail"); - - // Only now we can instantiate the circuit with the actual inputs - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - // Generate the proof - let proof = full_prover(¶ms, &pk, circuit, &public_input); - - // verify the proof to be true - assert!(full_verifier(¶ms, &vk, proof, &public_input)); - } - - // Passing an invalid root hash in the instance column should fail the permutation check between the computed root hash and the instance column root hash - #[test] - fn test_invalid_root_hash() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let invalid_root_hash = Fp::from(1000u64); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 - } - } - ]) - ); - - } - - #[test] - fn test_invalid_root_hash_with_full_prover() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_empty_circuit(); - - // we generate a universal trusted setup of our own for testing - let params = ParamsKZG::::setup(8, OsRng); - - // we generate the verification key and the proving key - // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys - let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); - let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk should not fail"); - - // Only now we can instantiate the circuit with the actual inputs - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let invalid_root_hash = Fp::from(1000u64); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; - - // Generate the proof - let proof = full_prover(¶ms, &pk, circuit, &public_input); - - // verify the proof to be false - assert!(!full_verifier(¶ms, &vk, proof, &public_input)); - - } - - // Passing an invalid leaf hash as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash - #[test] - fn test_invalid_leaf_hash_as_witness() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // invalidate leaf hash - circuit.leaf_hash = Fp::from(1000u64); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 - } - } - ]) - ); - } - - // Passing an invalid leaf hash in the instance column should fail the permutation check between the (valid) leaf hash added as part of the witness and the instance column leaf hash - #[test] - fn test_invalid_leaf_hash_as_instance() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // add invalid leaf hash in the instance column - let invalid_leaf_hash = Fp::from(1000u64); - - let public_input = vec![invalid_leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - } - }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 0 } }, - ]) - ); - } - - // Passing an invalid leaf balance as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash - #[test] - fn test_invalid_leaf_balance_as_witness() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let user_balance = Fp::from(11888u64); - - let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // invalid leaf balance - circuit.leaf_hash = Fp::from(1000u64); - - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 - } - } - ]) - ); - } - - - // Passing an invalid leaf balance in the instance column should fail the permutation check between the (valid) leaf balance added as part of the witness and the instance column leaf balance - #[test] - fn test_invalid_leaf_balance_as_instance() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // add invalid leaf balance in the instance column - let invalid_leaf_balance = Fp::from(1000u64); - - let public_input = vec![circuit.leaf_hash, invalid_leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::advice(), 1).into(), location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - } - }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 1 } }, - ]) - ); - } - - // Passing a non binary index should fail the bool constraint check, the two swap constraints and the permutation check between the computed root hash and the instance column root hash - #[test] - fn test_non_binary_index() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // invalidate path index inside the circuit - circuit.path_indices[0] = Fp::from(2); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::ConstraintNotSatisfied { - constraint: ((0, "bool constraint").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - }, - cell_values: vec![ - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - }, - cell_values: vec![ - (((Any::advice(), 0).into(), 0).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), - (((Any::advice(), 0).into(), 1).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), - (((Any::advice(), 2).into(), 0).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), - (((Any::advice(), 2).into(), 1).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 1, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - }, - cell_values: vec![ - (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), - (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 - } - } - ]) - ); - } - - // Swapping the indices should fail the permutation check between the computed root hash and the instance column root hash - #[test] - fn test_swapping_index() { - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - // swap indices - circuit.path_indices[0] = Fp::from(1); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 - } - } - ]) - ); - } - - // Passing an assets sum that is less than the liabilities sum should fail the lessThan constraint check - #[test] - fn test_is_not_less_than() { - - let less_than_assets_sum = Fp::from(556861u64); // less than liabilities sum (556862) - - let circuit = instantiate_circuit(less_than_assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; - - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); - - assert_eq!( - invalid_prover.verify(), - Err(vec![ - VerifyFailure::ConstraintNotSatisfied { - constraint: ((7, "verifies that `check` from current config equal to is_lt from LtChip").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (17, "enforce sum to be less than total assets").into(), - offset: 0 - }, - cell_values: vec![ - (((Any::advice(), 2).into(), 0).into(), "1".to_string()), - // The zero means that is not less than - (((Any::advice(), 11).into(), 0).into(), "0".to_string()) - ] - } - ]) - ); - - assert!(invalid_prover.verify().is_err()); - } - - #[cfg(feature = "dev-graph")] - #[test] - fn print_merkle_sum_tree() { - use plotters::prelude::*; - - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) - - let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - - let root = - BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)).into_drawing_area(); - root.fill(&WHITE).unwrap(); - let root = root - .titled("Merkle Sum Tree Layout", ("sans-serif", 60)) - .unwrap(); - - halo2_proofs::dev::CircuitLayout::default() - .render(8, &circuit, &root) - .unwrap(); - } -} \ No newline at end of file diff --git a/src/circuits/mod.rs b/src/circuits/mod.rs index 8a7c758b..9f47557d 100644 --- a/src/circuits/mod.rs +++ b/src/circuits/mod.rs @@ -1,2 +1,3 @@ pub mod merkle_sum_tree; -pub mod utils; \ No newline at end of file +pub mod utils; +pub mod tests; \ No newline at end of file diff --git a/src/circuits/tests.rs b/src/circuits/tests.rs new file mode 100644 index 00000000..f35fe011 --- /dev/null +++ b/src/circuits/tests.rs @@ -0,0 +1,435 @@ +#[cfg(test)] +mod test { + + use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof, big_int_to_fp}; + use crate::circuits::merkle_sum_tree::MerkleSumTreeCircuit; + use crate::circuits::utils::{full_prover, full_verifier}; + use halo2_proofs::{ + dev::{MockProver, FailureLocation, VerifyFailure}, + halo2curves::bn256::{Fr as Fp, Bn256}, + plonk::{Any, keygen_pk, keygen_vk}, + poly::{ + kzg::{ + commitment::{ParamsKZG}, + }, + }, + }; + use std::marker::PhantomData; + use rand::rngs::OsRng; + use num_bigint::{ToBigInt}; + + fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit{ + + let merkle_sum_tree= MerkleSumTree::new(path).unwrap(); + + let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); + + MerkleSumTreeCircuit { + leaf_hash: proof.entry.compute_leaf().hash, + leaf_balance: big_int_to_fp(proof.entry.balance()), + path_element_hashes: proof.sibling_hashes, + path_element_balances: proof.sibling_sums, + path_indices: proof.path_indices, + assets_sum, + root_hash: proof.root_hash, + _marker: PhantomData, + } + + } + + fn instantiate_empty_circuit() -> MerkleSumTreeCircuit{ + MerkleSumTreeCircuit { + leaf_hash: Fp::zero(), + leaf_balance: Fp::zero(), + path_element_hashes: vec![Fp::zero(); 4], + path_element_balances: vec![Fp::zero(); 4], + path_indices: vec![Fp::zero(); 4], + assets_sum : Fp::zero(), + root_hash: Fp::zero(), + _marker: PhantomData, + } + } + + #[test] + fn test_valid_merkle_sum_tree() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + valid_prover.assert_satisfied(); + + } + + #[test] + fn test_valid_merkle_sum_tree_2() { + + // Same as above but now the entries contain a balance that is greater than 64 bits + // liabilities sum is 18446744073710096590 + + let assets_sum_big_int = 18446744073710096591_u128.to_bigint().unwrap(); // greater than liabilities sum + + let assets_sum = big_int_to_fp(&assets_sum_big_int); + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + valid_prover.assert_satisfied(); + + } + + #[test] + fn test_valid_merkle_sum_tree_with_full_prover() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_empty_circuit(); + + // we generate a universal trusted setup of our own for testing + let params = ParamsKZG::::setup(8, OsRng); + + // we generate the verification key and the proving key + // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys + // Note: the dimension of the circuit used to generate the keys must be the same as the dimension of the circuit used to generate the proof + // In this case, the dimension are represented by the heigth of the merkle tree + let vk = keygen_vk(¶ms, &circuit).expect("vk generation should not fail"); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk generation should not fail"); + + // Only now we can instantiate the circuit with the actual inputs + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + // Generate the proof + let proof = full_prover(¶ms, &pk, circuit, &public_input); + + // verify the proof to be true + assert!(full_verifier(¶ms, &vk, proof, &public_input)); + } + + // Passing an invalid root hash in the instance column should fail the permutation check between the computed root hash and the instance column root hash + #[test] + fn test_invalid_root_hash() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let invalid_root_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + + } + + #[test] + fn test_invalid_root_hash_with_full_prover() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_empty_circuit(); + + // we generate a universal trusted setup of our own for testing + let params = ParamsKZG::::setup(8, OsRng); + + // we generate the verification key and the proving key + // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys + let vk = keygen_vk(¶ms, &circuit).expect("vk should not fail"); + let pk = keygen_pk(¶ms, vk.clone(), &circuit).expect("pk should not fail"); + + // Only now we can instantiate the circuit with the actual inputs + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let invalid_root_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + + // Generate the proof + let proof = full_prover(¶ms, &pk, circuit, &public_input); + + // verify the proof to be false + assert!(!full_verifier(¶ms, &vk, proof, &public_input)); + + } + + // Passing an invalid leaf hash as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash + #[test] + fn test_invalid_leaf_hash_as_witness() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // invalidate leaf hash + circuit.leaf_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + // Passing an invalid leaf hash in the instance column should fail the permutation check between the (valid) leaf hash added as part of the witness and the instance column leaf hash + #[test] + fn test_invalid_leaf_hash_as_instance() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // add invalid leaf hash in the instance column + let invalid_leaf_hash = Fp::from(1000u64); + + let public_input = vec![invalid_leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + } + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 0 } }, + ]) + ); + } + + // Passing an invalid leaf balance as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash + #[test] + fn test_invalid_leaf_balance_as_witness() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let user_balance = Fp::from(11888u64); + + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // invalid leaf balance + circuit.leaf_hash = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + + // Passing an invalid leaf balance in the instance column should fail the permutation check between the (valid) leaf balance added as part of the witness and the instance column leaf balance + #[test] + fn test_invalid_leaf_balance_as_instance() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // add invalid leaf balance in the instance column + let invalid_leaf_balance = Fp::from(1000u64); + + let public_input = vec![circuit.leaf_hash, invalid_leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::advice(), 1).into(), location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + } + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 1 } }, + ]) + ); + } + + // Passing a non binary index should fail the bool constraint check, the two swap constraints and the permutation check between the computed root hash and the instance column root hash + #[test] + fn test_non_binary_index() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // invalidate path index inside the circuit + circuit.path_indices[0] = Fp::from(2); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "bool constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 0).into(), 0).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), + (((Any::advice(), 0).into(), 1).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), + (((Any::advice(), 2).into(), 0).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), + (((Any::advice(), 2).into(), 1).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 1, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), + (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + ] + }, + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + // Swapping the indices should fail the permutation check between the computed root hash and the instance column root hash + #[test] + fn test_swapping_index() { + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + // swap indices + circuit.path_indices[0] = Fp::from(1); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, + VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } + } + ]) + ); + } + + // Passing an assets sum that is less than the liabilities sum should fail the lessThan constraint check + #[test] + fn test_is_not_less_than() { + + let less_than_assets_sum = Fp::from(556861u64); // less than liabilities sum (556862) + + let circuit = instantiate_circuit(less_than_assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + + let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + + assert_eq!( + invalid_prover.verify(), + Err(vec![ + VerifyFailure::ConstraintNotSatisfied { + constraint: ((7, "verifies that `check` from current config equal to is_lt from LtChip").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (17, "enforce sum to be less than total assets").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 2).into(), 0).into(), "1".to_string()), + // The zero means that is not less than + (((Any::advice(), 11).into(), 0).into(), "0".to_string()) + ] + } + ]) + ); + + assert!(invalid_prover.verify().is_err()); + } + + #[cfg(feature = "dev-graph")] + #[test] + fn print_merkle_sum_tree() { + use plotters::prelude::*; + + let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) + + let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + + let root = + BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)).into_drawing_area(); + root.fill(&WHITE).unwrap(); + let root = root + .titled("Merkle Sum Tree Layout", ("sans-serif", 60)) + .unwrap(); + + halo2_proofs::dev::CircuitLayout::default() + .render(8, &circuit, &root) + .unwrap(); + } +} \ No newline at end of file From db99ee2bc57ab36e12a63b83b64bc4459f6e8ddf Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 3 May 2023 12:33:36 +0200 Subject: [PATCH 19/25] feat: mst constructor throws an error if total balance overflows the bn256 prime --- src/merkle_sum_tree/csv/entry_16_overflow.csv | 17 +++++++++++++++++ src/merkle_sum_tree/tests.rs | 18 ++++++++++++++++++ src/merkle_sum_tree/utils/csv_parser.rs | 13 ++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 src/merkle_sum_tree/csv/entry_16_overflow.csv diff --git a/src/merkle_sum_tree/csv/entry_16_overflow.csv b/src/merkle_sum_tree/csv/entry_16_overflow.csv new file mode 100644 index 00000000..ca6f644c --- /dev/null +++ b/src/merkle_sum_tree/csv/entry_16_overflow.csv @@ -0,0 +1,17 @@ +username,balance +dxGaEAii,21888242871839275222246405745257275088548364400416034343698204186575808495616 +MBlfbBGI,67823 +lAhWlEWZ,18651 +nuZweYtO,22073 +gbdSwiuY,34897 +RZNneNuP,83296 +YsscHXkp,31699 +RkLzkDun,2087 +HlQlnEYI,30605 +RqkZOFYe,16881 +NjCSRAfD,41163 +pHniJMQY,14874 +dOGIMzKR,10032 +HfMDmNLp,55683 +xPLKzCBl,79731 +AtwIxZHo,35479 \ No newline at end of file diff --git a/src/merkle_sum_tree/tests.rs b/src/merkle_sum_tree/tests.rs index 7ce94fe0..a5fa769c 100644 --- a/src/merkle_sum_tree/tests.rs +++ b/src/merkle_sum_tree/tests.rs @@ -62,6 +62,24 @@ mod test { let mut proof_invalid_3 = proof; proof_invalid_3.sibling_sums[0] = 0.into(); } + + #[test] + fn test_mst_overflow() { + + let result = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_overflow.csv"); + // assert!(result.is_err(), "Expected an error due to balance overflow"); + + if let Err(e) = result { + assert_eq!( + e.to_string(), + "Balance is larger than the modulus" + ); + + + // Passing entries whose balance sum overflows the field should throw an error at the constructor + // MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_overflow.csv").unwrap() should throw Balance is larger than the modulus + } + } #[test] fn test_mst_with_bigint () { diff --git a/src/merkle_sum_tree/utils/csv_parser.rs b/src/merkle_sum_tree/utils/csv_parser.rs index c86be2fa..cc5e221d 100644 --- a/src/merkle_sum_tree/utils/csv_parser.rs +++ b/src/merkle_sum_tree/utils/csv_parser.rs @@ -16,13 +16,24 @@ pub fn parse_csv_to_entries>(path: P) -> Result, Box https://github.com/privacy-scaling-explorations/halo2curves/blob/main/src/bn256/fr.rs#L38 + const MODULUS_STR: &str = "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"; + + // throw error if balance is larger than the modulus + if balance_acc >= BigInt::parse_bytes(MODULUS_STR.as_bytes(), 16).unwrap() + { + return Err("Balance is larger than the modulus".into()); + } + Ok(entries) } From e1488f36b4359f6ba2aa7173f062a3e8bff77ac6 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Thu, 4 May 2023 09:16:18 +0200 Subject: [PATCH 20/25] style: run `cargo fmt` --- .github/workflows/rust.yml | 1 - src/chips/merkle_sum_tree.rs | 104 +++--- src/chips/mod.rs | 2 +- src/chips/poseidon/hash.rs | 15 +- src/chips/poseidon/spec.rs | 10 +- src/circuits/merkle_sum_tree.rs | 31 +- src/circuits/mod.rs | 2 +- src/circuits/tests.rs | 330 +++++++++++------- src/circuits/utils.rs | 43 ++- src/lib.rs | 2 +- src/merkle_sum_tree/entry.rs | 9 +- src/merkle_sum_tree/mod.rs | 8 +- src/merkle_sum_tree/mst.rs | 3 +- src/merkle_sum_tree/tests.rs | 99 +++--- src/merkle_sum_tree/utils/build_tree.rs | 2 +- src/merkle_sum_tree/utils/create_proof.rs | 2 +- src/merkle_sum_tree/utils/csv_parser.rs | 3 +- src/merkle_sum_tree/utils/hash.rs | 2 +- src/merkle_sum_tree/utils/index_of.rs | 2 +- src/merkle_sum_tree/utils/mod.rs | 6 +- .../utils/operation_helpers.rs | 4 +- .../utils/proof_verification.rs | 5 +- 22 files changed, 395 insertions(+), 290 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 663e03b0..7edf9de9 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -11,7 +11,6 @@ env: jobs: build: - runs-on: ubuntu-latest steps: diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index be977db3..d6e5de94 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -1,15 +1,15 @@ use crate::chips::poseidon::hash::{PoseidonChip, PoseidonConfig}; use crate::chips::poseidon::spec::MySpec; -use halo2_proofs::{circuit::*,plonk::*, poly::Rotation}; -use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; use eth_types::Field; +use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; +use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; const WIDTH: usize = 5; const RATE: usize = 4; const L: usize = 4; #[derive(Debug, Clone)] -pub struct MerkleSumTreeConfig { +pub struct MerkleSumTreeConfig { pub advice: [Column; 5], pub bool_selector: Selector, pub swap_selector: Selector, @@ -20,11 +20,11 @@ pub struct MerkleSumTreeConfig { pub lt_config: LtConfig, } #[derive(Debug, Clone)] -pub struct MerkleSumTreeChip { +pub struct MerkleSumTreeChip { config: MerkleSumTreeConfig, } -impl MerkleSumTreeChip { +impl MerkleSumTreeChip { pub fn construct(config: MerkleSumTreeConfig) -> Self { Self { config } } @@ -52,7 +52,7 @@ impl MerkleSumTreeChip { // enable equality for computed_sum copy constraint with instance column (col_e) meta.enable_equality(col_a); meta.enable_equality(col_b); - meta.enable_equality(col_c); + meta.enable_equality(col_c); meta.enable_equality(col_d); meta.enable_equality(col_e); meta.enable_equality(instance); @@ -80,9 +80,10 @@ impl MerkleSumTreeChip { let r2 = meta.query_advice(col_d, Rotation::next()); vec![ - s.clone() * (e.clone() * Expression::Constant(F::from(2)) * (c.clone() - a.clone()) - - (l1 - a) - - (c - r1)), + s.clone() + * (e.clone() * Expression::Constant(F::from(2)) * (c.clone() - a.clone()) + - (l1 - a) + - (c - r1)), s * (e * Expression::Constant(F::from(2)) * (d.clone() - b.clone()) - (l2 - b) - (d - r2)), @@ -100,12 +101,10 @@ impl MerkleSumTreeChip { let hash_inputs = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); - let poseidon_config = PoseidonChip::, WIDTH, RATE, L>::configure( - meta, - hash_inputs - ); + let poseidon_config = + PoseidonChip::, WIDTH, RATE, L>::configure(meta, hash_inputs); - // configure lt chip + // configure lt chip let lt_config = LtChip::configure( meta, |meta| meta.query_selector(lt_selector), @@ -121,19 +120,21 @@ impl MerkleSumTreeChip { lt_selector, instance, poseidon_config, - lt_config + lt_config, }; - meta.create_gate("verifies that `check` from current config equal to is_lt from LtChip", |meta| { - let q_enable = meta.query_selector(lt_selector); + meta.create_gate( + "verifies that `check` from current config equal to is_lt from LtChip", + |meta| { + let q_enable = meta.query_selector(lt_selector); - let check = meta.query_advice(col_c, Rotation::cur()); + let check = meta.query_advice(col_c, Rotation::cur()); - vec![q_enable * (config.lt_config.is_lt(meta, None) - check)] - }); + vec![q_enable * (config.lt_config.is_lt(meta, None) - check)] + }, + ); config - } pub fn assing_leaf_hash_and_balance( @@ -145,19 +146,18 @@ impl MerkleSumTreeChip { let (leaf_hash_cell, leaf_balance_cell) = layouter.assign_region( || "assign leaf hash", |mut region| { - let l = region.assign_advice( - || "leaf hash", - self.config.advice[0], - 0, - || Value::known(leaf_hash) + || "leaf hash", + self.config.advice[0], + 0, + || Value::known(leaf_hash), )?; let r = region.assign_advice( - || "leaf balance", - self.config.advice[1], - 0, - || Value::known(leaf_balance) + || "leaf balance", + self.config.advice[1], + 0, + || Value::known(leaf_balance), )?; Ok((l, r)) @@ -165,7 +165,6 @@ impl MerkleSumTreeChip { )?; Ok((leaf_hash_cell, leaf_balance_cell)) - } pub fn merkle_prove_layer( @@ -181,7 +180,7 @@ impl MerkleSumTreeChip { .assign_region( || "merkle prove layer", |mut region| { - // Row 0 + // Row 0 self.config.bool_selector.enable(&mut region, 0)?; self.config.swap_selector.enable(&mut region, 0)?; let l1 = prev_hash.copy_advice( @@ -208,11 +207,11 @@ impl MerkleSumTreeChip { 0, || Value::known(element_balance), )?; - let index = region.assign_advice(|| - "assign index", - self.config.advice[4], - 0, - || Value::known(index) + let index = region.assign_advice( + || "assign index", + self.config.advice[4], + 0, + || Value::known(index), )?; let mut l1_val = l1.value().map(|x| x.to_owned()); @@ -261,7 +260,10 @@ impl MerkleSumTreeChip { || r2_val, )?; - let computed_sum = left_balance.value().zip(right_balance.value()).map(|(a, b)| *a + b); + let computed_sum = left_balance + .value() + .zip(right_balance.value()) + .map(|(a, b)| *a + b); // Now we can assign the sum result to the computed_sum cell. let computed_sum_cell = region.assign_advice( @@ -304,14 +306,12 @@ impl MerkleSumTreeChip { mut layouter: impl Layouter, prev_computed_sum_cell: &AssignedCell, ) -> Result<(), Error> { - // Initiate chip config let chip = LtChip::construct(self.config.lt_config); layouter.assign_region( || "enforce sum to be less than total assets", |mut region| { - // copy the computed sum to the cell in the first column let computed_sum_cell = prev_computed_sum_cell.copy_advice( || "copy computed sum", @@ -325,8 +325,8 @@ impl MerkleSumTreeChip { || "copy total assets", self.config.instance, 3, - self.config.advice[1], - 0 + self.config.advice[1], + 0, )?; // set check to be equal to 1 @@ -337,14 +337,22 @@ impl MerkleSumTreeChip { || Value::known(F::from(1)), )?; - // enable lt seletor + // enable lt seletor self.config.lt_selector.enable(&mut region, 0)?; - total_assets_cell.value().zip(computed_sum_cell.value()).map(|(total_assets, computed_sum)| { - if let Err(e) = chip.assign(&mut region, 0, computed_sum.to_owned(), total_assets.to_owned()){ - println!("Error: {:?}", e); - }; - }); + total_assets_cell + .value() + .zip(computed_sum_cell.value()) + .map(|(total_assets, computed_sum)| { + if let Err(e) = chip.assign( + &mut region, + 0, + computed_sum.to_owned(), + total_assets.to_owned(), + ) { + println!("Error: {:?}", e); + }; + }); Ok(()) }, diff --git a/src/chips/mod.rs b/src/chips/mod.rs index 7fa813f0..22b3a66b 100644 --- a/src/chips/mod.rs +++ b/src/chips/mod.rs @@ -1,2 +1,2 @@ pub mod merkle_sum_tree; -pub mod poseidon; \ No newline at end of file +pub mod poseidon; diff --git a/src/chips/poseidon/hash.rs b/src/chips/poseidon/hash.rs index f9c6c619..658f6b68 100644 --- a/src/chips/poseidon/hash.rs +++ b/src/chips/poseidon/hash.rs @@ -32,8 +32,13 @@ pub struct PoseidonChip< _marker: PhantomData, } -impl, const WIDTH: usize, const RATE: usize, const L: usize> - PoseidonChip +impl< + F: FieldExt, + S: Spec, + const WIDTH: usize, + const RATE: usize, + const L: usize, + > PoseidonChip { pub fn construct(config: PoseidonConfig) -> Self { Self { @@ -64,9 +69,7 @@ impl, const WIDTH: usize, const RATE: usize rc_b.try_into().unwrap(), ); - PoseidonConfig { - pow5_config, - } + PoseidonConfig { pow5_config } } // L is the number of inputs to the hash function @@ -77,7 +80,6 @@ impl, const WIDTH: usize, const RATE: usize mut layouter: impl Layouter, input_cells: [AssignedCell; L], ) -> Result, Error> { - let pow5_chip = Pow5Chip::construct(self.config.pow5_config.clone()); // initialize the hasher @@ -87,5 +89,4 @@ impl, const WIDTH: usize, const RATE: usize )?; hasher.hash(layouter.namespace(|| "hash"), input_cells) } - } diff --git a/src/chips/poseidon/spec.rs b/src/chips/poseidon/spec.rs index 489abd33..87afe63b 100644 --- a/src/chips/poseidon/spec.rs +++ b/src/chips/poseidon/spec.rs @@ -1,5 +1,5 @@ use halo2_gadgets::poseidon::primitives::*; -use halo2_proofs::{arithmetic::FieldExt}; +use halo2_proofs::arithmetic::FieldExt; use std::marker::PhantomData; // 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 @@ -9,11 +9,13 @@ use std::marker::PhantomData; // Because of that we need to define a new Spec // MySpec struct allows us to define the parameters of the Poseidon hash function WIDTH and RATE #[derive(Debug, Clone, Copy)] -pub struct MySpec{ - _marker: PhantomData +pub struct MySpec { + _marker: PhantomData, } -impl Spec for MySpec { +impl Spec + for MySpec +{ fn full_rounds() -> usize { 8 } diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index 9e217a01..d5e8b6c2 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -1,10 +1,10 @@ use crate::chips::merkle_sum_tree::{MerkleSumTreeChip, MerkleSumTreeConfig}; +use eth_types::Field; use halo2_proofs::{circuit::*, plonk::*}; use std::marker::PhantomData; -use eth_types::Field; #[derive(Default)] -pub struct MerkleSumTreeCircuit { +pub struct MerkleSumTreeCircuit { pub leaf_hash: F, pub leaf_balance: F, pub path_element_hashes: Vec, @@ -12,11 +12,10 @@ pub struct MerkleSumTreeCircuit { pub path_indices: Vec, pub assets_sum: F, pub root_hash: F, - pub _marker: PhantomData + pub _marker: PhantomData, } -impl Circuit for MerkleSumTreeCircuit { - +impl Circuit for MerkleSumTreeCircuit { type Config = MerkleSumTreeConfig; type FloorPlanner = SimpleFloorPlanner; @@ -25,7 +24,6 @@ impl Circuit for MerkleSumTreeCircuit { } fn configure(meta: &mut ConstraintSystem) -> Self::Config { - // config columns for the merkle tree chip let col_a = meta.advice_column(); let col_b = meta.advice_column(); @@ -35,11 +33,7 @@ impl Circuit for MerkleSumTreeCircuit { let instance = meta.instance_column(); - MerkleSumTreeChip::configure( - meta, - [col_a, col_b, col_c, col_d, col_e], - instance, - ) + MerkleSumTreeChip::configure(meta, [col_a, col_b, col_c, col_d, col_e], instance) } fn synthesize( @@ -47,12 +41,19 @@ impl Circuit for MerkleSumTreeCircuit { config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { - let chip = MerkleSumTreeChip::construct(config); - let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance(layouter.namespace(|| "assign leaf"), self.leaf_hash, self.leaf_balance)?; + let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance( + layouter.namespace(|| "assign leaf"), + self.leaf_hash, + self.leaf_balance, + )?; chip.expose_public(layouter.namespace(|| "public leaf hash"), &leaf_hash, 0)?; - chip.expose_public(layouter.namespace(|| "public leaf balance"), &leaf_balance, 1)?; + chip.expose_public( + layouter.namespace(|| "public leaf balance"), + &leaf_balance, + 1, + )?; // apply it for level 0 of the merkle tree // node cells passed as inputs are the leaf_hash cell and the leaf_balance cell @@ -78,7 +79,7 @@ impl Circuit for MerkleSumTreeCircuit { )?; } - // enforce computed sum to be less than the assets sum + // enforce computed sum to be less than the assets sum chip.enforce_less_than(layouter.namespace(|| "enforce less than"), &next_sum)?; chip.expose_public(layouter.namespace(|| "public root"), &next_hash, 2)?; diff --git a/src/circuits/mod.rs b/src/circuits/mod.rs index 9f47557d..03755fe0 100644 --- a/src/circuits/mod.rs +++ b/src/circuits/mod.rs @@ -1,3 +1,3 @@ pub mod merkle_sum_tree; +pub mod tests; pub mod utils; -pub mod tests; \ No newline at end of file diff --git a/src/circuits/tests.rs b/src/circuits/tests.rs index f35fe011..2279a222 100644 --- a/src/circuits/tests.rs +++ b/src/circuits/tests.rs @@ -1,26 +1,21 @@ #[cfg(test)] mod test { - use crate::merkle_sum_tree::{MerkleSumTree, MerkleProof, big_int_to_fp}; use crate::circuits::merkle_sum_tree::MerkleSumTreeCircuit; use crate::circuits::utils::{full_prover, full_verifier}; + use crate::merkle_sum_tree::{big_int_to_fp, MerkleProof, MerkleSumTree}; use halo2_proofs::{ - dev::{MockProver, FailureLocation, VerifyFailure}, - halo2curves::bn256::{Fr as Fp, Bn256}, - plonk::{Any, keygen_pk, keygen_vk}, - poly::{ - kzg::{ - commitment::{ParamsKZG}, - }, - }, + dev::{FailureLocation, MockProver, VerifyFailure}, + halo2curves::bn256::{Bn256, Fr as Fp}, + plonk::{keygen_pk, keygen_vk, Any}, + poly::kzg::commitment::ParamsKZG, }; - use std::marker::PhantomData; + use num_bigint::ToBigInt; use rand::rngs::OsRng; - use num_bigint::{ToBigInt}; - - fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit{ + use std::marker::PhantomData; - let merkle_sum_tree= MerkleSumTree::new(path).unwrap(); + fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit { + let merkle_sum_tree = MerkleSumTree::new(path).unwrap(); let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); @@ -34,17 +29,16 @@ mod test { root_hash: proof.root_hash, _marker: PhantomData, } - } - fn instantiate_empty_circuit() -> MerkleSumTreeCircuit{ + fn instantiate_empty_circuit() -> MerkleSumTreeCircuit { MerkleSumTreeCircuit { leaf_hash: Fp::zero(), leaf_balance: Fp::zero(), path_element_hashes: vec![Fp::zero(); 4], path_element_balances: vec![Fp::zero(); 4], path_indices: vec![Fp::zero(); 4], - assets_sum : Fp::zero(), + assets_sum: Fp::zero(), root_hash: Fp::zero(), _marker: PhantomData, } @@ -52,22 +46,24 @@ mod test { #[test] fn test_valid_merkle_sum_tree() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); valid_prover.assert_satisfied(); - } #[test] fn test_valid_merkle_sum_tree_2() { - // Same as above but now the entries contain a balance that is greater than 64 bits // liabilities sum is 18446744073710096590 @@ -77,17 +73,20 @@ mod test { let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); valid_prover.assert_satisfied(); - } #[test] fn test_valid_merkle_sum_tree_with_full_prover() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_empty_circuit(); @@ -105,7 +104,12 @@ mod test { // Only now we can instantiate the circuit with the actual inputs let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; // Generate the proof let proof = full_prover(¶ms, &pk, circuit, &public_input); @@ -117,34 +121,41 @@ mod test { // Passing an invalid root hash in the instance column should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_root_hash() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); let invalid_root_hash = Fp::from(1000u64); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + invalid_root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 2 } + }, + VerifyFailure::Permutation { + column: (Any::advice(), 5).into(), + location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 } } ]) ); - } #[test] fn test_invalid_root_hash_with_full_prover() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_empty_circuit(); @@ -162,20 +173,23 @@ mod test { let invalid_root_hash = Fp::from(1000u64); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, invalid_root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + invalid_root_hash, + circuit.assets_sum, + ]; // Generate the proof let proof = full_prover(¶ms, &pk, circuit, &public_input); // verify the proof to be false assert!(!full_verifier(¶ms, &vk, proof, &public_input)); - } // Passing an invalid leaf hash as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_leaf_hash_as_witness() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); @@ -183,16 +197,26 @@ mod test { // invalidate leaf hash circuit.leaf_hash = Fp::from(1000u64); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 2 } + }, + VerifyFailure::Permutation { + column: (Any::advice(), 5).into(), + location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 } } ]) @@ -202,7 +226,6 @@ mod test { // Passing an invalid leaf hash in the instance column should fail the permutation check between the (valid) leaf hash added as part of the witness and the instance column leaf hash #[test] fn test_invalid_leaf_hash_as_instance() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); @@ -210,19 +233,29 @@ mod test { // add invalid leaf hash in the instance column let invalid_leaf_hash = Fp::from(1000u64); - let public_input = vec![invalid_leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + invalid_leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::advice(), 0).into(), location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 + VerifyFailure::Permutation { + column: (Any::advice(), 0).into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 } }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 0 } }, + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 0 } + }, ]) ); } @@ -230,7 +263,6 @@ mod test { // Passing an invalid leaf balance as input for the witness generation should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_invalid_leaf_balance_as_witness() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let user_balance = Fp::from(11888u64); @@ -240,27 +272,35 @@ mod test { // invalid leaf balance circuit.leaf_hash = Fp::from(1000u64); - let public_input = vec![circuit.leaf_hash, user_balance, circuit.root_hash, assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + user_balance, + circuit.root_hash, + assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 2 } + }, + VerifyFailure::Permutation { + column: (Any::advice(), 5).into(), + location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 } } ]) ); } - // Passing an invalid leaf balance in the instance column should fail the permutation check between the (valid) leaf balance added as part of the witness and the instance column leaf balance #[test] fn test_invalid_leaf_balance_as_instance() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); @@ -268,19 +308,29 @@ mod test { // add invalid leaf balance in the instance column let invalid_leaf_balance = Fp::from(1000u64); - let public_input = vec![circuit.leaf_hash, invalid_leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + invalid_leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::advice(), 1).into(), location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 + VerifyFailure::Permutation { + column: (Any::advice(), 1).into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 } }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 1 } }, + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 1 } + }, ]) ); } @@ -288,7 +338,6 @@ mod test { // Passing a non binary index should fail the bool constraint check, the two swap constraints and the permutation check between the computed root hash and the instance column root hash #[test] fn test_non_binary_index() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); @@ -296,57 +345,81 @@ mod test { // invalidate path index inside the circuit circuit.path_indices[0] = Fp::from(2); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::ConstraintNotSatisfied { - constraint: ((0, "bool constraint").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 + VerifyFailure::ConstraintNotSatisfied { + constraint: ((0, "bool constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![(((Any::advice(), 4).into(), 0).into(), "0x2".to_string()),] }, - cell_values: vec![ - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 0, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + ( + ((Any::advice(), 0).into(), 0).into(), + "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0" + .to_string() + ), + ( + ((Any::advice(), 0).into(), 1).into(), + "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a" + .to_string() + ), + ( + ((Any::advice(), 2).into(), 0).into(), + "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a" + .to_string() + ), + ( + ((Any::advice(), 2).into(), 1).into(), + "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0" + .to_string() + ), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), ] - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 }, - cell_values: vec![ - (((Any::advice(), 0).into(), 0).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), - (((Any::advice(), 0).into(), 1).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), - (((Any::advice(), 2).into(), 0).into(), "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a".to_string()), - (((Any::advice(), 2).into(), 1).into(), "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0".to_string()), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), + VerifyFailure::ConstraintNotSatisfied { + constraint: ((1, "swap constraint").into(), 1, "").into(), + location: FailureLocation::InRegion { + region: (1, "merkle prove layer").into(), + offset: 0 + }, + cell_values: vec![ + (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), + (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), + (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), + (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), ] - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 1, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 }, - cell_values: vec![ - (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), - (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 2 } + }, + VerifyFailure::Permutation { + column: (Any::advice(), 5).into(), + location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 + } } - } ]) ); } @@ -354,7 +427,6 @@ mod test { // Swapping the indices should fail the permutation check between the computed root hash and the instance column root hash #[test] fn test_swapping_index() { - let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) let mut circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); @@ -362,17 +434,27 @@ mod test { // swap indices circuit.path_indices[0] = Fp::from(1); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ - VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 } }, - VerifyFailure::Permutation { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { - region: (16, "permute state").into(), - offset: 36 + VerifyFailure::Permutation { + column: (Any::Instance, 0).into(), + location: FailureLocation::OutsideRegion { row: 2 } + }, + VerifyFailure::Permutation { + column: (Any::advice(), 5).into(), + location: FailureLocation::InRegion { + region: (16, "permute state").into(), + offset: 36 } } ]) @@ -382,20 +464,33 @@ mod test { // Passing an assets sum that is less than the liabilities sum should fail the lessThan constraint check #[test] fn test_is_not_less_than() { - let less_than_assets_sum = Fp::from(556861u64); // less than liabilities sum (556862) - let circuit = instantiate_circuit(less_than_assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); + let circuit = + instantiate_circuit(less_than_assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - let public_input = vec![circuit.leaf_hash, circuit.leaf_balance, circuit.root_hash, circuit.assets_sum]; + let public_input = vec![ + circuit.leaf_hash, + circuit.leaf_balance, + circuit.root_hash, + circuit.assets_sum, + ]; let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), - Err(vec![ - VerifyFailure::ConstraintNotSatisfied { - constraint: ((7, "verifies that `check` from current config equal to is_lt from LtChip").into(), 0, "").into(), + Err(vec![VerifyFailure::ConstraintNotSatisfied { + constraint: ( + ( + 7, + "verifies that `check` from current config equal to is_lt from LtChip" + ) + .into(), + 0, + "" + ) + .into(), location: FailureLocation::InRegion { region: (17, "enforce sum to be less than total assets").into(), offset: 0 @@ -404,9 +499,8 @@ mod test { (((Any::advice(), 2).into(), 0).into(), "1".to_string()), // The zero means that is not less than (((Any::advice(), 11).into(), 0).into(), "0".to_string()) - ] - } - ]) + ] + }]) ); assert!(invalid_prover.verify().is_err()); @@ -421,8 +515,8 @@ mod test { let circuit = instantiate_circuit(assets_sum, "src/merkle_sum_tree/csv/entry_16.csv"); - let root = - BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)).into_drawing_area(); + let root = BitMapBackend::new("prints/merkle-sum-tree-layout.png", (2048, 16384)) + .into_drawing_area(); root.fill(&WHITE).unwrap(); let root = root .titled("Merkle Sum Tree Layout", ("sans-serif", 60)) @@ -432,4 +526,4 @@ mod test { .render(8, &circuit, &root) .unwrap(); } -} \ No newline at end of file +} diff --git a/src/circuits/utils.rs b/src/circuits/utils.rs index 56596878..0b58f490 100644 --- a/src/circuits/utils.rs +++ b/src/circuits/utils.rs @@ -1,31 +1,27 @@ +use ark_std::{end_timer, start_timer}; use halo2_proofs::{ - halo2curves::bn256::{Fr as Fp, Bn256, G1Affine}, + halo2curves::bn256::{Bn256, Fr as Fp, G1Affine}, + plonk::{create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey}, poly::{ commitment::ParamsProver, kzg::{ - commitment::{ - ParamsKZG, - KZGCommitmentScheme, - }, - strategy::SingleStrategy, - multiopen::{ProverSHPLONK, VerifierSHPLONK} + commitment::{KZGCommitmentScheme, ParamsKZG}, + multiopen::{ProverSHPLONK, VerifierSHPLONK}, + strategy::SingleStrategy, }, }, - plonk::{ - create_proof, verify_proof, Circuit, ProvingKey, VerifyingKey + transcript::{ + Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer, }, - transcript::{Blake2bRead, Blake2bWrite, Challenge255, TranscriptReadBuffer, TranscriptWriterBuffer}, }; use rand::rngs::OsRng; -use ark_std::{end_timer, start_timer}; -pub fn full_prover > ( +pub fn full_prover>( params: &ParamsKZG, pk: &ProvingKey, circuit: C, public_input: &[Fp], ) -> Vec { - let pf_time = start_timer!(|| "Creating proof"); let mut transcript = Blake2bWrite::<_, _, Challenge255<_>>::init(vec![]); @@ -36,7 +32,14 @@ pub fn full_prover > ( _, Blake2bWrite, G1Affine, Challenge255>, _, - >(params, pk, &[circuit], &[&[public_input]], OsRng, &mut transcript) + >( + params, + pk, + &[circuit], + &[&[public_input]], + OsRng, + &mut transcript, + ) .expect("prover should not fail"); let proof = transcript.finalize(); end_timer!(pf_time); @@ -59,6 +62,12 @@ pub fn full_verifier( Challenge255, Blake2bRead<&[u8], G1Affine, Challenge255>, SingleStrategy<'_, Bn256>, - >(verifier_params, vk, strategy, &[&[public_input]], &mut transcript) - .is_ok() -} \ No newline at end of file + >( + verifier_params, + vk, + strategy, + &[&[public_input]], + &mut transcript, + ) + .is_ok() +} diff --git a/src/lib.rs b/src/lib.rs index 0ffbd878..3607ba43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,3 @@ pub mod chips; pub mod circuits; -pub mod merkle_sum_tree; \ No newline at end of file +pub mod merkle_sum_tree; diff --git a/src/merkle_sum_tree/entry.rs b/src/merkle_sum_tree/entry.rs index 22227ed3..d0832a1d 100644 --- a/src/merkle_sum_tree/entry.rs +++ b/src/merkle_sum_tree/entry.rs @@ -1,7 +1,7 @@ -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; -use crate::merkle_sum_tree::utils::{big_intify_username, poseidon, big_int_to_fp}; +use crate::merkle_sum_tree::utils::{big_int_to_fp, big_intify_username, poseidon}; use crate::merkle_sum_tree::Node; -use num_bigint::{BigInt}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use num_bigint::BigInt; #[derive(Default, Clone, Debug)] pub struct Entry { @@ -11,9 +11,7 @@ pub struct Entry { } impl Entry { - pub fn new(username: String, balance: BigInt) -> Result { - Ok(Entry { username_to_big_int: big_intify_username(&username), balance, @@ -28,7 +26,6 @@ impl Entry { big_int_to_fp(&self.balance), Fp::from(0), Fp::from(0), - ), balance: big_int_to_fp(&self.balance), } diff --git a/src/merkle_sum_tree/mod.rs b/src/merkle_sum_tree/mod.rs index 96083ee4..726b193d 100644 --- a/src/merkle_sum_tree/mod.rs +++ b/src/merkle_sum_tree/mod.rs @@ -1,8 +1,8 @@ mod entry; mod mst; -mod utils; mod tests; -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; +mod utils; +use halo2_proofs::halo2curves::bn256::Fr as Fp; #[derive(Default, Clone, Debug)] pub struct MerkleProof { @@ -21,6 +21,4 @@ pub struct Node { pub use entry::Entry; pub use mst::MerkleSumTree; -pub use utils::{big_intify_username, big_int_to_fp}; - - +pub use utils::{big_int_to_fp, big_intify_username}; diff --git a/src/merkle_sum_tree/mst.rs b/src/merkle_sum_tree/mst.rs index 351b0fbd..a4589daf 100644 --- a/src/merkle_sum_tree/mst.rs +++ b/src/merkle_sum_tree/mst.rs @@ -1,8 +1,8 @@ -use num_bigint::BigInt; use crate::merkle_sum_tree::utils::{ build_merkle_tree_from_entries, create_proof, index_of, parse_csv_to_entries, verify_proof, }; use crate::merkle_sum_tree::{Entry, MerkleProof, Node}; +use num_bigint::BigInt; pub struct MerkleSumTree { root: Node, @@ -60,5 +60,4 @@ impl MerkleSumTree { pub fn verify_proof(&self, proof: &MerkleProof) -> bool { verify_proof(proof) } - } diff --git a/src/merkle_sum_tree/tests.rs b/src/merkle_sum_tree/tests.rs index a5fa769c..5630af8f 100644 --- a/src/merkle_sum_tree/tests.rs +++ b/src/merkle_sum_tree/tests.rs @@ -1,9 +1,9 @@ #[cfg(test)] mod test { - use num_bigint::{ToBigInt, BigInt}; - use crate::merkle_sum_tree::{Entry, MerkleSumTree}; use crate::merkle_sum_tree::utils::big_int_to_fp; + use crate::merkle_sum_tree::{Entry, MerkleSumTree}; + use num_bigint::{BigInt, ToBigInt}; #[test] fn test_mst() { @@ -27,17 +27,24 @@ mod test { assert!(merkle_tree.verify_proof(&proof)); // Should generate different root hashes when changing the entry order - let merkle_tree_2 = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); + let merkle_tree_2 = + MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_switched_order.csv").unwrap(); assert_ne!(root.hash, merkle_tree_2.root().hash); // the balance total should be the same assert_eq!(root.balance, merkle_tree_2.root().balance); // should retrun the index of an entry that exist in the tree - assert_eq!(merkle_tree.index_of("AtwIxZHo", 35479.to_bigint().unwrap()), Some(15)); + assert_eq!( + merkle_tree.index_of("AtwIxZHo", 35479.to_bigint().unwrap()), + Some(15) + ); // shouldn't retrun the index of an entry that doesn't exist in the tree - assert_eq!(merkle_tree.index_of("AtwHHHHo", 35478.to_bigint().unwrap()), None); + assert_eq!( + merkle_tree.index_of("AtwHHHHo", 35478.to_bigint().unwrap()), + None + ); // should create valid proof for each entry in the tree and verify it for i in 0..15 { @@ -50,7 +57,8 @@ mod test { // shouldn't verify a proof with a wrong entry let mut proof_invalid_1 = proof.clone(); - proof_invalid_1.entry = Entry::new("AtwIxZHo".to_string(), 35479.to_bigint().unwrap()).unwrap(); + proof_invalid_1.entry = + Entry::new("AtwIxZHo".to_string(), 35479.to_bigint().unwrap()).unwrap(); assert!(!merkle_tree.verify_proof(&proof_invalid_1)); // shouldn't verify a proof with a wrong root hash @@ -62,60 +70,55 @@ mod test { let mut proof_invalid_3 = proof; proof_invalid_3.sibling_sums[0] = 0.into(); } - + #[test] fn test_mst_overflow() { - let result = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_overflow.csv"); // assert!(result.is_err(), "Expected an error due to balance overflow"); if let Err(e) = result { - assert_eq!( - e.to_string(), - "Balance is larger than the modulus" - ); - - - // Passing entries whose balance sum overflows the field should throw an error at the constructor - // MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_overflow.csv").unwrap() should throw Balance is larger than the modulus + assert_eq!(e.to_string(), "Balance is larger than the modulus"); + + // Passing entries whose balance sum overflows the field should throw an error at the constructor + // MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_overflow.csv").unwrap() should throw Balance is larger than the modulus } } #[test] - fn test_mst_with_bigint () { - // create new merkle tree with entries that have balances greater than 2^64 - let merkle_tree = MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_bigints.csv").unwrap(); - - // get root - let root = merkle_tree.root(); - - // expect root hash to be different than 0 - assert!(root.hash != 0.into()); - - // expect balance to match the sum of all entries - let exp_balance = { - let balance = 18446744073710096590_u128.to_bigint().unwrap(); - let (_, mut bytes) = BigInt::to_bytes_le(&balance); - bytes.resize(32, 0); - bytes - }; - - let root_balance = root.balance.to_bytes(); - - assert_eq!(root_balance.to_vec(), exp_balance); - - // expect depth to be 4 - assert!(*merkle_tree.depth() == 4_usize); - - // get proof for entry 0 - let proof = merkle_tree.generate_proof(0).unwrap(); - - // verify proof - assert!(merkle_tree.verify_proof(&proof)); + fn test_mst_with_bigint() { + // create new merkle tree with entries that have balances greater than 2^64 + let merkle_tree = + MerkleSumTree::new("src/merkle_sum_tree/csv/entry_16_bigints.csv").unwrap(); + + // get root + let root = merkle_tree.root(); + + // expect root hash to be different than 0 + assert!(root.hash != 0.into()); + + // expect balance to match the sum of all entries + let exp_balance = { + let balance = 18446744073710096590_u128.to_bigint().unwrap(); + let (_, mut bytes) = BigInt::to_bytes_le(&balance); + bytes.resize(32, 0); + bytes + }; + + let root_balance = root.balance.to_bytes(); + + assert_eq!(root_balance.to_vec(), exp_balance); + + // expect depth to be 4 + assert!(*merkle_tree.depth() == 4_usize); + + // get proof for entry 0 + let proof = merkle_tree.generate_proof(0).unwrap(); + + // verify proof + assert!(merkle_tree.verify_proof(&proof)); } #[test] fn test_big_int_conversion() { - let big_int = 3.to_bigint().unwrap(); let fp = big_int_to_fp(&big_int); @@ -135,4 +138,4 @@ mod test { let fp_3 = fp_2 - fp; assert_eq!(fp_3, 18446744073709551613.into()); } -} \ No newline at end of file +} diff --git a/src/merkle_sum_tree/utils/build_tree.rs b/src/merkle_sum_tree/utils/build_tree.rs index cd1983c4..a9ab3c04 100644 --- a/src/merkle_sum_tree/utils/build_tree.rs +++ b/src/merkle_sum_tree/utils/build_tree.rs @@ -1,6 +1,6 @@ use crate::merkle_sum_tree::utils::create_middle_node::create_middle_node; use crate::merkle_sum_tree::{Entry, Node}; -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; pub fn build_merkle_tree_from_entries( entries: &[Entry], diff --git a/src/merkle_sum_tree/utils/create_proof.rs b/src/merkle_sum_tree/utils/create_proof.rs index 07537c0b..01e0ff6e 100644 --- a/src/merkle_sum_tree/utils/create_proof.rs +++ b/src/merkle_sum_tree/utils/create_proof.rs @@ -1,5 +1,5 @@ use crate::merkle_sum_tree::{Entry, MerkleProof, Node}; -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; pub fn create_proof( index: usize, diff --git a/src/merkle_sum_tree/utils/csv_parser.rs b/src/merkle_sum_tree/utils/csv_parser.rs index cc5e221d..9cd99ac1 100644 --- a/src/merkle_sum_tree/utils/csv_parser.rs +++ b/src/merkle_sum_tree/utils/csv_parser.rs @@ -30,8 +30,7 @@ pub fn parse_csv_to_entries>(path: P) -> Result, Box= BigInt::parse_bytes(MODULUS_STR.as_bytes(), 16).unwrap() - { + if balance_acc >= BigInt::parse_bytes(MODULUS_STR.as_bytes(), 16).unwrap() { return Err("Balance is larger than the modulus".into()); } diff --git a/src/merkle_sum_tree/utils/hash.rs b/src/merkle_sum_tree/utils/hash.rs index 7d745a61..8649071a 100644 --- a/src/merkle_sum_tree/utils/hash.rs +++ b/src/merkle_sum_tree/utils/hash.rs @@ -1,6 +1,6 @@ use crate::chips::poseidon::spec::MySpec; use halo2_gadgets::poseidon::primitives::{self as poseidon, ConstantLength}; -use halo2_proofs::halo2curves::bn256::{Fr as Fp}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; pub fn poseidon(l1: Fp, l2: Fp, r1: Fp, r2: Fp) -> Fp { const WIDTH: usize = 5; diff --git a/src/merkle_sum_tree/utils/index_of.rs b/src/merkle_sum_tree/utils/index_of.rs index 110c8a5a..971ff9b9 100644 --- a/src/merkle_sum_tree/utils/index_of.rs +++ b/src/merkle_sum_tree/utils/index_of.rs @@ -1,5 +1,5 @@ -use num_bigint::BigInt; use crate::merkle_sum_tree::{Entry, Node}; +use num_bigint::BigInt; pub fn index_of(username: &str, balance: BigInt, nodes: &[Vec]) -> Option { let entry = Entry::new(username.to_string(), balance).unwrap(); diff --git a/src/merkle_sum_tree/utils/mod.rs b/src/merkle_sum_tree/utils/mod.rs index 253f96e7..f76c7610 100644 --- a/src/merkle_sum_tree/utils/mod.rs +++ b/src/merkle_sum_tree/utils/mod.rs @@ -4,15 +4,13 @@ mod create_proof; mod csv_parser; mod hash; mod index_of; -mod proof_verification; mod operation_helpers; +mod proof_verification; pub use build_tree::build_merkle_tree_from_entries; pub use create_proof::create_proof; pub use csv_parser::parse_csv_to_entries; pub use hash::poseidon; pub use index_of::index_of; -pub use proof_verification::verify_proof; pub use operation_helpers::*; - - +pub use proof_verification::verify_proof; diff --git a/src/merkle_sum_tree/utils/operation_helpers.rs b/src/merkle_sum_tree/utils/operation_helpers.rs index 6b4960c3..d3c1e712 100644 --- a/src/merkle_sum_tree/utils/operation_helpers.rs +++ b/src/merkle_sum_tree/utils/operation_helpers.rs @@ -1,5 +1,5 @@ +use halo2_proofs::halo2curves::{bn256::Fr as Fp, group::ff::PrimeField}; use num_bigint::{BigInt, Sign}; -use halo2_proofs::halo2curves::{bn256::{Fr as Fp}, group::ff::PrimeField}; // Return a BigUint representation of the username pub fn big_intify_username(username: &str) -> BigInt { @@ -9,4 +9,4 @@ pub fn big_intify_username(username: &str) -> BigInt { pub fn big_int_to_fp(big_int: &BigInt) -> Fp { Fp::from_str_vartime(&big_int.to_str_radix(10)[..]).unwrap() -} \ No newline at end of file +} diff --git a/src/merkle_sum_tree/utils/proof_verification.rs b/src/merkle_sum_tree/utils/proof_verification.rs index 76d8ca55..3979851c 100644 --- a/src/merkle_sum_tree/utils/proof_verification.rs +++ b/src/merkle_sum_tree/utils/proof_verification.rs @@ -1,7 +1,4 @@ -use crate::merkle_sum_tree::utils::{ - create_middle_node::create_middle_node, - big_int_to_fp, -}; +use crate::merkle_sum_tree::utils::{big_int_to_fp, create_middle_node::create_middle_node}; use crate::merkle_sum_tree::{MerkleProof, Node}; pub fn verify_proof(proof: &MerkleProof) -> bool { From dfc5d7e368c2aacff371845712230cd0c646d065 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 9 May 2023 09:01:53 +0200 Subject: [PATCH 21/25] added `constants` method for poseidon hasher The constants method for the poseidon hasher is necessary for a security standpoint. Adding this method forced the removal of any reference to Generic Field type, that has been replaced with the actual field type that we are using namely bn256 --- Cargo.lock | 1 - Cargo.toml | 1 - src/chips/merkle_sum_tree.rs | 68 +- src/chips/poseidon/hash.rs | 33 +- src/chips/poseidon/mod.rs | 1 + src/chips/poseidon/rate4_params.rs | 2532 ++++++++++++++++++++++++++++ src/chips/poseidon/spec.rs | 40 +- src/circuits/merkle_sum_tree.rs | 28 +- src/circuits/tests.rs | 25 +- src/merkle_sum_tree/utils/hash.rs | 3 +- 10 files changed, 2627 insertions(+), 105 deletions(-) create mode 100644 src/chips/poseidon/rate4_params.rs diff --git a/Cargo.lock b/Cargo.lock index 7c3baaf7..64491735 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,7 +448,6 @@ version = "0.1.0" dependencies = [ "ark-std", "csv", - "eth-types", "gadgets", "halo2_gadgets", "halo2_proofs", diff --git a/Cargo.toml b/Cargo.toml index 1e9e1627..5c29652b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,6 @@ dev-graph = ["halo2_proofs/dev-graph", "plotters"] [dependencies] halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} -eth-types = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} plotters = { version = "0.3.4", optional = true } rand = "0.8" diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index d6e5de94..625b5320 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -1,7 +1,7 @@ use crate::chips::poseidon::hash::{PoseidonChip, PoseidonConfig}; use crate::chips::poseidon::spec::MySpec; -use eth_types::Field; use gadgets::less_than::{LtChip, LtConfig, LtInstruction}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; use halo2_proofs::{circuit::*, plonk::*, poly::Rotation}; const WIDTH: usize = 5; @@ -9,31 +9,31 @@ const RATE: usize = 4; const L: usize = 4; #[derive(Debug, Clone)] -pub struct MerkleSumTreeConfig { +pub struct MerkleSumTreeConfig { pub advice: [Column; 5], pub bool_selector: Selector, pub swap_selector: Selector, pub sum_selector: Selector, pub lt_selector: Selector, pub instance: Column, - pub poseidon_config: PoseidonConfig, - pub lt_config: LtConfig, + pub poseidon_config: PoseidonConfig, + pub lt_config: LtConfig, } #[derive(Debug, Clone)] -pub struct MerkleSumTreeChip { - config: MerkleSumTreeConfig, +pub struct MerkleSumTreeChip { + config: MerkleSumTreeConfig, } -impl MerkleSumTreeChip { - pub fn construct(config: MerkleSumTreeConfig) -> Self { +impl MerkleSumTreeChip { + pub fn construct(config: MerkleSumTreeConfig) -> Self { Self { config } } pub fn configure( - meta: &mut ConstraintSystem, + meta: &mut ConstraintSystem, advice: [Column; 5], instance: Column, - ) -> MerkleSumTreeConfig { + ) -> MerkleSumTreeConfig { let col_a = advice[0]; let col_b = advice[1]; let col_c = advice[2]; @@ -62,7 +62,7 @@ impl MerkleSumTreeChip { meta.create_gate("bool constraint", |meta| { let s = meta.query_selector(bool_selector); let e = meta.query_advice(col_e, Rotation::cur()); - vec![s * e.clone() * (Expression::Constant(F::from(1)) - e)] + vec![s * e.clone() * (Expression::Constant(Fp::from(1)) - e)] }); // 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. @@ -81,10 +81,10 @@ impl MerkleSumTreeChip { vec![ s.clone() - * (e.clone() * Expression::Constant(F::from(2)) * (c.clone() - a.clone()) + * (e.clone() * Expression::Constant(Fp::from(2)) * (c.clone() - a.clone()) - (l1 - a) - (c - r1)), - s * (e * Expression::Constant(F::from(2)) * (d.clone() - b.clone()) + s * (e * Expression::Constant(Fp::from(2)) * (d.clone() - b.clone()) - (l2 - b) - (d - r2)), ] @@ -101,8 +101,7 @@ impl MerkleSumTreeChip { let hash_inputs = (0..WIDTH).map(|_| meta.advice_column()).collect::>(); - let poseidon_config = - PoseidonChip::, WIDTH, RATE, L>::configure(meta, hash_inputs); + let poseidon_config = PoseidonChip::::configure(meta, hash_inputs); // configure lt chip let lt_config = LtChip::configure( @@ -139,10 +138,10 @@ impl MerkleSumTreeChip { pub fn assing_leaf_hash_and_balance( &self, - mut layouter: impl Layouter, - leaf_hash: F, - leaf_balance: F, - ) -> Result<(AssignedCell, AssignedCell), Error> { + mut layouter: impl Layouter, + leaf_hash: Fp, + leaf_balance: Fp, + ) -> Result<(AssignedCell, AssignedCell), Error> { let (leaf_hash_cell, leaf_balance_cell) = layouter.assign_region( || "assign leaf hash", |mut region| { @@ -169,13 +168,13 @@ impl MerkleSumTreeChip { pub fn merkle_prove_layer( &self, - mut layouter: impl Layouter, - prev_hash: &AssignedCell, - prev_balance: &AssignedCell, - element_hash: F, - element_balance: F, - index: F, - ) -> Result<(AssignedCell, AssignedCell), Error> { + mut layouter: impl Layouter, + prev_hash: &AssignedCell, + prev_balance: &AssignedCell, + element_hash: Fp, + element_balance: Fp, + index: Fp, + ) -> Result<(AssignedCell, AssignedCell), Error> { let (left_hash, left_balance, right_hash, right_balance, computed_sum_cell) = layouter .assign_region( || "merkle prove layer", @@ -224,7 +223,7 @@ impl MerkleSumTreeChip { // if index is 0 return (l1, l2, r1, r2) else return (r1, r2, l1, l2) index.value().map(|x| x.to_owned()).map(|x| { - (l1_val, l2_val, r1_val, r2_val) = if x == F::zero() { + (l1_val, l2_val, r1_val, r2_val) = if x == Fp::zero() { (l1_val, l2_val, r1_val, r2_val) } else { (r1_val, r2_val, l1_val, l2_val) @@ -284,9 +283,8 @@ impl MerkleSumTreeChip { )?; // instantiate the poseidon_chip - let poseidon_chip = PoseidonChip::, WIDTH, RATE, L>::construct( - self.config.poseidon_config.clone(), - ); + let poseidon_chip = + PoseidonChip::::construct(self.config.poseidon_config.clone()); // The hash function inside the poseidon_chip performs the following action // 1. Copy the left and right cells from the previous row @@ -303,8 +301,8 @@ impl MerkleSumTreeChip { // Enforce computed sum to be less than total assets passed inside the instance column pub fn enforce_less_than( &self, - mut layouter: impl Layouter, - prev_computed_sum_cell: &AssignedCell, + mut layouter: impl Layouter, + prev_computed_sum_cell: &AssignedCell, ) -> Result<(), Error> { // Initiate chip config let chip = LtChip::construct(self.config.lt_config); @@ -334,7 +332,7 @@ impl MerkleSumTreeChip { || "check", self.config.advice[2], 0, - || Value::known(F::from(1)), + || Value::known(Fp::from(1)), )?; // enable lt seletor @@ -364,8 +362,8 @@ impl MerkleSumTreeChip { // Enforce copy constraint check between input cell and instance column at row passed as input pub fn expose_public( &self, - mut layouter: impl Layouter, - cell: &AssignedCell, + mut layouter: impl Layouter, + cell: &AssignedCell, row: usize, ) -> Result<(), Error> { layouter.constrain_instance(cell.cell(), self.config.instance, row) diff --git a/src/chips/poseidon/hash.rs b/src/chips/poseidon/hash.rs index 658f6b68..b854e0ec 100644 --- a/src/chips/poseidon/hash.rs +++ b/src/chips/poseidon/hash.rs @@ -7,7 +7,8 @@ is already implemented in halo2_gadgets, there is no wrapper chip that makes it // compared to `hash_with_instance` this version doesn't use any instance column. use halo2_gadgets::poseidon::{primitives::*, Hash, Pow5Chip, Pow5Config}; -use halo2_proofs::{arithmetic::FieldExt, circuit::*, plonk::*}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; +use halo2_proofs::{circuit::*, plonk::*}; use std::marker::PhantomData; #[derive(Debug, Clone)] @@ -15,32 +16,26 @@ use std::marker::PhantomData; // 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. // This means they are values that are known at compile time and can be used to specialize the implementation of the struct. // The actual chip provided by halo2_gadgets is added to the parent Chip. -pub struct PoseidonConfig { - pow5_config: Pow5Config, +pub struct PoseidonConfig { + pow5_config: Pow5Config, } #[derive(Debug, Clone)] pub struct PoseidonChip< - F: FieldExt, - S: Spec, + S: Spec, const WIDTH: usize, const RATE: usize, const L: usize, > { - config: PoseidonConfig, + config: PoseidonConfig, _marker: PhantomData, } -impl< - F: FieldExt, - S: Spec, - const WIDTH: usize, - const RATE: usize, - const L: usize, - > PoseidonChip +impl, const WIDTH: usize, const RATE: usize, const L: usize> + PoseidonChip { - pub fn construct(config: PoseidonConfig) -> Self { + pub fn construct(config: PoseidonConfig) -> Self { Self { config, _marker: PhantomData, @@ -49,9 +44,9 @@ impl< // Configuration of the PoseidonChip pub fn configure( - meta: &mut ConstraintSystem, + meta: &mut ConstraintSystem, hash_inputs: Vec>, - ) -> PoseidonConfig { + ) -> PoseidonConfig { let partial_sbox = meta.advice_column(); let rc_a = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); let rc_b = (0..WIDTH).map(|_| meta.fixed_column()).collect::>(); @@ -77,9 +72,9 @@ impl< // It uses the pow5_chip to compute the hash pub fn hash( &self, - mut layouter: impl Layouter, - input_cells: [AssignedCell; L], - ) -> Result, Error> { + mut layouter: impl Layouter, + input_cells: [AssignedCell; L], + ) -> Result, Error> { let pow5_chip = Pow5Chip::construct(self.config.pow5_config.clone()); // initialize the hasher diff --git a/src/chips/poseidon/mod.rs b/src/chips/poseidon/mod.rs index 9b6cf956..e88713d6 100644 --- a/src/chips/poseidon/mod.rs +++ b/src/chips/poseidon/mod.rs @@ -1,2 +1,3 @@ pub mod hash; +pub mod rate4_params; pub mod spec; diff --git a/src/chips/poseidon/rate4_params.rs b/src/chips/poseidon/rate4_params.rs new file mode 100644 index 00000000..29f8c42f --- /dev/null +++ b/src/chips/poseidon/rate4_params.rs @@ -0,0 +1,2532 @@ +//! Parameters for using rate 4 Poseidon with the BN256 field. +//! Patterned after [halo2_gadgets::poseidon::primitives::fp] +//! The parameters can be reproduced by running the following Sage script from +//! [this repository](https://github.com/daira/pasta-hadeshash): +//! +//! ```text +//! $ sage generate_parameters_grain.sage 1 0 254 5 8 60 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 --rust +//! ``` +//! +//! where 1 means "prime field", 0 means "non-negative sbox", 254 is the bitsize +//! of the field, 5 is the Poseidon width (rate + 1), 8 is the number of full +//! rounds, 60 is the number of partial rounds. +//! More info here => https://hackmd.io/@letargicus/SJOvx48Nn +use halo2_proofs::halo2curves::bn256::Fr as Fp; + +// Number of round constants: 340 +// Round constants for GF(p): +pub(crate) const ROUND_CONSTANTS: [[Fp; 5]; 68] = [ + [ + Fp::from_raw([ + 0x4f3c_2bd8_1a6d_a891, + 0xd889_bb4e_bd47_c386, + 0x7f53_e29c_cac9_8ed7, + 0x0eb5_44fe_e281_5dda, + ]), + Fp::from_raw([ + 0xba73_3f28_4751_28cb, + 0xa197_aeb1_2ea6_4713, + 0xf02f_dba7_dd73_7fbc, + 0x0554_d736_315b_8662, + ]), + Fp::from_raw([ + 0xf508_7c58_d5e8_c2d4, + 0x5490_7df0_c0fb_0035, + 0xbcd7_4805_6307_c377, + 0x2f83_b9df_259b_2b68, + ]), + Fp::from_raw([ + 0x1915_208f_5aba_9683, + 0x61f1_5f8b_41a7_5ef3, + 0x2447_ac83_0524_51b4, + 0x2ca7_0e2e_8d7f_39a1, + ]), + Fp::from_raw([ + 0x4ec7_19cb_83ec_fea9, + 0x9941_96f1_2ed2_2c5d, + 0x91b0_4d72_2227_1c94, + 0x1cb5_f931_9be6_a45e, + ]), + ], + [ + Fp::from_raw([ + 0xb9ea_08d1_8446_c122, + 0x21c7_bb47_b937_50c2, + 0xf8a4_2192_de7f_f616, + 0x2eb4_f99c_69f9_66eb, + ]), + Fp::from_raw([ + 0xd5f7_d099_e299_520e, + 0x0fc7_da8b_93ee_13b6, + 0xc519_8169_e405_d9ea, + 0x224a_28e5_a353_85a7, + ]), + Fp::from_raw([ + 0x2379_6b99_3bbd_82f7, + 0x36f3_3ecb_d9a0_f978, + 0xd8af_dd6a_fca4_9c30, + 0x0f74_11b4_65e6_00ee, + ]), + Fp::from_raw([ + 0x0626_f9ff_5d90_e4e3, + 0x19b2_08ae_3370_f99a, + 0xa2be_7150_392d_8d98, + 0x0f9d_0d5a_ad2c_9555, + ]), + Fp::from_raw([ + 0x6a12_d307_02d6_fba0, + 0x9732_b252_59cf_744b, + 0x6f52_a595_38d3_2922, + 0x1e9a_96dc_8292_bb59, + ]), + ], + [ + Fp::from_raw([ + 0xc2cd_4d52_8fb3_fe3c, + 0x3cfe_52ea_b4b9_45c6, + 0x887d_578c_4555_5e59, + 0x0878_0514_ccd9_0380, + ]), + Fp::from_raw([ + 0x59f1_19d6_29cc_b5fc, + 0xced6_4717_e355_6d5a, + 0xc814_9fa3_f73e_f8c2, + 0x2724_98fc_ed68_6c7a, + ]), + Fp::from_raw([ + 0xe3d0_ef8a_782e_f7df, + 0x45bd_350a_ff58_5f10, + 0x4b7c_b809_30bd_06eb, + 0x01ef_8f9d_d7c9_3aac, + ]), + Fp::from_raw([ + 0x97ea_0ae7_5493_4d30, + 0x8e88_6e64_bf3c_40aa, + 0x4dc0_8f22_2b46_9b13, + 0x045b_9f59_b659_5e61, + ]), + Fp::from_raw([ + 0xadf2_5842_95d6_1c66, + 0xa3e4_1e24_e247_a387, + 0x9fd6_f59d_2a40_ff8e, + 0x0ac1_e91c_57d9_da91, + ]), + ], + [ + Fp::from_raw([ + 0xd12e_68f0_4e62_d134, + 0x0fd6_7061_aee9_9979, + 0xc7f9_a421_353c_d89d, + 0x028a_1621_a940_54b0, + ]), + Fp::from_raw([ + 0xd02a_ae2a_0dcd_9dbc, + 0x50c1_9c3f_b3c9_6d09, + 0x9632_647e_d059_236e, + 0x26b4_1802_c071_ea4c, + ]), + Fp::from_raw([ + 0x4af3_4384_aedb_462b, + 0x05c9_de06_758d_b6a9, + 0xbaac_2f63_e468_215e, + 0x2fb5_dda8_072b_b72c, + ]), + Fp::from_raw([ + 0xdd05_c56e_e894_d850, + 0xad8a_b8ba_2a18_d383, + 0x44ff_3547_fd82_3249, + 0x2212_d3a0_f5fc_caf2, + ]), + Fp::from_raw([ + 0x5cd0_c7ee_d465_e2e3, + 0xa327_6fdb_19f4_4c01, + 0x58e4_dfae_ea09_be56, + 0x1b04_1ad5_b2f0_6842, + ]), + ], + [ + Fp::from_raw([ + 0xa91e_64af_b150_0eff, + 0x144f_b7e3_ac14_e846, + 0x8ecc_ff33_e76f_ded3, + 0x0a01_776b_b22f_4b6b, + ]), + Fp::from_raw([ + 0xf793_6440_810a_ce43, + 0xa4f3_3ae8_c15c_f827, + 0xf34d_3f27_5066_d549, + 0x2b7b_5674_aaec_c3cb, + ]), + Fp::from_raw([ + 0x4c1b_8033_81a3_bdfd, + 0x60b0_4225_7b78_fc00, + 0x4cf7_5779_ed54_b48c, + 0x29d2_99b8_0cd4_489e, + ]), + Fp::from_raw([ + 0x19dd_e304_24be_401e, + 0xa427_1100_32b5_e1dd, + 0x5764_1c21_9d72_1a74, + 0x1c46_831d_9a74_5293, + ]), + Fp::from_raw([ + 0x739a_e1d8_83e9_1269, + 0x3629_6c06_5767_4f80, + 0xf371_41dc_34d5_78e0, + 0x06d7_626c_953c_cb72, + ]), + ], + [ + Fp::from_raw([ + 0x0f10_8c71_cda2_930c, + 0xdc5c_440a_3022_cd96, + 0xc540_0274_8e0c_410e, + 0x28ff_ddc8_6f18_c136, + ]), + Fp::from_raw([ + 0xb8ec_0619_f6fb_c5e9, + 0xbe67_f1b7_ed2a_b6ad, + 0xf85d_eed0_9e40_0b17, + 0x2e67_f7ee_5e4a_a295, + ]), + Fp::from_raw([ + 0xe7ab_f22c_24e8_0f27, + 0xca56_859e_f759_e53c, + 0x0e97_f251_14a7_9a2d, + 0x26ce_38fa_636c_9063, + ]), + Fp::from_raw([ + 0xafac_b4c4_cfd3_5db1, + 0x42cb_3d16_a1f7_2721, + 0x4dd7_a01d_00a7_ffec, + 0x2e6e_07c3_c95b_f7c3, + ]), + Fp::from_raw([ + 0x384b_1470_da24_d8cc, + 0x8890_d276_612e_1246, + 0x5f91_d796_1c3a_54fb, + 0x2aa7_4f75_97f0_c9f4, + ]), + ], + [ + Fp::from_raw([ + 0x2ec0_ca63_9b7f_73fe, + 0x8a71_313c_1509_183e, + 0x2c7c_090f_668a_b45b, + 0x287d_681a_46a2_faae, + ]), + Fp::from_raw([ + 0x11e2_9f33_05e7_3c04, + 0xa5d3_106f_f565_aa3b, + 0xf4a4_0600_528f_3d7d, + 0x212b_d19d_f812_eaae, + ]), + Fp::from_raw([ + 0x3809_c284_0471_3504, + 0xf97f_d974_0926_dab9, + 0x1aaf_b14b_350e_b860, + 0x1154_f7cf_5191_86bf, + ]), + Fp::from_raw([ + 0xda74_7e79_661f_c207, + 0xbf51_5290_5be3_6583, + 0x4637_810a_4bd1_b16f, + 0x1dff_6385_cb31_f1c2, + ]), + Fp::from_raw([ + 0x63ea_3c60_6b55_1e5c, + 0x4011_a34d_5476_2528, + 0xc081_d34c_44c1_8e42, + 0x0e44_4582_d22b_4e76, + ]), + ], + [ + Fp::from_raw([ + 0xff72_d3aa_b7e4_eff8, + 0x8157_73e9_c284_6323, + 0xabab_6638_328f_02f1, + 0x0323_c9e4_33ba_66c4, + ]), + Fp::from_raw([ + 0xb70f_2c68_76a9_c29d, + 0x25b8_cf00_2740_112d, + 0x193b_ba79_cdec_448f, + 0x1274_6bbd_7179_1059, + ]), + Fp::from_raw([ + 0xfd04_9eb4_438a_2240, + 0xd466_c837_cf50_d73e, + 0xfd9b_9d37_5184_2c75, + 0x1173_b7d1_12c2_a798, + ]), + Fp::from_raw([ + 0x5fdb_4808_7032_43da, + 0xa8e5_713b_2502_6ebe, + 0x76d1_e555_d7fe_d13d, + 0x13d5_1c10_90a1_ad48, + ]), + Fp::from_raw([ + 0xf4a5_8ebe_b956_baa1, + 0xcb72_743f_0394_efe7, + 0xff8d_cb7c_bd2d_9743, + 0x0087_4c13_44a4_ad51, + ]), + ], + [ + Fp::from_raw([ + 0xa32a_5620_74fe_f08f, + 0x0eea_48d3_546e_97d6, + 0x65ce_236b_07f2_44fa, + 0x22df_2213_1aaa_b858, + ]), + Fp::from_raw([ + 0x8bf5_eb05_a919_f155, + 0x9845_24a5_9101_e6c1, + 0x8708_b437_a445_fc3e, + 0x0bf9_64d2_dbd2_5b90, + ]), + Fp::from_raw([ + 0x69b4_35b5_fc50_2f32, + 0x640b_9d73_a9ab_298c, + 0xa302_be1f_7f18_1e0e, + 0x09b1_8d9b_917a_55bc, + ]), + Fp::from_raw([ + 0xd674_5a50_6728_9e43, + 0xfbbb_c70a_6365_366d, + 0xa4bf_c1d5_bf3d_c05b, + 0x094f_5534_444f_ae36, + ]), + Fp::from_raw([ + 0x784f_ddc0_dc23_f01f, + 0xa3e2_40c0_da57_01cb, + 0x519f_a662_2af5_3a15, + 0x2999_bab1_a5f2_5210, + ]), + ], + [ + Fp::from_raw([ + 0x6957_5a11_b03a_3d23, + 0x8430_1bce_8a93_d136, + 0x1ca9_4db7_3710_e880, + 0x2f68_98c0_7581_f637, + ]), + Fp::from_raw([ + 0x6217_4b1a_6866_fccb, + 0x565d_eb1e_8e57_42f8, + 0xec16_d7e1_318a_4740, + 0x0726_8eaa_ba08_bc19, + ]), + Fp::from_raw([ + 0xa6c9_582c_41a0_529f, + 0x6260_3e07_8e1c_6689, + 0x1339_ff77_113b_c9eb, + 0x1862_79b0_0345_4db0, + ]), + Fp::from_raw([ + 0x0768_e552_4737_172c, + 0xb67e_2cc5_de9a_2275, + 0xe491_5bdd_04d3_e5dd, + 0x18a3_f736_5091_97d6, + ]), + Fp::from_raw([ + 0x7b59_87b8_7085_671d, + 0x725e_2d4b_cb2d_3a00, + 0x77cc_1e2e_d24c_808c, + 0x0a21_fa19_88cf_38d8, + ]), + ], + [ + Fp::from_raw([ + 0x0b36_a135_e785_fba2, + 0x8328_c184_a2c4_3bc0, + 0x1faf_5ef6_a646_2522, + 0x15b2_85cb_e26c_467f, + ]), + Fp::from_raw([ + 0x8cd2_8de3_e779_f161, + 0x0b77_75b7_c902_f578, + 0x8c08_b8c3_f980_6d56, + 0x164b_7062_c467_1cf0, + ]), + Fp::from_raw([ + 0x7a24_f651_249b_aa70, + 0x61c6_1d3d_43b6_e65d, + 0x86d9_865f_e7e5_0ef3, + 0x0890_ba08_19ac_0a6f, + ]), + Fp::from_raw([ + 0xcc2e_e0ae_d543_e922, + 0xa627_ac5c_b0eb_878c, + 0xa427_12e5_a721_e4ea, + 0x2fbe_a4d6_5d7e_d425, + ]), + Fp::from_raw([ + 0x103d_7f5f_379a_baaa, + 0x7b70_a58e_854a_b9b9, + 0x5403_03a3_b536_f85e, + 0x0492_bf38_3c36_fa55, + ]), + ], + [ + Fp::from_raw([ + 0x6a96_9d56_292d_c24e, + 0x1d61_85a9_ce85_675f, + 0x4e20_251c_5651_42d6, + 0x05e9_1fe9_44e9_4410, + ]), + Fp::from_raw([ + 0x7e38_0a76_e36e_6c1c, + 0x995b_9621_e6e4_9c3b, + 0x93d4_63cb_041a_cad0, + 0x12fe_5c20_29e4_b338, + ]), + Fp::from_raw([ + 0x06cd_01dc_6fa0_784e, + 0x1f26_29fa_dc89_4969, + 0x958f_7723_9214_7413, + 0x0241_54ad_f025_5d47, + ]), + Fp::from_raw([ + 0x8813_c134_57a4_5550, + 0xd798_8156_44f2_bbde, + 0x36ed_2462_a86b_d0ba, + 0x1882_4a09_e6af_af4a, + ]), + Fp::from_raw([ + 0xbb06_983e_3d5d_58a5, + 0xdf84_a630_af68_d50b, + 0xe9f2_55de_0c3d_bddd, + 0x0c8b_482d_ba0a_d51b, + ]), + ], + [ + Fp::from_raw([ + 0x4413_9287_8fdb_05e6, + 0x5a4f_a67f_cd6a_af86, + 0x363e_0a16_67d3_b67c, + 0x1732_5fd0_ab63_5871, + ]), + Fp::from_raw([ + 0x1cc6_d0e9_11fa_402e, + 0xe550_773f_a8d1_8bf7, + 0x122f_5af6_7b69_0f31, + 0x050a_e95f_6d2f_1519, + ]), + Fp::from_raw([ + 0x5771_ec84_edc5_0c40, + 0x4bbb_6295_f075_6988, + 0x038c_b288_d626_3676, + 0x0f0d_139a_0e81_e943, + ]), + Fp::from_raw([ + 0x9dbb_ca7b_8e74_7cd6, + 0x79b3_9ebc_7a1b_1c54, + 0xf70f_d2f2_c0f9_3d1a, + 0x1c0f_8697_7956_89cd, + ]), + Fp::from_raw([ + 0xfe53_6a16_dc1d_81e6, + 0x49be_23a4_b135_98f9, + 0x6d2b_c2e0_48bc_979e, + 0x2bd0_f940_ad93_6b79, + ]), + ], + [ + Fp::from_raw([ + 0x7ce5_4d1e_96ee_62cb, + 0xa06e_bb27_5e09_6d16, + 0x4778_c09a_0053_337f, + 0x27eb_1be2_7c9c_4e93, + ]), + Fp::from_raw([ + 0xf151_be62_548e_2aea, + 0x284f_bd30_7d1f_71b0, + 0x8f96_bdd3_155a_7ca3, + 0x2e48_89d8_30a6_7e5a, + ]), + Fp::from_raw([ + 0x6064_bbe6_fcc1_e305, + 0xc989_1f2c_adc1_65db, + 0x5d2e_c5e9_c5bd_9983, + 0x193f_e3db_0ab4_7d3c, + ]), + Fp::from_raw([ + 0x37cb_1302_7c83_e525, + 0x6e96_61c0_0967_9e4e, + 0xce41_5907_ad0c_40ed, + 0x2bf3_086e_96c3_6c7b, + ]), + Fp::from_raw([ + 0x9ff2_ea48_6e59_bb28, + 0xd5e7_e413_f741_ccf2, + 0xa98c_db69_7c6c_ad5d, + 0x12f1_6e2d_e6d4_ad46, + ]), + ], + [ + Fp::from_raw([ + 0xad91_9b03_43b9_2d2f, + 0x33f3_d5d6_ec6c_4bf0, + 0xa026_2e36_53dd_d19f, + 0x2a72_147d_2301_19f3, + ]), + Fp::from_raw([ + 0x77df_79ac_c10b_a974, + 0x0a2d_9bcc_2641_2e29, + 0x6dc4_7f95_7806_dc5f, + 0x21be_0e2c_4bfd_64e5, + ]), + Fp::from_raw([ + 0x137f_d4b6_c21b_444a, + 0x71b8_4fb9_11aa_57ae, + 0x2749_a3b5_4367_b25a, + 0x0e2d_7e1d_c946_d70b, + ]), + Fp::from_raw([ + 0x9f3f_07d4_b92b_3e2e, + 0xc31a_db0e_ae33_25dc, + 0x6170_a745_d8a4_188c, + 0x2667_f7fb_5a4f_a124, + ]), + Fp::from_raw([ + 0x830e_107d_a78e_3405, + 0x1550_c12b_08df_eb72, + 0x30a7_83b6_6064_697a, + 0x2ccc_6f43_1fb7_4007, + ]), + ], + [ + Fp::from_raw([ + 0x2ec5_0621_e38e_6e5d, + 0xae6d_bee9_e8ca_0c24, + 0x4f02_0146_2420_001f, + 0x0888_8a94_fc5a_2ca3, + ]), + Fp::from_raw([ + 0x6acf_cd3c_cbc5_3f2a, + 0xa0d2_fbe7_53af_88b3, + 0xad40_dd42_c9b6_fdd7, + 0x0297_7b34_eeaa_3cb6, + ]), + Fp::from_raw([ + 0x10b5_e9f9_71e1_6b9a, + 0xbfcf_e0d7_e6ff_8e96, + 0xfd6f_b6c9_ea13_a648, + 0x120c_cce1_3d28_b75c, + ]), + Fp::from_raw([ + 0xa0bf_2af3_2f86_ff3c, + 0x9278_7a45_75b2_bd73, + 0xc81e_1b97_70ea_098c, + 0x09fa_d226_9c4a_8e93, + ]), + Fp::from_raw([ + 0xf630_cffb_6009_2d6f, + 0x0deb_db70_775e_eb8a, + 0x0a4b_310e_4ac6_f0fa, + 0x0260_91fd_3d4c_44d5, + ]), + ], + [ + Fp::from_raw([ + 0xb904_fd2b_ca89_3994, + 0x2543_cc56_afad_6afc, + 0xbb7f_ba9d_fb6f_c321, + 0x2940_4aa2_ba56_5b77, + ]), + Fp::from_raw([ + 0xe720_1351_b7c8_83f9, + 0xf1ff_d865_90e0_827d, + 0xd4e8_7c25_4869_5b4e, + 0x2749_475c_399a_af39, + ]), + Fp::from_raw([ + 0xc755_7dab_65ff_a222, + 0xa2eb_e2dc_2e4d_a70a, + 0x3991_2b50_4246_85cb, + 0x098c_8423_2247_9f72, + ]), + Fp::from_raw([ + 0x0191_d0c0_53b5_1936, + 0x758a_ce14_c93c_4da4, + 0x3123_8e57_fead_7d5c, + 0x18ce_f581_222b_647e, + ]), + Fp::from_raw([ + 0x63b2_aa68_1698_de60, + 0x3cbc_0ca4_a108_f98d, + 0xd4e7_4674_5e43_711d, + 0x1317_7839_c68a_5080, + ]), + ], + [ + Fp::from_raw([ + 0xa907_d88e_5f0d_effd, + 0x26cc_4df7_12c0_e5f0, + 0xc088_f56f_4b74_3256, + 0x020c_a696_f531_e43e, + ]), + Fp::from_raw([ + 0x16e0_2838_7c7a_c022, + 0x93d1_3708_c646_841d, + 0xfa80_5a30_fc54_8db6, + 0x2723_0eed_e9cc_cfc9, + ]), + Fp::from_raw([ + 0x9057_d2fe_75bb_281c, + 0x6497_c059_69a0_1543, + 0xd64f_de34_a342_a178, + 0x0164_5911_c119_8b01, + ]), + Fp::from_raw([ + 0xfdca_4a45_1a5d_8643, + 0x1989_71e1_4487_056c, + 0x6e43_9c88_341c_e25f, + 0x2c32_3fe1_6481_bf49, + ]), + Fp::from_raw([ + 0x5af8_8ae6_db50_85c8, + 0xb022_c124_d3bf_fe8b, + 0x450b_d207_4c3e_22e1, + 0x0fc0_82df_e707_28e8, + ]), + ], + [ + Fp::from_raw([ + 0xfe8a_488b_4235_21ee, + 0xbe96_4211_6ac4_c77e, + 0xd8cd_ca56_8dcc_25b3, + 0x2052_c174_800d_b209, + ]), + Fp::from_raw([ + 0x8dd9_fd05_b3ec_e9c0, + 0x0be3_51ce_8129_065a, + 0xaf96_d621_d554_2319, + 0x28e4_20e1_0df2_fbb5, + ]), + Fp::from_raw([ + 0x49d6_e0bc_3ae5_069a, + 0x55d6_ae1b_dacd_1cb5, + 0x9f78_3c44_62a2_4db6, + 0x2569_8ca5_e24a_1b79, + ]), + Fp::from_raw([ + 0x34d9_b857_d698_4d35, + 0xa297_b610_7442_2ac1, + 0xcf8f_fbfa_57d5_1049, + 0x160a_9981_a5c8_9a57, + ]), + Fp::from_raw([ + 0x6464_2b06_9997_f3d4, + 0x8b7c_ebf5_9ddb_b0a0, + 0x34d9_b694_b843_f3bf, + 0x21c9_1a39_e145_c3bc, + ]), + ], + [ + Fp::from_raw([ + 0xd109_7546_1e41_734c, + 0xd6ea_a029_d93f_03b6, + 0xd2b0_9345_ef11_2345, + 0x1ac8_d80d_cd5e_e876, + ]), + Fp::from_raw([ + 0xb8db_cea5_657c_e02c, + 0x5d82_2895_e275_5544, + 0x7c16_62a4_174c_5222, + 0x0ab3_e6ad_0ecf_8b8e, + ]), + Fp::from_raw([ + 0xc5b2_237c_bdab_3377, + 0xa52e_f3ef_5909_b4e1, + 0x27e3_b0b9_17b3_a21c, + 0x1c67_5182_5126_20ae, + ]), + Fp::from_raw([ + 0x0c8a_9092_ac2b_ed45, + 0xe37a_4a3e_07a7_d75d, + 0x3d94_8d0c_85ba_d2e2, + 0x2cdb_c998_dfd7_affd, + ]), + Fp::from_raw([ + 0x3e49_1a13_3bb6_3b2e, + 0x2433_7350_309d_ff83, + 0x774b_f67c_c0de_e333, + 0x23b5_84a5_6e21_17b0, + ]), + ], + [ + Fp::from_raw([ + 0x2f3d_20de_1465_e9a5, + 0x0d13_3bc6_ba4e_c115, + 0x8cb7_3030_a3c9_d2a1, + 0x1e9e_2b31_0f60_ba9f, + ]), + Fp::from_raw([ + 0x60bc_d4f1_fa5b_22db, + 0xc9ab_5dab_9875_20c4, + 0xabc3_e720_140a_e746, + 0x0e01_e365_ba5b_3031, + ]), + Fp::from_raw([ + 0x1387_d85b_c126_4e68, + 0x4338_2011_b61c_9a4b, + 0x7b71_2734_0498_d5c4, + 0x0408_84cd_cfc6_4bfc, + ]), + Fp::from_raw([ + 0xa0a1_7de3_11ef_9668, + 0x353f_1724_d606_7ed0, + 0x0c74_a399_8f2b_ea36, + 0x190b_1ee1_205e_b950, + ]), + Fp::from_raw([ + 0xfc28_d1f8_32bd_3b2c, + 0xc1df_cf65_ce61_e165, + 0xd04f_52fc_23cd_9c08, + 0x1647_c72a_ec6c_4388, + ]), + ], + [ + Fp::from_raw([ + 0x842e_595b_71e4_541d, + 0x9f54_94d8_9fb4_8b02, + 0x7998_80cc_4c87_3626, + 0x2430_0063_46a0_145f, + ]), + Fp::from_raw([ + 0x3221_8502_52b7_57af, + 0x9d85_3902_bb16_bacb, + 0x3651_07a3_da3a_e7f6, + 0x177b_9a08_3439_17e1, + ]), + Fp::from_raw([ + 0x39b1_1d04_666d_f4f8, + 0x09cd_53d0_ae29_4234, + 0x4e58_862a_68f5_e326, + 0x04a4_20e6_42b1_1ae9, + ]), + Fp::from_raw([ + 0xcdfa_bedd_b6a2_5c8f, + 0xde24_6185_8e95_6ccc, + 0x105a_88fa_b0af_d810, + 0x25d0_e0f7_39fb_39fc, + ]), + Fp::from_raw([ + 0x2c4d_4ffa_0c74_0a27, + 0x320c_b156_10ea_ed45, + 0x8590_5cbf_5865_1edc, + 0x0447_6d91_b7ef_f2fd, + ]), + ], + [ + Fp::from_raw([ + 0xe978_0fd9_ca28_6fae, + 0x1c28_f6d5_e125_0cb5, + 0x8bc9_ca24_19eb_8dea, + 0x1090_c0b6_8b3d_7d7b, + ]), + Fp::from_raw([ + 0xbf28_99cb_5c66_17be, + 0x376f_2d43_5855_c10e, + 0x448a_725c_5c7c_d5ad, + 0x2539_3ce3_b925_6d50, + ]), + Fp::from_raw([ + 0x97d1_44f0_fab4_6630, + 0xed82_4388_d6b9_3426, + 0xfc86_2f30_6e6e_5830, + 0x2593_1c0c_7371_f4f1, + ]), + Fp::from_raw([ + 0x03f7_3f22_afbf_62f6, + 0x8a4d_3531_85d5_8082, + 0xc82a_ad51_b0fb_79cf, + 0x2396_cb50_1700_bbe6, + ]), + Fp::from_raw([ + 0xb3f0_ce03_b872_4884, + 0xa3dc_9068_c3cc_a7b5, + 0x54ea_748a_7129_a7b0, + 0x26a3_6348_3348_b589, + ]), + ], + [ + Fp::from_raw([ + 0xa7a0_e5b4_5fcd_6897, + 0xc99b_8933_3421_5f6b, + 0x8d6f_1535_b92c_5478, + 0x27ca_107c_a204_f2a1, + ]), + Fp::from_raw([ + 0x8d2d_02b3_b2d9_1056, + 0xac15_f730_1178_581d, + 0xe466_2bde_326b_2cce, + 0x26da_28fc_097e_d77c, + ]), + Fp::from_raw([ + 0x7297_1ba5_6ee0_cb89, + 0x6557_74c1_bb35_d575, + 0x703e_3055_070a_c9cc, + 0x056a_b351_691d_8bb3, + ]), + Fp::from_raw([ + 0x2d72_9af6_3237_6a90, + 0xc225_47a1_1ffc_5015, + 0xc76d_109a_2f48_1aa3, + 0x2638_b57f_23b7_54ae, + ]), + Fp::from_raw([ + 0xea7b_c551_9ede_7cef, + 0xc33e_46a5_32bd_ec80, + 0x32f4_92c2_6051_84fd, + 0x3047_54bb_8c57_d607, + ]), + ], + [ + Fp::from_raw([ + 0x752a_c93a_9b76_19ac, + 0x48ec_6857_fc55_4010, + 0x514f_155b_5806_cbf7, + 0x00d1_727f_8457_ee03, + ]), + Fp::from_raw([ + 0xc588_e509_4776_1fa3, + 0xb5bc_a868_05ec_9419, + 0x43ba_295a_303c_72fa, + 0x00ee_1f3c_66fb_c05c, + ]), + Fp::from_raw([ + 0x61f9_297e_b675_d972, + 0x10a1_9fbc_fc59_078c, + 0x4a76_b5a1_d824_15fd, + 0x0afa_fadc_f5b4_dd4a, + ]), + Fp::from_raw([ + 0xa899_6d12_4dd0_4d0a, + 0x65a2_3483_5a0a_6a5e, + 0x86ce_45e8_eed1_08ee, + 0x0b24_49f3_9746_085e, + ]), + Fp::from_raw([ + 0x6a7d_df48_23dd_5dd6, + 0xf6c6_f071_ec3b_dda7, + 0xc9f3_7b00_4522_7095, + 0x206b_0ce2_f1b2_c5b7, + ]), + ], + [ + Fp::from_raw([ + 0x52c7_e102_9459_409c, + 0xcaff_c3a4_ef20_fea8, + 0xb696_e674_3362_8cd6, + 0x0feb_a4fb_8783_4c7c, + ]), + Fp::from_raw([ + 0x8173_e972_336e_55d3, + 0x3b06_f131_5e6d_70e1, + 0x8926_752e_084e_0251, + 0x254d_bfac_74c4_9b0b, + ]), + Fp::from_raw([ + 0x3771_9edf_a0ca_8762, + 0x9606_c5bd_1791_0aeb, + 0x4655_168c_3675_59e1, + 0x0add_b137_2cee_4e16, + ]), + Fp::from_raw([ + 0x21a5_89d6_8831_d759, + 0x6ca4_d8d8_1b1a_e162, + 0xc799_024f_b019_f65c, + 0x26b2_5b7e_257f_3e97, + ]), + Fp::from_raw([ + 0x1960_b105_9e11_3ba3, + 0xa465_7b9a_b00b_db5b, + 0x413b_8d4c_6587_87e5, + 0x0909_95b7_9ace_c240, + ]), + ], + [ + Fp::from_raw([ + 0x2974_178d_44f7_3b7b, + 0xeb0d_8e40_e991_31f4, + 0xc572_9968_7843_cea3, + 0x08db_dc2e_21ef_11f2, + ]), + Fp::from_raw([ + 0x58f1_7093_9785_179f, + 0x42fe_9c49_1596_ab67, + 0x679f_af75_2a0f_78e3, + 0x09e8_aba6_7148_1197, + ]), + Fp::from_raw([ + 0x7bc7_c683_f136_3d5c, + 0x7efd_12a7_f9ee_c94b, + 0x6590_52a7_ebaf_816c, + 0x1deb_0518_0e83_3e45, + ]), + Fp::from_raw([ + 0x35fd_6523_e525_0879, + 0x2489_97e8_b2c2_4af3, + 0xa926_efbc_c04a_a9ee, + 0x19a7_0ec6_bdfc_9098, + ]), + Fp::from_raw([ + 0xeb93_abe1_0bbf_1f64, + 0x6635_3a37_77d8_a3f1, + 0x8799_86f9_aab4_8905, + 0x21d7_7366_0ada_fb8a, + ]), + ], + [ + Fp::from_raw([ + 0xf6f4_6617_841c_3901, + 0x97a6_b01f_cd66_7347, + 0x3e20_ba63_7b89_d5d3, + 0x09f1_890f_72e9_dc71, + ]), + Fp::from_raw([ + 0x08c2_145c_33b1_11c3, + 0x48fa_1f89_7bf2_19d6, + 0x2a30_0c61_e446_998d, + 0x05af_4593_61eb_454d, + ]), + Fp::from_raw([ + 0xfa12_fc85_0cc8_b513, + 0x5f33_6f15_f340_756c, + 0x664a_66dc_75a6_5733, + 0x0fa1_a1d6_829f_0345, + ]), + Fp::from_raw([ + 0x5365_5cf9_7a62_8bb0, + 0xf428_0fcf_87f6_36f8, + 0xbda0_b1c0_307a_d543, + 0x02e4_7a35_bcc0_c3a0, + ]), + Fp::from_raw([ + 0x3662_1895_204d_0f12, + 0x2034_1146_0112_4910, + 0xb8f9_0e78_bf4c_24b7, + 0x14f7_73e9_834c_6bde, + ]), + ], + [ + Fp::from_raw([ + 0xffb0_9196_2fc8_f7cc, + 0x642a_be7c_fd63_9992, + 0x255c_f19d_29bc_7d8e, + 0x102d_98cf_502e_d843, + ]), + Fp::from_raw([ + 0xcba6_86a7_e913_73c2, + 0x20d4_c73a_d329_4738, + 0x4c47_f6c6_5da7_ca23, + 0x043d_d5f4_aa5a_76dd, + ]), + Fp::from_raw([ + 0x4cfd_b2b2_6bd1_1efa, + 0x6f0e_7c79_743a_306f, + 0xa6c0_d29a_48d4_f267, + 0x2183_3819_c333_7194, + ]), + Fp::from_raw([ + 0xdc53_262a_3cff_2b53, + 0xeb4e_ca24_6c31_1eca, + 0xb474_a681_9d11_6ca3, + 0x0f28_1925_cf5e_e649, + ]), + Fp::from_raw([ + 0x93ce_662f_15c2_38d6, + 0xdf62_5dd6_0504_d5dc, + 0x4470_9c77_46d6_824e, + 0x0d3e_2477_a7b1_0beb, + ]), + ], + [ + Fp::from_raw([ + 0xacbf_c3ef_f5ae_fc44, + 0xd80f_80ab_51e7_3b49, + 0x56ff_8a01_be9c_de35, + 0x2cd7_f641_bedb_f669, + ]), + Fp::from_raw([ + 0x86dd_8613_1046_3cf8, + 0x9149_d240_4581_1d7a, + 0x4d09_380f_98b7_4e38, + 0x29e9_5b49_2bf2_f95f, + ]), + Fp::from_raw([ + 0xf8b3_c50d_f83c_c13e, + 0x9ae4_c51a_f6ff_eb57, + 0x266e_fca8_6a6c_810f, + 0x22da_66bc_62e8_f011, + ]), + Fp::from_raw([ + 0x4307_2d84_1925_554a, + 0x220d_b79e_8129_df36, + 0x3023_4917_94f4_aca3, + 0x0fe6_d30d_e7a8_2d16, + ]), + Fp::from_raw([ + 0xc7e0_7ecb_298f_d67f, + 0xd312_d03f_ef1a_dfec, + 0x123c_46ef_f185_c23a, + 0x0050_e842_a129_9909, + ]), + ], + [ + Fp::from_raw([ + 0x885c_bbdb_6310_8c21, + 0x666f_9ddf_714e_d7c5, + 0xbe34_cc53_a42d_7733, + 0x2130_a3a7_b322_1222, + ]), + Fp::from_raw([ + 0xe1d3_28e6_7b33_c9fa, + 0xaa66_731f_34a9_3280, + 0xd8d5_883f_e056_6c24, + 0x2df9_ee29_4edf_99e3, + ]), + Fp::from_raw([ + 0xa4b6_fc5f_c337_2762, + 0x5813_2396_dc25_0aeb, + 0xf26e_b68c_c21f_f541, + 0x1bf7_d6e4_89ad_8c0c, + ]), + Fp::from_raw([ + 0x5d47_78d3_1178_0e54, + 0xcf7b_8077_28bf_7fe3, + 0x61ea_f739_617a_b136, + 0x0c60_2fa1_55be_9587, + ]), + Fp::from_raw([ + 0xb63f_af01_21ed_7f21, + 0x5154_080a_2497_2fae, + 0x3240_7d86_b8d2_2d7d, + 0x2e50_e2c5_b36a_a205, + ]), + ], + [ + Fp::from_raw([ + 0xb459_123b_1803_32e1, + 0xf674_995e_e840_9b42, + 0x5710_d629_0ec4_f782, + 0x17c2_5109_82a7_b582, + ]), + Fp::from_raw([ + 0xb2ab_255f_376b_42a8, + 0x2133_7b53_8eab_d2f6, + 0x6803_ecf2_465b_885b, + 0x0b0d_52f0_3c8a_f727, + ]), + Fp::from_raw([ + 0x6197_2dd8_fab8_bd14, + 0xa9ac_77c6_c0f8_5d45, + 0x5953_d88a_63f8_0647, + 0x0f56_33df_1972_b945, + ]), + Fp::from_raw([ + 0x0354_9813_0a7f_1572, + 0x2478_0ff4_3e76_e929, + 0xe142_2e93_9681_1551, + 0x0ebf_7ad2_9ca1_3804, + ]), + Fp::from_raw([ + 0x661b_1103_a720_ffe2, + 0xe18f_94be_e27c_8a57, + 0x0b02_9621_73bb_a343, + 0x1aff_13c8_1bda_47e8, + ]), + ], + [ + Fp::from_raw([ + 0x5713_be57_efac_6c07, + 0xd3f3_1de1_a3b5_8ff3, + 0xda24_65be_8550_5862, + 0x2104_49db_f5cf_3061, + ]), + Fp::from_raw([ + 0xb93a_d1c3_ee62_9817, + 0x19d1_e2f1_d300_1044, + 0x7d75_cd6d_3c7b_9dbe, + 0x0882_30c2_794e_50c5, + ]), + Fp::from_raw([ + 0x17a4_d6db_b20e_7e3a, + 0x8cce_9a9e_16c7_7056, + 0xda08_dc46_4138_dfc7, + 0x1c40_8c25_6490_b0a1, + ]), + Fp::from_raw([ + 0x90dd_6f55_7e9e_3903, + 0x8f7c_7765_4d58_4404, + 0x22d1_7712_00fb_0765, + 0x0745_17e0_81eb_4c1f, + ]), + Fp::from_raw([ + 0xa05a_df61_e12f_dcbf, + 0xcee5_5305_59d6_cf0f, + 0x8852_4bdb_2036_91b4, + 0x02d0_4e9c_21df_1dbd, + ]), + ], + [ + Fp::from_raw([ + 0xa81d_b32c_f1b6_7b13, + 0xeb9b_4650_dae9_f11a, + 0x82e1_3ebd_75de_3b58, + 0x2eb7_a011_b8bc_e910, + ]), + Fp::from_raw([ + 0xe603_0c18_f0cf_17b5, + 0x4d2a_c6bf_9536_8304, + 0x299f_75d6_e8a8_49b5, + 0x2efd_a77e_d35f_4af0, + ]), + Fp::from_raw([ + 0xc51b_2440_192a_e064, + 0x61a7_3d10_852b_8114, + 0x2edd_beda_6520_6d4f, + 0x0919_9dca_fd50_ce64, + ]), + Fp::from_raw([ + 0x7618_1cb4_216e_1562, + 0xcb65_5d8c_1797_e9fa, + 0x4dd3_19db_666a_75b5, + 0x268c_5cfc_446d_399c, + ]), + Fp::from_raw([ + 0x0128_54ed_a11a_18dc, + 0x97b4_4e91_2cce_6687, + 0x26b0_e9a3_6c80_5786, + 0x2303_a652_c949_0718, + ]), + ], + [ + Fp::from_raw([ + 0x3707_3f4e_fb35_fbdf, + 0xbc53_53eb_1108_68d2, + 0xc3f0_41f3_1dc4_5922, + 0x27c5_3563_b12a_6ee2, + ]), + Fp::from_raw([ + 0x2c27_8f22_615d_2b0e, + 0x9969_b524_8cfe_90f4, + 0xf02b_d82d_0a51_0904, + 0x1201_a87e_af4a_e618, + ]), + Fp::from_raw([ + 0x6cb4_b43d_ab2a_443d, + 0xcafc_b1ba_2c51_e570, + 0xad82_1499_7bb0_69be, + 0x2c43_1694_39fc_d69e, + ]), + Fp::from_raw([ + 0x4fb9_847e_6304_d944, + 0x4f46_cbb3_6d70_2e3c, + 0xea03_c45d_6984_c689, + 0x0683_5973_1535_9040, + ]), + Fp::from_raw([ + 0x7779_5ad3_a798_d183, + 0xb042_5e15_8314_197b, + 0xafb9_3b12_8feb_d16f, + 0x0354_5706_706e_ab36, + ]), + ], + [ + Fp::from_raw([ + 0xedb3_93b7_1a0c_0e6b, + 0x740b_ed23_a6a3_7870, + 0xd35f_1fc0_51b3_1728, + 0x1a33_c254_ec11_7619, + ]), + Fp::from_raw([ + 0xf338_e517_f169_0c78, + 0x6e88_f71e_759b_87e6, + 0x67b0_c002_281c_af99, + 0x1ffe_6968_a447_0cd5, + ]), + Fp::from_raw([ + 0x107f_4e02_e355_b393, + 0x140d_dd5d_2a5c_4483, + 0xecb0_59c8_99fd_80f4, + 0x0fd6_6e03_ba88_08ff, + ]), + Fp::from_raw([ + 0x7be3_396b_7fe0_13ab, + 0x6c86_17a7_bdd5_d74a, + 0x1973_9455_2906_b17e, + 0x263a_b69f_13b9_66f8, + ]), + Fp::from_raw([ + 0x6eb7_e03e_39ba_4046, + 0x3bd8_7d5a_a395_8fdd, + 0x5054_d5a1_65de_413e, + 0x16a4_25e4_7d11_1062, + ]), + ], + [ + Fp::from_raw([ + 0xfcc5_f73a_f913_8d9a, + 0x53cc_31d1_3e39_e909, + 0xad75_2f03_c673_f0e2, + 0x2dc5_10a4_719e_c10c, + ]), + Fp::from_raw([ + 0xcb4a_a709_a945_76e5, + 0x423c_5179_329b_7a82, + 0x1bd1_cad2_3d07_dda3, + 0x24df_8e8d_856c_5b5e, + ]), + Fp::from_raw([ + 0xbdf2_4a6c_dce5_620f, + 0x7628_249a_01b0_9561, + 0x3cd5_c689_15a0_42e8, + 0x2bcc_94ff_4fc3_c76f, + ]), + Fp::from_raw([ + 0xb48b_e868_52da_97df, + 0xd329_5f52_c38c_ffe6, + 0x8de5_4e34_3df7_c429, + 0x076c_1e88_dc54_0c8d, + ]), + Fp::from_raw([ + 0xa990_fb8e_12cb_46e1, + 0xe40e_e160_1120_947d, + 0x1c05_1fb1_2d9a_5e4f, + 0x09b5_f209_a451_ac43, + ]), + ], + [ + Fp::from_raw([ + 0x0683_af75_eb67_7c07, + 0x64e9_424f_55b0_f1ea, + 0xaa88_d6a4_4135_a6ab, + 0x205f_17b0_d872_9e2e, + ]), + Fp::from_raw([ + 0x0cdd_1edf_9f23_7029, + 0x0916_81f0_a417_6172, + 0x9126_38c3_8be0_46cd, + 0x281c_5c68_8836_f6cf, + ]), + Fp::from_raw([ + 0x4afa_44f2_5926_21f5, + 0x009a_44e7_a02e_a50e, + 0x5f4d_6744_8c47_1cf3, + 0x1a05_3e68_78e9_00f4, + ]), + Fp::from_raw([ + 0x8174_2839_d59e_064c, + 0x68ef_cb89_7e7b_bee9, + 0x07fb_7cea_c84e_4f54, + 0x100d_c7d4_26de_be30, + ]), + Fp::from_raw([ + 0x0b1c_b31b_411e_49c8, + 0xb289_05bd_b62c_82c8, + 0xbb87_e2cf_adc8_b75f, + 0x1702_2672_a016_a957, + ]), + ], + [ + Fp::from_raw([ + 0xe0c2_7203_f954_f4d2, + 0x9fb8_b547_182b_170d, + 0x7105_3a87_ebe1_5123, + 0x1086_db7e_2760_fc8b, + ]), + Fp::from_raw([ + 0xb85d_d24f_d758_4064, + 0x2b41_fb65_a185_536f, + 0x0246_0ae4_c294_2fac, + 0x1538_4fe3_9d73_b633, + ]), + Fp::from_raw([ + 0x519e_5513_5770_9008, + 0x47b1_a853_205f_cfb5, + 0x4bf4_abc5_342c_6c74, + 0x2ebb_599f_e913_6d42, + ]), + Fp::from_raw([ + 0xd996_fac6_f4d3_7288, + 0x4cf2_7829_2b4c_e3ee, + 0xfec3_c0f0_542e_4c5a, + 0x1b4b_5e87_cfb9_262c, + ]), + Fp::from_raw([ + 0xb637_febe_659e_5057, + 0xbb4a_7581_bb4f_ba60, + 0x801f_3f82_e302_cafb, + 0x2465_053a_e50b_6885, + ]), + ], + [ + Fp::from_raw([ + 0x6e0a_b9d3_d548_59ef, + 0xda9f_05e1_8b37_08bf, + 0x095c_5bb5_d38f_1b97, + 0x114f_32ed_cdea_09cd, + ]), + Fp::from_raw([ + 0xee28_d8c2_543c_7148, + 0x2e5e_5519_f3d1_8123, + 0x6b38_7cd7_7be7_79ac, + 0x2bc7_0dfe_b2ba_ab2f, + ]), + Fp::from_raw([ + 0x12e4_9bfe_32c0_5415, + 0xa783_48b9_f6ec_68a4, + 0x775e_3a61_ad7e_77b6, + 0x01c9_bf7a_203c_e22b, + ]), + Fp::from_raw([ + 0x9f4a_d00c_cb57_ee9b, + 0x55cf_b575_ff6a_97cd, + 0x7bed_b029_5fbb_cec3, + 0x0514_b0fe_5909_ea88, + ]), + Fp::from_raw([ + 0x03e3_202d_7b6c_1b7e, + 0x2092_520b_12a2_01af, + 0x1a13_2a8b_0589_10a1, + 0x267c_76ec_8193_4cc8, + ]), + ], + [ + Fp::from_raw([ + 0xfb15_1cf7_57bd_e5d6, + 0xf162_2493_ce83_e95c, + 0xc78c_84ba_bbb4_70ad, + 0x2917_0e33_22b3_d8d5, + ]), + Fp::from_raw([ + 0x36dd_596c_a41d_9519, + 0x5c6f_09a4_5486_cab5, + 0xaf33_e5d3_873f_9c33, + 0x019f_6a81_24b1_9e33, + ]), + Fp::from_raw([ + 0x5405_d036_242b_60e9, + 0x009e_d8ea_1715_18ae, + 0x8b34_8e9d_b198_1c27, + 0x1904_aa4d_6908_544a, + ]), + Fp::from_raw([ + 0x3b93_2a47_6455_ff1a, + 0xb3ce_e1de_9dd6_f647, + 0xf7f0_4395_6694_e422, + 0x26f1_7873_949b_c679, + ]), + Fp::from_raw([ + 0xbdca_c9b1_8bc4_8f75, + 0x40c4_7603_1197_131e, + 0x193b_3372_0b8a_a540, + 0x1ac6_68f6_12b8_243c, + ]), + ], + [ + Fp::from_raw([ + 0xa52f_8156_8ef0_663d, + 0xccfb_f855_5be9_796d, + 0x196d_ae45_bf62_4766, + 0x0996_d961_a75c_0d07, + ]), + Fp::from_raw([ + 0x5425_b395_c24f_c819, + 0x74d1_71f9_9c63_febb, + 0xfd50_d1b4_383f_be66, + 0x030c_97e1_b8ca_d1d4, + ]), + Fp::from_raw([ + 0xee22_6f2d_8bd0_848f, + 0xb3e5_23f1_fe50_2642, + 0x3953_3702_55b6_8f89, + 0x06e3_ad6a_4690_0e2d, + ]), + Fp::from_raw([ + 0xa329_8905_88cc_916e, + 0x65cb_94b0_e045_5153, + 0x6b68_80e4_2f98_80f5, + 0x1d6b_3755_331c_d021, + ]), + Fp::from_raw([ + 0x3dd3_8c08_bae5_31f2, + 0xc351_89dc_0b85_ac03, + 0x59b0_4153_5e73_0ac8, + 0x28e4_dcba_4b96_f12a, + ]), + ], + [ + Fp::from_raw([ + 0x7a3b_3e4b_c4a4_7a14, + 0xb071_2a47_6260_376c, + 0x8ccf_484f_2974_b6a6, + 0x08b6_0860_46a8_3550, + ]), + Fp::from_raw([ + 0x8964_3e15_b9bb_3b52, + 0xb6fd_85fb_a6a0_536a, + 0x444b_cec9_7812_019b, + 0x162c_d2ca_7fe3_b5f1, + ]), + Fp::from_raw([ + 0x998c_01c6_4d48_3a76, + 0xb5c9_a9c1_1920_63d1, + 0x05af_5b11_937e_4f5c, + 0x28f1_e03b_aaea_9bbc, + ]), + Fp::from_raw([ + 0x6031_a0bb_6791_ce10, + 0x0127_d2aa_b4aa_7136, + 0xa395_af27_34c2_5faa, + 0x1bdb_0627_78d7_c15d, + ]), + Fp::from_raw([ + 0x5032_9ebd_d247_49cb, + 0x0fc9_8870_b232_4a8b, + 0xcb29_14e8_2962_7e0e, + 0x2375_8395_02e0_9890, + ]), + ], + [ + Fp::from_raw([ + 0xe92d_3361_1ed7_bb50, + 0x332d_c87c_fb2d_f456, + 0xad7c_5566_8dc9_423a, + 0x1fa8_662f_bcb6_1fb3, + ]), + Fp::from_raw([ + 0x3f7f_9736_079d_7694, + 0x46e2_fb2c_47a5_138f, + 0xf870_7f72_1716_c8a4, + 0x1e4f_ad2d_d6b0_a6f1, + ]), + Fp::from_raw([ + 0x61b0_f5f1_3731_ffe7, + 0xba3b_d050_059f_53d2, + 0x6df6_f5fc_dd1f_a788, + 0x2112_56d1_6c72_69fd, + ]), + Fp::from_raw([ + 0xf4a1_079b_12bc_c5a5, + 0xf420_60e5_74dd_a341, + 0x4f8e_2a2e_6af0_8318, + 0x2e49_084b_336e_ceaa, + ]), + Fp::from_raw([ + 0x758d_2589_1fb0_0bb9, + 0x1aec_ea08_dfe1_4cab, + 0x3bf3_5192_ac68_0821, + 0x0ce1_9f54_cdc3_9f7f, + ]), + ], + [ + Fp::from_raw([ + 0x84a8_468b_ab2c_14cb, + 0x8dc6_0451_e4ae_4e1c, + 0x3cc3_9422_1261_d874, + 0x0011_c5d5_6c39_0e89, + ]), + Fp::from_raw([ + 0x14a8_cd80_5157_9b4c, + 0xca60_e17b_fa39_b475, + 0x8a9e_05ee_6af3_dbb7, + 0x17d7_9ff0_6b63_ac2a, + ]), + Fp::from_raw([ + 0xcab0_0173_6390_15fa, + 0xb1a8_b35c_d641_6a2e, + 0xdc74_5600_9359_2b06, + 0x19a7_d3a4_46cb_5393, + ]), + Fp::from_raw([ + 0xaa6c_3c97_2370_37a6, + 0x31f0_4596_d892_8da7, + 0x2a80_8b2e_1b92_82f3, + 0x030c_00a0_933d_cdba, + ]), + Fp::from_raw([ + 0x9370_be8a_ab64_139c, + 0x935d_2d00_184c_4acc, + 0xae25_ad08_0695_382e, + 0x16bc_b447_ce2d_50f3, + ]), + ], + [ + Fp::from_raw([ + 0x3925_5b7c_d66f_eb1d, + 0xe621_24f3_7cab_7b6d, + 0x5ea4_ec87_1531_2997, + 0x1234_1b46_b015_0aa2, + ]), + Fp::from_raw([ + 0xdc89_212d_b6a4_9ff4, + 0x002f_c28e_296d_1044, + 0xb72a_97b2_bf61_0c84, + 0x0e86_d139_17f4_4050, + ]), + Fp::from_raw([ + 0xd059_025b_110c_7262, + 0x1052_a181_f8f2_eb14, + 0xd357_e00b_53d7_f30d, + 0x08e6_eb40_89d3_7d66, + ]), + Fp::from_raw([ + 0xe10c_48ce_97ca_7b18, + 0x0415_ccb3_51a1_e0ce, + 0x4738_d15d_d148_1a0c, + 0x2ea1_2385_6245_f6c8, + ]), + Fp::from_raw([ + 0x664d_b0f9_c84d_fa6f, + 0x4195_7890_2541_3abf, + 0x3446_e003_30b1_6310, + 0x2dca_72b2_ebca_b8c2, + ]), + ], + [ + Fp::from_raw([ + 0xc64a_26cb_d42b_6a6b, + 0xf2f6_b423_5f03_6fa4, + 0x6332_9f58_5ec9_24b3, + 0x06ff_9ed5_0d32_7e84, + ]), + Fp::from_raw([ + 0xc630_a453_5afb_f730, + 0xe2a6_0e0c_ca84_ea2a, + 0x47f7_c9bd_a3d5_4df8, + 0x246a_10b7_e3e0_0899, + ]), + Fp::from_raw([ + 0x5c96_b39d_688b_6691, + 0x2f84_6a71_5ae6_7ad7, + 0x1871_9ed9_9d70_0ee5, + 0x22a6_3501_c5f0_4b90, + ]), + Fp::from_raw([ + 0x40ec_07e5_14fa_e937, + 0xb916_4f58_351d_8aa1, + 0x7179_9ac5_d2e2_24cd, + 0x2f4c_5047_7f7f_d9c6, + ]), + Fp::from_raw([ + 0x0cda_32ca_d851_567e, + 0x38f0_ba8a_4a23_d4b5, + 0x13b1_7f4d_876d_9a1e, + 0x10ff_b7aa_d1f5_1c7d, + ]), + ], + [ + Fp::from_raw([ + 0x1a05_6935_c358_03ae, + 0x7840_2735_2187_e7af, + 0xa4d3_9722_532d_5420, + 0x0e9c_efdd_c3c2_d3be, + ]), + Fp::from_raw([ + 0xa3e8_3bdd_4ba6_2b41, + 0xa165_6f96_a33c_8978, + 0xc233_52e6_dc6e_a4af, + 0x07af_84a4_d314_1e7a, + ]), + Fp::from_raw([ + 0xe940_f3ec_8a22_c3c5, + 0x1a39_323d_6e89_b638, + 0xf8de_00d1_4b1e_566d, + 0x2d9e_31a1_0aeb_c761, + ]), + Fp::from_raw([ + 0x7656_747b_e27e_64c7, + 0xd360_34b3_14da_d844, + 0x33db_1afd_592f_66f1, + 0x27f1_9a65_32e6_6b53, + ]), + Fp::from_raw([ + 0x7e4d_617d_47d0_7ffd, + 0x180e_d99f_8f31_55cd, + 0x54b2_024c_3b4a_577a, + 0x0058_fa3c_8454_d633, + ]), + ], + [ + Fp::from_raw([ + 0x7089_6495_6816_a5d5, + 0xb041_4a20_5d3a_175d, + 0x6795_7c08_0699_343e, + 0x0416_27b6_715b_7809, + ]), + Fp::from_raw([ + 0x8988_8f12_b727_c52d, + 0xd982_0147_1cf1_f665, + 0x7f63_2e57_b958_ccec, + 0x006a_c49d_d925_3edc, + ]), + Fp::from_raw([ + 0xec46_a6bf_1830_1398, + 0xec0c_9c0d_6d25_a9a4, + 0xb1d8_c361_6bbe_3386, + 0x0131_adff_d8bd_7254, + ]), + Fp::from_raw([ + 0xcf03_0e1c_d8f9_f5b6, + 0x7797_7ad7_e25e_49a3, + 0x4138_e413_ef62_a283, + 0x1c4a_6f52_c9fc_cf7a, + ]), + Fp::from_raw([ + 0xd1f7_958d_2c26_45f6, + 0xfee0_48ae_2078_aeb7, + 0x9465_51b3_860e_a479, + 0x03f2_a6be_51ec_677f, + ]), + ], + [ + Fp::from_raw([ + 0x4d73_7639_6b8d_dc62, + 0x8a13_72d8_5431_1956, + 0x391a_0cb7_8ef3_a964, + 0x2da7_70aa_d2c2_eb09, + ]), + Fp::from_raw([ + 0x3806_2afb_75d6_4a03, + 0xc9d4_5fe8_66c3_59c7, + 0xdc18_02fe_bfab_02ce, + 0x1527_8463_665f_74cd, + ]), + Fp::from_raw([ + 0x8d58_0638_ac54_c773, + 0x34e3_8ea9_66a0_8a6f, + 0xc973_1027_0905_18d4, + 0x12fe_278a_a365_44ea, + ]), + Fp::from_raw([ + 0x21b0_6ff3_0b6a_23b6, + 0xd858_7604_ca4f_0d6e, + 0x4c45_d119_d3f4_cc7f, + 0x149b_9c80_2182_558a, + ]), + Fp::from_raw([ + 0x0adc_4959_b691_dfe4, + 0x55e0_44fd_60db_ac9a, + 0x17d1_9319_772f_3c98, + 0x0812_e7b4_d847_bc85, + ]), + ], + [ + Fp::from_raw([ + 0x2a66_9737_03a0_c61b, + 0x5558_13c7_e750_3aea, + 0xd8df_7f28_a0bf_aa7f, + 0x02ed_8d8d_deaf_e3d9, + ]), + Fp::from_raw([ + 0xa130_4038_662d_4db8, + 0x55e5_e4d9_a03d_6b6b, + 0x4deb_6029_f921_029e, + 0x0ebd_073b_a053_7b51, + ]), + Fp::from_raw([ + 0x44ee_75b6_2eff_9f59, + 0x55b3_e792_c6af_a08b, + 0x05c6_ba8d_2ccd_0282, + 0x15c7_54d5_b14b_2c42, + ]), + Fp::from_raw([ + 0xc29e_7cbc_30e8_732a, + 0x1cc1_2352_70f4_cbc5, + 0xb0ed_8fa6_fa31_1b39, + 0x1695_15c8_9ac5_479d, + ]), + Fp::from_raw([ + 0x6d91_61f5_cd9a_4fef, + 0x08bd_c29f_6ff0_3769, + 0x2388_f262_1001_1016, + 0x2547_9fbf_b3a6_8f98, + ]), + ], + [ + Fp::from_raw([ + 0xce09_486e_94be_6071, + 0xa7f8_e6e9_7218_2196, + 0x3c85_2cb0_311a_578c, + 0x1447_5c4b_d520_451f, + ]), + Fp::from_raw([ + 0x5c4e_7336_3d09_7799, + 0xd3a8_4750_2aec_8d5f, + 0xc9ba_f279_8833_a1df, + 0x045a_6910_66cc_66be, + ]), + Fp::from_raw([ + 0x013b_8bcb_37eb_a683, + 0x147a_8ca0_3722_1b90, + 0xb833_ac8a_11e3_a3f0, + 0x2602_9c0c_267c_799f, + ]), + Fp::from_raw([ + 0xb810_1d5b_948d_1641, + 0x73ce_12a6_a94a_3e45, + 0xf7c9_4696_9c1c_2608, + 0x163f_acb3_4ff5_72fb, + ]), + Fp::from_raw([ + 0xaf8e_e00c_4240_ee28, + 0x13e0_6a62_75e5_8688, + 0x1d96_9320_cc69_d5ec, + 0x2c71_4e96_e191_3b35, + ]), + ], + [ + Fp::from_raw([ + 0xd52b_804e_ff1d_5fa6, + 0xddd6_268f_06de_bfe2, + 0x5aba_8466_5ecd_2bf9, + 0x1c16_61e2_a7ce_74b7, + ]), + Fp::from_raw([ + 0xa8c3_c068_b7dc_2c71, + 0x6635_b34c_2a08_89fe, + 0x5e5a_f3e6_619a_47d2, + 0x06a6_9ae7_95ee_9bfe, + ]), + Fp::from_raw([ + 0x3141_6c85_d731_d46a, + 0x74db_dbad_f541_95c7, + 0xc5d2_8b4c_19a3_6093, + 0x113d_5853_5d89_2115, + ]), + Fp::from_raw([ + 0x2ca1_a740_63b4_6101, + 0xe534_f1ff_47f7_917a, + 0x38ff_97d7_61da_6042, + 0x2ab8_9102_e2b8_d5e6, + ]), + Fp::from_raw([ + 0x3980_4fd6_a15a_d1b3, + 0x3490_3189_3da2_b4fd, + 0x9627_30c4_5e69_9546, + 0x03c1_1ca7_9e41_fdfe, + ]), + ], + [ + Fp::from_raw([ + 0x6adf_040f_aaf2_669c, + 0xf7f6_7b4d_4cfe_846c, + 0x8801_4ddb_bbfc_9da1, + 0x2709_6c67_2621_4038, + ]), + Fp::from_raw([ + 0xaa9d_c2b5_7ef5_be0d, + 0x66db_790c_e486_130c, + 0xd504_d4de_eb53_b13c, + 0x2de3_2ad1_5497_aef4, + ]), + Fp::from_raw([ + 0x2557_5444_8eef_d001, + 0x28c7_38dd_daec_9f3d, + 0xfd5d_3413_1072_2a2d, + 0x0dc1_08f2_b0a2_80d2, + ]), + Fp::from_raw([ + 0x7d34_ca50_365d_832f, + 0xbcdc_3eeb_c409_be7c, + 0xc968_58a1_bb9e_fad5, + 0x1869_f3b7_63fe_8164, + ]), + Fp::from_raw([ + 0x8c6d_3376_7129_682d, + 0xb616_945e_16a5_68d4, + 0xf825_59fe_6a91_1843, + 0x022e_d3a2_d9ff_31cb, + ]), + ], + [ + Fp::from_raw([ + 0x10c6_aec0_77d0_26bc, + 0x92fc_a1f2_7c19_c266, + 0x3944_ed13_65bd_0e72, + 0x2155_d600_5210_169e, + ]), + Fp::from_raw([ + 0x3ea5_928c_8cae_ae85, + 0xec0c_0556_c91a_f3db, + 0xcae9_3263_f5f1_b4bb, + 0x0de1_ba7a_562a_8f7a, + ]), + Fp::from_raw([ + 0x6351_dfa7_da90_2563, + 0x126f_740b_ce8d_637b, + 0xcfce_5bf4_6ec7_da38, + 0x05db_b440_6024_beab, + ]), + Fp::from_raw([ + 0xc97e_5427_a368_fd5e, + 0x00e7_89e3_fcd7_2dcc, + 0xd4d8_dc8a_d778_d32c, + 0x05d4_149b_aac4_13be, + ]), + Fp::from_raw([ + 0x9212_e221_72c2_7b2e, + 0xf0b6_802f_a941_c787, + 0x9be5_046e_7397_e76f, + 0x01cd_f8b4_52d9_7c2b, + ]), + ], + [ + Fp::from_raw([ + 0x1b0a_2227_9d46_c07c, + 0xc4d7_c501_5ece_d8c7, + 0xaf80_85ff_81ad_ce33, + 0x1fc6_a718_6702_7f56, + ]), + Fp::from_raw([ + 0x568e_0fda_96aa_afc2, + 0xa9e1_85b7_5306_d9c3, + 0x5d4d_59a5_a7a3_a42b, + 0x1040_bef4_c642_d034, + ]), + Fp::from_raw([ + 0xa808_1475_ab8f_ad0d, + 0x2b21_e956_7643_1918, + 0xff2c_91b2_8933_4a4d, + 0x16b7_9c3a_6bf3_16e0, + ]), + Fp::from_raw([ + 0x3cbe_1cdc_d59f_474e, + 0xa340_70e5_2b60_1fc1, + 0x434b_3a13_87e3_c8c6, + 0x20df_f1bc_30f6_db6b, + ]), + Fp::from_raw([ + 0xdfb7_fd85_12ae_060d, + 0x062d_d417_1a72_6a8b, + 0xc254_9550_30a9_70f8, + 0x0212_ac2a_b7a6_eaae, + ]), + ], + [ + Fp::from_raw([ + 0x13c8_119f_6996_ae09, + 0xb02d_c031_34f0_0442, + 0x869a_109c_9215_637c, + 0x2f29_3774_9147_4442, + ]), + Fp::from_raw([ + 0x5837_d9f3_2d81_4bfa, + 0x73be_9f38_66aa_284c, + 0x25ec_93c3_3fea_6032, + 0x0984_ca6a_5f91_85d5, + ]), + Fp::from_raw([ + 0xeb0e_e929_4b24_f028, + 0xe491_361c_8a6b_d19c, + 0x0d29_9bd6_fa81_220d, + 0x0d08_0a6b_6b3b_6070, + ]), + Fp::from_raw([ + 0xce92_9ed7_c85a_4544, + 0x21ac_c85b_6400_264d, + 0x6789_5306_38cb_0ad8, + 0x0e65_cd99_e84b_052f, + ]), + Fp::from_raw([ + 0x5597_5da1_2736_920b, + 0xe30e_3d20_380f_f6a6, + 0x4808_f72c_716c_d05e, + 0x2e20_8875_bc7a_c122, + ]), + ], + [ + Fp::from_raw([ + 0x4a60_d1aa_8592_bad5, + 0xae2e_3b89_4afd_29f6, + 0x76a0_b0ff_3d7d_fac1, + 0x2989_f3ae_477c_2fd3, + ]), + Fp::from_raw([ + 0x3a74_c3ce_a718_9459, + 0x9181_06a4_6329_0a3e, + 0x9222_d101_e6fa_c0ce, + 0x1136_1ce5_44e9_4137, + ]), + Fp::from_raw([ + 0x34c8_bd05_a206_1438, + 0xd122_a822_b8fb_366c, + 0xa539_e10c_173f_6a75, + 0x1e8d_014b_86cb_5a7d, + ]), + Fp::from_raw([ + 0x212e_e2c2_8ee9_8733, + 0xa0c2_3241_67ef_6c91, + 0x7ba8_12ad_2955_8e23, + 0x173f_65ad_ec8d_eee2, + ]), + Fp::from_raw([ + 0x991b_d695_310e_ddd9, + 0x5da5_df7a_d454_99d0, + 0xafee_8bd0_c779_ac3e, + 0x01c3_6daa_f9f0_1f1b, + ]), + ], + [ + Fp::from_raw([ + 0x4a6f_5741_f381_e562, + 0xf277_d1a3_f2fc_8994, + 0xaa9a_b1c4_85bb_85ff, + 0x1353_acb0_8c05_adb4, + ]), + Fp::from_raw([ + 0x20f1_c87a_3b06_4d34, + 0x009d_33de_b4f9_3aeb, + 0x1860_e71e_a118_8ee4, + 0x2e5a_bd25_3720_7cad, + ]), + Fp::from_raw([ + 0x4217_26ba_8f69_455c, + 0x13de_b4eb_3491_3a13, + 0xd02e_edbb_7ab8_5625, + 0x191d_5c5e_daef_42d3, + ]), + Fp::from_raw([ + 0xee79_39dd_2dcd_089e, + 0x82a0_4c74_c127_de9d, + 0x82a2_63fe_a6d7_599d, + 0x11d7_f8d1_f269_2642, + ]), + Fp::from_raw([ + 0x1bad_0852_86ca_c971, + 0x3445_cb4c_d6bc_6f95, + 0x90f7_9ad5_e679_9797, + 0x0421_8fde_3668_29ed, + ]), + ], + [ + Fp::from_raw([ + 0x27b7_1c73_0d76_d6dd, + 0x027b_73b4_8930_1c32, + 0x0483_97ca_5f47_a202, + 0x0070_772f_7cf5_2453, + ]), + Fp::from_raw([ + 0xc1dc_04db_e3d2_b861, + 0x6768_1a98_cd05_1634, + 0xc865_b065_687a_1d9b, + 0x038a_389b_aef5_d9a7, + ]), + Fp::from_raw([ + 0xf198_6078_9015_a6e5, + 0x0f39_d009_66a5_0bea, + 0xcda4_46b2_b4b5_9ccd, + 0x09a5_eefa_b8b3_6a80, + ]), + Fp::from_raw([ + 0x9d16_212c_7584_cd8c, + 0x3d9e_c99e_dfac_b748, + 0xb969_c145_109b_4b58, + 0x01b5_8884_8b8b_47c8, + ]), + Fp::from_raw([ + 0xfed9_1e30_d429_54a6, + 0x545e_5abf_a323_d817, + 0x6e1a_f6df_c334_1419, + 0x0b84_6e4a_390e_560f, + ]), + ], + [ + Fp::from_raw([ + 0x85ca_bfe8_5ce7_2fe3, + 0xeb15_13bc_394f_c4f9, + 0x0d43_a02d_db90_0040, + 0x23a6_679c_7d9a_db66, + ]), + Fp::from_raw([ + 0xf72f_31d6_fe08_9254, + 0x4c34_02fb_7c85_eccc, + 0x3e5c_aa35_f135_1e9f, + 0x2e03_74a6_9919_7e34, + ]), + Fp::from_raw([ + 0x37d4_1913_a7a2_7b48, + 0x4b8c_c0b1_176b_b9ec, + 0x7f7a_08af_4cde_3ff6, + 0x0752_cd89_9e52_dc4d, + ]), + Fp::from_raw([ + 0x99e2_873d_d7ae_55a7, + 0x0275_142b_664b_802c, + 0xc349_a2b6_d573_97a5, + 0x068f_8813_1272_99da, + ]), + Fp::from_raw([ + 0xb374_330f_2da2_02b5, + 0x8687_2d04_a295_b5b8, + 0x6775_7416_7434_b3f9, + 0x2ba7_0a10_2355_d549, + ]), + ], + [ + Fp::from_raw([ + 0xb860_9e70_a0b5_0828, + 0x09f9_099b_825d_d289, + 0xa334_d1df_03b5_5213, + 0x2c46_7af8_8748_abf6, + ]), + Fp::from_raw([ + 0x96d2_9e57_63e8_f497, + 0xe3a5_2c2d_1a31_9572, + 0x0100_9a2b_448a_e881, + 0x05c5_f20b_ef1b_d827, + ]), + Fp::from_raw([ + 0x3ac8_c9fe_61b7_3871, + 0x0cd0_83a2_c649_d9f2, + 0x842a_381f_6006_e2c6, + 0x0dc6_385f_dc56_7be5, + ]), + Fp::from_raw([ + 0xd255_baa8_114b_369c, + 0x0378_d5b8_4150_d25e, + 0x19d4_9911_b867_0fa7, + 0x142d_3983_f3dc_7f7e, + ]), + Fp::from_raw([ + 0x00fa_18c1_b2df_67bf, + 0xf36f_86a7_a99a_a35c, + 0xfd7e_6d98_c96a_0fa0, + 0x29a0_1efb_2f6a_a894, + ]), + ], + [ + Fp::from_raw([ + 0xd2a1_af04_eb61_3a76, + 0x0ab9_e8af_c645_55b7, + 0x38c4_a506_6644_ec63, + 0x0525_ffee_737d_6051, + ]), + Fp::from_raw([ + 0xf177_cf42_3830_1dc8, + 0x7e16_4f61_4910_264e, + 0xf076_677c_a0e8_2276, + 0x1e80_7dca_81d7_9581, + ]), + Fp::from_raw([ + 0xdba5_6082_dbd8_757c, + 0x4c02_23e0_f733_a52f, + 0x9351_0816_4724_74d3, + 0x0385_fb3f_89c7_4dc9, + ]), + Fp::from_raw([ + 0xf558_f337_bab0_ea01, + 0xcfab_d701_6fd6_ef1a, + 0xe1a6_298e_53ca_e59f, + 0x0376_40dc_1afc_0143, + ]), + Fp::from_raw([ + 0xb7e4_c0de_8960_74b4, + 0x5fd5_6cf0_31da_8050, + 0xf12a_6c52_6082_9eee, + 0x1341_999a_1ed8_6919, + ]), + ], + [ + Fp::from_raw([ + 0x7b24_4f65_bed8_ece7, + 0x73af_d642_efdc_c565, + 0x5690_6d4b_afb1_0ad7, + 0x069e_b075_866b_0af3, + ]), + Fp::from_raw([ + 0xea19_09a6_1903_3696, + 0xd272_6101_d3af_aa02, + 0x95b3_8e8e_08b3_e646, + 0x171c_0b81_e621_36e3, + ]), + Fp::from_raw([ + 0x5575_1075_02ac_ced8, + 0xcbbd_cb39_bfe6_96f9, + 0xb6eb_55c3_1175_3e84, + 0x2c81_814c_9453_f51c, + ]), + Fp::from_raw([ + 0xe39b_8a86_a7c3_a604, + 0xb04b_3903_2adc_a92c, + 0xe3b3_3fad_cf27_4b2a, + 0x29d8_43c0_415d_35d9, + ]), + Fp::from_raw([ + 0xa766_9cf3_fae7_728b, + 0xd8e1_5ea5_947f_2cda, + 0x8436_bccd_abb7_8750, + 0x085d_6a10_70f3_513d, + ]), + ], + [ + Fp::from_raw([ + 0x9e2a_5cda_412f_c394, + 0xdbf1_dd4e_6706_b02c, + 0x10a4_4ba6_65bf_302c, + 0x1182_0363_ed54_1daa, + ]), + Fp::from_raw([ + 0x1058_40b7_51a1_6317, + 0xdfd3_150e_05f1_df5d, + 0x02b6_0d61_a837_85bd, + 0x2019_35a5_8f5c_57fc, + ]), + Fp::from_raw([ + 0xcfed_610e_87c0_2e9a, + 0x4679_4eed_d686_cd8e, + 0xe27a_952a_bd33_a03d, + 0x0a8c_2820_c569_71aa, + ]), + Fp::from_raw([ + 0xd50d_bc16_3a28_1877, + 0x650b_65ff_33e6_be1f, + 0x04ab_d6d0_bd75_00b6, + 0x1806_38ff_301a_64ca, + ]), + Fp::from_raw([ + 0x3e9b_321a_812d_d36b, + 0xf85c_a8a9_37cf_bec6, + 0x044f_9711_4a41_58a3, + 0x095c_7162_66f1_de59, + ]), + ], + [ + Fp::from_raw([ + 0x8d40_87a4_97d7_3490, + 0x83b6_18c5_c1a6_8781, + 0x20d8_6ffe_d6c7_ca15, + 0x17c3_1ea0_2fbc_3783, + ]), + Fp::from_raw([ + 0xe4d5_0a77_f192_a91b, + 0x3c17_a976_4ccd_660d, + 0x6a72_27e4_192d_149d, + 0x05b8_6c4b_b8ef_318b, + ]), + Fp::from_raw([ + 0xd6ce_74ba_986c_7a7b, + 0xab15_f4a6_ae0d_237c, + 0x6ff7_0d7e_a2fd_e2c7, + 0x265b_c95d_f4a4_c487, + ]), + Fp::from_raw([ + 0xd6c8_86d4_715c_7929, + 0x0870_1739_c5f5_b4b3, + 0xd9bb_e48f_5fef_2f69, + 0x2475_2b47_bc6c_6bc8, + ]), + Fp::from_raw([ + 0xe583_b932_4d97_4efe, + 0x78d6_24b9_8da9_6ee5, + 0xa0d8_6e52_7a96_4821, + 0x1481_4a1e_0f49_2a4e, + ]), + ], + [ + Fp::from_raw([ + 0xc752_3bca_906f_00bd, + 0x997c_8e04_1d3c_fb3d, + 0xbd60_5773_78f2_9381, + 0x10de_f931_073b_6479, + ]), + Fp::from_raw([ + 0x1b45_92c9_8610_175f, + 0xfa0b_880d_28c6_9d03, + 0x7f70_6c0d_8ab4_ed03, + 0x14f7_ae77_0bf7_e95f, + ]), + Fp::from_raw([ + 0x75c5_cc9d_ce1c_e589, + 0x9dec_c9d4_28eb_e4e7, + 0x9f92_6af4_0e80_35d1, + 0x1aef_50a0_cee7_51b5, + ]), + Fp::from_raw([ + 0x9512_14b5_7e73_cf5a, + 0x086c_2a2d_57d0_9602, + 0xba65_ca60_068d_fe3b, + 0x0419_3560_7172_f68e, + ]), + Fp::from_raw([ + 0x37a1_51d3_bf45_2cb8, + 0x0493_fbef_e83c_8198, + 0x573b_d083_959b_856c, + 0x2686_3e9d_d242_55d1, + ]), + ], + [ + Fp::from_raw([ + 0x6527_38e6_3ff8_b3af, + 0xf5ad_f251_ba62_052c, + 0xeb3d_7a06_8bd0_87c9, + 0x2036_efb6_f983_0965, + ]), + Fp::from_raw([ + 0xb00f_a4f1_b4f4_ee9b, + 0x30be_4f75_a753_f854, + 0x766b_639a_0299_69ca, + 0x0c71_2a97_5b74_dc9d, + ]), + Fp::from_raw([ + 0x3757_31d3_8753_9699, + 0x7afd_ff64_5649_2ca3, + 0x27af_c99b_fac1_e680, + 0x0801_4dab_3cd1_667e, + ]), + Fp::from_raw([ + 0xb530_09f7_9b34_e6a4, + 0x97c4_dd4d_37b4_e8f3, + 0xa82a_4a79_839d_6a2b, + 0x198d_0719_2db4_fac2, + ]), + Fp::from_raw([ + 0xfd15_784d_1f63_e572, + 0x2709_b29d_53bb_946d, + 0xb23b_4131_4268_97a3, + 0x29eb_1de4_2a3a_d381, + ]), + ], +]; +// n: 254 +// t: 5 +// N: 1270 +// Result Algorithm 1: +// [True, 0] +// Result Algorithm 2: +// [True, None] +// Result Algorithm 3: +// [True, None] +// Prime number: 0x0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001 +// MDS matrix: +pub(crate) const MDS: [[Fp; 5]; 5] = [ + [ + Fp::from_raw([ + 0x4fc6_fdbc_f38d_7da1, + 0x9f22_e57a_ce3c_d7f6, + 0x080b_0af1_33b9_e436, + 0x251e_7fdf_9959_1080, + ]), + Fp::from_raw([ + 0x4855_0486_e91c_7765, + 0xfe26_ea9c_a238_d6e3, + 0x47cb_d3b1_c17d_97c7, + 0x25fb_50b6_5acf_4fb0, + ]), + Fp::from_raw([ + 0x6ea1_e31e_d576_7833, + 0x6deb_5325_f367_a455, + 0x355f_39eb_f62f_91b0, + 0x293d_617d_7da7_2102, + ]), + Fp::from_raw([ + 0x6145_f14b_a6d3_c1c4, + 0x6659_9e57_5a9b_7edf, + 0x9601_11ac_25da_4743, + 0x104d_0295_ab00_c85e, + ]), + Fp::from_raw([ + 0x3ed5_b90f_2f69_c977, + 0x792b_3813_954f_e9bf, + 0x7dea_3e33_6cd9_6a39, + 0x0aaa_35e2_c84b_af11, + ]), + ], + [ + Fp::from_raw([ + 0x69f6_61b2_eb74_c839, + 0x0529_03dc_6609_ea69, + 0xc03e_17c1_d1dc_db02, + 0x2a70_b9f1_d4bb_ccdb, + ]), + Fp::from_raw([ + 0x115b_8e2e_991c_cd7a, + 0xba9f_92ad_8ef4_b979, + 0x6315_a993_4f1b_8a1b, + 0x2811_5465_1c92_1e74, + ]), + Fp::from_raw([ + 0xb86a_894f_7db3_6c37, + 0x8ccd_8fdb_9ee2_b45f, + 0x0b53_c732_134e_fa33, + 0x28c2_be2f_8264_f95f, + ]), + Fp::from_raw([ + 0xc4ec_8fa7_5e53_0a13, + 0x9b62_6d8c_b4dc_18dc, + 0x6d42_7c89_0b18_83bb, + 0x2188_8041_e6fe_bd54, + ]), + Fp::from_raw([ + 0x350d_65e2_cbff_4941, + 0xe810_930e_3ea4_574a, + 0x8019_5b95_92d8_cf2b, + 0x14dd_b5fa_da01_71db, + ]), + ], + [ + Fp::from_raw([ + 0xa4fa_8478_970d_401d, + 0x55b9_1bff_652a_d69a, + 0xdea4_3265_306a_37ed, + 0x2f69_a719_8e1f_bcc7, + ]), + Fp::from_raw([ + 0x6d6a_3747_594d_3052, + 0x67ba_312b_3414_0e71, + 0xad93_1ab8_0e37_bbb2, + 0x001c_1edd_6264_5b73, + ]), + Fp::from_raw([ + 0xa7f9_a4b2_28bf_c32b, + 0x39c4_0c60_3049_466f, + 0xce2f_2c96_c696_63c4, + 0x15b9_8ce9_3e47_bc64, + ]), + Fp::from_raw([ + 0x6505_1de3_3163_cf9c, + 0xcba8_458b_28e4_4d92, + 0x58f6_5be2_fbac_809f, + 0x12c7_e2ad_fa52_4e59, + ]), + Fp::from_raw([ + 0x531e_c2de_53bb_d167, + 0xaf67_ce79_816e_f468, + 0x4901_8222_e7b8_922e, + 0x2efc_2b90_d688_1348, + ]), + ], + [ + Fp::from_raw([ + 0xf1af_d6c5_f721_f830, + 0x3c3f_fa45_50bd_2514, + 0x1981_e55e_3e1a_29a1, + 0x0c3f_050a_6bf5_af15, + ]), + Fp::from_raw([ + 0xeda7_7843_20a1_a36e, + 0x8b2e_fe2e_cd42_4a73, + 0xfa75_ba79_92bd_34f0, + 0x0dec_54e6_dbf7_5205, + ]), + Fp::from_raw([ + 0x7cc7_5cf3_2d81_36fa, + 0x9836_4a11_f4d9_88fb, + 0x2022_5815_034b_1960, + 0x1c48_2a25_a729_f5df, + ]), + Fp::from_raw([ + 0xb8b6_07ae_9fd8_514a, + 0x0812_ac2f_c9a1_4a5f, + 0x5273_2624_e4ab_9436, + 0x2625_ce48_a7b3_9a42, + ]), + Fp::from_raw([ + 0x9fb9_f28a_f710_251f, + 0xed7e_f8e3_00b9_a8bb, + 0x86f7_cd4f_d710_c509, + 0x07f0_17a7_ebd5_6dd0, + ]), + ], + [ + Fp::from_raw([ + 0xe2f7_8c2c_cc2e_3595, + 0x7c5e_55c2_0146_259b, + 0xf97c_9d61_86c6_c3ea, + 0x2a20_e3a4_a0e5_7d92, + ]), + Fp::from_raw([ + 0x403b_01fe_b727_a549, + 0x7016_73ae_d820_d9c4, + 0xaafb_1e9a_5d63_c0ee, + 0x1049_f821_0566_b51f, + ]), + Fp::from_raw([ + 0x61b9_299b_82d6_9c8e, + 0xef35_7a69_e3e8_6b55, + 0x6800_2bd9_d1b9_6b4b, + 0x02ec_ac68_7ef5_b4b5, + ]), + Fp::from_raw([ + 0xd4eb_cf11_bbe1_e37b, + 0xbdcb_6b58_ba40_441e, + 0x6808_f88c_9ba9_03d3, + 0x2d3a_1aea_2e6d_4446, + ]), + Fp::from_raw([ + 0xc98d_803b_fed6_5e64, + 0xb39c_4a7a_72db_b6d9, + 0xc9ad_171e_4f35_fe49, + 0x1407_4bb1_4c98_2c81, + ]), + ], +]; + +// Inverse MDS matrix: +pub(crate) const MDS_INV: [[Fp; 5]; 5] = [ + [ + Fp::from_raw([ + 0x0a07_b795_463d_35d5, + 0xfa40_1f28_8668_6830, + 0x5530_d7a8_f9f8_9493, + 0x164c_e768_9cd3_4e71, + ]), + Fp::from_raw([ + 0xd023_820e_b560_9d5c, + 0x12b2_b6d1_8e5b_b2fc, + 0xf20e_a20e_014a_394a, + 0x1db3_93d0_c226_3141, + ]), + Fp::from_raw([ + 0x0036_4d7e_376c_668a, + 0x6384_229a_60b5_b10e, + 0xc5c1_feea_7573_b0bd, + 0x2ea4_b39c_798c_b58f, + ]), + Fp::from_raw([ + 0x9d41_b342_029b_a735, + 0x81bd_3a5a_0ed0_4aed, + 0x32f7_e059_1681_966b, + 0x2d2b_4966_cbb1_a878, + ]), + Fp::from_raw([ + 0x6442_41f9_86b0_fed0, + 0xe608_a349_b9dd_3773, + 0x05c8_7598_5566_7af4, + 0x245f_6aa5_4869_51fc, + ]), + ], + [ + Fp::from_raw([ + 0x44ed_2060_93d6_aabd, + 0x1b4b_d6e6_9583_8469, + 0xd986_605b_83ae_1016, + 0x2ee7_5426_48a7_61d8, + ]), + Fp::from_raw([ + 0x1519_0ef0_969c_8858, + 0x833b_15f6_f4e4_c37d, + 0x7ed0_b2b9_3fc0_a2a6, + 0x03f8_d436_2488_e27a, + ]), + Fp::from_raw([ + 0x7510_417e_0bb5_e415, + 0x9ddd_05b3_aebe_0373, + 0x5313_8d6d_7efd_5b13, + 0x0d71_55cc_3c81_7a44, + ]), + Fp::from_raw([ + 0x8e6b_f161_2749_b599, + 0xa98d_ded9_0fa5_0d82, + 0x2dd5_fa16_bd6a_ff83, + 0x2a3f_9c29_2dfd_d278, + ]), + Fp::from_raw([ + 0xb9a9_386f_96f8_8ce4, + 0x4584_a3ce_4044_b0db, + 0x6e4a_5dbc_7bbc_2f86, + 0x2ef0_87b9_5a30_db20, + ]), + ], + [ + Fp::from_raw([ + 0x9b0a_dc8b_e22c_2097, + 0x8b3b_3579_9934_113f, + 0x1e46_b9c6_5795_09bf, + 0x268b_8879_63db_67de, + ]), + Fp::from_raw([ + 0x2b8e_5163_2fdd_069d, + 0x2a8a_52a7_6b3b_1707, + 0xd25c_3c95_da1d_1f6e, + 0x07c4_cf63_17d3_1876, + ]), + Fp::from_raw([ + 0xfd70_2b37_cca4_7eae, + 0xcd50_3388_d240_f0a3, + 0x9621_c484_9c42_8d51, + 0x1bb9_9031_ff61_99b8, + ]), + Fp::from_raw([ + 0xe85d_b447_c038_bd9b, + 0x2ea1_4994_16b4_849e, + 0x30bc_6db6_5916_28d5, + 0x2bb7_f772_403e_18b5, + ]), + Fp::from_raw([ + 0xa70f_45bf_661b_a481, + 0x0242_4059_a4aa_f101, + 0x5d41_bdd3_ccf8_e33f, + 0x07d1_50a7_2e22_cdd1, + ]), + ], + [ + Fp::from_raw([ + 0x9305_cd67_3be8_ad9e, + 0xbcd5_922e_8e81_b550, + 0xa06a_0858_2093_30f5, + 0x1aea_063d_15e7_adc4, + ]), + Fp::from_raw([ + 0xe378_276b_fdd3_2f08, + 0x5c33_6747_63c7_7b6b, + 0x438c_2b06_0ec9_9174, + 0x1ab1_8e5f_72e6_7dcc, + ]), + Fp::from_raw([ + 0x3cd9_40ae_057a_9f08, + 0x14e1_9fd3_481b_ad42, + 0xe321_6274_cde2_e859, + 0x1473_c50d_66be_6cbe, + ]), + Fp::from_raw([ + 0xbb73_27dc_2956_b7a0, + 0xc8c6_e6e7_7849_266c, + 0xef9e_e1d4_c8aa_a35a, + 0x262e_f341_6da6_8dcb, + ]), + Fp::from_raw([ + 0x58b0_d3bf_0f3e_2fdc, + 0xe85b_23a5_7fd1_48e7, + 0xd9e0_31c1_0ea9_700d, + 0x229c_fdf3_f130_b726, + ]), + ], + [ + Fp::from_raw([ + 0x9062_3a1f_a068_26c5, + 0xdeff_a4d4_9dfd_b739, + 0xf986_809c_e528_a884, + 0x125e_07c3_24bf_c084, + ]), + Fp::from_raw([ + 0x60db_1afc_a351_100f, + 0x11f1_b42a_4390_9d72, + 0xc1d2_4a0d_c7fb_7b61, + 0x10ef_80f7_94f0_c1ce, + ]), + Fp::from_raw([ + 0x89de_85d9_ecd3_c179, + 0x15b0_d5de_8c2d_49b7, + 0xe31b_fe9d_3806_054c, + 0x1e2f_2d99_8606_58a8, + ]), + Fp::from_raw([ + 0x9a25_4567_4b42_7d94, + 0x107e_cbf4_a7f5_4066, + 0x9afd_5ae4_7f61_3afa, + 0x199f_0a09_d4d0_c2bf, + ]), + Fp::from_raw([ + 0x7497_9f2e_71b0_1a16, + 0x1429_9813_8018_4c81, + 0x0164_dd73_b1a7_c35e, + 0x1cef_d8df_4cfb_e341, + ]), + ], +]; diff --git a/src/chips/poseidon/spec.rs b/src/chips/poseidon/spec.rs index 87afe63b..d1153abe 100644 --- a/src/chips/poseidon/spec.rs +++ b/src/chips/poseidon/spec.rs @@ -1,34 +1,38 @@ +//! Specification for rate 4 Poseidon using the BN256 curve. +//! Patterned after [halo2_gadgets::poseidon::primitives::P128Pow5T3] + +use crate::chips::poseidon::rate4_params; use halo2_gadgets::poseidon::primitives::*; -use halo2_proofs::arithmetic::FieldExt; -use std::marker::PhantomData; - -// 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 -// This spec hardcodes the WIDTH and RATE parameters of the hash function to 3 and 2 respectively -// 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 -// Since the WIDTH parameter is used to define the number of hash_inputs column in the PoseidonChip. -// Because of that we need to define a new Spec -// MySpec struct allows us to define the parameters of the Poseidon hash function WIDTH and RATE +use halo2_proofs::arithmetic::Field; +use halo2_proofs::halo2curves::bn256::Fr as Fp; + #[derive(Debug, Clone, Copy)] -pub struct MySpec { - _marker: PhantomData, -} +pub struct MySpec; -impl Spec - for MySpec -{ +pub(crate) type Mds = [[Fp; T]; T]; + +impl Spec for MySpec { fn full_rounds() -> usize { 8 } fn partial_rounds() -> usize { - 56 + 60 } - fn sbox(val: F) -> F { + fn sbox(val: Fp) -> Fp { val.pow_vartime(&[5]) } fn secure_mds() -> usize { - 0 + unimplemented!() + } + + fn constants() -> (Vec<[Fp; 5]>, Mds, Mds) { + ( + rate4_params::ROUND_CONSTANTS[..].to_vec(), + rate4_params::MDS, + rate4_params::MDS_INV, + ) } } diff --git a/src/circuits/merkle_sum_tree.rs b/src/circuits/merkle_sum_tree.rs index d5e8b6c2..87f44a26 100644 --- a/src/circuits/merkle_sum_tree.rs +++ b/src/circuits/merkle_sum_tree.rs @@ -1,29 +1,27 @@ use crate::chips::merkle_sum_tree::{MerkleSumTreeChip, MerkleSumTreeConfig}; -use eth_types::Field; +use halo2_proofs::halo2curves::bn256::Fr as Fp; use halo2_proofs::{circuit::*, plonk::*}; -use std::marker::PhantomData; #[derive(Default)] -pub struct MerkleSumTreeCircuit { - pub leaf_hash: F, - pub leaf_balance: F, - pub path_element_hashes: Vec, - pub path_element_balances: Vec, - pub path_indices: Vec, - pub assets_sum: F, - pub root_hash: F, - pub _marker: PhantomData, +pub struct MerkleSumTreeCircuit { + pub leaf_hash: Fp, + pub leaf_balance: Fp, + pub path_element_hashes: Vec, + pub path_element_balances: Vec, + pub path_indices: Vec, + pub assets_sum: Fp, + pub root_hash: Fp, } -impl Circuit for MerkleSumTreeCircuit { - type Config = MerkleSumTreeConfig; +impl Circuit for MerkleSumTreeCircuit { + type Config = MerkleSumTreeConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { Self::default() } - fn configure(meta: &mut ConstraintSystem) -> Self::Config { + fn configure(meta: &mut ConstraintSystem) -> Self::Config { // config columns for the merkle tree chip let col_a = meta.advice_column(); let col_b = meta.advice_column(); @@ -39,7 +37,7 @@ impl Circuit for MerkleSumTreeCircuit { fn synthesize( &self, config: Self::Config, - mut layouter: impl Layouter, + mut layouter: impl Layouter, ) -> Result<(), Error> { let chip = MerkleSumTreeChip::construct(config); let (leaf_hash, leaf_balance) = chip.assing_leaf_hash_and_balance( diff --git a/src/circuits/tests.rs b/src/circuits/tests.rs index 2279a222..c2c4dd1d 100644 --- a/src/circuits/tests.rs +++ b/src/circuits/tests.rs @@ -12,9 +12,8 @@ mod test { }; use num_bigint::ToBigInt; use rand::rngs::OsRng; - use std::marker::PhantomData; - fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit { + fn instantiate_circuit(assets_sum: Fp, path: &str) -> MerkleSumTreeCircuit { let merkle_sum_tree = MerkleSumTree::new(path).unwrap(); let proof: MerkleProof = merkle_sum_tree.generate_proof(0).unwrap(); @@ -27,11 +26,10 @@ mod test { path_indices: proof.path_indices, assets_sum, root_hash: proof.root_hash, - _marker: PhantomData, } } - fn instantiate_empty_circuit() -> MerkleSumTreeCircuit { + fn instantiate_empty_circuit() -> MerkleSumTreeCircuit { MerkleSumTreeCircuit { leaf_hash: Fp::zero(), leaf_balance: Fp::zero(), @@ -40,7 +38,6 @@ mod test { path_indices: vec![Fp::zero(); 4], assets_sum: Fp::zero(), root_hash: Fp::zero(), - _marker: PhantomData, } } @@ -147,7 +144,7 @@ mod test { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { region: (16, "permute state").into(), - offset: 36 + offset: 38 } } ]) @@ -216,7 +213,7 @@ mod test { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { region: (16, "permute state").into(), - offset: 36 + offset: 38 } } ]) @@ -291,7 +288,7 @@ mod test { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { region: (16, "permute state").into(), - offset: 36 + offset: 38 } } ]) @@ -374,22 +371,22 @@ mod test { cell_values: vec![ ( ((Any::advice(), 0).into(), 0).into(), - "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0" + "0x14b2e288bf66ce6fe38eb889a4f4c4e5c00e71b3b96caa9018bdf36c280a6be0" .to_string() ), ( ((Any::advice(), 0).into(), 1).into(), - "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a" + "0xb92ac29c673ed3f380acdca783f2e6a9f62f27522cffd1a0a28bc952a7a755" .to_string() ), ( ((Any::advice(), 2).into(), 0).into(), - "0x17063e69d8505e34b85820ae85ed171e8a44f82aefdcceec66397495e3286b6a" + "0xb92ac29c673ed3f380acdca783f2e6a9f62f27522cffd1a0a28bc952a7a755" .to_string() ), ( ((Any::advice(), 2).into(), 1).into(), - "0x221a31fb6a7dfe98cfeca9b0a78061056f42f31f5d5719cfbc5c8110e38ed0b0" + "0x14b2e288bf66ce6fe38eb889a4f4c4e5c00e71b3b96caa9018bdf36c280a6be0" .to_string() ), (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), @@ -417,7 +414,7 @@ mod test { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { region: (16, "permute state").into(), - offset: 36 + offset: 38 } } ]) @@ -454,7 +451,7 @@ mod test { column: (Any::advice(), 5).into(), location: FailureLocation::InRegion { region: (16, "permute state").into(), - offset: 36 + offset: 38 } } ]) diff --git a/src/merkle_sum_tree/utils/hash.rs b/src/merkle_sum_tree/utils/hash.rs index 8649071a..01ee7e0e 100644 --- a/src/merkle_sum_tree/utils/hash.rs +++ b/src/merkle_sum_tree/utils/hash.rs @@ -7,6 +7,5 @@ pub fn poseidon(l1: Fp, l2: Fp, r1: Fp, r2: Fp) -> Fp { const RATE: usize = 4; const L: usize = 4; - poseidon::Hash::<_, MySpec, ConstantLength, WIDTH, RATE>::init() - .hash([l1, l2, r1, r2]) + poseidon::Hash::, WIDTH, RATE>::init().hash([l1, l2, r1, r2]) } From de68737be8a47a14a9ebb8bba207815ceaf768d6 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 9 May 2023 12:06:50 +0200 Subject: [PATCH 22/25] update halo2 version and related dependencies --- Cargo.lock | 212 ++++++++++++++++++----------------- Cargo.toml | 4 +- src/chips/merkle_sum_tree.rs | 2 + src/circuits/tests.rs | 24 ++-- 4 files changed, 128 insertions(+), 114 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64491735..bb2b9ad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,9 +44,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "0.7.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +checksum = "67fc08ce920c31afb70f013dcce1bfc3a3195de6a228474e45e1f145b36f8d04" dependencies = [ "memchr", ] @@ -62,9 +62,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.70" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8ce5e0f9f8d88245311066a578d72b7af3e7088f32783804676302df237e4" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "ark-std" @@ -97,7 +97,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.14", + "syn 2.0.15", ] [[package]] @@ -113,9 +113,9 @@ dependencies = [ [[package]] name = "auto_impl" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a8c1df849285fbacd587de7818cc7d13be6cd2cbcd47a04fb1801b0e2706e33" +checksum = "fee3da8ef1276b0bee5dd1c7258010d8fffd31801447323115a25560e1327b89" dependencies = [ "proc-macro-error", "proc-macro2", @@ -341,9 +341,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.12.0" +version = "3.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" [[package]] name = "byte-slice-cast" @@ -531,7 +531,7 @@ dependencies = [ "serde", "serde_derive", "sha2 0.10.6", - "sha3 0.10.7", + "sha3 0.10.8", "thiserror", ] @@ -631,9 +631,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] @@ -782,7 +782,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.14", + "syn 2.0.15", ] [[package]] @@ -799,7 +799,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.14", + "syn 2.0.15", ] [[package]] @@ -1015,9 +1015,9 @@ dependencies = [ "crypto-bigint", "der", "digest 0.10.6", - "ff", + "ff 0.12.1", "generic-array 0.14.7", - "group", + "group 0.12.1", "pkcs8", "rand_core", "sec1", @@ -1042,7 +1042,7 @@ dependencies = [ "serde", "serde_json", "sha2 0.10.6", - "sha3 0.10.7", + "sha3 0.10.8", "thiserror", "uuid", ] @@ -1050,7 +1050,7 @@ dependencies = [ [[package]] name = "eth-types" version = "0.1.0" -source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#fa320ea0435320a8312a383dfa0eb9e8bcd61dc8" +source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#b8ae7af639af035a6ad5081716012ee2ea48e7cb" dependencies = [ "ethers-core", "ethers-signers", @@ -1065,7 +1065,7 @@ dependencies = [ "serde", "serde_json", "serde_with", - "sha3 0.10.7", + "sha3 0.10.8", "strum", "strum_macros", "subtle", @@ -1084,7 +1084,7 @@ dependencies = [ "regex", "serde", "serde_json", - "sha3 0.10.7", + "sha3 0.10.8", "thiserror", "uint", ] @@ -1209,6 +1209,16 @@ name = "ff" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "bitvec 1.0.1", "rand_core", @@ -1229,12 +1239,12 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", - "miniz_oxide 0.6.2", + "miniz_oxide", ] [[package]] @@ -1319,7 +1329,7 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "gadgets" version = "0.1.0" -source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#fa320ea0435320a8312a383dfa0eb9e8bcd61dc8" +source = "git+https://github.com/privacy-scaling-explorations/zkevm-circuits#b8ae7af639af035a6ad5081716012ee2ea48e7cb" dependencies = [ "digest 0.7.6", "eth-types", @@ -1385,7 +1395,18 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core", "subtle", ] @@ -1393,14 +1414,14 @@ dependencies = [ [[package]] name = "halo2_gadgets" version = "0.2.0" -source = "git+https://github.com/privacy-scaling-explorations/halo2?tag=v2023_02_02#0a8646b78286a13d320759b1c585262d6536dce4" +source = "git+https://github.com/privacy-scaling-explorations/halo2?tag=v2023_04_20#be955686f86eb618f55d2320c0e042485b313d22" dependencies = [ "arrayvec", "bitvec 1.0.1", - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "halo2_proofs", - "halo2curves 0.2.1", + "halo2curves", "lazy_static", "rand", "subtle", @@ -1410,13 +1431,14 @@ dependencies = [ [[package]] name = "halo2_proofs" version = "0.2.0" -source = "git+https://github.com/privacy-scaling-explorations/halo2?tag=v2023_02_02#0a8646b78286a13d320759b1c585262d6536dce4" +source = "git+https://github.com/privacy-scaling-explorations/halo2?tag=v2023_04_20#be955686f86eb618f55d2320c0e042485b313d22" dependencies = [ "blake2b_simd", - "ff", - "group", - "halo2curves 0.3.1", + "ff 0.13.0", + "group 0.13.0", + "halo2curves", "plotters", + "rand_chacha", "rand_core", "rayon", "sha3 0.9.1", @@ -1426,32 +1448,16 @@ dependencies = [ [[package]] name = "halo2curves" -version = "0.2.1" -source = "git+https://github.com/privacy-scaling-explorations/halo2curves?tag=0.3.0#83c72d49762343ffc9576ca11a2aa615efe1029b" -dependencies = [ - "ff", - "group", - "lazy_static", - "num-bigint", - "num-traits", - "pasta_curves", - "rand", - "rand_core", - "static_assertions", - "subtle", -] - -[[package]] -name = "halo2curves" -version = "0.3.1" -source = "git+https://github.com/privacy-scaling-explorations/halo2curves.git?tag=0.3.1#9b67e19bca30a35208b0c1b41c1723771e2c9f49" +version = "0.3.2" +source = "git+https://github.com/privacy-scaling-explorations/halo2curves?tag=0.3.2#9f5c50810bbefe779ee5cf1d852b2fe85dc35d5e" dependencies = [ - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "num-bigint", "num-traits", "pasta_curves", + "paste", "rand", "rand_core", "static_assertions", @@ -1669,14 +1675,14 @@ dependencies = [ "ecdsa", "elliptic-curve", "sha2 0.10.6", - "sha3 0.10.7", + "sha3 0.10.8", ] [[package]] name = "keccak" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] @@ -1686,12 +1692,15 @@ name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +dependencies = [ + "spin", +] [[package]] name = "libc" -version = "0.2.141" +version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" +checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" [[package]] name = "libloading" @@ -1784,15 +1793,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "miniz_oxide" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.7.1" @@ -1909,9 +1909,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parity-scale-codec" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "637935964ff85a605d114591d4d2c13c5d1ba2806dae97cea6bf180238a749ac" +checksum = "5ddb756ca205bd108aee3c62c6d3c994e1df84a59b9d6d4a5ea42ee1fd5a9a28" dependencies = [ "arrayvec", "bitvec 1.0.1", @@ -1957,19 +1957,25 @@ dependencies = [ [[package]] name = "pasta_curves" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" dependencies = [ "blake2b_simd", - "ff", - "group", + "ff 0.13.0", + "group 0.13.0", "lazy_static", "rand", "static_assertions", "subtle", ] +[[package]] +name = "paste" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" + [[package]] name = "pathfinder_geometry" version = "0.5.1" @@ -2012,9 +2018,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.5.7" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -2038,9 +2044,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plotters" @@ -2098,7 +2104,7 @@ dependencies = [ "crc32fast", "fdeflate", "flate2", - "miniz_oxide 0.7.1", + "miniz_oxide", ] [[package]] @@ -2194,9 +2200,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.26" +version = "1.0.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" dependencies = [ "proc-macro2", ] @@ -2287,9 +2293,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.3" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" dependencies = [ "aho-corasick", "memchr", @@ -2298,9 +2304,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.6.29" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" [[package]] name = "rend" @@ -2505,22 +2511,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.160" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.162" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.14", + "syn 2.0.15", ] [[package]] @@ -2618,9 +2624,9 @@ dependencies = [ [[package]] name = "sha3" -version = "0.10.7" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54c2bb1a323307527314a36bfb73f24febb08ce2b8a554bf4ffd6f51ad15198c" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest 0.10.6", "keccak", @@ -2648,6 +2654,12 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "spki" version = "0.6.0" @@ -2717,9 +2729,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.14" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcf316d5356ed6847742d036f8a39c3b8435cac10bd528a4bd461928a6ab34d5" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -2769,7 +2781,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.14", + "syn 2.0.15", ] [[package]] @@ -2832,13 +2844,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -3105,9 +3117,9 @@ checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winnow" -version = "0.4.1" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8970b36c66498d8ff1d66685dc86b91b29db0c7739899012f63a63814b4b28" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" dependencies = [ "memchr", ] diff --git a/Cargo.toml b/Cargo.toml index 5c29652b..0732ee13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ edition = "2021" dev-graph = ["halo2_proofs/dev-graph", "plotters"] [dependencies] -halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} -halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_02_02"} +halo2_proofs = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_04_20"} +halo2_gadgets = { git = "https://github.com/privacy-scaling-explorations/halo2", tag = "v2023_04_20"} gadgets = { git = "https://github.com/privacy-scaling-explorations/zkevm-circuits"} plotters = { version = "0.3.4", optional = true } rand = "0.8" diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index 625b5320..c19683b7 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -307,6 +307,8 @@ impl MerkleSumTreeChip { // Initiate chip config let chip = LtChip::construct(self.config.lt_config); + chip.load(&mut layouter)?; + layouter.assign_region( || "enforce sum to be less than total assets", |mut region| { diff --git a/src/circuits/tests.rs b/src/circuits/tests.rs index c2c4dd1d..bf34c0be 100644 --- a/src/circuits/tests.rs +++ b/src/circuits/tests.rs @@ -54,7 +54,7 @@ mod test { circuit.assets_sum, ]; - let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let valid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); valid_prover.assert_satisfied(); } @@ -77,7 +77,7 @@ mod test { circuit.assets_sum, ]; - let valid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let valid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); valid_prover.assert_satisfied(); } @@ -89,7 +89,7 @@ mod test { let circuit = instantiate_empty_circuit(); // we generate a universal trusted setup of our own for testing - let params = ParamsKZG::::setup(8, OsRng); + let params = ParamsKZG::::setup(9, OsRng); // we generate the verification key and the proving key // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys @@ -158,7 +158,7 @@ mod test { let circuit = instantiate_empty_circuit(); // we generate a universal trusted setup of our own for testing - let params = ParamsKZG::::setup(8, OsRng); + let params = ParamsKZG::::setup(9, OsRng); // we generate the verification key and the proving key // we use an empty circuit just to enphasize that the circuit input are not relevant when generating the keys @@ -201,7 +201,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ @@ -237,7 +237,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -276,7 +276,7 @@ mod test { assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), Err(vec![ @@ -312,7 +312,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -349,7 +349,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -438,7 +438,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -473,7 +473,7 @@ mod test { circuit.assets_sum, ]; - let invalid_prover = MockProver::run(8, &circuit, vec![public_input]).unwrap(); + let invalid_prover = MockProver::run(9, &circuit, vec![public_input]).unwrap(); assert_eq!( invalid_prover.verify(), @@ -489,7 +489,7 @@ mod test { ) .into(), location: FailureLocation::InRegion { - region: (17, "enforce sum to be less than total assets").into(), + region: (18, "enforce sum to be less than total assets").into(), offset: 0 }, cell_values: vec![ From 131a94bb1e69464c53002c1dbc1363034cc089e9 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 9 May 2023 14:32:46 +0200 Subject: [PATCH 23/25] docs: add instructions to print circuit in readme --- README.md | 2 +- prints/merkle-sum-tree-layout.png | Bin 650564 -> 651519 bytes 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2e8cb13c..aefbc45e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ This library makes use of the [PSE Fork of Halo2](https://github.com/privacy-sca ## Usage `cargo build` -`cargo test -- --nocapture` +`cargo test --features dev-graph -- --nocapture` ## Chips diff --git a/prints/merkle-sum-tree-layout.png b/prints/merkle-sum-tree-layout.png index 70b12220b4f6b822883b2d440f0eaa8509926280..f6b490970a47000b9a62c242ddf0ee5cc3008334 100644 GIT binary patch delta 68726 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�DbMkhhvD5 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�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 From f7bf20794cb845f982aef896bc726859e8cf81d3 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Wed, 10 May 2023 09:11:32 +0200 Subject: [PATCH 24/25] docs: renamed to Summa Solvency --- Cargo.lock | 70 +++++++++++++++++++++++++++--------------------------- Cargo.toml | 2 +- README.md | 2 +- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bb2b9ad1..338e9661 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,9 +341,9 @@ checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "3c6ed94e98ecff0c12dd1b04c15ec0d7d9458ca8fe806cea6f12954efe74c63b" [[package]] name = "byte-slice-cast" @@ -442,23 +442,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "circuits-halo2" -version = "0.1.0" -dependencies = [ - "ark-std", - "csv", - "gadgets", - "halo2_gadgets", - "halo2_proofs", - "hex", - "num-bigint", - "plotters", - "rand", - "serde", - "tabbycat", -] - [[package]] name = "cmake" version = "0.1.50" @@ -1658,9 +1641,9 @@ checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "68c16e1bfd491478ab155fd8b4896b86f9ede344949b641e61501e07c2b8b4d5" dependencies = [ "wasm-bindgen", ] @@ -2716,6 +2699,23 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +[[package]] +name = "summa-solvency" +version = "0.1.0" +dependencies = [ + "ark-std", + "csv", + "gadgets", + "halo2_gadgets", + "halo2_proofs", + "hex", + "num-bigint", + "plotters", + "rand", + "serde", + "tabbycat", +] + [[package]] name = "syn" version = "1.0.109" @@ -2950,9 +2950,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "5b6cb788c4e39112fbe1822277ef6fb3c55cd86b95cb3d3c4c1c9597e4ac74b4" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2960,24 +2960,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "35e522ed4105a9d626d885b35d62501b30d9666283a5c8be12c14a8bdafe7822" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "358a79a0cb89d21db8120cbfb91392335913e4890665b1a7981d9e956903b434" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2985,28 +2985,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "4783ce29f09b9d93134d41297aded3a712b7b979e9c6f28c32cb88c973a94869" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "a901d592cafaa4d711bc324edfaff879ac700b19c3dfd60058d2b445be2691eb" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "16b5f940c7edfdc6d12126d98c9ef4d1b3d470011c47c76a6581df47ad9ba721" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index 0732ee13..f64edb20 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "circuits-halo2" +name = "summa-solvency" version = "0.1.0" edition = "2021" diff --git a/README.md b/README.md index aefbc45e..ae301de1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Circuits - Halo2 +# Summa Solvency This repository contains the Halo2 circuit implementation for the Proof of Solvency protocol. From 6c7d2f8938ab782394c0ce4846ceb55f46e95509 Mon Sep 17 00:00:00 2001 From: Enrico Bottazzi <85900164+enricobottazzi@users.noreply.github.com> Date: Tue, 23 May 2023 12:01:19 +0200 Subject: [PATCH 25/25] fix: modify `swap constraint` --- src/chips/merkle_sum_tree.rs | 9 ++----- src/circuits/tests.rs | 46 +----------------------------------- 2 files changed, 3 insertions(+), 52 deletions(-) diff --git a/src/chips/merkle_sum_tree.rs b/src/chips/merkle_sum_tree.rs index c19683b7..9d8030a4 100644 --- a/src/chips/merkle_sum_tree.rs +++ b/src/chips/merkle_sum_tree.rs @@ -80,13 +80,8 @@ impl MerkleSumTreeChip { let r2 = meta.query_advice(col_d, Rotation::next()); vec![ - s.clone() - * (e.clone() * Expression::Constant(Fp::from(2)) * (c.clone() - a.clone()) - - (l1 - a) - - (c - r1)), - s * (e * Expression::Constant(Fp::from(2)) * (d.clone() - b.clone()) - - (l2 - b) - - (d - r2)), + s.clone() * e.clone() * ((l1 - a) - (c - r1)), + s * e * ((l2 - b) - (d - r2)), ] }); diff --git a/src/circuits/tests.rs b/src/circuits/tests.rs index bf34c0be..ab475319 100644 --- a/src/circuits/tests.rs +++ b/src/circuits/tests.rs @@ -332,7 +332,7 @@ mod test { ); } - // Passing a non binary index should fail the bool constraint check, the two swap constraints and the permutation check between the computed root hash and the instance column root hash + // Passing a non binary index should fail the bool constraint check and the permutation check between the computed root hash and the instance column root hash #[test] fn test_non_binary_index() { let assets_sum = Fp::from(556863u64); // greater than liabilities sum (556862) @@ -362,50 +362,6 @@ mod test { }, cell_values: vec![(((Any::advice(), 4).into(), 0).into(), "0x2".to_string()),] }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 0, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - }, - cell_values: vec![ - ( - ((Any::advice(), 0).into(), 0).into(), - "0x14b2e288bf66ce6fe38eb889a4f4c4e5c00e71b3b96caa9018bdf36c280a6be0" - .to_string() - ), - ( - ((Any::advice(), 0).into(), 1).into(), - "0xb92ac29c673ed3f380acdca783f2e6a9f62f27522cffd1a0a28bc952a7a755" - .to_string() - ), - ( - ((Any::advice(), 2).into(), 0).into(), - "0xb92ac29c673ed3f380acdca783f2e6a9f62f27522cffd1a0a28bc952a7a755" - .to_string() - ), - ( - ((Any::advice(), 2).into(), 1).into(), - "0x14b2e288bf66ce6fe38eb889a4f4c4e5c00e71b3b96caa9018bdf36c280a6be0" - .to_string() - ), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, - VerifyFailure::ConstraintNotSatisfied { - constraint: ((1, "swap constraint").into(), 1, "").into(), - location: FailureLocation::InRegion { - region: (1, "merkle prove layer").into(), - offset: 0 - }, - cell_values: vec![ - (((Any::advice(), 1).into(), 0).into(), "0x2e70".to_string()), - (((Any::advice(), 1).into(), 1).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 0).into(), "0x108ef".to_string()), - (((Any::advice(), 3).into(), 1).into(), "0x2e70".to_string()), - (((Any::advice(), 4).into(), 0).into(), "0x2".to_string()), - ] - }, VerifyFailure::Permutation { column: (Any::Instance, 0).into(), location: FailureLocation::OutsideRegion { row: 2 }