1use std::{collections::HashMap, fs::File, io::Write, path::PathBuf};
2
3use primitive_types::H160;
4use rayon::prelude::*;
5use serde_derive::{Deserialize, Serialize};
6
7use crate::{
8 neo_builder::{AccountSigner, Transaction, TransactionBuilder, VerificationScript, Witness},
9 neo_clients::{APITrait, JsonRpcProvider, ProviderError, RpcClient},
10 neo_config::NeoConstants,
11 neo_contract::ContractError,
12 neo_crypto::{CryptoError, HashableForVec, KeyPair, Secp256r1Signature},
13 neo_protocol::{Account, AccountTrait, UnclaimedGas},
14 neo_types::{
15 contract::ContractMethodToken,
16 script_hash::ScriptHashExtension,
17 serde_with_utils::{
18 deserialize_hash_map_h160_account, deserialize_script_hash,
19 serialize_hash_map_h160_account, serialize_script_hash,
20 },
21 AddressExtension, ScryptParamsDef,
22 },
23 neo_wallets::{NEP6Account, NEP6Contract, NEP6Parameter, Nep6Wallet, WalletError, WalletTrait},
24};
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Wallet {
28 pub name: String,
29 pub version: String,
30 pub scrypt_params: ScryptParamsDef,
31 #[serde(deserialize_with = "deserialize_hash_map_h160_account")]
32 #[serde(serialize_with = "serialize_hash_map_h160_account")]
33 pub accounts: HashMap<H160, Account>,
34 #[serde(deserialize_with = "deserialize_script_hash")]
35 #[serde(serialize_with = "serialize_script_hash")]
36 pub(crate) default_account: H160,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub extra: Option<HashMap<String, String>>,
40}
41
42impl WalletTrait for Wallet {
43 type Account = Account;
44
45 fn name(&self) -> &String {
46 &self.name
47 }
48
49 fn version(&self) -> &String {
50 &self.version
51 }
52
53 fn scrypt_params(&self) -> &ScryptParamsDef {
54 &self.scrypt_params
55 }
56
57 fn accounts(&self) -> Vec<Self::Account> {
58 self.accounts
59 .clone()
60 .into_iter()
61 .map(|(_k, v)| v.clone())
62 .collect::<Vec<Self::Account>>()
63 }
64
65 fn default_account(&self) -> &Account {
66 &self.accounts[&self.default_account]
67 }
68
69 fn set_name(&mut self, name: String) {
70 self.name = name;
71 }
72
73 fn set_version(&mut self, version: String) {
74 self.version = version;
75 }
76
77 fn set_scrypt_params(&mut self, params: ScryptParamsDef) {
78 self.scrypt_params = params;
79 }
80
81 fn set_default_account(&mut self, default_account: H160) {
82 self.default_account = default_account.clone();
83 if let Some(account) = self.accounts.get_mut(&self.default_account) {
84 account.is_default = true;
85 }
86 }
87
88 fn add_account(&mut self, account: Self::Account) {
89 self.accounts.insert(account.get_script_hash().clone(), account);
92 }
93
94 fn remove_account(&mut self, hash: &H160) -> Option<Self::Account> {
95 self.accounts.remove(hash)
96 }
97}
98
99impl Wallet {
100 pub const DEFAULT_WALLET_NAME: &'static str = "NeoWallet";
102 pub const CURRENT_VERSION: &'static str = "1.0";
104
105 pub fn new() -> Self {
107 let account = match Account::create() {
108 Ok(mut acc) => {
109 acc.is_default = true;
110 acc
111 },
112 Err(e) => {
113 eprintln!("Failed to create account: {}", e);
114 return Self::default();
115 },
116 };
117
118 let mut accounts = HashMap::new();
119 accounts.insert(account.address_or_scripthash.script_hash(), account.clone());
120 Self {
121 name: "NeoWallet".to_string(),
122 version: "1.0".to_string(),
123 scrypt_params: ScryptParamsDef::default(),
124 accounts,
125 default_account: account.clone().address_or_scripthash.script_hash(),
126 extra: None,
127 }
128 }
129
130 pub fn default() -> Self {
132 Self {
133 name: "NeoWallet".to_string(),
134 version: "1.0".to_string(),
135 scrypt_params: ScryptParamsDef::default(),
136 accounts: HashMap::new(),
137 default_account: H160::default(),
138 extra: None,
139 }
140 }
141
142 pub fn to_nep6(&self) -> Result<Nep6Wallet, WalletError> {
144 let accounts = self
145 .accounts
146 .clone()
147 .into_iter()
148 .filter_map(|(_, account)| match NEP6Account::from_account(&account) {
149 Ok(nep6_account) => Some(nep6_account),
150 Err(e) => {
151 eprintln!("Failed to convert account to NEP6Account: {}", e);
152 None
153 },
154 })
155 .collect::<Vec<NEP6Account>>();
156
157 Ok(Nep6Wallet {
158 name: self.name.clone(),
159 version: self.version.clone(),
160 scrypt: self.scrypt_params.clone(),
161 accounts,
162 extra: None,
163 })
164 }
165
166 pub fn from_nep6(nep6: Nep6Wallet) -> Result<Self, WalletError> {
168 let accounts = nep6
169 .accounts()
170 .into_iter()
171 .filter_map(|v| v.to_account().ok())
172 .collect::<Vec<_>>();
173
174 let default_account_address =
176 if let Some(account) = nep6.accounts().iter().find(|a| a.is_default) {
177 account.address().clone()
178 } else if let Some(account) = nep6.accounts().first() {
179 eprintln!("No default account found, using first account");
180 account.address().clone()
181 } else {
182 eprintln!("No accounts found, using empty address");
183 String::new()
184 };
185
186 Ok(Self {
187 name: nep6.name().clone(),
188 version: nep6.version().clone(),
189 scrypt_params: nep6.scrypt().clone(),
190 accounts: accounts.into_iter().map(|a| (a.get_script_hash().clone(), a)).collect(),
191 default_account: default_account_address.address_to_script_hash().map_err(|e| {
192 WalletError::AccountState(format!(
193 "Failed to convert address to script hash: {}",
194 e
195 ))
196 })?,
197 extra: nep6.extra.clone(),
198 })
199 }
200
201 pub fn from_account(account: &Account) -> Result<Wallet, WalletError> {
214 let mut wallet: Wallet = Wallet::new();
215 wallet.add_account(account.clone());
216 wallet.set_default_account(account.get_script_hash());
217 Ok(wallet)
218 }
219
220 pub fn from_accounts(accounts: Vec<Account>) -> Result<Wallet, WalletError> {
246 let mut wallet: Wallet = Wallet::default();
256 for account in &accounts {
257 wallet.add_account(account.clone());
258 }
260 if let Some(first_account) = accounts.first() {
261 wallet.set_default_account(first_account.get_script_hash());
262 } else {
263 return Err(WalletError::NoAccounts);
264 }
265 Ok(wallet)
266 }
267
268 pub fn save_to_file(&self, path: PathBuf) -> Result<(), WalletError> {
269 let nep6 = self.to_nep6()?;
271
272 let json = serde_json::to_string(&nep6).map_err(|e| {
274 WalletError::AccountState(format!("Failed to serialize wallet to JSON: {e}"))
275 })?;
276
277 let mut file = File::create(path)
279 .map_err(|e| WalletError::FileError(format!("Failed to create wallet file: {e}")))?;
280 file.write_all(json.as_bytes())
281 .map_err(|e| WalletError::FileError(format!("Failed to write wallet file: {e}")))?;
282
283 Ok(())
284 }
285
286 pub fn get_account(&self, script_hash: &H160) -> Option<&Account> {
287 self.accounts.get(script_hash)
288 }
289
290 pub fn remove_account(&mut self, script_hash: &H160) -> bool {
291 self.accounts.remove(script_hash).is_some()
292 }
293
294 pub fn encrypt_accounts(&mut self, password: &str) {
295 for account in self.accounts.values_mut() {
296 if account.key_pair().is_some() {
298 if let Err(e) = account.encrypt_private_key(password) {
299 eprintln!(
300 "Warning: Failed to encrypt private key for account {}: {}",
301 account.get_address(),
302 e
303 );
304 }
305 }
306 }
307 }
308
309 pub fn encrypt_accounts_parallel(&mut self, password: &str) {
336 let errors: Vec<(String, String)> = self
338 .accounts
339 .par_iter_mut()
340 .filter_map(|(_, account)| {
341 if account.key_pair().is_some() {
343 match account.encrypt_private_key(password) {
344 Err(e) => Some((account.get_address(), e.to_string())),
345 Ok(_) => None,
346 }
347 } else {
348 None
349 }
350 })
351 .collect();
352
353 for (address, error) in errors {
355 eprintln!("Warning: Failed to encrypt private key for account {}: {}", address, error);
356 }
357 }
358
359 pub fn encrypt_accounts_parallel_with_threads(&mut self, password: &str, num_threads: usize) {
379 let pool = rayon::ThreadPoolBuilder::new().num_threads(num_threads).build().unwrap();
381
382 pool.install(|| {
383 self.encrypt_accounts_parallel(password);
384 });
385 }
386
387 pub fn encrypt_accounts_batch_parallel(&mut self, password: &str, batch_size: usize) {
408 use std::sync::{Arc, Mutex};
409
410 let accounts_to_encrypt: Vec<(H160, Account)> = self
412 .accounts
413 .iter()
414 .filter(|(_, account)| account.key_pair().is_some())
415 .map(|(hash, account)| (*hash, account.clone()))
416 .collect();
417
418 let results: Arc<Mutex<Vec<(H160, Result<Account, String>)>>> =
420 Arc::new(Mutex::new(Vec::new()));
421
422 accounts_to_encrypt.par_chunks(batch_size).for_each(|batch| {
423 let batch_results: Vec<(H160, Result<Account, String>)> = batch
424 .iter()
425 .map(|(hash, account)| {
426 let mut account_clone = account.clone();
427 match account_clone.encrypt_private_key(password) {
428 Ok(_) => (*hash, Ok(account_clone)),
429 Err(e) => (*hash, Err(format!("{}: {}", account.get_address(), e))),
430 }
431 })
432 .collect();
433
434 results.lock().unwrap().extend(batch_results);
435 });
436
437 let results = Arc::try_unwrap(results).unwrap().into_inner().unwrap();
439 for (hash, result) in results {
440 match result {
441 Ok(encrypted_account) => {
442 self.accounts.insert(hash, encrypted_account);
443 },
444 Err(error_msg) => {
445 eprintln!("Warning: Failed to encrypt private key for account {}", error_msg);
446 },
447 }
448 }
449 }
450
451 #[deprecated(since = "0.1.0", note = "Please use `create_wallet` instead")]
465 pub fn create(path: &PathBuf, password: &str) -> Result<Self, WalletError> {
466 Self::create_wallet(path, password)
467 }
468
469 #[deprecated(since = "0.1.0", note = "Please use `open_wallet` instead")]
483 pub fn open(path: &PathBuf, password: &str) -> Result<Self, WalletError> {
484 Self::open_wallet(path, password)
485 }
486
487 pub fn get_accounts(&self) -> Vec<&Account> {
489 self.accounts.values().collect()
490 }
491
492 pub fn create_account(&mut self) -> Result<&Account, WalletError> {
494 let account = Account::create()?;
495 self.add_account(account.clone());
496 Ok(self.get_account(&account.get_script_hash()).unwrap())
497 }
498
499 pub fn import_private_key(&mut self, wif: &str) -> Result<&Account, WalletError> {
501 let key_pair = KeyPair::from_wif(wif)
502 .map_err(|e| WalletError::AccountState(format!("Failed to import private key: {e}")))?;
503
504 let account = Account::from_key_pair(key_pair, None, None)
505 .map_err(|e| WalletError::ProviderError(e))?;
506 self.add_account(account.clone());
507 Ok(self.get_account(&account.get_script_hash()).unwrap())
508 }
509
510 pub fn verify_password(&self, password: &str) -> bool {
518 if self.accounts.is_empty() {
520 return false;
521 }
522
523 for account in self.accounts.values() {
525 if account.encrypted_private_key().is_none() {
527 continue;
528 }
529
530 if account.key_pair().is_some() {
532 continue;
533 }
534
535 let mut account_clone = account.clone();
537 match account_clone.decrypt_private_key(password) {
538 Ok(_) => return true, Err(_) => continue, }
541 }
542
543 false
545 }
546
547 pub fn change_password(
549 &mut self,
550 current_password: &str,
551 new_password: &str,
552 ) -> Result<(), WalletError> {
553 if !self.verify_password(current_password) {
554 return Err(WalletError::AccountState("Invalid password".to_string()));
555 }
556
557 for account in self.accounts.values_mut() {
559 if account.encrypted_private_key().is_some() && account.key_pair().is_none() {
560 if let Err(e) = account.decrypt_private_key(current_password) {
561 return Err(WalletError::DecryptionError(format!(
562 "Failed to decrypt account {}: {}",
563 account.get_address(),
564 e
565 )));
566 }
567 }
568 }
569
570 self.encrypt_accounts(new_password);
572
573 Ok(())
574 }
575
576 pub fn change_password_parallel(
598 &mut self,
599 current_password: &str,
600 new_password: &str,
601 ) -> Result<(), WalletError> {
602 if !self.verify_password(current_password) {
603 return Err(WalletError::AccountState("Invalid password".to_string()));
604 }
605
606 let accounts_to_decrypt: Vec<(H160, Account)> = self
608 .accounts
609 .iter()
610 .filter(|(_, account)| {
611 account.encrypted_private_key().is_some() && account.key_pair().is_none()
612 })
613 .map(|(hash, account)| (*hash, account.clone()))
614 .collect();
615
616 let decrypted_results: Vec<(H160, Result<Account, String>)> = accounts_to_decrypt
618 .into_par_iter()
619 .map(|(hash, account)| {
620 let mut account_clone = account.clone();
621 match account_clone.decrypt_private_key(current_password) {
622 Ok(_) => (hash, Ok(account_clone)),
623 Err(e) => (hash, Err(format!("{}: {}", account.get_address(), e))),
624 }
625 })
626 .collect();
627
628 for (_, result) in &decrypted_results {
630 if let Err(error_msg) = result {
631 return Err(WalletError::DecryptionError(format!(
632 "Failed to decrypt account {}",
633 error_msg
634 )));
635 }
636 }
637
638 for (hash, result) in decrypted_results {
640 if let Ok(decrypted_account) = result {
641 self.accounts.insert(hash, decrypted_account);
642 }
643 }
644
645 self.encrypt_accounts_parallel(new_password);
647
648 Ok(())
649 }
650
651 pub async fn get_unclaimed_gas<P>(&self, rpc_client: &P) -> Result<UnclaimedGas, WalletError>
653 where
654 P: JsonRpcProvider + APITrait + 'static,
655 <P as APITrait>::Error: Into<ProviderError>,
656 {
657 let mut total_unclaimed = UnclaimedGas::default();
658
659 for account in self.get_accounts() {
660 let script_hash = account.get_script_hash();
661 let unclaimed = rpc_client
662 .get_unclaimed_gas(script_hash)
663 .await
664 .map_err(|e| WalletError::ProviderError(e.into()))?;
665
666 total_unclaimed += unclaimed;
667 }
668
669 Ok(total_unclaimed)
670 }
671}
672
673impl Wallet {
674 pub async fn sign_message<S: Send + Sync + AsRef<[u8]>>(
703 &self,
704 message: S,
705 ) -> Result<Secp256r1Signature, WalletError> {
706 let message = message.as_ref();
707 let binding = message.hash256();
708 let message_hash = binding.as_slice();
709 self.default_account()
710 .clone()
711 .key_pair()
712 .clone()
713 .ok_or_else(|| WalletError::NoKeyPair)?
714 .private_key()
715 .sign_tx(message_hash)
716 .map_err(|_e| WalletError::NoKeyPair)
717 }
718
719 pub async fn get_witness<'a, P: JsonRpcProvider + 'static>(
751 &self,
752 tx: &Transaction<'a, P>,
753 ) -> Result<Witness, WalletError> {
754 let mut tx_with_chain = tx.clone();
755 if tx_with_chain.network().is_none() {
756 }
759
760 Witness::create(
761 tx.get_hash_data().await?,
762 &self.default_account().key_pair.clone().ok_or_else(|| WalletError::NoKeyPair)?,
763 )
764 .map_err(|_e| WalletError::NoKeyPair)
765 }
766
767 pub async fn sign_transaction<'a, P>(
779 &self,
780 tx_builder: &'a mut TransactionBuilder<'a, P>,
781 account_address: &str,
782 password: &str,
783 ) -> Result<Transaction<'a, P>, WalletError>
784 where
785 P: JsonRpcProvider + 'static,
786 {
787 let script_hash = H160::from_address(account_address)
789 .map_err(|e| WalletError::AccountState(format!("Invalid address: {e}")))?;
790
791 let account = self.get_account(&script_hash).ok_or_else(|| {
792 WalletError::AccountState(format!("Account not found: {account_address}"))
793 })?;
794
795 let key_pair = match account.key_pair() {
797 Some(kp) => kp.clone(),
798 None => {
799 let mut account_clone = account.clone();
801 account_clone.decrypt_private_key(password).map_err(|e| {
802 WalletError::DecryptionError(format!("Failed to decrypt account: {e}"))
803 })?;
804
805 match account_clone.key_pair() {
806 Some(kp) => kp.clone(),
807 None => return Err(WalletError::NoKeyPair),
808 }
809 },
810 };
811
812 let mut tx = tx_builder.get_unsigned_tx().await?;
814
815 let witness = Witness::create(tx.get_hash_data().await?, &key_pair)
817 .map_err(|e| WalletError::SigningError(format!("Failed to create witness: {e}")))?;
818
819 tx.add_witness(witness);
821
822 Ok(tx)
823 }
824
825 fn address(&self) -> String {
835 if let Some(account) = self.get_account(&self.default_account) {
837 account.address_or_scripthash.address().clone()
838 } else {
839 H160::default().to_address()
841 }
842 }
843
844 pub fn create_wallet(path: &PathBuf, password: &str) -> Result<Self, WalletError> {
855 let mut wallet = Wallet::new();
856
857 let account = Account::create().map_err(|e| WalletError::ProviderError(e))?;
859 wallet.add_account(account);
860
861 wallet.encrypt_accounts(password);
863
864 wallet.save_to_file(path.clone())?;
866
867 Ok(wallet)
868 }
869
870 pub fn open_wallet(path: &PathBuf, password: &str) -> Result<Self, WalletError> {
881 let wallet_json = std::fs::read_to_string(path)
883 .map_err(|e| WalletError::FileError(format!("Failed to read wallet file: {e}")))?;
884
885 let nep6_wallet: Nep6Wallet = serde_json::from_str(&wallet_json).map_err(|e| {
887 WalletError::DeserializationError(format!("Failed to parse wallet JSON: {e}"))
888 })?;
889
890 let mut wallet = Wallet::from_nep6(nep6_wallet)?;
892
893 let can_decrypt = wallet.verify_password(password);
895
896 if !can_decrypt {
897 return Err(WalletError::CryptoError(CryptoError::InvalidPassphrase(
898 "Invalid password".to_string(),
899 )));
900 }
901
902 Ok(wallet)
903 }
904
905 pub fn get_all_accounts(&self) -> Vec<&Account> {
911 self.accounts.values().collect()
912 }
913
914 pub fn create_new_account(&mut self) -> Result<&Account, WalletError> {
920 let account = Account::create().map_err(|e| WalletError::ProviderError(e))?;
921 let script_hash = account.address_or_scripthash.script_hash();
922 self.add_account(account);
923
924 Ok(self.get_account(&script_hash).unwrap())
925 }
926
927 pub fn import_from_wif(&mut self, private_key: &str) -> Result<&Account, WalletError> {
937 let key_pair = KeyPair::from_wif(private_key).map_err(|e| WalletError::CryptoError(e))?;
939
940 let account = Account::from_key_pair(key_pair, None, None)
942 .map_err(|e| WalletError::AccountState(format!("Failed to create account: {e}")))?;
943 let script_hash = account.address_or_scripthash.script_hash();
944
945 self.add_account(account);
947
948 Ok(self.get_account(&script_hash).unwrap())
949 }
950
951 pub async fn get_unclaimed_gas_as_float<P>(
961 &self,
962 rpc_client: &RpcClient<P>,
963 ) -> Result<f64, WalletError>
964 where
965 P: JsonRpcProvider + 'static,
966 {
967 let mut total_unclaimed = 0.0;
968
969 for account in self.accounts.values() {
971 let script_hash = account.address_or_scripthash.script_hash();
972
973 let unclaimed = rpc_client
975 .get_unclaimed_gas(script_hash)
976 .await
977 .map_err(|e| WalletError::ProviderError(e))?;
978
979 total_unclaimed += unclaimed.unclaimed.parse::<f64>().unwrap_or(0.0);
981 }
982
983 Ok(total_unclaimed)
984 }
985
986 fn network(&self) -> u32 {
995 self.extra
997 .as_ref()
998 .and_then(|extra| {
999 extra
1000 .get("network")
1001 .map(|n| n.parse::<u32>().unwrap_or(NeoConstants::MAGIC_NUMBER_MAINNET))
1002 })
1003 .unwrap_or(NeoConstants::MAGIC_NUMBER_MAINNET)
1004 }
1005
1006 pub fn with_network(mut self, network: u32) -> Self {
1028 let mut extra = self.extra.unwrap_or_default();
1029 extra.insert("network".to_string(), network.to_string());
1030 self.extra = Some(extra);
1031 self
1032 }
1033}
1034
1035#[cfg(test)]
1036mod tests {
1037 use crate::{
1038 neo_config::TestConstants,
1039 neo_protocol::{Account, AccountTrait},
1040 neo_wallets::{Wallet, WalletTrait},
1041 };
1042
1043 #[test]
1044 fn test_is_default() {
1045 let account = Account::from_address(TestConstants::DEFAULT_ACCOUNT_ADDRESS)
1046 .expect("Should be able to create account from valid address in test");
1047 let mut wallet: Wallet = Wallet::new();
1048 wallet.add_account(account.clone());
1049
1050 assert!(!account.is_default);
1051
1052 let hash = account.address_or_scripthash.script_hash();
1053 wallet.set_default_account(hash.clone());
1054 assert!(wallet.get_account(&hash).expect("Account should exist in wallet").is_default);
1055 }
1056
1057 #[test]
1070 fn test_create_default_wallet() {
1071 let wallet: Wallet = Wallet::default();
1072
1073 assert_eq!(&wallet.name, "NeoWallet");
1074 assert_eq!(&wallet.version, Wallet::CURRENT_VERSION);
1075 assert_eq!(wallet.accounts.len(), 0usize);
1076 }
1077
1078 #[test]
1079 fn test_create_wallet_with_accounts() {
1080 let account1 = Account::create().expect("Should be able to create account in test");
1081 let account2 = Account::create().expect("Should be able to create account in test");
1082
1083 let wallet = Wallet::from_accounts(vec![account1.clone(), account2.clone()])
1084 .expect("Should be able to create wallet from accounts in test");
1085
1086 assert_eq!(wallet.default_account(), &account1);
1087 assert_eq!(wallet.accounts.len(), 2);
1088 assert!(wallet
1089 .accounts
1090 .clone()
1091 .into_iter()
1092 .any(|(s, _)| s == account1.address_or_scripthash.script_hash()));
1093 assert!(wallet
1094 .accounts
1095 .clone()
1096 .into_iter()
1097 .any(|(s, _)| s == account2.address_or_scripthash.script_hash()));
1098 }
1099
1100 #[test]
1101 fn test_is_default_account() {
1102 let account = Account::create().expect("Should be able to create account in test");
1103 let mut wallet = Wallet::from_accounts(vec![account.clone()])
1104 .expect("Should be able to create wallet from accounts in test");
1105
1106 assert_eq!(wallet.default_account, account.get_script_hash());
1107 }
1108
1109 #[test]
1110 fn test_add_account() {
1111 let account = Account::create().expect("Should be able to create account in test");
1112 let mut wallet: Wallet = Wallet::new();
1113
1114 wallet.add_account(account.clone());
1115
1116 assert_eq!(wallet.accounts.len(), 2);
1117 assert_eq!(
1118 wallet.get_account(&account.address_or_scripthash.script_hash()),
1119 Some(&account)
1120 );
1121 }
1122
1123 #[test]
1124 fn test_encrypt_wallet() {
1125 let mut wallet: Wallet = Wallet::new();
1126 wallet.add_account(Account::create().expect("Should be able to create account in test"));
1127
1128 assert!(wallet.accounts()[0].key_pair().is_some());
1129 assert!(wallet.accounts()[1].key_pair().is_some());
1130
1131 wallet.encrypt_accounts("pw");
1132
1133 assert!(wallet.accounts()[0].key_pair().is_none());
1134 assert!(wallet.accounts()[1].key_pair().is_none());
1135 }
1136
1137 #[test]
1138 fn test_encrypt_wallet_parallel() {
1139 let mut wallet: Wallet = Wallet::new();
1140 for _ in 0..5 {
1142 wallet
1143 .add_account(Account::create().expect("Should be able to create account in test"));
1144 }
1145
1146 for account in wallet.accounts() {
1148 assert!(account.key_pair().is_some());
1149 }
1150
1151 wallet.encrypt_accounts_parallel("parallel_password");
1153
1154 for account in wallet.accounts() {
1156 assert!(account.key_pair().is_none());
1157 assert!(account.encrypted_private_key().is_some());
1158 }
1159 }
1160
1161 #[test]
1162 fn test_encrypt_wallet_batch_parallel() {
1163 let mut wallet: Wallet = Wallet::new();
1164 for _ in 0..10 {
1166 wallet
1167 .add_account(Account::create().expect("Should be able to create account in test"));
1168 }
1169
1170 for account in wallet.accounts() {
1172 assert!(account.key_pair().is_some());
1173 }
1174
1175 wallet.encrypt_accounts_batch_parallel("batch_password", 3);
1177
1178 for account in wallet.accounts() {
1180 assert!(account.key_pair().is_none());
1181 assert!(account.encrypted_private_key().is_some());
1182 }
1183 }
1184
1185 #[test]
1186 fn test_change_password_parallel() {
1187 let mut wallet = Wallet::new();
1188 for _ in 0..5 {
1190 wallet
1191 .add_account(Account::create().expect("Should be able to create account in test"));
1192 }
1193
1194 let old_password = "old_password";
1195 let new_password = "new_password";
1196
1197 wallet.encrypt_accounts(old_password);
1199
1200 assert!(wallet.verify_password(old_password));
1202 assert!(!wallet.verify_password(new_password));
1203
1204 wallet
1206 .change_password_parallel(old_password, new_password)
1207 .expect("Password change should succeed");
1208
1209 assert!(!wallet.verify_password(old_password));
1211 assert!(wallet.verify_password(new_password));
1212 }
1213
1214 #[test]
1215 fn test_verify_password() {
1216 let mut wallet = Wallet::new();
1217 let account = Account::create().unwrap();
1218 wallet.add_account(account.clone());
1219
1220 assert!(!wallet.verify_password("password123"));
1222
1223 wallet.encrypt_accounts("password123");
1225
1226 assert!(wallet.verify_password("password123"));
1228
1229 assert!(!wallet.verify_password("wrong_password"));
1231 }
1232}