sc_cli/params/
node_key_params.rs1use 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
27pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
31
32#[derive(Debug, Clone, Args)]
35pub struct NodeKeyParams {
36 #[arg(long, value_name = "KEY")]
50 pub node_key: Option<String>,
51
52 #[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
70 pub node_key_type: NodeKeyType,
71
72 #[arg(long, value_name = "FILE")]
82 pub node_key_file: Option<PathBuf>,
83
84 #[arg(long)]
97 pub unsafe_force_node_key_generation: bool,
98}
99
100impl NodeKeyParams {
101 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
133fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
135 error::Error::Input(format!("Invalid node key: {}", e))
136}
137
138fn 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 {
173 Ok(())
174 },
175 _ => Err(error::Error::Input("Unexpected node key config".into())),
176 })
177 })
178 }
179
180 assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
181 }
182
183 #[test]
184 fn test_node_key_config_file() {
185 fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
186 let params = NodeKeyParams {
187 node_key_type: NodeKeyType::Ed25519,
188 node_key: None,
189 node_key_file: Some(file),
190 unsafe_force_node_key_generation: false,
191 };
192
193 let node_key = params
194 .node_key(&PathBuf::from("not-used"), Role::Authority, false)
195 .expect("Creates node key config")
196 .into_keypair()
197 .expect("Creates node key pair");
198
199 if node_key.secret().as_ref() != key.as_ref() {
200 panic!("Invalid key")
201 }
202 }
203
204 let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
205 let file = tmp.path().join("mysecret").to_path_buf();
206 let key = ed25519::SecretKey::generate();
207
208 fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
209 check_key(file.clone(), &key);
210
211 fs::write(&file, &key).expect("Writes secret key");
212 check_key(file.clone(), &key);
213 }
214
215 #[test]
216 fn test_node_key_config_default() {
217 fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
218 where
219 F: Fn(NodeKeyParams) -> error::Result<()>,
220 {
221 NodeKeyType::value_variants().iter().try_for_each(|t| {
222 let node_key_type = *t;
223 f(NodeKeyParams {
224 node_key_type,
225 node_key: None,
226 node_key_file: None,
227 unsafe_force_node_key_generation,
228 })
229 })
230 }
231
232 fn some_config_dir(
233 net_config_dir: &PathBuf,
234 unsafe_force_node_key_generation: bool,
235 role: Role,
236 is_dev: bool,
237 ) -> error::Result<()> {
238 with_def_params(
239 |params| {
240 let dir = PathBuf::from(net_config_dir.clone());
241 let typ = params.node_key_type;
242 params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
243 NodeKeyConfig::Ed25519(sc_network::config::Secret::File(ref f))
244 if typ == NodeKeyType::Ed25519 &&
245 f == &dir.join(NODE_KEY_ED25519_FILE) =>
246 {
247 Ok(())
248 },
249 _ => Err(error::Error::Input("Unexpected node key config".into())),
250 })
251 },
252 unsafe_force_node_key_generation,
253 )
254 }
255
256 assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
257 assert!(
258 some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
259 );
260 assert!(
261 some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
262 );
263 assert!(matches!(
264 some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
265 Err(Error::NetworkKeyNotFound(_))
266 ));
267
268 let tempdir = TempDir::new().unwrap();
269 let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
270 assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
271 }
272}