Add thread-safety and BLE support for Ledger and Trezor devices (#3638)
What changed, and why it matters
This update adds Bluetooth Low Energy (BLE) support for Ledger and Trezor hardware wallets in Cake Wallet and adds a mutex lock around Trezor operations to prevent multiple actions from running at the same time. The changes are mostly feature additions and hardening, not a clear fix for an active security bug. There is no vendor statement saying this commit resolves a security vulnerability.
Treat as a routine feature/hardening commit. Review the changelog and diff of the bumped trezor-flutter ref for security fixes, verify BLE pairing and connection-state handling does not leave stale sessions or accept unauthorized devices, and test that the mutex correctly prevents deadlocks during long-running Trezor operations.
Security signals we found
Mutex added to serialize Trezor Monero operations (race-condition hardening)
BLE transport enabled for Ledger and Trezor (expanded hardware-wallet attack surface)
Connection-state listener and cleanup added for Trezor BLE (resource/connection management)
Dependency ref bump for trezor-flutter without visible changelog or security note
Evidence from the diff
The commit bumps the trezor-flutter dependency to a newer ref, wraps MoneroTrezorService calls (getWatchCredentials, syncKeyImages, signTransaction) in a static Mutex to serialize concurrent Trezor operations, adds BLE device enumeration and connection-state listeners for both Ledger and Trezor, and cleans up subscriptions in a new close() method. The mutex addresses a race-condition class issue; the BLE additions expand the hardware-wallet attack surface to include Bluetooth transports. The actual security properties of the new trezor-flutter ref are not visible in this diff.
Changed components
cw_monero/lib/trezor.dartlib/view_model/hardware_wallet/trezor_connect_view_model.dartlib/view_model/hardware_wallet/ledger_view_model.dartlib/src/screens/connect_device/connect_device_page.darttrezor-flutter dependency (ref 591d4b8c821b9c10561f848b866324a9a8dcb489)Inspect captured patch +77 / −9
### cw_bitcoin/pubspec.yaml
@@ -58,7 +58,7 @@ dependencies:
trezor_flutter:
git:
url: https://github.com/cake-tech/trezor-flutter
- ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c
+ ref: 591d4b8c821b9c10561f848b866324a9a8dcb489
path: trezor-flutter
bitbox_flutter:
path: ../scripts/bitbox_flutter
### cw_monero/lib/trezor.dart
@@ -1,12 +1,17 @@
import "dart:convert";
import "package:cw_core/hardware/hardware_wallet_service.dart";
+import "package:mutex/mutex.dart";
import "package:trezor_flutter/trezor_flutter.dart";
class MoneroTrezorService extends HardwareWalletService {
MoneroTrezorService(this.client);
final TrezorClient client;
+
+ static final Mutex _mutex = Mutex();
+
+ static Future<T> runBlocking<T>(Future<T> Function() operation) => _mutex.protect(operation);
}
class MoneroTrezorWatchCredentials {
@@ -22,7 +27,7 @@ class Trezor {
final MoneroTrezorService service;
Future<MoneroTrezorWatchCredentials> getWatchCredentials() async {
- final credentials = await TrezorMonero(service.client).getWatchCredentials();
+ final credentials = await MoneroTrezorService.runBlocking(TrezorMonero(service.client).getWatchCredentials);
return MoneroTrezorWatchCredentials(credentials.$1, credentials.$2);
}
@@ -45,11 +50,15 @@ class Trezor {
),
);
}
- final keyImages = await TrezorMonero(service.client).syncKeyImages(txIds);
+ final keyImages = await MoneroTrezorService.runBlocking(
+ () => TrezorMonero(service.client).syncKeyImages(txIds),
+ );
return jsonEncode(keyImages.toMap());
}
- Future<String> signTransaction(String json) =>
- TrezorMonero(service.client).signTransaction(jsonDecode(json) as Map<String, dynamic>);
+ Future<String> signTransaction(String json) => MoneroTrezorService.runBlocking(
+ () =>
+ TrezorMonero(service.client).signTransaction(jsonDecode(json) as Map<String, dynamic>),
+ );
}
### cw_monero/pubspec.yaml
@@ -33,7 +33,7 @@ dependencies:
trezor_flutter:
git:
url: https://github.com/cake-tech/trezor-flutter.git
- ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c
+ ref: 591d4b8c821b9c10561f848b866324a9a8dcb489
path: trezor-flutter
dev_dependencies:
### lib/src/screens/connect_device/connect_device_page.dart
@@ -108,6 +108,8 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
+ _loadConnectedDevices();
+
_bleStateTimer = Timer.periodic(
const Duration(seconds: 1),
(_) => widget.hardwareWalletVM.updateBleState(),
@@ -131,6 +133,23 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
});
}
+ Future<void> _loadConnectedDevices() async {
+ try {
+ final connected = await widget.hardwareWalletVM.getConnectedBleDevices();
+
+ printV(connected);
+ if (!mounted || connected.isEmpty) {
+ return;
+ }
+ setState(() {
+ bleDevices.addAll(connected);
+ longWait = false;
+ });
+ } catch (e) {
+ printV(e);
+ }
+ }
+
@override
void dispose() {
_bleRefreshTimer?.cancel();
### lib/view_model/hardware_wallet/hardware_wallet_view_model.dart
@@ -18,6 +18,8 @@ abstract class HardwareWalletViewModel {
Future<List<HardwareWalletDevice>> getAllUsbDevices();
+ Future<List<HardwareWalletDevice>> getConnectedBleDevices() async => [];
+
Future<void> stopScanning();
Future<bool> connectDevice(HardwareWalletDevice device, WalletType type);
### lib/view_model/hardware_wallet/ledger_view_model.dart
@@ -120,6 +120,14 @@ abstract class LedgerViewModelBase extends HardwareWalletViewModel with Store {
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
ledgerPlusUSB.devices.then((devices) => devices.map(LedgerHardwareWalletDevice.new).toList());
+ @override
+ Future<List<HardwareWalletDevice>> getConnectedBleDevices() async {
+ if (!_bleIsInitialized) {
+ return const [];
+ }
+ return (await ledgerPlusBLE.devices).map(LedgerHardwareWalletDevice.new).toList();
+ }
+
@override
Future<void> stopScanning() async {
if (_bleIsInitialized) {
### lib/view_model/hardware_wallet/trezor_connect_view_model.dart
@@ -48,6 +48,7 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
}
+ StreamSubscription<sdk.BleConnectionState>? _connectionChangeSubscription;
final connect_sdk.TrezorConnect trezorConnect;
final SecureStorage _secureStorage;
@@ -125,6 +126,14 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
Future<List<HardwareWalletDevice>> getAllUsbDevices() =>
trezorUSB.devices.then((devices) => devices.map(TrezorHardwareWalletDevice.new).toList());
+ @override
+ Future<List<HardwareWalletDevice>> getConnectedBleDevices() async {
+ if (!_bleIsInitialized) {
+ return const [];
+ }
+ return (await trezorBLE.devices).map(TrezorHardwareWalletDevice.new).toList();
+ }
+
@override
Future<void> stopScanning() async {
if (_bleIsInitialized) {
@@ -168,6 +177,9 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
device.connectionType == HardwareWalletConnectionType.ble ? trezorBLE : trezorUSB;
final connection = await trezorInterface.connect(device.device);
+ _connectionChangeSubscription ??=
+ trezorInterface.deviceStateChanges.listen(_connectionChangeListener);
+
if (!isRetry) {
unawaited(
showModalBottomSheet(
@@ -342,6 +354,13 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
}
+ void _connectionChangeListener(sdk.BleConnectionState event) {
+ printV("Ledger Device State Changed: $event");
+ if (event == sdk.BleConnectionState.disconnected && !isConnecting) {
+ _client = null;
+ }
+ }
+
Future<bool> syncKeyImages(WalletBase wallet) async {
if (wallet.type == WalletType.monero) {
try {
@@ -352,6 +371,17 @@ abstract class TrezorConnectViewModelBase extends HardwareWalletViewModel with S
}
return true;
}
+
+ @override
+ Future<void> close() async {
+ try {
+ await _connectionChangeSubscription?.cancel();
+
+ _connectionChangeSubscription = null;
+ isConnecting = false;
+ await stopScanning();
+ } catch (_) {}
+ }
}
abstract class TrezorParingState {
### pubspec_base.yaml
@@ -131,7 +131,7 @@ dependencies:
trezor_flutter:
git:
url: https://github.com/cake-tech/trezor-flutter.git
- ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c
+ ref: 591d4b8c821b9c10561f848b866324a9a8dcb489
path: trezor-flutter
bitbox_flutter:
path: ./scripts/bitbox_flutter
### pubspec_overrides.yaml
@@ -1481,12 +1481,12 @@ dependency_overrides:
trezor_flutter:
git:
url: https://github.com/cake-tech/trezor-flutter.git
- ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c
+ ref: 591d4b8c821b9c10561f848b866324a9a8dcb489
path: ./trezor-flutter
trezor_usb_transport:
git:
url: https://github.com/cake-tech/trezor-flutter.git
- ref: 02771769fc800c06e29ac9186e37e9c2aeb0b54c
+ ref: 591d4b8c821b9c10561f848b866324a9a8dcb489
path: ./trezor_usb_transport
tuple:
git:Why this scored 35/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.