diff --git a/src/bin.rs b/src/bin.rs index a5a6f35..81271d2 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -13,7 +13,7 @@ fn estimate_one_trial() -> Duration { let start = SystemTime::now(); const COUNT: u32 = 100; (0..COUNT).for_each(|_| { - trial(&prefix, 0, 10); + trial(&prefix, 0, 10, false); }); let elapsed = start.elapsed().unwrap(); elapsed.checked_div(COUNT).unwrap() @@ -92,13 +92,30 @@ fn main() -> Result<(), Box> { .takes_value(true) .help("NAME must be found within first RANGE chars of pubkey (default: 10)"), ) + .arg( + Arg::with_name("CASE") + .long("case") + .takes_value(false) + .help("case sensitive matching"), + ) + .arg( + Arg::with_name("BATCH") + .long("batch") + .takes_value(false) + .help("optimized for batch processing, return only the first match"), + ) .arg( Arg::with_name("NAME") .required(true) .help("string to find near the start of the pubkey"), ) - .get_matches(); - let prefix = matches.value_of("NAME").unwrap().to_ascii_lowercase(); + .get_matches(); + let case = matches.is_present("CASE"); + let batch = matches.is_present("BATCH"); + let mut prefix = matches.value_of("NAME").unwrap().to_string(); + if !case { + prefix.make_ascii_lowercase(); + } let len = prefix.len(); let end: usize = 44.min(match matches.value_of("RANGE") { Some(range) => range.parse()?, @@ -114,56 +131,61 @@ fn main() -> Result<(), Box> { return Err(ParseError(format!("range {} is too short for len={}", end, len)).into()); } - let offsets: u64 = 44.min((1 + end - len) as u64); - // todo: this is an approximation, offsets=2 != double the chances - let mut num = offsets; - let mut denom = 1u64; - prefix.chars().for_each(|c| { - if c.is_ascii_alphabetic() { - num *= 2; // letters can match both uppercase and lowercase - } - denom *= 64; // base64 - }); - let trials_per_key = denom / num; - - println!( - "searching for '{}' in pubkey[0..{}], one of every {} keys should match", - &prefix, end, trials_per_key - ); - - // todo: dividing by num_cpus will overestimate performance when the - // cores aren't actually distinct (hyperthreading?). My Core-i7 seems to - // run at half the speed that this predicts. + if !batch { + let offsets: u64 = 44.min((1 + end - len) as u64); + // todo: this is an approximation, offsets=2 != double the chances + let mut num = offsets; + let mut denom = 1u64; + prefix.chars().for_each(|c| { + if c.is_ascii_alphabetic() && !case { + num *= 2; // letters can match both uppercase and lowercase + } + denom *= 64; // base64 + }); + let trials_per_key = denom / num; - if trials_per_key < 2u64.pow(32) { - let est = estimate_one_trial(); println!( - "one trial takes {}, CPU cores available: {}", - format_time(duration_to_f64(est)), - num_cpus::get() - ); - let spk = duration_to_f64( - est // sec/trial on one core - .checked_div(num_cpus::get() as u32) // sec/trial with all cores - .unwrap() - .checked_mul(trials_per_key as u32) // sec/key (Duration) - .unwrap(), + "searching for '{}' in pubkey[0..{}], one of every {} keys should match", + &prefix, end, trials_per_key ); - let kps = 1.0 / spk; - println!( - "est yield: {} per key, {}", - format_time(spk), - format_rate(kps) - ); - } - println!("hit Ctrl-C to stop"); + // todo: dividing by num_cpus will overestimate performance when the + // cores aren't actually distinct (hyperthreading?). My Core-i7 seems to + // run at half the speed that this predicts. + + if trials_per_key < 2u64.pow(32) { + let est = estimate_one_trial(); + println!( + "one trial takes {}, CPU cores available: {}", + format_time(duration_to_f64(est)), + num_cpus::get() + ); + let spk = duration_to_f64( + est // sec/trial on one core + .checked_div(num_cpus::get() as u32) // sec/trial with all cores + .unwrap() + .checked_mul(trials_per_key as u32) // sec/key (Duration) + .unwrap(), + ); + let kps = 1.0 / spk; + println!( + "est yield: {} per key, {}", + format_time(spk), + format_rate(kps) + ); + } + + println!("hit Ctrl-C to stop"); + } // 1M trials takes about 10s on my laptop, so let it run for 1000s (0..100_000_000) .into_par_iter() - .map(|_| trial(&prefix, 0, end)) + .map(|_| trial(&prefix, 0, end, case)) .filter_map(|r| r) - .try_for_each(print)?; + .try_for_each(|r| Some({ + let res = print(r); + if batch || res.is_err() { return None; } + })); Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 31138c5..ce26414 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,13 +2,15 @@ use base64; use rand_core::OsRng; use x25519_dalek::{PublicKey, StaticSecret}; -pub fn trial(prefix: &str, start: usize, end: usize) -> Option<(String, String)> { +pub fn trial(prefix: &str, start: usize, end: usize, case: bool) -> Option<(String, String)> { let private = StaticSecret::new(&mut OsRng); let public = PublicKey::from(&private); let public_b64 = base64::encode(public.as_bytes()); - if public_b64[start..end] - .to_ascii_lowercase() - .contains(&prefix) + let mut start = public_b64[start..end].to_string(); + if !case { + start.make_ascii_lowercase() + } + if start.contains(&prefix) { let private_b64 = base64::encode(&private.to_bytes()); Some((private_b64, public_b64))