Merge Subkey into sc-cli (#4954)

* draft

* revert

* WIP

* all that remains is tests

* update Cargo.lock

* tests WIP

* WIP refactor node-template-runtime and node-runtime

* implments sc_cli::RuntimeAdapter for node_template_runtime::Runtime

* final draft

* fix update_config for subcommands

* proper AccountId decoding

* test-runtime tests

* revert

* move RuntimeAdapter to cli-utils

* use &'static str for TryFrom::<&'a str>::Error for Ss58AddressFormat

* tests

* add frame-system to sc-cli dev-dependencies

* add frame-system to sc-cli dev-dependencies

* fix ui test

* wip

* fixed inspect test

* bump impl version

* bump impl version, fixx spaces remove todos

* pallet-balances-cli, rustc for some reason cannot resolve pallet_balances_cli in node-cli 😩

* wip

* Subcommand::run takes &self

* can't believe i missed that 🤦🏾‍♂️

* bump wasm-bindgen for some reason

* adds key subcommand, rename generate-node-key to generate-node-id

* cargo update and crossed fingers 🤞🏽

* update ui test

* update more ui tests

* should be all good now

* revert subkey change

* revert subkey change

* adds frame-utilities-cli

* Apply suggestions from code review

Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>

* removes frame from sc-cli, fix license

* my editor and ci disagrees on line width

* bump spec version

* turn off default features for parity-scale-codec

* enable full_crypto feature for sp-core in cli-utils

* merge frame-utilities-cli with pallet-balances-cli

* remove full_crypto feature from sp_core in cli-utils

* bump Cargo.lock

* cli-utils -> frame-utils

* rename BlockNumber to GenericNumber, fix spaces

* fix spaces

* construct additional_signed manually

* sign test

* remove unused vars

* implement subkey with frame-utilities-cli and sc_cli

* fix moduleid test

* CI and clion disagree on line widths

* adds associated Params type to SignedExtensionProvider

* Apply suggestions from code review

Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>

* move some code around

* removes unneccesary generic params

* moves module_id back to frame_utilities_cli

* Apply suggestions from code review

Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* remove print_ext

* remove MaybeDisplay from pallet_balances::Trait::Balance

* a lot of stuff tbh

* adds ExtrasParamsBuilder

* remove tests for ModuleIdCmd

* address comments from PR

* bump Cargo.lock

* manually insert key into keystore

* remove unnecessary SharedParams

* add validation to vanity pattern, remove unused arg

* remove SharedParams from Sign, Vanity, Verify

* remove SharedParams from ModuleIdCmd, remove expect from Verify, new line to Cargo.toml

* remove SharedParams from InsertCmd

* 🤦🏾‍♂️

* deleted prometheus.yml

* move a few things around

* fix vanity test

Co-authored-by: Benjamin Kampmann <ben@gnunicorn.org>
Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
Co-authored-by: Benjamin Kampmann <ben@parity.io>
This commit is contained in:
Seun Lanlege
2020-08-20 10:55:03 +01:00
committed by GitHub
parent 481ad884d6
commit 6963272451
42 changed files with 2335 additions and 1522 deletions
@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error;
use crate::params::{BlockNumber, DatabaseParams, PruningParams, SharedParams};
use crate::params::{GenericNumber, DatabaseParams, PruningParams, SharedParams};
use crate::CliConfiguration;
use log::info;
use sc_service::{
@@ -44,13 +44,13 @@ pub struct ExportBlocksCmd {
///
/// Default is 1.
#[structopt(long = "from", value_name = "BLOCK")]
pub from: Option<BlockNumber>,
pub from: Option<GenericNumber>,
/// Specify last block number.
///
/// Default is best block.
#[structopt(long = "to", value_name = "BLOCK")]
pub to: Option<BlockNumber>,
pub to: Option<GenericNumber>,
/// Use binary output rather than JSON.
#[structopt(long)]
@@ -0,0 +1,91 @@
// 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 `generate` subcommand
use bip39::{MnemonicType, Mnemonic, Language};
use structopt::StructOpt;
use crate::{
utils::print_from_uri, KeystoreParams, Error,
with_crypto_scheme, NetworkSchemeFlag, OutputTypeFlag, CryptoSchemeFlag,
};
/// The `generate` command
#[derive(Debug, StructOpt)]
#[structopt(name = "generate", about = "Generate a random account")]
pub struct GenerateCmd {
/// The number of words in the phrase to generate. One of 12 (default), 15, 18, 21 and 24.
#[structopt(long, short = "w", value_name = "WORDS")]
words: Option<usize>,
#[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 GenerateCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let words = match self.words {
Some(words) => {
MnemonicType::for_word_count(words)
.map_err(|_| {
Error::Input("Invalid number of words given for phrase: must be 12/15/18/21/24".into())
})?
},
None => MnemonicType::Words12,
};
let mnemonic = Mnemonic::new(words, Language::English);
let password = self.keystore_params.read_password()?;
let maybe_network = self.network_scheme.network.clone();
let output = self.output_scheme.output_type.clone();
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
mnemonic.phrase(),
password,
maybe_network,
output
)
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::GenerateCmd;
use structopt::StructOpt;
#[test]
fn generate() {
let generate = GenerateCmd::from_iter(&["generate", "--password", "12345"]);
assert!(generate.run().is_ok())
}
}
@@ -0,0 +1,70 @@
// 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 `generate-node-key` subcommand
use crate::Error;
use structopt::StructOpt;
use std::{path::PathBuf, fs};
use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey};
/// The `generate-node-key` command
#[derive(Debug, StructOpt)]
#[structopt(
name = "generate-node-key",
about = "Generate a random node libp2p key, save it to file and print its peer ID"
)]
pub struct GenerateNodeKeyCmd {
/// Name of file to save secret key to.
#[structopt(long)]
file: PathBuf,
}
impl GenerateNodeKeyCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let file = &self.file;
let keypair = libp2p_ed25519::Keypair::generate();
let secret = keypair.secret();
let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id();
fs::write(file, hex::encode(secret.as_ref()))?;
println!("{}", peer_id);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::Builder;
use std::io::Read;
#[test]
fn generate_node_key() {
let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
let generate =
GenerateNodeKeyCmd::from_iter(&["generate-node-key", "--file", "/tmp/keyfile"]);
assert!(generate.run().is_ok());
let mut buf = String::new();
assert!(file.read_to_string(&mut buf).is_ok());
assert!(hex::decode(buf).is_ok());
}
}
@@ -0,0 +1,94 @@
// 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 `insert` subcommand
use crate::{Error, KeystoreParams, CryptoSchemeFlag, SharedParams, utils, with_crypto_scheme};
use structopt::StructOpt;
use sp_core::{crypto::KeyTypeId, traits::BareCryptoStore};
use std::convert::TryFrom;
use sc_service::config::KeystoreConfig;
use sc_keystore::Store as KeyStore;
use sp_core::crypto::SecretString;
/// The `insert` command
#[derive(Debug, StructOpt)]
#[structopt(
name = "insert",
about = "Insert a key to the keystore of a node."
)]
pub struct InsertCmd {
/// 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<String>,
/// Key type, examples: "gran", or "imon"
#[structopt(long)]
key_type: String,
#[allow(missing_docs)]
#[structopt(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[structopt(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[structopt(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl InsertCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let suri = utils::read_uri(self.suri.as_ref())?;
let base_path = self.shared_params.base_path.as_ref()
.ok_or_else(|| Error::Other("please supply base path".into()))?;
let (keystore, public) = match self.keystore_params.keystore_config(base_path)? {
KeystoreConfig::Path { path, password } => {
let public = with_crypto_scheme!(
self.crypto_scheme.scheme,
to_vec(&suri, password.clone())
)?;
let keystore = KeyStore::open(path, password)
.map_err(|e| format!("{}", e))?;
(keystore, public)
},
_ => unreachable!("keystore_config always returns path and password; qed")
};
let key_type = KeyTypeId::try_from(self.key_type.as_str())
.map_err(|_| {
Error::Other("Cannot convert argument to keytype: argument should be 4-character string".into())
})?;
keystore.write()
.insert_unknown(key_type, &suri, &public[..])
.map_err(|e| Error::Other(format!("{:?}", e)))?;
Ok(())
}
}
fn to_vec<P: sp_core::Pair>(uri: &str, pass: Option<SecretString>) -> Result<Vec<u8>, Error> {
let p = utils::pair_from_suri::<P>(uri, pass)?;
Ok(p.public().as_ref().to_vec())
}
@@ -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<String>,
#[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());
}
}
@@ -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());
}
}
+61
View File
@@ -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(),
}
}
}
+41 -12
View File
@@ -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),
}
/// Macro that helps implement CliConfiguration on an enum of subcommand automatically
@@ -425,5 +447,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
);
@@ -17,7 +17,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
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)]
+98
View File
@@ -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 <https://www.gnu.org/licenses/>.
//! 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<String>,
/// Message to sign, if not provided you will be prompted to
/// pass the message via STDIN
#[structopt(long)]
message: Option<String>,
/// 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<P: sp_core::Pair>(suri: &str, password: Option<SecretString>, message: Vec<u8>) -> error::Result<String> {
let pair = utils::pair_from_suri::<P>(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());
}
}
+233
View File
@@ -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 <https://www.gnu.org/licenses/>.
//! 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<P> = <P as sp_core::Pair>::Public;
/// Seed type for Runtime
pub type SeedFor<P> = <P as sp_core::Pair>::Seed;
/// helper method to fetch uri from `Option<String>` either as a file or read from stdin
pub fn read_uri(uri: Option<&String>) -> error::Result<String> {
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<Pair>(
uri: &str,
password: Option<SecretString>,
network_override: Ss58AddressFormat,
output: OutputType,
)
where
Pair: sp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
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::<Pair>(seed),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(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::<Pair>(seed),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(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::<Pair>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(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::<Pair>(seed) } else { "n/a".into() },
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(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::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(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::<Pair>(public_key.clone()),
format_account_id::<Pair>(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<P: Pair>(suri: &str, password: Option<SecretString>) -> Result<P, Error> {
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<P: sp_core::Pair>(seed: SeedFor<P>) -> String {
format!("0x{}", HexDisplay::from(&seed.as_ref()))
}
/// formats public key as hex
fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
format!("0x{}", HexDisplay::from(&public_key.as_ref()))
}
/// formats public key as accountId as hex
fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
where
PublicFor<P>: Into<MultiSigner>,
{
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
}
/// helper method for decoding hex
pub fn decode_hex<T: AsRef<[u8]>>(message: T) -> Result<Vec<u8>, 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<Vec<u8>, 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::<sp_core::ecdsa::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Sr25519 => {
$method::<sp_core::sr25519::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Ed25519 => {
$method::<sp_core::ed25519::Pair, $($generics),*>($($params),*)
}
}
};
}
+232
View File
@@ -0,0 +1,232 @@
// 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 <https://www.gnu.org/licenses/>.
//! 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<Pair>(desired: &str) -> Result<String, &'static str>
where
Pair: sp_core::Pair,
Pair::Public: IdentifyAccount,
<Pair::Public as 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::<Pair>(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 {
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(seed: &mut [u8]) {
for i in 0..seed.len() {
match seed[i] {
255 => {
seed[i] = 0;
}
_ => {
seed[i] += 1;
break;
}
}
}
}
/// 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) {
return (47 - pos) + (snip_size * 48);
}
}
0
}
/// checks that `pattern` is non-empty
fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
if pattern.is_empty() {
Err("Pattern must not be empty")
} else {
Ok(pattern.to_string())
}
}
#[cfg(test)]
mod tests {
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() {
let seed = generate_key::<sr25519::Pair>("j").unwrap();
assert!(
sr25519::Pair::from_seed_slice(&hex::decode(&seed[2..]).unwrap())
.unwrap()
.public()
.to_ss58check()
.contains("j"));
}
#[test]
fn test_score_1_char_100() {
let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
assert_eq!(score, 94);
}
#[test]
fn test_score_100() {
let score = calculate_score(
"Polkadot",
"5PolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim",
);
assert_eq!(score, 430);
}
#[test]
fn test_score_50_2() {
// 50% for the position + 50% for the size
assert_eq!(
calculate_score(
"Polkadot",
"5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"
),
238
);
}
#[test]
fn test_score_0() {
assert_eq!(
calculate_score(
"Polkadot",
"5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK"
),
0
);
}
#[cfg(feature = "bench")]
#[bench]
fn bench_paranoiac(b: &mut Bencher) {
b.iter(|| generate_key("polk"));
}
#[cfg(feature = "bench")]
#[bench]
fn bench_not_paranoiac(b: &mut Bencher) {
b.iter(|| generate_key("polk"));
}
}
+104
View File
@@ -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 <https://www.gnu.org/licenses/>.
//! 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<String>,
/// Message to verify, if not provided you will be prompted to
/// pass the message via STDIN
#[structopt(long)]
message: Option<String>,
/// 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<Pair>(sig_data: Vec<u8>, message: Vec<u8>, 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(())
}