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_runtime::traits::IdentifyAccount;
17
18pub const DEFAULT_DERIVATION_PATH: &str = "m/44'/189189'/0'/0/0";
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct WalletInfo {
24 pub name: String,
25 pub address: String,
26 pub created_at: chrono::DateTime<chrono::Utc>,
27 pub key_type: String,
28 pub derivation_path: String,
29}
30
31pub struct WalletManager {
33 wallets_dir: std::path::PathBuf,
34}
35
36impl WalletManager {
37 pub fn new() -> Result<Self> {
39 let wallets_dir = dirs::home_dir()
40 .ok_or(WalletError::KeyGeneration)?
41 .join(".quantus")
42 .join("wallets");
43
44 std::fs::create_dir_all(&wallets_dir)?;
46
47 Ok(Self { wallets_dir })
48 }
49
50 pub async fn create_wallet(&self, name: &str, password: Option<&str>) -> Result<WalletInfo> {
52 self.create_wallet_with_derivation_path(name, password, DEFAULT_DERIVATION_PATH)
53 .await
54 }
55
56 pub async fn create_wallet_with_derivation_path(
58 &self,
59 name: &str,
60 password: Option<&str>,
61 derivation_path: &str,
62 ) -> Result<WalletInfo> {
63 let keystore = Keystore::new(&self.wallets_dir);
65 if keystore.load_wallet(name)?.is_some() {
66 return Err(WalletError::AlreadyExists.into());
67 }
68
69 let mut seed = [0u8; 32];
71 rng().fill_bytes(&mut seed);
72 let mnemonic = generate_mnemonic(24, seed).map_err(|_| WalletError::KeyGeneration)?;
73 let lattice =
74 HDLattice::from_mnemonic(&mnemonic, None).expect("Failed to generate lattice");
75 let dilithium_keypair = lattice
76 .generate_derived_keys(derivation_path)
77 .map_err(|_| WalletError::KeyGeneration)?;
78 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
79
80 let mut metadata = std::collections::HashMap::new();
82 metadata.insert("version".to_string(), "1.0.0".to_string());
83 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
84 metadata.insert("derivation_path".to_string(), derivation_path.to_string());
85
86 let address = quantum_keypair.to_account_id_ss58check();
88
89 let wallet_data = WalletData {
90 name: name.to_string(),
91 keypair: quantum_keypair,
92 mnemonic: Some(mnemonic.clone()),
93 derivation_path: derivation_path.to_string(),
94 metadata,
95 };
96
97 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
100 keystore.save_wallet(&encrypted_wallet)?;
101
102 Ok(WalletInfo {
103 name: name.to_string(),
104 address,
105 created_at: encrypted_wallet.created_at,
106 key_type: "Dilithium ML-DSA-87".to_string(),
107 derivation_path: derivation_path.to_string(),
108 })
109 }
110
111 pub async fn create_developer_wallet(&self, name: &str) -> Result<WalletInfo> {
113 let keystore = Keystore::new(&self.wallets_dir);
115
116 let resonance_pair = match name {
118 "crystal_alice" => qp_dilithium_crypto::crystal_alice(),
119 "crystal_bob" => qp_dilithium_crypto::dilithium_bob(),
120 "crystal_charlie" => qp_dilithium_crypto::crystal_charlie(),
121 _ => return Err(WalletError::KeyGeneration.into()),
122 };
123
124 let quantum_keypair = QuantumKeyPair::from_resonance_pair(&resonance_pair);
125
126 use sp_core::crypto::Ss58Codec;
128 let resonance_addr = resonance_pair
129 .public()
130 .into_account()
131 .to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(189));
132 println!("🔑 Resonance pair: {:?}", resonance_addr);
133 println!("🔑 Quantum keypair: {:?}", quantum_keypair.to_account_id_ss58check());
134
135 let mut metadata = std::collections::HashMap::new();
137 metadata.insert("version".to_string(), "1.0.0".to_string());
138 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
139 metadata.insert("test_wallet".to_string(), "true".to_string());
140
141 let address = quantum_keypair.to_account_id_ss58check();
143
144 let wallet_data = WalletData {
145 name: name.to_string(),
146 keypair: quantum_keypair,
147 mnemonic: None, derivation_path: "m/".to_string(), metadata,
150 };
151
152 let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, "")?;
154 keystore.save_wallet(&encrypted_wallet)?;
155
156 Ok(WalletInfo {
157 name: name.to_string(),
158 address,
159 created_at: encrypted_wallet.created_at,
160 key_type: "Dilithium ML-DSA-87".to_string(),
161 derivation_path: "m/".to_string(),
162 })
163 }
164
165 pub fn export_mnemonic(&self, name: &str, password: Option<&str>) -> Result<String> {
167 let final_password = password::get_wallet_password(name, password.map(String::from), None)?;
168
169 let wallet_data = self.load_wallet(name, &final_password)?;
170
171 wallet_data.mnemonic.ok_or_else(|| WalletError::MnemonicNotAvailable.into())
172 }
173
174 pub fn list_wallets(&self) -> Result<Vec<WalletInfo>> {
176 let keystore = Keystore::new(&self.wallets_dir);
177 let wallet_names = keystore.list_wallets()?;
178
179 let mut wallets = Vec::new();
180 for name in wallet_names {
181 if let Some(encrypted_wallet) = keystore.load_wallet(&name)? {
182 let wallet_info = WalletInfo {
184 name: encrypted_wallet.name,
185 address: encrypted_wallet.address, created_at: encrypted_wallet.created_at,
187 key_type: "Dilithium ML-DSA-87".to_string(),
188 derivation_path: "[Encrypted]".to_string(), };
190 wallets.push(wallet_info);
191 }
192 }
193
194 wallets.sort_by(|a, b| b.created_at.cmp(&a.created_at));
196 Ok(wallets)
197 }
198
199 pub async fn import_wallet(
201 &self,
202 name: &str,
203 mnemonic: &str,
204 password: Option<&str>,
205 ) -> Result<WalletInfo> {
206 self.import_wallet_with_derivation_path(name, mnemonic, password, DEFAULT_DERIVATION_PATH)
207 .await
208 }
209
210 pub async fn create_wallet_no_derivation(
212 &self,
213 name: &str,
214 password: Option<&str>,
215 ) -> Result<WalletInfo> {
216 let keystore = Keystore::new(&self.wallets_dir);
218 if keystore.load_wallet(name)?.is_some() {
219 return Err(WalletError::AlreadyExists.into());
220 }
221
222 let mut seed = [0u8; 32];
224 rng().fill_bytes(&mut seed);
225 let mnemonic = generate_mnemonic(24, seed).map_err(|_| WalletError::KeyGeneration)?;
226 let lattice =
227 HDLattice::from_mnemonic(&mnemonic, None).map_err(|_| WalletError::KeyGeneration)?;
228 let dilithium_keypair = lattice.generate_keys();
229 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
230
231 let mut metadata = std::collections::HashMap::new();
233 metadata.insert("version".to_string(), "1.0.0".to_string());
234 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
235 metadata.insert("no_derivation".to_string(), "true".to_string());
236
237 let address = quantum_keypair.to_account_id_ss58check();
239
240 let wallet_data = WalletData {
241 name: name.to_string(),
242 keypair: quantum_keypair,
243 mnemonic: Some(mnemonic),
244 derivation_path: "master".to_string(),
245 metadata,
246 };
247
248 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
251 keystore.save_wallet(&encrypted_wallet)?;
252
253 Ok(WalletInfo {
254 name: name.to_string(),
255 address,
256 created_at: chrono::Utc::now(),
257 key_type: "Dilithium ML-DSA-87".to_string(),
258 derivation_path: "master".to_string(),
259 })
260 }
261
262 pub async fn import_wallet_no_derivation(
264 &self,
265 name: &str,
266 mnemonic: &str,
267 password: Option<&str>,
268 ) -> Result<WalletInfo> {
269 let keystore = Keystore::new(&self.wallets_dir);
271 if keystore.load_wallet(name)?.is_some() {
272 return Err(WalletError::AlreadyExists.into());
273 }
274
275 let lattice =
277 HDLattice::from_mnemonic(mnemonic, None).map_err(|_| WalletError::InvalidMnemonic)?;
278 let dilithium_keypair = lattice.generate_keys();
279 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
280
281 let mut metadata = std::collections::HashMap::new();
283 metadata.insert("version".to_string(), "1.0.0".to_string());
284 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
285 metadata.insert("imported".to_string(), "true".to_string());
286 metadata.insert("no_derivation".to_string(), "true".to_string());
287
288 let address = quantum_keypair.to_account_id_ss58check();
290
291 let wallet_data = WalletData {
292 name: name.to_string(),
293 keypair: quantum_keypair,
294 mnemonic: Some(mnemonic.to_string()),
295 derivation_path: "master".to_string(),
296 metadata,
297 };
298
299 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
302 keystore.save_wallet(&encrypted_wallet)?;
303
304 Ok(WalletInfo {
305 name: name.to_string(),
306 address,
307 created_at: chrono::Utc::now(),
308 key_type: "Dilithium ML-DSA-87".to_string(),
309 derivation_path: "master".to_string(),
310 })
311 }
312
313 pub async fn import_wallet_with_derivation_path(
315 &self,
316 name: &str,
317 mnemonic: &str,
318 password: Option<&str>,
319 derivation_path: &str,
320 ) -> Result<WalletInfo> {
321 let keystore = Keystore::new(&self.wallets_dir);
323 if keystore.load_wallet(name)?.is_some() {
324 return Err(WalletError::AlreadyExists.into());
325 }
326
327 let lattice =
329 HDLattice::from_mnemonic(mnemonic, None).map_err(|_| WalletError::InvalidMnemonic)?;
330 let dilithium_keypair = lattice
331 .generate_derived_keys(derivation_path)
332 .map_err(|_| WalletError::KeyGeneration)?;
333 let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
334
335 let mut metadata = std::collections::HashMap::new();
337 metadata.insert("version".to_string(), "1.0.0".to_string());
338 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
339 metadata.insert("imported".to_string(), "true".to_string());
340 metadata.insert("derivation_path".to_string(), derivation_path.to_string());
341
342 let address = quantum_keypair.to_account_id_ss58check();
344
345 let wallet_data = WalletData {
346 name: name.to_string(),
347 keypair: quantum_keypair,
348 mnemonic: Some(mnemonic.to_string()),
349 derivation_path: derivation_path.to_string(),
350 metadata,
351 };
352
353 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
356 keystore.save_wallet(&encrypted_wallet)?;
357
358 Ok(WalletInfo {
359 name: name.to_string(),
360 address,
361 created_at: encrypted_wallet.created_at,
362 key_type: "Dilithium ML-DSA-87".to_string(),
363 derivation_path: derivation_path.to_string(),
364 })
365 }
366
367 pub async fn create_wallet_from_seed(
369 &self,
370 name: &str,
371 seed_hex: &str,
372 password: Option<&str>,
373 ) -> Result<WalletInfo> {
374 let keystore = Keystore::new(&self.wallets_dir);
376 if keystore.load_wallet(name)?.is_some() {
377 return Err(WalletError::AlreadyExists.into());
378 }
379
380 if seed_hex.len() != 64 {
382 return Err(WalletError::InvalidMnemonic.into()); }
384
385 let seed_bytes = hex::decode(seed_hex).map_err(|_| WalletError::InvalidMnemonic)?;
387 if seed_bytes.len() != 32 {
388 return Err(WalletError::InvalidMnemonic.into());
389 }
390
391 let seed_array: [u8; 32] =
393 seed_bytes.try_into().map_err(|_| WalletError::InvalidMnemonic)?;
394
395 println!("Debug: seed_array length: {}", seed_array.len());
396 println!("Debug: seed_hex: {}", seed_hex);
397 println!("Debug: calling DilithiumPair::from_seed");
398
399 let dilithium_pair = qp_dilithium_crypto::types::DilithiumPair::from_seed(&seed_array)
400 .map_err(|e| {
401 println!("Debug: DilithiumPair::from_seed failed with error: {:?}", e);
402 WalletError::InvalidMnemonic
403 })?;
404
405 println!("Debug: DilithiumPair created successfully");
406
407 let quantum_keypair = QuantumKeyPair::from_resonance_pair(&dilithium_pair);
409
410 let mut metadata = std::collections::HashMap::new();
412 metadata.insert("version".to_string(), "1.0.0".to_string());
413 metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
414 metadata.insert("from_seed".to_string(), "true".to_string());
415
416 let address = quantum_keypair.to_account_id_ss58check();
418
419 let wallet_data = WalletData {
420 name: name.to_string(),
421 keypair: quantum_keypair,
422 mnemonic: None, derivation_path: "m/".to_string(), metadata,
425 };
426
427 let password = password.unwrap_or(""); let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
430 keystore.save_wallet(&encrypted_wallet)?;
431
432 Ok(WalletInfo {
433 name: name.to_string(),
434 address,
435 created_at: encrypted_wallet.created_at,
436 key_type: "Dilithium ML-DSA-87".to_string(),
437 derivation_path: "m/".to_string(),
438 })
439 }
440
441 pub fn get_wallet(&self, name: &str, password: Option<&str>) -> Result<Option<WalletInfo>> {
443 let keystore = Keystore::new(&self.wallets_dir);
444
445 if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
446 if let Some(pwd) = password {
447 match keystore.decrypt_wallet_data(&encrypted_wallet, pwd) {
449 Ok(wallet_data) => {
450 let address = wallet_data.keypair.to_account_id_ss58check();
451 Ok(Some(WalletInfo {
452 name: wallet_data.name,
453 address,
454 created_at: encrypted_wallet.created_at,
455 key_type: "Dilithium ML-DSA-87".to_string(),
456 derivation_path: wallet_data.derivation_path,
457 }))
458 },
459 Err(_) => {
460 Ok(Some(WalletInfo {
462 name: encrypted_wallet.name,
463 address: "[Wrong password]".to_string(),
464 created_at: encrypted_wallet.created_at,
465 key_type: "Dilithium ML-DSA-87".to_string(),
466 derivation_path: "[Wrong password]".to_string(),
467 }))
468 },
469 }
470 } else {
471 Ok(Some(WalletInfo {
473 name: encrypted_wallet.name,
474 address: encrypted_wallet.address, created_at: encrypted_wallet.created_at,
476 key_type: "Dilithium ML-DSA-87".to_string(),
477 derivation_path: "[Encrypted]".to_string(), }))
479 }
480 } else {
481 Ok(None)
482 }
483 }
484
485 pub fn load_wallet(&self, name: &str, password: &str) -> Result<WalletData> {
487 let keystore = Keystore::new(&self.wallets_dir);
488
489 let encrypted_wallet = keystore.load_wallet(name)?.ok_or(WalletError::NotFound)?;
491
492 let wallet_data = keystore.decrypt_wallet_data(&encrypted_wallet, password)?;
494
495 Ok(wallet_data)
496 }
497
498 pub fn delete_wallet(&self, name: &str) -> Result<bool> {
500 let keystore = Keystore::new(&self.wallets_dir);
501 keystore.delete_wallet(name)
502 }
503
504 pub fn find_wallet_address(&self, name: &str) -> Result<Option<String>> {
506 let keystore = Keystore::new(&self.wallets_dir);
507
508 if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
509 Ok(Some(encrypted_wallet.address))
511 } else {
512 Ok(None)
513 }
514 }
515}
516
517pub fn load_keypair_from_wallet(
518 wallet_name: &str,
519 password: Option<String>,
520 password_file: Option<String>,
521) -> Result<QuantumKeyPair> {
522 let wallet_manager = WalletManager::new()?;
523 let wallet_password = password::get_wallet_password(wallet_name, password, password_file)?;
524 let wallet_data = wallet_manager.load_wallet(wallet_name, &wallet_password)?;
525 let keypair = wallet_data.keypair;
526 Ok(keypair)
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532 use std::fs;
533 use tempfile::TempDir;
534
535 async fn create_test_wallet_manager() -> (WalletManager, TempDir) {
536 let temp_dir = TempDir::new().expect("Failed to create temp directory");
537 let wallets_dir = temp_dir.path().join("wallets");
538 fs::create_dir_all(&wallets_dir).expect("Failed to create wallets directory");
539
540 let wallet_manager = WalletManager { wallets_dir };
541
542 (wallet_manager, temp_dir)
543 }
544
545 #[tokio::test]
546 async fn test_wallet_creation() {
547 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
548
549 let wallet_info = wallet_manager
551 .create_wallet("test-wallet", Some("test-password"))
552 .await
553 .expect("Failed to create wallet");
554
555 assert_eq!(wallet_info.name, "test-wallet");
557 assert!(wallet_info.address.starts_with("qz")); assert_eq!(wallet_info.key_type, "Dilithium ML-DSA-87");
559 assert!(wallet_info.created_at <= chrono::Utc::now());
560 }
561
562 #[tokio::test]
563 async fn test_wallet_already_exists() {
564 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
565
566 wallet_manager
568 .create_wallet("duplicate-wallet", None)
569 .await
570 .expect("Failed to create first wallet");
571
572 let result = wallet_manager.create_wallet("duplicate-wallet", None).await;
574
575 assert!(result.is_err());
576 match result.unwrap_err() {
577 crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
578 _ => panic!("Expected AlreadyExists error"),
579 }
580 }
581
582 #[tokio::test]
583 async fn test_wallet_file_creation() {
584 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
585
586 let _ = wallet_manager
588 .create_wallet("file-test-wallet", Some("password123"))
589 .await
590 .expect("Failed to create wallet");
591
592 let wallet_file = wallet_manager.wallets_dir.join("file-test-wallet.json");
594 assert!(wallet_file.exists(), "Wallet file should exist");
595
596 let file_size = fs::metadata(&wallet_file).expect("Failed to get file metadata").len();
598 assert!(file_size > 0, "Wallet file should not be empty");
599 }
600
601 #[tokio::test]
602 async fn test_keystore_encryption_decryption() {
603 let temp_dir = TempDir::new().expect("Failed to create temp directory");
604 let keystore = keystore::Keystore::new(temp_dir.path());
605
606 let entropy = [1u8; 32]; let dilithium_keypair = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
609 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
610
611 let mut metadata = std::collections::HashMap::new();
612 metadata.insert("test_key".to_string(), "test_value".to_string());
613
614 let original_wallet_data = keystore::WalletData {
615 name: "test-wallet".to_string(),
616 keypair: quantum_keypair,
617 mnemonic: Some(
618 "test mnemonic phrase with twenty four words here for testing purposes only"
619 .to_string(),
620 ),
621 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
622 metadata,
623 };
624
625 let encrypted_wallet = keystore
627 .encrypt_wallet_data(&original_wallet_data, "test-password")
628 .expect("Failed to encrypt wallet data");
629
630 assert_eq!(encrypted_wallet.name, "test-wallet");
631 assert!(!encrypted_wallet.encrypted_data.is_empty());
632 assert!(!encrypted_wallet.argon2_salt.is_empty());
633 assert!(!encrypted_wallet.aes_nonce.is_empty());
634
635 let decrypted_wallet_data = keystore
637 .decrypt_wallet_data(&encrypted_wallet, "test-password")
638 .expect("Failed to decrypt wallet data");
639
640 assert_eq!(decrypted_wallet_data.name, original_wallet_data.name);
642 assert_eq!(decrypted_wallet_data.mnemonic, original_wallet_data.mnemonic);
643 assert_eq!(decrypted_wallet_data.metadata, original_wallet_data.metadata);
644 assert_eq!(
645 decrypted_wallet_data.keypair.public_key,
646 original_wallet_data.keypair.public_key
647 );
648 assert_eq!(
649 decrypted_wallet_data.keypair.private_key,
650 original_wallet_data.keypair.private_key
651 );
652 }
653
654 #[tokio::test]
655 async fn test_quantum_keypair_address_generation() {
656 let entropy = [2u8; 32]; let dilithium_keypair = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
659 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
660
661 let account_id = quantum_keypair.to_account_id_32();
663 let ss58_address = quantum_keypair.to_account_id_ss58check();
664
665 assert!(ss58_address.starts_with("qz"), "SS58 address should start with 5");
667 assert!(ss58_address.len() >= 47, "SS58 address should be at least 47 characters");
668
669 let converted_account_bytes = keystore::QuantumKeyPair::ss58_to_account_id(&ss58_address);
671 let account_bytes: &[u8] = account_id.as_ref();
672 assert_eq!(converted_account_bytes, account_bytes);
673 }
674
675 #[tokio::test]
676 async fn test_keystore_save_and_load() {
677 let temp_dir = TempDir::new().expect("Failed to create temp directory");
678 let keystore = keystore::Keystore::new(temp_dir.path());
679
680 let entropy = [3u8; 32]; let dilithium_keypair = qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(&entropy);
683 let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
684
685 let wallet_data = keystore::WalletData {
686 name: "save-load-test".to_string(),
687 keypair: quantum_keypair,
688 mnemonic: Some("save load test mnemonic phrase".to_string()),
689 derivation_path: DEFAULT_DERIVATION_PATH.to_string(),
690 metadata: std::collections::HashMap::new(),
691 };
692
693 let encrypted_wallet = keystore
694 .encrypt_wallet_data(&wallet_data, "save-load-password")
695 .expect("Failed to encrypt wallet");
696
697 keystore.save_wallet(&encrypted_wallet).expect("Failed to save wallet");
699
700 let loaded_wallet = keystore
702 .load_wallet("save-load-test")
703 .expect("Failed to load wallet")
704 .expect("Wallet should exist");
705
706 assert_eq!(loaded_wallet.name, encrypted_wallet.name);
708 assert_eq!(loaded_wallet.encrypted_data, encrypted_wallet.encrypted_data);
709 assert_eq!(loaded_wallet.argon2_salt, encrypted_wallet.argon2_salt);
710 assert_eq!(loaded_wallet.aes_nonce, encrypted_wallet.aes_nonce);
711
712 let non_existent = keystore
714 .load_wallet("non-existent-wallet")
715 .expect("Load should succeed but return None");
716 assert!(non_existent.is_none());
717 }
718
719 #[tokio::test]
720 async fn test_mnemonic_generation_and_key_derivation() {
721 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
722
723 let wallet1 = wallet_manager
725 .create_wallet("mnemonic-test-1", None)
726 .await
727 .expect("Failed to create wallet 1");
728
729 let wallet2 = wallet_manager
730 .create_wallet("mnemonic-test-2", None)
731 .await
732 .expect("Failed to create wallet 2");
733
734 assert_ne!(wallet1.address, wallet2.address);
736
737 assert!(wallet1.address.starts_with("qz"));
739 assert!(wallet2.address.starts_with("qz"));
740 }
741
742 #[tokio::test]
743 async fn test_wallet_import() {
744 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
745
746 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";
748
749 let imported_wallet = wallet_manager
751 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
752 .await
753 .expect("Failed to import wallet");
754
755 assert_eq!(imported_wallet.name, "imported-test-wallet");
757 assert!(imported_wallet.address.starts_with("qz"));
758 assert_eq!(imported_wallet.key_type, "Dilithium ML-DSA-87");
759
760 let imported_wallet2 = wallet_manager
762 .import_wallet("imported-test-wallet-2", test_mnemonic, None)
763 .await
764 .expect("Failed to import wallet again");
765
766 assert_eq!(imported_wallet.address, imported_wallet2.address);
767 }
768
769 #[tokio::test]
770 async fn test_known_values() {
771 sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(189));
772
773 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
774 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";
775 let expected_address = "qznMJss7Ls1SWBhvvL2CSHVbgTxEfnL9GgpvMTq5CWMEwfCoe"; let expected_address_no_derive = "qznBvupPsA9T8VJDuTDokKPiNUe88zMMUtHGA1AsGc8fXKSSA";
777
778 let imported_wallet = wallet_manager
779 .import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
780 .await
781 .expect("Failed to import wallet");
782
783 let imported_wallet_no_derive = wallet_manager
784 .import_wallet_no_derivation(
785 "imported-test-wallet_no_derive",
786 test_mnemonic,
787 Some("import-password"),
788 )
789 .await
790 .expect("Failed to import wallet");
791
792 assert_eq!(imported_wallet.address, expected_address, "address at index 0 is wrong");
793 assert_eq!(
794 imported_wallet_no_derive.address, expected_address_no_derive,
795 "no-derivation address is wrong"
796 );
797 }
798
799 #[tokio::test]
800 async fn test_wallet_import_invalid_mnemonic() {
801 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
802
803 let invalid_mnemonic = "invalid mnemonic phrase that should not work";
805
806 let result = wallet_manager.import_wallet("invalid-wallet", invalid_mnemonic, None).await;
807
808 assert!(result.is_err());
809 match result.unwrap_err() {
810 crate::error::QuantusError::Wallet(WalletError::InvalidMnemonic) => {},
811 _ => panic!("Expected InvalidMnemonic error"),
812 }
813 }
814
815 #[tokio::test]
816 async fn test_wallet_import_already_exists() {
817 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
818
819 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";
820
821 wallet_manager
823 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
824 .await
825 .expect("Failed to import first wallet");
826
827 let result = wallet_manager
829 .import_wallet("duplicate-import-wallet", test_mnemonic, None)
830 .await;
831
832 assert!(result.is_err());
833 match result.unwrap_err() {
834 crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
835 _ => panic!("Expected AlreadyExists error"),
836 }
837 }
838
839 #[tokio::test]
840 async fn test_list_wallets() {
841 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
842
843 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
845 assert_eq!(wallets.len(), 0);
846
847 wallet_manager
849 .create_wallet("wallet-1", Some("password1"))
850 .await
851 .expect("Failed to create wallet 1");
852
853 wallet_manager
854 .create_wallet("wallet-2", None)
855 .await
856 .expect("Failed to create wallet 2");
857
858 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";
859 wallet_manager
860 .import_wallet("imported-wallet", test_mnemonic, Some("password3"))
861 .await
862 .expect("Failed to import wallet");
863
864 let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
866
867 assert_eq!(wallets.len(), 3);
868
869 let wallet_names: Vec<&String> = wallets.iter().map(|w| &w.name).collect();
871 assert!(wallet_names.contains(&&"wallet-1".to_string()));
872 assert!(wallet_names.contains(&&"wallet-2".to_string()));
873 assert!(wallet_names.contains(&&"imported-wallet".to_string()));
874
875 for wallet in &wallets {
877 assert!(wallet.address.starts_with("qz")); assert_eq!(wallet.key_type, "Dilithium ML-DSA-87");
879 }
880
881 assert!(wallets[0].created_at >= wallets[1].created_at);
883 assert!(wallets[1].created_at >= wallets[2].created_at);
884 }
885
886 #[tokio::test]
887 async fn test_get_wallet() {
888 let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
889
890 let created_wallet = wallet_manager
892 .create_wallet("test-get-wallet", Some("test-password"))
893 .await
894 .expect("Failed to create wallet");
895
896 let wallet_info = wallet_manager
898 .get_wallet("test-get-wallet", None)
899 .expect("Failed to get wallet")
900 .expect("Wallet should exist");
901
902 assert_eq!(wallet_info.name, "test-get-wallet");
903 assert_eq!(wallet_info.address, created_wallet.address); let wallet_info = wallet_manager
908 .get_wallet("test-get-wallet", Some("wrong-password"))
909 .expect("Failed to get wallet")
910 .expect("Wallet should exist");
911
912 assert_eq!(wallet_info.name, "test-get-wallet");
913 assert_eq!(wallet_info.address, "[Wrong password]");
915
916 let wallet_info = wallet_manager
918 .get_wallet("test-get-wallet", Some("test-password"))
919 .expect("Failed to get wallet")
920 .expect("Wallet should exist");
921
922 assert_eq!(wallet_info.name, "test-get-wallet");
923 assert_eq!(wallet_info.address, created_wallet.address);
924 assert!(wallet_info.address.starts_with("qz"));
925
926 let result = wallet_manager
928 .get_wallet("non-existent-wallet", None)
929 .expect("Should not error on non-existent wallet");
930
931 assert!(result.is_none());
932 }
933}