diff --git a/.github/workflows/wasm_client_benchmark.yml b/.github/workflows/wasm_client_benchmark.yml new file mode 100644 index 000000000..5184a18eb --- /dev/null +++ b/.github/workflows/wasm_client_benchmark.yml @@ -0,0 +1,115 @@ +# Run WASM client benchmarks on an AWS instance and return parsed results to Slab CI bot. +name: WASM client benchmarks + +on: + workflow_dispatch: + inputs: + instance_id: + description: "Instance ID" + type: string + instance_image_id: + description: "Instance AMI ID" + type: string + instance_type: + description: "Instance product type" + type: string + runner_name: + description: "Action runner name" + type: string + request_id: + description: "Slab request ID" + type: string + +env: + CARGO_TERM_COLOR: always + RESULTS_FILENAME: parsed_benchmark_results_${{ github.sha }}.json + +jobs: + run-wasm-client-benchmarks: + name: Execute WASM client benchmarks in EC2 + runs-on: ${{ github.event.inputs.runner_name }} + if: ${{ !cancelled() }} + steps: + - name: Instance configuration used + run: | + echo "IDs: ${{ inputs.instance_id }}" + echo "AMI: ${{ inputs.instance_image_id }}" + echo "Type: ${{ inputs.instance_type }}" + echo "Request ID: ${{ inputs.request_id }}" + + - name: Get benchmark date + run: | + echo "BENCH_DATE=$(date --iso-8601=seconds)" >> "${GITHUB_ENV}" + + - name: Checkout tfhe-rs repo with tags + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + with: + fetch-depth: 0 + + - name: Set up home + # "Install rust" step require root user to have a HOME directory which is not set. + run: | + echo "HOME=/home/ubuntu" >> "${GITHUB_ENV}" + + - name: Install rust + uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af + with: + toolchain: nightly + override: true + + - name: Run benchmarks + run: | + make install_node + make bench_web_js_api_parallel + + - name: Parse results + run: | + make parse_wasm_benchmarks + + COMMIT_DATE="$(git --no-pager show -s --format=%cd --date=iso8601-strict ${{ github.sha }})" + COMMIT_HASH="$(git describe --tags --dirty)" + python3 ./ci/benchmark_parser.py tfhe/web_wasm_parallel_tests/public_key_ct_generation_timings.csv ${{ env.RESULTS_FILENAME }} \ + --database tfhe_rs \ + --hardware ${{ inputs.instance_type }} \ + --project-version "${COMMIT_HASH}" \ + --branch ${{ github.ref_name }} \ + --commit-date "${COMMIT_DATE}" \ + --bench-date "${{ env.BENCH_DATE }}" \ + --key-gen + + - name: Measure public key and ciphertext sizes in HL Api + run: | + make measure_hlapi_compact_pk_ct_sizes + + - name: Parse key and ciphertext sizes results + run: | + python3 ./ci/benchmark_parser.py tfhe/hlapi_cpk_and_cctl_sizes.csv ${{ env.RESULTS_FILENAME }} \ + --key-gen \ + --append-results + + - name: Upload parsed results artifact + uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce + with: + name: ${{ github.sha }}_wasm + path: ${{ env.RESULTS_FILENAME }} + + - name: Checkout Slab repo + uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab + with: + repository: zama-ai/slab + path: slab + token: ${{ secrets.CONCRETE_ACTIONS_TOKEN }} + + - name: Send data to Slab + shell: bash + run: | + echo "Computing HMac on results file" + SIGNATURE="$(slab/scripts/hmac_calculator.sh ${{ env.RESULTS_FILENAME }} '${{ secrets.JOB_SECRET }}')" + echo "Sending results to Slab..." + curl -v -k \ + -H "Content-Type: application/json" \ + -H "X-Slab-Repository: ${{ github.repository }}" \ + -H "X-Slab-Command: store_data_v2" \ + -H "X-Hub-Signature-256: sha256=${SIGNATURE}" \ + -d @${{ env.RESULTS_FILENAME }} \ + ${{ secrets.SLAB_URL }} diff --git a/.gitignore b/.gitignore index d11c01282..043239f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,4 @@ target/ # Some of our bench outputs /tfhe/benchmarks_parameters -/tfhe/shortint_key_sizes.csv +**/*.csv diff --git a/Makefile b/Makefile index 08e3c6635..419452125 100644 --- a/Makefile +++ b/Makefile @@ -370,6 +370,10 @@ ci_test_web_js_api_parallel: build_web_js_api_parallel no_tfhe_typo: @./scripts/no_tfhe_typo.sh +# +# Benchmarks +# + .PHONY: bench_integer # Run benchmarks for integer bench_integer: install_rs_check_toolchain RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) bench \ @@ -394,23 +398,54 @@ bench_pbs: install_rs_check_toolchain --bench pbs-bench \ --features=$(TARGET_ARCH_FEATURE),boolean,shortint,internal-keycache,$(AVX512_FEATURE) -p tfhe +.PHONY: bench_web_js_api_parallel # Run benchmarks for the web wasm api +bench_web_js_api_parallel: build_web_js_api_parallel + $(MAKE) -C tfhe/web_wasm_parallel_tests bench + +.PHONY: ci_bench_web_js_api_parallel # Run benchmarks for the web wasm api +ci_bench_web_js_api_parallel: build_web_js_api_parallel + source ~/.nvm/nvm.sh && \ + nvm use node && \ + $(MAKE) -C tfhe/web_wasm_parallel_tests bench-ci + +# +# Utility tools +# + +.PHONY: measure_hlapi_compact_pk_ct_sizes # Measure sizes of public keys and ciphertext for high-level API +measure_hlapi_compact_pk_ct_sizes: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ + --example hlapi_compact_pk_ct_sizes \ + --features=$(TARGET_ARCH_FEATURE),integer,internal-keycache + +.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint +measure_shortint_key_sizes: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ + --example shortint_key_sizes \ + --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache + +.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean +measure_boolean_key_sizes: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ + --example boolean_key_sizes \ + --features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache + .PHONY: parse_integer_benches # Run python parser to output a csv containing integer benches data parse_integer_benches: python3 ./ci/parse_integer_benches_to_csv.py \ --criterion-dir target/criterion \ --output-file "$(PARSE_INTEGER_BENCH_CSV_FILE)" -.PHONY: measure_shortint_key_sizes # Measure sizes of bootstrapping and key switching keys for shortint -measure_shortint_key_sizes: install_rs_check_toolchain - RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \ - --example shortint_key_sizes \ - --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache +.PHONY: parse_wasm_benchmarks # Parse benchmarks performed with WASM web client into a CSV file +parse_wasm_benchmarks: install_rs_check_toolchain + RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run --profile $(CARGO_PROFILE) \ + --example wasm_benchmarks_parser \ + --features=$(TARGET_ARCH_FEATURE),shortint,internal-keycache \ + -- tfhe/web_wasm_parallel_tests/test/benchmark_results -.PHONY: measure_boolean_key_sizes # Measure sizes of bootstrapping and key switching keys for boolean -measure_boolean_key_sizes: install_rs_check_toolchain - RUSTFLAGS="$(RUSTFLAGS)" cargo $(CARGO_RS_CHECK_TOOLCHAIN) run \ - --example boolean_key_sizes \ - --features=$(TARGET_ARCH_FEATURE),boolean,internal-keycache +# +# Real use case examples +# .PHONY: regex_engine # Run regex_engine example regex_engine: install_rs_check_toolchain diff --git a/ci/benchmark_parser.py b/ci/benchmark_parser.py index a1501b868..29fdf7e05 100644 --- a/ci/benchmark_parser.py +++ b/ci/benchmark_parser.py @@ -39,6 +39,8 @@ parser.add_argument('--walk-subdirs', dest='walk_subdirs', action='store_true', help='Check for results in subdirectories') parser.add_argument('--key-sizes', dest='key_sizes', action='store_true', help='Parse only the results regarding keys size measurements') +parser.add_argument('--key-gen', dest='key_gen', action='store_true', + help='Parse only the results regarding keys generation time measurements') parser.add_argument('--throughput', dest='throughput', action='store_true', help='Compute and append number of operations per second and' 'operations per dollar') @@ -177,9 +179,9 @@ def parse_estimate_file(directory): } -def parse_key_sizes(result_file): +def _parse_key_results(result_file, bench_type): """ - Parse file containing key sizes results. The file must be formatted as CSV. + Parse file containing results about operation on keys. The file must be formatted as CSV. :param result_file: results file as :class:`pathlib.Path` @@ -202,13 +204,35 @@ def parse_key_sizes(result_file): "test": test_name, "name": display_name, "class": "keygen", - "type": "keysize", + "type": bench_type, "operator": operator, "params": params}) return result_values, parsing_failures +def parse_key_sizes(result_file): + """ + Parse file containing key sizes results. The file must be formatted as CSV. + + :param result_file: results file as :class:`pathlib.Path` + + :return: tuple of :class:`list` as (data points, parsing failures) + """ + return _parse_key_results(result_file, "keysize") + + +def parse_key_gen_time(result_file): + """ + Parse file containing key generation time results. The file must be formatted as CSV. + + :param result_file: results file as :class:`pathlib.Path` + + :return: tuple of :class:`list` as (data points, parsing failures) + """ + return _parse_key_results(result_file, "latency") + + def get_parameters(bench_id): """ Get benchmarks parameters recorded for a given benchmark case. @@ -301,7 +325,7 @@ def check_mandatory_args(input_args): for arg_name in vars(input_args): if arg_name in ["results_dir", "output_file", "name_suffix", "append_results", "walk_subdirs", "key_sizes", - "throughput"]: + "key_gen", "throughput"]: continue if not getattr(input_args, arg_name): missing_args.append(arg_name) @@ -318,7 +342,15 @@ if __name__ == "__main__": #failures = [] raw_results = pathlib.Path(args.results) - if not args.key_sizes: + if args.key_sizes or args.key_gen: + if args.key_sizes: + print("Parsing key sizes results... ") + results, failures = parse_key_sizes(raw_results) + + if args.key_gen: + print("Parsing key generation time results... ") + results, failures = parse_key_gen_time(raw_results) + else: print("Parsing benchmark results... ") hardware_cost = None if args.throughput: @@ -334,9 +366,7 @@ if __name__ == "__main__": results, failures = recursive_parse(raw_results, args.walk_subdirs, args.name_suffix, args.throughput, hardware_cost) - else: - print("Parsing key sizes results... ") - results, failures = parse_key_sizes(raw_results) + print("Parsing results done") output_file = pathlib.Path(args.output_file) diff --git a/tfhe/Cargo.toml b/tfhe/Cargo.toml index 8eb8b1755..869b4f027 100644 --- a/tfhe/Cargo.toml +++ b/tfhe/Cargo.toml @@ -10,7 +10,13 @@ repository = "https://github.com/zama-ai/tfhe-rs" license = "BSD-3-Clause-Clear" description = "TFHE-rs is a fully homomorphic encryption (FHE) library that implements Zama's variant of TFHE." build = "build.rs" -exclude = ["/docs/", "/c_api_tests/", "/CMakeLists.txt", "/js_on_wasm_tests/"] +exclude = [ + "/docs/", + "/c_api_tests/", + "/CMakeLists.txt", + "/js_on_wasm_tests/", + "/web_wasm_parallel_tests/", +] rust-version = "1.67" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -23,7 +29,7 @@ lazy_static = { version = "1.4.0" } criterion = "0.4.0" doc-comment = "0.3.3" serde_json = "1.0.94" -clap = "4.2.7" +clap = { version = "4.2.7", features = ["derive"] } # Used in user documentation bincode = "1.3.3" fs2 = { version = "0.4.3" } @@ -83,12 +89,7 @@ experimental-force_fft_algo_dif4 = [] __c_api = ["cbindgen", "bincode"] boolean-c-api = ["boolean", "__c_api"] shortint-c-api = ["shortint", "__c_api"] -high-level-c-api = [ - "boolean-c-api", - "shortint-c-api", - "integer", - "__c_api" -] +high-level-c-api = ["boolean-c-api", "shortint-c-api", "integer", "__c_api"] __wasm_api = [ "wasm-bindgen", @@ -189,30 +190,44 @@ path = "benches/utilities.rs" harness = false required-features = ["boolean", "shortint", "integer", "internal-keycache"] +# Examples used as tools + +[[example]] +name = "wasm_benchmarks_parser" +path = "examples/utilities/wasm_benchmarks_parser.rs" +required-features = ["shortint", "internal-keycache"] + [[example]] name = "generates_test_keys" +path = "examples/utilities/generates_test_keys.rs" required-features = ["shortint", "internal-keycache"] [[example]] name = "boolean_key_sizes" +path = "examples/utilities/boolean_key_sizes.rs" required-features = ["boolean", "internal-keycache"] -[[example]] -name = "dark_market" -required-features = ["integer", "internal-keycache"] - [[example]] name = "shortint_key_sizes" +path = "examples/utilities/shortint_key_sizes.rs" required-features = ["shortint", "internal-keycache"] [[example]] -name = "integer_compact_pk_ct_sizes" +name = "hlapi_compact_pk_ct_sizes" +path = "examples/utilities/hlapi_compact_pk_ct_sizes.rs" required-features = ["integer", "internal-keycache"] [[example]] name = "micro_bench_and" +path = "examples/utilities/micro_bench_and.rs" required-features = ["boolean"] +# Real use-case examples + +[[example]] +name = "dark_market" +required-features = ["integer", "internal-keycache"] + [[example]] name = "regex_engine" required-features = ["integer"] diff --git a/tfhe/examples/integer_compact_pk_ct_sizes.rs b/tfhe/examples/integer_compact_pk_ct_sizes.rs deleted file mode 100644 index c9db6293e..000000000 --- a/tfhe/examples/integer_compact_pk_ct_sizes.rs +++ /dev/null @@ -1,92 +0,0 @@ -use rand::Rng; - -use tfhe::core_crypto::commons::numeric::Numeric; -use tfhe::integer::block_decomposition::{DecomposableInto, RecomposableFrom}; -use tfhe::integer::public_key::{CompactPublicKeyBig, CompactPublicKeySmall}; -use tfhe::integer::{gen_keys, U256}; -use tfhe::shortint::keycache::NamedParam; -use tfhe::shortint::parameters::*; - -pub fn main() { - fn size_func + RecomposableFrom + From>() { - let mut rng = rand::thread_rng(); - let num_bits = Scalar::BITS; - - let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK; - { - println!("Sizes for: {} and {num_bits} bits", params.name()); - let (cks, _) = gen_keys(params); - let pk = CompactPublicKeyBig::new(&cks); - - println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len()); - - let num_block = - (num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize; - - const MAX_CT: usize = 20; - - let mut clear_vec = Vec::with_capacity(MAX_CT); - // 5 inputs to a smart contract - let num_ct_for_this_iter = 5; - clear_vec.truncate(0); - for _ in 0..num_ct_for_this_iter { - let clear = rng.gen::(); - clear_vec.push(Scalar::from(clear)); - } - - let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block); - - println!( - "Compact CT list for {num_ct_for_this_iter} CTs: {} bytes", - bincode::serialize(&compact_encrypted_list).unwrap().len() - ); - - let ciphertext_vec = compact_encrypted_list.expand(); - - for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) { - let decrypted: Scalar = cks.decrypt_radix(ciphertext); - assert_eq!(decrypted, clear); - } - } - - let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK; - { - println!("Sizes for: {} and {num_bits} bits", params.name()); - let (cks, _) = gen_keys(params); - let pk = CompactPublicKeySmall::new(&cks); - - println!("PK size: {} bytes", bincode::serialize(&pk).unwrap().len()); - - let num_block = - (num_bits as f64 / (params.message_modulus.0 as f64).log(2.0)).ceil() as usize; - - const MAX_CT: usize = 20; - - let mut clear_vec = Vec::with_capacity(MAX_CT); - // 5 inputs to a smart contract - let num_ct_for_this_iter = 5; - clear_vec.truncate(0); - for _ in 0..num_ct_for_this_iter { - let clear = rng.gen::(); - clear_vec.push(Scalar::from(clear)); - } - - let compact_encrypted_list = pk.encrypt_slice_radix_compact(&clear_vec, num_block); - - println!( - "Compact CT list for {num_ct_for_this_iter} CTs: {} bytes", - bincode::serialize(&compact_encrypted_list).unwrap().len() - ); - - let ciphertext_vec = compact_encrypted_list.expand(); - - for (ciphertext, clear) in ciphertext_vec.iter().zip(clear_vec.iter().copied()) { - let decrypted: Scalar = cks.decrypt_radix(ciphertext); - assert_eq!(decrypted, clear); - } - } - } - - size_func::(); - size_func::(); -} diff --git a/tfhe/examples/boolean_key_sizes.rs b/tfhe/examples/utilities/boolean_key_sizes.rs similarity index 85% rename from tfhe/examples/boolean_key_sizes.rs rename to tfhe/examples/utilities/boolean_key_sizes.rs index 38a006387..cb412f916 100644 --- a/tfhe/examples/boolean_key_sizes.rs +++ b/tfhe/examples/utilities/boolean_key_sizes.rs @@ -1,4 +1,4 @@ -#[path = "../benches/utilities.rs"] +#[path = "../../benches/utilities.rs"] mod utilities; use crate::utilities::{write_to_json, OperatorType}; @@ -81,13 +81,6 @@ fn client_server_key_sizes(results_file: &Path) { } fn main() { - let work_dir = std::env::current_dir().unwrap(); - println!("work_dir: {}", std::env::current_dir().unwrap().display()); - // Change workdir so that the location of the keycache matches the one for tests - let mut new_work_dir = work_dir; - new_work_dir.push("tfhe"); - std::env::set_current_dir(new_work_dir).unwrap(); - - let results_file = Path::new("boolean_key_sizes.csv"); + let results_file = Path::new("tfhe/boolean_key_sizes.csv"); client_server_key_sizes(results_file) } diff --git a/tfhe/examples/generates_test_keys.rs b/tfhe/examples/utilities/generates_test_keys.rs similarity index 100% rename from tfhe/examples/generates_test_keys.rs rename to tfhe/examples/utilities/generates_test_keys.rs diff --git a/tfhe/examples/utilities/hlapi_compact_pk_ct_sizes.rs b/tfhe/examples/utilities/hlapi_compact_pk_ct_sizes.rs new file mode 100644 index 000000000..c17ddda4f --- /dev/null +++ b/tfhe/examples/utilities/hlapi_compact_pk_ct_sizes.rs @@ -0,0 +1,259 @@ +#[path = "../../benches/utilities.rs"] +mod utilities; +use crate::utilities::{write_to_json, OperatorType}; + +use rand::Rng; +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::Path; +use tfhe::integer::U256; +use tfhe::prelude::*; +use tfhe::shortint::keycache::NamedParam; +use tfhe::shortint::parameters::{ + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, +}; +use tfhe::{ + generate_keys, CompactFheUint256List, CompactFheUint32List, CompactPublicKey, ConfigBuilder, +}; + +fn write_result(file: &mut File, name: &str, value: usize) { + let line = format!("{name},{value}\n"); + let error_message = format!("cannot write {name} result into file"); + file.write_all(line.as_bytes()).expect(&error_message); +} + +pub fn cpk_and_cctl_sizes(results_file: &Path) { + const NB_CTXT: usize = 5; + + let mut rng = rand::thread_rng(); + + File::create(results_file).expect("create results file failed"); + let mut file = OpenOptions::new() + .create(true) + .truncate(true) + .write(true) + .open(results_file) + .expect("cannot open results file"); + + let operator = OperatorType::Atomic; + + { + let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK; + let config = ConfigBuilder::all_disabled() + .enable_custom_integers(params, None) + .build(); + let (client_key, _) = generate_keys(config); + let test_name = format!("hlapi_sizes_{}_cpk", params.name()); + + println!("Sizes for: {} and 32 bits", params.name()); + + let public_key = CompactPublicKey::new(&client_key); + + let cpk_size = bincode::serialize(&public_key).unwrap().len(); + + println!("PK size: {cpk_size} bytes"); + write_result(&mut file, &test_name, cpk_size); + write_to_json( + &test_name, + params, + params.name(), + "CPK", + &operator, + 0, + vec![], + ); + + let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_32_bits", params.name()); + + let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::()).collect(); + + let encrypted_inputs = CompactFheUint32List::encrypt(&vec_inputs, &public_key); + let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len(); + + println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size); + + write_result(&mut file, &test_name, cctl_size); + write_to_json( + &test_name, + params, + params.name(), + "CCTL", + &operator, + 0, + vec![], + ); + + let expanded_inputs = encrypted_inputs.expand(); + + vec_inputs + .iter() + .zip(expanded_inputs.iter()) + .for_each(|(&input, ct)| { + let clear: u32 = ct.decrypt(&client_key); + assert_eq!(clear, input); + }); + } + + { + let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK; + let config = ConfigBuilder::all_disabled() + .enable_custom_integers(params, None) + .build(); + let (client_key, _) = generate_keys(config); + let test_name = format!("hlapi_sizes_{}_cpk", params.name()); + + println!("Sizes for: {} and 32 bits", params.name()); + let public_key = CompactPublicKey::new(&client_key); + + let cpk_size = bincode::serialize(&public_key).unwrap().len(); + + println!("PK size: {cpk_size} bytes"); + write_result(&mut file, &test_name, cpk_size); + write_to_json( + &test_name, + params, + params.name(), + "CPK", + &operator, + 0, + vec![], + ); + + let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_32_bits", params.name()); + + let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::()).collect(); + + let encrypted_inputs = CompactFheUint32List::encrypt(&vec_inputs, &public_key); + let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len(); + + println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size); + + write_result(&mut file, &test_name, cctl_size); + write_to_json( + &test_name, + params, + params.name(), + "CCTL", + &operator, + 0, + vec![], + ); + + let expanded_inputs = encrypted_inputs.expand(); + + vec_inputs + .iter() + .zip(expanded_inputs.iter()) + .for_each(|(&input, ct)| { + let clear: u32 = ct.decrypt(&client_key); + assert_eq!(clear, input); + }); + } + + // 256 bits + { + let params = PARAM_MESSAGE_2_CARRY_2_COMPACT_PK; + let config = ConfigBuilder::all_disabled() + .enable_custom_integers(params, None) + .build(); + let (client_key, _) = generate_keys(config); + + println!("Sizes for: {} and 256 bits", params.name()); + + let public_key = CompactPublicKey::new(&client_key); + + println!( + "PK size: {} bytes", + bincode::serialize(&public_key).unwrap().len() + ); + + let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_256_bits", params.name()); + + let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::()).collect(); + + let encrypted_inputs = CompactFheUint256List::encrypt(&vec_inputs, &public_key); + let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len(); + + println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size); + + write_result(&mut file, &test_name, cctl_size); + write_to_json( + &test_name, + params, + params.name(), + "CCTL", + &operator, + 0, + vec![], + ); + + let expanded_inputs = encrypted_inputs.expand(); + + vec_inputs + .iter() + .zip(expanded_inputs.iter()) + .for_each(|(&input, ct)| { + let clear: U256 = ct.decrypt(&client_key); + assert_eq!(clear, U256::from(input)); + }); + } + + { + let params = PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK; + let config = ConfigBuilder::all_disabled() + .enable_custom_integers(params, None) + .build(); + let (client_key, _) = generate_keys(config); + + println!("Sizes for: {} and 256 bits", params.name()); + + let public_key = CompactPublicKey::new(&client_key); + + println!( + "PK size: {} bytes", + bincode::serialize(&public_key).unwrap().len() + ); + + let test_name = format!("hlapi_sizes_{}_cctl_{NB_CTXT}_len_256_bits", params.name()); + + let vec_inputs: Vec<_> = (0..NB_CTXT).map(|_| rng.gen::()).collect(); + + let encrypted_inputs = CompactFheUint256List::encrypt(&vec_inputs, &public_key); + let cctl_size = bincode::serialize(&encrypted_inputs).unwrap().len(); + + println!("Compact CT list for {NB_CTXT} CTs: {} bytes", cctl_size); + + write_result(&mut file, &test_name, cctl_size); + write_to_json( + &test_name, + params, + params.name(), + "CCTL", + &operator, + 0, + vec![], + ); + + let expanded_inputs = encrypted_inputs.expand(); + + vec_inputs + .iter() + .zip(expanded_inputs.iter()) + .for_each(|(&input, ct)| { + let clear: U256 = ct.decrypt(&client_key); + assert_eq!(clear, U256::from(input)); + }); + } +} + +fn main() { + let work_dir = std::env::current_dir().unwrap(); + println!("work_dir: {}", std::env::current_dir().unwrap().display()); + // Change workdir so that the location of the keycache matches the one for tests + let mut new_work_dir = work_dir; + new_work_dir.push("tfhe"); + std::env::set_current_dir(new_work_dir).unwrap(); + + let results_file = Path::new("hlapi_cpk_and_cctl_sizes.csv"); + cpk_and_cctl_sizes(results_file) +} diff --git a/tfhe/examples/micro_bench_and.rs b/tfhe/examples/utilities/micro_bench_and.rs similarity index 100% rename from tfhe/examples/micro_bench_and.rs rename to tfhe/examples/utilities/micro_bench_and.rs diff --git a/tfhe/examples/shortint_key_sizes.rs b/tfhe/examples/utilities/shortint_key_sizes.rs similarity index 98% rename from tfhe/examples/shortint_key_sizes.rs rename to tfhe/examples/utilities/shortint_key_sizes.rs index a78c8f4ab..43360ddc8 100644 --- a/tfhe/examples/shortint_key_sizes.rs +++ b/tfhe/examples/utilities/shortint_key_sizes.rs @@ -1,4 +1,4 @@ -#[path = "../benches/utilities.rs"] +#[path = "../../benches/utilities.rs"] mod utilities; use crate::utilities::{write_to_json, OperatorType}; diff --git a/tfhe/examples/utilities/wasm_benchmarks_parser.rs b/tfhe/examples/utilities/wasm_benchmarks_parser.rs new file mode 100644 index 000000000..6de97a8c1 --- /dev/null +++ b/tfhe/examples/utilities/wasm_benchmarks_parser.rs @@ -0,0 +1,82 @@ +#[path = "../../benches/utilities.rs"] +mod utilities; + +use crate::utilities::{write_to_json, OperatorType}; +use clap::Parser; +use std::collections::HashMap; +use std::fs; + +use std::fs::{File, OpenOptions}; +use std::io::Write; +use std::path::Path; +use tfhe::shortint::keycache::NamedParam; +use tfhe::shortint::parameters::{ + PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, +}; +use tfhe::shortint::ClassicPBSParameters; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + raw_results_dir: String, +} + +fn params_from_name(name: &str) -> ClassicPBSParameters { + match name.to_lowercase().as_str() { + "param_message_2_carry_2_compact_pk" => PARAM_MESSAGE_2_CARRY_2_COMPACT_PK, + "param_small_message_2_carry_2_compact_pk" => PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK, + _ => panic!("failed to get parameters for name '{name}'"), + } +} + +fn write_result(file: &mut File, name: &str, value: usize) { + let line = format!("{name},{value}\n"); + let error_message = format!("cannot write {name} result into file"); + file.write_all(line.as_bytes()).expect(&error_message); +} + +pub fn parse_wasm_benchmarks(results_file: &Path, raw_results_dir: &Path) { + File::create(results_file).expect("create results file failed"); + let mut file = OpenOptions::new() + .append(true) + .open(results_file) + .expect("cannot open parsed results file"); + + let operator = OperatorType::Atomic; + + for entry in raw_results_dir + .read_dir() + .expect("cannot read results directory") + .flatten() + { + let raw_results = fs::read_to_string(entry.path()).expect("cannot open raw results file"); + let results_as_json: HashMap = serde_json::from_str(&raw_results).unwrap(); + + for (full_name, val) in results_as_json.iter() { + let name_parts = full_name.split("_mean_").collect::>(); + let bench_name = name_parts[0]; + let params = params_from_name(name_parts[1]); + let value_in_ns = (val * 1_000_000_f32) as usize; + + write_result(&mut file, full_name, value_in_ns); + write_to_json( + full_name, + params, + params.name(), + bench_name, + &operator, + 0, + vec![], + ); + } + } +} + +fn main() { + let args = Args::parse(); + + let results_file = Path::new("tfhe/wasm_pk_gen.csv"); + let raw_results_dir = Path::new(&args.raw_results_dir); + + parse_wasm_benchmarks(results_file, raw_results_dir); +} diff --git a/tfhe/web_wasm_parallel_tests/Makefile b/tfhe/web_wasm_parallel_tests/Makefile index 4e619ed43..03d2c47ae 100644 --- a/tfhe/web_wasm_parallel_tests/Makefile +++ b/tfhe/web_wasm_parallel_tests/Makefile @@ -1,20 +1,25 @@ -test: +test: script = test +test-ci: script = test-separate-processes +bench: script = bench +bench-ci: script = bench-separate-processes + +run_server: npm install npm run build # This runs the server in background, saving its PID # to a file, so we can kill it when done { npm run server > /dev/null & echo $$! > server.PID; } - npm run test + npm run $(script) kill `cat server.PID` && rm server.PID -test-ci: - npm install - npm run build - # This runs the server in background, saving its PID - # to a file, so we can kill it when done - { npm run server > /dev/null & echo $$! > server.PID; } - npm run test-separate-processes - kill `cat server.PID` && rm server.PID +.PHONY: test # Run web client tests +test: run_server +.PHONY: test-ci # Run web client tests in CI +test-ci: run_server -.PHONY: test +.PHONY: bench # Run benchmarks on web client +bench: run_server + +.PHONY: bench-ci # Run benchmarks on web client in CI +bench-ci: run_server diff --git a/tfhe/web_wasm_parallel_tests/index.html b/tfhe/web_wasm_parallel_tests/index.html index 1decda980..a6d512253 100644 --- a/tfhe/web_wasm_parallel_tests/index.html +++ b/tfhe/web_wasm_parallel_tests/index.html @@ -33,6 +33,15 @@
+ + + + + + + +
+ diff --git a/tfhe/web_wasm_parallel_tests/index.js b/tfhe/web_wasm_parallel_tests/index.js index b3f626678..85f3d5711 100644 --- a/tfhe/web_wasm_parallel_tests/index.js +++ b/tfhe/web_wasm_parallel_tests/index.js @@ -32,7 +32,11 @@ async function setup() { 'compactPublicKeyTest256BitBig', 'compactPublicKeyTest256BitSmall', 'compressedCompactPublicKeyTest256BitBig', - 'compressedCompactPublicKeyTest256BitSmall' + 'compressedCompactPublicKeyTest256BitSmall', + 'compactPublicKeyBench32BitBig', + 'compactPublicKeyBench32BitSmall', + 'compactPublicKeyBench256BitBig', + 'compactPublicKeyBench256BitSmall' ] function setupBtn(id) { @@ -54,8 +58,11 @@ async function setup() { console.log("Running: ", id) try { - await fn() + let results = await fn() document.getElementById("testSuccess").checked = true + if (results !== undefined) { + document.getElementById("benchmarkResults").value = JSON.stringify(results); + } } catch (error) { console.error(`Test Failed: ${error}`) document.getElementById("testSuccess").checked = false diff --git a/tfhe/web_wasm_parallel_tests/package.json b/tfhe/web_wasm_parallel_tests/package.json index fec2c69f6..aca208c0d 100644 --- a/tfhe/web_wasm_parallel_tests/package.json +++ b/tfhe/web_wasm_parallel_tests/package.json @@ -4,10 +4,12 @@ "description": "", "main": "index.js", "scripts": { - "test": "jest ./test --runInBand", + "test": "jest ./test --runInBand --testNamePattern=Test", + "bench": "jest ./test --runInBand --testNamePattern=Bench", "build": "cp -r ../../tfhe/pkg ./ && webpack build ./index.js --mode production -o dist --output-filename index.js && cp index.html dist/ && cp favicon.ico dist/", "server": "serve --config ../serve.json dist/", - "test-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand", + "test-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand --testNamePattern=Test", + "bench-separate-processes": "jest --listTests | xargs -L 1 jest --runInBand --testNamePattern=Bench", "test2": "mocha" }, "author": "", diff --git a/tfhe/web_wasm_parallel_tests/test/common.mjs b/tfhe/web_wasm_parallel_tests/test/common.mjs index 470b3ec5a..a2bb4d5f0 100644 --- a/tfhe/web_wasm_parallel_tests/test/common.mjs +++ b/tfhe/web_wasm_parallel_tests/test/common.mjs @@ -1,5 +1,11 @@ import puppeteer from 'puppeteer'; import process from 'process'; +import * as fs from 'node:fs'; + +const benchmark_dir = __dirname + '/benchmark_results'; +if (!fs.existsSync(benchmark_dir)){ + fs.mkdirSync(benchmark_dir); +} function isRoot() { return process.getuid && process.getuid() === 0; @@ -8,10 +14,14 @@ function isRoot() { async function runActualTest(page, buttonId) { const buttonSelector = `input#${buttonId}` const successCheckBoxSelector = `input#testSuccess` + const benchmarkResultsSelector = `input#benchmarkResults` const testSuccessCheckbox = await page.waitForSelector( successCheckBoxSelector ); + const benchmarkResultsTextbox = await page.waitForSelector( + benchmarkResultsSelector + ); await page.waitForSelector(buttonSelector) const isCheckedBefore = await testSuccessCheckbox?.evaluate(el => el.checked); @@ -26,6 +36,12 @@ async function runActualTest(page, buttonId) { const isCheckedAfter = await testSuccessCheckbox?.evaluate(el => el.checked); expect(isCheckedAfter).toBe(true); + + const results = await benchmarkResultsTextbox?.evaluate(el => el.value); + if (results) { + const parsed_results = JSON.parse(results); + fs.writeFileSync(`${benchmark_dir}/${buttonId}.json`, results, {'flag': 'w'}); + } } async function runTestAttachedToButton(buttonId) { diff --git a/tfhe/web_wasm_parallel_tests/test/public-key-ct.test.js b/tfhe/web_wasm_parallel_tests/test/public-key-ct.test.js new file mode 100644 index 000000000..ccb9f96e3 --- /dev/null +++ b/tfhe/web_wasm_parallel_tests/test/public-key-ct.test.js @@ -0,0 +1,18 @@ +import { runTestAttachedToButton } from "./common.mjs"; + + +it('Compact Public Key Bench Big 32 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyBench32BitBig') +}); + +it('Compact Public Key Bench Small 32 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyBench32BitSmall') +}); + +it('Compact Public Key Bench Big 256 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyBench256BitBig') +}); + +it('Compact Public Key Bench Small 256 Bit', async () => { + await runTestAttachedToButton('compactPublicKeyBench256BitSmall') +}); diff --git a/tfhe/web_wasm_parallel_tests/worker.js b/tfhe/web_wasm_parallel_tests/worker.js index 33b54f93d..98aef5095 100644 --- a/tfhe/web_wasm_parallel_tests/worker.js +++ b/tfhe/web_wasm_parallel_tests/worker.js @@ -25,17 +25,25 @@ import init, { CompactFheUint256List, } from "./pkg/tfhe.js"; -function assert(cond, text){ - if( cond ) return; - if( console.assert.useDebugger ) debugger; - throw new Error(text || "Assertion failed!"); -}; +function assert(cond, text) { + if (cond) return; + if (console.assert.useDebugger) debugger; + throw new Error(text || "Assertion failed!"); +} -function assert_eq(a, b, text){ - if( a === b ) return; - if( console.assert.useDebugger ) debugger; - throw new Error(text || `Equality assertion failed!: ${a} != ${b}`); -}; +function assert_eq(a, b, text) { + if (a === b) return; + if (console.assert.useDebugger) debugger; + throw new Error(text || `Equality assertion failed!: ${a} != ${b}`); +} + +function append_param_name(bench_results, params_name) { + let results = {}; + for (const bench_name in bench_results) { + results[`${bench_name}_${params_name}`] = bench_results[bench_name]; + } + return results +} async function compressedPublicKeyTest() { let config = TfheConfigBuilder.all_disabled() @@ -45,7 +53,7 @@ async function compressedPublicKeyTest() { console.time('ClientKey Gen') let clientKey = TfheClientKey.generate(config); console.timeEnd('ClientKey Gen') - + console.time('CompressedPublicKey Gen') let compressedPublicKey = TfheCompressedPublicKey.new(clientKey); console.timeEnd('CompressedPublicKey Gen') @@ -119,8 +127,7 @@ async function compactPublicKeyTest32BitOnConfig(config) { assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -133,8 +140,7 @@ async function compactPublicKeyTest32BitOnConfig(config) { let encrypted_list = deserialized_list.expand(); assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -156,6 +162,84 @@ async function compactPublicKeyTest32BitSmall() { await compactPublicKeyTest32BitOnConfig(config) } +async function compactPublicKeyBench32BitOnConfig(config) { + const bench_loops = 100; + let bench_results = {}; + + console.time('ClientKey Gen') + let clientKey = TfheClientKey.generate(config); + console.timeEnd('ClientKey Gen') + + // Generate PK for encryption for later + console.time('CompactPublicKey Gen') + let publicKey = TfheCompactPublicKey.new(clientKey); + console.timeEnd('CompactPublicKey Gen') + + // Bench the pk generation for bench_loops iterations + let start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = TfheCompactPublicKey.new(clientKey); + } + let end = performance.now(); + const timing_1 = (end - start) / bench_loops + console.log('CompactPublicKey Gen bench: ', timing_1, ' ms'); + bench_results["compact_public_key_gen_32bit_mean"] = timing_1; + + let values = [0, 1, 2, 2394, U32_MAX]; + + // Encrypt compact CT list for serialization for later + console.time('CompactFheUint32List Encrypt') + let compact_list = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey); + console.timeEnd('CompactFheUint32List Encrypt') + + // Bench the encryption for bench_loops iterations + start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = CompactFheUint32List.encrypt_with_compact_public_key(values, publicKey); + } + end = performance.now(); + const timing_2 = (end - start) / bench_loops + console.log('CompactFheUint32List Encrypt bench: ', timing_2, ' ms'); + bench_results["compact_fheunit32_list_encrypt_mean"] = timing_2; + + let serialized_list = compact_list.serialize(); + console.log("Serialized CompactFheUint32List size: ", serialized_list.length); + + // Bench the serialization for bench_loops iterations + start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = compact_list.serialize(); + } + end = performance.now(); + const timing_3 = (end - start) / bench_loops + console.log('CompactFheUint32List serialization bench: ', timing_3, ' ms'); + bench_results["compact_fheunit32_list_serialization_mean"] = timing_3; + + return bench_results; +} + +async function compactPublicKeyBench32BitBig() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + return append_param_name( + await compactPublicKeyBench32BitOnConfig(config), + "PARAM_MESSAGE_2_CARRY_2_COMPACT_PK" + ); +} + +async function compactPublicKeyBench32BitSmall() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + return append_param_name( + await compactPublicKeyBench32BitOnConfig(config), + "PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK" + ); +} + async function compactPublicKeyTest256BitOnConfig(config) { console.time('ClientKey Gen') let clientKey = TfheClientKey.generate(config); @@ -181,8 +265,7 @@ async function compactPublicKeyTest256BitOnConfig(config) { assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -195,8 +278,7 @@ async function compactPublicKeyTest256BitOnConfig(config) { let encrypted_list = deserialized_list.expand(); assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -248,8 +330,7 @@ async function compressedCompactPublicKeyTest256BitOnConfig(config) { assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -262,8 +343,7 @@ async function compressedCompactPublicKeyTest256BitOnConfig(config) { let encrypted_list = deserialized_list.expand(); assert_eq(encrypted_list.length, values.length); - for (let i = 0; i < values.length; i++) - { + for (let i = 0; i < values.length; i++) { let decrypted = encrypted_list[i].decrypt(clientKey); assert_eq(decrypted, values[i]); } @@ -285,7 +365,83 @@ async function compressedCompactPublicKeyTest256BitSmall() { await compressedCompactPublicKeyTest256BitOnConfig(config) } +async function compactPublicKeyBench256BitOnConfig(config) { + const bench_loops = 100; + let bench_results = {} + console.time('ClientKey Gen') + let clientKey = TfheClientKey.generate(config); + console.timeEnd('ClientKey Gen') + + // Generate PK for encryption for later + console.time('CompactPublicKey Gen') + let publicKey = TfheCompactPublicKey.new(clientKey); + console.timeEnd('CompactPublicKey Gen') + + // Bench the pk generation for bench_loops iterations + let start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = TfheCompactPublicKey.new(clientKey); + } + let end = performance.now(); + const timing_1 = (end - start) / bench_loops + console.log('CompactPublicKey Gen bench: ', timing_1, ' ms'); + bench_results["compact_public_key_gen_256bit_mean"] = timing_1; + + let values = [0, 1, 2, 2394, U32_MAX].map((e) => BigInt(e)); + + // Encrypt compact CT list for serialization for later + console.time('CompactFheUint256List Encrypt') + let compact_list = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey); + console.timeEnd('CompactFheUint256List Encrypt') + + // Bench the encryption for bench_loops iterations + start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = CompactFheUint256List.encrypt_with_compact_public_key(values, publicKey); + } + end = performance.now(); + const timing_2 = (end - start) / bench_loops + console.log('CompactFheUint256List Encrypt bench: ', timing_2, ' ms'); + bench_results["compact_fheunit256_list_encrypt_mean"] = timing_2; + + let serialized_list = compact_list.serialize(); + console.log("Serialized CompactFheUint256List size: ", serialized_list.length); + + // Bench the serialization for bench_loops iterations + start = performance.now(); + for (let i = 0; i < bench_loops; i++) { + let _ = compact_list.serialize(); + } + end = performance.now(); + const timing_3 = (end - start) / bench_loops + console.log('CompactFheUint256List serialization bench: ', timing_3, ' ms'); + bench_results["compact_fheunit256_list_serialization_mean"] = timing_3; + + return bench_results +} + +async function compactPublicKeyBench256BitBig() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + return append_param_name( + await compactPublicKeyBench256BitOnConfig(config), + "PARAM_MESSAGE_2_CARRY_2_COMPACT_PK" + ); +} + +async function compactPublicKeyBench256BitSmall() { + const block_params = new ShortintParameters(ShortintParametersName.PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK); + let config = TfheConfigBuilder.all_disabled() + .enable_custom_integers(block_params) + .build(); + return append_param_name( + await compactPublicKeyBench256BitOnConfig(config), + "PARAM_SMALL_MESSAGE_2_CARRY_2_COMPACT_PK" + ); +} async function main() { await init() @@ -301,6 +457,10 @@ async function main() { compactPublicKeyTest256BitBig, compressedCompactPublicKeyTest256BitSmall, compressedCompactPublicKeyTest256BitBig, + compactPublicKeyBench32BitBig, + compactPublicKeyBench32BitSmall, + compactPublicKeyBench256BitBig, + compactPublicKeyBench256BitSmall, }) }