use anyhow::Result;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use vuke::analyze::{Analyzer, AnalyzerType, KeyMetadata, format_results, format_results_json, parse_private_key};
use vuke::derive::KeyDeriver;
use vuke::matcher::Matcher;
use vuke::network::parse_network;
use vuke::output::{ConsoleOutput, Output};
use vuke::source::{RangeSource, Source, StdinSource, TimestampSource, WordlistSource};
use vuke::transform::{Transform, TransformType};
#[derive(Parser)]
#[command(name = "vuke")]
#[command(about = "Research tool for studying vulnerable Bitcoin key generation practices")]
#[command(version)]
struct Cli {
#[command(subcommand)]
command: Command,
}
#[derive(Subcommand)]
enum Command {
Generate {
#[command(subcommand)]
source: SourceCommand,
#[arg(long, value_enum, num_args = 1.., default_value = "sha256")]
transform: Vec<TransformType>,
#[arg(long, default_value = "bitcoin")]
network: String,
#[arg(short, long)]
verbose: bool,
#[arg(short, long)]
output: Option<PathBuf>,
},
Scan {
#[command(subcommand)]
source: SourceCommand,
#[arg(long, value_enum, num_args = 1..)]
transform: Vec<TransformType>,
#[arg(long)]
targets: PathBuf,
#[arg(long, default_value = "bitcoin")]
network: String,
#[arg(short, long)]
output: Option<PathBuf>,
},
Single {
passphrase: String,
#[arg(long, value_enum, default_value = "sha256")]
transform: TransformType,
#[arg(long, default_value = "bitcoin")]
network: String,
},
Bench {
#[arg(long, value_enum, default_value = "sha256")]
transform: TransformType,
#[arg(long)]
json: bool,
},
Analyze {
key: String,
#[arg(long)]
fast: bool,
#[arg(long, value_name = "BITS", value_parser = clap::value_parser!(u8).range(1..=64))]
mask: Option<u8>,
#[arg(long, value_enum)]
analyzer: Option<Vec<AnalyzerType>>,
#[arg(long)]
json: bool,
},
}
#[derive(Subcommand, Clone)]
enum SourceCommand {
Range {
#[arg(long)]
start: u64,
#[arg(long)]
end: u64,
},
Wordlist {
#[arg(long)]
file: PathBuf,
},
Timestamps {
#[arg(long)]
start: String,
#[arg(long)]
end: String,
#[arg(long)]
microseconds: bool,
},
Stdin,
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Command::Generate {
source,
transform,
network,
verbose,
output,
} => {
let _network = parse_network(&network);
let out: Box<dyn Output> = match (output, verbose) {
(Some(path), true) => Box::new(ConsoleOutput::to_file_verbose(&path)?),
(Some(path), false) => Box::new(ConsoleOutput::to_file(&path)?),
(None, true) => Box::new(ConsoleOutput::verbose()),
(None, false) => Box::new(ConsoleOutput::new()),
};
run_generate(source, transform, out.as_ref())
}
Command::Scan {
source,
transform,
targets,
network: _,
output,
} => {
let out: Box<dyn Output> = match output {
Some(path) => Box::new(ConsoleOutput::to_file(&path)?),
None => Box::new(ConsoleOutput::new()),
};
run_scan(source, transform, targets, out.as_ref())
}
Command::Single {
passphrase,
transform,
network,
} => run_single(&passphrase, transform, &network),
Command::Bench { transform, json } => vuke::benchmark::run_benchmark(transform, json),
Command::Analyze { key, fast, mask, analyzer, json } => run_analyze(&key, fast, mask, analyzer, json),
}
}
fn run_generate(source_cmd: SourceCommand, transforms: Vec<TransformType>, output: &dyn Output) -> Result<()> {
let source = create_source(source_cmd)?;
let transforms = create_transforms(transforms);
eprintln!("Generating keys...");
let stats = source.process(&transforms, None, output)?;
output.flush()?;
eprintln!(
"Done. Inputs: {}, Keys: {}, Matches: {}",
stats.inputs_processed, stats.keys_generated, stats.matches_found
);
Ok(())
}
fn run_scan(
source_cmd: SourceCommand,
transforms: Vec<TransformType>,
targets: PathBuf,
output: &dyn Output,
) -> Result<()> {
eprintln!("Loading targets from {:?}...", targets);
let matcher = Matcher::load(&targets)?;
eprintln!("Loaded {} targets.", matcher.count());
let source = create_source(source_cmd)?;
let transforms = create_transforms(transforms);
eprintln!("Scanning...");
let stats = source.process(&transforms, Some(&matcher), output)?;
output.flush()?;
eprintln!(
"Done. Inputs: {}, Keys: {}, Matches: {}",
stats.inputs_processed, stats.keys_generated, stats.matches_found
);
Ok(())
}
fn run_single(passphrase: &str, transform_type: TransformType, network: &str) -> Result<()> {
use vuke::transform::Input;
let net = parse_network(network);
let deriver = KeyDeriver::with_network(net);
let transform = create_transform(transform_type);
let input = Input::from_string(passphrase.to_string());
let mut buffer = Vec::new();
transform.apply_batch(&[input], &mut buffer);
if buffer.is_empty() {
eprintln!("No key generated from passphrase.");
return Ok(());
}
for (source, key) in buffer {
let derived = deriver.derive(&key);
println!("Passphrase: \"{}\"", passphrase);
println!("Transform: {}", transform.name());
println!("Source: {}", source);
println!("---");
println!("Private Key (hex): {}", derived.private_key_hex);
println!("Private Key (decimal): {}", derived.private_key_decimal);
println!("Private Key (binary): {}", derived.private_key_binary);
println!("Bit Length: {}", derived.bit_length);
println!("Hamming Weight: {}", derived.hamming_weight);
println!("Leading Zeros (hex): {}", derived.leading_zeros);
println!("WIF (compressed): {}", derived.wif_compressed);
println!("WIF (uncompressed): {}", derived.wif_uncompressed);
println!("---");
println!("P2PKH (compressed): {}", derived.p2pkh_compressed);
println!("P2PKH (uncompressed): {}", derived.p2pkh_uncompressed);
println!("P2WPKH: {}", derived.p2wpkh);
}
Ok(())
}
fn create_source(cmd: SourceCommand) -> Result<Box<dyn Source>> {
match cmd {
SourceCommand::Range { start, end } => {
Ok(Box::new(RangeSource::new(start, end)))
}
SourceCommand::Wordlist { file } => {
Ok(Box::new(WordlistSource::from_file(&file)?))
}
SourceCommand::Timestamps { start, end, microseconds } => {
Ok(Box::new(TimestampSource::from_dates(&start, &end, microseconds)?))
}
SourceCommand::Stdin => {
Ok(Box::new(StdinSource::new()))
}
}
}
fn create_transforms(types: Vec<TransformType>) -> Vec<Box<dyn Transform>> {
types.into_iter().map(create_transform).collect()
}
fn create_transform(t: TransformType) -> Box<dyn Transform> {
use vuke::transform::*;
match t {
TransformType::Direct => Box::new(DirectTransform),
TransformType::Sha256 => Box::new(Sha256Transform),
TransformType::DoubleSha256 => Box::new(DoubleSha256Transform),
TransformType::Md5 => Box::new(Md5Transform),
TransformType::Milksad => Box::new(MilksadTransform),
TransformType::Armory => Box::new(ArmoryTransform::new()),
}
}
fn run_analyze(
key_input: &str,
fast: bool,
mask_bits: Option<u8>,
analyzer_types: Option<Vec<AnalyzerType>>,
json_output: bool,
) -> Result<()> {
use indicatif::ProgressBar;
use vuke::analyze::AnalysisConfig;
let key = parse_private_key(key_input)?;
let metadata = KeyMetadata::from_key(&key);
if let Some(bits) = mask_bits {
let key_bits = vuke::analyze::calculate_bit_length(&key);
if key_bits > bits as u16 {
eprintln!(
"Warning: key has {} bits but mask is {} bits. Key will be treated as already masked.",
key_bits, bits
);
}
}
let config = AnalysisConfig { mask_bits };
let analyzer_types = match analyzer_types {
Some(types) => types,
None if fast => AnalyzerType::fast(),
None => AnalyzerType::all(),
};
let analyzers: Vec<Box<dyn Analyzer>> = analyzer_types
.into_iter()
.map(|t| t.create())
.collect();
let mut results = Vec::new();
for analyzer in &analyzers {
let progress = if analyzer.is_brute_force() && !json_output {
let pb = ProgressBar::new(0);
pb.set_style(vuke::default_progress_style());
Some(pb)
} else {
None
};
let result = analyzer.analyze(&key, &config, progress.as_ref());
results.push(result);
}
if json_output {
println!("{}", format_results_json(&metadata, &results));
} else {
print!("{}", format_results(&metadata, &results));
}
Ok(())
}