From c7be4676851983127b92283155131852ed69dc0f Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 20 Sep 2023 14:46:25 +0200 Subject: [PATCH 001/101] DOC: dev-portal: initializ events page --- documentation/dev-portal/src/SUMMARY.md | 3 + .../dev-portal/src/events/hcpp23-serinko.md | 113 ++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 documentation/dev-portal/src/events/hcpp23-serinko.md diff --git a/documentation/dev-portal/src/SUMMARY.md b/documentation/dev-portal/src/SUMMARY.md index dcd96a9eef0..a30434e5b8c 100644 --- a/documentation/dev-portal/src/SUMMARY.md +++ b/documentation/dev-portal/src/SUMMARY.md @@ -50,6 +50,9 @@ - [Sending a Message Through the Mixnet](tutorials/simple-service-provider/sending-message.md) +# Events +- [HCPP23-serinko](./events/hcpp23-serinko.md) + # FAQ - [General](faq/general-faq.md) diff --git a/documentation/dev-portal/src/events/hcpp23-serinko.md b/documentation/dev-portal/src/events/hcpp23-serinko.md new file mode 100644 index 00000000000..6f8604338fa --- /dev/null +++ b/documentation/dev-portal/src/events/hcpp23-serinko.md @@ -0,0 +1,113 @@ +# HCPP 2023 - Securing the Lunarpunks Workshop + +The workshop will introduce ***why*** and ***how to use Nym platform as a network protection*** layer when using some of our favorite privacy applications. This page serves as an accessible guide alongside the talk and it includes all the steps, per-requizities and dependencies needed. Preferably the users interested in this setup start downloading and building the tools before the workshop or in the beginning of it so the limited time can be used for questions and addressing problems. This guide will stay online for another week after the event just in case people were not finished and want to catch up later. + +This page is a *how to guide* so it contains the setup steps only, to see the entire presentation please come to XXX at YYY. + +## Preparation + +During this workshop we will introduce NymConnect and Socks5 client. The difference between them is that the Socks5 client does everything Nymconnect does, but it has more optionality as it's run in a commandline. NymConnect is a one-button GUI application that wraps around the `nym-socks5-client` for proxying application traffic through the Mixnet. + +We will learn how to run over Nym Mixnet the following applications: Electrum Bitcoin wallet, Monero wallet (desktop and CLI), Matrix (Element app) and ircd chat. For those who want to run ircd through the Mixnet, `nym-socks5-client` client is a must. For all other applications you can choose if you settle with our slick app NymConnect which does all the job in the background or you prefer Socks5 client. + +> Any syntax in `<>` brackets is a user's/version unique variable. Exchange with a corresponding name without the `<>` brackets. + +## NymConnect Installation + +NymConnect for everyone who does not want to install and run `nym-socks5-client`. NymConnect is plug and play - fast and easy to download and run. It connects automatically to Electrum Bitcoin wallet, Monero wallet (desktop and CLI) and Matrix (Element app) after we set them up. + +1. [Download](https://nymtech.net/download/nymconnect) NymConnect +2. On Linux and Mac, make executable by opening terminal in the same directory and run: +```sh +chmod +x ./nym-connect_.AppImage +``` +3. Start the application +4. Click on `Connect` button to initialize the connection with the Mixnet +5. Anytime later you'll need to setup Host and Port in your applications, click on `IP` and `Port` to copy the values to clipboard +6. In case you have problems such as `Gateway Issues`, try to reconnect or restart the application + +## Building Nym Platform + +If you prefer to run to run `nym-socks5-client` the possibility is to download the pre-build binary or build the entire platform. To run ircd through the mixnet `nym-socks5-client` and `nym-network-requester` are mandatory. Before you start with donwload and installation, make sure you are on the same machine from which you connect to ircd. + +If you prefer to run to run `nym-socks5-client` the possibility is to download the pre-build binary or build the entire platform. To run ircd through the mixnet `nym-socks5-client` and `nym-network-requester` are mandatory. Before you start with download and installation, make sure you are on the same machine from which you connect to ircd. + +We recommend to clone and build the entire platform instead of individual binaries as it offers an easier update and more options down the road, however it takes a basic commandline knowledge and longer time. The [Nym platform](https://github.com/nymtech/nym) is written in Rust. For that to work we will need a few pre-requisities. If you prefer to download individual pre-build binaries, skip this part and go directly that chapter. + +### Prerequisites +- Debian/Ubuntu: `pkg-config`, `build-essential`, `libssl-dev`, `curl`, `jq`, `git` + +``` +apt install pkg-config build-essential libssl-dev curl jq git +``` + +- Arch/Manjaro: `base-devel` + +``` +pacman -S base-devel +``` + +- Mac OS X: `pkg-config` , `brew`, `openss1`, `protobuf`, `curl`, `git` +Running the following the script installs Homebrew and the above dependencies: + +``` +/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" +``` + +- `Rust & cargo >= {{minimum_rust_version}}` + +We recommend using the [Rust shell script installer](https://www.rust-lang.org/tools/install). Installing cargo from your package manager (e.g. `apt`) is not recommended as the packaged versions are usually too old. + +If you really don't want to use the shell script installer, the [Rust installation docs](https://forge.rust-lang.org/infra/other-installation-methods.html) contain instructions for many platforms. + +### Download and build Nym binaries +The following commands will compile binaries into the `nym/target/release` directory: + +```sh +rustup update +git clone https://github.com/nymtech/nym.git +cd nym +git checkout master # master branch has the latest release version: `develop` will most likely be incompatible with deployed public networks +cargo build --release # build your binaries with **mainnet** configuration +``` + +Quite a bit of stuff gets built. The key working parts for the workshop are: + +* [socks5 client](https://nymtech.net/docs/clients/socks5-client.html): `nym-socks5-client` +* [network requester](https://nymtech.net/operators/nodes/network-requester-setup.html): `nym-network-requester` + +## Pre-built Binaries + +The [Github releases page](https://github.com/nymtech/nym/releases) has pre-built binaries which should work on Ubuntu 20.04 and other Debian-based systems, but at this stage cannot be guaranteed to work everywhere. + +If the pre-built binaries don't work or are unavailable for your system, you will need to build the platform yourself. + +All Nym binaries must first be made executable. + +To make a binary executable, open terminal in the same directory and run: + +```sh +chmod +x ./ +# for example: chmod +x ./nym-network-requester +``` + +### Initialize Sock5 Client and Network Requester + +Whether you build the entire platform or downloaded binaries, they need to be initialised with `init` before being `run`. + +Navigate in your terminal to the directory where you have your `nym-socks5-client` and `nym-network-requester`. In case you build the entire platform it's in `nym/target/release` - you can change directory from the one where you build by: + +```sh +cd target/release +``` + +The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc. + +The `init` command will also create the necessary keypairs and configuration files at `~/.nym///` if these files do not already exist. **It will not overwrite existing keypairs if they are present.** + +You can reconfigure your binaries at any time by editing the config file located at `~/.nym///config/config.toml` and restarting the binary process. + +Once you have run `init`, you can start your binary with the `run` command, usually only accompanied by the `id` of the binary that you specified. + +This `id` is **never** transmitted over the network, and is used to select which local config and key files to use for startup. + From 5eb935a07982c095d93660b96aed39514c5c036c Mon Sep 17 00:00:00 2001 From: serinko <97586125+serinko@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:18:44 +0200 Subject: [PATCH 002/101] guide to electrum setup --- .../dev-portal/src/events/hcpp23-serinko.md | 66 ++++++++++++++++++- 1 file changed, 63 insertions(+), 3 deletions(-) diff --git a/documentation/dev-portal/src/events/hcpp23-serinko.md b/documentation/dev-portal/src/events/hcpp23-serinko.md index 6f8604338fa..ef71487f14c 100644 --- a/documentation/dev-portal/src/events/hcpp23-serinko.md +++ b/documentation/dev-portal/src/events/hcpp23-serinko.md @@ -91,23 +91,83 @@ chmod +x ./ # for example: chmod +x ./nym-network-requester ``` -### Initialize Sock5 Client and Network Requester +## Initialize Sock5 Client and Network Requester -Whether you build the entire platform or downloaded binaries, they need to be initialised with `init` before being `run`. +```admonish info +If you want to run your applications over NymConnect skip this chapter. `nym-socks5-client` and `nym-network-requester` is a must if you want to run ircd through the Mixnet. +``` +Whether you build the entire platform or downloaded binaries, `nym-socks5-client` and `nym-network-requester` need to be initialised with `init` before being `run`. -Navigate in your terminal to the directory where you have your `nym-socks5-client` and `nym-network-requester`. In case you build the entire platform it's in `nym/target/release` - you can change directory from the one where you build by: +In your terminal navigate to the directory where you have your `nym-socks5-client` and `nym-network-requester`. In case you build the entire platform it's in `nym/target/release` - you can change directory from the one where you build by: ```sh cd target/release ``` +**Network Requester** + The `init` command is usually where you pass flags specifying configuration arguments such as the gateway you wish to communicate with, the ports you wish your binary to listen on, etc. The `init` command will also create the necessary keypairs and configuration files at `~/.nym///` if these files do not already exist. **It will not overwrite existing keypairs if they are present.** You can reconfigure your binaries at any time by editing the config file located at `~/.nym///config/config.toml` and restarting the binary process. + +To run [ircd](https://darkrenaissance.github.io/darkfi/clients/nym_outbound.html) through the Mixnet you need to run your own [Network Requester](https://nymtech.net/operators/nodes/network-requester-setup.html) is needed to add known peer's domains/addresses to `~/.nym/service-providers/network-requester/allowed.list`. For all other applications `nym-socks5-client` (or NymCOnnect) is enough, no need to initialize and run `nym-network-requester`. + +Here are the steps to initialize `nym-network-requester`: + +```sh +1. cd to the directory with your binaries +2. ./nym-network-requester init --id +``` +This will print you information about your client `
`, it will look like: +```sh +The address of this client is: 8hUvtEyZK8umsdxxPS2BizQhEDmbNeXEPBZLgscE57Zh.5P2bWn6WybVL8QgoPEUHf6h2zXktmwrWaqaucEBZy7Vb@5vC8spDvw5VDQ8Zvd9fVvBhbUDv9jABR4cXzd4Kh5vz +``` + +**Socks5 Client** + +If you run `nym-socks5-client` instead of NymConnect, you can choose your `--provider` [here](https://explorer.nymtech.net/network-components/service-providers) or leave that flag empty and your client will chose one randomly. To run ircd, you will need to connect it to your `nym-network-requester` by using your `
` for your `nym-socks5-client` initialisation and add a flag `--use-reply-surbs true`. Run the command in the next terminal window: + +```sh +# to connect to your nym-network-requester +./nym-socks5-client init --use-reply-surbs true --id --provider
+ +# to run just the socks5 client +./nym-socks5-client init --id +``` + +**Run Clients** + Once you have run `init`, you can start your binary with the `run` command, usually only accompanied by the `id` of the binary that you specified. This `id` is **never** transmitted over the network, and is used to select which local config and key files to use for startup. +```sh +# network requester +./nym-network-requester run --id + +# socks5 client (in other terminal window) +./nym-socks5-client run --id +``` + +## Connect Privacy Enhanced Applications (PEApps) + +For simplification Electrum, Monero wallet and Matrix (Element) will be connected over NymConnect and ircd over `nym-socks5-client`. Whichever way you want to use, make sure it's connected to the Mixnet. + +```admonish info +This aims to connect your favourite applications Nym Mixnet, therefore does not include how to install these applications. +``` + +### Electrum Bitcoin wallet + +To download the Electrum visit the [official webpage](https://electrum.org/#download). To connect to the Mixnet follow these steps: + +1. Start and connect NymConnect (or `nym-socks5-client`) +2. Start your Electrum Bitcoin wallet +3. Go to: *Tools* -> *Network* -> *Proxy* +4. Set *Use proxy* to ✅, choose `SOCKS5` from the drop-down and add the values from your NymConnect application +5. Now your Electrum Bitcoin wallet will be connected only if your NymConnect or `nym-socks5-client` are connected. + +![Electrum Bitcoin wallet setup](../images/electrum_tutorial/electrum.gif) From b92527437a4a12c8615d02dd82bf371f7d40c112 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 15 Sep 2023 12:36:39 +0100 Subject: [PATCH 003/101] introduce edit account name --- nym-wallet/package.json | 2 +- .../src/components/Accounts/AccountItem.tsx | 36 ++++++++++++------- .../Accounts/modals/EditAccountModal.tsx | 26 ++++++++------ .../Accounts/modals/MnemonicModal.tsx | 2 +- nym-wallet/src/context/accounts.tsx | 4 +-- nym-wallet/src/context/mocks/accounts.tsx | 2 +- 6 files changed, 44 insertions(+), 28 deletions(-) diff --git a/nym-wallet/package.json b/nym-wallet/package.json index f3b4ae62d60..50b10f0c0e4 100644 --- a/nym-wallet/package.json +++ b/nym-wallet/package.json @@ -31,7 +31,7 @@ "@nymproject/mui-theme": "^1.0.0", "@nymproject/react": "^1.0.0", "@nymproject/types": "^1.0.0", - "@nymproject/node-tester": ">=1.2.0-rc.5", + "@nymproject/node-tester": ">=1.2.0-rc.5 || ^1", "@storybook/react": "^6.5.15", "@tauri-apps/api": "^1.2.0", "@tauri-apps/tauri-forage": "^1.0.0-beta.2", diff --git a/nym-wallet/src/components/Accounts/AccountItem.tsx b/nym-wallet/src/components/Accounts/AccountItem.tsx index c5b785319c1..2a0b70d5df1 100644 --- a/nym-wallet/src/components/Accounts/AccountItem.tsx +++ b/nym-wallet/src/components/Accounts/AccountItem.tsx @@ -1,5 +1,15 @@ import React, { useContext } from 'react'; -import { Box, ListItem, ListItemAvatar, ListItemButton, ListItemText, Tooltip, Typography } from '@mui/material'; +import EditIcon from '@mui/icons-material/Create'; +import { + Box, + IconButton, + ListItem, + ListItemAvatar, + ListItemButton, + ListItemText, + Tooltip, + Typography, +} from '@mui/material'; import { useClipboard } from 'use-clipboard-copy'; import { AccountsContext } from 'src/context'; import { AccountAvatar } from './AccountAvatar'; @@ -13,13 +23,24 @@ export const AccountItem = ({ address: string; onSelectAccount: () => void; }) => { - const { selectedAccount, setDialogToDisplay, setAccountMnemonic } = useContext(AccountsContext); + const { selectedAccount, setDialogToDisplay, setAccountMnemonic, handleAccountToEdit } = useContext(AccountsContext); const { copy, copied } = useClipboard({ copiedTimeout: 1000 }); return ( { + handleAccountToEdit(name); + setDialogToDisplay('Edit'); + }} + > + + + } > @@ -59,17 +80,6 @@ export const AccountItem = ({ } /> - {/* edit and remove accounts todo */} - {/* - { - e.stopPropagation(); - handleAccountToEdit(name); - }} - > - - - */} ); diff --git a/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx index e5dbd0464ee..a9625ee3544 100644 --- a/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx @@ -14,16 +14,18 @@ import { import { Close } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import { AccountsContext } from 'src/context'; +import { StyledBackButton } from 'src/components/StyledBackButton'; export const EditAccountModal = () => { + const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount, handleAccountToEdit } = + useContext(AccountsContext); const [accountName, setAccountName] = useState(''); - const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount } = useContext(AccountsContext); - const theme = useTheme(); - useEffect(() => { - setAccountName(accountToEdit ? accountToEdit?.id : ''); + if (accountToEdit) { + setAccountName(accountToEdit.id); + } }, [accountToEdit]); return ( @@ -38,17 +40,15 @@ export const EditAccountModal = () => { - Edit account name + Rename account setDialogToDisplay('Accounts')}> - - New wallet address - + Type the new name for your account { /> - + + { + handleAccountToEdit(undefined); + setDialogToDisplay('Accounts'); + }} + /> diff --git a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx index 611f4b6869a..8dc21c6673a 100644 --- a/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/MnemonicModal.tsx @@ -48,7 +48,7 @@ export const MnemonicModal = () => { Display mnemonic - theme.palette.text.disabled }}> + {`Display mnemonic for: ${accountMnemonic?.accountName}`} diff --git a/nym-wallet/src/context/accounts.tsx b/nym-wallet/src/context/accounts.tsx index e3937ff8ad2..4ef3d2b0014 100644 --- a/nym-wallet/src/context/accounts.tsx +++ b/nym-wallet/src/context/accounts.tsx @@ -17,7 +17,7 @@ type TAccounts = { handleAddAccount: (data: { accountName: string; mnemonic: string; password: string }) => void; setDialogToDisplay: (dialog?: TAccountsDialog) => void; handleSelectAccount: (data: { accountName: string; password: string }) => Promise; - handleAccountToEdit: (accountId: string) => void; + handleAccountToEdit: (accountId: string | undefined) => void; handleEditAccount: (account: AccountEntry) => void; handleImportAccount: (account: AccountEntry) => void; handleGetAccountMnemonic: (data: { password: string; accountName: string }) => void; @@ -72,7 +72,7 @@ export const AccountsProvider: FCWithChildren = ({ children }) => { const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]); - const handleAccountToEdit = (accountName: string) => + const handleAccountToEdit = (accountName: string | undefined) => setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async ({ accountName, password }: { accountName: string; password: string }) => { diff --git a/nym-wallet/src/context/mocks/accounts.tsx b/nym-wallet/src/context/mocks/accounts.tsx index 4186c8df340..d3efeef91ae 100644 --- a/nym-wallet/src/context/mocks/accounts.tsx +++ b/nym-wallet/src/context/mocks/accounts.tsx @@ -33,7 +33,7 @@ export const MockAccountsProvider: FCWithChildren = ({ children }) => { const handleImportAccount = (account: AccountEntry) => setAccounts((accs) => [...(accs ? [...accs] : []), account]); - const handleAccountToEdit = (accountName: string) => + const handleAccountToEdit = (accountName: string | undefined) => setAccountToEdit(accounts?.find((acc) => acc.id === accountName)); const handleSelectAccount = async ({ accountName }: { accountName: string; password: string }) => { From 0f8d1f643916d7f437aae6440a9db7c35614fe11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 09:37:06 +0200 Subject: [PATCH 004/101] Add unit test for adding duplicate account id --- .../src-tauri/src/wallet_storage/mod.rs | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index fbaffb8d872..689d4588081 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1513,11 +1513,63 @@ mod tests { .unwrap(); assert!(matches!( - append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password,), + append_account_to_login_at_file(&wallet_file, account1, hd_path, id1, id2, &password), Err(BackendError::WalletMnemonicAlreadyExistsInWalletLogin), )) } + #[test] + fn append_the_same_account_name_twice_fails() { + let _ = pretty_env_logger::init(); + + let store_dir = tempdir().unwrap(); + let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); + let mnemonic1 = Mnemonic::generate(24).unwrap(); + let mnemonic2 = Mnemonic::generate(24).unwrap(); + let mnemonic3 = Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + // The top-level login id. NOTE: the first account id is always set to default. + let login_id = LoginId::new("my_login_id".to_string()); + + // Store the first account under login_id. The first account id is always set to default + // name. + store_login_with_multiple_accounts_at_file( + &wallet_file, + mnemonic1.clone(), + hd_path.clone(), + login_id.clone(), + &password, + ) + .unwrap(); + + // Append another account (account2) to the same login (login_id) + let account2 = AccountId::new("account_2".to_string()); + + append_account_to_login_at_file( + &wallet_file, + mnemonic2.clone(), + hd_path.clone(), + login_id.clone(), + account2.clone(), + &password, + ) + .unwrap(); + + // Appending the third account, with same account id will fail + assert!(matches!( + append_account_to_login_at_file( + &wallet_file, + mnemonic3.clone(), + hd_path, + login_id, + account2, + &password, + ), + Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin), + )) + } + #[test] fn delete_the_same_account_twice_for_a_login_fails() { let store_dir = tempdir().unwrap(); From 9ae5ee38f66d42915a470982872d6b8f393d92af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 10:35:14 +0200 Subject: [PATCH 005/101] Add rename account to backend structs --- .../src/wallet_storage/account_data.rs | 16 +++ .../src-tauri/src/wallet_storage/mod.rs | 103 ++++++++++++++++++ 2 files changed, 119 insertions(+) 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 a609caf5803..3501e94d60d 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -226,6 +226,10 @@ impl MultipleAccounts { self.accounts.iter().find(|account| &account.id == id) } + pub(crate) fn get_account_mut(&mut self, id: &AccountId) -> Option<&mut WalletAccount> { + self.accounts.iter_mut().find(|account| &account.id == id) + } + pub(crate) fn get_account_with_mnemonic( &self, mnemonic: &bip39::Mnemonic, @@ -273,6 +277,14 @@ impl MultipleAccounts { self.accounts.retain(|accounts| &accounts.id != id); Ok(()) } + + pub(crate) fn rename(&mut self, id: &AccountId, new_id: &AccountId) -> Result<(), BackendError> { + let account = self + .get_account_mut(id) + .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?; + account.rename_id(new_id.clone()); + Ok(()) + } } impl From> for MultipleAccounts { @@ -300,6 +312,10 @@ impl WalletAccount { &self.id } + pub(crate) fn rename_id(&mut self, new_id: AccountId) { + self.id = new_id; + } + pub(crate) fn mnemonic(&self) -> &bip39::Mnemonic { match self.account { AccountData::Mnemonic(ref account) => account.mnemonic(), diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 689d4588081..a60bf61d183 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -425,6 +425,35 @@ fn remove_account_from_login_at_file( } } +fn rename_account_in_login_at_file( + filepath: &Path, + id: &LoginId, + account_id: &AccountId, + new_account_id: &AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + log::info!("Renaming associated account in login account: {id}"); + let mut stored_wallet = load_existing_wallet_at_file(filepath)?; + + let mut decrypted_login = stored_wallet.decrypt_login(id, password)?; + + // Rename the account + match decrypted_login { + StoredLogin::Mnemonic(_) => { + log::warn!("Encountered mnemonic login instead of list of accounts, aborting"); + return Err(BackendError::WalletUnexpectedMnemonicAccount); + } + StoredLogin::Multiple(ref mut accounts) => { + accounts.rename(account_id, new_account_id)?; + } + }; + + // Encrypt the new updated login and write to file + let encrypted_accounts = EncryptedLogin::encrypt(id.clone(), &decrypted_login, password)?; + stored_wallet.replace_encrypted_login(encrypted_accounts)?; + write_to_file(filepath, &stored_wallet) +} + #[cfg(test)] mod tests { use crate::wallet_storage::account_data::{MnemonicAccount, WalletAccount}; @@ -1893,6 +1922,80 @@ mod tests { assert_eq!(account.hd_path(), &hd_path); } + #[test] + fn rename_one_account_in_login_with_two_accounts() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = Mnemonic::generate(24).unwrap(); + let account2 = Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let login_id = LoginId::new("first".to_string()); + let account_id2 = AccountId::new("second".to_string()); + let renamed_account_id2 = AccountId::new("new_second".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet, + account1.clone(), + hd_path.clone(), + login_id.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet, + account2.clone(), + hd_path.clone(), + login_id.clone(), + account_id2.clone(), + &password, + ) + .unwrap(); + + // Load and confirm + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1.clone(), hd_path.clone()), + ), + WalletAccount::new( + account_id2.clone(), + MnemonicAccount::new(account2.clone(), hd_path.clone()), + ), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + + // Rename the second account to a new name + rename_account_in_login_at_file( + &wallet, + &login_id, + &account_id2, + &renamed_account_id2, + &password, + ) + .unwrap(); + + // Load and confirm + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1, hd_path.clone()), + ), + WalletAccount::new( + renamed_account_id2.clone(), + MnemonicAccount::new(account2, hd_path.clone()), + ), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + // 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] From 9fe1ee64363eade7cf0b4335b628875db1b3a26b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 11:12:24 +0200 Subject: [PATCH 006/101] Check against renaming to existing name --- .../src/wallet_storage/account_data.rs | 3 + .../src-tauri/src/wallet_storage/mod.rs | 124 ++++++++++++++++++ 2 files changed, 127 insertions(+) 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 3501e94d60d..d4ec75a009e 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -279,6 +279,9 @@ impl MultipleAccounts { } pub(crate) fn rename(&mut self, id: &AccountId, new_id: &AccountId) -> Result<(), BackendError> { + if self.get_account(new_id).is_some() { + return Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin); + } let account = self .get_account_mut(id) .ok_or(BackendError::WalletNoSuchAccountIdInWalletLogin)?; diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index a60bf61d183..67afbe2b8cc 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1922,6 +1922,54 @@ mod tests { assert_eq!(account.hd_path(), &hd_path); } + #[test] + fn rename_first_account_in_login() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let login_id = LoginId::new("first".to_string()); + + store_login_with_multiple_accounts_at_file( + &wallet, + account1.clone(), + hd_path.clone(), + login_id.clone(), + &password, + ) + .unwrap(); + + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1.clone(), hd_path.clone()), + )] + .into(); + assert_eq!(loaded_accounts, &expected); + + let renamed_account = AccountId::new("new_first".to_string()); + + rename_account_in_login_at_file( + &wallet, + &login_id, + &DEFAULT_FIRST_ACCOUNT_NAME.into(), + &renamed_account.clone(), + &password, + ) + .unwrap(); + + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![WalletAccount::new( + renamed_account.clone(), + MnemonicAccount::new(account1.clone(), hd_path.clone()), + )] + .into(); + assert_eq!(loaded_accounts, &expected); + } + #[test] fn rename_one_account_in_login_with_two_accounts() { let store_dir = tempdir().unwrap(); @@ -1996,6 +2044,82 @@ mod tests { assert_eq!(loaded_accounts, &expected); } + #[test] + fn rename_account_into_existing_account_id_fails() { + let store_dir = tempdir().unwrap(); + let wallet = store_dir.path().join(WALLET_INFO_FILENAME); + let account1 = Mnemonic::generate(24).unwrap(); + let account2 = Mnemonic::generate(24).unwrap(); + let hd_path: DerivationPath = COSMOS_DERIVATION_PATH.parse().unwrap(); + let password = UserPassword::new("password".to_string()); + let login_id = LoginId::new("first".to_string()); + let account_id2 = AccountId::new("second".to_string()); + let renamed_account_id2 = DEFAULT_FIRST_ACCOUNT_NAME.into(); + + store_login_with_multiple_accounts_at_file( + &wallet, + account1.clone(), + hd_path.clone(), + login_id.clone(), + &password, + ) + .unwrap(); + + append_account_to_login_at_file( + &wallet, + account2.clone(), + hd_path.clone(), + login_id.clone(), + account_id2.clone(), + &password, + ) + .unwrap(); + + // Load and confirm + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1.clone(), hd_path.clone()), + ), + WalletAccount::new( + account_id2.clone(), + MnemonicAccount::new(account2.clone(), hd_path.clone()), + ), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + + // Rename the second account to the name of the first one fails + assert!(matches!( + rename_account_in_login_at_file( + &wallet, + &login_id, + &account_id2, + &renamed_account_id2, + &password, + ), + Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin) + )); + + // Load and confirm nothing was changed + let loaded_login = load_existing_login_at_file(&wallet, &login_id, &password).unwrap(); + let loaded_accounts = loaded_login.as_multiple_accounts().unwrap(); + let expected = vec![ + WalletAccount::new( + DEFAULT_FIRST_ACCOUNT_NAME.into(), + MnemonicAccount::new(account1, hd_path.clone()), + ), + WalletAccount::new( + account_id2.clone(), + MnemonicAccount::new(account2, hd_path.clone()), + ), + ] + .into(); + assert_eq!(loaded_accounts, &expected); + } + // 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] From 4f4cb83456a2e3b873937e3302515ea7a6c2865e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 13:43:18 +0200 Subject: [PATCH 007/101] Add tauri functions --- nym-wallet/src-tauri/src/main.rs | 3 +- .../src/operations/mixnet/account.rs | 30 ++++++++++++++++++- .../src-tauri/src/wallet_storage/mod.rs | 14 +++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src-tauri/src/main.rs b/nym-wallet/src-tauri/src/main.rs index dd7cb4558ce..148dcded2bd 100644 --- a/nym-wallet/src-tauri/src/main.rs +++ b/nym-wallet/src-tauri/src/main.rs @@ -42,17 +42,18 @@ fn main() { mixnet::account::connect_with_mnemonic, mixnet::account::create_new_mnemonic, mixnet::account::create_password, - mixnet::account::update_password, mixnet::account::does_password_file_exist, mixnet::account::get_balance, mixnet::account::list_accounts, mixnet::account::logout, mixnet::account::remove_account_for_password, mixnet::account::remove_password, + mixnet::account::rename_account_for_password, mixnet::account::show_mnemonic_for_account_in_password, mixnet::account::sign_in_with_password, mixnet::account::sign_in_with_password_and_account_id, mixnet::account::switch_network, + mixnet::account::update_password, mixnet::account::validate_mnemonic, mixnet::admin::get_contract_settings, mixnet::admin::update_contract_settings, diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index 4060b1e8107..acc59539d0a 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -484,7 +484,8 @@ pub async fn add_account_for_password( }) } -// The first `AccoundId` when converting is the `LoginId` for the entry that was loaded. +// Set the tauri state with all the accounts in the wallet. +// NOTE: the first `AccoundId` when converting is the `LoginId` for the entry that was loaded. async fn set_state_with_all_accounts( stored_login: wallet_storage::StoredLogin, first_id_when_converting: wallet_storage::AccountId, @@ -547,6 +548,33 @@ pub async fn remove_account_for_password( set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await } +#[tauri::command] +pub async fn rename_account_for_password( + password: UserPassword, + account_id: &str, + new_account_id: &str, + state: tauri::State<'_, WalletState>, +) -> Result<(), BackendError> { + log::info!("Renaming account: {account_id} to {new_account_id}"); + // Currently we only support a single, default, id in the wallet + let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); + let account_id = wallet_storage::AccountId::new(account_id.to_string()); + let new_account_id = wallet_storage::AccountId::new(new_account_id.to_string()); + wallet_storage::rename_account_in_login( + &login_id, + &account_id, + &new_account_id, + &password, + )?; + + // Load from storage to reset the internal tuari state + let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; + // NOTE: Since we removed from a multi-account login, this id shouldn't be needed, but setting + // the state is supposed to be a general function + let first_account_id_when_converting = login_id.into(); + set_state_with_all_accounts(stored_login, first_account_id_when_converting, state).await +} + fn derive_address( mnemonic: bip39::Mnemonic, prefix: &str, diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index 67afbe2b8cc..c7d033feffb 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -425,6 +425,20 @@ fn remove_account_from_login_at_file( } } +/// Rename an account inside the encrypted login. +/// - If the encrypted login is just a single account, abort to be on the safe side. +/// - If the name already exists, abort. +pub(crate) fn rename_account_in_login( + id: &LoginId, + account_id: &AccountId, + new_account_id: &AccountId, + password: &UserPassword, +) -> Result<(), BackendError> { + let store_dir = get_storage_directory()?; + let filepath = store_dir.join(WALLET_INFO_FILENAME); + rename_account_in_login_at_file(&filepath, id, account_id, new_account_id, password) +} + fn rename_account_in_login_at_file( filepath: &Path, id: &LoginId, From 23d11ce52328303c3acc4ec6f68d0b0dcaeed5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 13:43:31 +0200 Subject: [PATCH 008/101] rustfmt --- nym-wallet/src-tauri/src/operations/mixnet/account.rs | 7 +------ nym-wallet/src-tauri/src/wallet_storage/account_data.rs | 6 +++++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/nym-wallet/src-tauri/src/operations/mixnet/account.rs b/nym-wallet/src-tauri/src/operations/mixnet/account.rs index acc59539d0a..6704009cb37 100644 --- a/nym-wallet/src-tauri/src/operations/mixnet/account.rs +++ b/nym-wallet/src-tauri/src/operations/mixnet/account.rs @@ -560,12 +560,7 @@ pub async fn rename_account_for_password( let login_id = wallet_storage::LoginId::new(DEFAULT_LOGIN_ID.to_string()); let account_id = wallet_storage::AccountId::new(account_id.to_string()); let new_account_id = wallet_storage::AccountId::new(new_account_id.to_string()); - wallet_storage::rename_account_in_login( - &login_id, - &account_id, - &new_account_id, - &password, - )?; + wallet_storage::rename_account_in_login(&login_id, &account_id, &new_account_id, &password)?; // Load from storage to reset the internal tuari state let stored_login = wallet_storage::load_existing_login(&login_id, &password)?; 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 d4ec75a009e..6c235bed34f 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/account_data.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/account_data.rs @@ -278,7 +278,11 @@ impl MultipleAccounts { Ok(()) } - pub(crate) fn rename(&mut self, id: &AccountId, new_id: &AccountId) -> Result<(), BackendError> { + pub(crate) fn rename( + &mut self, + id: &AccountId, + new_id: &AccountId, + ) -> Result<(), BackendError> { if self.get_account(new_id).is_some() { return Err(BackendError::WalletAccountIdAlreadyExistsInWalletLogin); } From 7429487f30d3d55f52fc828c50765da45dd0ea6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jon=20H=C3=A4ggblad?= Date: Thu, 21 Sep 2023 14:05:38 +0200 Subject: [PATCH 009/101] fix clippy --- nym-wallet/Cargo.lock | 2 +- nym-wallet/src-tauri/src/wallet_storage/mod.rs | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/nym-wallet/Cargo.lock b/nym-wallet/Cargo.lock index b00fed546d1..3cb32da98fb 100644 --- a/nym-wallet/Cargo.lock +++ b/nym-wallet/Cargo.lock @@ -3512,7 +3512,7 @@ dependencies = [ [[package]] name = "nym_wallet" -version = "1.2.8" +version = "1.2.7" dependencies = [ "async-trait", "base64 0.13.1", diff --git a/nym-wallet/src-tauri/src/wallet_storage/mod.rs b/nym-wallet/src-tauri/src/wallet_storage/mod.rs index c7d033feffb..1ebfc82b905 100644 --- a/nym-wallet/src-tauri/src/wallet_storage/mod.rs +++ b/nym-wallet/src-tauri/src/wallet_storage/mod.rs @@ -1563,8 +1563,6 @@ mod tests { #[test] fn append_the_same_account_name_twice_fails() { - let _ = pretty_env_logger::init(); - let store_dir = tempdir().unwrap(); let wallet_file = store_dir.path().join(WALLET_INFO_FILENAME); let mnemonic1 = Mnemonic::generate(24).unwrap(); From be8b9e5a83f5f80ea635b61607391fe6aecefb5d Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 22 Sep 2023 12:03:37 +0100 Subject: [PATCH 010/101] pass button title and modal title as prop --- .../src/components/Accounts/modals/AccountsModal.tsx | 2 ++ .../components/Accounts/modals/ConfirmPasswordModal.tsx | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/nym-wallet/src/components/Accounts/modals/AccountsModal.tsx b/nym-wallet/src/components/Accounts/modals/AccountsModal.tsx index 4b7f7020004..7c7df2e7ef0 100644 --- a/nym-wallet/src/components/Accounts/modals/AccountsModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/AccountsModal.tsx @@ -33,7 +33,9 @@ export const AccountsModal = () => { if (accountToSwitchTo) return ( { handleClose(); setDialogToDisplay('Accounts'); diff --git a/nym-wallet/src/components/Accounts/modals/ConfirmPasswordModal.tsx b/nym-wallet/src/components/Accounts/modals/ConfirmPasswordModal.tsx index 07baa9b8d81..11d42c55a9d 100644 --- a/nym-wallet/src/components/Accounts/modals/ConfirmPasswordModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/ConfirmPasswordModal.tsx @@ -6,10 +6,14 @@ import { AccountsContext } from 'src/context'; export const ConfirmPasswordModal = ({ accountName, + modalTitle, onClose, onConfirm, + buttonTitle, }: { accountName?: string; + modalTitle: string; + buttonTitle: string; onClose: () => void; onConfirm: (password: string) => Promise; }) => { @@ -27,7 +31,7 @@ export const ConfirmPasswordModal = ({ > - Switch account + {modalTitle} Confirm password @@ -36,7 +40,7 @@ export const ConfirmPasswordModal = ({ onConfirm={onConfirm} error={error} isLoading={isLoading} - buttonTitle="Switch account" + buttonTitle={buttonTitle} onCancel={onClose} /> From f35396481fd8d515071c26e07edafeb16e2fb348 Mon Sep 17 00:00:00 2001 From: fmtabbara Date: Fri, 22 Sep 2023 12:04:07 +0100 Subject: [PATCH 011/101] take user password when editing account name --- .../Accounts/modals/EditAccountModal.tsx | 53 ++++++++++++++----- nym-wallet/src/context/accounts.tsx | 39 ++++++++++++-- nym-wallet/src/context/mocks/accounts.tsx | 11 +++- nym-wallet/src/requests/account.ts | 15 ++++++ 4 files changed, 99 insertions(+), 19 deletions(-) diff --git a/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx b/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx index a9625ee3544..25b306a031f 100644 --- a/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx +++ b/nym-wallet/src/components/Accounts/modals/EditAccountModal.tsx @@ -15,23 +15,58 @@ import { Close } from '@mui/icons-material'; import { useTheme } from '@mui/material/styles'; import { AccountsContext } from 'src/context'; import { StyledBackButton } from 'src/components/StyledBackButton'; +import { ConfirmPasswordModal } from './ConfirmPasswordModal'; export const EditAccountModal = () => { - const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount, handleAccountToEdit } = + const { accountToEdit, dialogToDisplay, setDialogToDisplay, handleEditAccount, handleAccountToEdit, setError } = useContext(AccountsContext); + const [accountName, setAccountName] = useState(''); + const [showConfirmPassword, setShowConfirmPassword] = useState(false); const theme = useTheme(); + useEffect(() => { if (accountToEdit) { setAccountName(accountToEdit.id); } }, [accountToEdit]); + const handleClose = () => { + handleAccountToEdit(undefined); + setDialogToDisplay('Accounts'); + }; + + const onConfirmPassword = async (password: string) => { + if (accountToEdit) { + try { + await handleEditAccount({ account: accountToEdit, newAccountName: accountName, password }); + setShowConfirmPassword(false); + } catch (e) { + setError(`Error editing account: ${e}`); + } + } + }; + + if (showConfirmPassword) { + return ( + { + setShowConfirmPassword(false); + setError(undefined); + }} + onConfirm={onConfirmPassword} + /> + ); + } + return ( setDialogToDisplay('Accounts')} + onClose={handleClose} fullWidth PaperProps={{ style: { border: `1px solid ${theme.palette.nym.nymWallet.modal.border}` }, @@ -41,7 +76,7 @@ export const EditAccountModal = () => { Rename account - setDialogToDisplay('Accounts')}> + @@ -60,22 +95,14 @@ export const EditAccountModal = () => { - { - handleAccountToEdit(undefined); - setDialogToDisplay('Accounts'); - }} - /> +