diff --git a/bindings/core/src/method/wallet.rs b/bindings/core/src/method/wallet.rs index 54de129510..42fc4e4ace 100644 --- a/bindings/core/src/method/wallet.rs +++ b/bindings/core/src/method/wallet.rs @@ -4,6 +4,7 @@ #[cfg(feature = "stronghold")] use std::path::PathBuf; +use crypto::{keys::bip44::Bip44, signatures::ed25519::PublicKey}; use derivative::Derivative; #[cfg(feature = "events")] use iota_sdk::wallet::events::types::{WalletEvent, WalletEventType}; @@ -194,7 +195,13 @@ pub enum WalletMethod { /// Prepares to transition an implicit account to an account. /// Expected response: [`PreparedTransaction`](crate::Response::PreparedTransaction) #[serde(rename_all = "camelCase")] - PrepareImplicitAccountTransition { output_id: OutputId }, + PrepareImplicitAccountTransition { + output_id: OutputId, + #[serde(default)] + public_key: Option, + #[serde(default)] + bip_path: Option, + }, /// Returns the implicit accounts of the wallet. /// Expected response: [`OutputsData`](crate::Response::OutputsData) ImplicitAccounts, diff --git a/bindings/core/src/method_handler/wallet.rs b/bindings/core/src/method_handler/wallet.rs index 8c30d7bf11..2b3910ea0c 100644 --- a/bindings/core/src/method_handler/wallet.rs +++ b/bindings/core/src/method_handler/wallet.rs @@ -3,6 +3,7 @@ use std::time::Duration; +use crypto::signatures::ed25519::PublicKey; use iota_sdk::{ client::api::{ PreparedTransactionData, PreparedTransactionDataDto, SignedTransactionData, SignedTransactionDataDto, @@ -204,13 +205,25 @@ pub(crate) async fn call_wallet_method_internal(wallet: &Wallet, method: WalletM let implicit_account_creation_address = wallet.implicit_account_creation_address().await?; Response::Bech32Address(implicit_account_creation_address) } + WalletMethod::PrepareImplicitAccountTransition { + output_id, + public_key, + bip_path, + } => { + let data = if let Some(public_key_str) = public_key { + let public_key = PublicKey::try_from_bytes(prefix_hex::decode(public_key_str)?) + .map_err(iota_sdk::wallet::Error::from)?; + wallet + .prepare_implicit_account_transition(&output_id, Some(public_key)) + .await? + } else { + wallet.prepare_implicit_account_transition(&output_id, bip_path).await? + }; + Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) + } WalletMethod::ImplicitAccounts => { Response::OutputsData(wallet.data().await.implicit_accounts().cloned().collect()) } - WalletMethod::PrepareImplicitAccountTransition { output_id } => { - let data = wallet.prepare_implicit_account_transition(&output_id).await?; - Response::PreparedTransaction(PreparedTransactionDataDto::from(&data)) - } WalletMethod::IncomingTransactions => Response::Transactions( wallet .data() diff --git a/cli/src/wallet_cli/mod.rs b/cli/src/wallet_cli/mod.rs index e23a7ce614..175f1b5c74 100644 --- a/cli/src/wallet_cli/mod.rs +++ b/cli/src/wallet_cli/mod.rs @@ -9,6 +9,7 @@ use clap::{CommandFactory, Parser, Subcommand}; use colored::Colorize; use iota_sdk::{ client::request_funds_from_faucet, + crypto::signatures::ed25519::PublicKey, types::{ api::plugins::participation::types::ParticipationEventId, block::{ @@ -123,6 +124,7 @@ pub enum WalletCommand { ImplicitAccountTransition { /// Identifier of the implicit account output. output_id: OutputId, + public_key: Option, }, /// Lists the implicit accounts of the wallet. ImplicitAccounts, @@ -583,8 +585,18 @@ pub async fn implicit_account_creation_address_command(wallet: &Wallet) -> Resul } // `implicit-account-transition` command -pub async fn implicit_account_transition_command(wallet: &Wallet, output_id: OutputId) -> Result<(), Error> { - let transaction = wallet.implicit_account_transition(&output_id).await?; +pub async fn implicit_account_transition_command( + wallet: &Wallet, + output_id: OutputId, + public_key: Option, +) -> Result<(), Error> { + let public_key = public_key + .map(|s| { + PublicKey::try_from_bytes(prefix_hex::decode(s).map_err(|e| Error::Miscellaneous(e.to_string()))?) + .map_err(|e| Error::Miscellaneous(e.to_string())) + }) + .transpose()?; + let transaction = wallet.implicit_account_transition(&output_id, public_key).await?; println_log_info!( "Implicit account transition transaction sent:\n{:?}\n{:?}", @@ -1128,8 +1140,8 @@ pub async fn prompt_internal( WalletCommand::ImplicitAccountCreationAddress => { implicit_account_creation_address_command(wallet).await } - WalletCommand::ImplicitAccountTransition { output_id } => { - implicit_account_transition_command(wallet, output_id).await + WalletCommand::ImplicitAccountTransition { output_id, public_key } => { + implicit_account_transition_command(wallet, output_id, public_key).await } WalletCommand::ImplicitAccounts => implicit_accounts_command(wallet).await, WalletCommand::MeltNativeToken { token_id, amount } => { diff --git a/sdk/src/wallet/error.rs b/sdk/src/wallet/error.rs index 3e5eea5a01..a490e5a232 100644 --- a/sdk/src/wallet/error.rs +++ b/sdk/src/wallet/error.rs @@ -129,6 +129,12 @@ pub enum Error { ImplicitAccountNotFound, } +impl Error { + pub fn other(err: E) -> Self { + Self::Other(Box::new(err) as _) + } +} + // Serialize type with Display error impl Serialize for Error { fn serialize(&self, serializer: S) -> Result diff --git a/sdk/src/wallet/operations/transaction/account.rs b/sdk/src/wallet/operations/transaction/account.rs index 099f9f1fcf..5e0138dc42 100644 --- a/sdk/src/wallet/operations/transaction/account.rs +++ b/sdk/src/wallet/operations/transaction/account.rs @@ -1,6 +1,9 @@ // Copyright 2023 IOTA Stiftung // SPDX-License-Identifier: Apache-2.0 +use crypto::{keys::bip44::Bip44, signatures::ed25519::PublicKey}; +use derive_more::From; + use crate::{ client::{api::PreparedTransactionData, secret::SecretManage}, types::block::{ @@ -23,13 +26,27 @@ where crate::client::Error: From, { /// Transitions an implicit account to an account. - pub async fn implicit_account_transition(&self, output_id: &OutputId) -> Result { - self.sign_and_submit_transaction(self.prepare_implicit_account_transition(output_id).await?, None) - .await + pub async fn implicit_account_transition( + &self, + output_id: &OutputId, + key_source: Option>, + ) -> Result { + self.sign_and_submit_transaction( + self.prepare_implicit_account_transition(output_id, key_source).await?, + None, + ) + .await } /// Prepares to transition an implicit account to an account. - pub async fn prepare_implicit_account_transition(&self, output_id: &OutputId) -> Result { + pub async fn prepare_implicit_account_transition( + &self, + output_id: &OutputId, + key_source: Option>, + ) -> Result + where + crate::wallet::Error: From, + { let implicit_account_data = self.data().await.unspent_outputs.get(output_id).cloned(); let implicit_account = if let Some(implicit_account_data) = &implicit_account_data { @@ -42,20 +59,25 @@ where return Err(Error::ImplicitAccountNotFound); }; - let public_key = if let Some(bip_path) = self.bip_path().await { - self.secret_manager - .read() - .await - .generate_ed25519_public_keys( - bip_path.coin_type, - bip_path.account, - bip_path.address_index..bip_path.address_index + 1, - None, - ) - .await?[0] - } else { - // TODO https://github.com/iotaledger/iota-sdk/issues/1666 - todo!() + let key_source = match key_source.map(Into::into) { + Some(key_source) => key_source, + None => self.bip_path().await.ok_or(Error::MissingBipPath)?.into(), + }; + + let public_key = match key_source { + BlockIssuerKeySource::Key(public_key) => public_key, + BlockIssuerKeySource::Bip44Path(bip_path) => { + self.secret_manager + .read() + .await + .generate_ed25519_public_keys( + bip_path.coin_type, + bip_path.account, + bip_path.address_index..bip_path.address_index + 1, + None, + ) + .await?[0] + } }; let account = AccountOutput::build_with_amount(implicit_account.amount(), AccountId::from(output_id)) @@ -81,3 +103,9 @@ where .await } } + +#[derive(From)] +pub enum BlockIssuerKeySource { + Key(PublicKey), + Bip44Path(Bip44), +}