1
0
Fork 0
mirror of https://github.com/TransparentLC/opencl_vanity_gpg.git synced 2025-10-20 15:24:08 +00:00

refactor: tidy up

This commit is contained in:
GZTime 2025-01-09 00:58:37 +08:00
parent e224d84f1c
commit 7e70146a55
No known key found for this signature in database
GPG key ID: 373640C748EA3E19
8 changed files with 606 additions and 387 deletions

68
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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 <dummy@example.com>"), 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<String>,
/// 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<String>,
/// The dir where the vanity keys are saved
#[arg(short, long)]
output: Option<String>,
/// Device ID to use
#[arg(short, long)]
device: Option<usize>,
/// Adjust it to maximum your device's usage
#[arg(short, long)]
thread: Option<usize>,
/// 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<f64>,
/// 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<u32> length is a multiple of 16
fn manually_prepare_sha1(hashdata: Vec<u8>) -> Vec<u32> {
// 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<u8> to Vec<u32>
// 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<String> = 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<usize>; (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<f64>) -> String {
match Into::<f64>::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<dyn std::error::Error>> {
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 <dummy@example.com>"),
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<dyn std::error::Error>> {
_ => 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<dyn std::error::Error>> {
.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::<Vec<u32>>();
let (tx_result, rx_result) = channel::<Option<u32>>();
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<dyn std::error::Error>> {
.fill_val(0)
.build()?;
let (tx_hashdata, rx_hashdata) = channel::<Vec<u32>>();
let (tx_result, rx_result) = channel::<Option<u32>>();
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::<u32>::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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
)
.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<dyn std::error::Error>> {
Ok(())
}
fn opencl_thread(
buffer_result: Buffer<u32>,
pro_que: ProQue,
rx_hashdata: Receiver<Vec<u32>>,
tx_result: Sender<Option<u32>>,
) {
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::<u32>::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<f64>) -> 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
}

120
src/utils/args.rs Normal file
View file

@ -0,0 +1,120 @@
use clap::{Parser, ValueEnum};
use std::sync::LazyLock;
pub static ARGS: LazyLock<Args> = 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 <dummy@example.com>"), 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<String>,
/// 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<String>,
/// The dir where the vanity keys are saved
#[arg(short, long)]
pub output: Option<String>,
/// Device ID to use
#[arg(short, long)]
pub device: Option<usize>,
/// Adjust it to maximum your device's usage
#[arg(short, long)]
pub thread: Option<usize>,
/// 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<f64>,
/// 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 <dummy@example.com>"),
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,
}

87
src/utils/device.rs Normal file
View file

@ -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<usize>,
pub max_work_item_dimensions: u32,
// hold the actual device
pub device: Device,
}
pub struct DeviceList(pub Vec<DeviceInfo>);
impl DeviceInfo {
pub fn new(device: Device, platform: Platform) -> anyhow::Result<Self> {
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<Self> {
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::<Vec<String>>()
.join(", "),
self.max_work_item_dimensions
)
}
}
impl std::ops::Deref for DeviceList {
type Target = Vec<DeviceInfo>;
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(())
}
}

92
src/utils/hash_pattern.rs Normal file
View file

@ -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<Self, Self::Err> {
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<String> = 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<usize>; (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),
})
}
}

65
src/utils/mod.rs Normal file
View file

@ -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<u32> length is a multiple of 16
pub fn manually_prepare_sha1(hashdata: Vec<u8>) -> Vec<u32> {
// 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<u8> to Vec<u32>
// 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<f64>) -> String {
match Into::<f64>::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}")
}
}
}

View file

@ -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<u8> {
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<u8> {
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())
);
}
}
}