Skip to content

Commit cf6ce8c

Browse files
committed
Try to reconnect to the trezor device on IO error
1 parent 36c9a71 commit cf6ce8c

File tree

5 files changed

+200
-82
lines changed

5 files changed

+200
-82
lines changed

Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

wallet/Cargo.toml

+2-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ utxo = { path = "../utxo" }
2727
wallet-storage = { path = "./storage" }
2828
wallet-types = { path = "./types" }
2929
trezor-client = { git = "https://github.com/mintlayer/mintlayer-trezor-firmware", branch = "feature/mintlayer-pk", features = ["bitcoin", "mintlayer"], optional = true }
30+
rusb = { version = "0.9", package = "rusb", optional = true }
3031

3132
bip39 = { workspace = true, default-features = false, features = ["std", "zeroize"] }
3233
hex.workspace = true
@@ -44,5 +45,5 @@ rstest.workspace = true
4445
tempfile.workspace = true
4546

4647
[features]
47-
trezor = ["dep:trezor-client", "wallet-types/trezor"]
48+
trezor = ["dep:trezor-client", "dep:rusb", "wallet-types/trezor"]
4849
enable-trezor-device-tests = []

wallet/src/signer/trezor_signer/mod.rs

+179-63
Original file line numberDiff line numberDiff line change
@@ -123,21 +123,94 @@ pub enum TrezorError {
123123
MultipleSignaturesReturned,
124124
#[error("A multisig signature was returned for a single address from Device")]
125125
MultisigSignatureReturned,
126+
#[error("The file being loaded is a software wallet and does not correspond to the connected hardware wallet")]
127+
HardwareWalletDifferentFile,
128+
#[error("PublicKeys missmatch. Wrong device or passphrase:\nfile DeviceId \"{file_device_id}\", connected device \"{connected_device_id}\",\nfile label \"{file_label}\" and connected device label \"{connected_device_id}\"")]
129+
HardwareWalletDifferentMnemonicOrPassphrase {
130+
file_device_id: String,
131+
connected_device_id: String,
132+
file_label: String,
133+
connected_device_label: String,
134+
},
135+
#[error("The file being loaded correspond to the connected hardware wallet, but public keys are different. Maybe a wrong passphrase was entered?")]
136+
HardwareWalletDifferentPassphrase,
126137
}
127138

128139
pub struct TrezorSigner {
129140
chain_config: Arc<ChainConfig>,
130141
client: Arc<Mutex<Trezor>>,
142+
session_id: Vec<u8>,
131143
}
132144

133145
impl TrezorSigner {
134-
pub fn new(chain_config: Arc<ChainConfig>, client: Arc<Mutex<Trezor>>) -> Self {
146+
pub fn new(
147+
chain_config: Arc<ChainConfig>,
148+
client: Arc<Mutex<Trezor>>,
149+
session_id: Vec<u8>,
150+
) -> Self {
135151
Self {
136152
chain_config,
137153
client,
154+
session_id,
155+
}
156+
}
157+
158+
/// Calls initialize on the device with the current session_id.
159+
///
160+
/// If the operation fails due to an I/O error (which may indicate a lost connection to the device),
161+
/// the function will attempt to reconnect to the Trezor device once before returning an error.
162+
fn check_session(
163+
&mut self,
164+
db_tx: &impl WalletStorageReadLocked,
165+
key_chain: &impl AccountKeyChains,
166+
) -> SignerResult<()> {
167+
let mut client = self.client.lock().expect("poisoned lock");
168+
169+
match client.init_device(Some(self.session_id.clone())) {
170+
Ok(_) => Ok(()),
171+
// In case of a USB IO error try to reconnect, and try again
172+
Err(trezor_client::Error::TransportSendMessage(
173+
trezor_client::transport::error::Error::Usb(rusb::Error::Io),
174+
)) => {
175+
let (mut new_client, data, session_id) = find_trezor_device()?;
176+
177+
check_public_keys_against_key_chain(
178+
db_tx,
179+
&mut new_client,
180+
&data,
181+
key_chain,
182+
&self.chain_config,
183+
)?;
184+
185+
*client = new_client;
186+
self.session_id = session_id;
187+
Ok(())
188+
}
189+
Err(err) => Err(SignerError::TrezorError(TrezorError::DeviceError(
190+
err.to_string(),
191+
))),
138192
}
139193
}
140194

195+
/// Attempts to perform an operation on the Trezor client.
196+
///
197+
/// If the operation fails due to an I/O error (which may indicate a lost connection to the device),
198+
/// the function will attempt to reconnect to the Trezor device once before returning an error.
199+
fn perform_trezor_operation<F, R>(
200+
&mut self,
201+
operation: F,
202+
db_tx: &impl WalletStorageReadLocked,
203+
key_chain: &impl AccountKeyChains,
204+
) -> SignerResult<R>
205+
where
206+
F: Fn(&mut Trezor) -> Result<R, trezor_client::Error>,
207+
{
208+
self.check_session(db_tx, key_chain)?;
209+
210+
let mut client = self.client.lock().expect("poisoned lock");
211+
operation(&mut client).map_err(|e| TrezorError::DeviceError(e.to_string()).into())
212+
}
213+
141214
fn make_signature<F>(
142215
&self,
143216
signatures: &[MintlayerSignature],
@@ -282,7 +355,7 @@ impl Signer for TrezorSigner {
282355
&mut self,
283356
ptx: PartiallySignedTransaction,
284357
key_chain: &impl AccountKeyChains,
285-
_db_tx: &impl WalletStorageReadUnlocked,
358+
db_tx: &impl WalletStorageReadUnlocked,
286359
) -> SignerResult<(
287360
PartiallySignedTransaction,
288361
Vec<SignatureStatus>,
@@ -293,12 +366,13 @@ impl Signer for TrezorSigner {
293366
let utxos = to_trezor_utxo_msgs(&ptx, &self.chain_config)?;
294367
let chain_type = to_trezor_chain_type(&self.chain_config);
295368

296-
let new_signatures = self
297-
.client
298-
.lock()
299-
.expect("poisoned lock")
300-
.mintlayer_sign_tx(chain_type, inputs, outputs, utxos)
301-
.map_err(|err| TrezorError::DeviceError(err.to_string()))?;
369+
let new_signatures = self.perform_trezor_operation(
370+
move |client| {
371+
client.mintlayer_sign_tx(chain_type, inputs.clone(), outputs.clone(), utxos.clone())
372+
},
373+
db_tx,
374+
key_chain,
375+
)?;
302376

303377
let inputs_utxo_refs: Vec<_> = ptx.input_utxos().iter().map(|u| u.as_ref()).collect();
304378

@@ -436,11 +510,11 @@ impl Signer for TrezorSigner {
436510
message: &[u8],
437511
destination: &Destination,
438512
key_chain: &impl AccountKeyChains,
439-
_db_tx: &impl WalletStorageReadUnlocked,
513+
db_tx: &impl WalletStorageReadUnlocked,
440514
) -> SignerResult<ArbitraryMessageSignature> {
441515
let data = match key_chain.find_public_key(destination) {
442516
Some(FoundPubKey::Hierarchy(xpub)) => {
443-
let address_n = xpub
517+
let address_n: Vec<_> = xpub
444518
.get_derivation_path()
445519
.as_slice()
446520
.iter()
@@ -469,12 +543,19 @@ impl Signer for TrezorSigner {
469543

470544
let chain_type = to_trezor_chain_type(&self.chain_config);
471545

472-
let sig = self
473-
.client
474-
.lock()
475-
.expect("poisoned lock")
476-
.mintlayer_sign_message(chain_type, address_n, addr_type, message.to_vec())
477-
.map_err(|err| TrezorError::DeviceError(err.to_string()))?;
546+
let sig = self.perform_trezor_operation(
547+
move |client| {
548+
client.mintlayer_sign_message(
549+
chain_type,
550+
address_n.clone(),
551+
addr_type,
552+
message.to_vec(),
553+
)
554+
},
555+
db_tx,
556+
key_chain,
557+
)?;
558+
478559
let signature = Signature::from_raw_data(&sig, SignatureKind::Secp256k1Schnorr)
479560
.map_err(TrezorError::SignatureError)?;
480561

@@ -1144,6 +1225,7 @@ fn to_trezor_output_lock(lock: &OutputTimeLock) -> trezor_client::protos::Mintla
11441225
pub struct TrezorSignerProvider {
11451226
client: Arc<Mutex<Trezor>>,
11461227
data: TrezorData,
1228+
session_id: Vec<u8>,
11471229
}
11481230

11491231
impl std::fmt::Debug for TrezorSignerProvider {
@@ -1154,27 +1236,29 @@ impl std::fmt::Debug for TrezorSignerProvider {
11541236

11551237
impl TrezorSignerProvider {
11561238
pub fn new() -> Result<Self, TrezorError> {
1157-
let (client, data) =
1239+
let (client, data, session_id) =
11581240
find_trezor_device().map_err(|err| TrezorError::DeviceError(err.to_string()))?;
11591241

11601242
Ok(Self {
11611243
client: Arc::new(Mutex::new(client)),
11621244
data,
1245+
session_id,
11631246
})
11641247
}
11651248

11661249
pub fn load_from_database(
11671250
chain_config: Arc<ChainConfig>,
11681251
db_tx: &impl WalletStorageReadLocked,
11691252
) -> WalletResult<Self> {
1170-
let (client, data) = find_trezor_device().map_err(SignerError::TrezorError)?;
1253+
let (client, data, session_id) = find_trezor_device().map_err(SignerError::TrezorError)?;
11711254

11721255
let provider = Self {
11731256
client: Arc::new(Mutex::new(client)),
11741257
data,
1258+
session_id,
11751259
};
11761260

1177-
check_public_keys(db_tx, &provider, chain_config)?;
1261+
check_public_keys_against_db(db_tx, &provider, chain_config)?;
11781262

11791263
Ok(provider)
11801264
}
@@ -1184,25 +1268,12 @@ impl TrezorSignerProvider {
11841268
chain_config: &Arc<ChainConfig>,
11851269
account_index: U31,
11861270
) -> SignerResult<ExtendedPublicKey> {
1187-
let derivation_path = make_account_path(chain_config, account_index);
1188-
let account_path =
1189-
derivation_path.as_slice().iter().map(|c| c.into_encoded_index()).collect();
1190-
let chain_type = to_trezor_chain_type(chain_config);
1191-
let xpub = self
1192-
.client
1193-
.lock()
1194-
.expect("poisoned lock")
1195-
.mintlayer_get_public_key(chain_type, account_path)
1196-
.map_err(|e| SignerError::TrezorError(TrezorError::DeviceError(e.to_string())))?;
1197-
let chain_code = ChainCode::from(xpub.chain_code.0);
1198-
let account_pubkey = Secp256k1ExtendedPublicKey::new_unchecked(
1199-
derivation_path,
1200-
chain_code,
1201-
Secp256k1PublicKey::from_bytes(&xpub.public_key.serialize())
1202-
.map_err(|_| SignerError::TrezorError(TrezorError::InvalidKey))?,
1203-
);
1204-
let account_pubkey = ExtendedPublicKey::new(account_pubkey);
1205-
Ok(account_pubkey)
1271+
fetch_extended_pub_key(
1272+
&mut self.client.lock().expect("poisoned lock"),
1273+
chain_config,
1274+
account_index,
1275+
)
1276+
.map_err(SignerError::TrezorError)
12061277
}
12071278
}
12081279

@@ -1217,23 +1288,16 @@ fn to_trezor_chain_type(chain_config: &ChainConfig) -> MintlayerChainType {
12171288

12181289
/// Check that the public keys in the DB are the same as the ones with the connected hardware
12191290
/// wallet
1220-
fn check_public_keys(
1291+
fn check_public_keys_against_key_chain(
12211292
db_tx: &impl WalletStorageReadLocked,
1222-
provider: &TrezorSignerProvider,
1223-
chain_config: Arc<ChainConfig>,
1224-
) -> Result<(), WalletError> {
1225-
let first_acc = db_tx
1226-
.get_accounts_info()?
1227-
.iter()
1228-
.find_map(|(acc_id, info)| {
1229-
(info.account_index() == DEFAULT_ACCOUNT_INDEX).then_some(acc_id)
1230-
})
1231-
.cloned()
1232-
.ok_or(WalletError::WalletNotInitialized)?;
1233-
let expected_pk = provider.fetch_extended_pub_key(&chain_config, DEFAULT_ACCOUNT_INDEX)?;
1234-
let loaded_acc = provider.load_account_from_database(chain_config, db_tx, &first_acc)?;
1293+
client: &mut Trezor,
1294+
trezor_data: &TrezorData,
1295+
key_chain: &impl AccountKeyChains,
1296+
chain_config: &ChainConfig,
1297+
) -> SignerResult<()> {
1298+
let expected_pk = fetch_extended_pub_key(client, chain_config, key_chain.account_index())?;
12351299

1236-
if loaded_acc.key_chain().account_public_key() == &expected_pk {
1300+
if key_chain.account_public_key() == &expected_pk {
12371301
return Ok(());
12381302
}
12391303

@@ -1242,24 +1306,75 @@ fn check_public_keys(
12421306
HardwareWalletData::Trezor(data) => {
12431307
// If the device_id is the same but public keys are different, maybe a
12441308
// different passphrase was used
1245-
if data.device_id == provider.data.device_id {
1246-
return Err(WalletError::HardwareWalletDifferentPassphrase);
1309+
if data.device_id == trezor_data.device_id {
1310+
return Err(TrezorError::HardwareWalletDifferentPassphrase.into());
12471311
} else {
1248-
return Err(WalletError::HardwareWalletDifferentMnemonicOrPassphrase {
1312+
return Err(TrezorError::HardwareWalletDifferentMnemonicOrPassphrase {
12491313
file_device_id: data.device_id,
1250-
connected_device_id: provider.data.device_id.clone(),
1314+
connected_device_id: trezor_data.device_id.clone(),
12511315
file_label: data.label,
1252-
connected_device_label: provider.data.label.clone(),
1253-
});
1316+
connected_device_label: trezor_data.label.clone(),
1317+
}
1318+
.into());
12541319
}
12551320
}
12561321
}
12571322
}
12581323

1259-
Err(WalletError::HardwareWalletDifferentFile)
1324+
Err(TrezorError::HardwareWalletDifferentFile)?
1325+
}
1326+
1327+
fn fetch_extended_pub_key(
1328+
client: &mut Trezor,
1329+
chain_config: &ChainConfig,
1330+
account_index: U31,
1331+
) -> Result<ExtendedPublicKey, TrezorError> {
1332+
let derivation_path = make_account_path(chain_config, account_index);
1333+
let account_path = derivation_path.as_slice().iter().map(|c| c.into_encoded_index()).collect();
1334+
let chain_type = to_trezor_chain_type(chain_config);
1335+
let xpub = client
1336+
.mintlayer_get_public_key(chain_type, account_path)
1337+
.map_err(|e| TrezorError::DeviceError(e.to_string()))?;
1338+
let chain_code = ChainCode::from(xpub.chain_code.0);
1339+
let account_pubkey = Secp256k1ExtendedPublicKey::new_unchecked(
1340+
derivation_path,
1341+
chain_code,
1342+
Secp256k1PublicKey::from_bytes(&xpub.public_key.serialize())
1343+
.map_err(|_| TrezorError::InvalidKey)?,
1344+
);
1345+
let account_pubkey = ExtendedPublicKey::new(account_pubkey);
1346+
Ok(account_pubkey)
1347+
}
1348+
1349+
/// Check that the public keys in the DB are the same as the ones with the connected hardware
1350+
/// wallet
1351+
fn check_public_keys_against_db(
1352+
db_tx: &impl WalletStorageReadLocked,
1353+
provider: &TrezorSignerProvider,
1354+
chain_config: Arc<ChainConfig>,
1355+
) -> Result<(), WalletError> {
1356+
let first_acc = db_tx
1357+
.get_accounts_info()?
1358+
.iter()
1359+
.find_map(|(acc_id, info)| {
1360+
(info.account_index() == DEFAULT_ACCOUNT_INDEX).then_some(acc_id)
1361+
})
1362+
.cloned()
1363+
.ok_or(WalletError::WalletNotInitialized)?;
1364+
let loaded_acc =
1365+
provider.load_account_from_database(chain_config.clone(), db_tx, &first_acc)?;
1366+
1367+
check_public_keys_against_key_chain(
1368+
db_tx,
1369+
&mut provider.client.lock().expect("poisoned lock"),
1370+
&provider.data,
1371+
loaded_acc.key_chain(),
1372+
&chain_config,
1373+
)
1374+
.map_err(WalletError::SignerError)
12601375
}
12611376

1262-
fn find_trezor_device() -> Result<(Trezor, TrezorData), TrezorError> {
1377+
fn find_trezor_device() -> Result<(Trezor, TrezorData, Vec<u8>), TrezorError> {
12631378
let mut devices = find_devices(false)
12641379
.into_iter()
12651380
.filter(|device| device.model == Model::Trezor || device.model == Model::TrezorEmulator)
@@ -1287,16 +1402,17 @@ fn find_trezor_device() -> Result<(Trezor, TrezorData), TrezorError> {
12871402
label: features.label().to_owned(),
12881403
device_id: features.device_id().to_owned(),
12891404
};
1405+
let session_id = features.session_id().to_vec();
12901406

1291-
Ok((client, data))
1407+
Ok((client, data, session_id))
12921408
}
12931409

12941410
impl SignerProvider for TrezorSignerProvider {
12951411
type S = TrezorSigner;
12961412
type K = AccountKeyChainImplHardware;
12971413

12981414
fn provide(&mut self, chain_config: Arc<ChainConfig>, _account_index: U31) -> Self::S {
1299-
TrezorSigner::new(chain_config, self.client.clone())
1415+
TrezorSigner::new(chain_config, self.client.clone(), self.session_id.clone())
13001416
}
13011417

13021418
fn make_new_account(

0 commit comments

Comments
 (0)