Skip to content

Ledger Nano S+ USB Connection Failure #298

Description

@niteria

Disclaimer

This was debugged with AI and most of the report is generated by AI, but it has been reviewed by a software engineer (me). I can submit a pull request, but Fix 1 has potential of breaking other devices if they have Vendor Specific interface, but expect to be communicated with on interface 0.

Summary

Blockstream Green fails to connect to Ledger Nano S+ devices via USB. The app detects the device but hangs indefinitely during connection. This affects all Nano S+ devices (Product ID 0x5000) running firmware 2.01 or similar versions.

Affected Versions

  • Blockstream Green: v5.1.4-dev (and likely earlier versions)
  • Device: Ledger Nano S+
  • Firmware: 2.01 (and potentially other versions)
  • OS: Android 16 (API 36), but likely affects all Android versions

Steps to Reproduce

  1. Install Bitcoin Legacy app on Ledger Nano S+ (as recommended by Blockstream documentation)
  2. Open the Bitcoin Legacy app on the Ledger
  3. Connect Ledger Nano S+ to Android device via USB
  4. Grant USB permission when prompted
  5. In Blockstream Green, tap "Connect Hardware Wallet"
  6. Select "Ledger" from device list
  7. Select the detected Nano S+ device
  8. Result: App shows "Logging in..." spinner indefinitely, never completes connection

Expected Behavior

App should successfully connect to the Ledger Nano S+, authenticate the device, and display the wallet interface with balance and transaction history.

Actual Behavior

  • Device is detected via USB (VID: 0x2C97, PID: 0x5000)
  • USB permission is granted successfully
  • App navigates to device connection screen
  • Communication stalls after sending initial APDU command
  • No error message displayed to user
  • App remains in "connecting" state indefinitely

Root Cause Analysis

Two issues were identified in the USB communication layer:

Issue 1: Wrong USB Interface Selection

Location: hardware/src/main/java/com/btchip/comm/android/BTChipTransportAndroid.java

Problem: The code was hardcoded to use Interface 0:

UsbInterface dongleInterface = device.getInterface(0);

Impact: Interface 0 is HID (Human Interface Device) class, but Ledger Nano S+ uses Interface 1 (Vendor Specific, Class 255) for application communication.

USB Interface Layout on Nano S+:

  • Interface 0: HID Class (0x03) - Keyboard emulation, wrong interface
  • Interface 1: Vendor Specific (0xFF) - Correct interface for Ledger apps
  • Interface 2: HID Boot Class (0x03) - Boot keyboard, wrong interface

Issue 2: Missing Product ID Support

Location: hardware/src/main/java/com/btchip/comm/android/BTChipTransportAndroid.java

Problem: The Nano S+ product ID (0x5000) was not recognized in the supported devices list.

Current supported product IDs:

  • Nano S Legacy: 0x0001
  • Nano X Legacy: 0x0004
  • Nano S: 0x10xx (upper byte 0x10)
  • Nano X: 0x40xx (upper byte 0x40)
  • Missing: Nano S+: 0x50xx (upper byte 0x50)

Impact: Device was not recognized as a Ledger device (ledger=false), causing improper initialization of the transport layer.

Technical Details

APDU Communication

The initial APDU command sent was:

CLA: B0 (Ledger vendor-specific)
INS: 01 (Get Version)
P1:  00
P2:  00
Data: None

When using the wrong interface (Interface 0), the Ledger device never responds to this command, causing an indefinite hang.

Log Evidence

Before Fix:

Selected interface 0 (HID)
Creating transport for ledger=false productId=20480
BTChip: => b0010000
[No response - indefinite hang]

After Fix:

Selected Vendor Specific interface 1
Creating transport for ledger=true productId=20480
BTChip: => b0010000
[Device responds correctly]

Proposed Fix

Fix 1: Dynamic USB Interface Selection

Search for the Vendor Specific interface instead of hardcoding interface 0:

// Search for Vendor Specific interface (Class 255)
UsbInterface dongleInterface = null;
for (int i = 0; i < device.getInterfaceCount(); i++) {
    UsbInterface iface = device.getInterface(i);
    if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC) {
        dongleInterface = iface;
        break;
    }
}

// Fallback to interface 0 if no Vendor Specific interface found
if (dongleInterface == null) {
    dongleInterface = device.getInterface(0);
}

Fix 2: Add Nano S+ Product ID Support

Add constant and update all device detection logic:

private static final int PID_NANOS_PLUS = 0x50;  // Upper byte for 0x50xx range

// Update ledger detection:
ledger = ((device.getProductId() == PID_HID_LEDGER) || 
          (device.getProductId() == PID_HID_LEDGER_PROTON) ||
          (device.getProductId() == PID_NANOS_LEGACY) || 
          (device.getProductId() >> 8 == PID_NANOS) ||
          (device.getProductId() == PID_NANOX_LEGACY) || 
          (device.getProductId() >> 8 == PID_NANOX) ||
          (device.getProductId() >> 8 == PID_NANOS_PLUS));  // Add this line

// Update isLedgerWithScreen():
public static boolean isLedgerWithScreen(final UsbDevice d) {
    final int pId = d.getProductId();
    final boolean screenDevice = pId == PID_NANOS_LEGACY || 
                                  pId == PID_NANOX_LEGACY || 
                                  pId >> 8 == PID_NANOS || 
                                  pId >> 8 == PID_NANOX || 
                                  pId >> 8 == PID_NANOS_PLUS;  // Add this
    return screenDevice && d.getVendorId() == VID2;
}

// Update getDevice() enumeration:
if ((device.getVendorId() == VID || device.getVendorId() == VID2) &&
    ((device.getProductId() == PID_WINUSB) || 
     (device.getProductId() == PID_HID) ||
     (device.getProductId() == PID_NANOS_LEGACY) || 
     (device.getProductId() >> 8 == PID_NANOS) ||
     (device.getProductId() == PID_NANOX_LEGACY) || 
     (device.getProductId() >> 8 == PID_NANOX) ||
     (device.getProductId() >> 8 == PID_NANOS_PLUS) ||  // Add this
     (device.getProductId() == PID_HID_LEDGER_PROTON) || 
     (device.getProductId() == PID_HID_LEDGER))) {

Testing

Test Environment:

  • Device: Ledger Nano S+ (Firmware 2.01)
  • App: Bitcoin Legacy
  • Android Version: API 36
  • Blockstream Green: Development build with fixes applied

Results:

  • ✅ Device detected via USB
  • ✅ USB permission granted
  • ✅ Vendor Specific interface (Interface 1) selected
  • ✅ Device recognized as Ledger (ledger=true)
  • ✅ APDU communication successful
  • ✅ Wallet balance displayed
  • ✅ Transaction history loaded
  • ✅ Transaction signing
    • ❌ Taproot (starting with bc1p) addresses unsupported, I believe it's Bitcoin Legacy Ledger app limitation

Related Issues

  • This likely affects all Ledger Nano S+ devices regardless of firmware version
  • May also affect future Ledger devices using Vendor Specific USB interface
  • Does NOT affect Ledger Nano S, Nano X, or legacy devices (different product IDs)

References

Metadata

Metadata

Assignees

Labels

feature requestRequest to implement a new feature

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions