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 = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
604 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
605
606 let mut metadata = std::collections::HashMap::new();
607 metadata.insert("test_key".to_string(), "test_value".to_string());
608
609 let original_wallet_data = keystore::WalletData {
610 name: "test-wallet".to_string(),
611 keypair: quantum_keypair,
612 mnemonic: Some(
613 "test mnemonic phrase with twenty four words here for testing purposes only"
614 .to_string(),
615 ),
616 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
617 metadata,
618 };
619
620 let encrypted_wallet = keystore
622 .encrypt_wallet_data(&original_wallet_data, "test-password")
623 .expect("Failed to encrypt wallet data");
624
625 assert_eq!(encrypted_wallet.name, "test-wallet");
626 assert!(!encrypted_wallet.encrypted_data.is_empty());
627 assert!(!encrypted_wallet.argon2_salt.is_empty());
628 assert!(!encrypted_wallet.aes_nonce.is_empty());
629
630 let decrypted_wallet_data = keystore
632 .decrypt_wallet_data(&encrypted_wallet, "test-password")
633 .expect("Failed to decrypt wallet data");
634
635 assert_eq!(decrypted_wallet_data.name, original_wallet_data.name);
637 assert_eq!(decrypted_wallet_data.mnemonic, original_wallet_data.mnemonic);
638 assert_eq!(decrypted_wallet_data.metadata, original_wallet_data.metadata);
639 assert_eq!(
640 decrypted_wallet_data.keypair.public_key,
641 original_wallet_data.keypair.public_key
642 );
643 assert_eq!(
644 decrypted_wallet_data.keypair.private_key,
645 original_wallet_data.keypair.private_key
646 );
647 }
648
649 #[tokio::test]
650 async fn test_quantum_keypair_address_generation() {
651 let entropy = [2u8; 32]; let dilithium_keypair = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
654 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
655
656 let account_id = quantum_keypair.to_account_id_32();
658 let ss58_address = quantum_keypair.to_account_id_ss58check();
659
660 assert!(ss58_address.starts_with("qz"), "SS58 address should start with 5");
662 assert!(ss58_address.len() >= 47, "SS58 address should be at least 47 characters");
663
664 let converted_account_bytes = keystore::QuantumKeyPair::ss58_to_account_id(&ss58_address);
666 let account_bytes: &[u8] = account_id.as_ref();
667 assert_eq!(converted_account_bytes, account_bytes);
668 }
669
670 #[tokio::test]
671 async fn test_keystore_save_and_load() {
672 let temp_dir = TempDir::new().expect("Failed to create temp directory");
673 let keystore = keystore::Keystore::new(temp_dir.path());
674
675 let entropy = [3u8; 32]; let dilithium_keypair = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
678 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
679
680 let wallet_data = keystore::WalletData {
681 name: "save-load-test".to_string(),
682 keypair: quantum_keypair,
683 mnemonic: Some("save load test mnemonic phrase".to_string()),
684 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
685 metadata: std::collections::HashMap::new(),
686 };
687
688 let encrypted_wallet = keystore
689 .encrypt_wallet_data(&wallet_data, "save-load-password")
690 .expect("Failed to encrypt wallet");
691
692 keystore.save_wallet(&encrypted_wallet).expect("Failed to save wallet");
694
695 let loaded_wallet = keystore
697 .load_wallet("save-load-test")
698 .expect("Failed to load wallet")
699 .expect("Wallet should exist");
700
701 assert_eq!(loaded_wallet.name, encrypted_wallet.name);
703 assert_eq!(loaded_wallet.encrypted_data, encrypted_wallet.encrypted_data);
704 assert_eq!(loaded_wallet.argon2_salt, encrypted_wallet.argon2_salt);
705 assert_eq!(loaded_wallet.aes_nonce, encrypted_wallet.aes_nonce);
706
707 let non_existent = keystore
709 .load_wallet("non-existent-wallet")
710 .expect("Load should succeed but return None");
711 assert!(non_existent.is_none());
712 }
713
714 #[tokio::test]
715 async fn test_mnemonic_generation_and_key_derivation() {
716 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
717
718 let wallet1 = wallet_manager
720 .create_wallet("mnemonic-test-1", None)
721 .await
722 .expect("Failed to create wallet 1");
723
724 let wallet2 = wallet_manager
725 .create_wallet("mnemonic-test-2", None)
726 .await
727 .expect("Failed to create wallet 2");
728
729 assert_ne!(wallet1.address, wallet2.address);
731
732 assert!(wallet1.address.starts_with("qz"));
734 assert!(wallet2.address.starts_with("qz"));
735 }
736
737 #[tokio::test]
738 async fn test_wallet_import() {
739 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
740
741 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";
743
744 let imported_wallet = wallet_manager
746 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
747 .await
748 .expect("Failed to import wallet");
749
750 assert_eq!(imported_wallet.name, "imported-test-wallet");
752 assert!(imported_wallet.address.starts_with("qz"));
753 assert_eq!(imported_wallet.key_type, "Dilithium ML-DSA-87");
754
755 let imported_wallet2 = wallet_manager
757 .import_wallet("imported-test-wallet-2", test_mnemonic, None)
758 .await
759 .expect("Failed to import wallet again");
760
761 assert_eq!(imported_wallet.address, imported_wallet2.address);
762 }
763
764 #[tokio::test]
765 async fn test_known_values() {
766 sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(189));
767
768 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
769 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";
770 let expected_address = "qznMJss7Ls1SWBhvvL2CSHVbgTxEfnL9GgpvMTq5CWMEwfCoe"; let expected_address_no_derive = "qznBvupPsA9T8VJDuTDokKPiNUe88zMMUtHGA1AsGc8fXKSSA";
772
773 let imported_wallet = wallet_manager
774 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
775 .await
776 .expect("Failed to import wallet");
777
778 let imported_wallet_no_derive = wallet_manager
779 .import_wallet_no_derivation(
780 "imported-test-wallet_no_derive",
781 test_mnemonic,
782 Some("import-password"),
783 )
784 .await
785 .expect("Failed to import wallet");
786
787 assert_eq!(imported_wallet.address, expected_address, "address at index 0 is wrong");
788 assert_eq!(
789 imported_wallet_no_derive.address, expected_address_no_derive,
790 "no-derivation address is wrong"
791 );
792 }
793
794 #[tokio::test]
795 async fn test_wallet_import_invalid_mnemonic() {
796 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
797
798 let invalid_mnemonic = "invalid mnemonic phrase that should not work";
800
801 let result = wallet_manager.import_wallet("invalid-wallet", invalid_mnemonic, None).await;
802
803 assert!(result.is_err());
804 match result.unwrap_err() {
805 crate::error::QuantusError::Wallet(WalletError::InvalidMnemonic) => {},
806 _ => panic!("Expected InvalidMnemonic error"),
807 }
808 }
809
810 #[tokio::test]
811 async fn test_wallet_import_already_exists() {
812 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
813
814 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";
815
816 wallet_manager
818 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
819 .await
820 .expect("Failed to import first wallet");
821
822 let result = wallet_manager
824 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
825 .await;
826
827 assert!(result.is_err());
828 match result.unwrap_err() {
829 crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
830 _ => panic!("Expected AlreadyExists error"),
831 }
832 }
833
834 #[tokio::test]
835 async fn test_list_wallets() {
836 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
837
838 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
840 assert_eq!(wallets.len(), 0);
841
842 wallet_manager
844 .create_wallet("wallet-1", Some("password1"))
845 .await
846 .expect("Failed to create wallet 1");
847
848 wallet_manager
849 .create_wallet("wallet-2", None)
850 .await
851 .expect("Failed to create wallet 2");
852
853 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";
854 wallet_manager
855 .import_wallet("imported-wallet", test_mnemonic, Some("password3"))
856 .await
857 .expect("Failed to import wallet");
858
859 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
861
862 assert_eq!(wallets.len(), 3);
863
864 let wallet_names: Vec<&String> = wallets.iter().map(|w| &w.name).collect();
866 assert!(wallet_names.contains(&&"wallet-1".to_string()));
867 assert!(wallet_names.contains(&&"wallet-2".to_string()));
868 assert!(wallet_names.contains(&&"imported-wallet".to_string()));
869
870 for wallet in &wallets {
872 assert!(wallet.address.starts_with("qz")); assert_eq!(wallet.key_type, "Dilithium ML-DSA-87");
874 }
875
876 assert!(wallets[0].created_at >= wallets[1].created_at);
878 assert!(wallets[1].created_at >= wallets[2].created_at);
879 }
880
881 #[tokio::test]
882 async fn test_get_wallet() {
883 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
884
885 let created_wallet = wallet_manager
887 .create_wallet("test-get-wallet", Some("test-password"))
888 .await
889 .expect("Failed to create wallet");
890
891 let wallet_info = wallet_manager
893 .get_wallet("test-get-wallet", None)
894 .expect("Failed to get wallet")
895 .expect("Wallet should exist");
896
897 assert_eq!(wallet_info.name, "test-get-wallet");
898 assert_eq!(wallet_info.address, created_wallet.address); let wallet_info = wallet_manager
903 .get_wallet("test-get-wallet", Some("wrong-password"))
904 .expect("Failed to get wallet")
905 .expect("Wallet should exist");
906
907 assert_eq!(wallet_info.name, "test-get-wallet");
908 assert_eq!(wallet_info.address, "[Wrong password]");
910
911 let wallet_info = wallet_manager
913 .get_wallet("test-get-wallet", Some("test-password"))
914 .expect("Failed to get wallet")
915 .expect("Wallet should exist");
916
917 assert_eq!(wallet_info.name, "test-get-wallet");
918 assert_eq!(wallet_info.address, created_wallet.address);
919 assert!(wallet_info.address.starts_with("qz"));
920
921 let result = wallet_manager
923 .get_wallet("non-existent-wallet", None)
924 .expect("Should not error on non-existent wallet");
925
926 assert!(result.is_none());
927 }
928}