@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/README.md +173 -77
- package/dist/applyPR2137.js +36 -17
- package/dist/bitcoinjs-lib-internals.d.ts +10 -0
- package/dist/bitcoinjs-lib-internals.js +18 -0
- package/dist/descriptors.d.ts +161 -392
- package/dist/descriptors.js +608 -283
- package/dist/index.d.ts +2 -29
- package/dist/index.js +0 -14
- package/dist/keyExpressions.d.ts +4 -13
- package/dist/keyExpressions.js +15 -18
- package/dist/ledger.d.ts +14 -37
- package/dist/ledger.js +147 -119
- package/dist/miniscript.d.ts +20 -6
- package/dist/miniscript.js +64 -17
- package/dist/multipath.d.ts +13 -0
- package/dist/multipath.js +76 -0
- package/dist/networkUtils.d.ts +3 -0
- package/dist/networkUtils.js +16 -0
- package/dist/parseUtils.d.ts +7 -0
- package/dist/parseUtils.js +46 -0
- package/dist/psbt.d.ts +17 -13
- package/dist/psbt.js +34 -50
- package/dist/resourceLimits.d.ts +25 -0
- package/dist/resourceLimits.js +89 -0
- package/dist/scriptExpressions.d.ts +29 -77
- package/dist/scriptExpressions.js +19 -16
- package/dist/signers.d.ts +1 -21
- package/dist/signers.js +85 -129
- package/dist/tapMiniscript.d.ts +215 -0
- package/dist/tapMiniscript.js +515 -0
- package/dist/tapTree.d.ts +86 -0
- package/dist/tapTree.js +167 -0
- package/dist/types.d.ts +52 -6
- package/package.json +13 -13
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
|
-
|
|
133
|
-
if (
|
|
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
|
-
|
|
147
|
-
|
|
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 =
|
|
157
|
+
masterFingerprint = (0, uint8array_tools_1.fromHex)(await ledgerClient.getMasterFingerprint());
|
|
160
158
|
ledgerState.masterFingerprint = masterFingerprint;
|
|
161
159
|
}
|
|
162
160
|
return masterFingerprint;
|
|
163
161
|
}
|
|
164
|
-
|
|
165
|
-
|
|
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,
|
|
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
|
-
|
|
213
|
-
|
|
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
|
|
221
|
-
|
|
222
|
-
|
|
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
|
|
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 (
|
|
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 =
|
|
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 =
|
|
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
|
-
`[${
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
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
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
362
|
-
const
|
|
363
|
-
|
|
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
|
-
|
|
368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
454
|
+
placeholderToLedgerPlaceholder.set(key, `@${index}/**`);
|
|
413
455
|
const keyInfo = expansionMap[key];
|
|
414
456
|
if (keyInfo.masterFingerprint && keyInfo.originPath)
|
|
415
|
-
keyRoots.push(`[${keyInfo.masterFingerprint
|
|
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
|
-
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
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,
|
|
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,
|
|
525
|
+
async function ledgerPolicyFromStandard({ output, ledgerManager }) {
|
|
497
526
|
const result = await ledgerPolicyFromOutput({
|
|
498
527
|
output,
|
|
499
|
-
|
|
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,
|
|
559
|
+
async function ledgerPolicyFromState({ output, ledgerManager }) {
|
|
560
|
+
const { ledgerState } = ledgerManager;
|
|
532
561
|
const result = await ledgerPolicyFromOutput({
|
|
533
562
|
output,
|
|
534
|
-
|
|
535
|
-
ledgerState
|
|
563
|
+
ledgerManager
|
|
536
564
|
});
|
|
537
565
|
if (!result)
|
|
538
566
|
throw new Error(`Error: output does not have a ledger input`);
|
package/dist/miniscript.d.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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:
|
|
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
|
|
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
|
*
|