Skip to content

Ledger Nano S+ USB Connection Failure #298

@niteria

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

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions