sc_cli/commands/
utils.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! subcommand utilities
20use crate::{
21	error::{self, Error},
22	OutputType,
23};
24use serde_json::json;
25use sp_core::{
26	crypto::{
27		unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
28		Zeroize,
29	},
30	hexdisplay::HexDisplay,
31	Pair,
32};
33use sp_runtime::{traits::IdentifyAccount, MultiSigner};
34use std::path::PathBuf;
35
36/// Public key type for Runtime
37pub type PublicFor<P> = <P as sp_core::Pair>::Public;
38/// Seed type for Runtime
39pub type SeedFor<P> = <P as sp_core::Pair>::Seed;
40
41/// helper method to fetch uri from `Option<String>` either as a file or read from stdin
42pub fn read_uri(uri: Option<&String>) -> error::Result<String> {
43	let uri = if let Some(uri) = uri {
44		let file = PathBuf::from(&uri);
45		if file.is_file() {
46			std::fs::read_to_string(uri)?.trim_end().to_owned()
47		} else {
48			uri.into()
49		}
50	} else {
51		rpassword::prompt_password("URI: ")?
52	};
53
54	Ok(uri)
55}
56
57/// Try to parse given `uri` and print relevant information.
58///
59/// 1. Try to construct the `Pair` while using `uri` as input for [`sp_core::Pair::from_phrase`].
60///
61/// 2. Try to construct the `Pair` while using `uri` as input for
62/// [`sp_core::Pair::from_string_with_seed`].
63///
64/// 3. Try to construct the `Pair::Public` while using `uri` as input for
65///    [`sp_core::crypto::Ss58Codec::from_string_with_version`].
66pub fn print_from_uri<Pair>(
67	uri: &str,
68	password: Option<SecretString>,
69	network_override: Option<Ss58AddressFormat>,
70	output: OutputType,
71) where
72	Pair: sp_core::Pair,
73	Pair::Public: Into<MultiSigner>,
74{
75	let password = password.as_ref().map(|s| s.expose_secret().as_str());
76	let network_id = String::from(unwrap_or_default_ss58_version(network_override));
77	if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
78		let public_key = pair.public();
79		let network_override = unwrap_or_default_ss58_version(network_override);
80
81		match output {
82			OutputType::Json => {
83				let json = json!({
84					"secretPhrase": uri,
85					"networkId": network_id,
86					"secretSeed": format_seed::<Pair>(seed),
87					"publicKey": format_public_key::<Pair>(public_key.clone()),
88					"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
89					"accountId": format_account_id::<Pair>(public_key),
90					"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
91				});
92				println!(
93					"{}",
94					serde_json::to_string_pretty(&json).expect("Json pretty print failed")
95				);
96			},
97			OutputType::Text => {
98				println!(
99					"Secret phrase:       {}\n  \
100					Network ID:        {}\n  \
101					Secret seed:       {}\n  \
102					Public key (hex):  {}\n  \
103					Account ID:        {}\n  \
104					Public key (SS58): {}\n  \
105					SS58 Address:      {}",
106					uri,
107					network_id,
108					format_seed::<Pair>(seed),
109					format_public_key::<Pair>(public_key.clone()),
110					format_account_id::<Pair>(public_key.clone()),
111					public_key.to_ss58check_with_version(network_override),
112					pair.public().into().into_account().to_ss58check_with_version(network_override),
113				);
114			},
115		}
116	} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
117		let public_key = pair.public();
118		let network_override = unwrap_or_default_ss58_version(network_override);
119
120		match output {
121			OutputType::Json => {
122				let json = json!({
123					"secretKeyUri": uri,
124					"networkId": network_id,
125					"secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
126					"publicKey": format_public_key::<Pair>(public_key.clone()),
127					"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
128					"accountId": format_account_id::<Pair>(public_key),
129					"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
130				});
131				println!(
132					"{}",
133					serde_json::to_string_pretty(&json).expect("Json pretty print failed")
134				);
135			},
136			OutputType::Text => {
137				println!(
138					"Secret Key URI `{}` is account:\n  \
139					Network ID:        {}\n  \
140					Secret seed:       {}\n  \
141					Public key (hex):  {}\n  \
142					Account ID:        {}\n  \
143					Public key (SS58): {}\n  \
144					SS58 Address:      {}",
145					uri,
146					network_id,
147					if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
148					format_public_key::<Pair>(public_key.clone()),
149					format_account_id::<Pair>(public_key.clone()),
150					public_key.to_ss58check_with_version(network_override),
151					pair.public().into().into_account().to_ss58check_with_version(network_override),
152				);
153			},
154		}
155	} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
156		let network_override = network_override.unwrap_or(network);
157
158		match output {
159			OutputType::Json => {
160				let json = json!({
161					"publicKeyUri": uri,
162					"networkId": String::from(network_override),
163					"publicKey": format_public_key::<Pair>(public_key.clone()),
164					"accountId": format_account_id::<Pair>(public_key.clone()),
165					"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
166					"ss58Address": public_key.to_ss58check_with_version(network_override),
167				});
168
169				println!(
170					"{}",
171					serde_json::to_string_pretty(&json).expect("Json pretty print failed")
172				);
173			},
174			OutputType::Text => {
175				println!(
176					"Public Key URI `{}` is account:\n  \
177					 Network ID/Version: {}\n  \
178					 Public key (hex):   {}\n  \
179					 Account ID:         {}\n  \
180					 Public key (SS58):  {}\n  \
181					 SS58 Address:       {}",
182					uri,
183					String::from(network_override),
184					format_public_key::<Pair>(public_key.clone()),
185					format_account_id::<Pair>(public_key.clone()),
186					public_key.to_ss58check_with_version(network_override),
187					public_key.to_ss58check_with_version(network_override),
188				);
189			},
190		}
191	} else {
192		println!("Invalid phrase/URI given");
193	}
194}
195
196/// Try to parse given `public` as hex encoded public key and print relevant information.
197pub fn print_from_public<Pair>(
198	public_str: &str,
199	network_override: Option<Ss58AddressFormat>,
200	output: OutputType,
201) -> Result<(), Error>
202where
203	Pair: sp_core::Pair,
204	Pair::Public: Into<MultiSigner>,
205{
206	let public = array_bytes::hex2bytes(public_str)?;
207
208	let public_key = Pair::Public::try_from(&public)
209		.map_err(|_| "Failed to construct public key from given hex")?;
210
211	let network_override = unwrap_or_default_ss58_version(network_override);
212
213	match output {
214		OutputType::Json => {
215			let json = json!({
216				"networkId": String::from(network_override),
217				"publicKey": format_public_key::<Pair>(public_key.clone()),
218				"accountId": format_account_id::<Pair>(public_key.clone()),
219				"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
220				"ss58Address": public_key.to_ss58check_with_version(network_override),
221			});
222
223			println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
224		},
225		OutputType::Text => {
226			println!(
227				"Network ID/Version: {}\n  \
228				 Public key (hex):   {}\n  \
229				 Account ID:         {}\n  \
230				 Public key (SS58):  {}\n  \
231				 SS58 Address:       {}",
232				String::from(network_override),
233				format_public_key::<Pair>(public_key.clone()),
234				format_account_id::<Pair>(public_key.clone()),
235				public_key.to_ss58check_with_version(network_override),
236				public_key.to_ss58check_with_version(network_override),
237			);
238		},
239	}
240
241	Ok(())
242}
243
244/// generate a pair from suri
245pub fn pair_from_suri<P: Pair>(suri: &str, password: Option<SecretString>) -> Result<P, Error> {
246	let result = if let Some(pass) = password {
247		let mut pass_str = pass.expose_secret().clone();
248		let pair = P::from_string(suri, Some(&pass_str));
249		pass_str.zeroize();
250		pair
251	} else {
252		P::from_string(suri, None)
253	};
254
255	Ok(result.map_err(|err| format!("Invalid phrase {:?}", err))?)
256}
257
258/// formats seed as hex
259pub fn format_seed<P: sp_core::Pair>(seed: SeedFor<P>) -> String {
260	format!("0x{}", HexDisplay::from(&seed.as_ref()))
261}
262
263/// formats public key as hex
264fn format_public_key<P: sp_core::Pair>(public_key: PublicFor<P>) -> String {
265	format!("0x{}", HexDisplay::from(&public_key.as_ref()))
266}
267
268/// formats public key as accountId as hex
269fn format_account_id<P: sp_core::Pair>(public_key: PublicFor<P>) -> String
270where
271	PublicFor<P>: Into<MultiSigner>,
272{
273	format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
274}
275
276/// Allows for calling $method with appropriate crypto impl.
277#[macro_export]
278macro_rules! with_crypto_scheme {
279	(
280		$scheme:expr,
281		$method:ident ( $($params:expr),* $(,)?) $(,)?
282	) => {
283		$crate::with_crypto_scheme!($scheme, $method<>($($params),*))
284	};
285	(
286		$scheme:expr,
287		$method:ident<$($generics:ty),*>( $( $params:expr ),* $(,)?) $(,)?
288	) => {
289		match $scheme {
290			$crate::CryptoScheme::Ecdsa => {
291				$method::<sp_core::ecdsa::Pair, $($generics),*>($($params),*)
292			}
293			$crate::CryptoScheme::Sr25519 => {
294				$method::<sp_core::sr25519::Pair, $($generics),*>($($params),*)
295			}
296			$crate::CryptoScheme::Ed25519 => {
297				$method::<sp_core::ed25519::Pair, $($generics),*>($($params),*)
298			}
299		}
300	};
301}