diff --git a/backend/examples/summa_solvency_flow.rs b/backend/examples/summa_solvency_flow.rs index 953a040d..b4e63c8b 100644 --- a/backend/examples/summa_solvency_flow.rs +++ b/backend/examples/summa_solvency_flow.rs @@ -68,7 +68,7 @@ async fn main() -> Result<(), Box> { // // Initialize the `Round` instance to submit the liability commitment. let entry_csv = "../csv/entry_16.csv"; - let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES]; parse_csv_to_entries::<&str, N_CURRENCIES>(entry_csv, &mut entries, &mut cryptos).unwrap(); diff --git a/backend/src/tests.rs b/backend/src/tests.rs index 7e61fe64..30d12364 100644 --- a/backend/src/tests.rs +++ b/backend/src/tests.rs @@ -187,7 +187,7 @@ mod test { .await?; let entry_csv = "../csv/entry_16.csv"; - let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES]; parse_csv_to_entries::<&str, N_CURRENCIES>(entry_csv, &mut entries, &mut cryptos).unwrap(); @@ -299,7 +299,7 @@ mod test { // Initialize Round. let entry_csv = "../csv/entry_16.csv"; - let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES]; parse_csv_to_entries::<&str, N_CURRENCIES>(entry_csv, &mut entries, &mut cryptos).unwrap(); diff --git a/prover/rust-toolchain b/prover/rust-toolchain new file mode 100644 index 00000000..4524b7cb --- /dev/null +++ b/prover/rust-toolchain @@ -0,0 +1 @@ +nightly-2023-07-11 \ No newline at end of file diff --git a/prover/src/circuits/config/circuit_config.rs b/prover/src/circuits/config/circuit_config.rs index 8c2786a6..8357f357 100644 --- a/prover/src/circuits/config/circuit_config.rs +++ b/prover/src/circuits/config/circuit_config.rs @@ -36,7 +36,7 @@ pub trait CircuitConfig: Clone fn synthesize( &self, mut layouter: impl Layouter, - entries: &[Entry], + entries: &[Entry], concatenated_grand_total: &Fp, ) -> Result<(), Error> { // Initiate the range check chips @@ -58,7 +58,11 @@ pub trait CircuitConfig: Clone || "concatenated balance", self.get_concatenated_balance(), 0, - || Value::known(big_uint_to_fp::(&entry.concatenated_balance())), + || { + Value::known(big_uint_to_fp::( + &entry.concatenated_balance().unwrap(), + )) + }, )?; // Decompose the balances diff --git a/prover/src/circuits/summa_circuit.rs b/prover/src/circuits/summa_circuit.rs index ccdc4de2..0d7ca988 100644 --- a/prover/src/circuits/summa_circuit.rs +++ b/prover/src/circuits/summa_circuit.rs @@ -1,19 +1,19 @@ -use std::marker::PhantomData; - use halo2_proofs::{ + arithmetic::Field, circuit::{Layouter, SimpleFloorPlanner}, + halo2curves::{bn256::Fr as Fp, ff::PrimeField}, plonk::{Circuit, ConstraintSystem, Error, Expression}, poly::Rotation, }; - -use crate::{entry::Entry, utils::big_uint_to_fp}; - -use halo2_proofs::arithmetic::Field; -use halo2_proofs::halo2curves::bn256::Fr as Fp; use plonkish_backend::frontend::halo2::CircuitExt; use rand::RngCore; +use std::marker::PhantomData; use super::config::circuit_config::CircuitConfig; +use crate::{ + entry::Entry, + utils::{big_uint_to_fp, calculate_shift_bits}, +}; #[derive(Clone, Default)] pub struct SummaHyperplonk< @@ -21,7 +21,7 @@ pub struct SummaHyperplonk< const N_CURRENCIES: usize, CONFIG: CircuitConfig, > { - pub entries: Vec>, + pub entries: Vec>, pub concatenated_grand_total: Fp, _marker: PhantomData, } @@ -32,11 +32,12 @@ impl< CONFIG: CircuitConfig, > SummaHyperplonk { - pub fn init(user_entries: Vec>) -> Self { + pub fn init(user_entries: Vec>) -> Self { let mut concatenated_grand_total = Fp::ZERO; for entry in user_entries.iter() { - concatenated_grand_total += big_uint_to_fp::(&entry.concatenated_balance()); + concatenated_grand_total += + big_uint_to_fp::(&entry.concatenated_balance().unwrap()); } Self { @@ -49,7 +50,7 @@ impl< /// Initialize the circuit with an invalid grand total /// (for testing purposes only). #[cfg(test)] - pub fn init_invalid_grand_total(user_entries: Vec>) -> Self { + pub fn init_invalid_grand_total(user_entries: Vec>) -> Self { use plonkish_backend::util::test::seeded_std_rng; let concatenated_grand_total = Fp::random(seeded_std_rng()); @@ -103,19 +104,38 @@ impl< // Right-most balance column is for the least significant balance in concatenated balance. let mut balances_expr = meta.query_advice(balances[N_CURRENCIES - 1], Rotation::cur()); - // Base shift value for 84 bits. - let base_shift = Fp::from(1 << 63).mul(&Fp::from(1 << 21)); + let shift_bits = calculate_shift_bits::().unwrap(); - let mut current_shift = Expression::Constant(base_shift); + // The shift bits would not be exceed 93 bits + let base_shift = Fp::from_u128(1u128 << shift_bits); - for i in (0..N_CURRENCIES - 1).rev() { - let balance = meta.query_advice(balances[i], Rotation::cur()); - let shifted_balance = balance * current_shift.clone(); - balances_expr = balances_expr + shifted_balance; + let mut current_shift = Expression::Constant(base_shift); - if i != 0 { - current_shift = current_shift * Expression::Constant(base_shift); + // The number of currencies is limited to 3 because the range check bits are 64 for each currency. + // In other words, more than 3 currencies would exceed the maximum bit count of 254, which is number of bits in Bn254. + match N_CURRENCIES { + 1 => { + // No need to add any shift for the only balance + } + 2 => { + let balance = meta.query_advice(balances[0], Rotation::cur()); + let shifted_balance = balance * current_shift.clone(); + balances_expr = balances_expr + shifted_balance; + } + 3 => { + for i in (0..N_CURRENCIES - 1).rev() { + let balance = meta.query_advice(balances[i], Rotation::cur()); + let shifted_balance = balance * current_shift.clone(); + balances_expr = balances_expr + shifted_balance; + + if i != 0 { + current_shift = current_shift * Expression::Constant(base_shift); + } + } } + _ => panic!( + "Unsupported number of currencies, Only 1, 2, and 3 currencies are supported" + ), } // Ensure that the whole expression equals to the concatenated_balance diff --git a/prover/src/circuits/tests.rs b/prover/src/circuits/tests.rs index d40e65ac..c9fbd96d 100644 --- a/prover/src/circuits/tests.rs +++ b/prover/src/circuits/tests.rs @@ -165,7 +165,7 @@ fn test_summa_hyperplonk_e2e() { ); assert_eq!( fp_to_big_uint(&witness_polys[1].evaluate_as_univariate(&random_user_index)), - entries[random_user_index].concatenated_balance() + entries[random_user_index].concatenated_balance().unwrap() ); // Convert challenge into a multivariate form @@ -239,7 +239,7 @@ fn test_summa_hyperplonk_e2e() { Evaluation::new( 1, 0, - big_uint_to_fp::(&entries[random_user_index].concatenated_balance()), + big_uint_to_fp::(&entries[random_user_index].concatenated_balance().unwrap()), ), ]; diff --git a/prover/src/entry.rs b/prover/src/entry.rs index 37d4dbd4..e891f566 100644 --- a/prover/src/entry.rs +++ b/prover/src/entry.rs @@ -1,17 +1,17 @@ use num_bigint::BigUint; -use crate::utils::big_intify_username; +use crate::utils::{big_intify_username, calculate_shift_bits}; /// 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_CURRENCIES], username: String, } -impl Entry { +impl Entry { pub fn new(username: String, balances: [BigUint; N_CURRENCIES]) -> Result { Ok(Entry { username_as_big_uint: big_intify_username(&username), @@ -34,16 +34,17 @@ impl Entry { &self.balances } - pub fn concatenated_balance(&self) -> BigUint { + pub fn concatenated_balance(&self) -> Result { + let shift_bits = calculate_shift_bits::().unwrap(); + let mut concatenated_balance = BigUint::from(0u32); - // To arragne the balances in the correct order, we need to reverse the array + // Reverse the array to correctly order the balances for (i, balance) in self.balances.iter().rev().enumerate() { - // Shift bits: 1 for buffer; 19 is highest bit for two power of userbase; 64 is for the maximum range check limit - concatenated_balance += balance << ((1 + 19 + 64) * i); + concatenated_balance += balance << (shift_bits * i); } - concatenated_balance + Ok(concatenated_balance) } pub fn username_as_big_uint(&self) -> &BigUint { diff --git a/prover/src/utils/dummy_entries.rs b/prover/src/utils/dummy_entries.rs index 85e5b099..fb4dd60f 100644 --- a/prover/src/utils/dummy_entries.rs +++ b/prover/src/utils/dummy_entries.rs @@ -7,13 +7,13 @@ use crate::entry::Entry; // This is for testing purposes with a large dataset instead of using a CSV file pub fn generate_dummy_entries( -) -> Result>, Box> { +) -> Result>, Box> { // Ensure N_CURRENCIES is greater than 0. if N_CURRENCIES == 0 { return Err("N_CURRENCIES must be greater than 0".into()); } - let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; entries.par_iter_mut().for_each(|entry| { let mut rng = rand::thread_rng(); @@ -31,9 +31,6 @@ pub fn generate_dummy_entries( #[cfg(test)] mod tests { - use crate::utils::big_uint_to_fp; - use halo2_proofs::halo2curves::bn256::Fr as Fp; - use super::*; #[test] diff --git a/prover/src/utils/operation_helpers.rs b/prover/src/utils/operation_helpers.rs index 7e2f8f9e..0d16d6b3 100644 --- a/prover/src/utils/operation_helpers.rs +++ b/prover/src/utils/operation_helpers.rs @@ -59,3 +59,87 @@ pub fn uni_to_multivar_binary_index(x: &usize, num_vars: result } + +pub fn calculate_shift_bits( +) -> Result { + // Define the maximum number of bits that can be used, based on the modulus in bn254. + const MAX_ALLOWANCE_BITS: usize = 253; + + // Calculate the maximum number of bits that can be allocated to the user base, + // taking into account the number of currencies and the bits needed for the balances range check and buffer. + let maximum_allowance_user_base_bits = (MAX_ALLOWANCE_BITS / N_CURRENCIES) - 64 - 1; + + // Determine the number of bits needed to represent the user base. + // For example, if `N_USERS` is 1025, the user base bit count would be 11. + let user_base_bits = N_USERS.next_power_of_two().ilog2() as usize; + + if user_base_bits > maximum_allowance_user_base_bits { + return Err(format!( + "The bit count for the user base exceeds the maximum limit of {}", + maximum_allowance_user_base_bits + )); + } + + // Define shift bits: 1 for buffer, bits for user base that not exceed 19, and 64 bits for the balances range check + let shift_bits: usize = (1 + user_base_bits + 64).try_into().unwrap(); + + Ok(shift_bits) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_shift_bits() { + { + // Practical Nnumber of users cases + const N_USERS: usize = 1 << 28; + const N_CURRENCIES: usize = 1; + + let result = calculate_shift_bits::(); + assert_eq!(result.unwrap(), 93); + assert_eq!(93 * N_CURRENCIES < 253, true); + } + { + const N_USERS: usize = 1 << 28; + const N_CURRENCIES: usize = 2; + + let result = calculate_shift_bits::(); + assert_eq!(result.unwrap(), 93); + assert_eq!(93 * N_CURRENCIES < 253, true); + } + { + // Maximum number of user when N_CURRENCIES = 3 + const N_USERS: usize = 1 << 19; + const N_CURRENCIES: usize = 3; + + let result = calculate_shift_bits::(); + assert_eq!(result.unwrap(), 84); + assert_eq!(84 * N_CURRENCIES < 253, true); + } + { + // Error case in N_CURRENCIES = 2 with infeasible N_USERS + const N_USERS: usize = 1 << 63; + const N_CURRENCIES: usize = 2; + + let result = calculate_shift_bits::(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "The bit count for the user base exceeds the maximum limit of 61" + ); + } + { + const N_USERS: usize = 1 << 63; + const N_CURRENCIES: usize = 3; + + let result = calculate_shift_bits::(); + assert!(result.is_err()); + assert_eq!( + result.unwrap_err(), + "The bit count for the user base exceeds the maximum limit of 19" + ); + } + } +}