soil_cli/params/
node_key_params.rs1use clap::Args;
8use soil_network::config::{ed25519, NodeKeyConfig};
9use soil_service::Role;
10use std::{path::PathBuf, str::FromStr};
11use subsoil::core::H256;
12
13use crate::{arg_enums::NodeKeyType, error, Error};
14
15pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
19
20#[derive(Debug, Clone, Args)]
23pub struct NodeKeyParams {
24 #[arg(long, value_name = "KEY")]
38 pub node_key: Option<String>,
39
40 #[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
58 pub node_key_type: NodeKeyType,
59
60 #[arg(long, value_name = "FILE")]
70 pub node_key_file: Option<PathBuf>,
71
72 #[arg(long)]
85 pub unsafe_force_node_key_generation: bool,
86}
87
88impl NodeKeyParams {
89 pub fn node_key(
92 &self,
93 net_config_dir: &PathBuf,
94 role: Role,
95 is_dev: bool,
96 ) -> error::Result<NodeKeyConfig> {
97 Ok(match self.node_key_type {
98 NodeKeyType::Ed25519 => {
99 let secret = if let Some(node_key) = self.node_key.as_ref() {
100 parse_ed25519_secret(node_key)?
101 } else {
102 let key_path = self
103 .node_key_file
104 .clone()
105 .unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE));
106 if !self.unsafe_force_node_key_generation
107 && role.is_authority()
108 && !is_dev && !key_path.exists()
109 {
110 return Err(Error::NetworkKeyNotFound(key_path));
111 }
112 soil_network::config::Secret::File(key_path)
113 };
114
115 NodeKeyConfig::Ed25519(secret)
116 },
117 })
118 }
119}
120
121fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
123 error::Error::Input(format!("Invalid node key: {}", e))
124}
125
126fn parse_ed25519_secret(hex: &str) -> error::Result<soil_network::config::Ed25519Secret> {
128 H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| {
129 ed25519::SecretKey::try_from_bytes(bytes)
130 .map(soil_network::config::Secret::Input)
131 .map_err(invalid_node_key)
132 })
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138 use clap::ValueEnum;
139 use soil_network::config::ed25519;
140 use std::fs::{self, File};
141 use tempfile::TempDir;
142
143 #[test]
144 fn test_node_key_config_input() {
145 fn secret_input(net_config_dir: &PathBuf) -> error::Result<()> {
146 NodeKeyType::value_variants().iter().try_for_each(|t| {
147 let node_key_type = *t;
148 let sk = match node_key_type {
149 NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(),
150 };
151 let params = NodeKeyParams {
152 node_key_type,
153 node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))),
154 node_key_file: None,
155 unsafe_force_node_key_generation: false,
156 };
157 params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c {
158 NodeKeyConfig::Ed25519(soil_network::config::Secret::Input(ref ski))
159 if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() =>
160 {
161 Ok(())
162 },
163 _ => Err(error::Error::Input("Unexpected node key config".into())),
164 })
165 })
166 }
167
168 assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
169 }
170
171 #[test]
172 fn test_node_key_config_file() {
173 fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
174 let params = NodeKeyParams {
175 node_key_type: NodeKeyType::Ed25519,
176 node_key: None,
177 node_key_file: Some(file),
178 unsafe_force_node_key_generation: false,
179 };
180
181 let node_key = params
182 .node_key(&PathBuf::from("not-used"), Role::Authority, false)
183 .expect("Creates node key config")
184 .into_keypair()
185 .expect("Creates node key pair");
186
187 if node_key.secret().as_ref() != key.as_ref() {
188 panic!("Invalid key")
189 }
190 }
191
192 let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
193 let file = tmp.path().join("mysecret").to_path_buf();
194 let key = ed25519::SecretKey::generate();
195
196 fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
197 check_key(file.clone(), &key);
198
199 fs::write(&file, &key).expect("Writes secret key");
200 check_key(file.clone(), &key);
201 }
202
203 #[test]
204 fn test_node_key_config_default() {
205 fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
206 where
207 F: Fn(NodeKeyParams) -> error::Result<()>,
208 {
209 NodeKeyType::value_variants().iter().try_for_each(|t| {
210 let node_key_type = *t;
211 f(NodeKeyParams {
212 node_key_type,
213 node_key: None,
214 node_key_file: None,
215 unsafe_force_node_key_generation,
216 })
217 })
218 }
219
220 fn some_config_dir(
221 net_config_dir: &PathBuf,
222 unsafe_force_node_key_generation: bool,
223 role: Role,
224 is_dev: bool,
225 ) -> error::Result<()> {
226 with_def_params(
227 |params| {
228 let dir = PathBuf::from(net_config_dir.clone());
229 let typ = params.node_key_type;
230 params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
231 NodeKeyConfig::Ed25519(soil_network::config::Secret::File(ref f))
232 if typ == NodeKeyType::Ed25519
233 && f == &dir.join(NODE_KEY_ED25519_FILE) =>
234 {
235 Ok(())
236 },
237 _ => Err(error::Error::Input("Unexpected node key config".into())),
238 })
239 },
240 unsafe_force_node_key_generation,
241 )
242 }
243
244 assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
245 assert!(
246 some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
247 );
248 assert!(
249 some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
250 );
251 assert!(matches!(
252 some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
253 Err(Error::NetworkKeyNotFound(_))
254 ));
255
256 let tempdir = TempDir::new().unwrap();
257 let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
258 assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
259 }
260}