diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock index 76d3d004c5..7564a72a01 100644 --- a/substrate/Cargo.lock +++ b/substrate/Cargo.lock @@ -7454,6 +7454,7 @@ dependencies = [ "rpassword", "rustc-hex", "sc-rpc", + "serde_json", "sp-core", "sp-runtime", "substrate-bip39", diff --git a/substrate/bin/utils/subkey/Cargo.toml b/substrate/bin/utils/subkey/Cargo.toml index 9b7db6699f..d352cf28fa 100644 --- a/substrate/bin/utils/subkey/Cargo.toml +++ b/substrate/bin/utils/subkey/Cargo.toml @@ -28,6 +28,7 @@ derive_more = { version = "0.99.2" } sc-rpc = { version = "2.0.0", path = "../../../client/rpc" } jsonrpc-core-client = { version = "14.0.3", features = ["http"] } hyper = "0.12.35" +serde_json = "1.0" [features] bench = [] diff --git a/substrate/bin/utils/subkey/src/main.rs b/substrate/bin/utils/subkey/src/main.rs index c2fd7e28c2..52bacea384 100644 --- a/substrate/bin/utils/subkey/src/main.rs +++ b/substrate/bin/utils/subkey/src/main.rs @@ -25,6 +25,7 @@ use hex_literal::hex; use itertools::Itertools; 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, @@ -37,6 +38,24 @@ use std::{ mod rpc; mod vanity; +enum OutputType { + Json, + Text, +} + +impl<'a> TryFrom<&'a str> for OutputType { + type Error = (); + + fn try_from(s: &'a str) -> Result { + match s { + "json" => Ok(OutputType::Json), + "text" => Ok(OutputType::Text), + _ => Err(()), + } + } + +} + trait Crypto: Sized { type Pair: Pair; type Public: Public + Ss58Codec + AsRef<[u8]> + std::hash::Hash; @@ -55,50 +74,97 @@ trait Crypto: Sized { uri: &str, password: Option<&str>, network_override: Option, + output: OutputType, ) where ::Public: PublicT, { if let Ok((pair, seed)) = Self::Pair::from_phrase(uri, password) { let public_key = Self::public_from_pair(&pair); - println!("Secret phrase `{}` is account:\n \ - Secret seed: {}\n \ - Public key (hex): {}\n \ - Account ID: {}\n \ - SS58 Address: {}", - uri, - format_seed::(seed), - format_public_key::(public_key.clone()), - format_account_id::(public_key), - Self::ss58_from_pair(&pair) - ); + + match output { + OutputType::Json => { + let json = json!({ + "secretPhrase": uri, + "secretSeed": format_seed::(seed), + "publicKey": format_public_key::(public_key.clone()), + "accountId": format_account_id::(public_key), + "ss58Address": 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 \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", + uri, + format_seed::(seed), + format_public_key::(public_key.clone()), + format_account_id::(public_key), + 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); - println!("Secret Key URI `{}` is account:\n \ - Secret seed: {}\n \ - Public key (hex): {}\n \ - Account ID: {}\n \ - SS58 Address: {}", - uri, - if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, - format_public_key::(public_key.clone()), - format_account_id::(public_key), - Self::ss58_from_pair(&pair) - ); + + match output { + OutputType::Json => { + let json = json!({ + "secretKeyUri": uri, + "secretSeed": if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, + "publicKey": format_public_key::(public_key.clone()), + "accountId": format_account_id::(public_key), + "ss58Address": 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 \ + Secret seed: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", + uri, + if let Some(seed) = seed { format_seed::(seed) } else { "n/a".into() }, + format_public_key::(public_key.clone()), + format_account_id::(public_key), + Self::ss58_from_pair(&pair), + ); + }, + } + } else if let Ok((public_key, v)) = ::Public::from_string_with_version(uri) { let v = network_override.unwrap_or(v); - println!("Public Key URI `{}` is account:\n \ - Network ID/version: {}\n \ - Public key (hex): {}\n \ - Account ID: {}\n \ - SS58 Address: {}", - uri, - String::from(v), - format_public_key::(public_key.clone()), - format_account_id::(public_key.clone()), - public_key.to_ss58check_with_version(v) - ); + + match output { + OutputType::Json => { + let json = json!({ + "publicKeyUri": uri, + "networkId": String::from(v), + "publicKey": format_public_key::(public_key.clone()), + "accountId": format_account_id::(public_key.clone()), + "ss58Address": public_key.to_ss58check_with_version(v), + }); + println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed")); + }, + OutputType::Text => { + println!("Public Key URI `{}` is account:\n \ + Network ID/version: {}\n \ + Public key (hex): {}\n \ + Account ID: {}\n \ + SS58 Address: {}", + uri, + String::from(v), + format_public_key::(public_key.clone()), + format_account_id::(public_key.clone()), + public_key.to_ss58check_with_version(v), + ); + }, + } } else { println!("Invalid phrase/URI given"); } @@ -165,6 +231,7 @@ fn get_usage() -> String { [network] -n, --network 'Specify a network. One of {}. Default is {}' [password] -p, --password 'The password for the key' --password-interactive 'You will be prompted for the password for the key.' + [output] -o, --output 'Specify an output format. One of text, json. Default is text.' ", networks, default_network) } @@ -329,13 +396,20 @@ where 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); + C::print_from_uri(mnemonic.phrase(), password, maybe_network, output); } ("inspect", Some(matches)) => { - C::print_from_uri(&get_uri("uri", &matches)?, password, maybe_network); + C::print_from_uri(&get_uri("uri", &matches)?, password, maybe_network, output); } ("sign", Some(matches)) => { let suri = get_uri("suri", &matches)?; @@ -364,7 +438,7 @@ where .unwrap_or_default(); let result = vanity::generate_key::(&desired)?; let formated_seed = format_seed::(result.seed); - C::print_from_uri(&formated_seed, None, maybe_network); + C::print_from_uri(&formated_seed, None, maybe_network, output); } ("transfer", Some(matches)) => { let signer = read_pair::(matches.value_of("from"), password)?;