Skip to main content

soil_cli/commands/
insert_key.rs

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