1use crate::error::WalletError;
2use aes_gcm::{
3 aead::{Aead, AeadCore, KeyInit, OsRng},
4 Aes256Gcm, Key, Nonce,
5};
6use base64::{engine::general_purpose, Engine as _};
7use bip39::{Language, Mnemonic};
8use datalayer_driver::{
9 address_to_puzzle_hash, connect_random, get_coin_id, master_public_key_to_first_puzzle_hash,
10 master_public_key_to_wallet_synthetic_key, master_secret_key_to_wallet_synthetic_secret_key,
11 puzzle_hash_to_address, secret_key_to_public_key, sign_message, verify_signature, Bytes,
12 Bytes32, Coin, CoinSpend, NetworkType, Peer, PublicKey, SecretKey, Signature,
13};
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::env;
17use std::fs;
18use std::path::PathBuf;
19
20const KEYRING_FILE: &str = "keyring.json";
21#[allow(dead_code)]
23const CACHE_DURATION_MS: u64 = 5 * 60 * 1000; pub const DEFAULT_FEE_COIN_COST: u64 = 64_000_000;
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27struct EncryptedData {
28 data: String,
29 nonce: String,
30 salt: String,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
34struct KeyringData {
35 wallets: HashMap<String, EncryptedData>,
36}
37
38pub struct Wallet {
39 mnemonic: Option<String>,
40 wallet_name: String,
41}
42
43impl Wallet {
44 fn new(mnemonic: Option<String>, wallet_name: String) -> Self {
46 Self {
47 mnemonic,
48 wallet_name,
49 }
50 }
51
52 pub async fn load(
54 wallet_name: Option<String>,
55 create_on_undefined: bool,
56 ) -> Result<Self, WalletError> {
57 let name = wallet_name.unwrap_or_else(|| "default".to_string());
58
59 if let Some(mnemonic) = Self::get_wallet_from_keyring(&name).await? {
60 return Ok(Self::new(Some(mnemonic), name));
61 }
62
63 if create_on_undefined {
64 let new_mnemonic = Self::create_new_wallet(&name).await?;
67 return Ok(Self::new(Some(new_mnemonic), name));
68 }
69
70 Err(WalletError::WalletNotFound(name))
71 }
72
73 pub fn get_mnemonic(&self) -> Result<&str, WalletError> {
75 self.mnemonic
76 .as_deref()
77 .ok_or(WalletError::MnemonicNotLoaded)
78 }
79
80 pub fn get_wallet_name(&self) -> &str {
82 &self.wallet_name
83 }
84
85 pub async fn create_new_wallet(wallet_name: &str) -> Result<String, WalletError> {
87 let entropy = rand::random::<[u8; 32]>(); let mnemonic = Mnemonic::from_entropy_in(Language::English, &entropy)
89 .map_err(|_| WalletError::CryptoError("Failed to generate mnemonic".to_string()))?;
90 let mnemonic_str = mnemonic.to_string();
91 Self::save_wallet_to_keyring(wallet_name, &mnemonic_str).await?;
92 Ok(mnemonic_str)
93 }
94
95 pub async fn import_wallet(
97 wallet_name: &str,
98 seed: Option<&str>,
99 ) -> Result<String, WalletError> {
100 let mnemonic_str = match seed {
101 Some(s) => s.to_string(),
102 None => {
103 return Err(WalletError::MnemonicRequired);
105 }
106 };
107
108 Mnemonic::parse_in_normalized(Language::English, &mnemonic_str)
110 .map_err(|_| WalletError::InvalidMnemonic)?;
111
112 Self::save_wallet_to_keyring(wallet_name, &mnemonic_str).await?;
113 Ok(mnemonic_str)
114 }
115
116 pub async fn get_master_secret_key(&self) -> Result<SecretKey, WalletError> {
118 let mnemonic_str = self.get_mnemonic()?;
119 let mnemonic = Mnemonic::parse_in_normalized(Language::English, mnemonic_str)
120 .map_err(|_| WalletError::InvalidMnemonic)?;
121
122 let seed = mnemonic.to_seed("");
123 let sk = SecretKey::from_seed(&seed);
124 Ok(sk)
125 }
126
127 pub async fn get_public_synthetic_key(&self) -> Result<PublicKey, WalletError> {
129 let master_sk = self.get_master_secret_key().await?;
130 let master_pk = secret_key_to_public_key(&master_sk);
131 Ok(master_public_key_to_wallet_synthetic_key(&master_pk))
132 }
133
134 pub async fn get_private_synthetic_key(&self) -> Result<SecretKey, WalletError> {
136 let master_sk = self.get_master_secret_key().await?;
137 Ok(master_secret_key_to_wallet_synthetic_secret_key(&master_sk))
138 }
139
140 pub async fn get_owner_puzzle_hash(&self) -> Result<Bytes32, WalletError> {
142 let master_sk = self.get_master_secret_key().await?;
143 let master_pk = secret_key_to_public_key(&master_sk);
144 Ok(master_public_key_to_first_puzzle_hash(&master_pk))
145 }
146
147 pub async fn get_owner_public_key(&self) -> Result<String, WalletError> {
149 let owner_puzzle_hash = self.get_owner_puzzle_hash().await?;
150 puzzle_hash_to_address(owner_puzzle_hash, "xch")
152 .map_err(|e| WalletError::CryptoError(format!("Failed to encode address: {}", e)))
153 }
154
155 pub async fn delete_wallet(wallet_name: &str) -> Result<bool, WalletError> {
157 let keyring_path = Self::get_keyring_path()?;
158
159 if !keyring_path.exists() {
160 return Ok(false);
161 }
162
163 let content = fs::read_to_string(&keyring_path)
164 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
165
166 let mut keyring: KeyringData = serde_json::from_str(&content)
167 .map_err(|e| WalletError::SerializationError(e.to_string()))?;
168
169 if keyring.wallets.remove(wallet_name).is_some() {
170 let updated_content = serde_json::to_string_pretty(&keyring)
171 .map_err(|e| WalletError::SerializationError(e.to_string()))?;
172
173 fs::write(&keyring_path, updated_content)
174 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
175
176 Ok(true)
177 } else {
178 Ok(false)
179 }
180 }
181
182 pub async fn list_wallets() -> Result<Vec<String>, WalletError> {
184 let keyring_path = Self::get_keyring_path()?;
185
186 if !keyring_path.exists() {
187 return Ok(vec![]);
188 }
189
190 let content = fs::read_to_string(&keyring_path)
191 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
192
193 let keyring: KeyringData = serde_json::from_str(&content)
194 .map_err(|e| WalletError::SerializationError(e.to_string()))?;
195
196 Ok(keyring.wallets.keys().cloned().collect())
197 }
198
199 pub async fn create_key_ownership_signature(&self, nonce: &str) -> Result<String, WalletError> {
201 let message = format!(
202 "Signing this message to prove ownership of key.\n\nNonce: {}",
203 nonce
204 );
205 let private_synthetic_key = self.get_private_synthetic_key().await?;
206
207 let signature = sign_message(
208 Bytes::from(message.as_bytes().to_vec()),
209 private_synthetic_key,
210 )
211 .map_err(|e| WalletError::CryptoError(e.to_string()))?;
212
213 Ok(hex::encode(signature.to_bytes()))
214 }
215
216 pub async fn verify_key_ownership_signature(
218 nonce: &str,
219 signature: &str,
220 public_key: &str,
221 ) -> Result<bool, WalletError> {
222 let message = format!(
223 "Signing this message to prove ownership of key.\n\nNonce: {}",
224 nonce
225 );
226
227 let sig_bytes =
228 hex::decode(signature).map_err(|e| WalletError::CryptoError(e.to_string()))?;
229
230 let pk_bytes =
231 hex::decode(public_key).map_err(|e| WalletError::CryptoError(e.to_string()))?;
232
233 if pk_bytes.len() != 48 {
234 return Err(WalletError::CryptoError(
235 "Invalid public key length".to_string(),
236 ));
237 }
238
239 let mut pk_array = [0u8; 48];
240 pk_array.copy_from_slice(&pk_bytes);
241
242 let public_key = PublicKey::from_bytes(&pk_array)
243 .map_err(|e| WalletError::CryptoError(e.to_string()))?;
244
245 if sig_bytes.len() != 96 {
246 return Err(WalletError::CryptoError(
247 "Invalid signature length".to_string(),
248 ));
249 }
250
251 let mut sig_array = [0u8; 96];
252 sig_array.copy_from_slice(&sig_bytes);
253
254 let signature = Signature::from_bytes(&sig_array)
255 .map_err(|e| WalletError::CryptoError(e.to_string()))?;
256
257 verify_signature(
258 Bytes::from(message.as_bytes().to_vec()),
259 public_key,
260 signature,
261 )
262 .map_err(|e| WalletError::CryptoError(e.to_string()))
263 }
264
265 pub async fn select_unspent_coins(
267 &self,
268 peer: &Peer,
269 coin_amount: u64,
270 fee: u64,
271 omit_coins: Vec<Coin>,
272 ) -> Result<Vec<Coin>, WalletError> {
273 let owner_puzzle_hash = self.get_owner_puzzle_hash().await?;
274 let total_needed = coin_amount + fee;
275
276 let coin_states = datalayer_driver::async_api::get_all_unspent_coins_rust(
278 peer,
279 owner_puzzle_hash,
280 None, datalayer_driver::constants::get_mainnet_genesis_challenge(), )
283 .await
284 .map_err(|e| WalletError::NetworkError(format!("Failed to get unspent coins: {}", e)))?;
285
286 let omit_coin_ids: Vec<Bytes32> = omit_coins.iter().map(get_coin_id).collect();
288
289 let available_coins: Vec<Coin> = coin_states
290 .coin_states
291 .into_iter()
292 .map(|cs| cs.coin)
293 .filter(|coin| !omit_coin_ids.contains(&get_coin_id(coin)))
294 .collect();
295
296 let selected_coins = datalayer_driver::select_coins_rust(&available_coins, total_needed)
298 .map_err(|e| WalletError::DataLayerError(format!("Coin selection failed: {}", e)))?;
299
300 if selected_coins.is_empty() {
301 return Err(WalletError::NoUnspentCoins);
302 }
303
304 Ok(selected_coins)
305 }
306
307 pub async fn calculate_fee_for_coin_spends(
309 _peer: &Peer,
310 _coin_spends: Option<&[CoinSpend]>,
311 ) -> Result<u64, WalletError> {
312 Ok(1_000_000) }
315
316 pub async fn is_coin_spendable(peer: &Peer, coin_id: &Bytes32) -> Result<bool, WalletError> {
318 use datalayer_driver::async_api::is_coin_spent_rust;
319
320 let is_spent = is_coin_spent_rust(
322 peer,
323 *coin_id,
324 None, datalayer_driver::constants::get_mainnet_genesis_challenge(), )
327 .await
328 .map_err(|e| WalletError::NetworkError(format!("Failed to check coin status: {}", e)))?;
329
330 Ok(!is_spent)
332 }
333
334 pub async fn connect_random_peer(
336 network: NetworkType,
337 cert_path: &str,
338 key_path: &str,
339 ) -> Result<Peer, WalletError> {
340 connect_random(network, cert_path, key_path)
341 .await
342 .map_err(|e| WalletError::NetworkError(format!("Failed to connect to peer: {}", e)))
343 }
344
345 pub async fn connect_mainnet_peer() -> Result<Peer, WalletError> {
347 let home_dir = dirs::home_dir().ok_or_else(|| {
348 WalletError::FileSystemError("Could not find home directory".to_string())
349 })?;
350
351 let ssl_dir = home_dir
352 .join(".chia")
353 .join("mainnet")
354 .join("config")
355 .join("ssl")
356 .join("wallet");
357 let cert_path = ssl_dir.join("wallet_node.crt");
358 let key_path = ssl_dir.join("wallet_node.key");
359
360 Self::connect_random_peer(
361 NetworkType::Mainnet,
362 cert_path
363 .to_str()
364 .ok_or_else(|| WalletError::FileSystemError("Invalid cert path".to_string()))?,
365 key_path
366 .to_str()
367 .ok_or_else(|| WalletError::FileSystemError("Invalid key path".to_string()))?,
368 )
369 .await
370 }
371
372 pub async fn connect_testnet_peer() -> Result<Peer, WalletError> {
374 let home_dir = dirs::home_dir().ok_or_else(|| {
375 WalletError::FileSystemError("Could not find home directory".to_string())
376 })?;
377
378 let ssl_dir = home_dir
379 .join(".chia")
380 .join("testnet11")
381 .join("config")
382 .join("ssl")
383 .join("wallet");
384 let cert_path = ssl_dir.join("wallet_node.crt");
385 let key_path = ssl_dir.join("wallet_node.key");
386
387 Self::connect_random_peer(
388 NetworkType::Testnet11,
389 cert_path
390 .to_str()
391 .ok_or_else(|| WalletError::FileSystemError("Invalid cert path".to_string()))?,
392 key_path
393 .to_str()
394 .ok_or_else(|| WalletError::FileSystemError("Invalid key path".to_string()))?,
395 )
396 .await
397 }
398
399 pub fn address_to_puzzle_hash(address: &str) -> Result<Bytes32, WalletError> {
401 address_to_puzzle_hash(address)
402 .map_err(|e| WalletError::CryptoError(format!("Failed to decode address: {}", e)))
403 }
404
405 pub fn puzzle_hash_to_address(
407 puzzle_hash: Bytes32,
408 prefix: &str,
409 ) -> Result<String, WalletError> {
410 puzzle_hash_to_address(puzzle_hash, prefix)
411 .map_err(|e| WalletError::CryptoError(format!("Failed to encode address: {}", e)))
412 }
413
414 async fn get_wallet_from_keyring(wallet_name: &str) -> Result<Option<String>, WalletError> {
417 let keyring_path = Self::get_keyring_path()?;
418
419 if !keyring_path.exists() {
420 return Ok(None);
421 }
422
423 let content = fs::read_to_string(&keyring_path)
424 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
425
426 let keyring: KeyringData = serde_json::from_str(&content)
427 .map_err(|e| WalletError::SerializationError(e.to_string()))?;
428
429 if let Some(encrypted_data) = keyring.wallets.get(wallet_name) {
430 let decrypted = Self::decrypt_data(encrypted_data)?;
431 Ok(Some(decrypted))
432 } else {
433 Ok(None)
434 }
435 }
436
437 async fn save_wallet_to_keyring(wallet_name: &str, mnemonic: &str) -> Result<(), WalletError> {
438 let keyring_path = Self::get_keyring_path()?;
439
440 if let Some(parent) = keyring_path.parent() {
442 fs::create_dir_all(parent).map_err(|e| WalletError::FileSystemError(e.to_string()))?;
443 }
444
445 let mut keyring = if keyring_path.exists() {
446 let content = fs::read_to_string(&keyring_path)
447 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
448 serde_json::from_str(&content)
449 .map_err(|e| WalletError::SerializationError(e.to_string()))?
450 } else {
451 KeyringData {
452 wallets: HashMap::new(),
453 }
454 };
455
456 let encrypted_data = Self::encrypt_data(mnemonic)?;
457
458 keyring
459 .wallets
460 .insert(wallet_name.to_string(), encrypted_data);
461
462 let content = serde_json::to_string_pretty(&keyring)
463 .map_err(|e| WalletError::SerializationError(e.to_string()))?;
464
465 fs::write(&keyring_path, content)
466 .map_err(|e| WalletError::FileSystemError(e.to_string()))?;
467
468 Ok(())
469 }
470
471 fn get_keyring_path() -> Result<PathBuf, WalletError> {
472 if let Ok(test_path) = env::var("TEST_KEYRING_PATH") {
474 return Ok(PathBuf::from(test_path));
475 }
476
477 let home_dir = dirs::home_dir().ok_or_else(|| {
478 WalletError::FileSystemError("Could not find home directory".to_string())
479 })?;
480
481 Ok(home_dir.join(".dig").join(KEYRING_FILE))
482 }
483
484 fn encrypt_data(data: &str) -> Result<EncryptedData, WalletError> {
486 let salt = rand::random::<[u8; 16]>();
488
489 let mut key_bytes = [0u8; 32];
492 let password = b"mnemonic-seed"; for i in 0..32 {
496 key_bytes[i] = password[i % password.len()] ^ salt[i % salt.len()];
497 }
498
499 let key = Key::<Aes256Gcm>::from_slice(&key_bytes);
500 let cipher = Aes256Gcm::new(key);
501
502 let nonce = Aes256Gcm::generate_nonce(&mut OsRng);
504
505 let ciphertext = cipher
507 .encrypt(&nonce, data.as_bytes())
508 .map_err(|e| WalletError::CryptoError(format!("Encryption failed: {}", e)))?;
509
510 Ok(EncryptedData {
511 data: general_purpose::STANDARD.encode(&ciphertext),
512 nonce: general_purpose::STANDARD.encode(nonce),
513 salt: general_purpose::STANDARD.encode(salt),
514 })
515 }
516
517 fn decrypt_data(encrypted_data: &EncryptedData) -> Result<String, WalletError> {
519 let ciphertext = general_purpose::STANDARD
520 .decode(&encrypted_data.data)
521 .map_err(|e| WalletError::CryptoError(format!("Failed to decode ciphertext: {}", e)))?;
522
523 let nonce_bytes = general_purpose::STANDARD
524 .decode(&encrypted_data.nonce)
525 .map_err(|e| WalletError::CryptoError(format!("Failed to decode nonce: {}", e)))?;
526
527 let salt = general_purpose::STANDARD
528 .decode(&encrypted_data.salt)
529 .map_err(|e| WalletError::CryptoError(format!("Failed to decode salt: {}", e)))?;
530
531 let mut key_bytes = [0u8; 32];
533 let password = b"mnemonic-seed";
534
535 for i in 0..32 {
536 key_bytes[i] = password[i % password.len()] ^ salt[i % salt.len()];
537 }
538
539 let key = Key::<Aes256Gcm>::from_slice(&key_bytes);
540 let cipher = Aes256Gcm::new(key);
541
542 let nonce = Nonce::from_slice(&nonce_bytes);
543
544 let plaintext = cipher
546 .decrypt(nonce, ciphertext.as_ref())
547 .map_err(|e| WalletError::CryptoError(format!("Decryption failed: {}", e)))?;
548
549 String::from_utf8(plaintext).map_err(|e| {
550 WalletError::CryptoError(format!("Failed to convert decrypted data to string: {}", e))
551 })
552 }
553}
554
555#[cfg(test)]
556mod tests {
557 use super::*;
558 use std::env;
559 use tempfile::TempDir;
560
561 fn setup_test_env() -> TempDir {
563 let temp_dir = TempDir::new().unwrap();
564
565 let keyring_path = temp_dir.path().join("test_keyring.json");
567 env::set_var(
568 "TEST_KEYRING_PATH",
569 keyring_path.to_string_lossy().to_string(),
570 );
571
572 env::set_var("HOME", temp_dir.path());
574
575 temp_dir
576 }
577
578 #[tokio::test]
579 async fn test_wallet_creation() {
580 let _temp_dir = setup_test_env();
581
582 let mnemonic = Wallet::create_new_wallet("test_wallet").await.unwrap();
584
585 assert!(bip39::Mnemonic::parse_in_normalized(Language::English, &mnemonic).is_ok());
587
588 assert_eq!(mnemonic.split_whitespace().count(), 24);
590
591 let wallets = Wallet::list_wallets().await.unwrap();
593 assert!(wallets.contains(&"test_wallet".to_string()));
594 }
595
596 #[tokio::test]
597 async fn test_wallet_import() {
598 let _temp_dir = setup_test_env();
599
600 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";
602
603 let imported_mnemonic = Wallet::import_wallet("imported_wallet", Some(test_mnemonic))
605 .await
606 .unwrap();
607
608 assert_eq!(imported_mnemonic, test_mnemonic);
610
611 let wallet = Wallet::load(Some("imported_wallet".to_string()), false)
613 .await
614 .unwrap();
615 assert_eq!(wallet.get_mnemonic().unwrap(), test_mnemonic);
616 }
617
618 #[tokio::test]
619 async fn test_wallet_import_invalid_mnemonic() {
620 let _temp_dir = setup_test_env();
621
622 let invalid_mnemonic = "invalid mnemonic phrase that should fail validation";
624
625 let result = Wallet::import_wallet("invalid_wallet", Some(invalid_mnemonic)).await;
627 assert!(matches!(result, Err(WalletError::InvalidMnemonic)));
628 }
629
630 #[tokio::test]
631 async fn test_wallet_load_nonexistent() {
632 let _temp_dir = setup_test_env();
633
634 let result = Wallet::load(Some("nonexistent".to_string()), false).await;
636 assert!(matches!(result, Err(WalletError::WalletNotFound(_))));
637 }
638
639 #[tokio::test]
640 async fn test_wallet_load_with_creation() {
641 let _temp_dir = setup_test_env();
642
643 let wallet = Wallet::load(Some("auto_created".to_string()), true)
645 .await
646 .unwrap();
647
648 let mnemonic = wallet.get_mnemonic().unwrap();
650 assert!(bip39::Mnemonic::parse_in_normalized(Language::English, mnemonic).is_ok());
651
652 assert_eq!(wallet.get_wallet_name(), "auto_created");
654 }
655
656 #[tokio::test]
657 async fn test_key_derivation() {
658 let _temp_dir = setup_test_env();
659
660 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";
662
663 Wallet::import_wallet("key_test", Some(test_mnemonic))
664 .await
665 .unwrap();
666 let wallet = Wallet::load(Some("key_test".to_string()), false)
667 .await
668 .unwrap();
669
670 let master_sk = wallet.get_master_secret_key().await.unwrap();
672 let public_synthetic_key = wallet.get_public_synthetic_key().await.unwrap();
673 let private_synthetic_key = wallet.get_private_synthetic_key().await.unwrap();
674 let puzzle_hash = wallet.get_owner_puzzle_hash().await.unwrap();
675
676 assert_eq!(
678 secret_key_to_public_key(&private_synthetic_key),
679 public_synthetic_key
680 );
681
682 assert_eq!(puzzle_hash.as_ref().len(), 32);
684
685 let wallet2 = Wallet::load(Some("key_test".to_string()), false)
687 .await
688 .unwrap();
689 let master_sk2 = wallet2.get_master_secret_key().await.unwrap();
690 assert_eq!(master_sk.to_bytes(), master_sk2.to_bytes());
691 }
692
693 #[tokio::test]
694 async fn test_address_generation() {
695 let _temp_dir = setup_test_env();
696
697 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";
698
699 Wallet::import_wallet("address_test", Some(test_mnemonic))
700 .await
701 .unwrap();
702 let wallet = Wallet::load(Some("address_test".to_string()), false)
703 .await
704 .unwrap();
705
706 let address = wallet.get_owner_public_key().await.unwrap();
708
709 assert!(address.starts_with("xch1"));
711
712 assert!(address.len() >= 60 && address.len() <= 65);
714
715 let puzzle_hash = Wallet::address_to_puzzle_hash(&address).unwrap();
717 let converted_address = Wallet::puzzle_hash_to_address(puzzle_hash, "xch").unwrap();
718 assert_eq!(address, converted_address);
719 }
720
721 #[tokio::test]
722 async fn test_signature_creation_and_verification() {
723 let _temp_dir = setup_test_env();
724
725 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";
726
727 Wallet::import_wallet("sig_test", Some(test_mnemonic))
728 .await
729 .unwrap();
730 let wallet = Wallet::load(Some("sig_test".to_string()), false)
731 .await
732 .unwrap();
733
734 let nonce = "test_nonce_12345";
736 let signature = wallet.create_key_ownership_signature(nonce).await.unwrap();
737
738 assert!(hex::decode(&signature).is_ok());
740
741 let public_key = wallet.get_public_synthetic_key().await.unwrap();
743 let public_key_hex = hex::encode(public_key.to_bytes());
744
745 let is_valid = Wallet::verify_key_ownership_signature(nonce, &signature, &public_key_hex)
747 .await
748 .unwrap();
749 assert!(is_valid);
750
751 let is_valid_wrong =
753 Wallet::verify_key_ownership_signature("wrong_nonce", &signature, &public_key_hex)
754 .await
755 .unwrap();
756 assert!(!is_valid_wrong);
757 }
758
759 #[tokio::test]
760 async fn test_wallet_deletion() {
761 let _temp_dir = setup_test_env();
762
763 Wallet::create_new_wallet("delete_test").await.unwrap();
765
766 let wallets_before = Wallet::list_wallets().await.unwrap();
768 assert!(wallets_before.contains(&"delete_test".to_string()));
769
770 let deleted = Wallet::delete_wallet("delete_test").await.unwrap();
772 assert!(deleted);
773
774 let wallets_after = Wallet::list_wallets().await.unwrap();
776 assert!(!wallets_after.contains(&"delete_test".to_string()));
777
778 let not_deleted = Wallet::delete_wallet("nonexistent").await.unwrap();
780 assert!(!not_deleted);
781 }
782
783 #[tokio::test]
784 async fn test_multiple_wallets() {
785 let _temp_dir = setup_test_env();
786
787 Wallet::create_new_wallet("wallet1").await.unwrap();
789 Wallet::create_new_wallet("wallet2").await.unwrap();
790 Wallet::create_new_wallet("wallet3").await.unwrap();
791
792 let mut wallets = Wallet::list_wallets().await.unwrap();
794 wallets.sort(); assert_eq!(wallets.len(), 3);
797 assert!(wallets.contains(&"wallet1".to_string()));
798 assert!(wallets.contains(&"wallet2".to_string()));
799 assert!(wallets.contains(&"wallet3".to_string()));
800
801 let w1 = Wallet::load(Some("wallet1".to_string()), false)
803 .await
804 .unwrap();
805 let w2 = Wallet::load(Some("wallet2".to_string()), false)
806 .await
807 .unwrap();
808 let w3 = Wallet::load(Some("wallet3".to_string()), false)
809 .await
810 .unwrap();
811
812 assert_ne!(w1.get_mnemonic().unwrap(), w2.get_mnemonic().unwrap());
813 assert_ne!(w2.get_mnemonic().unwrap(), w3.get_mnemonic().unwrap());
814 assert_ne!(w1.get_mnemonic().unwrap(), w3.get_mnemonic().unwrap());
815 }
816
817 #[tokio::test]
818 async fn test_encryption_decryption() {
819 let test_data = "test mnemonic phrase for encryption";
821
822 let encrypted = Wallet::encrypt_data(test_data).unwrap();
823
824 assert_ne!(encrypted.data, test_data);
826 assert!(!encrypted.nonce.is_empty());
827 assert!(!encrypted.salt.is_empty());
828
829 let decrypted = Wallet::decrypt_data(&encrypted).unwrap();
831 assert_eq!(decrypted, test_data);
832 }
833
834 #[tokio::test]
835 async fn test_encryption_with_different_salts() {
836 let test_data = "same data";
837
838 let encrypted1 = Wallet::encrypt_data(test_data).unwrap();
840 let encrypted2 = Wallet::encrypt_data(test_data).unwrap();
841
842 assert_ne!(encrypted1.data, encrypted2.data);
844 assert_ne!(encrypted1.salt, encrypted2.salt);
845 assert_ne!(encrypted1.nonce, encrypted2.nonce);
846
847 let decrypted1 = Wallet::decrypt_data(&encrypted1).unwrap();
849 let decrypted2 = Wallet::decrypt_data(&encrypted2).unwrap();
850 assert_eq!(decrypted1, test_data);
851 assert_eq!(decrypted2, test_data);
852 }
853
854 #[tokio::test]
855 async fn test_invalid_signature_verification() {
856 let _temp_dir = setup_test_env();
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";
860 Wallet::import_wallet("invalid_sig_test", Some(test_mnemonic))
861 .await
862 .unwrap();
863 let wallet = Wallet::load(Some("invalid_sig_test".to_string()), false)
864 .await
865 .unwrap();
866
867 let public_key = wallet.get_public_synthetic_key().await.unwrap();
868 let public_key_hex = hex::encode(public_key.to_bytes());
869
870 let result =
872 Wallet::verify_key_ownership_signature("nonce", "invalid_hex", &public_key_hex).await;
873 assert!(result.is_err());
874
875 let short_sig = "deadbeef";
877 let result =
878 Wallet::verify_key_ownership_signature("nonce", short_sig, &public_key_hex).await;
879 assert!(result.is_err());
880
881 let result =
883 Wallet::verify_key_ownership_signature("nonce", &"a".repeat(192), "invalid_key").await;
884 assert!(result.is_err());
885 }
886
887 #[tokio::test]
888 async fn test_address_conversion_errors() {
889 let result = Wallet::address_to_puzzle_hash("invalid_address");
891 assert!(result.is_err());
892
893 let result = Wallet::address_to_puzzle_hash("");
895 assert!(result.is_err());
896 }
897
898 #[tokio::test]
899 async fn test_mnemonic_not_loaded_error() {
900 let wallet = Wallet::new(None, "empty_wallet".to_string());
902
903 let result = wallet.get_mnemonic();
905 assert!(matches!(result, Err(WalletError::MnemonicNotLoaded)));
906
907 let result = wallet.get_master_secret_key().await;
909 assert!(matches!(result, Err(WalletError::MnemonicNotLoaded)));
910 }
911
912 #[tokio::test]
913 async fn test_default_wallet_name() {
914 let _temp_dir = setup_test_env();
915
916 let wallet = Wallet::load(None, true).await.unwrap();
918 assert_eq!(wallet.get_wallet_name(), "default");
919
920 let wallets = Wallet::list_wallets().await.unwrap();
922 assert!(wallets.contains(&"default".to_string()));
923 }
924}