Skip to content

Commit

Permalink
Merge pull request #37 from bitcoinerlab/rbf
Browse files Browse the repository at this point in the history
Add RBF Parameter Support to PSBT Inputs, Update Dependencies, and Set RBF Default to True
  • Loading branch information
landabaso authored Jul 4, 2024
2 parents 0511e64 + 9ae22d3 commit 8fe1bc0
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 29 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@ To call `updatePsbtAsInput()`, use the following syntax:
```javascript
import { Psbt } from 'bitcoinjs-lib';
const psbt = new Psbt();
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout, rbf });
```

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.
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.

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.

Expand Down
32 changes: 16 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "@bitcoinerlab/descriptors",
"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.",
"homepage": "https://github.com/bitcoinerlab/descriptors",
"version": "2.1.0",
"version": "2.2.0",
"author": "Jose-Luis Landabaso",
"license": "MIT",
"repository": {
Expand Down Expand Up @@ -67,8 +67,8 @@
"yargs": "^17.7.2"
},
"dependencies": {
"@bitcoinerlab/miniscript": "^1.2.1",
"@bitcoinerlab/secp256k1": "^1.0.5",
"@bitcoinerlab/miniscript": "^1.4.0",
"@bitcoinerlab/secp256k1": "^1.1.1",
"bip32": "^4.0.0",
"bitcoinjs-lib": "^6.1.3",
"ecpair": "^2.1.0",
Expand Down
31 changes: 25 additions & 6 deletions src/descriptors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1173,6 +1173,7 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
txId?: string;
value?: number;
vout: number;
rbf?: boolean;
}) {
this.updatePsbtAsInput(params);
return params.psbt.data.inputs.length - 1;
Expand All @@ -1195,6 +1196,14 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
*
* When unsure, always use `txHex`, and skip `txId` and `value` for safety.
*
* Use `rbf` to mark whether this tx can be replaced with another with
* higher fee while being in the mempool. Note that a tx will automatically
* be marked as replacable if a single input requests it.
* Note that any transaction using a relative timelock (nSequence < 0x80000000)
* also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
* inherently replaceable. So don't set `rbf` to false if this is tx uses
* relative time locks.
*
* @returns A finalizer function to be used after signing the `psbt`.
* This function ensures that this input is properly finalized.
* The finalizer has this signature:
Expand All @@ -1207,13 +1216,15 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
txHex,
txId,
value,
vout //vector output index
vout, //vector output index
rbf = true
}: {
psbt: Psbt;
txHex?: string;
txId?: string;
value?: number;
vout: number;
rbf?: boolean;
}) {
if (txHex === undefined) {
console.warn(`Warning: missing txHex may allow fee attacks`);
Expand All @@ -1237,7 +1248,8 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
scriptPubKey: this.getScriptPubKey(),
isSegwit,
witnessScript: this.getWitnessScript(),
redeemScript: this.getRedeemScript()
redeemScript: this.getRedeemScript(),
rbf
});
const finalizer = ({
psbt,
Expand Down Expand Up @@ -1283,16 +1295,23 @@ export function DescriptorsFactory(ecc: TinySecp256k1Interface) {
scriptPubKey = out.script;
}
const locktime = this.getLockTime() || 0;
let sequence = this.getSequence();
if (sequence === undefined && locktime !== 0) sequence = 0xfffffffe;
if (sequence === undefined && locktime === 0) sequence = 0xffffffff;
const sequence = this.getSequence();
//We don't know whether the user opted for RBF or not. So check that
//at least one of the 2 sequences matches.
const sequenceNoRBF =
sequence !== undefined
? sequence
: locktime === 0
? 0xffffffff
: 0xfffffffe;
const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
const eqBuffers = (buf1: Buffer | undefined, buf2: Buffer | undefined) =>
buf1 instanceof Buffer && buf2 instanceof Buffer
? Buffer.compare(buf1, buf2) === 0
: buf1 === buf2;
if (
Buffer.compare(scriptPubKey, this.getScriptPubKey()) !== 0 ||
sequence !== inputSequence ||
(sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
locktime !== psbt.locktime ||
!eqBuffers(this.getWitnessScript(), input.witnessScript) ||
!eqBuffers(this.getRedeemScript(), input.redeemScript)
Expand Down
14 changes: 12 additions & 2 deletions src/psbt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ export function updatePsbt({
scriptPubKey,
isSegwit,
witnessScript,
redeemScript
redeemScript,
rbf
}: {
psbt: Psbt;
vout: number;
Expand All @@ -154,8 +155,11 @@ export function updatePsbt({
isSegwit: boolean;
witnessScript: Buffer | undefined;
redeemScript: Buffer | undefined;
rbf: boolean;
}): number {
//Some data-sanity checks:
if (sequence !== undefined && rbf && sequence > 0xfffffffd)
throw new Error(`Error: incompatible sequence and rbf settings`);
if (!isSegwit && txHex === undefined)
throw new Error(`Error: txHex is mandatory for Non-Segwit inputs`);
if (
Expand Down Expand Up @@ -209,13 +213,19 @@ export function updatePsbt({
// this input's sequence < 0xffffffff
if (sequence === undefined) {
//NOTE: if sequence is undefined, bitcoinjs-lib uses 0xffffffff as default
sequence = 0xfffffffe;
sequence = rbf ? 0xfffffffd : 0xfffffffe;
} else if (sequence > 0xfffffffe) {
throw new Error(
`Error: incompatible sequence: ${sequence} and locktime: ${locktime}`
);
}
if (sequence === undefined && rbf) sequence = 0xfffffffd;
psbt.setLocktime(locktime);
} else {
if (sequence === undefined) {
if (rbf) sequence = 0xfffffffd;
else sequence = 0xffffffff;
}
}

const input: PsbtInputExtended = {
Expand Down

0 comments on commit 8fe1bc0

Please sign in to comment.