partner_chains_cli/generate_keys/
mod.rs

1use self::config::KEYS_FILE_PATH;
2use crate::io::IOContext;
3use crate::keystore::*;
4use crate::permissioned_candidates::PermissionedCandidateKeys;
5use crate::{config::config_fields, *};
6use anyhow::{Context, anyhow};
7use serde::Deserialize;
8use sp_core::{Pair, ed25519};
9
10#[cfg(test)]
11mod tests;
12
13#[derive(Clone, Debug, clap::Parser)]
14pub struct GenerateKeysCmd {}
15
16#[derive(Debug)]
17pub struct GenerateKeysConfig {
18	pub substrate_node_base_path: String,
19}
20impl GenerateKeysConfig {
21	pub fn load<C: IOContext>(context: &C) -> Self {
22		Self {
23			substrate_node_base_path: config_fields::SUBSTRATE_NODE_DATA_BASE_PATH
24				.load_or_prompt_and_save(context),
25		}
26	}
27	fn keystore_path(&self) -> String {
28		keystore_path(&self.substrate_node_base_path)
29	}
30
31	fn network_key_path(&self) -> String {
32		let Self { substrate_node_base_path, .. } = self;
33		network_key_path(substrate_node_base_path)
34	}
35}
36
37fn network_key_directory(substrate_node_base_path: &str) -> String {
38	format!("{substrate_node_base_path}/network")
39}
40
41pub fn network_key_path(substrate_node_base_path: &str) -> String {
42	format!("{}/secret_ed25519", network_key_directory(substrate_node_base_path))
43}
44
45impl CmdRun for GenerateKeysCmd {
46	fn run<C: IOContext>(&self, context: &C) -> anyhow::Result<()> {
47		context.eprint(
48			"This 🧙 wizard will generate the following keys and save them to your node's keystore:",
49		);
50		context.eprint("→  an ECDSA Cross-chain key");
51		context.eprint("→  an ED25519 Grandpa key");
52		context.eprint("→  an SR25519 Aura key");
53		context.eprint("It will also generate a network key for your node if needed.");
54		context.enewline();
55
56		set_dummy_env_vars(context);
57
58		let config = GenerateKeysConfig::load(context);
59		context.enewline();
60
61		generate_spo_keys(&config, context)?;
62		context.enewline();
63
64		generate_network_key(&config, context)?;
65		context.enewline();
66
67		context.eprint("🚀 All done!");
68
69		Ok(())
70	}
71}
72
73pub fn set_dummy_env_vars<C: IOContext>(context: &C) {
74	context.set_env_var(
75		"GENESIS_UTXO",
76		"0000000000000000000000000000000000000000000000000000000000000000#0",
77	);
78	context.set_env_var("COMMITTEE_CANDIDATE_ADDRESS", "addr_10000");
79	context.set_env_var(
80		"D_PARAMETER_POLICY_ID",
81		"00000000000000000000000000000000000000000000000000000000",
82	);
83	context.set_env_var(
84		"PERMISSIONED_CANDIDATES_POLICY_ID",
85		"00000000000000000000000000000000000000000000000000000000",
86	);
87	context.set_env_var(
88		"NATIVE_TOKEN_POLICY_ID",
89		"00000000000000000000000000000000000000000000000000000000",
90	);
91	context.set_env_var(
92		"NATIVE_TOKEN_ASSET_NAME",
93		"00000000000000000000000000000000000000000000000000000000",
94	);
95	context.set_env_var(
96		"ILLIQUID_SUPPLY_VALIDATOR_ADDRESS",
97		"00000000000000000000000000000000000000000000000000000000",
98	);
99}
100
101pub fn generate_spo_keys<C: IOContext>(
102	config: &GenerateKeysConfig,
103	context: &C,
104) -> anyhow::Result<()> {
105	if prompt_can_write("keys file", KEYS_FILE_PATH, context) {
106		let cross_chain_key = generate_or_load_key(config, context, &CROSS_CHAIN)?;
107		context.enewline();
108		let grandpa_key = generate_or_load_key(config, context, &GRANDPA)?;
109		context.enewline();
110		let aura_key = generate_or_load_key(config, context, &AURA)?;
111		context.enewline();
112
113		let public_keys_json = serde_json::to_string_pretty(&PermissionedCandidateKeys {
114			sidechain_pub_key: cross_chain_key,
115			aura_pub_key: aura_key,
116			grandpa_pub_key: grandpa_key,
117		})
118		.expect("Failed to serialize public keys");
119		context.write_file(KEYS_FILE_PATH, &public_keys_json);
120
121		context.eprint(&format!(
122			"🔑 The following public keys were generated and saved to the {} file:",
123			KEYS_FILE_PATH,
124		));
125		context.print(&(public_keys_json).to_string());
126		context.eprint("You may share them with your chain governance authority");
127		context.eprint("if you wish to be included as a permissioned candidate.");
128	} else {
129		context.eprint("Refusing to overwrite keys file - skipping");
130	}
131	Ok(())
132}
133
134pub fn generate_network_key<C: IOContext>(
135	config: &GenerateKeysConfig,
136	context: &C,
137) -> anyhow::Result<()> {
138	let maybe_existing_key =
139		context.read_file(&config.network_key_path()).as_deref().map(decode_network_key);
140	match maybe_existing_key {
141		Some(Ok(_)) => {
142			context.eprint(
143				"🔑 A valid network key is already present in the keystore, skipping generation",
144			);
145		},
146		None => {
147			context.eprint("⚙️ Generating network key");
148			run_generate_network_key(config, context)?;
149		},
150		Some(Err(err)) => {
151			context.eprint(&format!(
152				"⚠️ Network key in keystore is invalid ({}), wizard will regenerate it",
153				err,
154			));
155			context.eprint("⚙️ Regenerating the network key");
156			context.delete_file(&config.network_key_path())?;
157			run_generate_network_key(config, context)?;
158		},
159	};
160	Ok(())
161}
162
163fn run_generate_network_key<C: IOContext>(
164	config: &GenerateKeysConfig,
165	context: &C,
166) -> anyhow::Result<()> {
167	let node_executable = context.current_executable()?;
168	let network_key_directory = network_key_directory(&config.substrate_node_base_path);
169	let network_key_path = config.network_key_path();
170	context.run_command(&format!("mkdir -p {network_key_directory}"))?;
171	context.run_command(&format!(
172		"{node_executable} key generate-node-key --file {network_key_path}"
173	))?;
174	Ok(())
175}
176
177pub fn decode_network_key(key_str: &str) -> anyhow::Result<ed25519::Pair> {
178	hex::decode(key_str)
179		.context("Invalid hex")
180		.and_then(|slice| ed25519::Pair::from_seed_slice(&slice).context("Invalid ed25519 bytes"))
181}
182
183#[derive(Debug, Deserialize)]
184#[serde(rename_all = "camelCase")]
185pub struct KeyGenerationOutput {
186	public_key: String,
187	secret_phrase: String,
188}
189
190pub fn generate_keys<C: IOContext>(
191	context: &C,
192	KeyDefinition { scheme, name, .. }: &KeyDefinition,
193) -> anyhow::Result<KeyGenerationOutput> {
194	let executable = context.current_executable()?;
195	context.eprint(&format!("⚙️ Generating {name} ({scheme}) key"));
196	let output = context
197		.run_command(&format!("{executable} key generate --scheme {scheme} --output-type json"))?;
198
199	serde_json::from_str(&output)
200		.map_err(|_| anyhow!("Failed to parse generated keys json: {output}"))
201}
202
203pub fn store_keys<C: IOContext>(
204	context: &C,
205	GenerateKeysConfig { substrate_node_base_path: base_path }: &GenerateKeysConfig,
206	key_def: &KeyDefinition,
207	KeyGenerationOutput { secret_phrase, public_key }: &KeyGenerationOutput,
208) -> anyhow::Result<()> {
209	let node_executable = context.current_executable()?;
210	let KeyDefinition { scheme, key_type, name } = key_def;
211	context.eprint(&format!("💾 Inserting {name} ({scheme}) key"));
212	let keystore_path = keystore_path(base_path);
213	let cmd = format!(
214		"{node_executable} key insert --keystore-path {keystore_path} --scheme {scheme} --key-type {key_type} --suri '{secret_phrase}'"
215	);
216	let _ = context.run_command(&cmd)?;
217	let store_path = format!("{}/{}{public_key}", keystore_path, key_def.key_type_hex(),);
218	context.eprint(&format!("💾 {name} key stored at {store_path}",));
219	Ok(())
220}
221
222pub fn generate_or_load_key<C: IOContext>(
223	config: &GenerateKeysConfig,
224	context: &C,
225	key_def: &KeyDefinition,
226) -> anyhow::Result<String> {
227	let keystore_path = config.keystore_path();
228	let existing_keys = context.list_directory(&keystore_path)?.unwrap_or_default();
229
230	if let Some(key) = find_existing_key(&existing_keys, key_def) {
231		if context.prompt_yes_no(
232			&format!("A {} key already exists in store: {key} - overwrite it?", key_def.name),
233			false,
234		) {
235			let new_key = generate_keys(context, key_def)?;
236			store_keys(context, config, key_def, &new_key)?;
237
238			let old_key_path = format!("{keystore_path}/{}{key}", key_def.key_type_hex());
239			context
240				.delete_file(&old_key_path)
241				.context(format!("Failed to overwrite {} key at {old_key_path}", key_def.name))?;
242
243			Ok(new_key.public_key)
244		} else {
245			Ok(format!("0x{key}"))
246		}
247	} else {
248		let new_key = generate_keys(context, key_def)?;
249		store_keys(context, config, key_def, &new_key)?;
250
251		Ok(new_key.public_key)
252	}
253}