1pub mod keystore;
9pub mod password;
10
11use crate::error::{Result, WalletError};
12pub use keystore::{Keystore, QuantumKeyPair, WalletData};
13use qp_rusty_crystals_hdwallet::{generate_mnemonic, HDLattice};
14use rand::{rng, RngCore};
15use serde::{Deserialize, Serialize};
16use sp_core::crypto::Ss58Codec;
17use sp_runtime::traits::IdentifyAccount;
18
19pub const DEFAULT_DERIVATION_PATH: &str = "m/44'/189189'/0'/0/0";
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct WalletInfo {
25 pub name: String,
26 pub address: String,
27 pub created_at: chrono::DateTime<chrono::Utc>,
28 pub key_type: String,
29 pub derivation_path: String,
30}
31
32pub struct WalletManager {
34 wallets_dir: std::path::PathBuf,
35}
36
37impl WalletManager {
38 pub fn new() -> Result<Self> {
40 let wallets_dir = dirs::home_dir()
41 .ok_or(WalletError::KeyGeneration)?
42 .join(".quantus")
43 .join("wallets");
44
45 std::fs::create_dir_all(&wallets_dir)?;
47
48 Ok(Self { wallets_dir })
49 }
50
51 pub async fn create_wallet(&self, name: &str, password: Option<&str>) -> Result<WalletInfo> {
53 self.create_wallet_with_derivation_path(name, password, DEFAULT_DERIVATION_PATH)
54 .await
55 }
56
57 pub async fn create_wallet_with_derivation_path(
59 &self,
60 name: &str,
61 password: Option<&str>,
62 derivation_path: &str,
63 ) -> Result<WalletInfo> {
64 let keystore = Keystore::new(&self.wallets_dir);
66 if keystore.load_wallet(name)?.is_some() {
67 return Err(WalletError::AlreadyExists.into());
68 }
69
70 let mut seed = [0u8; 32];
72 rng().fill_bytes(&mut seed);
73 let mnemonic = generate_mnemonic(24, seed).map_err(|_| WalletError::KeyGeneration)?;
74 let lattice =
75 HDLattice::from_mnemonic(&mnemonic, None).expect("Failed to generate lattice");
76 let dilithium_keypair = lattice
77 .generate_derived_keys(derivation_path)
78 .map_err(|_| WalletError::KeyGeneration)?;
79 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
80
81 let mut metadata = std::collections::HashMap::new();
83 metadata.insert("version".to_string(), "1.0.0".to_string());
84 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
85 metadata.insert("derivation_path".to_string(), derivation_path.to_string());
86
87 let address = quantum_keypair.to_account_id_ss58check();
89
90 let wallet_data = WalletData {
91 name: name.to_string(),
92 keypair: quantum_keypair,
93 mnemonic: Some(mnemonic.clone()),
94 derivation_path: derivation_path.to_string(),
95 metadata,
96 };
97
98 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
101 keystore.save_wallet(&encrypted_wallet)?;
102
103 Ok(WalletInfo {
104 name: name.to_string(),
105 address,
106 created_at: encrypted_wallet.created_at,
107 key_type: "Dilithium ML-DSA-87".to_string(),
108 derivation_path: derivation_path.to_string(),
109 })
110 }
111
112 pub async fn create_developer_wallet(&self, name: &str) -> Result<WalletInfo> {
114 let keystore = Keystore::new(&self.wallets_dir);
116
117 let resonance_pair = match name {
119 "crystal_alice" => qp_dilithium_crypto::crystal_alice(),
120 "crystal_bob" => qp_dilithium_crypto::dilithium_bob(),
121 "crystal_charlie" => qp_dilithium_crypto::crystal_charlie(),
122 _ => return Err(WalletError::KeyGeneration.into()),
123 };
124
125 let quantum_keypair = QuantumKeyPair::from_resonance_pair(&resonance_pair);
126
127 println!("🔑 Resonance pair: {:?}", resonance_pair.public().into_account().to_ss58check());
128 println!("🔑 Quantum keypair: {:?}", quantum_keypair.to_account_id_ss58check());
129
130 let mut metadata = std::collections::HashMap::new();
132 metadata.insert("version".to_string(), "1.0.0".to_string());
133 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
134 metadata.insert("test_wallet".to_string(), "true".to_string());
135
136 let address = quantum_keypair.to_account_id_ss58check();
138
139 let wallet_data = WalletData {
140 name: name.to_string(),
141 keypair: quantum_keypair,
142 mnemonic: None, derivation_path: "m/".to_string(), metadata,
145 };
146
147 let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, "")?;
149 keystore.save_wallet(&encrypted_wallet)?;
150
151 Ok(WalletInfo {
152 name: name.to_string(),
153 address,
154 created_at: encrypted_wallet.created_at,
155 key_type: "Dilithium ML-DSA-87".to_string(),
156 derivation_path: "m/".to_string(),
157 })
158 }
159
160 pub fn export_mnemonic(&self, name: &str, password: Option<&str>) -> Result<String> {
162 let final_password = password::get_wallet_password(name, password.map(String::from), None)?;
163
164 let wallet_data = self.load_wallet(name, &final_password)?;
165
166 wallet_data.mnemonic.ok_or_else(|| WalletError::MnemonicNotAvailable.into())
167 }
168
169 pub fn list_wallets(&self) -> Result<Vec<WalletInfo>> {
171 let keystore = Keystore::new(&self.wallets_dir);
172 let wallet_names = keystore.list_wallets()?;
173
174 let mut wallets = Vec::new();
175 for name in wallet_names {
176 if let Some(encrypted_wallet) = keystore.load_wallet(&name)? {
177 let wallet_info = WalletInfo {
179 name: encrypted_wallet.name,
180 address: encrypted_wallet.address, created_at: encrypted_wallet.created_at,
182 key_type: "Dilithium ML-DSA-87".to_string(),
183 derivation_path: "[Encrypted]".to_string(), };
185 wallets.push(wallet_info);
186 }
187 }
188
189 wallets.sort_by(|a, b| b.created_at.cmp(&a.created_at));
191 Ok(wallets)
192 }
193
194 pub async fn import_wallet(
196 &self,
197 name: &str,
198 mnemonic: &str,
199 password: Option<&str>,
200 ) -> Result<WalletInfo> {
201 self.import_wallet_with_derivation_path(name, mnemonic, password, DEFAULT_DERIVATION_PATH)
202 .await
203 }
204
205 pub async fn create_wallet_no_derivation(
207 &self,
208 name: &str,
209 password: Option<&str>,
210 ) -> Result<WalletInfo> {
211 let keystore = Keystore::new(&self.wallets_dir);
213 if keystore.load_wallet(name)?.is_some() {
214 return Err(WalletError::AlreadyExists.into());
215 }
216
217 let mut seed = [0u8; 32];
219 rng().fill_bytes(&mut seed);
220 let mnemonic = generate_mnemonic(24, seed).map_err(|_| WalletError::KeyGeneration)?;
221 let lattice =
222 HDLattice::from_mnemonic(&mnemonic, None).map_err(|_| WalletError::KeyGeneration)?;
223 let dilithium_keypair = lattice.generate_keys();
224 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
225
226 let mut metadata = std::collections::HashMap::new();
228 metadata.insert("version".to_string(), "1.0.0".to_string());
229 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
230 metadata.insert("no_derivation".to_string(), "true".to_string());
231
232 let address = quantum_keypair.to_account_id_ss58check();
234
235 let wallet_data = WalletData {
236 name: name.to_string(),
237 keypair: quantum_keypair,
238 mnemonic: Some(mnemonic),
239 derivation_path: "master".to_string(),
240 metadata,
241 };
242
243 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
246 keystore.save_wallet(&encrypted_wallet)?;
247
248 Ok(WalletInfo {
249 name: name.to_string(),
250 address,
251 created_at: chrono::Utc::now(),
252 key_type: "Dilithium ML-DSA-87".to_string(),
253 derivation_path: "master".to_string(),
254 })
255 }
256
257 pub async fn import_wallet_no_derivation(
259 &self,
260 name: &str,
261 mnemonic: &str,
262 password: Option<&str>,
263 ) -> Result<WalletInfo> {
264 let keystore = Keystore::new(&self.wallets_dir);
266 if keystore.load_wallet(name)?.is_some() {
267 return Err(WalletError::AlreadyExists.into());
268 }
269
270 let lattice =
272 HDLattice::from_mnemonic(mnemonic, None).map_err(|_| WalletError::InvalidMnemonic)?;
273 let dilithium_keypair = lattice.generate_keys();
274 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
275
276 let mut metadata = std::collections::HashMap::new();
278 metadata.insert("version".to_string(), "1.0.0".to_string());
279 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
280 metadata.insert("imported".to_string(), "true".to_string());
281 metadata.insert("no_derivation".to_string(), "true".to_string());
282
283 let address = quantum_keypair.to_account_id_ss58check();
285
286 let wallet_data = WalletData {
287 name: name.to_string(),
288 keypair: quantum_keypair,
289 mnemonic: Some(mnemonic.to_string()),
290 derivation_path: "master".to_string(),
291 metadata,
292 };
293
294 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
297 keystore.save_wallet(&encrypted_wallet)?;
298
299 Ok(WalletInfo {
300 name: name.to_string(),
301 address,
302 created_at: chrono::Utc::now(),
303 key_type: "Dilithium ML-DSA-87".to_string(),
304 derivation_path: "master".to_string(),
305 })
306 }
307
308 pub async fn import_wallet_with_derivation_path(
310 &self,
311 name: &str,
312 mnemonic: &str,
313 password: Option<&str>,
314 derivation_path: &str,
315 ) -> Result<WalletInfo> {
316 let keystore = Keystore::new(&self.wallets_dir);
318 if keystore.load_wallet(name)?.is_some() {
319 return Err(WalletError::AlreadyExists.into());
320 }
321
322 let lattice =
324 HDLattice::from_mnemonic(mnemonic, None).map_err(|_| WalletError::InvalidMnemonic)?;
325 let dilithium_keypair = lattice
326 .generate_derived_keys(derivation_path)
327 .map_err(|_| WalletError::KeyGeneration)?;
328 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
329
330 let mut metadata = std::collections::HashMap::new();
332 metadata.insert("version".to_string(), "1.0.0".to_string());
333 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
334 metadata.insert("imported".to_string(), "true".to_string());
335 metadata.insert("derivation_path".to_string(), derivation_path.to_string());
336
337 let address = quantum_keypair.to_account_id_ss58check();
339
340 let wallet_data = WalletData {
341 name: name.to_string(),
342 keypair: quantum_keypair,
343 mnemonic: Some(mnemonic.to_string()),
344 derivation_path: derivation_path.to_string(),
345 metadata,
346 };
347
348 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
351 keystore.save_wallet(&encrypted_wallet)?;
352
353 Ok(WalletInfo {
354 name: name.to_string(),
355 address,
356 created_at: encrypted_wallet.created_at,
357 key_type: "Dilithium ML-DSA-87".to_string(),
358 derivation_path: derivation_path.to_string(),
359 })
360 }
361
362 pub async fn create_wallet_from_seed(
364 &self,
365 name: &str,
366 seed_hex: &str,
367 password: Option<&str>,
368 ) -> Result<WalletInfo> {
369 let keystore = Keystore::new(&self.wallets_dir);
371 if keystore.load_wallet(name)?.is_some() {
372 return Err(WalletError::AlreadyExists.into());
373 }
374
375 if seed_hex.len() != 64 {
377 return Err(WalletError::InvalidMnemonic.into()); }
379
380 let seed_bytes = hex::decode(seed_hex).map_err(|_| WalletError::InvalidMnemonic)?;
382 if seed_bytes.len() != 32 {
383 return Err(WalletError::InvalidMnemonic.into());
384 }
385
386 let seed_array: [u8; 32] =
388 seed_bytes.try_into().map_err(|_| WalletError::InvalidMnemonic)?;
389
390 println!("Debug: seed_array length: {}", seed_array.len());
391 println!("Debug: seed_hex: {}", seed_hex);
392 println!("Debug: calling DilithiumPair::from_seed");
393
394 let dilithium_pair = qp_dilithium_crypto::types::DilithiumPair::from_seed(&seed_array)
395 .map_err(|e| {
396 println!("Debug: DilithiumPair::from_seed failed with error: {:?}", e);
397 WalletError::InvalidMnemonic
398 })?;
399
400 println!("Debug: DilithiumPair created successfully");
401
402 let quantum_keypair = QuantumKeyPair::from_resonance_pair(&dilithium_pair);
404
405 let mut metadata = std::collections::HashMap::new();
407 metadata.insert("version".to_string(), "1.0.0".to_string());
408 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
409 metadata.insert("from_seed".to_string(), "true".to_string());
410
411 let address = quantum_keypair.to_account_id_ss58check();
413
414 let wallet_data = WalletData {
415 name: name.to_string(),
416 keypair: quantum_keypair,
417 mnemonic: None, derivation_path: "m/".to_string(), metadata,
420 };
421
422 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
425 keystore.save_wallet(&encrypted_wallet)?;
426
427 Ok(WalletInfo {
428 name: name.to_string(),
429 address,
430 created_at: encrypted_wallet.created_at,
431 key_type: "Dilithium ML-DSA-87".to_string(),
432 derivation_path: "m/".to_string(),
433 })
434 }
435
436 pub fn get_wallet(&self, name: &str, password: Option<&str>) -> Result<Option<WalletInfo>> {
438 let keystore = Keystore::new(&self.wallets_dir);
439
440 if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
441 if let Some(pwd) = password {
442 match keystore.decrypt_wallet_data(&encrypted_wallet, pwd) {
444 Ok(wallet_data) => {
445 let address = wallet_data.keypair.to_account_id_ss58check();
446 Ok(Some(WalletInfo {
447 name: wallet_data.name,
448 address,
449 created_at: encrypted_wallet.created_at,
450 key_type: "Dilithium ML-DSA-87".to_string(),
451 derivation_path: wallet_data.derivation_path,
452 }))
453 },
454 Err(_) => {
455 Ok(Some(WalletInfo {
457 name: encrypted_wallet.name,
458 address: "[Wrong password]".to_string(),
459 created_at: encrypted_wallet.created_at,
460 key_type: "Dilithium ML-DSA-87".to_string(),
461 derivation_path: "[Wrong password]".to_string(),
462 }))
463 },
464 }
465 } else {
466 Ok(Some(WalletInfo {
468 name: encrypted_wallet.name,
469 address: encrypted_wallet.address, created_at: encrypted_wallet.created_at,
471 key_type: "Dilithium ML-DSA-87".to_string(),
472 derivation_path: "[Encrypted]".to_string(), }))
474 }
475 } else {
476 Ok(None)
477 }
478 }
479
480 pub fn load_wallet(&self, name: &str, password: &str) -> Result<WalletData> {
482 let keystore = Keystore::new(&self.wallets_dir);
483
484 let encrypted_wallet = keystore.load_wallet(name)?.ok_or(WalletError::NotFound)?;
486
487 let wallet_data = keystore.decrypt_wallet_data(&encrypted_wallet, password)?;
489
490 Ok(wallet_data)
491 }
492
493 pub fn delete_wallet(&self, name: &str) -> Result<bool> {
495 let keystore = Keystore::new(&self.wallets_dir);
496 keystore.delete_wallet(name)
497 }
498
499 pub fn find_wallet_address(&self, name: &str) -> Result<Option<String>> {
501 let keystore = Keystore::new(&self.wallets_dir);
502
503 if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
504 Ok(Some(encrypted_wallet.address))
506 } else {
507 Ok(None)
508 }
509 }
510}
511
512pub fn load_keypair_from_wallet(
513 wallet_name: &str,
514 password: Option<String>,
515 password_file: Option<String>,
516) -> Result<QuantumKeyPair> {
517 let wallet_manager = WalletManager::new()?;
518 let wallet_password = password::get_wallet_password(wallet_name, password, password_file)?;
519 let wallet_data = wallet_manager.load_wallet(wallet_name, &wallet_password)?;
520 let keypair = wallet_data.keypair;
521 Ok(keypair)
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527 use std::fs;
528 use tempfile::TempDir;
529
530 async fn create_test_wallet_manager() -> (WalletManager, TempDir) {
531 let temp_dir = TempDir::new().expect("Failed to create temp directory");
532 let wallets_dir = temp_dir.path().join("wallets");
533 fs::create_dir_all(&wallets_dir).expect("Failed to create wallets directory");
534
535 let wallet_manager = WalletManager { wallets_dir };
536
537 (wallet_manager, temp_dir)
538 }
539
540 #[tokio::test]
541 async fn test_wallet_creation() {
542 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
543
544 let wallet_info = wallet_manager
546 .create_wallet("test-wallet", Some("test-password"))
547 .await
548 .expect("Failed to create wallet");
549
550 assert_eq!(wallet_info.name, "test-wallet");
552 assert!(wallet_info.address.starts_with("qz")); assert_eq!(wallet_info.key_type, "Dilithium ML-DSA-87");
554 assert!(wallet_info.created_at <= chrono::Utc::now());
555 }
556
557 #[tokio::test]
558 async fn test_wallet_already_exists() {
559 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
560
561 wallet_manager
563 .create_wallet("duplicate-wallet", None)
564 .await
565 .expect("Failed to create first wallet");
566
567 let result = wallet_manager.create_wallet("duplicate-wallet", None).await;
569
570 assert!(result.is_err());
571 match result.unwrap_err() {
572 crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
573 _ => panic!("Expected AlreadyExists error"),
574 }
575 }
576
577 #[tokio::test]
578 async fn test_wallet_file_creation() {
579 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
580
581 let _ = wallet_manager
583 .create_wallet("file-test-wallet", Some("password123"))
584 .await
585 .expect("Failed to create wallet");
586
587 let wallet_file = wallet_manager.wallets_dir.join("file-test-wallet.json");
589 assert!(wallet_file.exists(), "Wallet file should exist");
590
591 let file_size = fs::metadata(&wallet_file).expect("Failed to get file metadata").len();
593 assert!(file_size > 0, "Wallet file should not be empty");
594 }
595
596 #[tokio::test]
597 async fn test_keystore_encryption_decryption() {
598 let temp_dir = TempDir::new().expect("Failed to create temp directory");
599 let keystore = keystore::Keystore::new(temp_dir.path());
600
601 let entropy = [1u8; 32]; let dilithium_keypair =
604 qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
605 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
606
607 let mut metadata = std::collections::HashMap::new();
608 metadata.insert("test_key".to_string(), "test_value".to_string());
609
610 let original_wallet_data = keystore::WalletData {
611 name: "test-wallet".to_string(),
612 keypair: quantum_keypair,
613 mnemonic: Some(
614 "test mnemonic phrase with twenty four words here for testing purposes only"
615 .to_string(),
616 ),
617 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
618 metadata,
619 };
620
621 let encrypted_wallet = keystore
623 .encrypt_wallet_data(&original_wallet_data, "test-password")
624 .expect("Failed to encrypt wallet data");
625
626 assert_eq!(encrypted_wallet.name, "test-wallet");
627 assert!(!encrypted_wallet.encrypted_data.is_empty());
628 assert!(!encrypted_wallet.argon2_salt.is_empty());
629 assert!(!encrypted_wallet.aes_nonce.is_empty());
630
631 let decrypted_wallet_data = keystore
633 .decrypt_wallet_data(&encrypted_wallet, "test-password")
634 .expect("Failed to decrypt wallet data");
635
636 assert_eq!(decrypted_wallet_data.name, original_wallet_data.name);
638 assert_eq!(decrypted_wallet_data.mnemonic, original_wallet_data.mnemonic);
639 assert_eq!(decrypted_wallet_data.metadata, original_wallet_data.metadata);
640 assert_eq!(
641 decrypted_wallet_data.keypair.public_key,
642 original_wallet_data.keypair.public_key
643 );
644 assert_eq!(
645 decrypted_wallet_data.keypair.private_key,
646 original_wallet_data.keypair.private_key
647 );
648 }
649
650 #[tokio::test]
651 async fn test_quantum_keypair_address_generation() {
652 let entropy = [2u8; 32]; let dilithium_keypair =
655 qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
656 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
657
658 let account_id = quantum_keypair.to_account_id_32();
660 let ss58_address = quantum_keypair.to_account_id_ss58check();
661
662 assert!(ss58_address.starts_with("qz"), "SS58 address should start with 5");
664 assert!(ss58_address.len() >= 47, "SS58 address should be at least 47 characters");
665
666 let converted_account_bytes = keystore::QuantumKeyPair::ss58_to_account_id(&ss58_address);
668 let account_bytes: &[u8] = account_id.as_ref();
669 assert_eq!(converted_account_bytes, account_bytes);
670 }
671
672 #[tokio::test]
673 async fn test_keystore_save_and_load() {
674 let temp_dir = TempDir::new().expect("Failed to create temp directory");
675 let keystore = keystore::Keystore::new(temp_dir.path());
676
677 let entropy = [3u8; 32]; let dilithium_keypair =
680 qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
681 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
682
683 let wallet_data = keystore::WalletData {
684 name: "save-load-test".to_string(),
685 keypair: quantum_keypair,
686 mnemonic: Some("save load test mnemonic phrase".to_string()),
687 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
688 metadata: std::collections::HashMap::new(),
689 };
690
691 let encrypted_wallet = keystore
692 .encrypt_wallet_data(&wallet_data, "save-load-password")
693 .expect("Failed to encrypt wallet");
694
695 keystore.save_wallet(&encrypted_wallet).expect("Failed to save wallet");
697
698 let loaded_wallet = keystore
700 .load_wallet("save-load-test")
701 .expect("Failed to load wallet")
702 .expect("Wallet should exist");
703
704 assert_eq!(loaded_wallet.name, encrypted_wallet.name);
706 assert_eq!(loaded_wallet.encrypted_data, encrypted_wallet.encrypted_data);
707 assert_eq!(loaded_wallet.argon2_salt, encrypted_wallet.argon2_salt);
708 assert_eq!(loaded_wallet.aes_nonce, encrypted_wallet.aes_nonce);
709
710 let non_existent = keystore
712 .load_wallet("non-existent-wallet")
713 .expect("Load should succeed but return None");
714 assert!(non_existent.is_none());
715 }
716
717 #[tokio::test]
718 async fn test_mnemonic_generation_and_key_derivation() {
719 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
720
721 let wallet1 = wallet_manager
723 .create_wallet("mnemonic-test-1", None)
724 .await
725 .expect("Failed to create wallet 1");
726
727 let wallet2 = wallet_manager
728 .create_wallet("mnemonic-test-2", None)
729 .await
730 .expect("Failed to create wallet 2");
731
732 assert_ne!(wallet1.address, wallet2.address);
734
735 assert!(wallet1.address.starts_with("qz"));
737 assert!(wallet2.address.starts_with("qz"));
738 }
739
740 #[tokio::test]
741 async fn test_wallet_import() {
742 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
743
744 let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art";
746
747 let imported_wallet = wallet_manager
749 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
750 .await
751 .expect("Failed to import wallet");
752
753 assert_eq!(imported_wallet.name, "imported-test-wallet");
755 assert!(imported_wallet.address.starts_with("qz"));
756 assert_eq!(imported_wallet.key_type, "Dilithium ML-DSA-87");
757
758 let imported_wallet2 = wallet_manager
760 .import_wallet("imported-test-wallet-2", test_mnemonic, None)
761 .await
762 .expect("Failed to import wallet again");
763
764 assert_eq!(imported_wallet.address, imported_wallet2.address);
765 }
766
767 #[tokio::test]
768 async fn test_known_values() {
769 sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(189));
770
771 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
772 let test_mnemonic = "orchard answer curve patient visual flower maze noise retreat penalty cage small earth domain scan pitch bottom crunch theme club client swap slice raven";
773 let expected_address = "qzjtZjisjHH71BBCzoPV2taXyanMqzXQSZsi9kVpDBRkEGL24";
774
775 let imported_wallet = wallet_manager
776 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
777 .await
778 .expect("Failed to import wallet");
779
780 assert_eq!(imported_wallet.address, expected_address);
781 }
782
783 #[tokio::test]
784 async fn test_wallet_import_invalid_mnemonic() {
785 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
786
787 let invalid_mnemonic = "invalid mnemonic phrase that should not work";
789
790 let result = wallet_manager.import_wallet("invalid-wallet", invalid_mnemonic, None).await;
791
792 assert!(result.is_err());
793 match result.unwrap_err() {
794 crate::error::QuantusError::Wallet(WalletError::InvalidMnemonic) => {},
795 _ => panic!("Expected InvalidMnemonic error"),
796 }
797 }
798
799 #[tokio::test]
800 async fn test_wallet_import_already_exists() {
801 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
802
803 let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art";
804
805 wallet_manager
807 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
808 .await
809 .expect("Failed to import first wallet");
810
811 let result = wallet_manager
813 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
814 .await;
815
816 assert!(result.is_err());
817 match result.unwrap_err() {
818 crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
819 _ => panic!("Expected AlreadyExists error"),
820 }
821 }
822
823 #[tokio::test]
824 async fn test_list_wallets() {
825 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
826
827 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
829 assert_eq!(wallets.len(), 0);
830
831 wallet_manager
833 .create_wallet("wallet-1", Some("password1"))
834 .await
835 .expect("Failed to create wallet 1");
836
837 wallet_manager
838 .create_wallet("wallet-2", None)
839 .await
840 .expect("Failed to create wallet 2");
841
842 let test_mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon art";
843 wallet_manager
844 .import_wallet("imported-wallet", test_mnemonic, Some("password3"))
845 .await
846 .expect("Failed to import wallet");
847
848 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
850
851 assert_eq!(wallets.len(), 3);
852
853 let wallet_names: Vec<&String> = wallets.iter().map(|w| &w.name).collect();
855 assert!(wallet_names.contains(&&"wallet-1".to_string()));
856 assert!(wallet_names.contains(&&"wallet-2".to_string()));
857 assert!(wallet_names.contains(&&"imported-wallet".to_string()));
858
859 for wallet in &wallets {
861 assert!(wallet.address.starts_with("qz")); assert_eq!(wallet.key_type, "Dilithium ML-DSA-87");
863 }
864
865 assert!(wallets[0].created_at >= wallets[1].created_at);
867 assert!(wallets[1].created_at >= wallets[2].created_at);
868 }
869
870 #[tokio::test]
871 async fn test_get_wallet() {
872 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
873
874 let created_wallet = wallet_manager
876 .create_wallet("test-get-wallet", Some("test-password"))
877 .await
878 .expect("Failed to create wallet");
879
880 let wallet_info = wallet_manager
882 .get_wallet("test-get-wallet", None)
883 .expect("Failed to get wallet")
884 .expect("Wallet should exist");
885
886 assert_eq!(wallet_info.name, "test-get-wallet");
887 assert_eq!(wallet_info.address, created_wallet.address); let wallet_info = wallet_manager
892 .get_wallet("test-get-wallet", Some("wrong-password"))
893 .expect("Failed to get wallet")
894 .expect("Wallet should exist");
895
896 assert_eq!(wallet_info.name, "test-get-wallet");
897 assert_eq!(wallet_info.address, "[Wrong password]");
899
900 let wallet_info = wallet_manager
902 .get_wallet("test-get-wallet", Some("test-password"))
903 .expect("Failed to get wallet")
904 .expect("Wallet should exist");
905
906 assert_eq!(wallet_info.name, "test-get-wallet");
907 assert_eq!(wallet_info.address, created_wallet.address);
908 assert!(wallet_info.address.starts_with("qz"));
909
910 let result = wallet_manager
912 .get_wallet("non-existent-wallet", None)
913 .expect("Should not error on non-existent wallet");
914
915 assert!(result.is_none());
916 }
917}