diff --git a/Cargo.lock b/Cargo.lock index 1d6a577058..27d1815d0a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8121,7 +8121,7 @@ dependencies = [ [[package]] name = "trezor-client" version = "0.1.4" -source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature/mintlayer-pk#554dbfd9f60afc879a033df07ca0a9ea321ac553" +source = "git+https://github.com/mintlayer/mintlayer-trezor-firmware?branch=feature/mintlayer-pk#c929c9a8f71ed9edfc679b96e3d4815fd8f3316b" dependencies = [ "bitcoin", "byteorder", @@ -8730,14 +8730,12 @@ dependencies = [ "mempool-types", "node-comm", "p2p-types", - "pos-accounting", "randomness", "rpc-description", "rstest", "serde", "serialization", "storage", - "storage-inmemory", "test-utils", "thiserror", "tokio", diff --git a/common/src/chain/tokens/tokens_utils.rs b/common/src/chain/tokens/tokens_utils.rs index cd8c9fe570..04caf8a3df 100644 --- a/common/src/chain/tokens/tokens_utils.rs +++ b/common/src/chain/tokens/tokens_utils.rs @@ -91,16 +91,16 @@ pub fn is_token_or_nft_issuance(output: &TxOutput) -> bool { } } -/// Get any referenced token by this output +/// Get any token referenced by this output /// ignore tokens V0 pub fn get_referenced_token_ids(output: &TxOutput) -> BTreeSet { match output { TxOutput::Transfer(v, _) | TxOutput::LockThenTransfer(v, _, _) | TxOutput::Burn(v) - | TxOutput::Htlc(v, _) => referenced_token_id(v), + | TxOutput::Htlc(v, _) => referenced_token_id(v).into_iter().collect(), | TxOutput::CreateOrder(data) => { - let mut tokens = referenced_token_id(data.ask()); + let mut tokens: BTreeSet<_> = referenced_token_id(data.ask()).into_iter().collect(); tokens.extend(referenced_token_id(data.give())); tokens } @@ -114,9 +114,9 @@ pub fn get_referenced_token_ids(output: &TxOutput) -> BTreeSet { } } -fn referenced_token_id(v: &OutputValue) -> BTreeSet { +fn referenced_token_id(v: &OutputValue) -> Option { match v { - OutputValue::Coin(_) | OutputValue::TokenV0(_) => BTreeSet::new(), - OutputValue::TokenV1(token_id, _) => BTreeSet::from_iter([*token_id]), + OutputValue::Coin(_) | OutputValue::TokenV0(_) => None, + OutputValue::TokenV1(token_id, _) => Some(*token_id), } } diff --git a/crypto/src/key/signature/mod.rs b/crypto/src/key/signature/mod.rs index 8bfefa9d8e..8f0cc1620a 100644 --- a/crypto/src/key/signature/mod.rs +++ b/crypto/src/key/signature/mod.rs @@ -19,6 +19,8 @@ use std::io::BufWriter; use num_derive::FromPrimitive; use serialization::{hex_encoded::HexEncoded, Decode, DecodeAll, Encode}; +use super::SignatureError; + #[derive(FromPrimitive)] pub enum SignatureKind { Secp256k1Schnorr = 0, @@ -77,6 +79,12 @@ impl Signature { Ok(decoded_sig) } + pub fn from_raw_data>(data: T) -> Result { + let decoded_sig = secp256k1::schnorr::Signature::from_slice(data.as_ref()) + .map_err(|_| SignatureError::SignatureConstructionError)?; + Ok(Self::Secp256k1Schnorr(decoded_sig)) + } + pub fn is_aggregable(&self) -> bool { match self { Self::Secp256k1Schnorr(_) => false, diff --git a/node-gui/src/backend/backend_impl.rs b/node-gui/src/backend/backend_impl.rs index 3db54e81c8..621b0a8eef 100644 --- a/node-gui/src/backend/backend_impl.rs +++ b/node-gui/src/backend/backend_impl.rs @@ -351,26 +351,7 @@ impl Backend { } #[cfg(feature = "trezor")] (WalletType::Trezor, ColdHotNodeController::Cold) => { - let client = make_cold_wallet_rpc_client(Arc::clone(&self.chain_config)); - - let (wallet_rpc, command_handler, best_block, accounts_info, accounts_data) = self - .create_wallet( - client, - file_path.clone(), - wallet_args, - import, - wallet_events, - ) - .await?; - - let wallet_data = WalletData { - controller: GuiHotColdController::Cold(wallet_rpc, command_handler), - accounts: accounts_data, - best_block, - updated: false, - }; - - (wallet_data, accounts_info, best_block) + return Err(BackendError::ColdTrezorNotSupported) } (WalletType::Hot, ColdHotNodeController::Cold) => { return Err(BackendError::HotNotSupported) @@ -577,32 +558,7 @@ impl Backend { } #[cfg(feature = "trezor")] (WalletType::Trezor, ColdHotNodeController::Cold) => { - let client = make_cold_wallet_rpc_client(Arc::clone(&self.chain_config)); - - let ( - wallet_rpc, - command_handler, - encryption_state, - best_block, - accounts_info, - accounts_data, - ) = self - .open_wallet( - client, - file_path.clone(), - wallet_events, - Some(HardwareWalletType::Trezor), - ) - .await?; - - let wallet_data = WalletData { - controller: GuiHotColdController::Cold(wallet_rpc, command_handler), - accounts: accounts_data, - best_block, - updated: false, - }; - - (wallet_data, accounts_info, best_block, encryption_state) + return Err(BackendError::ColdTrezorNotSupported) } (WalletType::Hot, ColdHotNodeController::Cold) => { return Err(BackendError::HotNotSupported) diff --git a/node-gui/src/backend/error.rs b/node-gui/src/backend/error.rs index c434dfd699..53fa20db86 100644 --- a/node-gui/src/backend/error.rs +++ b/node-gui/src/backend/error.rs @@ -41,6 +41,8 @@ pub enum BackendError { ColdWallet, #[error("Cannot interact with a hot wallet when in Cold wallet mode")] HotNotSupported, + #[error("Cannot use a Trezor wallet in a Cold wallet mode")] + ColdTrezorNotSupported, #[error("Invalid console command: {0}")] InvalidConsoleCommand(String), #[error("Empty console command")] diff --git a/node-gui/src/main_window/main_menu.rs b/node-gui/src/main_window/main_menu.rs index 4445cc6e96..120d492e51 100644 --- a/node-gui/src/main_window/main_menu.rs +++ b/node-gui/src/main_window/main_menu.rs @@ -110,19 +110,19 @@ fn make_menu_file<'a>(wallet_mode: WalletMode) -> Item<'a, MenuMessage, Theme, i WalletMode::Hot => { let menu = vec![ menu_item( - "Create new Hot wallet", + "Create new Software wallet", MenuMessage::CreateNewWallet { wallet_type: WalletType::Hot, }, ), menu_item( - "Recover Hot wallet", + "Recover Software wallet", MenuMessage::RecoverWallet { wallet_type: WalletType::Hot, }, ), menu_item( - "Open Hot wallet", + "Open Software wallet", MenuMessage::OpenWallet { wallet_type: WalletType::Hot, }, diff --git a/node-gui/src/main_window/mod.rs b/node-gui/src/main_window/mod.rs index fc6ef69e7c..b00c790e97 100644 --- a/node-gui/src/main_window/mod.rs +++ b/node-gui/src/main_window/mod.rs @@ -59,7 +59,7 @@ mod main_widget; enum ActiveDialog { None, WalletCreate { - generated_mnemonic: wallet_controller::mnemonic::Mnemonic, + wallet_args: WalletArgs, wallet_type: WalletType, }, WalletRecover { @@ -163,7 +163,7 @@ impl ImportOrCreate { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum WalletArgs { Software { mnemonic: String, @@ -276,11 +276,20 @@ impl MainWindow { MainWindowMessage::MenuMessage(menu_message) => match menu_message { MenuMessage::NoOp => Command::none(), MenuMessage::CreateNewWallet { wallet_type } => { - let generated_mnemonic = - wallet_controller::mnemonic::generate_new_mnemonic(self.language); + let wallet_args = match wallet_type { + WalletType::Hot | WalletType::Cold => WalletArgs::Software { + mnemonic: wallet_controller::mnemonic::generate_new_mnemonic( + self.language, + ) + .to_string(), + }, + #[cfg(feature = "trezor")] + WalletType::Trezor => WalletArgs::Trezor, + }; + self.active_dialog = ActiveDialog::WalletCreate { - generated_mnemonic, wallet_type, + wallet_args, }; Command::none() } @@ -801,13 +810,13 @@ impl MainWindow { ActiveDialog::None => Text::new("Nothing to show").into(), ActiveDialog::WalletCreate { - generated_mnemonic, wallet_type, + wallet_args, } => { let wallet_type = *wallet_type; - match wallet_type { - WalletType::Hot | WalletType::Cold => wallet_mnemonic_dialog( - Some(generated_mnemonic.clone()), + match wallet_args { + WalletArgs::Software { mnemonic } => wallet_mnemonic_dialog( + Some(mnemonic.clone()), Box::new(move |mnemonic| MainWindowMessage::ImportWalletMnemonic { args: WalletArgs::Software { mnemonic }, import: ImportOrCreate::Create, @@ -817,7 +826,7 @@ impl MainWindow { ) .into(), #[cfg(feature = "trezor")] - WalletType::Trezor => hw_wallet_create_dialog( + WalletArgs::Trezor => hw_wallet_create_dialog( Box::new(move || MainWindowMessage::ImportWalletMnemonic { args: WalletArgs::Trezor, import: ImportOrCreate::Create, diff --git a/node-gui/src/widgets/wallet_mnemonic.rs b/node-gui/src/widgets/wallet_mnemonic.rs index 9d52effd86..62d210baf1 100644 --- a/node-gui/src/widgets/wallet_mnemonic.rs +++ b/node-gui/src/widgets/wallet_mnemonic.rs @@ -21,13 +21,13 @@ use iced::{ use iced_aw::Card; pub struct WalletMnemonicDialog { - generated_mnemonic_opt: Option, + generated_mnemonic_opt: Option, on_import: Box Message>, on_close: Box Message>, } pub fn wallet_mnemonic_dialog( - generated_mnemonic_opt: Option, + generated_mnemonic_opt: Option, on_import: Box Message>, on_close: Box Message>, ) -> WalletMnemonicDialog { @@ -67,7 +67,7 @@ impl Component for WalletMnemonicDialog ImportEvent::Ok => { state.importing = true; let mnemonic = match &self.generated_mnemonic_opt { - Some(generated_mnemonic) => generated_mnemonic.to_string(), + Some(generated_mnemonic) => generated_mnemonic.clone(), None => state.entered_mnemonic.clone(), }; Some((self.on_import)(mnemonic)) @@ -78,7 +78,7 @@ impl Component for WalletMnemonicDialog fn view(&self, state: &Self::State) -> Element { let (mnemonic, action_text) = match &self.generated_mnemonic_opt { - Some(generated_mnemonic) => (generated_mnemonic.to_string(), "Create"), + Some(generated_mnemonic) => (generated_mnemonic.clone(), "Create"), None => (state.entered_mnemonic.clone(), "Recover"), }; diff --git a/wallet/src/send_request/mod.rs b/wallet/src/send_request/mod.rs index 0bf5c6aa7b..1c21f2410e 100644 --- a/wallet/src/send_request/mod.rs +++ b/wallet/src/send_request/mod.rs @@ -332,7 +332,7 @@ impl SendRequest { } } -/// Find aditional data for TxOutput, mainly for UI purposes +/// Find additional data for TxOutput, mainly for UI purposes fn find_additional_info( utxo: &TxOutput, additional_info: &BTreeMap, @@ -388,7 +388,7 @@ fn find_token_additional_info( UtxoAdditionalInfo::TokenInfo(data) => Ok(Some(data.clone())), UtxoAdditionalInfo::PoolInfo { staker_balance: _ } | UtxoAdditionalInfo::CreateOrder { ask: _, give: _ } => { - Err(WalletError::MissmatchedTokenAdditionalData(*token_id)) + Err(WalletError::MismatchedTokenAdditionalData(*token_id)) } })?, } diff --git a/wallet/src/signer/mod.rs b/wallet/src/signer/mod.rs index 0516d14abe..b526cf6c91 100644 --- a/wallet/src/signer/mod.rs +++ b/wallet/src/signer/mod.rs @@ -15,17 +15,23 @@ use std::sync::Arc; -use common::chain::{ - signature::{ - inputsig::{ - arbitrary_message::{ArbitraryMessageSignature, SignArbitraryMessageError}, - classical_multisig::multisig_partial_signature::PartiallySignedMultisigStructureError, +use common::{ + address::AddressError, + chain::{ + signature::{ + inputsig::{ + arbitrary_message::{ArbitraryMessageSignature, SignArbitraryMessageError}, + classical_multisig::multisig_partial_signature::PartiallySignedMultisigStructureError, + }, + DestinationSigError, }, - DestinationSigError, + ChainConfig, Destination, }, - ChainConfig, Destination, }; -use crypto::key::hdkd::{derivable::DerivationError, u31::U31}; +use crypto::key::{ + hdkd::{derivable::DerivationError, u31::U31}, + SignatureError, +}; use wallet_storage::{ WalletStorageReadLocked, WalletStorageReadUnlocked, WalletStorageWriteUnlocked, }; @@ -80,6 +86,10 @@ pub enum SignerError { UnsupportedTokensV0, #[error("Invalid TxOutput type as UTXO, cannot be spent")] InvalidUtxo, + #[error("Address error: {0}")] + AddressError(#[from] AddressError), + #[error("Signature error: {0}")] + SignatureError(#[from] SignatureError), } type SignerResult = Result; diff --git a/wallet/src/signer/trezor_signer/mod.rs b/wallet/src/signer/trezor_signer/mod.rs index 200459c228..5d55ae77d8 100644 --- a/wallet/src/signer/trezor_signer/mod.rs +++ b/wallet/src/signer/trezor_signer/mod.rs @@ -50,7 +50,7 @@ use crypto::key::{ extended::ExtendedPublicKey, hdkd::{chain_code::ChainCode, derivable::Derivable, u31::U31}, secp256k1::{extended_keys::Secp256k1ExtendedPublicKey, Secp256k1PublicKey}, - Signature, + Signature, SignatureError, }; use itertools::Itertools; use serialization::Encode; @@ -102,6 +102,10 @@ pub enum TrezorError { NoDeviceFound, #[error("Trezor device error: {0}")] DeviceError(String), + #[error("Invalid public key returned from trezor")] + InvalidKey, + #[error("Invalid Signature error: {0}")] + SignatureError(#[from] SignatureError), } pub struct TrezorSigner { @@ -126,7 +130,7 @@ impl TrezorSigner { secret: Option, key_chain: &impl AccountKeyChains, ) -> SignerResult<(Option, SignatureStatus)> { - let add_secret = |sig: StandardInputSignature| { + let add_secret_if_needed = |sig: StandardInputSignature| { let sig = if let Some(htlc_secret) = secret { let sig_with_secret = AuthorizedHashedTimelockContractSpend::Secret( htlc_secret, @@ -149,13 +153,17 @@ impl TrezorSigner { )), Destination::PublicKeyHash(_) => { if let Some(signature) = signature.first() { - let pk = - key_chain.find_public_key(destination).expect("found").into_public_key(); - let mut signature = signature.signature.clone(); - signature.insert(0, 0); - let sig = Signature::from_data(signature)?; + let pk = key_chain + .find_public_key(destination) + .ok_or(SignerError::DestinationNotFromThisWallet)? + .into_public_key(); + let sig = Signature::from_raw_data(&signature.signature) + .map_err(TrezorError::SignatureError)?; let sig = AuthorizedPublicKeyHashSpend::new(pk, sig); - let sig = add_secret(StandardInputSignature::new(sighash_type, sig.encode())); + let sig = add_secret_if_needed(StandardInputSignature::new( + sighash_type, + sig.encode(), + )); Ok((Some(sig), SignatureStatus::FullySigned)) } else { @@ -164,11 +172,13 @@ impl TrezorSigner { } Destination::PublicKey(_) => { if let Some(signature) = signature.first() { - let mut signature = signature.signature.clone(); - signature.insert(0, 0); - let sig = Signature::from_data(signature)?; + let sig = Signature::from_raw_data(&signature.signature) + .map_err(TrezorError::SignatureError)?; let sig = AuthorizedPublicKeySpend::new(sig); - let sig = add_secret(StandardInputSignature::new(sighash_type, sig.encode())); + let sig = add_secret_if_needed(StandardInputSignature::new( + sighash_type, + sig.encode(), + )); Ok((Some(sig), SignatureStatus::FullySigned)) } else { @@ -182,9 +192,8 @@ impl TrezorSigner { for sig in signature { if let Some(idx) = sig.multisig_idx { - let mut signature = sig.signature.clone(); - signature.insert(0, 0); - let sig = Signature::from_data(signature)?; + let sig = Signature::from_raw_data(&sig.signature) + .map_err(TrezorError::SignatureError)?; current_signatures.add_signature(idx as u8, sig); } } @@ -214,7 +223,7 @@ impl TrezorSigner { } }; - let sig = add_secret(StandardInputSignature::new( + let sig = add_secret_if_needed(StandardInputSignature::new( sighash_type, current_signatures.encode(), )); @@ -313,9 +322,8 @@ impl Signer for TrezorSigner { if let Some(signature) = new_signatures.get(i) { for sig in signature { if let Some(idx) = sig.multisig_idx { - let mut signature = sig.signature.clone(); - signature.insert(0, 0); - let sig = Signature::from_data(signature)?; + let sig = Signature::from_raw_data(&sig.signature) + .map_err(TrezorError::SignatureError)?; current_signatures.add_signature(idx as u8, sig); } } @@ -424,9 +432,7 @@ impl Signer for TrezorSigner { .map(|c| c.into_encoded_index()) .collect(); - let addr = Address::new(&self.chain_config, destination.clone()) - .expect("addressable") - .into_string(); + let addr = Address::new(&self.chain_config, destination.clone())?.into_string(); let sig = self .client @@ -434,9 +440,8 @@ impl Signer for TrezorSigner { .expect("poisoned lock") .mintlayer_sign_message(address_n, addr, message) .map_err(|err| TrezorError::DeviceError(err.to_string()))?; - let mut signature = sig; - signature.insert(0, 0); - let signature = Signature::from_data(signature)?; + let signature = + Signature::from_raw_data(&sig).map_err(TrezorError::SignatureError)?; match &destination { Destination::PublicKey(_) => AuthorizedPublicKeySpend::new(signature).encode(), @@ -486,12 +491,9 @@ fn to_trezor_input_msgs( (TxInput::Utxo(outpoint), Some(_), Some(dest)) => { to_trezor_utxo_input(outpoint, chain_config, dest, key_chain) } - (TxInput::Account(outpoint), _, Some(dest)) => Ok(to_trezor_account_input( - chain_config, - dest, - key_chain, - outpoint, - )), + (TxInput::Account(outpoint), _, Some(dest)) => { + to_trezor_account_input(chain_config, dest, key_chain, outpoint) + } (TxInput::AccountCommand(nonce, command), _, Some(dest)) => { to_trezor_account_command_input(chain_config, dest, key_chain, nonce, command) } @@ -509,90 +511,67 @@ fn to_trezor_account_command_input( command: &AccountCommand, ) -> SignerResult { let mut inp_req = MintlayerAccountCommandTxInput::new(); - inp_req - .set_address(Address::new(chain_config, dest.clone()).expect("addressable").into_string()); + inp_req.set_address(Address::new(chain_config, dest.clone())?.into_string()); inp_req.address_n = destination_to_address_paths(key_chain, dest); inp_req.set_nonce(nonce.value()); match command { AccountCommand::MintTokens(token_id, amount) => { let mut req = MintlayerMintTokens::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); req.set_amount(amount.into_atoms().to_be_bytes().to_vec()); inp_req.mint = Some(req).into(); } AccountCommand::UnmintTokens(token_id) => { let mut req = MintlayerUnmintTokens::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); inp_req.unmint = Some(req).into(); } AccountCommand::FreezeToken(token_id, unfreezable) => { let mut req = MintlayerFreezeToken::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); req.set_is_token_unfreezabe(unfreezable.as_bool()); inp_req.freeze_token = Some(req).into(); } AccountCommand::UnfreezeToken(token_id) => { let mut req = MintlayerUnfreezeToken::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); inp_req.unfreeze_token = Some(req).into(); } AccountCommand::LockTokenSupply(token_id) => { let mut req = MintlayerLockTokenSupply::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); inp_req.lock_token_supply = Some(req).into(); } AccountCommand::ChangeTokenAuthority(token_id, dest) => { let mut req = MintlayerChangeTokenAuhtority::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); - req.set_destination( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); + req.set_destination(Address::new(chain_config, dest.clone())?.into_string()); inp_req.change_token_authority = Some(req).into(); } AccountCommand::ChangeTokenMetadataUri(token_id, uri) => { let mut req = MintlayerChangeTokenMetadataUri::new(); - req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); req.set_metadata_uri(uri.clone()); inp_req.change_token_metadata_uri = Some(req).into(); } AccountCommand::ConcludeOrder(order_id) => { let mut req = MintlayerConcludeOrder::new(); - req.set_order_id( - Address::new(chain_config, *order_id).expect("addressable").into_string(), - ); + req.set_order_id(Address::new(chain_config, *order_id)?.into_string()); inp_req.conclude_order = Some(req).into(); } AccountCommand::FillOrder(order_id, amount, dest) => { let mut req = MintlayerFillOrder::new(); - req.set_order_id( - Address::new(chain_config, *order_id).expect("addressable").into_string(), - ); + req.set_order_id(Address::new(chain_config, *order_id)?.into_string()); req.set_amount(amount.into_atoms().to_be_bytes().to_vec()); - req.set_destination( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + req.set_destination(Address::new(chain_config, dest.clone())?.into_string()); inp_req.fill_order = Some(req).into(); } @@ -607,17 +586,14 @@ fn to_trezor_account_input( dest: &Destination, key_chain: &impl AccountKeyChains, outpoint: &common::chain::AccountOutPoint, -) -> MintlayerTxInput { +) -> SignerResult { let mut inp_req = MintlayerAccountTxInput::new(); - inp_req - .set_address(Address::new(chain_config, dest.clone()).expect("addressable").into_string()); + inp_req.set_address(Address::new(chain_config, dest.clone())?.into_string()); inp_req.address_n = destination_to_address_paths(key_chain, dest); inp_req.set_nonce(outpoint.nonce().value()); match outpoint.account() { AccountSpending::DelegationBalance(delegation_id, amount) => { - inp_req.set_delegation_id( - Address::new(chain_config, *delegation_id).expect("addressable").into_string(), - ); + inp_req.set_delegation_id(Address::new(chain_config, *delegation_id)?.into_string()); let mut value = MintlayerOutputValue::new(); value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); inp_req.value = Some(value).into(); @@ -625,7 +601,7 @@ fn to_trezor_account_input( } let mut inp = MintlayerTxInput::new(); inp.account = Some(inp_req).into(); - inp + Ok(inp) } fn to_trezor_utxo_input( @@ -648,8 +624,7 @@ fn to_trezor_utxo_input( inp_req.set_prev_hash(id.to_vec()); inp_req.set_prev_index(outpoint.output_index()); - inp_req - .set_address(Address::new(chain_config, dest.clone()).expect("addressable").into_string()); + inp_req.set_address(Address::new(chain_config, dest.clone())?.into_string()); inp_req.address_n = destination_to_address_paths(key_chain, dest); let mut inp = MintlayerTxInput::new(); @@ -730,9 +705,11 @@ fn to_trezor_utxo_msgs( chain_config: &ChainConfig, ) -> SignerResult>> { let mut utxos: BTreeMap<[u8; 32], BTreeMap> = BTreeMap::new(); + for (utxo, inp) in ptx.input_utxos().iter().zip(ptx.tx().inputs()) { - match (inp, utxo) { - (TxInput::Utxo(outpoint), Some(utxo)) => { + match inp { + TxInput::Utxo(outpoint) => { + let utxo = utxo.as_ref().ok_or(SignerError::MissingUtxo)?; let id = match outpoint.source_id() { OutPointSourceId::Transaction(id) => id.to_hash().0, OutPointSourceId::BlockReward(id) => id.to_hash().0, @@ -740,11 +717,7 @@ fn to_trezor_utxo_msgs( let out = to_trezor_output_msg(chain_config, &utxo.utxo, &utxo.additional_info)?; utxos.entry(id).or_default().insert(outpoint.output_index(), out); } - (TxInput::Utxo(_), None) => unimplemented!("missing utxo"), - (TxInput::Account(_) | TxInput::AccountCommand(_, _), Some(_)) => { - panic!("can't have accounts as UTXOs") - } - (TxInput::Account(_) | TxInput::AccountCommand(_, _), _) => {} + TxInput::Account(_) | TxInput::AccountCommand(_, _) => {} } } @@ -765,9 +738,7 @@ fn to_trezor_output_msg( chain_config, )?) .into(); - out_req.set_address( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + out_req.set_address(Address::new(chain_config, dest.clone())?.into_string()); let mut out = MintlayerTxOutput::new(); out.transfer = Some(out_req).into(); @@ -781,9 +752,7 @@ fn to_trezor_output_msg( chain_config, )?) .into(); - out_req.set_address( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + out_req.set_address(Address::new(chain_config, dest.clone())?.into_string()); out_req.lock = Some(to_trezor_output_lock(lock)).into(); @@ -806,21 +775,15 @@ fn to_trezor_output_msg( } TxOutput::CreateDelegationId(dest, pool_id) => { let mut out_req = MintlayerCreateDelegationIdTxOutput::new(); - out_req.set_pool_id( - Address::new(chain_config, *pool_id).expect("addressable").into_string(), - ); - out_req.set_destination( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + out_req.set_pool_id(Address::new(chain_config, *pool_id)?.into_string()); + out_req.set_destination(Address::new(chain_config, dest.clone())?.into_string()); let mut out = MintlayerTxOutput::new(); out.create_delegation_id = Some(out_req).into(); out } TxOutput::DelegateStaking(amount, delegation_id) => { let mut out_req = MintlayerDelegateStakingTxOutput::new(); - out_req.set_delegation_id( - Address::new(chain_config, *delegation_id).expect("addressable").into_string(), - ); + out_req.set_delegation_id(Address::new(chain_config, *delegation_id)?.into_string()); out_req.set_amount(amount.into_atoms().to_be_bytes().to_vec()); let mut out = MintlayerTxOutput::new(); out.delegate_staking = Some(out_req).into(); @@ -828,25 +791,16 @@ fn to_trezor_output_msg( } TxOutput::CreateStakePool(pool_id, pool_data) => { let mut out_req = MintlayerCreateStakePoolTxOutput::new(); - out_req.set_pool_id( - Address::new(chain_config, *pool_id).expect("addressable").into_string(), - ); + out_req.set_pool_id(Address::new(chain_config, *pool_id)?.into_string()); out_req.set_pledge(pool_data.pledge().into_atoms().to_be_bytes().to_vec()); - out_req.set_staker( - Address::new(chain_config, pool_data.staker().clone()) - .expect("addressable") - .into_string(), - ); + out_req + .set_staker(Address::new(chain_config, pool_data.staker().clone())?.into_string()); out_req.set_decommission_key( - Address::new(chain_config, pool_data.decommission_key().clone()) - .expect("addressable") - .into_string(), + Address::new(chain_config, pool_data.decommission_key().clone())?.into_string(), ); out_req.set_vrf_public_key( - Address::new(chain_config, pool_data.vrf_public_key().clone()) - .expect("addressable") - .into_string(), + Address::new(chain_config, pool_data.vrf_public_key().clone())?.into_string(), ); out_req .set_cost_per_block(pool_data.cost_per_block().into_atoms().to_be_bytes().to_vec()); @@ -860,12 +814,17 @@ fn to_trezor_output_msg( } TxOutput::ProduceBlockFromStake(dest, pool_id) => { let mut out_req = MintlayerProduceBlockFromStakeTxOutput::new(); - out_req.set_pool_id( - Address::new(chain_config, *pool_id).expect("addressable").into_string(), - ); - out_req.set_destination( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + out_req.set_pool_id(Address::new(chain_config, *pool_id)?.into_string()); + out_req.set_destination(Address::new(chain_config, dest.clone())?.into_string()); + let staker_balance = additional_info + .as_ref() + .and_then(|info| match info { + UtxoAdditionalInfo::PoolInfo { staker_balance } => Some(staker_balance), + UtxoAdditionalInfo::TokenInfo(_) + | UtxoAdditionalInfo::CreateOrder { ask: _, give: _ } => None, + }) + .ok_or(SignerError::MissingUtxoExtraInfo)?; + out_req.set_staker_balance(staker_balance.into_atoms().to_be_bytes().to_vec()); let mut out = MintlayerTxOutput::new(); out.produce_block_from_stake = Some(out_req).into(); out @@ -876,9 +835,7 @@ fn to_trezor_output_msg( match token_data.as_ref() { TokenIssuance::V1(data) => { out_req.set_authority( - Address::new(chain_config, data.authority.clone()) - .expect("addressable") - .into_string(), + Address::new(chain_config, data.authority.clone())?.into_string(), ); out_req.set_token_ticker(data.token_ticker.clone()); out_req.set_metadata_uri(data.metadata_uri.clone()); @@ -908,12 +865,8 @@ fn to_trezor_output_msg( } TxOutput::IssueNft(token_id, nft_data, dest) => { let mut out_req = MintlayerIssueNftTxOutput::new(); - out_req.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); - out_req.set_destination( - Address::new(chain_config, dest.clone()).expect("addressable").into_string(), - ); + out_req.set_token_id(Address::new(chain_config, *token_id)?.into_string()); + out_req.set_destination(Address::new(chain_config, dest.clone())?.into_string()); match nft_data.as_ref() { NftIssuance::V0(data) => { // @@ -939,8 +892,7 @@ fn to_trezor_output_msg( Address::new( chain_config, Destination::PublicKey(creator.public_key.clone()), - ) - .expect("addressable") + )? .into_string(), ); } @@ -967,16 +919,10 @@ fn to_trezor_output_msg( .into(); out_req.secret_hash = Some(lock.secret_hash.as_bytes().to_vec()); - out_req.set_spend_key( - Address::new(chain_config, lock.spend_key.clone()) - .expect("addressable") - .into_string(), - ); - out_req.set_refund_key( - Address::new(chain_config, lock.refund_key.clone()) - .expect("addressable") - .into_string(), - ); + out_req + .set_spend_key(Address::new(chain_config, lock.spend_key.clone())?.into_string()); + out_req + .set_refund_key(Address::new(chain_config, lock.refund_key.clone())?.into_string()); out_req.refund_timelock = Some(to_trezor_output_lock(&lock.refund_timelock)).into(); @@ -988,9 +934,7 @@ fn to_trezor_output_msg( let mut out_req = MintlayerCreateOrderTxOutput::new(); out_req.set_conclude_key( - Address::new(chain_config, data.conclude_key().clone()) - .expect("addressable") - .into_string(), + Address::new(chain_config, data.conclude_key().clone())?.into_string(), ); match additional_info { @@ -1038,9 +982,7 @@ fn to_trezor_output_value_with_token_info( let mut value = MintlayerOutputValue::new(); value.set_amount(amount.into_atoms().to_be_bytes().to_vec()); let mut token_value = MintlayerTokenOutputValue::new(); - token_value.set_token_id( - Address::new(chain_config, *token_id).expect("addressable").into_string(), - ); + token_value.set_token_id(Address::new(chain_config, *token_id)?.into_string()); match &token_info { Some(info) => { token_value.set_number_of_decimals(info.num_decimals as u32); @@ -1114,7 +1056,7 @@ impl TrezorSignerProvider { &self, chain_config: &Arc, account_index: U31, - ) -> Result { + ) -> SignerResult { let derivation_path = make_account_path(chain_config, account_index); let account_path = derivation_path.as_slice().iter().map(|c| c.into_encoded_index()).collect(); @@ -1128,7 +1070,8 @@ impl TrezorSignerProvider { let account_pubkey = Secp256k1ExtendedPublicKey::new( derivation_path, chain_code, - Secp256k1PublicKey::from_bytes(&xpub.public_key.serialize()).expect(""), + Secp256k1PublicKey::from_bytes(&xpub.public_key.serialize()) + .map_err(|_| SignerError::TrezorError(TrezorError::InvalidKey))?, ); let account_pubkey = ExtendedPublicKey::new(account_pubkey); Ok(account_pubkey) @@ -1164,7 +1107,8 @@ fn find_trezor_device() -> Result { .into_iter() .find(|device| device.model == Model::Trezor) .ok_or(TrezorError::NoDeviceFound)?; - let client = device.connect().map_err(|e| TrezorError::DeviceError(e.to_string()))?; + let mut client = device.connect().map_err(|e| TrezorError::DeviceError(e.to_string()))?; + client.init_device(None).map_err(|e| TrezorError::DeviceError(e.to_string()))?; Ok(client) } diff --git a/wallet/src/wallet/mod.rs b/wallet/src/wallet/mod.rs index 5b16d0562a..b9150cd7d0 100644 --- a/wallet/src/wallet/mod.rs +++ b/wallet/src/wallet/mod.rs @@ -262,8 +262,8 @@ pub enum WalletError { MissingPoolAdditionalData(PoolId), #[error("Missing additional data for Token {0}")] MissingTokenAdditionalData(TokenId), - #[error("Missmatched additional data for token {0}")] - MissmatchedTokenAdditionalData(TokenId), + #[error("Mismatched additional data for token {0}")] + MismatchedTokenAdditionalData(TokenId), #[error("Unsupported operation for a Hardware wallet")] UnsupportedHardwareWalletOperation, } diff --git a/wallet/wallet-cli-commands/src/command_handler/mod.rs b/wallet/wallet-cli-commands/src/command_handler/mod.rs index b0b513a421..d797eb52e5 100644 --- a/wallet/wallet-cli-commands/src/command_handler/mod.rs +++ b/wallet/wallet-cli-commands/src/command_handler/mod.rs @@ -33,7 +33,7 @@ use node_comm::node_traits::NodeInterface; use serialization::{hex::HexEncode, hex_encoded::HexEncoded}; use utils::qrcode::{QrCode, QrCodeError}; use wallet::version::get_version; -use wallet_controller::types::GenericTokenTransfer; +use wallet_controller::types::{GenericTokenTransfer, WalletTypeArgs}; use wallet_rpc_client::wallet_rpc_traits::{PartialOrSignedTx, WalletInterface}; use wallet_rpc_lib::types::{ Balances, ComposedTransaction, ControllerConfig, MnemonicInfo, NewTransaction, NftMetadata, @@ -45,7 +45,9 @@ use wallet_rpc_lib::types::{ use crate::helper_types::CliHardwareWalletType; #[cfg(feature = "trezor")] use wallet_rpc_lib::types::HardwareWalletType; -use wallet_types::partially_signed_transaction::PartiallySignedTransaction; +use wallet_types::{ + partially_signed_transaction::PartiallySignedTransaction, seed_phrase::StoreSeedPhrase, +}; use crate::{ errors::WalletCliCommandError, helper_types::parse_generic_token_transfer, @@ -152,22 +154,21 @@ where passphrase, hardware_wallet, } => { - let hardware_wallet = hardware_wallet.map(|t| match t { - #[cfg(feature = "trezor")] - CliHardwareWalletType::Trezor => HardwareWalletType::Trezor, - }); - - let newly_generated_mnemonic = self - .wallet() - .await? - .create_wallet( - wallet_path, - whether_to_store_seed_phrase.map_or(false, |x| x.to_bool()), + let store_seed_phrase = + whether_to_store_seed_phrase.map_or(StoreSeedPhrase::DoNotStore, Into::into); + let wallet_args = hardware_wallet.map_or( + WalletTypeArgs::Software { mnemonic, passphrase, - hardware_wallet, - ) - .await?; + store_seed_phrase, + }, + |t| match t { + #[cfg(feature = "trezor")] + CliHardwareWalletType::Trezor => WalletTypeArgs::Trezor, + }, + ); + let newly_generated_mnemonic = + self.wallet().await?.create_wallet(wallet_path, wallet_args).await?; self.wallet.update_wallet::().await; diff --git a/wallet/wallet-cli-commands/src/helper_types.rs b/wallet/wallet-cli-commands/src/helper_types.rs index 42c76cde4d..57e9c669a8 100644 --- a/wallet/wallet-cli-commands/src/helper_types.rs +++ b/wallet/wallet-cli-commands/src/helper_types.rs @@ -25,6 +25,7 @@ use common::{ use wallet_controller::types::{GenericCurrencyTransfer, GenericTokenTransfer}; use wallet_rpc_lib::types::{NodeInterface, PoolInfo, TokenTotalSupply}; use wallet_types::{ + seed_phrase::StoreSeedPhrase, utxo_types::{UtxoState, UtxoType}, with_locked::WithLocked, }; @@ -142,6 +143,15 @@ impl CliStoreSeedPhrase { } } +impl From for StoreSeedPhrase { + fn from(value: CliStoreSeedPhrase) -> Self { + match value { + CliStoreSeedPhrase::StoreSeedPhrase => StoreSeedPhrase::Store, + CliStoreSeedPhrase::DoNotStoreSeedPhrase => StoreSeedPhrase::DoNotStore, + } + } +} + #[derive(Debug, Clone, Copy, ValueEnum)] pub enum EnableOrDisable { Enable, diff --git a/wallet/wallet-cli-commands/src/lib.rs b/wallet/wallet-cli-commands/src/lib.rs index 705cc9cacc..e7313041c5 100644 --- a/wallet/wallet-cli-commands/src/lib.rs +++ b/wallet/wallet-cli-commands/src/lib.rs @@ -19,7 +19,8 @@ mod helper_types; pub use command_handler::CommandHandler; pub use errors::WalletCliCommandError; -use helper_types::{CliHardwareWalletType, YesNo}; +pub use helper_types::CliHardwareWalletType; +use helper_types::YesNo; use rpc::description::{Described, Module}; use wallet_rpc_lib::{types::NodeInterface, ColdWalletRpcDescription, WalletRpcDescription}; @@ -65,7 +66,8 @@ pub enum WalletManagementCommand { passphrase: Option, /// Create a wallet using a connected hardware wallet. Only the public keys will be kept in - /// the software wallet + /// the software wallet. Cannot specify a mnemonic or passphrase here, input them on the + /// hardware wallet instead when initializing the device. #[arg(long, conflicts_with_all(["mnemonic", "passphrase"]))] hardware_wallet: Option, }, @@ -76,14 +78,14 @@ pub enum WalletManagementCommand { wallet_path: PathBuf, /// If 'store-seed-phrase', the seed-phrase will be stored in the wallet file. - /// If 'do-not-store-seed-phrase', the seed-phrase will only be printed on the screen. /// Not storing the seed-phrase can be seen as a security measure /// to ensure sufficient secrecy in case that seed-phrase is reused /// elsewhere if this wallet is compromised. #[arg(required_unless_present("hardware_wallet"))] whether_to_store_seed_phrase: Option, - /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). If not specified, a new mnemonic phrase is generated and printed. + /// Mnemonic phrase (12, 15, or 24 words as a single quoted argument). + #[arg(required_unless_present("hardware_wallet"))] mnemonic: Option, /// Passphrase along the mnemonic @@ -91,8 +93,9 @@ pub enum WalletManagementCommand { passphrase: Option, /// Create a wallet using a connected hardware wallet. Only the public keys will be kept in - /// the software wallet - #[arg(long, conflicts_with_all(["mnemonic", "passphrase"]))] + /// the software wallet. Cannot specify a mnemonic or passphrase here, input them on the + /// hardware wallet instead when initializing the device. + #[arg(long, conflicts_with_all(["passphrase"]))] hardware_wallet: Option, }, diff --git a/wallet/wallet-cli-lib/src/config.rs b/wallet/wallet-cli-lib/src/config.rs index 436d7844a9..87e5923e31 100644 --- a/wallet/wallet-cli-lib/src/config.rs +++ b/wallet/wallet-cli-lib/src/config.rs @@ -20,6 +20,7 @@ use common::chain::config::{regtest_options::ChainConfigOptions, ChainType}; use crypto::key::hdkd::u31::U31; use utils::clap_utils; use utils_networking::NetworkAddressWithPort; +use wallet_rpc_lib::cmdline::CliHardwareWalletType; #[derive(Subcommand, Clone, Debug)] pub enum Network { @@ -84,6 +85,10 @@ pub struct CliArgs { #[clap(long, requires("wallet_file"))] pub force_change_wallet_type: bool, + /// Specified if the wallet file is of a hardware wallet type e.g. Trezor + #[arg(long, requires("wallet_file"))] + pub hardware_wallet: Option, + /// DEPRECATED: use start_staking_for_account instead! /// Start staking for the DEFAULT account after starting the wallet #[clap(long, requires("wallet_file"))] diff --git a/wallet/wallet-cli-lib/src/lib.rs b/wallet/wallet-cli-lib/src/lib.rs index 5fc3a8321b..385b0ad532 100644 --- a/wallet/wallet-cli-lib/src/lib.rs +++ b/wallet/wallet-cli-lib/src/lib.rs @@ -36,9 +36,10 @@ use node_comm::{make_cold_wallet_rpc_client, make_rpc_client, rpc_client::ColdWa use rpc::RpcAuthData; use tokio::sync::mpsc; use utils::{cookie::COOKIE_FILENAME, default_data_dir::default_data_dir_for_chain, ensure}; -use wallet_cli_commands::{ManageableWalletCommand, WalletCommand, WalletManagementCommand}; -use wallet_rpc_lib::types::NodeInterface; -use wallet_rpc_lib::{cmdline::make_wallet_config, config::WalletRpcConfig}; +use wallet_cli_commands::{ + CliHardwareWalletType, ManageableWalletCommand, WalletCommand, WalletManagementCommand, +}; +use wallet_rpc_lib::{cmdline::make_wallet_config, config::WalletRpcConfig, types::NodeInterface}; enum Mode { Interactive { @@ -284,8 +285,12 @@ fn setup_events_and_repl( wallet_path, encryption_password: args.wallet_password, force_change_wallet_type: args.force_change_wallet_type, - // TODO: add support for opening a hardware_wallet - hardware_wallet: None, + hardware_wallet: args.hardware_wallet.map(|hw| match hw { + #[cfg(feature = "trezor")] + wallet_rpc_lib::cmdline::CliHardwareWalletType::Trezor => { + CliHardwareWalletType::Trezor + } + }), }, ), res_tx, diff --git a/wallet/wallet-cli-lib/tests/cli_test_framework.rs b/wallet/wallet-cli-lib/tests/cli_test_framework.rs index 2e559a08bf..f9eb8dffa2 100644 --- a/wallet/wallet-cli-lib/tests/cli_test_framework.rs +++ b/wallet/wallet-cli-lib/tests/cli_test_framework.rs @@ -98,6 +98,7 @@ impl CliTestFramework { wallet_file: None, wallet_password: None, force_change_wallet_type: false, + hardware_wallet: None, start_staking: false, start_staking_for_account: vec![], node_rpc_address: Some(rpc_address.into()), @@ -127,6 +128,7 @@ impl CliTestFramework { wallet_file: None, wallet_password: None, force_change_wallet_type: false, + hardware_wallet: None, start_staking: false, start_staking_for_account: vec![], node_rpc_address: Some(rpc_address.into()), diff --git a/wallet/wallet-controller/Cargo.toml b/wallet/wallet-controller/Cargo.toml index a1a0c055ec..66f3afcae9 100644 --- a/wallet/wallet-controller/Cargo.toml +++ b/wallet/wallet-controller/Cargo.toml @@ -21,8 +21,6 @@ rpc-description = { path = "../../rpc/description" } randomness = { path = "../../randomness" } serialization = { path = "../../serialization" } storage = { path = "../../storage" } -storage-inmemory = { path = "../../storage/inmemory" } -pos-accounting = { path = "../../pos-accounting" } utils = { path = "../../utils" } utils-networking = { path = "../../utils/networking" } wallet = { path = ".." } diff --git a/wallet/wallet-controller/src/read.rs b/wallet/wallet-controller/src/read.rs index bd0ee4220c..2e9d1b2ab2 100644 --- a/wallet/wallet-controller/src/read.rs +++ b/wallet/wallet-controller/src/read.rs @@ -308,45 +308,22 @@ where pub async fn get_delegations( &self, ) -> Result, ControllerError> { - let delegations = match &self.wallet { - RuntimeWallet::Software(w) => { - let delegations = - w.get_delegations(self.account_index).map_err(ControllerError::WalletError)?; - let tasks: FuturesUnordered<_> = delegations - .into_iter() - .map(|(delegation_id, delegation_data)| { - self.get_delegation_share(delegation_data, *delegation_id).map(|res| { - res.map(|opt| { - opt.map(|(delegation_id, amount)| { - (delegation_id, delegation_data.pool_id, amount) - }) - }) - }) - }) - .collect(); - - tasks.try_collect::>().await?.into_iter().flatten().collect() - } - #[cfg(feature = "trezor")] - RuntimeWallet::Trezor(w) => { - let delegations = - w.get_delegations(self.account_index).map_err(ControllerError::WalletError)?; - let tasks: FuturesUnordered<_> = delegations - .into_iter() - .map(|(delegation_id, delegation_data)| { - self.get_delegation_share(delegation_data, *delegation_id).map(|res| { - res.map(|opt| { - opt.map(|(delegation_id, amount)| { - (delegation_id, delegation_data.pool_id, amount) - }) - }) + let tasks: FuturesUnordered<_> = self + .wallet + .get_delegations(self.account_index) + .map_err(ControllerError::WalletError)? + .map(|(delegation_id, delegation_data)| { + self.get_delegation_share(delegation_data, *delegation_id).map(|res| { + res.map(|opt| { + opt.map(|(delegation_id, amount)| { + (delegation_id, delegation_data.pool_id, amount) }) }) - .collect(); + }) + }) + .collect(); - tasks.try_collect::>().await?.into_iter().flatten().collect() - } - }; + let delegations = tasks.try_collect::>().await?.into_iter().flatten().collect(); Ok(delegations) } diff --git a/wallet/wallet-controller/src/runtime_wallet.rs b/wallet/wallet-controller/src/runtime_wallet.rs index c7b0242316..6c80469fff 100644 --- a/wallet/wallet-controller/src/runtime_wallet.rs +++ b/wallet/wallet-controller/src/runtime_wallet.rs @@ -1233,4 +1233,19 @@ impl RuntimeWallet { } } } + + pub fn get_delegations( + &self, + account_index: U31, + ) -> WalletResult + '_>> { + match self { + RuntimeWallet::Software(w) => w + .get_delegations(account_index) + .map(|it| -> Box> { Box::new(it) }), + #[cfg(feature = "trezor")] + RuntimeWallet::Trezor(w) => w + .get_delegations(account_index) + .map(|it| -> Box> { Box::new(it) }), + } + } } diff --git a/wallet/wallet-controller/src/types/mod.rs b/wallet/wallet-controller/src/types/mod.rs index ed6e307316..d7f5ecadbd 100644 --- a/wallet/wallet-controller/src/types/mod.rs +++ b/wallet/wallet-controller/src/types/mod.rs @@ -188,7 +188,7 @@ impl WalletTypeArgs { } } - pub fn parse_mnemonic( + pub fn parse_or_generate_mnemonic_if_needed( self, ) -> Result<(WalletTypeArgsComputed, CreatedWallet), mnemonic::Error> { match self { diff --git a/wallet/wallet-rpc-client/src/handles_client/mod.rs b/wallet/wallet-rpc-client/src/handles_client/mod.rs index 03b3027d9b..9d0df64079 100644 --- a/wallet/wallet-rpc-client/src/handles_client/mod.rs +++ b/wallet/wallet-rpc-client/src/handles_client/mod.rs @@ -124,42 +124,11 @@ where async fn create_wallet( &self, path: PathBuf, - store_seed_phrase: bool, - mnemonic: Option, - passphrase: Option, - hardware_wallet: Option, + wallet_args: WalletTypeArgs, ) -> Result { - let store_seed_phrase = if store_seed_phrase { - StoreSeedPhrase::Store - } else { - StoreSeedPhrase::DoNotStore - }; - - let args = match hardware_wallet { - None => WalletTypeArgs::Software { - mnemonic, - passphrase, - store_seed_phrase, - }, - #[cfg(feature = "trezor")] - Some(HardwareWalletType::Trezor) => { - ensure!( - mnemonic.is_none() - && passphrase.is_none() - && store_seed_phrase == StoreSeedPhrase::DoNotStore, - RpcError::HardwareWalletWithMnemonic - ); - WalletTypeArgs::Trezor - } - #[cfg(not(feature = "trezor"))] - Some(_) => { - return Err(RpcError::::InvalidHardwareWallet)?; - } - }; - let scan_blockchain = false; self.wallet_rpc - .create_wallet(path, args, false, scan_blockchain) + .create_wallet(path, wallet_args, false, scan_blockchain) .await .map(Into::into) .map_err(WalletRpcHandlesClientError::WalletRpcError) @@ -191,7 +160,7 @@ where mnemonic.is_none() && passphrase.is_none() && store_seed_phrase == StoreSeedPhrase::DoNotStore, - RpcError::HardwareWalletWithMnemonic + RpcError::HardwareWalletWithMnemonicOrPassphrase ); WalletTypeArgs::Trezor } diff --git a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs index 6e75868ebb..dd91bdec39 100644 --- a/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs +++ b/wallet/wallet-rpc-client/src/rpc_client/client_impl.rs @@ -37,7 +37,10 @@ use serialization::DecodeAll; use utils_networking::IpOrSocketAddress; use wallet::account::TxInfo; use wallet_controller::{ - types::{Balances, CreatedBlockInfo, GenericTokenTransfer, SeedWithPassPhrase, WalletInfo}, + types::{ + Balances, CreatedBlockInfo, GenericTokenTransfer, SeedWithPassPhrase, WalletInfo, + WalletTypeArgs, + }, ConnectedPeer, ControllerConfig, UtxoState, UtxoType, }; use wallet_rpc_lib::{ @@ -83,11 +86,18 @@ impl WalletInterface for ClientWalletRpc { async fn create_wallet( &self, path: PathBuf, - store_seed_phrase: bool, - mnemonic: Option, - passphrase: Option, - hardware_wallet: Option, + wallet_args: WalletTypeArgs, ) -> Result { + let (mnemonic, passphrase, store_seed_phrase, hardware_wallet) = match wallet_args { + WalletTypeArgs::Software { + mnemonic, + passphrase, + store_seed_phrase, + } => (mnemonic, passphrase, store_seed_phrase.should_save(), None), + #[cfg(feature = "trezor")] + WalletTypeArgs::Trezor => (None, None, false, Some(HardwareWalletType::Trezor)), + }; + ColdWalletRpcClient::create_wallet( &self.http_client, path.to_string_lossy().to_string(), diff --git a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs index 239316bba9..5f309cd760 100644 --- a/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs +++ b/wallet/wallet-rpc-client/src/wallet_rpc_traits.rs @@ -29,7 +29,9 @@ use serialization::hex_encoded::HexEncoded; use utils_networking::IpOrSocketAddress; use wallet::account::TxInfo; use wallet_controller::{ - types::{CreatedBlockInfo, GenericTokenTransfer, SeedWithPassPhrase, WalletInfo}, + types::{ + CreatedBlockInfo, GenericTokenTransfer, SeedWithPassPhrase, WalletInfo, WalletTypeArgs, + }, ConnectedPeer, ControllerConfig, UtxoState, UtxoType, }; use wallet_rpc_lib::types::{ @@ -70,10 +72,7 @@ pub trait WalletInterface { async fn create_wallet( &self, path: PathBuf, - store_seed_phrase: bool, - mnemonic: Option, - passphrase: Option, - hardware_wallet: Option, + wallet_args: WalletTypeArgs, ) -> Result; async fn recover_wallet( diff --git a/wallet/wallet-rpc-lib/src/cmdline.rs b/wallet/wallet-rpc-lib/src/cmdline.rs index c5b677720c..0893d2d310 100644 --- a/wallet/wallet-rpc-lib/src/cmdline.rs +++ b/wallet/wallet-rpc-lib/src/cmdline.rs @@ -15,6 +15,7 @@ use std::path::PathBuf; +use clap::ValueEnum; use common::chain::config::{regtest_options::ChainConfigOptions, ChainType}; use crypto::key::hdkd::u31::U31; use rpc::{ @@ -26,7 +27,10 @@ use utils::{ }; use utils_networking::NetworkAddressWithPort; -use crate::config::{WalletRpcConfig, WalletServiceConfig}; +use crate::{ + config::{WalletRpcConfig, WalletServiceConfig}, + types::HardwareWalletType, +}; /// Service providing an RPC interface to a wallet #[derive(clap::Parser)] @@ -78,6 +82,21 @@ impl WalletRpcDaemonCommand { } } +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum CliHardwareWalletType { + #[cfg(feature = "trezor")] + Trezor, +} + +impl From for HardwareWalletType { + fn from(value: CliHardwareWalletType) -> Self { + match value { + #[cfg(feature = "trezor")] + CliHardwareWalletType::Trezor => Self::Trezor, + } + } +} + #[derive(clap::Args)] #[command( version, @@ -98,6 +117,10 @@ pub struct WalletRpcDaemonChainArgs { #[arg(long, requires("wallet_file"))] force_change_wallet_type: bool, + /// Specified if the wallet file is of a hardware wallet type e.g. Trezor + #[arg(long, requires("wallet_file"))] + hardware_wallet: Option, + /// Start staking for the specified account after starting the wallet #[arg(long, value_name("ACC_NUMBER"), requires("wallet_file"))] start_staking_for_account: Vec, @@ -161,6 +184,7 @@ impl WalletRpcDaemonChainArgs { let Self { wallet_file, force_change_wallet_type, + hardware_wallet, rpc_bind_address, start_staking_for_account, node_rpc_address, @@ -185,6 +209,7 @@ impl WalletRpcDaemonChainArgs { wallet_file, force_change_wallet_type, start_staking_for_account, + hardware_wallet.map(Into::into), ); if cold_wallet { diff --git a/wallet/wallet-rpc-lib/src/config.rs b/wallet/wallet-rpc-lib/src/config.rs index c2029d2673..6bf0e55d07 100644 --- a/wallet/wallet-rpc-lib/src/config.rs +++ b/wallet/wallet-rpc-lib/src/config.rs @@ -22,6 +22,8 @@ use common::chain::config::{ use crypto::key::hdkd::u31::U31; use rpc::{rpc_creds::RpcCreds, RpcAuthData}; +use crate::types::HardwareWalletType; + #[derive(Clone)] pub enum NodeRpc { ColdWallet, @@ -45,6 +47,9 @@ pub struct WalletServiceConfig { /// Force change the wallet type from hot to cold or from cold to hot pub force_change_wallet_type: bool, + /// Specified if the wallet file is of a hardware wallet type e.g. Trezor + pub hardware_wallet_type: Option, + /// Start staking for account after starting the wallet pub start_staking_for_account: Vec, @@ -58,6 +63,7 @@ impl WalletServiceConfig { wallet_file: Option, force_change_wallet_type: bool, start_staking_for_account: Vec, + hardware_wallet_type: Option, ) -> Self { Self { chain_config: Arc::new(common::chain::config::Builder::new(chain_type).build()), @@ -65,6 +71,7 @@ impl WalletServiceConfig { force_change_wallet_type, start_staking_for_account, node_rpc: NodeRpc::ColdWallet, + hardware_wallet_type, } } diff --git a/wallet/wallet-rpc-lib/src/lib.rs b/wallet/wallet-rpc-lib/src/lib.rs index c4e9e476da..1b0e8f49d5 100644 --- a/wallet/wallet-rpc-lib/src/lib.rs +++ b/wallet/wallet-rpc-lib/src/lib.rs @@ -18,12 +18,14 @@ pub mod config; mod rpc; mod service; +use rpc::types::HardwareWalletType; pub use rpc::{ types, ColdWalletRpcClient, ColdWalletRpcDescription, ColdWalletRpcServer, RpcCreds, RpcError, WalletEventsRpcServer, WalletRpc, WalletRpcClient, WalletRpcDescription, WalletRpcServer, }; pub use service::{Event, EventStream, TxState, WalletHandle, /* WalletResult, */ WalletService,}; use wallet_controller::{NodeInterface, NodeRpcClient}; +use wallet_types::wallet_type::WalletType; use std::{fmt::Debug, time::Duration}; @@ -98,13 +100,17 @@ pub async fn start_services( where N: NodeInterface + Clone + Sync + Send + 'static + Debug, { + let wallet_type = wallet_config.hardware_wallet_type.map_or_else( + || node_rpc.is_cold_wallet_node().into(), + |hw| match hw { + #[cfg(feature = "trezor")] + HardwareWalletType::Trezor => WalletType::Trezor, + }, + ); // Start the wallet service let wallet_service = WalletService::start( wallet_config.chain_config, - // TODO add support for trezor wallet - wallet_config - .wallet_file - .map(|file| (file, node_rpc.is_cold_wallet_node().into())), + wallet_config.wallet_file.map(|file| (file, wallet_type)), wallet_config.force_change_wallet_type, wallet_config.start_staking_for_account, node_rpc, diff --git a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs index 85c8dc390f..a596fb5b07 100644 --- a/wallet/wallet-rpc-lib/src/rpc/server_impl.rs +++ b/wallet/wallet-rpc-lib/src/rpc/server_impl.rs @@ -114,7 +114,7 @@ where mnemonic.is_none() && passphrase.is_none() && store_seed_phrase == StoreSeedPhrase::DoNotStore, - RpcError::::HardwareWalletWithMnemonic + RpcError::::HardwareWalletWithMnemonicOrPassphrase ); WalletTypeArgs::Trezor } @@ -161,7 +161,7 @@ where mnemonic.is_none() && passphrase.is_none() && store_seed_phrase == StoreSeedPhrase::DoNotStore, - RpcError::::HardwareWalletWithMnemonic + RpcError::::HardwareWalletWithMnemonicOrPassphrase ); WalletTypeArgs::Trezor } diff --git a/wallet/wallet-rpc-lib/src/rpc/types.rs b/wallet/wallet-rpc-lib/src/rpc/types.rs index 1ac59e451e..7d7d05cc27 100644 --- a/wallet/wallet-rpc-lib/src/rpc/types.rs +++ b/wallet/wallet-rpc-lib/src/rpc/types.rs @@ -95,7 +95,7 @@ pub enum RpcError { EmptyMnemonic, #[error("Cannot specify a mnemonic or passphrase when using a hardware wallet")] - HardwareWalletWithMnemonic, + HardwareWalletWithMnemonicOrPassphrase, #[error("Invalid hardware wallet selection")] InvalidHardwareWallet, diff --git a/wallet/wallet-rpc-lib/src/service/worker.rs b/wallet/wallet-rpc-lib/src/service/worker.rs index 8acfac81b4..cc6597e541 100644 --- a/wallet/wallet-rpc-lib/src/service/worker.rs +++ b/wallet/wallet-rpc-lib/src/service/worker.rs @@ -193,7 +193,7 @@ where ); let wallet_type = args.wallet_type(self.node_rpc.is_cold_wallet_node()); let (computed_args, wallet_created) = - args.parse_mnemonic().map_err(RpcError::InvalidMnemonic)?; + args.parse_or_generate_mnemonic_if_needed().map_err(RpcError::InvalidMnemonic)?; let wallet = if scan_blockchain { WalletController::recover_wallet( diff --git a/wallet/wallet-rpc-lib/tests/utils.rs b/wallet/wallet-rpc-lib/tests/utils.rs index a54cda825b..d631f9c852 100644 --- a/wallet/wallet-rpc-lib/tests/utils.rs +++ b/wallet/wallet-rpc-lib/tests/utils.rs @@ -110,10 +110,11 @@ impl TestFramework { // Start the wallet service let (wallet_service, rpc_server) = { - let ws_config = WalletServiceConfig::new(chain_type, Some(wallet_path), false, vec![]) - .with_regtest_options(chain_config_options) - .unwrap() - .with_custom_chain_config(chain_config.clone()); + let ws_config = + WalletServiceConfig::new(chain_type, Some(wallet_path), false, vec![], None) + .with_regtest_options(chain_config_options) + .unwrap() + .with_custom_chain_config(chain_config.clone()); let bind_addr = "127.0.0.1:0".parse().unwrap(); let rpc_config = wallet_rpc_lib::config::WalletRpcConfig { bind_addr,