sc_cli/commands/
generate_node_key.rs1use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
22use clap::{Args, Parser};
23use libp2p_identity::{ed25519, Keypair};
24use sc_service::BasePath;
25use std::{
26 fs,
27 io::{self, Write},
28 path::PathBuf,
29};
30
31#[derive(Debug, Args, Clone)]
33pub struct GenerateKeyCmdCommon {
34 #[arg(long)]
37 file: Option<PathBuf>,
38
39 #[arg(long)]
42 bin: bool,
43}
44
45#[derive(Debug, Clone, Parser)]
47#[command(
48 name = "generate-node-key",
49 about = "Generate a random node key, write it to a file or stdout \
50 and write the corresponding peer-id to stderr"
51)]
52pub struct GenerateNodeKeyCmd {
53 #[clap(flatten)]
54 pub common: GenerateKeyCmdCommon,
55 #[arg(long, value_name = "CHAIN_SPEC")]
59 pub chain: Option<String>,
60 #[arg(long, conflicts_with_all = ["file", "default_base_path"])]
63 base_path: Option<PathBuf>,
64
65 #[arg(long, conflicts_with_all = ["base_path", "file"])]
68 default_base_path: bool,
69}
70
71impl GenerateKeyCmdCommon {
72 pub fn run(&self) -> Result<(), Error> {
74 generate_key(&self.file, self.bin, None, &None, false, None)
75 }
76}
77
78impl GenerateNodeKeyCmd {
79 pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
81 generate_key(
82 &self.common.file,
83 self.common.bin,
84 Some(chain_spec_id),
85 &self.base_path,
86 self.default_base_path,
87 Some(executable_name),
88 )
89 }
90}
91
92fn generate_key(
97 file: &Option<PathBuf>,
98 bin: bool,
99 chain_spec_id: Option<&str>,
100 base_path: &Option<PathBuf>,
101 default_base_path: bool,
102 executable_name: Option<&String>,
103) -> Result<(), Error> {
104 let keypair = ed25519::Keypair::generate();
105
106 let secret = keypair.secret();
107
108 let file_data = if bin {
109 secret.as_ref().to_owned()
110 } else {
111 array_bytes::bytes2hex("", secret).into_bytes()
112 };
113
114 match (file, base_path, default_base_path) {
115 (Some(file), None, false) => fs::write(file, file_data)?,
116 (None, Some(_), false) | (None, None, true) => {
117 let network_path = build_network_key_dir_or_default(
118 base_path.clone().map(BasePath::new),
119 chain_spec_id.unwrap_or_default(),
120 executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
121 );
122
123 fs::create_dir_all(network_path.as_path())?;
124
125 let key_path = network_path.join(NODE_KEY_ED25519_FILE);
126 if key_path.exists() {
127 eprintln!("Skip generation, a key already exists in {:?}", key_path);
128 return Err(Error::KeyAlreadyExistsInPath(key_path));
129 } else {
130 eprintln!("Generating key in {:?}", key_path);
131 fs::write(key_path, file_data)?
132 }
133 },
134 (None, None, false) => io::stdout().lock().write_all(&file_data)?,
135 (_, _, _) => {
136 return Err(Error::Input("Mutually exclusive arguments provided".into()));
138 },
139 }
140
141 eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
142
143 Ok(())
144}
145
146#[cfg(test)]
147pub mod tests {
148 use crate::DEFAULT_NETWORK_CONFIG_PATH;
149
150 use super::*;
151 use std::io::Read;
152 use tempfile::Builder;
153
154 #[test]
155 fn generate_node_key() {
156 let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
157 let file_path = file.path().display().to_string();
158 let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
159 assert!(generate.run("test", &String::from("test")).is_ok());
160 let mut buf = String::new();
161 assert!(file.read_to_string(&mut buf).is_ok());
162 assert!(array_bytes::hex2bytes(&buf).is_ok());
163 }
164
165 #[test]
166 fn generate_node_key_base_path() {
167 let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
168 let key_path = base_dir
169 .path()
170 .join("chains/test_id/")
171 .join(DEFAULT_NETWORK_CONFIG_PATH)
172 .join(NODE_KEY_ED25519_FILE);
173 let base_path = base_dir.path().display().to_string();
174 let generate =
175 GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
176 assert!(generate.run("test_id", &String::from("test")).is_ok());
177 let buf = fs::read_to_string(key_path.as_path()).unwrap();
178 assert!(array_bytes::hex2bytes(&buf).is_ok());
179
180 assert!(generate.run("test_id", &String::from("test")).is_err());
181 let new_buf = fs::read_to_string(key_path).unwrap();
182 assert_eq!(
183 array_bytes::hex2bytes(&new_buf).unwrap(),
184 array_bytes::hex2bytes(&buf).unwrap()
185 );
186 }
187}