@bitcoinerlab/descriptors 2.3.6 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +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 +512 -281
- 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 +118 -100
- package/dist/miniscript.d.ts +20 -6
- package/dist/miniscript.js +59 -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/stackResourceLimits.d.ts +17 -0
- package/dist/stackResourceLimits.js +35 -0
- package/dist/tapMiniscript.d.ts +193 -0
- package/dist/tapMiniscript.js +428 -0
- package/dist/tapTree.d.ts +76 -0
- package/dist/tapTree.js +163 -0
- package/dist/types.d.ts +46 -6
- package/package.json +13 -13
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.trLedger = exports.wpkhLedger = exports.shWpkhLedger = exports.pkhLedger = exports.trBIP32 = exports.wpkhBIP32 = exports.shWpkhBIP32 = exports.pkhBIP32 = void 0;
|
|
4
4
|
const bitcoinjs_lib_1 = require("bitcoinjs-lib");
|
|
5
5
|
const keyExpressions_1 = require("./keyExpressions");
|
|
6
|
+
const networkUtils_1 = require("./networkUtils");
|
|
6
7
|
function assertStandardKeyPath(keyPath) {
|
|
7
8
|
// Regular expression to match "/change/index" or "/change/*" format
|
|
8
9
|
const regex = /^\/[01]\/(\d+|\*)$/;
|
|
@@ -25,7 +26,7 @@ function standardExpressionsBIP32Maker(purpose, scriptTemplate) {
|
|
|
25
26
|
* - `{change:0, index:'*'}`.
|
|
26
27
|
*/
|
|
27
28
|
function standardScriptExpressionBIP32({ masterNode, network = bitcoinjs_lib_1.networks.bitcoin, keyPath, account, change, index, isPublic = true }) {
|
|
28
|
-
const originPath = `/${purpose}'/${
|
|
29
|
+
const originPath = `/${purpose}'/${(0, networkUtils_1.coinTypeFromNetwork)(network)}'/${account}'`;
|
|
29
30
|
if (keyPath !== undefined)
|
|
30
31
|
assertStandardKeyPath(keyPath);
|
|
31
32
|
const keyExpression = (0, keyExpressions_1.keyExpressionBIP32)({
|
|
@@ -49,24 +50,26 @@ exports.wpkhBIP32 = standardExpressionsBIP32Maker(84, 'wpkh(KEYEXPRESSION)');
|
|
|
49
50
|
/** @function */
|
|
50
51
|
exports.trBIP32 = standardExpressionsBIP32Maker(86, 'tr(KEYEXPRESSION)');
|
|
51
52
|
function standardExpressionsLedgerMaker(purpose, scriptTemplate) {
|
|
52
|
-
/**
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Computes the standard descriptor based on given parameters.
|
|
55
|
+
*
|
|
56
|
+
* You can define the output location either by:
|
|
57
|
+
* - Providing the full `keyPath` (e.g., "/0/2").
|
|
58
|
+
* OR
|
|
59
|
+
* - Specifying the `change` and `index` values separately (e.g., `{change:0, index:2}`).
|
|
60
|
+
*
|
|
61
|
+
* For ranged indexing, the `index` can be set as a wildcard '*'. For example:
|
|
62
|
+
* - `keyPath="/0/*"`
|
|
63
|
+
* OR
|
|
64
|
+
* - `{change:0, index:'*'}`.
|
|
65
|
+
*/
|
|
66
|
+
async function standardScriptExpressionLedger({ ledgerManager, account, keyPath, change, index }) {
|
|
67
|
+
const { network } = ledgerManager;
|
|
68
|
+
const originPath = `/${purpose}'/${(0, networkUtils_1.coinTypeFromNetwork)(network)}'/${account}'`;
|
|
65
69
|
if (keyPath !== undefined)
|
|
66
70
|
assertStandardKeyPath(keyPath);
|
|
67
71
|
const keyExpression = await (0, keyExpressions_1.keyExpressionLedger)({
|
|
68
|
-
|
|
69
|
-
ledgerState,
|
|
72
|
+
ledgerManager,
|
|
70
73
|
originPath,
|
|
71
74
|
keyPath,
|
|
72
75
|
change,
|
package/dist/signers.d.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { Psbt } from 'bitcoinjs-lib';
|
|
2
2
|
import type { ECPairInterface } from 'ecpair';
|
|
3
3
|
import type { BIP32Interface } from 'bip32';
|
|
4
|
-
import
|
|
5
|
-
import { LedgerState, LedgerManager } from './ledger';
|
|
4
|
+
import { LedgerManager } from './ledger';
|
|
6
5
|
/**
|
|
7
6
|
* Signs a specific input of a PSBT with an ECPair.
|
|
8
7
|
*
|
|
@@ -69,16 +68,6 @@ export declare function signInputLedger({ psbt, index, ledgerManager }: {
|
|
|
69
68
|
index: number;
|
|
70
69
|
ledgerManager: LedgerManager;
|
|
71
70
|
}): Promise<void>;
|
|
72
|
-
/**
|
|
73
|
-
* @hidden
|
|
74
|
-
*/
|
|
75
|
-
export declare function signInputLedger({ psbt, index, descriptor, ledgerClient, ledgerState }: {
|
|
76
|
-
psbt: Psbt;
|
|
77
|
-
index: number;
|
|
78
|
-
descriptor: DescriptorInstance;
|
|
79
|
-
ledgerClient: unknown;
|
|
80
|
-
ledgerState: LedgerState;
|
|
81
|
-
}): Promise<void>;
|
|
82
71
|
/**
|
|
83
72
|
* Signs the inputs of the `psbt` where the keys are controlled by a Ledger
|
|
84
73
|
* device.
|
|
@@ -93,12 +82,3 @@ export declare function signLedger({ psbt, ledgerManager }: {
|
|
|
93
82
|
psbt: Psbt;
|
|
94
83
|
ledgerManager: LedgerManager;
|
|
95
84
|
}): Promise<void>;
|
|
96
|
-
/**
|
|
97
|
-
* @hidden
|
|
98
|
-
*/
|
|
99
|
-
export declare function signLedger({ psbt, descriptors, ledgerClient, ledgerState }: {
|
|
100
|
-
psbt: Psbt;
|
|
101
|
-
descriptors: DescriptorInstance[];
|
|
102
|
-
ledgerClient: unknown;
|
|
103
|
-
ledgerState: LedgerState;
|
|
104
|
-
}): Promise<void>;
|
package/dist/signers.js
CHANGED
|
@@ -8,8 +8,7 @@ exports.signInputBIP32 = signInputBIP32;
|
|
|
8
8
|
exports.signBIP32 = signBIP32;
|
|
9
9
|
exports.signInputLedger = signInputLedger;
|
|
10
10
|
exports.signLedger = signLedger;
|
|
11
|
-
const
|
|
12
|
-
const bip341_1 = require("bitcoinjs-lib/src/payments/bip341");
|
|
11
|
+
const bitcoinjs_lib_internals_1 = require("./bitcoinjs-lib-internals");
|
|
13
12
|
const ledger_1 = require("./ledger");
|
|
14
13
|
const applyPR2137_1 = require("./applyPR2137");
|
|
15
14
|
function range(n) {
|
|
@@ -39,12 +38,12 @@ function signInputECPair({ psbt, index, ecpair }) {
|
|
|
39
38
|
const input = psbt.data.inputs[index];
|
|
40
39
|
if (!input)
|
|
41
40
|
throw new Error('Invalid index');
|
|
42
|
-
if ((0,
|
|
41
|
+
if ((0, bitcoinjs_lib_internals_1.isTaprootInput)(input)) {
|
|
43
42
|
// If script-path (tapLeafScript present) -> DO NOT TWEAK
|
|
44
43
|
if (input.tapLeafScript && input.tapLeafScript.length > 0)
|
|
45
44
|
psbt.signInput(index, ecpair);
|
|
46
45
|
else {
|
|
47
|
-
const hash = (0,
|
|
46
|
+
const hash = (0, bitcoinjs_lib_internals_1.tapTweakHash)(ecpair.publicKey.slice(1, 33), undefined);
|
|
48
47
|
const tweakedEcpair = ecpair.tweak(hash);
|
|
49
48
|
psbt.signInput(index, tweakedEcpair);
|
|
50
49
|
}
|
|
@@ -101,136 +100,91 @@ function signBIP32({ psbt, masterNode }) {
|
|
|
101
100
|
}
|
|
102
101
|
const ledgerSignaturesForInputIndex = (index, ledgerSignatures) => ledgerSignatures
|
|
103
102
|
.filter(([i]) => i === index)
|
|
104
|
-
.map(([_i, partialSignature]) =>
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
throw new Error(`Error: pass a valid ledgerClient`);
|
|
130
|
-
let ledgerSignatures;
|
|
131
|
-
if (ledgerManager) {
|
|
132
|
-
if (psbt.data.inputs[index]?.tapInternalKey)
|
|
133
|
-
throw new Error('Taproot inputs not yet supported for the Ledger device');
|
|
134
|
-
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
135
|
-
psbt,
|
|
136
|
-
index,
|
|
137
|
-
ledgerManager
|
|
138
|
-
});
|
|
139
|
-
if (!policy)
|
|
140
|
-
throw new Error(`Error: the ledger cannot sign this pstb input`);
|
|
141
|
-
if (policy.policyName && policy.policyHmac && policy.policyId) {
|
|
142
|
-
//non-standard policy
|
|
143
|
-
const walletPolicy = new WalletPolicy(policy.policyName, policy.ledgerTemplate, policy.keyRoots);
|
|
144
|
-
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
|
|
103
|
+
.map(([_i, partialSignature]) => partialSignature);
|
|
104
|
+
function addLedgerSignaturesToInput({ psbt, index, ledgerSignatures }) {
|
|
105
|
+
const input = psbt.data.inputs[index];
|
|
106
|
+
if (!input)
|
|
107
|
+
throw new Error(`Error: input ${index} not available`);
|
|
108
|
+
const signatures = ledgerSignaturesForInputIndex(index, ledgerSignatures);
|
|
109
|
+
if (signatures.length === 0)
|
|
110
|
+
throw new Error(`Error: no ledger signatures found for input ${index}`);
|
|
111
|
+
if ((0, bitcoinjs_lib_internals_1.isTaprootInput)(input)) {
|
|
112
|
+
// Ledger returns per-input signatures as [pubkey, signature, tapleafHash?].
|
|
113
|
+
// For taproot we must map them to PSBT taproot fields (not partialSig):
|
|
114
|
+
// - signatures with tapleafHash -> tapScriptSig[] (script-path)
|
|
115
|
+
// - signature without tapleafHash -> tapKeySig (key-path)
|
|
116
|
+
// A taproot input may contain script-path signatures, key-path signature,
|
|
117
|
+
// or both in edge cases; each must be written to its corresponding field.
|
|
118
|
+
const tapScriptSig = signatures
|
|
119
|
+
.filter((sig) => sig.tapleafHash)
|
|
120
|
+
.map((sig) => ({
|
|
121
|
+
pubkey: sig.pubkey,
|
|
122
|
+
signature: sig.signature,
|
|
123
|
+
leafHash: sig.tapleafHash
|
|
124
|
+
}));
|
|
125
|
+
const tapKeySigs = signatures.filter((sig) => !sig.tapleafHash);
|
|
126
|
+
if (tapScriptSig.length > 0) {
|
|
127
|
+
psbt.updateInput(index, { tapScriptSig });
|
|
145
128
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
129
|
+
if (tapKeySigs.length > 1)
|
|
130
|
+
throw new Error(`Error: expected at most one tapKeySig for input ${index}`);
|
|
131
|
+
const tapKeySig = tapKeySigs[0]?.signature;
|
|
132
|
+
if (tapKeySig) {
|
|
133
|
+
psbt.updateInput(index, { tapKeySig });
|
|
149
134
|
}
|
|
135
|
+
if (tapScriptSig.length === 0 && !tapKeySig)
|
|
136
|
+
throw new Error(`Error: no valid taproot ledger signatures found for input ${index}`);
|
|
150
137
|
}
|
|
151
138
|
else {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
ledgerState
|
|
158
|
-
});
|
|
159
|
-
if (!result)
|
|
160
|
-
throw new Error(`Error: output does not have a ledger input`);
|
|
161
|
-
const { ledgerTemplate, keyRoots } = result;
|
|
162
|
-
const standardPolicy = await (0, ledger_1.ledgerPolicyFromStandard)({
|
|
163
|
-
output,
|
|
164
|
-
ledgerClient,
|
|
165
|
-
ledgerState
|
|
166
|
-
});
|
|
167
|
-
if (standardPolicy) {
|
|
168
|
-
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), new DefaultWalletPolicy(ledgerTemplate, keyRoots[0]), null);
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
const policy = await (0, ledger_1.ledgerPolicyFromState)({
|
|
172
|
-
output,
|
|
173
|
-
ledgerClient,
|
|
174
|
-
ledgerState
|
|
175
|
-
});
|
|
176
|
-
if (!policy || !policy.policyName || !policy.policyHmac)
|
|
177
|
-
throw new Error(`Error: the descriptor's policy is not registered`);
|
|
178
|
-
const walletPolicy = new WalletPolicy(policy.policyName, ledgerTemplate, keyRoots);
|
|
179
|
-
ledgerSignatures = await ledgerClient.signPsbt(new PsbtV2().fromBitcoinJS(psbt), walletPolicy, policy.policyHmac);
|
|
180
|
-
}
|
|
139
|
+
const partialSig = signatures.map((sig) => ({
|
|
140
|
+
pubkey: sig.pubkey,
|
|
141
|
+
signature: sig.signature
|
|
142
|
+
}));
|
|
143
|
+
psbt.updateInput(index, { partialSig });
|
|
181
144
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
145
|
+
}
|
|
146
|
+
async function signInputLedger({ psbt, index, ledgerManager }) {
|
|
147
|
+
const { ledgerClient } = ledgerManager;
|
|
148
|
+
const { DefaultWalletPolicy, WalletPolicy, AppClient } = (await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient));
|
|
149
|
+
if (!(ledgerClient instanceof AppClient))
|
|
150
|
+
throw new Error(`Error: pass a valid ledgerClient`);
|
|
151
|
+
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
152
|
+
psbt,
|
|
153
|
+
index,
|
|
154
|
+
ledgerManager
|
|
185
155
|
});
|
|
156
|
+
if (!policy)
|
|
157
|
+
throw new Error(`Error: the ledger cannot sign this pstb input`);
|
|
158
|
+
let ledgerSignatures;
|
|
159
|
+
if (policy.policyName && policy.policyHmac && policy.policyId) {
|
|
160
|
+
//non-standard policy
|
|
161
|
+
const walletPolicy = new WalletPolicy(policy.policyName, policy.ledgerTemplate, policy.keyRoots);
|
|
162
|
+
const walletHmac = policy.policyHmac;
|
|
163
|
+
ledgerSignatures = await ledgerClient.signPsbt(psbt.toBase64(), walletPolicy, walletHmac);
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
//standard policy
|
|
167
|
+
ledgerSignatures = await ledgerClient.signPsbt(psbt.toBase64(), new DefaultWalletPolicy(policy.ledgerTemplate, policy.keyRoots[0]), null);
|
|
168
|
+
}
|
|
169
|
+
addLedgerSignaturesToInput({ psbt, index, ledgerSignatures });
|
|
186
170
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
* @overload
|
|
191
|
-
*/
|
|
192
|
-
async function signLedger({ psbt, descriptors, ledgerClient, ledgerState, ledgerManager }) {
|
|
193
|
-
if (!descriptors && !ledgerManager)
|
|
194
|
-
throw new Error(`ledgerManager not provided`);
|
|
195
|
-
if (descriptors && ledgerManager)
|
|
196
|
-
throw new Error(`Invalid usage: don't pass descriptors`);
|
|
197
|
-
if (ledgerManager && (ledgerClient || ledgerState))
|
|
198
|
-
throw new Error(`Invalid usage: either ledgerManager or ledgerClient + ledgerState`);
|
|
199
|
-
const outputs = descriptors;
|
|
200
|
-
if (ledgerManager)
|
|
201
|
-
({ ledgerClient, ledgerState } = ledgerManager);
|
|
202
|
-
if (!ledgerClient)
|
|
203
|
-
throw new Error(`ledgerManager not provided`);
|
|
204
|
-
if (!ledgerState)
|
|
205
|
-
throw new Error(`ledgerManager not provided`);
|
|
206
|
-
const { PsbtV2, DefaultWalletPolicy, WalletPolicy, AppClient } = (await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient));
|
|
171
|
+
async function signLedger({ psbt, ledgerManager }) {
|
|
172
|
+
const { ledgerClient } = ledgerManager;
|
|
173
|
+
const { DefaultWalletPolicy, WalletPolicy, AppClient } = (await (0, ledger_1.importAndValidateLedgerBitcoin)(ledgerClient));
|
|
207
174
|
if (!(ledgerClient instanceof AppClient))
|
|
208
175
|
throw new Error(`Error: pass a valid ledgerClient`);
|
|
209
176
|
const ledgerPolicies = [];
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
});
|
|
219
|
-
if (policy)
|
|
220
|
-
ledgerPolicies.push(policy);
|
|
221
|
-
}
|
|
222
|
-
else {
|
|
223
|
-
if (!outputs)
|
|
224
|
-
throw new Error(`outputs not provided`);
|
|
225
|
-
for (const output of outputs) {
|
|
226
|
-
const policy = (await (0, ledger_1.ledgerPolicyFromState)({ output, ledgerClient, ledgerState })) ||
|
|
227
|
-
(await (0, ledger_1.ledgerPolicyFromStandard)({ output, ledgerClient, ledgerState }));
|
|
228
|
-
if (policy)
|
|
229
|
-
ledgerPolicies.push(policy);
|
|
230
|
-
}
|
|
231
|
-
if (ledgerPolicies.length === 0)
|
|
232
|
-
throw new Error(`Error: there are no inputs which could be signed`);
|
|
177
|
+
for (let index = 0; index < psbt.data.inputs.length; index++) {
|
|
178
|
+
const policy = await (0, ledger_1.ledgerPolicyFromPsbtInput)({
|
|
179
|
+
psbt,
|
|
180
|
+
index,
|
|
181
|
+
ledgerManager
|
|
182
|
+
});
|
|
183
|
+
if (policy)
|
|
184
|
+
ledgerPolicies.push(policy);
|
|
233
185
|
}
|
|
186
|
+
if (ledgerPolicies.length === 0)
|
|
187
|
+
throw new Error(`Error: there are no inputs which could be signed`);
|
|
234
188
|
//cluster unique LedgerPolicies
|
|
235
189
|
const uniquePolicies = [];
|
|
236
190
|
for (const policy of ledgerPolicies) {
|
|
@@ -244,16 +198,18 @@ async function signLedger({ psbt, descriptors, ledgerClient, ledgerState, ledger
|
|
|
244
198
|
uniquePolicy.policyId) {
|
|
245
199
|
//non-standard policy
|
|
246
200
|
const walletPolicy = new WalletPolicy(uniquePolicy.policyName, uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots);
|
|
247
|
-
|
|
201
|
+
const walletHmac = uniquePolicy.policyHmac;
|
|
202
|
+
ledgerSignatures = await ledgerClient.signPsbt(psbt.toBase64(), walletPolicy, walletHmac);
|
|
248
203
|
}
|
|
249
204
|
else {
|
|
250
205
|
//standard policy
|
|
251
|
-
ledgerSignatures = await ledgerClient.signPsbt(
|
|
206
|
+
ledgerSignatures = await ledgerClient.signPsbt(psbt.toBase64(), new DefaultWalletPolicy(uniquePolicy.ledgerTemplate, uniquePolicy.keyRoots[0]), null);
|
|
252
207
|
}
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
208
|
+
const signedIndexes = [
|
|
209
|
+
...new Set(ledgerSignatures.map(([index]) => index))
|
|
210
|
+
];
|
|
211
|
+
for (const index of signedIndexes) {
|
|
212
|
+
addLedgerSignaturesToInput({ psbt, index, ledgerSignatures });
|
|
257
213
|
}
|
|
258
214
|
}
|
|
259
215
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const MAX_STACK_SIZE = 1000;
|
|
2
|
+
export declare const MAX_SCRIPT_ELEMENT_SIZE = 520;
|
|
3
|
+
export declare function assertConsensusStackResourceLimits({ stackItems, stackLabel, stackItemLabel }: {
|
|
4
|
+
stackItems: Uint8Array[];
|
|
5
|
+
stackLabel?: string;
|
|
6
|
+
stackItemLabel?: string;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function assertStandardPolicyStackItemCountLimit({ stackItems, maxStackItems, stackLabel }: {
|
|
9
|
+
stackItems: Uint8Array[];
|
|
10
|
+
maxStackItems: number;
|
|
11
|
+
stackLabel: string;
|
|
12
|
+
}): void;
|
|
13
|
+
export declare function assertStandardPolicyStackItemSizeLimit({ stackItems, maxStackItemSize, stackItemLabel }: {
|
|
14
|
+
stackItems: Uint8Array[];
|
|
15
|
+
maxStackItemSize: number;
|
|
16
|
+
stackItemLabel: string;
|
|
17
|
+
}): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2026 Jose-Luis Landabaso
|
|
3
|
+
// Distributed under the MIT software license
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.MAX_SCRIPT_ELEMENT_SIZE = exports.MAX_STACK_SIZE = void 0;
|
|
6
|
+
exports.assertConsensusStackResourceLimits = assertConsensusStackResourceLimits;
|
|
7
|
+
exports.assertStandardPolicyStackItemCountLimit = assertStandardPolicyStackItemCountLimit;
|
|
8
|
+
exports.assertStandardPolicyStackItemSizeLimit = assertStandardPolicyStackItemSizeLimit;
|
|
9
|
+
// See Sipa's Miniscript "Resource limitations":
|
|
10
|
+
// https://bitcoin.sipa.be/miniscript/
|
|
11
|
+
// and Bitcoin Core policy/consensus constants.
|
|
12
|
+
// Consensus: max number of elements in initial stack (and stack+altstack after
|
|
13
|
+
// each opcode execution).
|
|
14
|
+
exports.MAX_STACK_SIZE = 1000;
|
|
15
|
+
// Consensus: max size for any pushed stack element. This is an element limit,
|
|
16
|
+
// not a full script-size limit.
|
|
17
|
+
exports.MAX_SCRIPT_ELEMENT_SIZE = 520;
|
|
18
|
+
function assertConsensusStackResourceLimits({ stackItems, stackLabel = 'stack', stackItemLabel = 'stack item' }) {
|
|
19
|
+
if (stackItems.length > exports.MAX_STACK_SIZE)
|
|
20
|
+
throw new Error(`Error: ${stackLabel} has too many items, ${stackItems.length} is larger than ${exports.MAX_STACK_SIZE}`);
|
|
21
|
+
for (const stackItem of stackItems) {
|
|
22
|
+
if (stackItem.length > exports.MAX_SCRIPT_ELEMENT_SIZE)
|
|
23
|
+
throw new Error(`Error: ${stackItemLabel} is too large, ${stackItem.length} bytes is larger than ${exports.MAX_SCRIPT_ELEMENT_SIZE} bytes`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function assertStandardPolicyStackItemCountLimit({ stackItems, maxStackItems, stackLabel }) {
|
|
27
|
+
if (stackItems.length > maxStackItems)
|
|
28
|
+
throw new Error(`Error: ${stackLabel} has too many items, ${stackItems.length} is larger than ${maxStackItems}`);
|
|
29
|
+
}
|
|
30
|
+
function assertStandardPolicyStackItemSizeLimit({ stackItems, maxStackItemSize, stackItemLabel }) {
|
|
31
|
+
for (const stackItem of stackItems) {
|
|
32
|
+
if (stackItem.length > maxStackItemSize)
|
|
33
|
+
throw new Error(`Error: ${stackItemLabel} exceeds standard policy, ${stackItem.length} bytes is larger than ${maxStackItemSize} bytes`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { Network } from 'bitcoinjs-lib';
|
|
2
|
+
import type { BIP32API } from 'bip32';
|
|
3
|
+
import type { ECPairAPI } from 'ecpair';
|
|
4
|
+
import type { PartialSig, TapBip32Derivation } from 'bip174';
|
|
5
|
+
import type { Taptree } from 'bitcoinjs-lib/src/cjs/types';
|
|
6
|
+
import type { KeyInfo, Preimage, TimeConstraints } from './types';
|
|
7
|
+
import type { TapLeafInfo, TapTreeInfoNode, TapTreeNode } from './tapTree';
|
|
8
|
+
export type TaprootLeafSatisfaction = {
|
|
9
|
+
leaf: TapLeafInfo;
|
|
10
|
+
depth: number;
|
|
11
|
+
tapLeafHash: Uint8Array;
|
|
12
|
+
scriptSatisfaction: Uint8Array;
|
|
13
|
+
stackItems: Uint8Array[];
|
|
14
|
+
nLockTime: number | undefined;
|
|
15
|
+
nSequence: number | undefined;
|
|
16
|
+
totalWitnessSize: number;
|
|
17
|
+
};
|
|
18
|
+
export type TaprootPsbtLeafMetadata = {
|
|
19
|
+
leaf: TapLeafInfo;
|
|
20
|
+
depth: number;
|
|
21
|
+
tapLeafHash: Uint8Array;
|
|
22
|
+
controlBlock: Uint8Array;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Compiles a taproot miniscript tree into per-leaf metadata.
|
|
26
|
+
* Each leaf contains its expanded miniscript, expansion map, compiled tapscript
|
|
27
|
+
* and leaf version. This keeps the taproot script-path data ready for
|
|
28
|
+
* satisfactions and witness building.
|
|
29
|
+
*/
|
|
30
|
+
export declare function buildTapTreeInfo({ tapTree, network, BIP32, ECPair }: {
|
|
31
|
+
tapTree: TapTreeNode;
|
|
32
|
+
network?: Network;
|
|
33
|
+
BIP32: BIP32API;
|
|
34
|
+
ECPair: ECPairAPI;
|
|
35
|
+
}): TapTreeInfoNode;
|
|
36
|
+
export declare function tapTreeInfoToScriptTree(tapTreeInfo: TapTreeInfoNode): Taptree;
|
|
37
|
+
/**
|
|
38
|
+
* Builds taproot PSBT leaf metadata for every leaf in a `tapTreeInfo`.
|
|
39
|
+
*
|
|
40
|
+
* For each leaf, this function computes:
|
|
41
|
+
* - `tapLeafHash`: BIP341 leaf hash of tapscript + leaf version
|
|
42
|
+
* - `depth`: leaf depth in the tree (root children have depth 1)
|
|
43
|
+
* - `controlBlock`: script-path proof used in PSBT `tapLeafScript`
|
|
44
|
+
*
|
|
45
|
+
* The control block layout is:
|
|
46
|
+
*
|
|
47
|
+
* ```text
|
|
48
|
+
* [1-byte (leafVersion | parity)] [32-byte internal key]
|
|
49
|
+
* [32-byte sibling hash #1] ... [32-byte sibling hash #N]
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* where:
|
|
53
|
+
* - `parity` is derived from tweaking the internal key with the tree root
|
|
54
|
+
* - sibling hashes are the merkle path from that leaf to the root
|
|
55
|
+
*
|
|
56
|
+
* Example tree:
|
|
57
|
+
*
|
|
58
|
+
* ```text
|
|
59
|
+
* root
|
|
60
|
+
* / \
|
|
61
|
+
* L1 L2
|
|
62
|
+
* / \
|
|
63
|
+
* L3 L4
|
|
64
|
+
* ```
|
|
65
|
+
*
|
|
66
|
+
* Depths:
|
|
67
|
+
* - L1 depth = 1
|
|
68
|
+
* - L3 depth = 2
|
|
69
|
+
* - L4 depth = 2
|
|
70
|
+
*
|
|
71
|
+
* Conceptual output:
|
|
72
|
+
*
|
|
73
|
+
* ```text
|
|
74
|
+
* [
|
|
75
|
+
* L1 -> { depth: 1, tapLeafHash: h1, controlBlock: [v|p, ik, hash(L2)] }
|
|
76
|
+
* L3 -> { depth: 2, tapLeafHash: h3, controlBlock: [v|p, ik, hash(L4), hash(L1)] }
|
|
77
|
+
* L4 -> { depth: 2, tapLeafHash: h4, controlBlock: [v|p, ik, hash(L3), hash(L1)] }
|
|
78
|
+
* ]
|
|
79
|
+
* ```
|
|
80
|
+
*
|
|
81
|
+
* Legend:
|
|
82
|
+
* - `ik`: the 32-byte internal key placed in the control block.
|
|
83
|
+
* - `hash(X)`: the merkle sibling hash at each level when proving leaf `X`.
|
|
84
|
+
*
|
|
85
|
+
* Note: in this diagram, `L2` is a branch node (right subtree), not a leaf,
|
|
86
|
+
* so `hash(L2) = TapBranch(hash(L3), hash(L4))`.
|
|
87
|
+
*
|
|
88
|
+
* Notes:
|
|
89
|
+
* - Leaves are returned in deterministic left-first order.
|
|
90
|
+
* - One metadata entry is returned per leaf.
|
|
91
|
+
* - `controlBlock.length === 33 + 32 * depth`.
|
|
92
|
+
* - Throws if internal key is invalid or merkle path cannot be found.
|
|
93
|
+
*
|
|
94
|
+
* Typical usage:
|
|
95
|
+
* - Convert this metadata into PSBT `tapLeafScript[]` entries
|
|
96
|
+
* for all leaves.
|
|
97
|
+
*/
|
|
98
|
+
export declare function buildTaprootLeafPsbtMetadata({ tapTreeInfo, internalPubkey }: {
|
|
99
|
+
tapTreeInfo: TapTreeInfoNode;
|
|
100
|
+
internalPubkey: Uint8Array;
|
|
101
|
+
}): TaprootPsbtLeafMetadata[];
|
|
102
|
+
/**
|
|
103
|
+
* Builds PSBT `tapBip32Derivation` entries for taproot script-path spends.
|
|
104
|
+
*
|
|
105
|
+
* Leaf keys include the list of tapleaf hashes where they appear.
|
|
106
|
+
* If `internalKeyInfo` has derivation data, it is included with empty
|
|
107
|
+
* `leafHashes`.
|
|
108
|
+
*
|
|
109
|
+
* Example tree:
|
|
110
|
+
*
|
|
111
|
+
* ```text
|
|
112
|
+
* root
|
|
113
|
+
* / \
|
|
114
|
+
* L1 L2
|
|
115
|
+
*
|
|
116
|
+
* L1 uses key A
|
|
117
|
+
* L2 uses key A and key B
|
|
118
|
+
*
|
|
119
|
+
* h1 = tapleafHash(L1)
|
|
120
|
+
* h2 = tapleafHash(L2)
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* Then output is conceptually:
|
|
124
|
+
*
|
|
125
|
+
* ```text
|
|
126
|
+
* [
|
|
127
|
+
* key A -> leafHashes [h1, h2]
|
|
128
|
+
* key B -> leafHashes [h2]
|
|
129
|
+
* internal key -> leafHashes []
|
|
130
|
+
* ]
|
|
131
|
+
* ```
|
|
132
|
+
*
|
|
133
|
+
* Notes:
|
|
134
|
+
* - Keys missing `masterFingerprint` or `path` are skipped.
|
|
135
|
+
* - Duplicate pubkeys are merged.
|
|
136
|
+
* - If the same pubkey appears with conflicting derivation metadata,
|
|
137
|
+
* this function throws.
|
|
138
|
+
* - Output and `leafHashes` are sorted deterministically.
|
|
139
|
+
*/
|
|
140
|
+
export declare function buildTaprootBip32Derivations({ tapTreeInfo, internalKeyInfo }: {
|
|
141
|
+
tapTreeInfo: TapTreeInfoNode;
|
|
142
|
+
internalKeyInfo?: KeyInfo;
|
|
143
|
+
}): TapBip32Derivation[];
|
|
144
|
+
export declare function normalizeTaprootPubkey(pubkey: Uint8Array): Uint8Array;
|
|
145
|
+
/**
|
|
146
|
+
* Computes satisfactions for taproot script-path leaves.
|
|
147
|
+
*
|
|
148
|
+
* If `tapLeaf` is undefined, all satisfiable leaves are returned. If `tapLeaf`
|
|
149
|
+
* is provided, only that leaf is considered.
|
|
150
|
+
*
|
|
151
|
+
* Callers are expected to pass real signatures, or fake signatures generated
|
|
152
|
+
* during planning. See satisfyMiniscript() for how timeConstraints keep the
|
|
153
|
+
* chosen leaf consistent between planning and signing.
|
|
154
|
+
*/
|
|
155
|
+
export declare function collectTaprootLeafSatisfactions({ tapTreeInfo, preimages, signatures, timeConstraints, tapLeaf }: {
|
|
156
|
+
tapTreeInfo: TapTreeInfoNode;
|
|
157
|
+
preimages: Preimage[];
|
|
158
|
+
signatures: PartialSig[];
|
|
159
|
+
timeConstraints?: TimeConstraints;
|
|
160
|
+
tapLeaf?: Uint8Array | string;
|
|
161
|
+
}): TaprootLeafSatisfaction[];
|
|
162
|
+
/**
|
|
163
|
+
* Selects the taproot leaf satisfaction with the smallest total witness size.
|
|
164
|
+
* Assumes the input list is in left-first tree order for deterministic ties.
|
|
165
|
+
*/
|
|
166
|
+
export declare function selectBestTaprootLeafSatisfaction(satisfactions: TaprootLeafSatisfaction[]): TaprootLeafSatisfaction;
|
|
167
|
+
/**
|
|
168
|
+
* Collects a unique set of taproot leaf pubkeys (x-only) across the tree.
|
|
169
|
+
* This is useful for building fake signatures when no signer subset is given.
|
|
170
|
+
*/
|
|
171
|
+
export declare function collectTapTreePubkeys(tapTreeInfo: TapTreeInfoNode): Uint8Array[];
|
|
172
|
+
/**
|
|
173
|
+
* Returns the best satisfaction for a taproot tree, by witness size.
|
|
174
|
+
*
|
|
175
|
+
* If `tapLeaf` is provided, only that leaf is considered. If `tapLeaf` is a
|
|
176
|
+
* bytes, it is treated as a tapLeafHash and must match exactly one leaf. If
|
|
177
|
+
* `tapLeaf` is a string, it is treated as a miniscript leaf and must match
|
|
178
|
+
* exactly one leaf (whitespace-insensitive).
|
|
179
|
+
*
|
|
180
|
+
* This function is typically called twice:
|
|
181
|
+
* 1) Planning pass: call it with fake signatures (built by the caller) to
|
|
182
|
+
* choose the best leaf without requiring user signatures.
|
|
183
|
+
* 2) Signing pass: call it again with real signatures and the timeConstraints
|
|
184
|
+
* returned from the first pass (see satisfyMiniscript() for why this keeps
|
|
185
|
+
* the chosen leaf consistent between planning and signing).
|
|
186
|
+
*/
|
|
187
|
+
export declare function satisfyTapTree({ tapTreeInfo, signatures, preimages, tapLeaf, timeConstraints }: {
|
|
188
|
+
tapTreeInfo: TapTreeInfoNode;
|
|
189
|
+
signatures: PartialSig[];
|
|
190
|
+
preimages: Preimage[];
|
|
191
|
+
tapLeaf?: Uint8Array | string;
|
|
192
|
+
timeConstraints?: TimeConstraints;
|
|
193
|
+
}): TaprootLeafSatisfaction;
|