Skip to content

feat(wallets): add infrastructure for Trezor session_id reuse across multiple signers #10822

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
86 changes: 85 additions & 1 deletion crates/wallets/src/multi_wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<Vec<WalletSigner>>> {
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)
Expand Down
50 changes: 50 additions & 0 deletions crates/wallets/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>,
) -> Result<WalletSigner> {
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<u64>)> {
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>,
Expand Down
8 changes: 8 additions & 0 deletions crates/wallets/src/wallet_signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ impl WalletSigner {
Ok(Self::Trezor(trezor))
}

pub async fn from_trezor_path_with_session(
path: TrezorHDPath,
session_id: Option<u64>,
) -> Result<Self> {
let trezor = TrezorSigner::new(path, session_id).await?;
Ok(Self::Trezor(trezor))
}

pub async fn from_aws(key_id: String) -> Result<Self> {
#[cfg(feature = "aws-kms")]
{
Expand Down