diff --git a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs index 148e088e5b..d49e067450 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -70,7 +70,6 @@ impl StoredWallet { .accounts .iter_mut() .find(|account| &account.id == id) - //.map(|account| &mut account.account) .ok_or(BackendError::NoSuchIdInWallet) } @@ -132,6 +131,19 @@ pub(crate) struct EncryptedLogin { pub account: EncryptedData, } +impl EncryptedLogin { + pub(crate) fn encrypt( + id: LoginId, + login: &StoredLogin, + password: &UserPassword, + ) -> Result { + Ok(EncryptedLogin { + id, + account: super::encryption::encrypt_struct(login, password)?, + }) + } +} + /// A stored login is either a account, such as a mnemonic, or a list of multiple accounts where /// each has an inner id. Future proofed for having private key backed accounts. #[derive(Serialize, Deserialize, Debug, Zeroize)] @@ -144,17 +156,6 @@ pub(crate) enum StoredLogin { } impl StoredLogin { - pub(crate) fn new_mnemonic_backed_account( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> Self { - Self::Mnemonic(MnemonicAccount { mnemonic, hd_path }) - } - - pub(crate) fn new_multiple_login() -> Self { - Self::Multiple(MultipleAccounts::empty()) - } - #[cfg(test)] pub(crate) fn as_mnemonic_account(&self) -> Option<&MnemonicAccount> { match self { @@ -171,64 +172,19 @@ impl StoredLogin { } } + // Return the login as multiple accounts, and if there is only a single mnemonic backed account, + // return a set containing only the single account paired with the account id passed as function + // argument. pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { - StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), + StoredLogin::Mnemonic(ref account) => { + vec![WalletAccount::from_mnemonic_account(id, account.clone())].into() + } StoredLogin::Multiple(ref accounts) => accounts.clone(), } } } -/// An account backed by a unique mnemonic. -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] -pub(crate) struct MnemonicAccount { - mnemonic: bip39::Mnemonic, - #[serde(with = "display_hd_path")] - hd_path: DerivationPath, -} - -impl MnemonicAccount { - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { - &self.mnemonic - } - - pub(crate) fn hd_path(&self) -> &DerivationPath { - &self.hd_path - } - - pub(crate) fn into_wallet_account(self, id: AccountId) -> WalletAccount { - WalletAccount { - id, - account: self.into(), - } - } - - pub(crate) fn into_multiple(self, id: AccountId) -> MultipleAccounts { - MultipleAccounts::new(self.into_wallet_account(id)) - } -} - -impl Zeroize for MnemonicAccount { - fn zeroize(&mut self) { - // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) - // and the memory would have been filled with zeroes. - // - // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing - // of overwriting it with a fresh mnemonic that was never used before - // - // note: this function can only fail on an invalid word count, which clearly is not the case here - self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); - - // further note: we don't really care about the hd_path, there's nothing secret about it. - } -} - -impl Drop for MnemonicAccount { - fn drop(&mut self) { - self.zeroize() - } -} - /// Multiple stored accounts, each entry having an id and a data field. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct MultipleAccounts { @@ -236,18 +192,12 @@ pub(crate) struct MultipleAccounts { } impl MultipleAccounts { - pub(crate) fn empty() -> Self { + pub(crate) fn new() -> Self { MultipleAccounts { accounts: Vec::new(), } } - pub(crate) fn new(account: WalletAccount) -> Self { - MultipleAccounts { - accounts: vec![account], - } - } - pub(crate) fn get_accounts(&self) -> impl Iterator { self.accounts.iter() } @@ -317,11 +267,20 @@ impl WalletAccount { ) -> Self { Self { id, - account: AccountData::new_mnemonic_backed_account(mnemonic, hd_path), + account: AccountData::Mnemonic(MnemonicAccount::new(mnemonic, hd_path)), + } + } + + pub(crate) fn from_mnemonic_account(id: AccountId, mnemonic_account: MnemonicAccount) -> Self { + Self { + id, + account: AccountData::Mnemonic(mnemonic_account), } } } +/// An account usually is a mnemonic account, but in the future it might be backed by a private +/// key. #[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] #[serde(untagged)] #[zeroize(drop)] @@ -331,13 +290,6 @@ pub(crate) enum AccountData { } impl AccountData { - pub(crate) fn new_mnemonic_backed_account( - mnemonic: bip39::Mnemonic, - hd_path: DerivationPath, - ) -> AccountData { - AccountData::Mnemonic(MnemonicAccount { mnemonic, hd_path }) - } - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { match self { AccountData::Mnemonic(account) => account.mnemonic(), @@ -352,9 +304,47 @@ impl AccountData { } } -impl From for AccountData { - fn from(mnemonic_account: MnemonicAccount) -> Self { - AccountData::Mnemonic(mnemonic_account) +/// An account backed by a unique mnemonic. +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] +pub(crate) struct MnemonicAccount { + mnemonic: bip39::Mnemonic, + #[serde(with = "display_hd_path")] + hd_path: DerivationPath, +} + +impl MnemonicAccount { + pub(crate) fn new(mnemonic: bip39::Mnemonic, hd_path: DerivationPath) -> Self { + Self { mnemonic, hd_path } + } + + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { + &self.mnemonic + } + + #[cfg(test)] + pub(crate) fn hd_path(&self) -> &DerivationPath { + &self.hd_path + } +} + +impl Zeroize for MnemonicAccount { + fn zeroize(&mut self) { + // in ideal world, Mnemonic would have had zeroize defined on it (there's an almost year old PR that introduces it) + // and the memory would have been filled with zeroes. + // + // we really don't want to keep our real mnemonic in memory, so let's do the semi-nasty thing + // of overwriting it with a fresh mnemonic that was never used before + // + // note: this function can only fail on an invalid word count, which clearly is not the case here + self.mnemonic = bip39::Mnemonic::generate(self.mnemonic.word_count()).unwrap(); + + // further note: we don't really care about the hd_path, there's nothing secret about it. + } +} + +impl Drop for MnemonicAccount { + fn drop(&mut self) { + self.zeroize() } } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 636081e6eb..8aefd859ba 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -6,7 +6,6 @@ pub(crate) use crate::wallet_storage::password::{AccountId, LoginId, UserPasswor use crate::error::BackendError; use crate::platform_constants::{STORAGE_DIR_NAME, WALLET_INFO_FILENAME}; -use crate::wallet_storage::encryption::encrypt_struct; use cosmrs::bip32::DerivationPath; use std::fs::{self, create_dir_all, OpenOptions}; use std::path::PathBuf; @@ -108,12 +107,9 @@ fn store_wallet_login_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let new_account = StoredLogin::new_mnemonic_backed_account(mnemonic, hd_path); - let new_encrypted_account = EncryptedLogin { - id, - account: encrypt_struct(&new_account, password)?, - }; - + let new_account = account_data::MnemonicAccount::new(mnemonic, hd_path); + let new_login = StoredLogin::Mnemonic(new_account); + let new_encrypted_account = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_account)?; let file = OpenOptions::new() @@ -157,18 +153,10 @@ fn store_wallet_login_with_multiple_accounts_at_file( return Err(BackendError::WalletDifferentPasswordDetected); } - let mut new_accounts = account_data::MultipleAccounts::empty(); - new_accounts.add( - DEFAULT_FIRST_ACCOUNT_NAME.to_string().into(), - mnemonic, - hd_path, - )?; - + let mut new_accounts = account_data::MultipleAccounts::new(); + new_accounts.add(DEFAULT_FIRST_ACCOUNT_NAME.into(), mnemonic, hd_path)?; let new_login = StoredLogin::Multiple(new_accounts); - let new_encrypted_login = EncryptedLogin { - id, - account: encrypt_struct(&new_login, password)?, - }; + let new_encrypted_login = EncryptedLogin::encrypt(id, &new_login, password)?; stored_wallet.add_encrypted_login(new_encrypted_login)?; @@ -222,10 +210,7 @@ fn append_account_to_wallet_login_at_file( let mut accounts = decrypted_login.unwrap_into_multiple_accounts(first_id_when_converting); accounts.add(inner_id, mnemonic, hd_path)?; - let encrypted_accounts = EncryptedLogin { - id, - account: encrypt_struct(&StoredLogin::Multiple(accounts), password)?, - }; + let encrypted_accounts = EncryptedLogin::encrypt(id, &StoredLogin::Multiple(accounts), password)?; stored_wallet.replace_encrypted_login(encrypted_accounts)?; @@ -305,6 +290,7 @@ fn remove_account_from_wallet_login_at_file( let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; + // Remove the account let is_empty = match decrypted_login { StoredLogin::Mnemonic(_) => { log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); @@ -316,19 +302,17 @@ fn remove_account_from_wallet_login_at_file( } }; + // Remove the login, or encrypt the new updated login if is_empty { stored_wallet .remove_encrypted_login(id) .ok_or(BackendError::NoSuchIdInWallet)?; } else { - // Replace the encrypted login with the pruned one. - let encrypted_accounts = EncryptedLogin { - id: id.clone(), - account: encrypt_struct(&decrypted_login, password)?, - }; + let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; stored_wallet.replace_encrypted_login(encrypted_accounts)?; } + // Remove the file, or write the new file if stored_wallet.is_empty() { log::info!("Removing file: {:#?}", filepath); Ok(fs::remove_file(filepath)?) @@ -785,7 +769,7 @@ mod tests { #[test] fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); @@ -799,7 +783,7 @@ mod tests { // Store the first account store_wallet_login_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account1.clone(), cosmos_hd_path.clone(), id1.clone(), @@ -807,15 +791,14 @@ mod tests { ) .unwrap(); - let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc = loaded_account.as_mnemonic_account().unwrap(); + let login = load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); + let acc = login.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); // Add an extra account store_wallet_login_at_file( - wallet_file.clone(), + wallet.clone(), dummy_account2.clone(), different_hd_path.clone(), id2.clone(), @@ -825,12 +808,12 @@ mod tests { // first account should be unchanged let loaded_account = - load_existing_wallet_login_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_at_file(wallet.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc1.mnemonic()); assert_eq!(&cosmos_hd_path, acc1.hd_path()); - let loaded_account = load_existing_wallet_login_at_file(wallet_file, &id2, &password).unwrap(); + let loaded_account = load_existing_wallet_login_at_file(wallet, &id2, &password).unwrap(); let acc2 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path());