From 1e34cf310af8bf0ea7e58ddef7f48fca1f1596d1 Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Tue, 2 Jun 2026 19:29:10 -0300 Subject: [PATCH 1/2] Update RBF nsequence to 0xFFFFFFFD 0x01 signals opt-in RBF but also sets a BIP-68 relative timelock of 1 block, which is unintended. It is also an abnormal value that fingerprints the wallet on-chain, which is harmful in collaborative settings like payjoin. Most wallets use 0xFFFFFFFD to opt into RBF without imposing a timelock. --- lib/src/bitcoin/script/op_code/constant.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/bitcoin/script/op_code/constant.dart b/lib/src/bitcoin/script/op_code/constant.dart index 59ac86e..0725415 100644 --- a/lib/src/bitcoin/script/op_code/constant.dart +++ b/lib/src/bitcoin/script/op_code/constant.dart @@ -150,7 +150,7 @@ class BitcoinOpCodeConst { static const List emptyTxSequence = [0x00, 0x00, 0x00, 0x00]; static const List absoluteTimelockSequence = [0xfe, 0xff, 0xff, 0xff]; - static const List replaceByFeeSequence = [0x01, 0x00, 0x00, 0x00]; + static const List replaceByFeeSequence = [0xfd, 0xff, 0xff, 0xff]; /// Script version and Bitcoin-related identifiers static const int leafVersionTapscript = 0xc0; From 778d547393e52ccb8db6b444866e2eac32cdfe2f Mon Sep 17 00:00:00 2001 From: bc1cindy Date: Tue, 2 Jun 2026 19:29:11 -0300 Subject: [PATCH 2/2] Apply RBF nsequence to all inputs, not just inputs[0] Setting the sequence only on inputs[0] leaves the remaining inputs at 0xFFFFFFFF, producing a mixed pattern that itself fingerprints the wallet. Applying it to every input keeps nsequence uniform within the transaction. The same bug existed in both builders. --- .../transaction_builder/forked_transaction_builder.dart | 8 +++++--- lib/src/transaction_builder/transaction_builder.dart | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/src/transaction_builder/forked_transaction_builder.dart b/lib/src/transaction_builder/forked_transaction_builder.dart index a3c2973..e999905 100644 --- a/lib/src/transaction_builder/forked_transaction_builder.dart +++ b/lib/src/transaction_builder/forked_transaction_builder.dart @@ -259,9 +259,11 @@ that demonstrate the right to spend the bitcoins associated with the correspondi ); } final inputs = sortedUtxos.map((e) => e.utxo.toInput()).toList(); - if (enableRBF && inputs.isNotEmpty) { - inputs[0] = - inputs[0].copyWith(sequence: BitcoinOpCodeConst.replaceByFeeSequence); + if (enableRBF) { + for (int i = 0; i < inputs.length; i++) { + inputs[i] = + inputs[i].copyWith(sequence: BitcoinOpCodeConst.replaceByFeeSequence); + } } return Tuple(List.unmodifiable(inputs), List.unmodifiable(sortedUtxos)); diff --git a/lib/src/transaction_builder/transaction_builder.dart b/lib/src/transaction_builder/transaction_builder.dart index 36ffeac..0e29921 100644 --- a/lib/src/transaction_builder/transaction_builder.dart +++ b/lib/src/transaction_builder/transaction_builder.dart @@ -400,9 +400,11 @@ that demonstrate the right to spend the bitcoins associated with the correspondi ); } final inputs = sortedUtxos.map((e) => e.utxo.toInput()).toList(); - if (enableRBF && inputs.isNotEmpty) { - inputs[0] = - inputs[0].copyWith(sequence: BitcoinOpCodeConst.replaceByFeeSequence); + if (enableRBF) { + for (int i = 0; i < inputs.length; i++) { + inputs[i] = + inputs[i].copyWith(sequence: BitcoinOpCodeConst.replaceByFeeSequence); + } } return Tuple(List.unmodifiable(inputs), List.unmodifiable(sortedUtxos));