referrerpolicy=no-referrer-when-downgrade

sc_cli/commands/
insert_key.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//! Implementation of the `insert` subcommand
20
21use crate::{
22	utils, with_crypto_scheme, CryptoScheme, Error, KeystoreParams, SharedParams, SubstrateCli,
23};
24use clap::Parser;
25use sc_keystore::LocalKeystore;
26use sc_service::config::{BasePath, KeystoreConfig};
27use sp_core::crypto::{KeyTypeId, SecretString};
28use sp_keystore::KeystorePtr;
29
30/// The `insert` command
31#[derive(Debug, Clone, Parser)]
32#[command(name = "insert", about = "Insert a key to the keystore of a node.")]
33pub struct InsertKeyCmd {
34	/// The secret key URI.
35	/// If the value is a file, the file content is used as URI.
36	/// If not given, you will be prompted for the URI.
37	#[arg(long)]
38	suri: Option<String>,
39
40	/// Key type, examples: "gran", or "imon".
41	#[arg(long)]
42	key_type: String,
43
44	#[allow(missing_docs)]
45	#[clap(flatten)]
46	pub shared_params: SharedParams,
47
48	#[allow(missing_docs)]
49	#[clap(flatten)]
50	pub keystore_params: KeystoreParams,
51
52	/// The cryptography scheme that should be used to generate the key out of the given URI.
53	#[arg(long, value_name = "SCHEME", value_enum, ignore_case = true)]
54	pub scheme: CryptoScheme,
55}
56
57impl InsertKeyCmd {
58	/// Run the command
59	pub fn run<C: SubstrateCli>(&self, cli: &C) -> Result<(), Error> {
60		let suri = utils::read_uri(self.suri.as_ref())?;
61		let base_path = self
62			.shared_params
63			.base_path()?
64			.unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name()));
65		let chain_id = self.shared_params.chain_id(self.shared_params.is_dev());
66		let chain_spec = cli.load_spec(&chain_id)?;
67		let config_dir = base_path.config_dir(chain_spec.id());
68
69		let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? {
70			KeystoreConfig::Path { path, password } => {
71				let public = with_crypto_scheme!(self.scheme, to_vec(&suri, password.clone()))?;
72				let keystore: KeystorePtr = LocalKeystore::open(path, password)?.into();
73				(keystore, public)
74			},
75			_ => unreachable!("keystore_config always returns path and password; qed"),
76		};
77
78		let key_type =
79			KeyTypeId::try_from(self.key_type.as_str()).map_err(|_| Error::KeyTypeInvalid)?;
80
81		keystore
82			.insert(key_type, &suri, &public[..])
83			.map_err(|_| Error::KeystoreOperation)?;
84
85		Ok(())
86	}
87}
88
89fn to_vec<P: sp_core::Pair>(uri: &str, pass: Option<SecretString>) -> Result<Vec<u8>, Error> {
90	let p = utils::pair_from_suri::<P>(uri, pass)?;
91	Ok(p.public().as_ref().to_vec())
92}
93
94#[cfg(test)]
95mod tests {
96	use super::*;
97	use sc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension};
98	use sp_core::{sr25519::Pair, ByteArray, Pair as _};
99	use sp_keystore::Keystore;
100	use tempfile::TempDir;
101
102	struct Cli;
103
104	impl SubstrateCli for Cli {
105		fn impl_name() -> String {
106			"test".into()
107		}
108
109		fn impl_version() -> String {
110			"2.0".into()
111		}
112
113		fn description() -> String {
114			"test".into()
115		}
116
117		fn support_url() -> String {
118			"test.test".into()
119		}
120
121		fn copyright_start_year() -> i32 {
122			2021
123		}
124
125		fn author() -> String {
126			"test".into()
127		}
128
129		fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
130			let builder =
131				GenericChainSpec::<NoExtension, ()>::builder(Default::default(), NoExtension::None);
132			Ok(Box::new(
133				builder
134					.with_name("test")
135					.with_id("test_id")
136					.with_chain_type(ChainType::Development)
137					.with_genesis_config_patch(Default::default())
138					.build(),
139			))
140		}
141	}
142
143	#[test]
144	fn insert_with_custom_base_path() {
145		let path = TempDir::new().unwrap();
146		let path_str = format!("{}", path.path().display());
147		let (key, uri, _) = Pair::generate_with_phrase(None);
148
149		let inspect = InsertKeyCmd::parse_from(&[
150			"insert-key",
151			"-d",
152			&path_str,
153			"--key-type",
154			"test",
155			"--suri",
156			&uri,
157			"--scheme=sr25519",
158		]);
159		assert!(inspect.run(&Cli).is_ok());
160
161		let keystore =
162			LocalKeystore::open(path.path().join("chains").join("test_id").join("keystore"), None)
163				.unwrap();
164		assert!(keystore.has_keys(&[(key.public().to_raw_vec(), KeyTypeId(*b"test"))]));
165	}
166}