From 85d30ef4588dbff82dd58960a243a531efaf6199 Mon Sep 17 00:00:00 2001 From: /alex/ Date: Thu, 7 Mar 2024 16:24:09 +0100 Subject: [PATCH] Remove ed25519 address generation from wallet (#1586) * rm ed25519 address generation from wallet * rename fn * rm with_address * update bindings * handle builder address cases * unused import * re-add address to wallet options * fix tests * re-enable placeholder test * move tests * nodejs: address to wallet options * revert going through json file for address * revert unrelated change * Update sdk/tests/client/secret_manager/address_generation.rs Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> * recipient Co-authored-by: DaughterOfMars * bad wildcards * PR suggestion * import * example doc Co-authored-by: DaughterOfMars * fix example * rename generate methods * Core: add SecretManager:GenerateEd25519AddressAsBech32 method * update examples and tests * Core: simplify SecretManager:GenerateEd25519AddressAsBech32 method * Python: update binding * nit * core: undo rename; update docs * add todo * Python: nits * NodeJs: add binding method * nit * sdk: undo rename; format * core: import * NodeJs: method suffix * fix test * nit * remove single address generation binding * Python: small fix --------- Co-authored-by: Thoralf-M <46689931+Thoralf-M@users.noreply.github.com> Co-authored-by: Thibault Martinez Co-authored-by: DaughterOfMars Co-authored-by: Thibault Martinez --- bindings/core/src/method/secret_manager.rs | 2 +- bindings/core/src/method/wallet.rs | 17 -- bindings/core/src/method_handler/wallet.rs | 22 +- .../examples/how_tos/wallet/create-wallet.ts | 11 - .../nodejs/examples/wallet/getting-started.ts | 11 - .../lib/secret_manager/secret-manager.ts | 5 +- .../client/generate-addresses-options.ts | 2 + bindings/nodejs/src/wallet.rs | 2 +- .../secret_manager/secret_manager.spec.ts | 48 ++++ .../iota_sdk/secret_manager/secret_manager.py | 55 ++++- bindings/python/src/wallet.rs | 2 +- bindings/python/tests/test_secret_manager.py | 24 ++ ...eration_test.py => test_wallet_address.py} | 8 +- bindings/wasm/src/wallet.rs | 2 +- cli/src/cli.rs | 2 +- cli/src/wallet_cli/mod.rs | 2 +- sdk/examples/client/02_address_balance.rs | 14 +- sdk/examples/wallet/ledger_nano.rs | 18 +- sdk/examples/wallet/logger.rs | 13 +- .../offline_signing/0_generate_address.rs | 11 +- .../offline_signing/1_prepare_transaction.rs | 9 +- sdk/examples/wallet/spammer.rs | 10 - sdk/src/client/api/address.rs | 59 ++++- sdk/src/wallet/core/builder.rs | 12 +- sdk/src/wallet/core/mod.rs | 8 +- .../core/operations/address_generation.rs | 93 -------- sdk/src/wallet/core/operations/mod.rs | 1 - sdk/src/wallet/operations/block.rs | 2 +- sdk/tests/client/common/constants.rs | 2 + sdk/tests/client/common/mod.rs | 14 +- .../secret_manager/address_generation.rs | 111 ++++++++++ sdk/tests/client/secret_manager/mnemonic.rs | 16 +- sdk/tests/client/secret_manager/mod.rs | 1 + sdk/tests/client/secret_manager/stronghold.rs | 22 +- sdk/tests/wallet/address_generation.rs | 209 ------------------ sdk/tests/wallet/common/mod.rs | 2 +- sdk/tests/wallet/mod.rs | 1 - 37 files changed, 380 insertions(+), 463 deletions(-) create mode 100644 bindings/nodejs/tests/secret_manager/secret_manager.spec.ts create mode 100644 bindings/python/tests/test_secret_manager.py rename bindings/python/tests/{address_generation_test.py => test_wallet_address.py} (91%) delete mode 100644 sdk/src/wallet/core/operations/address_generation.rs create mode 100644 sdk/tests/client/secret_manager/address_generation.rs delete mode 100644 sdk/tests/wallet/address_generation.rs diff --git a/bindings/core/src/method/secret_manager.rs b/bindings/core/src/method/secret_manager.rs index 2a13acbca0..4cb40cf985 100644 --- a/bindings/core/src/method/secret_manager.rs +++ b/bindings/core/src/method/secret_manager.rs @@ -19,7 +19,7 @@ use crate::OmittedDebug; #[serde(tag = "name", content = "data", rename_all = "camelCase")] #[non_exhaustive] pub enum SecretManagerMethod { - /// Generate Ed25519 addresses. + /// Generate multiple Ed25519 addresses at once. GenerateEd25519Addresses { /// Addresses generation options options: GetAddressesOptions, diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index e5d1bad209..1522beb9a8 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -19,7 +19,6 @@ use iota_sdk::{ client::{ api::{transaction_builder::Burn, PreparedTransactionDataDto, SignedTransactionDataDto}, node_manager::node::NodeAuth, - secret::GenerateAddressOptions, }, types::block::{ address::Hrp, @@ -456,27 +455,11 @@ pub enum WalletMethod { /// Expected response: [`OutputsData`](crate::Response::OutputsData) #[serde(rename_all = "camelCase")] UnspentOutputs { filter_options: Option }, - /// Emits an event for testing if the event system is working /// Expected response: [`Ok`](crate::Response::Ok) #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] EmitTestEvent { event: WalletEvent }, - - // TODO: reconsider whether to have the following methods on the wallet - /// Generate an address without storing it - /// Expected response: [`Bech32Address`](crate::Response::Bech32Address) - #[serde(rename_all = "camelCase")] - GenerateEd25519Address { - /// Account index - account_index: u32, - /// Account index - address_index: u32, - /// Options - options: Option, - /// Bech32 HRP - bech32_hrp: Option, - }, /// Get the ledger nano status /// Expected response: [`LedgerNanoStatus`](crate::Response::LedgerNanoStatus) #[cfg(feature = "ledger_nano")] diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index a4fbb1f626..cc7086f978 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -6,10 +6,7 @@ use std::time::Duration; use crypto::signatures::ed25519::PublicKey; use iota_sdk::{ client::api::{PreparedTransactionData, SignedTransactionData, SignedTransactionDataDto}, - types::{ - block::{address::ToBech32Ext, output::feature::BlockIssuerKeySource}, - TryFromDto, - }, + types::{block::output::feature::BlockIssuerKeySource, TryFromDto}, wallet::{types::TransactionWithMetadataDto, Wallet}, }; @@ -73,23 +70,6 @@ pub(crate) async fn call_wallet_method_internal( let ledger_nano_status = wallet.get_ledger_nano_status().await?; Response::LedgerNanoStatus(ledger_nano_status) } - WalletMethod::GenerateEd25519Address { - account_index, - address_index, - options, - bech32_hrp, - } => { - let address = wallet - .generate_ed25519_address(account_index, address_index, options) - .await?; - - let bech32_hrp = match bech32_hrp { - Some(bech32_hrp) => bech32_hrp, - None => *wallet.address().await.hrp(), - }; - - Response::Bech32Address(address.to_bech32(bech32_hrp)) - } #[cfg(feature = "stronghold")] WalletMethod::SetStrongholdPassword { password } => { wallet.set_stronghold_password(password).await?; diff --git a/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts b/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts index 882caa0736..1609378d19 100644 --- a/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts +++ b/bindings/nodejs/examples/how_tos/wallet/create-wallet.ts @@ -44,18 +44,7 @@ async function run() { // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! await secretManager.storeMnemonic(process.env.MNEMONIC as string); - const walletAddress = await secretManager.generateEd25519Addresses({ - coinType: CoinType.IOTA, - accountIndex: 0, - range: { - start: 0, - end: 1, - }, - bech32Hrp: 'tst', - }); - const walletOptions: WalletOptions = { - address: walletAddress[0], storagePath: process.env.WALLET_DB_PATH, clientOptions: { nodes: [process.env.NODE_URL as string], diff --git a/bindings/nodejs/examples/wallet/getting-started.ts b/bindings/nodejs/examples/wallet/getting-started.ts index 6d08c99e2d..95d119ce79 100644 --- a/bindings/nodejs/examples/wallet/getting-started.ts +++ b/bindings/nodejs/examples/wallet/getting-started.ts @@ -44,18 +44,7 @@ async function run() { // The mnemonic can't be retrieved from the Stronghold file, so make a backup in a secure place! await secretManager.storeMnemonic(mnemonic); - const wallet_address = await secretManager.generateEd25519Addresses({ - coinType: CoinType.IOTA, - accountIndex: 0, - range: { - start: 0, - end: 1, - }, - bech32Hrp: 'tst', - }); - const walletOptions: WalletOptions = { - address: wallet_address[0], storagePath: WALLET_DB_PATH, clientOptions: { nodes: [NODE_URL as string], diff --git a/bindings/nodejs/lib/secret_manager/secret-manager.ts b/bindings/nodejs/lib/secret_manager/secret-manager.ts index 76747845b8..3f65aa864d 100644 --- a/bindings/nodejs/lib/secret_manager/secret-manager.ts +++ b/bindings/nodejs/lib/secret_manager/secret-manager.ts @@ -21,6 +21,7 @@ import { UnsignedBlock, Block, parseBlock, + Bech32Address, } from '../types'; import { plainToInstance } from 'class-transformer'; @@ -44,14 +45,14 @@ export class SecretManager { } /** - * Generate Ed25519 addresses. + * Generate multiple Ed25519 addresses at once. * * @param generateAddressesOptions Options to generate addresses. * @returns An array of generated addresses. */ async generateEd25519Addresses( generateAddressesOptions: GenerateAddressesOptions, - ): Promise { + ): Promise { const response = await this.methodHandler.callMethod({ name: 'generateEd25519Addresses', data: { diff --git a/bindings/nodejs/lib/types/client/generate-addresses-options.ts b/bindings/nodejs/lib/types/client/generate-addresses-options.ts index a66e7ec431..342baf257e 100644 --- a/bindings/nodejs/lib/types/client/generate-addresses-options.ts +++ b/bindings/nodejs/lib/types/client/generate-addresses-options.ts @@ -3,6 +3,8 @@ import type { CoinType } from './constants'; import type { Range } from './range'; +// TODO: Rename (to GetAddressOptions) and refactor (move out range field), +// so we can use it for the single address generation method as well? /** * Input options for GenerateAddresses */ diff --git a/bindings/nodejs/src/wallet.rs b/bindings/nodejs/src/wallet.rs index 4d26911d2c..e7f7a0a5b1 100644 --- a/bindings/nodejs/src/wallet.rs +++ b/bindings/nodejs/src/wallet.rs @@ -121,7 +121,7 @@ pub async fn get_client(wallet: External) -> Result) -> Result> { if let Some(wallet) = &**wallet.as_ref().read().await { - Ok(External::new(wallet.get_secret_manager().clone())) + Ok(External::new(wallet.secret_manager().clone())) } else { Err(destroyed_err("Wallet")) } diff --git a/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts b/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts new file mode 100644 index 0000000000..98a4a00336 --- /dev/null +++ b/bindings/nodejs/tests/secret_manager/secret_manager.spec.ts @@ -0,0 +1,48 @@ +// Copyright 2023 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +import 'reflect-metadata'; + +import { describe, it, expect } from '@jest/globals'; +import { + CoinType, + SecretManager, + Utils, +} from '../../lib/'; + +describe('SecretManager', () => { + it('generate IOTA Ed25519 address', async () => { + const mnemonicSecretManager = { + mnemonic: "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast" + }; + + let bech32_hrp = Utils.iotaMainnetProtocolParameters().bech32Hrp; + + const secretManager = SecretManager.create(mnemonicSecretManager); + const addresses = await secretManager.generateEd25519Addresses({ + coinType: CoinType.IOTA, + bech32Hrp: bech32_hrp, + }); + + expect(addresses[0]).toEqual('iota1qpg2xkj66wwgn8p2ggnp7p582gj8g6p79us5hve2tsudzpsr2ap4skprwjg'); + + }, 20000); + + it('generate Shimmer Ed25519 address', async () => { + const mnemonicSecretManager = { + mnemonic: "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast" + }; + + let bech32_hrp = Utils.shimmerMainnetProtocolParameters().bech32Hrp; + + const secretManager = SecretManager.create(mnemonicSecretManager); + const addresses = await secretManager.generateEd25519Addresses({ + coinType: CoinType.Shimmer, + bech32Hrp: bech32_hrp, + range: { start: 0, end: 1 }, + }); + + expect(addresses[0]).toEqual('smr1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6ckj80y'); + + }, 20000); +}); diff --git a/bindings/python/iota_sdk/secret_manager/secret_manager.py b/bindings/python/iota_sdk/secret_manager/secret_manager.py index d78b807206..36c0b5901b 100644 --- a/bindings/python/iota_sdk/secret_manager/secret_manager.py +++ b/bindings/python/iota_sdk/secret_manager/secret_manager.py @@ -125,24 +125,69 @@ def _call_method(self, name, data=None): return json_response['payload'] return response + # TODO: Should we include `bech32` in the method name? + def generate_ed25519_address(self, + coin_type: int, + bech32_hrp: str, + account_index: Optional[int] = None, + address_index: Optional[int] = None, + internal: Optional[bool] = None, + legder_nano_prompt: Optional[bool] = None): + """Generate a single Ed25519 address. + + Args: + coin_type: The coin type to generate the address for. + bech32_hrp: The bech32 HRP (human readable part) to use. + account_index: An account index. + address_index: An address index. + internal: Whether the generated address should be internal. + ledger_nano_prompt: Whether to display the address on Ledger Nano devices. + + Returns: + The generated Ed25519 address. + """ + + options = {} + options['coinType'] = coin_type + options['bech32Hrp'] = bech32_hrp + if address_index is not None: + options['range'] = {} + options['range']['start'] = address_index + options['range']['end'] = address_index + 1 + if account_index is not None: + options['accountIndex'] = account_index + if internal is not None or legder_nano_prompt is not None: + options['options'] = {} + if internal is not None: + options['options']['internal'] = internal + if legder_nano_prompt is not None: + options['options']['ledgerNanoPrompot'] = legder_nano_prompt + + return self._call_method('generateEd25519Addresses', { + 'options': options + })[0] + # pylint: disable=unused-argument + + # TODO: Should `coin_type` and `bech32_hrp` be mandatory to provide? + # TODO: Should we include `bech32` in the method name? def generate_ed25519_addresses(self, + coin_type: Optional[int] = None, + bech32_hrp: Optional[str] = None, account_index: Optional[int] = None, start: Optional[int] = None, end: Optional[int] = None, internal: Optional[bool] = None, - coin_type: Optional[int] = None, - bech32_hrp: Optional[str] = None, ledger_nano_prompt: Optional[bool] = None): - """Generate Ed25519 addresses. + """Generate multiple Ed25519 addresses at once. Args: + coin_type: The coin type to generate addresses for. + bech32_hrp: The bech32 HRP (human readable part) to use. account_index: An account index. start: The start index of the addresses to generate. end: The end index of the addresses to generate. internal: Whether the generated addresses should be internal. - coin_type: The coin type to generate addresses for. - bech32_hrp: The bech32 HRP (human readable part) to use. ledger_nano_prompt: Whether to display the address on Ledger Nano devices. Returns: diff --git a/bindings/python/src/wallet.rs b/bindings/python/src/wallet.rs index b42f1a2b8f..8a977c1e70 100644 --- a/bindings/python/src/wallet.rs +++ b/bindings/python/src/wallet.rs @@ -116,7 +116,7 @@ pub fn get_secret_manager_from_wallet(wallet: &Wallet) -> Result Result Result { if let Some(wallet) = &*method_handler.0.read().await { - Ok(SecretManagerMethodHandler::new(wallet.get_secret_manager().clone())) + Ok(SecretManagerMethodHandler::new(wallet.secret_manager().clone())) } else { // Notify that the wallet was destroyed Err(destroyed_err("Wallet")) diff --git a/cli/src/cli.rs b/cli/src/cli.rs index cf614cc480..d6f247346c 100644 --- a/cli/src/cli.rs +++ b/cli/src/cli.rs @@ -169,7 +169,7 @@ pub async fn new_wallet(cli: Cli) -> Result, Error> { if storage_path.is_dir() { match Wallet::builder().with_storage_path(storage_path).finish().await { Ok(wallet) => { - let linked_secret_manager = match &mut *wallet.get_secret_manager().write().await { + let linked_secret_manager = match &mut *wallet.secret_manager().write().await { SecretManager::Stronghold(stronghold) => { let snapshot_path = stronghold.snapshot_path().to_path_buf(); let snapshot_exists = snapshot_path.exists(); diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index 18ba7fac9b..b6c0f3aa06 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -1377,7 +1377,7 @@ pub enum PromptResponse { } async fn ensure_password(wallet: &Wallet) -> Result<(), Error> { - if matches!(*wallet.get_secret_manager().read().await, SecretManager::Stronghold(_)) + if matches!(*wallet.secret_manager().read().await, SecretManager::Stronghold(_)) && !wallet.is_stronghold_password_available().await? { let password = get_password("Stronghold password", false)?; diff --git a/sdk/examples/client/02_address_balance.rs b/sdk/examples/client/02_address_balance.rs index 148ba7eefb..b9e027b50f 100644 --- a/sdk/examples/client/02_address_balance.rs +++ b/sdk/examples/client/02_address_balance.rs @@ -11,8 +11,8 @@ use iota_sdk::{ client::{ - api::GetAddressesOptions, node_api::indexer::query_parameters::BasicOutputQueryParameters, - secret::SecretManager, Client, + api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, + node_api::indexer::query_parameters::BasicOutputQueryParameters, secret::SecretManager, Client, }, types::block::output::NativeTokensBuilder, }; @@ -36,14 +36,8 @@ async fn main() -> Result<(), Box> { // Generate the first address let first_address = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::from_client(&client) - .await? - .with_account_index(0) - .with_range(0..1), - ) - .await?[0] - .clone(); + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, client.get_bech32_hrp().await?, None) + .await?; // Get output ids of outputs that can be controlled by this address without further unlock constraints let output_ids_response = client diff --git a/sdk/examples/wallet/ledger_nano.rs b/sdk/examples/wallet/ledger_nano.rs index ffd7ed0fbb..bdb9391db3 100644 --- a/sdk/examples/wallet/ledger_nano.rs +++ b/sdk/examples/wallet/ledger_nano.rs @@ -23,8 +23,6 @@ use iota_sdk::{ wallet::{ClientOptions, Wallet}, }; -// The address to send coins to -const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The amount of base coins we'll send const SEND_AMOUNT: u64 = 1_000_000; @@ -38,24 +36,20 @@ async fn main() -> Result<(), Box> { } let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; - let secret_manager = LedgerSecretManager::new(true); + let secret_manager = SecretManager::LedgerNano(LedgerSecretManager::new(true)); + let wallet = Wallet::builder() - .with_secret_manager(SecretManager::LedgerNano(secret_manager)) + .with_secret_manager(secret_manager) .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; + let recv_address = wallet.address().await; + println!("recipient address: {recv_address}"); println!("{:?}", wallet.get_ledger_nano_status().await?); - println!("Generating address..."); - let now = tokio::time::Instant::now(); - let address = wallet.generate_ed25519_address(0, 0, None).await?; - println!("took: {:.2?}", now.elapsed()); - - println!("ADDRESS:\n{address:#?}"); - let now = tokio::time::Instant::now(); let balance = wallet.sync(None).await?; println!("Wallet synced in: {:.2?}", now.elapsed()); @@ -63,7 +57,7 @@ async fn main() -> Result<(), Box> { println!("Balance BEFORE:\n{:?}", balance.base_coin()); println!("Sending the coin-transfer transaction..."); - let transaction = wallet.send(SEND_AMOUNT, RECV_ADDRESS, None).await?; + let transaction = wallet.send(SEND_AMOUNT, recv_address, None).await?; println!("Transaction sent: {}", transaction.transaction_id); wallet diff --git a/sdk/examples/wallet/logger.rs b/sdk/examples/wallet/logger.rs index 9a8b877281..907a75120d 100644 --- a/sdk/examples/wallet/logger.rs +++ b/sdk/examples/wallet/logger.rs @@ -10,6 +10,7 @@ use iota_sdk::{ client::{ + api::GetAddressesOptions, constants::SHIMMER_COIN_TYPE, secret::{mnemonic::MnemonicSecretManager, SecretManager}, }, @@ -38,18 +39,20 @@ async fn main() -> Result<(), Box> { // Restore a wallet let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; + let secret_manager = SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic( + std::env::var("MNEMONIC").unwrap(), + )?); + let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; - let wallet = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) + + let wallet = Wallet::::builder() .with_storage_path(&std::env::var("WALLET_DB_PATH").unwrap()) .with_client_options(client_options) + .with_secret_manager(secret_manager) .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - println!("Generating address..."); - let _ = wallet.generate_ed25519_address(0, 0, None).await?; - println!("Syncing wallet"); wallet.sync(None).await?; diff --git a/sdk/examples/wallet/offline_signing/0_generate_address.rs b/sdk/examples/wallet/offline_signing/0_generate_address.rs index 35689871e3..c8f6d21b19 100644 --- a/sdk/examples/wallet/offline_signing/0_generate_address.rs +++ b/sdk/examples/wallet/offline_signing/0_generate_address.rs @@ -14,6 +14,7 @@ use iota_sdk::{ secret::{stronghold::StrongholdSecretManager, SecretManager}, }, crypto::keys::{bip39::Mnemonic, bip44::Bip44}, + types::block::address::Bech32Address, wallet::{ClientOptions, Wallet}, }; @@ -50,17 +51,17 @@ async fn main() -> Result<(), Box> { .with_bip_path(Bip44::new(SHIMMER_COIN_TYPE)) .finish() .await?; - println!("Generated a new wallet"); - write_wallet_address_to_file(&wallet).await + write_wallet_address_to_file(&wallet.address().await).await?; + + Ok(()) } -async fn write_wallet_address_to_file(wallet: &Wallet) -> Result<(), Box> { +async fn write_wallet_address_to_file(address: &Bech32Address) -> Result<(), Box> { use tokio::io::AsyncWriteExt; - let wallet_address = wallet.address().await; - let json = serde_json::to_string_pretty(&wallet_address)?; + let json = serde_json::to_string_pretty(address)?; let mut file = tokio::io::BufWriter::new(tokio::fs::File::create(ADDRESS_FILE_PATH).await?); println!("example.address.json:\n{json}"); file.write_all(json.as_bytes()).await?; diff --git a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs index ca0df7dc2c..93c374fc96 100644 --- a/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs +++ b/sdk/examples/wallet/offline_signing/1_prepare_transaction.rs @@ -9,16 +9,19 @@ //! ``` use iota_sdk::{ - client::{api::PreparedTransactionDataDto, constants::SHIMMER_COIN_TYPE, secret::SecretManager}, + client::{ + api::PreparedTransactionDataDto, constants::SHIMMER_COIN_TYPE, secret::SecretManager, + stronghold::StrongholdAdapter, + }, crypto::keys::bip44::Bip44, types::block::address::Bech32Address, wallet::{ClientOptions, SendParams, Wallet}, }; const ONLINE_WALLET_DB_PATH: &str = "./examples/wallet/offline_signing/example-online-walletdb"; -const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; const PREPARED_TRANSACTION_FILE_PATH: &str = "./examples/wallet/offline_signing/example.prepared_transaction.json"; const PROTOCOL_PARAMETERS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.protocol_parameters.json"; +const ADDRESS_FILE_PATH: &str = "./examples/wallet/offline_signing/example.address.json"; // Address to which we want to send the amount. const RECV_ADDRESS: &str = "rms1qpszqzadsym6wpppd6z037dvlejmjuke7s24hm95s9fg9vpua7vluaw60xu"; // The amount to send. @@ -35,7 +38,7 @@ async fn main() -> Result<(), Box> { let params = [SendParams::new(SEND_AMOUNT, RECV_ADDRESS)?]; - // Recovers addresses from example `0_address_generation`. + // Recovers the address generated by the example `0_generate_address.rs`. let address = read_address_from_file().await?; let client_options = ClientOptions::new().with_node(&std::env::var("NODE_URL").unwrap())?; diff --git a/sdk/examples/wallet/spammer.rs b/sdk/examples/wallet/spammer.rs index e9e24818fd..52786a6f73 100644 --- a/sdk/examples/wallet/spammer.rs +++ b/sdk/examples/wallet/spammer.rs @@ -48,19 +48,9 @@ async fn main() -> Result<(), Box> { let secret_manager = MnemonicSecretManager::try_from_mnemonic(std::env::var("MNEMONIC").unwrap())?; let bip_path = Bip44::new(SHIMMER_COIN_TYPE); - let address = Bech32Address::new( - Hrp::from_str_unchecked("smr"), - Address::from( - secret_manager - .generate_ed25519_addresses(bip_path.coin_type, bip_path.account, 0..1, None) - .await?[0], - ), - ); - let wallet = Wallet::builder() .with_secret_manager(SecretManager::Mnemonic(secret_manager)) .with_client_options(client_options) - .with_address(address) .with_bip_path(bip_path) .finish() .await?; diff --git a/sdk/src/client/api/address.rs b/sdk/src/client/api/address.rs index a341dc1a26..d8a70a96b4 100644 --- a/sdk/src/client/api/address.rs +++ b/sdk/src/client/api/address.rs @@ -16,6 +16,9 @@ use crate::{ utils::ConvertTo, }; +// TODO: Should we rename ths struct to `GetAddressOptions`, thereby moving out the `range` field, so +// it can be used by `GenerateEd25519Address` and `GenerateEd25519Addresses`? Do we even still need +// the latter? #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(default)] @@ -33,6 +36,9 @@ pub struct GetAddressesOptions { } impl GetAddressesOptions { + // TODO: can we remove this function? It's not clear from the outside that it's just the default + // with a requested HRP. I think the caller can just do what this function does. Also ... with this + // we do several API requests unnecessarily since oftentimes we could just re-use the HRP. pub async fn from_client(client: &Client) -> Result { Ok(Self::default().with_bech32_hrp(client.get_bech32_hrp().await?)) } @@ -97,7 +103,34 @@ impl Default for GetAddressesOptions { } impl SecretManager { - /// Get a vector of public bech32 addresses + // TODO: while `SecretManage::generate...` returns `Ed25519Address`, `SecretManager` + // converts those to `Bech32Address`es, hence, should we add `bech32` to its method name + // to make that the difference clear? + // TODO: make `account_index` and `address_index` impl Into>? + /// Generates a Bech32 formatted Ed25519 address. + pub async fn generate_ed25519_address( + &self, + coin_type: u32, + account_index: u32, + address_index: u32, + bech32_hrp: impl ConvertTo, + options: impl Into> + Send, + ) -> Result { + let hrp: Hrp = bech32_hrp.convert()?; + Ok(SecretManage::generate_ed25519_addresses( + self, + coin_type, + account_index, + address_index..address_index + 1, + options, + ) + // Panic: if the secret manager hasn't failed then there must be an address. + .await?[0] + .to_bech32(hrp)) + } + + // TODO: Same as for `generate_ed25519_address`. + /// Generates a vector of Bech32 formatted Ed25519 addresses. pub async fn generate_ed25519_addresses( &self, GetAddressesOptions { @@ -117,7 +150,29 @@ impl SecretManager { ) } - /// Get a vector of EVM address strings + /// Generates a single EVM address hex string. + pub async fn generate_evm_address( + &self, + coin_type: u32, + account_index: u32, + address_index: u32, + options: impl Into> + Send, + ) -> Result { + Ok(prefix_hex::encode( + SecretManage::generate_evm_addresses( + self, + coin_type, + account_index, + address_index..address_index + 1, + options, + ) + // Panic: if the secret manager hasn't failed then there must be an address. + .await?[0] + .as_ref(), + )) + } + + /// Generates a vector of EVM address hex strings. pub async fn generate_evm_addresses( &self, GetAddressesOptions { diff --git a/sdk/src/wallet/core/builder.rs b/sdk/src/wallet/core/builder.rs index ec36ec2c6c..7848733294 100644 --- a/sdk/src/wallet/core/builder.rs +++ b/sdk/src/wallet/core/builder.rs @@ -170,11 +170,12 @@ where // May use a previously stored secret manager if it wasn't provided if self.secret_manager.is_none() { - let secret_manager = loaded_wallet_builder - .as_ref() - .and_then(|builder| builder.secret_manager.clone()); - - self.secret_manager = secret_manager; + self.secret_manager.replace( + loaded_wallet_builder + .as_ref() + .and_then(|builder| builder.secret_manager.clone()) + .ok_or(WalletError::MissingParameter("secret_manager"))?, + ); } let mut verify_address = false; @@ -277,6 +278,7 @@ where last_synced: Mutex::new(0), background_syncing_status, client, + // TODO: make secret manager optional secret_manager: self.secret_manager.expect("make WalletInner::secret_manager optional?"), #[cfg(feature = "events")] event_emitter: tokio::sync::RwLock::new(EventEmitter::new()), diff --git a/sdk/src/wallet/core/mod.rs b/sdk/src/wallet/core/mod.rs index f6bef8d88a..6e7a8e30cd 100644 --- a/sdk/src/wallet/core/mod.rs +++ b/sdk/src/wallet/core/mod.rs @@ -427,12 +427,12 @@ impl Wallet { } impl WalletInner { - /// Get the [SecretManager] - pub fn get_secret_manager(&self) -> &Arc> { + /// Get the [`SecretManager`] of the wallet. + pub fn secret_manager(&self) -> &Arc> { &self.secret_manager } - /// Listen to wallet events, empty vec will listen to all events + /// Listen to wallet events, empty vec will listen to all events. #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] pub async fn listen + Send>(&self, events: I, handler: F) @@ -444,7 +444,7 @@ impl WalletInner { emitter.on(events, handler); } - /// Remove wallet event listeners, empty vec will remove all listeners + /// Remove wallet event listeners, empty vec will remove all listeners. #[cfg(feature = "events")] #[cfg_attr(docsrs, doc(cfg(feature = "events")))] pub async fn clear_listeners + Send>(&self, events: I) diff --git a/sdk/src/wallet/core/operations/address_generation.rs b/sdk/src/wallet/core/operations/address_generation.rs deleted file mode 100644 index e950efe78b..0000000000 --- a/sdk/src/wallet/core/operations/address_generation.rs +++ /dev/null @@ -1,93 +0,0 @@ -// Copyright 2022 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -use crate::{ - client::{ - secret::{GenerateAddressOptions, SecretManage, SecretManager}, - ClientError, - }, - types::block::address::Ed25519Address, - wallet::{Wallet, WalletError}, -}; -#[cfg(all(feature = "events", feature = "ledger_nano"))] -use crate::{ - types::block::address::ToBech32Ext, - wallet::events::types::{AddressData, WalletEvent}, -}; - -impl Wallet { - /// Generate an address without storing it - /// ```ignore - /// let public_addresses = wallet - /// .generate_ed25519_address(None) - /// .await?; - /// ``` - pub async fn generate_ed25519_address( - &self, - account_index: u32, - address_index: u32, - options: impl Into> + Send, - ) -> Result { - // TODO: not sure yet whether we also should allow this method to generate addresses for different bip - // paths. - let coin_type = self.bip_path().await.ok_or(WalletError::MissingBipPath)?.coin_type; - - let address = match &*self.secret_manager.read().await { - #[cfg(feature = "ledger_nano")] - SecretManager::LedgerNano(ledger_nano) => { - // If we don't sync, then we want to display the prompt on the ledger with the address. But the user - // needs to have it visible on the computer first, so we need to generate it without the - // prompt first - let options = options.into(); - #[cfg(feature = "events")] - if options.as_ref().map_or(false, |o| o.ledger_nano_prompt) { - let changed_options = options.map(|mut options| { - // Change options so ledger will not show the prompt the first time - options.ledger_nano_prompt = false; - options - }); - // Generate without prompt to be able to display it - let address = ledger_nano - .generate_ed25519_addresses( - coin_type, - account_index, - address_index..address_index + 1, - changed_options, - ) - .await?; - - let bech32_hrp = self.bech32_hrp().await; - - self.emit(WalletEvent::LedgerAddressGeneration(AddressData { - address: address[0].to_bech32(bech32_hrp), - })) - .await; - } - // Generate with prompt so the user can verify - ledger_nano - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - #[cfg(feature = "stronghold")] - SecretManager::Stronghold(stronghold) => { - stronghold - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - SecretManager::Mnemonic(mnemonic) => { - mnemonic - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - #[cfg(feature = "private_key_secret_manager")] - SecretManager::PrivateKey(private_key) => { - private_key - .generate_ed25519_addresses(coin_type, account_index, address_index..address_index + 1, options) - .await? - } - SecretManager::Placeholder => return Err(ClientError::PlaceholderSecretManager.into()), - }; - - Ok(*address.first().ok_or(WalletError::MissingParameter("address"))?) - } -} diff --git a/sdk/src/wallet/core/operations/mod.rs b/sdk/src/wallet/core/operations/mod.rs index e01ca173a6..058c1e84dc 100644 --- a/sdk/src/wallet/core/operations/mod.rs +++ b/sdk/src/wallet/core/operations/mod.rs @@ -1,7 +1,6 @@ // Copyright 2021 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -pub(crate) mod address_generation; pub(crate) mod background_syncing; pub(crate) mod client; #[cfg(feature = "ledger_nano")] diff --git a/sdk/src/wallet/operations/block.rs b/sdk/src/wallet/operations/block.rs index bf3ea48ce2..afdfb18240 100644 --- a/sdk/src/wallet/operations/block.rs +++ b/sdk/src/wallet/operations/block.rs @@ -56,7 +56,7 @@ where let block = unsigned_block .sign_ed25519( - &*self.get_secret_manager().read().await, + &*self.secret_manager().read().await, self.bip_path().await.ok_or(WalletError::MissingBipPath)?, ) .await?; diff --git a/sdk/tests/client/common/constants.rs b/sdk/tests/client/common/constants.rs index 7f7a02fb2b..c06eb0d4e6 100644 --- a/sdk/tests/client/common/constants.rs +++ b/sdk/tests/client/common/constants.rs @@ -4,3 +4,5 @@ pub static NODE_LOCAL: &str = "http://localhost:8050"; pub static FAUCET_URL: &str = "http://localhost:8091/api/enqueue"; + +pub static DEFAULT_MNEMONIC: &str = "inhale gorilla deny three celery song category owner lottery rent author wealth penalty crawl hobby obtain glad warm early rain clutch slab august bleak"; diff --git a/sdk/tests/client/common/mod.rs b/sdk/tests/client/common/mod.rs index ef018ba902..5e249dca6c 100644 --- a/sdk/tests/client/common/mod.rs +++ b/sdk/tests/client/common/mod.rs @@ -10,7 +10,7 @@ mod constants; // }; use iota_sdk::client::Client; -pub use self::constants::{FAUCET_URL, NODE_LOCAL}; +pub use self::constants::{DEFAULT_MNEMONIC, FAUCET_URL, NODE_LOCAL}; /// Sets up a Client with node health ignored. pub async fn setup_client_with_node_health_ignored() -> Client { @@ -62,3 +62,15 @@ pub async fn setup_client_with_node_health_ignored() -> Client { // } // panic!("Faucet no longer wants to hand over coins"); // } + +#[allow(dead_code)] +pub(crate) fn setup(path: &str) -> Result<(), Box> { + // Ignore error in case the path didn't exist yet. + std::fs::remove_dir_all(path).ok(); + Ok(()) +} + +#[allow(dead_code)] +pub(crate) fn tear_down(path: &str) -> Result<(), Box> { + Ok(std::fs::remove_dir_all(path)?) +} diff --git a/sdk/tests/client/secret_manager/address_generation.rs b/sdk/tests/client/secret_manager/address_generation.rs new file mode 100644 index 0000000000..7ff9584cce --- /dev/null +++ b/sdk/tests/client/secret_manager/address_generation.rs @@ -0,0 +1,111 @@ +// Copyright 2024 IOTA Stiftung +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(feature = "stronghold")] +use crypto::keys::bip39::Mnemonic; +use crypto::keys::bip44::Bip44; +#[cfg(feature = "stronghold")] +use iota_sdk::client::secret::stronghold::StrongholdSecretManager; +#[cfg(feature = "ledger_nano")] +use iota_sdk::client::secret::{ledger_nano::LedgerSecretManager, GenerateAddressOptions}; +#[cfg(feature = "events")] +use iota_sdk::wallet::events::{WalletEvent, WalletEventType}; +use iota_sdk::{ + client::{ + api::GetAddressesOptions, + constants::{IOTA_COIN_TYPE, SHIMMER_COIN_TYPE}, + secret::{mnemonic::MnemonicSecretManager, SecretManager}, + ClientError, + }, + types::block::address::{Hrp, ToBech32Ext}, + wallet::{ClientOptions, Wallet}, +}; +use pretty_assertions::assert_eq; + +use crate::client::common::{setup, tear_down, DEFAULT_MNEMONIC, NODE_LOCAL}; + +#[tokio::test] +async fn address_generation_mnemonic() -> Result<(), Box> { + let secret_manager = + SecretManager::Mnemonic(MnemonicSecretManager::try_from_mnemonic(DEFAULT_MNEMONIC.to_owned())?); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address, + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" + ); + + Ok(()) +} + +#[cfg(feature = "stronghold")] +#[tokio::test] +async fn address_generation_stronghold() -> Result<(), Box> { + let storage_path = "test-storage/address_generation_stronghold"; + setup(storage_path)?; + + iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); + + let secret_manager = StrongholdSecretManager::builder() + .password("some_hopefully_secure_password".to_owned()) + .build(format!("{}/test.stronghold", storage_path))?; + + secret_manager + .store_mnemonic(Mnemonic::from(DEFAULT_MNEMONIC.to_string())) + .await?; + + let secret_manager = SecretManager::Stronghold(secret_manager); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address, + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" + ); + + tear_down(storage_path) +} + +#[tokio::test] +#[cfg(feature = "ledger_nano")] +#[ignore = "requires ledger nano instance"] +async fn address_generation_ledger() -> Result<(), Box> { + let mut secret_manager = LedgerSecretManager::new(true); + secret_manager.non_interactive = true; + + let secret_manager = SecretManager::LedgerNano(secret_manager); + + let address = secret_manager + .generate_ed25519_address(IOTA_COIN_TYPE, 0, 0, "smr", None) + .await?; + + assert_eq!( + address.to_bech32_unchecked("smr"), + // Address generated with bip32 path: [44, 4218, 0, 0, 0]. + // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. + // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster + // seven myth punch hobby comfort wild raise skin". + "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" + ); + + Ok(()) +} + +#[tokio::test] +async fn address_generation_placeholder() { + let secret_manager = SecretManager::Placeholder; + + assert!(matches!( + secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, "smr", None) + .await, + Err(ClientError::PlaceholderSecretManager) + )); +} diff --git a/sdk/tests/client/secret_manager/mnemonic.rs b/sdk/tests/client/secret_manager/mnemonic.rs index faca1dc980..84cf4e6cbe 100644 --- a/sdk/tests/client/secret_manager/mnemonic.rs +++ b/sdk/tests/client/secret_manager/mnemonic.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::{ - api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, ClientError, + api::GetAddressesOptions, + constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, + secret::SecretManager, + ClientError, }; use pretty_assertions::assert_eq; @@ -11,18 +14,13 @@ async fn mnemonic_secret_manager() -> Result<(), ClientError> { let dto = r#"{"mnemonic": "acoustic trophy damage hint search taste love bicycle foster cradle brown govern endless depend situate athlete pudding blame question genius transfer van random vast"}"#; let secret_manager: SecretManager = dto.parse()?; - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - .with_account_index(0) - .with_range(0..1), - ) + let address = secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap(); assert_eq!( - addresses[0], + address, "rms1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6v3ea5a" ); diff --git a/sdk/tests/client/secret_manager/mod.rs b/sdk/tests/client/secret_manager/mod.rs index 4e2a7988d5..4ed72f4a0b 100644 --- a/sdk/tests/client/secret_manager/mod.rs +++ b/sdk/tests/client/secret_manager/mod.rs @@ -1,6 +1,7 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +mod address_generation; mod mnemonic; #[cfg(feature = "private_key_secret_manager")] mod private_key; diff --git a/sdk/tests/client/secret_manager/stronghold.rs b/sdk/tests/client/secret_manager/stronghold.rs index 84b5f55a8c..bb087859ee 100644 --- a/sdk/tests/client/secret_manager/stronghold.rs +++ b/sdk/tests/client/secret_manager/stronghold.rs @@ -2,7 +2,10 @@ // SPDX-License-Identifier: Apache-2.0 use iota_sdk::client::{ - api::GetAddressesOptions, constants::SHIMMER_TESTNET_BECH32_HRP, secret::SecretManager, ClientError, + api::GetAddressesOptions, + constants::{SHIMMER_COIN_TYPE, SHIMMER_TESTNET_BECH32_HRP}, + secret::SecretManager, + ClientError, }; use pretty_assertions::assert_eq; @@ -24,18 +27,13 @@ async fn stronghold_secret_manager() -> Result<(), ClientError> { panic!("expect a Stronghold secret manager, but it's not the case!"); } - let addresses = secret_manager - .generate_ed25519_addresses( - GetAddressesOptions::default() - .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - .with_account_index(0) - .with_range(0..1), - ) + let address = secret_manager + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap(); assert_eq!( - addresses[0], + address, "rms1qzev36lk0gzld0k28fd2fauz26qqzh4hd4cwymlqlv96x7phjxcw6v3ea5a" ); @@ -64,11 +62,7 @@ async fn stronghold_mnemonic_missing() -> Result<(), ClientError> { // Generating addresses will fail because no mnemonic has been stored let error = SecretManager::Stronghold(stronghold_secret_manager) - .generate_ed25519_addresses( - GetAddressesOptions::default(), - // .with_bech32_hrp(SHIMMER_TESTNET_BECH32_HRP) - // .with_coin_type(iota_sdk::client::constants::SHIMMER_COIN_TYPE) - ) + .generate_ed25519_address(SHIMMER_COIN_TYPE, 0, 0, SHIMMER_TESTNET_BECH32_HRP, None) .await .unwrap_err(); diff --git a/sdk/tests/wallet/address_generation.rs b/sdk/tests/wallet/address_generation.rs deleted file mode 100644 index 2540e51c85..0000000000 --- a/sdk/tests/wallet/address_generation.rs +++ /dev/null @@ -1,209 +0,0 @@ -// Copyright 2023 IOTA Stiftung -// SPDX-License-Identifier: Apache-2.0 - -#[cfg(feature = "stronghold")] -use crypto::keys::bip39::Mnemonic; -use crypto::keys::bip44::Bip44; -#[cfg(feature = "stronghold")] -use iota_sdk::client::secret::stronghold::StrongholdSecretManager; -#[cfg(feature = "ledger_nano")] -use iota_sdk::client::secret::{ledger_nano::LedgerSecretManager, GenerateAddressOptions}; -#[cfg(feature = "events")] -use iota_sdk::wallet::events::{WalletEvent, WalletEventType}; -use iota_sdk::{ - client::{ - constants::IOTA_COIN_TYPE, - secret::{mnemonic::MnemonicSecretManager, SecretManager}, - }, - types::block::{address::ToBech32Ext, protocol::iota_mainnet_protocol_parameters}, - wallet::{ClientOptions, Wallet}, -}; -use pretty_assertions::assert_eq; - -use crate::wallet::common::{setup, tear_down, DEFAULT_MNEMONIC, NODE_LOCAL}; - -#[tokio::test] -async fn wallet_address_generation_mnemonic() -> Result<(), Box> { - let storage_path = "test-storage/wallet_address_generation_mnemonic"; - setup(storage_path)?; - - let client_options = ClientOptions::new() - .with_node(NODE_LOCAL)? - .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()); - let secret_manager = MnemonicSecretManager::try_from_mnemonic(DEFAULT_MNEMONIC.to_owned())?; - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::Mnemonic(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" - ); - - tear_down(storage_path) -} - -#[cfg(feature = "stronghold")] -#[tokio::test] -async fn wallet_address_generation_stronghold() -> Result<(), Box> { - let storage_path = "test-storage/wallet_address_generation_stronghold"; - setup(storage_path)?; - - iota_stronghold::engine::snapshot::try_set_encrypt_work_factor(0).unwrap(); - - let secret_manager = StrongholdSecretManager::builder() - .password("some_hopefully_secure_password".to_owned()) - .build("test-storage/wallet_address_generation_stronghold/test.stronghold")?; - secret_manager - .store_mnemonic(Mnemonic::from(DEFAULT_MNEMONIC.to_string())) - .await?; - - let client_options = ClientOptions::new() - .with_node(NODE_LOCAL)? - .with_protocol_parameters(iota_mainnet_protocol_parameters().clone()); - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::Stronghold(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - "smr1qrpwecegav7eh0z363ca69laxej64rrt4e3u0rtycyuh0mam3vq3ulygj9p" - ); - - tear_down(storage_path) -} - -#[tokio::test] -#[cfg(all(feature = "ledger_nano", feature = "events"))] -#[ignore = "requires ledger nano instance"] -async fn wallet_address_generation_ledger() -> Result<(), Box> { - let storage_path = "test-storage/wallet_address_generation_ledger"; - setup(storage_path)?; - - let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - let mut secret_manager = LedgerSecretManager::new(true); - secret_manager.non_interactive = true; - - #[allow(unused_mut)] - let mut wallet_builder = Wallet::builder() - .with_secret_manager(SecretManager::LedgerNano(secret_manager)) - .with_client_options(client_options) - .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - - #[cfg(feature = "storage")] - { - wallet_builder = wallet_builder.with_storage_path(storage_path); - } - let wallet = wallet_builder.finish().await?; - - let address = wallet.generate_ed25519_address(0, 0, None).await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - let (sender, mut receiver) = tokio::sync::mpsc::channel(1); - - wallet - .listen([WalletEventType::LedgerAddressGeneration], move |event| { - if let WalletEvent::LedgerAddressGeneration(address) = event { - sender - .try_send(address.address.clone()) - .expect("too many LedgerAddressGeneration events"); - } else { - panic!("expected LedgerAddressGeneration event") - } - }) - .await; - - let address = wallet - .generate_ed25519_address( - 0, - 0, - Some(GenerateAddressOptions { - ledger_nano_prompt: true, - ..Default::default() - }), - ) - .await?; - - assert_eq!( - address.to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - assert_eq!( - receiver - .recv() - .await - .expect("never received event") - .into_inner() - .to_bech32_unchecked("smr"), - // Address generated with bip32 path: [44, 4218, 0, 0, 0]. - // This address was generated with a MnemonicSecretManager and the ledger simulator mnemonic. - // "glory promote mansion idle axis finger extra february uncover one trip resource lawn turtle enact monster - // seven myth punch hobby comfort wild raise skin". - "smr1qqdnv60ryxynaeyu8paq3lp9rkll7d7d92vpumz88fdj4l0pn5mruy3qdpm" - ); - - tear_down(storage_path) -} - -// #[tokio::test] -// async fn wallet_address_generation_placeholder() -> Result<(), Box> { -// let storage_path = "test-storage/wallet_address_generation_placeholder"; -// setup(storage_path)?; - -// let client_options = ClientOptions::new().with_node(NODE_LOCAL)?; - -// #[allow(unused_mut)] -// let mut wallet_builder = Wallet::builder() -// .with_secret_manager(SecretManager::Placeholder) -// .with_client_options(client_options) -// .with_bip_path(Bip44::new(IOTA_COIN_TYPE)); - -// #[cfg(feature = "storage")] -// { -// wallet_builder = wallet_builder.with_storage_path(storage_path); -// } -// let wallet = wallet_builder.finish().await?; - -// if let Err(Error::Client(error)) = wallet.generate_ed25519_address(0, 0, None).await { -// assert!(matches!(*error, ClientError::PlaceholderSecretManager)) -// } else { -// panic!("expected PlaceholderSecretManager") -// } - -// tear_down(storage_path) -// } diff --git a/sdk/tests/wallet/common/mod.rs b/sdk/tests/wallet/common/mod.rs index 82a48d1679..045ca06ce3 100644 --- a/sdk/tests/wallet/common/mod.rs +++ b/sdk/tests/wallet/common/mod.rs @@ -15,7 +15,7 @@ use iota_sdk::{ wallet::{ClientOptions, Wallet}, }; -pub use self::constants::*; +pub use self::constants::{DEFAULT_MNEMONIC, FAUCET_URL, NODE_LOCAL, NODE_OTHER}; /// It creates a new wallet with a mnemonic secret manager, a client options object, /// SHIMMER_COIN_TYPE, and a storage path diff --git a/sdk/tests/wallet/mod.rs b/sdk/tests/wallet/mod.rs index 0cb359191e..4ef0f24d96 100644 --- a/sdk/tests/wallet/mod.rs +++ b/sdk/tests/wallet/mod.rs @@ -1,7 +1,6 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 -mod address_generation; #[cfg(all(feature = "stronghold", feature = "storage"))] mod backup_restore; mod balance;