From a7caf97b73055d15930e7a9767e0541e8a558616 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 15:12:07 +0200 Subject: [PATCH 1/9] wallet: derive_address --- nym-wallet/src-tauri/src/main.rs | 4 +-- .../src/operations/mixnet/account.rs | 33 +++++++++++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index d5c7d9ccf8..dba176ab7e 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -43,16 +43,16 @@ fn main() { mixnet::account::get_balance, mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, + mixnet::account::list_accounts, mixnet::account::list_accounts_for_password, - mixnet::account::sign_in_decrypted_account, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, + mixnet::account::sign_in_decrypted_account, mixnet::account::sign_in_with_password, mixnet::account::switch_network, mixnet::account::update_validator_urls, mixnet::account::validate_mnemonic, - mixnet::account::validate_mnemonic, mixnet::admin::get_contract_settings, mixnet::admin::update_contract_settings, mixnet::bond::bond_gateway, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 0a278cf7c7..c448d74939 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -14,13 +14,14 @@ use cosmrs::bip32::DerivationPath; use itertools::Itertools; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; -use std::collections::{BTreeMap, HashMap}; +use std::collections::HashMap; use std::convert::TryInto; use std::str::FromStr; use std::sync::Arc; use strum::IntoEnumIterator; use tokio::sync::RwLock; use url::Url; +use validator_client::nymd::wallet::DirectSecp256k1HdWallet; use validator_client::{nymd::SigningNymdClient, Client}; @@ -53,7 +54,7 @@ pub struct CreatedAccount { #[cfg_attr(test, derive(ts_rs::TS))] #[cfg_attr(test, ts(export, export_to = "../src/types/rust/createdaccount.ts"))] -#[derive(Clone, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct AccountEntry { id: String, address: String, @@ -471,6 +472,11 @@ pub async fn sign_in_with_password( w_state.set_all_accounts(all_accounts); } + let all_accounts = list_accounts(state.clone()).await?; + for account in all_accounts { + dbg!(&account); + } + _connect_with_mnemonic(mnemonic, state).await } @@ -507,20 +513,35 @@ pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), wallet_storage::remove_account_from_wallet_login(&id, &inner_id, &password) } +fn derive_address( + mnemonic: bip39::Mnemonic, + prefix: &str, +) -> Result { + let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; + let accounts = wallet.try_derive_accounts()?; + Ok(accounts[0].address().clone()) +} + #[tauri::command] pub async fn list_accounts( state: tauri::State<'_, Arc>>, ) -> Result, BackendError> { let state = state.read().await; - let all_accounts = state.get_all_accounts(); - let a = all_accounts + let network: Network = state.current_network().into(); + let prefix = network.bech32_prefix(); + + let all_accounts = state + .get_all_accounts() .values() .map(|account| AccountEntry { id: account.id.clone(), - address: "placeholder".to_string(), + address: derive_address(account.mnemonic.clone(), prefix) + .unwrap() + .to_string(), }) .collect(); - Ok(a) + + Ok(all_accounts) } // WIP(JON): consider changing return type From 05374393ef9efe48137bee5fa1ea76f6626431be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 15:36:06 +0200 Subject: [PATCH 2/9] wallet: contructors for DecryptedAccount --- .../src/operations/mixnet/account.rs | 18 +++-------------- nym-wallet/src-tauri/src/state.rs | 20 ++++++++++++++++--- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index c448d74939..d6d5a81c7d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -442,29 +442,17 @@ pub async fn sign_in_with_password( } }; - // Keep track of all accounts - // WIP(JON): simplify + // Keep track of all accounts for that id let all_accounts: HashMap<_, _> = match stored_account { StoredLogin::Mnemonic(ref account) => [( id.to_string(), - DecryptedAccount { - id: id.to_string(), - mnemonic: account.mnemonic().clone(), - }, + DecryptedAccount::new(id.to_string(), account.mnemonic().clone()), )] .into_iter() .collect(), StoredLogin::Multiple(ref accounts) => accounts .get_accounts() - .map(|account| { - ( - account.id.to_string(), - DecryptedAccount { - id: account.id.to_string(), - mnemonic: account.account.mnemonic().clone(), - }, - ) - }) + .map(|account| (account.id.to_string(), account.into())) .collect(), }; { diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index c1672d9391..bc98dbcb4e 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -1,8 +1,8 @@ use crate::config::{Config, OptionalValidators, ValidatorUrl}; use crate::error::BackendError; use crate::network::Network; +use crate::wallet_storage::account_data::WalletAccount; -use bip39::Mnemonic; use strum::IntoEnumIterator; use validator_client::nymd::SigningNymdClient; use validator_client::Client; @@ -180,8 +180,22 @@ macro_rules! client { #[derive(Clone)] pub(crate) struct DecryptedAccount { pub id: String, - pub mnemonic: Mnemonic, - // address: String + pub mnemonic: bip39::Mnemonic, +} + +impl DecryptedAccount { + pub fn new(id: String, mnemonic: bip39::Mnemonic) -> Self { + Self { id, mnemonic } + } +} + +impl From<&WalletAccount> for DecryptedAccount { + fn from(wallet_account: &WalletAccount) -> Self { + Self { + id: wallet_account.id.to_string(), + mnemonic: wallet_account.account.mnemonic().clone(), + } + } } #[macro_export] From f7be9e7e6f694cfe9e9c3459f19e5436a9692b20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:27:24 +0200 Subject: [PATCH 3/9] wallet: remove DecryptedAccount and make Accounts clone --- .../src/operations/mixnet/account.rs | 38 +++++++------------ nym-wallet/src-tauri/src/state.rs | 28 ++------------ .../src/wallet_storage/account_data.rs | 19 ++++++++-- 3 files changed, 32 insertions(+), 53 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index d6d5a81c7d..55b0ac30d0 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -3,8 +3,8 @@ use crate::config::Config; use crate::error::BackendError; use crate::network::Network as WalletNetwork; use crate::nymd_client; -use crate::state::{DecryptedAccount, State}; -use crate::wallet_storage::account_data::StoredLogin; +use crate::state::State; +use crate::wallet_storage::account_data::{StoredLogin, WalletAccount}; use crate::wallet_storage::{self, DEFAULT_WALLET_ACCOUNT_ID}; use bip39::{Language, Mnemonic}; @@ -442,29 +442,18 @@ pub async fn sign_in_with_password( } }; - // Keep track of all accounts for that id - let all_accounts: HashMap<_, _> = match stored_account { - StoredLogin::Mnemonic(ref account) => [( - id.to_string(), - DecryptedAccount::new(id.to_string(), account.mnemonic().clone()), - )] - .into_iter() - .collect(), - StoredLogin::Multiple(ref accounts) => accounts - .get_accounts() - .map(|account| (account.id.to_string(), account.into())) - .collect(), - }; { + // Keep track of all accounts for that id + let all_accounts: HashMap = stored_account + .into_multiple_accounts(id) + .into_accounts() + .map(|a| (a.id.to_string(), a)) + .collect(); + let mut w_state = state.write().await; w_state.set_all_accounts(all_accounts); } - let all_accounts = list_accounts(state.clone()).await?; - for account in all_accounts { - dbg!(&account); - } - _connect_with_mnemonic(mnemonic, state).await } @@ -522,8 +511,8 @@ pub async fn list_accounts( .get_all_accounts() .values() .map(|account| AccountEntry { - id: account.id.clone(), - address: derive_address(account.mnemonic.clone(), prefix) + id: account.id.to_string(), + address: derive_address(account.account.mnemonic().clone(), prefix) .unwrap() .to_string(), }) @@ -554,14 +543,15 @@ pub async fn sign_in_decrypted_account( account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { - let account = { + let mnemonic = { let state = state.read().await; state .get_all_accounts() .get(account_id) .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() .clone() }; - let mnemonic = account.mnemonic; _connect_with_mnemonic(mnemonic, state).await } diff --git a/nym-wallet/src-tauri/src/state.rs b/nym-wallet/src-tauri/src/state.rs index bc98dbcb4e..ae9f2c07e0 100644 --- a/nym-wallet/src-tauri/src/state.rs +++ b/nym-wallet/src-tauri/src/state.rs @@ -21,7 +21,7 @@ pub struct State { // All the accounts the we get from decrypting the wallet. We hold on to these for being able to // switch accounts on-the-fly - all_accounts: HashMap, + all_accounts: HashMap, /// Validators that have been fetched dynamically, probably during startup. fetched_validators: OptionalValidators, @@ -68,12 +68,12 @@ impl State { self.current_network } - pub(crate) fn set_all_accounts(&mut self, all_accounts: HashMap) { + pub(crate) fn set_all_accounts(&mut self, all_accounts: HashMap) { self.all_accounts.clear(); self.all_accounts.extend(all_accounts) } - pub(crate) fn get_all_accounts(&self) -> &HashMap { + pub(crate) fn get_all_accounts(&self) -> &HashMap { &self.all_accounts } @@ -176,28 +176,6 @@ macro_rules! client { }; } -// Keep track of mnemonics on the backend, so we can switch accounts -#[derive(Clone)] -pub(crate) struct DecryptedAccount { - pub id: String, - pub mnemonic: bip39::Mnemonic, -} - -impl DecryptedAccount { - pub fn new(id: String, mnemonic: bip39::Mnemonic) -> Self { - Self { id, mnemonic } - } -} - -impl From<&WalletAccount> for DecryptedAccount { - fn from(wallet_account: &WalletAccount) -> Self { - Self { - id: wallet_account.id.to_string(), - mnemonic: wallet_account.account.mnemonic().clone(), - } - } -} - #[macro_export] macro_rules! nymd_client { ($state:ident) => { 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 7336a33c0e..dca15df57e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -159,10 +159,17 @@ impl StoredLogin { StoredLogin::Multiple(accounts) => Some(accounts), } } + + pub(crate) fn into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + match self { + StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), + StoredLogin::Multiple(ref accounts) => accounts.clone(), + } + } } /// An account backed by a unique mnemonic. -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)] pub(crate) struct MnemonicAccount { mnemonic: bip39::Mnemonic, #[serde(with = "display_hd_path")] @@ -215,7 +222,7 @@ impl Drop for MnemonicAccount { } /// Multiple stored accounts, each entry having an id and a data field. -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct MultipleAccounts { accounts: Vec, } @@ -235,6 +242,10 @@ impl MultipleAccounts { self.accounts.iter().find(|account| &account.id == id) } + pub(crate) fn into_accounts(self) -> impl Iterator { + self.accounts.into_iter() + } + #[allow(unused)] pub(crate) fn len(&self) -> usize { self.accounts.len() @@ -274,7 +285,7 @@ impl From> for MultipleAccounts { } /// An entry in the list of stored accounts -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] pub(crate) struct WalletAccount { pub id: AccountId, pub account: AccountData, @@ -293,7 +304,7 @@ impl WalletAccount { } } -#[derive(Serialize, Deserialize, Debug, Zeroize, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Clone, Debug, Zeroize, PartialEq, Eq)] #[serde(untagged)] #[zeroize(drop)] pub(crate) enum AccountData { From 7e356ea3b3627b666c7c140a7bfa03e255abfbe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:49:57 +0200 Subject: [PATCH 4/9] wallet: simplify account handling --- .../src/operations/mixnet/account.rs | 2 +- .../src/wallet_storage/account_data.rs | 10 ++------- .../src-tauri/src/wallet_storage/mod.rs | 21 +++++-------------- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 55b0ac30d0..5709dcdf58 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -445,7 +445,7 @@ pub async fn sign_in_with_password( { // Keep track of all accounts for that id let all_accounts: HashMap = stored_account - .into_multiple_accounts(id) + .unwrap_into_multiple_accounts(id) .into_accounts() .map(|a| (a.id.to_string(), a)) .collect(); 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 dca15df57e..b6b334e814 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -160,7 +160,7 @@ impl StoredLogin { } } - pub(crate) fn into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { + pub(crate) fn unwrap_into_multiple_accounts(self, id: AccountId) -> MultipleAccounts { match self { StoredLogin::Mnemonic(ref account) => account.clone().into_multiple(id), StoredLogin::Multiple(ref accounts) => accounts.clone(), @@ -177,17 +177,11 @@ pub(crate) struct MnemonicAccount { } impl MnemonicAccount { - pub(crate) fn generate_new(&self) -> MnemonicAccount { - MnemonicAccount { - mnemonic: bip39::Mnemonic::generate(self.mnemonic().word_count()).unwrap(), - hd_path: self.hd_path().clone(), - } - } - pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { &self.mnemonic } + #[cfg(test)] pub(crate) fn hd_path(&self) -> &DerivationPath { &self.hd_path } diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 82c7585d35..9dfaa74fa4 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -151,26 +151,15 @@ fn append_account_to_wallet_login_information_at_file( result => result?, }; - let mut decrypted_login = stored_wallet.decrypt_login(&id, password)?; + let decrypted_login = stored_wallet.decrypt_login(&id, password)?; - // WIP(JON): redo this since we now can clone the mnemonic - // Since we can't clone the mnemonic, we have to perform a little dance were we add the mnemonic - // to the inner enum payload, while also converting by swapping if necessary. - if let StoredLogin::Multiple(ref mut accounts) = decrypted_login { - accounts.add(inner_id, mnemonic, hd_path)?; - } else if let StoredLogin::Mnemonic(ref mut account) = decrypted_login { - // Move out the account by swapping, since we can't clone. - let account = std::mem::replace(account, account.generate_new()); - // Convert the enum variant - let mut accounts = account.into_multiple(id.clone()); - accounts.add(inner_id, mnemonic, hd_path)?; - // Overwrite the stored login with the new enum variant - decrypted_login = StoredLogin::Multiple(accounts); - } + // Add accounts to the inner structure + let mut accounts = decrypted_login.unwrap_into_multiple_accounts(id.clone()); + accounts.add(inner_id, mnemonic, hd_path)?; let encrypted_accounts = EncryptedLogin { id, - account: encrypt_struct(&decrypted_login, password)?, + account: encrypt_struct(&StoredLogin::Multiple(accounts), password)?, }; stored_wallet.replace_encrypted_login(encrypted_accounts)?; From 6d874cc34aec02aa131db2eeb8bfd498550dd6d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Fri, 29 Apr 2022 16:53:51 +0200 Subject: [PATCH 5/9] wallet: remove list_accounts_for_password --- nym-wallet/src-tauri/src/main.rs | 1 - .../src-tauri/src/operations/mixnet/account.rs | 17 ----------------- 2 files changed, 18 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index dba176ab7e..0130ca496c 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -44,7 +44,6 @@ fn main() { mixnet::account::get_validator_api_urls, mixnet::account::get_validator_nymd_urls, mixnet::account::list_accounts, - mixnet::account::list_accounts_for_password, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 5709dcdf58..b75f7ef34d 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -521,23 +521,6 @@ pub async fn list_accounts( Ok(all_accounts) } -// WIP(JON): consider changing return type -#[tauri::command] -pub fn list_accounts_for_password(password: &str) -> Result, BackendError> { - // Currently we only support a single, default, id in the wallet - let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); - let password = wallet_storage::UserPassword::new(password.to_string()); - let login = wallet_storage::load_existing_wallet_login_information(&id, &password)?; - let ids = match login { - StoredLogin::Mnemonic(_) => vec![id.to_string()], - StoredLogin::Multiple(ref accounts) => accounts - .get_accounts() - .map(|account| account.id.to_string()) - .collect::>(), - }; - Ok(ids) -} - #[tauri::command] pub async fn sign_in_decrypted_account( account_id: &str, From 30a41261ea7aecaf985c3323c45e1d82baf1ce21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 3 May 2022 20:03:46 +0200 Subject: [PATCH 6/9] wallet: add more log statements --- nym-wallet/src-tauri/src/error.rs | 2 ++ .../src-tauri/src/operations/mixnet/account.rs | 14 ++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/nym-wallet/src-tauri/src/error.rs b/nym-wallet/src-tauri/src/error.rs index ef0da6a5f8..65f1bac4b2 100644 --- a/nym-wallet/src-tauri/src/error.rs +++ b/nym-wallet/src-tauri/src/error.rs @@ -97,6 +97,8 @@ pub enum BackendError { WalletUnexpectedMultipleAccounts, #[error("Unexpted mnemonic account found")] WalletUnexpectedMnemonicAccount, + #[error("Failed to derive address from mnemonic")] + FailedToDeriveAddress, } impl Serialize for BackendError { diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index b75f7ef34d..b630cfac91 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -21,7 +21,7 @@ use std::sync::Arc; use strum::IntoEnumIterator; use tokio::sync::RwLock; use url::Url; -use validator_client::nymd::wallet::DirectSecp256k1HdWallet; +use validator_client::nymd::wallet::{AccountData, DirectSecp256k1HdWallet}; use validator_client::{nymd::SigningNymdClient, Client}; @@ -470,6 +470,7 @@ pub fn add_account_for_password( password: &str, inner_id: &str, ) -> Result<(), BackendError> { + log::info!("Adding account for the current password: {inner_id}"); let mnemonic = Mnemonic::from_str(mnemonic)?; let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); // Currently we only support a single, default, id in the wallet @@ -483,6 +484,7 @@ pub fn add_account_for_password( #[tauri::command] pub fn remove_account_for_password(password: &str, inner_id: &str) -> Result<(), BackendError> { + log::info!("Removing account: {inner_id}"); // Currently we only support a single, default, id in the wallet let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); let inner_id = wallet_storage::AccountId::new(inner_id.to_string()); @@ -494,9 +496,12 @@ fn derive_address( mnemonic: bip39::Mnemonic, prefix: &str, ) -> Result { - let wallet = DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)?; - let accounts = wallet.try_derive_accounts()?; - Ok(accounts[0].address().clone()) + DirectSecp256k1HdWallet::from_mnemonic(prefix, mnemonic)? + .try_derive_accounts()? + .first() + .map(AccountData::address) + .cloned() + .ok_or(BackendError::FailedToDeriveAddress) } #[tauri::command] @@ -526,6 +531,7 @@ pub async fn sign_in_decrypted_account( account_id: &str, state: tauri::State<'_, Arc>>, ) -> Result { + log::info!("Signing in to already decrypted account: {account_id}"); let mnemonic = { let state = state.read().await; state From df4c6493d40796a590da6cde350994434bf2be50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Tue, 3 May 2022 23:20:16 +0200 Subject: [PATCH 7/9] wallet: split and add more wallet_store tests --- .../src-tauri/src/wallet_storage/mod.rs | 364 +++++++++++++----- 1 file changed, 263 insertions(+), 101 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 9dfaa74fa4..f97a2f0ed6 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -292,8 +292,7 @@ mod tests { // I'm not 100% sure how to feel about having to touch the file system at all #[test] - #[allow(clippy::too_many_lines)] - fn storing_wallet_information() { + fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); @@ -371,13 +370,9 @@ mod tests { let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - - if let StoredLogin::Mnemonic(ref acc) = loaded_account { - assert_eq!(&dummy_account1, acc.mnemonic()); - assert_eq!(&cosmos_hd_path, acc.hd_path()); - } else { - todo!(); - } + let acc = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); // Can't store extra account if you use different password assert!(matches!( @@ -415,21 +410,62 @@ mod tests { // first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc1) = loaded_account { - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - } else { - todo!(); - } + 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_information_at_file(wallet_file, &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()); + } + + #[test] + fn store_and_remove_wallet_login_information() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + // Store two accounts with two different passwords + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + different_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Load and compare + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.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_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc2) = loaded_account { - assert_eq!(&dummy_account2, acc2.mnemonic()); - assert_eq!(&different_hd_path, acc2.hd_path()); - } else { - todo!(); - } + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account2, acc2.mnemonic()); + assert_eq!(&different_hd_path, acc2.hd_path()); // Fails to delete non-existent id in the wallet let id3 = AccountId::new("phony".to_string()); @@ -444,12 +480,210 @@ mod tests { // The first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - if let StoredLogin::Mnemonic(ref acc1) = loaded_account { - assert_eq!(&dummy_account1, acc1.mnemonic()); - assert_eq!(&cosmos_hd_path, acc1.hd_path()); - } else { - todo!(); - } + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc1.mnemonic()); + assert_eq!(&cosmos_hd_path, acc1.hd_path()); + + // And we can't load the second one anymore + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); + + // Delete the first account + assert!(wallet_file.exists()); + remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + + // The file should now be removed + assert!(!wallet_file.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn append_accounts_to_existing_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let acc2 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc2.mnemonic(), &dummy_account2); + assert_eq!(acc2.hd_path(), &cosmos_hd_path); + + // Add a third mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + // Add a fourth mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account4.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Check that we can still load all four + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + + #[test] + fn append_accounts_and_remove() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account4 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + let id4 = AccountId::new("fourth".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Add a third mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3, + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + // Add a fourth mnenonic grouped together with the second one + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account4.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id4.clone(), + &password, + ) + .unwrap(); + + // Delete the third mnemonic, from the second login entry + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id3, &password).unwrap(); + + // Check that we can still load all three others + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account( + id2.clone(), + dummy_account2, + cosmos_hd_path.clone(), + ), + WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path.clone()), + ] + .into(); + assert_eq!(accounts, &expected); + + // Delete the second account, this will also delete the fourth mnemonic that is grouped with the + // second one. + remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); + + // Check that we can still load the first + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + // and the loading the deleted one fails + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password) + .unwrap_err(); // Delete the first account assert!(wallet_file.exists()); @@ -459,10 +693,10 @@ mod tests { assert!(!wallet_file.exists()); } + // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored + // wallets created with older versions. #[test] fn decrypt_stored_wallet() { - pretty_env_logger::init(); - const SAVED_WALLET: &str = "src/wallet_storage/test-data/saved-wallet.json"; let wallet_file = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(SAVED_WALLET); @@ -504,76 +738,4 @@ mod tests { &cosmos_hd_path, ); } - - #[test] - fn append_a_third_account() { - let store_dir = tempdir().unwrap(); - let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); - - let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); - let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); - let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); - - let password = UserPassword::new("password".to_string()); - - let id1 = AccountId::new("first".to_string()); - let id2 = AccountId::new("second".to_string()); - let id3 = AccountId::new("third".to_string()); - - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ) - .unwrap(); - - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account2.clone(), - cosmos_hd_path.clone(), - id2.clone(), - &password, - ) - .unwrap(); - - // Check that it's there as the correct non-multiple type - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); - let acc2 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc2.mnemonic(), &dummy_account2); - assert_eq!(acc2.hd_path(), &cosmos_hd_path); - - // Add a third mnenonic grouped together with the second one - append_account_to_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account3.clone(), - cosmos_hd_path.clone(), - id2.clone(), - id3.clone(), - &password, - ) - .unwrap(); - - // Check that we can still load all three - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); - - let loaded_accounts = - load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); - let accounts = loaded_accounts.as_multiple_accounts().unwrap(); - - let expected = vec![ - WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), - WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), - ] - .into(); - - assert_eq!(accounts, &expected); - } } From 1a5580229bb0ed99f86c6b69dc99bc98493a9c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 4 May 2022 01:35:57 +0200 Subject: [PATCH 8/9] wallet: more unit tests --- .../src-tauri/src/wallet_storage/mod.rs | 684 +++++++++++++++--- 1 file changed, 568 insertions(+), 116 deletions(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index f97a2f0ed6..f4e2243bc9 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -290,7 +290,260 @@ mod tests { use std::str::FromStr; use tempfile::tempdir; - // I'm not 100% sure how to feel about having to touch the file system at all + #[test] + fn trying_to_load_nonexistant_wallet_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let id1 = AccountId::new("first".to_string()); + let password = UserPassword::new("password".to_string()); + + assert!(matches!( + load_existing_wallet_at_file(wallet_file.clone()), + Err(BackendError::WalletFileNotFound), + )); + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + remove_wallet_login_information_at_file(wallet_file, &id1).unwrap_err(); + } + + #[test] + fn store_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(wallet_file).unwrap(); + assert_eq!(stored_wallet.len(), 1); + + let login = stored_wallet.get_encrypted_login_by_index(0).unwrap(); + assert_eq!(login.id, AccountId::new("first".to_string())); + + // some actual ciphertext was saved + assert!(!login.account.ciphertext().is_empty()); + } + + #[test] + fn store_twice_for_the_same_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + // Store the first login + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // and storing the same id again fails + assert!(matches!( + store_wallet_login_information_at_file( + wallet_file, + dummy_account1, + cosmos_hd_path, + id1, + &password, + ), + Err(BackendError::IdAlreadyExistsInWallet), + )); + } + + #[test] + fn load_with_wrong_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1.clone(), + &password, + ) + .unwrap(); + + // Trying to load it with wrong password now fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &bad_password), + Err(BackendError::DecryptionError), + )); + } + + #[test] + fn load_with_wrong_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + // Trying to load with the wrong id + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); + } + + #[test] + fn store_and_load_a_single_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let id1 = AccountId::new("first".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let acc = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(&dummy_account1, acc.mnemonic()); + assert_eq!(&cosmos_hd_path, acc.hd_path()); + } + + #[test] + fn store_a_second_login_with_a_different_password_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + let bad_password = UserPassword::new("bad-password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1, + &password, + ) + .unwrap(); + + // Can't store a second login if you use different password + assert!(matches!( + store_wallet_login_information_at_file( + wallet_file, + dummy_account2, + cosmos_hd_path, + id2, + &bad_password + ), + Err(BackendError::WalletDifferentPasswordDetected), + )); + } + + #[test] + fn store_two_mnemonic_accounts_gives_different_salts_and_iv() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let different_hd_path: DerivationPath = "m".parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + // Store the first account + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); + let encrypted_blob = &stored_wallet + .get_encrypted_login_by_index(0) + .unwrap() + .account; + + // keep track of salt and iv for future assertion + let original_iv = encrypted_blob.iv().to_vec(); + let original_salt = encrypted_blob.salt().to_vec(); + + // Add an extra account + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + different_hd_path, + id2, + &password, + ) + .unwrap(); + + let loaded_accounts = load_existing_wallet_at_file(wallet_file).unwrap(); + assert_eq!(loaded_accounts.len(), 2); + let encrypted_blob = &loaded_accounts + .get_encrypted_login_by_index(1) + .unwrap() + .account; + + // fresh IV and salt are used + assert_ne!(original_iv, encrypted_blob.iv()); + assert_ne!(original_salt, encrypted_blob.salt()); + } + #[test] fn store_two_mnemonic_accounts_using_two_logins() { let store_dir = tempdir().unwrap(); @@ -302,21 +555,10 @@ mod tests { let different_hd_path: DerivationPath = "m".parse().unwrap(); let password = UserPassword::new("password".to_string()); - let bad_password = UserPassword::new("bad-password".to_string()); let id1 = AccountId::new("first".to_string()); let id2 = AccountId::new("second".to_string()); - // Nothing was stored on the disk - assert!(matches!( - load_existing_wallet_at_file(wallet_file.clone()), - Err(BackendError::WalletFileNotFound), - )); - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), - Err(BackendError::WalletFileNotFound), - )); - // Store the first account store_wallet_login_information_at_file( wallet_file.clone(), @@ -327,66 +569,13 @@ mod tests { ) .unwrap(); - let stored_wallet = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); - assert_eq!(stored_wallet.len(), 1); - assert_eq!( - stored_wallet.get_encrypted_login_by_index(0).unwrap().id, - AccountId::new("first".to_string()) - ); - let encrypted_blob = &stored_wallet - .get_encrypted_login_by_index(0) - .unwrap() - .account; - - // some actual ciphertext was saved - assert!(!encrypted_blob.ciphertext().is_empty()); - - // keep track of salt and iv for future assertion - let original_iv = encrypted_blob.iv().to_vec(); - let original_salt = encrypted_blob.salt().to_vec(); - - // trying to load it with wrong password now fails - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &bad_password), - Err(BackendError::DecryptionError), - )); - // and with the wrong id also fails - assert!(matches!( - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), - Err(BackendError::NoSuchIdInWallet), - )); - - // and storing the same id again fails - assert!(matches!( - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account1.clone(), - cosmos_hd_path.clone(), - id1.clone(), - &password, - ), - Err(BackendError::IdAlreadyExistsInWallet), - )); - let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(&dummy_account1, acc.mnemonic()); assert_eq!(&cosmos_hd_path, acc.hd_path()); - // Can't store extra account if you use different password - assert!(matches!( - store_wallet_login_information_at_file( - wallet_file.clone(), - dummy_account2.clone(), - cosmos_hd_path.clone(), - id2.clone(), - &bad_password - ), - Err(BackendError::WalletDifferentPasswordDetected), - )); - - // add extra account properly now + // Add an extra account store_wallet_login_information_at_file( wallet_file.clone(), dummy_account2.clone(), @@ -396,17 +585,6 @@ mod tests { ) .unwrap(); - let loaded_accounts = load_existing_wallet_at_file(wallet_file.clone()).unwrap(); - assert_eq!(2, loaded_accounts.len()); - let encrypted_blob = &loaded_accounts - .get_encrypted_login_by_index(1) - .unwrap() - .account; - - // fresh IV and salt are used - assert_ne!(original_iv, encrypted_blob.iv()); - assert_ne!(original_salt, encrypted_blob.salt()); - // first account should be unchanged let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); @@ -421,6 +599,35 @@ mod tests { assert_eq!(&different_hd_path, acc2.hd_path()); } + #[test] + fn remove_non_existent_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path, + id1, + &password, + ) + .unwrap(); + + // Fails to delete non-existent id in the wallet + assert!(matches!( + remove_wallet_login_information_at_file(wallet_file, &id2), + Err(BackendError::NoSuchIdInWallet), + )); + } + #[test] fn store_and_remove_wallet_login_information() { let store_dir = tempdir().unwrap(); @@ -467,13 +674,6 @@ mod tests { assert_eq!(&dummy_account2, acc2.mnemonic()); assert_eq!(&different_hd_path, acc2.hd_path()); - // Fails to delete non-existent id in the wallet - let id3 = AccountId::new("phony".to_string()); - assert!(matches!( - remove_wallet_login_information_at_file(wallet_file.clone(), &id3), - Err(BackendError::NoSuchIdInWallet), - )); - // Delete the second account remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); @@ -504,6 +704,58 @@ mod tests { )); } + #[test] + fn append_account_converts_the_type() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1.clone(), + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + // Check that it's there as the correct non-multiple type + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + let acc1 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc1.mnemonic(), &dummy_account1); + assert_eq!(acc1.hd_path(), &cosmos_hd_path); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + // Check that it is now multiple mnemonic type + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id1, dummy_account1, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + #[test] fn append_accounts_to_existing_login() { let store_dir = tempdir().unwrap(); @@ -547,7 +799,7 @@ mod tests { assert_eq!(acc2.mnemonic(), &dummy_account2); assert_eq!(acc2.hd_path(), &cosmos_hd_path); - // Add a third mnenonic grouped together with the second one + // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3.clone(), @@ -557,8 +809,6 @@ mod tests { &password, ) .unwrap(); - - // Add a fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account4.clone(), @@ -569,7 +819,7 @@ mod tests { ) .unwrap(); - // Check that we can still load all four + // Check that we can load all four let loaded_account = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); @@ -589,7 +839,215 @@ mod tests { } #[test] - fn append_accounts_and_remove() { + fn delete_the_same_account_twice_for_a_login_fails() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // Delete the same again fails + // WIP(JON) + //assert!(matches!( + remove_account_from_wallet_login_at_file(wallet_file, &id1, &id2, &password).unwrap(); + //Err(BackendError::NoSuchIdInWallet), + //)); + } + + #[test] + fn delete_appended_account_doesnt_affect_others() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2.clone(), + cosmos_hd_path.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id2.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); + + // The second login one is still there + let loaded_accounts = + load_existing_wallet_login_information_at_file(wallet_file, &id2, &password).unwrap(); + let accounts = loaded_accounts.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new_mnemonic_backed_account(id2, dummy_account2, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account(id3, dummy_account3, cosmos_hd_path), + ] + .into(); + assert_eq!(accounts, &expected); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_the_file_when_empty() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path, + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // The file should now be removed + assert!(!wallet_file.exists()); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password), + Err(BackendError::WalletFileNotFound), + )); + } + + #[test] + fn remove_all_accounts_for_a_login_removes_that_login() { + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + + let dummy_account1 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account2 = bip39::Mnemonic::generate(24).unwrap(); + let dummy_account3 = bip39::Mnemonic::generate(24).unwrap(); + let cosmos_hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + + let password = UserPassword::new("password".to_string()); + + let id1 = AccountId::new("first".to_string()); + let id2 = AccountId::new("second".to_string()); + let id3 = AccountId::new("third".to_string()); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account1, + cosmos_hd_path.clone(), + id1.clone(), + &password, + ) + .unwrap(); + + append_account_to_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account2, + cosmos_hd_path.clone(), + id1.clone(), + id2.clone(), + &password, + ) + .unwrap(); + + store_wallet_login_information_at_file( + wallet_file.clone(), + dummy_account3.clone(), + cosmos_hd_path.clone(), + id3.clone(), + &password, + ) + .unwrap(); + + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id1, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id1, &id2, &password).unwrap(); + + // And trying to load when the file is gone fails + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password), + Err(BackendError::NoSuchIdInWallet), + )); + + // The other login is still there + let loaded_account = + load_existing_wallet_login_information_at_file(wallet_file, &id3, &password).unwrap(); + let acc3 = loaded_account.as_mnemonic_account().unwrap(); + assert_eq!(acc3.mnemonic(), &dummy_account3); + assert_eq!(acc3.hd_path(), &cosmos_hd_path); + } + + #[test] + fn append_accounts_and_remove_appended_accounts() { let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); @@ -624,7 +1082,7 @@ mod tests { ) .unwrap(); - // Add a third mnenonic grouped together with the second one + // Add a third and fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account3, @@ -634,8 +1092,6 @@ mod tests { &password, ) .unwrap(); - - // Add a fourth mnenonic grouped together with the second one append_account_to_wallet_login_information_at_file( wallet_file.clone(), dummy_account4.clone(), @@ -649,13 +1105,7 @@ mod tests { // Delete the third mnemonic, from the second login entry remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id3, &password).unwrap(); - // Check that we can still load all three others - let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); - let acc1 = loaded_account.as_mnemonic_account().unwrap(); - assert_eq!(acc1.mnemonic(), &dummy_account1); - assert_eq!(acc1.hd_path(), &cosmos_hd_path); - + // Check that we can still load the other accounts let loaded_accounts = load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password).unwrap(); let accounts = loaded_accounts.as_multiple_accounts().unwrap(); @@ -665,32 +1115,29 @@ mod tests { dummy_account2, cosmos_hd_path.clone(), ), - WalletAccount::new_mnemonic_backed_account(id4, dummy_account4, cosmos_hd_path.clone()), + WalletAccount::new_mnemonic_backed_account( + id4.clone(), + dummy_account4, + cosmos_hd_path.clone(), + ), ] .into(); assert_eq!(accounts, &expected); - // Delete the second account, this will also delete the fourth mnemonic that is grouped with the - // second one. - remove_wallet_login_information_at_file(wallet_file.clone(), &id2).unwrap(); + // Delete the second and fourth mnemonic from the second login entry removes the login entry + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id2, &password).unwrap(); + remove_account_from_wallet_login_at_file(wallet_file.clone(), &id2, &id4, &password).unwrap(); + assert!(matches!( + load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password), + Err(BackendError::NoSuchIdInWallet), + )); - // Check that we can still load the first + // The first login is still available let loaded_account = - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id1, &password).unwrap(); + load_existing_wallet_login_information_at_file(wallet_file, &id1, &password).unwrap(); let acc1 = loaded_account.as_mnemonic_account().unwrap(); assert_eq!(acc1.mnemonic(), &dummy_account1); assert_eq!(acc1.hd_path(), &cosmos_hd_path); - - // and the loading the deleted one fails - load_existing_wallet_login_information_at_file(wallet_file.clone(), &id2, &password) - .unwrap_err(); - - // Delete the first account - assert!(wallet_file.exists()); - remove_wallet_login_information_at_file(wallet_file.clone(), &id1).unwrap(); - - // The file should now be removed - assert!(!wallet_file.exists()); } // Test to that decrypts a stored file from the repo, to make sure we are able to decrypt stored @@ -738,4 +1185,9 @@ mod tests { &cosmos_hd_path, ); } + + #[test] + fn decrypt_stored_wallet_with_multiple_accounts() { + // WIP(JON) + } } From 7adee63ebe79d84f3152a33ea58c8400257475f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Wed, 4 May 2022 10:01:34 +0200 Subject: [PATCH 9/9] wallet: add show_mnemonic_for_account_in_password --- nym-wallet/src-tauri/src/main.rs | 1 + .../src/operations/mixnet/account.rs | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index 0130ca496c..151a13c9a1 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -47,6 +47,7 @@ fn main() { mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, + mixnet::account::show_mnemonic_for_account_in_password, mixnet::account::sign_in_decrypted_account, mixnet::account::sign_in_with_password, mixnet::account::switch_network, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index b630cfac91..e146983df7 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -526,6 +526,30 @@ pub async fn list_accounts( Ok(all_accounts) } +#[tauri::command] +pub fn show_mnemonic_for_account_in_password( + account_id: String, + password: String, +) -> Result { + log::info!("Getting mnemonic for: {account_id}"); + let id = wallet_storage::AccountId::new(DEFAULT_WALLET_ACCOUNT_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id); + let password = wallet_storage::UserPassword::new(password); + let stored_account = wallet_storage::load_existing_wallet_login_information(&id, &password)?; + + let mnemonic = match stored_account { + StoredLogin::Mnemonic(_) => return Err(BackendError::WalletUnexpectedMnemonicAccount), + StoredLogin::Multiple(ref accounts) => accounts + .get_account(&account_id) + .ok_or(BackendError::NoSuchIdInWalletLoginEntry)? + .account + .mnemonic() + .clone(), + }; + + Ok(mnemonic.to_string()) +} + #[tauri::command] pub async fn sign_in_decrypted_account( account_id: &str,