Assorted improvements for ethstore and ethkey (#6961)

* Testing many passwords for presale wallet.

* Add multiple threads.

* WiP: ethkey brain wallets recover.

* Refactor pre-sale-wallet cracking.

* Generate in multiple threads. Brain with prefix.

* Validate bain wallet phrase.

* Brain wallet recovery.

* Self-review fixes.

* Fix tests.

* More docs.

* Bump versions.

* Remove cmd_find from borked merge.

* Update wasm submodules.

* Use threadpool.
This commit is contained in:
Tomasz Drwięga
2017-12-01 09:40:07 +01:00
committed by Svyatoslav Nikolsky
parent d1bf0e0e62
commit 7663451116
16 changed files with 674 additions and 119 deletions

View File

@@ -15,32 +15,36 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
extern crate docopt;
extern crate rustc_hex;
extern crate serde;
#[macro_use]
extern crate serde_derive;
extern crate env_logger;
extern crate ethkey;
extern crate panic_hook;
extern crate parity_wordlist;
extern crate rustc_hex;
extern crate serde;
extern crate threadpool;
#[macro_use]
extern crate serde_derive;
use std::{env, fmt, process};
use std::num::ParseIntError;
use std::{env, fmt, process, io, sync};
use docopt::Docopt;
use ethkey::{KeyPair, Random, Brain, BrainPrefix, Prefix, Error as EthkeyError, Generator, sign, verify_public, verify_address, brain_recover};
use rustc_hex::{ToHex, FromHex, FromHexError};
use ethkey::{KeyPair, Random, Brain, Prefix, Error as EthkeyError, Generator, sign, verify_public, verify_address};
use std::io;
pub const USAGE: &'static str = r#"
Ethereum keys generator.
Copyright 2016, 2017 Parity Technologies (UK) Ltd
Usage:
ethkey info <secret> [options]
ethkey info <secret-or-phrase> [options]
ethkey generate random [options]
ethkey generate prefix <prefix> <iterations> [options]
ethkey generate brain <seed> [options]
ethkey generate prefix <prefix> [options]
ethkey sign <secret> <message>
ethkey verify public <public> <signature> <message>
ethkey verify address <address> <signature> <message>
ethkey recover <address> <known-phrase>
ethkey [-h | --help]
Options:
@@ -48,15 +52,15 @@ Options:
-s, --secret Display only the secret.
-p, --public Display only the public.
-a, --address Display only the address.
-b, --brain Use parity brain wallet algorithm.
Commands:
info Display public and address of the secret.
generate Generates new ethereum key.
random Random generation.
prefix Random generation, but address must start with a prefix
brain Generate new key from string seed.
generate random Generates new random ethereum key.
generate prefix Random generation, but address must start with a prefix.
sign Sign message using secret.
verify Verify signer of the signature.
recover Try to find brain phrase matching given address from partial phrase.
"#;
#[derive(Debug, Deserialize)]
@@ -65,15 +69,15 @@ struct Args {
cmd_generate: bool,
cmd_random: bool,
cmd_prefix: bool,
cmd_brain: bool,
cmd_sign: bool,
cmd_verify: bool,
cmd_public: bool,
cmd_address: bool,
cmd_recover: bool,
arg_prefix: String,
arg_iterations: String,
arg_seed: String,
arg_secret: String,
arg_secret_or_phrase: String,
arg_known_phrase: String,
arg_message: String,
arg_public: String,
arg_address: String,
@@ -81,6 +85,7 @@ struct Args {
flag_secret: bool,
flag_public: bool,
flag_address: bool,
flag_brain: bool,
}
#[derive(Debug)]
@@ -157,6 +162,7 @@ impl DisplayMode {
fn main() {
panic_hook::set();
env_logger::init().expect("Logger initialized only once.");
match execute(env::args()) {
Ok(ok) => println!("{}", ok),
@@ -167,9 +173,13 @@ fn main() {
}
}
fn display(keypair: KeyPair, mode: DisplayMode) -> String {
fn display(result: (KeyPair, Option<String>), mode: DisplayMode) -> String {
let keypair = result.0;
match mode {
DisplayMode::KeyPair => format!("{}", keypair),
DisplayMode::KeyPair => match result.1 {
Some(extra_data) => format!("{}\n{}", extra_data, keypair),
None => format!("{}", keypair)
},
DisplayMode::Secret => format!("{}", keypair.secret().to_hex()),
DisplayMode::Public => format!("{:?}", keypair.public()),
DisplayMode::Address => format!("{:?}", keypair.address()),
@@ -182,23 +192,53 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
return if args.cmd_info {
let display_mode = DisplayMode::new(&args);
let secret = args.arg_secret.parse().map_err(|_| EthkeyError::InvalidSecret)?;
let keypair = KeyPair::from_secret(secret)?;
Ok(display(keypair, display_mode))
let result = if args.flag_brain {
let phrase = args.arg_secret_or_phrase;
let phrase_info = validate_phrase(&phrase);
let keypair = Brain::new(phrase).generate().expect("Brain wallet generator is infallible; qed");
(keypair, Some(phrase_info))
} else {
let secret = args.arg_secret_or_phrase.parse().map_err(|_| EthkeyError::InvalidSecret)?;
(KeyPair::from_secret(secret)?, None)
};
Ok(display(result, display_mode))
} else if args.cmd_generate {
let display_mode = DisplayMode::new(&args);
let keypair = if args.cmd_random {
Random.generate()?
let result = if args.cmd_random {
if args.flag_brain {
let mut brain = BrainPrefix::new(vec![0], usize::max_value(), BRAIN_WORDS);
let keypair = brain.generate()?;
let phrase = format!("recovery phrase: {}", brain.phrase());
(keypair, Some(phrase))
} else {
(Random.generate()?, None)
}
} else if args.cmd_prefix {
let prefix = args.arg_prefix.from_hex()?;
let iterations = usize::from_str_radix(&args.arg_iterations, 10)?;
Prefix::new(prefix, iterations).generate()?
} else if args.cmd_brain {
Brain::new(args.arg_seed).generate().expect("Brain wallet generator is infallible; qed")
let brain = args.flag_brain;
in_threads(move || {
let iterations = 1024;
let prefix = prefix.clone();
move || {
let prefix = prefix.clone();
let res = if brain {
let mut brain = BrainPrefix::new(prefix, iterations, BRAIN_WORDS);
let result = brain.generate();
let phrase = format!("recovery phrase: {}", brain.phrase());
result.map(|keypair| (keypair, Some(phrase)))
} else {
let result = Prefix::new(prefix, iterations).generate();
result.map(|res| (res, None))
};
Ok(res.map(Some).unwrap_or(None))
}
})?
} else {
unreachable!();
return Ok(format!("{}", USAGE))
};
Ok(display(keypair, display_mode))
Ok(display(result, display_mode))
} else if args.cmd_sign {
let secret = args.arg_secret.parse().map_err(|_| EthkeyError::InvalidSecret)?;
let message = args.arg_message.parse().map_err(|_| EthkeyError::InvalidMessage)?;
@@ -214,14 +254,89 @@ fn execute<S, I>(command: I) -> Result<String, Error> where I: IntoIterator<Item
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
verify_address(&address, &signature, &message)?
} else {
unreachable!();
return Ok(format!("{}", USAGE))
};
Ok(format!("{}", ok))
} else if args.cmd_recover {
let display_mode = DisplayMode::new(&args);
let known_phrase = args.arg_known_phrase;
let address = args.arg_address.parse().map_err(|_| EthkeyError::InvalidAddress)?;
let (phrase, keypair) = in_threads(move || {
let mut it = brain_recover::PhrasesIterator::from_known_phrase(&known_phrase, BRAIN_WORDS);
move || {
let mut i = 0;
while let Some(phrase) = it.next() {
i += 1;
let keypair = Brain::new(phrase.clone()).generate().unwrap();
if keypair.address() == address {
return Ok(Some((phrase, keypair)))
}
if i >= 1024 {
return Ok(None)
}
}
Err(EthkeyError::Custom("Couldn't find any results.".into()))
}
})?;
Ok(display((keypair, Some(phrase)), display_mode))
} else {
unreachable!();
Ok(format!("{}", USAGE))
}
}
const BRAIN_WORDS: usize = 12;
fn validate_phrase(phrase: &str) -> String {
match Brain::validate_phrase(phrase, BRAIN_WORDS) {
Ok(()) => format!("The recovery phrase looks correct.\n"),
Err(err) => format!("The recover phrase was not generated by Parity: {}", err)
}
}
fn in_threads<F, X, O>(prepare: F) -> Result<O, EthkeyError> where
O: Send + 'static,
X: Send + 'static,
F: Fn() -> X,
X: FnMut() -> Result<Option<O>, EthkeyError>,
{
let pool = threadpool::Builder::new().build();
let (tx, rx) = sync::mpsc::sync_channel(1);
let is_done = sync::Arc::new(sync::atomic::AtomicBool::default());
for _ in 0..pool.max_count() {
let is_done = is_done.clone();
let tx = tx.clone();
let mut task = prepare();
pool.execute(move || {
loop {
if is_done.load(sync::atomic::Ordering::SeqCst) {
return;
}
let res = match task() {
Ok(None) => continue,
Ok(Some(v)) => Ok(v),
Err(err) => Err(err),
};
// We are interested only in the first response.
let _ = tx.send(res);
}
});
}
if let Ok(solution) = rx.recv() {
is_done.store(true, sync::atomic::Ordering::SeqCst);
return solution;
}
Err(EthkeyError::Custom("No results found.".into()))
}
#[cfg(test)]
mod tests {
use super::execute;
@@ -242,13 +357,15 @@ address: 26d1ec50b4e62c1d1a40d16e7cacc6a6580757d5".to_owned();
#[test]
fn brain() {
let command = vec!["ethkey", "generate", "brain", "this is sparta"]
let command = vec!["ethkey", "info", "--brain", "this is sparta"]
.into_iter()
.map(Into::into)
.collect::<Vec<String>>();
let expected =
"secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
"The recover phrase was not generated by Parity: The word 'this' does not come from the dictionary.
secret: aa22b54c0cb43ee30a014afe5ef3664b1cde299feabca46cd3167a85a57c39f2
public: c4c5398da6843632c123f543d714d2d2277716c11ff612b2a2f23c6bda4d6f0327c31cd58c55a9572c3cc141dade0c32747a13b7ef34c241b26c84adbb28fcf4
address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
assert_eq!(execute(command).unwrap(), expected);
@@ -256,7 +373,7 @@ address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
#[test]
fn secret() {
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--secret"]
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--secret"]
.into_iter()
.map(Into::into)
.collect::<Vec<String>>();
@@ -267,7 +384,7 @@ address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
#[test]
fn public() {
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--public"]
let command = vec!["ethkey", "info", "--brain", "this is sparta", "--public"]
.into_iter()
.map(Into::into)
.collect::<Vec<String>>();
@@ -278,7 +395,7 @@ address: 006e27b6a72e1f34c626762f3c4761547aff1421".to_owned();
#[test]
fn address() {
let command = vec!["ethkey", "generate", "brain", "this is sparta", "--address"]
let command = vec!["ethkey", "info", "-b", "this is sparta", "--address"]
.into_iter()
.map(Into::into)
.collect::<Vec<String>>();