partner_chains_cli/generate_keys/
mod.rs1use 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}