Skip to content

Commit

Permalink
Created dummy entry generator for benchmark (#273)
Browse files Browse the repository at this point in the history
  • Loading branch information
sifnoc authored Mar 12, 2024
1 parent 66ebc91 commit 69505f2
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 329 deletions.
4 changes: 2 additions & 2 deletions backend/examples/summa_solvency_flow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use summa_solvency::{

const K: u32 = 17;
const N_CURRENCIES: usize = 2;
const N_POINTS: usize = 3;
const N_POINTS: usize = N_CURRENCIES + 1;
const N_USERS: usize = 16;
const USER_INDEX: usize = 0;

Expand Down Expand Up @@ -128,7 +128,7 @@ async fn main() -> Result<(), Box<dyn Error>> {

// 4. Verify Inclusion Proof
//
// The `snapshot_time` denotes the specific moment when entries were created for polynomal encoding.
// The `snapshot_time` denotes the specific moment when entries were created for polynomal interpolation.
// This timestamp is established during the initialization of the Round instance.
let snapshot_time = U256::from(timestamp);

Expand Down
2 changes: 1 addition & 1 deletion backend/src/contracts/abi/Summa.json

Large diffs are not rendered by default.

592 changes: 318 additions & 274 deletions backend/src/contracts/generated/summa_contract.rs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion backend/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ mod test {

const K: u32 = 17;
const N_CURRENCIES: usize = 2;
const N_POINTS: usize = 3;
const N_POINTS: usize = N_CURRENCIES + 1;
const N_USERS: usize = 16;
const PARAMS_PATH: &str = "../backend/ptau/hermez-raw-17";

Expand Down
18 changes: 9 additions & 9 deletions contracts/src/Summa.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ contract Summa is Ownable {
// Convenience mapping to check if an address has already been verified
mapping(bytes32 => uint256) private _ownershipProofByAddress;

// zkSNARK verifier of the valid polynomial encoding
IVerifier private immutable polynomialEncodingVerifier;
// zkSNARK verifier of the valid polynomial interpolation
IVerifier private immutable polynomialInterpolationVerifier;

// KZG verifier of the grand sum
IVerifier private immutable grandSumVerifier;
Expand All @@ -72,7 +72,7 @@ contract Summa is Ownable {
/**
* Summa contract
* @param _verifyingKey The address of the verification key contract
* @param _polynomialEncodingVerifier the address of the polynomial encoding zkSNARK verifier
* @param _polynomialInterpolationVerifier the address of the polynomial interpolation zkSNARK verifier
* @param _grandSumVerifier the address of the grand sum KZG verifier
* @param _inclusionVerifier the address of the inclusion KZG verifier
* @param cryptocurrencyNames the names of the cryptocurrencies whose balances are encoded in the polynomials
Expand All @@ -81,7 +81,7 @@ contract Summa is Ownable {
*/
constructor(
address _verifyingKey,
IVerifier _polynomialEncodingVerifier,
IVerifier _polynomialInterpolationVerifier,
IVerifier _grandSumVerifier,
IInclusionVerifier _inclusionVerifier,
string[] memory cryptocurrencyNames,
Expand Down Expand Up @@ -110,10 +110,10 @@ contract Summa is Ownable {
"The config parameters do not correspond to the verifying key"
);
require(
address(_polynomialEncodingVerifier) != address(0),
"Invalid polynomial encoding verifier address"
address(_polynomialInterpolationVerifier) != address(0),
"Invalid polynomial interpolation verifier address"
);
polynomialEncodingVerifier = _polynomialEncodingVerifier;
polynomialInterpolationVerifier = _polynomialInterpolationVerifier;
require(
address(_grandSumVerifier) != address(0),
"Invalid grand sum verifier address"
Expand Down Expand Up @@ -216,7 +216,7 @@ contract Summa is Ownable {

/**
* @dev Submit commitment for a CEX
* @param snarkProof ZK proof of the valid polynomial encoding
* @param snarkProof ZK proof of the valid polynomial interpolation
* @param grandSumProof kzg proof of the grand sum
* @param totalBalances The array of total balances in the grand sum
* @param timestamp The timestamp at which the CEX took the snapshot of its assets and liabilities
Expand All @@ -235,7 +235,7 @@ contract Summa is Ownable {
uint[] memory args = new uint[](1);
args[0] = 1; // Workaround to satisfy the verifier (TODO remove after https://github.com/summa-dev/halo2-solidity-verifier/issues/1 is resolved)
require(
polynomialEncodingVerifier.verifyProof(verifyingKey, snarkProof, args),
polynomialInterpolationVerifier.verifyProof(verifyingKey, snarkProof, args),
"Invalid snark proof"
);
require(
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/Summa.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ describe("Summa Contract", () => {
["ETH", "BTC"],
8,
])
).to.be.revertedWith("Invalid polynomial encoding verifier address");
).to.be.revertedWith("Invalid polynomial interpolation verifier address");
});


Expand Down
8 changes: 4 additions & 4 deletions kzg_prover/README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Summa V2: Polynomial Encoding Approach
# Summa V2: Polynomial Interpolation Approach

## Motivation

[Summa V1](https://github.com/summa-dev/summa-solvency/releases/tag/merkle_sum_tree_v1.1) was using a Merkle sum tree (MST) as the main data structure and a cryptographic commitment. MST that has $n$ leaves involves $2n-1$ hashing operations, making it computationally demanding. Additionally, the MST inclusion proofs in Summa V1 have to be wrapped into a ZK-SNARK, making it infeasible to generate all of them at once for the entire user base of the Custodian (~100M users).
[Summa V1](https://github.com/summa-dev/summa-solvency/releases/tag/merkle_sum_tree_v1.1.1) was using a Merkle sum tree (MST) as the main data structure and a cryptographic commitment. MST that has $n$ leaves involves $2n-1$ hashing operations, making it computationally demanding. Additionally, the MST inclusion proofs in Summa V1 have to be wrapped into a ZK-SNARK, making it infeasible to generate all of them at once for the entire user base of the Custodian (~100M users).

## Univariate Grand Sum Calculation

The grand total of all the Custodian's $n$ user cryptocurrency balances is the Custodian's liabilities $S$. Summa V2 is using a property of the _sum of all roots of unity in a finite field_ being _equal to zero_ to find the liabilities. This property allows to efficiently calculate the grand sum of univariate polynomial evaluations. Summa V2 takes advantage of that by encoding the user balances into a univariate polynomial in a special way. The resulting proof of solvency protocol has the following steps:
The grand total of all the Custodian's $n$ user cryptocurrency balances is the Custodian's liabilities $S$. Summa V2 is using a property of the _sum of all roots of unity in a finite field_ being _equal to zero_ to find the liabilities. This property allows to efficiently calculate the grand sum of univariate polynomial evaluations. Summa V2 takes advantage of that by interpolating the user balances into a univariate polynomial in a special way. The resulting proof of solvency protocol has the following steps:

1. construct a polynomial of degree $d = n - 1$ that interpolates the points $(\omega^i, b_i)$ where $i \in 0..n-1$ is the user index, $\omega^i$ is the power of an $n$-th primitive root of unity ($x$ value), and $b_i$ is the $i$-th user balance value ($y$ value);
2. multiply the constant term $a_0$ of the polynomial by $n$ to obtain the grand sum:
Expand All @@ -27,7 +27,7 @@ The algorithm works as follows:

1. Assign all the user balances to an unblinded advice column of the [circuit](../kzg_prover/src/circuits/univariate_grand_sum.rs). The unblinded advice column is a special kind of advice column without the random values (blinding factors) added at the bottom. The constant term of such polynomial correctly yields the grand total of user balances according to (1) because the polynomial only interpolates the user balances but not the blinding factors (as in the case with a normal advice column).
2. Assign the user IDs (e.g., hashes of user emails) to another (normal) advice column.
3. Generate the ZK-SNARK proof for the circuit, effectively encoding the balance values into a polynomial and performing a KZG commitment to this polynomial.
3. Generate the ZK-SNARK proof for the circuit, effectively interpolating the balance values into a polynomial and performing a KZG commitment to this polynomial.
4. Perform a KZG opening proof of the polynomial at $x=0$ and publicly reveal the constant term $a_0$ of the polynomial. The public can then calculate the liabilities by multiplying the $a_0$ by $d + 1$ where $d$ is the polynomial degree.
5. Privately provide to each user a KZG proof of the corresponding user opening (namely, the openings of the user ID and balance polynomials). Cross-checking the balance opening and the user ID opening $\omega^i$ value ensures that no malicious Custodian can provide the same balance opening to multiple users with the identical balance value.

Expand Down
41 changes: 14 additions & 27 deletions kzg_prover/benches/kzg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use summa_solvency::{
},
cryptocurrency::Cryptocurrency,
entry::Entry,
utils::{big_uint_to_fp, parse_csv_to_entries},
utils::{big_uint_to_fp, generate_dummy_entries},
};

fn bench_kzg<
Expand All @@ -31,7 +31,6 @@ fn bench_kzg<
CONFIG: CircuitConfig<N_CURRENCIES, N_USERS>,
>(
name: &str,
csv_path: &str,
) where
[(); N_CURRENCIES + 1]:,
{
Expand All @@ -57,9 +56,7 @@ fn bench_kzg<
let verifying_grand_sum_bench_name = format!("<{}> verifying grand sum", name);
let verifying_user_bench_name = format!("<{}> verifying user inclusion", name);

let mut entries: Vec<Entry<N_CURRENCIES>> = vec![Entry::init_empty(); N_USERS];
let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES];
parse_csv_to_entries::<&str, N_CURRENCIES>(csv_path, &mut entries, &mut cryptos).unwrap();
let entries = generate_dummy_entries::<N_USERS, N_CURRENCIES>().unwrap();

// Calculate total for all entry columns
let mut csv_total: Vec<BigUint> = vec![BigUint::from(0u32); N_CURRENCIES];
Expand Down Expand Up @@ -261,75 +258,65 @@ fn bench_kzg<
}

fn criterion_benchmark(_c: &mut Criterion) {
const N_CURRENCIES: usize = 2;
const N_POINTS: usize = 3;
const N_CURRENCIES: usize = 1;
const N_POINTS: usize = N_CURRENCIES + 1;

// Demonstrating that a higher value of K has a more significant impact on benchmark performance than the number of users
#[cfg(not(feature = "no_range_check"))]
{
const K: u32 = 18;
const N_USERS: usize = 16;
const K: u32 = 17;
const N_USERS: usize = 2usize.pow(K) + 2usize.pow(16) - 6; // Subtracting 2^16 (reserved for range checks) and 6 (reserved rows) from 2^K.
bench_kzg::<
K,
N_USERS,
N_CURRENCIES,
N_POINTS,
UnivariateGrandSumConfig<N_CURRENCIES, N_USERS>,
>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
>(format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str());
}
#[cfg(not(feature = "no_range_check"))]
{
const K: u32 = 17;
const N_USERS: usize = 64;
const K: u32 = 18;
const N_USERS: usize = 2usize.pow(K) - 2usize.pow(16) - 6; // Subtracting 2^16 (reserved for range checks) and 6 (reserved rows) from 2^K.
bench_kzg::<
K,
N_USERS,
N_CURRENCIES,
N_POINTS,
UnivariateGrandSumConfig<N_CURRENCIES, N_USERS>,
>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
>(format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str());
}
//Use the following benchmarks for quick evaluation/prototyping (no range check)
#[cfg(feature = "no_range_check")]
{
const K: u32 = 9;
const N_USERS: usize = 64;
const N_USERS: usize = 2usize.pow(K) - 6;
bench_kzg::<K, N_USERS, N_CURRENCIES, N_POINTS, NoRangeCheckConfig<N_CURRENCIES, N_USERS>>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
}
#[cfg(feature = "no_range_check")]
{
const K: u32 = 10;
const N_USERS: usize = 64;
const N_USERS: usize = 2usize.pow(K) - 6;
bench_kzg::<K, N_USERS, N_CURRENCIES, N_POINTS, NoRangeCheckConfig<N_CURRENCIES, N_USERS>>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
}
#[cfg(feature = "no_range_check")]
{
const K: u32 = 11;
const N_USERS: usize = 64;
const N_USERS: usize = 2usize.pow(K) - 6;
bench_kzg::<K, N_USERS, N_CURRENCIES, N_POINTS, NoRangeCheckConfig<N_CURRENCIES, N_USERS>>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
}
#[cfg(feature = "no_range_check")]
{
const K: u32 = 12;
const N_USERS: usize = 64;
const N_USERS: usize = 2usize.pow(K) - 6;
bench_kzg::<K, N_USERS, N_CURRENCIES, N_POINTS, NoRangeCheckConfig<N_CURRENCIES, N_USERS>>(
format!("K = {K}, N_USERS = {N_USERS}, N_CURRENCIES = {N_CURRENCIES}").as_str(),
format!("../csv/entry_{N_USERS}.csv").as_str(),
);
}
}
Expand Down
2 changes: 1 addition & 1 deletion kzg_prover/src/circuits/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ mod test {

const K: u32 = 17;
const N_CURRENCIES: usize = 2;
const N_POINTS: usize = 3;
const N_POINTS: usize = N_CURRENCIES + 1;
const N_USERS: usize = 16;

#[test]
Expand Down
12 changes: 6 additions & 6 deletions kzg_prover/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use crate::utils::big_intify_username;
/// 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<const N_ASSETS: usize> {
pub struct Entry<const N_CURRENCIES: usize> {
username_as_big_uint: BigUint,
balances: [BigUint; N_ASSETS],
balances: [BigUint; N_CURRENCIES],
username: String,
}

impl<const N_ASSETS: usize> Entry<N_ASSETS> {
pub fn new(username: String, balances: [BigUint; N_ASSETS]) -> Result<Self, &'static str> {
impl<const N_CURRENCIES: usize> Entry<N_CURRENCIES> {
pub fn new(username: String, balances: [BigUint; N_CURRENCIES]) -> Result<Self, &'static str> {
Ok(Entry {
username_as_big_uint: big_intify_username(&username),
balances,
Expand All @@ -21,7 +21,7 @@ impl<const N_ASSETS: usize> Entry<N_ASSETS> {
}

pub fn init_empty() -> Self {
let empty_balances: [BigUint; N_ASSETS] = std::array::from_fn(|_| BigUint::from(0u32));
let empty_balances: [BigUint; N_CURRENCIES] = std::array::from_fn(|_| BigUint::from(0u32));

Entry {
username_as_big_uint: BigUint::from(0u32),
Expand All @@ -30,7 +30,7 @@ impl<const N_ASSETS: usize> Entry<N_ASSETS> {
}
}

pub fn balances(&self) -> &[BigUint; N_ASSETS] {
pub fn balances(&self) -> &[BigUint; N_CURRENCIES] {
&self.balances
}

Expand Down
6 changes: 3 additions & 3 deletions kzg_prover/src/utils/csv_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ use std::path::Path;
use crate::cryptocurrency::Cryptocurrency;
use crate::entry::Entry;

pub fn parse_csv_to_entries<P: AsRef<Path>, const N_ASSETS: usize>(
pub fn parse_csv_to_entries<P: AsRef<Path>, const N_CURRENCIES: usize>(
path: P,
entries: &mut [Entry<N_ASSETS>],
entries: &mut [Entry<N_CURRENCIES>],
cryptocurrencies: &mut [Cryptocurrency],
) -> Result<(), Box<dyn Error>> {
let file = File::open(path)?;
Expand Down Expand Up @@ -39,7 +39,7 @@ pub fn parse_csv_to_entries<P: AsRef<Path>, const N_ASSETS: usize>(
}
}

let mut balances_acc: Vec<BigUint> = vec![BigUint::from(0_usize); N_ASSETS];
let mut balances_acc: Vec<BigUint> = vec![BigUint::from(0_usize); N_CURRENCIES];

for (i, result) in rdr.deserialize().enumerate() {
let record: HashMap<String, String> = result?;
Expand Down
63 changes: 63 additions & 0 deletions kzg_prover/src/utils/dummy_entries.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
use num_bigint::BigUint;
use rand::{distributions::Alphanumeric, Rng};
use rayon::prelude::*;
use std::error::Error;

use crate::entry::Entry;

// This is for testing purposes with a large dataset instead of using a CSV file
pub fn generate_dummy_entries<const N_USERS: usize, const N_CURRENCIES: usize>(// entries: &mut [Entry<N_CURRENCIES>],
// cryptocurrencies: &mut [Cryptocurrency],
) -> Result<Vec<Entry<N_CURRENCIES>>, Box<dyn Error>> {
// 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<Entry<N_CURRENCIES>> = vec![Entry::init_empty(); N_USERS];

entries.par_iter_mut().for_each(|entry| {
let mut rng = rand::thread_rng();

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));

*entry = Entry::new(username, balances).expect("Failed to create entry");
});

Ok(entries)
}

#[cfg(test)]
mod tests {
use super::*;
use crate::cryptocurrency::Cryptocurrency;
use crate::entry::Entry;

#[test]
fn test_generate_random_entries() {
const N_USERS: usize = 1 << 17;
const N_CURRENCIES: usize = 2;

// Attempt to generate random entries
let entries = generate_dummy_entries::<N_USERS, N_CURRENCIES>().unwrap();

// Verify that entries are populated
assert_eq!(entries.len(), N_USERS);
for entry in entries {
assert!(!entry.username().is_empty());
assert_eq!(entry.balances().len(), N_CURRENCIES);
}
}

#[test]
fn test_asset_not_zero() {
const N_USERS: usize = 1 << 17;
const N_CURRENCIES: usize = 0;

// `N_CURRENCIES` is zero, so this should fail
assert!(generate_dummy_entries::<N_USERS, N_CURRENCIES>().is_err());
}
}
2 changes: 2 additions & 0 deletions kzg_prover/src/utils/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
pub mod amortized_kzg;
mod csv_parser;
mod dummy_entries;
mod operation_helpers;

pub use csv_parser::parse_csv_to_entries;
pub use dummy_entries::generate_dummy_entries;
pub use operation_helpers::*;

0 comments on commit 69505f2

Please sign in to comment.