Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support batch mode (quit after a single key) and case-sensitive matching #28

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 67 additions & 45 deletions src/bin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -92,13 +92,30 @@ fn main() -> Result<(), Box<dyn Error>> {
.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()?,
Expand All @@ -114,56 +131,61 @@ fn main() -> Result<(), Box<dyn Error>> {
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(())
}
10 changes: 6 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down