quantus_cli/wallet/
mod.rs

1/// Wallet management module
2///
3/// This module provides functionality for:
4/// - Creating quantum-safe wallets using Dilithium keys
5/// - Importing/exporting wallets with mnemonic phrases
6/// - Encrypting/decrypting wallet data
7/// - Managing multiple wallets
8pub 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/// Wallet information structure
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct WalletInfo {
21	pub name: String,
22	pub address: String,
23	pub created_at: chrono::DateTime<chrono::Utc>,
24	pub key_type: String,
25}
26
27/// Main wallet manager
28pub struct WalletManager {
29	wallets_dir: std::path::PathBuf,
30}
31
32impl WalletManager {
33	/// Create a new wallet manager
34	pub fn new() -> Result<Self> {
35		let wallets_dir = dirs::home_dir()
36			.ok_or(WalletError::KeyGeneration)?
37			.join(".quantus")
38			.join("wallets");
39
40		// Create directory if it doesn't exist
41		std::fs::create_dir_all(&wallets_dir)?;
42
43		Ok(Self { wallets_dir })
44	}
45
46	/// Create a new wallet
47	pub async fn create_wallet(&self, name: &str, password: Option<&str>) -> Result<WalletInfo> {
48		// Check if wallet already exists
49		let keystore = Keystore::new(&self.wallets_dir);
50		if keystore.load_wallet(name)?.is_some() {
51			return Err(WalletError::AlreadyExists.into());
52		}
53
54		// Generate a new Dilithium keypair'
55		let mut seed = [0u8; 32];
56		rng().fill_bytes(&mut seed);
57		let mnemonic = generate_mnemonic(24, seed).map_err(|_| WalletError::KeyGeneration)?;
58		let lattice =
59			HDLattice::from_mnemonic(&mnemonic, None).expect("Failed to generate lattice");
60		let dilithium_keypair = lattice.generate_keys();
61		let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
62
63		// Create wallet data
64		let mut metadata = std::collections::HashMap::new();
65		metadata.insert("version".to_string(), "1.0.0".to_string());
66		metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
67
68		// Generate address from public key (simplified version)
69		let address = quantum_keypair.to_account_id_ss58check();
70
71		let wallet_data = WalletData {
72			name: name.to_string(),
73			keypair: quantum_keypair,
74			mnemonic: Some(mnemonic.clone()),
75			metadata,
76		};
77
78		// Encrypt and save the wallet
79		let password = password.unwrap_or(""); // Use empty password if none provided
80		let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
81		keystore.save_wallet(&encrypted_wallet)?;
82
83		Ok(WalletInfo {
84			name: name.to_string(),
85			address,
86			created_at: encrypted_wallet.created_at,
87			key_type: "Dilithium ML-DSA-87".to_string(),
88		})
89	}
90
91	/// Create a new developer wallet
92	pub async fn create_developer_wallet(&self, name: &str) -> Result<WalletInfo> {
93		// Check if wallet already exists
94		let keystore = Keystore::new(&self.wallets_dir);
95
96		// Generate the appropriate test keypair
97		let resonance_pair = match name {
98			"crystal_alice" => qp_dilithium_crypto::crystal_alice(),
99			"crystal_bob" => qp_dilithium_crypto::dilithium_bob(),
100			"crystal_charlie" => qp_dilithium_crypto::crystal_charlie(),
101			_ => return Err(WalletError::KeyGeneration.into()),
102		};
103
104		let quantum_keypair = QuantumKeyPair::from_resonance_pair(&resonance_pair);
105
106		println!("🔑 Resonance pair: {:?}", resonance_pair.public().into_account().to_ss58check());
107		println!("🔑 Quantum keypair: {:?}", quantum_keypair.to_account_id_ss58check());
108
109		// Create wallet data
110		let mut metadata = std::collections::HashMap::new();
111		metadata.insert("version".to_string(), "1.0.0".to_string());
112		metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
113		metadata.insert("test_wallet".to_string(), "true".to_string());
114
115		// Generate address from public key
116		let address = quantum_keypair.to_account_id_ss58check();
117
118		let wallet_data = WalletData {
119			name: name.to_string(),
120			keypair: quantum_keypair,
121			mnemonic: None, // Test wallets don't have mnemonics
122			metadata,
123		};
124
125		// Encrypt and save the wallet with empty password for test wallets
126		let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, "")?;
127		keystore.save_wallet(&encrypted_wallet)?;
128
129		Ok(WalletInfo {
130			name: name.to_string(),
131			address,
132			created_at: encrypted_wallet.created_at,
133			key_type: "Dilithium ML-DSA-87".to_string(),
134		})
135	}
136
137	/// Export a wallet's mnemonic phrase
138	pub fn export_mnemonic(&self, name: &str, password: Option<&str>) -> Result<String> {
139		let final_password = password::get_wallet_password(name, password.map(String::from), None)?;
140
141		let wallet_data = self.load_wallet(name, &final_password)?;
142
143		wallet_data.mnemonic.ok_or_else(|| WalletError::MnemonicNotAvailable.into())
144	}
145
146	/// List all wallets
147	pub fn list_wallets(&self) -> Result<Vec<WalletInfo>> {
148		let keystore = Keystore::new(&self.wallets_dir);
149		let wallet_names = keystore.list_wallets()?;
150
151		let mut wallets = Vec::new();
152		for name in wallet_names {
153			if let Some(encrypted_wallet) = keystore.load_wallet(&name)? {
154				// Create wallet info using stored public address
155				let wallet_info = WalletInfo {
156					name: encrypted_wallet.name,
157					address: encrypted_wallet.address, // Address is stored unencrypted
158					created_at: encrypted_wallet.created_at,
159					key_type: "Dilithium ML-DSA-87".to_string(),
160				};
161				wallets.push(wallet_info);
162			}
163		}
164
165		// Sort by creation date (newest first)
166		wallets.sort_by(|a, b| b.created_at.cmp(&a.created_at));
167		Ok(wallets)
168	}
169
170	/// Import wallet from mnemonic phrase
171	pub async fn import_wallet(
172		&self,
173		name: &str,
174		mnemonic: &str,
175		password: Option<&str>,
176	) -> Result<WalletInfo> {
177		// Check if wallet already exists
178		let keystore = Keystore::new(&self.wallets_dir);
179		if keystore.load_wallet(name)?.is_some() {
180			return Err(WalletError::AlreadyExists.into());
181		}
182
183		// Validate and import from mnemonic
184		let lattice =
185			HDLattice::from_mnemonic(mnemonic, None).map_err(|_| WalletError::InvalidMnemonic)?;
186		let dilithium_keypair = lattice.generate_keys();
187		let quantum_keypair = QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
188
189		// Create wallet data
190		let mut metadata = std::collections::HashMap::new();
191		metadata.insert("version".to_string(), "1.0.0".to_string());
192		metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
193		metadata.insert("imported".to_string(), "true".to_string());
194
195		// Generate address from public key
196		let address = quantum_keypair.to_account_id_ss58check();
197
198		let wallet_data = WalletData {
199			name: name.to_string(),
200			keypair: quantum_keypair,
201			mnemonic: Some(mnemonic.to_string()),
202			metadata,
203		};
204
205		// Encrypt and save the wallet
206		let password = password.unwrap_or(""); // Use empty password if none provided
207		let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
208		keystore.save_wallet(&encrypted_wallet)?;
209
210		Ok(WalletInfo {
211			name: name.to_string(),
212			address,
213			created_at: encrypted_wallet.created_at,
214			key_type: "Dilithium ML-DSA-87".to_string(),
215		})
216	}
217
218	/// Create wallet from 32-byte seed
219	pub async fn create_wallet_from_seed(
220		&self,
221		name: &str,
222		seed_hex: &str,
223		password: Option<&str>,
224	) -> Result<WalletInfo> {
225		// Check if wallet already exists
226		let keystore = Keystore::new(&self.wallets_dir);
227		if keystore.load_wallet(name)?.is_some() {
228			return Err(WalletError::AlreadyExists.into());
229		}
230
231		// Validate seed hex format (should be 64 hex characters for 32 bytes)
232		if seed_hex.len() != 64 {
233			return Err(WalletError::InvalidMnemonic.into()); // Reusing error type
234		}
235
236		// Convert hex to bytes
237		let seed_bytes = hex::decode(seed_hex).map_err(|_| WalletError::InvalidMnemonic)?;
238		if seed_bytes.len() != 32 {
239			return Err(WalletError::InvalidMnemonic.into());
240		}
241
242		// Create DilithiumPair from seed
243		let seed_array: [u8; 32] =
244			seed_bytes.try_into().map_err(|_| WalletError::InvalidMnemonic)?;
245
246		println!("Debug: seed_array length: {}", seed_array.len());
247		println!("Debug: seed_hex: {}", seed_hex);
248		println!("Debug: calling DilithiumPair::from_seed");
249
250		let dilithium_pair = qp_dilithium_crypto::types::DilithiumPair::from_seed(&seed_array)
251			.map_err(|e| {
252				println!("Debug: DilithiumPair::from_seed failed with error: {:?}", e);
253				WalletError::InvalidMnemonic
254			})?;
255
256		println!("Debug: DilithiumPair created successfully");
257
258		// Convert to QuantumKeyPair
259		let quantum_keypair = QuantumKeyPair::from_resonance_pair(&dilithium_pair);
260
261		// Create wallet data
262		let mut metadata = std::collections::HashMap::new();
263		metadata.insert("version".to_string(), "1.0.0".to_string());
264		metadata.insert("algorithm".to_string(), "ML-DSA-87".to_string());
265		metadata.insert("from_seed".to_string(), "true".to_string());
266
267		// Generate address from public key
268		let address = quantum_keypair.to_account_id_ss58check();
269
270		let wallet_data = WalletData {
271			name: name.to_string(),
272			keypair: quantum_keypair,
273			mnemonic: None, // No mnemonic for seed-based wallets
274			metadata,
275		};
276
277		// Encrypt and save the wallet
278		let password = password.unwrap_or(""); // Use empty password if none provided
279		let encrypted_wallet = keystore.encrypt_wallet_data(&wallet_data, password)?;
280		keystore.save_wallet(&encrypted_wallet)?;
281
282		Ok(WalletInfo {
283			name: name.to_string(),
284			address,
285			created_at: encrypted_wallet.created_at,
286			key_type: "Dilithium ML-DSA-87".to_string(),
287		})
288	}
289
290	/// Get wallet by name with password for decryption
291	pub fn get_wallet(&self, name: &str, password: Option<&str>) -> Result<Option<WalletInfo>> {
292		let keystore = Keystore::new(&self.wallets_dir);
293
294		if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
295			if let Some(pwd) = password {
296				// Decrypt and show full details
297				match keystore.decrypt_wallet_data(&encrypted_wallet, pwd) {
298					Ok(wallet_data) => {
299						let address = wallet_data.keypair.to_account_id_ss58check();
300						Ok(Some(WalletInfo {
301							name: wallet_data.name,
302							address,
303							created_at: encrypted_wallet.created_at,
304							key_type: "Dilithium ML-DSA-87".to_string(),
305						}))
306					},
307					Err(_) => {
308						// Wrong password, return basic info
309						Ok(Some(WalletInfo {
310							name: encrypted_wallet.name,
311							address: "[Wrong password]".to_string(),
312							created_at: encrypted_wallet.created_at,
313							key_type: "Dilithium ML-DSA-87".to_string(),
314						}))
315					},
316				}
317			} else {
318				// No password provided, return basic info with public address
319				Ok(Some(WalletInfo {
320					name: encrypted_wallet.name,
321					address: encrypted_wallet.address, // Address is public
322					created_at: encrypted_wallet.created_at,
323					key_type: "Dilithium ML-DSA-87".to_string(),
324				}))
325			}
326		} else {
327			Ok(None)
328		}
329	}
330
331	/// Load a wallet from disk and decrypt it with the provided password
332	pub fn load_wallet(&self, name: &str, password: &str) -> Result<WalletData> {
333		let keystore = Keystore::new(&self.wallets_dir);
334
335		// Load the encrypted wallet
336		let encrypted_wallet = keystore.load_wallet(name)?.ok_or(WalletError::NotFound)?;
337
338		// Decrypt the wallet data using the provided password
339		let wallet_data = keystore.decrypt_wallet_data(&encrypted_wallet, password)?;
340
341		Ok(wallet_data)
342	}
343
344	/// Delete a wallet
345	pub fn delete_wallet(&self, name: &str) -> Result<bool> {
346		let keystore = Keystore::new(&self.wallets_dir);
347		keystore.delete_wallet(name)
348	}
349
350	/// Find wallet by name and return its address
351	pub fn find_wallet_address(&self, name: &str) -> Result<Option<String>> {
352		let keystore = Keystore::new(&self.wallets_dir);
353
354		if let Some(encrypted_wallet) = keystore.load_wallet(name)? {
355			// Return the stored address (it's stored unencrypted)
356			Ok(Some(encrypted_wallet.address))
357		} else {
358			Ok(None)
359		}
360	}
361}
362
363pub fn load_keypair_from_wallet(
364	wallet_name: &str,
365	password: Option<String>,
366	password_file: Option<String>,
367) -> Result<QuantumKeyPair> {
368	let wallet_manager = WalletManager::new()?;
369	let wallet_password = password::get_wallet_password(wallet_name, password, password_file)?;
370	let wallet_data = wallet_manager.load_wallet(wallet_name, &wallet_password)?;
371	let keypair = wallet_data.keypair;
372	Ok(keypair)
373}
374
375#[cfg(test)]
376mod tests {
377	use super::*;
378	use std::fs;
379	use tempfile::TempDir;
380
381	async fn create_test_wallet_manager() -> (WalletManager, TempDir) {
382		let temp_dir = TempDir::new().expect("Failed to create temp directory");
383		let wallets_dir = temp_dir.path().join("wallets");
384		fs::create_dir_all(&wallets_dir).expect("Failed to create wallets directory");
385
386		let wallet_manager = WalletManager { wallets_dir };
387
388		(wallet_manager, temp_dir)
389	}
390
391	#[tokio::test]
392	async fn test_wallet_creation() {
393		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
394
395		// Test wallet creation
396		let wallet_info = wallet_manager
397			.create_wallet("test-wallet", Some("test-password"))
398			.await
399			.expect("Failed to create wallet");
400
401		// Verify wallet info
402		assert_eq!(wallet_info.name, "test-wallet");
403		assert!(wallet_info.address.starts_with("qz")); // SS58 addresses start with 5
404		assert_eq!(wallet_info.key_type, "Dilithium ML-DSA-87");
405		assert!(wallet_info.created_at <= chrono::Utc::now());
406	}
407
408	#[tokio::test]
409	async fn test_wallet_already_exists() {
410		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
411
412		// Create first wallet
413		wallet_manager
414			.create_wallet("duplicate-wallet", None)
415			.await
416			.expect("Failed to create first wallet");
417
418		// Try to create wallet with same name
419		let result = wallet_manager.create_wallet("duplicate-wallet", None).await;
420
421		assert!(result.is_err());
422		match result.unwrap_err() {
423			crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
424			_ => panic!("Expected AlreadyExists error"),
425		}
426	}
427
428	#[tokio::test]
429	async fn test_wallet_file_creation() {
430		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
431
432		// Create wallet
433		let _ = wallet_manager
434			.create_wallet("file-test-wallet", Some("password123"))
435			.await
436			.expect("Failed to create wallet");
437
438		// Check if wallet file exists
439		let wallet_file = wallet_manager.wallets_dir.join("file-test-wallet.json");
440		assert!(wallet_file.exists(), "Wallet file should exist");
441
442		// Verify file is not empty
443		let file_size = fs::metadata(&wallet_file).expect("Failed to get file metadata").len();
444		assert!(file_size > 0, "Wallet file should not be empty");
445	}
446
447	#[tokio::test]
448	async fn test_keystore_encryption_decryption() {
449		let temp_dir = TempDir::new().expect("Failed to create temp directory");
450		let keystore = keystore::Keystore::new(temp_dir.path());
451
452		// Create test wallet data
453		let entropy = [1u8; 32]; // Use fixed entropy for deterministic tests
454		let dilithium_keypair =
455			qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
456		let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
457
458		let mut metadata = std::collections::HashMap::new();
459		metadata.insert("test_key".to_string(), "test_value".to_string());
460
461		let original_wallet_data = keystore::WalletData {
462			name: "test-wallet".to_string(),
463			keypair: quantum_keypair,
464			mnemonic: Some(
465				"test mnemonic phrase with twenty four words here for testing purposes only"
466					.to_string(),
467			),
468			metadata,
469		};
470
471		// Test encryption
472		let encrypted_wallet = keystore
473			.encrypt_wallet_data(&original_wallet_data, "test-password")
474			.expect("Failed to encrypt wallet data");
475
476		assert_eq!(encrypted_wallet.name, "test-wallet");
477		assert!(!encrypted_wallet.encrypted_data.is_empty());
478		assert!(!encrypted_wallet.argon2_salt.is_empty());
479		assert!(!encrypted_wallet.aes_nonce.is_empty());
480
481		// Test decryption
482		let decrypted_wallet_data = keystore
483			.decrypt_wallet_data(&encrypted_wallet, "test-password")
484			.expect("Failed to decrypt wallet data");
485
486		// Verify decrypted data matches original
487		assert_eq!(decrypted_wallet_data.name, original_wallet_data.name);
488		assert_eq!(decrypted_wallet_data.mnemonic, original_wallet_data.mnemonic);
489		assert_eq!(decrypted_wallet_data.metadata, original_wallet_data.metadata);
490		assert_eq!(
491			decrypted_wallet_data.keypair.public_key,
492			original_wallet_data.keypair.public_key
493		);
494		assert_eq!(
495			decrypted_wallet_data.keypair.private_key,
496			original_wallet_data.keypair.private_key
497		);
498	}
499
500	#[tokio::test]
501	async fn test_quantum_keypair_address_generation() {
502		// Generate keypair
503		let entropy = [2u8; 32]; // Use different entropy for variety
504		let dilithium_keypair =
505			qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
506		let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
507
508		// Test address generation
509		let account_id = quantum_keypair.to_account_id_32();
510		let ss58_address = quantum_keypair.to_account_id_ss58check();
511
512		// Verify SS58 address format
513		assert!(ss58_address.starts_with("qz"), "SS58 address should start with 5");
514		assert!(ss58_address.len() >= 47, "SS58 address should be at least 47 characters");
515
516		// Test round-trip conversion
517		let converted_account_bytes = keystore::QuantumKeyPair::ss58_to_account_id(&ss58_address);
518		let account_bytes: &[u8] = account_id.as_ref();
519		assert_eq!(converted_account_bytes, account_bytes);
520	}
521
522	#[tokio::test]
523	async fn test_keystore_save_and_load() {
524		let temp_dir = TempDir::new().expect("Failed to create temp directory");
525		let keystore = keystore::Keystore::new(temp_dir.path());
526
527		// Create and encrypt wallet data
528		let entropy = [3u8; 32]; // Use different entropy for each test
529		let dilithium_keypair =
530			qp_rusty_crystals_dilithium::ml_dsa_87::Keypair::generate(Some(&entropy));
531		let quantum_keypair = keystore::QuantumKeyPair::from_dilithium_keypair(&dilithium_keypair);
532
533		let wallet_data = keystore::WalletData {
534			name: "save-load-test".to_string(),
535			keypair: quantum_keypair,
536			mnemonic: Some("save load test mnemonic phrase".to_string()),
537			metadata: std::collections::HashMap::new(),
538		};
539
540		let encrypted_wallet = keystore
541			.encrypt_wallet_data(&wallet_data, "save-load-password")
542			.expect("Failed to encrypt wallet");
543
544		// Save wallet
545		keystore.save_wallet(&encrypted_wallet).expect("Failed to save wallet");
546
547		// Load wallet
548		let loaded_wallet = keystore
549			.load_wallet("save-load-test")
550			.expect("Failed to load wallet")
551			.expect("Wallet should exist");
552
553		// Verify loaded wallet matches saved wallet
554		assert_eq!(loaded_wallet.name, encrypted_wallet.name);
555		assert_eq!(loaded_wallet.encrypted_data, encrypted_wallet.encrypted_data);
556		assert_eq!(loaded_wallet.argon2_salt, encrypted_wallet.argon2_salt);
557		assert_eq!(loaded_wallet.aes_nonce, encrypted_wallet.aes_nonce);
558
559		// Test loading non-existent wallet
560		let non_existent = keystore
561			.load_wallet("non-existent-wallet")
562			.expect("Load should succeed but return None");
563		assert!(non_existent.is_none());
564	}
565
566	#[tokio::test]
567	async fn test_mnemonic_generation_and_key_derivation() {
568		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
569
570		// Create multiple wallets to test mnemonic uniqueness
571		let wallet1 = wallet_manager
572			.create_wallet("mnemonic-test-1", None)
573			.await
574			.expect("Failed to create wallet 1");
575
576		let wallet2 = wallet_manager
577			.create_wallet("mnemonic-test-2", None)
578			.await
579			.expect("Failed to create wallet 2");
580
581		// Addresses should be different (extremely unlikely to be the same)
582		assert_ne!(wallet1.address, wallet2.address);
583
584		// Both should be valid SS58 addresses
585		assert!(wallet1.address.starts_with("qz"));
586		assert!(wallet2.address.starts_with("qz"));
587	}
588
589	#[tokio::test]
590	async fn test_wallet_import() {
591		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
592
593		// Test mnemonic phrase (24 words)
594		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";
595
596		// Import wallet
597		let imported_wallet = wallet_manager
598			.import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
599			.await
600			.expect("Failed to import wallet");
601
602		// Verify wallet info
603		assert_eq!(imported_wallet.name, "imported-test-wallet");
604		assert!(imported_wallet.address.starts_with("qz"));
605		assert_eq!(imported_wallet.key_type, "Dilithium ML-DSA-87");
606
607		// Import the same mnemonic again should create the same address
608		let imported_wallet2 = wallet_manager
609			.import_wallet("imported-test-wallet-2", test_mnemonic, None)
610			.await
611			.expect("Failed to import wallet again");
612
613		assert_eq!(imported_wallet.address, imported_wallet2.address);
614	}
615
616	#[tokio::test]
617	async fn test_known_values() {
618		sp_core::crypto::set_default_ss58_version(sp_core::crypto::Ss58AddressFormat::custom(189));
619
620		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
621		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";
622		let expected_address = "qzpKnCCUvfXQdanRBkoPVDxcXbLja9JkYzv26hTQwP9C5mZWP";
623
624		let imported_wallet = wallet_manager
625			.import_wallet("imported-test-wallet", test_mnemonic, Some("import-password"))
626			.await
627			.expect("Failed to import wallet");
628
629		// qzoYcXrTfvjpK1yn3fVAXktWQ6QLJ2ke7gLXyqadre8xxaQ5G
630		assert_eq!(imported_wallet.address, expected_address);
631	}
632
633	#[tokio::test]
634	async fn test_wallet_import_invalid_mnemonic() {
635		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
636
637		// Test with invalid mnemonic
638		let invalid_mnemonic = "invalid mnemonic phrase that should not work";
639
640		let result = wallet_manager.import_wallet("invalid-wallet", invalid_mnemonic, None).await;
641
642		assert!(result.is_err());
643		match result.unwrap_err() {
644			crate::error::QuantusError::Wallet(WalletError::InvalidMnemonic) => {},
645			_ => panic!("Expected InvalidMnemonic error"),
646		}
647	}
648
649	#[tokio::test]
650	async fn test_wallet_import_already_exists() {
651		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
652
653		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";
654
655		// Import first wallet
656		wallet_manager
657			.import_wallet("duplicate-import-wallet", test_mnemonic, None)
658			.await
659			.expect("Failed to import first wallet");
660
661		// Try to import with same name
662		let result = wallet_manager
663			.import_wallet("duplicate-import-wallet", test_mnemonic, None)
664			.await;
665
666		assert!(result.is_err());
667		match result.unwrap_err() {
668			crate::error::QuantusError::Wallet(WalletError::AlreadyExists) => {},
669			_ => panic!("Expected AlreadyExists error"),
670		}
671	}
672
673	#[tokio::test]
674	async fn test_list_wallets() {
675		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
676
677		// Initially should be empty
678		let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
679		assert_eq!(wallets.len(), 0);
680
681		// Create some wallets
682		wallet_manager
683			.create_wallet("wallet-1", Some("password1"))
684			.await
685			.expect("Failed to create wallet 1");
686
687		wallet_manager
688			.create_wallet("wallet-2", None)
689			.await
690			.expect("Failed to create wallet 2");
691
692		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";
693		wallet_manager
694			.import_wallet("imported-wallet", test_mnemonic, Some("password3"))
695			.await
696			.expect("Failed to import wallet");
697
698		// List wallets
699		let wallets = wallet_manager.list_wallets().expect("Failed to list wallets");
700
701		assert_eq!(wallets.len(), 3);
702
703		// Check that all wallet names are present
704		let wallet_names: Vec<&String> = wallets.iter().map(|w| &w.name).collect();
705		assert!(wallet_names.contains(&&"wallet-1".to_string()));
706		assert!(wallet_names.contains(&&"wallet-2".to_string()));
707		assert!(wallet_names.contains(&&"imported-wallet".to_string()));
708
709		// Check that addresses are real addresses (now stored unencrypted)
710		for wallet in &wallets {
711			assert!(wallet.address.starts_with("qz")); // Real SS58 addresses start with 5
712			assert_eq!(wallet.key_type, "Dilithium ML-DSA-87");
713		}
714
715		// Check sorting (newest first)
716		assert!(wallets[0].created_at >= wallets[1].created_at);
717		assert!(wallets[1].created_at >= wallets[2].created_at);
718	}
719
720	#[tokio::test]
721	async fn test_get_wallet() {
722		let (wallet_manager, _temp_dir) = create_test_wallet_manager().await;
723
724		// Create a wallet
725		let created_wallet = wallet_manager
726			.create_wallet("test-get-wallet", Some("test-password"))
727			.await
728			.expect("Failed to create wallet");
729
730		// Test getting wallet without password
731		let wallet_info = wallet_manager
732			.get_wallet("test-get-wallet", None)
733			.expect("Failed to get wallet")
734			.expect("Wallet should exist");
735
736		assert_eq!(wallet_info.name, "test-get-wallet");
737		assert_eq!(wallet_info.address, created_wallet.address); // Now returns real address
738
739		// Test getting wallet with wrong password
740		// Now with real quantum-safe encryption, wrong password should be detected
741		let wallet_info = wallet_manager
742			.get_wallet("test-get-wallet", Some("wrong-password"))
743			.expect("Failed to get wallet")
744			.expect("Wallet should exist");
745
746		assert_eq!(wallet_info.name, "test-get-wallet");
747		// With real encryption, wrong password returns placeholder text
748		assert_eq!(wallet_info.address, "[Wrong password]");
749
750		// Test getting wallet with correct password
751		let wallet_info = wallet_manager
752			.get_wallet("test-get-wallet", Some("test-password"))
753			.expect("Failed to get wallet")
754			.expect("Wallet should exist");
755
756		assert_eq!(wallet_info.name, "test-get-wallet");
757		assert_eq!(wallet_info.address, created_wallet.address);
758		assert!(wallet_info.address.starts_with("qz"));
759
760		// Test getting non-existent wallet
761		let result = wallet_manager
762			.get_wallet("non-existent-wallet", None)
763			.expect("Should not error on non-existent wallet");
764
765		assert!(result.is_none());
766	}
767}