1use libp2p_identity::PeerId;
2use 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 = "OPENMINA_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, clap::Args)]
50pub struct MinaKeyPair {
51 #[arg(long, short = 's', env = "OPENMINA_SEC_KEY")]
52 secret_key: Option<AccountSecretKey>,
53}
54
55impl MinaKeyPair {
56 pub fn run(self) -> anyhow::Result<()> {
57 let secret_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand);
58 let public_key = secret_key.public_key();
59 println!("secret key: {secret_key}");
60 println!("public key: {public_key}");
61
62 Ok(())
63 }
64}
65
66#[derive(Debug, Clone, clap::Args)]
93pub struct MinaEncryptedKey {
94 #[arg(long, short = 's', env = "OPENMINA_ENC_KEY")]
97 secret_key: Option<AccountSecretKey>,
98
99 #[arg(env = "MINA_PRIVKEY_PASS", default_value = "")]
102 password: String,
103
104 #[arg(long, short = 'f', default_value = "mina_encrypted_key.json")]
106 file: String,
107}
108
109impl MinaEncryptedKey {
110 pub fn run(self) -> anyhow::Result<()> {
127 let secret_key = self.secret_key.unwrap_or_else(AccountSecretKey::rand);
128 let public_key = secret_key.public_key();
129
130 let public_key_file = format!("{}.pub", self.file);
132
133 if File::open(&public_key_file).is_ok() {
134 return Err(anyhow::anyhow!(
135 "Public key file '{}' already exists. Please choose a different file name.",
136 public_key_file
137 ));
138 }
139
140 secret_key
141 .to_encrypted_file(&self.file, &self.password)
142 .map_err(|e| {
143 anyhow::anyhow!("Failed to encrypt key: {} into path '{}'", e, self.file,)
144 })?;
145 let mut public_key_file = File::create(public_key_file)
147 .map_err(|e| anyhow::anyhow!("Failed to create public key file: {}", e))?;
148 public_key_file
149 .write_all(public_key.to_string().as_bytes())
150 .map_err(|e| anyhow::anyhow!("Failed to write public key: {}", e))?;
151
152 println!("secret key: {secret_key}");
153 println!("public key: {public_key}");
154 Ok(())
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use std::fs;
162 use tempfile::TempDir;
163
164 #[test]
165 fn test_mina_encrypted_key_generates_random_key() {
166 let temp_dir = TempDir::new().unwrap();
167 let file_path = temp_dir.path().join("test_key.json");
168 let file_path_str = file_path.to_str().unwrap().to_string();
169
170 let cmd = MinaEncryptedKey {
171 secret_key: None,
172 password: "test_password".to_string(),
173 file: file_path_str.clone(),
174 };
175
176 let result = cmd.run();
177 assert!(result.is_ok());
178
179 assert!(file_path.exists());
181
182 let file_content = fs::read_to_string(&file_path).unwrap();
184 assert!(file_content.starts_with('{'));
185 assert!(file_content.ends_with('}'));
186 }
187
188 #[test]
189 fn test_mina_encrypted_key_with_provided_secret_key() {
190 let temp_dir = TempDir::new().unwrap();
191 let file_path = temp_dir.path().join("test_key_provided.json");
192 let file_path_str = file_path.to_str().unwrap().to_string();
193
194 let secret_key = AccountSecretKey::rand();
195 let expected_public_key = secret_key.public_key();
196
197 let cmd = MinaEncryptedKey {
198 secret_key: Some(secret_key),
199 password: "test_password".to_string(),
200 file: file_path_str.clone(),
201 };
202
203 let result = cmd.run();
204 assert!(result.is_ok());
205
206 assert!(file_path.exists());
208
209 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "test_password");
211 assert!(loaded_key.is_ok());
212 let loaded_key = loaded_key.unwrap();
213 assert_eq!(loaded_key.public_key(), expected_public_key);
214 }
215
216 #[test]
217 fn test_mina_encrypted_key_with_empty_password() {
218 let temp_dir = TempDir::new().unwrap();
219 let file_path = temp_dir.path().join("test_key_no_pass.json");
220 let file_path_str = file_path.to_str().unwrap().to_string();
221
222 let cmd = MinaEncryptedKey {
223 secret_key: None,
224 password: "".to_string(),
225 file: file_path_str.clone(),
226 };
227
228 let result = cmd.run();
229 assert!(result.is_ok());
230
231 assert!(file_path.exists());
233
234 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "");
236 assert!(loaded_key.is_ok());
237 }
238
239 #[test]
240 fn test_mina_encrypted_key_wrong_password_fails() {
241 let temp_dir = TempDir::new().unwrap();
242 let file_path = temp_dir.path().join("test_key_wrong_pass.json");
243 let file_path_str = file_path.to_str().unwrap().to_string();
244
245 let cmd = MinaEncryptedKey {
246 secret_key: None,
247 password: "correct_password".to_string(),
248 file: file_path_str.clone(),
249 };
250
251 let result = cmd.run();
252 assert!(result.is_ok());
253
254 let loaded_key = AccountSecretKey::from_encrypted_file(&file_path_str, "wrong_password");
256 assert!(loaded_key.is_err());
257 }
258
259 #[test]
260 fn test_mina_encrypted_key_invalid_file_path_fails() {
261 let cmd = MinaEncryptedKey {
262 secret_key: None,
263 password: "test_password".to_string(),
264 file: "/invalid/path/that/does/not/exist/key.json".to_string(),
265 };
266
267 let result = cmd.run();
268 assert!(result.is_err());
269 }
270
271 #[test]
272 fn test_mina_encrypted_key_roundtrip_compatibility() {
273 let temp_dir = TempDir::new().unwrap();
274 let file_path = temp_dir.path().join("test_roundtrip.json");
275 let file_path_str = file_path.to_str().unwrap().to_string();
276
277 let original_secret_key = AccountSecretKey::rand();
279 let original_public_key = original_secret_key.public_key();
280 let password = "roundtrip_test_password";
281
282 let cmd = MinaEncryptedKey {
283 secret_key: Some(original_secret_key.clone()),
284 password: password.to_string(),
285 file: file_path_str.clone(),
286 };
287
288 let result = cmd.run();
289 assert!(result.is_ok());
290
291 let loaded_secret_key = AccountSecretKey::from_encrypted_file(&file_path_str, password);
293 assert!(loaded_secret_key.is_ok());
294 let loaded_secret_key = loaded_secret_key.unwrap();
295 let loaded_public_key = loaded_secret_key.public_key();
296
297 assert_eq!(original_public_key, loaded_public_key);
299 assert_eq!(
300 original_secret_key.to_string(),
301 loaded_secret_key.to_string()
302 );
303 }
304
305 #[test]
306 fn test_mina_key_pair_generates_random_key() {
307 let cmd = MinaKeyPair { secret_key: None };
308
309 let result = cmd.run();
310 assert!(result.is_ok());
311 }
312
313 #[test]
314 fn test_mina_key_pair_with_provided_secret_key() {
315 let secret_key = AccountSecretKey::rand();
316 let cmd = MinaKeyPair {
317 secret_key: Some(secret_key),
318 };
319
320 let result = cmd.run();
321 assert!(result.is_ok());
322 }
323
324 #[test]
325 fn test_p2p_key_pair_generates_random_key() {
326 let cmd = P2PKeyPair {
327 p2p_secret_key: None,
328 };
329
330 let result = cmd.run();
331 assert!(result.is_ok());
332 }
333
334 #[test]
335 fn test_p2p_key_pair_with_provided_secret_key() {
336 let secret_key = SecretKey::rand();
337 let cmd = P2PKeyPair {
338 p2p_secret_key: Some(secret_key),
339 };
340
341 let result = cmd.run();
342 assert!(result.is_ok());
343 }
344}