Skip to content

Add selection of trezor device #1895

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 15 additions & 1 deletion node-gui/src/main_window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,20 @@ impl MainWindow {
backend_sender,
)
.map(MainWindowMessage::MainWidgetMessage),
ConsoleCommand::ChoiceMenu(menu) => self
.main_widget
.update(
MainWidgetMessage::TabsMessage(TabsMessage::WalletMessage(
wallet_id,
WalletMessage::ConsoleOutput(format!(
"{}\n{}",
menu.header(),
menu.completion_list().join("\n")
)),
)),
backend_sender,
)
.map(MainWindowMessage::MainWidgetMessage),
ConsoleCommand::ClearScreen
| ConsoleCommand::ClearHistory
| ConsoleCommand::PrintHistory
Expand Down Expand Up @@ -708,7 +722,7 @@ impl MainWindow {
}
}
#[cfg(feature = "trezor")]
WalletArgs::Trezor => WalletTypeArgs::Trezor,
WalletArgs::Trezor => WalletTypeArgs::Trezor { device_id: None },
};

self.file_dialog_active = true;
Expand Down
109 changes: 85 additions & 24 deletions wallet/src/signer/trezor_signer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,13 +108,26 @@ use crate::{

use super::{Signer, SignerError, SignerProvider, SignerResult};

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct FoundDevice {
pub name: String,
pub device_id: String,
}

#[derive(Debug, Clone, Eq, PartialEq)]
pub struct SelectedDevice {
pub device_id: String,
}

/// Signer errors
#[derive(thiserror::Error, Debug, Eq, PartialEq)]
pub enum TrezorError {
#[error("No connected Trezor device found")]
NoDeviceFound,
#[error("There are multiple connected Trezor devices found")]
NoUniqueDeviceFound,
#[error("No compatible Trezor device found with Mintlayer compatibilities")]
NoCompatibleDeviceFound,
#[error("There are multiple connected Trezor devices found {0:?}")]
NoUniqueDeviceFound(Vec<FoundDevice>),
#[error("Cannot get the supported features for the connected Trezor device")]
CannotGetDeviceFeatures,
#[error("The connected Trezor device does not support the Mintlayer capabilities, please install the correct firmware")]
Expand Down Expand Up @@ -142,6 +155,8 @@ pub enum TrezorError {
},
#[error("The file being loaded correspond to the connected hardware wallet, but public keys are different. Maybe a wrong passphrase was entered?")]
HardwareWalletDifferentPassphrase,
#[error("Missing hardware wallet data in database")]
MissingHardwareWalletData,
}

pub struct TrezorSigner {
Expand Down Expand Up @@ -180,7 +195,8 @@ impl TrezorSigner {
Err(trezor_client::Error::TransportSendMessage(
trezor_client::transport::error::Error::Usb(rusb::Error::Io),
)) => {
let (mut new_client, data, session_id) = find_trezor_device()?;
let selected = None;
let (mut new_client, data, session_id) = find_trezor_device(selected)?;

check_public_keys_against_key_chain(
db_tx,
Expand Down Expand Up @@ -1425,9 +1441,8 @@ impl std::fmt::Debug for TrezorSignerProvider {
}

impl TrezorSignerProvider {
pub fn new() -> Result<Self, TrezorError> {
let (client, data, session_id) =
find_trezor_device().map_err(|err| TrezorError::DeviceError(err.to_string()))?;
pub fn new(selected: Option<SelectedDevice>) -> Result<Self, TrezorError> {
let (client, data, session_id) = find_trezor_device(selected)?;

Ok(Self {
client: Arc::new(Mutex::new(client)),
Expand All @@ -1440,7 +1455,24 @@ impl TrezorSignerProvider {
chain_config: Arc<ChainConfig>,
db_tx: &impl WalletStorageReadLocked,
) -> WalletResult<Self> {
let (client, data, session_id) = find_trezor_device().map_err(SignerError::TrezorError)?;
let selected = None;
let (client, data, session_id) = match find_trezor_device(selected) {
Ok(data) => (data.0, data.1, data.2),
Err(TrezorError::NoUniqueDeviceFound(_)) => {
if let Some(HardwareWalletData::Trezor(data)) = db_tx.get_hardware_wallet_data()? {
let selected = SelectedDevice {
device_id: data.device_id,
};

find_trezor_device(Some(selected)).map_err(SignerError::TrezorError)?
} else {
return Err(
SignerError::TrezorError(TrezorError::MissingHardwareWalletData).into(),
);
}
}
Err(err) => return Err(SignerError::TrezorError(err).into()),
};

let provider = Self {
client: Arc::new(Mutex::new(client)),
Expand Down Expand Up @@ -1564,30 +1596,59 @@ fn check_public_keys_against_db(
.map_err(WalletError::SignerError)
}

fn find_trezor_device() -> Result<(Trezor, TrezorData, Vec<u8>), TrezorError> {
let mut devices = find_devices(false)
fn find_trezor_device(
selected: Option<SelectedDevice>,
) -> Result<(Trezor, TrezorData, Vec<u8>), TrezorError> {
let devices = find_devices(false);
ensure!(!devices.is_empty(), TrezorError::NoDeviceFound);

let mut devices = devices
.into_iter()
.filter(|device| device.model == Model::Trezor || device.model == Model::TrezorEmulator)
.filter_map(|d| {
d.connect().ok().and_then(|mut c| {
c.init_device(None).ok()?;

c.features()?
.capabilities
.iter()
.filter_map(|c| c.enum_value().ok())
.contains(&Capability::Capability_Mintlayer)
.then_some(c)
})
})
.collect_vec();

let device = match devices.len() {
0 => return Err(TrezorError::NoDeviceFound),
1 => devices.remove(0),
_ => return Err(TrezorError::NoUniqueDeviceFound),
let client = if let Some(position) = devices.iter().position(|d| {
d.features()
.is_some_and(|f| selected.as_ref().is_none_or(|s| s.device_id == f.device_id()))
}) {
devices.remove(position)
} else {
match devices.len() {
0 => return Err(TrezorError::NoCompatibleDeviceFound),
1 => devices.remove(0),
_ => {
let devices = devices
.into_iter()
.filter_map(|c| {
c.features().map(|f| FoundDevice {
name: if !f.label().is_empty() {
f.label()
} else {
f.model()
}
.to_owned(),
device_id: f.device_id().to_owned(),
})
})
.collect();
return Err(TrezorError::NoUniqueDeviceFound(devices));
}
}
};
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()))?;

let features = client.features().ok_or(TrezorError::CannotGetDeviceFeatures)?;
ensure!(
features
.capabilities
.iter()
.filter_map(|c| c.enum_value().ok())
.contains(&Capability::Capability_Mintlayer),
TrezorError::MintlayerFeaturesNotSupported
);

let data = TrezorData {
label: features.label().to_owned(),
device_id: features.device_id().to_owned(),
Expand Down
53 changes: 47 additions & 6 deletions wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ use crate::key_chain::{
use crate::send_request::{
make_issue_token_outputs, IssueNftArguments, SelectedInputs, StakePoolCreationArguments,
};
#[cfg(feature = "trezor")]
use crate::signer::trezor_signer::{FoundDevice, TrezorError};

use crate::signer::{Signer, SignerError, SignerProvider};
use crate::wallet_events::{WalletEvents, WalletEventsNoOp};
use crate::{Account, SendRequest};
Expand Down Expand Up @@ -306,6 +309,35 @@ pub fn create_wallet_in_memory() -> WalletResult<Store<DefaultBackend>> {
Ok(Store::new(DefaultBackend::new_in_memory())?)
}

pub enum WalletCreation<W> {
Wallet(W),
#[cfg(feature = "trezor")]
MultipleAvalableTrezorDevices(Vec<FoundDevice>),
}

impl<W> WalletCreation<W> {
pub fn map_wallet<F, W2>(self, f: F) -> WalletCreation<W2>
where
F: Fn(W) -> W2,
{
match self {
Self::Wallet(w) => WalletCreation::Wallet(f(w)),
#[cfg(feature = "trezor")]
Self::MultipleAvalableTrezorDevices(devices) => {
WalletCreation::MultipleAvalableTrezorDevices(devices)
}
}
}

pub fn wallet(self) -> Option<W> {
match self {
WalletCreation::Wallet(w) => Some(w),
#[cfg(feature = "trezor")]
WalletCreation::MultipleAvalableTrezorDevices(_) => None,
}
}
}

impl<B, P> Wallet<B, P>
where
B: storage::Backend + 'static,
Expand All @@ -317,10 +349,12 @@ where
best_block: (BlockHeight, Id<GenBlock>),
wallet_type: WalletType,
signer_provider: F,
) -> WalletResult<Self> {
) -> WalletResult<WalletCreation<Self>> {
let mut wallet = Self::new_wallet(chain_config, db, wallet_type, signer_provider)?;

wallet.set_best_block(best_block.0, best_block.1)?;
if let WalletCreation::Wallet(ref mut w) = wallet {
w.set_best_block(best_block.0, best_block.1)?;
}

Ok(wallet)
}
Expand All @@ -330,7 +364,7 @@ where
db: Store<B>,
wallet_type: WalletType,
signer_provider: F,
) -> WalletResult<Self> {
) -> WalletResult<WalletCreation<Self>> {
Self::new_wallet(chain_config, db, wallet_type, signer_provider)
}

Expand All @@ -339,14 +373,21 @@ where
db: Store<B>,
wallet_type: WalletType,
signer_provider: F,
) -> WalletResult<Self> {
) -> WalletResult<WalletCreation<Self>> {
let mut db_tx = db.transaction_rw_unlocked(None)?;

db_tx.set_storage_version(CURRENT_WALLET_VERSION)?;
db_tx.set_chain_info(&ChainInfo::new(chain_config.as_ref()))?;
db_tx.set_lookahead_size(LOOKAHEAD_SIZE)?;
db_tx.set_wallet_type(wallet_type)?;
let mut signer_provider = signer_provider(&mut db_tx)?;
let mut signer_provider = match signer_provider(&mut db_tx) {
Ok(x) => x,
#[cfg(feature = "trezor")]
Err(WalletError::SignerError(SignerError::TrezorError(
TrezorError::NoUniqueDeviceFound(devices),
))) => return Ok(WalletCreation::MultipleAvalableTrezorDevices(devices)),
Err(err) => return Err(err),
};

if let Some(data) = signer_provider.get_hardware_wallet_data() {
db_tx.set_hardware_wallet_data(data)?;
Expand Down Expand Up @@ -380,7 +421,7 @@ where
signer_provider,
};

Ok(wallet)
Ok(WalletCreation::Wallet(wallet))
}

/// Migrate the wallet DB from version 1 to version 2
Expand Down
11 changes: 11 additions & 0 deletions wallet/src/wallet/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ fn create_wallet_with_mnemonic(chain_config: Arc<ChainConfig>, mnemonic: &str) -
},
)
.unwrap()
.wallet()
.unwrap()
}

#[track_caller]
Expand Down Expand Up @@ -436,7 +438,10 @@ fn wallet_migration_to_v2(#[case] seed: Seed) {
)?)
},
)
.unwrap()
.wallet()
.unwrap();

verify_wallet_balance(&chain_config, &wallet, genesis_amount);

let password = Some("password".into());
Expand Down Expand Up @@ -543,6 +548,8 @@ fn wallet_seed_phrase_retrieval(#[case] seed: Seed) {
)?)
},
)
.unwrap()
.wallet()
.unwrap();

let wallet_passphrase = PassPhrase::new(zeroize::Zeroizing::new(wallet_passphrase));
Expand Down Expand Up @@ -638,6 +645,8 @@ fn wallet_seed_phrase_check_address() {
)?)
},
)
.unwrap()
.wallet()
.unwrap();

let address = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap();
Expand All @@ -664,6 +673,8 @@ fn wallet_seed_phrase_check_address() {
)?)
},
)
.unwrap()
.wallet()
.unwrap();

let address = wallet.get_new_address(DEFAULT_ACCOUNT_INDEX).unwrap();
Expand Down
1 change: 1 addition & 0 deletions wallet/wallet-cli-commands/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ clap = { workspace = true, features = ["derive"] }
async-trait.workspace = true
crossterm.workspace = true
directories.workspace = true
dyn-clone.workspace = true
humantime.workspace = true
hex.workspace = true
itertools.workspace = true
Expand Down
Loading
Loading