sc_cli/commands/
generate_node_key.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Implementation of the `generate-node-key` subcommand
19
20use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
21use clap::{Args, Parser};
22use libp2p_identity::{ed25519, Keypair};
23use sc_service::BasePath;
24use std::{
25	fs,
26	io::{self, Write},
27	path::PathBuf,
28};
29
30/// Common arguments accross all generate key commands, subkey and node.
31#[derive(Debug, Args, Clone)]
32pub struct GenerateKeyCmdCommon {
33	/// Name of file to save secret key to.
34	/// If not given, the secret key is printed to stdout.
35	#[arg(long)]
36	file: Option<PathBuf>,
37
38	/// The output is in raw binary format.
39	/// If not given, the output is written as an hex encoded string.
40	#[arg(long)]
41	bin: bool,
42}
43
44/// The `generate-node-key` command
45#[derive(Debug, Clone, Parser)]
46#[command(
47	name = "generate-node-key",
48	about = "Generate a random node key, write it to a file or stdout \
49		 	and write the corresponding peer-id to stderr"
50)]
51pub struct GenerateNodeKeyCmd {
52	#[clap(flatten)]
53	pub common: GenerateKeyCmdCommon,
54	/// Specify the chain specification.
55	///
56	/// It can be any of the predefined chains like dev, local, staging, polkadot, kusama.
57	#[arg(long, value_name = "CHAIN_SPEC")]
58	pub chain: Option<String>,
59	/// A directory where the key should be saved. If a key already
60	/// exists in the directory, it won't be overwritten.
61	#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
62	base_path: Option<PathBuf>,
63
64	/// Save the key in the default directory. If a key already
65	/// exists in the directory, it won't be overwritten.
66	#[arg(long, conflicts_with_all = ["base_path", "file"])]
67	default_base_path: bool,
68}
69
70impl GenerateKeyCmdCommon {
71	/// Run the command
72	pub fn run(&self) -> Result<(), Error> {
73		generate_key(&self.file, self.bin, None, &None, false, None)
74	}
75}
76
77impl GenerateNodeKeyCmd {
78	/// Run the command
79	pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
80		generate_key(
81			&self.common.file,
82			self.common.bin,
83			Some(chain_spec_id),
84			&self.base_path,
85			self.default_base_path,
86			Some(executable_name),
87		)
88	}
89}
90
91// Utility function for generating a key based on the provided CLI arguments
92//
93// `file`  - Name of file to save secret key to
94// `bin`
95fn generate_key(
96	file: &Option<PathBuf>,
97	bin: bool,
98	chain_spec_id: Option<&str>,
99	base_path: &Option<PathBuf>,
100	default_base_path: bool,
101	executable_name: Option<&String>,
102) -> Result<(), Error> {
103	let keypair = ed25519::Keypair::generate();
104
105	let secret = keypair.secret();
106
107	let file_data = if bin {
108		secret.as_ref().to_owned()
109	} else {
110		array_bytes::bytes2hex("", secret).into_bytes()
111	};
112
113	match (file, base_path, default_base_path) {
114		(Some(file), None, false) => fs::write(file, file_data)?,
115		(None, Some(_), false) | (None, None, true) => {
116			let network_path = build_network_key_dir_or_default(
117				base_path.clone().map(BasePath::new),
118				chain_spec_id.unwrap_or_default(),
119				executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
120			);
121
122			fs::create_dir_all(network_path.as_path())?;
123
124			let key_path = network_path.join(NODE_KEY_ED25519_FILE);
125			if key_path.exists() {
126				eprintln!("Skip generation, a key already exists in {:?}", key_path);
127				return Err(Error::KeyAlreadyExistsInPath(key_path));
128			} else {
129				eprintln!("Generating key in {:?}", key_path);
130				fs::write(key_path, file_data)?
131			}
132		},
133		(None, None, false) => io::stdout().lock().write_all(&file_data)?,
134		(_, _, _) => {
135			// This should not happen, arguments are marked as mutually exclusive.
136			return Err(Error::Input("Mutually exclusive arguments provided".into()));
137		},
138	}
139
140	eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
141
142	Ok(())
143}
144
145#[cfg(test)]
146pub mod tests {
147	use crate::DEFAULT_NETWORK_CONFIG_PATH;
148
149	use super::*;
150	use std::io::Read;
151	use tempfile::Builder;
152
153	#[test]
154	fn generate_node_key() {
155		let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
156		let file_path = file.path().display().to_string();
157		let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
158		assert!(generate.run("test", &String::from("test")).is_ok());
159		let mut buf = String::new();
160		assert!(file.read_to_string(&mut buf).is_ok());
161		assert!(array_bytes::hex2bytes(&buf).is_ok());
162	}
163
164	#[test]
165	fn generate_node_key_base_path() {
166		let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
167		let key_path = base_dir
168			.path()
169			.join("chains/test_id/")
170			.join(DEFAULT_NETWORK_CONFIG_PATH)
171			.join(NODE_KEY_ED25519_FILE);
172		let base_path = base_dir.path().display().to_string();
173		let generate =
174			GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
175		assert!(generate.run("test_id", &String::from("test")).is_ok());
176		let buf = fs::read_to_string(key_path.as_path()).unwrap();
177		assert!(array_bytes::hex2bytes(&buf).is_ok());
178
179		assert!(generate.run("test_id", &String::from("test")).is_err());
180		let new_buf = fs::read_to_string(key_path).unwrap();
181		assert_eq!(
182			array_bytes::hex2bytes(&new_buf).unwrap(),
183			array_bytes::hex2bytes(&buf).unwrap()
184		);
185	}
186}