diff --git a/kzg_prover/src/circuits/tests.rs b/kzg_prover/src/circuits/tests.rs index 5e4e4813..6d759d1b 100644 --- a/kzg_prover/src/circuits/tests.rs +++ b/kzg_prover/src/circuits/tests.rs @@ -3,16 +3,18 @@ mod test { use crate::circuits::solvency_v2::SolvencyV2; use crate::circuits::utils::{ - full_prover, full_verifier, generate_setup_artifacts, open_grand_sums, - verify_grand_sum_openings, verify_kzg_proof, + full_prover, full_verifier, generate_setup_artifacts, open_grand_sums, open_user_balances, + verify_grand_sum_openings, verify_user_inclusion, }; + use crate::cryptocurrency::Cryptocurrency; + use crate::entry::Entry; use crate::utils::parse_csv_to_entries; use halo2_proofs::{ dev::{FailureLocation, MockProver, VerifyFailure}, halo2curves::bn256::Fr as Fp, plonk::Any, }; - use num_bigint::{BigUint, ToBigUint}; + use num_bigint::BigUint; const K: u32 = 9; @@ -24,9 +26,13 @@ mod test { fn test_valid_solvency_v2() { let path = "src/csv/entry_16.csv"; - let (_, entries) = parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path).unwrap(); + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES]; + let _ = + parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path, &mut entries, &mut cryptos) + .unwrap(); - let circuit = SolvencyV2::::init(entries); + let circuit = SolvencyV2::::init(entries.to_vec()); let valid_prover = MockProver::run(K, &circuit, vec![vec![]]).unwrap(); @@ -76,7 +82,12 @@ mod test { // Only now we can instantiate the circuit with the actual inputs let path = "src/csv/entry_16.csv"; - let (_, entries) = parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path).unwrap(); + let mut entries: Vec> = vec![Entry::init_empty(); N_USERS]; + let mut cryptos = vec![Cryptocurrency::init_empty(); N_CURRENCIES]; + + let _ = + parse_csv_to_entries::<&str, N_CURRENCIES, N_BYTES>(path, &mut entries, &mut cryptos) + .unwrap(); // Calculate total for all entry columns let mut csv_total: Vec = vec![BigUint::from(0u32); N_CURRENCIES]; @@ -87,7 +98,7 @@ mod test { } } - let circuit = SolvencyV2::::init(entries); + let circuit = SolvencyV2::::init(entries.to_vec()); let valid_prover = MockProver::run(K, &circuit, vec![vec![]]).unwrap(); @@ -95,7 +106,8 @@ mod test { // 1. Proving phase // The Custodian generates the ZK proof - let (zk_proof, advice_polys) = full_prover(¶ms, &pk, circuit.clone(), vec![vec![]]); + let (zk_proof, advice_polys, omega) = + full_prover(¶ms, &pk, circuit.clone(), vec![vec![]]); // Both the Custodian and the Verifier know what column range are the balance columns let balance_column_range = 1..N_CURRENCIES + 1; @@ -109,10 +121,26 @@ mod test { balance_column_range, ); + // The Custodian creates a KZG proof of the 4th user balances inclusion + let user_index = 3_u16; + + let balance_column_range = 1..N_CURRENCIES + 1; + let user_balances_kzg_proofs = open_user_balances::( + &advice_polys.advice_polys, + &advice_polys.advice_blinds, + ¶ms, + balance_column_range, + omega, + user_index, + ); + // 2. Verification phase // The Verifier verifies the ZK proof assert!(full_verifier(¶ms, &vk, &zk_proof, vec![vec![]])); + // The Verifier is able to independently extract the omega from the verification key + let omega = pk.get_vk().get_domain().get_omega(); + // The Custodian communicates the polynomial degree to the Verifier let poly_degree = u64::try_from(advice_polys.advice_polys[0].len()).unwrap(); @@ -134,7 +162,24 @@ mod test { assert_eq!(csv_total[i], grand_sum[i]); } - //TODO next: make openings at "user" points + let balance_column_range = 1..N_CURRENCIES + 1; + let (balances_verified, balance_values) = verify_user_inclusion::( + ¶ms, + &zk_proof, + user_balances_kzg_proofs, + balance_column_range, + omega, + user_index, + ); + + let fourth_user_csv_entry = entries.get(user_index as usize).unwrap(); + for i in 0..N_CURRENCIES { + assert!(balances_verified[i]); + assert_eq!( + *fourth_user_csv_entry.balances().get(i).unwrap(), + balance_values[i] + ); + } } #[cfg(feature = "dev-graph")] diff --git a/kzg_prover/src/circuits/utils.rs b/kzg_prover/src/circuits/utils.rs index 5b8388f9..7773c3f4 100644 --- a/kzg_prover/src/circuits/utils.rs +++ b/kzg_prover/src/circuits/utils.rs @@ -3,7 +3,7 @@ use std::{fs::File, ops::Range}; use ark_std::{end_timer, start_timer}; use ethers::types::U256; use halo2_proofs::{ - arithmetic::eval_polynomial, + arithmetic::{eval_polynomial, Field}, halo2curves::{ bn256::{Bn256, Fr as Fp, G1Affine}, ff::{PrimeField, WithSmallOrderMulGroup}, @@ -89,6 +89,7 @@ pub fn full_prover>( ) -> ( Vec, AdviceSingle, + Fp, ) { let pf_time = start_timer!(|| "Creating proof"); @@ -111,7 +112,10 @@ pub fn full_prover>( end_timer!(pf_time); let advice_polys = advice_polys[0].clone(); - (proof, advice_polys) + + let omega = pk.get_vk().get_domain().get_omega(); + + (proof, advice_polys, omega) } /// Creates the univariate polynomial grand sum openings @@ -138,6 +142,35 @@ pub fn open_grand_sums( kzg_proofs } +pub fn open_user_balances( + advice_poly: &[Polynomial], + advice_blind: &[Blind], + params: &ParamsKZG, + balance_column_range: Range, + omega: Fp, + user_index: u16, +) -> Vec> { + let omega_raised = omega.pow_vartime([user_index as u64]); + let mut kzg_proofs = Vec::new(); + balance_column_range.for_each(|i| { + kzg_proofs.push( + create_kzg_proof::< + KZGCommitmentScheme, + ProverSHPLONK<'_, Bn256>, + Challenge255, + Blake2bWrite, G1Affine, Challenge255>, + >( + params, + advice_poly[i].clone(), + advice_blind[i], + omega_raised, + ) + .to_vec(), + ) + }); + kzg_proofs +} + /// Verifies the univariate polynomial grand sum openings pub fn verify_grand_sum_openings( params: &ParamsKZG, @@ -182,6 +215,54 @@ pub fn verify_grand_sum_openings( (verification_results, constant_terms) } +pub fn verify_user_inclusion( + params: &ParamsKZG, + zk_proof: &[u8], + kzg_proofs: Vec>, + balance_column_range: Range, + omega: Fp, + user_index: u16, +) -> (Vec, Vec) { + let mut transcript: Blake2bRead<&[u8], G1Affine, Challenge255> = + Blake2bRead::<_, _, Challenge255<_>>::init(zk_proof); + + //Read the commitment points for all the advice polynomials from the proof transcript and put them into a vector + let mut advice_commitments = Vec::new(); + for i in 0..N_CURRENCIES + balance_column_range.start { + let point = transcript.read_point().unwrap(); + // Skip the balances column commitment + if i != 0 { + advice_commitments.push(point); + } + } + + let mut verification_results = Vec::::new(); + let mut balances = Vec::::new(); + + for (i, advice_commitment) in advice_commitments.iter().enumerate() { + let (verified, eval_at_challenge) = verify_kzg_proof::< + KZGCommitmentScheme, + VerifierSHPLONK<'_, Bn256>, + Challenge255, + Blake2bRead<_, _, Challenge255<_>>, + AccumulatorStrategy<_>, + >( + params, + &kzg_proofs[i], + omega.pow_vartime([user_index as u64]), + *advice_commitment, + ); + verification_results.push(verified); + + if verified { + balances.push(fp_to_big_uint(eval_at_challenge)); + } else { + balances.push(BigUint::from(0u8)); + } + } + (verification_results, balances) +} + /// Creates a KZG proof for a polynomial evaluation at a challenge fn create_kzg_proof< 'params, diff --git a/kzg_prover/src/cryptocurrency.rs b/kzg_prover/src/cryptocurrency.rs index 8a6f15af..abb4b32f 100644 --- a/kzg_prover/src/cryptocurrency.rs +++ b/kzg_prover/src/cryptocurrency.rs @@ -3,3 +3,12 @@ pub struct Cryptocurrency { pub name: String, pub chain: String, } + +impl Cryptocurrency { + pub fn init_empty() -> Self { + Cryptocurrency { + name: "".to_string(), + chain: "".to_string(), + } + } +} diff --git a/kzg_prover/src/utils/csv_parser.rs b/kzg_prover/src/utils/csv_parser.rs index d3ebffeb..7f025971 100644 --- a/kzg_prover/src/utils/csv_parser.rs +++ b/kzg_prover/src/utils/csv_parser.rs @@ -9,37 +9,37 @@ use crate::entry::Entry; pub fn parse_csv_to_entries, const N_ASSETS: usize, const N_BYTES: usize>( path: P, -) -> Result<(Vec, Vec>), Box> { + entries: &mut [Entry], + cryptocurrencies: &mut [Cryptocurrency], +) -> Result<(), Box> { let file = File::open(path)?; let mut rdr = csv::ReaderBuilder::new().from_reader(file); let headers = rdr.headers()?.clone(); - let mut cryptocurrencies: Vec = Vec::with_capacity(N_ASSETS); // Extracting cryptocurrency names from column names - for header in headers.iter().skip(1) { + for (i, header) in headers.iter().skip(1).enumerate() { // Skipping 'username' column let parts: Vec<&str> = header.split('_').collect(); if parts.len() == 3 && parts[0] == "balance" { - cryptocurrencies.push(Cryptocurrency { + cryptocurrencies[i] = Cryptocurrency { name: parts[1].to_owned(), chain: parts[2].to_owned(), - }); + }; } else { // Throw an error if the header is malformed return Err(format!("Invalid header: {}", header).into()); } } - let mut entries = Vec::new(); let mut balances_acc: Vec = vec![BigUint::from(0_usize); N_ASSETS]; - for result in rdr.deserialize() { + for (i, result) in rdr.deserialize().enumerate() { let record: HashMap = result?; let username = record.get("username").ok_or("Username not found")?.clone(); let mut balances_big_int = Vec::new(); - for cryptocurrency in &cryptocurrencies { + for cryptocurrency in &mut *cryptocurrencies { let balance_str = record .get(format!("balance_{}_{}", cryptocurrency.name, cryptocurrency.chain).as_str()) .ok_or(format!( @@ -60,7 +60,7 @@ pub fn parse_csv_to_entries, const N_ASSETS: usize, const N_BYTES .collect(); let entry = Entry::new(username, balances_big_int.try_into().unwrap())?; - entries.push(entry); + entries[i] = entry; } // Iterate through the balance accumulator and throw error if any balance is not in range 0, 2 ^ (8 * N_BYTES): @@ -73,5 +73,5 @@ pub fn parse_csv_to_entries, const N_ASSETS: usize, const N_BYTES } } - Ok((cryptocurrencies, entries)) + Ok(()) }