diff --git a/Cargo.lock b/Cargo.lock index 6ccc376326370915e1d650a462a496c651947e10..e5907165271c519889e8fd98b613c30c34cb671e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,14 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -91,18 +100,6 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "476e9cd489f9e121e02ffa6014a8ef220ecb15c05ed23fc34cca13925dc283fb" -[[package]] -name = "bstr" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473fc6b38233f9af7baa94fb5852dca389e3d95b8e21c8e3719301462c5d9faf" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] - [[package]] name = "bumpalo" version = "3.4.0" @@ -237,28 +234,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da24927b5b899890bcb29205436c957b7892ec3a3fbffce81d710b9611e77778" -[[package]] -name = "csv" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9d58633299b24b515ac72a3f869f8b91306a3cec616a602843a383acd6f9e97" -dependencies = [ - "bstr", - "csv-core", - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "digest" version = "0.9.0" @@ -304,10 +279,10 @@ name = "g1force" version = "0.1.0" dependencies = [ "bruteforce", - "csv", "dup-crypto", "indicatif", "rayon", + "regex", "structopt", ] @@ -364,12 +339,6 @@ dependencies = [ "regex", ] -[[package]] -name = "itoa" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" - [[package]] name = "js-sys" version = "0.3.46" @@ -516,16 +485,10 @@ version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" -dependencies = [ - "byteorder", + "thread_local", ] [[package]] @@ -549,12 +512,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "ryu" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" - [[package]] name = "scopeguard" version = "1.1.0" @@ -685,6 +642,15 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb9bc092d0d51e76b2b19d9d85534ffc9ec2db959a2523cdae0697e2972cd447" +dependencies = [ + "lazy_static", +] + [[package]] name = "typenum" version = "1.12.0" diff --git a/Cargo.toml b/Cargo.toml index ac926510b71a826aae2e6581920f160514cdf32a..9d35faa2380a81d47bc0c84e6be1f9d77c9646bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,8 @@ license = "AGPL3" [dependencies] bruteforce = "0.2.0" -csv = "1.1.5" dup-crypto = { version = "0.38.0", default-features = false, features = ["scrypt"] } indicatif = "0.15.0" rayon = "1.5.0" +regex = "1.4.3" structopt = "0.3.21" diff --git a/README.md b/README.md index 1b4442c11b079bacd0134fcf943ae1e30de2731b..c65ca2581b91aaeb9107661d3357c88289c26dcf 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Scrypt bruteforce for Duniter currencies ## Build +You need to switch to Rust Nightly first. + cargo build --release ## Use @@ -31,4 +33,4 @@ Dictionary attack should be implemented. ## License -GNU AGPL v3, CopyLeft 2020 Pascal Engélibert +GNU AGPL v3, CopyLeft 2020-2021 Pascal Engélibert diff --git a/src/main.rs b/src/main.rs index f5fa8df1dd712ca72b58b03f22d7d803eac6c208..b8f05b769174f3bcca21ed347dc14c958cb2ce2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,8 @@ +#![feature(bool_to_option)] +#![feature(iter_map_while)] +#![feature(maybe_uninit_ref)] +#![feature(str_split_once)] + use bruteforce::{charset::Charset, BruteForce}; use dup_crypto::{ bases::b58::ToBase58, @@ -8,7 +13,13 @@ use dup_crypto::{ }; use indicatif::{ProgressBar, ProgressStyle}; use rayon::prelude::*; -use std::{collections::HashMap, io::stdin, sync::Mutex}; +use regex::Regex; +use std::{ + collections::HashMap, + io::{stdin, BufRead, BufReader}, + mem::MaybeUninit, + sync::RwLock, +}; use structopt::StructOpt; @@ -19,7 +30,7 @@ const CHARSET: Charset = Charset::new(&[ '5', '6', '7', '8', '9', '_', '.', '-', '!', '@', '*', '$', '?', '&', '%', ' ', ]); -struct PubkeyData { +struct PubkeyMetadata { amount: u32, member: bool, } @@ -27,155 +38,223 @@ struct PubkeyData { #[derive(Clone, Debug, StructOpt)] #[structopt(name = "g1force")] pub struct MainOpt { + /// Use metadata in CSV (amount & member) + #[structopt(short, long)] + metadata: bool, + /// Number of iterations #[structopt(short, long)] ntests: usize, + /// Use regex (Perl-style) #[structopt(short, long)] - wanted_pubkey: Option<String>, + regex: bool, + + /// Wanted pubkey or regex. If empty, reads stdin. + #[structopt(short, long)] + pubkey: Option<String>, } -fn main() { - let opt = MainOpt::from_args(); +enum InputData { + Pubkey(PublicKey), + PubkeyList(Vec<PublicKey>), + PubkeyListWithMetadata(HashMap<PublicKey, PubkeyMetadata>), + Regex(Regex), +} - if let Some(ref wanted_pubkey) = opt.wanted_pubkey { - let wanted_pubkey = PublicKey::from_base58(&wanted_pubkey).expect("Invalid pubkey"); - - let generator = KeyPairFromSaltedPasswordGenerator::with_default_parameters(); - - let progress_bar = ProgressBar::new((opt.ntests as u64).pow(2)); - progress_bar.set_style( - ProgressStyle::default_bar().template( - "{elapsed_precise} {eta} {per_sec} {pos}/{len} {percent}% {wide_bar}", - ), - ); - - let mut i = 0usize; - let mut bf1 = BruteForce::new(CHARSET); - let mut buf = Vec::<String>::new(); - progress_bar.tick(); - loop { - while buf.len() < 16 && i < opt.ntests { - if let Some(s1) = bf1.next() { - buf.push(s1); - i += 1; - } else { - break; - } +impl InputData { + fn new(opt: &MainOpt) -> Self { + if let Some(ref pubkey) = opt.pubkey { + if opt.regex { + Self::Regex(Regex::new(pubkey).expect("Invalid regex")) + } else { + Self::Pubkey(PublicKey::from_base58(pubkey).expect("Invalid pubkey")) } - if buf.is_empty() { - break; + } else if opt.metadata { + let mut pubkeys = HashMap::<PublicKey, PubkeyMetadata>::new(); + + for (i, raw_line) in BufReader::new(stdin()).lines().enumerate() { + let raw_line = raw_line.expect("Error reading CSV"); + let mut rows = raw_line.split(&[' ', '\t', ',', ';'][..]); + let pubkey = PublicKey::from_base58( + rows.next().expect(&format!("Missing pubkey line {}", i)), + ) + .expect(&format!("Invalid pubkey line {}", i)); + pubkeys.insert( + pubkey, + PubkeyMetadata { + amount: rows + .next() + .expect(&format!("Missing amount line {}", i)) + .parse() + .expect(&format!("Invalid amount line {}", i)), + member: rows.next().expect(&format!("Missing member line {}", i)) == "1", + }, + ); } - buf.par_iter().for_each(|s1| { - let mut j = 0usize; - let bf2 = BruteForce::new(CHARSET); - for s2 in bf2 { - let pubkey = generator - .generate(SaltedPassword::new(s1.clone(), s2.clone())) - .public_key(); - if wanted_pubkey == pubkey { - eprintln!("Found!"); - println!("{}\t{}", s1, s2); - std::process::exit(0); - } - if j >= opt.ntests { - break; - } - j += 1; - } - progress_bar.inc(opt.ntests as u64); - }); - buf.clear(); + Self::PubkeyListWithMetadata(pubkeys) + } else { + let mut pubkeys = Vec::<PublicKey>::new(); + + for (i, raw_line) in BufReader::new(stdin()).lines().enumerate() { + let raw_line = raw_line.expect("Error reading CSV"); + let pubkey = PublicKey::from_base58( + raw_line + .split_once(&[' ', '\t', ',', ';'][..]) + .expect(&format!("Missing pubkey line {}", i)) + .0, + ) + .expect(&format!("Invalid pubkey line {}", i)); + pubkeys.push(pubkey); + } + Self::PubkeyList(pubkeys) } - eprintln!("Not found...\n"); - } else { - eprintln!("Importing data..."); - - let mut pubkeys = HashMap::<PublicKey, PubkeyData>::new(); - - let mut reader = csv::Reader::from_reader(stdin()); - for raw_line in reader.records().filter_map(|x| x.ok()) { - let mut rows = raw_line.as_slice().split('\t'); - let pubkey = PublicKey::from_base58(rows.next().unwrap()).unwrap(); - pubkeys.insert( - pubkey, - PubkeyData { - amount: rows.next().unwrap().parse().unwrap(), - member: rows.next().unwrap() == "1", - }, - ); + } +} + +struct Tester<'a> { + pubkey: MaybeUninit<&'a PublicKey>, + pubkey_list: MaybeUninit<&'a Vec<PublicKey>>, + pubkey_list_with_metadata: MaybeUninit<&'a HashMap<PublicKey, PubkeyMetadata>>, + regex: MaybeUninit<&'a Regex>, + + /// Returns (match, stop) + test: fn(&Self, &PublicKey) -> (bool, bool), +} + +impl<'a> Tester<'a> { + fn new(data: &'a InputData) -> Self { + match data { + InputData::Pubkey(pubkey) => Self { + pubkey: MaybeUninit::new(pubkey), + pubkey_list: MaybeUninit::uninit(), + pubkey_list_with_metadata: MaybeUninit::uninit(), + regex: MaybeUninit::uninit(), + test: Self::test_pubkey, + }, + InputData::PubkeyList(pubkey_list) => Self { + pubkey: MaybeUninit::uninit(), + pubkey_list: MaybeUninit::new(pubkey_list), + pubkey_list_with_metadata: MaybeUninit::uninit(), + regex: MaybeUninit::uninit(), + test: Self::test_pubkey_list, + }, + InputData::PubkeyListWithMetadata(pubkey_list_with_metadata) => Self { + pubkey: MaybeUninit::uninit(), + pubkey_list: MaybeUninit::uninit(), + pubkey_list_with_metadata: MaybeUninit::new(pubkey_list_with_metadata), + regex: MaybeUninit::uninit(), + test: Self::test_pubkey_list_with_metadata, + }, + InputData::Regex(regex) => Self { + pubkey: MaybeUninit::uninit(), + pubkey_list: MaybeUninit::uninit(), + pubkey_list_with_metadata: MaybeUninit::uninit(), + regex: MaybeUninit::new(regex), + test: Self::test_regex, + }, } + } - let found = Mutex::new(Vec::<(PublicKey, String, String)>::new()); - - let generator = KeyPairFromSaltedPasswordGenerator::with_default_parameters(); - - let progress_bar = ProgressBar::new((opt.ntests as u64).pow(2)); - progress_bar.set_style( - ProgressStyle::default_bar().template( - "{elapsed_precise} {eta} {per_sec} {pos}/{len} {percent}% {wide_bar}", - ), - ); - - let mut i = 0usize; - let mut bf1 = BruteForce::new(CHARSET); - let mut buf = Vec::<String>::new(); - progress_bar.tick(); - loop { - while buf.len() < 16 && i < opt.ntests { - if let Some(s1) = bf1.next() { - buf.push(s1); - i += 1; - } else { + fn test_pubkey(&self, x: &PublicKey) -> (bool, bool) { + (x == unsafe { self.pubkey.assume_init() }, true) + } + + fn test_pubkey_list(&self, x: &PublicKey) -> (bool, bool) { + (unsafe { self.pubkey_list.assume_init() }.contains(x), false) + } + + fn test_pubkey_list_with_metadata(&self, x: &PublicKey) -> (bool, bool) { + ( + unsafe { self.pubkey_list_with_metadata.assume_init() }.contains_key(x), + false, + ) + } + + fn test_regex(&self, x: &PublicKey) -> (bool, bool) { + ( + unsafe { self.regex.assume_init() }.is_match(&x.to_base58()), + false, + ) + } +} + +fn main() { + let opt = MainOpt::from_args(); + let data = InputData::new(&opt); + + let tester = Tester::new(&data); + + let generator = KeyPairFromSaltedPasswordGenerator::with_default_parameters(); + + let progress_bar = ProgressBar::new(opt.ntests as u64); + progress_bar.set_style( + ProgressStyle::default_bar() + .template("{elapsed_precise} {eta} {per_sec} {pos}/{len} {percent}% {wide_bar}"), + ); + progress_bar.tick(); + + let found = RwLock::new(Vec::<(PublicKey, String, String)>::new()); + let continue_test = RwLock::new(true); + + BruteForce::new(CHARSET) + .enumerate() + .map_while(|(i, s1)| (*continue_test.read().unwrap() && i < opt.ntests).then_some(s1)) + .par_bridge() + .for_each(|s1| { + let mut j = 0usize; + for s2 in BruteForce::new(CHARSET) { + let pubkey = generator + .generate(SaltedPassword::new(s1.clone(), s2.clone())) + .public_key(); + let (res_match, res_stop) = (tester.test)(&tester, &pubkey); + if res_match { + let mut found = found.write().unwrap(); + found.push((pubkey, s1.clone(), s2)); + } + if res_stop { + let mut continue_test = continue_test.write().unwrap(); + *continue_test = true; + } + if j >= opt.ntests { break; } + j += 1; } - if buf.is_empty() { - break; - } - buf.par_iter().for_each(|s1| { - let mut j = 0usize; - let bf2 = BruteForce::new(CHARSET); - for s2 in bf2 { - let pubkey = generator - .generate(SaltedPassword::new(s1.clone(), s2.clone())) - .public_key(); - if pubkeys.contains_key(&pubkey) { - let mut mutex = found.lock().unwrap(); - mutex.push((pubkey, s1.clone(), s2)); - } - if j >= opt.ntests { - break; - } - j += 1; - } - progress_bar.inc(opt.ntests as u64); - }); - buf.clear(); - } + progress_bar.inc(1u64); + }); - let mut total_amount = 0u32; - let mut total_member = 0u32; + let found = found.into_inner().unwrap(); - let found = found.lock().unwrap(); - for line in found.iter() { - let data = pubkeys.get(&line.0).unwrap(); - total_amount += data.amount; - if data.member { - total_member += 1; + match data { + InputData::PubkeyListWithMetadata(pubkey_list_with_metadata) => { + let mut total_amount = 0u32; + let mut total_member = 0u32; + + for (pubkey, s1, s2) in found { + let metadata = pubkey_list_with_metadata.get(&pubkey).unwrap(); + total_amount += metadata.amount; + if metadata.member { + total_member += 1; + } + println!( + "{}\t{}\t{}\t{}\t{}", + pubkey.to_base58(), + s1, + s2, + metadata.amount, + metadata.member as u8, + ); } - println!( - "{}\t{}\t{}\t{}\t{}", - line.0.to_base58(), - line.1, - line.2, - data.amount, - data.member as u8 - ); - } - eprintln!("Total amount: {}", total_amount); - eprintln!("Total member: {}", total_member); - eprintln!(""); + eprintln!("Total amount: {}", total_amount); + eprintln!("Total member: {}", total_member); + } + _ => { + for (pubkey, s1, s2) in found { + println!("{}\t{}\t{}", pubkey.to_base58(), s1, s2); + } + } } + eprintln!(); }