From 7e70146a556b30b8c1fb31a74db89b2e0a5b973e Mon Sep 17 00:00:00 2001 From: GZTime Date: Thu, 9 Jan 2025 00:58:37 +0800 Subject: [PATCH] refactor: tidy up --- Cargo.lock | 68 +++ Cargo.toml | 22 +- src/main.rs | 485 +++++------------- src/utils/args.rs | 120 +++++ src/utils/device.rs | 87 ++++ src/utils/hash_pattern.rs | 92 ++++ src/utils/mod.rs | 65 +++ .../vanity_secret_key.rs} | 54 +- 8 files changed, 606 insertions(+), 387 deletions(-) create mode 100644 src/utils/args.rs create mode 100644 src/utils/device.rs create mode 100644 src/utils/hash_pattern.rs create mode 100644 src/utils/mod.rs rename src/{vanity_gpg.rs => utils/vanity_secret_key.rs} (90%) diff --git a/Cargo.lock b/Cargo.lock index d5dcab0..528ee2c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,6 +116,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "argon2" version = "0.5.3" @@ -364,6 +370,19 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" +[[package]] +name = "console" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -742,6 +761,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "enum_primitive" version = "0.1.1" @@ -956,6 +981,19 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indicatif" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281" +dependencies = [ + "console", + "number_prefix", + "portable-atomic", + "unicode-width", + "web-time", +] + [[package]] name = "inout" version = "0.1.3" @@ -1171,6 +1209,12 @@ dependencies = [ "syn", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "ocb3" version = "0.1.0" @@ -1238,11 +1282,13 @@ checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" name = "opencl_vanity_gpg" version = "0.1.0" dependencies = [ + "anyhow", "byteorder", "chrono", "clap", "env_logger", "hex", + "indicatif", "log", "ocl", "pgp", @@ -1408,6 +1454,12 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "portable-atomic" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1749,6 +1801,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -1837,6 +1895,16 @@ version = "0.2.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index ee490ef..61f11f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,13 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -byteorder = "1.5.0" -chrono = { version = "0.4.39", default-features = false, features = ["now"] } -clap = { version = "4.5.23", features = ["derive"] } -env_logger = { version = "0.11.6", default-features = false, features = ["auto-color", "humantime"] } -hex = "0.4.3" -log = "0.4.22" -ocl = "0.19.7" -pgp = "0.14.2" -rand = "0.8.5" -smallvec = "1.13.2" +byteorder = "1.5" +chrono = { version = "0.4", default-features = false, features = ["now"] } +clap = { version = "4.5", features = ["derive"] } +env_logger = { version = "0.11", default-features = false, features = ["auto-color", "humantime"] } +hex = "0.4" +log = "0.4" +ocl = "0.19" +pgp = "0.14" +rand = "0.8" +smallvec = "1.13" +anyhow = "1.0" +indicatif = "0.17" diff --git a/src/main.rs b/src/main.rs index f2f2f85..54e7b4f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,272 +1,39 @@ -mod vanity_gpg; - -use clap::Parser; +use anyhow::bail; +use indicatif::{ProgressBar, ProgressState, ProgressStyle}; use log::{debug, info, warn}; use ocl::{Buffer, Device, Platform, ProQue}; use pgp::types::PublicKeyTrait; use rand::thread_rng; -use std::{fs, io, io::Write, mem, path::Path, sync::mpsc::channel, thread, time::Instant}; -use vanity_gpg::{CipherSuite, VanitySecretKey}; +use std::{fmt::Write, fs, path::Path, str::FromStr, sync::mpsc::*, thread, time::Instant}; +use utils::*; -#[derive(Parser, Debug)] -#[command(version, about, long_about = None)] -struct Args { - /// Cipher suite of the vanity key - /// ed25519, ecdsa-****, rsa**** => Primary key - /// cv25519, ecdh-**** => Subkey - /// Use gpg CLI for further editing of the key. - #[arg(short, long, default_value_t, value_enum, verbatim_doc_comment)] - cipher_suite: vanity_gpg::CipherSuite, +mod utils; +pub use utils::ARGS; - /// OpenPGP compatible user ID - #[arg(short, long, default_value_t = String::from("Dummy "), verbatim_doc_comment)] - user_id: String, - - /// A pattern less than 40 chars for matching fingerprints - /// Format: - /// * 0-9A-F are fixed, G-Z are wildcards - /// * Other chars will be ignored - /// * Case insensitive - /// Example: - /// * 11XXXX** may output a fingerprint ends with 11222234 or 11AAAABF - /// * 11XXYYZZ may output a fingerprint ends with 11223344 or 11AABBCC - #[arg(short, long, verbatim_doc_comment)] - pattern: Option, - - /// OpenCL kernel function for uint h[5] for matching fingerprints - /// Ignore the pattern and no estimate is given if this has been set - /// - /// Example: - /// - /// * (h[4] & 0xFFFF) == 0x1234 outputs a fingerprint ends with 1234 - /// * (h[0] & 0xFFFF0000) == 0xABCD0000 outputs a fingerprint starts with ABCD - #[arg(short, long, verbatim_doc_comment)] - filter: Option, - - /// The dir where the vanity keys are saved - #[arg(short, long)] - output: Option, - - /// Device ID to use - #[arg(short, long)] - device: Option, - - /// Adjust it to maximum your device's usage - #[arg(short, long)] - thread: Option, - - /// Adjust it to maximum your device's usage - #[arg(short, long, default_value_t = 1 << 9)] - iteration: usize, - - /// Exit after a specified time in seconds - #[arg(long)] - timeout: Option, - - /// Exit after getting a vanity key - #[arg(long, default_value_t = false)] - oneshot: bool, - - /// Don't print progress - #[arg(long, default_value_t = false)] - no_progress: bool, - - /// Don't print armored secret key - #[arg(long, default_value_t = false)] - no_secret_key_logging: bool, - - /// Show available OpenCL devices then exit - #[arg(long, default_value_t = false)] - device_list: bool, -} - -/// Do SHA-1 padding manually -/// A SHA-1 block is 512 bit, so the output Vec length is a multiple of 16 -fn manually_prepare_sha1(hashdata: Vec) -> Vec { - // Length after padding - // Fill with 0x80 0x00 ... to 448 mod 512 bit, which is 56 mod 64 bytes - // plus u64's 8 bytes, the length is a multiple of 64 - let padded_length = hashdata.len() + (64 - ((hashdata.len() + 8) % 64)) + 8; - let mut result_u8 = Vec::with_capacity(padded_length); - result_u8.extend_from_slice(&hashdata); - result_u8.push(0x80); - result_u8.resize(padded_length, 0); - - // convert Vec to Vec - // https://stackoverflow.com/questions/49690459/converting-a-vecu32-to-vecu8-in-place-and-with-minimal-overhead - let mut result_u32 = unsafe { - let ptr = result_u8.as_mut_ptr() as *mut u32; - let length = result_u8.len() / 4; - let capacity = result_u8.capacity() / 4; - mem::forget(result_u8); - Vec::from_raw_parts(ptr, length, capacity) - }; - - // assert_eq!(result_u32.len() % 16, 0); - // SHA-1 uses big-endian words and length - for pos in &mut result_u32 { - *pos = pos.to_be(); - } - - let bit_length = hashdata.len() * 8; - result_u32[padded_length / 4 - 1] = (bit_length) as u32; - result_u32[padded_length / 4 - 2] = (bit_length >> 32) as u32; - result_u32 -} - -fn parse_pattern(pattern: String) -> (String, f64) { - let pattern = match pattern.trim().replace(" ", "").to_ascii_uppercase() { - x if x.len() <= 40 => "*".repeat(40 - x.len()) + &x, - _ => panic!("Invalid pattern"), - }; - let mut parts: Vec = vec![]; - - // Handle fixed 0-9A-F - let mut fixed_pos_count: usize = 0; - for i in 0..=4 { - let mut mask = String::new(); - let mut value = String::new(); - let mut activated = false; - for j in 0..8 { - let char = *pattern.chars().nth(i * 8 + j).get_or_insert(' '); - if char.is_ascii_hexdigit() { - fixed_pos_count += 1; - mask += "F"; - value += &String::from(char); - activated = true; - } else { - mask += "0"; - value += "0"; - } - } - if activated { - parts.push(format!("(h[{i}] & 0x{mask}) == 0x{value}")); - } - } - - // Handle wildcard G-Z - let mut wildcard_pos_all: [Vec; (b'Z' - b'G' + 1) as usize] = - std::default::Default::default(); - for (i, wildcard) in pattern.chars().enumerate() { - if ('G'..='Z').contains(&wildcard) { - wildcard_pos_all[((wildcard as u8) - b'G') as usize].push(i); - } - } - let mut wildcard_pos_count = 0; - for wildcard in 'G'..='Z' { - let wildcard_pos = &wildcard_pos_all[((wildcard as u8) - b'G') as usize]; - if wildcard_pos.len() >= 2 { - for i in 1..wildcard_pos.len() { - let left_index = wildcard_pos[i - 1] / 8; - let right_index = wildcard_pos[i] / 8; - let left_digit = 7 - wildcard_pos[i - 1] % 8; - let right_digit = 7 - wildcard_pos[i] % 8; - parts.push(format!( - "(/* {}: h[{}][{}] == h[{}][{}] */ (h[{}] {} {}) & 0xF{}) == (h[{}] & 0xF{})", - wildcard, - left_index, - left_digit, - right_index, - right_digit, - left_index, - if right_digit > left_digit { "<<" } else { ">>" }, - right_digit.abs_diff(left_digit) * 4, - "0".repeat(right_digit), - right_index, - "0".repeat(right_digit), - )); - } - wildcard_pos_count += wildcard_pos.len() - 1; - } - } - let filter = if !parts.is_empty() { - parts.join(" && ") - } else { - String::from("true") - }; - ( - filter, - (16f64).powi((fixed_pos_count + wildcard_pos_count) as i32), - ) -} - -fn format_number(v: impl Into) -> String { - match Into::::into(v) { - // v if v >= 1e9f64 => { return format!("{:.02}g", v / 1e9f64); }, - v if v >= 1e6f64 => { - format!("{:.02}m", v / 1e6f64) - } - v if v >= 1e3f64 => { - format!("{:.02}k", v / 1e3f64) - } - v => { - format!("{v:.02}") - } - } -} - -fn main() -> Result<(), Box> { +fn main() -> anyhow::Result<()> { env_logger::Builder::from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), ) .format_indent(None) .init(); - let args = if cfg!(debug_assertions) { - Args { - cipher_suite: CipherSuite::RSA3072, - user_id: String::from("Dummy "), - pattern: Some(String::from("XXXYYYZZZWWW")), - filter: None, - output: None, - device: None, - thread: None, - iteration: 512, - timeout: None, - oneshot: true, - no_progress: true, - no_secret_key_logging: false, - device_list: false, - } - } else { - Args::parse() - }; - debug!("{:?}", &args); + debug!("{:?}", &ARGS); - let device_list: Vec<(Platform, Device)> = - Platform::list() - .iter() - .rfold(Vec::new(), |mut list, platform| { - if let Ok(devices) = Device::list_all(platform) { - let mut devices = devices.iter().map(|device| (*platform, *device)).collect(); - list.append(&mut devices); - } - list - }); - if args.device_list { - for (index, (platform, device)) in device_list.iter().enumerate() { - info!( - "Device #{} - {}", - index, - format!( - "{} ({}, MaxWorkGroupSize={}, MaxWorkItemSizes={}, MaxWorkItemDimensions={})", - device.name()?, - platform.name()?, - device.info(ocl::core::DeviceInfo::MaxWorkGroupSize)?, - device.info(ocl::core::DeviceInfo::MaxWorkItemSizes)?, - device.info(ocl::core::DeviceInfo::MaxWorkItemDimensions)?, - ) - ); - } + let device_list = utils::DeviceList::new()?; + + if ARGS.list_device { + info!("Available OpenCL devices: \n{:?}", device_list); return Ok(()); } - let device = match args.device { - Some(i) => device_list[i].1, + let device = match ARGS.device { + Some(i) => device_list[i].device, None => Device::first(Platform::default())?, }; + info!("Using device: {}", device.name()?); - let dimension: usize = match args.thread { + + let dimension = match ARGS.thread { Some(v) => v, None => match device.info(ocl::core::DeviceInfo::MaxWorkItemSizes)? { ocl::core::DeviceInfoResult::MaxWorkItemSizes(wgs) => { @@ -277,7 +44,8 @@ fn main() -> Result<(), Box> { _ => unreachable!(), }, }; - let iteration: usize = args.iteration; + + let iteration = ARGS.iteration; info!( "You will get vanity keys created after {}", chrono::Utc::now() @@ -285,38 +53,45 @@ fn main() -> Result<(), Box> { .unwrap() .to_rfc3339_opts(chrono::SecondsFormat::Millis, true), ); - if args.output.is_none() { - if args.no_secret_key_logging { + + if ARGS.output.is_none() { + if ARGS.no_secret_key_logging { warn!("No output dir given and you disabled secret key logging. You have no chance to save generated vanity keys."); } else { warn!("No output dir given. Generated vanity keys will not be saved."); } } - let (filter, estimate) = match args.filter { - Some(filter) => (filter, None), - None => match args.pattern { + let (filter, estimate) = match &ARGS.filter { + Some(filter) => (filter.clone(), None), + None => match &ARGS.pattern { Some(pattern) => { - let (filter, estimate) = parse_pattern(pattern); - (filter, Some(estimate)) + let hash_pattern = HashPattern::from_str(pattern)?; + (hash_pattern.filter, Some(hash_pattern.possibliity)) } - None => panic!("No filter or pattern given"), + None => bail!("No filter or pattern given"), }, }; debug!("Filter: {filter}"); let mut rng = thread_rng(); - match args.cipher_suite { + match ARGS.cipher_suite { CipherSuite::RSA2048 | CipherSuite::RSA3072 | CipherSuite::RSA4096 => { warn!("Generating RSA vanity keys is not recommended. Too slow!") } _ => (), }; - let mut vanity_key = VanitySecretKey::new(args.cipher_suite, args.user_id.clone(), &mut rng); + let mut vanity_key = VanitySecretKey::new(ARGS.cipher_suite, ARGS.user_id.clone(), &mut rng); let mut hashdata = manually_prepare_sha1(vanity_key.hashdata()); + let (tx_hashdata, rx_hashdata) = channel::>(); + let (tx_result, rx_result) = channel::>(); + + let mut hashed: usize = 0; + let mut start = Instant::now(); + let pro_que = ProQue::builder() .src( std::include_str!("shader.cl").replace( @@ -338,113 +113,28 @@ fn main() -> Result<(), Box> { .fill_val(0) .build()?; - let (tx_hashdata, rx_hashdata) = channel::>(); - let (tx_result, rx_result) = channel::>(); - - let mut hashed: usize = 0; - let mut hashed_count: usize = 0; - let mut start = Instant::now(); - - thread::spawn(move || { - let mut vec = vec![0; buffer_result.len()]; - debug!("OpenCL thread ready"); - while let Ok(hashdata) = rx_hashdata.recv() { - buffer_result.cmd().fill(0, None).enq().unwrap(); - let buffer_hashdata = Buffer::::builder() - .queue(pro_que.queue().clone()) - .len(hashdata.len()) - .copy_host_slice(&hashdata) - .build() - .unwrap(); - let kernel = pro_que - .kernel_builder("vanity_sha1") - .arg(&buffer_hashdata) - .arg(&buffer_result) - .arg(iteration as u64) - .build() - .unwrap(); - - unsafe { - kernel.enq().unwrap(); - } - - buffer_result.read(&mut vec).enq().unwrap(); - tx_result - .send(match vec[0] { - 0 => None, - x => Some(x), - }) - .unwrap(); - } - debug!("OpenCL thread quit"); - }); + thread::spawn(move || opencl_thread(buffer_result, pro_que, rx_hashdata, tx_result)); + let bench_size = (dimension * iteration) as u64; + let bar = init_progress_bar(estimate); loop { debug!("Send key to OpenCL thread"); tx_hashdata.send(hashdata)?; let vanity_key_next = - VanitySecretKey::new(args.cipher_suite, args.user_id.clone(), &mut rng); + VanitySecretKey::new(ARGS.cipher_suite, ARGS.user_id.clone(), &mut rng); let hashdata_next = manually_prepare_sha1(vanity_key_next.hashdata()); debug!("Receive result from OpenCL thread"); let vanity_timestamp = rx_result.recv()?; hashed += dimension * iteration; - hashed_count += 1; + let elapsed = start.elapsed().as_secs_f64(); - if !args.no_progress { - match estimate { - Some(estimate) => print!( - "[{}] {}/{} {:.02}x {:.02}s {} hash/s \r", - match hashed_count % 16 { - x if x < 8 => format!("{}>))'>{}", " ".repeat(x), " ".repeat(7 - x)), - x => format!("{}<'((<{}", " ".repeat(15 - x), " ".repeat(x - 8)), - }, - format_number(hashed as f64), - format_number(estimate), - (hashed as f64) / estimate, - elapsed, - format_number((hashed as f64) / elapsed), - ), - None => print!( - "[{}] {} {:.02}s {} hash/s \r", - match hashed_count % 16 { - x if x < 8 => format!("{}>))'>{}", " ".repeat(x), " ".repeat(7 - x)), - x => format!("{}<'((<{}", " ".repeat(15 - x), " ".repeat(x - 8)), - }, - format_number(hashed as f64), - elapsed, - format_number((hashed as f64) / elapsed), - ), - } - io::stdout().flush()?; - } + bar.inc(bench_size); if let Some(vanity_timestamp) = vanity_timestamp { vanity_key.edit_timestamp(vanity_timestamp, &mut rng); - if args.no_secret_key_logging { - info!("Get a vanity key!"); - } else { - info!("Get a vanity key: \n{}", vanity_key.to_armored_string()?); - } - info!( - "Created at: {} ({})", - vanity_key - .secret_key - .created_at() - .to_rfc3339_opts(chrono::SecondsFormat::Millis, true), - vanity_key.secret_key.created_at().timestamp(), - ); - info!( - "Fingerprint #0: {}", - hex::encode_upper(vanity_key.secret_key.fingerprint().as_bytes()) - ); - for (i, subkey) in vanity_key.secret_key.secret_subkeys.iter().enumerate() { - info!( - "Fingerprint #{}: {}", - i + 1, - hex::encode_upper(subkey.fingerprint().as_bytes()) - ); - } + vanity_key.log_state(); + match estimate { Some(estimate) => info!( "Hashed: {} ({:.02}x) Time: {:.02}s Speed: {} hash/s", @@ -460,7 +150,8 @@ fn main() -> Result<(), Box> { format_number((hashed as f64) / elapsed), ), } - if let Some(ref output_dir) = args.output { + + if let Some(ref output_dir) = ARGS.output { fs::write( Path::new(output_dir).join(format!( "{}-sec.asc", @@ -470,13 +161,16 @@ fn main() -> Result<(), Box> { ) .unwrap(); } - if args.oneshot { + + if ARGS.oneshot { break; } + hashed = 0; start = Instant::now(); } - if let Some(timeout) = args.timeout { + + if let Some(timeout) = ARGS.timeout { if elapsed > timeout { info!("Timeout!"); break; @@ -489,3 +183,80 @@ fn main() -> Result<(), Box> { Ok(()) } + +fn opencl_thread( + buffer_result: Buffer, + pro_que: ProQue, + rx_hashdata: Receiver>, + tx_result: Sender>, +) { + let mut vec = vec![0; buffer_result.len()]; + debug!("OpenCL thread ready"); + while let Ok(hashdata) = rx_hashdata.recv() { + buffer_result.cmd().fill(0, None).enq().unwrap(); + + let buffer_hashdata = Buffer::::builder() + .queue(pro_que.queue().clone()) + .len(hashdata.len()) + .copy_host_slice(&hashdata) + .build() + .unwrap(); + + let kernel = pro_que + .kernel_builder("vanity_sha1") + .arg(&buffer_hashdata) + .arg(&buffer_result) + .arg(ARGS.iteration as u64) + .build() + .unwrap(); + + unsafe { + kernel.enq().unwrap(); + } + + buffer_result.read(&mut vec).enq().unwrap(); + tx_result + .send(match vec[0] { + 0 => None, + x => Some(x), + }) + .unwrap(); + } + debug!("OpenCL thread quit"); +} + +fn init_progress_bar(estimate: Option) -> ProgressBar { + let bar = match estimate { + Some(estimate) => ProgressBar::new(estimate as u64), + None => ProgressBar::new_spinner(), + }; + + bar.set_style( + ProgressStyle::default_spinner() + .template("[{elapsed_precise}] {bar:40.cyan/blue} {progress} {rate}") + .unwrap() + .progress_chars("##-") + .with_key("progress", |state: &ProgressState, w: &mut dyn Write| { + write!( + w, + "{}/{}", + format_number(state.pos() as f64), + match state.len() { + None => "???".to_string(), + Some(x) => format_number(x as f64), + } + ) + .unwrap() + }) + .with_key("rate", |state: &ProgressState, w: &mut dyn Write| { + write!( + w, + "{} hash/s", + format_number((state.pos() as f64) / state.elapsed().as_secs_f64()), + ) + .unwrap() + }), + ); + + bar +} diff --git a/src/utils/args.rs b/src/utils/args.rs new file mode 100644 index 0000000..260de50 --- /dev/null +++ b/src/utils/args.rs @@ -0,0 +1,120 @@ +use clap::{Parser, ValueEnum}; +use std::sync::LazyLock; + +pub static ARGS: LazyLock = LazyLock::new(if cfg!(debug_assertions) { + Args::default +} else { + Args::parse +}); + +#[derive(Parser, Debug)] +#[command(version, about, long_about = None)] +pub struct Args { + /// Cipher suite of the vanity key + /// ed25519, ecdsa-****, rsa**** => Primary key + /// cv25519, ecdh-**** => Subkey + /// Use gpg CLI for further editing of the key. + #[arg(short, long, default_value_t, value_enum, verbatim_doc_comment)] + pub cipher_suite: CipherSuite, + + /// OpenPGP compatible user ID + #[arg(short, long, default_value_t = String::from("Dummy "), verbatim_doc_comment)] + pub user_id: String, + + /// A pattern less than 40 chars for matching fingerprints + /// Format: + /// * 0-9A-F are fixed, G-Z are wildcards + /// * Other chars will be ignored + /// * Case insensitive + /// + /// ## Example: + /// + /// * 11XXXX** may output a fingerprint ends with 11222234 or 11AAAABF + /// * 11XXYYZZ may output a fingerprint ends with 11223344 or 11AABBCC + #[arg(short, long, verbatim_doc_comment)] + pub pattern: Option, + + /// OpenCL kernel function for uint h[5] for matching fingerprints + /// Ignore the pattern and no estimate is given if this has been set + /// + /// ## Example: + /// + /// * (h[4] & 0xFFFF) == 0x1234 outputs a fingerprint ends with 1234 + /// * (h[0] & 0xFFFF0000) == 0xABCD0000 outputs a fingerprint starts with ABCD + #[arg(short, long, verbatim_doc_comment)] + pub filter: Option, + + /// The dir where the vanity keys are saved + #[arg(short, long)] + pub output: Option, + + /// Device ID to use + #[arg(short, long)] + pub device: Option, + + /// Adjust it to maximum your device's usage + #[arg(short, long)] + pub thread: Option, + + /// Adjust it to maximum your device's usage + #[arg(short, long, default_value_t = 1 << 9)] + pub iteration: usize, + + /// Exit after a specified time in seconds + #[arg(long)] + pub timeout: Option, + + /// Exit after getting a vanity key + #[arg(long, default_value_t = false)] + pub oneshot: bool, + + /// Don't print progress + #[arg(long, default_value_t = false)] + pub no_progress: bool, + + /// Don't print armored secret key + #[arg(long, default_value_t = false)] + pub no_secret_key_logging: bool, + + /// Show available OpenCL devices then exit + #[arg(long, default_value_t = false)] + pub list_device: bool, +} + +impl Default for Args { + fn default() -> Self { + Self { + cipher_suite: CipherSuite::RSA3072, + user_id: String::from("Dummy "), + pattern: Some(String::from("XXXYYYZZZWWW")), + filter: None, + output: None, + device: None, + thread: None, + iteration: 512, + timeout: None, + oneshot: true, + no_progress: true, + no_secret_key_logging: false, + list_device: false, + } + } +} + +/// Cipher Suites +#[derive(ValueEnum, Default, Clone, Copy, Debug)] +#[clap(rename_all = "kebab_case")] +pub enum CipherSuite { + #[default] + Ed25519, + Cv25519, + RSA2048, + RSA3072, + RSA4096, + EcdhP256, + EcdhP384, + EcdhP521, + EcdsaP256, + EcdsaP384, + EcdsaP521, +} diff --git a/src/utils/device.rs b/src/utils/device.rs new file mode 100644 index 0000000..9ce459f --- /dev/null +++ b/src/utils/device.rs @@ -0,0 +1,87 @@ +use ocl::core::{DeviceInfo as OclInfo, DeviceInfoResult}; +use ocl::{Device, Platform}; + +pub struct DeviceInfo { + pub name: String, + pub platform_name: String, + pub max_work_group_size: usize, + pub max_work_item_sizes: Vec, + pub max_work_item_dimensions: u32, + + // hold the actual device + pub device: Device, +} + +pub struct DeviceList(pub Vec); + +impl DeviceInfo { + pub fn new(device: Device, platform: Platform) -> anyhow::Result { + Ok(Self { + name: device.name()?, + platform_name: platform.name()?, + max_work_group_size: match device.info(OclInfo::MaxWorkGroupSize)? { + DeviceInfoResult::MaxWorkGroupSize(size) => size, + _ => unreachable!(), + }, + max_work_item_sizes: match device.info(OclInfo::MaxWorkItemSizes)? { + DeviceInfoResult::MaxWorkItemSizes(wgs) => wgs, + _ => unreachable!(), + }, + max_work_item_dimensions: match device.info(OclInfo::MaxWorkItemDimensions)? { + DeviceInfoResult::MaxWorkItemDimensions(dim) => dim, + _ => unreachable!(), + }, + device, + }) + } +} + +impl DeviceList { + pub fn new() -> anyhow::Result { + let platforms = Platform::list(); + let mut list = Vec::with_capacity(platforms.len()); + for platform in platforms { + if let Ok(devices) = Device::list_all(platform) { + for device in devices { + list.push(DeviceInfo::new(device, platform)?); + } + } + } + Ok(Self(list)) + } +} + +impl std::fmt::Debug for DeviceInfo { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!( + f, + "{} ({}, MaxWorkGroupSize={}, MaxWorkItemSizes={}, MaxWorkItemDimensions={})", + self.name, + self.platform_name, + self.max_work_group_size, + self.max_work_item_sizes + .iter() + .map(|x| x.to_string()) + .collect::>() + .join(", "), + self.max_work_item_dimensions + ) + } +} + +impl std::ops::Deref for DeviceList { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::fmt::Debug for DeviceList { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + for (index, device) in self.0.iter().enumerate() { + writeln!(f, "Device #{} - {:?}\n", index, device)?; + } + Ok(()) + } +} diff --git a/src/utils/hash_pattern.rs b/src/utils/hash_pattern.rs new file mode 100644 index 0000000..d92a627 --- /dev/null +++ b/src/utils/hash_pattern.rs @@ -0,0 +1,92 @@ +use std::str::FromStr; + +use anyhow::bail; + +#[derive(Debug, Clone)] +pub struct HashPattern { + pub filter: String, + pub possibliity: f64, +} + +impl FromStr for HashPattern { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + let pattern = match s.trim().replace(" ", "").to_ascii_uppercase() { + x if x.len() <= 40 => "*".repeat(40 - x.len()) + &x, + _ => bail!("Invalid pattern: {}", s), + }; + let mut parts: Vec = vec![]; + + // Handle fixed 0-9A-F + let mut fixed_pos_count: usize = 0; + for i in 0..=4 { + let mut mask = String::new(); + let mut value = String::new(); + let mut activated = false; + for j in 0..8 { + let char = *pattern.chars().nth(i * 8 + j).get_or_insert(' '); + if char.is_ascii_hexdigit() { + fixed_pos_count += 1; + mask += "F"; + value += &String::from(char); + activated = true; + } else { + mask += "0"; + value += "0"; + } + } + if activated { + parts.push(format!("(h[{i}] & 0x{mask}) == 0x{value}")); + } + } + + // Handle wildcard G-Z + let mut wildcard_pos_all: [Vec; (b'Z' - b'G' + 1) as usize] = + std::default::Default::default(); + for (i, wildcard) in pattern.chars().enumerate() { + if ('G'..='Z').contains(&wildcard) { + wildcard_pos_all[((wildcard as u8) - b'G') as usize].push(i); + } + } + let mut wildcard_pos_count = 0; + + for wildcard in 'G'..='Z' { + let wildcard_pos = &wildcard_pos_all[((wildcard as u8) - b'G') as usize]; + if wildcard_pos.len() >= 2 { + for i in 1..wildcard_pos.len() { + let left_index = wildcard_pos[i - 1] / 8; + let right_index = wildcard_pos[i] / 8; + let left_digit = 7 - wildcard_pos[i - 1] % 8; + let right_digit = 7 - wildcard_pos[i] % 8; + parts.push(format!( + "(/* {}: h[{}][{}] == h[{}][{}] */ (h[{}] {} {}) & 0xF{}) == (h[{}] & 0xF{})", + wildcard, + left_index, + left_digit, + right_index, + right_digit, + left_index, + if right_digit > left_digit { "<<" } else { ">>" }, + right_digit.abs_diff(left_digit) * 4, + "0".repeat(right_digit), + right_index, + "0".repeat(right_digit), + )); + } + wildcard_pos_count += wildcard_pos.len() - 1; + } + } + + let filter = if !parts.is_empty() { + parts.join(" && ") + } else { + String::from("true") + }; + + Ok(HashPattern { + filter, + possibliity: (16f64).powi((fixed_pos_count + wildcard_pos_count) as i32), + }) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..eddcbc6 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,65 @@ +mod args; +mod device; +mod hash_pattern; +mod vanity_secret_key; + +use std::mem; + +pub use args::*; +pub use device::DeviceList; +pub use hash_pattern::HashPattern; +pub use vanity_secret_key::VanitySecretKey; + +/// Do SHA-1 padding manually +/// A SHA-1 block is 512 bit, so the output Vec length is a multiple of 16 +pub fn manually_prepare_sha1(hashdata: Vec) -> Vec { + // Length after padding + // Fill with 0x80 0x00 ... to 448 mod 512 bit, which is 56 mod 64 bytes + // plus u64's 8 bytes, the length is a multiple of 64 + let padded_length = hashdata.len() + (64 - ((hashdata.len() + 8) % 64)) + 8; + let mut result_u8 = Vec::with_capacity(padded_length); + result_u8.extend_from_slice(&hashdata); + result_u8.push(0x80); + result_u8.resize(padded_length, 0); + + // convert Vec to Vec + // https://stackoverflow.com/questions/49690459/converting-a-vecu32-to-vecu8-in-place-and-with-minimal-overhead + let mut result_u32 = unsafe { + let ptr = result_u8.as_mut_ptr() as *mut u32; + let length = result_u8.len() / 4; + let capacity = result_u8.capacity() / 4; + mem::forget(result_u8); + Vec::from_raw_parts(ptr, length, capacity) + }; + + // assert_eq!(result_u32.len() % 16, 0); + // SHA-1 uses big-endian words and length + for pos in &mut result_u32 { + *pos = pos.to_be(); + } + + let bit_length = hashdata.len() * 8; + result_u32[padded_length / 4 - 1] = (bit_length) as u32; + result_u32[padded_length / 4 - 2] = (bit_length >> 32) as u32; + result_u32 +} + +pub fn format_number(v: impl Into) -> String { + match Into::::into(v) { + v if v >= 1e12f64 => { + format!("{:.02}t", v / 1e12f64) + } + v if v >= 1e9f64 => { + format!("{:.02}b", v / 1e9f64) + } + v if v >= 1e6f64 => { + format!("{:.02}m", v / 1e6f64) + } + v if v >= 1e3f64 => { + format!("{:.02}k", v / 1e3f64) + } + v => { + format!("{v:.02}") + } + } +} diff --git a/src/vanity_gpg.rs b/src/utils/vanity_secret_key.rs similarity index 90% rename from src/vanity_gpg.rs rename to src/utils/vanity_secret_key.rs index 3850b55..deeca36 100644 --- a/src/vanity_gpg.rs +++ b/src/utils/vanity_secret_key.rs @@ -1,6 +1,5 @@ use byteorder::{BigEndian, ByteOrder}; -use clap::ValueEnum; -use log::debug; +use log::{debug, info}; use pgp::{ composed::{key::SecretKeyParamsBuilder, KeyType}, crypto::{ecc_curve::ECCCurve, hash::HashAlgorithm, sym::SymmetricKeyAlgorithm}, @@ -12,6 +11,10 @@ use pgp::{ use rand::{CryptoRng, Rng}; use smallvec::smallvec; +use crate::ARGS; + +use super::CipherSuite; + /// Get the data used to calculate the fingerprint of a private key fn build_secret_key_hashdata(secret_key: impl SecretKeyTrait) -> Vec { let mut hashdata = vec![0x99, 0, 0, 0x04, 0, 0, 0, 0]; @@ -26,24 +29,6 @@ fn build_secret_key_hashdata(secret_key: impl SecretKeyTrait) -> Vec { hashdata } -/// Cipher Suites -#[derive(ValueEnum, Default, Clone, Copy, Debug)] -#[clap(rename_all = "kebab_case")] -pub enum CipherSuite { - #[default] - Ed25519, - Cv25519, - RSA2048, - RSA3072, - RSA4096, - EcdhP256, - EcdhP384, - EcdhP521, - EcdsaP256, - EcdsaP384, - EcdsaP521, -} - pub struct VanitySecretKey { pub cipher_suite: CipherSuite, pub secret_key: SignedSecretKey, @@ -241,4 +226,33 @@ impl VanitySecretKey { self.secret_key .to_armored_string(pgp::ArmorOptions::default()) } + + pub fn log_state(&self) { + if ARGS.no_secret_key_logging { + info!("Get a vanity key!"); + } else { + info!( + "Get a vanity key: \n{}", + self.to_armored_string().unwrap_or_default() + ); + } + info!( + "Created at: {} ({})", + self.secret_key + .created_at() + .to_rfc3339_opts(chrono::SecondsFormat::Millis, true), + self.secret_key.created_at().timestamp(), + ); + info!( + "Fingerprint #0: {}", + hex::encode_upper(self.secret_key.fingerprint().as_bytes()) + ); + for (i, subkey) in self.secret_key.secret_subkeys.iter().enumerate() { + info!( + "Fingerprint #{}: {}", + i + 1, + hex::encode_upper(subkey.fingerprint().as_bytes()) + ); + } + } }