fix: prevent RBF for Silent Payment addresses in Bitcoin transactions (#2760)
What changed, and why it matters
This commit stops users from using Replace-By-Fee (RBF) on Bitcoin transactions that were sent to Silent Payment addresses. RBF lets someone bump a low-fee Bitcoin transaction to speed it up, but doing it with Silent Payments could break privacy or cause the recipient's address to be reused or revealed in a harmful way. The fix checks the saved recipient address and disables the RBF option when it matches a Silent Payment address pattern.
Treat as a low-to-moderate correctness/privacy fix. Verify that AddressValidator.silentPaymentAddressPatternMainnet is accurate and that the description lookup reliably returns the recipient address for all outgoing Bitcoin transactions. Consider whether testnet/regtest Silent Payment addresses need similar handling.
Security signals we found
UI-level guard disabling RBF for Silent Payment recipients
Pattern-based detection of silent payment address format
Prevents potential address reuse / privacy degradation path
No cryptographic or transaction-layer change
Evidence from the diff
In TransactionDetailsViewModelBase._checkForRBF(), the code now looks up the transaction description by txHash or a composite key, reads description?.recipientAddress, and tests it against AddressValidator.silentPaymentAddressPatternMainnet. If it matches, canReplaceByFee is set to false and the method returns before calling bitcoin!.canReplaceByFee(). This prevents RBF UI/actions for outgoing Bitcoin transactions to BIP-352 Silent Payment addresses.
Changed components
lib/view_model/transaction_details_view_model.dartBitcoin wallet transaction details / RBF flowInspect captured patch +11 / −0
diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart
index 810d08c1..20736952 100644
--- a/lib/view_model/transaction_details_view_model.dart
+++ b/lib/view_model/transaction_details_view_model.dart
@@ -1,3 +1,4 @@
+import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/currency_for_wallet_type.dart';
@@ -947,6 +948,16 @@ abstract class TransactionDetailsViewModelBase with Store {
Future<void> _checkForRBF(TransactionInfo tx) async {
if (wallet.type == WalletType.bitcoin &&
transactionInfo.direction == TransactionDirection.outgoing) {
+ final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
+ final description = transactionDescriptionBox.values
+ .firstWhereOrNull((val) => val.id == descriptionKey || val.id == transactionInfo.txHash);
+
+ if (RegExp(AddressValidator.silentPaymentAddressPatternMainnet)
+ .hasMatch(description?.recipientAddress ?? "")) {
+ canReplaceByFee = false;
+ return;
+ }
+
rawTransaction = await bitcoin!.canReplaceByFee(wallet, tx);
if (rawTransaction != null) {
canReplaceByFee = true;
Why this scored 43/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.