1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
56// 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.
1011// 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.
1516// 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/>.
1819//! Implementation of the `generate-node-key` subcommand
2021use 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};
3031/// 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)]
37file: Option<PathBuf>,
3839/// The output is in raw binary format.
40 /// If not given, the output is written as an hex encoded string.
41#[arg(long)]
42bin: bool,
43}
4445/// 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)]
54pub 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")]
59pub 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>,
6465/// 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}
7071impl GenerateKeyCmdCommon {
72/// Run the command
73pub fn run(&self) -> Result<(), Error> {
74 generate_key(&self.file, self.bin, None, &None, false, None)
75 }
76}
7778impl GenerateNodeKeyCmd {
79/// Run the command
80pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
81 generate_key(
82&self.common.file,
83self.common.bin,
84Some(chain_spec_id),
85&self.base_path,
86self.default_base_path,
87Some(executable_name),
88 )
89 }
90}
9192// 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> {
104let keypair = ed25519::Keypair::generate();
105106let secret = keypair.secret();
107108let file_data = if bin {
109 secret.as_ref().to_owned()
110 } else {
111 array_bytes::bytes2hex("", secret).into_bytes()
112 };
113114match (file, base_path, default_base_path) {
115 (Some(file), None, false) => fs::write(file, file_data)?,
116 (None, Some(_), false) | (None, None, true) => {
117let 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 );
122123 fs::create_dir_all(network_path.as_path())?;
124125let key_path = network_path.join(NODE_KEY_ED25519_FILE);
126if key_path.exists() {
127eprintln!("Skip generation, a key already exists in {:?}", key_path);
128return Err(Error::KeyAlreadyExistsInPath(key_path));
129 } else {
130eprintln!("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.
137return Err(Error::Input("Mutually exclusive arguments provided".into()));
138 },
139 }
140141eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
142143Ok(())
144}
145146#[cfg(test)]
147pub mod tests {
148use crate::DEFAULT_NETWORK_CONFIG_PATH;
149150use super::*;
151use std::io::Read;
152use tempfile::Builder;
153154#[test]
155fn generate_node_key() {
156let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
157let file_path = file.path().display().to_string();
158let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
159assert!(generate.run("test", &String::from("test")).is_ok());
160let mut buf = String::new();
161assert!(file.read_to_string(&mut buf).is_ok());
162assert!(array_bytes::hex2bytes(&buf).is_ok());
163 }
164165#[test]
166fn generate_node_key_base_path() {
167let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
168let key_path = base_dir
169 .path()
170 .join("chains/test_id/")
171 .join(DEFAULT_NETWORK_CONFIG_PATH)
172 .join(NODE_KEY_ED25519_FILE);
173let base_path = base_dir.path().display().to_string();
174let generate =
175 GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
176assert!(generate.run("test_id", &String::from("test")).is_ok());
177let buf = fs::read_to_string(key_path.as_path()).unwrap();
178assert!(array_bytes::hex2bytes(&buf).is_ok());
179180assert!(generate.run("test_id", &String::from("test")).is_err());
181let new_buf = fs::read_to_string(key_path).unwrap();
182assert_eq!(
183 array_bytes::hex2bytes(&new_buf).unwrap(),
184 array_bytes::hex2bytes(&buf).unwrap()
185 );
186 }
187}