@scure/btc-signer 1.3.1 → 1.4.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.
Files changed (95) hide show
  1. package/README.md +7 -10
  2. package/_type_test.d.ts.map +1 -0
  3. package/_type_test.js.map +1 -0
  4. package/esm/_type_test.d.ts +2 -0
  5. package/esm/_type_test.js.map +1 -0
  6. package/{lib → esm}/index.d.ts +5 -4
  7. package/esm/index.d.ts.map +1 -0
  8. package/{lib/esm → esm}/index.js +10 -3
  9. package/esm/index.js.map +1 -0
  10. package/{lib → esm}/payment.d.ts.map +1 -1
  11. package/{lib/esm → esm}/payment.js +6 -6
  12. package/esm/payment.js.map +1 -0
  13. package/{lib → esm}/psbt.d.ts +2 -16
  14. package/esm/psbt.d.ts.map +1 -0
  15. package/{lib/esm → esm}/psbt.js +7 -9
  16. package/esm/psbt.js.map +1 -0
  17. package/{lib → esm}/script.d.ts +15 -1
  18. package/{lib → esm}/script.d.ts.map +1 -1
  19. package/{lib/esm → esm}/script.js +14 -5
  20. package/esm/script.js.map +1 -0
  21. package/{lib → esm}/transaction.d.ts +16 -10
  22. package/esm/transaction.d.ts.map +1 -0
  23. package/{lib/esm → esm}/transaction.js +45 -19
  24. package/esm/transaction.js.map +1 -0
  25. package/{lib → esm}/utils.d.ts +4 -4
  26. package/esm/utils.d.ts.map +1 -0
  27. package/{lib/esm → esm}/utils.js +3 -2
  28. package/esm/utils.js.map +1 -0
  29. package/{lib → esm}/utxo.d.ts +18 -21
  30. package/esm/utxo.d.ts.map +1 -0
  31. package/{lib/esm → esm}/utxo.js +68 -30
  32. package/esm/utxo.js.map +1 -0
  33. package/index.d.ts +20 -0
  34. package/index.d.ts.map +1 -0
  35. package/{lib/index.js → index.js} +10 -2
  36. package/index.js.map +1 -0
  37. package/package.json +41 -44
  38. package/payment.d.ts +177 -0
  39. package/payment.d.ts.map +1 -0
  40. package/{lib/payment.js → payment.js} +17 -17
  41. package/payment.js.map +1 -0
  42. package/psbt.d.ts +820 -0
  43. package/psbt.d.ts.map +1 -0
  44. package/{lib/psbt.js → psbt.js} +12 -14
  45. package/psbt.js.map +1 -0
  46. package/script.d.ts +168 -0
  47. package/script.d.ts.map +1 -0
  48. package/{lib/script.js → script.js} +17 -8
  49. package/script.js.map +1 -0
  50. package/src/index.ts +17 -3
  51. package/src/payment.ts +8 -8
  52. package/src/psbt.ts +7 -9
  53. package/src/script.ts +14 -5
  54. package/src/transaction.ts +50 -20
  55. package/src/utils.ts +3 -2
  56. package/src/utxo.ts +91 -26
  57. package/transaction.d.ts +229 -0
  58. package/transaction.d.ts.map +1 -0
  59. package/{lib/transaction.js → transaction.js} +56 -30
  60. package/transaction.js.map +1 -0
  61. package/utils.d.ts +30 -0
  62. package/utils.d.ts.map +1 -0
  63. package/{lib/utils.js → utils.js} +13 -11
  64. package/utils.js.map +1 -0
  65. package/utxo.d.ts +248 -0
  66. package/utxo.d.ts.map +1 -0
  67. package/{lib/utxo.js → utxo.js} +72 -34
  68. package/utxo.js.map +1 -0
  69. package/lib/_type_test.js.map +0 -1
  70. package/lib/esm/_type_test.js.map +0 -1
  71. package/lib/esm/index.js.map +0 -1
  72. package/lib/esm/payment.js.map +0 -1
  73. package/lib/esm/psbt.js.map +0 -1
  74. package/lib/esm/script.js.map +0 -1
  75. package/lib/esm/transaction.js.map +0 -1
  76. package/lib/esm/utils.js.map +0 -1
  77. package/lib/esm/utxo.js.map +0 -1
  78. package/lib/index.d.ts.map +0 -1
  79. package/lib/index.js.map +0 -1
  80. package/lib/payment.js.map +0 -1
  81. package/lib/psbt.d.ts.map +0 -1
  82. package/lib/psbt.js.map +0 -1
  83. package/lib/script.js.map +0 -1
  84. package/lib/transaction.d.ts.map +0 -1
  85. package/lib/transaction.js.map +0 -1
  86. package/lib/utils.d.ts.map +0 -1
  87. package/lib/utils.js.map +0 -1
  88. package/lib/utxo.d.ts.map +0 -1
  89. package/lib/utxo.js.map +0 -1
  90. /package/{lib/_type_test.d.ts → _type_test.d.ts} +0 -0
  91. /package/{lib/_type_test.js → _type_test.js} +0 -0
  92. /package/{lib → esm}/_type_test.d.ts.map +0 -0
  93. /package/{lib/esm → esm}/_type_test.js +0 -0
  94. /package/{lib/esm → esm}/package.json +0 -0
  95. /package/{lib → esm}/payment.d.ts +0 -0
@@ -1,10 +1,10 @@
1
1
  import * as P from 'micro-packed';
2
2
  import { hex } from '@scure/base';
3
-
4
3
  import { Address, CustomScript, OutScript, checkScript, tapLeafHash } from './payment.js';
5
4
  import * as psbt from './psbt.js'; // circular
6
- import { CompactSizeLen, RawOutput, RawTx, RawWitness, Script, VarBytes } from './script.js';
7
- import { NETWORK, Bytes, concatBytes, isBytes } from './utils.js';
5
+ import { CompactSizeLen, Script, VarBytes } from './script.js';
6
+ import { RawOutput, RawTx, RawOldTx, RawWitness } from './script.js';
7
+ import { NETWORK, Bytes, concatBytes, isBytes, equalBytes } from './utils.js';
8
8
  import * as u from './utils.js';
9
9
  import { getInputType, toVsize, normalizeInput, getPrevOut } from './utxo.js'; // circular
10
10
 
@@ -108,7 +108,7 @@ function getTaprootKeys(
108
108
  internalKey: Bytes,
109
109
  merkleRoot: Bytes = P.EMPTY
110
110
  ) {
111
- if (P.equalBytes(internalKey, pubKey)) {
111
+ if (equalBytes(internalKey, pubKey)) {
112
112
  privKey = u.taprootTweakPrivKey(privKey, merkleRoot);
113
113
  pubKey = u.pubSchnorr(privKey);
114
114
  }
@@ -284,6 +284,11 @@ export class Transaction {
284
284
  toPSBT(PSBTVersion = this.opts.PSBTVersion) {
285
285
  if (PSBTVersion !== 0 && PSBTVersion !== 2)
286
286
  throw new Error(`Wrong PSBT version=${PSBTVersion}`);
287
+ // if (PSBTVersion === 0 && this.inputs.length === 0) {
288
+ // throw new Error(
289
+ // 'PSBT version=0 export for transaction without inputs disabled, please use version=2. Please check `toPSBT` method for explanation.'
290
+ // );
291
+ // }
287
292
  const inputs = this.inputs.map((i) => psbt.cleanPSBTFields(PSBTVersion, psbt.PSBTInput, i));
288
293
  for (const inp of inputs) {
289
294
  // Don't serialize empty fields
@@ -294,7 +299,23 @@ export class Transaction {
294
299
  const outputs = this.outputs.map((i) => psbt.cleanPSBTFields(PSBTVersion, psbt.PSBTOutput, i));
295
300
  const global = { ...this.global };
296
301
  if (PSBTVersion === 0) {
297
- global.unsignedTx = RawTx.decode(this.unsignedTx);
302
+ /*
303
+ - Bitcoin raw transaction expects to have at least 1 input because it uses case with zero inputs as marker for SegWit
304
+ - this means we cannot serialize raw tx with zero inputs since it will be parsed as SegWit tx
305
+ - Parsing of PSBTv0 depends on unsignedTx (it looks for input count here)
306
+ - BIP-174 requires old serialization format (without witnesses) inside global, which solves this
307
+ */
308
+ global.unsignedTx = RawOldTx.decode(
309
+ RawOldTx.encode({
310
+ version: this.version,
311
+ lockTime: this.lockTime,
312
+ inputs: this.inputs.map(inputBeforeSign).map((i) => ({
313
+ ...i,
314
+ finalScriptSig: P.EMPTY,
315
+ })),
316
+ outputs: this.outputs.map(outputBeforeSign),
317
+ })
318
+ );
298
319
  delete global.fallbackLocktime;
299
320
  delete global.txVersion;
300
321
  } else {
@@ -502,6 +523,12 @@ export class Transaction {
502
523
  this.checkOutputIdx(idx);
503
524
  return cloneDeep(this.outputs[idx]);
504
525
  }
526
+ getOutputAddress(idx: number, network = NETWORK): string | undefined {
527
+ const out = this.getOutput(idx);
528
+ if (!out.script) return;
529
+ return Address(network).encode(OutScript.decode(out.script));
530
+ }
531
+
505
532
  get outputsLength() {
506
533
  return this.outputs.length;
507
534
  }
@@ -512,7 +539,10 @@ export class Transaction {
512
539
  ): psbt.TransactionOutput {
513
540
  let { amount, script } = o;
514
541
  if (amount === undefined) amount = cur?.amount;
515
- if (typeof amount !== 'bigint') throw new Error('amount must be bigint sats');
542
+ if (typeof amount !== 'bigint')
543
+ throw new Error(
544
+ `Wrong amount type, should be of type bigint in sats, but got ${amount} of type ${typeof amount}`
545
+ );
516
546
  if (typeof script === 'string') script = hex.decode(script);
517
547
  if (script === undefined) script = cur?.script;
518
548
  let res: psbt.PSBTKeyMapKeys<typeof psbt.PSBTOutput> = { ...cur, ...o, amount, script };
@@ -700,7 +730,7 @@ export class Transaction {
700
730
  .map(([pubKey, { path }]) => {
701
731
  let s = privateKey as HDKey;
702
732
  for (const i of path) s = s.deriveChild(i);
703
- if (!P.equalBytes(s.publicKey, pubKey)) throw new Error('bip32Derivation: wrong pubKey');
733
+ if (!equalBytes(s.publicKey, pubKey)) throw new Error('bip32Derivation: wrong pubKey');
704
734
  if (!s.privateKey) throw new Error('bip32Derivation: no privateKey');
705
735
  return s;
706
736
  });
@@ -735,7 +765,6 @@ export class Transaction {
735
765
  // Taproot
736
766
  const prevOut = getPrevOut(input);
737
767
  if (inputType.txType === 'taproot') {
738
- if (input.tapBip32Derivation) throw new Error('tapBip32Derivation unsupported');
739
768
  const prevOuts = this.inputs.map(getPrevOut);
740
769
  const prevOutScript = prevOuts.map((i) => i.script);
741
770
  const amount = prevOuts.map((i) => i.amount);
@@ -754,7 +783,7 @@ export class Transaction {
754
783
  merkleRoot
755
784
  );
756
785
  const [taprootPubKey, _] = u.taprootTweakPubkey(input.tapInternalKey, merkleRoot);
757
- if (P.equalBytes(taprootPubKey, pubKey)) {
786
+ if (equalBytes(taprootPubKey, pubKey)) {
758
787
  const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
759
788
  const sig = concatBytes(
760
789
  u.signSchnorr(hash, privKey, _auxRand),
@@ -772,7 +801,7 @@ export class Transaction {
772
801
  const ver = _script[_script.length - 1];
773
802
  const hash = tapLeafHash(script, ver);
774
803
  // NOTE: no need to tweak internal key here, since we don't support nested p2tr
775
- const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, schnorrPub));
804
+ const pos = scriptDecoded.findIndex((i) => isBytes(i) && equalBytes(i, schnorrPub));
776
805
  // Skip if there is no public key in tapLeafScript
777
806
  if (pos === -1) continue;
778
807
  const msg = this.preimageWitnessV1(
@@ -806,8 +835,7 @@ export class Transaction {
806
835
  let hasPubkey = false;
807
836
  const pubKeyHash = u.hash160(pubKey);
808
837
  for (const i of Script.decode(inputType.lastScript)) {
809
- if (isBytes(i) && (P.equalBytes(i, pubKey) || P.equalBytes(i, pubKeyHash)))
810
- hasPubkey = true;
838
+ if (isBytes(i) && (equalBytes(i, pubKey) || equalBytes(i, pubKeyHash))) hasPubkey = true;
811
839
  }
812
840
  if (!hasPubkey) throw new Error(`Input script doesn't have pubKey: ${inputType.lastScript}`);
813
841
  let hash;
@@ -870,14 +898,14 @@ export class Transaction {
870
898
  const ver = _script[_script.length - 1];
871
899
  const outScript = OutScript.decode(script);
872
900
  const hash = tapLeafHash(script, ver);
873
- const scriptSig = input.tapScriptSig.filter((i) => P.equalBytes(i[0].leafHash, hash));
901
+ const scriptSig = input.tapScriptSig.filter((i) => equalBytes(i[0].leafHash, hash));
874
902
  let signatures: Bytes[] = [];
875
903
  if (outScript.type === 'tr_ms') {
876
904
  const m = outScript.m;
877
905
  const pubkeys = outScript.pubkeys;
878
906
  let added = 0;
879
907
  for (const pub of pubkeys) {
880
- const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
908
+ const sigIdx = scriptSig.findIndex((i) => equalBytes(i[0].pubKey, pub));
881
909
  // Should have exact amount of signatures (more -- will fail)
882
910
  if (added === m || sigIdx === -1) {
883
911
  signatures.push(P.EMPTY);
@@ -890,7 +918,7 @@ export class Transaction {
890
918
  if (added !== m) continue;
891
919
  } else if (outScript.type === 'tr_ns') {
892
920
  for (const pub of outScript.pubkeys) {
893
- const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
921
+ const sigIdx = scriptSig.findIndex((i) => equalBytes(i[0].pubKey, pub));
894
922
  if (sigIdx === -1) continue;
895
923
  signatures.push(scriptSig[sigIdx][1]);
896
924
  }
@@ -900,7 +928,7 @@ export class Transaction {
900
928
  const scriptDecoded = Script.decode(script);
901
929
  signatures = scriptSig
902
930
  .map(([{ pubKey }, signature]) => {
903
- const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, pubKey));
931
+ const pos = scriptDecoded.findIndex((i) => isBytes(i) && equalBytes(i, pubKey));
904
932
  if (pos === -1)
905
933
  throw new Error('finalize/taproot: cannot find position of pubkey in script');
906
934
  return { signature, pos };
@@ -951,7 +979,7 @@ export class Transaction {
951
979
  let signatures = [];
952
980
  // partial: [pubkey, sign]
953
981
  for (const pub of pubkeys) {
954
- const sign = input.partialSig.find((s) => P.equalBytes(pub, s[0]));
982
+ const sign = input.partialSig.find((s) => equalBytes(pub, s[0]));
955
983
  if (!sign) continue;
956
984
  signatures.push(sign[1]);
957
985
  }
@@ -1022,9 +1050,11 @@ export class Transaction {
1022
1050
  );
1023
1051
  }
1024
1052
  }
1025
- const thisUnsigned = this.global.unsignedTx ? RawTx.encode(this.global.unsignedTx) : P.EMPTY;
1026
- const otherUnsigned = other.global.unsignedTx ? RawTx.encode(other.global.unsignedTx) : P.EMPTY;
1027
- if (!P.equalBytes(thisUnsigned, otherUnsigned))
1053
+ const thisUnsigned = this.global.unsignedTx ? RawOldTx.encode(this.global.unsignedTx) : P.EMPTY;
1054
+ const otherUnsigned = other.global.unsignedTx
1055
+ ? RawOldTx.encode(other.global.unsignedTx)
1056
+ : P.EMPTY;
1057
+ if (!equalBytes(thisUnsigned, otherUnsigned))
1028
1058
  throw new Error(`Transaction/combine: different unsigned tx`);
1029
1059
  this.global = psbt.mergeKeyMap(psbt.PSBTGlobal, this.global, other.global);
1030
1060
  for (let i = 0; i < this.inputs.length; i++) this.updateInput(i, other.inputs[i], true);
package/src/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { isBytes, concatBytes, U32LE } from 'micro-packed';
1
+ import { utils as packedUtils, U32LE } from 'micro-packed';
2
2
  import { ripemd160 } from '@noble/hashes/ripemd160';
3
3
  import { sha256 } from '@noble/hashes/sha256';
4
4
  import { secp256k1 as secp, schnorr } from '@noble/curves/secp256k1';
@@ -7,7 +7,8 @@ export type Bytes = Uint8Array;
7
7
  const Point = secp.ProjectivePoint;
8
8
  const CURVE_ORDER = secp.CURVE.n;
9
9
 
10
- export { sha256, isBytes, concatBytes };
10
+ const { isBytes, concatBytes, equalBytes } = packedUtils;
11
+ export { sha256, isBytes, concatBytes, equalBytes };
11
12
 
12
13
  export const hash160 = (msg: Bytes) => ripemd160(sha256(msg));
13
14
  export const sha256x2 = (...msgs: Bytes[]) => sha256(sha256(concatBytes(...msgs)));
package/src/utxo.ts CHANGED
@@ -10,7 +10,15 @@ import {
10
10
  SignatureHash,
11
11
  Transaction,
12
12
  } from './transaction.js'; // circular
13
- import { NETWORK, Bytes, compareBytes, isBytes, TAPROOT_UNSPENDABLE_KEY, sha256 } from './utils.js';
13
+ import {
14
+ NETWORK,
15
+ Bytes,
16
+ compareBytes,
17
+ equalBytes,
18
+ isBytes,
19
+ TAPROOT_UNSPENDABLE_KEY,
20
+ sha256,
21
+ } from './utils.js';
14
22
  import { validatePubkey, PubT } from './utils.js';
15
23
 
16
24
  // Normalizes input
@@ -153,7 +161,7 @@ function iterLeafs(tapLeafScript: TapLeafScript, sigSize: number, customScripts?
153
161
  const csEncoded = c.encode(scriptDecoded);
154
162
  if (csEncoded === undefined) continue;
155
163
  const pubKeys = scriptDecoded.filter((i) => {
156
- if (!P.isBytes(i)) return false;
164
+ if (!isBytes(i)) return false;
157
165
  try {
158
166
  validatePubkey(i, PubT.schnorr);
159
167
  return true;
@@ -187,7 +195,7 @@ function estimateInput(
187
195
  // schnorr sig is always 64 bytes. except for cases when sighash is not default!
188
196
  if (inputType.txType === 'taproot') {
189
197
  const SCHNORR_SIG_SIZE = inputType.sighash !== SignatureHash.DEFAULT ? 65 : 64;
190
- if (input.tapInternalKey && !P.equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
198
+ if (input.tapInternalKey && !equalBytes(input.tapInternalKey, TAPROOT_UNSPENDABLE_KEY)) {
191
199
  witness = [new Uint8Array(SCHNORR_SIG_SIZE)];
192
200
  } else if (input.tapLeafScript) {
193
201
  witness = iterLeafs(input.tapLeafScript, SCHNORR_SIG_SIZE, opts.customScripts);
@@ -260,7 +268,10 @@ export type EstimatorOpts = TxOpts & {
260
268
  bip69?: boolean; // https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
261
269
  network?: typeof NETWORK;
262
270
  dust?: number; // how much vbytes considered dust?
271
+ dustRelayFeeRate?: bigint; // fee per dust byte (DUST_RELAY_TX_FEE)
263
272
  createTx?: boolean; // Create tx inside selection
273
+ requiredInputs?: psbt.TransactionInputUpdate[]; // these inputs always will be used
274
+ allowSameUtxo?: boolean; // allow using UTXO multiple times (for test purposes)
264
275
  };
265
276
 
266
277
  function getScript(o: Output, opts: TxOpts = {}, network = NETWORK) {
@@ -274,7 +285,12 @@ function getScript(o: Output, opts: TxOpts = {}, network = NETWORK) {
274
285
  script = OutScript.encode(Address(network).decode(o.address));
275
286
  }
276
287
  if (!script) throw new Error('Estimator: wrong output script');
277
- if (typeof o.amount !== 'bigint') throw new Error(`Estimator: wrong output amount=${o.amount}`);
288
+ if (typeof o.amount !== 'bigint')
289
+ throw new Error(
290
+ `Estimator: wrong output amount=${
291
+ o.amount
292
+ }, should be of type bigint but got ${typeof o.amount}.`
293
+ );
278
294
  if (script && !opts.allowUnknownOutputs && OutScript.decode(script).type === 'unknown') {
279
295
  throw new Error(
280
296
  'Estimator: unknown output script type, there is a chance that input is unspendable. Pass allowUnknownOutputs=true, if you sure'
@@ -302,6 +318,7 @@ export class _Estimator {
302
318
  private baseWeight: number;
303
319
  private changeWeight: number;
304
320
  private amount: bigint;
321
+ private requiredIndices: number[] = [];
305
322
  private normalizedInputs: {
306
323
  inputType: ReturnType<typeof getInputType>;
307
324
  normalized: ReturnType<typeof normalizeInput>;
@@ -309,24 +326,51 @@ export class _Estimator {
309
326
  value: bigint;
310
327
  estimate: { weight: number; hasWitnesses: boolean };
311
328
  }[];
312
- // https://github.com/bitcoin/bitcoin/blob/f90603ac6d24f5263649675d51233f1fce8b2ecd/src/policy/policy.cpp#L44
313
- // 32 + 4 + 1 + 107 + 4
314
329
  // Dust used in accumExact + change address algo
315
330
  // - change address: can be smaller for segwit
316
331
  // - accumExact: ???
317
- private dust = 148n; // compat with coinselect
318
-
332
+ private dust: bigint; // total dust limit (3||opts.dustRelayFeeRate * 182||opts.dust). Default: 546
319
333
  constructor(
320
- private inputs: psbt.TransactionInputUpdate[],
334
+ inputs: psbt.TransactionInputUpdate[],
321
335
  private outputs: Output[],
322
336
  private opts: EstimatorOpts
323
337
  ) {
324
338
  if (typeof opts.feePerByte !== 'bigint')
325
- throw new Error(`Estimator: wrong feePerByte=${opts.feePerByte}`);
326
- if (opts.dust) {
327
- if (typeof opts.dust !== 'bigint') throw new Error(`Estimator: wrong dust=${opts.dust}`);
328
- this.dust = opts.dust;
339
+ throw new Error(
340
+ `Estimator: wrong feePerByte=${
341
+ opts.feePerByte
342
+ }, should be of type bigint but got ${typeof opts.feePerByte}.`
343
+ );
344
+ // Dust stuff
345
+ // TODO: think about this more:
346
+ // - current dust filters tx which cannot be relayed by core
347
+ // - but actual dust meaning is 'can be this amount spent?'
348
+ // - dust contains full tx size. but we can use other inputs to pay for outputDust (and parially inputsDust)?
349
+ // - not sure if we can spent anything with feePerByte: 3. It will be relayed, but will it be mined?
350
+ // - for now it works exactly as bitcoin-core. But will create change/outputs which cannot be spent (reasonable).
351
+ // Number of bytes needed to create and spend a UTXO.
352
+ // https://github.com/bitcoin/bitcoin/blob/27a770b34b8f1dbb84760f442edb3e23a0c2420b/src/policy/policy.cpp#L28-L41
353
+ const inputsDust = 32 + 4 + 1 + 107 + 4; // NOTE: can be smaller for segwit tx?
354
+ const outputDust = 34; // NOTE: 'nSize = GetSerializeSize(txout)'
355
+ const dustBytes = opts.dust === undefined ? BigInt(inputsDust + outputDust) : opts.dust;
356
+ if (typeof dustBytes !== 'bigint') {
357
+ throw new Error(
358
+ `Estimator: wrong dust=${opts.dust}, should be of type bigint but got ${typeof opts.dust}.`
359
+ );
360
+ }
361
+ // 3 sat/vb is the default minimum fee rate used to calculate dust thresholds by bitcoin core.
362
+ // 3000 sat/kvb -> 3 sat/vb.
363
+ // https://github.com/bitcoin/bitcoin/blob/27a770b34b8f1dbb84760f442edb3e23a0c2420b/src/policy/policy.h#L55
364
+ const dustFee = opts.dustRelayFeeRate === undefined ? 3n : opts.dustRelayFeeRate;
365
+ if (typeof dustFee !== 'bigint') {
366
+ throw new Error(
367
+ `Estimator: wrong dustRelayFeeRate=${opts.dustRelayFeeRate}, should be of type bigint but got ${typeof opts.dustRelayFeeRate}.`
368
+ );
329
369
  }
370
+ // Dust uses feePerbyte by default, but we allow separate dust fee if needed
371
+ this.dust = dustBytes * dustFee;
372
+ if (opts.requiredInputs !== undefined && !Array.isArray(opts.requiredInputs))
373
+ throw new Error(`Estimator: wrong required inputs=${opts.requiredInputs}`);
330
374
  const network = opts.network || NETWORK;
331
375
  let amount = 0n;
332
376
  // Base weight: tx with outputs, no inputs
@@ -348,9 +392,19 @@ export class _Estimator {
348
392
  this.baseWeight = baseWeight;
349
393
  this.changeWeight = changeWeight;
350
394
  this.amount = amount;
351
- this.normalizedInputs = this.inputs.map((i) => {
395
+ const allInputs = Array.from(inputs);
396
+ if (opts.requiredInputs) {
397
+ for (let i = 0; i < opts.requiredInputs.length; i++)
398
+ this.requiredIndices.push(allInputs.push(opts.requiredInputs[i]) - 1);
399
+ }
400
+ const inputKeys = new Set();
401
+ this.normalizedInputs = allInputs.map((i) => {
352
402
  const normalized = normalizeInput(i, undefined, undefined, opts.disableScriptCheck);
353
403
  inputBeforeSign(normalized); // check fields
404
+ const key = `${hex.encode(normalized.txid!)}:${normalized.index}`;
405
+ if (!opts.allowSameUtxo && inputKeys.has(key))
406
+ throw new Error(`Estimator: same input passed multiple times: ${key}`);
407
+ inputKeys.add(key);
354
408
  const inputType = getInputType(normalized, opts.allowLegacyWitnessUtxo);
355
409
  const prev = getPrevOut(normalized);
356
410
  const estimate = estimateInput(inputType, normalized, this.opts);
@@ -359,7 +413,7 @@ export class _Estimator {
359
413
  });
360
414
  }
361
415
  private checkInputIdx(idx: number) {
362
- if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
416
+ if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.normalizedInputs.length)
363
417
  throw new Error(`Wrong input index=${idx}`);
364
418
  return idx;
365
419
  }
@@ -389,7 +443,7 @@ export class _Estimator {
389
443
 
390
444
  // Sort by value instead of amount
391
445
  get biggest() {
392
- return this.inputs
446
+ return this.normalizedInputs
393
447
  .map((_i, j) => j)
394
448
  .sort((a, b) => _cmpBig(this.normalizedInputs[b].value, this.normalizedInputs[a].value));
395
449
  }
@@ -400,7 +454,7 @@ export class _Estimator {
400
454
  // Otherwise, we have no way to know which tx is oldest
401
455
  // Explorers usually give UTXO in this order.
402
456
  get oldest() {
403
- return this.inputs.map((_i, j) => j);
457
+ return this.normalizedInputs.map((_i, j) => j);
404
458
  }
405
459
  get newest() {
406
460
  return this.oldest.reverse();
@@ -409,7 +463,6 @@ export class _Estimator {
409
463
  // exact(biggest) will select one big utxo which is closer to targetValue+dust, if possible.
410
464
  // If not, it will accumulate largest utxo until value is close to targetValue+dust.
411
465
  accumulate(indices: number[], exact = false, skipNegative = true, all = false) {
412
- const { feePerByte } = this.opts;
413
466
  // TODO: how to handle change addresses?
414
467
  // - cost of input
415
468
  // - cost of change output (if input requires change)
@@ -425,6 +478,22 @@ export class _Estimator {
425
478
  const targetAmount = this.amount;
426
479
  const res = [];
427
480
  let fee;
481
+ for (const idx of this.requiredIndices) {
482
+ this.checkInputIdx(idx);
483
+ const { estimate, amount } = this.normalizedInputs[idx];
484
+ let newWeight = weight + estimate.weight;
485
+ if (!hasWitnesses && estimate.hasWitnesses) newWeight += 2; // enable witness if needed
486
+ const totalWeight = newWeight + 4 * CompactSizeLen.encode(num).length; // number of outputs can change weight
487
+ fee = this.getSatoshi(totalWeight);
488
+ weight = newWeight;
489
+ if (estimate.hasWitnesses) hasWitnesses = true;
490
+ num++;
491
+ inputsAmount += amount;
492
+ res.push(idx);
493
+ // inputsAmount is enough to cover cost of tx
494
+ if (!all && targetAmount + fee <= inputsAmount)
495
+ return { indices: res, fee, weight: totalWeight, total: inputsAmount };
496
+ }
428
497
  for (const idx of indices) {
429
498
  this.checkInputIdx(idx);
430
499
  const { estimate, amount, value } = this.normalizedInputs[idx];
@@ -433,11 +502,7 @@ export class _Estimator {
433
502
  const totalWeight = newWeight + 4 * CompactSizeLen.encode(num).length; // number of outputs can change weight
434
503
  fee = this.getSatoshi(totalWeight);
435
504
  // Best case scenario exact(biggest) -> we find biggest output, less than target+threshold
436
- if (exact) {
437
- const dust = this.dust * feePerByte;
438
- // skip if added value is bigger than dust
439
- if (amount + inputsAmount > targetAmount + fee + dust) continue;
440
- }
505
+ if (exact && amount + inputsAmount > targetAmount + fee + this.dust) continue; // skip if added value is bigger than dust
441
506
  // Negative: cost of using input is more than value provided (negative)
442
507
  // By default 'blackjack' mode in coinselect doesn't use that, which means
443
508
  // it will use negative output if sorted by 'smallest'
@@ -448,7 +513,7 @@ export class _Estimator {
448
513
  inputsAmount += amount;
449
514
  res.push(idx);
450
515
  // inputsAmount is enough to cover cost of tx
451
- if (!all && targetAmount + fee < inputsAmount)
516
+ if (!all && targetAmount + fee <= inputsAmount)
452
517
  return { indices: res, fee, weight: totalWeight, total: inputsAmount };
453
518
  }
454
519
  if (all) {
@@ -469,7 +534,7 @@ export class _Estimator {
469
534
  private select(strategy: SelectionStrategy) {
470
535
  if (strategy === 'all') {
471
536
  return this.accumulate(
472
- this.inputs.map((_, j) => j),
537
+ this.normalizedInputs.map((_, j) => j),
473
538
  false,
474
539
  true,
475
540
  true
@@ -523,7 +588,7 @@ export class _Estimator {
523
588
  outputs = this.sortOutputs(outputs).map((i) => outputs[i]);
524
589
  }
525
590
  const res = {
526
- inputs: inputs.map((i) => this.inputs[i]),
591
+ inputs: inputs.map((i) => this.normalizedInputs[i].normalized),
527
592
  outputs,
528
593
  fee,
529
594
  weight: this.opts.alwaysChange ? s.weight : changeWeight,
@@ -0,0 +1,229 @@
1
+ import * as P from 'micro-packed';
2
+ import { CustomScript } from './payment.js';
3
+ import * as psbt from './psbt.js';
4
+ import { Bytes } from './utils.js';
5
+ interface HDKey {
6
+ publicKey: Bytes;
7
+ privateKey: Bytes;
8
+ fingerprint: number;
9
+ derive(path: string): HDKey;
10
+ deriveChild(index: number): HDKey;
11
+ sign(hash: Bytes): Bytes;
12
+ }
13
+ export type Signer = Bytes | HDKey;
14
+ export declare const PRECISION = 8;
15
+ export declare const DEFAULT_VERSION = 2;
16
+ export declare const DEFAULT_LOCKTIME = 0;
17
+ export declare const DEFAULT_SEQUENCE = 4294967295;
18
+ export declare const Decimal: {
19
+ encode: (from: bigint) => string;
20
+ decode: (to: string) => bigint;
21
+ };
22
+ export declare const def: <T>(value: T | undefined, def: T) => T;
23
+ export declare function cloneDeep<T>(obj: T): T;
24
+ export type TxOpts = {
25
+ version?: number;
26
+ lockTime?: number;
27
+ PSBTVersion?: number;
28
+ /** @deprecated Use `allowUnknownOutputs` */
29
+ allowUnknowOutput?: boolean;
30
+ allowUnknownOutputs?: boolean;
31
+ /** @deprecated Use `allowUnknownInputs` */
32
+ allowUnknowInput?: boolean;
33
+ allowUnknownInputs?: boolean;
34
+ disableScriptCheck?: boolean;
35
+ bip174jsCompat?: boolean;
36
+ allowLegacyWitnessUtxo?: boolean;
37
+ lowR?: boolean;
38
+ customScripts?: CustomScript[];
39
+ };
40
+ /**
41
+ * Internal, exported only for backwards-compat. Use `SigHash` instead.
42
+ * @deprecated
43
+ */
44
+ export declare enum SignatureHash {
45
+ DEFAULT = 0,
46
+ ALL = 1,
47
+ NONE = 2,
48
+ SINGLE = 3,
49
+ ANYONECANPAY = 128
50
+ }
51
+ export declare enum SigHash {
52
+ DEFAULT = 0,
53
+ ALL = 1,
54
+ NONE = 2,
55
+ SINGLE = 3,
56
+ DEFAULT_ANYONECANPAY = 128,
57
+ ALL_ANYONECANPAY = 129,
58
+ NONE_ANYONECANPAY = 130,
59
+ SINGLE_ANYONECANPAY = 131
60
+ }
61
+ export type TransactionInputRequired = {
62
+ txid: Bytes;
63
+ index: number;
64
+ sequence: number;
65
+ finalScriptSig: Bytes;
66
+ };
67
+ export declare function inputBeforeSign(i: psbt.TransactionInput): TransactionInputRequired;
68
+ declare function validateOpts(opts: TxOpts): Readonly<{
69
+ version: number;
70
+ lockTime: number;
71
+ PSBTVersion: number;
72
+ /** @deprecated Use `allowUnknownOutputs` */
73
+ allowUnknowOutput?: boolean;
74
+ allowUnknownOutputs?: boolean;
75
+ /** @deprecated Use `allowUnknownInputs` */
76
+ allowUnknowInput?: boolean;
77
+ allowUnknownInputs?: boolean;
78
+ disableScriptCheck?: boolean;
79
+ bip174jsCompat?: boolean;
80
+ allowLegacyWitnessUtxo?: boolean;
81
+ lowR?: boolean;
82
+ customScripts?: CustomScript[];
83
+ }>;
84
+ export declare class Transaction {
85
+ private global;
86
+ private inputs;
87
+ private outputs;
88
+ readonly opts: ReturnType<typeof validateOpts>;
89
+ constructor(opts?: TxOpts);
90
+ static fromRaw(raw: Bytes, opts?: TxOpts): Transaction;
91
+ static fromPSBT(psbt_: Bytes, opts?: TxOpts): Transaction;
92
+ toPSBT(PSBTVersion?: number): Uint8Array;
93
+ get lockTime(): number;
94
+ get version(): number;
95
+ private inputStatus;
96
+ private inputSighash;
97
+ private signStatus;
98
+ get isFinal(): boolean;
99
+ get hasWitnesses(): boolean;
100
+ get weight(): number;
101
+ get vsize(): number;
102
+ toBytes(withScriptSig?: boolean, withWitness?: boolean): Uint8Array;
103
+ get unsignedTx(): Bytes;
104
+ get hex(): string;
105
+ get hash(): string;
106
+ get id(): string;
107
+ private checkInputIdx;
108
+ getInput(idx: number): psbt.PSBTKeyMapKeys<{
109
+ readonly nonWitnessUtxo: readonly [0, false, P.CoderType<P.StructInput<{
110
+ version: number;
111
+ segwitFlag: boolean | undefined;
112
+ inputs: P.StructInput<{
113
+ txid: any;
114
+ index: any;
115
+ finalScriptSig: any;
116
+ sequence: any;
117
+ }>[];
118
+ outputs: P.StructInput<{
119
+ amount: any;
120
+ script: any;
121
+ }>[];
122
+ witnesses: P.Option<Uint8Array[][]>;
123
+ lockTime: number;
124
+ }>>, readonly [], readonly [0, 2], false];
125
+ readonly witnessUtxo: readonly [1, false, P.CoderType<P.StructInput<{
126
+ amount: bigint;
127
+ script: Uint8Array;
128
+ }>>, readonly [], readonly [0, 2], false];
129
+ readonly partialSig: readonly [2, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
130
+ readonly sighashType: readonly [3, false, P.CoderType<number>, readonly [], readonly [0, 2], false];
131
+ readonly redeemScript: readonly [4, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
132
+ readonly witnessScript: readonly [5, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
133
+ readonly bip32Derivation: readonly [6, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
134
+ fingerprint: number;
135
+ path: number[];
136
+ }>>, readonly [], readonly [0, 2], false];
137
+ readonly finalScriptSig: readonly [7, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
138
+ readonly finalScriptWitness: readonly [8, false, P.CoderType<Uint8Array[]>, readonly [], readonly [0, 2], false];
139
+ readonly porCommitment: readonly [9, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
140
+ readonly ripemd160: readonly [10, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
141
+ readonly sha256: readonly [11, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
142
+ readonly hash160: readonly [12, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
143
+ readonly hash256: readonly [13, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
144
+ readonly txid: readonly [14, false, P.CoderType<Uint8Array>, readonly [2], readonly [2], true];
145
+ readonly index: readonly [15, false, P.CoderType<number>, readonly [2], readonly [2], true];
146
+ readonly sequence: readonly [16, false, P.CoderType<number>, readonly [], readonly [2], true];
147
+ readonly requiredTimeLocktime: readonly [17, false, P.CoderType<number>, readonly [], readonly [2], false];
148
+ readonly requiredHeightLocktime: readonly [18, false, P.CoderType<number>, readonly [], readonly [2], false];
149
+ readonly tapKeySig: readonly [19, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
150
+ readonly tapScriptSig: readonly [20, P.CoderType<P.StructInput<{
151
+ pubKey: Uint8Array;
152
+ leafHash: Uint8Array;
153
+ }>>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
154
+ readonly tapLeafScript: readonly [21, P.CoderType<P.StructInput<{
155
+ version: number;
156
+ internalKey: Uint8Array;
157
+ merklePath: Uint8Array[];
158
+ }>>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
159
+ readonly tapBip32Derivation: readonly [22, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
160
+ hashes: Uint8Array[];
161
+ der: P.StructInput<{
162
+ fingerprint: any;
163
+ path: any;
164
+ }>;
165
+ }>>, readonly [], readonly [0, 2], false];
166
+ readonly tapInternalKey: readonly [23, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
167
+ readonly tapMerkleRoot: readonly [24, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
168
+ readonly proprietary: readonly [252, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
169
+ }>;
170
+ get inputsLength(): number;
171
+ addInput(input: psbt.TransactionInputUpdate, _ignoreSignStatus?: boolean): number;
172
+ updateInput(idx: number, input: psbt.TransactionInputUpdate, _ignoreSignStatus?: boolean): void;
173
+ private checkOutputIdx;
174
+ getOutput(idx: number): psbt.PSBTKeyMapKeys<{
175
+ readonly redeemScript: readonly [0, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
176
+ readonly witnessScript: readonly [1, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
177
+ readonly bip32Derivation: readonly [2, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
178
+ fingerprint: number;
179
+ path: number[];
180
+ }>>, readonly [], readonly [0, 2], false];
181
+ readonly amount: readonly [3, false, P.CoderType<bigint>, readonly [2], readonly [2], true];
182
+ readonly script: readonly [4, false, P.CoderType<Uint8Array>, readonly [2], readonly [2], true];
183
+ readonly tapInternalKey: readonly [5, false, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
184
+ readonly tapTree: readonly [6, false, P.CoderType<P.StructInput<{
185
+ depth: number;
186
+ version: number;
187
+ script: Uint8Array;
188
+ }>[]>, readonly [], readonly [0, 2], false];
189
+ readonly tapBip32Derivation: readonly [7, P.CoderType<Uint8Array>, P.CoderType<P.StructInput<{
190
+ hashes: Uint8Array[];
191
+ der: P.StructInput<{
192
+ fingerprint: any;
193
+ path: any;
194
+ }>;
195
+ }>>, readonly [], readonly [0, 2], false];
196
+ readonly proprietary: readonly [252, P.CoderType<Uint8Array>, P.CoderType<Uint8Array>, readonly [], readonly [0, 2], false];
197
+ }>;
198
+ getOutputAddress(idx: number, network?: {
199
+ bech32: string;
200
+ pubKeyHash: number;
201
+ scriptHash: number;
202
+ wif: number;
203
+ }): string | undefined;
204
+ get outputsLength(): number;
205
+ private normalizeOutput;
206
+ addOutput(o: psbt.TransactionOutputUpdate, _ignoreSignStatus?: boolean): number;
207
+ updateOutput(idx: number, output: psbt.TransactionOutputUpdate, _ignoreSignStatus?: boolean): void;
208
+ addOutputAddress(address: string, amount: bigint, network?: {
209
+ bech32: string;
210
+ pubKeyHash: number;
211
+ scriptHash: number;
212
+ wif: number;
213
+ }): number;
214
+ get fee(): bigint;
215
+ private preimageLegacy;
216
+ preimageWitnessV0(idx: number, prevOutScript: Bytes, hashType: number, amount: bigint): Uint8Array;
217
+ preimageWitnessV1(idx: number, prevOutScript: Bytes[], hashType: number, amount: bigint[], codeSeparator?: number, leafScript?: Bytes, leafVer?: number, annex?: Bytes): Uint8Array;
218
+ signIdx(privateKey: Signer, idx: number, allowedSighash?: SigHash[], _auxRand?: Bytes): boolean;
219
+ sign(privateKey: Signer, allowedSighash?: number[], _auxRand?: Bytes): number;
220
+ finalizeIdx(idx: number): void;
221
+ finalize(): void;
222
+ extract(): Uint8Array;
223
+ combine(other: Transaction): this;
224
+ clone(): Transaction;
225
+ }
226
+ export declare function PSBTCombine(psbts: Bytes[]): Bytes;
227
+ export declare function bip32Path(path: string): number[];
228
+ export {};
229
+ //# sourceMappingURL=transaction.d.ts.map