| // Copyright 2022 Google LLC | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //      http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | //! A manual benchmark for more interactive parameter-twiddling. | 
 |  | 
 | #![allow(clippy::unwrap_used, clippy::indexing_slicing)] | 
 |  | 
 | use clap::Parser as _; | 
 | use crypto_provider_rustcrypto::RustCrypto; | 
 | use ldt::{LdtCipher, LdtDecryptCipher, LdtEncryptCipher, LdtKey, Mix, Swap, XorPadder}; | 
 |  | 
 | use crypto_provider::{CryptoProvider, CryptoRng}; | 
 | use ldt_tbc::TweakableBlockCipher; | 
 | use rand::{distributions, seq::SliceRandom, Rng as _, SeedableRng as _}; | 
 | use sha2::digest::{generic_array, Digest as _}; | 
 | use std::time; | 
 | use subtle::ConstantTimeEq as _; | 
 |  | 
 | use rand_ext::*; | 
 | use xts_aes::XtsAes128; | 
 |  | 
 | fn main() { | 
 |     let args = Args::parse(); | 
 |     let mut rng = <RustCrypto as CryptoProvider>::CryptoRng::new(); | 
 |  | 
 |     // generate a suitable number of random keys | 
 |     let scenarios = (0..args.keys) | 
 |         .map(|_| { | 
 |             random_ldt_scenario::<16, XtsAes128<RustCrypto>, Swap, RustCrypto>(&mut rng, args.len) | 
 |         }) | 
 |         .collect::<Vec<_>>(); | 
 |  | 
 |     let padder = XorPadder::from([0x42; crypto_provider::aes::BLOCK_SIZE]); | 
 |  | 
 |     let ciphertexts = scenarios | 
 |         .iter() | 
 |         .map(|s| { | 
 |             let mut ciphertext = s.plaintext.clone(); | 
 |             s.ldt_enc.encrypt(&mut ciphertext[..], &padder).unwrap(); | 
 |             ciphertext | 
 |         }) | 
 |         .collect::<Vec<_>>(); | 
 |  | 
 |     let not_found_distrib = distributions::Uniform::from(0_f64..=100_f64); | 
 |     let unfindable_ciphertext = random_vec::<RustCrypto>(&mut rng, args.len); | 
 |  | 
 |     let mut histogram = hdrhistogram::Histogram::<u64>::new(3).unwrap(); | 
 |     let mut buf = Vec::new(); | 
 |  | 
 |     let mut hasher = sha2::Sha256::new(); | 
 |     let mut hash_output = generic_array::GenericArray::default(); | 
 |  | 
 |     let mut rc_rng = rand::rngs::StdRng::from_entropy(); | 
 |     let found = (0..args.trials) | 
 |         .map(|_| { | 
 |             let ciphertext = if rc_rng.sample(not_found_distrib) <= args.not_found_pct as f64 { | 
 |                 &unfindable_ciphertext | 
 |             } else { | 
 |                 ciphertexts.choose(&mut rc_rng).unwrap() | 
 |             }; | 
 |  | 
 |             let start = time::Instant::now(); | 
 |  | 
 |             let found = scenarios.iter().any(|scenario| { | 
 |                 hasher.reset(); | 
 |  | 
 |                 buf.clear(); | 
 |                 buf.extend_from_slice(ciphertext.as_slice()); | 
 |                 scenario.ldt_dec.decrypt(&mut buf, &padder).unwrap(); | 
 |  | 
 |                 hasher.update(&buf[..MATCH_LEN]); | 
 |                 hasher.finalize_into_reset(&mut hash_output); | 
 |  | 
 |                 let arr_ref: &[u8; 32] = hash_output.as_ref(); | 
 |                 arr_ref.ct_eq(&scenario.plaintext_prefix_hash).into() | 
 |             }); | 
 |  | 
 |             histogram.record((start.elapsed().as_micros()) as u64).unwrap(); | 
 |  | 
 |             found | 
 |         }) | 
 |         .filter(|&found| found) | 
 |         .count(); | 
 |  | 
 |     println!( | 
 |         "Found {} of {} ({}%)", | 
 |         found, | 
 |         args.trials, | 
 |         (found as f64) / (args.trials as f64) * 100_f64 | 
 |     ); | 
 |  | 
 |     println!( | 
 |         "90%ile:    {}μs\n95%ile:    {}μs\n99%ile:    {}μs\n99.9%ile:  {}μs\n99.99%ile: {}μs\nMax:       {}μs", | 
 |         histogram.value_at_quantile(0.90), | 
 |         histogram.value_at_quantile(0.95), | 
 |         histogram.value_at_quantile(0.99), | 
 |         histogram.value_at_quantile(0.999), | 
 |         histogram.value_at_quantile(0.9999), | 
 |         histogram.max(), | 
 |     ); | 
 | } | 
 |  | 
 | #[derive(clap::Parser, Debug)] | 
 | struct Args { | 
 |     /// How many keys/plaintexts/ciphertexts to generate | 
 |     #[clap(long, default_value_t = 1000)] | 
 |     keys: u64, | 
 |     /// How many trials to run | 
 |     #[clap(long, default_value_t = 100_000)] | 
 |     trials: u64, | 
 |     /// Plaintext len | 
 |     #[clap(long, default_value_t = 24)] | 
 |     len: usize, | 
 |     /// What percentage of decryptions should fail to find a match | 
 |     #[clap(long, default_value_t = 50)] | 
 |     not_found_pct: u8, | 
 | } | 
 |  | 
 | /// How much of the plaintext should be hashed for subsequent matching | 
 | const MATCH_LEN: usize = 16; | 
 |  | 
 | struct LdtScenario<const B: usize, T: TweakableBlockCipher<B>, M: Mix> { | 
 |     ldt_enc: LdtEncryptCipher<B, T, M>, | 
 |     ldt_dec: LdtDecryptCipher<B, T, M>, | 
 |     plaintext: Vec<u8>, | 
 |     plaintext_prefix_hash: [u8; 32], | 
 | } | 
 |  | 
 | fn random_ldt_scenario<const B: usize, T: TweakableBlockCipher<B>, M: Mix, C: CryptoProvider>( | 
 |     rng: &mut C::CryptoRng, | 
 |     plaintext_len: usize, | 
 | ) -> LdtScenario<B, T, M> { | 
 |     let ldt_key: LdtKey<T::Key> = LdtKey::from_random::<C>(rng); | 
 |     let ldt_enc = LdtEncryptCipher::new(&ldt_key); | 
 |     let ldt_dec = LdtDecryptCipher::new(&ldt_key); | 
 |     let plaintext = random_vec::<C>(rng, plaintext_len); | 
 |  | 
 |     let mut hasher = sha2::Sha256::new(); | 
 |     let mut plaintext_prefix_hash = generic_array::GenericArray::default(); | 
 |     hasher.update(&plaintext[..MATCH_LEN]); | 
 |     hasher.finalize_into_reset(&mut plaintext_prefix_hash); | 
 |  | 
 |     LdtScenario { ldt_enc, ldt_dec, plaintext, plaintext_prefix_hash: plaintext_prefix_hash.into() } | 
 | } |