Skip to content

Commit 8fe1bc0

Browse files
authored
Merge pull request #37 from bitcoinerlab/rbf
Add RBF Parameter Support to PSBT Inputs, Update Dependencies, and Set RBF Default to True
2 parents 0511e64 + 9ae22d3 commit 8fe1bc0

File tree

5 files changed

+58
-29
lines changed

5 files changed

+58
-29
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,10 @@ To call `updatePsbtAsInput()`, use the following syntax:
8484
```javascript
8585
import { Psbt } from 'bitcoinjs-lib';
8686
const psbt = new Psbt();
87-
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
87+
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout, rbf });
8888
```
8989

90-
Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction.
90+
Here, `psbt` refers to an instance of the [bitcoinjs-lib Psbt class](https://github.com/bitcoinjs/bitcoinjs-lib). The parameter `txHex` denotes a hex string that serializes the previous transaction containing this output. Meanwhile, `vout` is an integer that marks the position of the output within that transaction. Finally, `rbf` is an optional parameter (defaulting to `true`) used to indicate whether the transaction uses Replace-By-Fee (RBF). When RBF is enabled, transactions can be replaced while they are in the mempool with others that have higher fees. Note that RBF is enabled for the entire transaction if at least one input signals it. Also, note that transactions using relative time locks inherently opt into RBF due to the `nSequence` range used.
9191

9292
The method returns the `inputFinalizer()` function. This finalizer function completes a PSBT input by adding the unlocking script (`scriptWitness` or `scriptSig`) that satisfies the previous output's spending conditions. Bear in mind that both `scriptSig` and `scriptWitness` incorporate signatures. As such, you should complete all necessary signing operations before calling `inputFinalizer()`. Detailed [explanations on the `inputFinalizer` method](#signers-and-finalizers-finalize-psbt-input) can be found in the Signers and Finalizers section.
9393

package-lock.json

Lines changed: 16 additions & 16 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@bitcoinerlab/descriptors",
33
"description": "This library parses and creates Bitcoin Miniscript Descriptors and generates Partially Signed Bitcoin Transactions (PSBTs). It provides PSBT finalizers and signers for single-signature, BIP32 and Hardware Wallets.",
44
"homepage": "https://github.com/bitcoinerlab/descriptors",
5-
"version": "2.1.0",
5+
"version": "2.2.0",
66
"author": "Jose-Luis Landabaso",
77
"license": "MIT",
88
"repository": {
@@ -67,8 +67,8 @@
6767
"yargs": "^17.7.2"
6868
},
6969
"dependencies": {
70-
"@bitcoinerlab/miniscript": "^1.2.1",
71-
"@bitcoinerlab/secp256k1": "^1.0.5",
70+
"@bitcoinerlab/miniscript": "^1.4.0",
71+
"@bitcoinerlab/secp256k1": "^1.1.1",
7272
"bip32": "^4.0.0",
7373
"bitcoinjs-lib": "^6.1.3",
7474
"ecpair": "^2.1.0",

src/descriptors.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,6 +1173,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
11731173
txId?: string;
11741174
value?: number;
11751175
vout: number;
1176+
rbf?: boolean;
11761177
}) {
11771178
this.updatePsbtAsInput(params);
11781179
return params.psbt.data.inputs.length - 1;
@@ -1195,6 +1196,14 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
11951196
*
11961197
* When unsure, always use `txHex`, and skip `txId` and `value` for safety.
11971198
*
1199+
* Use `rbf` to mark whether this tx can be replaced with another with
1200+
* higher fee while being in the mempool. Note that a tx will automatically
1201+
* be marked as replacable if a single input requests it.
1202+
* Note that any transaction using a relative timelock (nSequence < 0x80000000)
1203+
* also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
1204+
* inherently replaceable. So don't set `rbf` to false if this is tx uses
1205+
* relative time locks.
1206+
*
11981207
* @returns A finalizer function to be used after signing the `psbt`.
11991208
* This function ensures that this input is properly finalized.
12001209
* The finalizer has this signature:
@@ -1207,13 +1216,15 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
12071216
txHex,
12081217
txId,
12091218
value,
1210-
vout //vector output index
1219+
vout, //vector output index
1220+
rbf = true
12111221
}: {
12121222
psbt: Psbt;
12131223
txHex?: string;
12141224
txId?: string;
12151225
value?: number;
12161226
vout: number;
1227+
rbf?: boolean;
12171228
}) {
12181229
if (txHex === undefined) {
12191230
console.warn(`Warning: missing txHex may allow fee attacks`);
@@ -1237,7 +1248,8 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
12371248
scriptPubKey: this.getScriptPubKey(),
12381249
isSegwit,
12391250
witnessScript: this.getWitnessScript(),
1240-
redeemScript: this.getRedeemScript()
1251+
redeemScript: this.getRedeemScript(),
1252+
rbf
12411253
});
12421254
const finalizer = ({
12431255
psbt,
@@ -1283,16 +1295,23 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
12831295
scriptPubKey = out.script;
12841296
}
12851297
const locktime = this.getLockTime() || 0;
1286-
let sequence = this.getSequence();
1287-
if (sequence === undefined && locktime !== 0) sequence = 0xfffffffe;
1288-
if (sequence === undefined && locktime === 0) sequence = 0xffffffff;
1298+
const sequence = this.getSequence();
1299+
//We don't know whether the user opted for RBF or not. So check that
1300+
//at least one of the 2 sequences matches.
1301+
const sequenceNoRBF =
1302+
sequence !== undefined
1303+
? sequence
1304+
: locktime === 0
1305+
? 0xffffffff
1306+
: 0xfffffffe;
1307+
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
12891308
const eqBuffers = (buf1: Buffer | undefined, buf2: Buffer | undefined) =>
12901309
buf1 instanceof Buffer && buf2 instanceof Buffer
12911310
? Buffer.compare(buf1, buf2) === 0
12921311
: buf1 === buf2;
12931312
if (
12941313
Buffer.compare(scriptPubKey, this.getScriptPubKey()) !== 0 ||
1295-
sequence !== inputSequence ||
1314+
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
12961315
locktime !== psbt.locktime ||
12971316
!eqBuffers(this.getWitnessScript(), input.witnessScript) ||
12981317
!eqBuffers(this.getRedeemScript(), input.redeemScript)

src/psbt.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,8 @@ export function updatePsbt({
140140
scriptPubKey,
141141
isSegwit,
142142
witnessScript,
143-
redeemScript
143+
redeemScript,
144+
rbf
144145
}: {
145146
psbt: Psbt;
146147
vout: number;
@@ -154,8 +155,11 @@ export function updatePsbt({
154155
isSegwit: boolean;
155156
witnessScript: Buffer | undefined;
156157
redeemScript: Buffer | undefined;
158+
rbf: boolean;
157159
}): number {
158160
//Some data-sanity checks:
161+
if (sequence !== undefined && rbf && sequence > 0xfffffffd)
162+
throw new Error(`Error: incompatible sequence and rbf settings`);
159163
if (!isSegwit && txHex === undefined)
160164
throw new Error(`Error: txHex is mandatory for Non-Segwit inputs`);
161165
if (
@@ -209,13 +213,19 @@ export function updatePsbt({
209213
// this input's sequence < 0xffffffff
210214
if (sequence === undefined) {
211215
//NOTE: if sequence is undefined, bitcoinjs-lib uses 0xffffffff as default
212-
sequence = 0xfffffffe;
216+
sequence = rbf ? 0xfffffffd : 0xfffffffe;
213217
} else if (sequence > 0xfffffffe) {
214218
throw new Error(
215219
`Error: incompatible sequence: ${sequence} and locktime: ${locktime}`
216220
);
217221
}
222+
if (sequence === undefined && rbf) sequence = 0xfffffffd;
218223
psbt.setLocktime(locktime);
224+
} else {
225+
if (sequence === undefined) {
226+
if (rbf) sequence = 0xfffffffd;
227+
else sequence = 0xffffffff;
228+
}
219229
}
220230

221231
const input: PsbtInputExtended = {

0 commit comments

Comments
 (0)