referrerpolicy=no-referrer-when-downgrade

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: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! Implementation of the `generate-node-key` subcommand
20
21use 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/// Common arguments accross all generate key commands, subkey and node.
32#[derive(Debug, Args, Clone)]
33pub struct GenerateKeyCmdCommon {
34	/// Name of file to save secret key to.
35	/// If not given, the secret key is printed to stdout.
36	#[arg(long)]
37	file: Option<PathBuf>,
38
39	/// The output is in raw binary format.
40	/// If not given, the output is written as an hex encoded string.
41	#[arg(long)]
42	bin: bool,
43}
44
45/// The `generate-node-key` command
46#[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	/// Specify the chain specification.
56	///
57	/// It can be any of the predefined chains like dev, local, staging, polkadot, kusama.
58	#[arg(long, value_name = "CHAIN_SPEC")]
59	pub chain: Option<String>,
60	/// A directory where the key should be saved. If a key already
61	/// exists in the directory, it won't be overwritten.
62	#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
63	base_path: Option<PathBuf>,
64
65	/// Save the key in the default directory. If a key already
66	/// exists in the directory, it won't be overwritten.
67	#[arg(long, conflicts_with_all = ["base_path", "file"])]
68	default_base_path: bool,
69}
70
71impl GenerateKeyCmdCommon {
72	/// Run the command
73	pub fn run(&self) -> Result<(), Error> {
74		generate_key(&self.file, self.bin, None, &None, false, None)
75	}
76}
77
78impl GenerateNodeKeyCmd {
79	/// Run the command
80	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
92// Utility function for generating a key based on the provided CLI arguments
93//
94// `file`  - Name of file to save secret key to
95// `bin`
96fn 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			// This should not happen, arguments are marked as mutually exclusive.
137			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}