Skip to content

Conversation

@OBorce
Copy link
Contributor

@OBorce OBorce commented Jul 30, 2025

  • Ledger signer
  • Ledger tests

Tests assume the ledger app is already running in the emulator same as Trezor tests

@OBorce OBorce changed the base branch from master to refactor/wallet-async-signing July 30, 2025 13:35
@OBorce OBorce force-pushed the feature/ledger_signer branch 2 times, most recently from ecf51c0 to abe8a88 Compare July 31, 2025 08:24
@OBorce OBorce force-pushed the refactor/wallet-async-signing branch from 59bd742 to 9099e90 Compare August 22, 2025 10:57
@OBorce OBorce force-pushed the feature/ledger_signer branch from abe8a88 to 89285d1 Compare August 22, 2025 10:58
@OBorce OBorce marked this pull request as ready for review August 25, 2025 12:43
@OBorce OBorce force-pushed the feature/ledger_signer branch from 5f20a4f to 4c64407 Compare August 25, 2025 12:56
Copy link
Contributor

@ImplOfAnImpl ImplOfAnImpl left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't dug into the code much yet, will continue next week.

Tests assume the ledger app repo is cloned next to this one with name ledger-mintlayer

To be honest, I'm not a huge fan of this approach. And also of the fact that the emulator is always started automatically. E.g. in the Trezor case it was sometimes useful to see the emulator logs to understand what went wrong.
Was there any particular reason to do it this way instead of expecting the emuator to be running?

@OBorce OBorce force-pushed the refactor/wallet-async-signing branch 3 times, most recently from f21e82b to 75ff652 Compare September 18, 2025 22:31
@OBorce OBorce force-pushed the feature/ledger_signer branch 4 times, most recently from 61292c5 to eeb6484 Compare September 23, 2025 22:28
@OBorce OBorce marked this pull request as draft September 25, 2025 08:51
@OBorce OBorce force-pushed the refactor/wallet-async-signing branch from 75ff652 to 96016cf Compare September 25, 2025 08:57
@OBorce OBorce force-pushed the feature/ledger_signer branch 3 times, most recently from 5d3edf0 to d6fd484 Compare September 30, 2025 23:37
@OBorce OBorce marked this pull request as ready for review October 1, 2025 07:09
@OBorce OBorce force-pushed the feature/ledger_signer branch from 39d3e58 to d6fd484 Compare October 3, 2025 07:07
Comment on lines 197 to 146
pub async fn get_app_name<L: Exchange>(ledger: &mut L) -> Result<Vec<u8>, ledger_lib::Error> {
let msg_buf = [CLA, Ins::APP_NAME, 0, P2::DONE];
ledger.exchange(&msg_buf, Duration::from_millis(100)).await
}

#[allow(dead_code)]
pub async fn check_current_app<L: Exchange>(ledger: &mut L) -> SignerResult<()> {
let resp = get_app_name(ledger)
.await
.map_err(|err| LedgerError::DeviceError(err.to_string()))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good way to check for our app, because the INS values are app-specific.

There is a standard way to obtain the app name via CLA=0xB0 and INS=1. E.g. here it's handled by the SDK on the device - https://github.com/LedgerHQ/ledger-device-rust-sdk/blob/4262899a325b9b2fe10f2524d8e4b2f9fec38b83/ledger_device_sdk/src/io_legacy.rs#L330-L331
(The INS is processed inside the handle_bolos_apdu function).

Also, ledger-proto contains something called AppInfoReq which mentions CLA 0xB0 and INS 1, so I guess you don't have to construct the APDU by hand and parse the request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason it always returns "app" for app name and the OS version instead of the opened app. So, I kept our own instructions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason it always returns "app" for app name and the OS version instead of the opened app. So, I kept our own instructions.

Well, I tried sending [b0, 1, 0, 0] to my NanoSPlus and got "Mintlayer"/"0.1.0" for our app and "Ethereum"/"1.18.0" for Ethereum.
What device are you using? Can you double check?

In any case, this has to be investigated further. If b0/01 doesn't work indeed, we must at least document that fact, mentioning the particular situations.

@OBorce OBorce force-pushed the feature/ledger_signer branch from d6fd484 to 2d5300b Compare October 26, 2025 23:34
@OBorce OBorce changed the base branch from refactor/wallet-async-signing to master October 26, 2025 23:39
@OBorce OBorce force-pushed the feature/ledger_signer branch from 2d5300b to fbbfd75 Compare October 26, 2025 23:49
@OBorce OBorce force-pushed the feature/ledger_signer branch from fbbfd75 to 1ae1ecc Compare October 28, 2025 09:03
Comment on lines +183 to +185
Err(ledger_lib::Error::Timeout) => {
continue;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's a good idea to ignore timeouts indefinitely. Perhaps we should only allow a few timeouts and then fail?
But why do we even need to handle a timeout differently that other connection errors that a re handled below?

}
}

/// Calls initialize on the device with the current session_id.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is neither initialize nor session_id in the Ledger case.

let mut client = self.client.lock().await;
operation(&mut client)
.await
.map_err(|e| LedgerError::DeviceError(e.to_string()).into())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

operation returns SignerError, not an error from the device directly.

Plz remove these explicit calls of to_string and implement From<ledger_lib::Error> for LedgerError instead


ensure!(
response_status == OK_RESPONSE,
LedgerError::ErrorResponse(response_status)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's parse the status code (e.g. using ledger-proto's StatusCode, which implements TryFromPrimitive) and provide a more informative error instead of this ErrorResponse(u16). E.g. currently I'm getting "Ledger error: Received an error response from the Ledger device: 21781" when trying to connect to a locked device, this is not very informative.

.await
.map_err(|err| LedgerError::DeviceError(err.to_string()))?;

let device = devices.pop().ok_or(LedgerError::NoDeviceFound)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On my Linux machine I get the following here:

devices: [LedgerInfo { model: NanoSPlus, conn: Usb(UsbInfo { vid: 11415, pid: 20480, path: Some("3-2:1.0") }) }, LedgerInfo { model: NanoSPlus, conn: Usb(UsbInfo { vid: 11415, pid: 20480, path: Some("3-2:1.1") }) }]

They are 2 separate HID interfaces to the same actual device, one of them is the APDU interface and another - the FIDO/U2F one (according to ChatGPT).
In my case your code selects the non-APDU interface and I get empty responses from it.

So,

  1. What are you getting on Windows here? Also, what device(s) are you using?
  2. Relying on a particular interface having a particular index in the returned vector won't work in general. According to ChatGPT, we should prefer the one with interface_number == 0, but rust-ledger doesn't expose interface_number. I.e. this needs further investigation after which we should either make a PR to rust-ledger with the corresponding improvement or fork it.

Comment on lines 197 to 146
pub async fn get_app_name<L: Exchange>(ledger: &mut L) -> Result<Vec<u8>, ledger_lib::Error> {
let msg_buf = [CLA, Ins::APP_NAME, 0, P2::DONE];
ledger.exchange(&msg_buf, Duration::from_millis(100)).await
}

#[allow(dead_code)]
pub async fn check_current_app<L: Exchange>(ledger: &mut L) -> SignerResult<()> {
let resp = get_app_name(ledger)
.await
.map_err(|err| LedgerError::DeviceError(err.to_string()))?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason it always returns "app" for app name and the OS version instead of the opened app. So, I kept our own instructions.

Well, I tried sending [b0, 1, 0, 0] to my NanoSPlus and got "Mintlayer"/"0.1.0" for our app and "Ethereum"/"1.18.0" for Ethereum.
What device are you using? Can you double check?

In any case, this has to be investigated further. If b0/01 doesn't work indeed, we must at least document that fact, mentioning the particular situations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants