Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RBF Parameter Support to PSBT Inputs, Update Dependencies, and Set RBF Default to True #37

Merged
merged 2 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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