fix(mwc): serialize openWallet FFI calls with mutex
What changed, and why it matters
This change adds a lock (mutex) around wallet-opening operations for the Mimblewimblecoin (MWC) wallet. Without the lock, multiple parts of the app could try to open the same wallet at the same time through the native library, which could cause crashes, corrupted wallet state, or the wallet being opened twice. The fix makes these calls happen one at a time.
Review whether other libMwc FFI calls (closeWallet, transaction creation, slate operations) also need serialization, and confirm the mutex covers the entire open-and-store critical section consistently. Consider adding tests that exercise concurrent wallet open attempts.
Security signals we found
Race condition mitigation in wallet open path
Concurrent FFI calls to native wallet library serialized
Prevents duplicate wallet open / state corruption
No explicit security claim in commit message
Evidence from the diff
The commit introduces a _walletOpenMutex and wraps all libMwc.openWallet() calls plus the subsequent secure-storage write of the wallet handle in Mutex.protect(). Previously, _ensureWalletOpen() read secure storage and called openWallet without serialization. Other call sites in wallet creation and restoration also called openWallet directly. The change serializes these critical sections to prevent concurrent FFI invocations against the underlying libmwc/mimblewimblecoin native wallet library.
Changed components
lib/wallets/wallet/impl/mimblewimblecoin_wallet.dartcrypto_plugins/flutter_libmwc (referenced submodule/directory only, no diff shown)Inspect captured patch +55 / −42
diff --git a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
index 3850cb7..dad7b36 100644
--- a/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
+++ b/lib/wallets/wallet/impl/mimblewimblecoin_wallet.dart
@@ -41,6 +41,7 @@ class MimblewimblecoinWallet extends Bip39Wallet {
: super(Mimblewimblecoin(network));
final syncMutex = Mutex();
+ final _walletOpenMutex = Mutex();
NodeModel? _mimblewimblecoinNode;
Timer? timer;
@@ -95,24 +96,29 @@ class MimblewimblecoinWallet extends Bip39Wallet {
}
Future<String> _ensureWalletOpen() async {
- final existing = await secureStorageInterface.read(
- key: '${walletId}_wallet',
- );
- if (existing != null && existing.isNotEmpty) return existing;
+ return await _walletOpenMutex.protect(() async {
+ final existing = await secureStorageInterface.read(
+ key: '${walletId}_wallet',
+ );
+ if (existing != null && existing.isNotEmpty) return existing;
- final config = await _getRealConfig();
- final password = await secureStorageInterface.read(
- key: '${walletId}_password',
- );
- if (password == null) {
- throw Exception('Wallet password not found');
- }
- final opened = await libMwc.openWallet(config: config, password: password);
- await secureStorageInterface.write(
- key: '${walletId}_wallet',
- value: opened,
- );
- return opened;
+ final config = await _getRealConfig();
+ final password = await secureStorageInterface.read(
+ key: '${walletId}_password',
+ );
+ if (password == null) {
+ throw Exception('Wallet password not found');
+ }
+ final opened = await libMwc.openWallet(
+ config: config,
+ password: password,
+ );
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: opened,
+ );
+ return opened;
+ });
}
/// Returns an empty String on success, error message on failure.
@@ -894,14 +900,17 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
//Open wallet
- encodedWallet = await libMwc.openWallet(
- config: stringConfig,
- password: password,
- );
- await secureStorageInterface.write(
- key: '${walletId}_wallet',
- value: encodedWallet,
- );
+ encodedWallet = await _walletOpenMutex.protect(() async {
+ final opened = await libMwc.openWallet(
+ config: stringConfig,
+ password: password,
+ );
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: opened,
+ );
+ return opened;
+ });
//Store MwcMqs address info
await _generateAndStoreReceivingAddressForIndex(0);
@@ -935,14 +944,16 @@ class MimblewimblecoinWallet extends Bip39Wallet {
key: '${walletId}_password',
);
- final walletOpen = await libMwc.openWallet(
- config: config,
- password: password!,
- );
- await secureStorageInterface.write(
- key: '${walletId}_wallet',
- value: walletOpen,
- );
+ await _walletOpenMutex.protect(() async {
+ final walletOpen = await libMwc.openWallet(
+ config: config,
+ password: password!,
+ );
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: walletOpen,
+ );
+ });
await updateNode();
} catch (e, s) {
@@ -1144,14 +1155,16 @@ class MimblewimblecoinWallet extends Bip39Wallet {
);
//Open Wallet
- final walletOpen = await libMwc.openWallet(
- config: stringConfig,
- password: password,
- );
- await secureStorageInterface.write(
- key: '${walletId}_wallet',
- value: walletOpen,
- );
+ await _walletOpenMutex.protect(() async {
+ final walletOpen = await libMwc.openWallet(
+ config: stringConfig,
+ password: password,
+ );
+ await secureStorageInterface.write(
+ key: '${walletId}_wallet',
+ value: walletOpen,
+ );
+ });
await _generateAndStoreReceivingAddressForIndex(
mimblewimblecoinData.receivingIndex,
Why this scored 40/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.