From c2a3ee2cae951e715e1d77e47bf0fc67b1232ae2 Mon Sep 17 00:00:00 2001 From: "enrico.eth" <85900164+enricobottazzi@users.noreply.github.com> Date: Fri, 1 Dec 2023 13:08:07 +0100 Subject: [PATCH] Mantainance (#202) --- backend/README.md | 2 +- backend/examples/summa_solvency_flow.rs | 8 +- backend/src/apis/csv_parser.rs | 2 +- backend/src/apis/mod.rs | 9 +- backend/src/apis/round.rs | 44 ++-- backend/src/contracts/abi/Summa.json | 2 +- .../src/contracts/generated/summa_contract.rs | 116 +++++------ backend/src/tests.rs | 12 +- contracts/scripts/deploy.ts | 6 +- contracts/src/Summa.sol | 8 +- contracts/test/Summa.ts | 2 +- .../merkle_sum_tree/csv => csv}/entry_16.csv | 0 .../csv => csv}/entry_16_bigints.csv | 0 .../csv => csv}/entry_16_modified.csv | 0 .../csv => csv}/entry_16_no_overflow.csv | 0 .../csv => csv}/entry_16_overflow.csv | 0 .../csv => csv}/entry_16_overflow_2.csv | 0 .../csv => csv}/entry_16_switched_order.csv | 0 {backend/src/apis/csv => csv}/signatures.csv | 0 .../examples => csv}/states/entry_16_1.csv | 0 .../examples => csv}/states/entry_16_2.csv | 0 .../examples => csv}/states/entry_16_3.csv | 0 .../examples => csv}/states/entry_16_4.csv | 0 .../examples => csv}/states/entry_16_5.csv | 0 zk_prover/README.md | 12 +- zk_prover/benches/full_solvency_flow.rs | 46 ++--- zk_prover/examples/gen_commitment.rs | 4 +- zk_prover/examples/gen_inclusion_verifier.rs | 46 ++++- .../inclusion_proof_solidity_calldata.json | 2 +- .../examples/nova_incremental_verifier.rs | 37 ++-- zk_prover/prints/mst-inclusion-layout.png | Bin 1842560 -> 1846594 bytes zk_prover/src/chips/merkle_sum_tree.rs | 16 +- zk_prover/src/chips/range/range_check.rs | 1 - .../circom/incremental_mst_inclusion.circom | 18 +- zk_prover/src/circom/merkle_sum_tree.circom | 112 +++++----- zk_prover/src/circuits/merkle_sum_tree.rs | 191 +++++++++--------- zk_prover/src/circuits/tests.rs | 75 +++---- zk_prover/src/merkle_sum_tree/entry.rs | 20 +- zk_prover/src/merkle_sum_tree/mod.rs | 18 +- zk_prover/src/merkle_sum_tree/mst.rs | 70 +++---- zk_prover/src/merkle_sum_tree/node.rs | 82 ++++---- zk_prover/src/merkle_sum_tree/tests.rs | 52 ++--- zk_prover/src/merkle_sum_tree/tree.rs | 74 +++---- .../src/merkle_sum_tree/utils/build_tree.rs | 40 ++-- .../src/merkle_sum_tree/utils/csv_parser.rs | 8 +- 45 files changed, 573 insertions(+), 562 deletions(-) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_bigints.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_modified.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_no_overflow.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_overflow.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_overflow_2.csv (100%) rename {zk_prover/src/merkle_sum_tree/csv => csv}/entry_16_switched_order.csv (100%) rename {backend/src/apis/csv => csv}/signatures.csv (100%) rename {zk_prover/examples => csv}/states/entry_16_1.csv (100%) rename {zk_prover/examples => csv}/states/entry_16_2.csv (100%) rename {zk_prover/examples => csv}/states/entry_16_3.csv (100%) rename {zk_prover/examples => csv}/states/entry_16_4.csv (100%) rename {zk_prover/examples => csv}/states/entry_16_5.csv (100%) diff --git a/backend/README.md b/backend/README.md index ced8177a..19b6aaa6 100644 --- a/backend/README.md +++ b/backend/README.md @@ -50,7 +50,7 @@ cargo test --release -- --nocapture ### Generating and updating verifier contract for Backend -The verifier contract in the backend were generated using a predefined set of parameters: `N_ASSETS = 2` and `N_BYTES=14`, as indicated [here](https://github.com/summa-dev/summa-solvency/blob/master/zk_prover/examples/gen_inclusion_verifier.rs#L21-L22). +The verifier contract in the backend were generated using a predefined set of parameters: `N_CURRENCIES = 2` and `N_BYTES=14`, as indicated [here](https://github.com/summa-dev/summa-solvency/blob/master/zk_prover/examples/gen_inclusion_verifier.rs#L21-L22). If you intend to work with different parameters, you'll need to adjust these hard-coded values and then generate new verifier contract. The process described below assists in both generating the verifier and updating the Summa contract, which integrates the new verifier as constructors. diff --git a/backend/examples/summa_solvency_flow.rs b/backend/examples/summa_solvency_flow.rs index 0199c5b4..4076b6a0 100644 --- a/backend/examples/summa_solvency_flow.rs +++ b/backend/examples/summa_solvency_flow.rs @@ -15,7 +15,7 @@ use summa_backend::{ }; use summa_solvency::merkle_sum_tree::MerkleSumTree; -const N_ASSETS: usize = 2; +const N_CURRENCIES: usize = 2; const USER_INDEX: usize = 0; #[tokio::main] @@ -43,7 +43,7 @@ async fn main() -> Result<(), Box> { .await?; // Each CEX prepares its own `signature` CSV file. - let signature_csv_path = "src/apis/csv/signatures.csv"; + let signature_csv_path = "../csv/signatures.csv"; let mut address_ownership_client = AddressOwnership::new(&signer, signature_csv_path).unwrap(); // Dispatch the proof of address ownership. @@ -58,7 +58,7 @@ async fn main() -> Result<(), Box> { // // Initialize the `Round` instance to submit the liability commitment. let params_path = "ptau/hermez-raw-11"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let entry_csv = "../csv/entry_16.csv"; let mst = MerkleSumTree::new(entry_csv).unwrap(); // Using the `round` instance, the commitment is dispatched to the Summa contract with the `dispatch_commitment` method. @@ -109,7 +109,7 @@ async fn main() -> Result<(), Box> { let leaf_hash = public_inputs[0]; assert_eq!( leaf_hash, - leaf_hash_from_inputs::(user_name.clone(), balances.clone()) + leaf_hash_from_inputs::(user_name.clone(), balances.clone()) ); // Get `mst_root` from contract. the `mst_root` is disptached by CEX with specific time `snapshot_time`. diff --git a/backend/src/apis/csv_parser.rs b/backend/src/apis/csv_parser.rs index ff65681a..672fd14d 100644 --- a/backend/src/apis/csv_parser.rs +++ b/backend/src/apis/csv_parser.rs @@ -52,7 +52,7 @@ mod tests { #[test] fn test_parse_csv_to_signature() { - let path = "src/apis/csv/signatures.csv"; + let path = "../csv/signatures.csv"; let address_ownership = parse_signature_csv(path).unwrap(); let first_address_ownership = AddressOwnershipProof { diff --git a/backend/src/apis/mod.rs b/backend/src/apis/mod.rs index 5f82f744..8cdc087b 100644 --- a/backend/src/apis/mod.rs +++ b/backend/src/apis/mod.rs @@ -7,9 +7,12 @@ use num_bigint::BigUint; use num_traits::Num; use summa_solvency::merkle_sum_tree::Entry; -pub fn leaf_hash_from_inputs(username: String, balances: Vec) -> U256 +pub fn leaf_hash_from_inputs( + username: String, + balances: Vec, +) -> U256 where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { // Convert balances to BigUint let balances: Vec = balances @@ -17,7 +20,7 @@ where .map(|balance| BigUint::from_str_radix(balance, 10).unwrap()) .collect(); - let entry: Entry = Entry::new(username, balances.try_into().unwrap()).unwrap(); + let entry: Entry = Entry::new(username, balances.try_into().unwrap()).unwrap(); // Convert Fp to U256 let hash_str = format!("{:?}", entry.compute_leaf().hash); diff --git a/backend/src/apis/round.rs b/backend/src/apis/round.rs index 30e8688b..dc6e495d 100644 --- a/backend/src/apis/round.rs +++ b/backend/src/apis/round.rs @@ -38,35 +38,35 @@ impl MstInclusionProof { } } -pub struct Snapshot { - pub mst: Box>, +pub struct Snapshot { + pub mst: Box>, trusted_setup: SetupArtifacts, } -pub struct Round<'a, const LEVELS: usize, const N_ASSETS: usize, const N_BYTES: usize> { +pub struct Round<'a, const LEVELS: usize, const N_CURRENCIES: usize, const N_BYTES: usize> { timestamp: u64, - snapshot: Snapshot, + snapshot: Snapshot, signer: &'a SummaSigner, } -impl - Round<'_, LEVELS, N_ASSETS, N_BYTES> +impl + Round<'_, LEVELS, N_CURRENCIES, N_BYTES> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { pub fn new<'a>( signer: &'a SummaSigner, - mst: Box>, + mst: Box>, params_path: &str, timestamp: u64, - ) -> Result, Box> + ) -> Result, Box> where - [(); N_ASSETS + 2]: Sized, + [(); N_CURRENCIES + 2]: Sized, { Ok(Round { timestamp, - snapshot: Snapshot::::new(mst, params_path).unwrap(), + snapshot: Snapshot::::new(mst, params_path).unwrap(), signer: &signer, }) } @@ -114,7 +114,7 @@ where user_index: usize, ) -> Result where - [(); N_ASSETS + 2]: Sized, + [(); N_CURRENCIES + 2]: Sized, { Ok(self .snapshot @@ -123,17 +123,17 @@ where } } -impl - Snapshot +impl + Snapshot where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { pub fn new( - mst: Box>, + mst: Box>, params_path: &str, - ) -> Result, Box> { - let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); + ) -> Result, Box> { + let mst_inclusion_circuit = MstInclusionCircuit::::init_empty(); // get k from ptau file name let parts: Vec<&str> = params_path.split("-").collect(); @@ -154,10 +154,10 @@ where user_index: usize, ) -> Result where - [(); N_ASSETS + 2]: Sized, + [(); N_CURRENCIES + 2]: Sized, { let merkle_proof = self.mst.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); // Currently, default manner of generating a inclusion proof for solidity-verifier. let calldata = gen_proof_solidity_calldata( diff --git a/backend/src/contracts/abi/Summa.json b/backend/src/contracts/abi/Summa.json index e64dc963..224b5e3f 100644 --- a/backend/src/contracts/abi/Summa.json +++ b/backend/src/contracts/abi/Summa.json @@ -1 +1 @@ -{"_format":"hh-sol-artifact-1","contractName":"Summa","sourceName":"src/Summa.sol","abi":[{"inputs":[{"internalType":"contract IVerifier","name":"_inclusionVerifier","type":"address"},{"internalType":"uint16","name":"mstLevels","type":"uint16"},{"internalType":"uint16","name":"assetsCount","type":"uint16"},{"internalType":"uint8","name":"balanceByteRange","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"indexed":false,"internalType":"struct Summa.AddressOwnershipProof[]","name":"addressOwnershipProofs","type":"tuple[]"}],"name":"AddressOwnershipProofSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"mstRoot","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"rootBalances","type":"uint256[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"chain","type":"string"}],"indexed":false,"internalType":"struct Summa.Cryptocurrency[]","name":"cryptocurrencies","type":"tuple[]"}],"name":"LiabilitiesCommitmentSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"addressOwnershipProofs","outputs":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"commitments","outputs":[{"internalType":"uint256","name":"mstRoot","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"config","outputs":[{"internalType":"uint16","name":"mstLevels","type":"uint16"},{"internalType":"uint16","name":"assetsCount","type":"uint16"},{"internalType":"uint8","name":"balanceByteRange","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"addressHash","type":"bytes32"}],"name":"getAddressOwnershipProof","outputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"internalType":"struct Summa.AddressOwnershipProof","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"mstRoot","type":"uint256"},{"internalType":"uint256[]","name":"rootBalances","type":"uint256[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"chain","type":"string"}],"internalType":"struct Summa.Cryptocurrency[]","name":"cryptocurrencies","type":"tuple[]"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"submitCommitment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"internalType":"struct Summa.AddressOwnershipProof[]","name":"_addressOwnershipProofs","type":"tuple[]"}],"name":"submitProofOfAddressOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"proof","type":"bytes"},{"internalType":"uint256[]","name":"publicInputs","type":"uint256[]"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"verifyInclusionProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x60a06040523480156200001157600080fd5b5060405162001cf938038062001cf9833981016040819052620000349162000110565b6200003f33620000a8565b6001600160a01b03939093166080526040805160608101825261ffff938416808252929093166020840181905260ff90941692018290526001805463ffffffff1916909117620100009093029290921760ff60201b191664010000000090910217905562000181565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805161ffff811681146200010b57600080fd5b919050565b600080600080608085870312156200012757600080fd5b84516001600160a01b03811681146200013f57600080fd5b93506200014f60208601620000f8565b92506200015f60408601620000f8565b9150606085015160ff811681146200017657600080fd5b939692955090935050565b608051611b5c6200019d6000396000610b5b0152611b5c6000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063a3c4bcf811610066578063a3c4bcf814610169578063c7ddca0e1461018c578063c8e581471461019f578063da64a750146101c2578063f2fde38b146101d557600080fd5b806319b33968146100a357806349ce8997146100cc578063715018a6146100fa57806379502c55146101045780638da5cb5b1461014e575b600080fd5b6100b66100b13660046111d3565b6101e8565b6040516100c391906112a7565b60405180910390f35b6100ec6100da3660046111d3565b60046020526000908152604090205481565b6040519081526020016100c3565b610102610500565b005b60015461012a9061ffff8082169162010000810490911690640100000000900460ff1683565b6040805161ffff948516815293909216602084015260ff16908201526060016100c3565b6000546040516001600160a01b0390911681526020016100c3565b61017c6101773660046111d3565b610514565b6040516100c394939291906112c1565b61010261019a366004611440565b610774565b6101b26101ad366004611605565b610a15565b60405190151581526020016100c3565b6101026101d0366004611672565b610bdb565b6101026101e33660046117b9565b610fa7565b6102136040518060800160405280606081526020016060815260200160608152602001606081525090565b60008281526003602052604090205461026a5760405162461bcd60e51b81526020600482015260146024820152731059191c995cdcc81b9bdd081d995c9a599a595960621b60448201526064015b60405180910390fd5b600082815260036020526040902054600290610288906001906117f8565b8154811061029857610298611811565b90600052602060002090600402016040518060800160405290816000820180546102c190611827565b80601f01602080910402602001604051908101604052809291908181526020018280546102ed90611827565b801561033a5780601f1061030f5761010080835404028352916020019161033a565b820191906000526020600020905b81548152906001019060200180831161031d57829003601f168201915b5050505050815260200160018201805461035390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461037f90611827565b80156103cc5780601f106103a1576101008083540402835291602001916103cc565b820191906000526020600020905b8154815290600101906020018083116103af57829003601f168201915b505050505081526020016002820180546103e590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461041190611827565b801561045e5780601f106104335761010080835404028352916020019161045e565b820191906000526020600020905b81548152906001019060200180831161044157829003601f168201915b5050505050815260200160038201805461047790611827565b80601f01602080910402602001604051908101604052809291908181526020018280546104a390611827565b80156104f05780601f106104c5576101008083540402835291602001916104f0565b820191906000526020600020905b8154815290600101906020018083116104d357829003601f168201915b5050505050815250509050919050565b610508611020565b610512600061107a565b565b6002818154811061052457600080fd5b906000526020600020906004020160009150905080600001805461054790611827565b80601f016020809104026020016040519081016040528092919081815260200182805461057390611827565b80156105c05780601f10610595576101008083540402835291602001916105c0565b820191906000526020600020905b8154815290600101906020018083116105a357829003601f168201915b5050505050908060010180546105d590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461060190611827565b801561064e5780601f106106235761010080835404028352916020019161064e565b820191906000526020600020905b81548152906001019060200180831161063157829003601f168201915b50505050509080600201805461066390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461068f90611827565b80156106dc5780601f106106b1576101008083540402835291602001916106dc565b820191906000526020600020905b8154815290600101906020018083116106bf57829003601f168201915b5050505050908060030180546106f190611827565b80601f016020809104026020016040519081016040528092919081815260200182805461071d90611827565b801561076a5780601f1061073f5761010080835404028352916020019161076a565b820191906000526020600020905b81548152906001019060200180831161074d57829003601f168201915b5050505050905084565b61077c611020565b60005b81518110156109da57600082828151811061079c5761079c611811565b6020026020010151600001516040516020016107b89190611861565b60408051601f19818403018152918152815160209283012060008181526003909352912054909150801561082e5760405162461bcd60e51b815260206004820152601860248201527f4164647265737320616c726561647920766572696669656400000000000000006044820152606401610261565b600284848151811061084257610842611811565b6020908102919091018101518254600181018455600093845291909220825160049092020190819061087490826118cc565b506020820151600182019061088990826118cc565b506040820151600282019061089e90826118cc565b50606082015160038201906108b390826118cc565b50506002546000848152600360205260409020555083518490849081106108dc576108dc611811565b60200260200101516000015151600014158015610918575083838151811061090657610906611811565b60200260200101516020015151600014155b8015610943575083838151811061093157610931611811565b60200260200101516040015151600014155b801561096e575083838151811061095c5761095c611811565b60200260200101516060015151600014155b6109c55760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642070726f6f66206f662061646472657373206f776e65727368604482015261069760f41b6064820152608401610261565b505080806109d29061198c565b91505061077f565b507f382315d4d56a6035e1899bffe77d9becefaf5f2650e4323b27854857a045465881604051610a0a91906119a5565b60405180910390a150565b600082600181518110610a2a57610a2a611811565b6020026020010151600460008481526020019081526020016000206000015414610a895760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b60025b8351811015610b4357838181518110610aa757610aa7611811565b602002602001015160046000858152602001908152602001600020600101600283610ad291906117f8565b81548110610ae257610ae2611811565b906000526020600020015414610b315760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420726f6f742062616c616e636560601b6044820152606401610261565b80610b3b8161198c565b915050610a8c565b50604051630bd205a960e41b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bd205a9090610b929086908890600401611a42565b602060405180830381865afa158015610baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd39190611a67565b949350505050565b610be3611020565b83600003610c265760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b8151835114610c955760405162461bcd60e51b815260206004820152603560248201527f526f6f74206c696162696c69746965732073756d7320616e64206c696162696c6044820152740d2e8d2cae640dceadac4cae440dad2e6dac2e8c6d605b1b6064820152608401610261565b6000825167ffffffffffffffff811115610cb157610cb1611319565b604051908082528060200260200182016040528015610ce457816020015b6060815260200190600190039081610ccf5790505b5090506000835167ffffffffffffffff811115610d0357610d03611319565b604051908082528060200260200182016040528015610d3657816020015b6060815260200190600190039081610d215790505b50905060005b8451811015610edc57848181518110610d5757610d57611811565b60200260200101516020015151600014158015610d935750848181518110610d8157610d81611811565b60200260200101516000015151600014155b610dd85760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642063727970746f63757272656e637960501b6044820152606401610261565b858181518110610dea57610dea611811565b6020026020010151600003610e535760405162461bcd60e51b815260206004820152602960248201527f416c6c20726f6f742073756d732073686f756c642062652067726561746572206044820152687468616e207a65726f60b81b6064820152608401610261565b848181518110610e6557610e65611811565b602002602001015160000151838281518110610e8357610e83611811565b6020026020010181905250848181518110610ea057610ea0611811565b602002602001015160200151828281518110610ebe57610ebe611811565b60200260200101819052508080610ed49061198c565b915050610d3c565b5060408051608081018252878152602080820188815282840186905260608301859052600087815260048352939093208251815592518051929392610f2792600185019201906110ca565b5060408201518051610f43916002840191602090910190611115565b5060608201518051610f5f916003840191602090910190611115565b50905050827f88bfc7389cb831ea0208ff106da6f5c9f88036ba084f1eb008d2788d3d45998d878787604051610f9793929190611a89565b60405180910390a2505050505050565b610faf611020565b6001600160a01b0381166110145760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610261565b61101d8161107a565b50565b6000546001600160a01b031633146105125760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610261565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b828054828255906000526020600020908101928215611105579160200282015b828111156111055782518255916020019190600101906110ea565b50611111929150611167565b5090565b82805482825590600052602060002090810192821561115b579160200282015b8281111561115b578251829061114b90826118cc565b5091602001919060010190611135565b5061111192915061117c565b5b808211156111115760008155600101611168565b808211156111115760006111908282611199565b5060010161117c565b5080546111a590611827565b6000825580601f106111b5575050565b601f01602090049060005260206000209081019061101d9190611167565b6000602082840312156111e557600080fd5b5035919050565b60005b838110156112075781810151838201526020016111ef565b50506000910152565b600081518084526112288160208601602086016111ec565b601f01601f19169290920160200192915050565b60008151608084526112516080850182611210565b90506020830151848203602086015261126a8282611210565b915050604083015184820360408601526112848282611210565b9150506060830151848203606086015261129e8282611210565b95945050505050565b6020815260006112ba602083018461123c565b9392505050565b6080815260006112d46080830187611210565b82810360208401526112e68187611210565b905082810360408401526112fa8186611210565b9050828103606084015261130e8185611210565b979650505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561135257611352611319565b60405290565b6040805190810167ffffffffffffffff8111828210171561135257611352611319565b604051601f8201601f1916810167ffffffffffffffff811182821017156113a4576113a4611319565b604052919050565b600067ffffffffffffffff8211156113c6576113c6611319565b5060051b60200190565b600082601f8301126113e157600080fd5b813567ffffffffffffffff8111156113fb576113fb611319565b61140e601f8201601f191660200161137b565b81815284602083860101111561142357600080fd5b816020850160208301376000918101602001919091529392505050565b6000602080838503121561145357600080fd5b823567ffffffffffffffff8082111561146b57600080fd5b818501915085601f83011261147f57600080fd5b813561149261148d826113ac565b61137b565b81815260059190911b830184019084810190888311156114b157600080fd5b8585015b83811015611592578035858111156114cc57600080fd5b86016080818c03601f190112156114e35760008081fd5b6114eb61132f565b88820135878111156114fd5760008081fd5b61150b8d8b838601016113d0565b825250604080830135888111156115225760008081fd5b6115308e8c838701016113d0565b8b84015250606080840135898111156115495760008081fd5b6115578f8d838801016113d0565b838501525060808401359150888211156115715760008081fd5b61157f8e8c848701016113d0565b90830152508452509186019186016114b5565b5098975050505050505050565b600082601f8301126115b057600080fd5b813560206115c061148d836113ac565b82815260059290921b840181019181810190868411156115df57600080fd5b8286015b848110156115fa57803583529183019183016115e3565b509695505050505050565b60008060006060848603121561161a57600080fd5b833567ffffffffffffffff8082111561163257600080fd5b61163e878388016113d0565b9450602086013591508082111561165457600080fd5b506116618682870161159f565b925050604084013590509250925092565b6000806000806080858703121561168857600080fd5b84359350602085013567ffffffffffffffff808211156116a757600080fd5b6116b38883890161159f565b945060408701359150808211156116c957600080fd5b818701915087601f8301126116dd57600080fd5b6116ea61148d83356113ac565b82358082526020808301929160051b8501018a81111561170957600080fd5b602085015b818110156117a557848135111561172457600080fd5b803586016040818e03601f1901121561173c57600080fd5b611744611358565b60208201358781111561175657600080fd5b6117658f6020838601016113d0565b82525060408201358781111561177a57600080fd5b6117898f6020838601016113d0565b602083015250808652505060208401935060208101905061170e565b50979a969950976060013596505050505050565b6000602082840312156117cb57600080fd5b81356001600160a01b03811681146112ba57600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561180b5761180b6117e2565b92915050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061183b57607f821691505b60208210810361185b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516118738184602087016111ec565b9190910192915050565b601f8211156118c757600081815260208120601f850160051c810160208610156118a45750805b601f850160051c820191505b818110156118c3578281556001016118b0565b5050505b505050565b815167ffffffffffffffff8111156118e6576118e6611319565b6118fa816118f48454611827565b8461187d565b602080601f83116001811461192f57600084156119175750858301515b600019600386901b1c1916600185901b1785556118c3565b600085815260208120601f198616915b8281101561195e5788860151825594840194600190910190840161193f565b508582101561197c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161199e5761199e6117e2565b5060010190565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156119fa57603f198886030184526119e885835161123c565b945092850192908501906001016119cc565b5092979650505050505050565b600081518084526020808501945080840160005b83811015611a3757815187529582019590820190600101611a1b565b509495945050505050565b604081526000611a556040830185611a07565b828103602084015261129e8185611210565b600060208284031215611a7957600080fd5b815180151581146112ba57600080fd5b83815260006020606081840152611aa36060840186611a07565b6040848203818601528186518084528484019150848160051b85010185890160005b83811015611b1557868303601f1901855281518051878552611ae988860182611210565b918a0151858303868c0152919050611b018183611210565b968a01969450505090870190600101611ac5565b50909b9a505050505050505050505056fea26469706673582212201433ee99272373b6723c253f90ffe01940889b9641f0849319f2f32a36cb145a64736f6c63430008120033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061009e5760003560e01c8063a3c4bcf811610066578063a3c4bcf814610169578063c7ddca0e1461018c578063c8e581471461019f578063da64a750146101c2578063f2fde38b146101d557600080fd5b806319b33968146100a357806349ce8997146100cc578063715018a6146100fa57806379502c55146101045780638da5cb5b1461014e575b600080fd5b6100b66100b13660046111d3565b6101e8565b6040516100c391906112a7565b60405180910390f35b6100ec6100da3660046111d3565b60046020526000908152604090205481565b6040519081526020016100c3565b610102610500565b005b60015461012a9061ffff8082169162010000810490911690640100000000900460ff1683565b6040805161ffff948516815293909216602084015260ff16908201526060016100c3565b6000546040516001600160a01b0390911681526020016100c3565b61017c6101773660046111d3565b610514565b6040516100c394939291906112c1565b61010261019a366004611440565b610774565b6101b26101ad366004611605565b610a15565b60405190151581526020016100c3565b6101026101d0366004611672565b610bdb565b6101026101e33660046117b9565b610fa7565b6102136040518060800160405280606081526020016060815260200160608152602001606081525090565b60008281526003602052604090205461026a5760405162461bcd60e51b81526020600482015260146024820152731059191c995cdcc81b9bdd081d995c9a599a595960621b60448201526064015b60405180910390fd5b600082815260036020526040902054600290610288906001906117f8565b8154811061029857610298611811565b90600052602060002090600402016040518060800160405290816000820180546102c190611827565b80601f01602080910402602001604051908101604052809291908181526020018280546102ed90611827565b801561033a5780601f1061030f5761010080835404028352916020019161033a565b820191906000526020600020905b81548152906001019060200180831161031d57829003601f168201915b5050505050815260200160018201805461035390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461037f90611827565b80156103cc5780601f106103a1576101008083540402835291602001916103cc565b820191906000526020600020905b8154815290600101906020018083116103af57829003601f168201915b505050505081526020016002820180546103e590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461041190611827565b801561045e5780601f106104335761010080835404028352916020019161045e565b820191906000526020600020905b81548152906001019060200180831161044157829003601f168201915b5050505050815260200160038201805461047790611827565b80601f01602080910402602001604051908101604052809291908181526020018280546104a390611827565b80156104f05780601f106104c5576101008083540402835291602001916104f0565b820191906000526020600020905b8154815290600101906020018083116104d357829003601f168201915b5050505050815250509050919050565b610508611020565b610512600061107a565b565b6002818154811061052457600080fd5b906000526020600020906004020160009150905080600001805461054790611827565b80601f016020809104026020016040519081016040528092919081815260200182805461057390611827565b80156105c05780601f10610595576101008083540402835291602001916105c0565b820191906000526020600020905b8154815290600101906020018083116105a357829003601f168201915b5050505050908060010180546105d590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461060190611827565b801561064e5780601f106106235761010080835404028352916020019161064e565b820191906000526020600020905b81548152906001019060200180831161063157829003601f168201915b50505050509080600201805461066390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461068f90611827565b80156106dc5780601f106106b1576101008083540402835291602001916106dc565b820191906000526020600020905b8154815290600101906020018083116106bf57829003601f168201915b5050505050908060030180546106f190611827565b80601f016020809104026020016040519081016040528092919081815260200182805461071d90611827565b801561076a5780601f1061073f5761010080835404028352916020019161076a565b820191906000526020600020905b81548152906001019060200180831161074d57829003601f168201915b5050505050905084565b61077c611020565b60005b81518110156109da57600082828151811061079c5761079c611811565b6020026020010151600001516040516020016107b89190611861565b60408051601f19818403018152918152815160209283012060008181526003909352912054909150801561082e5760405162461bcd60e51b815260206004820152601860248201527f4164647265737320616c726561647920766572696669656400000000000000006044820152606401610261565b600284848151811061084257610842611811565b6020908102919091018101518254600181018455600093845291909220825160049092020190819061087490826118cc565b506020820151600182019061088990826118cc565b506040820151600282019061089e90826118cc565b50606082015160038201906108b390826118cc565b50506002546000848152600360205260409020555083518490849081106108dc576108dc611811565b60200260200101516000015151600014158015610918575083838151811061090657610906611811565b60200260200101516020015151600014155b8015610943575083838151811061093157610931611811565b60200260200101516040015151600014155b801561096e575083838151811061095c5761095c611811565b60200260200101516060015151600014155b6109c55760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642070726f6f66206f662061646472657373206f776e65727368604482015261069760f41b6064820152608401610261565b505080806109d29061198c565b91505061077f565b507f382315d4d56a6035e1899bffe77d9becefaf5f2650e4323b27854857a045465881604051610a0a91906119a5565b60405180910390a150565b600082600181518110610a2a57610a2a611811565b6020026020010151600460008481526020019081526020016000206000015414610a895760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b60025b8351811015610b4357838181518110610aa757610aa7611811565b602002602001015160046000858152602001908152602001600020600101600283610ad291906117f8565b81548110610ae257610ae2611811565b906000526020600020015414610b315760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420726f6f742062616c616e636560601b6044820152606401610261565b80610b3b8161198c565b915050610a8c565b50604051630bd205a960e41b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bd205a9090610b929086908890600401611a42565b602060405180830381865afa158015610baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd39190611a67565b949350505050565b610be3611020565b83600003610c265760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b8151835114610c955760405162461bcd60e51b815260206004820152603560248201527f526f6f74206c696162696c69746965732073756d7320616e64206c696162696c6044820152740d2e8d2cae640dceadac4cae440dad2e6dac2e8c6d605b1b6064820152608401610261565b6000825167ffffffffffffffff811115610cb157610cb1611319565b604051908082528060200260200182016040528015610ce457816020015b6060815260200190600190039081610ccf5790505b5090506000835167ffffffffffffffff811115610d0357610d03611319565b604051908082528060200260200182016040528015610d3657816020015b6060815260200190600190039081610d215790505b50905060005b8451811015610edc57848181518110610d5757610d57611811565b60200260200101516020015151600014158015610d935750848181518110610d8157610d81611811565b60200260200101516000015151600014155b610dd85760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642063727970746f63757272656e637960501b6044820152606401610261565b858181518110610dea57610dea611811565b6020026020010151600003610e535760405162461bcd60e51b815260206004820152602960248201527f416c6c20726f6f742073756d732073686f756c642062652067726561746572206044820152687468616e207a65726f60b81b6064820152608401610261565b848181518110610e6557610e65611811565b602002602001015160000151838281518110610e8357610e83611811565b6020026020010181905250848181518110610ea057610ea0611811565b602002602001015160200151828281518110610ebe57610ebe611811565b60200260200101819052508080610ed49061198c565b915050610d3c565b5060408051608081018252878152602080820188815282840186905260608301859052600087815260048352939093208251815592518051929392610f2792600185019201906110ca565b5060408201518051610f43916002840191602090910190611115565b5060608201518051610f5f916003840191602090910190611115565b50905050827f88bfc7389cb831ea0208ff106da6f5c9f88036ba084f1eb008d2788d3d45998d878787604051610f9793929190611a89565b60405180910390a2505050505050565b610faf611020565b6001600160a01b0381166110145760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610261565b61101d8161107a565b50565b6000546001600160a01b031633146105125760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610261565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b828054828255906000526020600020908101928215611105579160200282015b828111156111055782518255916020019190600101906110ea565b50611111929150611167565b5090565b82805482825590600052602060002090810192821561115b579160200282015b8281111561115b578251829061114b90826118cc565b5091602001919060010190611135565b5061111192915061117c565b5b808211156111115760008155600101611168565b808211156111115760006111908282611199565b5060010161117c565b5080546111a590611827565b6000825580601f106111b5575050565b601f01602090049060005260206000209081019061101d9190611167565b6000602082840312156111e557600080fd5b5035919050565b60005b838110156112075781810151838201526020016111ef565b50506000910152565b600081518084526112288160208601602086016111ec565b601f01601f19169290920160200192915050565b60008151608084526112516080850182611210565b90506020830151848203602086015261126a8282611210565b915050604083015184820360408601526112848282611210565b9150506060830151848203606086015261129e8282611210565b95945050505050565b6020815260006112ba602083018461123c565b9392505050565b6080815260006112d46080830187611210565b82810360208401526112e68187611210565b905082810360408401526112fa8186611210565b9050828103606084015261130e8185611210565b979650505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561135257611352611319565b60405290565b6040805190810167ffffffffffffffff8111828210171561135257611352611319565b604051601f8201601f1916810167ffffffffffffffff811182821017156113a4576113a4611319565b604052919050565b600067ffffffffffffffff8211156113c6576113c6611319565b5060051b60200190565b600082601f8301126113e157600080fd5b813567ffffffffffffffff8111156113fb576113fb611319565b61140e601f8201601f191660200161137b565b81815284602083860101111561142357600080fd5b816020850160208301376000918101602001919091529392505050565b6000602080838503121561145357600080fd5b823567ffffffffffffffff8082111561146b57600080fd5b818501915085601f83011261147f57600080fd5b813561149261148d826113ac565b61137b565b81815260059190911b830184019084810190888311156114b157600080fd5b8585015b83811015611592578035858111156114cc57600080fd5b86016080818c03601f190112156114e35760008081fd5b6114eb61132f565b88820135878111156114fd5760008081fd5b61150b8d8b838601016113d0565b825250604080830135888111156115225760008081fd5b6115308e8c838701016113d0565b8b84015250606080840135898111156115495760008081fd5b6115578f8d838801016113d0565b838501525060808401359150888211156115715760008081fd5b61157f8e8c848701016113d0565b90830152508452509186019186016114b5565b5098975050505050505050565b600082601f8301126115b057600080fd5b813560206115c061148d836113ac565b82815260059290921b840181019181810190868411156115df57600080fd5b8286015b848110156115fa57803583529183019183016115e3565b509695505050505050565b60008060006060848603121561161a57600080fd5b833567ffffffffffffffff8082111561163257600080fd5b61163e878388016113d0565b9450602086013591508082111561165457600080fd5b506116618682870161159f565b925050604084013590509250925092565b6000806000806080858703121561168857600080fd5b84359350602085013567ffffffffffffffff808211156116a757600080fd5b6116b38883890161159f565b945060408701359150808211156116c957600080fd5b818701915087601f8301126116dd57600080fd5b6116ea61148d83356113ac565b82358082526020808301929160051b8501018a81111561170957600080fd5b602085015b818110156117a557848135111561172457600080fd5b803586016040818e03601f1901121561173c57600080fd5b611744611358565b60208201358781111561175657600080fd5b6117658f6020838601016113d0565b82525060408201358781111561177a57600080fd5b6117898f6020838601016113d0565b602083015250808652505060208401935060208101905061170e565b50979a969950976060013596505050505050565b6000602082840312156117cb57600080fd5b81356001600160a01b03811681146112ba57600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561180b5761180b6117e2565b92915050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061183b57607f821691505b60208210810361185b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516118738184602087016111ec565b9190910192915050565b601f8211156118c757600081815260208120601f850160051c810160208610156118a45750805b601f850160051c820191505b818110156118c3578281556001016118b0565b5050505b505050565b815167ffffffffffffffff8111156118e6576118e6611319565b6118fa816118f48454611827565b8461187d565b602080601f83116001811461192f57600084156119175750858301515b600019600386901b1c1916600185901b1785556118c3565b600085815260208120601f198616915b8281101561195e5788860151825594840194600190910190840161193f565b508582101561197c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161199e5761199e6117e2565b5060010190565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156119fa57603f198886030184526119e885835161123c565b945092850192908501906001016119cc565b5092979650505050505050565b600081518084526020808501945080840160005b83811015611a3757815187529582019590820190600101611a1b565b509495945050505050565b604081526000611a556040830185611a07565b828103602084015261129e8185611210565b600060208284031215611a7957600080fd5b815180151581146112ba57600080fd5b83815260006020606081840152611aa36060840186611a07565b6040848203818601528186518084528484019150848160051b85010185890160005b83811015611b1557868303601f1901855281518051878552611ae988860182611210565b918a0151858303868c0152919050611b018183611210565b968a01969450505090870190600101611ac5565b50909b9a505050505050505050505056fea26469706673582212201433ee99272373b6723c253f90ffe01940889b9641f0849319f2f32a36cb145a64736f6c63430008120033","linkReferences":{},"deployedLinkReferences":{}} \ No newline at end of file +{"_format":"hh-sol-artifact-1","contractName":"Summa","sourceName":"src/Summa.sol","abi":[{"inputs":[{"internalType":"contract IVerifier","name":"_inclusionVerifier","type":"address"},{"internalType":"uint16","name":"mstLevels","type":"uint16"},{"internalType":"uint16","name":"currenciesCount","type":"uint16"},{"internalType":"uint8","name":"balanceByteRange","type":"uint8"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"indexed":false,"internalType":"struct Summa.AddressOwnershipProof[]","name":"addressOwnershipProofs","type":"tuple[]"}],"name":"AddressOwnershipProofSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"timestamp","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"mstRoot","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"rootBalances","type":"uint256[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"chain","type":"string"}],"indexed":false,"internalType":"struct Summa.Cryptocurrency[]","name":"cryptocurrencies","type":"tuple[]"}],"name":"LiabilitiesCommitmentSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"addressOwnershipProofs","outputs":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"commitments","outputs":[{"internalType":"uint256","name":"mstRoot","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"config","outputs":[{"internalType":"uint16","name":"mstLevels","type":"uint16"},{"internalType":"uint16","name":"currenciesCount","type":"uint16"},{"internalType":"uint8","name":"balanceByteRange","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"addressHash","type":"bytes32"}],"name":"getAddressOwnershipProof","outputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"internalType":"struct Summa.AddressOwnershipProof","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"mstRoot","type":"uint256"},{"internalType":"uint256[]","name":"rootBalances","type":"uint256[]"},{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"chain","type":"string"}],"internalType":"struct Summa.Cryptocurrency[]","name":"cryptocurrencies","type":"tuple[]"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"submitCommitment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"cexAddress","type":"string"},{"internalType":"string","name":"chain","type":"string"},{"internalType":"bytes","name":"signature","type":"bytes"},{"internalType":"bytes","name":"message","type":"bytes"}],"internalType":"struct Summa.AddressOwnershipProof[]","name":"_addressOwnershipProofs","type":"tuple[]"}],"name":"submitProofOfAddressOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"proof","type":"bytes"},{"internalType":"uint256[]","name":"publicInputs","type":"uint256[]"},{"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"verifyInclusionProof","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}],"bytecode":"0x60a06040523480156200001157600080fd5b5060405162001cf938038062001cf9833981016040819052620000349162000110565b6200003f33620000a8565b6001600160a01b03939093166080526040805160608101825261ffff938416808252929093166020840181905260ff90941692018290526001805463ffffffff1916909117620100009093029290921760ff60201b191664010000000090910217905562000181565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b805161ffff811681146200010b57600080fd5b919050565b600080600080608085870312156200012757600080fd5b84516001600160a01b03811681146200013f57600080fd5b93506200014f60208601620000f8565b92506200015f60408601620000f8565b9150606085015160ff811681146200017657600080fd5b939692955090935050565b608051611b5c6200019d6000396000610b5b0152611b5c6000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063a3c4bcf811610066578063a3c4bcf814610169578063c7ddca0e1461018c578063c8e581471461019f578063da64a750146101c2578063f2fde38b146101d557600080fd5b806319b33968146100a357806349ce8997146100cc578063715018a6146100fa57806379502c55146101045780638da5cb5b1461014e575b600080fd5b6100b66100b13660046111d3565b6101e8565b6040516100c391906112a7565b60405180910390f35b6100ec6100da3660046111d3565b60046020526000908152604090205481565b6040519081526020016100c3565b610102610500565b005b60015461012a9061ffff8082169162010000810490911690640100000000900460ff1683565b6040805161ffff948516815293909216602084015260ff16908201526060016100c3565b6000546040516001600160a01b0390911681526020016100c3565b61017c6101773660046111d3565b610514565b6040516100c394939291906112c1565b61010261019a366004611440565b610774565b6101b26101ad366004611605565b610a15565b60405190151581526020016100c3565b6101026101d0366004611672565b610bdb565b6101026101e33660046117b9565b610fa7565b6102136040518060800160405280606081526020016060815260200160608152602001606081525090565b60008281526003602052604090205461026a5760405162461bcd60e51b81526020600482015260146024820152731059191c995cdcc81b9bdd081d995c9a599a595960621b60448201526064015b60405180910390fd5b600082815260036020526040902054600290610288906001906117f8565b8154811061029857610298611811565b90600052602060002090600402016040518060800160405290816000820180546102c190611827565b80601f01602080910402602001604051908101604052809291908181526020018280546102ed90611827565b801561033a5780601f1061030f5761010080835404028352916020019161033a565b820191906000526020600020905b81548152906001019060200180831161031d57829003601f168201915b5050505050815260200160018201805461035390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461037f90611827565b80156103cc5780601f106103a1576101008083540402835291602001916103cc565b820191906000526020600020905b8154815290600101906020018083116103af57829003601f168201915b505050505081526020016002820180546103e590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461041190611827565b801561045e5780601f106104335761010080835404028352916020019161045e565b820191906000526020600020905b81548152906001019060200180831161044157829003601f168201915b5050505050815260200160038201805461047790611827565b80601f01602080910402602001604051908101604052809291908181526020018280546104a390611827565b80156104f05780601f106104c5576101008083540402835291602001916104f0565b820191906000526020600020905b8154815290600101906020018083116104d357829003601f168201915b5050505050815250509050919050565b610508611020565b610512600061107a565b565b6002818154811061052457600080fd5b906000526020600020906004020160009150905080600001805461054790611827565b80601f016020809104026020016040519081016040528092919081815260200182805461057390611827565b80156105c05780601f10610595576101008083540402835291602001916105c0565b820191906000526020600020905b8154815290600101906020018083116105a357829003601f168201915b5050505050908060010180546105d590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461060190611827565b801561064e5780601f106106235761010080835404028352916020019161064e565b820191906000526020600020905b81548152906001019060200180831161063157829003601f168201915b50505050509080600201805461066390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461068f90611827565b80156106dc5780601f106106b1576101008083540402835291602001916106dc565b820191906000526020600020905b8154815290600101906020018083116106bf57829003601f168201915b5050505050908060030180546106f190611827565b80601f016020809104026020016040519081016040528092919081815260200182805461071d90611827565b801561076a5780601f1061073f5761010080835404028352916020019161076a565b820191906000526020600020905b81548152906001019060200180831161074d57829003601f168201915b5050505050905084565b61077c611020565b60005b81518110156109da57600082828151811061079c5761079c611811565b6020026020010151600001516040516020016107b89190611861565b60408051601f19818403018152918152815160209283012060008181526003909352912054909150801561082e5760405162461bcd60e51b815260206004820152601860248201527f4164647265737320616c726561647920766572696669656400000000000000006044820152606401610261565b600284848151811061084257610842611811565b6020908102919091018101518254600181018455600093845291909220825160049092020190819061087490826118cc565b506020820151600182019061088990826118cc565b506040820151600282019061089e90826118cc565b50606082015160038201906108b390826118cc565b50506002546000848152600360205260409020555083518490849081106108dc576108dc611811565b60200260200101516000015151600014158015610918575083838151811061090657610906611811565b60200260200101516020015151600014155b8015610943575083838151811061093157610931611811565b60200260200101516040015151600014155b801561096e575083838151811061095c5761095c611811565b60200260200101516060015151600014155b6109c55760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642070726f6f66206f662061646472657373206f776e65727368604482015261069760f41b6064820152608401610261565b505080806109d29061198c565b91505061077f565b507f382315d4d56a6035e1899bffe77d9becefaf5f2650e4323b27854857a045465881604051610a0a91906119a5565b60405180910390a150565b600082600181518110610a2a57610a2a611811565b6020026020010151600460008481526020019081526020016000206000015414610a895760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b60025b8351811015610b4357838181518110610aa757610aa7611811565b602002602001015160046000858152602001908152602001600020600101600283610ad291906117f8565b81548110610ae257610ae2611811565b906000526020600020015414610b315760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420726f6f742062616c616e636560601b6044820152606401610261565b80610b3b8161198c565b915050610a8c565b50604051630bd205a960e41b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bd205a9090610b929086908890600401611a42565b602060405180830381865afa158015610baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd39190611a67565b949350505050565b610be3611020565b83600003610c265760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b8151835114610c955760405162461bcd60e51b815260206004820152603560248201527f526f6f74206c696162696c69746965732073756d7320616e64206c696162696c6044820152740d2e8d2cae640dceadac4cae440dad2e6dac2e8c6d605b1b6064820152608401610261565b6000825167ffffffffffffffff811115610cb157610cb1611319565b604051908082528060200260200182016040528015610ce457816020015b6060815260200190600190039081610ccf5790505b5090506000835167ffffffffffffffff811115610d0357610d03611319565b604051908082528060200260200182016040528015610d3657816020015b6060815260200190600190039081610d215790505b50905060005b8451811015610edc57848181518110610d5757610d57611811565b60200260200101516020015151600014158015610d935750848181518110610d8157610d81611811565b60200260200101516000015151600014155b610dd85760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642063727970746f63757272656e637960501b6044820152606401610261565b858181518110610dea57610dea611811565b6020026020010151600003610e535760405162461bcd60e51b815260206004820152602960248201527f416c6c20726f6f742073756d732073686f756c642062652067726561746572206044820152687468616e207a65726f60b81b6064820152608401610261565b848181518110610e6557610e65611811565b602002602001015160000151838281518110610e8357610e83611811565b6020026020010181905250848181518110610ea057610ea0611811565b602002602001015160200151828281518110610ebe57610ebe611811565b60200260200101819052508080610ed49061198c565b915050610d3c565b5060408051608081018252878152602080820188815282840186905260608301859052600087815260048352939093208251815592518051929392610f2792600185019201906110ca565b5060408201518051610f43916002840191602090910190611115565b5060608201518051610f5f916003840191602090910190611115565b50905050827f88bfc7389cb831ea0208ff106da6f5c9f88036ba084f1eb008d2788d3d45998d878787604051610f9793929190611a89565b60405180910390a2505050505050565b610faf611020565b6001600160a01b0381166110145760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610261565b61101d8161107a565b50565b6000546001600160a01b031633146105125760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610261565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b828054828255906000526020600020908101928215611105579160200282015b828111156111055782518255916020019190600101906110ea565b50611111929150611167565b5090565b82805482825590600052602060002090810192821561115b579160200282015b8281111561115b578251829061114b90826118cc565b5091602001919060010190611135565b5061111192915061117c565b5b808211156111115760008155600101611168565b808211156111115760006111908282611199565b5060010161117c565b5080546111a590611827565b6000825580601f106111b5575050565b601f01602090049060005260206000209081019061101d9190611167565b6000602082840312156111e557600080fd5b5035919050565b60005b838110156112075781810151838201526020016111ef565b50506000910152565b600081518084526112288160208601602086016111ec565b601f01601f19169290920160200192915050565b60008151608084526112516080850182611210565b90506020830151848203602086015261126a8282611210565b915050604083015184820360408601526112848282611210565b9150506060830151848203606086015261129e8282611210565b95945050505050565b6020815260006112ba602083018461123c565b9392505050565b6080815260006112d46080830187611210565b82810360208401526112e68187611210565b905082810360408401526112fa8186611210565b9050828103606084015261130e8185611210565b979650505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561135257611352611319565b60405290565b6040805190810167ffffffffffffffff8111828210171561135257611352611319565b604051601f8201601f1916810167ffffffffffffffff811182821017156113a4576113a4611319565b604052919050565b600067ffffffffffffffff8211156113c6576113c6611319565b5060051b60200190565b600082601f8301126113e157600080fd5b813567ffffffffffffffff8111156113fb576113fb611319565b61140e601f8201601f191660200161137b565b81815284602083860101111561142357600080fd5b816020850160208301376000918101602001919091529392505050565b6000602080838503121561145357600080fd5b823567ffffffffffffffff8082111561146b57600080fd5b818501915085601f83011261147f57600080fd5b813561149261148d826113ac565b61137b565b81815260059190911b830184019084810190888311156114b157600080fd5b8585015b83811015611592578035858111156114cc57600080fd5b86016080818c03601f190112156114e35760008081fd5b6114eb61132f565b88820135878111156114fd5760008081fd5b61150b8d8b838601016113d0565b825250604080830135888111156115225760008081fd5b6115308e8c838701016113d0565b8b84015250606080840135898111156115495760008081fd5b6115578f8d838801016113d0565b838501525060808401359150888211156115715760008081fd5b61157f8e8c848701016113d0565b90830152508452509186019186016114b5565b5098975050505050505050565b600082601f8301126115b057600080fd5b813560206115c061148d836113ac565b82815260059290921b840181019181810190868411156115df57600080fd5b8286015b848110156115fa57803583529183019183016115e3565b509695505050505050565b60008060006060848603121561161a57600080fd5b833567ffffffffffffffff8082111561163257600080fd5b61163e878388016113d0565b9450602086013591508082111561165457600080fd5b506116618682870161159f565b925050604084013590509250925092565b6000806000806080858703121561168857600080fd5b84359350602085013567ffffffffffffffff808211156116a757600080fd5b6116b38883890161159f565b945060408701359150808211156116c957600080fd5b818701915087601f8301126116dd57600080fd5b6116ea61148d83356113ac565b82358082526020808301929160051b8501018a81111561170957600080fd5b602085015b818110156117a557848135111561172457600080fd5b803586016040818e03601f1901121561173c57600080fd5b611744611358565b60208201358781111561175657600080fd5b6117658f6020838601016113d0565b82525060408201358781111561177a57600080fd5b6117898f6020838601016113d0565b602083015250808652505060208401935060208101905061170e565b50979a969950976060013596505050505050565b6000602082840312156117cb57600080fd5b81356001600160a01b03811681146112ba57600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561180b5761180b6117e2565b92915050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061183b57607f821691505b60208210810361185b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516118738184602087016111ec565b9190910192915050565b601f8211156118c757600081815260208120601f850160051c810160208610156118a45750805b601f850160051c820191505b818110156118c3578281556001016118b0565b5050505b505050565b815167ffffffffffffffff8111156118e6576118e6611319565b6118fa816118f48454611827565b8461187d565b602080601f83116001811461192f57600084156119175750858301515b600019600386901b1c1916600185901b1785556118c3565b600085815260208120601f198616915b8281101561195e5788860151825594840194600190910190840161193f565b508582101561197c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161199e5761199e6117e2565b5060010190565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156119fa57603f198886030184526119e885835161123c565b945092850192908501906001016119cc565b5092979650505050505050565b600081518084526020808501945080840160005b83811015611a3757815187529582019590820190600101611a1b565b509495945050505050565b604081526000611a556040830185611a07565b828103602084015261129e8185611210565b600060208284031215611a7957600080fd5b815180151581146112ba57600080fd5b83815260006020606081840152611aa36060840186611a07565b6040848203818601528186518084528484019150848160051b85010185890160005b83811015611b1557868303601f1901855281518051878552611ae988860182611210565b918a0151858303868c0152919050611b018183611210565b968a01969450505090870190600101611ac5565b50909b9a505050505050505050505056fea264697066735822122045d9336f0b6314796496c7a1cf160c19b42a61943a90fb5bbe2cfcef107bb5db64736f6c63430008120033","deployedBytecode":"0x608060405234801561001057600080fd5b506004361061009e5760003560e01c8063a3c4bcf811610066578063a3c4bcf814610169578063c7ddca0e1461018c578063c8e581471461019f578063da64a750146101c2578063f2fde38b146101d557600080fd5b806319b33968146100a357806349ce8997146100cc578063715018a6146100fa57806379502c55146101045780638da5cb5b1461014e575b600080fd5b6100b66100b13660046111d3565b6101e8565b6040516100c391906112a7565b60405180910390f35b6100ec6100da3660046111d3565b60046020526000908152604090205481565b6040519081526020016100c3565b610102610500565b005b60015461012a9061ffff8082169162010000810490911690640100000000900460ff1683565b6040805161ffff948516815293909216602084015260ff16908201526060016100c3565b6000546040516001600160a01b0390911681526020016100c3565b61017c6101773660046111d3565b610514565b6040516100c394939291906112c1565b61010261019a366004611440565b610774565b6101b26101ad366004611605565b610a15565b60405190151581526020016100c3565b6101026101d0366004611672565b610bdb565b6101026101e33660046117b9565b610fa7565b6102136040518060800160405280606081526020016060815260200160608152602001606081525090565b60008281526003602052604090205461026a5760405162461bcd60e51b81526020600482015260146024820152731059191c995cdcc81b9bdd081d995c9a599a595960621b60448201526064015b60405180910390fd5b600082815260036020526040902054600290610288906001906117f8565b8154811061029857610298611811565b90600052602060002090600402016040518060800160405290816000820180546102c190611827565b80601f01602080910402602001604051908101604052809291908181526020018280546102ed90611827565b801561033a5780601f1061030f5761010080835404028352916020019161033a565b820191906000526020600020905b81548152906001019060200180831161031d57829003601f168201915b5050505050815260200160018201805461035390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461037f90611827565b80156103cc5780601f106103a1576101008083540402835291602001916103cc565b820191906000526020600020905b8154815290600101906020018083116103af57829003601f168201915b505050505081526020016002820180546103e590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461041190611827565b801561045e5780601f106104335761010080835404028352916020019161045e565b820191906000526020600020905b81548152906001019060200180831161044157829003601f168201915b5050505050815260200160038201805461047790611827565b80601f01602080910402602001604051908101604052809291908181526020018280546104a390611827565b80156104f05780601f106104c5576101008083540402835291602001916104f0565b820191906000526020600020905b8154815290600101906020018083116104d357829003601f168201915b5050505050815250509050919050565b610508611020565b610512600061107a565b565b6002818154811061052457600080fd5b906000526020600020906004020160009150905080600001805461054790611827565b80601f016020809104026020016040519081016040528092919081815260200182805461057390611827565b80156105c05780601f10610595576101008083540402835291602001916105c0565b820191906000526020600020905b8154815290600101906020018083116105a357829003601f168201915b5050505050908060010180546105d590611827565b80601f016020809104026020016040519081016040528092919081815260200182805461060190611827565b801561064e5780601f106106235761010080835404028352916020019161064e565b820191906000526020600020905b81548152906001019060200180831161063157829003601f168201915b50505050509080600201805461066390611827565b80601f016020809104026020016040519081016040528092919081815260200182805461068f90611827565b80156106dc5780601f106106b1576101008083540402835291602001916106dc565b820191906000526020600020905b8154815290600101906020018083116106bf57829003601f168201915b5050505050908060030180546106f190611827565b80601f016020809104026020016040519081016040528092919081815260200182805461071d90611827565b801561076a5780601f1061073f5761010080835404028352916020019161076a565b820191906000526020600020905b81548152906001019060200180831161074d57829003601f168201915b5050505050905084565b61077c611020565b60005b81518110156109da57600082828151811061079c5761079c611811565b6020026020010151600001516040516020016107b89190611861565b60408051601f19818403018152918152815160209283012060008181526003909352912054909150801561082e5760405162461bcd60e51b815260206004820152601860248201527f4164647265737320616c726561647920766572696669656400000000000000006044820152606401610261565b600284848151811061084257610842611811565b6020908102919091018101518254600181018455600093845291909220825160049092020190819061087490826118cc565b506020820151600182019061088990826118cc565b506040820151600282019061089e90826118cc565b50606082015160038201906108b390826118cc565b50506002546000848152600360205260409020555083518490849081106108dc576108dc611811565b60200260200101516000015151600014158015610918575083838151811061090657610906611811565b60200260200101516020015151600014155b8015610943575083838151811061093157610931611811565b60200260200101516040015151600014155b801561096e575083838151811061095c5761095c611811565b60200260200101516060015151600014155b6109c55760405162461bcd60e51b815260206004820152602260248201527f496e76616c69642070726f6f66206f662061646472657373206f776e65727368604482015261069760f41b6064820152608401610261565b505080806109d29061198c565b91505061077f565b507f382315d4d56a6035e1899bffe77d9becefaf5f2650e4323b27854857a045465881604051610a0a91906119a5565b60405180910390a150565b600082600181518110610a2a57610a2a611811565b6020026020010151600460008481526020019081526020016000206000015414610a895760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b60025b8351811015610b4357838181518110610aa757610aa7611811565b602002602001015160046000858152602001908152602001600020600101600283610ad291906117f8565b81548110610ae257610ae2611811565b906000526020600020015414610b315760405162461bcd60e51b8152602060048201526014602482015273496e76616c696420726f6f742062616c616e636560601b6044820152606401610261565b80610b3b8161198c565b915050610a8c565b50604051630bd205a960e41b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063bd205a9090610b929086908890600401611a42565b602060405180830381865afa158015610baf573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610bd39190611a67565b949350505050565b610be3611020565b83600003610c265760405162461bcd60e51b815260206004820152601060248201526f125b9d985b1a59081354d5081c9bdbdd60821b6044820152606401610261565b8151835114610c955760405162461bcd60e51b815260206004820152603560248201527f526f6f74206c696162696c69746965732073756d7320616e64206c696162696c6044820152740d2e8d2cae640dceadac4cae440dad2e6dac2e8c6d605b1b6064820152608401610261565b6000825167ffffffffffffffff811115610cb157610cb1611319565b604051908082528060200260200182016040528015610ce457816020015b6060815260200190600190039081610ccf5790505b5090506000835167ffffffffffffffff811115610d0357610d03611319565b604051908082528060200260200182016040528015610d3657816020015b6060815260200190600190039081610d215790505b50905060005b8451811015610edc57848181518110610d5757610d57611811565b60200260200101516020015151600014158015610d935750848181518110610d8157610d81611811565b60200260200101516000015151600014155b610dd85760405162461bcd60e51b8152602060048201526016602482015275496e76616c69642063727970746f63757272656e637960501b6044820152606401610261565b858181518110610dea57610dea611811565b6020026020010151600003610e535760405162461bcd60e51b815260206004820152602960248201527f416c6c20726f6f742073756d732073686f756c642062652067726561746572206044820152687468616e207a65726f60b81b6064820152608401610261565b848181518110610e6557610e65611811565b602002602001015160000151838281518110610e8357610e83611811565b6020026020010181905250848181518110610ea057610ea0611811565b602002602001015160200151828281518110610ebe57610ebe611811565b60200260200101819052508080610ed49061198c565b915050610d3c565b5060408051608081018252878152602080820188815282840186905260608301859052600087815260048352939093208251815592518051929392610f2792600185019201906110ca565b5060408201518051610f43916002840191602090910190611115565b5060608201518051610f5f916003840191602090910190611115565b50905050827f88bfc7389cb831ea0208ff106da6f5c9f88036ba084f1eb008d2788d3d45998d878787604051610f9793929190611a89565b60405180910390a2505050505050565b610faf611020565b6001600160a01b0381166110145760405162461bcd60e51b815260206004820152602660248201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160448201526564647265737360d01b6064820152608401610261565b61101d8161107a565b50565b6000546001600160a01b031633146105125760405162461bcd60e51b815260206004820181905260248201527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65726044820152606401610261565b600080546001600160a01b038381166001600160a01b0319831681178455604051919092169283917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e09190a35050565b828054828255906000526020600020908101928215611105579160200282015b828111156111055782518255916020019190600101906110ea565b50611111929150611167565b5090565b82805482825590600052602060002090810192821561115b579160200282015b8281111561115b578251829061114b90826118cc565b5091602001919060010190611135565b5061111192915061117c565b5b808211156111115760008155600101611168565b808211156111115760006111908282611199565b5060010161117c565b5080546111a590611827565b6000825580601f106111b5575050565b601f01602090049060005260206000209081019061101d9190611167565b6000602082840312156111e557600080fd5b5035919050565b60005b838110156112075781810151838201526020016111ef565b50506000910152565b600081518084526112288160208601602086016111ec565b601f01601f19169290920160200192915050565b60008151608084526112516080850182611210565b90506020830151848203602086015261126a8282611210565b915050604083015184820360408601526112848282611210565b9150506060830151848203606086015261129e8282611210565b95945050505050565b6020815260006112ba602083018461123c565b9392505050565b6080815260006112d46080830187611210565b82810360208401526112e68187611210565b905082810360408401526112fa8186611210565b9050828103606084015261130e8185611210565b979650505050505050565b634e487b7160e01b600052604160045260246000fd5b6040516080810167ffffffffffffffff8111828210171561135257611352611319565b60405290565b6040805190810167ffffffffffffffff8111828210171561135257611352611319565b604051601f8201601f1916810167ffffffffffffffff811182821017156113a4576113a4611319565b604052919050565b600067ffffffffffffffff8211156113c6576113c6611319565b5060051b60200190565b600082601f8301126113e157600080fd5b813567ffffffffffffffff8111156113fb576113fb611319565b61140e601f8201601f191660200161137b565b81815284602083860101111561142357600080fd5b816020850160208301376000918101602001919091529392505050565b6000602080838503121561145357600080fd5b823567ffffffffffffffff8082111561146b57600080fd5b818501915085601f83011261147f57600080fd5b813561149261148d826113ac565b61137b565b81815260059190911b830184019084810190888311156114b157600080fd5b8585015b83811015611592578035858111156114cc57600080fd5b86016080818c03601f190112156114e35760008081fd5b6114eb61132f565b88820135878111156114fd5760008081fd5b61150b8d8b838601016113d0565b825250604080830135888111156115225760008081fd5b6115308e8c838701016113d0565b8b84015250606080840135898111156115495760008081fd5b6115578f8d838801016113d0565b838501525060808401359150888211156115715760008081fd5b61157f8e8c848701016113d0565b90830152508452509186019186016114b5565b5098975050505050505050565b600082601f8301126115b057600080fd5b813560206115c061148d836113ac565b82815260059290921b840181019181810190868411156115df57600080fd5b8286015b848110156115fa57803583529183019183016115e3565b509695505050505050565b60008060006060848603121561161a57600080fd5b833567ffffffffffffffff8082111561163257600080fd5b61163e878388016113d0565b9450602086013591508082111561165457600080fd5b506116618682870161159f565b925050604084013590509250925092565b6000806000806080858703121561168857600080fd5b84359350602085013567ffffffffffffffff808211156116a757600080fd5b6116b38883890161159f565b945060408701359150808211156116c957600080fd5b818701915087601f8301126116dd57600080fd5b6116ea61148d83356113ac565b82358082526020808301929160051b8501018a81111561170957600080fd5b602085015b818110156117a557848135111561172457600080fd5b803586016040818e03601f1901121561173c57600080fd5b611744611358565b60208201358781111561175657600080fd5b6117658f6020838601016113d0565b82525060408201358781111561177a57600080fd5b6117898f6020838601016113d0565b602083015250808652505060208401935060208101905061170e565b50979a969950976060013596505050505050565b6000602082840312156117cb57600080fd5b81356001600160a01b03811681146112ba57600080fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111561180b5761180b6117e2565b92915050565b634e487b7160e01b600052603260045260246000fd5b600181811c9082168061183b57607f821691505b60208210810361185b57634e487b7160e01b600052602260045260246000fd5b50919050565b600082516118738184602087016111ec565b9190910192915050565b601f8211156118c757600081815260208120601f850160051c810160208610156118a45750805b601f850160051c820191505b818110156118c3578281556001016118b0565b5050505b505050565b815167ffffffffffffffff8111156118e6576118e6611319565b6118fa816118f48454611827565b8461187d565b602080601f83116001811461192f57600084156119175750858301515b600019600386901b1c1916600185901b1785556118c3565b600085815260208120601f198616915b8281101561195e5788860151825594840194600190910190840161193f565b508582101561197c5787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006001820161199e5761199e6117e2565b5060010190565b6000602080830181845280855180835260408601915060408160051b870101925083870160005b828110156119fa57603f198886030184526119e885835161123c565b945092850192908501906001016119cc565b5092979650505050505050565b600081518084526020808501945080840160005b83811015611a3757815187529582019590820190600101611a1b565b509495945050505050565b604081526000611a556040830185611a07565b828103602084015261129e8185611210565b600060208284031215611a7957600080fd5b815180151581146112ba57600080fd5b83815260006020606081840152611aa36060840186611a07565b6040848203818601528186518084528484019150848160051b85010185890160005b83811015611b1557868303601f1901855281518051878552611ae988860182611210565b918a0151858303868c0152919050611b018183611210565b968a01969450505090870190600101611ac5565b50909b9a505050505050505050505056fea264697066735822122045d9336f0b6314796496c7a1cf160c19b42a61943a90fb5bbe2cfcef107bb5db64736f6c63430008120033","linkReferences":{},"deployedLinkReferences":{}} \ No newline at end of file diff --git a/backend/src/contracts/generated/summa_contract.rs b/backend/src/contracts/generated/summa_contract.rs index fe899e90..dc13985c 100644 --- a/backend/src/contracts/generated/summa_contract.rs +++ b/backend/src/contracts/generated/summa_contract.rs @@ -11,7 +11,7 @@ pub use summa::*; )] pub mod summa { #[rustfmt::skip] - const __ABI: &str = "[{\"inputs\":[{\"internalType\":\"contract IVerifier\",\"name\":\"_inclusionVerifier\",\"type\":\"address\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"mstLevels\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"assetsCount\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint8\",\"name\":\"balanceByteRange\",\"type\":\"uint8\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof[]\",\"name\":\"addressOwnershipProofs\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}],\"indexed\":false}],\"type\":\"event\",\"name\":\"AddressOwnershipProofSubmitted\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[],\"indexed\":true},{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[],\"indexed\":false},{\"internalType\":\"uint256[]\",\"name\":\"rootBalances\",\"type\":\"uint256[]\",\"components\":[],\"indexed\":false},{\"internalType\":\"struct Summa.Cryptocurrency[]\",\"name\":\"cryptocurrencies\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]}],\"indexed\":false}],\"type\":\"event\",\"name\":\"LiabilitiesCommitmentSubmitted\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\",\"components\":[],\"indexed\":true},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\",\"components\":[],\"indexed\":true}],\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"addressOwnershipProofs\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"commitments\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[]}]},{\"inputs\":[],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"config\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"mstLevels\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"assetsCount\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint8\",\"name\":\"balanceByteRange\",\"type\":\"uint8\",\"components\":[]}]},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"addressHash\",\"type\":\"bytes32\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"getAddressOwnershipProof\",\"outputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof\",\"name\":\"\",\"type\":\"tuple\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]}]},{\"inputs\":[],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\",\"components\":[]}]},{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"renounceOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[]},{\"internalType\":\"uint256[]\",\"name\":\"rootBalances\",\"type\":\"uint256[]\",\"components\":[]},{\"internalType\":\"struct Summa.Cryptocurrency[]\",\"name\":\"cryptocurrencies\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]}]},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"submitCommitment\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof[]\",\"name\":\"_addressOwnershipProofs\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"submitProofOfAddressOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"transferOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"uint256[]\",\"name\":\"publicInputs\",\"type\":\"uint256[]\",\"components\":[]},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"verifyInclusionProof\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\",\"components\":[]}]}]"; + const __ABI: &str = "[{\"inputs\":[{\"internalType\":\"contract IVerifier\",\"name\":\"_inclusionVerifier\",\"type\":\"address\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"mstLevels\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"currenciesCount\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint8\",\"name\":\"balanceByteRange\",\"type\":\"uint8\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof[]\",\"name\":\"addressOwnershipProofs\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}],\"indexed\":false}],\"type\":\"event\",\"name\":\"AddressOwnershipProofSubmitted\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[],\"indexed\":true},{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[],\"indexed\":false},{\"internalType\":\"uint256[]\",\"name\":\"rootBalances\",\"type\":\"uint256[]\",\"components\":[],\"indexed\":false},{\"internalType\":\"struct Summa.Cryptocurrency[]\",\"name\":\"cryptocurrencies\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]}],\"indexed\":false}],\"type\":\"event\",\"name\":\"LiabilitiesCommitmentSubmitted\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\",\"components\":[],\"indexed\":true},{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\",\"components\":[],\"indexed\":true}],\"type\":\"event\",\"name\":\"OwnershipTransferred\",\"outputs\":[],\"anonymous\":false},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"addressOwnershipProofs\",\"outputs\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"commitments\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[]}]},{\"inputs\":[],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"config\",\"outputs\":[{\"internalType\":\"uint16\",\"name\":\"mstLevels\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint16\",\"name\":\"currenciesCount\",\"type\":\"uint16\",\"components\":[]},{\"internalType\":\"uint8\",\"name\":\"balanceByteRange\",\"type\":\"uint8\",\"components\":[]}]},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"addressHash\",\"type\":\"bytes32\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"getAddressOwnershipProof\",\"outputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof\",\"name\":\"\",\"type\":\"tuple\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]}]},{\"inputs\":[],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\",\"components\":[]}]},{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"renounceOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"mstRoot\",\"type\":\"uint256\",\"components\":[]},{\"internalType\":\"uint256[]\",\"name\":\"rootBalances\",\"type\":\"uint256[]\",\"components\":[]},{\"internalType\":\"struct Summa.Cryptocurrency[]\",\"name\":\"cryptocurrencies\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]}]},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"submitCommitment\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"struct Summa.AddressOwnershipProof[]\",\"name\":\"_addressOwnershipProofs\",\"type\":\"tuple[]\",\"components\":[{\"internalType\":\"string\",\"name\":\"cexAddress\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"string\",\"name\":\"chain\",\"type\":\"string\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"bytes\",\"name\":\"message\",\"type\":\"bytes\",\"components\":[]}]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"submitProofOfAddressOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\",\"components\":[]}],\"stateMutability\":\"nonpayable\",\"type\":\"function\",\"name\":\"transferOwnership\",\"outputs\":[]},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"proof\",\"type\":\"bytes\",\"components\":[]},{\"internalType\":\"uint256[]\",\"name\":\"publicInputs\",\"type\":\"uint256[]\",\"components\":[]},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\",\"components\":[]}],\"stateMutability\":\"view\",\"type\":\"function\",\"name\":\"verifyInclusionProof\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\",\"components\":[]}]}]"; ///The parsed JSON ABI of the contract. pub static SUMMA_ABI: ::ethers::contract::Lazy<::ethers::core::abi::Abi> = ::ethers::contract::Lazy::new(|| ::ethers::core::utils::__serde_json::from_str(__ABI).expect("ABI is always valid")); @@ -7391,38 +7391,38 @@ pub mod summa { 34, 18, 32, - 20, + 69, + 217, 51, - 238, - 153, - 39, - 35, - 115, - 182, - 114, - 60, - 37, - 63, - 144, - 255, - 224, - 25, - 64, - 136, - 155, + 111, + 11, + 99, + 20, + 121, + 100, 150, - 65, - 240, - 132, - 147, + 199, + 161, + 207, + 22, + 12, 25, - 242, - 243, + 180, 42, - 54, - 203, - 20, - 90, + 97, + 148, + 58, + 144, + 251, + 91, + 190, + 44, + 252, + 239, + 16, + 123, + 181, + 219, 100, 115, 111, @@ -14402,38 +14402,38 @@ pub mod summa { 34, 18, 32, - 20, + 69, + 217, 51, - 238, - 153, - 39, - 35, - 115, - 182, - 114, - 60, - 37, - 63, - 144, - 255, - 224, - 25, - 64, - 136, - 155, + 111, + 11, + 99, + 20, + 121, + 100, 150, - 65, - 240, - 132, - 147, + 199, + 161, + 207, + 22, + 12, 25, - 242, - 243, + 180, 42, - 54, - 203, - 20, - 90, + 97, + 148, + 58, + 144, + 251, + 91, + 190, + 44, + 252, + 239, + 16, + 123, + 181, + 219, 100, 115, 111, @@ -15169,7 +15169,7 @@ pub mod summa { )] pub struct ConfigReturn { pub mst_levels: u16, - pub assets_count: u16, + pub currencies_count: u16, pub balance_byte_range: u8, } ///Container type for all return fields from the `getAddressOwnershipProof` function with signature `getAddressOwnershipProof(bytes32)` and selector `0x19b33968` diff --git a/backend/src/tests.rs b/backend/src/tests.rs index 572641e0..95d1efab 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -73,15 +73,15 @@ pub async fn initialize_test_env( // The number of levels of the Merkle sum tree let mst_levels = 4; - //The number of cryptocurrency assets per user included in the Merkle sum tree - let assets_count = 2; + //The number of cryptocurrencies supported by the Merkle sum tree + let currencies_count = 2; // The number of bytes used to represent the balance of a cryptocurrency in the Merkle sum tree let balance_byte_range = 14; let args: &[Token] = &[ Token::Address(inclusion_verifier_contract.address()), Token::Uint(mst_levels.into()), - Token::Uint(assets_count.into()), + Token::Uint(currencies_count.into()), Token::Uint(balance_byte_range.into()), ]; // Deploy Summa contract @@ -157,7 +157,7 @@ mod test { .await?; let params_path = "ptau/hermez-raw-11"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let entry_csv = "../csv/entry_16.csv"; let mst = MerkleSumTree::new(entry_csv).unwrap(); let mut round_one = @@ -202,7 +202,7 @@ mod test { .await?; let mut address_ownership_client = - AddressOwnership::new(&signer, "src/apis/csv/signatures.csv").unwrap(); + AddressOwnership::new(&signer, "../csv/signatures.csv").unwrap(); address_ownership_client .dispatch_proof_of_address_ownership() @@ -236,7 +236,7 @@ mod test { // Initialize round let params_path = "ptau/hermez-raw-11"; - let entry_csv = "../zk_prover/src/merkle_sum_tree/csv/entry_16.csv"; + let entry_csv = "../csv/entry_16.csv"; let mst = MerkleSumTree::new(entry_csv).unwrap(); let mut round = Round::<4, 2, 14>::new(&signer, Box::new(mst), params_path, 1).unwrap(); diff --git a/contracts/scripts/deploy.ts b/contracts/scripts/deploy.ts index 28b95ab1..969517ab 100644 --- a/contracts/scripts/deploy.ts +++ b/contracts/scripts/deploy.ts @@ -17,14 +17,14 @@ async function main() { // The number of levels of the Merkle sum tree const mstLevels = 4; - //The number of cryptocurrency assets per user included in the Merkle sum tree - const assetsCount = 2; + //The number of cryptocurrencies supported by the Merkle sum tree + const currenciesCount = 2; // The number of bytes used to represent the balance of a cryptocurrency in the Merkle sum tree const balanceByteRange = 14; const summa = await ethers.deployContract("Summa", [ inclusionVerifier.address, mstLevels, - assetsCount, + currenciesCount, balanceByteRange, ]); diff --git a/contracts/src/Summa.sol b/contracts/src/Summa.sol index aaaae3e2..82d8ccbd 100644 --- a/contracts/src/Summa.sol +++ b/contracts/src/Summa.sol @@ -12,12 +12,12 @@ contract Summa is Ownable { /** * @dev Struct representing the configuration of the Summa instance * @param mstLevels The number of levels of the Merkle sum tree - * @param assetsCount The number of cryptocurrency assets per user included in the Merkle sum tree + * @param currenciesCount The number of cryptocurrencies supported by the Merkle sum tree * @param balanceByteRange The number of bytes used to represent the balance of a cryptocurrency in the Merkle sum tree */ struct SummaConfig { uint16 mstLevels; - uint16 assetsCount; + uint16 currenciesCount; uint8 balanceByteRange; } /** @@ -98,11 +98,11 @@ contract Summa is Ownable { constructor( IVerifier _inclusionVerifier, uint16 mstLevels, - uint16 assetsCount, + uint16 currenciesCount, uint8 balanceByteRange ) { inclusionVerifier = _inclusionVerifier; - config = SummaConfig(mstLevels, assetsCount, balanceByteRange); + config = SummaConfig(mstLevels, currenciesCount, balanceByteRange); } /** diff --git a/contracts/test/Summa.ts b/contracts/test/Summa.ts index b70b1e4f..e7e9aa12 100644 --- a/contracts/test/Summa.ts +++ b/contracts/test/Summa.ts @@ -60,7 +60,7 @@ describe("Summa Contract", () => { const summa = await ethers.deployContract("Summa", [ inclusionVerifier.address, 4, // The number of levels of the Merkle sum tree - 2, // The number of cryptocurrency assets per user included in the Merkle sum tree + 2, // The number of cryptocurrencies supported by the Merkle sum tree 14, // The number of bytes used to represent the balance of a cryptocurrency in the Merkle sum tree ]); await summa.deployed(); diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16.csv b/csv/entry_16.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16.csv rename to csv/entry_16.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_bigints.csv b/csv/entry_16_bigints.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_bigints.csv rename to csv/entry_16_bigints.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_modified.csv b/csv/entry_16_modified.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_modified.csv rename to csv/entry_16_modified.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_no_overflow.csv b/csv/entry_16_no_overflow.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_no_overflow.csv rename to csv/entry_16_no_overflow.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_overflow.csv b/csv/entry_16_overflow.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_overflow.csv rename to csv/entry_16_overflow.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_overflow_2.csv b/csv/entry_16_overflow_2.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_overflow_2.csv rename to csv/entry_16_overflow_2.csv diff --git a/zk_prover/src/merkle_sum_tree/csv/entry_16_switched_order.csv b/csv/entry_16_switched_order.csv similarity index 100% rename from zk_prover/src/merkle_sum_tree/csv/entry_16_switched_order.csv rename to csv/entry_16_switched_order.csv diff --git a/backend/src/apis/csv/signatures.csv b/csv/signatures.csv similarity index 100% rename from backend/src/apis/csv/signatures.csv rename to csv/signatures.csv diff --git a/zk_prover/examples/states/entry_16_1.csv b/csv/states/entry_16_1.csv similarity index 100% rename from zk_prover/examples/states/entry_16_1.csv rename to csv/states/entry_16_1.csv diff --git a/zk_prover/examples/states/entry_16_2.csv b/csv/states/entry_16_2.csv similarity index 100% rename from zk_prover/examples/states/entry_16_2.csv rename to csv/states/entry_16_2.csv diff --git a/zk_prover/examples/states/entry_16_3.csv b/csv/states/entry_16_3.csv similarity index 100% rename from zk_prover/examples/states/entry_16_3.csv rename to csv/states/entry_16_3.csv diff --git a/zk_prover/examples/states/entry_16_4.csv b/csv/states/entry_16_4.csv similarity index 100% rename from zk_prover/examples/states/entry_16_4.csv rename to csv/states/entry_16_4.csv diff --git a/zk_prover/examples/states/entry_16_5.csv b/csv/states/entry_16_5.csv similarity index 100% rename from zk_prover/examples/states/entry_16_5.csv rename to csv/states/entry_16_5.csv diff --git a/zk_prover/README.md b/zk_prover/README.md index c8123d69..d92bd631 100644 --- a/zk_prover/README.md +++ b/zk_prover/README.md @@ -26,7 +26,7 @@ For real-world situations, you must provide the path of a specific `ptau` file t ## Build a Commitment -A `gen_commitment.rs` script is provided to generate a commitment out of a Merkle Sum Tree. In particular, the example takes a csv file located in "src/merkle_sum_tree/csv/entry_16.csv", build a Merkle Sum Tree and extract a commitment out it. The commitment is made of the `root_hash` and the `root_balances`. +A `gen_commitment.rs` script is provided to generate a commitment out of a Merkle Sum Tree. In particular, the example takes a csv file located in "../csv/entry_16.csv", build a Merkle Sum Tree and extract a commitment out it. The commitment is made of the `root_hash` and the `root_balances`. The script will eventually generate a `commitment_solidity_calldata.json` file that contains some testing calldata to be used within `contracts` and `backend` to test the publishing of the commitment to the Summa Smart Contract. @@ -36,7 +36,7 @@ The script can be run as follows: cargo run --release --example gen_commitment ``` -Note that the generic parameters of the Merkle Sum Tree `N_ASSETS` and `N_BYTES` are set to `2` and `14`. This means that this should go in pair with a Inclusion Verifier Circuit tuned to the same generic parameters. +Note that the generic parameters of the Merkle Sum Tree `N_CURRENCIES` and `N_BYTES` are set to `2` and `14`. This means that this should go in pair with a Inclusion Verifier Circuit tuned to the same generic parameters. ## Build an Inclusion Verifier Contract @@ -48,11 +48,11 @@ cargo run --release --example gen_inclusion_verifier The script will generate a new `InclusionVerifier.sol` and `InclusionVerifier.yul` contracts in `contracts/src`. -Note that the generic parameters of the circuits `LEVELS`, `N_ASSETS` and `N_BYTES` are set to `4`, `2` and `14`. This means that the circuit is tuned to verify the proof of inclusion for an exchange with a userbase of 4 levels (2^4 = 16 users), 2 assets and a balances in a range of 14 bytes. These parameters can be changed in the script. +Note that the generic parameters of the circuits `LEVELS`, `N_CURRENCIES` and `N_BYTES` are set to `4`, `2` and `14`. This means that the circuit is tuned to verify the proof of inclusion for an exchange with a userbase of 4 levels (2^4 = 16 users), 2 currencies and a balances in a range of 14 bytes. These parameters can be changed in the script. The verifier are generated based on an unsafe setup. For a production ready verifier, the setup should be generated by providing a `ptau` file generated after a trusted setup ceremony to `generate_setup_artifacts` function. -On top of that the script will also generate a `inclusion_proof_solidity_calldata.json` file that contains some testing calldata to be used within `contracts` and `backend` to test the verifier. Again, in the example, the proof is generated based on the `src/merkle_sum_tree/csv/entry_16.csv` file for a specific `user_index`, which is set to 0 by default. If you want to generate a testing proof for a different file, you can change the path in the script. If you want to generate a proof for a different `user_index`, you can change the `user_index` in the script. +On top of that the script will also generate a `inclusion_proof_solidity_calldata.json` file that contains some testing calldata to be used within `contracts` and `backend` to test the verifier. Again, in the example, the proof is generated based on the `../csv/entry_16.csv` file for a specific `user_index`, which is set to 0 by default. If you want to generate a testing proof for a different file, you can change the path in the script. If you want to generate a proof for a different `user_index`, you can change the `user_index` in the script. ## Incremental Nova Verifier @@ -102,7 +102,7 @@ You can set the following parameters to run the benches: - `LEVELS` -> the number of entries in the merkle sum tree. By default it is set to 20, which means that the benches will run for 2^20 entries. - `SAMPLE_SIZE` -> the number of samples to run for each bench. By default it is set to 10, which is the minimum allowed by criterion.rs -- `N_ASSETS and PATH_NAME` -> the number of assets to be used in the benchmarking. By default it is set to 1. For now you can only switch it between 1 and 2 as these are the only csv folder available. More will be added soon. +- `N_CURRENCIES and PATH_NAME` -> the number of currencies to be used in the benchmarking. By default it is set to 1. For now you can only switch it between 1 and 2 as these are the only csv folder available. More will be added soon. Note that the `k` of the circuit may vary based on the LEVELS @@ -112,7 +112,7 @@ Furthermore the benchmarking function `verify_zk_proof_benchmark` will also prin Run on MacBook Pro 2023, M2 Pro, 32GB RAM, 12 cores -2^20 entries (1048576) users, 1 asset +2^20 entries (1048576) users, 1 currency | MST init | | -------- | diff --git a/zk_prover/benches/full_solvency_flow.rs b/zk_prover/benches/full_solvency_flow.rs index 64909236..997b3e33 100644 --- a/zk_prover/benches/full_solvency_flow.rs +++ b/zk_prover/benches/full_solvency_flow.rs @@ -10,7 +10,7 @@ use summa_solvency::{ const SAMPLE_SIZE: usize = 10; const LEVELS: usize = 20; -const N_ASSETS: usize = 1; +const N_CURRENCIES: usize = 1; const PATH_NAME: &str = "one_asset"; const N_BYTES: usize = 14; @@ -23,13 +23,13 @@ fn build_mstree(_c: &mut Criterion) { ); let bench_name = format!( - "build Merkle sum tree for 2 power of {} entries with {} assets", - LEVELS, N_ASSETS + "build Merkle sum tree for 2 power of {} entries with {} currencies", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { - MerkleSumTree::::new(&csv_file).unwrap(); + MerkleSumTree::::new(&csv_file).unwrap(); }) }); } @@ -43,13 +43,13 @@ fn build_sorted_mstree(_c: &mut Criterion) { ); let bench_name = format!( - "build sorted Merkle sum tree for 2 power of {} entries with {} assets", - LEVELS, N_ASSETS + "build sorted Merkle sum tree for 2 power of {} entries with {} currencies", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { - MerkleSumTree::::new_sorted(&csv_file).unwrap(); + MerkleSumTree::::new_sorted(&csv_file).unwrap(); }) }); } @@ -57,13 +57,13 @@ fn build_sorted_mstree(_c: &mut Criterion) { fn verification_key_gen_mst_inclusion_circuit(_c: &mut Criterion) { let mut criterion = Criterion::default().sample_size(SAMPLE_SIZE); - let empty_circuit = MstInclusionCircuit::::init_empty(); + let empty_circuit = MstInclusionCircuit::::init_empty(); let (params, _, _) = generate_setup_artifacts(13, None, empty_circuit.clone()).unwrap(); let bench_name = format!( - "gen verification key for 2 power of {} entries with {} assets mst inclusion circuit", - LEVELS, N_ASSETS + "gen verification key for 2 power of {} entries with {} currencies mst inclusion circuit", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { @@ -75,13 +75,13 @@ fn verification_key_gen_mst_inclusion_circuit(_c: &mut Criterion) { fn proving_key_gen_mst_inclusion_circuit(_c: &mut Criterion) { let mut criterion = Criterion::default().sample_size(SAMPLE_SIZE); - let empty_circuit = MstInclusionCircuit::::init_empty(); + let empty_circuit = MstInclusionCircuit::::init_empty(); let (params, _, vk) = generate_setup_artifacts(13, None, empty_circuit.clone()).unwrap(); let bench_name = format!( - "gen proving key for 2 power of {} entries with {} assets mst inclusion circuit", - LEVELS, N_ASSETS + "gen proving key for 2 power of {} entries with {} currencies mst inclusion circuit", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { @@ -93,7 +93,7 @@ fn proving_key_gen_mst_inclusion_circuit(_c: &mut Criterion) { fn generate_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { let mut criterion = Criterion::default().sample_size(SAMPLE_SIZE); - let empty_circuit = MstInclusionCircuit::::init_empty(); + let empty_circuit = MstInclusionCircuit::::init_empty(); let (params, pk, _) = generate_setup_artifacts(13, None, empty_circuit).unwrap(); @@ -102,7 +102,7 @@ fn generate_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { PATH_NAME, PATH_NAME, LEVELS ); - let merkle_sum_tree = MerkleSumTree::::new(&csv_file).unwrap(); + let merkle_sum_tree = MerkleSumTree::::new(&csv_file).unwrap(); // Only now we can instantiate the circuit with the actual inputs @@ -110,11 +110,11 @@ fn generate_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let bench_name = format!( - "generate zk proof - tree of 2 power of {} entries with {} assets mst inclusion circuit", - LEVELS, N_ASSETS + "generate zk proof - tree of 2 power of {} entries with {} currencies mst inclusion circuit", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { @@ -126,7 +126,7 @@ fn generate_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { fn verify_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { let mut criterion = Criterion::default().sample_size(SAMPLE_SIZE); - let empty_circuit = MstInclusionCircuit::::init_empty(); + let empty_circuit = MstInclusionCircuit::::init_empty(); let (params, pk, vk) = generate_setup_artifacts(13, None, empty_circuit).unwrap(); @@ -135,7 +135,7 @@ fn verify_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { PATH_NAME, PATH_NAME, LEVELS ); - let merkle_sum_tree = MerkleSumTree::::new(&csv_file).unwrap(); + let merkle_sum_tree = MerkleSumTree::::new(&csv_file).unwrap(); // Only now we can instantiate the circuit with the actual inputs @@ -143,15 +143,15 @@ fn verify_zk_proof_mst_inclusion_circuit(_c: &mut Criterion) { let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let proof = full_prover(¶ms, &pk, circuit.clone(), circuit.instances()); println!("proof size in bytes: {}", proof.len()); let bench_name = format!( - "verify zk proof - tree of 2 power of {} entries with {} assets mst inclusion circuit", - LEVELS, N_ASSETS + "verify zk proof - tree of 2 power of {} entries with {} currencies mst inclusion circuit", + LEVELS, N_CURRENCIES ); criterion.bench_function(&bench_name, |b| { b.iter(|| { diff --git a/zk_prover/examples/gen_commitment.rs b/zk_prover/examples/gen_commitment.rs index f0a820ce..e7a5e87e 100644 --- a/zk_prover/examples/gen_commitment.rs +++ b/zk_prover/examples/gen_commitment.rs @@ -8,12 +8,12 @@ use summa_solvency::{ merkle_sum_tree::{MerkleSumTree, Tree}, }; -const N_ASSETS: usize = 2; +const N_CURRENCIES: usize = 2; const N_BYTES: usize = 14; fn main() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let root = merkle_sum_tree.root(); diff --git a/zk_prover/examples/gen_inclusion_verifier.rs b/zk_prover/examples/gen_inclusion_verifier.rs index 65e4e820..7859133a 100644 --- a/zk_prover/examples/gen_inclusion_verifier.rs +++ b/zk_prover/examples/gen_inclusion_verifier.rs @@ -1,5 +1,8 @@ #![feature(generic_const_exprs)] +use halo2_proofs::halo2curves::{bn256::Fr as Fp, ff::PrimeField}; +use num_bigint::BigInt; +use num_traits::Num; use serde_json::to_string_pretty; use snark_verifier_sdk::{ evm::{evm_verify, gen_evm_proof_shplonk, gen_evm_verifier_shplonk}, @@ -18,12 +21,15 @@ use summa_solvency::{ }; const LEVELS: usize = 4; -const N_ASSETS: usize = 2; +const N_CURRENCIES: usize = 2; const N_BYTES: usize = 14; fn main() { + // Assert that there is no risk of overflow in the Merkle Root given the combination of `N_BYTES` and `LEVELS` + assert!(!is_there_risk_of_overflow(N_BYTES, LEVELS), "There is a risk of balance overflow in the Merkle Root, given the combination of `N_BYTES` and `LEVELS`"); + // In order to generate the verifier we create the circuit using the init_empty() method, which means that the circuit is not initialized with any data. - let circuit = MstInclusionCircuit::::init_empty(); + let circuit = MstInclusionCircuit::::init_empty(); // generate a universal trusted setup for testing, along with the verification key (vk) and the proving key (pk). let (params, pk, _) = @@ -35,17 +41,18 @@ fn main() { let yul_output_path = "../contracts/src/InclusionVerifier.yul"; let sol_output_path = "../contracts/src/InclusionVerifier.sol"; - let deployment_code = gen_evm_verifier_shplonk::>( - ¶ms, - pk.get_vk(), - num_instances, - Some(Path::new(yul_output_path)), - ); + let deployment_code = + gen_evm_verifier_shplonk::>( + ¶ms, + pk.get_vk(), + num_instances, + Some(Path::new(yul_output_path)), + ); write_verifier_sol_from_yul(yul_output_path, sol_output_path).unwrap(); let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv").unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); // In order to generate a proof for testing purpose we create the circuit using the init() method // which takes as input the merkle sum tree and the index of the leaf we are generating the proof for. @@ -55,7 +62,7 @@ fn main() { let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Generate the circuit with the actual inputs - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let instances = circuit.instances(); @@ -83,3 +90,22 @@ fn main() { print!("gas_cost: {:?}", gas_cost); } + +// Calculate the maximum value that the Merkle Root can have, given N_BYTES and LEVELS +fn calculate_max_root_balance(n_bytes: usize, n_levels: usize) -> BigInt { + // The max value that can be stored in a leaf node or a sibling node, according to the constraint set in the circuit + let max_leaf_value = BigInt::from(2).pow(n_bytes as u32 * 8) - 1; + max_leaf_value * (n_levels + 1) +} + +// Given a combination of `N_BYTES` and `LEVELS`, check if there is a risk of overflow in the Merkle Root +fn is_there_risk_of_overflow(n_bytes: usize, n_levels: usize) -> bool { + // Calculate the max root balance value + let max_root_balance = calculate_max_root_balance(n_bytes, n_levels); + + // The modulus of the BN256 curve + let modulus = BigInt::from_str_radix(&Fp::MODULUS[2..], 16).unwrap(); + + // Check if the max balance value is greater than the prime + max_root_balance > modulus +} diff --git a/zk_prover/examples/inclusion_proof_solidity_calldata.json b/zk_prover/examples/inclusion_proof_solidity_calldata.json index 92709698..900a80c1 100644 --- a/zk_prover/examples/inclusion_proof_solidity_calldata.json +++ b/zk_prover/examples/inclusion_proof_solidity_calldata.json @@ -1,5 +1,5 @@ { - "proof": "0x1e94df5c9e64286482e5050d12f10c53aa607cacd84fcd3a408737cead3693ad0f8e478f584318016f885c0e4be56a1636097d8b7424d64855de037d349fd92825862131d1648f278249284b604b4f5d93dfc50d0004f8474bf569f972b113650da88dc30d36b0ae248d8754d55662af2c7aac4ba1c86372785af882100e61d217bd21ad7a8819c037ef132f2a340dd8fcf4c5b5dd661bf1844993c6ec82dfd92b91b7f6fb83a72a5262b18f8f324c425dffa7ad2b23a285c1b1bdb1c8bde4030f42e31819c3537ff954577c47ae8a0747a8a8390bfed8faa59188112f61216e1549c7aeeb46d2963adde4bd51d844784d449fdd54c75fdbb713dd8fd8764a1607159a94b280978d855eb0a11c7a578b6ea07c6d6d0c97350e18b5e8d36927372d79f80df140bc930b5678f407342dd9067a82fe9c75e4113754fc19310e4757289e2025af670afc8aafc721d3e66834c4867a1d1dbd63359f7b5b354f9f93e327550e310805831e15dfa2376775d7e66c47747a89e4494fdb4d8e72a6476e2e2c741d4eed06fcb84ca69d7aee5bcdb9963187e098fedb7d5f405a31febb6bc3106004d8dad642cc32ff95aab5fc3330d8dc260197db3ba080fb2ebb1bf238351ffdfa4e5035ebf3e987f6d3da44ee87ee96d16e6a187d6a9b3bb28fc1570492240ee384c6682c222540ecbc3d6f4d1cba8f403d28ca6e1a395e64dd4527665c08810e1fb264f8ce06552f268fea148055c1f5729c207dd576ca28765b4cc0900155eefd6fd19255a20eacbbaf3c87a7b62dffa5b73c046bad798f27c6623fe610290fc95e4a0073e52b3b5708aac83f505e15940956abe3db3d2f312bbc01ea0577ffc2ecd8e5f1326a255af26d19adbddf7f495843ab0bf1957693530ec9410d47037d00345c5743c63fb3c6fbd771823d63558584fc4f6fe571d449ae1bb62fbf0906b43c8b81d657380606f2e6012e081fe1a702380875161832e19e9eb22df226ad3769820128866d631b0c366ad69ae7df6039bacc0856444abb45587e2841231df7c4edd7b664087d46b39a53c3cc010466169d3c4b1f6bf3b80cf7661c7b1c59e43a04d737851b448273a1c9f41680c8b024921541a08ed2b737ba8e25a921838b6d2730e8058c91ef25d2251a6d09e3056eed5f0948418ad2e106ec14f7100b366d055525317cb40ed75d9bda85c8fd98cd29f0a93aa6f90539334119f762877fbf7f4f2a6b6f9a10145f709257104b9ae2dec184a7c1df8275159f2d2fd9fff812ec5fc42f93c472e617fc1563e813dc6cd6ca7cdbfb41bea2a72e228ecbf9bab2c50e5304b664772db087ac2a53d45d8a4b8c63f55335ee644ef52fd5f9b6e746b25bb6cd19e744d76cd1414ebe8d6b5fde6b468cca5107819a6d05ba9c2c1091a4eb1d79629636ca3400a3b69a781ba8ecbc7f59779634e5ae132f250945ffd72ade50063fb5a07dc6f56c3ff1cae9ae3d1c2bf201258bf79f1329ed689867a7e51e0d50920019f42a45f1b812b8f4de5a47ecabe2f32c20384f16e94dc9c5f78799506b33b59723e7dc5386bc31002eb3867b50ded5b92658de12f0e914b260e8f61a1184842721087c79021c54283e68635b0cb0cd9aa7d6bf0a145ade522e4faa01a78eca82f4ad4660745a05ab763007c402b6ee8a9e73a30f3e4dc893d23caf4fc757ebe0de6123978dea5bee1648833f6c60c413a250461d8cf808b8922b6ae123ba013b570cb4ab6fe663d1170d6859972496534965a40e5c2dfaabae417a4468039254f1107b358691045edff5154b2c88cdc99e770a0a4dda09ffc7d86bff1c6bdb1ae00a0ac582cb2c0110327ded5e85524cb9257a11825499dbc673b3d771b00a33480190425999ca32f3d8dd40c950ab81f6dc662a51907294f6687dcc481c02cf15914cd073eeba34113a52f2e04dccc79e6b401d485d4fff8488abd6b5eb1c7bf74a3ee86b2f7c07c1e93bd08e9ea5dc62566b2078fec5aea206f3dc10b9e7afdf448e0089d47006beb576b244aa692b961dde236f7ab501573043b9ae540c63bd80688ade75b29939f0b58145e0c6171da4dc29f6e6bc99aab9e2a2c95d2049a5081a7e1f617ab947f89a6d7e242bd546dfa50ebb5a4c63e3d88ba029db0a8f43f88e55d365ed589ea83df35e0c1098b80f2d0fe7960a8b16c190c9fc70a01aa7b0fc48c1b9bf3c02d0e73f3a0a3f40c4a47b2580c46f97a5ae8d92ce9d7c04d4a3c532e0585a706121a2ff2a447026b05c9b16c0ed803fe1c901e0e92899f9ca4a4aa1207c9e94cc11647793e4df7d809eb214d54b9e5052eb2fcf087abc5e14a6fa9018bb445e622651e0202060f4d92ca22231adfacf0e4feda0dfc1c4f51c54b401b6a95a696b1796c24ab0949e3120d311ab9d6e2a30f1a20e147490b61683674cdc926e8ce8665c8c9c2dff8cd5ccfd2e96ce6ef75f0bfe0c4edbd192b0a5dcd672a4e18119f5bbeb38a542b1b8ccc111294bfa7bc3ff716b4496dfc23ebdaf113d0b29a1266fcf3b5c81fadd4a91cd1221117b444b3f63414069ff07237707db17728f39cca47506702b091ce6830e2dd956d03068cd2fba7314c65f231475288e147754408534d19eea99a58469331d0149d618165409b780bb02759a92f08372beb9764a3c1e5e517902dab8000409ddfd38080e91bb0d01d218380020d24050975a27d354bf512dd785754a486218b02373e541d2bf55ce1b77c8912c0135a2c0775686f7c6734b8627cb93f72d2950397efe6daa89cbaef859e7dd639fe91c94f422eeec53065ba6ed8f78dfa72d1105af9e61d70ecb7ecea72e275d7dbce40fce85c795e3c20ae719251abd33061ca0cc43ec1421947e6a285a1f40790386d19fed65f26875a6656b282193ef10a7a763e61584de359c71f9dceae78189ff2272024b4cf141f58a7e94ec3c121150de85e9401461b0ffd26bb373e2dd4865572dd298b5f92babc182f8e229682d454f436f48d2857b8739b904cff81f5d5f3bb3088e9d2510140753fbc59900", + "proof": "0x2b8ec217faa4ff648136bd4fb5836ae599f2c7025d232df42c8edb977dd0dce120abf6efa3de5bfd0387687a8c5a31852c6a8e336d9881281be4896a5f56fbec0f52ab47f864ad21a3bccf14bf1ead807a2a09d1632b66f47a1b3d39ca9db4852a1863993de31e1fefc5d3d83df19638aceba560e20a18c499ef794c7eac07181117dc661ebe16ef4747828d2700b10b960e91bd253f4988c74ea3deb493fdde2b103c8cbfc595711ba36e4870623eefde8b5ce8e63f55ee1041c0b0526dc096278bb5dd249ac507adac511d223bb3201a8b14fbd8783902786904fda81dfa9b1bbbc67a76f54d73698aecbdc5e410d0d67a0a5912b1bdbf82b5868357cb684a0fbfdb9b4edb1f4021843572f1164b6628ccd628fbe9ec2075700cfa7a33f0462b7aab71071cae1ea223a0edcbc12488f8bf2d1fb81eba838e8d1641f91bc724301794f3cee5354776e99016447933681bfb9bbe32679a5693fd6f1413c2bc7d14a389f96365bb0addb52cec732e1990cdf1daf73e994d1c41f5f59645715e5a1d3b0015abaff3179104ed73f4e679c321b54de127bed122f2f2d129e15696b406b8395241aef1905fa3ba1e99c25ef40a4edd45c9ba53a052df0747a15d892c05a3a4f7fbf7e060ea50aa6373f32ed5de44d52cc02b1a08fe7ecfe48efb35151745982a2eb06d8891f3043183e0b432e58df1be4c67a1bbba5791320890e17111adfaf477216e37ebb2db397af59accd8ea14e047595d103d8ec4da13a22bd91ccea57b6f614b5e4f01f1281f85d7c8a4908c765c3ab73f0d14eb89f6f6bcdf1834490bf67f71ea578e0ff5692c3c16d4e2db0362b6eb55355b66e177eb91c209e67c95637a58d426edf89ad22b55bfbe440335a97e26f711e6607d8007d5362484e71a5cb5ebaabddf7970dd486d8c89c5524102a25c7203d059b687a3fb930df18e7fcfdc3940e3ee1297bcd5f24c22c2883b4d0cb2c5eb22f1e01d02bc7e0162838206bd631a050aa3c3f07a9474185735af8e3eddea823c7882ddd4c3c6141f2e41e3df26a5a0fe60afffa9903b923b25f2055e915793e7a9d942fa58a916a7a9a3a7ef18141c3e04134e506910d86a9ae0c09f3973ce6693bfe6432d921120763bbf6de53e7408db420ae014185ef69b1c7f203f79f61b4b3a28f836d5224c0bbabb23ccc7cf5e286cc243c95d412daaffd0271762daf9e465e76f60941c4b6a68f591ba7bb37a1005c2ce1e42eb91309e367f8f690fb8acc2f8102f07149927c8f0510c68bbcb6f98dc94d869d2415caa0e51bb7d862187e407699694194e516d961b00e33729aff5c4a4b1a112e6a208424217ee4548857ec7f9a97510e5be95e89dc1876229a87e86d661a58186ff22149390aeee5c5a4d861870c912ccf5acdd6a00eaafbd2dbd72b75e7a83005e9c8974474e28d7c37deac073780144011b7e7c3f7ce4d388852e34de994244db8c83d6c0185e70f3ec7276c5c4274d0eb5cb0ec0a9da8d2662aa77c60d004e20abe9339380e523cec01a7a0dda1dda17a53549b9539fc9a7fca3f303479ac9da7e6adb1013effdaf9aa6709e362c4ae1e2078c215c5581627f54bef6b55b2d5618b85df9e43fa2b9fe2d182be811066206e6224c5b632a4962a7eb3d6a8f29b10a6aa62aac8627fe6fc0f4b1b52a27c252ed2bb6f5f6c5e0d4987ef595b039e343984c5115e23b31b8aee89e7910dd40c368fd25c31eeeaf9d90206af9a09b26ec7b2e0acaa9206861efe3615f154c29f8722d3f9c9bdc4eee9c233bb97bdf08387a3a900ab0b6ee4d7634622c0852b3418d8318925a1f7f0365f83378b5ff13dc04c2d0f44290b17555f2d5db2e3e66ebab912dd822540d343f03e711b84b97433ad04b5f3b64641037ac2836027a959589488c1f1abe7b1a1728b1157738289c28a871a80c3fbdf0287271c0186bd9c324f838e4628bb7ffef0f6ce6906cfafe73ed2ba19575026de192576a06e532b372545fd6f690c0bdec35cd7e675f63b12a714c0bf1bc4a485fc14ad90c399b3dc32d63824044bc85d3a78b1943071350c2deed66a26d58a954c15ae01151f857d877633bd8a7a343af19c639c7965e34023b6f056ff6fcab5e3a58ff0b1d192e4bdbd63f4a6fa8d7865f452efe6aa7f01ee507de5ba0537e9a2b5b3722a25cc2ac937ea7c5d106af18db42be2704cc2c99e14307d9c27c5df4a793491d2a60089e118b151951899d0679fe31675ab75bf6d27cb3f5a0711805b4e3d41823fd8eab381199d8df15db53ce2f9d7d11e740c40302d5264823ffdc0b32e324768f5359e0ae0a78ea27713740010c919dce2d719c6bda51f87bbcfad2eec224a5feede2c93f76d746a042d20c2110edd9e3d67c68321adf17c1b5a93695072df36d505530b3db3f0d0f19a56e7a3cf19cf74e1c92e26a54e71b10e8bd97dc0a1d78b3de56fd28a80404695ed0dd979b3f49ffad4d573460882beaf828a4bb13fb65524b2ac61ea54c32fba196d38bb45016ddc10c356d089b6ca00c2da0880599fbfaa7db836f4cef2636b7a5c171f235f31478ff042f04926880ddaa863a1fb71fd14345862a5a8594743d7470c36ed6897616aa0ed22d5938bef7b283db279f21d671afb9e7744b82f86cee5a79ad96a222ce5da3d413bfb4abe5fee17d20fb01b51a5a68b8bfa5ffa6f112a88fd654e5936f3c86283a15c6ff96b6d4b305422f2c95734c2d36e835502db84726f159e0d4a58c12bf9aea22ddfeb4e10a23c852c21dddc01fdd73051cccd604331bbaff6fdd1b9b4b05176c4866f7ea260339ca3d20fee4259b3664428aa7bd925774815ffff772b2cc8d3ab49b0e18430c91ec310edf4bfa0b25e1ad9e03c8aaea974b5153b6552fffd0014715e19e9521d120f3d4937faad988f61acfa8708df3734c47b5bdfdc5e5b5c12cbb90e3b003678e1fb05ef01dbf967f978d64770ba731872439088e691b43d3ffde3d44c80e0f72444f7073ce8959f3c9718f290710fd35bd1a84f9b3a10beaa2da7c7b58", "public_inputs": [ "0xe113acd03b98f0bab0ef6f577245d5d008cbcc19ef2dab3608aa4f37f72a407", "0x18d6ab953235a811edffa4cead74ea045e7cd2085771a2269d59dca054c955b1", diff --git a/zk_prover/examples/nova_incremental_verifier.rs b/zk_prover/examples/nova_incremental_verifier.rs index 50965d89..3841d94f 100644 --- a/zk_prover/examples/nova_incremental_verifier.rs +++ b/zk_prover/examples/nova_incremental_verifier.rs @@ -15,7 +15,7 @@ use num_bigint::BigUint; use serde_json::json; use summa_solvency::merkle_sum_tree::utils::big_intify_username; -const N_ASSETS: usize = 2; +const N_CURRENCIES: usize = 2; /// In this scenario the Exchange is generating an incremental inclusion proof for a user after 3 rounds. /// It means that starting from this proof, the user can verify their correct inclusion in the Liabilities Tree for each round up to round 3 in a single proof. @@ -26,18 +26,16 @@ fn run_test(circuit_filepath: String, witness_gen_filepath: String) { let liabilities_state_0 = Fr::from_str("0").unwrap(); // Merkle Proof represents the inclusion proof for the user 0 for each state - let merkle_proof_1 = - build_merkle_proof("examples/states/entry_16_1.csv".to_string(), 0).unwrap(); + let merkle_proof_1 = build_merkle_proof("../csv/states/entry_16_1.csv".to_string(), 0).unwrap(); let liabilities_state_1 = build_liabilities_state_cur(liabilities_state_0, merkle_proof_1.root.hash); - let merkle_proof_2 = - build_merkle_proof("examples/states/entry_16_2.csv".to_string(), 0).unwrap(); + let merkle_proof_2 = build_merkle_proof("../csv/states/entry_16_2.csv".to_string(), 0).unwrap(); let liabilities_state_2 = build_liabilities_state_cur(liabilities_state_1, merkle_proof_2.root.hash); let merkle_proof_3 = - build_merkle_proof("examples/states/entry_16_3.csv".to_string(), 0).unwrap(); + build_merkle_proof("../csv/states/entry_16_3.csv".to_string(), 0).unwrap(); let liabilities_state_3 = build_liabilities_state_cur(liabilities_state_2, merkle_proof_3.root.hash); @@ -240,24 +238,28 @@ use poseidon_rs::{Fr, Poseidon}; // Note that we cannot reuse the MerkleSumTree implementation from zk_prover because it is not compatible with circom's Poseidon Hasher #[derive(Clone, Debug)] -struct Node { +struct Node { hash: Fr, - balance: [Fr; N_ASSETS], + balance: [Fr; N_CURRENCIES], } #[derive(Clone, Debug)] -struct MerkleProof { +struct MerkleProof { username: String, user_balances: Vec, path_element_hashes: Vec, path_element_balances: Vec>, path_indices: Vec, - root: Node, + root: Node, } -impl Node { +impl Node { /// Constructs a new Node given left and right child hashes. - fn new(left: &Node, right: &Node, hasher: &Poseidon) -> Node { + fn new( + left: &Node, + right: &Node, + hasher: &Poseidon, + ) -> Node { let mut input = vec![left.hash]; input.extend(left.balance); input.push(right.hash); @@ -265,8 +267,8 @@ impl Node { let mut balance = vec![]; - // iterate over N_ASSETS - for i in 0..N_ASSETS { + // iterate over N_CURRENCIES + for i in 0..N_CURRENCIES { let mut sum = Fr::from_str("0").unwrap(); sum.add_assign(&left.balance[i]); sum.add_assign(&right.balance[i]); @@ -282,7 +284,10 @@ impl Node { } /// Generates a Merkle proof of inclusion for a leaf at a given index -fn build_merkle_proof(csv_filepath: String, user_index: usize) -> Option> { +fn build_merkle_proof( + csv_filepath: String, + user_index: usize, +) -> Option> { let file = File::open(csv_filepath).expect("Unable to open file"); let reader = BufReader::new(file); @@ -310,7 +315,7 @@ fn build_merkle_proof(csv_filepath: String, user_index: usize) -> OptionuEWyN2&ExS`nhPmR8h&r~xu;tVgM( znp&)@ijekLrItcO#ehIIh>D636_Gh%My3pe%-MVY*2dNj?>X1?{oe2UM{|+vY3;S1 z=f3afepY5h@{RW7ujkt@o^f#D;f0jL3+)dtbU3`wv2>wR9usgqJlvh)_ctTupoi(S z!X*}dem^!|k5It7*IZ^Vg`cPX@Y_GcK!WXj5Pm7T^Plf%Soz~Av(r6WgX(Vo`a6sb zzIxIAUjTD*+7AMr(sX{9GD;5+oC@mowLvLOxmC@DEd5=s4QS@fpwn5lu8o9=iVG1U znWNUcpD@0VQ=!iX51gH5gQ_n)0)cL+lQ$DDQffAeGA0ixCU&#EP1!7gO>KL^7T&C@ z+}ILZe0}8S!r%W<8KsPJ7X<~8W1XS4{up8ND=%|o=A{h0N2$p|OiidcS4kXGnnFFb zIV|)0EZs7;Zk=*cikh;OmN-=J5FO2{AHeHnV*ycpvxwwVnclF!={8P+O*A4dXYJ!_a-$|_D~ zbnF%pCK1w5z`{6cne`%K0>tz^H4()D{L7u#1$^<4?wl^RaM3(#kp96(l(gL zznxpODGQD?Yw668ZaBUn>I(I>W5)uYy<7_;OKMo@B&Vzu8m0Ko(k)1HsBkP;XyM7! z&ghN{CFzu?fh!7YIHFk=lOHvWe=o7h$i=%8@d0+k59`LgqsAjynp%aSOKDw4;2iPZ zv@(0Jxq5(kU=p{p zvVgn$5RhK1v z>Y8usM_ZMs#_bUkyT0YwN@aP14MncLz}7;I0duxHnU-IRQ?E>IIGQ90pHbP|A@k+b z%ZdByRMz&OZckC=VQXaX38|g2bAOP@Br@$(8NVt}x>bTp+nr`F*m0&^+t-1b3y}jI zMMgV`@rb1QFCoA|y5t`oiIhE&#)ztumXDZ*k?I1(BBHQux70VVT{=67)}JHC68e@a zg38jg3Egm7)Rrs9r7TKXz0~$f_nD+1$C3MwLA zXN{JzS)-Xn<4&4&O7o)ZV{NKi7>X4J#}rY3_6?_imz)Ky9Wuix+Q}qrj413guIzq2 z%kp@5`?GQVE#SpQR!b@OTuxGuQ3=v|E894xQgCgi+N*r+lnADKFm5W*1DXlF#o6|F!86mulF4f&I?Nkq$Xo}YDJnh zzpKl7oPiETBa>@|1F^EcY}C}knD`oGz;&3xHOlhxny84W-evD^W8nx=u1@ooQs48~ zKv@!t6oH_+;ar)aj(BP$Q~{`^ZVz(1sz9=mV(T{ACe)aLjoIY3psn{^HK$HE(h*HW z0#=%kF}r|cX=f1Ultcx`Ho~aDW*PVFS**~kOGq#ECr`oAyd}Bwm$#p~bE89oWd~!w zF)xupP-7f~19akP7OIZTH=GPlP|<=MPI|GhJ`}4}8be}u_X3$l7ZK&187gB2N8@r$ zX^cl$#=a<9q1f;^+$_x)TO0M*kcD&up{@nH8G}9LypvG^GQJxZ*{Ye=pP5)qLF9n@ z65d@PT%g&loNOXA(im$a2S1IPuGE1S|I@Ydn_`hRs!%Q^Y;s~!-Ml$Lw?E;+I=8f8 zAb8vr!Oa)gSP^bvVPU5wBz zF5Y`s$75obMfM=6oi=O1Q>)PYS&U3Kkf1GjebM2e$#Eq`vS;}jqrM3|ikt$jWq61vt0KlcL#x{2WELiI zgMr^;;t$USVc%2nFQh!p8cy0#kYS3!pK?xlB^IwtNxt;U`TL(l8gm;mR7S9k<21{D z&CfafU5t6vYZ$`5LqU5nb1F#CFHiXL4@s5`heMN~L_zbB`X@W>ry%VTYzb>}jTj+= ze-8uy_pOg~Yohv-5(eH^i~z(OP6n^EdmHh~CwKBzbbRpgigM*gC3mqX!*+CZQH;4h zF`|zlvsSQ*PFk)@tSZkpAMLtU<4_47vYv`P7*ZbS=tE$-{IsOi_BGY)-@Tlz3q)=8 z%B0yXYJGyMSpICP5^Lsy>JL4dBewZbsi%V1tCG>R70&VO&=rG~({HC-zsgIt+)qjP z>G{`fH8m3gr9L&ms!zbzutvts%71{j-gSBjN&6w+cC%y8b#wcz+h1ApSlr>K$@_JO ziU)*4^7vJ&wshIPPfE{};Ex!VS|yRIw$;en+c#GZg$oDUtOt<6SoE?wgm}T(xxPXj zVf`Yy>jq|661b&3veLY|Wx}rhoPJ!|WxW%vyy0;6_mii8JeZT8+)>Lw($^N%V1KxU zA#H!K;y_bF%aRqJ*w(tcpGs6$nZC~Is>V)5k9Ke>j3-30!`8P`@Xqt+YMUFV*H1a8 zooj(1*NPGbSGr2rKc9Wj`GY0YJt(FX@%uwG%#~rSkvzk-tcd=FQ!!e&y|4A+#!$^X z_^Z{=fA&V;?6LevUdNiCiEp+XP*=&*#K!cehk_=1;iN}ue;xRozjsn8*UCo_zqe6a zIJ*ux4%#=l`G=H`yr~I6O?7PjLY8JDY8%MD^%W5*9H|ppE70+w1+t#-DD&;yMh$uz z{9tj}w1S9?GNvjAMflUiOs45`c6u8SIQ)tM#DD7*PQTRfK2iI3%Yf3ns@TGK>zm!h z7WI7a{XVCcz_Nu7-qYX>=^4?|4ZzibBAwGuW7>m6M<+KYIBAK!VD}(xMf`7>ov{DB z5Lc#Y))XT-p-A)BCI%<{YU8QO>Bo<6TAcHj8DEIkSDDsE+O~Kbz-U%v?}boaVJtF|qXyb> zd%p#UsiFYaGHe)*_b3!Sg_Z4V6$eHmt=23aFH4Q#oRZCMIgy862FO#I4-$v=6a?v? zI#&&J$X?LU5sOJ_E<$Zzh|&v+C4b3XUFsy6EsGd~3sRH3QY=^KBb86}3Jr~A-j3pp z9qPi;%GR|}V@)ihAhy0Ihh-R5S7Nfw_1RVZwNY5A(x}f=~{;08&kp&}U-Tgy47 zyJFkV+dfe6b=#wCwP+8O&=erLP?Ugpmou*y;@U#tV4`d=S2m1r5G@_mONFD($B!FM z)nh==7RtZ94DWgRDW|-X=%(Ul4jo#{nwZZHTUXoIco9M*hNTd-93w9jmO3~{Lh>Qq zEna2akW3qZvNU7{VwrQ;%V*Z-a)kF`UV$e}tQ@Xo;Co2`>SEJSksb-w&PBRf+%fa* z+wQ;S5cwNmo5nd1Xq-KEgBH3+8c>YU)L>?ulOII|w&hLtS-|buuPQ)#rNpF&!5tb; zWS&JQ|42$8s>Ffz&NT0w$RqwaI}glSSc5;L^5x9ZQ`I8k4kxOIv1i92JJ5n2XuCI+!66$0Z%LZudR zQ+2I}U5&!=7Gd%yI3z>M{u3LDD{_Z#LHLp1rfL$ND!9ts0RiE^VQ+WohJF`0&i*+E$&V1sG@X=ZsL3p0t4Gt`05wS-K8~7XCq8(m z#%0l>(6qi-n4L6<$V6FcB9|~o@+U1E+dF?*(-4n-e8UBD5ONMZF~AAKf%J+s-kdN# zKIW*iuasN)J#xa9Jo0#5wY5@C$8B;dVktmBwH{)b3R&Y#!m$B1evC?G2F~7Mr+9iy zVdSC4C$VXVL+|cNlq>LMEM4g}bgZ5X&(_Z*hMk67yGRL&^thd8 zTlSYfW8TO->9#1RcnY#xsz@C?r+a>iI$Gu14O?`5cO> zd^AF%WoVl_)w5#IfVA@1w3*2hkC%SGZv1~zm)A=B>ZFJ+mKcNIQ4-W{?1O&^%FbyG zE$OG(_+b`b8vHuEi#V!GdvbALU4Q{g$1#oP33BEKMFfSGB`kjRIgv1;O|lyb-A zDCOlK+t;InEhb31*nm{(6!$QU{^t`mfpivUD?icF}tv*RJ>hc>tz@PPB7Cb)U8L0!qs6`D~h#9Cj6Nx z_}FK>0mBHXG92IHF7L8su$`zMx((r z8AFt8uPZLsH3nn2nz$KOthzqBC@wBIZG8)yxRgy-w{=yiP1+>kWHj0zD;x+9n*5&b zU@$Z?edCN>+yLsO(*2)Xckj-#8`K@Eij2aVkg?X1BvqyEZIrOG9=7kR3yfKn(z`0C z+cL^HpzV#UU{)9dS;7VgSb*)GgkJ~KgM`&Tm#HTsmr5lF4=-Tg8t|(SLmUMM=h5Dm zvfgs?pyZ$#e!em6RC`m6+B~4Nj-wW{*c@5?9laY+F3s?ZOs}*gRZWJiS5B6)@Mx6a zDoxF-1J-4#uFlPsJ-L+=JC}FjU-6XP#}ZEbWn~OKmM%*3*hpDic`J_p(>T;*T1+Z6 z{#b35$t#n4@*$)3)0oA?0i~sgVTEammzh^-2RL#U`m(?GrvI0tmL={M)FC_ZFATdLXZB%-WroxdmJij=P>m!<7nw(VSWq-8b3=E*U3XDKvZOua^I zd#HSQVdIw6-tMg*u00LeM`*4dA_B@z+ARC5f981b8S^r;qHMRFBl|77p(Rx#NtHf- zQ6uHU;QL1|KGN#7Bm*gBTjPaKv!Vzx+C#1F&Fou)S^PUKmG=rOo0-G}mkeFnlLfXN zi<%9)u9bYSYcG21qa3SXtWMYhB?ll+yUijg+U7<2{cK~`{4alO+jYn}4L&4bSbmVB zeTRAQdXm{?w&BpVDDngQApR;(PYa*Yx{8-G0O4Io+P)ErU!N>I?tF=sn_ zgx~=6x@^y``{|xv_3iu2*gbi=ukzjJUtd0SXuv8P9jY{pR@t(Ik2`eFkZ=g{Mx^QPH2c^rV>qIE^sr}S&^f*yYZO=P+gEmdibt@q^Q_4%ln7Ot4~a`lOVi|B+x;uX zas0kSxI-C>uY*j1B)AZN9k!0guPVFBOCwnf+akSj_kRX#hO4)Z9ZiE z!!5}B?#5J(U&xij@49aSvuD37SMaoRsTBvUi$zbQ$^+{841udLr`J}#Hae>?m$7d# z6>11Du)J(c080I^%-R!-|mw1M;-PH6uYxk0o*mnDN$;N^` z-EWe=dgZsD9bWYZo-(H3iM3H`-2>;yr?=4IT>Qwd;~snha56oj=mgL9ScDl!^8Plz zmdl?w7MjFexnYo<3Q8pwH+HvGCZcQ_mj?PcMT8A z+1fG(6nELrlLqYe9&*k}uAU;Q?y0^oO55Jq!9jE4j`ul!T%U5*B>84>%jgN@?Q65^jz0UB;Dec~ z-TVVacfjw{eo$T4ja3=9bfAuscZze;<3ge!-;}x}Np->O- z{vw5DdrRR#`beyBSftc0W~I*#PWXv!D`%zENlicQx{F)}XX?BnUzbc~vxqG9zrtC4vajdHLa?FHfj(VC!d5+F1Uzu1A>h+`uN1KQBahi2K;%OAbhl|4 zIHtx2*-J#5Mz{3~b11cR-&D&rj%64MSd9UDA~2O&ccCR7nL2^b?1c#63t#wBX)TSWscf&mc}5x$dK zL(_G!0$}8y19RVZ+6a2jy7_`?Cnx8CCDy-QxRX=m|D^DTlP0jV(0$M*-PgM(fki@=;JXF7QN zm5UGMFz{@2@SRaGheRrPpAD=&>2esHIN>taXN!TvCSE8Jj$ktw=H*4pmsEiJ2i)d6 zpx7u#evjsB-z@~Ir+fGVuY4Nap)^5s>bMUL%vs^&=bJsG{qxbtq!lrOA20`+U#B{T zFfn9ft}^2z6}~1Y!$DIn#&=%>+ZN57wIn8eB2aA6Dq)N^Uj1w*y-|&Iu~`FGSOalU z1AQ|5c!#R1^{g05Ej(052j5q?#QmS%FL)!!HNfkrcSp5bX#S?@W%!Y%(dOz@3pnw% z+lxT$>_PXk%Oo>%UV|02>4N#H0ztskI`E@@Zr8Cz&uU=XoMJK2j{r$0XC-nB2wrr1 z%^o=qLNUjD01mmhhvg}4^%C=ChNVPo{boGxSOH64OifoZGxAyxv(lnx5#?;+XQi!_ zMf}V(9~9fpGhzL}QV6y_^rFoVGCy@*3<^0`MU1mhn@Dsgp}jCI)Unhs-8_3G0R70# zKWIp3k;aFi#(37`ht+{nr4d)-=?u*e99=MCGUjakg1#BDJS*GQ)|Z|svi5V4L!s#l zmsrn>iQ4~odnh%16t#{P*v8qc^wKSs zU8=30DQGJY*g`@6hxWcsh_*mPs-^e{kU#aJv7@6btLq0w zI%LMCgK!jBrgn*`M@?wu;ex?1K&t6@3o;t%9;7)taa6ch3HroZjgv8Hy2a7Ls0E@ zbW$f9%CC?+i1?F>8M?RCh94p5EtE|) zW>I}VXzUk~fm|1hXm1)bh){%7m!pLb0A+n@+eh1PV|#=TN)c^hjHwV!yTPkc<{AaY zdQ_7IYXf=JS^WO+v*m*(hE1ikJ%RH@iR*LNBRd#o;Q9Vc8mNu7Pn)4VyDL9-+=Z$k zA8VQO`PlT7?+^SMeS+sbi#}h0{PQmF+GlqI?E#M$XHPD#GlR?w+KKkcz)FEpN^~-a8YQMN68H3>XQKoSDfmK( z=^pc1#Ac)>Q7~lWTjj(A(>9k`4ZeQK-OnAfeCQ0*jIB#?eoewpU~VgILCy~fUl&aD z)^G1!JHvqvBL7WY2j=9^`~a(mIxYM=eW1pKq*>HNBb&2lag-`YISy|>Kf37rsT)`V znQzvs4b#wxY{Vu-^dB$ye)J{kyGa+%YwSSD@@awK!2>!S{G*c=NT~%|H$lxJJbk1U z^vrVc-uPKe`5|l7c(_cpF`sV@=0A>Y*<3aT|LXly>ErjxlrTcq7FOL}i^0`1PCn4v z8Fy;;q_uA(X)f3{?OtE>P#-uI zx4mKx{v~dk!G<80H$hdD+lra&S7-Jq&et|29@p7{6rqRrYPo0}sy1-Z1kGI%&OKmk z)fN>OX8J@Fz`u$Dl^MDg>rZSvPtxZUa71fJ?BvxSl8jz=vFi)#;u+23c@5xeUq_}d z2~SeKBcoY*YZN@qU;!j!t<@@P$;Pu(`kNy|X)URR+nhGxuWJFFd@1BC| z7Jd8L7D@C}l$_3a>wk?)-%#t4(1`-I##daKAc_$E5q~1EwG#Mq1FR+ICA{{k8UET`slAPAV|N4 za>}5rgvxUhxY)(1>-mASi_sc86c0frNV#IDQDsjeSplfo;x=b7K^lT8WO`V-8v6Xl zxFCL4MP*$;=_$7HTh`vsj9a7B%KiqnK3<_acA*5k`<5Gg04JwN;IYV_Gkc4n1*RDm zeh5`6S)Co>e6wvO>dAVAopM6YGJhu4385bOjTNLg zQoX^o?`SLSWoq!SWQOky)VXE~(+`1%MV_<33!b!H6bw+}yl9~9prfl#_hq*EFzXu= zrZkVnnC?hED@tht%4J^j9WjV>zy`#T=HOtg$k}jU7LfaU_&Mk^Y^RDneZb6bU1rhX z{P@$+X0p@{YBoFh`Uw#WloEEZHC5n%kumWgAXAP_a;^w~=W0hkChuO3+VogN93d-1 z>o!&-3)O1evPg_AQcf&l81>xF1M1s@HpsG-);v@Lgliy>cKpQS_5Z&&KNsHoXmKhF z^G2}(#8wv0VZ|ktTU9#Ll!|Cb4HLY3pGpJsD(PMUJKef10qZ5P4pa@);R>##Pl=pz z>?+3rY`ft&3({*Ry1TudurHR_w?a+y%7bA1o<{L~E7D_YZXup^;^vg@Ps~8Ej*7Ur z-xW?A3F99BgLZ`nyxj@e7O@eVQ1b(0G3xxgE10c-eaPjzxBdhK)wV&lumNBXL|7-- zrQQ{RUE~VxScMNqEAe`Q#1nGN5{nH=F!OWpsK%bK1wh;BOq+Hmn)PY<7VyJ|_TDZ9 z^4UXEZ`(q`NhHeHgpS`ZSE-@yW3MX1WJbg&w`NiWxh!)n%Hk<+O?^X_ymVwmPB<7V z>xBc^G4?Q{vv?7Y3E`2@9$A+6&nziVpima4O{4BAA#t0|s zBSto$7#&F|+cII}E1(D#N16o&>V#uW>Pqi4b6u1noTXbvjHQTpDD>9EZjpHhzwkLCCF@6;SSyuml!o;V@8O%cA;ZY-{PV;PuIE zN|yd2$6Ug&PKK);6Yvrd)+o2#XYo4w6wrFaRda^b|YM!wwronGFnZfi@7)geR2II+gUbPYFi?B%K0P zXT7YW0#is4YazUi)HbNI$t%gbGeg5)PTg@#Bf&>1_#*`hD9(Rmb%-K#h?R-=CYSeL z&W80uZYNh6CmgGb!WzR50%UAEs)NOr7-*MJ5V|P!)VTE3A*R@w!h!fG{l=EjfhSLv zPNl7R%x7{+ow<=?K8@np!7?ikz1zaq)uHvgO3&ndmw?*E!EYD-fb3=wjSz{nIw=eGhE*o!6L;)=nRptxCgjr{F9;cOJjR^ z#U~Nfjg5mU6&|*pB#F*jcwKil7Kp}Gs+f%qjS6Y~j<^oj?p&E}qs*Gcf+I5Ar!YDw zCpL%#9`fNwDoS$QxRF9=LSwK|G_AJ+HRkdgLJ?cV1{m-mJ3!T@Q(_FVf?i&wJXbg_ z<=cZDFM!>gUCC%P%T!~`XAz^Ya3#hLGEDo$ni)*}ooM^&7U7Lx+hbVvs({kpsrQ6% zd_Ow%E_^DL@l{dA<3M9q=z4EWSwSSUVkug?&28afrZXaI8OxlnHbaUcLDL`QMCo$T z(?k0XaOr7}c1tN#SJR4_N?a<|*)jEBM0chr1%0nkM9?0YmMI%e z3%ZuRUeKbo6WgFx4DWNvvl|Uo>LSGkq1yT5K}AhX3%|1ty9+DJ!Lk+}){v7wd0J^} zuzSFLhB0Sw|KHFT}kJn2nlvvZ0h1p1&Rc zKCFs}qf{vf@wniV!CyvYLv&$JAZuV_zHlf{qJ6~dYF;(UQyEuCbgP`BqtzPZZSmwV zw1Oz{CRRgsH%~||sEttr!_fBT&%_?Z7hRDJtrWDwRVBw^>9qz%gEgG%!xxf@d)N~f zV2N?+G=Dxz1G{gz`|;z7o}8%RCit#pFh!lA?^>EmVZp*A4}Y{U;gUY=t()~M!@LsX zSr*a2(t20#t-|x!ro&v@$(VFY9#?jIExIpu^cOXWyJjGc&ydmC=g@!MpGd~4{|(8= z+`M-2VQ0{j>mCT^<~aC)H-2`RP4Nc@J83?S9d|d@fw^n#=}4z~w^F-5f1lo*rCo>s_Q z%karzCh=6HXzR3P6-16lM(LK-eU@<_I&(f`Xeh`e7pdDJF}g@Jch#6eX}-d;#vxkz zH<^fRG_&|g@uqySoKF~u|8lIeU3S|{(>a)+#n8bVcCAz&FV=qu2Uo6px^sNzfKXz7 zm8tv7sb!;O%8#Ok(kl)1RWR=vdB~6)ALC%R*nhXT6gynsT(P3o5v+F4 z_2FEkH+WCE>bfYcfNMW>TT4RD-?!pmtGA<{BPx8912(L*pXJ`8#COLg#fI3Oe9t0m zoB|V+X3)FV{tLteX4aN*hc%G2L{w)i|7BBcNncz
1i){{(RyS1~e3&yMHFX|if zx*`wo3rAm?b988*YD`tq*TKOv7y~RKHQ`co24;%m2uiY|uBunr`Q6rj##KNzT0&+u z=whsGe&y`S6lhFQLB|EdHo(T)B({5I5IRWpnAO9+jHH$MAA!HsMC?++2oajU(%Tbi z3@@E_WZ0+GO#MGMO}LBMGJ4E>7P*@TD~y9TkHYwo`TM!Q0cD?i%mX=}dwAIsuK^^} z!+)`kJ!cd>2F*HL>O-se=zP_pZAvB|bsuL3{iIre|m)C52 z_9$4|u;;;g;$!$51yy!Itlx%|V zx}w-?n{{zOvP8_Igky@p)BRZ*w1iLrT5wp7n-TJz7~dg8a4NiWdZlz5{3*P6`}eD#Y8jOUl{cbQRfcoKRZLpTE7*VekOL4?gEk+Enk2y z3k)m*taQhP9y|B9wm-wvQ-l2$R88y;fzG?s*X$t?>Zx+HAWBmc3u{wiU9LRd5HsEa zr@#Z0cWzFTdFrac{<|@0BI9?Pt>gsHQl#~9aI;w4*=GI`=E_*%QD7%j!QYZG zeTizk>i)T){Vi=*l$y82`#wq>qT+)b*w*|! z5`r`mk1{3<9Qh8hez9`mak%O`Jr-P9$E&n$XSjk!o+CYU#E6eb$QYkNJbk(&NdFTx z-6|ata>e1syKLirn4*u!&EHdd|1d1jj`zFOF3Y~}GT45EYVkv`7F1h@*sfTubXe9W z15!Go-vKyGuO(JqsPap*oPIM6i)9lhQ` z<~zU{F!E^h6&R)@6ii}5$^=#_ueH9iEmttzSdrYLf*>_UW@vXt>v;TMom6m6X8)`E zXW}r)i%RULWr8>5)UmBGRei@L7_`RTVwnyFNA}G^Y;a%`$N(E(rtyNngMp1JWevgn z8ynSSeLM(DWrk>B|B+>h!XbF=-BCT>y9gD;$sNMpV7%E|{NiwkjDWfs%-5fmTEHvE zsD4O9HJhb9%9%L9Fef1s!2vomT>xv^ov{KM5U3$WzRkxAA!z16t&?gaAxS%9ay4S1 z60eL})n>C8se_mp74~{7`{%PJTJZju6@xma1sVs+B$k(!RE&0(x0sKAVT7#w3gNMg_W`W}sF1tZWHZ{b)VF$?1rjF5|4dWu&3t@%r0@VgrGK%1pG zD@~k>gl0Z+GE1!R4dtK&ucMmE$A{1!*i4X!j7o)#&>Qg5(tw~!i zo>;`Pk%ezadssTqk?Hm_`2K}yzA$!(U-k3{@6Pj>#)P4R5ntqv4#eSjdlJJ2`O5wn zey_N7T9nos=^jW>xsk3I=-VMu-Kk8__!G@fU0@Hi|A5eE9<_tES1a0v7f|sG{sj`Hafj^_BOjwnt1#OoeWJ_i|RZLzz-yd&sCV##K6j zNY!UC6~pjJ__c1;(WI6BW_VQ-N#yL9#CLUeBBAEc!GR3yDoJB?4pEuQtym4j?CV+* ztla^7?4o+}6vOis!+A1YBH~*%I@Wf~%;#-C^n0*|6&SS!y!?ckZEu$h4laYD)BPvZ zozo(EJ(-^o zr*}h-liSSNiF{uEG`_74eZgpjj?P2SgL56m90!hm6NAEl_0oyFYO{{pFcgq|YsGVu z3~CR$S3B(j_)N!TZVs6qml8zrvGOj++`#JVK6J27Gmqx*n?7Qq89$u}qJ)lC` z*2(LEtvI*Xm5;K~Fe6W!#j*ihLxrt>4Yzv9I3gqi)eeUK#qZV7Jax0tT@%V$JGrJK zAi$VVrs;qmsn@TFkUGO|X zlB1740dqG%+&$-xOK8rtbU*)3uBb{AhISxoF1~ZcDBIkez=xGUSOl`>Xuq80`maoV z#@O`?l`P&499%Sm?w6m_010nXe#TJP`uJFN85Y&uKDvG)0#L5|yksA=71Vs}Hp`tf z@TGjX=y2jvd7+vPHheLCu6HJU)Ez>YcDQG5Z?1svbG98|xzC^@y+W!`lTAR6)uWyJ z!^9jM;>UIlVW39ZdYG<{+jVx(n3l=0TtGj3`5Kq2#yU`JoTn<+lRbf(3Tw@L9Sr;} zLaP*c>M2TF*$qim7CI549D=W0AcG#wSg+@=`&rJpokCWqo>i^D^%B~f;HZ+iz}~JJ zNR-s?-uaa={Z5!(IUXS*smIE&RT)`udBzYII2MB#|7-~li{-!BolSPQ?d#Yr*1{gW zHs}=dticqj{cJAip}Y9n!y3z_6OO*q^v7VM#}S~MPovG7oz?_9*W{K=stH5!QHl8n z#$VDl$UE<{0hFGFH`aX_beyGr2i*nkKE8E@vXPFLfiv{vs5OyF-7r*rlwyQ=57BfBQPH?z5uYrS`UA*U5g_O!0A(g`w zRUL_B{>I_VZh4Lwt9}9=$EMGQZUncb;Hlgt(4+gINT69vy{^(EoA*jIe&Au9r{6rX zPHCrckxaHiVLOZH7vY~QN`W3%*wwN5UO-h_2n&>Kq|EYFE^o=N?76)R)m%Y|e6?|q zGkHNW0X%CcKK2nJaOr~EQu|~SaOyl4{9}qT4n3By%5Q?iNyyL@t1TcV?Zznof%E=S zSufP07|?C16soI8ei%_bT6H6YKLKsJWc0_Mux*DIe6PN4mVs>POnF0q;*G+|)SQg;=Kb;k61X~ebCzvLI>9j{p+v(;-I*y-i?vVBfH z`1+(Hum@FOskh@xPG=f?p`9<&cg9@M?>ocW;lX|k5a+1=OLF&Q_!xPuwF!d_im^jb zQA9D)hRM+X6l;RG;!GR|tALy_9CE^aT{h5IPV;vp=LHx8j{fuHJK*|Z5YU=@Zc1u7 z!ZAhV=KeuU-T~B)>3#rx&EqxjV37I|@(Nskx(HRJS_XOAWPF{gri*1vWs$(*3fugx zu5B!jrQe7^AdpCK5$P%WnSE)>!>%wOFP6)9Rw*LG%2baR!H%KXY6$o)BuNH@h58I* zclY&;1u%aRzFr!-@Ix{d{U;7IkHVAYec<@5J@_%w-gDv9uON{Unz^#$q$u8dEJ0Tb z2chA&I8D#09d2p?J^S3fDNC>PD4cz0tVURvBin2|pJN_EEiG}U zK%W>|PjLm31@^9?db9y5BazP&EEypg-xlHp&)a6cBnf2SAMtc?Mh(dOl18`3kt?f- zd6XT=%|rm{2*a0=B{dUcxHueI{^U+LNGgK*5w72yR2%kdW8$;>CtRm~`9Emgk}E|m zYjORn9bxXj-?i@)MFL6}+De2=*yNnb_QJr~fm+FABup~F5cY%7<aC=ueHq#C|^*07yD2( zJgC5v8eJ8#Z;}*CvnsKU)V4$cT;2NP;hzYI8ES11lW4882dWTzI>iDsW(V3FB?Peo z63iarl=Y-WJw5ctNkw*4E&blX2V9y4>x_|gkbIVnfz&!k#Mp$M0}Ttr7>*yVs?tG0 zimjO}Ai4nC>h=;?;!LGYCmUq8C5_$$a|5RN(jh8P6S$EK34-!Y_DxstMQuHbQICw41UM*CA@QnQ2Oa%CR$5)rOH0MX2!qhZLFVOv=za{YGB^t!i1Aw1eGASL;zLSuF9M0!bTia^ zM+6OC=A2_@TQ=DbsVbCxS+82fRqc;59fB&iP~DJ#HUHXq@oe?NSFnNZaRj_u4WBW! z4HVype5*p0F+yfMd{~V4=5eEjXT+HLz&jLgCU}=Oo$h#bb6_MW@o=I$oN!J9DoCn4 zVHHE2*RTSgU?O^0DJeP#qA2culqm4aAxHn&hh4dXN5NH%u(Ber>J5?2dcHEVzXT<2 zKK`J(iMkYg23(ecd5@_*AmpTv3z*hK2}ZWFZF3_E82Ak)|4?kkd9gW9nJ$*{>m`^; zgjzexv2*7b=4N)hn}g|;af~Cd=s>x1e41s7(jrnqFc98XxDej_7cF=gLC)YEy>(!Hi zuozig&l+4>eU4$>0eA2|N%^*R7b!;Och4s~^BvM)|7=j1c`(|c z_EO~^4uq0U_x;nk#W40%LrpNcI-2YP2~Ngs@o*D>k4k<|?GotFBM!@Sal%0f(R&os zzv1NtcJB39H(R$XcfUlxiV2H)y4Wip689vA-j479*gv5(f(=)u`MNaO&2jvtWqV6= zD){=2i@yW3#R1BdY2FUs45a`{yXPW%Ei`Pox%yNbTVW`OnLBCZmPhf5}_eqit; zEh$5>CLv+jD-yn@sfEE*t%EzGM2U4PQR{By3rGLNoh8t$H?a)C=rMe`kp~1O+TW&} zjM#V7rEY_-&fQuSR8gE?!J*rMSBhuQ!F#8v+g!G6dZ*{Z`??XU6Zl89?;M|?Zn(wD zX_$Z1c^68asNnl_wgU=xZ#v`qaZyX`@x&%tTas2L`$m5>c30Sp`9Filt7@OlOU(J{I?M@il|39i+^$7%x z?P=;&QG@9_v@o7XZlt zM{q7S{aeuUuA}cfXT>D!3%nB5?I0we{*(mUWB!`EJ?=v$cB>0Av9euYw2S)POJT+5 z$(fG#-XcX`*(|@hgF{;AELc%LP($a}gv0m3w?LjHbpY)7PV_m)r>l%368u147{4i0 z&`-uReqX521h@PUu87k_fP)uZd{@HG30uH_;%+jwokZipvFuSBJ|vSnIa8NE$B0nP zJHhnW39|o9ec9fw5|sa&dI~z&AgM?`M|BOp`klO7Sywq)fe(1+YgR!eGrmY->Sf~2 zPz9DMc^s1Xz(m~B;7nVn!W1J#_{h;1+Y?yRpPI!F^espi6kN{*P0o2%Kn8@jBi+4g zWB#M&_U3y#&Yw(IqB&pf^mojKoE#i{;z(a)Er|NIJ_?7!9*vB*M2*9~UQ$66?cV+8 zY;ZWoQ{)HO@7Vvts}c%JV~05D5B6te)dSw)I=h^nT>-QDFUO{xuam(hXIn5b8M^i; zwCII}eemQxFn6g(AiWkVE;sZ;$GQ2i7{iQwG!!`(D)<&?d!2pkyVHTkW%pMBm2JP! z!{PDfor@w0gio|*Ke^%r*NrxkG8eobMe&BlJpxb%sQxo9g-i?*glV zPmb5WvnW0kJfJ!HuQrmem_$>PB)TF+2J=tRRX3o-*wUFXs!VNztE7&AGVoJa&n~Fy z36RML2bVt2AjmB6pvmPQz(9BW$n!p2U=gBlxP8z4qu#JeZVy~HxiS7`R~x3__S}Gh zwO)5+vxG}^FAm@TC4Hw~ofQH&sLGWqpYtt}uN-!?9cG_;9v+AsXr2ej1JnZPdoR<* zD8W;dg9wIm@z12jI>7#21ta!L-_=fEK!JS9#ZS7?-Xn9R_n*+ylz7w$!v6{PzxwrB zu#q2X_q5rs{xPkPk4o%i;_p!r=vseip@|_kgdMM(zDu zj8^F@w`g!1EXWuFpqU5V>31qrY5NYg`Eg9JJ8l??nrc{Sw&i{?)vzu$pO%=R^wR<3 zHwPZzE zG@dOtUxa=SGhZ8|)|Nx*KpjD~b;<>`u%-~}Ry#qqY{GO2&p3-?(PW{W1%h^wJ& zZk`o>4V90+x~_C5@Jh@8t=%T{8!@HuyQwBj~Z(T6*h>F#sUQP zC1}2aEwu+(wkp(oTVmG2mW{@a{1o~5wOK4(4yq%!!Kt7dUpPS?-^r7kv8r0~H*iM~ zSe3y}HWb~0d&{t4Bh%T_P%e30=0bmCP)<2i zTHzg(81#yzB9|gIi=zZK*h3R47#AhJ0I)k$a(jnP2DF|Q#4ymS6~sEWweK|aJl~_y z5gk{kW1_HU)~2Xn?u*BAQU0Su{-aQW?0f$Y*FPIWT!TG?v*7kCb_k@Mx?^nJ_K0eM zF|zON7>jOm1-aQv)OeF34Iys8EGH7yhVq|6f&t#+RE$H)sFj#u?`#9PrJAub6&bCm zQNi6{Fd^JG>_2TA7D;XkFj|D52@FaMubr*rsh1tuMz-Jnb_((gP$7@fUql4*NNLdQ zG4i9{O{w_-mXhM%se;X&kei`{uu>?6@Z~X-J(XRZ5eIs1RIwwhrHr$uQC&D|0xFK4 zMg=dar2!x02z9Xh(*Si^xGcneM`#+j7`}xS{*K=_RS>O_zMBB|EPD-wMvT(U#pp-? z%iztzB9+EN%2%6OEA7M+AF8oDmg#hG48!6124=(rGyegY`*#m-$CVLVDB!9VZd2x_ zFnzmZJP0D+2o-cUpwQ=Dg$JO&OTf>TKJ^u;e1hDjj9k;D7+k>}u#6MKD~;uahu2iv zsdZ6ola9qYyK29;1~yV2W)ZqtxKkCg9f$hc2)Q#I&kT@0NGP10>6xJ!=Zu&_U8Scb z^sIZeqwR{6q#;G)B=RBMJne;5?Hz6A42d!5v>8elPSAnK9#gZ^+=hYh*Ns*z;xE8b zXU832DaXUOd>^!!!M2Z^r7d}mijm#yZ0W!dGH5IpFQsa}h*sRdg0y$yGI^?1R6g8= zX}OViyGcC_RC7E67rgrawfE*xP2BtczhM`|u!XQD#f@64(RvjvOKfeg*IG$iTWMX0 z)?2-G3y8{+kkNWuYN)B1S}v$TIB6mX++u%=bOGbh-C_ ze*gW>`F_6foScI*nKd)-_iKGVmJXY>!=$`O1#DN@rQ}y9{lWH!gEU3@bqZ7{LtxG% z`JL=iI)6tyG%0F!!9u+B4&kNDUCNAoFtbeRlu=hzX0SByA?RpP8=J*rDtlH6LsIJI zP+h+2c=G4%8wZ|1?T%?=dbtk@7WuTp*o*#!0>Udv4%WyYOxI&SP*+MHnPK0=R5EbH zCh}{}9jX@86Fp*fhZN~zF?9RGUnC(I$HY5YB%uCZu=`<+kB=94Z-YCH)ww5OXbab7 z;Hm&DXAt8TW4XOO%CLpP+9BZ|v}+NvA!e@hJ$@ml}d5+|G1$tk8Z0 zT4gK?Oe^J)O|!dTG$!}@L#T9e!*%nP!ge8M8FbR?#3h44_Q)eK(qNu>illOdFZunLj1x)Sf0~@J680=9xqR{uNj&{ zh}E=ly2SE2tKpPK~jqa zOF_N3Xjat{dG{t)%(w+gy%8E4ED{#mp|-)a?=l$wWjA*YuY|eLgeK#>5?C3G9e(<7 z;yM4?)=0bwPMz_-M85kKH|5K$r2~B?F*BmM(wy{-8sj`?`U| znNSfk^>AP?@Sp4+>Lyob2=ljY341Btd_1P|-wo;Y7m93kP~MCW&Ih=KxgdZr?u zyi*omu;ta@f)-xsqi!M7R*D&**g((hA}tjTzvAWZWVb4_O$YC7u4 z`Hr-IE`GdOcr!=ft2rXEDE~(&*iZ zMEs`hMGk^Yv@~_-!I|(3f0&1p!ra=T1RX8o`1tWV6-Bb)C~D^785K|w6w1uRjl4n4 z(vr!`j;C)Tv3QayXS8YR+aE_;o!B*`z}XvW!e#bPQ;o`dMpWYxCY{NPfhP{nx0hlIIEa+Og!TeN?Z8?KrkfKNWMaanPD(Htw zDq#eT^3>{ea3+9VJKe*;gV%Qf49jmg(m+vSOTT-8kh5xSFrReFZ%X%q+Ax1 z%JTZ~W={OezVI_1eyGy;m;DX}I(-7CPvGUa&L;rSCzt_d-3tx|#rtWmNrPv&|4eZL@GKv}+FK_8&k4YD0`QywJSPCp3BYp# z@SFfVCjjq%4*(B90N(3fQB`wEAta`A21eeWm>??K@c@cX?huk zZ<@g$SO$AY)#fkfmNjnlwS!$z{-OCVC61+$a&!*X#bn>XMiN=@fJ+=?%YgqiT42~< zpor$i`Sq6 z`XHj4lRWqt$&3hs9eAyi{3pOu^hd@|0m;2yJ^|QX@>fyGZFUyk!fX*X%ktm&DmYyJ zOndhH3Tl~0io>^9eWh!N{Kn4@!GeFehPwF{KFP8eJpSm9QUGzfoT~~7VQGW-jN6Ka3`r=Px%vI(l9Lyr06_7T#MIsf{wG4(5DoO z_joS!ac3Lf+eAXHvW`w^2WxPzIEoOwnh8a9c#L2l7v7%yvYk;tq74`aCMWwvdRC4h z>r?IE^`PJh?of;gcYxG?deabjm+`_VDl;D)`Rr_urjcu0h}e{z8s)@;5E!0!}l%+in8H{pkt?&u& zxzMbgE+1*DP#}+N$gL!SV-W#C!MC5`9>QPn9x7}lX=?opvA&#ZzslCRqFI2q#FsYn z({;u&$X#R`)40FLE?jjaXivzEL)mn6Fekyse2eL*<2k+(TMu(B-*6me-eqtsFqpPr z(!Okoh4UDv-6NnaU-OL!2>f#-#}zk!=P8_GBry=XYF$@}U}}V+qlo zPV^q?3z!i9`c6Tv&G9SAUw{x4K^J8EKuGPxm!RC}Rg#_}gkrAQkTF#ud~$&lI8{5K zv|B>^LTo)eJlH?SmzE&1-=VLryiEKLH!a)YyW!t&7**IOK_N6GAPB4}4{%4Iwkmtb zfp$-m<0nieFsjbnh>fI@jb7y#$*90bu@D(#%NEyioo&4;wttadyi=%G;`SwMf-mge zAb)a!ZCFVipC=#Yvm?#d#g07^$2UwIXA+eVrp7elTpf)|)I-TpaKpBK1rv-UtbTyw)J_#9uiEBX;LUQ`$bb+?WiJ;>zle&0C{=r z>#m7uhr!x4bh?Wcit1eOc-w`t0I=o-Grbz(svs-EM*~W4(L*BEB@abaRM@)a?N}{) zhO5ZTIPVr5KAQpg4$C|l;P_IvFyxztRhQs#;x-pa{v+ob%Gy#jnnf)mgHW!z;lak~ zqdK^2(%vRn633t}-z~8=nVvARMcA=HNB|*D6?VZXs}OVSJN5bF`IjtYx6GDz+Lefb z!|mFU+-pKi611xT>4n<<(kw846>PkN{k$iv&$tYSHzlTkEvsBYLGExsgxgBS_SXD? zZzCD|F-sk4KLGg7xovU%4TimnaHUG*4YXSlpKE?o+LnpGH zl4M%Jeh=3W7xf{v3(H;)J@^J;2INd{G21puT92jOxtTL)06*lh(W%5mZIw; zZr4d6M7pSb5(Z>63m>T}aIJy^ zLtw&i7pYLz5x8zE>|q=N{ORToCq!U-F_{Tq?)p5AP>j(A{9| zR@ZRS(ijiEc-1`$_BKsdjRhT-#Je1mDuLwUOBl}PVSYHd*$R_Ik{IP>dVQ!a&oVn+$$&dY81@+ z49V)`UOBl}zXc4P+$-$ALc5&YD<}8L$-Q!NubkW~C-=(9z4G>Oa<81+tN&-YSFQ2V z1&4gkyeCcZ@OZ^5r#8%AS{oK1%y;6hoVY6|?#hX~a^kL>xGN{_%89#j;;x*yD<|&C ziMw**uAI25QNrdZ@x_U|vY+D_4@qz*?#hX~a^kL>xGN{_%89#r)+}If;;x*yD<|&i zkEmiN?#hX~`u_=cH9)5k^NJM_eLoRv-+BSQcA!%2cuEfiwD-9LhZ1uk@745sgbE@2 zu*51J03CDO=pm!R{_f`A^Rzxna)=gOSc^wdf?yA4%r8Cj677o7*5LUh`WMufj-|u) zR=4Tc7v%ciwE-U>)(U%I`&lH!IlxY0%I{XC0_KPAVfpP#eM0Fp62cp2j#8qx3eb&z zvnwvdt=O;xwtgM+2@V3a_2WW3;Av&O{|u$udDNFawOfyx_O*2%BWA}p7ue#8q5Ix{n4O~lLdy-Hqg1wjo>{=bsUP8J(g~LS^3IcSD4T=D> zZqb4nH`5>-(L8Z4Miip>KSlNQOq{L zvK>bqgVB;hn`YQ<^X!jUj!JcTpGsdfk(pgvBe-9&*gC|q9OA1IsByY_so+ku46>(E znv=3`QSIHV>?X3`5EOml66`+Oc%0A42u1QDvCdysiiM^P$Zcs$nklpWfJol6E}_3w zCF}c@TqrQt$QzVMkEqfm6qcBZfnZX`k+Enu^HbDZN$diG75 zuzr!y9t%E@4=fS3M1`7=PJuiN6~{{MzS34#ybPWk&*} z%4g10+{)MP4{;~q-=Sd5QR;FEY-HT&7qouI+(GhyWpOJ|$b`{d=MHpaV-$&XfHn5p zu1Wm|L|1w`zm##!O_=A6R2Ml(l1UeKv4l@-!q3vuw5MwlzU~`o#qcx|A7`q@h-t6^ z*$UYv2S^C0xr=SCkS)o{zbX%NHB2}TqRRck!An;IW&zb1?+CEvYCtGsjKn^`vMfS0 z=`6=K(UH0rszeDR3WDT2 zQuP3fBA5I@9L_*2AQcmcS4KBc)*c!(+(Z{&S|wS8D{pV^nx`3acuRyQe)jK|QX|ai zH{p^Lkm^kfMJB^{3hzGGrdg*vyBj%4vfnb##@HYAY^y`tBo3W;RMTKvc`s7TwH;(< z=Sa=nhOBNOYSts>M4n}8(|7cZzbPtoVSRMShQj)%vnQ|A@eH{TGpHUpB>rytA1Q~p z>X!0_{nL^fgi@#>UqvgBpAV1Q9ioQj^9Zv*efC<&#jn3 zj2~SE@8uHM1^V1oKw4kOGFnab0Tm*}t6#IHk$6rNfm!-(n2&%*ZLPN{NG3yFYg zJb|~ZvwOS-_!oQ9DWn8KFhvizOKIVgn8f9kNN==|FJo{Zno*_JPkBB(J-tjK)&V+Z z_)Y_lzodQ$mR8fY=YRKMAej4C-+4aDMkxZ6{O%K5Ex_LuGQj>3;1_~i#vth|AL;MI zH6w?+(p!XT_+VGI#>(0ZF-P$nl>eARn0XY4O=Q|3nX@T6ag|AlIKm;xj$MHwYk6ur zA2reAhSstmq!+V4WD;7%jNoQSH%E6k3{?K06n{ce;^O04vWM1jhqqwHb(0+s|8!Mh zzoP{GwgBpsK@{J|#=FF=71eE z_wf^4&4-{6fi$x~BiDyHARXR}$=V%uSh@?Wk zyY%$<`EHjU{`wi%m+m_i92n;wa_F{?AMJ!&N=HjubT$N`-+~$o)8w~vQ&I57%7Bmv z&(m{u4f5t?lh3F%T4;m!GBv$TXe5D&V0Sw`2x4OcLgCL#?kt$j8gf*b?5~Zo5(kzQ z32M%9AOl`|(<2B_Cyn<7chC7pfGPg8&ET!|ewlvZu#UghAlq?= zgP)3yQ$6IEPfLWIU1Ey;u_RkT;&@%sTMZ8&y-(tRl)LK?-1fn_c+i@rXn?qGxfL?% zoAV-L^#!QKm1$hd(qD_l`ymTan%!K(#U*0!S0RpS)s?}iY)1pvR*YD4)7vaR@>Q^d zJ&Nx|1G_}jPzoCyr_nvd>f4o>o4zNZ6fQr28JdVt9xW*EoXV=magNVyh9N zK;yX@clFI@@6Aqde!71cIKLE<<+=N*uLZjb+H+)e;g#BH4l|XwAw?QEOhyS6tHvL=oBmYrQmRq z<)(prxMY^eTLgjX*6V5l(m)T3&4u8qzx#BG2)rNW#c-9x8i0DzSeol!6#d}9X`d)J znJM`n;K17+G{Ct}^_mx#oa_aS%8WCZy^PJ@3$h-;8?Z>J1j6z~q%y$+9{yM?gFv;RTbxSYc{{9T zlfcG$>q!n`^?@_JSi9I88(G3Ow9a*#VbS#E$-+m&cXaj#zZAFp!XK8x-jjiCM& zN>KRyz^$(TzNHYVAe;uhN4#fz=D2hzW*3KOF^vM2p|B8UtyI{JV*3GX^D^5PEZcb# zB2r|-(PwCM*h}fyI>SbCz(ICXO#A$7dkvzll@EmrT54q0{GyQ!T+2b^o+%g7^!A~I z4U<7++C?zrF+OC{^Q|EL%#zripU}U9N==6#Zy~Xk%0C>f4~A$)aBP`>C}6CndB9VTH2Mq5`9$-axEzhMa>Nm&^jm|>V4Q5gBTRj^tm=Uzfg%-Sa^%tO2z&=LzpzZFG0Q#_MEy*AIqE1w-$|uF0VNzkVjlxK^u;BYEB)iG^3R zQPfX(TN$qFFYJ5xINlWJYc`9Jr79DNYg}dgdQoMBDHg#?Amp?3Q7;87V9Sh4prz>4gukHY|ynQ0UsaxKmKt9jc)6Fqg8k`@M83gvF`9(tUCd!qd zpqIi6^tp5+Zj=<+p>un|lwU`4{OmgVW%nl#q?`(7eF$mkeWm$t+ztj&@4C^Zl$OcH z$opg2pOwyPmR%;Kh=H`PpPu@2065kQ3dfBPb4yFffPz!=f@yC2N_7F)HEw+H!Xpy> zYpaZ>YvP_j6(dt|X=6#;@YHnenDBzM2V@CH|3M5k&0fBS`|!>LuK><_ZV^% z#%OZ7eC5smc%~T)Of!dVoQ9egVV3Fa3uR^6*Cm8^RXSo*P%wSGSnI%uCV$tfXd75F z(LFdM&#S0rTIH_=^5%-{hZx~d6C)hN7n*}JSbibC4y%v?HI9g(jXQ2S!0t2N!GGQP zRwcCV6EqXqy6dE2sJ0X-%($8u`no;yzpWY52D!}X(1B^XkY0H;yppN`ni+n<-jknu zp8kLm;IG<@=hq{lSWjQ{#$-4=G^QOg~0wXJFA)eTFSPs}*ycL+wTI{+R@5 zOxG_!`!a2s4=JN`84H&+JuQL?aobvHxdWowfk|cWiG@M-&NSV zd53TGM^P)Zu27~5SMiGOdV-051v-Nzo zEg$9Al|`dZrX|?RxauAiym*EEn%dqi!j~(Rj~hRzT`6nb6tC4P%pm1U7urOeshbNS z$%SIeE$p>@4GS4NyF3L?nl)p}&Xxtwm^Rre=L6c08>B$&M>JMZTcRui&NDW%v!iy+| z;k@ERW*4?0q8JLqk9+K%twreU7OqVM{lHFxyVm}Ml)m0ib8mh#R29TJIyQbxjZRdhpw-ol=NXAdTRJG9_LhGnQug3i z^j21U^5gi+y^jN1cx9_tU=TuxGj^pOg^}zE)B5f50LSGuOaA1SUP#@DC_mYx#~^KcllJ)04;QZHs$i%kPPA{| z{P4<@->>lb*FEyy!O0kIDy>5~%0}{6;@#h2Ah%$Dw_ab=SO4@s#iL*9<33$yUvj0S zi1gB)1up^PjIRfRly54Dznb>A$)?OPtASK6L5Rib@R zh0(T~_sPZ%Y~$R)l5(RPbtd;tZ*HjK%-}$ovaoypmaTVVV)`m?lIs-f($eJR^ZULW zxRN!zBRVs&pg><&H=niOpQHEt-j0KW*J`CWAv6u5-|&pQ-qT!?>FZM?A+w=3$Ndz=R1MtK^H{WXN6rMNv6 ztNDmn`j*CC*|H8B21wKM(GQ7`u5|=@P{6KgYH-j`XYoCi_I22v=eK2m@R!a{_8XxD zKHuQQ{<$jf?i#{|xC=li#G5h7A3n~swsUo zPkcnsrJ4`j2Pq*4A>}xF#6)$^Hph7=3%H(V7~o{guIRIsrI~}b0)Q`p@C-Mg;7>;97L9^0r)If_x4S=bgrG8*I_~Ig; zK8dz$={NnfHVTr)HuJbT{-78)2;>{n|u^>rePhDv;;Fn=49ZLa33 z8WM&V?PlT~EI5(aR1(`2HumVzbKLhQ;LbUJ#%Rb(3PWBSD50ZLw<8?;Db%iyzPsKb z5)hK?W_Viw<)^VhlXWE(cO7uG5jxmis!GQYG-QSU7I{m3mlF6?RyARm7J|I~h;7}i zww!zzU?!ClqwUq_R&)Ot+Ag_*Q3WQJxgzqg$)}iW8?E-Y>kuk>uflJ zdEihru{Rv;tzq{ypu>wf88_f)SH;BJFhQ5nZf+1~^=(mH?z?}onw$nL9ctUb)?C|^ zzmeO+gqa<0>ma{FiWpa*!rRaZ{na9a@p|UnccH`h{%sn4i@p!Bn&d-1sHvYLTXLVI zpVu>W2ibTvs;$OM)B5hJ##diV@84f5G)eJ+aQVO@)=_NQ>npfhjKIX6 z=G(w%>29`NhFWe&EU85ev$x8^Qx&WG*p_wjt(h0`-D> zXwhIpv55P)l{h7FXzvKD%hZ*&yJ?6?$!Tg?v1%S_B6V92TVhcQz|3>lLUk+sN(o-a zP3WJU&|iT3f1XnMF1GpUDa~TE%WXy4AEW&onde$b#J$+~Ij<5$I)EDW4n_oz8}ME@ zr4I5{<%ae(=@5A@MD?+3-9F6hZ))RjQr6+UJfVJeLRVdp@ggdR5kRg)w5vFNSfZAh zLaWbb{c#T_yCqg^eSpgQilAKTPV!IdS3L7QAG&JB$+n%%T`)q;)%SCV<_fiN;BT0t zRlRj91BR(#M&&W}?j6ih$h6$x*}vwiWo3iw5q%752^DMCpKa`bFAZm0wYM3*Dz|N_ ziY@Eb!Vmt);und-r_zXL4*e2BwHhY2qXZ_H07_HppU;vmc9OFju1O=s3A&k55&&tllP6o7$}263vvu!$VEYNbd&uLnHL)c!(*T zJ=~Z)JUk?jDfyy1_Txq4y^rlj%zLIOBDu z_feU;a~e9Z#C~5vbW~K<6`zCI>m>y+;&XcCXo_ofTW`bi+Bsu>tow|NsX~d-*F7$t zY27&(56!()I6Li@W;H82w^%NUh`$1Rg8L){x>PT>m$Gobc4ISq=U#+Es3u2ky(HdRP}^oKOg4q11B;Np z7@{*>sAu)&HicK-nuhk-WKE>HF@BeaUsv0Exp(LnVlwiRdWH16tA8qkaoLEBp_fN`pqHT`0} zuqsyGQ#=Mp7r4KKK&RPT(GT-T3wJ3q_DKl?muQQQ?^nQxKHUu8#uQb0O(e%Hxob{a zQQvk&Jco5s{wP=WI@}Syt_p^tDpJ_ZaaEftZ#1W&*g99J=2f8lXR0US0`Grl>zQHtJk~pj^Q!PxP za*)S`EWJ*Yu*3Q%@#BxE8tG;$?RyGh9c1p*cE(p!m?GK2IuZT^*l$Y&HG3E%pH{() z*Y!^a<+iO|luMTqQ_yqg0;>e_QfUp$j))>-ch(iAC-j>TB6>xE>T=g&n7ONQC9^2- zj%-Lb$-8PKmU^b+I~Ye+n>$&!At$c;Bso&HwaR!K`bMgm_%CB$Tsv7)@DWg*^j-y) z%=Zm=$8j$Qo0pgW-%Ph{?>z3#(DH5X9o&q`N?;w<`b ziKuI2Dc9D8jMxyqlyQ>o3hFkxzY@fkJNn>*j(6tB)k$+>=%TElJz(ZM zm*CeX?d|Jp_Z7L9@XGpW+g>D!M^naz zqf)HS`$NifX+|!!3~HwnVQ|0~TeC6KVVEOMgPBd&1VWFgR-%S=Levv_@{@OH(8#qKc9#V2+zY)sTel)hy{_gGh??aWIx!ndk7HB$*Ord< zS&(B8DI7m?dJ+4{9(Lz2!+tLutbKKC;MfbFC>-lA6xkp{V6uBC1SXU$_hTZF8u&1n zq24uux13DM-Upt!t1l*=`Lbrd?3OaSQ6#KY694@1`i-F{DTZHI#Ayt#L(MP&773j~ z_RzXEZTGE0j`atL?f1)|E;{e>&~Te&2LX#@jC)1M4fuGCnJHF`Sq#r;p3h64&mGL% z#j#&BT``vD_hDvNv6zjL8sF1-Vo>r(H4hr(-a5KWXv66&1`Ftrmj?*?tT} z1#|D|o6}c2&Wkb^siZ%cU>=gs9?3>0^hdV!7tBtv7oZ){D5Md*dH>UOJGWo9=Ckdd3+``zs2?M7WU)*OG21k0d{+?^T<3oB z>UyT`01RUAM!Y$hx?x2Il)*+E!|bGYGFxK=UjpQ0O++ltATxVJ16$jCsX{J<^;3b0 z{`U4xa=L!|dsBu;QQm)QQT9-cP`Ru~2T0Q#=B$wwQw=+~rq(|FK63Qx&a8wv&RVMD zULOWc7nqrdR&1HU#cM>60ZK! zQVYy~&XMR|;TgyRXpT_Fjq90;eDY3Ne8HAig9}=CrH{IWOj{{tfDw@%G7w98!pq;u zZdGQRzMk+o47^g3ZDm*wto+jRDu%Uedo}ms$D4&Wa|FJcBNB*f!+X?o@&-*=5^u$` z`W@mp&|IU0fRdNpSA#sE-`cpD-{yGM!ZhDy42GpjB;ny{$!=ZqV2{JR?B02-2QO9+ zwXdcSRg$8jR0u=vM%x(Yq6gh!`r0=Ql58{BMPf=Rn9f{LtYi1b#x{Ck%eV hrC)hP^acg}8WFK#XKs7`82E4T+lgFlQ#eW delta 63001 zcmce;dt6iX{|C;v2!c2Twjl`9$jD5mA5&0Zv-*4@^P|iB7N0~}X30lU2a*oh`u6$V9>2%$AERB)IXmb5 ze!ZW!*ZbO|^xW$7!%?0QiF@blpF`R|$8-N2ul;kpE9UqVGdztr*w!^Y+!HATGqz1z zx)jGZ)MP1~D1HVX?=Ka$I5+aU zR!&^j@Bd&z`Ct=bQ(!jwR5CExM>ncX2UB!`977??GNP+-%hxyM%lhk??o2hWVkXnR z8O2&UbY&GaZL650tt_jMOg>(pGwSpgD0r@G68Am2B&psUQ&RN{Uq8o>lWi*mhT4nJ(h;`F+96v4lv&kI8aPoTx`C8dH33j;;9BEdsKg z)1F73o;>EctX#J1M@h9#70|e5yKppG*0HMQe*IpF-AKnSv#`AS`T+rxue1uzNVAHR z{UVNOfaOX>T;#mGCcD5=uCu?vH7u3b3t5h2mbDe-51t5S3yl$`Y&=ru+`@Hjk$Ac~ z*={WxYvTrv7zTYeT6^e-X=+TDU`&DE*5Nm-`0y(g#l_8R?6y^Oqkd=X@(n^`qsUz> zRVSro8-Sp#6j3=7y{&wORiHl*tnB2e#~Ke6V|U2zt3Ys5IZ1?RPYF_GUifcYYNWsM~b4X`>-NbIK&mh&tNxTp*X@LrGw{&+$~2ulNf#E_Zz^ zb`{exIj3D#Sujp~Ud3pQxMcqo#kOuo8`xmCrVLRL9<~knH6}Q23HzgE7O+|?PAji~a zRS8dz469uOh0MM}x&8n8a(r$3o?cELNn5R(!OpoSRz6DTn}O@MiY>o7oive^hm42I zdtPU3sI2}vr(;I}ZYw~VOB2SpC64>3TvL*akf-iRy&R~uda?A&(D7C3s~aLuGKH;Y zS6PLvF~X0wh*~r?&4nf7n&>qf0$e?GY&=HPmnitlS6x~i_BcjRpO;dZ&YNq#(h-Rp z==h1VAs?h_?$_1*<4Kuuus-en3E%(2T(y2RNBY9SWYxgwT43IdIPqSPtXZEg* zL2QlkRwr^JSuJR)0GodGr^X^ZsR+7yjHnJQeZ9)krFO2yF|OD+$aSBnO_q3HX--*zr^*n)8akFTH2>*U;C?S9xf$a>HpV{&B8;q-sy_B%lY-7fnoe;Xc9sB0 z?Y}(O)%@{RaPgKum2?~Qte--i-I=M(?(SptmQ|_yaBS$$Gs!nwdM4Fp3u%K=(9ClU zaIix>p)!1X=KE=532IX&st-^P@kE+Lk%lMwh^ZFri^&pO1qy}x8beU3winaw#cKNz zyTBBv>J~0YsiE0E=i1V>RvKcM_vBn=BoL%i{kKbx^-v;6VD?&4m}l=e*z_6xcLe{1LxwyH)A>llP*;K`Q|&p)PBkguT`|Epms8qib$&!7H!4+iM)bL@6{hj#QV%|v^_G@ba(s{mi9y+{Aph^`v0XKIV73sgkaDS?k z!4>OEiSa{;h zupvosIf-8(bsyu{TM%2GT5)m@Psh*W*}fl95ACS_S?4mIJ zJ%;ngYF)h3hGd25=b^Slq7WN@DeW}ulITNEeQg_}<6}gr&wjK{n^l#RHRbKM(orRU zU(yA)T<55wTYEU}UrJRs(FdD3#?x0=E-!Sv2-QZ5h7*x>_mF(q{rc@lzpto0NjOAi zj?Lwzx37|qNA2Z0e-LANsPGX_WKy^q#0m1h+yCNR`jc`;lSt2(yEVk>A$e##Au;`J z$F|y4=;#@(+%(#U_iU}LtD6og&XE}IO9fY*Etd=Myi$^{Z(=+PAwb0@CozBJ&JGdg zl$&UblWP7D5QgY?sK;Azy_D^2;bPyy3D5{$WV!oL_v>nF6Ps@=W>~*0@Of))d?^>V zux&qZ9XG_zD(>O&M-uk}hW)4{P|zV2x>&Ay9Rg>!5k5l6n{}qvM}9Tqt58)Jk7YJ2 zEWlsy?*719ia63lwmh_ZE8AHF_*F5UZw-kYrRZQDVhbp-{DNjz0e;k}ueBiKEgvfN zSab7^ZYAR8!)JfDKi|NY_obG|xYksWd7e{lVV%0)FCibHYO$lqB!p8)Vb`{hYl_15|}nus&{3lJI4x; z{wU7groWlGyRWshY<-F^)c@=i`CVv!7q5TQF`%|D$1*z!+&7-Wsl0r-U2)Z^mFZh_ z*c&=GiQDon<5P~kfrb6VuX)l$XHUo&GN;KCNNy#KV>JV`Ug# zWj~&V7^L#g%P_Ob)|@hMP&Iz+Pb=TmVNy!ZF}iaV>aN&e<{F#O?b3zYy#gT8q54e$ zenpfR>3kz)gyd<*!RaJO8_}C!-Z*9H5*&d`vc|2(@4`WbHas~w&YQNujQ_fizRO)y zV=w#sI-b>ZX7`o;f24o%%eHS_@DQ7W!WX(@Amzxh)kkKmn_GuxU1`kd|HlXXnc+C@ zYF4`*M)2Yd@GdK!9rA4;IzzF76a~cd1b6giU`NTo@Fs~0isQVgo^yu4Y1Ahe(b*4i zJmdo-h_RKaahJ=q&;J3}69^w?{spy?SU;E8Zql7*o=U_yKhTh3+$6@Tbk*ypid`2u zh-S?CV13Hy9J*yl?3CyEHf>6<2z@3P9|X$<%Ca6uxMIVj1lv(R3HAlcB}3id;Ld5Z ze`u}$r+jTtg4x-w$J`CEs8l91Ocq-riBV4QUVSoZNv+R0xxP;XT5Oa#$kR&-pB`Tu zExNDl;_3Epj=sXQ6spGNqzw1eDg|~j-cHBsdwO;;#j+8;{BCkhbJ{NSzferqO}hKM z*g2$5$ZS4P%CsM{*B}K};pG_7ite(vr=$J>~3ewj7&M9kgCiZS?>9&-ugp_EKGYL zN7oW3-jHVfkYO1XV?(Tq|5zU(_}>ijx##|ZoB#-j|&hmw}3BZ_w1 z?UgS*;Gm?Ez^3E=3qf})g-SAjBU5~6VGqJ;AplE}O!@;jpHM`E(jQZN?vRkZblXWt zp|~3tkk^RiL-)=)5RD{fWI+`Zuqf?<=U(s zacxl{sxu zJF#&EA5F@6Kto3J6UOf~ctH+wy;+Q9>#T1{9K8j?i+Q_HvGbhTOYY2LIU7aVd`UGH zLe{^*wY;QuMu|=S64!^{W?SSNOIpKa?PUU8fuLnxjWPe@!FJ~!q&E=>w*9KJ_0wH9 z6-coXu|IrN;O-8Vc&QCuVtXH|pA*fF=(|L>m4Gf!DxJx4n^d}EDdq;c^F#8$GMTvp zwXm^XAhV3fHl*3tv9RR6ww*Qh6I=*J@xui!q_+n1@dKv^USc~t6dYeHS&^vIOu zv6X%`3a6k)fxGlCNsNi_b#iAaV^p>;Ae4$0+{hD*4YkI6Jh(rAxAE#HVtBIAj|sfn z3+Y-j-Px8@^_Lvb2&QIFqtcRP4-!c_+adAzTat0=@9^l=V^gVM;lC+t5S=s47QD6~ z`wqkHqRX2)1dp~=4PN?14Qg2-6p;F+H*J=-Y9wAyhzgxL1A>3@=ZUaS`u3|nsiX~Q z^1(RF$Uqt)t_ZwR^>c-#L}#z!*!Jtj<4{u+YKmk1P0W?oh1V$fa*amRLzB1aTrCBz z7M<&cKyIJJ)iY@fRe+#AMi6Ut=A$;s(}fZJ9OU+pw@1>pUtH8j+Oub=*m6IvF=O~@ zPUD!BRJtaDKfF%+4Txn0MuJ&C_(zgRh4Vj}2RxU0Qo)^YAKyYe-={F=qxlnWzC_Z> zM>;R|B}>(N80kmx0)IF zO|#?7nFrgJ+}60;FIR6^D(3NlZCsI!!rHH`=^NGI77ltVt>(J@GMFH(;bx^^Ze({$ zTuTblH`K_|zSn(>W1hp*Erx5-UVkdpcULb>~dzDR3gjN0`Lq-1KZ zolwBQ^<)&4HbRJB5D~Yh8R~vRRs@g!HDk9D*~o5xw7f3U0Y`^(6=;$7&Xc#7$sToJ zwKoGW+iJJvi{@}+jW*3k_|nNFID9HC37^{e^o^iOU0hiwk153 z{d+a;7H8@AaeBGfpmclY6R{7E@RqJk^!bPc7RV{lKoLNtkmTT^oI*jis;sqiyiPr~ zae4or0ek7{z${3Q?sxG@Ai0STv>oHP&isi1y2cA|3mH*M;vu1{>6{lo>BTvf>JeGp zyf%@V=k6?U>LsE_>va9<;foCW+Z}XlFUQf$u?DluXVAGrNJGl-rYkI?7dl!dANLho zzK*LLox^b)sg(*_*?7I0ITnZ7Q?Xt}Bjj{9SlEkTW$Sa>T6<=tjw_P0xy zbf4u|*7DTZ76Yz-rMtWPi>62SbAhNmu}i3FZ^Y|cbNB6wDiI@u27SQ|XaBou_YM{= zRNM1uEjrnF6Vu;zh3gnXY#pUSO$>gU<7)3)^|cr?sIk*J=Rj$Ky#??a8a>a3l(b3_ zXS4vya{UT-XUv;9V@eU2P?6HBW`)*ap{j9?VwzAwy;2_}We<^i0H_1A~NoPNS+=XMRXSsaX{734V&L-LPx8JfJW!N9Vd(#1b z0?UO#Jt-P*;J9uL4ldP?pjZMN0v|oCDy&Zd>4@J`U@a1Oo@A z9bjdq@(Lo!#&<1o_U0-~Kw-_sLdk_xQJc9v|@kRd6|5fkz+B62XCw z#~vMjLt@_qF3k0Ta;__m(->gxk5=tzZB{N=x&^Y9s#S@3Wdy&n#v$5!cYB7RlhEWP zFazuk9mCWD4O#T?;lthpwPCSBC3P-F9mj+md(5)fe?eutJu!Zms+IDFy6EFMQb>xjf|ByWR)?(dfnUl7eQT!C0~4=no}$o=B^x`w>? z(MekA?|iS5{iR8>AR?lCr|3>ad~LXFFpmW1xRg_wZjGYb{o`x@z_^f`g<=;irfdAi zh7a?7V-c+U%$ZfNLWM4YJoGol*2%4kuXI})S;ixh+K<1@PEX&Is(Ixp2IIbv!SW_J z?xyG6gkLpm2C7tNIZ)>eu8dk zLQT6jm|0(iO#Gh)*-6YjT60w?z*jY#0%>A z$%p!`BZs#yI~06)eq%;2>C!;#niW@OHXguVYTqS`w1bbDGEUl_{Hp-OM_6ydw z6}V#-M@QJG*`fs}(edkzYR49?eVC3d(v3Gk`Ifd|16VZMj|!M?dHRt~gCj3`YCSht zK*s98nINFhKa^ApIBC;f@Qg177tE9p!bPQo_nwWc^Kv;P1i8c1#c|aEE{Gz`4IJYp zF#GhRne$v$#Cc!mcqo=xO9GDWgs@*{jO5;5FX~FdZa^3VNnl|N@_?7Ehyhrv_c~I{ z6JK_gJjges`w}5a!gWhq)AMVq4r6j}xxwMc06OS)czKgBFngURZPNA~TzxzS_Qr-# z!$;)C#9YMYEKM-a<$0)$W|n;`6up%xyrY0U*=H`8R_99t*AM$sfT7-pIvMf*l#r!Z zEnv|%{`*Nzu;Ok26-=x3_7AE37&QhU&l{YmNh#JJ;k2&ry95@>sZrj}4pan4OMNJ$ zNnp`ZpT%AyW*b-=OAYZ{zz203eQ1=d$p8GVDG|kaVEQZyHOP&@rMjm{-pyx>X<7ZJ zV?2QFJ|&W2`8rx{I7i0=2_3!Yhdn(mmD+7y$8@ff53FNqM|72As)NmTC_Bkvq3i(* z-CpbepI$Ezy&4og?PTo5K&TRK-T%~!1Z$4_&jaWRZz|YG_6Y(#`~0a0K5IfFYIQYm zARBx$#||{a)K}U}tQcY-=MQ#PRaXt81<#j=*Oiv{!8db`6vq*>?3t^HAADY$I6!35N4D-C4zGSG8w3*6AFFt?uuX*=~TJ=inx>(=34q$Z3)|j?!&6IgU$e=Xn$? z>I$Mp9Vrmq*;VDZqH}-JO+zeXxn-Wn=0sekHR5G06T@O0WF!Pq0NWhogJ?^Uj{PRS z+WflMr!&JT)ZtpOl|*+eMT~g}Vv*-hyyNOGfKX6W&vxII2pTjZhZKhzdx@{Dk}a~v zWECd$ULs7dzr1Lx^RN`FMn$*On7J^iX$f#&XNc_ZE{c(^q-mI5f&Z!;)kTQln%S*( zftTL*oD=CU8E=ELpq!wMsCy(@ay+d>Y|TMq5a{~c1gtp7Vm7@KcpD+Sdc+N9qr&Y- z5?V?HBLv3E&Mk2E^AM<*wK;2=Gy*LR(VuH=WRAMg{&{lj3}@kN1GfL+VTXDA^}}X+<+v6XPWf_1K=<{V5s@ptB=k>Ukiof$ zPx>FCj^nZ5^lI;|z;{L9-zRBO32p{neU}z8?f%eaw&>oD*8AJ$r|LQeE)|lRXw#Q% ze3a0pYOn0GIAI+qDus1ns}JR0z5mo&VVzx!m}zW*#EO_e*10_Bf(U1HzXIkHI0T{& zCw$hQp^p6M8l_W~t>&Sw0K`V<^8zTn)_=7G~aWa|8sp?5i-`d0qJzaYDuK`=*He7>BxZV_4Z*o;~`kR_|nKit)E-m3Qm zr)PQ5{1i6_vmcb}J%}H=*f%NX7|nNE($fO<+_M5Dn(Dol1pTE=(&`R z3pTWAE}3(>vuk?GYO=O$F~we?yX!dBV-NWr-O2Kv=#ufzQac^v)!TU>eXaKbFa|9! z@Xk)3kcd|0Oby5QC6s0$@uUeJ@Pf(_AtcCGhhAA$<)2j>s?&v~+d z{`=`@DD2rD3Kl+q0vdAMdnPcHP-X!2m;RH$+BnbP*~Eoq-(SCNZl{h|gmmLOxrB?> zND%HfJg=4;fn@)*Szc-73CCvzhl0WV(;{Ycav$<)p=~vU;&F(qs)Cr3(!$b134omu zNL^j2?vVG5Fb8f*k!y*<-RJi=l|X*9cm1mQ`<)4}fY&@qtGQ1=RpN)7{2oHb&k2JD zWLG7+%^c=X9*QUJg@=>jd2%y&`D^bP;Dc8v5rFl2;5(ig1GxATdFG@O5ME}qgNzxI zLjv5}=2wJxyg28ooKFx28d&%Wg%(Jd-4(~z`+IyHv{5}#q=WI0KNo2nT)5=_cN$~a zGy6nQZ>hRmUAl%|Fpcpk5L3NpeK4nfp?2f)c;R3y`9exy*@JZGw~!YeYK5yY)T>Oc zHYRV6_5M)b^xjL)K)?T+b(zcIt8Iq!F^H_-Njbl;DU^%+DwD?Bu9la1zyrv5VzsJ0 zzVJ%{10-$?T(Tve)fWrrV=O!syp9R;hE@kJr|}SX_r?JcG^jJaITWGp{YO*-fvDYf zaS^c^|07e|*#&Ke#2|&@+&U!z0%bQ^iitIb&_pMS!W%)>GrI_Mm>366pYVQ(T;sQN z>EL#~VCC}6=E*4^f!q&0XDqABKc%ZCe$>e=Cecz|_L_}JY*@krW)0n55}lPxyz^-> zGxH?vRW%Qai>u>TC?6ElPb8}a( zG73Msy(*=^RrFDlIj0{iT2H3>ZTWuzKVM{`mU)O{D%Wsx#C*f_N02 zH@d+TZ&(Fqzvo4LJ#IQdG4QTP*WnR$e=}#v^H@~8ZMG7+vr=ee`z`HT6O5qd-DtNkH9in(940&-@jG!Z!o);I(ssvgHb;C$n(IW zv!oC(80($({~Rv*K)9IR4o;t&Hk0H97Ve))ga3&t^nbxqf3h&+laPsHhw=}A9B(q@ z^2TUn+==kCqYLx|k_A0I{<9Y7)%e&tbR-$EsQ3%sygE>)lr`~d)~4BabL>%I4;jWY zR;yg^ss|F({|@T_ul~y?7(hTP33Jofu0EE{>2}x4Wc5}NF8!C}bMv+r=V0KFGKdD= z{%%r82oVkR)X(SOg<{Jx9dB%Rbd?&EFAa92WugHzvAW<%gN+u==}jPa*U-0lpp z>6-WFfU_(xLrRMctRlUWj#g`zTU%L%4#d$|P;IhYt~g$;W8(QNt1n{Lux&L8n+3Ws zQ&+MKQK+E<#WZCPAmoLyi{(YuiBEL0ol8$W^e9Jr#&edy#fdq^XO$N=YACFeQ{FVI zy+~K8hLO;f6ODzIE(@2QOsl;6bf)`;i@*G5`FGI)kutABqW_@|pXfN0OknI}e%5jZQwGnkS zDaA}T%r>mjVKc;r7bK3K5L16NdizYIxdVm-^P1);6dK`;yc%;FYDpH2HZt8UaeDy_ zDWN9lY%3zIl~!k`qRUyV&OTFM`y$_y&j&BP8yJpMj{wB(Xe~v$foQ0U)wfvdtl-3D z_Id-id!5*EobJ5HMonwF2cR&nbKO?BF2jh5n`zCo5}_hn5@Pm3y8~MAU%4ma_l$DI zu6~dMc-cxA4Wq+EQVM>TF2ot`drqNNqjf`F`mqFeI1!bm%iM$t+{jQXOTP*o*NP1@ zB>J{SEj=OuYeZ(+j*DG4$=EeUO&^pce@!dzxx{tDNaQJo>zWBQghv-dc*#wvh=sTm zj-erCyH2k+u&};bsS73@^)yjW1a!nuS0XZwiLKchq%USoWSpfQb(HCh=fpWyiR)IE zrUj9WR?9kcLZ=jUtU_M_!bXMbHgS2y_(64S_GlX!AC}0QN=3t6sBzvERyUOVqZ)pI@fWreFV+wtwyayg60^+RbA5UHjH_uHVL?R{YM_{>m2ZpM30E=RiLi76e0k&_oS{%a!D%1M zIED@uv9Ji$fSJ%fQC%E@?A1U?%VPparb*%&Oqbif$v}Ns;|{nemYyR?yK=}11@!g3 zMn|`6wc3I{Zvy<+Rw#?(cRSj&TI`i2-J`g%xFb_bXQCA+Eih0HyLqXU&jO)D)fm z{+3Sehg0X19HMX!F$ecy;t#b#6=VO^V zRX*6q)UxH!LN&-#Bixel?-g)H4T>119}~MW)WW`3NCo^yty#Ha$icu8v25QyiM_JG zS*Wv@5U|-%6rC`%7%GR^Yf^cMj!V7&4F4lpJ^1IF`d3q~`cZZw!(V^6DjqiOs3 z^NoglRKt(7{!&V+Z4?Y7VMEZcu(E~QwzOQ;f^V04X0rWMt|cjb-H#tfFoz16ZkRZO zRl791j(T|pG*6w?8pg(6pX7qgf3Ir!>LCJm;X zcoi*oKznEOha$N>TGV@dq}=%h zFDKe#;<7HibE`(j8ibNKL#+QyX+K3C-X?5-C6{G`+WrVJ1)%f`Q*La20j!J}Cn8OU z^8^ol1+{c|%l1_4sqkDe_pDAstdle|C-CVyIiYoH6++k*Ym;Y7|QGqZZ7-^2CoUU>h*I{{kA1D;itU5dM2~Sv#uzLyApOgvOcRE z?QHLY^2gP3&!bsuo@A-qgA(gnXvJuJlFus(Q1e3?|MBb2aF!j%oeb2v$PecvvgUY8H-Ka=TR`)Q-V@iRGc_I^#HWtU9#jlvE(v(>`X zH7DV1_HARuZwRdaHaI$K7Pzx&N(d-EG;Qjl*%P)w)X)=!7NUbIc6O$7hgEckFYIY) z*}@vpR$H&dq0Vrb)wgCDUQx)+2%m;bSs72yIiqwf#O-2}xDU)43JUkcD}esK?@an@ z2|W*!IinG3!~Kh!^V>(S@U`~)R-u|u zS$VpoNm5;sZ^Rlkv)90@X*9(p2=zWYXEzXLJe!$K4LzK$Zjt^hWbEdoC|tlyqCY&#llVD z63aH<#6;ITp#oxqn=q3%kkkAJWiH9hH33c%W!Z}o_ah$qAbH2jz?V>tB|=49P5n)C z&dq0^HcQ@s`d>ImY9TMCjHXwe{NcdwEDTC}>cQg&$&06*e=qO+>ff#x-v+QVq#3{@ z3rYiYKcDbetQksfEEs!@gDlc^U`&c=lzi9$66)bHbK(tb>@l4=dPe=~J+n^U8!T@+H_TW#cU9pR@?^J*WbJwBDC`8l zAXx2BlL+{R9OS{|h!-1pnUU*OPL<32O-YWyI z?)45`aNEme4i?*9pxck2mT2VCA~AB8hgchh50Wz4tq_E7zpa5X8sd6L#|^dM2*WcJ z`Vo=1SJV+oQci+Jd zx!1tjH=sm!)7$%Ro*p)-+uM5@2~2&9<_~WClk@^enB?t?|Kkf!@2dIC3iYq}339LRc#>mV&!K_muVUr}-dPXyJYhIijGg&QER;lqWXd8{@Pmc5?d4Tm1 zevFXp(R|itn(?_}V?D!pi3=gq_+p@+PkI>~erXcb)58TanA5|SFtd$03!E@tefPnz z9;UT*5}Bor2x42WM`H(ebO*BxzA4%Pl^zb_%S2;>Mz6mIPW?imEyo^k+%13ct;wM_ zA}CN_-vx()8fmLf`TV!C{*QkQzy>5R`lWZi=QmAY?__Tg0{G)+_9h7~(wtYsj&mcb z#|H_vcR8@8XCC^u^!jfX*T$|c;5%CBIeiD?ii&E}YHl~VY8mbz{V#Dk?-Y|4GuP?9 zMR(ujqYcUF?R_x!UQ`Z$MNFrM;Ch^_HL<2Oj^LS7^u~hhDf-90tk8dzE7{eMWiMfM zdu^;#(_u=t^OLa(vb<+fca&4dFeGl3u@M+479DE=c#mYn>S^a0Y*;Pq`Cw)a9r=zKzIf~seCm*$)^diE(PUsFT z)>?_Si|+n&n^zQtS&6{ax#sf?Tx?j()or&3t;;32u`kwrPl5MT*EE-&Kc2Q=Rgq@X z6c5<_7HP8;*tQX^Lk`=7!?UalT+Hzd1}}Z*KbzzWj?hD9g71c?6yK7@s7S-E+T*`W ziUc(^(OswW}t^3X~f*N+uTXe*1}hBQNheJp5Vg^q>4 z&g@^pv3!hJU|9^1_r-1Z>Ie^yuO;S`+8KZ@T(d4|1U~0i7Q|h=?B!}EyYEG0r;dJiVRaNyb*CRp`ao>{Ebe^FHx(JQKDbQ-<3XPyeC+d){{Ezkr zEDcOw5)cl=J@7|&UjoV=@|#nt{^|EGNn?4LM%&>7GUmhPi`RR}oBYACce1Fa8i$Ak z+;c)^h4^OK>N$f%!m@Ph&lSXTfrj|PpvhEZj@Akrfou>qFdz&v*$0W*>$Y(5JN0zz z(u(s5zMJ&AUeqJ|IoJgW+Icc>@23&)i5w2I{?6)`wmY#}Cc=0zI50A9?2bt2n5 z)F_12O|5B?kMiaa8u+udw%oz__srF#nM};(X_v(}x zp^Yo=MbURR!N4w&!zKT$^zs;;+2IWvctm!q$SDzFsUq@15z(6z`aFjXC_$KH)X1)s|NI(^QX=vUbpf77eAfScf*tg zGTslQ#9mS&4N2Z|TQ%xjDebb%fGKG)3}%N-@6?T@Sa;`TxNb0TqnK#D(?embk>wuB zx1WUGNbXrO)kppK-IR~P1qOxs+};>rMUwMpvMpDQLo#-P?wI}O)fM_=yFC|lfU(1{ zKWAy-tCvG)HN$1{=3Uu(AC7*i+LR#1U@CSD4tm4S{xc$Ejxw|>cB*=OHQi1WU`RIU zBG^13Zl=Z;fLHH(Q+*&7!ruTHDgME;L7TySB(muCK|Q!5n}D)x*Ire!a2b&+>jRel5+xMzRI=kQDYD`6vMSZqK9eq zB@l}s)~`!HfgE(#`*S~5Spnx9IqY@SUGNHqS}RrtQ#xSVfi$=ettO8wQt|Zn&RI5 zVZR#W0koZ0E$g+?^%5R=YI)r5FCm%X&MustJ_Ag>Kn?`&jgh~T&dH{XlE9U5uj$Vd z`3Bg{3Z-EpdxV6}RXtw98U;xEZ?_3og1Vug&6m5~7rmuBqP&Crq!;wTQ>REzEcRaf zdXNXiYESzFpIBJaFZZZslR#WE#XlgMh#ke35-}uDH|7@(R#GWJ;jG&d*F8Ggmn<^E zcWYr2rC45{EHLIIEC5WJR|LrQ4fK2NyXXG2X+rkIrU_oZ8z><1`N_|L+D}N{o{X1( zWLeORLUHC+u;G=!(78Z>S><<&YK%=at~1yu%%;SM#uRFem#X*C$QtZ^f&0BQyG7^9 zndpM?JU;;N?M1V1KBANHoAy1XE=xaMen>Ajt!3mr=vPLsn4e z3)yS_GgGtpP-_>D__%Z5gPb3qHP%DXC2W@YxmPE8%-#r^n3#C9>#4IiVIJZK!P}<- zLqaC{EBcAn`X*Us-C!GK+mDm+Hl729n~nhFY$J2vfD|m}GFI+UnHMk3Bmw*Sb3_1AupZvU161Ck?Gl6Cf4tK-uGTN^LuvNUJ6 zhYifSA_|HmiZo9Mg%uU3<2$f2q4(OpNl_!70t z02?qQI1D1#VJ={LB@-G|adLYp z`($T!krWy0>T*LLo0GjiN#u2LeG_!f8gt&YW z82CI~IDJi+XUg*+H_B%#$pkk2(&KSwh6ZHd&rjkBD{K_3j)I>t@*o31y@-gA) zs-K@QdDA|I$;7N&qGsUl67|CsM`RWM0|b|0^ogJ%XU@b|awZ~;F*lDxKNnyB?T(qg zk+Uuz?5jnN5qc6nm3ZaJdg_qEtt~*@SLCS(dR>Cu6W2Tl7mY$6P%@r|x}v$}&*{}0 z%I!+%jQyTH)WP?Rr8+%TTm!QrIvN6-`iJJT<|G?mn-50dQRuL*GSlUB^+LEDCmSgJ zXccx|WPk|0SY=#yY0huU=mglocTd>Q$W#9rIlytBWo4JexZ2Fyx;fAav9ndffvi1M z8%z@GA1_J`-y7qrdLq`8D_3c)u*KuNZhTGY;&8@&2m&A@!gy-;|6oE9+)Bg@NyH~x z`qaZj(qBDPye4(&(;JW!^`xvK&o-ke!+ewHE6*>9oY;*f`?U6I-zX7VfeXw5L`rzt z{pubfVfmlkj+li1o7D{$jtdWlFH{_Wovx3#=MArQ?N$~db{HVKw!hlWi<@{+2iq3a z>z^88$dcdcwY0;}$BP8!&hT31PguuUut&-h19#WLKtaYElc<36KU2um#01ByknFou zJW-Qk1HbEBaOs{(HL<_Vb8~m7dUluCDpN;wbnRG#-0DOdwk>hNRwPSKLJ3po>QZY~ zuQ}NX87vG_Qb>8=t~Q9`6&%e2ie$>nS$%Zgh{AQ9D^d0hwKl5VLe*!#kw&7jN|7^^ z&q&)#n3=9a9Xg`TPs%SNKoBGc#`qT}zTFJB}m1?-zLn>UGV6Jc} z*L0UH=oM>qoVJcQ9GVn9We@H`NniQ1vI%Z}A%LK06n@tW9GMAINwXJE4~sd<$d-ca zZRfr5wpGc0xZ7dAN@lTj@vK`|Gb(mKCRCLAn{9)bed7czT!a=0Z zRI1Og1nXT1j?RZq@JC{&7-xOZOW?%Xz~}r3iyj*ASHSe2C=AbMV%Wp=m7}2hmFX{% z#9(jobXs9~MZxfIVW_>rsfbvlnTt6McoCIfddEqJsmKjbG>DLv)t5 zr9+|0RNBGb+`tf0)ae(OJj+Phx{o835rm+1Dr9O11(U8NbW>>%TxP|ID| zHO0>h4{(FobwT?JFYXTmi(><6J`g---2XIA4D#gRZa<;|5JtL5?`DBi0!_gM3APAi`qxaGI#e`)~sL2T8f+L;|W_kwpi@4yzx!krswaVdsl^d^L8mxLW73n89;lJX*|3vzT~@!Y%B2c)ob zkB*mc@%?^oJsas6eAv5~IaEY!Ii!cG4p_9;kLoL6cBtB+AQ1U^;EY!WCGILHSKOza zTu#W5<1xhV9Z5xcr91F$wy4h1@@u2;deQ`DfsP#!$G!;aPWyy_otG(L-XAA-go9J_ zNR(CFzr5y)i#Uh7g|xAE_T&CczFlC&nHBL1JuWVaa0aw`#b_lCfyVHje&dx&_l2s))ifQTkvB zZ2nkmoXc`uVmK$om%>u3gOLE?z5-`u5(4*T7=UwBhr z_`~-m1SG+|XqmRBM+;|*jy1Z80h2bk4*==R>ESO-$di02&v_fRPKiZLQkOn8zAvM; zRB(%ojyB2bp#PNj3)mFoPX!r~A>m*J)dy1Z+9@-`+;DFlIc#gzVNe8e#>JJ2$G>Oc z6Wf2mPtpQiUrKC0JRw+SF4)^eLFtk9ACq%o*AzAMbHjgYC?!vV0@>4#RXWQv!LTa+kuhEp7pk{VGDHBC)t&N%OQIXjJ@Ih+t2~>=wTcDw|>F%UpYL+O{ za3eXFuU$?SxDo|RV819!=$d#B$y)O#iBBo$0$rW!T||?;rDcnnCowq~1V8C|dZvz& z4Dz2K&QgXYi0nQ`cbYfRrs|9i1V+)G zy$tkc{3m%rl70G00EH!zxVAA!C#wd%7p@Lg3?1W)&B8;+SL$b1zn{?dV5sNiwCpTlER;D{1ossv|Xtldu6kx z5rITRrHV)e6_6$Dku3`$`*OeM6&7u^&-1+RAMbJejvs&YD260tz3=Nf&+qy9?0O$M z!xJ}AgIu#KQPYJ{<0h_67HY3IC>#$naqb5X+S2jp?@96Y)gQgr^vPZH?dGw5==@*Z zN79W}IFgF-td|M4#Jli_c(X8b%{sr4HzXkzT{Hacx5(wrVQp8D6tw9!Y9CdZRLrQlY){H0&FAGRhWB)G8q zR&uUfSR_w$RN%o6j%_M)=uRS?cXAvilx477-6D7#drj+GdyPguU=-?V`F9|b$F%n- z90uskzDJM%lIpX6uDoybnlCY_E%Jfj$-73mf3%Ka)D(y030#<4Uuh{W+~g4A@0Qx zlb{V8OIz?K@f}tbL)dPNY{N9nuzUE-ckxk$G04a}QAL=cKoKJ7t`byM*-Utqs^jW0 zR}9J=EX#2*xI(teT7WZS)(U<$G8N6R$fL++UMCkjma!~W3J-9}1W`?@%$ z=^)eGjA>|ikHaU8#N{aio7qrji+6Bz;)<8XS*k^c@hHKySI7CgV=@)`c9nO)SZhud zj8DLEsh}uJ)?1%)!iqlVq|Zd!uiamxWsC9HgYrT7;>JNRRRB64<3XhdbIIirYrb5M z6gn79$yOWJ!}K7`Hq02&8DB8Y$U_$Z&*A{+&JB&3N*zD;(V^dLE<1@Vlo;;CAmqJgWG0w}x8IF&9NBn0xc7V@yYVf> z`s8>mE|CEVb7tkjh}Jcs!fP3_GKIY?Gq0N-XTE@WM%qhkb3UOB5oNk>i=}Lej-OfJ z0wrje6z)-U=(GDwwYVRm-az!GNb(G=48hF|Ilw*Y@T+i4Y&s#f-5;aqw5-ZswJGh4 zly?@B2oUY#vGmCb5~}R@MV!kv$NZ3#4!Bd>PMRPv_}d-1kFKvf^=Adl|7DM2j3O$0 zv$YOp?*jC}dCzgaFi4aK*Klr+w<6g3wenGQP*GsFz9m!rkQJ07m0_if;o)M|(ih038O?+x!C{33{YReNhmB$CXh z2e_8EpvFm_;}v%X0r~3=WJa=Ln})R~i54+O@z@T9HzU9TF$APd*k z1OsfUl+-KZ9~JM(41*=4^d=>UII_wV`iZ0js-{(|d7oR)9_eb^DrjyUjVEqkY7kGR zdMq5gdE7i>Jx_b%b`O4bO--9VriCn5X+DRC3k4lW7PnGH)1r&hfuWJ1?3>HhhKY>< z3P%`W=>bL*q?B*lZzVx!DP00lHl2Fz{^C07oMC9U&ZOh`MeDreqmb3=K1 zL^+(|ho)ar@8dxl<%J7#Htc4cd-fM^jKft>TL09{WwWv4YC618j%8QRL8a)pa!JMJ z23`2TKOQ(oYivvV|4F=C_0YK|9B6(b&WaIKhMkpCmK&0~JS$6ARhr^1l~}1%Fcch zX)pTG=UxCLTgy8Yco1r++Y47GUy@^J!iVHCfL5g8jAZ{I9fp>@q);TDM9G}Jd?cZL zTH&1#66cQR(g&|MxRA*|ePk#z@R$l+0 z@EQ#SGKF>~J8eRUW9qxWrovmCZMK4WylE4<&R1{YO3Ffj-#; z!#X4inlfqvi^yaUr!&~yKB0%l+C&?86PCoeUoN?zCCj0y%&ku<@#V_xgA07PZBa2J z04ZTq2&oTwZbR?9=^BX|r+K)eF_8=^y0OA_LclH{k>)shp(edGPY6X-+^W&9ExFuZ z21V)vp8g|eS+nH)3YG&=PQi+MBcdGg9Ou6w+^)j?a)p9jR~yDx8;QLzZaWQ_kzo*} zI`A0&%83zFKU5&{^vgW?!8nQ;eQkB02z(Enls1>zK!H?7nTNebY`+eSTrdqQqnNE@#y>6VQLXg?Xy0A<62QG$ zC;WQcRh<5_`^upQZMzc2Wyi$h70(%<=0e5OA04>v&Okfw!x=uhKMDv{{=tv#z}TNc zi4j@P)1t}aP$>1})U!B}h(Nu10RogU1`yJIy z!T0#)#)f1ep(Zr=I;ya8dp(D}E;&94j=j)qplz6DcqA=9N>|VFUEu) zyS)y>dtjE{=mAr;P@M<308v@xNL~L&2yh$h@k+V2Zph~ax3va zJ;JbSOn?*M2k1{rR0zsbp_EkcNEJ|S%80W59NM6eYar-xQlar?rZ-)$KlPw?3(tlF zN|8MqVa_GTp^q@ur6-;b%SuR;V@{~~6vy%iD3^U5S+ioV&Q-&XYVurNa-=c=CREt$ zq~=wPP#?;H87Fr0i!ViF72S{c(<#`*=ZqdbYhW`)`nUsNdfqbgmw-MI8&5H9xQfEE zUli3jw>I^VX-R!h#&ldz_ra3v5gyrmA=+-0%80AdlLW<5qJkl4%aE%A3tVZMT};CQ zvF(`JzFh72l0n>42L+!?UCuPE;p%RS%?;{46*m<=JxxuWNT{ME$eL=|9hYQ{Zjq+R z4Jw!&w3%r3t349yHug6tA z1m;aUZcV3?w6rcLg&k4$0m(6&GX_^_F?e8uI^P64tM5@`HJve{?fy5VtKyO7h9{j0 zhg>8;e_2bNPT)d_xP-U&hf*%@X2kI`|DX}jwY z(VDIbede8@J@Zyv317uQpOSFqT(*6T9}G7Z?HJ?F3?;g%+?_?=BOY)99NKgC?~}`= zXf5?Dg}9X@XxS$MIzMqega1nvw6RvM2f%t2tYLs6SRB`m50P;lhZ}4cM1sRhkt(wf zN1CC!A5~B>qtLvTX?>NgaTCW|ce71r*gciPZXnF;qtRG}4`YByykBj4SJL+$7fM`@JraHJj{ExylzmU5f{$UxkPqt)d_ZEEUlX~gu6A|9 z(xdM2Di@Y^1r5F(WK3gr{yjIOTu+@64YT4DkwBZ_ir>0B63bZUozLRxHeyajMf*i~ z&yP6!pZ!pDZDG8fk8m;_T{Br2`z1w)U!H}C86WP-OwFIua-;~L) z-}X#}sHyuE`(>W`DP|9jE#BeH2c??h!KmUp&sUHNV17h9epF1-hf`(`CXG=T`!P6m zjelbQ5-+<~Y6IOtQ@*Sxr?&l~RFG3;za_EU$5Am899H(4|85w;?Y*Nm3%Q0bpcXCH zWf9sixtXE6+3nrXdN@zyIp;D!*Q_obi&))^8x(LckT?dtYN4UCg zuE8tj0z}Y_#&5q))x0I~h*R&2YYHWL%E7*I>IN*<+=KN`VjR%$AAKct4HM;mMx7J~ ze;^HzISVACoRj&O*PwuH72cm3fpn9hwpaPBeS|U=!tf_!u4ff<)lm$!;jN(m4>=AyG2#9%HPfiEU@t8)K-0C5q z18D>b=cT<2HC5IW7IE}zu{B(%ki+^5?jCEn3iKA zSuf@mLToUwpvYRFXyKA>n&H zo?3z;H&L%=p8r=MdYnfek4{8-2TXq+I4EmspS$xnb0E0jq(G}TQv;;S{zBzZh;nOz zRKBgFdxoqztMD;2S)l@!;sC~(u>I$Jo&ttLLShNKQ^L`ixhLadw@FcSX;6`HW_m57Q2GnhbA_s~LK4-wM@wyDE zU~j(ZJvP(rGe6f|rI~@B{TY|W{14Be`cl5dr7%Cgcbm-t&m|&@(RWN}-h9ghpid_* zgZWKp7tm~4rmeo=)LXy#@Pnd8APrN}|3>-j(c8ebv*=Ya>uC{TW4d~HX=zpNu$-j0tGTU%Z{bxX=;bK3cJ~2?!|Nv;c-B*Z8|&kJzPHtM)K=)hTn_O7NLzO>C=}Oqoa6tPOLV3#5JzM zKOvIi8h|u5&p`Ly`_gc9VI{>M`D}%z^qXN`bkuajJ%B1xhh>XV(PIh&HO9G85q}ER z@355W=Z>MzPDnX~MM}e)zYHw5;qvgY_^5)1wKBio9=bmyN2?8S+}`c53c`e@NgFTB z30u6nu)CDqDJ66)VS9+MWJlFQn)PT(c-mGa^dP&Qz09^-VGf+d%TE#3WB7@nxka3$ zy&mjxS2_1}qGIzSNZNc;mL3eT_@2Tog3j2Kh9m}Z@`(QcYq0*7SUMW(6vlPs6detBXfjI0jLa=Dxo1yG+snPr*7CzE;*8#$rZ!H;oRa?l~vHYnU6!i;0C-Oc0XT?$Z|0(2|Tf z;V`VO(0`p{YlA@<8YC~oAH652uY$EXzePfVW+~WdJ%E{`NjMWRdES0-x09jfiywso{;F^la-N_W?C2^+=$u0C?i z!-KJ7$MQkq=X!KqO=tVSFv1_=5Q+V4YfFisJ%qS4<=wNnB{I`n^4`G6FV6(O?wKvd zI3zdWlMGd4QuE@$V-fpvCrW`VFO#kYf~|i3YQ4mG3#iAqFMRiRE2R%Xvf1~Otu+yFt9)~qY=-oIW58*@9Ax*dt*$LP?@vwRpBaN&o#(txpk{dQXYW4J$PP~>uq~B8qJ|XOwSfYHXuQh5pNdXy$Y*Dw&g8Z zZwUc}D3ev#1_Q=xjv8hKZLLu|9&rT4(e@`$x;%9QAFr^<19otITpkp*lK?5K$&edE z6{h*@d&%jy&ZbNTuCpzKxLXBSDz>H`cpcB}Q3HWzfCIlUMPo1O{Y+r#(=N@oD*AHS z*~bPvRd^8)C~q%O7h2z}dMG!Sh6rwj2z3AR!x^pd7LSjR7SZO6;g z@PK@I)bT5RCRi?Yz}&pZ^x{i#!5zZvv_cR(tmsUTMmQkqHh<~#YxnM*-o1P8;L*_? z*J+lml)(g=blw^&XrN}Tp)%ZNi5@wHPsqClz9u^#(*u}g3d{UmQhse!0gz~(tsTOV zK(C%F&7%0hMk#5vBDH0*%w8+jKp8$43YcFXgM`TT7i*jVFh3;%2R(4z3GaU#)g`h! zm>2ul0kOAE*yuBPW<<0U7cb@NKVnh~TEc{|pQ?x%sBDrxe}h4lCqoep!L zZK$1(g`p_H%!VUTRuk79S##+xhc0K?w~AcxzJjs^K(J4<8$9?lx`|R-XNKntZQq7= zSTF(8;pd0W)k7H}?`b%sgtS-wHqLp@LUS5#Fj>uXj%)3>oY$S8=-3YA9AO`*b67(7 zto~hgw6F*=^*os(O8|{qrP|icv4Pd*CVviZcWBx=RZ3Icsa2&=J>P{7_-f!E8hNGC zv}KLo#D_(>6sNZ?Dr~DQn3f2Y?wxBQ;vPr5GBw!tYuBzS-pObajyDw-ubZ9(Y@l5b z_&mb4?3TODimUcO@)3R$3H%qbE1f$TiD5iT9|LqBz>Fwr<&|A9%QCccdvvHYPCq}c zvx;boQs@)d^$T2nKH{QzcI#DMuUxu^!P}GJDt0VVdlbMdf~UqC7T|TxE}24SZ|H_3 zcNhWBx*0!xD7zDZvW_#F;|;blwtMQns#Gh7)T;$JZv^GtUHGG~6vFx7&(5t=+H#5c z8?^~0a)MwaCbYW!;-klZ2yXwweG|weSsHJJrm0TNa(F@Lq(4DMjz`~BGnkp*u3aGg z)x&Qd1?_y~6-enoML&D^qti=V$4`G`fYB_q?dzMjHXEBAIHJM5Vt(np0plj?YUM6Qk}rqaq*%aRfF+`4k<-XePmZqLsKTC zS+SWy(F@(Vj!;2mM!7PO$HQ*5>I-YD>Y%tQgw}0y5ly&94nf$9gCSCEIg$UmaQUHN zlzzbduc+a)=W2F%4qmQ;Psx;zqx*RfwxtP7`|xI}_2ibk)HP~`&|usJsJ@FyM}M2W zPmj>p&~h%-pDOqwI(-iGXCX|0w^4q_Wy)FtsMakk;S;NHU2?Ma$CAh=m=oyd2J0B~ z>O^V?Doud>k_B(R{D?G#+h8pJx7C852=VVB;Lg1YKC3{tv;}gCb6%gWg(zF??>twH zLWyyNiK&83gZiMvdban(d;* zQo-ay7!b02{Ej8A3d1tCokm!HhVP%)yqC@6@4>OT=l<3rfgaf#os$S~DvUb|DI(>% z7m&(l#jkXWv#euoH=fwQBxzA@qet&g6*dsf330^+947;aqTOZ`C|Pk(8U~fuD~-?v z$swD#VgnRL=HhX9)(1kE8@Fc2)JSY-4TFw1)9dRMsmozzU915O*+I53SuwE#CbnYZ zdZ%&%fq!l_5nwJOp(M}>9~{oX&y_j3KEQ+*7Urvkoh?ijnE}Qv6(n4%Y=9Cfs?0Wo zvW#1~?q65fA;DS(#ZyK&uDxz$8Q*?=dOhSu{TTGDtlDd^1u)`I+`uu89Er*v_)S3P z&U)s#8nZH4n^~@?F!R&R(dgHQp8m*D;p0aImBtJ<-XD&ms;&DW`iggxhJ3Q zbKe3c5G=8x6D;j(qAN|e=HmP$(f-(%tG_H6Q1%S%2<653g;iPndRS6O`Sz%HR&Blu z&L)<%S7hmdN|M;TM{%KkyS_NGJt!)>&iuW|eD&GO$i@`Mm!%D=6C5LrAh}|bX#>ob zc(~}yFC8FYb5-DSX#FLCtlo&?Nn*t5RS*TS16(Kn>r@cV?KYaPNlgcg=6}bCg+^G+ zEIIW3xh^o=DD;?(1BN}wGuV~yO_x}|lxTV-eMi@pC|Z8M9c>lvO{1;8a^47*vKfaO znquQl)44f^R{&^9A&699fX%Y(62NAR9oXE6 z_8T0qYF7D1Hdz5d6bCA-_V|R9`fi9-2QJSpaAZbyY=^Ny*`pm*5oM9x#j+-PT+=df z@7HoZUP?i^zw*7)66;kp6m}eIa6Q%deTdx*>ct!(9N7E^m(V+(w)|iHU$^|j(W(g7 z3BE3{{4-sz(qP77d`1Us zGF2%@*yt`{4#=j8b%nNJ_K!ew(UmkmaFE+H*bAK+%78W?Ji_6Arf;%0Sjqs zf-w?`If?A)_r&?zwq3buexs__1w+zDKsrbvgyxKh-#SC%QrcDo#$?Aq5ufUq8YS%I z)NQLg>9=<#5cHpsikWpOp9KZQUNyIuB+8$WlA(3&MMYnnI8iyUbd?{wVIT8p))_~} zK|qV^w%e4r@XUTvC`;V1;gWbeWE`4KDD3+htZog*&&0Vu-NNfjIkJ!M0@{5yiulU& zRTQ~``lk;D&L|eq#3c4_2BxCzU4`1L!aIS59W#D+zTW=Ot>F3$wgCyP@p+w!)@SB4 z3C~O4HBcxS;r@QoaDT?mwU`wIoS+eZ`z0fIwm1AY8vdh6gJ+L9;yWxL8y1ia3&@5A zWWxfoVFB5&fD8g|cI&W!Y*;|Xu?-8z6pmp5*|30YSU~pwuYhbM?wg4wC$Uk|ys>nt zvXri%xY|JRT;>HB64>a~l+hQ=C)uF*)H&4T6I?+ZV1SWf7^l%vM9Xv1`QK_L3fk^3 zj_G|FtmCF9p< z*l|d`)|={Dr$$X`#yF%|>+O!d+c7d2-T#&D>&^_jtnX(#l!JlGqefwC%M?PK_8nk# zx@A&|eFMUQy+cA&#qMx4H8eEwfmv|n{ z^IXOD1p?hG|5QbsHT%MfL_=1e4!0PUtEO^#b9VtE zj_ZIKt?L`dLqm?WC;%f-jWo^2%XF2U10F^Q1%sn{Px+=FECg8_^_Y1Vsjnsq7l#sWNRnU z(r)8s_~576Yoru7uC5d$c|ccDtVfid0ci72&}PhD?#&p0MU3C%-3T}yN%T|v@IW#$ zQu@u-F`%l3yvIF&xJhRr4`?!OgZ?7Uf*m_1)-B@da8+=RZ{W&lC{7bBcBFsG3z%VyBSJh3bF-}atvp4!{ec=r3ya0}wQS?fs#xM*9;qiod56{Z zgXI{%bm1wXCGUJj+B}}m0d`;Ia$Y*O$%17~AQuw_zN2^kPuS?F4?*7uY}|}1q_(a`LuCt%YXzu zO9W7hH=HSav~-c(O<^aNUS>nw*-#m&w8uH<%;eN7xIw$0^LzAh>WEuOcBohvzBr#7G-0)SgHj;VC<$Te;pns(s?hyifn5F# z7<=%Igb_Fc`ba39w;livBKj@+Xhc!BcL2Xxe+Avwj8+ zV3HF984{ejahAf#H>5vM{Xl08F_s`F00>4uM%1o;vm)xp?ZiUsI3Kwmt;JXRj6 zXoTDvK=6_=fVU$>s9Yih)5w2CD)_rYQ^ik1r+6;jbFkL_*8jKR_AxFn2zNPUf$EAq zvhltsO@k(VKp8)>8AKp3TWmu`@-Yl%at$_LV8R>_+rziyPH5ljV3D>_a(f^#htroi zDQz6`y-E*y)g?u-T?N{xT5djLArOJi_E4e8Wu6W#5!Fq*+GY}4ZieZ?2M zQ&Ou!v0_>d!VU693A-f=CUYBl3L9F3p!4XXqgVs+s3&$_4QUDY(;ihx|kLqY|X?V zB_YU{O}M%AVt=-ozm_Y4vlTOJNB1{)Oc@65qry!^EG zaD@|)B*j=CC9t)SV-^Aw2y!gnz$0gYslw6j98q0y6;EB8o0yKzb0T`Un4moJl~cUi zh@Sw#vopb+f}7xhCOwDDhR6Ndz&DKf1_mW(`iLOMww*p(x_l`$jzTFw zMX!!y^bb_e{tE1EUL7<)cbwEi_uulSyHAa4_dz@NdNSQCDXG$mi<$~)Wd_{<$q6>HU&i0(k5>#ghif?CD5tzq`{!>y@pd@|K3|Hz5;*0cNUv7R!dpsj>E@+MGPQe^M7BE{+ zuv0poByQL3KG#N(K}!1xZp_Mb9_;w|cx|@YbPQyT_G29T9ht*xD)g6~&9=sUak}E2@ENRSt-fN#y|3U;pHVW)TW?1>_Tql( z-!I&B!vy8~_fNq$=q13!IL=KQP&%umtAp!=))ppA?=)V#8@}+$`Af;)3@lGVGm%d! zH4=u9QVyU^D*CI0BiU915H3jSWleo|)7c2aO{V3rLNjNz-erZxR&YOF=&yN;z0C+q zq`@E;>EqO>k_2w7b8KfNO+OKZp~6eiX(vMk>wb4A1jAf(YFmk;f=kSdwr9bZhSR8r zH%(FSX+;~K05R^{Y%gy&N0bR&tApVDs-Ir+8iCJC_7b#LLk~oE(?^X*hn~Rhs(-w6 zhwvTg=1;xv)(#t*F>Ewt*wD=Wm`cY&yJ17~u%UU_(0ssX|M#$=dDzg5h7HYlH9{^N z#005}_VBU@%o51r8z@N%9{y zG!Gk^hYijDXBnF7Bcx|~kyxO%ilH@6#EQ>R*i*tP7 zgwp}MU#ZqnxR?$_be0Iu@{Sjbq@31o`dISiie{dy zg|QREDs4ZPr^&Hc0#Rbz0;#Wke#XH-K2~}_X2kXcq+RFjHwnU<=I=2?mxA3lCMjVr zC!3>~TJmtnfZc z07;u~$l;e_Ad0{q?f3>Y88OW(kq3Q6ry#5>@#&mQ#4USvx9RbEq8S?Fg72A z@j*LOEwbHZ1ck}AW6e(^Pf)ak-yK$16H~a@#Rkg4rYb(%sJdKm8_a)|0FlWkP%{Lr zCGzCyZD(Yy9zyiPXs$%h1H2PLz=mX30x-;bz3f&kLH6M=q$}hR z7_6`q#VifZf`f8(4=5q6tstxf$-@=Jf7Clm$l^+ZOmU=xL?}sY+XC{Iq<~T5oF97h zZVzo49Zd*2dut6yz4tPT@-F3riDr8&HS^mmB zwxvh(au3a{>N-f~I=%9*U!G91hkO+uryN1}l73Lj&@ciGd!tPf=S_%0r)fItzp!bV zIE%f|v;#G_k{adjtq&~OMP%Sh&}>)uO80z79YZ|pf9|qBW|6mJ3Y$u1p?F7%C6{Ba zOiA4v6(?B&8fG?`q3$~(J~T7!yCbD~!28h}1jbe~5PQuuNX% zSOL_XAvLwa0~>Nu6aOnIwIM>S868s9HPvW^35iE3={c3h_Axf)g_}AhLJ}Zauac1% z$!1xu-nRIXmaB(E`+zfgAnSeRcL(YZ$=#sxc{HAjNkW^B6}+EPpJYHplW5lJD>vk| z_wWC-t{01(Hy!)kYh4uqLlg4bS@KQqtk$!uH*K$ee8X)*P>=LcDj21^Zp&?@iX;H@ z*z$w$g{BmzDS99bEFV8~28fq}&xArjHR4WkuSyW;P;<1uBKR~QT= zQin>dn+C@Y7Z@@GL+yWg)7`*ngl2z2pDYMX))FXKd*u~8vPyhsSrJ3OsdTUgl@3j-4b_+{;VBGOL-@%JL^LKQUM*p311&nTin z$VU|6d7x{MkLTL8OtSm$7wj-M6jQ|^&r5|}A}n%#TwGjV5@~qYU`SwpQ~?lg@EDZy z%TS)soi-IX-6pK4w%fgUJS9{AV|439FNVs2A#L#zfd(hIx@t7(v{yj5;5I6#EiJg)C2tCp(KH@} zCKHoyvF&XpW{;EjQKc7#3oN&Q_EkQB4kyBXd$N9GgEu&vua9Lg;Jc^&z8t1Q2pc=x zu1VA*!r%_(FUmC_^6rm?kp=4_?^j*3dNSAi1BA^t#`;idG0D~+J+aUkuCzpS;l9`F z?(s8M??8<|dA)+IE&ho6gZCJ}1@E~q?)3;NmJmH+Ta!ZGe5Sx3)L}*VPU{Hwk>l{! zBnMQwATVR5D0?=5j=+ZQdsDH+^1GWJ{uH+J-ux25mkASWtYjw{HnQz@N}Z)IANjll zYpnVr>S*+Bo=+fBSltt(x4fvXl=W;dt=jE1{~`CmK?lq(-1O@$TkQW3Svs+#4dAkm zjr<};!Q{lM*eE;<@)*I-w_<}9{m4b$)%o@xR3 ztFTrIgCtCH3HC&fpL(_HG`_VJBocc8Dg6CEJy(Nq0?%pKfHGW%;ra01Ge?c~f34Rg zcgHn-6}$Ufem0;7?YY=RoeIO7*jq|8hsX>VmLD5x1>Rpa%k+7QJ36@AJ-}6`LkDF( zpya;E6E=E8qEaFFeV$B@GM|az+O6n)ER-I=ZE=^N#Hl03qu2l8{&z zyh223xO{7W&}z!uw@6p85+^1owzKUxC-eF-P+*6Flx7r4o}xl!k>%y_s8-7r_J#PvRGM z-_anZaO2dntU~tWsC1$PI5)5bS^`+RQ(Vz(He33L7p6oW1bcyc!GKQ;x z2UbLsOUXC?AvER46Jdn_nw(|wzGNb`_rV3MjP|D{s5d9ppfPhj0tnDV*0pB{JH3{l z0Fofvl7)v#^_SS%+RKC{ME)snLrY6!^BL#z2efnw>j`=F4+b;G@EtIsEz!azkxVs% za0ejtiyYCIg?{mDk9VV&totpF`C(jhjJzpCt`8J$id2WflU(xrv#dVK5`7fQ;edHchrf`VvkW(w&%=sb=-KK8%&!C$k24~cXu$!v*p&iT=F*4 zT(%&WWx7b?kw#M;KI%9Rd~g74zliOrLeNaRWmm$~u2F^+&?SPpGwhCc3BzHI*+U>t z?UFW_VA`ynrxv`8-e1n}pE7u>-5m2_wYw8Ss7W-!SINjUoJVQBndIlCDDh{nz-i*} zv`SP`(02!&1l$!ej?xT~vC7n0S&QtjE`6j27OE=CABuaacnCoq6F$I-{^DtFV_t zw)x5(XN2QYTTgP+z!`rRHLZHd?~_FNhPw3f?V-mXS08s{Ibi<1jANavGCpM$k1q!w zV$+;!w^x?zT#%cuUYr8EvZ&*ABjS431yexQSQCFPA}=@BC9e5Yu)*nkk4QP;;Y1lT zmy7rhW?qBQF|U`3O@AlW_Y7vTGPo0dC?t<%Dgo+wzzC=TERazs)bmu9s zHy6{D3%Rtyd9-5Rvszxiz@)0?YAhbXqP-1=WI`ZL!OLdofsS&%ZQk-0NHA(ZXEh za&L9A>(L+fDEsb63}df>7ssV?UVWNX5L%aNf$!Y4(!%DoyK1>|H4L(I)tg;d)~zBK zw>5boos|B{#Ev;E$CyUoL{L7Klsxyyj&vZO?>!lwE~&7|a0e~T7PJR$wK^Om_MDee zZ=Tk+J{q`mVBcE64bU$<2jmdGNul!DUeZ55H0fiHap>M#V8br&K;Qo0HjW7nXW$ZZ$YAv3)k>$d6GPnQp)gHRI?hc{wbPt zag-kw@byd&)D-iQuWM>BR%KJ>9obB$Pkr8i0~?XS{Y3xlFqwcTe`5r?d1Nec8mFlR z825mpexlN2CFaGf;&r0*ySuS_nHV3_bEh(UD}#0(G}qMH>f<^W*ab#(?2+zk0c|DF zVgnZ@x0`&$ny)r)&Yb~TmR+Sgb}ZP%w47$uRUhXpBsy4*J}o{<)YgWfi3N1OQKfR# z$+Vpy`$X~o8W~8PcESVBy;!t8Ij$v_GvdkBJ9h90@BDA>BGP#m%VFkN0Sk0#WY4Td z)1HLJ45D*ygE=9x?Tp+aatvsxPsvHE!p$Nq(mkAB)^EO{jJzJHYqp%h-#btgV5#r_*3bw{~x&Q7)g>kHbv0nukj>mg`=&I<% zi7$pokilp zH%iF~8bBKaz+k#K$j8=Ih$a=$$NS=WW;{x62W6B4W|phqod5-PfR_YB#!OOMe(s|T zRR1zHfO5mB$add960GZ3mhHQLWFw$M1V+O>aRjdfYqeakS=-MuX24!<4@Ap88an~8 zfAyGy=s(he5VwOK@0Gd}%dJb_+jCA0L){aQh&75a4jTaCXXcKZHDhW+<1&sJHl%i> zy-pwJlp|qbdTs8gIbL8{K6T`Ja_5F14VGR{K(uXis$V!LmO+kIudtM=%?2^B9e8tL zqXj1HS3NjD-v5`i5cy1{2D#tLD(_b=!4V9*mV$gJ!S-qoMeny=pgLn2Jw+5O%MS1r z#NYniX1fd#hek38j`>554WyN@hbKzsEvKbb?_+Ad<6g?kO@+9$wxwK!sL&Lin z!lo>a{zJu=_&tSRkHWvM2jzwsodsE!t5S5JS?BrZThI{!>4lVf=`Fk)K5{?*_G(P88^lX5RImG z_RVn!h8Won{m_ru8U1YQ@{LdBN<+Vl`!VS_V448)a-6Yg==W?fiofhRa}uLd#$y3| zyMFs&D~K+?)F*<+`lrZ%_kz|T&TIR8Hxz9A`2pEuO49(OJ8b;-y$vnB#~7{$pJtY9y< ze#IqEu9!fxmdP`C`_6F*cUZ0U{ZrGXRKKS z{^sMilCEs)kYTYi<>`-b8fgOE3BD0L=?sv%gn{H49sI^UjEK<-Qm|G0(*^1cx@={? zo;o~d-@ZkG1y3+tTvuIv^iq*mmD5{2@QaC_xS;Ft#Ob-NwOO~tMt}gDoE94m^{RWi z$r~4_(}Mf|k=)rY!=BpzPx<#warTgLWPJsRQxeNXW;zyKgO8QCjfne3 zc~6$``n=^f66B5%z@ZR1vgGEO{jZP(5a{uJbgaFmEgUytw*2;9`GaDn;h$OWjdGM+ zir7Dy9Wjp_9wrf98lU6ojK zkTZ9GrBk6bvTOl@6=t{t&0)FHQQn8H96dK12k zF|K}lRi4!8s2GapsW=MgXPHA21aSeoDZ~ z4Y;bMxUzFTd=-4|Z51j(Fcv_NHKc);eQzWCgxCPrXPkPk{w#y`9b=GOZEz(<#sb$&*{VayD<736MK0^iP`Z z+RS1{A4346!vBC+i*S4sjG(mQ}OJc*u||E751}o%a@>D^->z&+f$pJ zRE{MiZZ9-nA6NE$6{CK;F1gAaCV(Nmpf#i1WCf{6S*Z-fg|e zH1FbAVX3+smf%q2M>~x2j(8$`F7oD3{Rsz{jM_qlWg-R7_{F(1gbqbnzKXK6|IZI6 zsF5aAb7k6i;a474)UplXP!o!aH)2smj^J^iTFo)lzfg}QHvBA4>s(1%vUyvGh=tWB z$`)KvU>%jMf+gtMnwDtkTZU%U8?iv;n0b*{w*Z%R#0!&VYM|6psW9zhYF~UQxGP?u zsy?y;Ho_}G<~K+s8i}jv-~7NY4iyk><<<(C5SJp5ux5$MxeeIzyqDSVLiXHDqZOB^ z4>)Dedu6@F9HV#KE)UZ+8ZRr#an~r!Ng;~i{Txk;4$r{?Xme{hU!9U}3@w0DB2?>_ zzRfmfEKq2A#C@M|G!&-Ihnu2~UF3V{L$xtC-h@cQ`}?5^BNF(GojTWSgd36HbApEy~Wp%RUM-icnvUh`Wg~qGJpHyFF zO4lDfy#1p;x|YL##3Ph6Vf*gjtr5m7=PUo*3-2sZzaI{zWey2-;cMNV0n9A9KHe2q zd(%pH!OYysN^FFwC5TCk`88dCGm54V=B1JN_!kxC_w3UNVcE&uVRyb(Kp z&g*`rs#v?FPbvY`|rB6pHHF%to%VU+hu#mo8C?c&nzmsa{nXTB1Bs4P|nplOQG9kI9R#w?1 zYyo-C?*wOUM!r1M3J%zNzn4PSsjufP%{@}NVZR#|UNHR%BJ|AC;FSu2!J07Q-uh1gG9M+(Z<(|ji9@s~e+pn|uIT-F|#*{xZU+3M>C;)CK z+vp|NoDf3QuoYAO+I3~2sk@Ef-;8Svlr)5scYLLEkNN zAMgIJ$|V`-yC&M~>Cc1q6p6ZkYXi-}p)Z9(jh62m6CmZ3dxF|ScyjHWF8I^&9OyYF=;4Y z*1xDABNH}VgG9_p6+q4aD7VpRD6(tdFF`4>#3^uJ(F_$QDcFLZHKG;SV7ly>OsJHy zH!9-ghWvuorLqN@1cgD1&3Yt!QEubv_{-GtsQAg63YI-RMPYwX!j5s!cJ1C>|00;5 z^+nV_#J^oNLe^6YPr=b-#0GXV%h3!uBP590;q2?^`WaOG_#n;a-GG? z?Bjim|pgre21JUh&dHT86@k?{j?6$FVg44V% zIH`Ew23$iX@8*UybS7~24t@Rl&rU=}3QrPhz6uLD9J(7YM8_H0nEG0|nZefi%Gc(1 zb!G_ehGmE6>7aYM{n^VLMQWQjDy$EatH1%MhyvnOqlhnGuNF(hkI5CT+yc=63=nj#0{SWGCT>9DH zbM6e&Fh~81-A@;MC2@@!C%UE-7UvfPYqqHM(>R3jL$+lmhx9$<7kCMZlaLBvC)R`& zb_Mcvp|;j%oNq*L2I%|=*4 zgta@Ws%T_&O7o&1V!~=0L^-bhvo~0+T@+g1_oK*Otxk8!s(sgUb1KZ5sDA?>}3R^Mm z=d0h1vgm;690h(pa?arP$OTOZ9OaMp`nk^|mVLRV4(d2id{9Z_yw5yL>^Aw)o_eia zmMl4wbaa1^qB*TPGG&MHK}U;Evk+R_!C|t8+Yc*~RNblzEitgOM=uqprk)Rn0*rNE z%eIZiMsRG018#cLgdf(HNK66}%|()%5)}8(C)*Uae;bA6PkkVg;+V3v6&e-tkc#@P69=&!0uF`+M~M zFR69Eu2(+>9>5Hm=SHt1SFwX<2=xDcyIyp4|H&!T(vKw`Pytnq9nq^w#XrTeCq2-H+ayJ$h?)bF=d3t=XfuW{=*QJ$h^Q z=&jkq;?`{7&Dr8WECIxlKr98s(!iUvWj=|B%q)KMI`6t`G6N8Jy85}Sb4q9e0M?uc AK>z>% diff --git a/zk_prover/src/chips/merkle_sum_tree.rs b/zk_prover/src/chips/merkle_sum_tree.rs index afaf361b..7cc72b0f 100644 --- a/zk_prover/src/chips/merkle_sum_tree.rs +++ b/zk_prover/src/chips/merkle_sum_tree.rs @@ -26,11 +26,11 @@ pub struct MerkleSumTreeConfig { /// * `s * (left_balance + right_balance - computed_sum)`. It constraints the computed sum to be equal to the sum of the left and right balances (if `sum_selector` is toggled). #[derive(Debug, Clone)] -pub struct MerkleSumTreeChip { +pub struct MerkleSumTreeChip { config: MerkleSumTreeConfig, } -impl MerkleSumTreeChip { +impl MerkleSumTreeChip { pub fn construct(config: MerkleSumTreeConfig) -> Self { Self { config } } @@ -76,7 +76,7 @@ impl MerkleSumTreeChip { }); meta.create_gate("sum constraint", |meta| { - (0..N_ASSETS) + (0..N_CURRENCIES) .map(|_| { let left_balance = meta.query_advice(col_a, Rotation::cur()); let right_balance = meta.query_advice(col_b, Rotation::cur()); @@ -175,7 +175,7 @@ impl MerkleSumTreeChip { ) } - /// Assign the nodes balance for a single asset in a region following this layout on 3 advice columns: + /// Assign the nodes balance for a single currency in a region following this layout on 3 advice columns: /// /// | a | b | c | /// | ------------ | ------------- | ---------- | @@ -201,7 +201,7 @@ impl MerkleSumTreeChip { Error, > { layouter.assign_region( - || "assign nodes balances per asset", + || "assign nodes balances per currency", |mut region| { // enable the bool_and_swap_selector at row 0 self.config.bool_and_swap_selector.enable(&mut region, 0)?; @@ -245,14 +245,14 @@ impl MerkleSumTreeChip { }); // Perform the assignment according to the swap at offset 1 - let left_balance_asset = region.assign_advice( + let left_currency_balance = region.assign_advice( || "assign left balance after swap", self.config.advice[0], 1, || l1_val, )?; - let right_balance_asset = region.assign_advice( + let right_currency_balance = region.assign_advice( || "assign right balance after swap", self.config.advice[1], 1, @@ -267,7 +267,7 @@ impl MerkleSumTreeChip { let sum_cell = region.assign_advice(|| "sum of balances", self.config.advice[2], 1, || sum)?; - Ok((left_balance_asset, right_balance_asset, sum_cell)) + Ok((left_currency_balance, right_currency_balance, sum_cell)) }, ) } diff --git a/zk_prover/src/chips/range/range_check.rs b/zk_prover/src/chips/range/range_check.rs index cb835764..d1421770 100644 --- a/zk_prover/src/chips/range/range_check.rs +++ b/zk_prover/src/chips/range/range_check.rs @@ -66,7 +66,6 @@ impl RangeCheckChip { lookup_u8_table: Column, lookup_enable_selector: Selector, ) -> RangeCheckConfig { - meta.annotate_lookup_any_column(lookup_u8_table, || "LOOKUP_MAXBITS_RANGE"); meta.lookup_any( diff --git a/zk_prover/src/circom/incremental_mst_inclusion.circom b/zk_prover/src/circom/incremental_mst_inclusion.circom index 98ca38d3..3dd14bdd 100644 --- a/zk_prover/src/circom/incremental_mst_inclusion.circom +++ b/zk_prover/src/circom/incremental_mst_inclusion.circom @@ -8,9 +8,9 @@ Inputs: --------- - step_in[2] : `user_state_prev` and `liabilities_state_prev` from the previous step of the IVC - username: username of the user whose inclusion in the merkle sum tree we want to prove -- user_balances[N_ASSETS]: balances of the user whose inclusion in the merkle sum tree we want to prove +- user_balances[N_CURRENCIES]: balances of the user whose inclusion in the merkle sum tree we want to prove - path_element_hashes[LEVELS]: hashes of elements of the merkle path -- path_element_balances[LEVELS][N_ASSETS]: balances of the elements of the merkle path +- path_element_balances[LEVELS][N_CURRENCIES]: balances of the elements of the merkle path - path_indices[LEVELS]: binary selector that indicates whether given path_element is on the left or right side of merkle path Outputs: @@ -22,7 +22,7 @@ Outputs: Parameters: ------------ - LEVELS: number of levels in the merkle sum tree -- N_ASSETS: number of assets for each user +- N_CURRENCIES: number of currencies for each user - N_BYTES: range of the balances of the users Functionality: @@ -32,21 +32,21 @@ Functionality: 3. Starting from the `leaf_hash` and the Merkle Proof, compute the `root_hash` of the resulting Merkle Sum Tree 4. Starting from `liabilities_state_prev` and `root_hash`, compute `liabilities_state_cur` as H(`liabilities_state_prev`, `root_hash`) */ -template IncrementalMstInclusion (LEVELS, N_ASSETS, N_BYTES) { +template IncrementalMstInclusion (LEVELS, N_CURRENCIES, N_BYTES) { signal input step_in[2]; signal input username; - signal input user_balances[N_ASSETS]; + signal input user_balances[N_CURRENCIES]; signal input path_element_hashes[LEVELS]; - signal input path_element_balances[LEVELS][N_ASSETS]; + signal input path_element_balances[LEVELS][N_CURRENCIES]; signal input path_indices[LEVELS]; signal output step_out[2]; // 1. - component build_leaf_hash = Poseidon(1 + N_ASSETS); + component build_leaf_hash = Poseidon(1 + N_CURRENCIES); build_leaf_hash.inputs[0] <== username; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { build_leaf_hash.inputs[i + 1] <== user_balances[i]; } @@ -56,7 +56,7 @@ template IncrementalMstInclusion (LEVELS, N_ASSETS, N_BYTES) { build_user_state_cur.inputs[1] <== build_leaf_hash.out; // 3. - component check_inclusion = MerkleSumTreeInclusion(LEVELS, N_ASSETS, N_BYTES); + component check_inclusion = MerkleSumTreeInclusion(LEVELS, N_CURRENCIES, N_BYTES); check_inclusion.leaf_hash <== build_leaf_hash.out; check_inclusion.leaf_balances <== user_balances; diff --git a/zk_prover/src/circom/merkle_sum_tree.circom b/zk_prover/src/circom/merkle_sum_tree.circom index 5056ea8e..94cb83c0 100644 --- a/zk_prover/src/circom/merkle_sum_tree.circom +++ b/zk_prover/src/circom/merkle_sum_tree.circom @@ -7,12 +7,12 @@ include "./node_modules/circomlib/circuits/mux1.circom"; /* Inputs: --------- -- left_balances[N_ASSETS] : Balances of the left node -- right_balances[N_ASSETS] : Balances of the right node +- left_balances[N_CURRENCIES] : Balances of the left node +- right_balances[N_CURRENCIES] : Balances of the right node Outputs: --------- -- out_balances[N_ASSETS] : Each element of `out_balances` is the sum of the corresponding elements in left_balances and right_balances. +- out_balances[N_CURRENCIES] : Each element of `out_balances` is the sum of the corresponding elements in left_balances and right_balances. Ex. out_balances[0] = left_balances[0] + right_balances[0] Functionality: @@ -27,15 +27,15 @@ When the output will be used as an input to another summation. When the next lev */ -template Summer(N_ASSETS, N_BYTES) { - signal input left_balances[N_ASSETS]; - signal input right_balances[N_ASSETS]; - signal output out_balances[N_ASSETS]; +template Summer(N_CURRENCIES, N_BYTES) { + signal input left_balances[N_CURRENCIES]; + signal input right_balances[N_CURRENCIES]; + signal output out_balances[N_CURRENCIES]; - component left_in_range[N_ASSETS]; - component right_in_range[N_ASSETS]; + component left_in_range[N_CURRENCIES]; + component right_in_range[N_CURRENCIES]; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { left_in_range[i] = Num2Bits(8*N_BYTES); right_in_range[i] = Num2Bits(8*N_BYTES); @@ -50,21 +50,21 @@ template Summer(N_ASSETS, N_BYTES) { Inputs: --------- - left_hash: Hash of the left node -- left_balances[N_ASSETS] : Balances of the left node +- left_balances[N_CURRENCIES] : Balances of the left node - right_hash: Hash of the right node -- right_balances[N_ASSETS] : Balances of the right node +- right_balances[N_CURRENCIES] : Balances of the right node - s: binary selector Outputs: --------- - swapped_left_hash: left_hash if s = 0, right_hash if s = 1 -- swapped_left_balances[N_ASSETS]: left_balances if s = 0, right_balances if s = 1 +- swapped_left_balances[N_CURRENCIES]: left_balances if s = 0, right_balances if s = 1 - swapped_right_hash: right_hash if s = 0, left_hash if s = 1 -- swapped_right_balances[N_ASSETS]: right_balances if s = 0, left_balances if s = 1 +- swapped_right_balances[N_CURRENCIES]: right_balances if s = 0, left_balances if s = 1 Parameters: ------------ -- N_ASSETS: number of assets for each user +- N_CURRENCIES: number of currencies for each user Functionality: -------------- @@ -72,57 +72,57 @@ Functionality: 2. Constraint that s is either 0 or 1 */ -template Swapper(N_ASSETS) { +template Swapper(N_CURRENCIES) { signal input left_hash; - signal input left_balances[N_ASSETS]; + signal input left_balances[N_CURRENCIES]; signal input right_hash; - signal input right_balances[N_ASSETS]; + signal input right_balances[N_CURRENCIES]; signal input s; signal output swapped_left_hash; - signal output swapped_left_balances[N_ASSETS]; + signal output swapped_left_balances[N_CURRENCIES]; signal output swapped_right_hash; - signal output swapped_right_balances[N_ASSETS]; + signal output swapped_right_balances[N_CURRENCIES]; s * (1 - s) === 0; - component mux = MultiMux1(2 + 2*N_ASSETS); + component mux = MultiMux1(2 + 2*N_CURRENCIES); mux.c[0][0] <== left_hash; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { mux.c[1 + i][0] <== left_balances[i]; } - mux.c[1 + N_ASSETS][0] <== right_hash; + mux.c[1 + N_CURRENCIES][0] <== right_hash; - for (var i = 0; i < N_ASSETS; i++) { - mux.c[2 + N_ASSETS + i][0] <== right_balances[i]; + for (var i = 0; i < N_CURRENCIES; i++) { + mux.c[2 + N_CURRENCIES + i][0] <== right_balances[i]; } mux.c[0][1] <== right_hash; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { mux.c[1 + i][1] <== right_balances[i]; } - mux.c[1 + N_ASSETS][1] <== left_hash; + mux.c[1 + N_CURRENCIES][1] <== left_hash; - for (var i = 0; i < N_ASSETS; i++) { - mux.c[2 + N_ASSETS + i][1] <== left_balances[i]; + for (var i = 0; i < N_CURRENCIES; i++) { + mux.c[2 + N_CURRENCIES + i][1] <== left_balances[i]; } mux.s <== s; swapped_left_hash <== mux.out[0]; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { swapped_left_balances[i] <== mux.out[1 + i]; } - swapped_right_hash <== mux.out[1 + N_ASSETS]; + swapped_right_hash <== mux.out[1 + N_CURRENCIES]; - for (var i = 0; i < N_ASSETS; i++) { - swapped_right_balances[i] <== mux.out[2 + N_ASSETS + i]; + for (var i = 0; i < N_CURRENCIES; i++) { + swapped_right_balances[i] <== mux.out[2 + N_CURRENCIES + i]; } } @@ -130,43 +130,43 @@ template Swapper(N_ASSETS) { Inputs: --------- - left_hash: Hash of the left node -- left_balances[N_ASSETS] : Balances of the left node +- left_balances[N_CURRENCIES] : Balances of the left node - right_hash: Hash of the right node -- right_balances[N_ASSETS] : Balances of the right node +- right_balances[N_CURRENCIES] : Balances of the right node Outputs: --------- -- hash: poseidon hash of (left_hash, left_balances[0], ..., left_balances[N_ASSETS - 1], right_hash, right_balances[0], ..., right_balances[N_ASSETS - 1]) +- hash: poseidon hash of (left_hash, left_balances[0], ..., left_balances[N_CURRENCIES - 1], right_hash, right_balances[0], ..., right_balances[N_CURRENCIES - 1]) Parameters: ------------ -- N_ASSETS: number of assets for each user +- N_CURRENCIES: number of currencies for each user Functionality: -------------- 1. Perform the hashing of two nodes belonging to a level of the merkle sum tree */ -template Hasher(N_ASSETS) { +template Hasher(N_CURRENCIES) { signal input left_hash; - signal input left_balances[N_ASSETS]; + signal input left_balances[N_CURRENCIES]; signal input right_hash; - signal input right_balances[N_ASSETS]; + signal input right_balances[N_CURRENCIES]; signal output hash; // 1. - component hasher = Poseidon(2 + 2*N_ASSETS); + component hasher = Poseidon(2 + 2*N_CURRENCIES); hasher.inputs[0] <== left_hash; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { hasher.inputs[1 + i] <== left_balances[i]; } - hasher.inputs[1 + N_ASSETS] <== right_hash; + hasher.inputs[1 + N_CURRENCIES] <== right_hash; - for (var i = 0; i < N_ASSETS; i++) { - hasher.inputs[2 + N_ASSETS + i] <== right_balances[i]; + for (var i = 0; i < N_CURRENCIES; i++) { + hasher.inputs[2 + N_CURRENCIES + i] <== right_balances[i]; } hash <== hasher.out; @@ -177,9 +177,9 @@ template Hasher(N_ASSETS) { Inputs: --------- - leaf_hash: hash of the leaf node that we want to prove inclusion for -- leaf_balances[N_ASSETS]: balances of the leaf node that we want to prove inclusion for +- leaf_balances[N_CURRENCIES]: balances of the leaf node that we want to prove inclusion for - path_element_hashes[LEVELS]: hashes of elements of the merkle path -- path_element_balances[LEVELS][N_ASSETS]: balances of the elements of the merkle path +- path_element_balances[LEVELS][N_CURRENCIES]: balances of the elements of the merkle path - path_indices[LEVELS]: binary selector that indicates whether given path_element is on the left or right side of merkle path Outputs: @@ -189,7 +189,7 @@ Outputs: Parameters: ------------ - LEVELS: number of levels in the merkle sum tree -- N_ASSETS: number of assets for each user +- N_CURRENCIES: number of currencies for each user - N_BYTES: range of the balances of the users Functionality: @@ -203,11 +203,11 @@ Notes: ------ - The summer is performed before the swapper because the swap doesn't influence the summation. */ -template MerkleSumTreeInclusion(LEVELS, N_ASSETS, N_BYTES) { +template MerkleSumTreeInclusion(LEVELS, N_CURRENCIES, N_BYTES) { signal input leaf_hash; - signal input leaf_balances[N_ASSETS]; + signal input leaf_balances[N_CURRENCIES]; signal input path_element_hashes[LEVELS]; - signal input path_element_balances[LEVELS][N_ASSETS]; + signal input path_element_balances[LEVELS][N_CURRENCIES]; signal input path_indices[LEVELS]; signal output root_hash; @@ -218,13 +218,13 @@ template MerkleSumTreeInclusion(LEVELS, N_ASSETS, N_BYTES) { for (var i = 0; i < LEVELS; i++) { // 1. - summers[i] = Summer(N_ASSETS, N_BYTES); + summers[i] = Summer(N_CURRENCIES, N_BYTES); summers[i].left_balances <== i == 0 ? leaf_balances : summers[i - 1].out_balances; summers[i].right_balances <== path_element_balances[i]; // 2. - swappers[i] = Swapper(N_ASSETS); + swappers[i] = Swapper(N_CURRENCIES); swappers[i].left_hash <== i == 0 ? leaf_hash : hashers[i - 1].hash; swappers[i].left_balances <== i == 0 ? leaf_balances : summers[i - 1].out_balances; @@ -233,7 +233,7 @@ template MerkleSumTreeInclusion(LEVELS, N_ASSETS, N_BYTES) { swappers[i].s <== path_indices[i]; // 3. - hashers[i] = Hasher(N_ASSETS); + hashers[i] = Hasher(N_CURRENCIES); hashers[i].left_hash <== swappers[i].swapped_left_hash; hashers[i].left_balances <== swappers[i].swapped_left_balances; @@ -243,9 +243,9 @@ template MerkleSumTreeInclusion(LEVELS, N_ASSETS, N_BYTES) { } // 4. - component root_balance_in_range[N_ASSETS]; + component root_balance_in_range[N_CURRENCIES]; - for (var i = 0; i < N_ASSETS; i++) { + for (var i = 0; i < N_CURRENCIES; i++) { root_balance_in_range[i] = Num2Bits(8*N_BYTES); root_balance_in_range[i].in <== summers[LEVELS - 1].out_balances[i]; } diff --git a/zk_prover/src/circuits/merkle_sum_tree.rs b/zk_prover/src/circuits/merkle_sum_tree.rs index 7771c7bd..7b3b9d2d 100644 --- a/zk_prover/src/circuits/merkle_sum_tree.rs +++ b/zk_prover/src/circuits/merkle_sum_tree.rs @@ -17,7 +17,7 @@ use snark_verifier_sdk::CircuitExt; /// # Type Parameters /// /// * `LEVELS`: The number of levels of the merkle sum tree. In particular, it indicates the number of hashing operations that are performed from the leaf to the root. For example a tree with 16 entries has 4 levels. -/// * `N_ASSETS`: The number of assets for which the solvency is verified. +/// * `N_CURRENCIES`: The number of currencies for which the solvency is verified. /// * `N_BYTES`: The number of bytes in which the balances should lie /// /// # Fields @@ -28,27 +28,27 @@ use snark_verifier_sdk::CircuitExt; /// * `sibling_middle_node_hash_preimages`: The preimages of the hashes that corresponds to the Sibling Middle Nodes (part of the Merkle Proof). /// * `root`: The root of the Merkle Sum Tree #[derive(Clone)] -pub struct MstInclusionCircuit +pub struct MstInclusionCircuit where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - pub entry: Entry, + pub entry: Entry, pub path_indices: Vec, - pub sibling_leaf_node_hash_preimage: [Fp; N_ASSETS + 1], - pub sibling_middle_node_hash_preimages: Vec<[Fp; N_ASSETS + 2]>, - pub root: Node, + pub sibling_leaf_node_hash_preimage: [Fp; N_CURRENCIES + 1], + pub sibling_middle_node_hash_preimages: Vec<[Fp; N_CURRENCIES + 2]>, + pub root: Node, } -impl CircuitExt - for MstInclusionCircuit +impl CircuitExt + for MstInclusionCircuit where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - /// Returns the number of public inputs of the circuit. It is {2 + N_ASSETS}, namely the leaf hash to be verified inclusion of, the root hash of the merkle sum tree and the root balances of the merkle sum tree. + /// Returns the number of public inputs of the circuit. It is {2 + N_CURRENCIES}, namely the leaf hash to be verified inclusion of, the root hash of the merkle sum tree and the root balances of the merkle sum tree. fn num_instance(&self) -> Vec { - vec![{ 2 + N_ASSETS }] + vec![{ 2 + N_CURRENCIES }] } /// Returns the values of the public inputs of the circuit. Namely the leaf hash to be verified inclusion of and the root hash of the merkle sum tree. fn instances(&self) -> Vec> { @@ -58,35 +58,35 @@ where } } -impl CircuitBase - for MstInclusionCircuit +impl CircuitBase + for MstInclusionCircuit where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { } -impl - MstInclusionCircuit +impl + MstInclusionCircuit where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { pub fn init_empty() -> Self { Self { entry: Entry::init_empty(), path_indices: vec![Fp::zero(); LEVELS], - sibling_leaf_node_hash_preimage: [Fp::zero(); N_ASSETS + 1], - sibling_middle_node_hash_preimages: vec![[Fp::zero(); N_ASSETS + 2]; LEVELS], + sibling_leaf_node_hash_preimage: [Fp::zero(); N_CURRENCIES + 1], + sibling_middle_node_hash_preimages: vec![[Fp::zero(); N_CURRENCIES + 2]; LEVELS], root: Node::init_empty(), } } /// Initializes the circuit with the merkle proof and the entry of the user of which the inclusion is to be verified. - pub fn init(merkle_proof: MerkleProof) -> Self + pub fn init(merkle_proof: MerkleProof) -> Self where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { assert_eq!(merkle_proof.path_indices.len(), LEVELS); assert_eq!( @@ -106,37 +106,37 @@ where /// Configuration for the Mst Inclusion circuit /// # Type Parameters /// -/// * `N_ASSETS`: The number of assets for which the solvency is verified. +/// * `N_CURRENCIES`: The number of currencies for which the solvency is verified. /// * `N_BYTES`: The number of bytes in which the balances should lie /// /// # Fields /// /// * `merkle_sum_tree_config`: Configuration for the merkle sum tree -/// * `poseidon_entry_config`: Configuration for the poseidon hash function with WIDTH = 2 and RATE = 1 and input length of N_ASSETS + 1. Needed to perform the hashing from the entry to the leaf. -/// * `poseidon_middle_config`: Configuration for the poseidon hash function with WIDTH = 2 and RATE = 1 and input length of N_ASSETS + 2. Needed to perform hashings from the leaf to the root. +/// * `poseidon_entry_config`: Configuration for the poseidon hash function with WIDTH = 2 and RATE = 1 and input length of N_CURRENCIES + 1. Needed to perform the hashing from the entry to the leaf. +/// * `poseidon_middle_config`: Configuration for the poseidon hash function with WIDTH = 2 and RATE = 1 and input length of N_CURRENCIES + 2. Needed to perform hashings from the leaf to the root. /// * `range_check_config`: Configuration for the range check chip /// * `instance`: Instance column used to store the public inputs /// * `advices`: Advice columns used to store the private inputs #[derive(Debug, Clone)] -pub struct MstInclusionConfig +pub struct MstInclusionConfig where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { merkle_sum_tree_config: MerkleSumTreeConfig, - poseidon_entry_config: PoseidonConfig<2, 1, { N_ASSETS + 1 }>, - poseidon_middle_config: PoseidonConfig<2, 1, { N_ASSETS + 2 }>, + poseidon_entry_config: PoseidonConfig<2, 1, { N_CURRENCIES + 1 }>, + poseidon_middle_config: PoseidonConfig<2, 1, { N_CURRENCIES + 2 }>, range_check_config: RangeCheckConfig, instance: Column, advices: [Column; 3], fixed_columns: [Column; 5], } -impl MstInclusionConfig +impl MstInclusionConfig where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { pub fn configure(meta: &mut ConstraintSystem) -> Self { // the max number of advices columns needed is WIDTH + 1 given requirement of the poseidon config @@ -154,17 +154,18 @@ where // enable constant for the fixed_column[2], this is required for the poseidon chip and the range check chip meta.enable_constant(fixed_columns[2]); - let poseidon_entry_config = PoseidonChip::::configure( - meta, - advices[0..2].try_into().unwrap(), - advices[2], - fixed_columns[0..2].try_into().unwrap(), - fixed_columns[2..4].try_into().unwrap(), - ); + let poseidon_entry_config = + PoseidonChip::::configure( + meta, + advices[0..2].try_into().unwrap(), + advices[2], + fixed_columns[0..2].try_into().unwrap(), + fixed_columns[2..4].try_into().unwrap(), + ); // in fact, the poseidon config requires #WIDTH advice columns for state and 1 for partial_sbox, #WIDTH fixed columns for rc_a and #WIDTH for rc_b let poseidon_middle_config = - PoseidonChip::::configure( + PoseidonChip::::configure( meta, advices[0..2].try_into().unwrap(), advices[2], @@ -177,8 +178,8 @@ where meta.enable_equality(*col); } - // the configuration of merkle_sum_tree will always require 3 advices, no matter the number of assets - let merkle_sum_tree_config = MerkleSumTreeChip::::configure( + // the configuration of merkle_sum_tree will always require 3 advices, no matter the number of currencies + let merkle_sum_tree_config = MerkleSumTreeChip::::configure( meta, advices[0..3].try_into().unwrap(), selectors[0..2].try_into().unwrap(), @@ -206,13 +207,13 @@ where } } -impl Circuit - for MstInclusionCircuit +impl Circuit + for MstInclusionCircuit where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - type Config = MstInclusionConfig; + type Config = MstInclusionConfig; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { @@ -221,7 +222,7 @@ where /// Configures the circuit fn configure(meta: &mut ConstraintSystem) -> Self::Config { - MstInclusionConfig::::configure(meta) + MstInclusionConfig::::configure(meta) } fn synthesize( @@ -231,15 +232,17 @@ where ) -> Result<(), Error> { // build auxiliary chips let merkle_sum_tree_chip = - MerkleSumTreeChip::::construct(config.merkle_sum_tree_config); + MerkleSumTreeChip::::construct(config.merkle_sum_tree_config); - let poseidon_entry_chip = PoseidonChip::::construct( - config.poseidon_entry_config, - ); + let poseidon_entry_chip = + PoseidonChip::::construct( + config.poseidon_entry_config, + ); - let poseidon_middle_chip = PoseidonChip::::construct( - config.poseidon_middle_config, - ); + let poseidon_middle_chip = + PoseidonChip::::construct( + config.poseidon_middle_config, + ); let range_check_chip = RangeCheckChip::::construct(config.range_check_config); @@ -254,7 +257,7 @@ where // Assign the entry balances to the witness let mut current_balances = vec![]; - for i in 0..N_ASSETS { + for i in 0..N_CURRENCIES { let balance = self.assign_value_to_witness( layouter.namespace(|| format!("assign entry balance {}", i)), big_uint_to_fp(&self.entry.balances()[i]), @@ -265,14 +268,14 @@ where } // Perform the hashing to username and balances to obtain the leaf hash - // create an hash_input array of length N_ASSETS + 1 that contains the entry username and the entry balances + // create an hash_input array of length N_CURRENCIES + 1 that contains the entry username and the entry balances let entry_hasher_input_vec: Vec> = [username] .iter() .chain(current_balances.iter()) .map(|x| x.to_owned()) .collect(); - let entry_hasher_input: [AssignedCell; N_ASSETS + 1] = + let entry_hasher_input: [AssignedCell; N_CURRENCIES + 1] = match entry_hasher_input_vec.try_into() { Ok(arr) => arr, Err(_) => panic!("Failed to convert Vec to Array"), @@ -312,17 +315,17 @@ where )?; // Assign balances from sibling leaf node hash preimage to the circuit - for asset in 0..N_ASSETS { + for currency in 0..N_CURRENCIES { let leaf_node_sibling_balance = self.assign_value_to_witness( - layouter.namespace(|| format!("sibling leaf node balance {}", asset)), - self.sibling_leaf_node_hash_preimage[asset + 1], + layouter.namespace(|| format!("sibling leaf node balance {}", currency)), + self.sibling_leaf_node_hash_preimage[currency + 1], "sibling leaf balance", config.advices[1], )?; sibling_balances.push(leaf_node_sibling_balance); } - // create an hash_input array of length N_ASSETS + 1 that contains the sibling_leaf_node_username and the sibling_balances (the sibling leaf node hash preimage) + // create an hash_input array of length N_CURRENCIES + 1 that contains the sibling_leaf_node_username and the sibling_balances (the sibling leaf node hash preimage) let sibling_hasher_input_vec: Vec> = [sibling_leaf_node_username] .iter() @@ -330,7 +333,7 @@ where .map(|x| x.to_owned()) .collect(); - let sibling_hasher_input: [AssignedCell; N_ASSETS + 1] = + let sibling_hasher_input: [AssignedCell; N_CURRENCIES + 1] = match sibling_hasher_input_vec.try_into() { Ok(arr) => arr, Err(_) => panic!("Failed to convert Vec to Array"), @@ -343,25 +346,25 @@ where )?; // For level 0, perform range check on the leaf node balances and on the sibling node balances - for asset in 0..N_ASSETS { + for currency in 0..N_CURRENCIES { // Each balance cell is constrained to be within the range defined by N_BYTES range_check_chip.assign( layouter.namespace(|| { format!( - "{}: asset {}: range check leaf balance", - namespace_prefix, asset + "{}: currency {}: range check leaf balance", + namespace_prefix, currency ) }), - ¤t_balances[asset], + ¤t_balances[currency], )?; range_check_chip.assign( layouter.namespace(|| { format!( - "{}: asset {}: range check sibling balance", - namespace_prefix, asset + "{}: currency {}: range check sibling balance", + namespace_prefix, currency ) }), - &sibling_balances[asset], + &sibling_balances[currency], )?; } @@ -372,10 +375,10 @@ where // Perform the hashing of sibling node hash preimage to obtain the sibling node hash else { // Assign balances from sibling middle node hash preimage to the circuit - for asset in 0..N_ASSETS { + for currency in 0..N_CURRENCIES { let middle_node_sibling_balance = self.assign_value_to_witness( - layouter.namespace(|| format!("sibling node balance {}", asset)), - self.sibling_middle_node_hash_preimages[level - 1][asset], + layouter.namespace(|| format!("sibling node balance {}", currency)), + self.sibling_middle_node_hash_preimages[level - 1][currency], "sibling node balance", config.advices[1], )?; @@ -385,7 +388,7 @@ where // Assign middle_node_sibling_child_left_hash from middle node hash preimage to the circuit let middle_node_sibling_child_left_hash = self.assign_value_to_witness( layouter.namespace(|| format!("sibling left hash")), - self.sibling_middle_node_hash_preimages[level - 1][N_ASSETS], + self.sibling_middle_node_hash_preimages[level - 1][N_CURRENCIES], "sibling left hash", config.advices[2], )?; @@ -393,12 +396,12 @@ where // Assign middle_node_sibling_child_right_hash from middle node hash preimage to the circuit let middle_node_sibling_child_right_hash = self.assign_value_to_witness( layouter.namespace(|| format!("sibling right hash")), - self.sibling_middle_node_hash_preimages[level - 1][N_ASSETS + 1], + self.sibling_middle_node_hash_preimages[level - 1][N_CURRENCIES + 1], "sibling right hash", config.advices[2], )?; - // create an hash_input array of length 2 + N_ASSETS that contains the sibling balances, the middle_node_sibling_child_left_hash and the middle_node_sibling_child_right_hash + // create an hash_input array of length 2 + N_CURRENCIES that contains the sibling balances, the middle_node_sibling_child_left_hash and the middle_node_sibling_child_right_hash let sibling_hasher_input_vec: Vec> = sibling_balances .iter() .chain([middle_node_sibling_child_left_hash].iter()) @@ -406,7 +409,7 @@ where .map(|x| x.to_owned()) .collect(); - let sibling_hasher_input: [AssignedCell; N_ASSETS + 2] = + let sibling_hasher_input: [AssignedCell; N_CURRENCIES + 2] = match sibling_hasher_input_vec.try_into() { Ok(arr) => arr, Err(_) => panic!("Failed to convert Vec to Array"), @@ -419,16 +422,16 @@ where )?; // For other levels, only perform range on the sibling node balances. Any risk of overflow of the `current_balances` will be checked during verification - for asset in 0..N_ASSETS { + for currency in 0..N_CURRENCIES { // Each balance cell is constrained to be within the range defined by N_BYTES range_check_chip.assign( layouter.namespace(|| { format!( - "{}: asset {}: range check sibling balance", - namespace_prefix, asset + "{}: currency {}: range check sibling balance", + namespace_prefix, currency ) }), - &sibling_balances[asset], + &sibling_balances[currency], )?; } @@ -457,17 +460,17 @@ where let mut right_balances = vec![]; // For every level, perform the swap of the balances (between `current_balances` and `sibling_balances`) according to the swap bit - for asset in 0..N_ASSETS { + for currency in 0..N_CURRENCIES { let (left_balance, right_balance, next_balance) = merkle_sum_tree_chip .swap_balances_per_level( layouter.namespace(|| { format!( - "{}: asset {}: assign nodes balance", - namespace_prefix, asset + "{}: currency {}: assign nodes balance", + namespace_prefix, currency ) }), - ¤t_balances[asset], - &sibling_balances[asset], + ¤t_balances[currency], + &sibling_balances[currency], &swap_bit_level, )?; @@ -476,7 +479,7 @@ where right_balances.push(right_balance); } - // create an hash_input array of length N_ASSETS + 2 that contains the next balances, the left hash and the right hash + // create an hash_input array of length N_CURRENCIES + 2 that contains the next balances, the left hash and the right hash let middle_hasher_input_vec: Vec> = next_balances .iter() .chain([hash_left_current].iter()) @@ -484,7 +487,7 @@ where .map(|x| x.to_owned()) .collect(); - let middle_hasher_input: [AssignedCell; N_ASSETS + 2] = + let middle_hasher_input: [AssignedCell; N_CURRENCIES + 2] = match middle_hasher_input_vec.try_into() { Ok(arr) => arr, Err(_) => panic!("Failed to convert Vec to Array"), diff --git a/zk_prover/src/circuits/tests.rs b/zk_prover/src/circuits/tests.rs index a04c29af..5a021059 100644 --- a/zk_prover/src/circuits/tests.rs +++ b/zk_prover/src/circuits/tests.rs @@ -17,7 +17,7 @@ mod test { use num_bigint::ToBigUint; use snark_verifier_sdk::CircuitExt; - const N_ASSETS: usize = 2; + const N_CURRENCIES: usize = 2; const LEVELS: usize = 4; const N_BYTES: usize = 14; const K: u32 = 11; @@ -25,19 +25,18 @@ mod test { #[test] fn test_valid_merkle_sum_tree() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); for user_index in 0..16 { // get proof for entry ˆuser_indexˆ let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let valid_prover = MockProver::run(K, &circuit, circuit.instances()).unwrap(); assert_eq!(circuit.instances()[0].len(), circuit.num_instance()[0]); - assert_eq!(circuit.instances()[0].len(), 2 + N_ASSETS); + assert_eq!(circuit.instances()[0].len(), 2 + N_CURRENCIES); valid_prover.assert_satisfied(); } @@ -45,7 +44,7 @@ mod test { #[test] fn test_valid_merkle_sum_tree_with_full_prover() { - let circuit = MstInclusionCircuit::::init_empty(); + let circuit = MstInclusionCircuit::::init_empty(); // Generate a universal trusted setup for testing purposes. // @@ -56,8 +55,7 @@ mod test { let (params, pk, vk) = generate_setup_artifacts(K, None, circuit).unwrap(); let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; @@ -65,7 +63,7 @@ mod test { let user_entry = merkle_sum_tree.get_entry(user_index); // Only now we can instantiate the circuit with the actual inputs - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); // Generate the proof let proof = full_prover(¶ms, &pk, circuit.clone(), circuit.instances()); @@ -82,9 +80,9 @@ mod test { let expected_root_hash = merkle_sum_tree.root().hash; assert_eq!(circuit.instances()[0][1], expected_root_hash); - // public inputs [2, 2+N_ASSETS - 1] are the root balances + // public inputs [2, 2+N_CURRENCIES - 1] are the root balances let expected_root_balances = merkle_sum_tree.root().balances; - for i in 0..N_ASSETS { + for i in 0..N_CURRENCIES { assert_eq!(circuit.instances()[0][2 + i], expected_root_balances[i]); } } @@ -93,13 +91,12 @@ mod test { #[test] fn test_invalid_root_hash() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let mut instances = circuit.instances(); let invalid_root_hash = Fp::from(1000u64); @@ -127,21 +124,20 @@ mod test { #[test] fn test_invalid_root_hash_as_instance_with_full_prover() { - let circuit = MstInclusionCircuit::::init_empty(); + let circuit = MstInclusionCircuit::::init_empty(); // generate a universal trusted setup for testing, along with the verification key (vk) and the proving key (pk). let (params, pk, vk) = generate_setup_artifacts(K, None, circuit).unwrap(); let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Only now we can instantiate the circuit with the actual inputs - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let invalid_root_hash = Fp::from(1000u64); @@ -162,15 +158,14 @@ mod test { #[test] fn test_invalid_entry_balance_as_witness() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Only now we can instantiate the circuit with the actual inputs - let mut circuit = MstInclusionCircuit::::init(merkle_proof); + let mut circuit = MstInclusionCircuit::::init(merkle_proof); let instances = circuit.instances(); @@ -203,14 +198,14 @@ mod test { VerifyFailure::Permutation { column: (Any::advice(), 2).into(), location: FailureLocation::InRegion { - region: (111, "assign nodes balances per asset").into(), + region: (111, "assign nodes balances per currency").into(), offset: 1 } }, VerifyFailure::Permutation { column: (Any::advice(), 2).into(), location: FailureLocation::InRegion { - region: (112, "assign nodes balances per asset").into(), + region: (112, "assign nodes balances per currency").into(), offset: 1 } }, @@ -238,15 +233,14 @@ mod test { #[test] fn test_invalid_leaf_hash_as_instance() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Only now we can instantiate the circuit with the actual inputs - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let mut instances = circuit.instances(); let invalid_leaf_hash = Fp::from(1000u64); @@ -272,19 +266,18 @@ mod test { ); } - // Passing a non binary index should fail the bool constraint inside "assign nodes hashes per merkle tree level" and "assign nodes balances per asset" region 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 inside "assign nodes hashes per merkle tree level" and "assign nodes balances per currency" region and the permutation check between the computed root hash and the instance column root hash #[test] fn test_non_binary_index() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Only now we can instantiate the circuit with the actual inputs - let mut circuit = MstInclusionCircuit::::init(merkle_proof); + let mut circuit = MstInclusionCircuit::::init(merkle_proof); let instances = circuit.instances(); @@ -307,7 +300,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((6, "bool constraint").into(), 0, "").into(), location: FailureLocation::InRegion { - region: (27, "assign nodes balances per asset").into(), + region: (27, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![(((Any::advice(), 2).into(), 0).into(), "0x2".to_string()),] @@ -315,7 +308,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((6, "bool constraint").into(), 0, "").into(), location: FailureLocation::InRegion { - region: (28, "assign nodes balances per asset").into(), + region: (28, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![(((Any::advice(), 2).into(), 0).into(), "0x2".to_string()),] @@ -373,7 +366,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((7, "swap constraint").into(), 0, "").into(), location: FailureLocation::InRegion { - region: (27, "assign nodes balances per asset").into(), + region: (27, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![ @@ -386,7 +379,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((7, "swap constraint").into(), 1, "").into(), location: FailureLocation::InRegion { - region: (27, "assign nodes balances per asset").into(), + region: (27, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![ @@ -399,7 +392,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((7, "swap constraint").into(), 0, "").into(), location: FailureLocation::InRegion { - region: (28, "assign nodes balances per asset").into(), + region: (28, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![ @@ -412,7 +405,7 @@ mod test { VerifyFailure::ConstraintNotSatisfied { constraint: ((7, "swap constraint").into(), 1, "").into(), location: FailureLocation::InRegion { - region: (28, "assign nodes balances per asset").into(), + region: (28, "assign nodes balances per currency").into(), offset: 0 }, cell_values: vec![ @@ -441,15 +434,14 @@ mod test { #[test] fn test_swapping_index() { let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); // Only now we can instantiate the circuit with the actual inputs - let mut circuit = MstInclusionCircuit::::init(merkle_proof); + let mut circuit = MstInclusionCircuit::::init(merkle_proof); let instances = circuit.instances(); @@ -482,14 +474,13 @@ mod test { use plotters::prelude::*; let merkle_sum_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let user_index = 0; let merkle_proof = merkle_sum_tree.generate_proof(user_index).unwrap(); - let circuit = MstInclusionCircuit::::init(merkle_proof); + let circuit = MstInclusionCircuit::::init(merkle_proof); let root = BitMapBackend::new("prints/mst-inclusion-layout.png", (2048, 32768)) .into_drawing_area(); diff --git a/zk_prover/src/merkle_sum_tree/entry.rs b/zk_prover/src/merkle_sum_tree/entry.rs index be6fdb35..9f4d886b 100644 --- a/zk_prover/src/merkle_sum_tree/entry.rs +++ b/zk_prover/src/merkle_sum_tree/entry.rs @@ -5,14 +5,14 @@ use num_bigint::BigUint; /// An entry in the Merkle Sum Tree from the database of the CEX. /// It contains the username and the balances of the user. #[derive(Clone, Debug)] -pub struct Entry { +pub struct Entry { username_as_big_uint: BigUint, - balances: [BigUint; N_ASSETS], + balances: [BigUint; N_CURRENCIES], username: String, } -impl Entry { - pub fn new(username: String, balances: [BigUint; N_ASSETS]) -> Result { +impl Entry { + pub fn new(username: String, balances: [BigUint; N_CURRENCIES]) -> Result { Ok(Entry { username_as_big_uint: big_intify_username(&username), balances, @@ -21,7 +21,7 @@ impl Entry { } pub fn init_empty() -> Self { - let empty_balances: [BigUint; N_ASSETS] = std::array::from_fn(|_| BigUint::from(0u32)); + let empty_balances: [BigUint; N_CURRENCIES] = std::array::from_fn(|_| BigUint::from(0u32)); Entry { username_as_big_uint: BigUint::from(0u32), @@ -30,9 +30,9 @@ impl Entry { } } - pub fn compute_leaf(&self) -> Node + pub fn compute_leaf(&self) -> Node where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { Node::leaf(&self.username_as_big_uint, &self.balances) } @@ -40,15 +40,15 @@ impl Entry { /// Stores the new balance values /// /// Returns the updated node - pub fn recompute_leaf(&mut self, updated_balances: &[BigUint; N_ASSETS]) -> Node + pub fn recompute_leaf(&mut self, updated_balances: &[BigUint; N_CURRENCIES]) -> Node where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { self.balances = updated_balances.clone(); Node::leaf(&self.username_as_big_uint, updated_balances) } - pub fn balances(&self) -> &[BigUint; N_ASSETS] { + pub fn balances(&self) -> &[BigUint; N_CURRENCIES] { &self.balances } diff --git a/zk_prover/src/merkle_sum_tree/mod.rs b/zk_prover/src/merkle_sum_tree/mod.rs index 4d2e69c6..c8f7f731 100644 --- a/zk_prover/src/merkle_sum_tree/mod.rs +++ b/zk_prover/src/merkle_sum_tree/mod.rs @@ -11,18 +11,18 @@ use halo2_proofs::halo2curves::bn256::Fr as Fp; /// Fields: /// * `entry`: The entry for which the proof is generated /// * `root`: The root of the Merkle Sum Tree -/// * `sibling_leaf_node_hash_preimage`: The hash preimage of the sibling leaf node. The hash preimage is equal to `[sibling_username, sibling.balance[0], sibling.balance[1], ... sibling.balance[N_ASSETS - 1]]` -/// * `sibling_middle_node_hash_preimages`: The hash preimages of the sibling middle nodes. The hash preimage is equal to `[sibling_left_child.balance[0] + sibling_right_child.balance[0], sibling_left_child.balance[1] + sibling_right_child.balance[1], ..., sibling_left_child.balance[N_ASSETS - 1] + sibling_right_child.balance[N_ASSETS - 1], sibling_left_child.hash, sibling_right_child.hash]` +/// * `sibling_leaf_node_hash_preimage`: The hash preimage of the sibling leaf node. The hash preimage is equal to `[sibling_username, sibling.balance[0], sibling.balance[1], ... sibling.balance[N_CURRENCIES - 1]]` +/// * `sibling_middle_node_hash_preimages`: The hash preimages of the sibling middle nodes. The hash preimage is equal to `[sibling_left_child.balance[0] + sibling_right_child.balance[0], sibling_left_child.balance[1] + sibling_right_child.balance[1], ..., sibling_left_child.balance[N_CURRENCIES - 1] + sibling_right_child.balance[N_CURRENCIES - 1], sibling_left_child.hash, sibling_right_child.hash]` #[derive(Clone, Debug)] -pub struct MerkleProof +pub struct MerkleProof where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - pub entry: Entry, - pub root: Node, - pub sibling_leaf_node_hash_preimage: [Fp; N_ASSETS + 1], - pub sibling_middle_node_hash_preimages: Vec<[Fp; N_ASSETS + 2]>, + pub entry: Entry, + pub root: Node, + pub sibling_leaf_node_hash_preimage: [Fp; N_CURRENCIES + 1], + pub sibling_middle_node_hash_preimages: Vec<[Fp; N_CURRENCIES + 2]>, pub path_indices: Vec, } diff --git a/zk_prover/src/merkle_sum_tree/mst.rs b/zk_prover/src/merkle_sum_tree/mst.rs index 3ceb94e8..c9c25058 100644 --- a/zk_prover/src/merkle_sum_tree/mst.rs +++ b/zk_prover/src/merkle_sum_tree/mst.rs @@ -8,29 +8,29 @@ use num_bigint::BigUint; /// Merkle Sum Tree Data Structure. /// /// A Merkle Sum Tree is a binary Merkle Tree with the following properties: -/// * Each Entry of a Merkle Sum Tree is a pair of a username and #N_ASSETS balances. -/// * Each Leaf Node contains a hash and #N_ASSETS balances. The hash is equal to `H(username, balance[0], balance[1], ... balance[N_ASSETS - 1])`. The balances are equal to the balances associated to the entry -/// * Each Middle Node contains a hash and #N_ASSETS balances. The hash is equal to `H(LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_ASSETS - 1] + RightChild.balance[N_ASSETS - 1], LeftChild.hash, RightChild.hash)`. The balances are equal to the sum of the balances of the child nodes per each cryptocurrency. +/// * Each Entry of a Merkle Sum Tree is a pair of a username and #N_CURRENCIES balances. +/// * Each Leaf Node contains a hash and #N_CURRENCIES balances. The hash is equal to `H(username, balance[0], balance[1], ... balance[N_CURRENCIES - 1])`. The balances are equal to the balances associated to the entry +/// * Each Middle Node contains a hash and #N_CURRENCIES balances. The hash is equal to `H(LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_CURRENCIES - 1] + RightChild.balance[N_CURRENCIES - 1], LeftChild.hash, RightChild.hash)`. The balances are equal to the sum of the balances of the child nodes per each cryptocurrency. /// * The Root Node represents the committed state of the Tree and contains the sum of all the entries' balances per each cryptocurrency. /// /// # Type Parameters /// -/// * `N_ASSETS`: The number of cryptocurrencies for each user account +/// * `N_CURRENCIES`: The number of cryptocurrencies for each user account /// * `N_BYTES`: Range in which each node balance should lie #[derive(Debug, Clone)] -pub struct MerkleSumTree { - root: Node, - nodes: Vec>>, +pub struct MerkleSumTree { + root: Node, + nodes: Vec>>, depth: usize, - entries: Vec>, + entries: Vec>, cryptocurrencies: Vec, is_sorted: bool, } -impl Tree - for MerkleSumTree +impl Tree + for MerkleSumTree { - fn root(&self) -> &Node { + fn root(&self) -> &Node { &self.root } @@ -38,19 +38,19 @@ impl Tree &self.depth } - fn leaves(&self) -> &[Node] { + fn leaves(&self) -> &[Node] { &self.nodes[0] } - fn nodes(&self) -> &[Vec>] { + fn nodes(&self) -> &[Vec>] { &self.nodes } - fn get_entry(&self, index: usize) -> &Entry { + fn get_entry(&self, index: usize) -> &Entry { &self.entries[index] } - fn entries(&self) -> &[Entry] { + fn entries(&self) -> &[Entry] { &self.entries } fn cryptocurrencies(&self) -> &[Cryptocurrency] { @@ -64,7 +64,7 @@ pub struct Cryptocurrency { pub chain: String, } -impl MerkleSumTree { +impl MerkleSumTree { /// Builds a Merkle Sum Tree from a CSV file stored at `path`. The CSV file must be formatted as follows: /// /// `username,balance__,balance__,...` @@ -72,10 +72,10 @@ impl MerkleSumTree Result> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - let (cryptocurrencies, entries) = parse_csv_to_entries::<&str, N_ASSETS, N_BYTES>(path)?; + let (cryptocurrencies, entries) = parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path)?; Self::from_entries(entries, cryptocurrencies, false) } @@ -86,11 +86,11 @@ impl MerkleSumTree Result> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let (cryptocurrencies, mut entries) = - parse_csv_to_entries::<&str, N_ASSETS, N_BYTES>(path)?; + parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path)?; entries.sort_by(|a, b| a.username().cmp(b.username())); @@ -98,13 +98,13 @@ impl MerkleSumTree>, + entries: Vec>, cryptocurrencies: Vec, is_sorted: bool, - ) -> Result, Box> + ) -> Result, Box> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let depth = (entries.len() as f64).log2().ceil() as usize; @@ -137,11 +137,11 @@ impl MerkleSumTree Result, Box> + new_balances: &[BigUint; N_CURRENCIES], + ) -> Result, Box> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let index = self.index_of_username(username)?; @@ -156,12 +156,12 @@ impl MerkleSumTree MerkleSumTree Result> where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { if !self.is_sorted { self.entries diff --git a/zk_prover/src/merkle_sum_tree/node.rs b/zk_prover/src/merkle_sum_tree/node.rs index 19da8f0d..5e115bd3 100644 --- a/zk_prover/src/merkle_sum_tree/node.rs +++ b/zk_prover/src/merkle_sum_tree/node.rs @@ -5,19 +5,19 @@ use halo2_proofs::halo2curves::bn256::Fr as Fp; use num_bigint::BigUint; #[derive(Clone, Debug)] -pub struct Node { +pub struct Node { pub hash: Fp, - pub balances: [Fp; N_ASSETS], + pub balances: [Fp; N_CURRENCIES], } -impl Node { +impl Node { /// Builds a leaf-level node of the MST - /// The leaf node hash is equal to `H(username, balance[0], balance[1], ... balance[N_ASSETS - 1])` - /// The balances are equal to `balance[0], balance[1], ... balance[N_ASSETS - 1]` - pub fn leaf(username: &BigUint, balances: &[BigUint; N_ASSETS]) -> Node + /// The leaf node hash is equal to `H(username, balance[0], balance[1], ... balance[N_CURRENCIES - 1])` + /// The balances are equal to `balance[0], balance[1], ... balance[N_CURRENCIES - 1]` + pub fn leaf(username: &BigUint, balances: &[BigUint; N_CURRENCIES]) -> Node where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { - let mut hash_preimage = [Fp::zero(); N_ASSETS + 1]; + let mut hash_preimage = [Fp::zero(); N_CURRENCIES + 1]; hash_preimage[0] = big_uint_to_fp(username); for (i, balance) in hash_preimage.iter_mut().enumerate().skip(1) { *balance = big_uint_to_fp(&balances[i - 1]); @@ -26,35 +26,35 @@ impl Node { Node::leaf_node_from_preimage(&hash_preimage) } /// Builds a "middle" (non-leaf-level) node of the MST - /// The middle node hash is equal to `H(LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_ASSETS - 1] + RightChild.balance[N_ASSETS - 1], LeftChild.hash, RightChild.hash)` - /// The balances are equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_ASSETS - 1] + RightChild.balance[N_ASSETS - 1]` - pub fn middle(child_l: &Node, child_r: &Node) -> Node + /// The middle node hash is equal to `H(LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_CURRENCIES - 1] + RightChild.balance[N_CURRENCIES - 1], LeftChild.hash, RightChild.hash)` + /// The balances are equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_CURRENCIES - 1] + RightChild.balance[N_CURRENCIES - 1]` + pub fn middle(child_l: &Node, child_r: &Node) -> Node where - [(); N_ASSETS + 2]: Sized, + [(); N_CURRENCIES + 2]: Sized, { - let mut hash_preimage = [Fp::zero(); N_ASSETS + 2]; - for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_ASSETS) { + let mut hash_preimage = [Fp::zero(); N_CURRENCIES + 2]; + for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_CURRENCIES) { *balance = child_l.balances[i] + child_r.balances[i]; } - hash_preimage[N_ASSETS] = child_l.hash; - hash_preimage[N_ASSETS + 1] = child_r.hash; + hash_preimage[N_CURRENCIES] = child_l.hash; + hash_preimage[N_CURRENCIES + 1] = child_r.hash; Node::middle_node_from_preimage(&hash_preimage) } - pub fn init_empty() -> Node + pub fn init_empty() -> Node where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { Node { hash: Fp::zero(), - balances: [Fp::zero(); N_ASSETS], + balances: [Fp::zero(); N_CURRENCIES], } } - pub fn leaf_node_from_preimage(preimage: &[Fp; N_ASSETS + 1]) -> Node + pub fn leaf_node_from_preimage(preimage: &[Fp; N_CURRENCIES + 1]) -> Node where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { Node { hash: Self::poseidon_hash_leaf(preimage[0], preimage[1..].try_into().unwrap()), @@ -63,50 +63,50 @@ impl Node { } /// Builds a middle-level node of the MST - /// The hash preimage must be equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_ASSETS - 1] + RightChild.balance[N_ASSETS - 1], LeftChild.hash, RightChild.hash` - /// The balances are equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_ASSETS - 1] + RightChild.balance[N_ASSETS - 1]` - pub fn middle_node_from_preimage(preimage: &[Fp; N_ASSETS + 2]) -> Node + /// The hash preimage must be equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_CURRENCIES - 1] + RightChild.balance[N_CURRENCIES - 1], LeftChild.hash, RightChild.hash` + /// The balances are equal to `LeftChild.balance[0] + RightChild.balance[0], LeftChild.balance[1] + RightChild.balance[1], ..., LeftChild.balance[N_CURRENCIES - 1] + RightChild.balance[N_CURRENCIES - 1]` + pub fn middle_node_from_preimage(preimage: &[Fp; N_CURRENCIES + 2]) -> Node where - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { Node { hash: Self::poseidon_hash_middle( - preimage[0..N_ASSETS].try_into().unwrap(), - preimage[N_ASSETS], - preimage[N_ASSETS + 1], + preimage[0..N_CURRENCIES].try_into().unwrap(), + preimage[N_CURRENCIES], + preimage[N_CURRENCIES + 1], ), - balances: preimage[0..N_ASSETS].try_into().unwrap(), + balances: preimage[0..N_CURRENCIES].try_into().unwrap(), } } fn poseidon_hash_middle( - balances_sum: [Fp; N_ASSETS], + balances_sum: [Fp; N_CURRENCIES], hash_child_left: Fp, hash_child_right: Fp, ) -> Fp where - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - let mut hash_inputs: [Fp; N_ASSETS + 2] = [Fp::zero(); N_ASSETS + 2]; + let mut hash_inputs: [Fp; N_CURRENCIES + 2] = [Fp::zero(); N_CURRENCIES + 2]; - hash_inputs[0..N_ASSETS].copy_from_slice(&balances_sum); - hash_inputs[N_ASSETS] = hash_child_left; - hash_inputs[N_ASSETS + 1] = hash_child_right; + hash_inputs[0..N_CURRENCIES].copy_from_slice(&balances_sum); + hash_inputs[N_CURRENCIES] = hash_child_left; + hash_inputs[N_CURRENCIES + 1] = hash_child_right; - poseidon::Hash::, 2, 1>::init() + poseidon::Hash::, 2, 1>::init() .hash(hash_inputs) } - fn poseidon_hash_leaf(username: Fp, balances: [Fp; N_ASSETS]) -> Fp + fn poseidon_hash_leaf(username: Fp, balances: [Fp; N_CURRENCIES]) -> Fp where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { - let mut hash_inputs: [Fp; N_ASSETS + 1] = [Fp::zero(); N_ASSETS + 1]; + let mut hash_inputs: [Fp; N_CURRENCIES + 1] = [Fp::zero(); N_CURRENCIES + 1]; hash_inputs[0] = username; - hash_inputs[1..N_ASSETS + 1].copy_from_slice(&balances); + hash_inputs[1..N_CURRENCIES + 1].copy_from_slice(&balances); - poseidon::Hash::, 2, 1>::init() + poseidon::Hash::, 2, 1>::init() .hash(hash_inputs) } } diff --git a/zk_prover/src/merkle_sum_tree/tests.rs b/zk_prover/src/merkle_sum_tree/tests.rs index c00b672b..4d1f25bd 100644 --- a/zk_prover/src/merkle_sum_tree/tests.rs +++ b/zk_prover/src/merkle_sum_tree/tests.rs @@ -6,15 +6,14 @@ mod test { use num_bigint::{BigUint, ToBigUint}; use rand::Rng as _; - const N_ASSETS: usize = 2; + const N_CURRENCIES: usize = 2; const N_BYTES: usize = 8; #[test] fn test_mst() { // create new merkle tree let merkle_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); // get root let root = merkle_tree.root(); @@ -33,10 +32,9 @@ 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("../csv/entry_16_switched_order.csv") + .unwrap(); assert_ne!(root.hash, merkle_tree_2.root().hash); // the balance total should be the same @@ -71,16 +69,13 @@ mod test { #[test] fn test_update_mst_leaf() { let merkle_tree_1 = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let root_hash_1 = merkle_tree_1.root().hash; //Create the second tree with the 7th entry different from the the first tree - let mut merkle_tree_2 = MerkleSumTree::::new( - "src/merkle_sum_tree/csv/entry_16_modified.csv", - ) - .unwrap(); + let mut merkle_tree_2 = + MerkleSumTree::::new("../csv/entry_16_modified.csv").unwrap(); let root_hash_2 = merkle_tree_2.root().hash; assert!(root_hash_1 != root_hash_2); @@ -99,8 +94,7 @@ mod test { #[test] fn test_update_invalid_mst_leaf() { let mut merkle_tree = - MerkleSumTree::::new_sorted("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new_sorted("../csv/entry_16.csv").unwrap(); let new_root = merkle_tree.update_leaf( "non_existing_user", //This username is not present in the tree @@ -115,15 +109,13 @@ mod test { #[test] fn test_sorted_mst() { let merkle_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let old_root_balances = merkle_tree.root().balances; let old_root_hash = merkle_tree.root().hash; let sorted_merkle_tree = - MerkleSumTree::::new_sorted("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new_sorted("../csv/entry_16.csv").unwrap(); let new_root_balances = sorted_merkle_tree.root().balances; let new_root_hash = sorted_merkle_tree.root().hash; @@ -137,9 +129,7 @@ mod test { // Passing a csv file with a single entry that has a balance that is not in the expected range will fail #[test] fn test_mst_overflow_1() { - let result = MerkleSumTree::::new( - "src/merkle_sum_tree/csv/entry_16_overflow.csv", - ); + let result = MerkleSumTree::::new("../csv/entry_16_overflow.csv"); if let Err(e) = result { assert_eq!( @@ -152,9 +142,7 @@ mod test { #[test] // Passing a csv file in which the entries have a balance in the range, but while summing it generates a ndoe in which the balance is not in the expected range will fail fn test_mst_overflow_2() { - let result = MerkleSumTree::::new( - "src/merkle_sum_tree/csv/entry_16_overflow_2.csv", - ); + let result = MerkleSumTree::::new("../csv/entry_16_overflow_2.csv"); if let Err(e) = result { assert_eq!( @@ -167,9 +155,7 @@ mod test { // Passing a csv file with a single entry that has a balance that is the maximum that can fit in the expected range will not fail #[test] fn test_mst_no_overflow() { - let result = MerkleSumTree::::new( - "src/merkle_sum_tree/csv/entry_16_no_overflow.csv", - ); + let result = MerkleSumTree::::new("../csv/entry_16_no_overflow.csv"); assert!(result.is_ok()); } @@ -198,8 +184,7 @@ mod test { #[test] fn get_middle_node_hash_preimage() { let merkle_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); let depth = *merkle_tree.depth(); @@ -219,7 +204,7 @@ mod test { .get_middle_node_hash_preimage(level, index) .unwrap(); - let computed_middle_node = Node::::middle_node_from_preimage(&hash_preimage); + let computed_middle_node = Node::::middle_node_from_preimage(&hash_preimage); // The hash of the middle node should match the hash computed from the hash preimage assert_eq!(middle_node.hash, computed_middle_node.hash); @@ -228,8 +213,7 @@ mod test { #[test] fn get_leaf_node_hash_preimage() { let merkle_tree = - MerkleSumTree::::new("src/merkle_sum_tree/csv/entry_16.csv") - .unwrap(); + MerkleSumTree::::new("../csv/entry_16.csv").unwrap(); // Generate a random number between 0 and 15 let mut rng = rand::thread_rng(); @@ -241,7 +225,7 @@ mod test { // Fetch the hash preimage of the leaf let hash_preimage = merkle_tree.get_leaf_node_hash_preimage(index).unwrap(); - let computed_leaf = Node::::leaf_node_from_preimage(&hash_preimage); + let computed_leaf = Node::::leaf_node_from_preimage(&hash_preimage); // The hash of the leaf should match the hash computed from the hash preimage assert_eq!(leaf.hash, computed_leaf.hash); diff --git a/zk_prover/src/merkle_sum_tree/tree.rs b/zk_prover/src/merkle_sum_tree/tree.rs index 7f7fb3ba..d100e67f 100644 --- a/zk_prover/src/merkle_sum_tree/tree.rs +++ b/zk_prover/src/merkle_sum_tree/tree.rs @@ -4,34 +4,34 @@ use crate::merkle_sum_tree::{Entry, MerkleProof, Node}; use halo2_proofs::halo2curves::bn256::Fr as Fp; /// A trait representing the basic operations for a Merkle-Sum-like Tree. -pub trait Tree { +pub trait Tree { /// Returns a reference to the root node. - fn root(&self) -> &Node; + fn root(&self) -> &Node; /// Returns the depth of the tree. fn depth(&self) -> &usize; /// Returns a slice of the leaf nodes. - fn leaves(&self) -> &[Node]; + fn leaves(&self) -> &[Node]; /// Returns a slice of the nodes. - fn nodes(&self) -> &[Vec>]; + fn nodes(&self) -> &[Vec>]; /// Returns the cryptocurrencies whose balances are in the tree. The order of cryptocurrencies and balances is supposed to agree for all the entries. fn cryptocurrencies(&self) -> &[Cryptocurrency]; - fn get_entry(&self, index: usize) -> &Entry; + fn get_entry(&self, index: usize) -> &Entry; - fn entries(&self) -> &[Entry]; + fn entries(&self) -> &[Entry]; /// Returns the hash preimage of a middle node. fn get_middle_node_hash_preimage( &self, level: usize, index: usize, - ) -> Result<[Fp; N_ASSETS + 2], Box> + ) -> Result<[Fp; N_CURRENCIES + 2], Box> where - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { if level == 0 || level > *self.depth() { return Err(Box::from("Invalid depth")); @@ -47,16 +47,16 @@ pub trait Tree { let right_child = &self.nodes()[level - 1][2 * index + 1]; // Constructing preimage - let mut preimage = [Fp::zero(); N_ASSETS + 2]; + let mut preimage = [Fp::zero(); N_CURRENCIES + 2]; // for each balance in the left and right child, add them together and store in preimage - for (i, balance) in preimage.iter_mut().enumerate().take(N_ASSETS) { + for (i, balance) in preimage.iter_mut().enumerate().take(N_CURRENCIES) { *balance = left_child.balances[i] + right_child.balances[i]; } // Add left and right child hashes to preimage - preimage[N_ASSETS] = left_child.hash; - preimage[N_ASSETS + 1] = right_child.hash; + preimage[N_CURRENCIES] = left_child.hash; + preimage[N_CURRENCIES + 1] = right_child.hash; Ok(preimage) } @@ -65,9 +65,9 @@ pub trait Tree { fn get_leaf_node_hash_preimage( &self, index: usize, - ) -> Result<[Fp; N_ASSETS + 1], Box> + ) -> Result<[Fp; N_CURRENCIES + 1], Box> where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { // Fetch entry corresponding to index let entry = self @@ -76,13 +76,13 @@ pub trait Tree { .ok_or_else(|| Box::::from("Node not found"))?; // Constructing preimage - let mut preimage = [Fp::zero(); N_ASSETS + 1]; + let mut preimage = [Fp::zero(); N_CURRENCIES + 1]; // Add username to preimage preimage[0] = big_uint_to_fp(entry.username_as_big_uint()); // Add balances to preimage - for (i, balance) in preimage.iter_mut().enumerate().skip(1).take(N_ASSETS) { + for (i, balance) in preimage.iter_mut().enumerate().skip(1).take(N_CURRENCIES) { *balance = big_uint_to_fp(&entry.balances()[i - 1]); } @@ -93,10 +93,10 @@ pub trait Tree { fn generate_proof( &self, index: usize, - ) -> Result, Box> + ) -> Result, Box> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let nodes = self.nodes(); let depth = *self.depth(); @@ -110,7 +110,7 @@ pub trait Tree { let sibling_leaf_index = if index % 2 == 0 { index + 1 } else { index - 1 }; - let sibling_leaf_node_hash_preimage: [Fp; N_ASSETS + 1] = + let sibling_leaf_node_hash_preimage: [Fp; N_CURRENCIES + 1] = self.get_leaf_node_hash_preimage(sibling_leaf_index)?; let mut path_indices = vec![Fp::zero(); depth]; let mut current_index = index; @@ -142,48 +142,48 @@ pub trait Tree { } /// Verifies a MerkleProof. - fn verify_proof(&self, proof: &MerkleProof) -> bool + fn verify_proof(&self, proof: &MerkleProof) -> bool where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let mut node = proof.entry.compute_leaf(); let sibling_leaf_node = - Node::::leaf_node_from_preimage(&proof.sibling_leaf_node_hash_preimage); + Node::::leaf_node_from_preimage(&proof.sibling_leaf_node_hash_preimage); - let mut hash_preimage = [Fp::zero(); N_ASSETS + 2]; - for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_ASSETS) { + let mut hash_preimage = [Fp::zero(); N_CURRENCIES + 2]; + for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_CURRENCIES) { *balance = node.balances[i] + sibling_leaf_node.balances[i]; } if proof.path_indices[0] == 0.into() { - hash_preimage[N_ASSETS] = node.hash; - hash_preimage[N_ASSETS + 1] = sibling_leaf_node.hash; + hash_preimage[N_CURRENCIES] = node.hash; + hash_preimage[N_CURRENCIES + 1] = sibling_leaf_node.hash; node = Node::middle_node_from_preimage(&hash_preimage); } else { - hash_preimage[N_ASSETS] = sibling_leaf_node.hash; - hash_preimage[N_ASSETS + 1] = node.hash; + hash_preimage[N_CURRENCIES] = sibling_leaf_node.hash; + hash_preimage[N_CURRENCIES + 1] = node.hash; node = Node::middle_node_from_preimage(&hash_preimage); } for i in 1..proof.path_indices.len() { - let sibling_node = Node::::middle_node_from_preimage( + let sibling_node = Node::::middle_node_from_preimage( &proof.sibling_middle_node_hash_preimages[i - 1], ); - let mut hash_preimage = [Fp::zero(); N_ASSETS + 2]; - for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_ASSETS) { + let mut hash_preimage = [Fp::zero(); N_CURRENCIES + 2]; + for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_CURRENCIES) { *balance = node.balances[i] + sibling_node.balances[i]; } if proof.path_indices[i] == 0.into() { - hash_preimage[N_ASSETS] = node.hash; - hash_preimage[N_ASSETS + 1] = sibling_node.hash; + hash_preimage[N_CURRENCIES] = node.hash; + hash_preimage[N_CURRENCIES + 1] = sibling_node.hash; node = Node::middle_node_from_preimage(&hash_preimage); } else { - hash_preimage[N_ASSETS] = sibling_node.hash; - hash_preimage[N_ASSETS + 1] = node.hash; + hash_preimage[N_CURRENCIES] = sibling_node.hash; + hash_preimage[N_CURRENCIES + 1] = node.hash; node = Node::middle_node_from_preimage(&hash_preimage); } } diff --git a/zk_prover/src/merkle_sum_tree/utils/build_tree.rs b/zk_prover/src/merkle_sum_tree/utils/build_tree.rs index 8d63e22f..596fb3ca 100644 --- a/zk_prover/src/merkle_sum_tree/utils/build_tree.rs +++ b/zk_prover/src/merkle_sum_tree/utils/build_tree.rs @@ -2,23 +2,23 @@ use crate::merkle_sum_tree::{Entry, Node}; use halo2_proofs::halo2curves::bn256::Fr as Fp; use rayon::prelude::*; -pub fn build_merkle_tree_from_leaves( - leaves: &[Node], +pub fn build_merkle_tree_from_leaves( + leaves: &[Node], depth: usize, - nodes: &mut Vec>>, -) -> Result, Box> + nodes: &mut Vec>>, +) -> Result, Box> where - [usize; N_ASSETS + 1]: Sized, - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 1]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { let n = leaves.len(); - let mut tree: Vec>> = Vec::with_capacity(depth + 1); + let mut tree: Vec>> = Vec::with_capacity(depth + 1); tree.push(vec![ Node { hash: Fp::from(0), - balances: [Fp::from(0); N_ASSETS] + balances: [Fp::from(0); N_CURRENCIES] }; n ]); @@ -30,7 +30,7 @@ where tree.push(vec![ Node { hash: Fp::from(0), - balances: [Fp::from(0); N_ASSETS] + balances: [Fp::from(0); N_CURRENCIES] }; nodes_in_level ]); @@ -49,11 +49,11 @@ where Ok(root) } -pub fn build_leaves_from_entries( - entries: &[Entry], -) -> Vec> +pub fn build_leaves_from_entries( + entries: &[Entry], +) -> Vec> where - [usize; N_ASSETS + 1]: Sized, + [usize; N_CURRENCIES + 1]: Sized, { let leaves = entries .par_iter() @@ -63,23 +63,23 @@ where leaves } -fn build_middle_level(level: usize, tree: &mut [Vec>]) +fn build_middle_level(level: usize, tree: &mut [Vec>]) where - [usize; N_ASSETS + 2]: Sized, + [usize; N_CURRENCIES + 2]: Sized, { - let results: Vec> = (0..tree[level - 1].len()) + let results: Vec> = (0..tree[level - 1].len()) .into_par_iter() .step_by(2) .map(|index| { - let mut hash_preimage = [Fp::zero(); N_ASSETS + 2]; + let mut hash_preimage = [Fp::zero(); N_CURRENCIES + 2]; - for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_ASSETS) { + for (i, balance) in hash_preimage.iter_mut().enumerate().take(N_CURRENCIES) { *balance = tree[level - 1][index].balances[i] + tree[level - 1][index + 1].balances[i]; } - hash_preimage[N_ASSETS] = tree[level - 1][index].hash; - hash_preimage[N_ASSETS + 1] = tree[level - 1][index + 1].hash; + hash_preimage[N_CURRENCIES] = tree[level - 1][index].hash; + hash_preimage[N_CURRENCIES + 1] = tree[level - 1][index + 1].hash; Node::middle_node_from_preimage(&hash_preimage) }) .collect(); diff --git a/zk_prover/src/merkle_sum_tree/utils/csv_parser.rs b/zk_prover/src/merkle_sum_tree/utils/csv_parser.rs index 9df4100e..fc089eb6 100644 --- a/zk_prover/src/merkle_sum_tree/utils/csv_parser.rs +++ b/zk_prover/src/merkle_sum_tree/utils/csv_parser.rs @@ -5,14 +5,14 @@ use std::error::Error; use std::fs::File; use std::path::Path; -pub fn parse_csv_to_entries, const N_ASSETS: usize, const N_BYTES: usize>( +pub fn parse_csv_to_entries, const N_CURRENCIES: usize, const N_BYTES: usize>( path: P, -) -> Result<(Vec, Vec>), Box> { +) -> Result<(Vec, Vec>), Box> { let file = File::open(path)?; let mut rdr = csv::ReaderBuilder::new().from_reader(file); let headers = rdr.headers()?.clone(); - let mut cryptocurrencies: Vec = Vec::with_capacity(N_ASSETS); + let mut cryptocurrencies: Vec = Vec::with_capacity(N_CURRENCIES); // Extracting cryptocurrency names from column names for header in headers.iter().skip(1) { @@ -30,7 +30,7 @@ pub fn parse_csv_to_entries, const N_ASSETS: usize, const N_BYTES } let mut entries = Vec::new(); - let mut balances_acc: Vec = vec![BigUint::from(0_usize); N_ASSETS]; + let mut balances_acc: Vec = vec![BigUint::from(0_usize); N_CURRENCIES]; for result in rdr.deserialize() { let record: HashMap = result?;