diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index 39ba6fefa1480..be0aa4ff7c9b0 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -376,9 +376,93 @@ impl MultiWalletOpts { Ok(None) } + /// Returns Trezor hardware wallet signers if the trezor flag is set. + /// + /// This implementation includes infrastructure for session_id reuse to avoid + /// repeated passphrase entry when multiple TrezorSigner instances are created. + /// + /// # Current Status + /// + /// The infrastructure is in place to support session_id reuse, but the actual + /// session_id extraction is currently limited by the alloy-signer-trezor crate + /// which doesn't expose the session_id after TrezorSigner creation. + /// + /// # How it works + /// + /// 1. Creates the first TrezorSigner which establishes a new session with the device + /// 2. Attempts to extract the session_id from this first connection (currently returns None) + /// 3. For subsequent TrezorSigner instances, tries to reuse the session_id if available + /// 4. Falls back to creating new sessions if session_id is not available + /// + /// # Complete Solution Requirements + /// + /// To fully implement session_id reuse, the alloy-signer-trezor crate would need + /// to be modified to either: + /// - Expose the session_id from TrezorSigner instances + /// - Provide a method to clone/share sessions between instances + /// - Allow session_id to be retrieved after successful connection + /// + /// # Related Issue + /// + /// This addresses issue #9388: "trezor session_id reuse across multiple TrezorSigner instances" pub async fn trezors(&self) -> Result>> { if self.trezor { - create_hw_wallets!(self, utils::create_trezor_signer, wallets); + let mut wallets = vec![]; + + // Get the first wallet and attempt to extract session_id for reuse + let (first_wallet, session_id) = if let Some(hd_paths) = &self.hd_paths { + if !hd_paths.is_empty() { + utils::create_trezor_signer_and_get_session(Some(&hd_paths[0]), 0).await? + } else { + utils::create_trezor_signer_and_get_session(None, 0).await? + } + } else if let Some(mnemonic_indexes) = &self.mnemonic_indexes { + if !mnemonic_indexes.is_empty() { + utils::create_trezor_signer_and_get_session(None, mnemonic_indexes[0]).await? + } else { + utils::create_trezor_signer_and_get_session(None, 0).await? + } + } else { + utils::create_trezor_signer_and_get_session(None, 0).await? + }; + + wallets.push(first_wallet); + + // For subsequent wallets, try to reuse the session_id if available + // Currently, session_id will be None due to limitations in alloy-signer-trezor + // but the infrastructure is in place for when this becomes available + + // Create remaining wallets from HD paths if any (starting from index 1) + if let Some(hd_paths) = &self.hd_paths { + for path in hd_paths.iter().skip(1) { + let hw = if session_id.is_some() { + utils::create_trezor_signer_with_session(Some(path), 0, session_id).await? + } else { + // Fallback to creating without session reuse + utils::create_trezor_signer(Some(path), 0).await? + }; + wallets.push(hw); + } + } + + // Create wallets from mnemonic indexes if any + let skip_first_index = + self.hd_paths.is_some() && !self.hd_paths.as_ref().unwrap().is_empty(); + if let Some(mnemonic_indexes) = &self.mnemonic_indexes { + let start_index = if skip_first_index { 0 } else { 1 }; + + for mnemonic_index in mnemonic_indexes.iter().skip(start_index) { + let hw = if session_id.is_some() { + utils::create_trezor_signer_with_session(None, *mnemonic_index, session_id) + .await? + } else { + // Fallback to creating without session reuse + utils::create_trezor_signer(None, *mnemonic_index).await? + }; + wallets.push(hw); + } + } + return Ok(Some(wallets)); } Ok(None) diff --git a/crates/wallets/src/utils.rs b/crates/wallets/src/utils.rs index ab1871d6b90b0..efc3d3d664615 100644 --- a/crates/wallets/src/utils.rs +++ b/crates/wallets/src/utils.rs @@ -86,6 +86,56 @@ Make sure it's connected and unlocked, with no other conflicting desktop wallet }) } +/// Creates [WalletSigner] instance from given Trezor parameters with an optional session_id. +/// If session_id is provided, it will be reused for the new TrezorSigner instance. +pub async fn create_trezor_signer_with_session( + hd_path: Option<&str>, + mnemonic_index: u32, + session_id: Option, +) -> Result { + let derivation = if let Some(hd_path) = hd_path { + TrezorHDPath::Other(hd_path.to_owned()) + } else { + TrezorHDPath::TrezorLive(mnemonic_index as usize) + }; + + WalletSigner::from_trezor_path_with_session(derivation, session_id).await.wrap_err_with(|| { + "\ +Could not connect to Trezor device. +Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." + }) +} + +/// Creates a [WalletSigner] instance from given Trezor parameters and attempts to return +/// the session_id for reuse. Note: This is a best-effort approach as extracting session_id +/// from TrezorSigner is not directly supported by the alloy-signer-trezor crate. +pub async fn create_trezor_signer_and_get_session( + hd_path: Option<&str>, + mnemonic_index: u32, +) -> Result<(WalletSigner, Option)> { + let derivation = if let Some(hd_path) = hd_path { + TrezorHDPath::Other(hd_path.to_owned()) + } else { + TrezorHDPath::TrezorLive(mnemonic_index as usize) + }; + + // Create the TrezorSigner with None session_id (which will create a new session) + let signer = WalletSigner::from_trezor_path_with_session(derivation, None) + .await + .wrap_err_with(|| { + "\ +Could not connect to Trezor device. +Make sure it's connected and unlocked, with no other conflicting desktop wallet apps open." + })?; + + // Unfortunately, we cannot extract the session_id from the created TrezorSigner + // as it's not exposed by the alloy-signer-trezor crate. This would require + // modifications to that crate to expose the session_id. + // For now, we return None as the session_id, but the infrastructure is in place + // for when this becomes available. + Ok((signer, None)) +} + pub fn maybe_get_keystore_path( maybe_path: Option<&str>, maybe_name: Option<&str>, diff --git a/crates/wallets/src/wallet_signer.rs b/crates/wallets/src/wallet_signer.rs index a5d5b70843e5f..afb85fb09076d 100644 --- a/crates/wallets/src/wallet_signer.rs +++ b/crates/wallets/src/wallet_signer.rs @@ -53,6 +53,14 @@ impl WalletSigner { Ok(Self::Trezor(trezor)) } + pub async fn from_trezor_path_with_session( + path: TrezorHDPath, + session_id: Option, + ) -> Result { + let trezor = TrezorSigner::new(path, session_id).await?; + Ok(Self::Trezor(trezor)) + } + pub async fn from_aws(key_id: String) -> Result { #[cfg(feature = "aws-kms")] {