diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index a11aa8d28b..332b0a286a 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -2792,6 +2792,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "subkey" version = "0.1.0" dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-primitives 0.1.0", ] diff --git a/substrate/subkey/Cargo.toml b/substrate/subkey/Cargo.toml index 171c5163b2..9e4249d54d 100644 --- a/substrate/subkey/Cargo.toml +++ b/substrate/subkey/Cargo.toml @@ -6,6 +6,7 @@ authors = ["Parity Technologies "] [dependencies] substrate-primitives = { version = "*", path = "../core/primitives" } rand = "0.4" +clap = { version = "~2.32", features = ["yaml"] } [features] bench = [] diff --git a/substrate/subkey/src/cli.yml b/substrate/subkey/src/cli.yml new file mode 100644 index 0000000000..30e6be0626 --- /dev/null +++ b/substrate/subkey/src/cli.yml @@ -0,0 +1,25 @@ +name: subkey +author: "Parity Team " +about: A substrate key utility +subcommands: + - restore: + about: Gets a SS58 public key from the provided seed phrase + args: + - seed: + index: 1 + required: true + help: 32 bytes long seed phrase used to restore the public key. If the provided seed is shorter than that, then + it will be right-padded with 0x20 bytes (ASCII space). If the provided seed is longer than + 32 bytes then seed will be truncated. + - vanity: + about: Generate vanity address + args: + - pattern: + index: 1 + help: Desired pattern + - number: + short: n + long: number + help: Number of keys to generate + takes_value: true + default_value: "1" diff --git a/substrate/subkey/src/main.rs b/substrate/subkey/src/main.rs index 2b824664a8..afe78f2a0b 100644 --- a/substrate/subkey/src/main.rs +++ b/substrate/subkey/src/main.rs @@ -1,173 +1,74 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + #![cfg_attr(feature = "bench", feature(test))] #[cfg(feature = "bench")] extern crate test; extern crate substrate_primitives; extern crate rand; -use rand::{OsRng, Rng}; -use std::env::args; +#[macro_use] +extern crate clap; + use substrate_primitives::{ed25519::Pair, hexdisplay::HexDisplay}; -use std::cmp; -fn good_waypoint(done: u64) -> u64 { - match done { - 0 ... 1_000_000 => 100_000, - 0 ... 10_000_000 => 1_000_000, - 0 ... 100_000_000 => 10_000_000, - _ => 100_000_000, - } -} - -fn next_seed(mut seed: [u8; 32]) -> [u8; 32] { - for i in 0..32 { - match seed[i] { - 255 => { seed[i] = 0; } - _ => { seed[i] += 1; break; } - } - } - return seed; -} - -/// A structure used to carry both Pair and seed. -/// This should usually NOT been used. If unsure, use Pair. -pub struct KeyPair { - pub pair: Pair, - pub seed: [u8; 32], - pub score: usize, -} - -/// Calculate the score of a key based on the desired -/// input. -fn calculate_score(_desired: &str, key: &str) -> usize { - for truncate in 0.._desired.len() { - let snip_size = _desired.len() - truncate; - let truncated = &_desired[0..snip_size]; - if let Some(pos) = key.find(truncated) { - let score = cmp::min(100, (51 - pos) + (snip_size * 50 / _desired.len())); - return score; - } - } - 0 -} - -pub fn generate_key(_desired: &str, _amount: usize, paranoiac: bool) -> Result, &str> { - println!("Generating {} keys with pattern '{}'", _amount, &_desired); - - let top = 30 + (_desired.len() * 32); - let mut best = 0; - let mut seed = [0u8; 32]; - let mut done = 0; - let mut res = vec![]; - - OsRng::new().unwrap().fill_bytes(&mut seed[..]); - - loop { - if res.len() >= _amount { break; } - - // reset to a new random seed at beginning and regularly after for paranoia. - if paranoiac || done % 100000 == 0 { - OsRng::new().unwrap().fill_bytes(&mut seed[..]); - } - - let p = Pair::from_seed(&seed); - let ss58 = p.public().to_ss58check(); - let score = calculate_score(&_desired, &ss58); - if score > best || _desired.len() < 2 { - best = score; - let keypair = KeyPair { - pair: p, - seed: seed.clone(), - score: score, - }; - res.push(keypair); - if best == top { - println!("best: {} == top: {}", best, top); - break; - } - } - seed = next_seed(seed); - done += 1; - - if done % good_waypoint(done) == 0 { - println!("Stopping after {} keys searched", done); - break; - } - } - res.sort_unstable_by(|a, b| b.score.cmp(&a.score)); - Ok(res) -} +mod vanity; fn main() { - let desired: String = args().nth(1).unwrap_or_default(); - let amount_of_keys: String = args().nth(2).unwrap_or_else(|| String::from("1")); - let amount_of_keys: usize = amount_of_keys.parse::().expect("Failed to parse number"); + let yaml = load_yaml!("cli.yml"); + let matches = clap::App::from_yaml(yaml).get_matches(); - let keys = generate_key(&desired, amount_of_keys, true).expect("Key generation failed"); - for key in keys { - println!("{} - {} ({}%)", - key.pair.public().to_ss58check(), - HexDisplay::from(&key.seed), - key.score); + match matches.subcommand() { + ("vanity", Some(matches)) => { + let desired: String = matches.value_of("pattern").map(str::to_string).unwrap_or_default(); + let amount_of_keys = matches.value_of("number") + .expect("`number` has a default value; thus it can't be None; qed"); + let amount_of_keys: usize = amount_of_keys.parse::().expect("Failed to parse number"); + + let keys = vanity::generate_key(&desired, amount_of_keys, true).expect("Key generation failed"); + for key in keys { + println!("{} - {} ({}%)", + key.pair.public().to_ss58check(), + HexDisplay::from(&key.seed), + key.score); + } + } + ("restore", Some(matches)) => { + let mut raw_seed = matches.value_of("seed") + .map(str::as_bytes) + .expect("seed parameter is required; thus it can't be None; qed"); + + if raw_seed.len() > 32 { + raw_seed = &raw_seed[..32]; + println!("seed is too long and will be truncated to: {}", HexDisplay::from(&raw_seed)); + } + + // Copy the raw_seed into a buffer that already contains ' ' 0x20. + // This will effectively get us padding for seeds shorter than 32. + let mut seed = [' ' as u8; 32]; + let len = raw_seed.len().min(32); + seed[..len].copy_from_slice(&raw_seed[..len]); + let pair = Pair::from_seed(&seed); + + println!("{}: {}", HexDisplay::from(&seed), pair.public().to_ss58check()); + }, + _ => print_usage(&matches), } } -#[cfg(test)] -mod tests { - use super::*; - #[cfg(feature = "bench")] - use test::Bencher; - - #[test] - fn test_generation_no_args() { - assert!(generate_key("",1, false).unwrap().len() == 1); - } - - #[test] - fn test_generation_with_single_char() { - assert!(generate_key("j", 1, false).unwrap().len() == 1); - } - - #[test] - fn test_generation_with_args() { - assert!(generate_key("polka", 2, false).unwrap().len() == 2); - } - - #[test] - fn test_score_1_char_100() { - let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"); - assert!(score == 100, format!("Wrong score, we found {}", score)); - } - - #[test] - fn test_score_100() { - let score = calculate_score("Polkadot", "5PolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"); - assert!( score == 100, format!("Wrong score, we found {}", score)); - } - - #[test] - fn test_score_50_2() { - // 50% for the position + 50% for the size - assert!(calculate_score("Polkadot", "5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim") == 75); - } - - #[test] - fn test_score_0() { - assert!(calculate_score("Polkadot", "5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK") == 0); - } - - #[cfg(feature = "bench")] - #[bench] - fn bench_paranoiac(b: &mut Bencher) { - b.iter(|| { - generate_key("polka", 3, true) - }); - } - - #[cfg(feature = "bench")] - #[bench] - fn bench_not_paranoiac(b: &mut Bencher) { - b.iter(|| { - generate_key("polka", 3, false) - }); - } +fn print_usage(matches: &clap::ArgMatches) { + println!("{}", matches.usage()); } diff --git a/substrate/subkey/src/vanity.rs b/substrate/subkey/src/vanity.rs new file mode 100644 index 0000000000..0d6d43065d --- /dev/null +++ b/substrate/subkey/src/vanity.rs @@ -0,0 +1,168 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +use rand::{OsRng, Rng}; +use substrate_primitives::{ed25519::Pair, hexdisplay::HexDisplay}; +use std::cmp; + +fn good_waypoint(done: u64) -> u64 { + match done { + 0 ... 1_000_000 => 100_000, + 0 ... 10_000_000 => 1_000_000, + 0 ... 100_000_000 => 10_000_000, + _ => 100_000_000, + } +} + +fn next_seed(mut seed: [u8; 32]) -> [u8; 32] { + for i in 0..32 { + match seed[i] { + 255 => { seed[i] = 0; } + _ => { seed[i] += 1; break; } + } + } + return seed; +} + +/// A structure used to carry both Pair and seed. +/// This should usually NOT been used. If unsure, use Pair. +pub struct KeyPair { + pub pair: Pair, + pub seed: [u8; 32], + pub score: usize, +} + +/// Calculate the score of a key based on the desired +/// input. +fn calculate_score(_desired: &str, key: &str) -> usize { + for truncate in 0.._desired.len() { + let snip_size = _desired.len() - truncate; + let truncated = &_desired[0..snip_size]; + if let Some(pos) = key.find(truncated) { + let score = cmp::min(100, (51 - pos) + (snip_size * 50 / _desired.len())); + return score; + } + } + 0 +} + +pub fn generate_key(_desired: &str, _amount: usize, paranoiac: bool) -> Result, &str> { + println!("Generating {} keys with pattern '{}'", _amount, &_desired); + + let top = 30 + (_desired.len() * 32); + let mut best = 0; + let mut seed = [0u8; 32]; + let mut done = 0; + let mut res = vec![]; + + OsRng::new().unwrap().fill_bytes(&mut seed[..]); + + loop { + if res.len() >= _amount { break; } + + // reset to a new random seed at beginning and regularly after for paranoia. + if paranoiac || done % 100000 == 0 { + OsRng::new().unwrap().fill_bytes(&mut seed[..]); + } + + let p = Pair::from_seed(&seed); + let ss58 = p.public().to_ss58check(); + let score = calculate_score(&_desired, &ss58); + if score > best || _desired.len() < 2 { + best = score; + let keypair = KeyPair { + pair: p, + seed: seed.clone(), + score: score, + }; + res.push(keypair); + if best == top { + println!("best: {} == top: {}", best, top); + break; + } + } + seed = next_seed(seed); + done += 1; + + if done % good_waypoint(done) == 0 { + println!("Stopping after {} keys searched", done); + break; + } + } + res.sort_unstable_by(|a, b| b.score.cmp(&a.score)); + Ok(res) +} + +#[cfg(test)] +mod tests { + use super::*; + #[cfg(feature = "bench")] + use test::Bencher; + + #[test] + fn test_generation_no_args() { + assert!(generate_key("",1, false).unwrap().len() == 1); + } + + #[test] + fn test_generation_with_single_char() { + assert!(generate_key("j", 1, false).unwrap().len() == 1); + } + + #[test] + fn test_generation_with_args() { + assert!(generate_key("polka", 2, false).unwrap().len() == 2); + } + + #[test] + fn test_score_1_char_100() { + let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"); + assert!(score == 100, format!("Wrong score, we found {}", score)); + } + + #[test] + fn test_score_100() { + let score = calculate_score("Polkadot", "5PolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"); + assert!( score == 100, format!("Wrong score, we found {}", score)); + } + + #[test] + fn test_score_50_2() { + // 50% for the position + 50% for the size + assert!(calculate_score("Polkadot", "5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim") == 75); + } + + #[test] + fn test_score_0() { + assert!(calculate_score("Polkadot", "5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK") == 0); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_paranoiac(b: &mut Bencher) { + b.iter(|| { + generate_key("polka", 3, true) + }); + } + + #[cfg(feature = "bench")] + #[bench] + fn bench_not_paranoiac(b: &mut Bencher) { + b.iter(|| { + generate_key("polka", 3, false) + }); + } +}