, Error> {
+ let p = utils::pair_from_suri::(uri, pass)?;
+ Ok(p.public().as_ref().to_vec())
+}
diff --git a/client/cli/src/commands/inspect.rs b/client/cli/src/commands/inspect.rs
new file mode 100644
index 0000000000000..3356d7ca07ad8
--- /dev/null
+++ b/client/cli/src/commands/inspect.rs
@@ -0,0 +1,95 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Implementation of the `inspect` subcommand
+
+use crate::{
+ utils, KeystoreParams, with_crypto_scheme, NetworkSchemeFlag,
+ OutputTypeFlag, CryptoSchemeFlag, Error,
+};
+use structopt::StructOpt;
+/// The `inspect` command
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "inspect-key",
+ about = "Gets a public key and a SS58 address from the provided Secret URI"
+)]
+pub struct InspectCmd {
+ /// A Key URI to be inspected. May be a secret seed, secret URI
+ /// (with derivation paths and password), SS58 or public URI.
+ /// If the value is a file, the file content is used as URI.
+ /// If not given, you will be prompted for the URI.
+ #[structopt(long)]
+ uri: Option,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub keystore_params: KeystoreParams,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub network_scheme: NetworkSchemeFlag,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub output_scheme: OutputTypeFlag,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub crypto_scheme: CryptoSchemeFlag,
+}
+
+impl InspectCmd {
+ /// Run the command
+ pub fn run(&self) -> Result<(), Error> {
+ let uri = utils::read_uri(self.uri.as_ref())?;
+ let password = self.keystore_params.read_password()?;
+
+ use utils::print_from_uri;
+ with_crypto_scheme!(
+ self.crypto_scheme.scheme,
+ print_from_uri(
+ &uri,
+ password,
+ self.network_scheme.network.clone(),
+ self.output_scheme.output_type.clone()
+ )
+ );
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::InspectCmd;
+ use structopt::StructOpt;
+
+ #[test]
+ fn inspect() {
+ let words =
+ "remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
+ let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
+
+ let inspect =
+ InspectCmd::from_iter(&["inspect-key", "--uri", words, "--password", "12345"]);
+ assert!(inspect.run().is_ok());
+
+ let inspect = InspectCmd::from_iter(&["inspect-key", "--uri", seed]);
+ assert!(inspect.run().is_ok());
+ }
+}
diff --git a/client/cli/src/commands/inspect_node_key.rs b/client/cli/src/commands/inspect_node_key.rs
new file mode 100644
index 0000000000000..be0b88589d5e9
--- /dev/null
+++ b/client/cli/src/commands/inspect_node_key.rs
@@ -0,0 +1,75 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Implementation of the `inspect-node-key` subcommand
+
+use crate::{Error, NetworkSchemeFlag};
+use std::fs;
+use libp2p::identity::{PublicKey, ed25519};
+use std::path::PathBuf;
+use structopt::StructOpt;
+
+/// The `inspect-node-key` command
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "inspect-node-key",
+ about = "Print the peer ID corresponding to the node key in the given file."
+)]
+pub struct InspectNodeKeyCmd {
+ /// Name of file to read the secret key from.
+ #[structopt(long)]
+ file: PathBuf,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub network_scheme: NetworkSchemeFlag,
+}
+
+impl InspectNodeKeyCmd {
+ /// runs the command
+ pub fn run(&self) -> Result<(), Error> {
+ let mut file_content = hex::decode(fs::read(&self.file)?)
+ .map_err(|_| "failed to decode secret as hex")?;
+ let secret = ed25519::SecretKey::from_bytes(&mut file_content)
+ .map_err(|_| "Bad node key file")?;
+
+ let keypair = ed25519::Keypair::from(secret);
+ let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id();
+
+ println!("{}", peer_id);
+
+ Ok(())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use super::super::GenerateNodeKeyCmd;
+
+ #[test]
+ fn inspect_node_key() {
+ let path = tempfile::tempdir().unwrap().into_path().join("node-id").into_os_string();
+ let path = path.to_str().unwrap();
+ let cmd = GenerateNodeKeyCmd::from_iter(&["generate-node-key", "--file", path.clone()]);
+
+ assert!(cmd.run().is_ok());
+
+ let cmd = InspectNodeKeyCmd::from_iter(&["inspect-node-key", "--file", path]);
+ assert!(cmd.run().is_ok());
+ }
+}
diff --git a/client/cli/src/commands/key.rs b/client/cli/src/commands/key.rs
new file mode 100644
index 0000000000000..61145eace1071
--- /dev/null
+++ b/client/cli/src/commands/key.rs
@@ -0,0 +1,61 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Key related CLI utilities
+
+use crate::Error;
+use structopt::StructOpt;
+
+use super::{
+ insert::InsertCmd,
+ inspect::InspectCmd,
+ generate::GenerateCmd,
+ inspect_node_key::InspectNodeKeyCmd,
+ generate_node_key::GenerateNodeKeyCmd,
+};
+
+/// key utilities for the cli.
+#[derive(Debug, StructOpt)]
+pub enum KeySubcommand {
+ /// Generate a random node libp2p key, save it to file and print its peer ID
+ GenerateNodeKey(GenerateNodeKeyCmd),
+
+ /// Generate a random account
+ Generate(GenerateCmd),
+
+ /// Gets a public key and a SS58 address from the provided Secret URI
+ InspectKey(InspectCmd),
+
+ /// Print the peer ID corresponding to the node key in the given file
+ InspectNodeKey(InspectNodeKeyCmd),
+
+ /// Insert a key to the keystore of a node.
+ Insert(InsertCmd),
+}
+
+impl KeySubcommand {
+ /// run the key subcommands
+ pub fn run(&self) -> Result<(), Error> {
+ match self {
+ KeySubcommand::GenerateNodeKey(cmd) => cmd.run(),
+ KeySubcommand::Generate(cmd) => cmd.run(),
+ KeySubcommand::InspectKey(cmd) => cmd.run(),
+ KeySubcommand::Insert(cmd) => cmd.run(),
+ KeySubcommand::InspectNodeKey(cmd) => cmd.run(),
+ }
+ }
+}
diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs
index 04cce66bef80d..4271c39867353 100644
--- a/client/cli/src/commands/mod.rs
+++ b/client/cli/src/commands/mod.rs
@@ -21,20 +21,42 @@ mod export_blocks_cmd;
mod export_state_cmd;
mod import_blocks_cmd;
mod purge_chain_cmd;
+mod sign;
+mod verify;
+mod vanity;
mod revert_cmd;
mod run_cmd;
+mod generate_node_key;
+mod generate;
+mod insert;
+mod inspect_node_key;
+mod inspect;
+mod key;
+pub mod utils;
-pub use self::build_spec_cmd::BuildSpecCmd;
-pub use self::check_block_cmd::CheckBlockCmd;
-pub use self::export_blocks_cmd::ExportBlocksCmd;
-pub use self::export_state_cmd::ExportStateCmd;
-pub use self::import_blocks_cmd::ImportBlocksCmd;
-pub use self::purge_chain_cmd::PurgeChainCmd;
-pub use self::revert_cmd::RevertCmd;
-pub use self::run_cmd::RunCmd;
use std::fmt::Debug;
use structopt::StructOpt;
+pub use self::{
+ build_spec_cmd::BuildSpecCmd,
+ check_block_cmd::CheckBlockCmd,
+ export_blocks_cmd::ExportBlocksCmd,
+ export_state_cmd::ExportStateCmd,
+ import_blocks_cmd::ImportBlocksCmd,
+ purge_chain_cmd::PurgeChainCmd,
+ sign::SignCmd,
+ generate::GenerateCmd,
+ insert::InsertCmd,
+ inspect::InspectCmd,
+ generate_node_key::GenerateNodeKeyCmd,
+ inspect_node_key::InspectNodeKeyCmd,
+ key::KeySubcommand,
+ vanity::VanityCmd,
+ verify::VerifyCmd,
+ revert_cmd::RevertCmd,
+ run_cmd::RunCmd,
+};
+
/// All core commands that are provided by default.
///
/// The core commands are split into multiple subcommands and `Run` is the default subcommand. From
@@ -54,14 +76,14 @@ pub enum Subcommand {
/// Validate a single block.
CheckBlock(CheckBlockCmd),
+ /// Export state as raw chain spec.
+ ExportState(ExportStateCmd),
+
/// Revert chain to the previous state.
Revert(RevertCmd),
/// Remove the whole chain data.
PurgeChain(PurgeChainCmd),
-
- /// Export state as raw chain spec.
- ExportState(ExportStateCmd),
}
// TODO: move to config.rs?
@@ -413,5 +435,12 @@ macro_rules! substrate_cli_subcommands {
}
substrate_cli_subcommands!(
- Subcommand => BuildSpec, ExportBlocks, ImportBlocks, CheckBlock, Revert, PurgeChain, ExportState
+ Subcommand =>
+ BuildSpec,
+ ExportBlocks,
+ ExportState,
+ ImportBlocks,
+ CheckBlock,
+ Revert,
+ PurgeChain
);
diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs
index bbfb0d2ff99a7..b2e3c1bf8e2b6 100644
--- a/client/cli/src/commands/revert_cmd.rs
+++ b/client/cli/src/commands/revert_cmd.rs
@@ -17,7 +17,7 @@
// along with this program. If not, see .
use crate::error;
-use crate::params::{BlockNumber, PruningParams, SharedParams};
+use crate::params::{GenericNumber, PruningParams, SharedParams};
use crate::CliConfiguration;
use sc_service::chain_ops::revert_chain;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT};
@@ -32,7 +32,7 @@ use sc_client_api::{Backend, UsageProvider};
pub struct RevertCmd {
/// Number of blocks to revert.
#[structopt(default_value = "256")]
- pub num: BlockNumber,
+ pub num: GenericNumber,
#[allow(missing_docs)]
#[structopt(flatten)]
diff --git a/client/cli/src/commands/sign.rs b/client/cli/src/commands/sign.rs
new file mode 100644
index 0000000000000..605fd5b12313f
--- /dev/null
+++ b/client/cli/src/commands/sign.rs
@@ -0,0 +1,98 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2018-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program 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.
+
+// This program 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 this program. If not, see .
+
+//! Implementation of the `sign` subcommand
+use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams};
+use structopt::StructOpt;
+use sp_core::crypto::SecretString;
+
+/// The `sign` command
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "sign",
+ about = "Sign a message, with a given (secret) key"
+)]
+pub struct SignCmd {
+ /// The secret key URI.
+ /// If the value is a file, the file content is used as URI.
+ /// If not given, you will be prompted for the URI.
+ #[structopt(long)]
+ suri: Option,
+
+ /// Message to sign, if not provided you will be prompted to
+ /// pass the message via STDIN
+ #[structopt(long)]
+ message: Option,
+
+ /// The message on STDIN is hex-encoded data
+ #[structopt(long)]
+ hex: bool,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub keystore_params: KeystoreParams,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub crypto_scheme: CryptoSchemeFlag,
+}
+
+
+impl SignCmd {
+ /// Run the command
+ pub fn run(&self) -> error::Result<()> {
+ let message = utils::read_message(self.message.as_ref(), self.hex)?;
+ let suri = utils::read_uri(self.suri.as_ref())?;
+ let password = self.keystore_params.read_password()?;
+
+ let signature = with_crypto_scheme!(
+ self.crypto_scheme.scheme,
+ sign(&suri, password, message)
+ )?;
+
+ println!("{}", signature);
+ Ok(())
+ }
+}
+
+fn sign(suri: &str, password: Option, message: Vec) -> error::Result {
+ let pair = utils::pair_from_suri::(suri, password)?;
+ Ok(format!("{}", hex::encode(pair.sign(&message))))
+}
+
+#[cfg(test)]
+mod test {
+ use super::SignCmd;
+ use structopt::StructOpt;
+
+ #[test]
+ fn sign() {
+ let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
+
+ let sign = SignCmd::from_iter(&[
+ "sign",
+ "--suri",
+ seed,
+ "--message",
+ &seed[2..],
+ "--password",
+ "12345"
+ ]);
+ assert!(sign.run().is_ok());
+ }
+}
diff --git a/client/cli/src/commands/utils.rs b/client/cli/src/commands/utils.rs
new file mode 100644
index 0000000000000..96b6128057a6e
--- /dev/null
+++ b/client/cli/src/commands/utils.rs
@@ -0,0 +1,233 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program 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.
+
+// This program 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 this program. If not, see .
+
+//! subcommand utilities
+use std::{io::Read, path::PathBuf};
+use sp_core::{
+ Pair, hexdisplay::HexDisplay,
+ crypto::{Ss58Codec, Ss58AddressFormat},
+};
+use sp_runtime::{MultiSigner, traits::IdentifyAccount};
+use crate::{OutputType, error::{self, Error}};
+use serde_json::json;
+use sp_core::crypto::{SecretString, Zeroize, ExposeSecret};
+
+/// Public key type for Runtime
+pub type PublicFor
=
::Public;
+/// Seed type for Runtime
+pub type SeedFor
=
::Seed;
+
+/// helper method to fetch uri from `Option` either as a file or read from stdin
+pub fn read_uri(uri: Option<&String>) -> error::Result {
+ let uri = if let Some(uri) = uri {
+ let file = PathBuf::from(&uri);
+ if file.is_file() {
+ std::fs::read_to_string(uri)?
+ .trim_end()
+ .to_owned()
+ } else {
+ uri.into()
+ }
+ } else {
+ rpassword::read_password_from_tty(Some("URI: "))?
+ };
+
+ Ok(uri)
+}
+
+/// print formatted pair from uri
+pub fn print_from_uri(
+ uri: &str,
+ password: Option,
+ network_override: Ss58AddressFormat,
+ output: OutputType,
+)
+ where
+ Pair: sp_core::Pair,
+ Pair::Public: Into,
+{
+ let password = password.as_ref().map(|s| s.expose_secret().as_str());
+ if let Ok((pair, seed)) = Pair::from_phrase(uri, password.clone()) {
+ let public_key = pair.public();
+
+ match output {
+ OutputType::Json => {
+ let json = json!({
+ "secretPhrase": uri,
+ "secretSeed": format_seed::(seed),
+ "publicKey": format_public_key::(public_key.clone()),
+ "accountId": format_account_id::(public_key),
+ "ss58Address": pair.public().into().into_account().to_ss58check(),
+ });
+ println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
+ },
+ OutputType::Text => {
+ println!("Secret phrase `{}` is account:\n \
+ Secret seed: {}\n \
+ Public key (hex): {}\n \
+ Account ID: {}\n \
+ SS58 Address: {}",
+ uri,
+ format_seed::(seed),
+ format_public_key::(public_key.clone()),
+ format_account_id::(public_key),
+ pair.public().into().into_account().to_ss58check(),
+ );
+ },
+ }
+ } else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password.clone()) {
+ let public_key = pair.public();
+
+ match output {
+ OutputType::Json => {
+ let json = json!({
+ "secretKeyUri": uri,
+ "secretSeed": if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() },
+ "publicKey": format_public_key::(public_key.clone()),
+ "accountId": format_account_id::(public_key),
+ "ss58Address": pair.public().into().into_account().to_ss58check(),
+ });
+ println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
+ },
+ OutputType::Text => {
+ println!("Secret Key URI `{}` is account:\n \
+ Secret seed: {}\n \
+ Public key (hex): {}\n \
+ Account ID: {}\n \
+ SS58 Address: {}",
+ uri,
+ if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() },
+ format_public_key::(public_key.clone()),
+ format_account_id::(public_key),
+ pair.public().into().into_account().to_ss58check(),
+ );
+ },
+ }
+ } else if let Ok((public_key, _v)) = Pair::Public::from_string_with_version(uri) {
+ let v = network_override;
+
+ match output {
+ OutputType::Json => {
+ let json = json!({
+ "publicKeyUri": uri,
+ "networkId": String::from(v),
+ "publicKey": format_public_key::(public_key.clone()),
+ "accountId": format_account_id::(public_key.clone()),
+ "ss58Address": public_key.to_ss58check_with_version(v),
+ });
+ println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
+ },
+ OutputType::Text => {
+ println!("Public Key URI `{}` is account:\n \
+ Network ID/version: {}\n \
+ Public key (hex): {}\n \
+ Account ID: {}\n \
+ SS58 Address: {}",
+ uri,
+ String::from(v),
+ format_public_key::(public_key.clone()),
+ format_account_id::(public_key.clone()),
+ public_key.to_ss58check_with_version(v),
+ );
+ },
+ }
+ } else {
+ println!("Invalid phrase/URI given");
+ }
+}
+
+/// generate a pair from suri
+pub fn pair_from_suri(suri: &str, password: Option) -> Result {
+ let result = if let Some(pass) = password {
+ let mut pass_str = pass.expose_secret().clone();
+ let pair = P::from_string(suri, Some(&pass_str));
+ pass_str.zeroize();
+ pair
+ } else {
+ P::from_string(suri, None)
+ };
+
+ Ok(result.map_err(|err| format!("Invalid phrase {:?}", err))?)
+}
+
+/// formats seed as hex
+pub fn format_seed(seed: SeedFor) -> String {
+ format!("0x{}", HexDisplay::from(&seed.as_ref()))
+}
+
+/// formats public key as hex
+fn format_public_key(public_key: PublicFor) -> String {
+ format!("0x{}", HexDisplay::from(&public_key.as_ref()))
+}
+
+/// formats public key as accountId as hex
+fn format_account_id(public_key: PublicFor) -> String
+ where
+ PublicFor
: Into,
+{
+ format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
+}
+
+/// helper method for decoding hex
+pub fn decode_hex>(message: T) -> Result, Error> {
+ let mut message = message.as_ref();
+ if message[..2] == [b'0', b'x'] {
+ message = &message[2..]
+ }
+ hex::decode(message)
+ .map_err(|e| Error::Other(format!("Invalid hex ({})", e)))
+}
+
+/// checks if message is Some, otherwise reads message from stdin and optionally decodes hex
+pub fn read_message(msg: Option<&String>, should_decode: bool) -> Result, Error> {
+ let mut message = vec![];
+ match msg {
+ Some(m) => {
+ message = decode_hex(m)?;
+ },
+ None => {
+ std::io::stdin().lock().read_to_end(&mut message)?;
+ if should_decode {
+ message = decode_hex(&message)?;
+ }
+ }
+ }
+ Ok(message)
+}
+
+
+/// Allows for calling $method with appropriate crypto impl.
+#[macro_export]
+macro_rules! with_crypto_scheme {
+ ($scheme:expr, $method:ident($($params:expr),*)) => {
+ with_crypto_scheme!($scheme, $method<>($($params),*))
+ };
+ ($scheme:expr, $method:ident<$($generics:ty),*>($($params:expr),*)) => {
+ match $scheme {
+ $crate::CryptoScheme::Ecdsa => {
+ $method::($($params),*)
+ }
+ $crate::CryptoScheme::Sr25519 => {
+ $method::($($params),*)
+ }
+ $crate::CryptoScheme::Ed25519 => {
+ $method::($($params),*)
+ }
+ }
+ };
+}
diff --git a/bin/utils/subkey/src/vanity.rs b/client/cli/src/commands/vanity.rs
similarity index 56%
rename from bin/utils/subkey/src/vanity.rs
rename to client/cli/src/commands/vanity.rs
index d09aeeef25a47..e6f923f73c45c 100644
--- a/bin/utils/subkey/src/vanity.rs
+++ b/client/cli/src/commands/vanity.rs
@@ -5,7 +5,7 @@
// This program 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
+// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
@@ -16,9 +16,97 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
-use super::{PublicOf, PublicT, Crypto};
-use sp_core::Pair;
+//! implementation of the `vanity` subcommand
+
+use crate::{
+ error, utils, with_crypto_scheme,
+ CryptoSchemeFlag, NetworkSchemeFlag, OutputTypeFlag,
+};
+use sp_core::crypto::Ss58Codec;
+use structopt::StructOpt;
use rand::{rngs::OsRng, RngCore};
+use sp_runtime::traits::IdentifyAccount;
+
+/// The `vanity` command
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "vanity",
+ about = "Generate a seed that provides a vanity address"
+)]
+pub struct VanityCmd {
+ /// Desired pattern
+ #[structopt(long, parse(try_from_str = assert_non_empty_string))]
+ pattern: String,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ network_scheme: NetworkSchemeFlag,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ output_scheme: OutputTypeFlag,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ crypto_scheme: CryptoSchemeFlag,
+}
+
+impl VanityCmd {
+ /// Run the command
+ pub fn run(&self) -> error::Result<()> {
+ let formated_seed = with_crypto_scheme!(self.crypto_scheme.scheme, generate_key(&self.pattern))?;
+ use utils::print_from_uri;
+ with_crypto_scheme!(
+ self.crypto_scheme.scheme,
+ print_from_uri(
+ &formated_seed,
+ None,
+ self.network_scheme.network.clone(),
+ self.output_scheme.output_type.clone()
+ )
+ );
+ Ok(())
+ }
+}
+
+/// genertae a key based on given pattern
+fn generate_key(desired: &str) -> Result
+ where
+ Pair: sp_core::Pair,
+ Pair::Public: IdentifyAccount,
+ ::AccountId: Ss58Codec,
+{
+ println!("Generating key containing pattern '{}'", desired);
+
+ let top = 45 + (desired.len() * 48);
+ let mut best = 0;
+ let mut seed = Pair::Seed::default();
+ let mut done = 0;
+
+ loop {
+ if done % 100000 == 0 {
+ OsRng.fill_bytes(seed.as_mut());
+ } else {
+ next_seed(seed.as_mut());
+ }
+
+ let p = Pair::from_seed(&seed);
+ let ss58 = p.public().into_account().to_ss58check();
+ let score = calculate_score(&desired, &ss58);
+ if score > best || desired.len() < 2 {
+ best = score;
+ if best >= top {
+ println!("best: {} == top: {}", best, top);
+ return Ok(utils::format_seed::(seed.clone()));
+ }
+ }
+ done += 1;
+
+ if done % good_waypoint(done) == 0 {
+ println!("{} keys searched; best is {}/{} complete", done, best, top);
+ }
+ }
+}
fn good_waypoint(done: u64) -> u64 {
match done {
@@ -43,14 +131,6 @@ fn next_seed(seed: &mut [u8]) {
}
}
-/// A structure used to carry both Pair and seed.
-/// This should usually NOT been used. If unsure, use Pair.
-pub(super) struct KeyPair {
- pub pair: C::Pair,
- pub seed: ::Seed,
- pub score: usize,
-}
-
/// Calculate the score of a key based on the desired
/// input.
fn calculate_score(_desired: &str, key: &str) -> usize {
@@ -64,77 +144,40 @@ fn calculate_score(_desired: &str, key: &str) -> usize {
0
}
-/// Validate whether the char is allowed to be used in base58.
-/// num 0, lower l, upper I and O are not allowed.
-fn validate_base58(c :char) -> bool {
- c.is_alphanumeric() && !"0lIO".contains(c)
-}
-
-pub(super) fn generate_key(desired: &str) -> Result, &'static str> where
- PublicOf: PublicT,
-{
- if desired.is_empty() {
- return Err("Pattern must not be empty");
- }
-
- if !desired.chars().all(validate_base58) {
- return Err("Pattern can only contains valid characters in base58 \
- (all alphanumeric except for 0, l, I and O)");
- }
-
- eprintln!("Generating key containing pattern '{}'", desired);
-
- let top = 45 + (desired.len() * 48);
- let mut best = 0;
- let mut seed = ::Seed::default();
- let mut done = 0;
-
- loop {
- if done % 100000 == 0 {
- OsRng.fill_bytes(seed.as_mut());
- } else {
- next_seed(seed.as_mut());
- }
-
- let p = C::Pair::from_seed(&seed);
- let ss58 = C::ss58_from_pair(&p);
- let score = calculate_score(&desired, &ss58);
- if score > best || desired.len() < 2 {
- best = score;
- let keypair = KeyPair {
- pair: p,
- seed: seed.clone(),
- score: score,
- };
- if best >= top {
- eprintln!("best: {} == top: {}", best, top);
- return Ok(keypair);
- }
- }
- done += 1;
-
- if done % good_waypoint(done) == 0 {
- eprintln!("{} keys searched; best is {}/{} complete", done, best, top);
- }
+/// checks that `pattern` is non-empty
+fn assert_non_empty_string(pattern: &str) -> Result {
+ if pattern.is_empty() {
+ Err("Pattern must not be empty")
+ } else {
+ Ok(pattern.to_string())
}
}
+
#[cfg(test)]
mod tests {
- use super::super::Ed25519;
use super::*;
use sp_core::{crypto::Ss58Codec, Pair};
+ use sp_core::sr25519;
#[cfg(feature = "bench")]
use test::Bencher;
+ use structopt::StructOpt;
+
+ #[test]
+ fn vanity() {
+ let vanity = VanityCmd::from_iter(&["vanity", "--pattern", "j"]);
+ assert!(vanity.run().is_ok());
+ }
#[test]
fn test_generation_with_single_char() {
- assert!(generate_key::("j")
- .unwrap()
- .pair
- .public()
- .to_ss58check()
- .contains("j"));
+ let seed = generate_key::("j").unwrap();
+ assert!(
+ sr25519::Pair::from_seed_slice(&hex::decode(&seed[2..]).unwrap())
+ .unwrap()
+ .public()
+ .to_ss58check()
+ .contains("j"));
}
#[test]
@@ -175,22 +218,6 @@ mod tests {
);
}
- #[test]
- fn test_invalid_pattern() {
- assert!(generate_key::("").is_err());
- assert!(generate_key::("0").is_err());
- assert!(generate_key::("l").is_err());
- assert!(generate_key::("I").is_err());
- assert!(generate_key::("O").is_err());
- assert!(generate_key::("!").is_err());
- }
-
- #[test]
- fn test_valid_pattern() {
- assert!(generate_key::("o").is_ok());
- assert!(generate_key::("L").is_ok());
- }
-
#[cfg(feature = "bench")]
#[bench]
fn bench_paranoiac(b: &mut Bencher) {
diff --git a/client/cli/src/commands/verify.rs b/client/cli/src/commands/verify.rs
new file mode 100644
index 0000000000000..ad16c11d5e441
--- /dev/null
+++ b/client/cli/src/commands/verify.rs
@@ -0,0 +1,104 @@
+// This file is part of Substrate.
+
+// Copyright (C) 2019-2020 Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program 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.
+
+// This program 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 this program. If not, see .
+
+//! implementation of the `verify` subcommand
+
+use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag};
+use sp_core::{Public, crypto::Ss58Codec};
+use structopt::StructOpt;
+
+/// The `verify` command
+#[derive(Debug, StructOpt)]
+#[structopt(
+ name = "verify",
+ about = "Verify a signature for a message, provided on STDIN, with a given (public or secret) key"
+)]
+pub struct VerifyCmd {
+ /// Signature, hex-encoded.
+ sig: String,
+
+ /// The public or secret key URI.
+ /// If the value is a file, the file content is used as URI.
+ /// If not given, you will be prompted for the URI.
+ uri: Option,
+
+ /// Message to verify, if not provided you will be prompted to
+ /// pass the message via STDIN
+ #[structopt(long)]
+ message: Option,
+
+ /// The message on STDIN is hex-encoded data
+ #[structopt(long)]
+ hex: bool,
+
+ #[allow(missing_docs)]
+ #[structopt(flatten)]
+ pub crypto_scheme: CryptoSchemeFlag,
+}
+
+impl VerifyCmd {
+ /// Run the command
+ pub fn run(&self) -> error::Result<()> {
+ let message = utils::read_message(self.message.as_ref(), self.hex)?;
+ let sig_data = utils::decode_hex(&self.sig)?;
+ let uri = utils::read_uri(self.uri.as_ref())?;
+ let uri = if uri.starts_with("0x") {
+ &uri[2..]
+ } else {
+ &uri
+ };
+
+ with_crypto_scheme!(
+ self.crypto_scheme.scheme,
+ verify(sig_data, message, uri)
+ )
+ }
+}
+
+fn verify(sig_data: Vec, message: Vec, uri: &str) -> error::Result<()>
+ where
+ Pair: sp_core::Pair,
+ Pair::Signature: Default + AsMut<[u8]>,
+{
+ let mut signature = Pair::Signature::default();
+ if sig_data.len() != signature.as_ref().len() {
+ return Err(error::Error::Other(format!(
+ "signature has an invalid length. read {} bytes, expected {} bytes",
+ sig_data.len(),
+ signature.as_ref().len(),
+ )));
+ }
+ signature.as_mut().copy_from_slice(&sig_data);
+
+ let pubkey = if let Ok(pubkey_vec) = hex::decode(uri) {
+ Pair::Public::from_slice(pubkey_vec.as_slice())
+ } else {
+ Pair::Public::from_string(uri)
+ .map_err(|_| {
+ error::Error::Other(format!("Invalid URI; expecting either a secret URI or a public URI."))
+ })?
+ };
+
+ if Pair::verify(&signature, &message, &pubkey) {
+ println!("Signature verifies correctly.");
+ } else {
+ return Err(error::Error::Other("Signature invalid.".into()))
+ }
+
+ Ok(())
+}
diff --git a/client/cli/src/error.rs b/client/cli/src/error.rs
index f091354be154b..7404d31fcf7bb 100644
--- a/client/cli/src/error.rs
+++ b/client/cli/src/error.rs
@@ -18,6 +18,8 @@
//! Initialization errors.
+
+
/// Result type alias for the CLI.
pub type Result = std::result::Result;
@@ -32,6 +34,8 @@ pub enum Error {
Service(sc_service::Error),
/// Client error
Client(sp_blockchain::Error),
+ /// scale codec error
+ Codec(parity_scale_codec::Error),
/// Input error
#[from(ignore)]
Input(String),
@@ -65,6 +69,7 @@ impl std::error::Error for Error {
Error::Cli(ref err) => Some(err),
Error::Service(ref err) => Some(err),
Error::Client(ref err) => Some(err),
+ Error::Codec(ref err) => Some(err),
Error::Input(_) => None,
Error::InvalidListenMultiaddress => None,
Error::Other(_) => None,
diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs
index f940ab0b95d71..1b8844df3e039 100644
--- a/client/cli/src/lib.rs
+++ b/client/cli/src/lib.rs
@@ -21,7 +21,7 @@
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
-mod arg_enums;
+pub mod arg_enums;
mod commands;
mod config;
mod error;
diff --git a/client/cli/src/params/keystore_params.rs b/client/cli/src/params/keystore_params.rs
index a6eb438cc0780..3c04d63144595 100644
--- a/client/cli/src/params/keystore_params.rs
+++ b/client/cli/src/params/keystore_params.rs
@@ -21,7 +21,9 @@ use sc_service::config::KeystoreConfig;
use std::fs;
use std::path::PathBuf;
use structopt::StructOpt;
-use sp_core::crypto::SecretString;
+use crate::error;
+use sp_core::crypto::{SecretString, Zeroize};
+use std::str::FromStr;
/// default sub directory for the key store
const DEFAULT_KEYSTORE_CONFIG_PATH: &'static str = "keystore";
@@ -73,7 +75,6 @@ impl KeystoreParams {
let mut password = input_keystore_password()?;
let secret = std::str::FromStr::from_str(password.as_str())
.map_err(|()| "Error reading password")?;
- use sp_core::crypto::Zeroize;
password.zeroize();
Some(secret)
}
@@ -84,7 +85,6 @@ impl KeystoreParams {
.map_err(|e| format!("{}", e))?;
let secret = std::str::FromStr::from_str(password.as_str())
.map_err(|()| "Error reading password")?;
- use sp_core::crypto::Zeroize;
password.zeroize();
Some(secret)
} else {
@@ -98,6 +98,22 @@ impl KeystoreParams {
Ok(KeystoreConfig::Path { path, password })
}
+
+ /// helper method to fetch password from `KeyParams` or read from stdin
+ pub fn read_password(&self) -> error::Result