Blockstream / Blockstream/green_android

Ledger Nano S+ USB Connection Failure

Open
#298 1 comment 0 reactions 1 assignee Claimed by @ardier16 View on GitHub
feature request
Dominant language
Kotlin
Stars
268
Forks
106
PR merge metrics
No merged PRs in 30d

Description

## 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:
```java
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:

```java
// 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:

```java
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

- [Ledger USB Protocol Documentation](https://developers.ledger.com/docs/transport/)
- [Android USB Host Documentation](https://developer.android.com/guide/topics/connectivity/usb/host)
- USB Vendor ID: 0x2C97 (Ledger)
- USB Product ID: 0x5000 (Nano S+)

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.