From 8082b600889409278c600cdcb2f555fc9959db28 Mon Sep 17 00:00:00 2001 From: Alex Kuzmin Date: Tue, 23 Apr 2024 19:01:20 +0800 Subject: [PATCH] Verify user inclusion in a test --- backend/Cargo.lock | 2 +- prover/Cargo.lock | 2 +- prover/Cargo.toml | 2 +- prover/src/chips/range/utils.rs | 6 +- prover/src/circuits/summa_circuit.rs | 9 +- prover/src/circuits/tests.rs | 183 ++++++++++++++++++-------- prover/src/utils/operation_helpers.rs | 54 +++++++- 7 files changed, 191 insertions(+), 67 deletions(-) diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f6a50e97..687679d1 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/han0110/plonkish#303cf244803ea56d1ac8c24829ec4c67e4e798ab" +source = "git+https://github.com/summa-dev/plonkish?branch=summa-changes#50093af18280f4e1efd79ac258ae9c65b9401999" dependencies = [ "bincode", "bitvec 1.0.1", diff --git a/prover/Cargo.lock b/prover/Cargo.lock index 97fe6344..9ddf20f5 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/han0110/plonkish#303cf244803ea56d1ac8c24829ec4c67e4e798ab" +source = "git+https://github.com/summa-dev/plonkish?branch=summa-changes#50093af18280f4e1efd79ac258ae9c65b9401999" dependencies = [ "bincode", "bitvec", diff --git a/prover/Cargo.toml b/prover/Cargo.toml index f469265c..f44bd0d1 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/han0110/plonkish", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } +plonkish_backend = { git = "https://github.com/summa-dev/plonkish", branch="summa-changes", package = "plonkish_backend", features= ["frontend-halo2", "benchmark"] } plotters = { version = "0.3.4", optional = true } rand = "0.8" csv = "1.1" diff --git a/prover/src/chips/range/utils.rs b/prover/src/chips/range/utils.rs index 058fbd4a..1c9fa41c 100644 --- a/prover/src/chips/range/utils.rs +++ b/prover/src/chips/range/utils.rs @@ -11,7 +11,7 @@ use crate::utils::{big_uint_to_fp, fp_to_big_uint}; /// Example: /// decompose_fp_to_bytes(0x1f2f3f, 2) -> [0x3f, 0x2f] pub fn decompose_fp_to_bytes(value: Fp, n: usize) -> Vec { - let value_biguint = fp_to_big_uint(value); + let value_biguint = fp_to_big_uint(&value); let mut bytes = value_biguint.to_bytes_le(); @@ -33,7 +33,7 @@ pub fn decompose_fp_to_bytes(value: Fp, n: usize) -> Vec { /// If value is decomposed in #byte pairs which are less than n, then the returned byte pairs are padded with 0s at the most significant byte pairs. /// If value is decomposed in #byte pairs which are greater than n, then the most significant byte pairs are truncated. A warning is printed. pub fn decompose_fp_to_byte_pairs(value: Fp, n: usize) -> Vec { - let value_biguint = fp_to_big_uint(value); + let value_biguint = fp_to_big_uint(&value); let mut bytes = value_biguint.to_bytes_le(); // Ensure the bytes vector has an even length for pairs of bytes. @@ -80,7 +80,7 @@ mod testing { #[test] fn test_fp_to_big_uint() { let f = Fp::from(5); - let big_uint = fp_to_big_uint(f); + let big_uint = fp_to_big_uint(&f); assert_eq!(big_uint, BigUint::from(5u8)); } diff --git a/prover/src/circuits/summa_circuit.rs b/prover/src/circuits/summa_circuit.rs index 2e2dfd0d..ea6e8bdb 100644 --- a/prover/src/circuits/summa_circuit.rs +++ b/prover/src/circuits/summa_circuit.rs @@ -150,11 +150,11 @@ pub mod summa_hyperplonk { running_sum_values.push(vec![]); region.assign_advice( - || "username", + || format!("username {}", i), config.username, i, || { - Value::known(big_uint_to_fp( + Value::known(big_uint_to_fp::( self.entries[i].username_as_big_uint(), )) }, @@ -163,7 +163,8 @@ pub mod summa_hyperplonk { let mut assigned_balances_row = vec![]; for (j, balance) in self.entries[i].balances().iter().enumerate() { - let balance_value = Value::known(big_uint_to_fp(balance)); + let balance_value: Value = + Value::known(big_uint_to_fp(balance)); let assigned_balance = region.assign_advice( || format!("balance {}", j), @@ -266,7 +267,7 @@ pub mod summa_hyperplonk { 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)); + instances.extend(self.grand_total.iter().map(big_uint_to_fp::)); vec![instances] } } diff --git a/prover/src/circuits/tests.rs b/prover/src/circuits/tests.rs index ba9c2f24..a93db350 100644 --- a/prover/src/circuits/tests.rs +++ b/prover/src/circuits/tests.rs @@ -1,67 +1,94 @@ +use halo2_proofs::arithmetic::Field; use plonkish_backend::{ - backend::{hyperplonk::HyperPlonk, PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo}, + backend::{hyperplonk::HyperPlonk, PlonkishBackend, PlonkishCircuit}, frontend::halo2::Halo2Circuit, halo2_curves::bn256::{Bn256, Fr as Fp}, pcs::{multilinear::MultilinearKzg, PolynomialCommitmentScheme}, - util::{ - arithmetic::PrimeField, - transcript::{InMemoryTranscript, Keccak256Transcript, TranscriptRead, TranscriptWrite}, - DeserializeOwned, Serialize, + util::transcript::{ + FieldTranscriptRead, FieldTranscriptWrite, InMemoryTranscript, Keccak256Transcript, }, Error::InvalidSumcheck, }; -use std::hash::Hash; use rand::{ rngs::{OsRng, StdRng}, - CryptoRng, RngCore, SeedableRng, + CryptoRng, Rng, RngCore, SeedableRng, }; use crate::{ - circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, utils::generate_dummy_entries, + circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, + utils::{ + big_uint_to_fp, fp_to_big_uint, generate_dummy_entries, uni_to_multivar_binary_index, + MultilinearAsUnivariate, + }, }; const K: u32 = 17; const N_CURRENCIES: usize = 2; const N_USERS: usize = 1 << 16; -pub fn run_plonkish_backend( - num_vars: usize, - circuit_fn: impl Fn(usize) -> (PlonkishCircuitInfo, C), -) where - F: PrimeField + Hash + Serialize + DeserializeOwned, - Pb: PlonkishBackend, - T: TranscriptRead<>::CommitmentChunk, F> - + TranscriptWrite<>::CommitmentChunk, F> - + InMemoryTranscript, - C: PlonkishCircuit, -{ - let (circuit_info, circuit) = circuit_fn(num_vars); +pub fn seeded_std_rng() -> impl RngCore + CryptoRng { + StdRng::seed_from_u64(OsRng.next_u64()) +} + +#[test] +fn test_summa_hyperplonk() { + type ProvingBackend = HyperPlonk>; + let entries = generate_dummy_entries::().unwrap(); + let circuit = SummaHyperplonk::::init(entries.to_vec()); + let num_vars = K; + + let circuit_fn = |num_vars| { + let circuit = Halo2Circuit::>::new::< + ProvingBackend, + >(num_vars, circuit.clone()); + (circuit.circuit_info().unwrap(), circuit) + }; + + let (circuit_info, circuit) = circuit_fn(num_vars as usize); let instances = circuit.instances(); - let param = Pb::setup(&circuit_info, seeded_std_rng()).unwrap(); + let param = ProvingBackend::setup(&circuit_info, seeded_std_rng()).unwrap(); + + let (prover_parameters, verifier_parameters) = + ProvingBackend::preprocess(¶m, &circuit_info).unwrap(); - let (pp, vp) = Pb::preprocess(¶m, &circuit_info).unwrap(); + let (witness_polys, proof_transcript) = { + let mut proof_transcript = Keccak256Transcript::new(()); - let proof = { - let mut transcript = T::new(()); - Pb::prove(&pp, &circuit, &mut transcript, seeded_std_rng()).unwrap(); - transcript.into_proof() + let witness_polys = ProvingBackend::prove( + &prover_parameters, + &circuit, + &mut proof_transcript, + seeded_std_rng(), + ) + .unwrap(); + (witness_polys, proof_transcript) }; - let result = { - let mut transcript = T::from_proof((), proof.as_slice()); - Pb::verify(&vp, instances, &mut transcript, seeded_std_rng()) + let num_points = 3; + + 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, Ok(())); let wrong_instances = instances[0] .iter() - .map(|instance| *instance + F::ONE) + .map(|instance| *instance + Fp::ONE) .collect::>(); let wrong_result = { - let mut transcript = T::from_proof((), proof.as_slice()); - Pb::verify( - &vp, + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + ProvingBackend::verify( + &verifier_parameters, &vec![wrong_instances], &mut transcript, seeded_std_rng(), @@ -73,28 +100,80 @@ pub fn run_plonkish_backend( "Consistency failure at round 1".to_string() )) ); -} -pub fn seeded_std_rng() -> impl RngCore + CryptoRng { - StdRng::seed_from_u64(OsRng.next_u64()) -} + //Create an evaluation challenge at a random "user index" + let fraction: f64 = rand::thread_rng().gen(); + let random_user_index = (fraction * (entries.len() as f64)) as usize; -#[test] -fn test_summa_hyperplonk() { - type Pb = HyperPlonk>; - let entries = generate_dummy_entries::().unwrap(); - let circuit = SummaHyperplonk::::init(entries.to_vec()); - let num_vars = K; - run_plonkish_backend::, _>( - num_vars.try_into().unwrap(), - |num_vars| { - let circuit = Halo2Circuit::>::new::( - num_vars, - circuit.clone(), - ); - (circuit.circuit_info().unwrap(), circuit) - }, + assert_eq!( + fp_to_big_uint(&witness_polys[0].evaluate_as_univariate(&random_user_index)), + *entries[random_user_index].username_as_big_uint() + ); + assert_eq!( + fp_to_big_uint(&witness_polys[1].evaluate_as_univariate(&random_user_index)), + entries[random_user_index].balances()[0] ); + + // Convert challenge into a multivariate form + let multivariate_challenge = + uni_to_multivar_binary_index(&random_user_index, num_vars as usize); + + let mut kzg_transcript = Keccak256Transcript::new(()); + + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + + let user_entry_commitments = MultilinearKzg::::read_commitments( + &verifier_parameters.pcs, + num_points, + &mut transcript, + ) + .unwrap(); + let user_entry_polynomials = witness_polys.iter().take(num_points).collect::>(); + + //Store the user index multi-variable in the transcript for the verifier + for binary_var in multivariate_challenge.iter() { + kzg_transcript.write_field_element(binary_var).unwrap(); + } + + MultilinearKzg::::open( + &prover_parameters.pcs, + user_entry_polynomials[0], + &user_entry_commitments[0], + &multivariate_challenge, + &user_entry_polynomials[0].evaluate(&multivariate_challenge), + &mut kzg_transcript, + ) + .unwrap(); + + let kzg_proof = kzg_transcript.into_proof(); + + // Verifier side + let mut kzg_transcript = Keccak256Transcript::from_proof((), kzg_proof.as_slice()); + + // The verifier knows the ZK-SNARK proof, can extract the polynomial commitments + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + let user_entry_commitments = MultilinearKzg::::read_commitments( + &verifier_parameters.pcs, + num_points, + &mut transcript, + ) + .unwrap(); + + //The verifier doesn't know the mapping of their "user index" to the multi-variable index, reads it from the transcript + let mut multivariate_challenge = Vec::new(); + for _ in 0..num_vars { + multivariate_challenge.push(kzg_transcript.read_field_element().unwrap()); + } + + MultilinearKzg::::verify( + &verifier_parameters.pcs, + &user_entry_commitments[0], + &multivariate_challenge, + //The user knows their evaluation at the challenge point + &big_uint_to_fp(entries[random_user_index].username_as_big_uint()), + &mut kzg_transcript, + ) + .unwrap(); } #[cfg(feature = "dev-graph")] diff --git a/prover/src/utils/operation_helpers.rs b/prover/src/utils/operation_helpers.rs index 81704c63..7e2f8f9e 100644 --- a/prover/src/utils/operation_helpers.rs +++ b/prover/src/utils/operation_helpers.rs @@ -1,5 +1,9 @@ -use halo2_proofs::halo2curves::{bn256::Fr as Fp, group::ff::PrimeField}; +use halo2_proofs::{arithmetic::Field, halo2curves::group::ff::PrimeField}; use num_bigint::BigUint; +use plonkish_backend::{ + poly::multilinear::MultilinearPolynomial, + util::expression::rotate::{BinaryField, Rotatable}, +}; /// Return a BigUint representation of the username pub fn big_intify_username(username: &str) -> BigUint { @@ -7,11 +11,51 @@ pub fn big_intify_username(username: &str) -> BigUint { BigUint::from_bytes_be(utf8_bytes) } /// Converts a BigUint to a Field Element -pub fn big_uint_to_fp(big_uint: &BigUint) -> Fp { - Fp::from_str_vartime(&big_uint.to_str_radix(10)[..]).unwrap() +pub fn big_uint_to_fp(big_uint: &BigUint) -> F { + F::from_str_vartime(&big_uint.to_str_radix(10)[..]).unwrap() } /// Converts a Field element to a BigUint -pub fn fp_to_big_uint(f: Fp) -> BigUint { - BigUint::from_bytes_le(f.to_bytes().as_slice()) +pub fn fp_to_big_uint(f: &F) -> BigUint { + BigUint::from_bytes_le(f.to_repr().as_ref()) +} + +/// Trait to evaluate a multilinear polynomial in binary field as a univariate polynomial +pub trait MultilinearAsUnivariate { + /// Evaluate the multilinear polynomial as a univariate polynomial + /// at the point x + fn evaluate_as_univariate(&self, x: &usize) -> F; +} + +impl MultilinearAsUnivariate for MultilinearPolynomial { + fn evaluate_as_univariate(&self, x: &usize) -> F { + let x_as_binary_vars = uni_to_multivar_binary_index(x, self.num_vars()); + self.evaluate(x_as_binary_vars.as_slice()) + } +} + +/// Converts a single-variable polynomial index into a multivariate index in the binary field +pub fn uni_to_multivar_binary_index(x: &usize, num_vars: usize) -> Vec { + //The binary field is necessary to map an index to an evaluation point + let binary_field = BinaryField::new(num_vars).usable_indices(); + //Mapping the univariate point index to a multivariate evaluation point + let x_in_binary_field = binary_field[*x]; + let x_as_big_uint = BigUint::from(x_in_binary_field); + let bits = x_as_big_uint.bits(); + let mut result = vec![]; + assert!( + bits <= num_vars as u64, + "Number of bits in x exceeds num_vars" + ); + + // Ensure that bits are extended to match num_vars with 0-padding + for i in 0..num_vars { + result.push(if x_as_big_uint.bit(i as u64) { + F::ONE + } else { + F::ZERO + }); + } + + result }