From 2e84f4041235ba995f8342456a264f7dd77bfa9b Mon Sep 17 00:00:00 2001 From: Alex Kuzmin <6849426+alxkzmn@users.noreply.github.com> Date: Mon, 13 May 2024 19:22:14 +0800 Subject: [PATCH] V3 benchmarks (#291) * Verify user inclusion in a test * Perform batch KZG opening/verification * Remove magic number * Add user inclusion benchmarks and verification benchmarks * Code review fixes --- .github/workflows/benchmark.yml | 6 +- prover/benches/proof_of_liabilities.rs | 167 +++++++++++++++++++++---- prover/src/chips/range/range_check.rs | 2 +- prover/src/chips/range/utils.rs | 68 +--------- 4 files changed, 152 insertions(+), 91 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 6843de34..5d0762b2 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -4,9 +4,9 @@ on: workflow_dispatch: inputs: run_benchmark: - description: 'Run benchmark tests (yes/no)' + description: "Please confirm running the benchmarks by typing 'yes' in the input box." required: true - default: 'no' + default: "no" jobs: wakeup: @@ -26,7 +26,7 @@ jobs: aws-region: us-west-2 - name: Wakeup runner - run: .github/scripts/wakeup.sh + run: .github/scripts/wakeup.sh benchmark: runs-on: [summa-solvency-runner] diff --git a/prover/benches/proof_of_liabilities.rs b/prover/benches/proof_of_liabilities.rs index b47d2075..5e866eaf 100644 --- a/prover/benches/proof_of_liabilities.rs +++ b/prover/benches/proof_of_liabilities.rs @@ -3,7 +3,7 @@ use plonkish_backend::{ backend::{hyperplonk::HyperPlonk, PlonkishBackend, PlonkishCircuit, PlonkishCircuitInfo}, frontend::halo2::Halo2Circuit, halo2_curves::bn256::{Bn256, Fr as Fp}, - pcs::multilinear::MultilinearKzg, + pcs::{multilinear::MultilinearKzg, Evaluation, PolynomialCommitmentScheme}, util::{ test::std_rng, transcript::{InMemoryTranscript, Keccak256Transcript}, @@ -11,10 +11,11 @@ use plonkish_backend::{ }; use rand::{ rngs::{OsRng, StdRng}, - CryptoRng, RngCore, SeedableRng, + CryptoRng, Rng, RngCore, SeedableRng, }; use summa_hyperplonk::{ - circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, utils::generate_dummy_entries, + circuits::summa_circuit::summa_hyperplonk::SummaHyperplonk, + utils::{big_uint_to_fp, generate_dummy_entries, uni_to_multivar_binary_index}, }; fn bench_summa() { @@ -22,38 +23,30 @@ fn bench_summa() let mut c = Criterion::default().sample_size(10); let grand_sum_proof_bench_name = format!("<{}> grand sum proof", name); + let inclusion_proof_bench_name = format!("<{}> user inclusion proof", name); - type Pb = HyperPlonk>; + let grand_sum_verification_bench_name = format!("<{}> grand sum verification", name); + let inclusion_verification_bench_name = format!("<{}> user inclusion verification", name); + + type ProvingBackend = HyperPlonk>; let entries = generate_dummy_entries::().unwrap(); let halo2_circuit = SummaHyperplonk::::init(entries.to_vec()); - let circuit = Halo2Circuit::>::new::( + let circuit = Halo2Circuit::>::new::( K as usize, halo2_circuit.clone(), ); let circuit_info: PlonkishCircuitInfo<_> = circuit.circuit_info().unwrap(); 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 (pp, vp) = Pb::preprocess(¶m, &circuit_info).unwrap(); - - let mut transcript = Keccak256Transcript::default(); - let proof = { - Pb::prove(&pp, &circuit, &mut transcript, std_rng()).unwrap(); - transcript.into_proof() - }; - - let accept = { - let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); - Pb::verify(&vp, instances, &mut transcript, std_rng()).is_ok() - }; - assert!(accept); + let (pp, vp) = ProvingBackend::preprocess(¶m, &circuit_info).unwrap(); c.bench_function(&grand_sum_proof_bench_name, |b| { b.iter_batched( || { - Halo2Circuit::>::new::( + Halo2Circuit::>::new::( K as usize, halo2_circuit.clone(), ) @@ -61,20 +54,146 @@ fn bench_summa() |circuit| { let mut transcript = Keccak256Transcript::default(); - Pb::prove(&pp, &circuit, &mut transcript, std_rng()).unwrap(); + ProvingBackend::prove(&pp, &circuit, &mut transcript, std_rng()).unwrap(); transcript.into_proof(); }, - criterion::BatchSize::SmallInput, // Choose an appropriate batch size + criterion::BatchSize::LargeInput, + ) + }); + + let (prover_parameters, verifier_parameters) = + ProvingBackend::preprocess(¶m, &circuit_info).unwrap(); + + let (witness_polys, _) = { + 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 num_points = N_CURRENCIES + 1; + let user_entry_polynomials = witness_polys.iter().take(num_points).collect::>(); + + let mut transcript = Keccak256Transcript::default(); + let proof = { + ProvingBackend::prove(&pp, &circuit, &mut transcript, std_rng()).unwrap(); + transcript.into_proof() + }; + + let mut transcript = Keccak256Transcript::from_proof((), proof.as_slice()); + + let user_entry_commitments = MultilinearKzg::::read_commitments( + &verifier_parameters.pcs, + num_points, + &mut transcript, + ) + .unwrap(); + + //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; + + let num_vars = K; + + let multivariate_challenge = + uni_to_multivar_binary_index(&random_user_index, num_vars as usize); + + let mut evals = vec![]; + + for i in 0..N_CURRENCIES + 1 { + if i == 0 { + evals.push(Evaluation::new( + i, + 0, + big_uint_to_fp::(entries[random_user_index].username_as_big_uint()), + )); + } else { + evals.push(Evaluation::new( + i, + 0, + big_uint_to_fp::(&entries[random_user_index].balances()[i - 1]), + )); + } + } + + c.bench_function(&inclusion_proof_bench_name, |b| { + b.iter_batched( + || { + ( + user_entry_polynomials.clone(), + multivariate_challenge.clone(), + ) + }, + |(user_entry_polynomials, multivariate_challenge)| { + let mut kzg_transcript = Keccak256Transcript::new(()); + MultilinearKzg::::batch_open( + &prover_parameters.pcs, + user_entry_polynomials, + &user_entry_commitments, + &[multivariate_challenge], + &evals, + &mut kzg_transcript, + ) + .unwrap(); + }, + criterion::BatchSize::LargeInput, + ) + }); + + c.bench_function(&grand_sum_verification_bench_name, |b| { + b.iter_batched( + || (Keccak256Transcript::from_proof((), proof.as_slice())), + |mut transcript| { + let accept = + { ProvingBackend::verify(&vp, instances, &mut transcript, std_rng()).is_ok() }; + assert!(accept); + }, + criterion::BatchSize::LargeInput, + ) + }); + + c.bench_function(&inclusion_verification_bench_name, |b| { + b.iter_batched( + || { + let mut kzg_transcript = Keccak256Transcript::new(()); + MultilinearKzg::::batch_open( + &prover_parameters.pcs, + user_entry_polynomials.clone(), + &user_entry_commitments, + &[multivariate_challenge.clone()], + &evals, + &mut kzg_transcript, + ) + .unwrap(); + (kzg_transcript.into_proof(), multivariate_challenge.clone()) + }, + |(kzg_proof, multivariate_challenge)| { + let mut kzg_transcript = Keccak256Transcript::from_proof((), kzg_proof.as_slice()); + MultilinearKzg::::batch_verify( + &verifier_parameters.pcs, + &user_entry_commitments, + &[multivariate_challenge], + &evals, + &mut kzg_transcript, + ) + .unwrap(); + }, + criterion::BatchSize::LargeInput, ) }); } fn criterion_benchmark(_c: &mut Criterion) { - const N_CURRENCIES: usize = 2; + const N_CURRENCIES: usize = 1; { const K: u32 = 17; - const N_USERS: usize = 1 << 16 as usize; + const N_USERS: usize = (1 << K as usize) - 6; bench_summa::(); } } diff --git a/prover/src/chips/range/range_check.rs b/prover/src/chips/range/range_check.rs index c814f211..26598aa4 100644 --- a/prover/src/chips/range/range_check.rs +++ b/prover/src/chips/range/range_check.rs @@ -137,7 +137,7 @@ impl RangeCheckU64Chip { let ks = element .value() .copied() - .map(|x| decompose_fp_to_byte_pairs(x, 4)) + .map(|x| decompose_fp_to_byte_pairs(&x, 4)) .transpose_vec(4); // Initialize an empty vector of cells for the truncated right-shifted values of the element to be checked. diff --git a/prover/src/chips/range/utils.rs b/prover/src/chips/range/utils.rs index 1c9fa41c..48d798d7 100644 --- a/prover/src/chips/range/utils.rs +++ b/prover/src/chips/range/utils.rs @@ -3,37 +3,11 @@ use num_bigint::BigUint; use crate::utils::{big_uint_to_fp, fp_to_big_uint}; -/// Converts value Fp to n bytes of bytes in little endian order. -/// If value is decomposed in #bytes which are less than n, then the returned bytes are padded with 0s at the most significant bytes. -/// Example: -/// decompose_fp_to_bytes(0x1f2f3f, 4) -> [0x3f, 0x2f, 0x1f, 0x00] -/// If value is decomposed in #bytes which are greater than n, then the most significant bytes are truncated. A warning is printed. -/// 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 mut bytes = value_biguint.to_bytes_le(); - - // Pad with 0s at the most significant bytes if bytes length is less than n. - while bytes.len() < n { - bytes.push(0); - } - - // If the bytes length exceeds n, print a warning and truncate the byte array at the most significant bytes. - if bytes.len() > n { - println!("Warning: `decompose_fp_to_bytes` value is decomposed in #bytes which are greater than n. Truncating the output to fit the specified length."); - bytes.truncate(n); - } - - bytes -} - /// Converts value Fp to array of n byte pairs in little endian order. /// 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); +pub fn decompose_fp_to_byte_pairs(value: &Fp, n: usize) -> Vec { + 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. @@ -84,62 +58,30 @@ mod testing { assert_eq!(big_uint, BigUint::from(5u8)); } - // convert a 32 bit number in 4 bytes. Should correctly convert to 4 bytes - #[test] - fn test_decompose_fp_to_bytes_no_padding() { - let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_bytes(f, 4); - assert_eq!(bytes, vec![0x4f, 0x3f, 0x2f, 0x1f]); - } - // convert a 32 bit number in 2 byte pairs. Should correctly convert to 2 byte pairs #[test] fn test_decompose_fp_byte_pairs_no_padding() { let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_byte_pairs(f, 2); + let bytes = decompose_fp_to_byte_pairs(&f, 2); assert_eq!(bytes, vec![0x3f4f, 0x1f2f]); } - // convert a 32 bit number in 6 bytes. Should correctly convert to 6 bytes in which the last 2 bytes are 0 padded. - #[test] - fn test_decompose_fp_to_bytes_padding() { - let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_bytes(f, 6); - assert_eq!(bytes, vec![0x4f, 0x3f, 0x2f, 0x1f, 0x00, 0x00]); - } - // convert a 32 bit number in 3 byte pairs. Should correctly convert to 3 byte pairs in which the last pair is 0 padded. #[test] fn test_decompose_fp_to_byte_pairs_padding() { let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_byte_pairs(f, 3); + let bytes = decompose_fp_to_byte_pairs(&f, 3); assert_eq!(bytes, vec![0x3f4f, 0x1f2f, 0x00]); } - // convert a 32 bit number in 2 bytes. Should convert to 2 bytes and truncate the most significant bytes and emit a warning - #[test] - fn test_decompose_fp_to_bytes_overflow() { - let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_bytes(f, 2); - assert_eq!(bytes, vec![0x4f, 0x3f]); - } - // convert a 32 bit number in 1 byte pair. Should convert to a byte pair and truncate the most significant byte pair and emit a warning #[test] fn test_decompose_fp_to_byte_pairs_overflow() { let f = Fp::from(0x1f2f3f4f); - let bytes = decompose_fp_to_byte_pairs(f, 1); + let bytes = decompose_fp_to_byte_pairs(&f, 1); assert_eq!(bytes, vec![0x3f4f]); } - // convert a 40 bit number in 2 bytes. Should convert to 2 most significant bytes and truncate the least significant byte - #[test] - fn test_decompose_fp_to_bytes_overflow_2() { - let f = Fp::from(0xf1f2f3f); - let bytes = decompose_fp_to_bytes(f, 2); - assert_eq!(bytes, vec![0x3f, 0x2f]); - } - #[test] fn test_pow_2() { let pow = pow_of_two(8);