diff --git a/Cargo.lock b/Cargo.lock index 9fafbf6f49cec..514469df42f58 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -79,6 +79,11 @@ dependencies = [ "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base58" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "base64" version = "0.9.0" @@ -310,6 +315,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "ed25519" version = "0.1.0" dependencies = [ + "base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "blake2-rfc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", "hex-literal 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "ring 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", "substrate-primitives 0.1.0", @@ -1839,6 +1846,14 @@ name = "strsim" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "subkey" +version = "0.1.0" +dependencies = [ + "ed25519 0.1.0", + "substrate-primitives 0.1.0", +] + [[package]] name = "substrate-bft" version = "0.1.0" @@ -2706,6 +2721,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" "checksum backtrace 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbbf59b1c43eefa8c3ede390fcc36820b4999f7914104015be25025e0d62af2" "checksum backtrace-sys 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "44585761d6161b0f57afc49482ab6bd067e4edef48c12a152c237eb0203f7661" +"checksum base58 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5024ee8015f02155eee35c711107ddd9a9bf3cb689cf2a9089c97e79b6e1ae83" "checksum base64 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "229d032f1a99302697f10b27167ae6d03d49d032e6a8e2550e8d3fc13356d2b4" "checksum bigint 4.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5442186ef6560f30f1ee4b9c1e4c87a35a6879d3644550cc248ec2b955eb5fcd" "checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" diff --git a/Cargo.toml b/Cargo.toml index ea3dc865ab2e4..61f26c765753c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -60,6 +60,7 @@ members = [ "demo/executor", "demo/cli", "safe-mix", + "subkey", ] exclude = [ "polkadot/runtime/wasm", diff --git a/subkey/Cargo.toml b/subkey/Cargo.toml new file mode 100644 index 0000000000000..942da8bdd6c18 --- /dev/null +++ b/subkey/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "subkey" +version = "0.1.0" +authors = ["Parity Technologies "] + +[dependencies] +ed25519 = { version = "*", path = "../substrate/ed25519" } +substrate-primitives = { version = "*", path = "../substrate/primitives" } diff --git a/subkey/src/main.rs b/subkey/src/main.rs new file mode 100644 index 0000000000000..dba14ef37f877 --- /dev/null +++ b/subkey/src/main.rs @@ -0,0 +1,55 @@ +extern crate ed25519; +extern crate substrate_primitives; + +use std::env::args; +use ed25519::Pair; +use substrate_primitives::hexdisplay::HexDisplay; + +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 main() { + if args().len() != 2 { + println!("Usage: subkey "); + return; + } + let desired = args().last().unwrap(); + let score = |s: &str| { + for truncate in 0..desired.len() - 1 { + let snip_size = desired.len() - truncate; + let truncated = &desired[0..snip_size]; + if let Some(pos) = s.find(truncated) { + return (31 - pos) + (snip_size * 32); + } + } + 0 + }; + let top = 30 + (desired.len() * 32); + let mut best = 0; + let mut seed = Pair::generate().public().0; + let mut done = 0; + loop { + let p = Pair::from_seed(&seed); + let ss58 = p.public().to_ss58check(); + let s = score(&ss58); + if s > best { + println!("{}: {} ({}% complete)", ss58, HexDisplay::from(&seed), s * 100 / top); + best = s; + if best == top { + break; + } + } + seed = p.public().0; + done += 1; + + if done % good_waypoint(done) == 0 { + println!("{} keys searched", done); + } + } +} diff --git a/substrate/ed25519/Cargo.toml b/substrate/ed25519/Cargo.toml index 1241bd69bf15d..34494fc8be9b0 100644 --- a/substrate/ed25519/Cargo.toml +++ b/substrate/ed25519/Cargo.toml @@ -8,3 +8,5 @@ ring = "0.12" untrusted = "0.5" substrate-primitives = { version = "0.1", path = "../primitives" } hex-literal = "0.1" +base58 = "0.1" +blake2-rfc = "0.2" diff --git a/substrate/ed25519/src/lib.rs b/substrate/ed25519/src/lib.rs index 390caa46c46a0..f659ad5405b9d 100644 --- a/substrate/ed25519/src/lib.rs +++ b/substrate/ed25519/src/lib.rs @@ -17,11 +17,14 @@ //! Simple Ed25519 API. extern crate ring; +extern crate base58; extern crate substrate_primitives as primitives; extern crate untrusted; +extern crate blake2_rfc; use ring::{rand, signature}; use primitives::hash::H512; +use base58::{ToBase58, FromBase58}; #[cfg(test)] #[macro_use] @@ -67,6 +70,14 @@ impl ::std::hash::Hash for Public { } } +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum PublicError { + BadBase58, + BadLength, + UnknownVersion, + InvalidChecksum, +} + impl Public { /// A new instance from the given 32-byte `data`. pub fn from_raw(data: [u8; 32]) -> Self { @@ -80,6 +91,24 @@ impl Public { Public(r) } + /// Some if the string is a properly encoded SS58Check address. + pub fn from_ss58check(s: &str) -> Result { + let d = s.from_base58().map_err(|_| PublicError::BadBase58)?; // failure here would be invalid encoding. + if d.len() != 35 { + // Invalid length. + return Err(PublicError::BadLength); + } + if d[0] != 42 { + // Invalid version. + return Err(PublicError::UnknownVersion); + } + if d[33..35] != blake2_rfc::blake2b::blake2b(64, &[], &d[0..33]).as_bytes()[0..2] { + // Invalid checksum. + return Err(PublicError::InvalidChecksum); + } + Ok(Self::from_slice(&d[1..33])) + } + /// Return a `Vec` filled with raw data. pub fn to_raw_vec(self) -> Vec { let r: &[u8; 32] = self.as_ref(); @@ -96,6 +125,15 @@ impl Public { pub fn as_array_ref(&self) -> &[u8; 32] { self.as_ref() } + + /// Return the ss58-check string for this key. + pub fn to_ss58check(&self) -> String { + let mut v = vec![42u8]; + v.extend(self.as_slice()); + let r = blake2_rfc::blake2b::blake2b(64, &[], &v); + v.extend(&r.as_bytes()[0..2]); + v.to_base58() + } } impl AsRef<[u8; 32]> for Public { @@ -281,4 +319,21 @@ mod test { let pair = Pair::generate(); let _pair2 = pair.derive_child_probably_bad(b"session_1234"); } + + #[test] + fn ss58check_roundtrip_works() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let s = public.to_ss58check(); + println!("Correct: {}", s); + let cmp = Public::from_ss58check(&s).unwrap(); + assert_eq!(cmp, public); + } + + #[test] + fn ss58check_known_works() { + let k = "5CGavy93sZgPPjHyziRohwVumxiHXMGmQLyuqQP4ZFx5vRU9"; + let enc = hex!["090fa15cb5b1666222fff584b4cc2b1761fe1e238346b340491b37e25ea183ff"]; + assert_eq!(Public::from_ss58check(k).unwrap(), Public::from_raw(enc)); + } }