From 2460efe324bbbf000e97e2c254990bb6321b84cd Mon Sep 17 00:00:00 2001 From: Alex Kuzmin Date: Mon, 13 May 2024 19:27:54 +0800 Subject: [PATCH 1/3] Apply sumcheck to nonzero constraints directly --- .vscode/settings.json | 1 + backend/Cargo.lock | 2 +- prover/Cargo.lock | 2 +- prover/Cargo.toml | 2 +- prover/benches/proof_of_liabilities.rs | 25 +- prover/src/chips/range/range_check.rs | 28 +- prover/src/chips/range/tests.rs | 9 +- prover/src/circuits/config/circuit_config.rs | 119 ++++++ prover/src/circuits/config/mod.rs | 3 + .../circuits/config/no_range_check_config.rs | 75 ++++ .../src/circuits/config/range_check_config.rs | 130 +++++++ prover/src/circuits/mod.rs | 1 + prover/src/circuits/summa_circuit.rs | 355 ++++++------------ prover/src/circuits/tests.rs | 110 +++++- 14 files changed, 576 insertions(+), 286 deletions(-) create mode 100644 prover/src/circuits/config/circuit_config.rs create mode 100644 prover/src/circuits/config/mod.rs create mode 100644 prover/src/circuits/config/no_range_check_config.rs create mode 100644 prover/src/circuits/config/range_check_config.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index dffd4e77..ddd9eed2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,4 +4,5 @@ "editor.formatOnSave": true, "editor.formatOnSaveMode": "file" }, + "cSpell.words": ["hyperplonk", "plonkish", "layouter", "sumcheck"] } diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 687679d1..95318bca 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2427,7 +2427,7 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plonkish_backend" version = "0.1.0" -source = "git+https://github.com/summa-dev/plonkish?branch=summa-changes#50093af18280f4e1efd79ac258ae9c65b9401999" +source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#e37ba53dcc8f8bd6e7add4e479d0e3295ee80661" dependencies = [ "bincode", "bitvec 1.0.1", diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 9ddf20f5..610ff69a 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -989,7 +989,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plonkish_backend" version = "0.1.0" -source = "git+https://github.com/summa-dev/plonkish?branch=summa-changes#50093af18280f4e1efd79ac258ae9c65b9401999" +source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#e37ba53dcc8f8bd6e7add4e479d0e3295ee80661" dependencies = [ "bincode", "bitvec", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index f44bd0d1..a897e375 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -13,7 +13,7 @@ parallel = ["dep:rayon"] frontend-halo2 = ["dep:halo2_proofs"] [dependencies] -plonkish_backend = { git = "https://github.com/summa-dev/plonkish", branch="summa-changes", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } +plonkish_backend = { git = "https://github.com/summa-dev/plonkish", branch="nonzero-constraints", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } plotters = { version = "0.3.4", optional = true } rand = "0.8" csv = "1.1" diff --git a/prover/benches/proof_of_liabilities.rs b/prover/benches/proof_of_liabilities.rs index 5e866eaf..0ac4a505 100644 --- a/prover/benches/proof_of_liabilities.rs +++ b/prover/benches/proof_of_liabilities.rs @@ -14,7 +14,7 @@ use rand::{ CryptoRng, Rng, RngCore, SeedableRng, }; use summa_hyperplonk::{ - circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, + circuits::{config::range_check_config::RangeCheckConfig, summa_circuit::SummaHyperplonk}, utils::{big_uint_to_fp, generate_dummy_entries, uni_to_multivar_binary_index}, }; @@ -30,12 +30,15 @@ fn bench_summa() type ProvingBackend = HyperPlonk>; let entries = generate_dummy_entries::().unwrap(); - let halo2_circuit = SummaHyperplonk::::init(entries.to_vec()); + let halo2_circuit = + SummaHyperplonk::>::init( + entries.to_vec(), + ); - let circuit = Halo2Circuit::>::new::( - K as usize, - halo2_circuit.clone(), - ); + let circuit = Halo2Circuit::< + Fp, + SummaHyperplonk>, + >::new::(K as usize, halo2_circuit.clone()); let circuit_info: PlonkishCircuitInfo<_> = circuit.circuit_info().unwrap(); let instances = circuit.instances(); @@ -46,10 +49,10 @@ fn bench_summa() c.bench_function(&grand_sum_proof_bench_name, |b| { b.iter_batched( || { - Halo2Circuit::>::new::( - K as usize, - halo2_circuit.clone(), - ) + Halo2Circuit::< + Fp, + SummaHyperplonk>, + >::new::(K as usize, halo2_circuit.clone()) }, |circuit| { let mut transcript = Keccak256Transcript::default(); @@ -189,7 +192,7 @@ fn bench_summa() } fn criterion_benchmark(_c: &mut Criterion) { - const N_CURRENCIES: usize = 1; + const N_CURRENCIES: usize = 100; { const K: u32 = 17; diff --git a/prover/src/chips/range/range_check.rs b/prover/src/chips/range/range_check.rs index 26598aa4..76c84fcb 100644 --- a/prover/src/chips/range/range_check.rs +++ b/prover/src/chips/range/range_check.rs @@ -2,7 +2,7 @@ use crate::chips::range::utils::decompose_fp_to_byte_pairs; use halo2_proofs::arithmetic::Field; use halo2_proofs::circuit::{AssignedCell, Region, Value}; use halo2_proofs::halo2curves::bn256::Fr as Fp; -use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed}; +use halo2_proofs::plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Selector}; use halo2_proofs::poly::Rotation; use std::fmt::Debug; @@ -27,8 +27,7 @@ use std::fmt::Debug; /// # Fields /// /// * `zs`: Four advice columns - contain the truncated right-shifted values of the element to be checked -/// * `z0`: An advice column - for storing the zero value from the instance column -/// * `instance`: An instance column - zero value provided to the circuit +/// * `selector`: Selector used to enable the range check /// /// # Assumptions /// @@ -36,8 +35,9 @@ use std::fmt::Debug; /// /// Patterned after [halo2_gadgets](https://github.com/privacy-scaling-explorations/halo2/blob/main/halo2_gadgets/src/utilities/decompose_running_sum.rs) #[derive(Debug, Copy, Clone)] -pub struct RangeCheckU64Config { +pub struct RangeCheckChipConfig { zs: [Column; 4], + selector: Selector, } /// Helper chip that verifies that the element witnessed in a given cell lies within the u64 range. @@ -72,22 +72,23 @@ pub struct RangeCheckU64Config { /// zs[3] == z0 #[derive(Debug, Clone)] pub struct RangeCheckU64Chip { - config: RangeCheckU64Config, + config: RangeCheckChipConfig, } impl RangeCheckU64Chip { - pub fn construct(config: RangeCheckU64Config) -> Self { + pub fn construct(config: RangeCheckChipConfig) -> Self { Self { config } } /// Configures the Range Chip - /// Note: the lookup table should be loaded with values from `0` to `2^16 - 1` otherwise the range check will fail. + /// Note: the lookup table should be loaded with values from `0` to `2^16 - 1`, otherwise the range check will fail. pub fn configure( meta: &mut ConstraintSystem, z: Column, zs: [Column; 4], range_u16: Column, - ) -> RangeCheckU64Config { + range_check_enabled: Selector, + ) -> RangeCheckChipConfig { // Constraint that the difference between the element to be checked and the 0-th truncated right-shifted value of the element to be within the range. // z - 2^16⋅zs[0] = ks[0] ∈ range_u16 meta.lookup_any( @@ -99,7 +100,9 @@ impl RangeCheckU64Chip { let range_u16 = meta.query_fixed(range_u16, Rotation::cur()); - let diff = element - zero_truncation * Expression::Constant(Fp::from(1 << 16)); + let s = meta.query_selector(range_check_enabled); + + let diff = s * (element - zero_truncation * Expression::Constant(Fp::from(1 << 16))); vec![(diff, range_u16)] }, @@ -123,7 +126,10 @@ impl RangeCheckU64Chip { ); } - RangeCheckU64Config { zs } + RangeCheckChipConfig { + zs, + selector: range_check_enabled, + } } /// Assign the truncated right-shifted values of the element to be checked to the corresponding columns zs at offset 0 starting from the element to be checked. @@ -163,6 +169,8 @@ impl RangeCheckU64Chip { zs.push(z.clone()); } + self.config.selector.enable(region, 0)?; + Ok(()) } } diff --git a/prover/src/chips/range/tests.rs b/prover/src/chips/range/tests.rs index f9425443..3647c9d6 100644 --- a/prover/src/chips/range/tests.rs +++ b/prover/src/chips/range/tests.rs @@ -1,4 +1,4 @@ -use crate::chips::range::range_check::{RangeCheckU64Chip, RangeCheckU64Config}; +use crate::chips::range::range_check::{RangeCheckChipConfig, RangeCheckU64Chip}; use halo2_proofs::{ circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}, halo2curves::bn256::Fr as Fp, @@ -87,7 +87,7 @@ impl AddChip { #[derive(Debug, Clone)] pub struct TestConfig { pub addchip_config: AddConfig, - pub range_check_config: RangeCheckU64Config, + pub range_check_config: RangeCheckChipConfig, pub range_u16: Column, pub instance: Column, } @@ -134,7 +134,10 @@ impl Circuit for TestCircuit { let instance = meta.instance_column(); meta.enable_equality(instance); - let range_check_config = RangeCheckU64Chip::configure(meta, c, zs, range_u16); + let range_check_selector = meta.complex_selector(); + + let range_check_config = + RangeCheckU64Chip::configure(meta, c, zs, range_u16, range_check_selector); let addchip_config = AddChip::configure(meta, a, b, c, add_selector); diff --git a/prover/src/circuits/config/circuit_config.rs b/prover/src/circuits/config/circuit_config.rs new file mode 100644 index 00000000..51382773 --- /dev/null +++ b/prover/src/circuits/config/circuit_config.rs @@ -0,0 +1,119 @@ +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Instance}, +}; + +use crate::{entry::Entry, utils::big_uint_to_fp}; + +use crate::chips::range::range_check::RangeCheckU64Chip; +use halo2_proofs::halo2curves::bn256::Fr as Fp; + +/// The abstract configuration of the circuit. +/// The default implementation assigns the entries and grand total to the circuit, and constrains +/// grand total to the instance values. +/// +/// The specific implementations have to provide the range check logic. +pub trait CircuitConfig: Clone { + /// Configures the circuit + fn configure( + meta: &mut ConstraintSystem, + username: Column, + balances: [Column; N_CURRENCIES], + instance: Column, + ) -> Self; + + fn get_username(&self) -> Column; + + fn get_balances(&self) -> [Column; N_CURRENCIES]; + + fn get_instance(&self) -> Column; + + /// Assigns the entries to the circuit, constrains the grand total to the instance values. + fn synthesize( + &self, + mut layouter: impl Layouter, + entries: &[Entry], + grand_total: &[Fp], + ) -> Result<(), Error> { + // Initiate the range check chips + let range_check_chips = self.initialize_range_check_chips(); + + for (i, entry) in entries.iter().enumerate() { + let last_decompositions = layouter.assign_region( + || format!("assign entry {} to the table", i), + |mut region| { + region.assign_advice( + || "username", + self.get_username(), + 0, + || Value::known(big_uint_to_fp::(entry.username_as_big_uint())), + )?; + + let mut last_decompositions = vec![]; + + for (j, balance) in entry.balances().iter().enumerate() { + let assigned_balance = region.assign_advice( + || format!("balance {}", j), + self.get_balances()[j], + 0, + || Value::known(big_uint_to_fp(balance)), + )?; + + let mut zs = Vec::with_capacity(4); + + if !range_check_chips.is_empty() { + range_check_chips[j].assign(&mut region, &mut zs, &assigned_balance)?; + + last_decompositions.push(zs[3].clone()); + } + } + + Ok(last_decompositions) + }, + )?; + + self.constrain_decompositions(last_decompositions, &mut layouter)?; + } + + let assigned_total = layouter.assign_region( + || "assign total".to_string(), + |mut region| { + let mut assigned_total = vec![]; + + for (j, total) in grand_total.iter().enumerate() { + let balance_total = region.assign_advice( + || format!("total {}", j), + self.get_balances()[j], + 0, + || Value::known(total.neg()), + )?; + + assigned_total.push(balance_total); + } + + Ok(assigned_total) + }, + )?; + + for (j, total) in assigned_total.iter().enumerate() { + layouter.constrain_instance(total.cell(), self.get_instance(), 1 + j)?; + } + + self.load_lookup_table(layouter)?; + + Ok(()) + } + + /// Initializes the range check chips + fn initialize_range_check_chips(&self) -> Vec; + + /// Loads the lookup table + fn load_lookup_table(&self, layouter: impl Layouter) -> Result<(), Error>; + + /// Constrains the last decompositions of the balances to be zero + fn constrain_decompositions( + &self, + last_decompositions: Vec>, + layouter: &mut impl Layouter, + ) -> Result<(), Error>; +} diff --git a/prover/src/circuits/config/mod.rs b/prover/src/circuits/config/mod.rs new file mode 100644 index 00000000..361692f8 --- /dev/null +++ b/prover/src/circuits/config/mod.rs @@ -0,0 +1,3 @@ +pub mod circuit_config; +pub mod no_range_check_config; +pub mod range_check_config; diff --git a/prover/src/circuits/config/no_range_check_config.rs b/prover/src/circuits/config/no_range_check_config.rs new file mode 100644 index 00000000..5d6d4ada --- /dev/null +++ b/prover/src/circuits/config/no_range_check_config.rs @@ -0,0 +1,75 @@ +use halo2_proofs::{ + circuit::Layouter, + plonk::{Advice, Column, ConstraintSystem, Error, Instance}, +}; + +use crate::chips::range::range_check::RangeCheckU64Chip; +use halo2_proofs::halo2curves::bn256::Fr as Fp; + +use super::circuit_config::CircuitConfig; + +/// Configuration that does not perform range checks. Warning: not for use in production! +/// The circuit without range checks can use a lower K value (9+) than the full circuit (convenient for prototyping and testing). +/// +/// # Type Parameters +/// +/// * `N_CURRENCIES`: The number of currencies for which the solvency is verified. +/// * `N_USERS`: The number of users for which the solvency is verified. +/// +/// # Fields +/// +/// * `username`: Advice column used to store the usernames of the users +/// * `balances`: Advice columns used to store the balances of the users +#[derive(Clone)] +pub struct NoRangeCheckConfig { + username: Column, + balances: [Column; N_CURRENCIES], + instance: Column, +} + +impl CircuitConfig + for NoRangeCheckConfig +{ + fn configure( + _: &mut ConstraintSystem, + username: Column, + balances: [Column; N_CURRENCIES], + instance: Column, + ) -> NoRangeCheckConfig { + Self { + username, + balances, + instance, + } + } + + fn get_username(&self) -> Column { + self.username + } + + fn get_balances(&self) -> [Column; N_CURRENCIES] { + self.balances + } + + fn get_instance(&self) -> Column { + self.instance + } + + // The following methods are not implemented for NoRangeCheckConfig + + fn initialize_range_check_chips(&self) -> Vec { + vec![] + } + + fn load_lookup_table(&self, _: impl Layouter) -> Result<(), Error> { + Ok(()) + } + + fn constrain_decompositions( + &self, + _: Vec>, + _: &mut impl Layouter, + ) -> Result<(), Error> { + Ok(()) + } +} diff --git a/prover/src/circuits/config/range_check_config.rs b/prover/src/circuits/config/range_check_config.rs new file mode 100644 index 00000000..7a7c0e8a --- /dev/null +++ b/prover/src/circuits/config/range_check_config.rs @@ -0,0 +1,130 @@ +use halo2_proofs::{ + circuit::{Layouter, Value}, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Instance}, +}; + +use crate::chips::range::range_check::{RangeCheckChipConfig, RangeCheckU64Chip}; +use halo2_proofs::halo2curves::bn256::Fr as Fp; + +use super::circuit_config::CircuitConfig; + +/// Configuration that performs range checks. +/// +/// # Type Parameters +/// +/// * `N_CURRENCIES`: The number of currencies for which the solvency is verified. +/// * `N_USERS`: The number of users for which the solvency is verified. +/// +/// # Fields +/// +/// * `username`: Advice column used to store the usernames of the users +/// * `balances`: Advice columns used to store the balances of the users +/// * `range_check_configs`: Range check chip configurations +/// * `range_u16`: Fixed column used to store the lookup table +/// * `instance`: Instance column used to constrain the last balance decomposition +#[derive(Clone)] +pub struct RangeCheckConfig { + username: Column, + balances: [Column; N_CURRENCIES], + range_check_configs: [RangeCheckChipConfig; N_CURRENCIES], + range_u16: Column, + instance: Column, +} + +impl CircuitConfig + for RangeCheckConfig +{ + fn configure( + meta: &mut ConstraintSystem, + username: Column, + balances: [Column; N_CURRENCIES], + instance: Column, + ) -> Self { + let range_u16 = meta.fixed_column(); + + meta.enable_constant(range_u16); + + meta.annotate_lookup_any_column(range_u16, || "LOOKUP_MAXBITS_RANGE"); + + let range_check_selector = meta.complex_selector(); + + // Create an empty array of range check configs + let mut range_check_configs = Vec::with_capacity(N_CURRENCIES); + + for balance_column in balances.iter() { + let z = *balance_column; + // Create 4 advice columns for each range check chip + let zs = [(); 4].map(|_| meta.advice_column()); + + for column in &zs { + meta.enable_equality(*column); + } + + let range_check_config = + RangeCheckU64Chip::configure(meta, z, zs, range_u16, range_check_selector); + + range_check_configs.push(range_check_config); + } + + Self { + username, + balances, + range_check_configs: range_check_configs.try_into().unwrap(), + range_u16, + instance, + } + } + + fn get_username(&self) -> Column { + self.username + } + + fn get_balances(&self) -> [Column; N_CURRENCIES] { + self.balances + } + + fn get_instance(&self) -> Column { + self.instance + } + + fn initialize_range_check_chips(&self) -> Vec { + self.range_check_configs + .iter() + .map(|config| RangeCheckU64Chip::construct(*config)) + .collect::>() + } + + fn load_lookup_table(&self, mut layouter: impl Layouter) -> Result<(), Error> { + // Load lookup table for range check u64 chip + let range = 1 << 16; + + layouter.assign_region( + || "load range check table of 16 bits".to_string(), + |mut region| { + for i in 0..range { + region.assign_fixed( + || "assign cell in fixed column", + self.range_u16, + i, + || Value::known(Fp::from(i as u64)), + )?; + } + Ok(()) + }, + )?; + + Ok(()) + } + + /// Constrains the last decompositions of each balance to a zero value (necessary for range checks) + fn constrain_decompositions( + &self, + last_decompositions: Vec>, + layouter: &mut impl Layouter, + ) -> Result<(), Error> { + for last_decomposition in last_decompositions { + layouter.constrain_instance(last_decomposition.cell(), self.instance, 0)?; + } + Ok(()) + } +} diff --git a/prover/src/circuits/mod.rs b/prover/src/circuits/mod.rs index 1f44e02c..283abc4a 100644 --- a/prover/src/circuits/mod.rs +++ b/prover/src/circuits/mod.rs @@ -1,3 +1,4 @@ +pub mod config; pub mod summa_circuit; #[cfg(test)] mod tests; diff --git a/prover/src/circuits/summa_circuit.rs b/prover/src/circuits/summa_circuit.rs index ea6e8bdb..e8762d45 100644 --- a/prover/src/circuits/summa_circuit.rs +++ b/prover/src/circuits/summa_circuit.rs @@ -1,274 +1,129 @@ -pub mod summa_hyperplonk { - - use crate::chips::range::range_check::{RangeCheckU64Chip, RangeCheckU64Config}; - use crate::entry::Entry; - use crate::utils::big_uint_to_fp; - use halo2_proofs::arithmetic::Field; - use halo2_proofs::halo2curves::bn256::Fr as Fp; - use halo2_proofs::plonk::{Expression, Selector}; - use halo2_proofs::poly::Rotation; - use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner, Value}, - plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance}, - }; - use num_bigint::BigUint; - use plonkish_backend::frontend::halo2::CircuitExt; - use rand::RngCore; - - #[derive(Clone)] - pub struct SummaConfig { - username: Column, - balances: [Column; N_CURRENCIES], - running_sums: [Column; N_CURRENCIES], - range_check_configs: [RangeCheckU64Config; N_CURRENCIES], - range_u16: Column, - instance: Column, - selector: Selector, - } - - impl SummaConfig { - fn configure(meta: &mut ConstraintSystem, running_sum_selector: &Selector) -> Self { - let username = meta.advice_column(); - - let balances = [(); N_CURRENCIES].map(|_| meta.advice_column()); - let running_sums = [(); N_CURRENCIES].map(|_| meta.advice_column()); - - for column in &running_sums { - meta.enable_equality(*column); - } - - let range_u16 = meta.fixed_column(); - - meta.enable_constant(range_u16); - - meta.annotate_lookup_any_column(range_u16, || "LOOKUP_MAXBITS_RANGE"); - - // Create an empty array of range check configs - let mut range_check_configs = Vec::with_capacity(N_CURRENCIES); - - let instance = meta.instance_column(); - meta.enable_equality(instance); - - for item in balances.iter().take(N_CURRENCIES) { - let z = *item; - // Create 4 advice columns for each range check chip - let zs = [(); 4].map(|_| meta.advice_column()); - - for column in &zs { - meta.enable_equality(*column); - } - - let range_check_config = RangeCheckU64Chip::configure(meta, z, zs, range_u16); - - range_check_configs.push(range_check_config); - } - - meta.create_gate("Running sum gate", |meta| { - let mut running_sum_constraint = vec![]; - let s = meta.query_selector(*running_sum_selector); - for j in 0..N_CURRENCIES { - let prev_running_sum = meta.query_advice(running_sums[j], Rotation::prev()); - let curr_running_sum = meta.query_advice(running_sums[j], Rotation::cur()); - let curr_balance = meta.query_advice(balances[j], Rotation::cur()); - running_sum_constraint.push( - s.clone() - * (curr_running_sum.clone() - prev_running_sum - curr_balance.clone()) - + (Expression::Constant(Fp::ONE) - s.clone()) - * (curr_running_sum - curr_balance), - ) - } - running_sum_constraint - }); +use std::marker::PhantomData; + +use halo2_proofs::{ + circuit::{Layouter, SimpleFloorPlanner}, + plonk::{Circuit, ConstraintSystem, Error}, + 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 super::config::circuit_config::CircuitConfig; + +#[derive(Clone, Default)] +pub struct SummaHyperplonk< + const N_USERS: usize, + const N_CURRENCIES: usize, + CONFIG: CircuitConfig, +> { + pub entries: Vec>, + pub grand_total: Vec, + _marker: PhantomData, +} - Self { - username, - balances, - running_sums, - range_check_configs: range_check_configs.try_into().unwrap(), - range_u16, - instance, - selector: *running_sum_selector, +impl< + const N_USERS: usize, + const N_CURRENCIES: usize, + CONFIG: CircuitConfig, + > SummaHyperplonk +{ + pub fn init(user_entries: Vec>) -> Self { + let mut grand_total = vec![Fp::ZERO; N_CURRENCIES]; + for entry in user_entries.iter() { + for (i, balance) in entry.balances().iter().enumerate() { + grand_total[i] += big_uint_to_fp::(balance); } } - } - #[derive(Clone, Default)] - pub struct SummaHyperplonk { - pub entries: Vec>, - pub grand_total: Vec, - } - - impl SummaHyperplonk { - pub fn init(user_entries: Vec>) -> Self { - let mut grand_total = vec![BigUint::from(0u64); N_CURRENCIES]; - for entry in user_entries.iter() { - for (i, balance) in entry.balances().iter().enumerate() { - grand_total[i] += balance; - } - } - - Self { - entries: user_entries, - grand_total, - } + Self { + entries: user_entries, + grand_total, + _marker: PhantomData, } } - impl Circuit - for SummaHyperplonk - { - type Config = SummaConfig; - type FloorPlanner = SimpleFloorPlanner; + /// Initialize the circuit with an invalid grand total + /// (for testing purposes only). + #[cfg(test)] + pub fn init_invalid_grand_total(user_entries: Vec>) -> Self { + use plonkish_backend::util::test::seeded_std_rng; - fn without_witnesses(&self) -> Self { - unimplemented!() + let mut grand_total = vec![Fp::ZERO; N_CURRENCIES]; + for i in 0..N_CURRENCIES { + grand_total[i] = Fp::random(seeded_std_rng()); } - fn configure(meta: &mut ConstraintSystem) -> Self::Config { - meta.set_minimum_degree(4); - let running_sum_selector = &meta.complex_selector(); - SummaConfig::configure(meta, running_sum_selector) + Self { + entries: user_entries, + grand_total, + _marker: PhantomData, } + } +} - fn synthesize( - &self, - config: Self::Config, - mut layouter: impl Layouter, - ) -> Result<(), Error> { - // Assign entries - let (assigned_balances, last_running_sums) = layouter - .assign_region( - || "assign user entries", - |mut region| { - // create a bidimensional vector to store the assigned balances. The first dimension is N_USERS, the second dimension is N_CURRENCIES - let mut assigned_balances = vec![]; - - let mut running_sum_values = vec![vec![]]; - let mut last_assigned_running_sums = vec![]; - - for i in 0..N_USERS { - running_sum_values.push(vec![]); - - region.assign_advice( - || format!("username {}", i), - config.username, - i, - || { - Value::known(big_uint_to_fp::( - self.entries[i].username_as_big_uint(), - )) - }, - )?; - - let mut assigned_balances_row = vec![]; - - for (j, balance) in self.entries[i].balances().iter().enumerate() { - let balance_value: Value = - Value::known(big_uint_to_fp(balance)); - - let assigned_balance = region.assign_advice( - || format!("balance {}", j), - config.balances[j], - i, - || balance_value, - )?; - - assigned_balances_row.push(assigned_balance); - - let prev_running_sum_value = if i == 0 { - Value::known(Fp::ZERO) - } else { - running_sum_values[i - 1][j] - }; - - running_sum_values[i].push(prev_running_sum_value + balance_value); - - let assigned_running_sum = region.assign_advice( - || format!("running sum {}", j), - config.running_sums[j], - i, - || running_sum_values[i][j], - )?; - - if i == N_USERS - 1 { - last_assigned_running_sums.push(assigned_running_sum); - } - } - - if i > 0 { - config.selector.enable(&mut region, i)?; - } - - assigned_balances.push(assigned_balances_row); - } - - Ok((assigned_balances, last_assigned_running_sums)) - }, - ) - .unwrap(); - - // Initialize the range check chips - let range_check_chips = config - .range_check_configs - .iter() - .map(|config| RangeCheckU64Chip::construct(*config)) - .collect::>(); - - // Load lookup table for range check u64 chip - let range = 1 << 16; +impl< + const N_USERS: usize, + const N_CURRENCIES: usize, + CONFIG: CircuitConfig, + > Circuit for SummaHyperplonk +{ + type Config = CONFIG; + type FloorPlanner = SimpleFloorPlanner; + + fn without_witnesses(&self) -> Self { + unimplemented!() + } - layouter.assign_region( - || "load range check table of 16 bits".to_string(), - |mut region| { - for i in 0..range { - region.assign_fixed( - || "assign cell in fixed column", - config.range_u16, - i, - || Value::known(Fp::from(i as u64)), - )?; - } - Ok(()) - }, - )?; + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + meta.set_minimum_degree(4); - // Perform range check on the assigned balances - for (i, user_balances) in assigned_balances.iter().enumerate().take(N_USERS) { - for (j, balance) in user_balances.iter().enumerate() { - let mut zs = Vec::with_capacity(4); + let username = meta.advice_column(); - layouter.assign_region( - || format!("Perform range check on balance {} of user {}", j, i), - |mut region| { - range_check_chips[j].assign(&mut region, &mut zs, balance)?; - Ok(()) - }, - )?; + let balances = [(); N_CURRENCIES].map(|_| meta.advice_column()); + for column in &balances { + meta.enable_equality(*column); + } - layouter.constrain_instance(zs[3].cell(), config.instance, 0)?; - } + meta.create_gate("Balance sumcheck gate", |meta| { + let mut nonzero_constraint = vec![]; + for balance in balances { + let current_balance = meta.query_advice(balance, Rotation::cur()); + nonzero_constraint.push(current_balance.clone()); } + nonzero_constraint + }); - for (i, last_running_sum) in last_running_sums.iter().enumerate().take(N_CURRENCIES) { - layouter.constrain_instance(last_running_sum.cell(), config.instance, 1 + i)?; - } + let instance = meta.instance_column(); + meta.enable_equality(instance); - Ok(()) - } + CONFIG::configure(meta, username, balances, instance) } - impl CircuitExt - for SummaHyperplonk - { - fn rand(_: usize, _: impl RngCore) -> Self { - unimplemented!() - } + fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> { + CONFIG::synthesize(&config, layouter, &self.entries, &self.grand_total) + } +} - fn instances(&self) -> Vec> { - // The last decomposition of each range check chip should be zero - let mut instances = vec![Fp::ZERO]; - instances.extend(self.grand_total.iter().map(big_uint_to_fp::)); - vec![instances] - } +impl< + const N_USERS: usize, + const N_CURRENCIES: usize, + CONFIG: CircuitConfig, + > CircuitExt for SummaHyperplonk +{ + fn rand(_: usize, _: impl RngCore) -> Self { + unimplemented!() + } + + fn instances(&self) -> Vec> { + // The 1st element is zero because the last decomposition of each range check chip should be zero + vec![vec![Fp::ZERO] + .into_iter() + .chain(self.grand_total.iter().map(|x| x.neg())) + .collect::>()] } } diff --git a/prover/src/circuits/tests.rs b/prover/src/circuits/tests.rs index 58c518e5..88bbb4af 100644 --- a/prover/src/circuits/tests.rs +++ b/prover/src/circuits/tests.rs @@ -1,7 +1,8 @@ use halo2_proofs::arithmetic::Field; +use plonkish_backend::Error::InvalidSnark; use plonkish_backend::{ backend::{hyperplonk::HyperPlonk, PlonkishBackend, PlonkishCircuit}, - frontend::halo2::Halo2Circuit, + frontend::halo2::{CircuitExt, Halo2Circuit}, halo2_curves::bn256::{Bn256, Fr as Fp}, pcs::{multilinear::MultilinearKzg, Evaluation, PolynomialCommitmentScheme}, util::{ @@ -12,14 +13,16 @@ use plonkish_backend::{ }, Error::InvalidSumcheck, }; - use rand::{ rngs::{OsRng, StdRng}, CryptoRng, Rng, RngCore, SeedableRng, }; use crate::{ - circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, + circuits::{ + config::{no_range_check_config::NoRangeCheckConfig, range_check_config::RangeCheckConfig}, + summa_circuit::SummaHyperplonk, + }, utils::{ big_uint_to_fp, fp_to_big_uint, generate_dummy_entries, uni_to_multivar_binary_index, MultilinearAsUnivariate, @@ -27,23 +30,47 @@ use crate::{ }; const K: u32 = 17; const N_CURRENCIES: usize = 2; -const N_USERS: usize = 1 << 16; +// One row is reserved for the grand total. +// TODO find out what occupies one extra row +const N_USERS: usize = (1 << K) - 2; pub fn seeded_std_rng() -> impl RngCore + CryptoRng { StdRng::seed_from_u64(OsRng.next_u64()) } #[test] -fn test_summa_hyperplonk() { +fn test_summa_hyperplonk_e2e() { type ProvingBackend = HyperPlonk>; let entries = generate_dummy_entries::().unwrap(); - let circuit = SummaHyperplonk::::init(entries.to_vec()); + + let halo2_circuit = + SummaHyperplonk::>::init( + entries.to_vec(), + ); + + let neg_grand_total = halo2_circuit + .grand_total + .iter() + .fold(Fp::ZERO, |acc, f| acc + f) + .neg(); + + // We're putting the negated grand total at the end of each balance column, + // so the sumcheck over such balance column would yield zero (given the special gate, + // see the circuit). + assert!( + neg_grand_total + == halo2_circuit.instances()[0] + .iter() + .fold(Fp::ZERO, |acc, instance| { acc + instance }) + ); + let num_vars = K; let circuit_fn = |num_vars| { - let circuit = Halo2Circuit::>::new::< - ProvingBackend, - >(num_vars, circuit.clone()); + let circuit = Halo2Circuit::< + Fp, + SummaHyperplonk>, + >::new::(num_vars, halo2_circuit.clone()); (circuit.circuit_info().unwrap(), circuit) }; @@ -241,6 +268,71 @@ fn test_summa_hyperplonk() { .unwrap(); } +/// Test the sumcheck failure case +/// The grand total is set to a random value, which will cause the sumcheck to fail +/// because the sum of all valid balances is not equal to the negated random grand total +#[test] +fn test_sumcheck_fail() { + type ProvingBackend = HyperPlonk>; + let entries = generate_dummy_entries::().unwrap(); + + let halo2_circuit = SummaHyperplonk::< + N_USERS, + N_CURRENCIES, + NoRangeCheckConfig, + >::init_invalid_grand_total(entries.to_vec()); + + let num_vars = K; + + let circuit_fn = |num_vars| { + let circuit = Halo2Circuit::< + Fp, + SummaHyperplonk>, + >::new::(num_vars, halo2_circuit.clone()); + (circuit.circuit_info().unwrap(), circuit) + }; + + let (circuit_info, circuit) = circuit_fn(num_vars as usize); + let instances = circuit.instances(); + + let param = ProvingBackend::setup(&circuit_info, seeded_std_rng()).unwrap(); + + let (prover_parameters, verifier_parameters) = + ProvingBackend::preprocess(¶m, &circuit_info).unwrap(); + + let (_, proof_transcript) = { + let mut proof_transcript = Keccak256Transcript::new(()); + + let witness_polys = ProvingBackend::prove( + &prover_parameters, + &circuit, + &mut proof_transcript, + seeded_std_rng(), + ) + .unwrap(); + (witness_polys, proof_transcript) + }; + + let proof = proof_transcript.into_proof(); + + let mut transcript; + let result: Result<(), plonkish_backend::Error> = { + transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + ProvingBackend::verify( + &verifier_parameters, + instances, + &mut transcript, + seeded_std_rng(), + ) + }; + assert_eq!( + result, + Err(InvalidSnark( + "Unmatched between sum_check output and query evaluation".to_string() + )) + ); +} + #[cfg(feature = "dev-graph")] #[test] fn print_univariate_grand_sum_circuit() { From b1fea0aceb7c347fc66e68d4d86bf3baf78c53db Mon Sep 17 00:00:00 2001 From: JinHwan Date: Fri, 26 Jul 2024 19:47:07 +0900 Subject: [PATCH 2/3] Added concatenated balance for nonzero constraint process (#294) * Added concatenated balance for nonzero constraint process * fix: order of assigning balance value following the order of concatenated balance * fixing typo * Fix selector and concatenated balance on nonzero-constrain * fix typo * fix: following review comments * fix: added assertions for N_CURRENCIES in the gate * fix: gh workflow --- .github/workflows/benchmark.yml | 47 ---- .github/workflows/contracts.yml | 23 -- .github/workflows/rust.yml | 68 ++---- backend/Cargo.lock | 2 +- backend/examples/summa_solvency_flow.rs | 2 +- backend/src/tests.rs | 4 +- prover/Cargo.lock | 216 ++++++++++-------- prover/Cargo.toml | 2 +- prover/prints/range-check-layout.png | Bin 77731 -> 81281 bytes prover/prints/summa-hyperplonk-layout.png | Bin 1082947 -> 1070080 bytes prover/rust-toolchain | 1 + prover/src/circuits/config/circuit_config.rs | 60 +++-- .../circuits/config/no_range_check_config.rs | 12 +- .../src/circuits/config/range_check_config.rs | 16 +- prover/src/circuits/summa_circuit.rs | 117 +++++++--- prover/src/circuits/tests.rs | 60 ++--- prover/src/entry.rs | 19 +- prover/src/utils/dummy_entries.rs | 6 +- prover/src/utils/operation_helpers.rs | 84 +++++++ 19 files changed, 413 insertions(+), 326 deletions(-) delete mode 100644 .github/workflows/benchmark.yml delete mode 100644 .github/workflows/contracts.yml create mode 100644 prover/rust-toolchain diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index 5d0762b2..00000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: Benchmark - -on: - workflow_dispatch: - inputs: - run_benchmark: - description: "Please confirm running the benchmarks by typing 'yes' in the input box." - required: true - default: "no" - -jobs: - wakeup: - runs-on: ubuntu-latest - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.run_benchmark == 'yes' }} - permissions: - id-token: write - contents: read - steps: - - uses: actions/checkout@v3 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::490752553772:role/summa-solvency-ec2-slc - role-duration-seconds: 900 - aws-region: us-west-2 - - - name: Wakeup runner - run: .github/scripts/wakeup.sh - - benchmark: - runs-on: [summa-solvency-runner] - needs: [wakeup] - if: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.run_benchmark == 'yes' }} - steps: - - uses: actions/checkout@v3 - - - name: Run Benchmark Tests - run: | - cd prover - cargo bench - - - name: Upload Benchmark Results - uses: actions/upload-artifact@v2 - with: - name: benchmark-results - path: prover/target/criterion diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml deleted file mode 100644 index f05c06f8..00000000 --- a/.github/workflows/contracts.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Contracts - -on: - push: - branches: ["*"] - pull_request: - branches: ["*"] - -jobs: - tests: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - name: Install packages - uses: actions/setup-node@v3 - - run: | - cd contracts - npm ci - - name: Run Tests - run: | - cd contracts - npx hardhat test diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 1ba15d57..c72b2a4c 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -14,53 +14,31 @@ env: CARGO_TERM_COLOR: always jobs: - wakeup: + test-zk-prover: runs-on: ubuntu-latest - permissions: - id-token: write - contents: read - - steps: - - uses: actions/checkout@v3 - - - name: Configure AWS Credentials - uses: aws-actions/configure-aws-credentials@v4 - with: - role-to-assume: arn:aws:iam::490752553772:role/summa-solvency-ec2-slc - role-duration-seconds: 900 - aws-region: us-west-2 - - - name: Wakeup runner - run: .github/scripts/wakeup.sh - - build: - runs-on: [summa-solvency-runner] - needs: [wakeup] - steps: - uses: actions/checkout@v3 - - - name: Set Environment - run: echo "PATH=/home/ubuntu/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" >> "$GITHUB_ENV" - - - name: Install solc - run: (hash svm 2>/dev/null || cargo install --version 0.2.23 svm-rs) && svm install 0.8.20 && solc --version - - - name: Test Prover + - name: Test Zk Prover run: | cd prover - cargo test --release -- --nocapture - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - # TODO restore workflow - # - name: Test backend - # run: | - # cd backend - # cargo test --release -- --nocapture - - # - name: Test example - # run: | - # cd backend - # cargo run --release --example summa_solvency_flow + cargo test --release --features dev-graph -- --nocapture + + # TODO: restore workflow after fix backend + # test-backend: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Test backend + # run: | + # cd backend + # cargo test --release -- --nocapture + + # test-backend-examples: + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v3 + # - name: Test backend example + # run: | + # cd backend + # cargo run --release --example summa_solvency_flow + \ No newline at end of file diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 95318bca..24e50410 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -2427,7 +2427,7 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "plonkish_backend" version = "0.1.0" -source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#e37ba53dcc8f8bd6e7add4e479d0e3295ee80661" +source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#c9dc12571d4a9aa06a419598ff2422b42770ae91" dependencies = [ "bincode", "bitvec 1.0.1", 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/Cargo.lock b/prover/Cargo.lock index 610ff69a..7dd74ea1 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -34,9 +34,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "ark-std" @@ -73,9 +73,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bincode" @@ -154,9 +154,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytemuck" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d6d68c57235a3a081186990eca2867354726650f42f7516ca50c28d6281fd15" +checksum = "78834c15cb5d5efe3452d58b1e8ba890dd62d21907f867f383358198e56ebca5" [[package]] name = "byteorder" @@ -172,9 +172,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.94" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f6e324229dc011159fcc089755d1e2e216a90d43a7dea6853ca740b84f35e7" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" [[package]] name = "cfg-if" @@ -213,12 +213,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" -[[package]] -name = "const-cstr" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3d0b5ff30645a68f35ece8cea4556ca14ef8a1651455f789a099a0513532a6" - [[package]] name = "constant_time_eq" version = "0.3.0" @@ -243,9 +237,9 @@ checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "core-graphics" -version = "0.22.3" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -267,9 +261,9 @@ dependencies = [ [[package]] name = "core-text" -version = "19.2.0" +version = "20.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5" dependencies = [ "core-foundation", "core-graphics", @@ -288,9 +282,9 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -352,9 +346,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -366,6 +360,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "cstr" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68523903c8ae5aacfa32a0d9ae60cadeb764e1da14ee0d26b1f3089f13a54636" +dependencies = [ + "proc-macro2", + "quote", +] + [[package]] name = "csv" version = "1.3.0" @@ -510,9 +514,9 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "fdeflate" @@ -536,9 +540,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -546,9 +550,9 @@ dependencies = [ [[package]] name = "float-ord" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7bad48618fdb549078c333a7a8528acb57af271d0433bdecd523eb620628364e" +checksum = "8ce81f49ae8a0482e4c55ea62ebbd7e5a686af544c00b9d090bba3ff9be97b3d" [[package]] name = "fnv" @@ -558,11 +562,11 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "font-kit" -version = "0.11.0" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21fe28504d371085fae9ac7a3450f0b289ab71e07c8e57baa3fb68b9e57d6ce5" +checksum = "2845a73bbd781e691ab7c2a028c579727cd254942e8ced57ff73e0eafd60de87" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "byteorder", "core-foundation", "core-graphics", @@ -570,7 +574,7 @@ dependencies = [ "dirs-next", "dwrote", "float-ord", - "freetype", + "freetype-sys", "lazy_static", "libc", "log", @@ -583,34 +587,36 @@ dependencies = [ [[package]] name = "foreign-types" -version = "0.3.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ + "foreign-types-macros", "foreign-types-shared", ] [[package]] -name = "foreign-types-shared" -version = "0.1.1" +name = "foreign-types-macros" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] [[package]] -name = "freetype" -version = "0.7.1" +name = "foreign-types-shared" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efc8599a3078adf8edeb86c71e9f8fa7d88af5ca31e806a867756081f90f5d83" -dependencies = [ - "freetype-sys", - "libc", -] +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" [[package]] name = "freetype-sys" -version = "0.19.0" +version = "0.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66ee28c39a43d89fbed8b4798fb4ba56722cfd2b5af81f9326c27614ba88ecd5" +checksum = "0e7edc5b9669349acfda99533e9e0bcf26a51862ab43b08ee7745c55d28eb134" dependencies = [ "cc", "libc", @@ -636,9 +642,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -839,9 +845,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libloading" @@ -877,9 +883,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", "simd-adler32", @@ -887,11 +893,10 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -907,9 +912,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -951,9 +956,9 @@ dependencies = [ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pathfinder_geometry" @@ -989,7 +994,7 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plonkish_backend" version = "0.1.0" -source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#e37ba53dcc8f8bd6e7add4e479d0e3295ee80661" +source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#c9dc12571d4a9aa06a419598ff2422b42770ae91" dependencies = [ "bincode", "bitvec", @@ -1009,9 +1014,9 @@ dependencies = [ [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" dependencies = [ "chrono", "font-kit", @@ -1029,15 +1034,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" [[package]] name = "plotters-bitmap" -version = "0.3.3" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cebbe1f70205299abc69e8b295035bb52a6a70ee35474ad10011f0a4efb8543" +checksum = "f7e7f6fb8302456d7c264a94dada86f76d76e1a03e2294ee86ca7da92983b0a6" dependencies = [ "gif", "image", @@ -1046,9 +1051,9 @@ dependencies = [ [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" dependencies = [ "plotters-backend", ] @@ -1083,9 +1088,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "1.0.80" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56dea16b0a29e94408b9aa5e2940a4eedbd128a1ba20e8f7ae60fd3d465af0e" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] @@ -1206,9 +1211,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1221,15 +1226,15 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] @@ -1246,20 +1251,20 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.66", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1345,9 +1350,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.59" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a6531ffc7b071655e4ce2e04bd464c4830bb585a61cabb96cf808f05172615a" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", @@ -1382,22 +1387,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.66", ] [[package]] @@ -1429,7 +1434,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.66", ] [[package]] @@ -1443,9 +1448,9 @@ dependencies = [ [[package]] name = "ttf-parser" -version = "0.17.1" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff" +checksum = "17f77d76d837a7830fe1d4f12b7b4ba4192c1888001c7164257e4bc6d21d96b4" [[package]] name = "typenum" @@ -1461,9 +1466,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" [[package]] name = "version_check" @@ -1508,7 +1513,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.66", "wasm-bindgen-shared", ] @@ -1530,7 +1535,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.59", + "syn 2.0.66", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1575,11 +1580,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys", ] [[package]] @@ -1597,6 +1602,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.5" @@ -1681,11 +1695,11 @@ dependencies = [ [[package]] name = "yeslogic-fontconfig-sys" -version = "3.2.0" +version = "5.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2bbd69036d397ebbff671b1b8e4d918610c181c5a16073b96f984a38d08c386" +checksum = "ffb6b23999a8b1a997bf47c7bb4d19ad4029c3327bb3386ebe0a5ff584b33c7a" dependencies = [ - "const-cstr", + "cstr", "dlib", "once_cell", "pkg-config", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index a897e375..16740928 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -30,4 +30,4 @@ criterion= "0.3" [[bench]] name = "proof_of_liabilities" -harness = false \ No newline at end of file +harness = false diff --git a/prover/prints/range-check-layout.png b/prover/prints/range-check-layout.png index 579656ef6855d9618fbe5a45f6d4a0cb77afa0cc..a95aff4d314173424d3a80922a7952e0f84919f8 100644 GIT binary patch literal 81281 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 literal 77731 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 VyZ7Dok4MQ*+A-VF#v^A+egGKV6R7|I diff --git a/prover/prints/summa-hyperplonk-layout.png b/prover/prints/summa-hyperplonk-layout.png index 497b11f35f8a911a864deece366d8c717a15a9d0..4b1b6d10f89f6b0f9b299664ff8210950fee39e1 100644 GIT binary patch delta 19159 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 delta 32872 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 OLH`;uC;Ox2yZ#G^Ng#6o 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 51382773..8fbcbf94 100644 --- a/prover/src/circuits/config/circuit_config.rs +++ b/prover/src/circuits/config/circuit_config.rs @@ -1,6 +1,6 @@ use halo2_proofs::{ circuit::{Layouter, Value}, - plonk::{Advice, Column, ConstraintSystem, Error, Instance}, + plonk::{Advice, Column, ConstraintSystem, Error, Instance, Selector}, }; use crate::{entry::Entry, utils::big_uint_to_fp}; @@ -18,12 +18,16 @@ pub trait CircuitConfig: Clone fn configure( meta: &mut ConstraintSystem, username: Column, + concatenated_balance: Column, + selector: Selector, balances: [Column; N_CURRENCIES], instance: Column, ) -> Self; fn get_username(&self) -> Column; + fn get_concatenated_balance(&self) -> Column; + fn get_balances(&self) -> [Column; N_CURRENCIES]; fn get_instance(&self) -> Column; @@ -32,8 +36,8 @@ pub trait CircuitConfig: Clone fn synthesize( &self, mut layouter: impl Layouter, - entries: &[Entry], - grand_total: &[Fp], + entries: &[Entry], + concatenated_grand_total: &Fp, ) -> Result<(), Error> { // Initiate the range check chips let range_check_chips = self.initialize_range_check_chips(); @@ -49,7 +53,19 @@ pub trait CircuitConfig: Clone || Value::known(big_uint_to_fp::(entry.username_as_big_uint())), )?; - let mut last_decompositions = vec![]; + region.assign_advice( + || "concatenated balance", + self.get_concatenated_balance(), + 0, + || { + Value::known(big_uint_to_fp::( + &entry.concatenated_balance().unwrap(), + )) + }, + )?; + + // Decompose the balances + let mut assigned_balances = Vec::new(); for (j, balance) in entry.balances().iter().enumerate() { let assigned_balance = region.assign_advice( @@ -59,10 +75,16 @@ pub trait CircuitConfig: Clone || Value::known(big_uint_to_fp(balance)), )?; + assigned_balances.push(assigned_balance); + } + + let mut last_decompositions = vec![]; + + for (j, assigned_balance) in assigned_balances.iter().enumerate() { let mut zs = Vec::with_capacity(4); if !range_check_chips.is_empty() { - range_check_chips[j].assign(&mut region, &mut zs, &assigned_balance)?; + range_check_chips[j].assign(&mut region, &mut zs, assigned_balance)?; last_decompositions.push(zs[3].clone()); } @@ -76,28 +98,20 @@ pub trait CircuitConfig: Clone } let assigned_total = layouter.assign_region( - || "assign total".to_string(), + || "assign concatenated total".to_string(), |mut region| { - let mut assigned_total = vec![]; - - for (j, total) in grand_total.iter().enumerate() { - let balance_total = region.assign_advice( - || format!("total {}", j), - self.get_balances()[j], - 0, - || Value::known(total.neg()), - )?; - - assigned_total.push(balance_total); - } - - Ok(assigned_total) + let balance_total = region.assign_advice( + || format!("concateneated total({} currencies)", N_CURRENCIES), + self.get_concatenated_balance(), + 0, + || Value::known(concatenated_grand_total.neg()), + )?; + + Ok(balance_total) }, )?; - for (j, total) in assigned_total.iter().enumerate() { - layouter.constrain_instance(total.cell(), self.get_instance(), 1 + j)?; - } + layouter.constrain_instance(assigned_total.cell(), self.get_instance(), 1)?; self.load_lookup_table(layouter)?; diff --git a/prover/src/circuits/config/no_range_check_config.rs b/prover/src/circuits/config/no_range_check_config.rs index 5d6d4ada..15a70b38 100644 --- a/prover/src/circuits/config/no_range_check_config.rs +++ b/prover/src/circuits/config/no_range_check_config.rs @@ -1,6 +1,6 @@ use halo2_proofs::{ circuit::Layouter, - plonk::{Advice, Column, ConstraintSystem, Error, Instance}, + plonk::{Advice, Column, ConstraintSystem, Error, Instance, Selector}, }; use crate::chips::range::range_check::RangeCheckU64Chip; @@ -19,10 +19,13 @@ use super::circuit_config::CircuitConfig; /// # Fields /// /// * `username`: Advice column used to store the usernames of the users +/// * `concatenated_balance`: Advice column used to store the concatenated balances of the users /// * `balances`: Advice columns used to store the balances of the users +/// * `instance`: Instance column used to constrain the last balance decomposition #[derive(Clone)] pub struct NoRangeCheckConfig { username: Column, + concatenated_balance: Column, balances: [Column; N_CURRENCIES], instance: Column, } @@ -33,11 +36,14 @@ impl CircuitConfig, username: Column, + concatenated_balance: Column, + _selector: Selector, balances: [Column; N_CURRENCIES], instance: Column, ) -> NoRangeCheckConfig { Self { username, + concatenated_balance, balances, instance, } @@ -47,6 +53,10 @@ impl CircuitConfig Column { + self.concatenated_balance + } + fn get_balances(&self) -> [Column; N_CURRENCIES] { self.balances } diff --git a/prover/src/circuits/config/range_check_config.rs b/prover/src/circuits/config/range_check_config.rs index 7a7c0e8a..08ad6f94 100644 --- a/prover/src/circuits/config/range_check_config.rs +++ b/prover/src/circuits/config/range_check_config.rs @@ -1,6 +1,6 @@ use halo2_proofs::{ circuit::{Layouter, Value}, - plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Instance}, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Instance, Selector}, }; use crate::chips::range::range_check::{RangeCheckChipConfig, RangeCheckU64Chip}; @@ -18,6 +18,7 @@ use super::circuit_config::CircuitConfig; /// # Fields /// /// * `username`: Advice column used to store the usernames of the users +/// * `concatenated_balance`: Advice column used to store the concatenated balances of the users /// * `balances`: Advice columns used to store the balances of the users /// * `range_check_configs`: Range check chip configurations /// * `range_u16`: Fixed column used to store the lookup table @@ -25,6 +26,7 @@ use super::circuit_config::CircuitConfig; #[derive(Clone)] pub struct RangeCheckConfig { username: Column, + concatenated_balance: Column, balances: [Column; N_CURRENCIES], range_check_configs: [RangeCheckChipConfig; N_CURRENCIES], range_u16: Column, @@ -37,6 +39,8 @@ impl CircuitConfig, username: Column, + concatenated_balance: Column, + selector: Selector, balances: [Column; N_CURRENCIES], instance: Column, ) -> Self { @@ -46,8 +50,6 @@ impl CircuitConfig CircuitConfig CircuitConfig Column { + self.concatenated_balance + } + fn get_balances(&self) -> [Column; N_CURRENCIES] { self.balances } diff --git a/prover/src/circuits/summa_circuit.rs b/prover/src/circuits/summa_circuit.rs index e8762d45..199809bd 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}, - plonk::{Circuit, ConstraintSystem, Error}, + 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,8 +21,8 @@ pub struct SummaHyperplonk< const N_CURRENCIES: usize, CONFIG: CircuitConfig, > { - pub entries: Vec>, - pub grand_total: Vec, + pub entries: Vec>, + pub concatenated_grand_total: Fp, _marker: PhantomData, } @@ -32,17 +32,17 @@ impl< CONFIG: CircuitConfig, > SummaHyperplonk { - pub fn init(user_entries: Vec>) -> Self { - let mut grand_total = vec![Fp::ZERO; N_CURRENCIES]; + pub fn init(user_entries: Vec>) -> Self { + let mut concatenated_grand_total = Fp::ZERO; + for entry in user_entries.iter() { - for (i, balance) in entry.balances().iter().enumerate() { - grand_total[i] += big_uint_to_fp::(balance); - } + concatenated_grand_total += + big_uint_to_fp::(&entry.concatenated_balance().unwrap()); } Self { entries: user_entries, - grand_total, + concatenated_grand_total, _marker: PhantomData, } } @@ -50,17 +50,14 @@ 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 mut grand_total = vec![Fp::ZERO; N_CURRENCIES]; - for i in 0..N_CURRENCIES { - grand_total[i] = Fp::random(seeded_std_rng()); - } + let concatenated_grand_total = Fp::random(seeded_std_rng()); Self { entries: user_entries, - grand_total, + concatenated_grand_total, _marker: PhantomData, } } @@ -84,28 +81,83 @@ impl< let username = meta.advice_column(); + let concatenated_balance = meta.advice_column(); + meta.enable_equality(concatenated_balance); + + meta.create_gate("Concatenated balance sumcheck gate", |meta| { + let current_balance = meta.query_advice(concatenated_balance, Rotation::cur()); + vec![current_balance.clone()] + }); + + let q_enable = meta.complex_selector(); + let balances = [(); N_CURRENCIES].map(|_| meta.advice_column()); for column in &balances { meta.enable_equality(*column); } - meta.create_gate("Balance sumcheck gate", |meta| { - let mut nonzero_constraint = vec![]; - for balance in balances { - let current_balance = meta.query_advice(balance, Rotation::cur()); - nonzero_constraint.push(current_balance.clone()); + meta.create_gate("Concatenated balance validation check gate", |meta| { + let s = meta.query_selector(q_enable); + + let concatenated_balance = meta.query_advice(concatenated_balance, Rotation::cur()); + + // 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()); + + let shift_bits = calculate_shift_bits::().unwrap(); + + // The shift bits would not be exceed 93 bits + let base_shift = Fp::from_u128(1u128 << shift_bits); + + let mut current_shift = Expression::Constant(base_shift); + + // The number of currencies is limited 1 or 3 because the range check chip logic. + // 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 + println!("For a better performance for single currency, check out V3c. More details at: https://github.com/summa-dev/summa-solvency/tree/v3c"); + }, + 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 and 3 currencies are supported" + ), } - nonzero_constraint + + // Ensure that the whole expression equals to the concatenated_balance + vec![s * (concatenated_balance - balances_expr)] }); let instance = meta.instance_column(); meta.enable_equality(instance); - CONFIG::configure(meta, username, balances, instance) + CONFIG::configure( + meta, + username, + concatenated_balance, + q_enable, + balances, + instance, + ) } fn synthesize(&self, config: Self::Config, layouter: impl Layouter) -> Result<(), Error> { - CONFIG::synthesize(&config, layouter, &self.entries, &self.grand_total) + CONFIG::synthesize( + &config, + layouter, + &self.entries, + &self.concatenated_grand_total, + ) } } @@ -121,9 +173,6 @@ impl< fn instances(&self) -> Vec> { // The 1st element is zero because the last decomposition of each range check chip should be zero - vec![vec![Fp::ZERO] - .into_iter() - .chain(self.grand_total.iter().map(|x| x.neg())) - .collect::>()] + vec![vec![Fp::ZERO, self.concatenated_grand_total.neg()]] } } diff --git a/prover/src/circuits/tests.rs b/prover/src/circuits/tests.rs index 88bbb4af..c9fbd96d 100644 --- a/prover/src/circuits/tests.rs +++ b/prover/src/circuits/tests.rs @@ -29,9 +29,8 @@ use crate::{ }, }; const K: u32 = 17; -const N_CURRENCIES: usize = 2; +const N_CURRENCIES: usize = 3; // One row is reserved for the grand total. -// TODO find out what occupies one extra row const N_USERS: usize = (1 << K) - 2; pub fn seeded_std_rng() -> impl RngCore + CryptoRng { @@ -48,21 +47,12 @@ fn test_summa_hyperplonk_e2e() { entries.to_vec(), ); - let neg_grand_total = halo2_circuit - .grand_total - .iter() - .fold(Fp::ZERO, |acc, f| acc + f) - .neg(); + let neg_grand_total = halo2_circuit.concatenated_grand_total.neg(); // We're putting the negated grand total at the end of each balance column, // so the sumcheck over such balance column would yield zero (given the special gate, // see the circuit). - assert!( - neg_grand_total - == halo2_circuit.instances()[0] - .iter() - .fold(Fp::ZERO, |acc, instance| { acc + instance }) - ); + assert!(neg_grand_total == halo2_circuit.instances()[0][1]); let num_vars = K; @@ -95,8 +85,6 @@ fn test_summa_hyperplonk_e2e() { (witness_polys, proof_transcript) }; - let num_points = N_CURRENCIES + 1; - let proof = proof_transcript.into_proof(); let mut transcript; @@ -177,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].balances()[0] + entries[random_user_index].concatenated_balance().unwrap() ); // Convert challenge into a multivariate form @@ -188,6 +176,9 @@ fn test_summa_hyperplonk_e2e() { let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + // Username and Concatenated balance + let num_points = 2; + let user_entry_commitments = MultilinearKzg::::read_commitments( &verifier_parameters.pcs, num_points, @@ -239,24 +230,18 @@ fn test_summa_hyperplonk_e2e() { multivariate_challenge.push(kzg_transcript.read_field_element().unwrap()); } - //The user knows their evaluation at the challenge point - let evals: Vec> = (0..N_CURRENCIES + 1) - .map(|i| { - if i == 0 { - Evaluation::new( - i, - 0, - big_uint_to_fp::(entries[random_user_index].username_as_big_uint()), - ) - } else { - Evaluation::new( - i, - 0, - big_uint_to_fp::(&entries[random_user_index].balances()[i - 1]), - ) - } - }) - .collect(); + let evals = vec![ + Evaluation::new( + 0, + 0, + big_uint_to_fp::(entries[random_user_index].username_as_big_uint()), + ), + Evaluation::new( + 1, + 0, + big_uint_to_fp::(&entries[random_user_index].concatenated_balance().unwrap()), + ), + ]; MultilinearKzg::::batch_verify( &verifier_parameters.pcs, @@ -340,7 +325,10 @@ fn print_univariate_grand_sum_circuit() { let entries = generate_dummy_entries::().unwrap(); - let circuit = SummaHyperplonk::::init(entries.to_vec()); + let circuit = + SummaHyperplonk::>::init( + entries.to_vec(), + ); let root = BitMapBackend::new("prints/summa-hyperplonk-layout.png", (2048, 32768)).into_drawing_area(); @@ -350,6 +338,6 @@ fn print_univariate_grand_sum_circuit() { .unwrap(); halo2_proofs::dev::CircuitLayout::default() - .render::, _, true>(K, &circuit, &root) + .render::>, _, true>(K, &circuit, &root) .unwrap(); } diff --git a/prover/src/entry.rs b/prover/src/entry.rs index daaef76c..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,6 +34,19 @@ impl Entry { &self.balances } + pub fn concatenated_balance(&self) -> Result { + let shift_bits = calculate_shift_bits::().unwrap(); + + let mut concatenated_balance = BigUint::from(0u32); + + // Reverse the array to correctly order the balances + for (i, balance) in self.balances.iter().rev().enumerate() { + concatenated_balance += balance << (shift_bits * i); + } + + Ok(concatenated_balance) + } + pub fn username_as_big_uint(&self) -> &BigUint { &self.username_as_big_uint } diff --git a/prover/src/utils/dummy_entries.rs b/prover/src/utils/dummy_entries.rs index f566b9c0..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(); @@ -21,7 +21,7 @@ pub fn generate_dummy_entries( let username: String = (0..10).map(|_| rng.sample(Alphanumeric) as char).collect(); let balances: [BigUint; N_CURRENCIES] = - std::array::from_fn(|_| BigUint::from(rng.gen_range(1000..90000) as u32)); + std::array::from_fn(|_| BigUint::from(rng.gen_range(10000..90000) as u32)); *entry = Entry::new(username, balances).expect("Failed to create entry"); }); 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" + ); + } + } +} From 0ac3961062cd605d50c98b6f9e7e7567099872e8 Mon Sep 17 00:00:00 2001 From: sifnoc Date: Fri, 26 Jul 2024 11:28:04 +0000 Subject: [PATCH 3/3] changed plonkish branch, summa --- prover/Cargo.lock | 3 ++- prover/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 7dd74ea1..38632912 100644 --- a/prover/Cargo.lock +++ b/prover/Cargo.lock @@ -994,10 +994,11 @@ checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "plonkish_backend" version = "0.1.0" -source = "git+https://github.com/summa-dev/plonkish?branch=nonzero-constraints#c9dc12571d4a9aa06a419598ff2422b42770ae91" +source = "git+https://github.com/summa-dev/plonkish?branch=summa#57916a9f1afe4a8d2f2fa31f4e11da453f14bc5b" dependencies = [ "bincode", "bitvec", + "byteorder", "generic-array", "halo2_proofs", "halo2curves 0.3.3", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 16740928..5035ee31 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -13,7 +13,7 @@ parallel = ["dep:rayon"] frontend-halo2 = ["dep:halo2_proofs"] [dependencies] -plonkish_backend = { git = "https://github.com/summa-dev/plonkish", branch="nonzero-constraints", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } +plonkish_backend = { git = "https://github.com/summa-dev/plonkish", branch="summa", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } plotters = { version = "0.3.4", optional = true } rand = "0.8" csv = "1.1"