@bitcoinerlab/descriptors 2.3.6 → 3.0.1

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/dist/ledger.js CHANGED
@@ -36,15 +36,17 @@ exports.ledgerPolicyFromState = ledgerPolicyFromState;
36
36
  */
37
37
  const descriptors_1 = require("./descriptors");
38
38
  const bitcoinjs_lib_1 = require("bitcoinjs-lib");
39
+ const uint8array_tools_1 = require("uint8array-tools");
40
+ const networkUtils_1 = require("./networkUtils");
39
41
  const re_1 = require("./re");
40
42
  /**
41
- * Dynamically imports the 'ledger-bitcoin' module and, if provided, checks if `ledgerClient` is an instance of `AppClient`.
43
+ * Dynamically imports the '@ledgerhq/ledger-bitcoin' module and, if provided, checks if `ledgerClient` is an instance of `AppClient`.
42
44
  *
43
45
  * @async
44
46
  * @param {unknown} ledgerClient - An optional parameter that, if provided, is checked to see if it's an instance of `AppClient`.
45
47
  * @throws {Error} Throws an error if `ledgerClient` is provided but is not an instance of `AppClient`.
46
- * @throws {Error} Throws an error if the 'ledger-bitcoin' module cannot be imported. This typically indicates that the 'ledger-bitcoin' peer dependency is not installed.
47
- * @returns {Promise<unknown>} Returns a promise that resolves with the entire 'ledger-bitcoin' module if it can be successfully imported. We force it to return an unknown type so that the declaration of this function won't break projects that don't use ledger-bitcoin as dependency
48
+ * @throws {Error} Throws an error if the '@ledgerhq/ledger-bitcoin' module cannot be imported. This typically indicates that the '@ledgerhq/ledger-bitcoin' peer dependency is not installed.
49
+ * @returns {Promise<unknown>} Returns a promise that resolves with the entire '@ledgerhq/ledger-bitcoin' module if it can be successfully imported. We force it to return an unknown type so that the declaration of this function won't break projects that don't use @ledgerhq/ledger-bitcoin as dependency
48
50
  *
49
51
  * @example
50
52
  *
@@ -59,22 +61,22 @@ async function importAndValidateLedgerBitcoin(ledgerClient) {
59
61
  let ledgerBitcoinModule;
60
62
  try {
61
63
  // Originally, the code used dynamic imports:
62
- // ledgerBitcoinModule = await import('ledger-bitcoin');
64
+ // ledgerBitcoinModule = await import('@ledgerhq/ledger-bitcoin');
63
65
  // However, in React Native with the Metro bundler, there's an issue with
64
66
  // recognizing dynamic imports inside try-catch blocks. For details, refer to:
65
67
  // https://github.com/react-native-community/discussions-and-proposals/issues/120
66
68
  // The dynamic import gets transpiled to:
67
- // ledgerBitcoinModule = Promise.resolve().then(() => __importStar(require('ledger-bitcoin')));
69
+ // ledgerBitcoinModule = Promise.resolve().then(() => __importStar(require('@ledgerhq/ledger-bitcoin')));
68
70
  // Metro bundler fails to recognize the above as conditional. Hence, it tries
69
- // to require 'ledger-bitcoin' unconditionally, leading to potential errors if
70
- // 'ledger-bitcoin' is not installed (given it's an optional peerDependency).
71
+ // to require '@ledgerhq/ledger-bitcoin' unconditionally, leading to potential errors if
72
+ // '@ledgerhq/ledger-bitcoin' is not installed (given it's an optional peerDependency).
71
73
  // To bypass this, we directly use require:
72
74
  // eslint-disable-next-line @typescript-eslint/no-require-imports
73
- ledgerBitcoinModule = require('ledger-bitcoin');
75
+ ledgerBitcoinModule = require('@ledgerhq/ledger-bitcoin');
74
76
  }
75
77
  catch (error) {
76
78
  void error;
77
- throw new Error('Could not import "ledger-bitcoin". This is a peer dependency and needs to be installed explicitly. Please run "npm install ledger-bitcoin" to use Ledger Hardware Wallet functionality.');
79
+ throw new Error('Could not import "@ledgerhq/ledger-bitcoin". This is a peer dependency and needs to be installed explicitly. Please run "npm install @ledgerhq/ledger-bitcoin" to use Ledger Hardware Wallet functionality.');
78
80
  }
79
81
  const { AppClient } = ledgerBitcoinModule;
80
82
  if (ledgerClient !== undefined && !(ledgerClient instanceof AppClient)) {
@@ -129,8 +131,10 @@ function isLedgerStandard({ ledgerTemplate, keyRoots, network = bitcoinjs_lib_1.
129
131
  const originPath = keyRoots[0]?.match(re_1.reOriginPath)?.[1];
130
132
  if (!originPath)
131
133
  return false;
132
- //Network is the 6th character: /44'/0'
133
- if (originPath[5] !== (network === bitcoinjs_lib_1.networks.bitcoin ? '0' : '1'))
134
+ const originCoinType = originPath.match(/^\/\d+'\/([01])'/)?.[1];
135
+ if (!originCoinType)
136
+ return false;
137
+ if (originCoinType !== `${(0, networkUtils_1.coinTypeFromNetwork)(network)}`)
134
138
  return false;
135
139
  if ((ledgerTemplate === 'pkh(@0/**)' &&
136
140
  originPath.match(/^\/44'\/[01]'\/(\d+)'$/)) ||
@@ -143,32 +147,20 @@ function isLedgerStandard({ ledgerTemplate, keyRoots, network = bitcoinjs_lib_1.
143
147
  return true;
144
148
  return false;
145
149
  }
146
- /** @overload */
147
- async function getLedgerMasterFingerPrint({ ledgerClient, ledgerState, ledgerManager }) {
148
- if (ledgerManager && (ledgerClient || ledgerState))
149
- throw new Error(`ledgerClient and ledgerState have been deprecated`);
150
- if (ledgerManager)
151
- ({ ledgerClient, ledgerState } = ledgerManager);
152
- if (!ledgerClient || !ledgerState)
153
- throw new Error(`Could not retrieve ledgerClient or ledgerState`);
150
+ async function getLedgerMasterFingerPrint({ ledgerManager }) {
151
+ const { ledgerClient, ledgerState } = ledgerManager;
154
152
  const { AppClient } = (await importAndValidateLedgerBitcoin(ledgerClient));
155
153
  if (!(ledgerClient instanceof AppClient))
156
154
  throw new Error(`Error: pass a valid ledgerClient`);
157
155
  let masterFingerprint = ledgerState.masterFingerprint;
158
156
  if (!masterFingerprint) {
159
- masterFingerprint = Buffer.from(await ledgerClient.getMasterFingerprint(), 'hex');
157
+ masterFingerprint = (0, uint8array_tools_1.fromHex)(await ledgerClient.getMasterFingerprint());
160
158
  ledgerState.masterFingerprint = masterFingerprint;
161
159
  }
162
160
  return masterFingerprint;
163
161
  }
164
- /** @hidden */
165
- async function getLedgerXpub({ originPath, ledgerClient, ledgerState, ledgerManager }) {
166
- if (ledgerManager && (ledgerClient || ledgerState))
167
- throw new Error(`ledgerClient and ledgerState have been deprecated`);
168
- if (ledgerManager)
169
- ({ ledgerClient, ledgerState } = ledgerManager);
170
- if (!ledgerClient || !ledgerState)
171
- throw new Error(`Could not retrieve ledgerClient or ledgerState`);
162
+ async function getLedgerXpub({ originPath, ledgerManager }) {
163
+ const { ledgerClient, ledgerState } = ledgerManager;
172
164
  const { AppClient } = (await importAndValidateLedgerBitcoin(ledgerClient));
173
165
  if (!(ledgerClient instanceof AppClient))
174
166
  throw new Error(`Error: pass a valid ledgerClient`);
@@ -199,7 +191,7 @@ async function getLedgerXpub({ originPath, ledgerClient, ledgerState, ledgerMana
199
191
  * All considerations in the header of this file are applied
200
192
  */
201
193
  async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
202
- const { ledgerState, ledgerClient, ecc, network } = ledgerManager;
194
+ const { ledgerState, ecc, network } = ledgerManager;
203
195
  const { Output } = (0, descriptors_1.DescriptorsFactory)(ecc);
204
196
  const input = psbt.data.inputs[index];
205
197
  if (!input)
@@ -209,28 +201,31 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
209
201
  const vout = psbt.txInputs[index]?.index;
210
202
  if (vout === undefined)
211
203
  throw new Error(`Could not extract vout from nonWitnessUtxo for input ${index}.`);
212
- scriptPubKey = bitcoinjs_lib_1.Transaction.fromBuffer(input.nonWitnessUtxo).outs[vout]
213
- ?.script;
204
+ const nonWitnessScript = bitcoinjs_lib_1.Transaction.fromBuffer(input.nonWitnessUtxo).outs[vout]?.script;
205
+ scriptPubKey = nonWitnessScript;
214
206
  }
215
207
  else if (input.witnessUtxo) {
216
208
  scriptPubKey = input.witnessUtxo.script;
217
209
  }
218
210
  if (!scriptPubKey)
219
211
  throw new Error(`Could not retrieve scriptPubKey for input ${index}.`);
220
- const bip32Derivations = input.bip32Derivation;
221
- if (!bip32Derivations || !bip32Derivations.length)
222
- throw new Error(`Input ${index} does not contain bip32 derivations.`);
212
+ const keyDerivations = [
213
+ ...(input.bip32Derivation || []),
214
+ ...(input.tapBip32Derivation || [])
215
+ ];
216
+ if (keyDerivations.length === 0)
217
+ throw new Error(`Input ${index} does not contain bip32 or tapBip32 derivations.`);
223
218
  const ledgerMasterFingerprint = await getLedgerMasterFingerPrint({
224
219
  ledgerManager
225
220
  });
226
- for (const bip32Derivation of bip32Derivations) {
221
+ for (const keyDerivation of keyDerivations) {
227
222
  //get the keyRoot and keyPath. If it matches one of our policies then
228
223
  //we are still not sure this is the policy that must be used yet
229
224
  //So we must use the template and the keyRoot of each policy and compute the
230
225
  //scriptPubKey:
231
- if (Buffer.compare(bip32Derivation.masterFingerprint, ledgerMasterFingerprint) === 0) {
226
+ if ((0, uint8array_tools_1.compare)(keyDerivation.masterFingerprint, ledgerMasterFingerprint) === 0) {
232
227
  // Match /m followed by n consecutive hardened levels and then 2 consecutive unhardened levels:
233
- const match = bip32Derivation.path.match(/m((\/\d+['hH])*)(\/\d+\/\d+)?/);
228
+ const match = keyDerivation.path.match(/m((\/\d+['hH])*)(\/\d+\/\d+)?/);
234
229
  const originPath = match ? match[1] : undefined; //n consecutive hardened levels
235
230
  const keyPath = match ? match[3] : undefined; //2 unhardened levels or undefined
236
231
  if (originPath && keyPath) {
@@ -239,7 +234,7 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
239
234
  throw new Error(`keyPath ${keyPath} incorrectly extracted`);
240
235
  const change = parseInt(strChange, 10);
241
236
  const index = parseInt(strIndex, 10);
242
- const coinType = network === bitcoinjs_lib_1.networks.bitcoin ? 0 : 1;
237
+ const coinType = (0, networkUtils_1.coinTypeFromNetwork)(network);
243
238
  //standard policy candidate. This policy will be added to the pool
244
239
  //of policies below and check if it produces the correct scriptPubKey
245
240
  let standardPolicy;
@@ -254,15 +249,11 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
254
249
  ? 'tr(@0/**)'
255
250
  : undefined;
256
251
  if (standardTemplate) {
257
- const xpub = await getLedgerXpub({
258
- originPath,
259
- ledgerClient,
260
- ledgerState
261
- });
252
+ const xpub = await getLedgerXpub({ originPath, ledgerManager });
262
253
  standardPolicy = {
263
254
  ledgerTemplate: standardTemplate,
264
255
  keyRoots: [
265
- `[${ledgerMasterFingerprint.toString('hex')}${originPath}]${xpub}`
256
+ `[${(0, uint8array_tools_1.toHex)(ledgerMasterFingerprint)}${originPath}]${xpub}`
266
257
  ]
267
258
  };
268
259
  }
@@ -277,18 +268,16 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
277
268
  // Replace change (making sure the value in the change level for the
278
269
  // template of the policy meets the change in bip32Derivation):
279
270
  descriptor = descriptor.replace(/\/\*\*/g, `/<0;1>/*`);
280
- const regExpMN = new RegExp(`/<(\\d+);(\\d+)>`, 'g');
281
- let matchMN;
282
- while (descriptor && (matchMN = regExpMN.exec(descriptor)) !== null) {
283
- const [M, N] = [
284
- parseInt(matchMN[1], 10),
285
- parseInt(matchMN[2], 10)
286
- ];
271
+ let tupleMismatch = false;
272
+ descriptor = descriptor.replace(/\/<(\d+);(\d+)>/g, (token, strM, strN) => {
273
+ const [M, N] = [parseInt(strM, 10), parseInt(strN, 10)];
287
274
  if (M === change || N === change)
288
- descriptor = descriptor.replace(`/<${M};${N}>`, `/${change}`);
289
- else
290
- descriptor = undefined;
291
- }
275
+ return `/${change}`;
276
+ tupleMismatch = true;
277
+ return token;
278
+ });
279
+ if (tupleMismatch)
280
+ descriptor = undefined;
292
281
  if (descriptor) {
293
282
  // Replace index:
294
283
  descriptor = descriptor.replace(/\/\*/g, `/${index}`);
@@ -310,8 +299,12 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
310
299
  else
311
300
  descriptor = undefined;
312
301
  }
313
- else
314
- descriptor = undefined;
302
+ else {
303
+ // Keys without origin info are treated as external by Ledger
304
+ // and are allowed in policy matching.
305
+ if (descriptor)
306
+ descriptor = descriptor.replace(new RegExp(`@${i}`, 'g'), keyRoot);
307
+ }
315
308
  }
316
309
  //verify the scriptPubKey from the input vs. the one obtained from
317
310
  //the policy after having filled in the keyPath in the template
@@ -320,7 +313,7 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
320
313
  descriptor,
321
314
  network
322
315
  }).getScriptPubKey();
323
- if (Buffer.compare(policyScriptPubKey, scriptPubKey) === 0) {
316
+ if ((0, uint8array_tools_1.compare)(policyScriptPubKey, scriptPubKey) === 0) {
324
317
  return policy;
325
318
  }
326
319
  }
@@ -358,24 +351,73 @@ async function ledgerPolicyFromPsbtInput({ ledgerManager, psbt, index }) {
358
351
  * This function takes into account all the considerations regarding Ledger
359
352
  * policy implementation details expressed in the header of this file.
360
353
  */
361
- async function ledgerPolicyFromOutput({ output, ledgerClient, ledgerState }) {
362
- const expandedExpression = output.expand().expandedExpression;
363
- const expansionMap = output.expand().expansionMap;
354
+ async function ledgerPolicyFromOutput({ output, ledgerManager }) {
355
+ const expanded = output.expand();
356
+ let expandedExpression = expanded.expandedExpression;
357
+ const expansionMap = expanded.expansionMap
358
+ ? { ...expanded.expansionMap }
359
+ : undefined;
360
+ // Taproot script-path keys are expanded in tapTreeInfo leaf expansion maps,
361
+ // not in the top-level expansionMap. For ledger policy derivation we remap
362
+ // leaf-local placeholders to global placeholders and merge all leaf keys into
363
+ // the top-level expansionMap.
364
+ if (expandedExpression?.startsWith('tr(@0,') &&
365
+ expansionMap &&
366
+ expanded.tapTreeInfo) {
367
+ const keyExpressionToGlobalPlaceholder = new Map(Object.entries(expansionMap).map(([placeholder, keyInfo]) => [
368
+ keyInfo.keyExpression,
369
+ placeholder
370
+ ]));
371
+ let nextPlaceholderIndex = Object.keys(expansionMap).length;
372
+ const globalPlaceholderFor = (keyInfo) => {
373
+ const existing = keyExpressionToGlobalPlaceholder.get(keyInfo.keyExpression);
374
+ if (existing)
375
+ return existing;
376
+ const placeholder = `@${nextPlaceholderIndex}`;
377
+ nextPlaceholderIndex += 1;
378
+ keyExpressionToGlobalPlaceholder.set(keyInfo.keyExpression, placeholder);
379
+ expansionMap[placeholder] = keyInfo;
380
+ return placeholder;
381
+ };
382
+ const remapTapTree = (node) => {
383
+ if ('expression' in node) {
384
+ // Prefer descriptor-level expanded expression for policy templates so
385
+ // script expressions (e.g. sortedmulti_a) are preserved. Fall back to
386
+ // expandedMiniscript for older metadata.
387
+ let remappedMiniscript = node.expandedExpression ?? node.expandedMiniscript;
388
+ if (!remappedMiniscript)
389
+ throw new Error(`Error: taproot leaf expansion not available`);
390
+ const localEntries = Object.entries(node.expansionMap);
391
+ const localToGlobalPlaceholder = new Map();
392
+ for (const [localPlaceholder, keyInfo] of localEntries) {
393
+ const globalPlaceholder = globalPlaceholderFor(keyInfo);
394
+ localToGlobalPlaceholder.set(localPlaceholder, globalPlaceholder);
395
+ }
396
+ remappedMiniscript = remappedMiniscript.replace(/@\d+/g, placeholder => localToGlobalPlaceholder.get(placeholder) ?? placeholder);
397
+ return remappedMiniscript;
398
+ }
399
+ return `{${remapTapTree(node.left)},${remapTapTree(node.right)}}`;
400
+ };
401
+ expandedExpression = `tr(@0,${remapTapTree(expanded.tapTreeInfo)})`;
402
+ }
364
403
  if (!expandedExpression || !expansionMap)
365
404
  throw new Error(`Error: invalid output`);
366
405
  const ledgerMasterFingerprint = await getLedgerMasterFingerPrint({
367
- ledgerClient,
368
- ledgerState
406
+ ledgerManager
407
+ });
408
+ // Keep placeholders in numeric order (@0, @1, @2, ...). This avoids
409
+ // lexicographic pitfalls like @10 being ordered before @2.
410
+ const allKeys = Object.keys(expansionMap).sort((a, b) => {
411
+ const aIndex = Number(a.slice(1));
412
+ const bIndex = Number(b.slice(1));
413
+ if (Number.isNaN(aIndex) || Number.isNaN(bIndex))
414
+ return a.localeCompare(b);
415
+ return aIndex - bIndex;
369
416
  });
370
- //It's important to have keys sorted in ascii order. keys
371
- //are of this type: @0, @1, @2, .... and they also appear in the expandedExpression
372
- //in ascending ascii order. Note that Object.keys(expansionMap ) does not ensure
373
- //that the order is respected and so we force it.
374
- const allKeys = Object.keys(expansionMap).sort();
375
417
  const ledgerKeys = allKeys.filter(key => {
376
418
  const masterFingerprint = expansionMap[key]?.masterFingerprint;
377
419
  return (masterFingerprint &&
378
- Buffer.compare(masterFingerprint, ledgerMasterFingerprint) === 0);
420
+ (0, uint8array_tools_1.compare)(masterFingerprint, ledgerMasterFingerprint) === 0);
379
421
  });
380
422
  if (ledgerKeys.length === 0)
381
423
  return null;
@@ -392,8 +434,8 @@ async function ledgerPolicyFromOutput({ output, ledgerClient, ledgerState }) {
392
434
  if (!/^\/[01]\/\d+$/.test(keyPath))
393
435
  throw new Error(`Error: key paths must be /<1;0>/index, where change is 1 or 0 and index >= 0`);
394
436
  const keyRoots = [];
395
- let ledgerTemplate = expandedExpression;
396
- allKeys.forEach(key => {
437
+ const placeholderToLedgerPlaceholder = new Map();
438
+ allKeys.forEach((key, index) => {
397
439
  if (key !== ledgerKey) {
398
440
  //This block here only does data integrity assertions:
399
441
  const otherKeyInfo = expansionMap[key];
@@ -409,55 +451,46 @@ async function ledgerPolicyFromOutput({ output, ledgerClient, ledgerState }) {
409
451
  throw new Error(`Error: all keyPaths must be the same for Ledger being able to sign: ${otherKeyInfo.keyPath} !== ${keyPath}`);
410
452
  }
411
453
  }
412
- ledgerTemplate = ledgerTemplate.replaceAll(key, `@${keyRoots.length}/**`);
454
+ placeholderToLedgerPlaceholder.set(key, `@${index}/**`);
413
455
  const keyInfo = expansionMap[key];
414
456
  if (keyInfo.masterFingerprint && keyInfo.originPath)
415
- keyRoots.push(`[${keyInfo.masterFingerprint?.toString('hex')}${keyInfo.originPath}]${keyInfo?.bip32?.neutered().toBase58()}`);
457
+ keyRoots.push(`[${(0, uint8array_tools_1.toHex)(keyInfo.masterFingerprint)}${keyInfo.originPath}]${keyInfo?.bip32?.neutered().toBase58()}`);
416
458
  else
417
459
  keyRoots.push(`${keyInfo?.bip32?.neutered().toBase58()}`);
418
460
  });
461
+ const ledgerTemplate = expandedExpression.replace(/@\d+/g, placeholder => placeholderToLedgerPlaceholder.get(placeholder) ?? placeholder);
419
462
  return { ledgerTemplate, keyRoots };
420
463
  }
421
464
  /**
422
- * To be removed in v3.0 and replaced by a version that does not accept
423
- * descriptors
424
- * @overload
425
- **/
426
- async function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, ledgerManager, policyName }) {
427
- if (typeof descriptor !== 'string' && ledgerManager)
428
- throw new Error(`Invalid usage: descriptor must be a string`);
429
- if (ledgerManager && (ledgerClient || ledgerState))
430
- throw new Error(`Invalid usage: either ledgerManager or ledgerClient + ledgerState`);
431
- if (ledgerManager)
432
- ({ ledgerClient, ledgerState } = ledgerManager);
433
- if (!ledgerClient)
434
- throw new Error(`ledgerManager not provided`);
435
- if (!ledgerState)
436
- throw new Error(`ledgerManager not provided`);
465
+ * Registers a policy based on a provided descriptor.
466
+ *
467
+ * This function will:
468
+ * 1. Store the policy in `ledgerState` inside the `ledgerManager`.
469
+ * 2. Avoid re-registering if the policy was previously registered.
470
+ * 3. Skip registration if the policy is considered "standard".
471
+ *
472
+ * It's important to understand the nature of the Ledger Policy being registered:
473
+ * - While a descriptor might point to a specific output index of a particular change address,
474
+ * the corresponding Ledger Policy abstracts this and represents potential outputs for
475
+ * all addresses (both external and internal).
476
+ * - This means that the registered Ledger Policy is a generalized version of the descriptor,
477
+ * not assuming specific values for the keyPath.
478
+ */
479
+ async function registerLedgerWallet({ descriptor, ledgerManager, policyName }) {
480
+ const { ledgerClient, ledgerState, ecc, network } = ledgerManager;
437
481
  const { WalletPolicy, AppClient } = (await importAndValidateLedgerBitcoin(ledgerClient));
438
482
  if (!(ledgerClient instanceof AppClient))
439
483
  throw new Error(`Error: pass a valid ledgerClient`);
440
- let output;
441
- if (typeof descriptor === 'string') {
442
- if (!ledgerManager)
443
- throw new Error(`ledgerManager not provided`);
444
- const { Output } = (0, descriptors_1.DescriptorsFactory)(ledgerManager.ecc);
445
- output = new Output({
446
- descriptor,
447
- ...(descriptor.includes('*') ? { index: 0 } : {}), //if ranged set any index
448
- network: ledgerManager.network
449
- });
450
- }
451
- else
452
- output = descriptor;
453
- if (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState }))
454
- return;
455
- const result = await ledgerPolicyFromOutput({
456
- output,
457
- ledgerClient,
458
- ledgerState
484
+ const { Output } = (0, descriptors_1.DescriptorsFactory)(ecc);
485
+ const output = new Output({
486
+ descriptor,
487
+ ...(descriptor.includes('*') ? { index: 0 } : {}), //if ranged set any index
488
+ network
459
489
  });
460
- if (await ledgerPolicyFromStandard({ output, ledgerClient, ledgerState }))
490
+ if (await ledgerPolicyFromStandard({ output, ledgerManager }))
491
+ return;
492
+ const result = await ledgerPolicyFromOutput({ output, ledgerManager });
493
+ if (await ledgerPolicyFromStandard({ output, ledgerManager }))
461
494
  return;
462
495
  if (!result)
463
496
  throw new Error(`Error: output does not have a ledger input`);
@@ -466,11 +499,7 @@ async function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, led
466
499
  ledgerState.policies = [];
467
500
  let walletPolicy, policyHmac;
468
501
  //Search in ledgerState first
469
- const policy = await ledgerPolicyFromState({
470
- output,
471
- ledgerClient,
472
- ledgerState
473
- });
502
+ const policy = await ledgerPolicyFromState({ output, ledgerManager });
474
503
  if (policy) {
475
504
  if (policy.policyName !== policyName)
476
505
  throw new Error(`Error: policy was already registered with a different name: ${policy.policyName}`);
@@ -493,11 +522,10 @@ async function registerLedgerWallet({ descriptor, ledgerClient, ledgerState, led
493
522
  /**
494
523
  * Retrieve a standard ledger policy or null if it does correspond.
495
524
  **/
496
- async function ledgerPolicyFromStandard({ output, ledgerClient, ledgerState }) {
525
+ async function ledgerPolicyFromStandard({ output, ledgerManager }) {
497
526
  const result = await ledgerPolicyFromOutput({
498
527
  output,
499
- ledgerClient,
500
- ledgerState
528
+ ledgerManager
501
529
  });
502
530
  if (!result)
503
531
  throw new Error(`Error: descriptor does not have a ledger input`);
@@ -528,11 +556,11 @@ function comparePolicies(policyA, policyB) {
528
556
  /**
529
557
  * Retrieve a ledger policy from ledgerState or null if it does not exist yet.
530
558
  **/
531
- async function ledgerPolicyFromState({ output, ledgerClient, ledgerState }) {
559
+ async function ledgerPolicyFromState({ output, ledgerManager }) {
560
+ const { ledgerState } = ledgerManager;
532
561
  const result = await ledgerPolicyFromOutput({
533
562
  output,
534
- ledgerClient,
535
- ledgerState
563
+ ledgerManager
536
564
  });
537
565
  if (!result)
538
566
  throw new Error(`Error: output does not have a ledger input`);
@@ -1,7 +1,7 @@
1
1
  import { Network } from 'bitcoinjs-lib';
2
2
  import type { ECPairAPI } from 'ecpair';
3
3
  import type { BIP32API } from 'bip32';
4
- import type { PartialSig } from 'bip174/src/lib/interfaces';
4
+ import type { PartialSig } from 'bip174';
5
5
  import type { Preimage, TimeConstraints, ExpansionMap } from './types';
6
6
  /**
7
7
  * Expand a miniscript to a generalized form using variables instead of key
@@ -21,10 +21,11 @@ export declare function expandMiniscript({ miniscript, isSegwit, isTaproot, netw
21
21
  expandedMiniscript: string;
22
22
  expansionMap: ExpansionMap;
23
23
  };
24
- export declare function miniscript2Script({ expandedMiniscript, expansionMap }: {
24
+ export declare function miniscript2Script({ expandedMiniscript, expansionMap, tapscript }: {
25
25
  expandedMiniscript: string;
26
26
  expansionMap: ExpansionMap;
27
- }): Buffer;
27
+ tapscript?: boolean;
28
+ }): Uint8Array;
28
29
  /**
29
30
  * Assumptions:
30
31
  * The attacker does not have access to any of the private keys of public keys
@@ -36,19 +37,32 @@ export declare function miniscript2Script({ expandedMiniscript, expansionMap }:
36
37
  * Pass timeConstraints to search for the first solution with this nLockTime and
37
38
  * nSequence. Throw if no solution is possible using these constraints.
38
39
  *
40
+ * Time constraints are used to keep the chosen satisfaction stable between the
41
+ * planning pass (fake signatures) and the signing pass (real signatures).
42
+ * We run the satisfier once with fake signatures to discover the implied
43
+ * nLockTime/nSequence without requiring user signatures. If real signatures
44
+ * had the same length, the satisfier would typically pick the same
45
+ * minimal-weight solution again. But ECDSA signature sizes can vary (71–73
46
+ * bytes), which may change which solution is considered "smallest".
47
+ *
48
+ * Passing the previously derived timeConstraints in the second pass forces the
49
+ * same solution to be selected, ensuring locktime/sequence do not change
50
+ * between planning and finalization.
51
+ *
39
52
  * Don't pass timeConstraints (this is the default) if you want to get the
40
53
  * smallest size solution altogether.
41
54
  *
42
55
  * If a solution is not found this function throws.
43
56
  */
44
- export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures, preimages, timeConstraints }: {
57
+ export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures, preimages, timeConstraints, tapscript }: {
45
58
  expandedMiniscript: string;
46
59
  expansionMap: ExpansionMap;
47
60
  signatures?: PartialSig[];
48
61
  preimages?: Preimage[];
49
62
  timeConstraints?: TimeConstraints;
63
+ tapscript?: boolean;
50
64
  }): {
51
- scriptSatisfaction: Buffer;
65
+ scriptSatisfaction: Uint8Array;
52
66
  nLockTime: number | undefined;
53
67
  nSequence: number | undefined;
54
68
  };
@@ -78,7 +92,7 @@ export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, si
78
92
  * However, the `0` number is an edge case that we specially handle with this
79
93
  * function.
80
94
  *
81
- * bitcoinjs-lib's `bscript.number.encode(0)` produces an empty Buffer.
95
+ * bitcoinjs-lib's `bscript.number.encode(0)` produces an empty array.
82
96
  * This is what the Bitcoin interpreter does and it is what `script.number.encode` was
83
97
  * implemented to do.
84
98
  *