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
+844 -497
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -180,6 +180,7 @@ members = [
"utils/build-script-utils",
"utils/fork-tree",
"utils/frame/benchmarking-cli",
"utils/frame/frame-utilities-cli",
"utils/frame/rpc/support",
"utils/frame/rpc/system",
"utils/wasm-builder",
+1 -1
View File
@@ -1,5 +1,5 @@
use sc_cli::{RunCmd, Subcommand};
use structopt::StructOpt;
use sc_cli::{RunCmd, Subcommand};
#[derive(Debug, StructOpt)]
pub struct Cli {
@@ -66,8 +66,8 @@ impl SubstrateCli for Cli {
pub fn run() -> sc_cli::Result<()> {
let cli = Cli::from_args();
match &cli.subcommand {
Some(subcommand) => {
match cli.subcommand {
Some(ref subcommand) => {
let runner = cli.create_runner(subcommand)?;
runner.run_subcommand(subcommand, |config| {
let PartialComponents { client, backend, task_manager, import_queue, .. }
+2
View File
@@ -131,6 +131,7 @@ structopt = { version = "0.3.8", optional = true }
node-inspect = { version = "0.8.0-rc5", optional = true, path = "../inspect" }
frame-benchmarking-cli = { version = "2.0.0-rc5", optional = true, path = "../../../utils/frame/benchmarking-cli" }
substrate-build-script-utils = { version = "2.0.0-rc5", optional = true, path = "../../../utils/build-script-utils" }
substrate-frame-cli = { version = "2.0.0-rc5", optional = true, path = "../../../utils/frame/frame-utilities-cli" }
[build-dependencies.sc-cli]
version = "0.8.0-rc5"
@@ -150,6 +151,7 @@ cli = [
"node-inspect",
"sc-cli",
"frame-benchmarking-cli",
"substrate-frame-cli",
"sc-service/db",
"structopt",
"substrate-build-script-utils",
+13 -1
View File
@@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use sc_cli::RunCmd;
use sc_cli::{RunCmd, KeySubcommand, SignCmd, VanityCmd, VerifyCmd};
use structopt::StructOpt;
/// An overarching CLI command definition.
@@ -37,6 +37,9 @@ pub enum Subcommand {
#[structopt(flatten)]
Base(sc_cli::Subcommand),
/// Key management cli utilities
Key(KeySubcommand),
/// The custom inspect subcommmand for decoding blocks and extrinsics.
#[structopt(
name = "inspect",
@@ -47,4 +50,13 @@ pub enum Subcommand {
/// The custom benchmark subcommmand benchmarking runtime pallets.
#[structopt(name = "benchmark", about = "Benchmark runtime pallets.")]
Benchmark(frame_benchmarking_cli::BenchmarkCmd),
/// Verify a signature for a message, provided on STDIN, with a given (public or secret) key.
Verify(VerifyCmd),
/// Generate a seed that provides a vanity address.
Vanity(VanityCmd),
/// Sign a message, with a given (secret) key.
Sign(SignCmd),
}
+4
View File
@@ -93,6 +93,10 @@ pub fn run() -> Result<()> {
Ok(())
}
}
Some(Subcommand::Key(cmd)) => cmd.run(),
Some(Subcommand::Sign(cmd)) => cmd.run(),
Some(Subcommand::Verify(cmd)) => cmd.run(),
Some(Subcommand::Vanity(cmd)) => cmd.run(),
Some(Subcommand::Base(subcommand)) => {
let runner = cli.create_runner(subcommand)?;
runner.run_subcommand(subcommand, |config| {
+17 -12
View File
@@ -83,6 +83,7 @@ use impls::{CurrencyToVoteHandler, Author};
/// Constant values used within the runtime.
pub mod constants;
use constants::{time::*, currency::*};
use sp_runtime::generic::Era;
/// Weights for pallets used in the runtime.
mod weights;
@@ -654,9 +655,9 @@ parameter_types! {
pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2;
}
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime where
Call: From<LocalCall>,
impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime
where
Call: From<LocalCall>,
{
fn create_transaction<C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>>(
call: Call,
@@ -664,6 +665,7 @@ impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for R
account: AccountId,
nonce: Index,
) -> Option<(Call, <UncheckedExtrinsic as traits::Extrinsic>::SignaturePayload)> {
let tip = 0;
// take the biggest period possible.
let period = BlockHashCount::get()
.checked_next_power_of_two()
@@ -674,22 +676,25 @@ impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for R
// The `System::block_number` is initialized with `n+1`,
// so the actual block number is `n`.
.saturating_sub(1);
let tip = 0;
let extra: SignedExtra = (
let era = Era::mortal(period, current_block);
let extra = (
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
frame_system::CheckEra::<Runtime>::from(era),
frame_system::CheckNonce::<Runtime>::from(nonce),
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(tip),
);
let raw_payload = SignedPayload::new(call, extra).map_err(|e| {
debug::warn!("Unable to create signed payload: {:?}", e);
}).ok()?;
let signature = raw_payload.using_encoded(|payload| {
C::sign(payload, public)
})?;
let raw_payload = SignedPayload::new(call, extra)
.map_err(|e| {
debug::warn!("Unable to create signed payload: {:?}", e);
})
.ok()?;
let signature = raw_payload
.using_encoded(|payload| {
C::sign(payload, public)
})?;
let address = Indices::unlookup(account);
let (call, extra, _) = raw_payload.deconstruct();
Some((call, (address, signature.into(), extra)))
+8 -21
View File
@@ -7,34 +7,21 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
[[bin]]
path = "src/main.rs"
name = "subkey"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
futures = "0.1.29"
sp-core = { version = "2.0.0-rc5", path = "../../../primitives/core" }
node-runtime = { version = "2.0.0-rc5", path = "../../node/runtime" }
node-primitives = { version = "2.0.0-rc5", path = "../../node/primitives" }
sp-runtime = { version = "2.0.0-rc5", path = "../../../primitives/runtime" }
rand = "0.7.2"
clap = "2.33.0"
tiny-bip39 = "0.7"
substrate-bip39 = "0.4.1"
hex = "0.4.0"
hex-literal = "0.2.1"
codec = { package = "parity-scale-codec", version = "1.3.4" }
sc-cli = { version = "0.8.0-rc5", path = "../../../client/cli" }
substrate-frame-cli = { version = "2.0.0-rc5", path = "../../../utils/frame/frame-utilities-cli" }
structopt = "0.3.14"
frame-system = { version = "2.0.0-rc5", path = "../../../frame/system" }
pallet-balances = { version = "2.0.0-rc5", path = "../../../frame/balances" }
pallet-transaction-payment = { version = "2.0.0-rc5", path = "../../../frame/transaction-payment" }
pallet-grandpa = { version = "2.0.0-rc5", path = "../../../frame/grandpa" }
rpassword = "4.0.1"
itertools = "0.8.2"
derive_more = { version = "0.99.2" }
sc-rpc = { version = "2.0.0-rc5", path = "../../../client/rpc" }
jsonrpc-core-client = { version = "14.2.0", features = ["http"] }
hyper = "0.12.35"
libp2p = { version = "0.23.0", default-features = false }
serde_json = "1.0"
sp-core = { version = "2.0.0-rc5", path = "../../../primitives/core" }
[features]
bench = []
+2 -2
View File
@@ -31,7 +31,7 @@ OUTPUT:
`subkey` expects a message to come in on STDIN, one way to sign a message would look like this:
```bash
echo -n <msg> | subkey sign <seed,mnemonic>
echo -n <msg> | subkey sign --suri <seed,mnemonic>
OUTPUT:
a69da4a6ccbf81dbbbfad235fa12cf8528c18012b991ae89214de8d20d29c1280576ced6eb38b7406d1b7e03231df6dd4a5257546ddad13259356e1c3adfb509
@@ -72,7 +72,7 @@ Will output a signed and encoded `UncheckedMortalCompactExtrinsic` as hex.
=== Inspecting a module ID
```bash
subkey --network kusama moduleid "py/trsry"
subkey module-id "py/trsry" --network kusama
OUTPUT:
Public Key URI `F3opxRbN5ZbjJNU511Kj2TLuzFcDq9BGduA9TgiECafpg29` is account:
+81
View File
@@ -0,0 +1,81 @@
// 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/>.
use structopt::StructOpt;
use sc_cli::{
Error, VanityCmd, SignCmd, VerifyCmd, InsertCmd,
GenerateNodeKeyCmd, GenerateCmd, InspectCmd, InspectNodeKeyCmd
};
use substrate_frame_cli::ModuleIdCmd;
use sp_core::crypto::Ss58Codec;
#[derive(Debug, StructOpt)]
#[structopt(
name = "subkey",
author = "Parity Team <admin@parity.io>",
about = "Utility for generating and restoring with Substrate keys",
)]
pub enum Subkey {
/// 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),
/// Inspect a module ID address
ModuleId(ModuleIdCmd),
/// Sign a message, with a given (secret) key.
Sign(SignCmd),
/// Generate a seed that provides a vanity address.
Vanity(VanityCmd),
/// Verify a signature for a message, provided on STDIN, with a given (public or secret) key.
Verify(VerifyCmd),
}
/// Run the subkey command, given the apropriate runtime.
pub fn run<R>() -> Result<(), Error>
where
R: frame_system::Trait,
R::AccountId: Ss58Codec
{
match Subkey::from_args() {
Subkey::GenerateNodeKey(cmd) => cmd.run()?,
Subkey::Generate(cmd) => cmd.run()?,
Subkey::InspectKey(cmd) => cmd.run()?,
Subkey::InspectNodeKey(cmd) => cmd.run()?,
Subkey::Insert(cmd) => cmd.run()?,
Subkey::ModuleId(cmd) => cmd.run::<R>()?,
Subkey::Vanity(cmd) => cmd.run()?,
Subkey::Verify(cmd) => cmd.run()?,
Subkey::Sign(cmd) => cmd.run()?,
};
Ok(())
}
+4 -808
View File
@@ -16,814 +16,10 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg_attr(feature = "bench", feature(test))]
#[cfg(feature = "bench")]
extern crate test;
//! Subkey utility, based on node_runtime.
use bip39::{Language, Mnemonic, MnemonicType};
use clap::{App, ArgMatches, SubCommand};
use codec::{Decode, Encode};
use hex_literal::hex;
use itertools::Itertools;
use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey};
use node_primitives::{Balance, Hash, Index, AccountId, Signature};
use node_runtime::{BalancesCall, Call, Runtime, SignedPayload, UncheckedExtrinsic, VERSION};
use serde_json::json;
use sp_core::{
crypto::{set_default_ss58_version, Ss58AddressFormat, Ss58Codec},
ed25519, sr25519, ecdsa, Pair, Public, H256, hexdisplay::HexDisplay,
};
use sp_runtime::{traits::{AccountIdConversion, IdentifyAccount, Verify}, generic::Era, ModuleId};
use std::{
convert::{TryInto, TryFrom}, io::{stdin, Read}, str::FromStr, path::PathBuf, fs, fmt,
};
use node_runtime::Runtime;
mod rpc;
mod vanity;
enum OutputType {
Json,
Text,
}
impl<'a> TryFrom<&'a str> for OutputType {
type Error = ();
fn try_from(s: &'a str) -> Result<OutputType, ()> {
match s {
"json" => Ok(OutputType::Json),
"text" => Ok(OutputType::Text),
_ => Err(()),
}
}
}
trait Crypto: Sized {
type Pair: Pair<Public = Self::Public>;
type Public: Public + Ss58Codec + AsRef<[u8]> + std::hash::Hash;
fn pair_from_suri(suri: &str, password: Option<&str>) -> Self::Pair {
Self::Pair::from_string(suri, password).expect("Invalid phrase")
}
fn ss58_from_pair(pair: &Self::Pair) -> String where
<Self::Pair as Pair>::Public: PublicT,
{
pair.public().into_runtime().into_account().to_ss58check()
}
fn public_from_pair(pair: &Self::Pair) -> Self::Public {
pair.public()
}
fn print_from_uri(
uri: &str,
password: Option<&str>,
network_override: Option<Ss58AddressFormat>,
output: OutputType,
) where
<Self::Pair as Pair>::Public: PublicT,
{
let v = network_override.unwrap_or_default();
if let Ok((pair, seed)) = Self::Pair::from_phrase(uri, password) {
let public_key = Self::public_from_pair(&pair);
match output {
OutputType::Json => {
let json = json!({
"secretPhrase": uri,
"networkId": String::from(v),
"secretSeed": format_seed::<Self>(seed),
"publicKey": format_public_key::<Self>(public_key.clone()),
"accountId": format_account_id::<Self>(public_key),
"ss58Address": Self::ss58_from_pair(&pair),
});
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
},
OutputType::Text => {
println!("Secret phrase `{}` is account:\n \
Network ID/version: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
SS58 Address: {}",
uri,
String::from(v),
format_seed::<Self>(seed),
format_public_key::<Self>(public_key.clone()),
format_account_id::<Self>(public_key),
Self::ss58_from_pair(&pair),
);
},
}
} else if let Ok((pair, seed)) = Self::Pair::from_string_with_seed(uri, password) {
let public_key = Self::public_from_pair(&pair);
match output {
OutputType::Json => {
let json = json!({
"secretKeyUri": uri,
"networkId": String::from(v),
"secretSeed": if let Some(seed) = seed { format_seed::<Self>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Self>(public_key.clone()),
"accountId": format_account_id::<Self>(public_key),
"ss58Address": Self::ss58_from_pair(&pair),
});
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
},
OutputType::Text => {
println!("Secret Key URI `{}` is account:\n \
Network ID/version: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
SS58 Address: {}",
uri,
String::from(v),
if let Some(seed) = seed { format_seed::<Self>(seed) } else { "n/a".into() },
format_public_key::<Self>(public_key.clone()),
format_account_id::<Self>(public_key),
Self::ss58_from_pair(&pair),
);
},
}
} else if let Ok((public_key, v)) =
<Self::Pair as Pair>::Public::from_string_with_version(uri)
{
let v = network_override.unwrap_or(v);
match output {
OutputType::Json => {
let json = json!({
"publicKeyUri": uri,
"networkId": String::from(v),
"publicKey": format_public_key::<Self>(public_key.clone()),
"accountId": format_account_id::<Self>(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::<Self>(public_key.clone()),
format_account_id::<Self>(public_key.clone()),
public_key.to_ss58check_with_version(v),
);
},
}
} else {
eprintln!("Invalid phrase/URI given");
}
}
}
struct Ed25519;
impl Crypto for Ed25519 {
type Pair = ed25519::Pair;
type Public = ed25519::Public;
fn pair_from_suri(suri: &str, password_override: Option<&str>) -> Self::Pair {
ed25519::Pair::from_legacy_string(suri, password_override)
}
}
struct Sr25519;
impl Crypto for Sr25519 {
type Pair = sr25519::Pair;
type Public = sr25519::Public;
}
struct Ecdsa;
impl Crypto for Ecdsa {
type Pair = ecdsa::Pair;
type Public = ecdsa::Public;
}
type SignatureOf<C> = <<C as Crypto>::Pair as Pair>::Signature;
type PublicOf<C> = <<C as Crypto>::Pair as Pair>::Public;
type SeedOf<C> = <<C as Crypto>::Pair as Pair>::Seed;
type AccountPublic = <Signature as Verify>::Signer;
trait SignatureT: AsRef<[u8]> + AsMut<[u8]> + Default {
/// Converts the signature into a runtime account signature, if possible. If not possible, bombs out.
fn into_runtime(self) -> Signature {
panic!("This cryptography isn't supported for this runtime.")
}
}
trait PublicT: Sized + AsRef<[u8]> + Ss58Codec {
/// Converts the public key into a runtime account public key, if possible. If not possible, bombs out.
fn into_runtime(self) -> AccountPublic {
panic!("This cryptography isn't supported for this runtime.")
}
}
impl SignatureT for sr25519::Signature { fn into_runtime(self) -> Signature { self.into() } }
impl SignatureT for ed25519::Signature { fn into_runtime(self) -> Signature { self.into() } }
impl SignatureT for ecdsa::Signature { fn into_runtime(self) -> Signature { self.into() } }
impl PublicT for sr25519::Public { fn into_runtime(self) -> AccountPublic { self.into() } }
impl PublicT for ed25519::Public { fn into_runtime(self) -> AccountPublic { self.into() } }
impl PublicT for ecdsa::Public { fn into_runtime(self) -> AccountPublic { self.into() } }
fn get_usage() -> String {
let networks = Ss58AddressFormat::all().iter().cloned().map(String::from).join("/");
let default_network = String::from(Ss58AddressFormat::default());
format!("
-e, --ed25519 'Use Ed25519/BIP39 cryptography'
-k, --secp256k1 'Use SECP256k1/ECDSA/BIP39 cryptography'
-s, --sr25519 'Use Schnorr/Ristretto x25519/BIP39 cryptography'
[network] -n, --network <network> 'Specify a network. One of {}. Default is {}'
[password] -p, --password <password> 'The password for the key'
--password-interactive 'You will be prompted for the password for the key.'
[output] -o, --output <output> 'Specify an output format. One of text, json. Default is text.'
", networks, default_network)
}
fn get_app<'a, 'b>(usage: &'a str) -> App<'a, 'b> {
App::new("subkey")
.author("Parity Team <admin@parity.io>")
.about("Utility for generating and restoring with Substrate keys")
.version(env!("CARGO_PKG_VERSION"))
.args_from_usage(usage)
.subcommands(vec![
SubCommand::with_name("generate")
.about("Generate a random account")
.args_from_usage("[words] -w, --words <words> \
'The number of words in the phrase to generate. One of 12 \
(default), 15, 18, 21 and 24.'
"),
SubCommand::with_name("generate-node-key")
.about("Generate a random node libp2p key, save it to file and print its peer ID")
.args_from_usage("[file] 'Name of file to save secret key to'"),
SubCommand::with_name("inspect")
.about("Gets a public key and a SS58 address from the provided Secret URI")
.args_from_usage("[uri] '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.'
"),
SubCommand::with_name("inspect-node-key")
.about("Print the peer ID corresponding to the node key in the given file")
.args_from_usage("[file] 'Name of file to read the secret key from'"),
SubCommand::with_name("sign")
.about("Sign a message, provided on STDIN, with a given (secret) key")
.args_from_usage("
-h, --hex 'The message on STDIN is hex-encoded data'
<suri> '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.'
"),
SubCommand::with_name("sign-transaction")
.about("Sign transaction from encoded Call. Returns a signed and encoded \
UncheckedMortalCompactExtrinsic as hex.")
.args_from_usage("
-c, --call <call> 'The call, hex-encoded.'
-n, --nonce <nonce> 'The nonce.'
-p, --password <password> 'The password for the key.'
-h, --prior-block-hash <prior-block-hash> 'The prior block hash, hex-encoded.'
-s, --suri <suri> 'The secret key URI.'
"),
SubCommand::with_name("transfer")
.about("Author and sign a Node pallet_balances::Transfer transaction with a given (secret) key")
.args_from_usage("
<genesis> -g, --genesis <genesis> 'The genesis hash or a recognized \
chain identifier (dev, elm, alex).'
<from> 'The signing secret key URI.'
<to> 'The destination account public key URI.'
<amount> 'The number of units to transfer.'
<index> 'The signing account's transaction index.'
"),
SubCommand::with_name("vanity")
.about("Generate a seed that provides a vanity address")
.args_from_usage("
-n, --number <number> 'Number of keys to generate'
<pattern> 'Desired pattern'
"),
SubCommand::with_name("verify")
.about("Verify a signature for a message, provided on STDIN, with a given \
(public or secret) key")
.args_from_usage("
-h, --hex 'The message on STDIN is hex-encoded data'
<sig> 'Signature, hex-encoded.'
<uri> '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.'
"),
SubCommand::with_name("insert")
.about("Insert a key to the keystore of a node")
.args_from_usage("
<suri> '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.'
<key-type> 'Key type, examples: \"gran\", or \"imon\" '
[node-url] 'Node JSON-RPC endpoint, default \"http:://localhost:9933\"'
"),
SubCommand::with_name("moduleid")
.about("Inspect a module ID address")
.args_from_usage("
<id> 'The module ID used to derive the account'
")
])
}
fn main() -> Result<(), Error> {
let usage = get_usage();
let matches = get_app(&usage).get_matches();
if matches.is_present("ed25519") {
return execute::<Ed25519>(matches);
}
if matches.is_present("secp256k1") {
return execute::<Ecdsa>(matches)
}
return execute::<Sr25519>(matches)
}
/// Get `URI` from CLI or prompt the user.
///
/// `URI` is extracted from `matches` by using `match_name`.
///
/// If the `URI` given as CLI argument is a file, the file content is taken as `URI`.
/// If no `URI` is given to the CLI, the user is prompted for it.
fn get_uri(match_name: &str, matches: &ArgMatches) -> Result<String, Error> {
let uri = if let Some(uri) = matches.value_of(match_name) {
let file = PathBuf::from(uri);
if file.is_file() {
fs::read_to_string(uri)?
.trim_end()
.into()
} else {
uri.into()
}
} else {
rpassword::read_password_from_tty(Some("URI: "))?
};
Ok(uri)
}
#[derive(derive_more::Display, derive_more::From)]
enum Error {
Static(&'static str),
Io(std::io::Error),
Formatted(String),
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
fn static_err(msg: &'static str) -> Result<(), Error> {
Err(Error::Static(msg))
}
fn execute<C: Crypto>(matches: ArgMatches) -> Result<(), Error>
where
SignatureOf<C>: SignatureT,
PublicOf<C>: PublicT,
{
let password_interactive = matches.is_present("password-interactive");
let password = matches.value_of("password");
let password = if password.is_some() && password_interactive {
return static_err("`--password` given and `--password-interactive` selected!");
} else if password_interactive {
Some(
rpassword::read_password_from_tty(Some("Key password: "))?
)
} else {
password.map(Into::into)
};
let password = password.as_ref().map(String::as_str);
let maybe_network: Option<Ss58AddressFormat> = match matches.value_of("network").map(|network| {
network
.try_into()
.map_err(|_| Error::Static("Invalid network name. See --help for available networks."))
}) {
Some(Err(e)) => return Err(e),
Some(Ok(v)) => Some(v),
None => None,
};
if let Some(network) = maybe_network {
set_default_ss58_version(network);
}
let output: OutputType = match matches.value_of("output").map(TryInto::try_into) {
Some(Err(_)) => return Err(Error::Static("Invalid output name. See --help for available outputs.")),
Some(Ok(v)) => v,
None => OutputType::Text,
};
match matches.subcommand() {
("generate", Some(matches)) => {
let mnemonic = generate_mnemonic(matches)?;
C::print_from_uri(mnemonic.phrase(), password, maybe_network, output);
}
("generate-node-key", Some(matches)) => {
let file = matches.value_of("file").ok_or(Error::Static("Output file name is required"))?;
let keypair = libp2p_ed25519::Keypair::generate();
let secret = keypair.secret();
let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id();
fs::write(file, secret.as_ref())?;
println!("{}", peer_id);
}
("inspect", Some(matches)) => {
C::print_from_uri(&get_uri("uri", &matches)?, password, maybe_network, output);
}
("inspect-node-key", Some(matches)) => {
let file = matches.value_of("file").ok_or(Error::Static("Input file name is required"))?;
let mut file_content = fs::read(file)?;
let secret = libp2p_ed25519::SecretKey::from_bytes(&mut file_content)
.map_err(|_| Error::Static("Bad node key file"))?;
let keypair = libp2p_ed25519::Keypair::from(secret);
let peer_id = PublicKey::Ed25519(keypair.public()).into_peer_id();
println!("{}", peer_id);
}
("sign", Some(matches)) => {
let suri = get_uri("suri", &matches)?;
let should_decode = matches.is_present("hex");
let message = read_message_from_stdin(should_decode)?;
let signature = do_sign::<C>(&suri, message, password)?;
println!("{}", signature);
}
("verify", Some(matches)) => {
let uri = get_uri("uri", &matches)?;
let should_decode = matches.is_present("hex");
let message = read_message_from_stdin(should_decode)?;
let is_valid_signature = do_verify::<C>(matches, &uri, message)?;
if is_valid_signature {
println!("Signature verifies correctly.");
} else {
return static_err("Signature invalid.");
}
}
("vanity", Some(matches)) => {
let desired: String = matches
.value_of("pattern")
.map(str::to_string)
.unwrap_or_default();
let result = vanity::generate_key::<C>(&desired)?;
let formated_seed = format_seed::<C>(result.seed);
C::print_from_uri(&formated_seed, None, maybe_network, output);
}
("transfer", Some(matches)) => {
let signer = read_pair::<C>(matches.value_of("from"), password)?;
let index = read_required_parameter::<Index>(matches, "index")?;
let genesis_hash = read_genesis_hash(matches)?;
let to: AccountId = read_account_id(matches.value_of("to"));
let amount = read_required_parameter::<Balance>(matches, "amount")?;
let function = Call::Balances(BalancesCall::transfer(to.into(), amount));
let extrinsic = create_extrinsic::<C>(function, index, signer, genesis_hash);
print_extrinsic(extrinsic);
}
("sign-transaction", Some(matches)) => {
let signer = read_pair::<C>(matches.value_of("suri"), password)?;
let index = read_required_parameter::<Index>(matches, "nonce")?;
let genesis_hash = read_genesis_hash(matches)?;
let call = matches.value_of("call").expect("call is required; qed");
let function: Call = hex::decode(&call)
.ok()
.and_then(|x| Decode::decode(&mut &x[..]).ok())
.unwrap();
let extrinsic = create_extrinsic::<C>(function, index, signer, genesis_hash);
print_extrinsic(extrinsic);
}
("insert", Some(matches)) => {
let suri = get_uri("suri", &matches)?;
let pair = read_pair::<C>(Some(&suri), password)?;
let node_url = matches.value_of("node-url").unwrap_or("http://localhost:9933");
let key_type = matches.value_of("key-type").ok_or(Error::Static("Key type id is required"))?;
// Just checking
let _key_type_id = sp_core::crypto::KeyTypeId::try_from(key_type)
.map_err(|_| Error::Static("Cannot convert argument to keytype: argument should be 4-character string"))?;
let rpc = rpc::RpcClient::new(node_url.to_string());
rpc.insert_key(
key_type.to_string(),
suri,
sp_core::Bytes(pair.public().as_ref().to_vec()),
);
}
("moduleid", Some(matches)) => {
let id = get_uri("id", &matches)?;
if id.len() != 8 {
Err("a module id must be a string of 8 characters")?
}
let id_fixed_array: [u8; 8] = id.as_bytes().try_into()
.map_err(|_| Error::Static("Cannot convert argument to moduleid: argument should be 8-character string"))?;
let account_id: AccountId = ModuleId(id_fixed_array).into_account();
let v = maybe_network.unwrap_or(Ss58AddressFormat::SubstrateAccount);
C::print_from_uri(&account_id.to_ss58check_with_version(v), password, maybe_network, output);
}
_ => print_usage(&matches),
}
Ok(())
}
/// Creates a new randomly generated mnemonic phrase.
fn generate_mnemonic(matches: &ArgMatches) -> Result<Mnemonic, Error> {
let words = match matches.value_of("words") {
Some(words) => {
let num = usize::from_str(words).map_err(|_| Error::Static("Invalid number given for --words"))?;
MnemonicType::for_word_count(num)
.map_err(|_| Error::Static("Invalid number of words given for phrase: must be 12/15/18/21/24"))?
},
None => MnemonicType::Words12,
};
Ok(Mnemonic::new(words, Language::English))
}
fn do_sign<C: Crypto>(suri: &str, message: Vec<u8>, password: Option<&str>) -> Result<String, Error>
where
SignatureOf<C>: SignatureT,
PublicOf<C>: PublicT,
{
let pair = read_pair::<C>(Some(suri), password)?;
let signature = pair.sign(&message);
Ok(format_signature::<C>(&signature))
}
fn do_verify<C: Crypto>(matches: &ArgMatches, uri: &str, message: Vec<u8>) -> Result<bool, Error>
where
SignatureOf<C>: SignatureT,
PublicOf<C>: PublicT,
{
let signature = read_signature::<C>(matches)?;
let pubkey = read_public_key::<C>(Some(uri));
Ok(<<C as Crypto>::Pair as Pair>::verify(&signature, &message, &pubkey))
}
fn decode_hex<T: AsRef<[u8]>>(message: T) -> Result<Vec<u8>, Error> {
hex::decode(message).map_err(|e| Error::Formatted(format!("Invalid hex ({})", e)))
}
fn read_message_from_stdin(should_decode: bool) -> Result<Vec<u8>, Error> {
let mut message = vec![];
stdin()
.lock()
.read_to_end(&mut message)?;
if should_decode {
message = decode_hex(&message)?;
}
Ok(message)
}
fn read_required_parameter<T: FromStr>(matches: &ArgMatches, name: &str) -> Result<T, Error> where
<T as FromStr>::Err: std::fmt::Debug,
{
let str_value = matches
.value_of(name)
.expect("parameter is required; thus it can't be None; qed");
str::parse::<T>(str_value).map_err(|_|
Error::Formatted(format!("Invalid `{}' parameter; expecting an integer.", name))
)
}
fn read_genesis_hash(matches: &ArgMatches) -> Result<H256, Error> {
let genesis_hash: Hash = match matches.value_of("genesis").unwrap_or("alex") {
"elm" => hex!["10c08714a10c7da78f40a60f6f732cf0dba97acfb5e2035445b032386157d5c3"].into(),
"alex" => hex!["dcd1346701ca8396496e52aa2785b1748deb6db09551b72159dcb3e08991025b"].into(),
h => Decode::decode(&mut &decode_hex(h)?[..])
.expect("Invalid genesis hash or unrecognized chain identifier"),
};
println!(
"Using a genesis hash of {}",
HexDisplay::from(&genesis_hash.as_ref())
);
Ok(genesis_hash)
}
fn read_signature<C: Crypto>(matches: &ArgMatches) -> Result<SignatureOf<C>, Error>
where
SignatureOf<C>: SignatureT,
PublicOf<C>: PublicT,
{
let sig_data = matches
.value_of("sig")
.expect("signature parameter is required; thus it can't be None; qed");
let mut signature = <<C as Crypto>::Pair as Pair>::Signature::default();
let sig_data = decode_hex(sig_data)?;
if sig_data.len() != signature.as_ref().len() {
return Err(Error::Formatted(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);
Ok(signature)
}
fn read_public_key<C: Crypto>(matched_uri: Option<&str>) -> PublicOf<C>
where
PublicOf<C>: PublicT,
{
let uri = matched_uri.expect("parameter is required; thus it can't be None; qed");
let uri = if uri.starts_with("0x") {
&uri[2..]
} else {
uri
};
if let Ok(pubkey_vec) = hex::decode(uri) {
<C as Crypto>::Public::from_slice(pubkey_vec.as_slice())
} else {
<C as Crypto>::Public::from_string(uri)
.ok()
.expect("Invalid URI; expecting either a secret URI or a public URI.")
}
}
fn read_account_id(matched_uri: Option<&str>) -> AccountId {
let uri = matched_uri.expect("parameter is required; thus it can't be None; qed");
let uri = if uri.starts_with("0x") {
&uri[2..]
} else {
uri
};
if let Ok(data_vec) = hex::decode(uri) {
AccountId::try_from(data_vec.as_slice())
.expect("Invalid hex length for account ID; should be 32 bytes")
} else {
AccountId::from_ss58check(uri).ok()
.expect("Invalid SS58-check address given for account ID.")
}
}
fn read_pair<C: Crypto>(
matched_suri: Option<&str>,
password: Option<&str>,
) -> Result<<C as Crypto>::Pair, Error> where
SignatureOf<C>: SignatureT,
PublicOf<C>: PublicT,
{
let suri = matched_suri.ok_or(Error::Static("parameter is required; thus it can't be None; qed"))?;
Ok(C::pair_from_suri(suri, password))
}
fn format_signature<C: Crypto>(signature: &SignatureOf<C>) -> String {
format!("{}", HexDisplay::from(&signature.as_ref()))
}
fn format_seed<C: Crypto>(seed: SeedOf<C>) -> String {
format!("0x{}", HexDisplay::from(&seed.as_ref()))
}
fn format_public_key<C: Crypto>(public_key: PublicOf<C>) -> String {
format!("0x{}", HexDisplay::from(&public_key.as_ref()))
}
fn format_account_id<C: Crypto>(public_key: PublicOf<C>) -> String where
PublicOf<C>: PublicT,
{
format!("0x{}", HexDisplay::from(&public_key.into_runtime().into_account().as_ref()))
}
fn create_extrinsic<C: Crypto>(
function: Call,
index: Index,
signer: C::Pair,
genesis_hash: H256,
) -> UncheckedExtrinsic where
PublicOf<C>: PublicT,
SignatureOf<C>: SignatureT,
{
let extra = |i: Index, f: Balance| {
(
frame_system::CheckSpecVersion::<Runtime>::new(),
frame_system::CheckTxVersion::<Runtime>::new(),
frame_system::CheckGenesis::<Runtime>::new(),
frame_system::CheckEra::<Runtime>::from(Era::Immortal),
frame_system::CheckNonce::<Runtime>::from(i),
frame_system::CheckWeight::<Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(f),
)
};
let raw_payload = SignedPayload::from_raw(
function,
extra(index, 0),
(
VERSION.spec_version,
VERSION.transaction_version,
genesis_hash,
genesis_hash,
(),
(),
(),
),
);
let signature = raw_payload.using_encoded(|payload| signer.sign(payload)).into_runtime();
let signer = signer.public().into_runtime();
let (function, extra, _) = raw_payload.deconstruct();
UncheckedExtrinsic::new_signed(
function,
signer.into_account().into(),
signature,
extra,
)
}
fn print_extrinsic(extrinsic: UncheckedExtrinsic) {
println!("0x{}", HexDisplay::from(&extrinsic.encode()));
}
fn print_usage(matches: &ArgMatches) {
println!("{}", matches.usage());
}
#[cfg(test)]
mod tests {
use super::*;
fn test_generate_sign_verify<CryptoType: Crypto>()
where
SignatureOf<CryptoType>: SignatureT,
PublicOf<CryptoType>: PublicT,
{
let usage = get_usage();
let app = get_app(&usage);
let password = None;
// Generate public key and seed.
let arg_vec = vec!["subkey", "generate"];
let matches = app.clone().get_matches_from(arg_vec);
let matches = matches.subcommand().1.unwrap();
let mnemonic = generate_mnemonic(matches).expect("generate failed");
let (pair, seed) =
<<CryptoType as Crypto>::Pair as Pair>::from_phrase(mnemonic.phrase(), password)
.unwrap();
let public_key = CryptoType::public_from_pair(&pair);
let public_key = format_public_key::<CryptoType>(public_key);
let seed = format_seed::<CryptoType>(seed);
let message = "Blah Blah\n".as_bytes().to_vec();
let signature = do_sign::<CryptoType>(&seed, message.clone(), password).expect("signing failed");
// Verify the previous signature.
let arg_vec = vec!["subkey", "verify", &signature[..], &public_key[..]];
let matches = get_app(&usage).get_matches_from(arg_vec);
let matches = matches.subcommand().1.unwrap();
assert!(do_verify::<CryptoType>(matches, &public_key, message).expect("verify failed"));
}
#[test]
fn generate_sign_verify_should_work_for_ed25519() {
test_generate_sign_verify::<Ed25519>();
}
#[test]
fn generate_sign_verify_should_work_for_sr25519() {
test_generate_sign_verify::<Sr25519>();
}
#[test]
fn should_work() {
let s = "0123456789012345678901234567890123456789012345678901234567890123";
let d1: Hash = hex::decode(s)
.ok()
.and_then(|x| Decode::decode(&mut &x[..]).ok())
.unwrap();
let d2: Hash = {
let mut gh: [u8; 32] = Default::default();
gh.copy_from_slice(hex::decode(s).unwrap().as_ref());
Hash::from(gh)
};
assert_eq!(d1, d2);
}
fn main() -> Result<(), sc_cli::Error> {
subkey::run::<Runtime>()
}
-51
View File
@@ -1,51 +0,0 @@
// 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/>.
//! Helper to run commands against current node RPC
use futures::Future;
use hyper::rt;
use node_primitives::Hash;
use sc_rpc::author::AuthorClient;
use jsonrpc_core_client::transports::http;
use sp_core::Bytes;
pub struct RpcClient { url: String }
impl RpcClient {
pub fn new(url: String) -> Self { Self { url } }
pub fn insert_key(
&self,
key_type: String,
suri: String,
public: Bytes,
) {
let url = self.url.clone();
rt::run(
http::connect(&url)
.and_then(|client: AuthorClient<Hash, Hash>| {
client.insert_key(key_type, suri, public).map(|_| ())
})
.map_err(|e| {
eprintln!("Error inserting key: {:?}", e);
})
);
}
}
+8
View File
@@ -23,7 +23,13 @@ lazy_static = "1.4.0"
tokio = { version = "0.2.21", features = [ "signal", "rt-core", "rt-threaded", "blocking" ] }
futures = "0.3.4"
fdlimit = "0.1.4"
libp2p = "0.22"
parity-scale-codec = "1.3.0"
hex = "0.4.2"
rand = "0.7.3"
bip39 = "0.6.0-beta.1"
serde_json = "1.0.41"
sc-keystore = { version = "2.0.0-rc5", path = "../keystore" }
sc-informant = { version = "0.8.0-rc5", path = "../informant" }
sp-panic-handler = { version = "2.0.0-rc5", path = "../../primitives/panic-handler" }
sc-client-api = { version = "2.0.0-rc5", path = "../api" }
@@ -53,6 +59,8 @@ nix = "0.17.0"
[dev-dependencies]
tempfile = "3.1.0"
sp-io = { version = "2.0.0-rc3", path = "../../primitives/io" }
sp-application-crypto = { version = "2.0.0-alpha.2", default-features = false, path = "../../primitives/application-crypto" }
[features]
wasmtime = [
+17
View File
@@ -85,6 +85,23 @@ arg_enum! {
}
}
arg_enum! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum CryptoScheme {
Ed25519,
Sr25519,
Ecdsa,
}
}
arg_enum! {
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum OutputType {
Json,
Text,
}
}
arg_enum! {
/// How to execute blocks
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -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),*)
}
}
};
}
@@ -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 <https://www.gnu.org/licenses/>.
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<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 {
@@ -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<C: Crypto> {
pub pair: C::Pair,
pub seed: <C::Pair as Pair>::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<C: Crypto>(desired: &str) -> Result<KeyPair<C>, &'static str> where
PublicOf<C>: 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 = <C::Pair as 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 = 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<String, &'static str> {
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::<Ed25519>("j")
.unwrap()
.pair
.public()
.to_ss58check()
.contains("j"));
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]
@@ -175,22 +218,6 @@ mod tests {
);
}
#[test]
fn test_invalid_pattern() {
assert!(generate_key::<Ed25519>("").is_err());
assert!(generate_key::<Ed25519>("0").is_err());
assert!(generate_key::<Ed25519>("l").is_err());
assert!(generate_key::<Ed25519>("I").is_err());
assert!(generate_key::<Ed25519>("O").is_err());
assert!(generate_key::<Ed25519>("!").is_err());
}
#[test]
fn test_valid_pattern() {
assert!(generate_key::<Ed25519>("o").is_ok());
assert!(generate_key::<Ed25519>("L").is_ok());
}
#[cfg(feature = "bench")]
#[bench]
fn bench_paranoiac(b: &mut Bencher) {
+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(())
}
+5
View File
@@ -18,6 +18,8 @@
//! Initialization errors.
/// Result type alias for the CLI.
pub type Result<T> = std::result::Result<T, Error>;
@@ -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,
+1 -1
View File
@@ -21,7 +21,7 @@
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
mod arg_enums;
pub mod arg_enums;
mod commands;
mod config;
mod error;
@@ -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<Option<SecretString>> {
let (password_interactive, password) = (self.password_interactive, self.password.clone());
let pass = if password_interactive {
let mut password = rpassword::read_password_from_tty(Some("Key password: "))?;
let pass = Some(FromStr::from_str(&password).map_err(|()| "Error reading password")?);
password.zeroize();
pass
} else {
password
};
Ok(pass)
}
}
#[cfg(not(target_os = "unknown"))]
+57 -10
View File
@@ -25,8 +25,11 @@ mod pruning_params;
mod shared_params;
mod transaction_pool_params;
use std::{fmt::Debug, str::FromStr};
use std::{fmt::Debug, str::FromStr, convert::TryFrom};
use sp_runtime::{generic::BlockId, traits::{Block as BlockT, NumberFor}};
use sp_core::crypto::Ss58AddressFormat;
use crate::arg_enums::{OutputType, CryptoScheme};
use structopt::StructOpt;
pub use crate::params::database_params::*;
pub use crate::params::import_params::*;
@@ -39,10 +42,10 @@ pub use crate::params::shared_params::*;
pub use crate::params::transaction_pool_params::*;
/// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a decimal.
#[derive(Debug)]
pub struct BlockNumber(String);
#[derive(Debug, Clone)]
pub struct GenericNumber(String);
impl FromStr for BlockNumber {
impl FromStr for GenericNumber {
type Err = String;
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
@@ -57,15 +60,15 @@ impl FromStr for BlockNumber {
}
}
impl BlockNumber {
impl GenericNumber {
/// Wrapper on top of `std::str::parse<N>` but with `Error` as a `String`
///
/// See `https://doc.rust-lang.org/std/primitive.str.html#method.parse` for more elaborate
/// documentation.
pub fn parse<N>(&self) -> Result<N, String>
where
N: FromStr,
N::Err: std::fmt::Debug,
where
N: FromStr,
N::Err: std::fmt::Debug,
{
FromStr::from_str(&self.0).map_err(|e| format!("Failed to parse block number: {:?}", e))
}
@@ -89,7 +92,7 @@ impl FromStr for BlockNumberOrHash {
Ok(Self(block_number.into()))
}
} else {
BlockNumber::from_str(block_number).map(|v| Self(v.0))
GenericNumber::from_str(block_number).map(|v| Self(v.0))
}
}
}
@@ -109,11 +112,55 @@ impl BlockNumberOrHash {
.map_err(|e| format!("Failed to parse block hash: {:?}", e))?
))
} else {
BlockNumber(self.0.clone()).parse().map(BlockId::Number)
GenericNumber(self.0.clone()).parse().map(BlockId::Number)
}
}
}
/// Optional flag for specifying crypto algorithm
#[derive(Debug, StructOpt)]
pub struct CryptoSchemeFlag {
/// cryptography scheme
#[structopt(
long,
value_name = "SCHEME",
possible_values = &CryptoScheme::variants(),
case_insensitive = true,
default_value = "Sr25519"
)]
pub scheme: CryptoScheme,
}
/// Optional flag for specifying output type
#[derive(Debug, StructOpt)]
pub struct OutputTypeFlag {
/// output format
#[structopt(
long,
value_name = "FORMAT",
possible_values = &OutputType::variants(),
case_insensitive = true,
default_value = "Text"
)]
pub output_type: OutputType,
}
/// Optional flag for specifying network scheme
#[derive(Debug, StructOpt)]
pub struct NetworkSchemeFlag {
/// network address format
#[structopt(
long,
value_name = "NETWORK",
possible_values = &Ss58AddressFormat::all_names()[..],
parse(try_from_str = Ss58AddressFormat::try_from),
case_insensitive = true,
default_value = "polkadot"
)]
pub network: Ss58AddressFormat,
}
#[cfg(test)]
mod tests {
use super::*;
-1
View File
@@ -177,7 +177,6 @@ use sp_runtime::{
},
};
use frame_system::{self as system, ensure_signed, ensure_root};
pub use self::imbalances::{PositiveImbalance, NegativeImbalance};
pub trait WeightInfo {
@@ -19,7 +19,7 @@ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for tr
14 | | }
15 | | }
| |_- type in trait
16 |
16 |
17 | sp_api::impl_runtime_apis! {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found struct `std::string::String`
|
@@ -19,7 +19,7 @@ error[E0053]: method `Api_test_runtime_api_impl` has an incompatible type for tr
14 | | }
15 | | }
| |_- type in trait
16 |
16 |
17 | sp_api::impl_runtime_apis! {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found `&u64`
|
+23 -6
View File
@@ -317,8 +317,7 @@ lazy_static::lazy_static! {
macro_rules! ss58_address_format {
( $( $identifier:tt => ($number:expr, $name:expr, $desc:tt) )* ) => (
/// A known address (sub)format/network ID for SS58.
#[derive(Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "std", derive(Debug))]
#[derive(Copy, Clone, PartialEq, Eq, crate::RuntimeDebug)]
pub enum Ss58AddressFormat {
$(#[doc = $desc] $identifier),*,
/// Use a manually provided numeric value.
@@ -337,6 +336,12 @@ macro_rules! ss58_address_format {
];
impl Ss58AddressFormat {
/// names of all address formats
pub fn all_names() -> &'static [&'static str] {
&[
$($name),*,
]
}
/// All known address formats.
pub fn all() -> &'static [Ss58AddressFormat] {
&ALL_SS58_ADDRESS_FORMATS
@@ -380,17 +385,29 @@ macro_rules! ss58_address_format {
}
}
impl<'a> TryFrom<&'a str> for Ss58AddressFormat {
type Error = ();
/// Error encountered while parsing `Ss58AddressFormat` from &'_ str
/// unit struct for now.
#[derive(Copy, Clone, PartialEq, Eq, crate::RuntimeDebug)]
pub struct ParseError;
fn try_from(x: &'a str) -> Result<Ss58AddressFormat, ()> {
impl<'a> TryFrom<&'a str> for Ss58AddressFormat {
type Error = ParseError;
fn try_from(x: &'a str) -> Result<Ss58AddressFormat, Self::Error> {
match x {
$($name => Ok(Ss58AddressFormat::$identifier)),*,
a => a.parse::<u8>().map_err(|_| ()).and_then(TryFrom::try_from),
a => a.parse::<u8>().map(Ss58AddressFormat::Custom).map_err(|_| ParseError),
}
}
}
#[cfg(feature = "std")]
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "failed to parse network value as u8")
}
}
#[cfg(feature = "std")]
impl Default for Ss58AddressFormat {
fn default() -> Self {
+1
View File
@@ -50,6 +50,7 @@ pub use impl_serde::serialize as bytes;
#[cfg(feature = "full_crypto")]
pub mod hashing;
#[cfg(feature = "full_crypto")]
pub use hashing::{blake2_128, blake2_256, twox_64, twox_128, twox_256, keccak_256};
pub mod hexdisplay;
+1 -1
View File
@@ -159,7 +159,7 @@ impl BuildStorage for () {
fn assimilate_storage(
&self,
_: &mut sp_core::storage::Storage,
)-> Result<(), String> {
) -> Result<(), String> {
Err("`assimilate_storage` not implemented for `()`".into())
}
}
+1
View File
@@ -59,6 +59,7 @@ use cfg_if::cfg_if;
// Ensure Babe and Aura use the same crypto to simplify things a bit.
pub use sp_consensus_babe::{AuthorityId, SlotNumber, AllowedSlots};
pub type AuraId = sp_consensus_aura::sr25519::AuthorityId;
// Include the WASM binary
@@ -0,0 +1,22 @@
[package]
name = "substrate-frame-cli"
version = "2.0.0-rc5"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
license = "Apache-2.0"
homepage = "https://substrate.dev"
repository = "https://github.com/paritytech/substrate/"
description = "cli interface for FRAME"
documentation = "https://docs.rs/substrate-frame-cli"
[dependencies]
sp-core = { version = "2.0.0-rc5", path = "../../../primitives/core" }
sc-cli = { version = "0.8.0-rc5", path = "../../../client/cli" }
sp-runtime = { version = "2.0.0-rc5", path = "../../../primitives/runtime" }
structopt = "0.3.8"
frame-system = { version = "2.0.0-rc5", path = "../../../frame/system" }
[dev-dependencies]
[features]
default = []
@@ -0,0 +1,23 @@
// 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.
//! frame-system CLI utilities
mod module_id;
pub use module_id::ModuleIdCmd;
@@ -0,0 +1,96 @@
// 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 `moduleid` subcommand
use sc_cli::{
Error, utils::print_from_uri, CryptoSchemeFlag,
OutputTypeFlag, KeystoreParams, with_crypto_scheme,
};
use sp_runtime::ModuleId;
use sp_runtime::traits::AccountIdConversion;
use sp_core::crypto::{Ss58Codec, Ss58AddressFormat};
use std::convert::{TryInto, TryFrom};
use structopt::StructOpt;
/// The `moduleid` command
#[derive(Debug, StructOpt)]
#[structopt(
name = "moduleid",
about = "Inspect a module ID address"
)]
pub struct ModuleIdCmd {
/// The module ID used to derive the account
id: String,
/// network address format
#[structopt(
long,
value_name = "NETWORK",
possible_values = &Ss58AddressFormat::all_names()[..],
parse(try_from_str = Ss58AddressFormat::try_from),
case_insensitive = true,
default_value = "substrate"
)]
pub network: Ss58AddressFormat,
#[allow(missing_docs)]
#[structopt(flatten)]
pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[structopt(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
#[allow(missing_docs)]
#[structopt(flatten)]
pub keystore_params: KeystoreParams,
}
impl ModuleIdCmd {
/// runs the command
pub fn run<R>(&self) -> Result<(), Error>
where
R: frame_system::Trait,
R::AccountId: Ss58Codec,
{
if self.id.len() != 8 {
Err("a module id must be a string of 8 characters")?
}
let password = self.keystore_params.read_password()?;
let id_fixed_array: [u8; 8] = self.id.as_bytes()
.try_into()
.map_err(|_| "Cannot convert argument to moduleid: argument should be 8-character string")?;
let account_id: R::AccountId = ModuleId(id_fixed_array).into_account();
let network = self.network;
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
&account_id.to_ss58check_with_version(network),
password,
network,
self.output_scheme.output_type.clone()
)
);
Ok(())
}
}