referrerpolicy=no-referrer-when-downgrade

sc_cli/params/
node_key_params.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
19use clap::Args;
20use sc_network::config::{ed25519, NodeKeyConfig};
21use sc_service::Role;
22use sp_core::H256;
23use std::{path::PathBuf, str::FromStr};
24
25use crate::{arg_enums::NodeKeyType, error, Error};
26
27/// The file name of the node's Ed25519 secret key inside the chain-specific
28/// network config directory, if neither `--node-key` nor `--node-key-file`
29/// is specified in combination with `--node-key-type=ed25519`.
30pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
31
32/// Parameters used to create the `NodeKeyConfig`, which determines the keypair
33/// used for libp2p networking.
34#[derive(Debug, Clone, Args)]
35pub struct NodeKeyParams {
36	/// Secret key to use for p2p networking.
37	///
38	/// The value is a string that is parsed according to the choice of
39	/// `--node-key-type` as follows:
40	///
41	///  - `ed25519`: the value is parsed as a hex-encoded Ed25519 32 byte secret key (64 hex
42	///    chars)
43	///
44	/// The value of this option takes precedence over `--node-key-file`.
45	///
46	/// WARNING: Secrets provided as command-line arguments are easily exposed.
47	/// Use of this option should be limited to development and testing. To use
48	/// an externally managed secret key, use `--node-key-file` instead.
49	#[arg(long, value_name = "KEY")]
50	pub node_key: Option<String>,
51
52	/// Crypto primitive to use for p2p networking.
53	///
54	/// The secret key of the node is obtained as follows:
55	///
56	/// - If the `--node-key` option is given, the value is parsed as a secret key according to the
57	///   type. See the documentation for `--node-key`.
58	///
59	/// - If the `--node-key-file` option is given, the secret key is read from the specified file.
60	///   See the documentation for `--node-key-file`.
61	///
62	/// - Otherwise, the secret key is read from a file with a predetermined, type-specific name
63	///   from the chain-specific network config directory inside the base directory specified by
64	///   `--base-dir`. If this file does not exist, it is created with a newly generated secret
65	///   key of the chosen type.
66	///
67	/// The node's secret key determines the corresponding public key and hence the
68	/// node's peer ID in the context of libp2p.
69	#[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
70	pub node_key_type: NodeKeyType,
71
72	/// File from which to read the node's secret key to use for p2p networking.
73	///
74	/// The contents of the file are parsed according to the choice of `--node-key-type`
75	/// as follows:
76	///
77	/// - `ed25519`: the file must contain an unencoded 32 byte or hex encoded Ed25519 secret key.
78	///
79	/// If the file does not exist, it is created with a newly generated secret key of
80	/// the chosen type.
81	#[arg(long, value_name = "FILE")]
82	pub node_key_file: Option<PathBuf>,
83
84	/// Forces key generation if node-key-file file does not exist.
85	///
86	/// This is an unsafe feature for production networks, because as an active authority
87	/// other authorities may depend on your node having a stable identity and they might
88	/// not being able to reach you if your identity changes after entering the active set.
89	///
90	/// For minimal node downtime if no custom `node-key-file` argument is provided
91	/// the network-key is usually persisted accross nodes restarts,
92	/// in the `network` folder from directory provided in `--base-path`
93	///
94	/// Warning!! If you ever run the node with this argument, make sure
95	/// you remove it for the subsequent restarts.
96	#[arg(long)]
97	pub unsafe_force_node_key_generation: bool,
98}
99
100impl NodeKeyParams {
101	/// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context
102	/// of an optional network config storage directory.
103	pub fn node_key(
104		&self,
105		net_config_dir: &PathBuf,
106		role: Role,
107		is_dev: bool,
108	) -> error::Result<NodeKeyConfig> {
109		Ok(match self.node_key_type {
110			NodeKeyType::Ed25519 => {
111				let secret = if let Some(node_key) = self.node_key.as_ref() {
112					parse_ed25519_secret(node_key)?
113				} else {
114					let key_path = self
115						.node_key_file
116						.clone()
117						.unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE));
118					if !self.unsafe_force_node_key_generation &&
119						role.is_authority() &&
120						!is_dev && !key_path.exists()
121					{
122						return Err(Error::NetworkKeyNotFound(key_path))
123					}
124					sc_network::config::Secret::File(key_path)
125				};
126
127				NodeKeyConfig::Ed25519(secret)
128			},
129		})
130	}
131}
132
133/// Create an error caused by an invalid node key argument.
134fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
135	error::Error::Input(format!("Invalid node key: {}", e))
136}
137
138/// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`.
139fn parse_ed25519_secret(hex: &str) -> error::Result<sc_network::config::Ed25519Secret> {
140	H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| {
141		ed25519::SecretKey::try_from_bytes(bytes)
142			.map(sc_network::config::Secret::Input)
143			.map_err(invalid_node_key)
144	})
145}
146
147#[cfg(test)]
148mod tests {
149	use super::*;
150	use clap::ValueEnum;
151	use sc_network::config::ed25519;
152	use std::fs::{self, File};
153	use tempfile::TempDir;
154
155	#[test]
156	fn test_node_key_config_input() {
157		fn secret_input(net_config_dir: &PathBuf) -> error::Result<()> {
158			NodeKeyType::value_variants().iter().try_for_each(|t| {
159				let node_key_type = *t;
160				let sk = match node_key_type {
161					NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(),
162				};
163				let params = NodeKeyParams {
164					node_key_type,
165					node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))),
166					node_key_file: None,
167					unsafe_force_node_key_generation: false,
168				};
169				params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c {
170					NodeKeyConfig::Ed25519(sc_network::config::Secret::Input(ref ski))
171						if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() =>
172						Ok(()),
173					_ => Err(error::Error::Input("Unexpected node key config".into())),
174				})
175			})
176		}
177
178		assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
179	}
180
181	#[test]
182	fn test_node_key_config_file() {
183		fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
184			let params = NodeKeyParams {
185				node_key_type: NodeKeyType::Ed25519,
186				node_key: None,
187				node_key_file: Some(file),
188				unsafe_force_node_key_generation: false,
189			};
190
191			let node_key = params
192				.node_key(&PathBuf::from("not-used"), Role::Authority, false)
193				.expect("Creates node key config")
194				.into_keypair()
195				.expect("Creates node key pair");
196
197			if node_key.secret().as_ref() != key.as_ref() {
198				panic!("Invalid key")
199			}
200		}
201
202		let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
203		let file = tmp.path().join("mysecret").to_path_buf();
204		let key = ed25519::SecretKey::generate();
205
206		fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
207		check_key(file.clone(), &key);
208
209		fs::write(&file, &key).expect("Writes secret key");
210		check_key(file.clone(), &key);
211	}
212
213	#[test]
214	fn test_node_key_config_default() {
215		fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
216		where
217			F: Fn(NodeKeyParams) -> error::Result<()>,
218		{
219			NodeKeyType::value_variants().iter().try_for_each(|t| {
220				let node_key_type = *t;
221				f(NodeKeyParams {
222					node_key_type,
223					node_key: None,
224					node_key_file: None,
225					unsafe_force_node_key_generation,
226				})
227			})
228		}
229
230		fn some_config_dir(
231			net_config_dir: &PathBuf,
232			unsafe_force_node_key_generation: bool,
233			role: Role,
234			is_dev: bool,
235		) -> error::Result<()> {
236			with_def_params(
237				|params| {
238					let dir = PathBuf::from(net_config_dir.clone());
239					let typ = params.node_key_type;
240					params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
241						NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f))
242							if typ == NodeKeyType::Ed25519 &&
243								f == &dir.join(NODE_KEY_ED25519_FILE) =>
244							Ok(()),
245						_ => Err(error::Error::Input("Unexpected node key config".into())),
246					})
247				},
248				unsafe_force_node_key_generation,
249			)
250		}
251
252		assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
253		assert!(
254			some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
255		);
256		assert!(
257			some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
258		);
259		assert!(matches!(
260			some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
261			Err(Error::NetworkKeyNotFound(_))
262		));
263
264		let tempdir = TempDir::new().unwrap();
265		let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
266		assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
267	}
268}