@bitcoinerlab/descriptors-core 3.1.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 +710 -0
- package/dist/adapters/applyPR2137.d.ts +2 -0
- package/dist/adapters/applyPR2137.js +150 -0
- package/dist/adapters/bitcoinjs.d.ts +8 -0
- package/dist/adapters/bitcoinjs.js +36 -0
- package/dist/adapters/scure/address.d.ts +2 -0
- package/dist/adapters/scure/address.js +50 -0
- package/dist/adapters/scure/bip32.d.ts +2 -0
- package/dist/adapters/scure/bip32.js +16 -0
- package/dist/adapters/scure/common.d.ts +14 -0
- package/dist/adapters/scure/common.js +36 -0
- package/dist/adapters/scure/ecpair.d.ts +2 -0
- package/dist/adapters/scure/ecpair.js +58 -0
- package/dist/adapters/scure/payments.d.ts +2 -0
- package/dist/adapters/scure/payments.js +216 -0
- package/dist/adapters/scure/psbt.d.ts +43 -0
- package/dist/adapters/scure/psbt.js +382 -0
- package/dist/adapters/scure/script.d.ts +20 -0
- package/dist/adapters/scure/script.js +163 -0
- package/dist/adapters/scure/transaction.d.ts +2 -0
- package/dist/adapters/scure/transaction.js +32 -0
- package/dist/adapters/scure.d.ts +6 -0
- package/dist/adapters/scure.js +37 -0
- package/dist/adapters/scureKeys.d.ts +4 -0
- package/dist/adapters/scureKeys.js +135 -0
- package/dist/bip174.d.ts +87 -0
- package/dist/bip174.js +12 -0
- package/dist/bitcoinLib.d.ts +385 -0
- package/dist/bitcoinLib.js +19 -0
- package/dist/bitcoinjs-lib-internals.d.ts +6 -0
- package/dist/bitcoinjs-lib-internals.js +60 -0
- package/dist/bitcoinjs.d.ts +12 -0
- package/dist/bitcoinjs.js +18 -0
- package/dist/checksum.d.ts +6 -0
- package/dist/checksum.js +58 -0
- package/dist/crypto.d.ts +3 -0
- package/dist/crypto.js +79 -0
- package/dist/descriptors.d.ts +481 -0
- package/dist/descriptors.js +1888 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.js +87 -0
- package/dist/keyExpressions.d.ts +124 -0
- package/dist/keyExpressions.js +310 -0
- package/dist/keyInterfaces.d.ts +5 -0
- package/dist/keyInterfaces.js +50 -0
- package/dist/ledger.d.ts +183 -0
- package/dist/ledger.js +618 -0
- package/dist/miniscript.d.ts +125 -0
- package/dist/miniscript.js +310 -0
- 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/networks.d.ts +16 -0
- package/dist/networks.js +31 -0
- package/dist/parseUtils.d.ts +7 -0
- package/dist/parseUtils.js +46 -0
- package/dist/psbt.d.ts +40 -0
- package/dist/psbt.js +228 -0
- package/dist/re.d.ts +31 -0
- package/dist/re.js +79 -0
- package/dist/resourceLimits.d.ts +28 -0
- package/dist/resourceLimits.js +84 -0
- package/dist/scriptExpressions.d.ts +95 -0
- package/dist/scriptExpressions.js +98 -0
- package/dist/scure.d.ts +4 -0
- package/dist/scure.js +10 -0
- package/dist/signers.d.ts +161 -0
- package/dist/signers.js +324 -0
- package/dist/tapMiniscript.d.ts +231 -0
- package/dist/tapMiniscript.js +524 -0
- package/dist/tapTree.d.ts +91 -0
- package/dist/tapTree.js +166 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +4 -0
- package/package.json +148 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ECPairAPILike, BIP32APILike } from './bitcoinLib';
|
|
2
|
+
import { type Network } from './networks';
|
|
3
|
+
import type { PartialSig } from './bip174';
|
|
4
|
+
import type { Preimage, TimeConstraints, ExpansionMap } from './types';
|
|
5
|
+
import type { BitcoinLib } from './bitcoinLib';
|
|
6
|
+
/**
|
|
7
|
+
* Expand a miniscript to a generalized form using variables instead of key
|
|
8
|
+
* expressions. Variables will be of this form: @0, @1, ...
|
|
9
|
+
* This is done so that it can be compiled with compileMiniscript and
|
|
10
|
+
* satisfied with satisfier.
|
|
11
|
+
* Also compute pubkeys from descriptors to use them later.
|
|
12
|
+
*/
|
|
13
|
+
export declare function expandMiniscript({ miniscript, isSegwit, isTaproot, network, ECPair, BIP32 }: {
|
|
14
|
+
miniscript: string;
|
|
15
|
+
isSegwit: boolean;
|
|
16
|
+
isTaproot: boolean;
|
|
17
|
+
network?: Network;
|
|
18
|
+
ECPair: ECPairAPILike;
|
|
19
|
+
BIP32: BIP32APILike;
|
|
20
|
+
}): {
|
|
21
|
+
expandedMiniscript: string;
|
|
22
|
+
expansionMap: ExpansionMap;
|
|
23
|
+
};
|
|
24
|
+
export declare function miniscript2Script({ expandedMiniscript, expansionMap, tapscript, scriptLib }: {
|
|
25
|
+
expandedMiniscript: string;
|
|
26
|
+
expansionMap: ExpansionMap;
|
|
27
|
+
tapscript?: boolean;
|
|
28
|
+
scriptLib: BitcoinLib['script'];
|
|
29
|
+
}): Uint8Array;
|
|
30
|
+
/**
|
|
31
|
+
* Assumptions:
|
|
32
|
+
* The attacker does not have access to any of the private keys of public keys
|
|
33
|
+
* that participate in the Script.
|
|
34
|
+
*
|
|
35
|
+
* The attacker only has access to hash preimages that honest users have access
|
|
36
|
+
* to as well.
|
|
37
|
+
*
|
|
38
|
+
* Pass timeConstraints to search for the first solution with this nLockTime and
|
|
39
|
+
* nSequence. Throw if no solution is possible using these constraints.
|
|
40
|
+
*
|
|
41
|
+
* Time constraints are used to keep the chosen satisfaction stable between the
|
|
42
|
+
* planning pass (fake signatures) and the signing pass (real signatures).
|
|
43
|
+
* We run the satisfier once with fake signatures to discover the implied
|
|
44
|
+
* nLockTime/nSequence without requiring user signatures. If real signatures
|
|
45
|
+
* had the same length, the satisfier would typically pick the same
|
|
46
|
+
* minimal-weight solution again. But ECDSA signature sizes can vary (71–73
|
|
47
|
+
* bytes), which may change which solution is considered "smallest".
|
|
48
|
+
*
|
|
49
|
+
* Passing the previously derived timeConstraints in the second pass forces the
|
|
50
|
+
* same solution to be selected, ensuring locktime/sequence do not change
|
|
51
|
+
* between planning and finalization.
|
|
52
|
+
*
|
|
53
|
+
* Don't pass timeConstraints (this is the default) if you want to get the
|
|
54
|
+
* smallest size solution altogether.
|
|
55
|
+
*
|
|
56
|
+
* If a solution is not found this function throws.
|
|
57
|
+
*/
|
|
58
|
+
export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures, preimages, timeConstraints, tapscript, scriptLib }: {
|
|
59
|
+
expandedMiniscript: string;
|
|
60
|
+
expansionMap: ExpansionMap;
|
|
61
|
+
signatures?: PartialSig[];
|
|
62
|
+
preimages?: Preimage[];
|
|
63
|
+
timeConstraints?: TimeConstraints;
|
|
64
|
+
tapscript?: boolean;
|
|
65
|
+
scriptLib: BitcoinLib['script'];
|
|
66
|
+
}): {
|
|
67
|
+
scriptSatisfaction: Uint8Array;
|
|
68
|
+
nLockTime: number | undefined;
|
|
69
|
+
nSequence: number | undefined;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
*
|
|
73
|
+
* Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
|
|
74
|
+
* when encoding numbers to be compiled with `fromASM` to avoid problems.
|
|
75
|
+
*
|
|
76
|
+
* Motivation:
|
|
77
|
+
*
|
|
78
|
+
* Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
|
|
79
|
+
* Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
|
|
80
|
+
*
|
|
81
|
+
* This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
|
|
82
|
+
* can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
|
|
83
|
+
* does.
|
|
84
|
+
*
|
|
85
|
+
* Note that `fromASM` already converts integers from 1 to 16 to
|
|
86
|
+
* OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
|
|
87
|
+
* This is done in Bitcoin to save some bits.
|
|
88
|
+
*
|
|
89
|
+
* Neither this function nor `script.number.encode` convert numbers to
|
|
90
|
+
* their op code equivalent since this is done later in `fromASM`.
|
|
91
|
+
*
|
|
92
|
+
* Both functions simply convert numbers to Little Endian.
|
|
93
|
+
*
|
|
94
|
+
* However, the `0` number is an edge case that we specially handle with this
|
|
95
|
+
* function.
|
|
96
|
+
*
|
|
97
|
+
* bitcoinjs-lib's `scriptLib.number.encode(0)` produces an empty array.
|
|
98
|
+
* This is what the Bitcoin interpreter does and it is what `script.number.encode` was
|
|
99
|
+
* implemented to do.
|
|
100
|
+
*
|
|
101
|
+
* The problem is `scriptLib.number.encode(0).toString('hex')` produces an
|
|
102
|
+
* empty string and thus it should not be used to serialize number zero before `fromASM`.
|
|
103
|
+
*
|
|
104
|
+
* A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
|
|
105
|
+
* compiled).
|
|
106
|
+
*
|
|
107
|
+
* So, this function will produce a string in hex format in Little Endian
|
|
108
|
+
* encoding for integers not equal to `0` and it will return `OP_0` for `0`.
|
|
109
|
+
*
|
|
110
|
+
* Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
|
|
111
|
+
*
|
|
112
|
+
* Use it in combination with `fromASM` like this:
|
|
113
|
+
*
|
|
114
|
+
* ```javascript
|
|
115
|
+
* //To produce "0 1 OP_ADD":
|
|
116
|
+
* fromASM(
|
|
117
|
+
* `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
|
|
118
|
+
* .trim().replace(/\s+/g, ' ')
|
|
119
|
+
* )
|
|
120
|
+
* ```
|
|
121
|
+
*
|
|
122
|
+
* @param {number} number An integer.
|
|
123
|
+
* @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
|
|
124
|
+
*/
|
|
125
|
+
export declare function numberEncodeAsm(number: number, scriptLib: BitcoinLib['script']): string;
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
|
+
// Distributed under the MIT software license
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.expandMiniscript = expandMiniscript;
|
|
39
|
+
exports.miniscript2Script = miniscript2Script;
|
|
40
|
+
exports.satisfyMiniscript = satisfyMiniscript;
|
|
41
|
+
exports.numberEncodeAsm = numberEncodeAsm;
|
|
42
|
+
const keyExpressions_1 = require("./keyExpressions");
|
|
43
|
+
const networks_1 = require("./networks");
|
|
44
|
+
const RE = __importStar(require("./re"));
|
|
45
|
+
const miniscript_1 = require("@bitcoinerlab/miniscript");
|
|
46
|
+
const uint8array_tools_1 = require("uint8array-tools");
|
|
47
|
+
const crypto_1 = require("./crypto");
|
|
48
|
+
/**
|
|
49
|
+
* Expand a miniscript to a generalized form using variables instead of key
|
|
50
|
+
* expressions. Variables will be of this form: @0, @1, ...
|
|
51
|
+
* This is done so that it can be compiled with compileMiniscript and
|
|
52
|
+
* satisfied with satisfier.
|
|
53
|
+
* Also compute pubkeys from descriptors to use them later.
|
|
54
|
+
*/
|
|
55
|
+
function expandMiniscript({ miniscript, isSegwit, isTaproot, network = networks_1.networks.bitcoin, ECPair, BIP32 }) {
|
|
56
|
+
const reKeyExp = isTaproot
|
|
57
|
+
? RE.reTaprootKeyExp
|
|
58
|
+
: isSegwit
|
|
59
|
+
? RE.reSegwitKeyExp
|
|
60
|
+
: RE.reNonSegwitKeyExp;
|
|
61
|
+
const expansionMap = {};
|
|
62
|
+
let keyIndex = 0;
|
|
63
|
+
const keyExpressionRegex = new RegExp(String.raw `^${reKeyExp}$`);
|
|
64
|
+
const replaceKeyExpression = (keyExpression) => {
|
|
65
|
+
const trimmed = keyExpression.trim();
|
|
66
|
+
if (!trimmed)
|
|
67
|
+
throw new Error(`Error: expected a keyExpression but got ${keyExpression}`);
|
|
68
|
+
if (!keyExpressionRegex.test(trimmed))
|
|
69
|
+
throw new Error(`Error: expected a keyExpression but got ${trimmed}`);
|
|
70
|
+
const key = `@${keyIndex}`;
|
|
71
|
+
keyIndex += 1;
|
|
72
|
+
expansionMap[key] = (0, keyExpressions_1.parseKeyExpression)({
|
|
73
|
+
keyExpression: trimmed,
|
|
74
|
+
isSegwit,
|
|
75
|
+
isTaproot,
|
|
76
|
+
network,
|
|
77
|
+
ECPair,
|
|
78
|
+
BIP32
|
|
79
|
+
});
|
|
80
|
+
return key;
|
|
81
|
+
};
|
|
82
|
+
// These are the Miniscript fragments where key arguments are expected.
|
|
83
|
+
// We only replace key expressions inside these fragments to avoid confusing
|
|
84
|
+
// hash preimages (e.g. sha256(03...)) with keys.
|
|
85
|
+
//
|
|
86
|
+
// Note: sorted script expressions (`sortedmulti`, `sortedmulti_a`) are
|
|
87
|
+
// intentionally excluded here. They are descriptor-level expressions (not
|
|
88
|
+
// Miniscript fragments) and are handled in descriptor/tap-tree code paths
|
|
89
|
+
// before Miniscript compilation.
|
|
90
|
+
const keyBearingFragmentRegex = /\b(pk|pkh|multi_a|multi)\(([^()]*)\)/g;
|
|
91
|
+
const expandedMiniscript = miniscript.replace(keyBearingFragmentRegex, (_, name, inner) => {
|
|
92
|
+
if (name === 'pk' || name === 'pkh')
|
|
93
|
+
return `${name}(${replaceKeyExpression(inner)})`;
|
|
94
|
+
//now do *multi* which has arguments:
|
|
95
|
+
const parts = inner.split(',').map(part => part.trim());
|
|
96
|
+
if (parts.length < 2)
|
|
97
|
+
throw new Error(`Error: invalid miniscript ${miniscript} (missing keys)`);
|
|
98
|
+
const k = parts[0] ?? '';
|
|
99
|
+
if (!k)
|
|
100
|
+
throw new Error(`Error: invalid miniscript ${miniscript} (missing threshold)`);
|
|
101
|
+
const replacedKeys = parts
|
|
102
|
+
.slice(1)
|
|
103
|
+
.map(keyExpression => replaceKeyExpression(keyExpression));
|
|
104
|
+
return `${name}(${[k, ...replacedKeys].join(',')})`;
|
|
105
|
+
});
|
|
106
|
+
//Do some assertions. Miniscript must not have duplicate keys, also all
|
|
107
|
+
//keyExpressions must produce a valid pubkey (unless it's ranged and we want
|
|
108
|
+
//to expand a generalized form, then we don't check)
|
|
109
|
+
const pubkeysHex = Object.values(expansionMap)
|
|
110
|
+
.filter(keyInfo => keyInfo.keyExpression.indexOf('*') === -1)
|
|
111
|
+
.map(keyInfo => {
|
|
112
|
+
if (!keyInfo.pubkey)
|
|
113
|
+
throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
|
|
114
|
+
return (0, uint8array_tools_1.toHex)(keyInfo.pubkey);
|
|
115
|
+
});
|
|
116
|
+
if (new Set(pubkeysHex).size !== pubkeysHex.length) {
|
|
117
|
+
throw new Error(`Error: miniscript ${miniscript} is not sane: contains duplicate public keys.`);
|
|
118
|
+
}
|
|
119
|
+
return { expandedMiniscript, expansionMap };
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Particularize an expanded ASM expression using the variables in
|
|
123
|
+
* expansionMap.
|
|
124
|
+
* This is the kind of the opposite to what expandMiniscript does.
|
|
125
|
+
* Signatures and preimages are already subsituted by the satisfier calling
|
|
126
|
+
* this function.
|
|
127
|
+
*/
|
|
128
|
+
function substituteAsm({ expandedAsm, expansionMap, scriptLib }) {
|
|
129
|
+
//Replace back variables into the pubkeys previously computed.
|
|
130
|
+
let asm = Object.keys(expansionMap).reduce((accAsm, key) => {
|
|
131
|
+
const pubkey = expansionMap[key]?.pubkey;
|
|
132
|
+
if (!pubkey) {
|
|
133
|
+
throw new Error(`Error: invalid expansionMap for ${key}`);
|
|
134
|
+
}
|
|
135
|
+
return accAsm
|
|
136
|
+
.replaceAll(`<${key}>`, `<${(0, uint8array_tools_1.toHex)(pubkey)}>`)
|
|
137
|
+
.replaceAll(`<HASH160(${key})>`, `<${(0, uint8array_tools_1.toHex)((0, crypto_1.hash160)(pubkey))}>`);
|
|
138
|
+
}, expandedAsm);
|
|
139
|
+
//Now clean it and prepare it so that fromASM can be called:
|
|
140
|
+
asm = asm
|
|
141
|
+
.trim()
|
|
142
|
+
//Replace one or more consecutive whitespace characters (spaces, tabs,
|
|
143
|
+
//or line breaks) with a single space.
|
|
144
|
+
.replace(/\s+/g, ' ')
|
|
145
|
+
//Now encode numbers to little endian hex. Note that numbers are not
|
|
146
|
+
//enclosed in <>, since <> represents hex code already encoded.
|
|
147
|
+
//The regex below will match one or more digits within a string,
|
|
148
|
+
//except if the sequence is surrounded by "<" and ">"
|
|
149
|
+
.replace(/(<\d+>)|\b\d+\b/g, match => match.startsWith('<') ? match : numberEncodeAsm(Number(match), scriptLib))
|
|
150
|
+
//we don't have numbers anymore, now it's safe to remove < and > since we
|
|
151
|
+
//know that every remaining is either an op_code or a hex encoded number
|
|
152
|
+
.replace(/[<>]/g, '');
|
|
153
|
+
return asm;
|
|
154
|
+
}
|
|
155
|
+
function miniscript2Script({ expandedMiniscript, expansionMap, tapscript = false, scriptLib }) {
|
|
156
|
+
const compiled = (0, miniscript_1.compileMiniscript)(expandedMiniscript, { tapscript });
|
|
157
|
+
if (compiled.issane !== true) {
|
|
158
|
+
throw new Error(`Error: Miniscript ${expandedMiniscript} is not sane`);
|
|
159
|
+
}
|
|
160
|
+
return scriptLib.fromASM(substituteAsm({
|
|
161
|
+
expandedAsm: compiled.asm,
|
|
162
|
+
expansionMap,
|
|
163
|
+
scriptLib
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Assumptions:
|
|
168
|
+
* The attacker does not have access to any of the private keys of public keys
|
|
169
|
+
* that participate in the Script.
|
|
170
|
+
*
|
|
171
|
+
* The attacker only has access to hash preimages that honest users have access
|
|
172
|
+
* to as well.
|
|
173
|
+
*
|
|
174
|
+
* Pass timeConstraints to search for the first solution with this nLockTime and
|
|
175
|
+
* nSequence. Throw if no solution is possible using these constraints.
|
|
176
|
+
*
|
|
177
|
+
* Time constraints are used to keep the chosen satisfaction stable between the
|
|
178
|
+
* planning pass (fake signatures) and the signing pass (real signatures).
|
|
179
|
+
* We run the satisfier once with fake signatures to discover the implied
|
|
180
|
+
* nLockTime/nSequence without requiring user signatures. If real signatures
|
|
181
|
+
* had the same length, the satisfier would typically pick the same
|
|
182
|
+
* minimal-weight solution again. But ECDSA signature sizes can vary (71–73
|
|
183
|
+
* bytes), which may change which solution is considered "smallest".
|
|
184
|
+
*
|
|
185
|
+
* Passing the previously derived timeConstraints in the second pass forces the
|
|
186
|
+
* same solution to be selected, ensuring locktime/sequence do not change
|
|
187
|
+
* between planning and finalization.
|
|
188
|
+
*
|
|
189
|
+
* Don't pass timeConstraints (this is the default) if you want to get the
|
|
190
|
+
* smallest size solution altogether.
|
|
191
|
+
*
|
|
192
|
+
* If a solution is not found this function throws.
|
|
193
|
+
*/
|
|
194
|
+
function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [], preimages = [], timeConstraints, tapscript = false, scriptLib }) {
|
|
195
|
+
//convert 'sha256(6c...33)' to: { ['<sha256_preimage(6c...33)>']: '10...5f'}
|
|
196
|
+
const preimageMap = {};
|
|
197
|
+
preimages.forEach(preimage => {
|
|
198
|
+
preimageMap['<' + preimage.digest.replace('(', '_preimage(') + '>'] =
|
|
199
|
+
'<' + preimage.preimage + '>';
|
|
200
|
+
});
|
|
201
|
+
//convert the pubkeys in signatures into [{['<sig(@0)>']: '30450221'}, ...]
|
|
202
|
+
//get the keyExpressions: @0, @1 from the keys in expansionMap
|
|
203
|
+
const expandedSignatureMap = {};
|
|
204
|
+
signatures.forEach(signature => {
|
|
205
|
+
const pubkeyHex = (0, uint8array_tools_1.toHex)(signature.pubkey);
|
|
206
|
+
const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey && (0, uint8array_tools_1.toHex)(expansionMap[k].pubkey) === pubkeyHex);
|
|
207
|
+
expandedSignatureMap['<sig(' + keyExpression + ')>'] =
|
|
208
|
+
'<' + (0, uint8array_tools_1.toHex)(signature.signature) + '>';
|
|
209
|
+
});
|
|
210
|
+
const expandedKnownsMap = { ...preimageMap, ...expandedSignatureMap };
|
|
211
|
+
const knowns = Object.keys(expandedKnownsMap);
|
|
212
|
+
//satisfier verifies again internally whether expandedKnownsMap with given knowns is sane
|
|
213
|
+
const { nonMalleableSats } = (0, miniscript_1.satisfier)(expandedMiniscript, {
|
|
214
|
+
knowns,
|
|
215
|
+
tapscript
|
|
216
|
+
});
|
|
217
|
+
if (!Array.isArray(nonMalleableSats) || !nonMalleableSats[0])
|
|
218
|
+
throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}`);
|
|
219
|
+
let sat;
|
|
220
|
+
if (!timeConstraints) {
|
|
221
|
+
sat = nonMalleableSats[0];
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
sat = nonMalleableSats.find(nonMalleableSat => nonMalleableSat.nSequence === timeConstraints.nSequence &&
|
|
225
|
+
nonMalleableSat.nLockTime === timeConstraints.nLockTime);
|
|
226
|
+
if (sat === undefined) {
|
|
227
|
+
throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}. Could not find solutions for sequence ${timeConstraints.nSequence} & locktime=${timeConstraints.nLockTime}. Signatures are applied to a hash that depends on sequence and locktime. Did you provide all the signatures wrt the signers keys declared and include all preimages?`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
//substitute signatures and preimages:
|
|
231
|
+
let expandedAsm = sat.asm;
|
|
232
|
+
//replace in expandedAsm all the <sig(@0)> and <sha256_preimage(6c...33)>
|
|
233
|
+
//to <304...01> and <107...5f> ...
|
|
234
|
+
for (const search in expandedKnownsMap) {
|
|
235
|
+
const replace = expandedKnownsMap[search];
|
|
236
|
+
if (!replace || replace === '<>')
|
|
237
|
+
throw new Error(`Error: invalid expandedKnownsMap`);
|
|
238
|
+
expandedAsm = expandedAsm.replaceAll(search, replace);
|
|
239
|
+
}
|
|
240
|
+
const scriptSatisfaction = scriptLib.fromASM(substituteAsm({ expandedAsm, expansionMap, scriptLib }));
|
|
241
|
+
return {
|
|
242
|
+
scriptSatisfaction,
|
|
243
|
+
nLockTime: sat.nLockTime,
|
|
244
|
+
nSequence: sat.nSequence
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
*
|
|
249
|
+
* Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
|
|
250
|
+
* when encoding numbers to be compiled with `fromASM` to avoid problems.
|
|
251
|
+
*
|
|
252
|
+
* Motivation:
|
|
253
|
+
*
|
|
254
|
+
* Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
|
|
255
|
+
* Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
|
|
256
|
+
*
|
|
257
|
+
* This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
|
|
258
|
+
* can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
|
|
259
|
+
* does.
|
|
260
|
+
*
|
|
261
|
+
* Note that `fromASM` already converts integers from 1 to 16 to
|
|
262
|
+
* OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
|
|
263
|
+
* This is done in Bitcoin to save some bits.
|
|
264
|
+
*
|
|
265
|
+
* Neither this function nor `script.number.encode` convert numbers to
|
|
266
|
+
* their op code equivalent since this is done later in `fromASM`.
|
|
267
|
+
*
|
|
268
|
+
* Both functions simply convert numbers to Little Endian.
|
|
269
|
+
*
|
|
270
|
+
* However, the `0` number is an edge case that we specially handle with this
|
|
271
|
+
* function.
|
|
272
|
+
*
|
|
273
|
+
* bitcoinjs-lib's `scriptLib.number.encode(0)` produces an empty array.
|
|
274
|
+
* This is what the Bitcoin interpreter does and it is what `script.number.encode` was
|
|
275
|
+
* implemented to do.
|
|
276
|
+
*
|
|
277
|
+
* The problem is `scriptLib.number.encode(0).toString('hex')` produces an
|
|
278
|
+
* empty string and thus it should not be used to serialize number zero before `fromASM`.
|
|
279
|
+
*
|
|
280
|
+
* A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
|
|
281
|
+
* compiled).
|
|
282
|
+
*
|
|
283
|
+
* So, this function will produce a string in hex format in Little Endian
|
|
284
|
+
* encoding for integers not equal to `0` and it will return `OP_0` for `0`.
|
|
285
|
+
*
|
|
286
|
+
* Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
|
|
287
|
+
*
|
|
288
|
+
* Use it in combination with `fromASM` like this:
|
|
289
|
+
*
|
|
290
|
+
* ```javascript
|
|
291
|
+
* //To produce "0 1 OP_ADD":
|
|
292
|
+
* fromASM(
|
|
293
|
+
* `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
|
|
294
|
+
* .trim().replace(/\s+/g, ' ')
|
|
295
|
+
* )
|
|
296
|
+
* ```
|
|
297
|
+
*
|
|
298
|
+
* @param {number} number An integer.
|
|
299
|
+
* @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
|
|
300
|
+
*/
|
|
301
|
+
function numberEncodeAsm(number, scriptLib) {
|
|
302
|
+
if (Number.isSafeInteger(number) === false) {
|
|
303
|
+
throw new Error(`Error: invalid number ${number}`);
|
|
304
|
+
}
|
|
305
|
+
if (number === 0) {
|
|
306
|
+
return 'OP_0';
|
|
307
|
+
}
|
|
308
|
+
else
|
|
309
|
+
return (0, uint8array_tools_1.toHex)(scriptLib.number.encode(number));
|
|
310
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves all multipath tuple segments (for example `/<0;1>/*`) in lockstep
|
|
3
|
+
* using the provided `change` value.
|
|
4
|
+
*
|
|
5
|
+
* - `/**` is first canonicalized to `/<0;1>/*`.
|
|
6
|
+
* - All tuples in the descriptor must have the same cardinality.
|
|
7
|
+
* - Tuple values must be strictly increasing decimal numbers.
|
|
8
|
+
* - `change` must match one of the values in each tuple.
|
|
9
|
+
*/
|
|
10
|
+
export declare function resolveMultipathDescriptor({ descriptor, change }: {
|
|
11
|
+
descriptor: string;
|
|
12
|
+
change?: number;
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveMultipathDescriptor = resolveMultipathDescriptor;
|
|
4
|
+
/**
|
|
5
|
+
* Replaces the receive/change shorthand `/**` with its canonical multipath
|
|
6
|
+
* representation `/<0;1>/*`.
|
|
7
|
+
*/
|
|
8
|
+
function expandReceiveChangeShorthand(descriptor) {
|
|
9
|
+
return descriptor.replace(/\/\*\*/g, '/<0;1>/*');
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Parses a multipath tuple body from `<...>`.
|
|
13
|
+
*
|
|
14
|
+
* This implementation intentionally accepts only decimal numbers (no hardened
|
|
15
|
+
* suffixes) and enforces strict left-to-right increase as a safety feature to
|
|
16
|
+
* catch likely human errors such as `<1;0>`.
|
|
17
|
+
*/
|
|
18
|
+
function parseMultipathTuple(tupleBody) {
|
|
19
|
+
const parts = tupleBody.split(';');
|
|
20
|
+
if (parts.length < 2)
|
|
21
|
+
throw new Error(`Error: multipath tuple must contain at least 2 values, got <${tupleBody}>`);
|
|
22
|
+
const values = parts.map(part => {
|
|
23
|
+
if (!/^(0|[1-9]\d*)$/.test(part))
|
|
24
|
+
throw new Error(`Error: multipath tuple values must be decimal numbers, got <${tupleBody}>`);
|
|
25
|
+
const value = Number(part);
|
|
26
|
+
if (!Number.isSafeInteger(value))
|
|
27
|
+
throw new Error(`Error: multipath tuple value overflow, got <${tupleBody}>`);
|
|
28
|
+
return value;
|
|
29
|
+
});
|
|
30
|
+
for (let i = 1; i < values.length; i++) {
|
|
31
|
+
const prev = values[i - 1];
|
|
32
|
+
const current = values[i];
|
|
33
|
+
if (prev === undefined || current === undefined)
|
|
34
|
+
throw new Error(`Error: invalid multipath tuple <${tupleBody}>`);
|
|
35
|
+
if (current <= prev)
|
|
36
|
+
throw new Error(`Error: multipath tuple values must be strictly increasing from left to right, got <${tupleBody}>`);
|
|
37
|
+
}
|
|
38
|
+
return values;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Resolves all multipath tuple segments (for example `/<0;1>/*`) in lockstep
|
|
42
|
+
* using the provided `change` value.
|
|
43
|
+
*
|
|
44
|
+
* - `/**` is first canonicalized to `/<0;1>/*`.
|
|
45
|
+
* - All tuples in the descriptor must have the same cardinality.
|
|
46
|
+
* - Tuple values must be strictly increasing decimal numbers.
|
|
47
|
+
* - `change` must match one of the values in each tuple.
|
|
48
|
+
*/
|
|
49
|
+
function resolveMultipathDescriptor({ descriptor, change }) {
|
|
50
|
+
const canonicalDescriptor = expandReceiveChangeShorthand(descriptor);
|
|
51
|
+
const tupleMatches = Array.from(canonicalDescriptor.matchAll(/\/<([^<>]+)>/g), match => ({
|
|
52
|
+
token: match[0],
|
|
53
|
+
tupleBody: match[1],
|
|
54
|
+
values: parseMultipathTuple(match[1])
|
|
55
|
+
}));
|
|
56
|
+
if (tupleMatches.length === 0)
|
|
57
|
+
return canonicalDescriptor;
|
|
58
|
+
if (change === undefined)
|
|
59
|
+
throw new Error(`Error: change was not provided for multipath descriptor`);
|
|
60
|
+
if (!Number.isInteger(change) || change < 0)
|
|
61
|
+
throw new Error(`Error: invalid change ${change}`);
|
|
62
|
+
const tupleSize = tupleMatches[0]?.values.length;
|
|
63
|
+
if (!tupleSize)
|
|
64
|
+
throw new Error(`Error: invalid multipath tuple`);
|
|
65
|
+
for (const tupleMatch of tupleMatches) {
|
|
66
|
+
if (tupleMatch.values.length !== tupleSize)
|
|
67
|
+
throw new Error(`Error: all multipath tuples must have the same number of options`);
|
|
68
|
+
}
|
|
69
|
+
let resolvedDescriptor = canonicalDescriptor;
|
|
70
|
+
for (const tupleMatch of tupleMatches) {
|
|
71
|
+
if (!tupleMatch.values.includes(change))
|
|
72
|
+
throw new Error(`Error: change ${change} not found in multipath tuple <${tupleMatch.tupleBody}>`);
|
|
73
|
+
resolvedDescriptor = resolvedDescriptor.replaceAll(tupleMatch.token, `/${change}`);
|
|
74
|
+
}
|
|
75
|
+
return resolvedDescriptor;
|
|
76
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.isBitcoinMainnet = isBitcoinMainnet;
|
|
4
|
+
exports.coinTypeFromNetwork = coinTypeFromNetwork;
|
|
5
|
+
const networks_1 = require("./networks");
|
|
6
|
+
function isBitcoinMainnet(network) {
|
|
7
|
+
return (network.bech32 === networks_1.networks.bitcoin.bech32 &&
|
|
8
|
+
network.bip32.public === networks_1.networks.bitcoin.bip32.public &&
|
|
9
|
+
network.bip32.private === networks_1.networks.bitcoin.bip32.private &&
|
|
10
|
+
network.pubKeyHash === networks_1.networks.bitcoin.pubKeyHash &&
|
|
11
|
+
network.scriptHash === networks_1.networks.bitcoin.scriptHash &&
|
|
12
|
+
network.wif === networks_1.networks.bitcoin.wif);
|
|
13
|
+
}
|
|
14
|
+
function coinTypeFromNetwork(network) {
|
|
15
|
+
return isBitcoinMainnet(network) ? 0 : 1;
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Network {
|
|
2
|
+
messagePrefix: string;
|
|
3
|
+
bech32: string;
|
|
4
|
+
bip32: {
|
|
5
|
+
public: number;
|
|
6
|
+
private: number;
|
|
7
|
+
};
|
|
8
|
+
pubKeyHash: number;
|
|
9
|
+
scriptHash: number;
|
|
10
|
+
wif: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const networks: {
|
|
13
|
+
bitcoin: Network;
|
|
14
|
+
testnet: Network;
|
|
15
|
+
regtest: Network;
|
|
16
|
+
};
|
package/dist/networks.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Copyright (c) 2026 Jose-Luis Landabaso - https://bitcoinerlab.com
|
|
3
|
+
// Distributed under the MIT software license
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
exports.networks = void 0;
|
|
6
|
+
exports.networks = {
|
|
7
|
+
bitcoin: {
|
|
8
|
+
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
|
9
|
+
bech32: 'bc',
|
|
10
|
+
bip32: { public: 0x0488b21e, private: 0x0488ade4 },
|
|
11
|
+
pubKeyHash: 0x00,
|
|
12
|
+
scriptHash: 0x05,
|
|
13
|
+
wif: 0x80
|
|
14
|
+
},
|
|
15
|
+
testnet: {
|
|
16
|
+
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
|
17
|
+
bech32: 'tb',
|
|
18
|
+
bip32: { public: 0x043587cf, private: 0x04358394 },
|
|
19
|
+
pubKeyHash: 0x6f,
|
|
20
|
+
scriptHash: 0xc4,
|
|
21
|
+
wif: 0xef
|
|
22
|
+
},
|
|
23
|
+
regtest: {
|
|
24
|
+
messagePrefix: '\x18Bitcoin Signed Message:\n',
|
|
25
|
+
bech32: 'bcrt',
|
|
26
|
+
bip32: { public: 0x043587cf, private: 0x04358394 },
|
|
27
|
+
pubKeyHash: 0x6f,
|
|
28
|
+
scriptHash: 0xc4,
|
|
29
|
+
wif: 0xef
|
|
30
|
+
}
|
|
31
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.splitTopLevelComma = splitTopLevelComma;
|
|
4
|
+
function splitTopLevelComma({ expression, onError }) {
|
|
5
|
+
let braceDepth = 0;
|
|
6
|
+
let parenDepth = 0;
|
|
7
|
+
let commaIndex = -1;
|
|
8
|
+
for (let i = 0; i < expression.length; i++) {
|
|
9
|
+
const char = expression[i];
|
|
10
|
+
if (!char)
|
|
11
|
+
continue;
|
|
12
|
+
if (char === '{') {
|
|
13
|
+
braceDepth++;
|
|
14
|
+
}
|
|
15
|
+
else if (char === '}') {
|
|
16
|
+
if (braceDepth === 0)
|
|
17
|
+
throw onError(expression);
|
|
18
|
+
braceDepth--;
|
|
19
|
+
}
|
|
20
|
+
else if (char === '(') {
|
|
21
|
+
//Track miniscript argument lists so we don't split on commas inside them.
|
|
22
|
+
parenDepth++;
|
|
23
|
+
}
|
|
24
|
+
else if (char === ')') {
|
|
25
|
+
if (parenDepth === 0)
|
|
26
|
+
throw onError(expression);
|
|
27
|
+
parenDepth--;
|
|
28
|
+
}
|
|
29
|
+
else if (char === ',') {
|
|
30
|
+
if (braceDepth === 0 && parenDepth === 0) {
|
|
31
|
+
if (commaIndex !== -1)
|
|
32
|
+
throw onError(expression);
|
|
33
|
+
commaIndex = i;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (braceDepth !== 0 || parenDepth !== 0)
|
|
38
|
+
throw onError(expression);
|
|
39
|
+
if (commaIndex === -1)
|
|
40
|
+
return null;
|
|
41
|
+
const left = expression.slice(0, commaIndex).trim();
|
|
42
|
+
const right = expression.slice(commaIndex + 1).trim();
|
|
43
|
+
if (!left || !right)
|
|
44
|
+
throw onError(expression);
|
|
45
|
+
return { left, right };
|
|
46
|
+
}
|