@bitcoinerlab/descriptors 2.0.3 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -84,10 +84,10 @@ To call `updatePsbtAsInput()`, use the following syntax:
84
84
  ```javascript
85
85
  import { Psbt } from 'bitcoinjs-lib';
86
86
  const psbt = new Psbt();
87
- const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout });
87
+ const inputFinalizer = output.updatePsbtAsInput({ psbt, txHex, vout, rbf });
88
88
  ```
89
89
 
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.
91
91
 
92
92
  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.
93
93
 
@@ -115,8 +115,40 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
115
115
  getNetwork(): Network;
116
116
  /**
117
117
  * Whether this `Output` is Segwit.
118
+ *
119
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
120
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
121
+ * (Script Hash-Witness Public Key Hash).
122
+ * For inputs using arbitrary scripts (not standard addresses),
123
+ * use a descriptor in the format `sh(MINISCRIPT)`.
124
+ *
118
125
  */
119
126
  isSegwit(): boolean | undefined;
127
+ /**
128
+ * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
129
+ * for this Output.
130
+ */
131
+ guessOutput(): {
132
+ isPKH: boolean;
133
+ isWPKH: boolean;
134
+ isSH: boolean;
135
+ };
136
+ /**
137
+ * Computes the Weight Unit contributions of this Output as if it were the
138
+ * input in a tx.
139
+ *
140
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
141
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
142
+ * (Script Hash-Witness Public Key Hash).
143
+ * For inputs using arbitrary scripts (not standard addresses),
144
+ * use a descriptor in the format `sh(MINISCRIPT)`.
145
+ */
146
+ inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
147
+ /**
148
+ * Computes the Weight Unit contributions of this Output as if it were the
149
+ * output in a tx.
150
+ */
151
+ outputWeight(): number;
120
152
  /** @deprecated - Use updatePsbtAsInput instead
121
153
  * @hidden
122
154
  */
@@ -126,6 +158,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
126
158
  txId?: string;
127
159
  value?: number;
128
160
  vout: number;
161
+ rbf?: boolean;
129
162
  }): number;
130
163
  /**
131
164
  * Sets this output as an input of the provided `psbt` and updates the
@@ -144,6 +177,14 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
144
177
  *
145
178
  * When unsure, always use `txHex`, and skip `txId` and `value` for safety.
146
179
  *
180
+ * Use `rbf` to mark whether this tx can be replaced with another with
181
+ * higher fee while being in the mempool. Note that a tx will automatically
182
+ * be marked as replacable if a single input requests it.
183
+ * Note that any transaction using a relative timelock (nSequence < 0x80000000)
184
+ * also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
185
+ * inherently replaceable. So don't set `rbf` to false if this is tx uses
186
+ * relative time locks.
187
+ *
147
188
  * @returns A finalizer function to be used after signing the `psbt`.
148
189
  * This function ensures that this input is properly finalized.
149
190
  * The finalizer has this signature:
@@ -151,12 +192,13 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
151
192
  * `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void`
152
193
  *
153
194
  */
154
- updatePsbtAsInput({ psbt, txHex, txId, value, vout }: {
195
+ updatePsbtAsInput({ psbt, txHex, txId, value, vout, rbf }: {
155
196
  psbt: Psbt;
156
197
  txHex?: string;
157
198
  txId?: string;
158
199
  value?: number;
159
200
  vout: number;
201
+ rbf?: boolean;
160
202
  }): ({ psbt, validate }: {
161
203
  psbt: Psbt;
162
204
  /** Runs further test on the validity of the signatures.
@@ -365,8 +407,40 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
365
407
  getNetwork(): Network;
366
408
  /**
367
409
  * Whether this `Output` is Segwit.
410
+ *
411
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
412
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
413
+ * (Script Hash-Witness Public Key Hash).
414
+ * For inputs using arbitrary scripts (not standard addresses),
415
+ * use a descriptor in the format `sh(MINISCRIPT)`.
416
+ *
368
417
  */
369
418
  isSegwit(): boolean | undefined;
419
+ /**
420
+ * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
421
+ * for this Output.
422
+ */
423
+ guessOutput(): {
424
+ isPKH: boolean;
425
+ isWPKH: boolean;
426
+ isSH: boolean;
427
+ };
428
+ /**
429
+ * Computes the Weight Unit contributions of this Output as if it were the
430
+ * input in a tx.
431
+ *
432
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
433
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
434
+ * (Script Hash-Witness Public Key Hash).
435
+ * For inputs using arbitrary scripts (not standard addresses),
436
+ * use a descriptor in the format `sh(MINISCRIPT)`.
437
+ */
438
+ inputWeight(isSegwitTx: boolean, signatures: PartialSig[] | 'DANGEROUSLY_USE_FAKE_SIGNATURES'): number;
439
+ /**
440
+ * Computes the Weight Unit contributions of this Output as if it were the
441
+ * output in a tx.
442
+ */
443
+ outputWeight(): number;
370
444
  /** @deprecated - Use updatePsbtAsInput instead
371
445
  * @hidden
372
446
  */
@@ -376,6 +450,7 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
376
450
  txId?: string;
377
451
  value?: number;
378
452
  vout: number;
453
+ rbf?: boolean;
379
454
  }): number;
380
455
  /**
381
456
  * Sets this output as an input of the provided `psbt` and updates the
@@ -394,6 +469,14 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
394
469
  *
395
470
  * When unsure, always use `txHex`, and skip `txId` and `value` for safety.
396
471
  *
472
+ * Use `rbf` to mark whether this tx can be replaced with another with
473
+ * higher fee while being in the mempool. Note that a tx will automatically
474
+ * be marked as replacable if a single input requests it.
475
+ * Note that any transaction using a relative timelock (nSequence < 0x80000000)
476
+ * also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
477
+ * inherently replaceable. So don't set `rbf` to false if this is tx uses
478
+ * relative time locks.
479
+ *
397
480
  * @returns A finalizer function to be used after signing the `psbt`.
398
481
  * This function ensures that this input is properly finalized.
399
482
  * The finalizer has this signature:
@@ -401,12 +484,13 @@ export declare function DescriptorsFactory(ecc: TinySecp256k1Interface): {
401
484
  * `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void`
402
485
  *
403
486
  */
404
- updatePsbtAsInput({ psbt, txHex, txId, value, vout }: {
487
+ updatePsbtAsInput({ psbt, txHex, txId, value, vout, rbf }: {
405
488
  psbt: Psbt;
406
489
  txHex?: string;
407
490
  txId?: string;
408
491
  value?: number;
409
492
  vout: number;
493
+ rbf?: boolean;
410
494
  }): ({ psbt, validate }: {
411
495
  psbt: Psbt;
412
496
  /** Runs further test on the validity of the signatures.
@@ -35,9 +35,14 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
35
35
  if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
36
36
  return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
37
37
  };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
38
41
  Object.defineProperty(exports, "__esModule", { value: true });
39
42
  exports.DescriptorsFactory = void 0;
43
+ const lodash_memoize_1 = __importDefault(require("lodash.memoize"));
40
44
  const bitcoinjs_lib_1 = require("bitcoinjs-lib");
45
+ const varuint_bitcoin_1 = require("varuint-bitcoin");
41
46
  const { p2sh, p2wpkh, p2pkh, p2pk, p2wsh, p2tr } = bitcoinjs_lib_1.payments;
42
47
  const bip32_1 = require("bip32");
43
48
  const ecpair_1 = require("ecpair");
@@ -57,6 +62,28 @@ function countNonPushOnlyOPs(script) {
57
62
  throw new Error(`Error: cound not decompile ${script}`);
58
63
  return decompile.filter(op => typeof op === 'number' && op > bitcoinjs_lib_1.script.OPS['OP_16']).length;
59
64
  }
65
+ function vectorSize(someVector) {
66
+ const length = someVector.length;
67
+ return ((0, varuint_bitcoin_1.encodingLength)(length) +
68
+ someVector.reduce((sum, witness) => {
69
+ return sum + varSliceSize(witness);
70
+ }, 0));
71
+ }
72
+ function varSliceSize(someScript) {
73
+ const length = someScript.length;
74
+ return (0, varuint_bitcoin_1.encodingLength)(length) + length;
75
+ }
76
+ /**
77
+ * This function will typically return 73; since it assumes a signature size of
78
+ * 72 bytes (this is the max size of a DER encoded signature) and it adds 1
79
+ * extra byte for encoding its length
80
+ */
81
+ function signatureSize(signature) {
82
+ const length = signature === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
83
+ ? 72
84
+ : signature.signature.length;
85
+ return (0, varuint_bitcoin_1.encodingLength)(length) + length;
86
+ }
60
87
  /*
61
88
  * Returns a bare descriptor without checksum and particularized for a certain
62
89
  * index (if desc was a range descriptor)
@@ -184,22 +211,28 @@ function DescriptorsFactory(ecc) {
184
211
  }
185
212
  try {
186
213
  payment = p2pkh({ output, network });
214
+ isSegwit = false;
187
215
  }
188
216
  catch (e) { }
189
217
  try {
190
218
  payment = p2sh({ output, network });
219
+ // It assumes that an addr(SH_ADDRESS) is always a add(SH_WPKH) address
220
+ isSegwit = true;
191
221
  }
192
222
  catch (e) { }
193
223
  try {
194
224
  payment = p2wpkh({ output, network });
225
+ isSegwit = true;
195
226
  }
196
227
  catch (e) { }
197
228
  try {
198
229
  payment = p2wsh({ output, network });
230
+ isSegwit = true;
199
231
  }
200
232
  catch (e) { }
201
233
  try {
202
234
  payment = p2tr({ output, network });
235
+ isSegwit = true;
203
236
  }
204
237
  catch (e) { }
205
238
  if (!payment) {
@@ -480,6 +513,25 @@ function DescriptorsFactory(ecc) {
480
513
  __classPrivateFieldSet(this, _Output_signersPubKeys, [this.getScriptPubKey()], "f");
481
514
  }
482
515
  }
516
+ this.getSequence = (0, lodash_memoize_1.default)(this.getSequence);
517
+ this.getLockTime = (0, lodash_memoize_1.default)(this.getLockTime);
518
+ const getSignaturesKey = (signatures) => signatures === 'DANGEROUSLY_USE_FAKE_SIGNATURES'
519
+ ? signatures
520
+ : signatures
521
+ .map(s => `${s.pubkey.toString('hex')}-${s.signature.toString('hex')}`)
522
+ .join('|');
523
+ this.getScriptSatisfaction = (0, lodash_memoize_1.default)(this.getScriptSatisfaction,
524
+ // resolver function:
525
+ getSignaturesKey);
526
+ this.guessOutput = (0, lodash_memoize_1.default)(this.guessOutput);
527
+ this.inputWeight = (0, lodash_memoize_1.default)(this.inputWeight,
528
+ // resolver function:
529
+ (isSegwitTx, signatures) => {
530
+ const segwitKey = isSegwitTx ? 'segwit' : 'non-segwit';
531
+ const signaturesKey = getSignaturesKey(signatures);
532
+ return `${segwitKey}-${signaturesKey}`;
533
+ });
534
+ this.outputWeight = (0, lodash_memoize_1.default)(this.outputWeight);
483
535
  }
484
536
  /**
485
537
  * Creates and returns an instance of bitcoinjs-lib
@@ -607,10 +659,228 @@ function DescriptorsFactory(ecc) {
607
659
  }
608
660
  /**
609
661
  * Whether this `Output` is Segwit.
662
+ *
663
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
664
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
665
+ * (Script Hash-Witness Public Key Hash).
666
+ * For inputs using arbitrary scripts (not standard addresses),
667
+ * use a descriptor in the format `sh(MINISCRIPT)`.
668
+ *
610
669
  */
611
670
  isSegwit() {
612
671
  return __classPrivateFieldGet(this, _Output_isSegwit, "f");
613
672
  }
673
+ /**
674
+ * Returns the tuple: `{ isPKH: boolean; isWPKH: boolean; isSH: boolean; }`
675
+ * for this Output.
676
+ */
677
+ guessOutput() {
678
+ function guessSH(output) {
679
+ try {
680
+ bitcoinjs_lib_1.payments.p2sh({ output });
681
+ return true;
682
+ }
683
+ catch (err) {
684
+ return false;
685
+ }
686
+ }
687
+ function guessWPKH(output) {
688
+ try {
689
+ bitcoinjs_lib_1.payments.p2wpkh({ output });
690
+ return true;
691
+ }
692
+ catch (err) {
693
+ return false;
694
+ }
695
+ }
696
+ function guessPKH(output) {
697
+ try {
698
+ bitcoinjs_lib_1.payments.p2pkh({ output });
699
+ return true;
700
+ }
701
+ catch (err) {
702
+ return false;
703
+ }
704
+ }
705
+ const isPKH = guessPKH(this.getScriptPubKey());
706
+ const isWPKH = guessWPKH(this.getScriptPubKey());
707
+ const isSH = guessSH(this.getScriptPubKey());
708
+ if ([isPKH, isWPKH, isSH].filter(Boolean).length > 1)
709
+ throw new Error('Cannot have multiple output types.');
710
+ return { isPKH, isWPKH, isSH };
711
+ }
712
+ // References for inputWeight & outputWeight:
713
+ // https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c
714
+ // https://bitcoinops.org/en/tools/calc-size/
715
+ // Look for byteLength: https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/ts_src/transaction.ts
716
+ // https://github.com/bitcoinjs/coinselect/blob/master/utils.js
717
+ /**
718
+ * Computes the Weight Unit contributions of this Output as if it were the
719
+ * input in a tx.
720
+ *
721
+ * *NOTE:* When the descriptor in an input is `addr(address)`, it is assumed
722
+ * that any `addr(SH_TYPE_ADDRESS)` is in fact a Segwit `SH_WPKH`
723
+ * (Script Hash-Witness Public Key Hash).
724
+ * For inputs using arbitrary scripts (not standard addresses),
725
+ * use a descriptor in the format `sh(MINISCRIPT)`.
726
+ */
727
+ inputWeight(
728
+ /**
729
+ * Indicates if the transaction is a Segwit transaction.
730
+ * If a transaction isSegwitTx, a single byte is then also required for
731
+ * non-witness inputs to encode the length of the empty witness stack:
732
+ * encodeLength(0) + 0 = 1
733
+ * Read more:
734
+ * https://gist.github.com/junderw/b43af3253ea5865ed52cb51c200ac19c?permalink_comment_id=4760512#gistcomment-4760512
735
+ */
736
+ isSegwitTx,
737
+ /*
738
+ * Array of `PartialSig`. Each `PartialSig` includes
739
+ * a public key and its corresponding signature. This parameter
740
+ * enables the accurate calculation of signature sizes.
741
+ * Pass 'DANGEROUSLY_USE_FAKE_SIGNATURES' to assume 72 bytes in length.
742
+ * Mainly used for testing.
743
+ */
744
+ signatures) {
745
+ if (this.isSegwit() && !isSegwitTx)
746
+ throw new Error(`a tx is segwit if at least one input is segwit`);
747
+ const errorMsg = 'Input type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
748
+ sh(wpkh(KEY)), sh(wsh(MINISCRIPT)), sh(MINISCRIPT), wsh(MINISCRIPT), \
749
+ addr(PKH_ADDRESS), addr(WPKH_ADDRESS), addr(SH_WPKH_ADDRESS).';
750
+ //expand any miniscript-based descriptor. If not miniscript-based, then it's
751
+ //an addr() descriptor. For those, we can only guess their type.
752
+ const expansion = this.expand().expandedExpression;
753
+ const { isPKH, isWPKH, isSH } = this.guessOutput();
754
+ if (!expansion && !isPKH && !isWPKH && !isSH)
755
+ throw new Error(errorMsg);
756
+ const firstSignature = signatures && typeof signatures[0] === 'object'
757
+ ? signatures[0]
758
+ : 'DANGEROUSLY_USE_FAKE_SIGNATURES';
759
+ if (expansion ? expansion.startsWith('pkh(') : isPKH) {
760
+ return (
761
+ // Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (sig:73) + (pubkey:34)
762
+ (32 + 4 + 4 + 1 + signatureSize(firstSignature) + 34) * 4 +
763
+ //Segwit:
764
+ (isSegwitTx ? 1 : 0));
765
+ }
766
+ else if (expansion ? expansion.startsWith('wpkh(') : isWPKH) {
767
+ if (!isSegwitTx)
768
+ throw new Error('Should be SegwitTx');
769
+ return (
770
+ // Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1)
771
+ 41 * 4 +
772
+ // Segwit: (push_count:1) + (sig:73) + (pubkey:34)
773
+ (1 + signatureSize(firstSignature) + 34));
774
+ }
775
+ else if (expansion ? expansion.startsWith('sh(wpkh(') : isSH) {
776
+ if (!isSegwitTx)
777
+ throw new Error('Should be SegwitTx');
778
+ return (
779
+ // Non-segwit: (txid:32) + (vout:4) + (sequence:4) + (script_len:1) + (p2wpkh:23)
780
+ // -> p2wpkh_script: OP_0 OP_PUSH20 <public_key_hash>
781
+ // -> p2wpkh: (script_len:1) + (script:22)
782
+ 64 * 4 +
783
+ // Segwit: (push_count:1) + (sig:73) + (pubkey:34)
784
+ (1 + signatureSize(firstSignature) + 34));
785
+ }
786
+ else if (expansion?.startsWith('sh(wsh(')) {
787
+ if (!isSegwitTx)
788
+ throw new Error('Should be SegwitTx');
789
+ const witnessScript = this.getWitnessScript();
790
+ if (!witnessScript)
791
+ throw new Error('sh(wsh) must provide witnessScript');
792
+ const payment = bitcoinjs_lib_1.payments.p2sh({
793
+ redeem: bitcoinjs_lib_1.payments.p2wsh({
794
+ redeem: {
795
+ input: this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES'),
796
+ output: witnessScript
797
+ }
798
+ })
799
+ });
800
+ if (!payment || !payment.input || !payment.witness)
801
+ throw new Error('Could not create payment');
802
+ return (
803
+ //Non-segwit
804
+ 4 * (40 + varSliceSize(payment.input)) +
805
+ //Segwit
806
+ vectorSize(payment.witness));
807
+ }
808
+ else if (expansion?.startsWith('sh(')) {
809
+ const redeemScript = this.getRedeemScript();
810
+ if (!redeemScript)
811
+ throw new Error('sh() must provide redeemScript');
812
+ const payment = bitcoinjs_lib_1.payments.p2sh({
813
+ redeem: {
814
+ input: this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES'),
815
+ output: redeemScript
816
+ }
817
+ });
818
+ if (!payment || !payment.input)
819
+ throw new Error('Could not create payment');
820
+ if (payment.witness?.length)
821
+ throw new Error('A legacy p2sh payment should not cointain a witness');
822
+ return (
823
+ //Non-segwit
824
+ 4 * (40 + varSliceSize(payment.input)) +
825
+ //Segwit:
826
+ (isSegwitTx ? 1 : 0));
827
+ }
828
+ else if (expansion?.startsWith('wsh(')) {
829
+ const witnessScript = this.getWitnessScript();
830
+ if (!witnessScript)
831
+ throw new Error('wsh must provide witnessScript');
832
+ const payment = bitcoinjs_lib_1.payments.p2wsh({
833
+ redeem: {
834
+ input: this.getScriptSatisfaction(signatures || 'DANGEROUSLY_USE_FAKE_SIGNATURES'),
835
+ output: witnessScript
836
+ }
837
+ });
838
+ if (!payment || !payment.input || !payment.witness)
839
+ throw new Error('Could not create payment');
840
+ return (
841
+ //Non-segwit
842
+ 4 * (40 + varSliceSize(payment.input)) +
843
+ //Segwit
844
+ vectorSize(payment.witness));
845
+ }
846
+ else {
847
+ throw new Error(errorMsg);
848
+ }
849
+ }
850
+ /**
851
+ * Computes the Weight Unit contributions of this Output as if it were the
852
+ * output in a tx.
853
+ */
854
+ outputWeight() {
855
+ const errorMsg = 'Output type not implemented. Currently supported: pkh(KEY), wpkh(KEY), \
856
+ sh(ANYTHING), wsh(ANYTHING), addr(PKH_ADDRESS), addr(WPKH_ADDRESS), \
857
+ addr(SH_WPKH_ADDRESS)';
858
+ //expand any miniscript-based descriptor. If not miniscript-based, then it's
859
+ //an addr() descriptor. For those, we can only guess their type.
860
+ const expansion = this.expand().expandedExpression;
861
+ const { isPKH, isWPKH, isSH } = this.guessOutput();
862
+ if (!expansion && !isPKH && !isWPKH && !isSH)
863
+ throw new Error(errorMsg);
864
+ if (expansion ? expansion.startsWith('pkh(') : isPKH) {
865
+ // (p2pkh:26) + (amount:8)
866
+ return 34 * 4;
867
+ }
868
+ else if (expansion ? expansion.startsWith('wpkh(') : isWPKH) {
869
+ // (p2wpkh:23) + (amount:8)
870
+ return 31 * 4;
871
+ }
872
+ else if (expansion ? expansion.startsWith('sh(') : isSH) {
873
+ // (p2sh:24) + (amount:8)
874
+ return 32 * 4;
875
+ }
876
+ else if (expansion?.startsWith('wsh(')) {
877
+ // (p2wsh:35) + (amount:8)
878
+ return 43 * 4;
879
+ }
880
+ else {
881
+ throw new Error(errorMsg);
882
+ }
883
+ }
614
884
  /** @deprecated - Use updatePsbtAsInput instead
615
885
  * @hidden
616
886
  */
@@ -635,6 +905,14 @@ function DescriptorsFactory(ecc) {
635
905
  *
636
906
  * When unsure, always use `txHex`, and skip `txId` and `value` for safety.
637
907
  *
908
+ * Use `rbf` to mark whether this tx can be replaced with another with
909
+ * higher fee while being in the mempool. Note that a tx will automatically
910
+ * be marked as replacable if a single input requests it.
911
+ * Note that any transaction using a relative timelock (nSequence < 0x80000000)
912
+ * also falls within the RBF range (nSequence < 0xFFFFFFFE), making it
913
+ * inherently replaceable. So don't set `rbf` to false if this is tx uses
914
+ * relative time locks.
915
+ *
638
916
  * @returns A finalizer function to be used after signing the `psbt`.
639
917
  * This function ensures that this input is properly finalized.
640
918
  * The finalizer has this signature:
@@ -642,8 +920,8 @@ function DescriptorsFactory(ecc) {
642
920
  * `( { psbt, validate = true } : { psbt: Psbt; validate: boolean | undefined } ) => void`
643
921
  *
644
922
  */
645
- updatePsbtAsInput({ psbt, txHex, txId, value, vout //vector output index
646
- }) {
923
+ updatePsbtAsInput({ psbt, txHex, txId, value, vout, //vector output index
924
+ rbf = true }) {
647
925
  if (txHex === undefined) {
648
926
  console.warn(`Warning: missing txHex may allow fee attacks`);
649
927
  }
@@ -664,7 +942,8 @@ function DescriptorsFactory(ecc) {
664
942
  scriptPubKey: this.getScriptPubKey(),
665
943
  isSegwit,
666
944
  witnessScript: this.getWitnessScript(),
667
- redeemScript: this.getRedeemScript()
945
+ redeemScript: this.getRedeemScript(),
946
+ rbf
668
947
  });
669
948
  const finalizer = ({ psbt, validate = true }) => this.finalizePsbtInput({ index, psbt, validate });
670
949
  return finalizer;
@@ -802,16 +1081,20 @@ function DescriptorsFactory(ecc) {
802
1081
  scriptPubKey = out.script;
803
1082
  }
804
1083
  const locktime = this.getLockTime() || 0;
805
- let sequence = this.getSequence();
806
- if (sequence === undefined && locktime !== 0)
807
- sequence = 0xfffffffe;
808
- if (sequence === undefined && locktime === 0)
809
- sequence = 0xffffffff;
1084
+ const sequence = this.getSequence();
1085
+ //We don't know whether the user opted for RBF or not. So check that
1086
+ //at least one of the 2 sequences matches.
1087
+ const sequenceNoRBF = sequence !== undefined
1088
+ ? sequence
1089
+ : locktime === 0
1090
+ ? 0xffffffff
1091
+ : 0xfffffffe;
1092
+ const sequenceRBF = sequence !== undefined ? sequence : 0xfffffffd;
810
1093
  const eqBuffers = (buf1, buf2) => buf1 instanceof Buffer && buf2 instanceof Buffer
811
1094
  ? Buffer.compare(buf1, buf2) === 0
812
1095
  : buf1 === buf2;
813
1096
  if (Buffer.compare(scriptPubKey, this.getScriptPubKey()) !== 0 ||
814
- sequence !== inputSequence ||
1097
+ (sequenceRBF !== inputSequence && sequenceNoRBF !== inputSequence) ||
815
1098
  locktime !== psbt.locktime ||
816
1099
  !eqBuffers(this.getWitnessScript(), input.witnessScript) ||
817
1100
  !eqBuffers(this.getRedeemScript(), input.redeemScript)) {
package/dist/psbt.d.ts CHANGED
@@ -21,7 +21,7 @@ export declare function finalScriptsFuncFactory(scriptSatisfaction: Buffer, netw
21
21
  /**
22
22
  * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
23
23
  */
24
- export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript }: {
24
+ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }: {
25
25
  psbt: Psbt;
26
26
  vout: number;
27
27
  txHex?: string;
@@ -34,5 +34,6 @@ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, l
34
34
  isSegwit: boolean;
35
35
  witnessScript: Buffer | undefined;
36
36
  redeemScript: Buffer | undefined;
37
+ rbf: boolean;
37
38
  }): number;
38
39
  export {};
package/dist/psbt.js CHANGED
@@ -1,33 +1,10 @@
1
1
  "use strict";
2
2
  // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
3
3
  // Distributed under the MIT software license
4
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
5
- if (k2 === undefined) k2 = k;
6
- var desc = Object.getOwnPropertyDescriptor(m, k);
7
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8
- desc = { enumerable: true, get: function() { return m[k]; } };
9
- }
10
- Object.defineProperty(o, k2, desc);
11
- }) : (function(o, m, k, k2) {
12
- if (k2 === undefined) k2 = k;
13
- o[k2] = m[k];
14
- }));
15
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
16
- Object.defineProperty(o, "default", { enumerable: true, value: v });
17
- }) : function(o, v) {
18
- o["default"] = v;
19
- });
20
- var __importStar = (this && this.__importStar) || function (mod) {
21
- if (mod && mod.__esModule) return mod;
22
- var result = {};
23
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
- __setModuleDefault(result, mod);
25
- return result;
26
- };
27
4
  Object.defineProperty(exports, "__esModule", { value: true });
28
5
  exports.updatePsbt = exports.finalScriptsFuncFactory = void 0;
29
6
  const bitcoinjs_lib_1 = require("bitcoinjs-lib");
30
- const varuint = __importStar(require("bip174/src/lib/converter/varint"));
7
+ const varuint_bitcoin_1 = require("varuint-bitcoin");
31
8
  function reverseBuffer(buffer) {
32
9
  if (buffer.length < 1)
33
10
  return buffer;
@@ -48,9 +25,9 @@ function witnessStackToScriptWitness(witness) {
48
25
  }
49
26
  function writeVarInt(i) {
50
27
  const currentLen = buffer.length;
51
- const varintLen = varuint.encodingLength(i);
28
+ const varintLen = (0, varuint_bitcoin_1.encodingLength)(i);
52
29
  buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
53
- varuint.encode(i, buffer, currentLen);
30
+ (0, varuint_bitcoin_1.encode)(i, buffer, currentLen);
54
31
  }
55
32
  function writeVarSlice(slice) {
56
33
  writeVarInt(slice.length);
@@ -109,8 +86,10 @@ exports.finalScriptsFuncFactory = finalScriptsFuncFactory;
109
86
  /**
110
87
  * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
111
88
  */
112
- function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript }) {
89
+ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript, rbf }) {
113
90
  //Some data-sanity checks:
91
+ if (sequence !== undefined && rbf && sequence > 0xfffffffd)
92
+ throw new Error(`Error: incompatible sequence and rbf settings`);
114
93
  if (!isSegwit && txHex === undefined)
115
94
  throw new Error(`Error: txHex is mandatory for Non-Segwit inputs`);
116
95
  if (isSegwit &&
@@ -152,13 +131,23 @@ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysIn
152
131
  // this input's sequence < 0xffffffff
153
132
  if (sequence === undefined) {
154
133
  //NOTE: if sequence is undefined, bitcoinjs-lib uses 0xffffffff as default
155
- sequence = 0xfffffffe;
134
+ sequence = rbf ? 0xfffffffd : 0xfffffffe;
156
135
  }
157
136
  else if (sequence > 0xfffffffe) {
158
137
  throw new Error(`Error: incompatible sequence: ${sequence} and locktime: ${locktime}`);
159
138
  }
139
+ if (sequence === undefined && rbf)
140
+ sequence = 0xfffffffd;
160
141
  psbt.setLocktime(locktime);
161
142
  }
143
+ else {
144
+ if (sequence === undefined) {
145
+ if (rbf)
146
+ sequence = 0xfffffffd;
147
+ else
148
+ sequence = 0xffffffff;
149
+ }
150
+ }
162
151
  const input = {
163
152
  hash: reverseBuffer(Buffer.from(txId, 'hex')),
164
153
  index: vout
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@bitcoinerlab/descriptors",
3
3
  "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.",
4
4
  "homepage": "https://github.com/bitcoinerlab/descriptors",
5
- "version": "2.0.3",
5
+ "version": "2.2.0",
6
6
  "author": "Jose-Luis Landabaso",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -32,7 +32,7 @@
32
32
  "docs": "typedoc --options ./node_modules/@bitcoinerlab/configs/typedoc.json",
33
33
  "build:src": "tsc --project ./node_modules/@bitcoinerlab/configs/tsconfig.src.json",
34
34
  "build:fixtures": "node test/tools/generateBitcoinCoreFixtures.js -i test/fixtures/descriptor_tests.cpp | npx prettier --parser typescript > test/fixtures/bitcoinCore.ts",
35
- "build:test": "npm run build:fixtures && tsc --project ./node_modules/@bitcoinerlab/configs/tsconfig.test.json",
35
+ "build:test": "npm run build:fixtures && tsc --project ./node_modules/@bitcoinerlab/configs/tsconfig.test.json --resolveJsonModule",
36
36
  "build": "npm run build:src && npm run build:test",
37
37
  "lint": "eslint --ignore-path .gitignore --ext .ts src/ test/",
38
38
  "ensureTester": "./node_modules/@bitcoinerlab/configs/scripts/ensureTester.sh",
@@ -58,6 +58,7 @@
58
58
  "devDependencies": {
59
59
  "@bitcoinerlab/configs": "github:bitcoinerlab/configs",
60
60
  "@ledgerhq/hw-transport-node-hid": "^6.27.12",
61
+ "@types/lodash.memoize": "^4.1.9",
61
62
  "bip39": "^3.0.4",
62
63
  "bip65": "^1.0.3",
63
64
  "bip68": "^1.0.4",
@@ -66,10 +67,12 @@
66
67
  "yargs": "^17.7.2"
67
68
  },
68
69
  "dependencies": {
69
- "@bitcoinerlab/miniscript": "^1.2.1",
70
- "@bitcoinerlab/secp256k1": "^1.0.5",
70
+ "@bitcoinerlab/miniscript": "^1.4.0",
71
+ "@bitcoinerlab/secp256k1": "^1.1.1",
71
72
  "bip32": "^4.0.0",
72
73
  "bitcoinjs-lib": "^6.1.3",
73
- "ecpair": "^2.1.0"
74
+ "ecpair": "^2.1.0",
75
+ "lodash.memoize": "^4.1.2",
76
+ "varuint-bitcoin": "^1.1.2"
74
77
  }
75
78
  }