mina_cli/commands/
misc.rs1use libp2p_identity::PeerId;
2use mina_node::{account::AccountSecretKey, p2p::identity::SecretKey};
3use std::{fs::File, io::Write};
4
5#[derive(Debug, clap::Args)]
6pub struct Misc {
7 #[command(subcommand)]
8 command: MiscCommand,
9}
10
11impl Misc {
12 pub fn run(self) -> anyhow::Result<()> {
13 match self.command {
14 MiscCommand::MinaEncryptedKey(command) => command.run(),
15 MiscCommand::MinaKeyPair(command) => command.run(),
16 MiscCommand::P2PKeyPair(command) => command.run(),
17 }
18 }
19}
20
21#[derive(Clone, Debug, clap::Subcommand)]
22pub enum MiscCommand {
23 MinaEncryptedKey(MinaEncryptedKey),
24 MinaKeyPair(MinaKeyPair),
25 P2PKeyPair(P2PKeyPair),
26}
27
28#[derive(Debug, Clone, clap::Args)]
29pub struct P2PKeyPair {
30 #[arg(long, short = 's', env = "MINA_P2P_SEC_KEY")]
31 p2p_secret_key: Option<SecretKey>,
32}
33
34impl P2PKeyPair {
35 pub fn run(self) -> anyhow::Result<()> {
36 let secret_key = self.p2p_secret_key.unwrap_or_else(SecretKey::rand);
37 let public_key = secret_key.public_key();
38 let peer_id = public_key.peer_id();
39 let libp2p_peer_id = PeerId::try_from(peer_id)?;
40 println!("secret key: {secret_key}");
41 println!("public key: {public_key}");
42 println!("peer_id: {peer_id}");
43 println!("libp2p_id: {libp2p_peer_id}");
44
45 Ok(())
46 }
47}
48
49#[derive(Debug, Clone, Default, clap::Args)]
55pub struct MinaKeyPair {
56 #[arg(long, short = 's', env = "MINA_SEC_KEY")]
57 secret_key: Option<AccountSecretKey>,
58}
59
60impl MinaKeyPair {
61 pub fn run(self) -> anyhow::Result<()> {
62 let private_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand);
63 let public_key = private_key.public_key();
64
65 println!("secret key: {private_key}");
66 println!("public key: {public_key}");
67
68 Ok(())
69 }
70}
71
72#[derive(Debug, Clone, clap::Args)]
99pub struct MinaEncryptedKey {
100 #[arg(long, short = 's', env = "MINA_ENC_KEY")]
103 secret_key: Option<AccountSecretKey>,
104
105 #[arg(env = "MINA_PRIVKEY_PASS", default_value = "")]
108 password: String,
109
110 #[arg(long, short = 'f', default_value = "mina_encrypted_key.json")]
112 file: String,
113}
114
115impl MinaEncryptedKey {
116 pub fn run(self) -> anyhow::Result<()> {
133 let secret_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand);
134 let public_key = secret_key.public_key();
135
136 let public_key_file = format!("{}.pub", self.file);
138
139 if File::open(&public_key_file).is_ok() {
140 return Err(anyhow::anyhow!(
141 "Public key file '{}' already exists. Please choose a different file name.",
142 public_key_file
143 ));
144 }
145
146 secret_key
147 .to_encrypted_file(&self.file, &self.password)
148 .map_err(|e| {
149 anyhow::anyhow!("Failed to encrypt key: {} into path '{}'", e, self.file,)
150 })?;
151 let mut public_key_file = File::create(public_key_file)
153 .map_err(|e| anyhow::anyhow!("Failed to create public key file: {}", e))?;
154 public_key_file
155 .write_all(public_key.to_string().as_bytes())
156 .map_err(|e| anyhow::anyhow!("Failed to write public key: {}", e))?;
157
158 println!("secret key: {secret_key}");
159 println!("public key: {public_key}");
160 Ok(())
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::fs;
168 use tempfile::TempDir;
169
170 #[test]
171 fn test_mina_encrypted_key_generates_random_key() {
172 let temp_dir = TempDir::new().unwrap();
173 let file_path = temp_dir.path().join("test_key.json");
174 let file_path_str = file_path.to_str().unwrap().to_string();
175
176 let cmd = MinaEncryptedKey {
177 secret_key: None,
178 password: "test_password".to_string(),
179 file: file_path_str.clone(),
180 };
181
182 let result = cmd.run();
183 assert!(result.is_ok());
184
185 assert!(file_path.exists());
187
188 let file_content = fs::read_to_string(&file_path).unwrap();
190 assert!(file_content.starts_with('{'));
191 assert!(file_content.ends_with('}'));
192 }
193
194 #[test]
195 fn test_mina_encrypted_key_with_provided_secret_key() {
196 let temp_dir = TempDir::new().unwrap();
197 let file_path = temp_dir.path().join("test_key_provided.json");
198 let file_path_str = file_path.to_str().unwrap().to_string();
199
200 let secret_key = AccountSecretKey::rand();
201 let expected_public_key = secret_key.public_key();
202
203 let cmd = MinaEncryptedKey {
204 secret_key: Some(secret_key),
205 password: "test_password".to_string(),
206 file: file_path_str.clone(),
207 };
208
209 let result = cmd.run();
210 assert!(result.is_ok());
211
212 assert!(file_path.exists());
214
215 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "test_password");
217 assert!(loaded_key.is_ok());
218 let loaded_key = loaded_key.unwrap();
219 assert_eq!(loaded_key.public_key(), expected_public_key);
220 }
221
222 #[test]
223 fn test_mina_encrypted_key_with_empty_password() {
224 let temp_dir = TempDir::new().unwrap();
225 let file_path = temp_dir.path().join("test_key_no_pass.json");
226 let file_path_str = file_path.to_str().unwrap().to_string();
227
228 let cmd = MinaEncryptedKey {
229 secret_key: None,
230 password: "".to_string(),
231 file: file_path_str.clone(),
232 };
233
234 let result = cmd.run();
235 assert!(result.is_ok());
236
237 assert!(file_path.exists());
239
240 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "");
242 assert!(loaded_key.is_ok());
243 }
244
245 #[test]
246 fn test_mina_encrypted_key_wrong_password_fails() {
247 let temp_dir = TempDir::new().unwrap();
248 let file_path = temp_dir.path().join("test_key_wrong_pass.json");
249 let file_path_str = file_path.to_str().unwrap().to_string();
250
251 let cmd = MinaEncryptedKey {
252 secret_key: None,
253 password: "correct_password".to_string(),
254 file: file_path_str.clone(),
255 };
256
257 let result = cmd.run();
258 assert!(result.is_ok());
259
260 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "wrong_password");
262 assert!(loaded_key.is_err());
263 }
264
265 #[test]
266 fn test_mina_encrypted_key_invalid_file_path_fails() {
267 let cmd = MinaEncryptedKey {
268 secret_key: None,
269 password: "test_password".to_string(),
270 file: "/invalid/path/that/does/not/exist/key.json".to_string(),
271 };
272
273 let result = cmd.run();
274 assert!(result.is_err());
275 }
276
277 #[test]
278 fn test_mina_encrypted_key_roundtrip_compatibility() {
279 let temp_dir = TempDir::new().unwrap();
280 let file_path = temp_dir.path().join("test_roundtrip.json");
281 let file_path_str = file_path.to_str().unwrap().to_string();
282
283 let original_secret_key = AccountSecretKey::rand();
285 let original_public_key = original_secret_key.public_key();
286 let password = "roundtrip_test_password";
287
288 let cmd = MinaEncryptedKey {
289 secret_key: Some(original_secret_key.clone()),
290 password: password.to_string(),
291 file: file_path_str.clone(),
292 };
293
294 let result = cmd.run();
295 assert!(result.is_ok());
296
297 let loaded_secret_key = AccountSecretKey::from_encrypted_file(&file_path_str, password);
299 assert!(loaded_secret_key.is_ok());
300 let loaded_secret_key = loaded_secret_key.unwrap();
301 let loaded_public_key = loaded_secret_key.public_key();
302
303 assert_eq!(original_public_key, loaded_public_key);
305 assert_eq!(
306 original_secret_key.to_string(),
307 loaded_secret_key.to_string()
308 );
309 }
310
311 #[test]
312 fn test_mina_key_pair_generates_random_key() {
313 let cmd = MinaKeyPair::default();
314
315 let result = cmd.run();
316 assert!(result.is_ok());
317 }
318
319 #[test]
320 fn test_mina_key_pair_with_provided_secret_key() {
321 let secret_key = AccountSecretKey::rand();
322 let cmd = MinaKeyPair {
323 secret_key: Some(secret_key),
324 };
325
326 let result = cmd.run();
327 assert!(result.is_ok());
328 }
329
330 #[test]
331 fn test_p2p_key_pair_generates_random_key() {
332 let cmd = P2PKeyPair {
333 p2p_secret_key: None,
334 };
335
336 let result = cmd.run();
337 assert!(result.is_ok());
338 }
339
340 #[test]
341 fn test_p2p_key_pair_with_provided_secret_key() {
342 let secret_key = SecretKey::rand();
343 let cmd = P2PKeyPair {
344 p2p_secret_key: Some(secret_key),
345 };
346
347 let result = cmd.run();
348 assert!(result.is_ok());
349 }
350}