@kukks/bitcoin-descriptors 3.0.2

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.
@@ -0,0 +1,12 @@
1
+ export type { KeyInfo, Expansion } from './types.js';
2
+ export type { OutputInstance } from './descriptors.js';
3
+ export { DescriptorsFactory, OutputConstructor } from './descriptors.js';
4
+ export { DescriptorChecksum as checksum } from './checksum.js';
5
+ import * as signers from './signers.js';
6
+ export { signers };
7
+ export { keyExpressionBIP32 } from './keyExpressions.js';
8
+ import * as scriptExpressions from './scriptExpressions.js';
9
+ export { scriptExpressions };
10
+ export type { PsbtLike } from './psbt.js';
11
+ export { networks } from './networks.js';
12
+ export type { Network } from './networks.js';
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ export { DescriptorsFactory } from './descriptors.js';
4
+ export { DescriptorChecksum as checksum } from './checksum.js';
5
+ import * as signers from './signers.js';
6
+ export { signers };
7
+ export { keyExpressionBIP32 } from './keyExpressions.js';
8
+ import * as scriptExpressions from './scriptExpressions.js';
9
+ export { scriptExpressions };
10
+ export { networks } from './networks.js';
@@ -0,0 +1,59 @@
1
+ import { Network } from './networks.js';
2
+ import type { ECPairAPI, BIP32API, BIP32Interface } from './types.js';
3
+ import type { KeyInfo } from './types.js';
4
+ /**
5
+ * Parses a key expression (xpub, xprv, pubkey or wif) into {@link KeyInfo | `KeyInfo`}.
6
+ *
7
+ * For example, given this `keyExpression`: `"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*"`, this is its parsed result:
8
+ *
9
+ * ```javascript
10
+ * {
11
+ * keyExpression:
12
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
13
+ * keyPath: '/1/2/3/4/*',
14
+ * originPath: "/49'/0'/0'",
15
+ * path: "m/49'/0'/0'/1/2/3/4/*",
16
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
17
+ * }
18
+ * ```
19
+ */
20
+ export declare function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network }: {
21
+ keyExpression: string;
22
+ /** @default networks.bitcoin */
23
+ network?: Network;
24
+ /**
25
+ * Indicates if this key expression belongs to a a SegWit output. When set,
26
+ * further checks are done to ensure the public key (if present in the
27
+ * expression) is compressed (33 bytes).
28
+ */
29
+ isSegwit?: boolean;
30
+ /**
31
+ * Indicates if this key expression belongs to a Taproot output. For Taproot,
32
+ * the key must be represented as an x-only public key (32 bytes).
33
+ * If a 33-byte compressed pubkey is derived, it is converted to its x-only
34
+ * representation.
35
+ */
36
+ isTaproot?: boolean;
37
+ ECPair: ECPairAPI;
38
+ BIP32: BIP32API;
39
+ }): KeyInfo;
40
+ /**
41
+ * Constructs a key expression string from its constituent components.
42
+ *
43
+ * This function essentially performs the reverse operation of
44
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}. For detailed
45
+ * explanations and examples of the terms used here, refer to
46
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
47
+ */
48
+ export declare function keyExpressionBIP32({ masterNode, originPath, keyPath, change, index, isPublic }: {
49
+ masterNode: BIP32Interface;
50
+ originPath: string;
51
+ change?: number | undefined;
52
+ index?: number | undefined | '*';
53
+ keyPath?: string | undefined;
54
+ /**
55
+ * Compute an xpub or xprv
56
+ * @default true
57
+ */
58
+ isPublic?: boolean;
59
+ }): string;
@@ -0,0 +1,197 @@
1
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ import { networks } from './networks.js';
4
+ import { hex } from '@scure/base';
5
+ import { concatBytes } from '@scure/btc-signer/utils.js';
6
+ import * as RE from './re.js';
7
+ const derivePath = (node, path) => {
8
+ if (typeof path !== 'string') {
9
+ throw new Error(`Error: invalid derivation path ${path}`);
10
+ }
11
+ const parsedPath = path.replaceAll('H', "'").replaceAll('h', "'").slice(1);
12
+ const splitPath = parsedPath.split('/');
13
+ for (const element of splitPath) {
14
+ const unhardened = element.endsWith("'") ? element.slice(0, -1) : element;
15
+ if (!Number.isInteger(Number(unhardened)) ||
16
+ Number(unhardened) >= 0x80000000)
17
+ throw new Error(`Error: BIP 32 path element overflow`);
18
+ }
19
+ return node.derivePath(parsedPath);
20
+ };
21
+ /**
22
+ * Parses a key expression (xpub, xprv, pubkey or wif) into {@link KeyInfo | `KeyInfo`}.
23
+ *
24
+ * For example, given this `keyExpression`: `"[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*"`, this is its parsed result:
25
+ *
26
+ * ```javascript
27
+ * {
28
+ * keyExpression:
29
+ * "[d34db33f/49'/0'/0']tpubDCdxmvzJ5QBjTN8oCjjyT2V58AyZvA1fkmCeZRC75QMoaHcVP2m45Bv3hmnR7ttAwkb2UNYyoXdHVt4gwBqRrJqLUU2JrM43HippxiWpHra/1/2/3/4/*",
30
+ * keyPath: '/1/2/3/4/*',
31
+ * originPath: "/49'/0'/0'",
32
+ * path: "m/49'/0'/0'/1/2/3/4/*",
33
+ * // Other relevant properties of the type `KeyInfo`: `pubkey`, `ecpair` & `bip32` interfaces, `masterFingerprint`, etc.
34
+ * }
35
+ * ```
36
+ */
37
+ export function parseKeyExpression({ keyExpression, isSegwit, isTaproot, ECPair, BIP32, network = networks.bitcoin }) {
38
+ let pubkey; //won't be computed for ranged keyExpressions
39
+ let ecpair;
40
+ let bip32;
41
+ let masterFingerprint;
42
+ let originPath;
43
+ let keyPath;
44
+ let path;
45
+ const isRanged = keyExpression.indexOf('*') !== -1;
46
+ const reKeyExp = isTaproot
47
+ ? RE.reTaprootKeyExp
48
+ : isSegwit
49
+ ? RE.reSegwitKeyExp
50
+ : RE.reNonSegwitKeyExp;
51
+ const rePubKey = isTaproot
52
+ ? RE.reTaprootPubKey
53
+ : isSegwit
54
+ ? RE.reSegwitPubKey
55
+ : RE.reNonSegwitPubKey;
56
+ //Validate the keyExpression:
57
+ const keyExpressions = keyExpression.match(reKeyExp);
58
+ if (keyExpressions === null || keyExpressions[0] !== keyExpression) {
59
+ throw new Error(`Error: expected a keyExpression but got ${keyExpression}`);
60
+ }
61
+ const reOriginAnchoredStart = RegExp(String.raw `^(${RE.reOrigin})?`); //starts with ^origin
62
+ const mOrigin = keyExpression.match(reOriginAnchoredStart);
63
+ if (mOrigin) {
64
+ const bareOrigin = mOrigin[0].replace(/[[\]]/g, ''); //strip the "[" and "]" in [origin]
65
+ const reMasterFingerprintAnchoredStart = String.raw `^(${RE.reMasterFingerprint})`;
66
+ const mMasterFingerprint = bareOrigin.match(reMasterFingerprintAnchoredStart);
67
+ const masterFingerprintHex = mMasterFingerprint
68
+ ? mMasterFingerprint[0]
69
+ : '';
70
+ originPath = bareOrigin.replace(masterFingerprintHex, '');
71
+ if (masterFingerprintHex.length > 0) {
72
+ if (masterFingerprintHex.length !== 8)
73
+ throw new Error(`Error: masterFingerprint ${masterFingerprintHex} invalid for keyExpression: ${keyExpression}`);
74
+ masterFingerprint = hex.decode(masterFingerprintHex);
75
+ }
76
+ }
77
+ //Remove the origin (if it exists) and store result in actualKey
78
+ const actualKey = keyExpression.replace(reOriginAnchoredStart, '');
79
+ let mPubKey, mWIF, mXpubKey, mXprvKey;
80
+ //match pubkey:
81
+ if ((mPubKey = actualKey.match(RE.anchorStartAndEnd(rePubKey))) !== null) {
82
+ pubkey = hex.decode(mPubKey[0]);
83
+ if (isTaproot && pubkey.length === 32)
84
+ //convert the xonly point to a compressed point assuming even parity
85
+ pubkey = concatBytes(Uint8Array.from([0x02]), pubkey);
86
+ ecpair = ECPair.fromPublicKey(pubkey, { network });
87
+ //Validate the pubkey (compressed or uncompressed)
88
+ if (!ECPair.isPoint(pubkey) ||
89
+ !(pubkey.length === 33 || pubkey.length === 65)) {
90
+ throw new Error(`Error: invalid pubkey`);
91
+ }
92
+ //Do an extra check in case we know this pubkey refers to a segwit input
93
+ if (typeof isSegwit === 'boolean' &&
94
+ isSegwit &&
95
+ pubkey.length !== 33 //Inside wpkh and wsh, only compressed public keys are permitted.
96
+ ) {
97
+ throw new Error(`Error: invalid pubkey`);
98
+ }
99
+ //match WIF:
100
+ }
101
+ else if ((mWIF = actualKey.match(RE.anchorStartAndEnd(RE.reWIF))) !== null) {
102
+ ecpair = ECPair.fromWIF(mWIF[0], network);
103
+ //fromWIF will throw if the wif is not valid
104
+ pubkey = ecpair.publicKey;
105
+ //Check segwit requires compressed keys
106
+ if (typeof isSegwit === 'boolean' && isSegwit && pubkey.length !== 33) {
107
+ throw new Error(`Error: invalid pubkey`);
108
+ }
109
+ //match xpub:
110
+ }
111
+ else if ((mXpubKey = actualKey.match(RE.anchorStartAndEnd(RE.reXpubKey))) !== null) {
112
+ const xPubKey = mXpubKey[0];
113
+ const xPub = xPubKey.match(RE.reXpub)?.[0];
114
+ if (!xPub)
115
+ throw new Error(`Error: xpub could not be matched`);
116
+ bip32 = BIP32.fromBase58(xPub, network);
117
+ const mPath = xPubKey.match(RE.rePath);
118
+ if (mPath !== null) {
119
+ keyPath = xPubKey.match(RE.rePath)?.[0];
120
+ if (!keyPath)
121
+ throw new Error(`Error: could not extract a path`);
122
+ //fromBase58 and derivePath will throw if xPub or path are not valid
123
+ if (!isRanged)
124
+ pubkey = derivePath(bip32, keyPath).publicKey;
125
+ }
126
+ else {
127
+ pubkey = bip32.publicKey;
128
+ }
129
+ //match xprv:
130
+ }
131
+ else if ((mXprvKey = actualKey.match(RE.anchorStartAndEnd(RE.reXprvKey))) !== null) {
132
+ const xPrvKey = mXprvKey[0];
133
+ const xPrv = xPrvKey.match(RE.reXprv)?.[0];
134
+ if (!xPrv)
135
+ throw new Error(`Error: xprv could not be matched`);
136
+ bip32 = BIP32.fromBase58(xPrv, network);
137
+ const mPath = xPrvKey.match(RE.rePath);
138
+ if (mPath !== null) {
139
+ keyPath = xPrvKey.match(RE.rePath)?.[0];
140
+ if (!keyPath)
141
+ throw new Error(`Error: could not extract a path`);
142
+ //fromBase58 and derivePath will throw if xPrv or path are not valid
143
+ if (!isRanged)
144
+ pubkey = derivePath(bip32, keyPath).publicKey;
145
+ }
146
+ else {
147
+ pubkey = bip32.publicKey;
148
+ }
149
+ }
150
+ else {
151
+ throw new Error(`Error: could not get pubkey for keyExpression ${keyExpression}`);
152
+ }
153
+ if (originPath || keyPath) {
154
+ path = `m${originPath ?? ''}${keyPath ?? ''}`;
155
+ }
156
+ if (pubkey !== undefined && isTaproot && pubkey.length === 33)
157
+ // If we get a 33-byte compressed key, drop the first byte.
158
+ pubkey = pubkey.slice(1, 33);
159
+ return {
160
+ keyExpression,
161
+ ...(pubkey !== undefined ? { pubkey } : {}),
162
+ ...(ecpair !== undefined ? { ecpair } : {}),
163
+ ...(bip32 !== undefined ? { bip32 } : {}),
164
+ ...(masterFingerprint !== undefined ? { masterFingerprint } : {}),
165
+ ...(originPath !== undefined && originPath !== '' ? { originPath } : {}),
166
+ ...(keyPath !== undefined && keyPath !== '' ? { keyPath } : {}),
167
+ ...(path !== undefined ? { path } : {})
168
+ };
169
+ }
170
+ function assertChangeIndexKeyPath({ change, index, keyPath }) {
171
+ if (!((change === undefined && index === undefined) ||
172
+ (change !== undefined && index !== undefined)))
173
+ throw new Error(`Error: Pass change and index or neither`);
174
+ if ((change !== undefined) === (keyPath !== undefined))
175
+ throw new Error(`Error: Pass either change and index or a keyPath`);
176
+ }
177
+ /**
178
+ * Constructs a key expression string from its constituent components.
179
+ *
180
+ * This function essentially performs the reverse operation of
181
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}. For detailed
182
+ * explanations and examples of the terms used here, refer to
183
+ * {@link _Internal_.ParseKeyExpression | ParseKeyExpression}.
184
+ */
185
+ export function keyExpressionBIP32({ masterNode, originPath, keyPath, change, index, isPublic = true }) {
186
+ assertChangeIndexKeyPath({ change, index, keyPath });
187
+ const masterFingerprint = masterNode.fingerprint;
188
+ const origin = `[${hex.encode(masterFingerprint)}${originPath}]`;
189
+ const xpub = isPublic
190
+ ? masterNode.derivePath(`m${originPath}`).neutered().toBase58().toString()
191
+ : masterNode.derivePath(`m${originPath}`).toBase58().toString();
192
+ const keyRoot = `${origin}${xpub}`;
193
+ if (keyPath !== undefined)
194
+ return `${keyRoot}${keyPath}`;
195
+ else
196
+ return `${keyRoot}/${change}/${index}`;
197
+ }
@@ -0,0 +1,55 @@
1
+ import { Network } from './networks.js';
2
+ import type { ECPairAPI } from './types.js';
3
+ import type { BIP32API } from './types.js';
4
+ import type { PartialSig } from './types.js';
5
+ import type { Preimage, TimeConstraints, ExpansionMap } from './types.js';
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: ECPairAPI;
19
+ BIP32: BIP32API;
20
+ }): {
21
+ expandedMiniscript: string;
22
+ expansionMap: ExpansionMap;
23
+ };
24
+ export declare function miniscript2Script({ expandedMiniscript, expansionMap }: {
25
+ expandedMiniscript: string;
26
+ expansionMap: ExpansionMap;
27
+ }): Uint8Array;
28
+ /**
29
+ * Assumptions:
30
+ * The attacker does not have access to any of the private keys of public keys
31
+ * that participate in the Script.
32
+ *
33
+ * The attacker only has access to hash preimages that honest users have access
34
+ * to as well.
35
+ *
36
+ * Pass timeConstraints to search for the first solution with this nLockTime and
37
+ * nSequence. Throw if no solution is possible using these constraints.
38
+ *
39
+ * Don't pass timeConstraints (this is the default) if you want to get the
40
+ * smallest size solution altogether.
41
+ *
42
+ * If a solution is not found this function throws.
43
+ */
44
+ export declare function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures, preimages, timeConstraints }: {
45
+ expandedMiniscript: string;
46
+ expansionMap: ExpansionMap;
47
+ signatures?: PartialSig[];
48
+ preimages?: Preimage[];
49
+ timeConstraints?: TimeConstraints;
50
+ }): {
51
+ scriptSatisfaction: Uint8Array;
52
+ nLockTime: number | undefined;
53
+ nSequence: number | undefined;
54
+ };
55
+ export { numberEncodeAsm } from './scriptUtils.js';
@@ -0,0 +1,163 @@
1
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ import { networks } from './networks.js';
4
+ import { fromASM, numberEncodeAsm } from './scriptUtils.js';
5
+ import { hash160 } from '@scure/btc-signer/utils.js';
6
+ import { hex } from '@scure/base';
7
+ import { parseKeyExpression } from './keyExpressions.js';
8
+ import * as RE from './re.js';
9
+ import { compileMiniscript, satisfier } from '@bitcoinerlab/miniscript';
10
+ /**
11
+ * Expand a miniscript to a generalized form using variables instead of key
12
+ * expressions. Variables will be of this form: @0, @1, ...
13
+ * This is done so that it can be compiled with compileMiniscript and
14
+ * satisfied with satisfier.
15
+ * Also compute pubkeys from descriptors to use them later.
16
+ */
17
+ export function expandMiniscript({ miniscript, isSegwit, isTaproot, network = networks.bitcoin, ECPair, BIP32 }) {
18
+ if (isTaproot)
19
+ throw new Error('Taproot miniscript not yet supported.');
20
+ const reKeyExp = isTaproot
21
+ ? RE.reTaprootKeyExp
22
+ : isSegwit
23
+ ? RE.reSegwitKeyExp
24
+ : RE.reNonSegwitKeyExp;
25
+ const expansionMap = {};
26
+ const expandedMiniscript = miniscript.replace(RegExp(reKeyExp, 'g'), (keyExpression) => {
27
+ const key = '@' + Object.keys(expansionMap).length;
28
+ expansionMap[key] = parseKeyExpression({
29
+ keyExpression,
30
+ isSegwit,
31
+ network,
32
+ ECPair,
33
+ BIP32
34
+ });
35
+ return key;
36
+ });
37
+ //Do some assertions. Miniscript must not have duplicate keys, also all
38
+ //keyExpressions must produce a valid pubkey (unless it's ranged and we want
39
+ //to expand a generalized form, then we don't check)
40
+ const pubkeysHex = Object.values(expansionMap)
41
+ .filter(keyInfo => keyInfo.keyExpression.indexOf('*') === -1)
42
+ .map(keyInfo => {
43
+ if (!keyInfo.pubkey)
44
+ throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
45
+ return hex.encode(keyInfo.pubkey);
46
+ });
47
+ if (new Set(pubkeysHex).size !== pubkeysHex.length) {
48
+ throw new Error(`Error: miniscript ${miniscript} is not sane: contains duplicate public keys.`);
49
+ }
50
+ return { expandedMiniscript, expansionMap };
51
+ }
52
+ /**
53
+ * Particularize an expanded ASM expression using the variables in
54
+ * expansionMap.
55
+ * This is the kind of the opposite to what expandMiniscript does.
56
+ * Signatures and preimages are already subsituted by the satisfier calling
57
+ * this function.
58
+ */
59
+ function substituteAsm({ expandedAsm, expansionMap }) {
60
+ //Replace back variables into the pubkeys previously computed.
61
+ let asm = Object.keys(expansionMap).reduce((accAsm, key) => {
62
+ const pubkey = expansionMap[key]?.pubkey;
63
+ if (!pubkey) {
64
+ throw new Error(`Error: invalid expansionMap for ${key}`);
65
+ }
66
+ return accAsm
67
+ .replaceAll(`<${key}>`, `<${hex.encode(pubkey)}>`)
68
+ .replaceAll(`<HASH160(${key})>`, `<${hex.encode(hash160(pubkey))}>`);
69
+ }, expandedAsm);
70
+ //Now clean it and prepare it so that fromASM can be called:
71
+ asm = asm
72
+ .trim()
73
+ //Replace one or more consecutive whitespace characters (spaces, tabs,
74
+ //or line breaks) with a single space.
75
+ .replace(/\s+/g, ' ')
76
+ //Now encode numbers to little endian hex. Note that numbers are not
77
+ //enclosed in <>, since <> represents hex code already encoded.
78
+ //The regex below will match one or more digits within a string,
79
+ //except if the sequence is surrounded by "<" and ">"
80
+ .replace(/(<\d+>)|\b\d+\b/g, match => match.startsWith('<') ? match : numberEncodeAsm(Number(match)))
81
+ //we don't have numbers anymore, now it's safe to remove < and > since we
82
+ //know that every remaining is either an op_code or a hex encoded number
83
+ .replace(/[<>]/g, '');
84
+ return asm;
85
+ }
86
+ export function miniscript2Script({ expandedMiniscript, expansionMap }) {
87
+ const compiled = compileMiniscript(expandedMiniscript);
88
+ if (compiled.issane !== true) {
89
+ throw new Error(`Error: Miniscript ${expandedMiniscript} is not sane`);
90
+ }
91
+ return fromASM(substituteAsm({ expandedAsm: compiled.asm, expansionMap }));
92
+ }
93
+ /**
94
+ * Assumptions:
95
+ * The attacker does not have access to any of the private keys of public keys
96
+ * that participate in the Script.
97
+ *
98
+ * The attacker only has access to hash preimages that honest users have access
99
+ * to as well.
100
+ *
101
+ * Pass timeConstraints to search for the first solution with this nLockTime and
102
+ * nSequence. Throw if no solution is possible using these constraints.
103
+ *
104
+ * Don't pass timeConstraints (this is the default) if you want to get the
105
+ * smallest size solution altogether.
106
+ *
107
+ * If a solution is not found this function throws.
108
+ */
109
+ export function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [], preimages = [], timeConstraints }) {
110
+ //convert 'sha256(6c...33)' to: { ['<sha256_preimage(6c...33)>']: '10...5f'}
111
+ const preimageMap = {};
112
+ preimages.forEach(preimage => {
113
+ preimageMap['<' + preimage.digest.replace('(', '_preimage(') + '>'] =
114
+ '<' + preimage.preimage + '>';
115
+ });
116
+ //convert the pubkeys in signatures into [{['<sig(@0)>']: '30450221'}, ...]
117
+ //get the keyExpressions: @0, @1 from the keys in expansionMap
118
+ const expandedSignatureMap = {};
119
+ signatures.forEach(signature => {
120
+ const pubkeyHex = hex.encode(signature.pubkey);
121
+ const keyExpression = Object.keys(expansionMap).find(k => {
122
+ const pk = expansionMap[k]?.pubkey;
123
+ return pk ? hex.encode(pk) === pubkeyHex : false;
124
+ });
125
+ expandedSignatureMap['<sig(' + keyExpression + ')>'] =
126
+ '<' + hex.encode(signature.signature) + '>';
127
+ });
128
+ const expandedKnownsMap = { ...preimageMap, ...expandedSignatureMap };
129
+ const knowns = Object.keys(expandedKnownsMap);
130
+ //satisfier verifies again internally whether expandedKnownsMap with given knowns is sane
131
+ const { nonMalleableSats } = satisfier(expandedMiniscript, { knowns });
132
+ if (!Array.isArray(nonMalleableSats) || !nonMalleableSats[0])
133
+ throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}`);
134
+ let sat;
135
+ if (!timeConstraints) {
136
+ sat = nonMalleableSats[0];
137
+ }
138
+ else {
139
+ sat = nonMalleableSats.find(nonMalleableSat => nonMalleableSat.nSequence === timeConstraints.nSequence &&
140
+ nonMalleableSat.nLockTime === timeConstraints.nLockTime);
141
+ if (sat === undefined) {
142
+ 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?`);
143
+ }
144
+ }
145
+ //substitute signatures and preimages:
146
+ let expandedAsm = sat.asm;
147
+ //replace in expandedAsm all the <sig(@0)> and <sha256_preimage(6c...33)>
148
+ //to <304...01> and <107...5f> ...
149
+ for (const search in expandedKnownsMap) {
150
+ const replace = expandedKnownsMap[search];
151
+ if (!replace || replace === '<>')
152
+ throw new Error(`Error: invalid expandedKnownsMap`);
153
+ expandedAsm = expandedAsm.replaceAll(search, replace);
154
+ }
155
+ const scriptSatisfaction = fromASM(substituteAsm({ expandedAsm, expansionMap }));
156
+ return {
157
+ scriptSatisfaction,
158
+ nLockTime: sat.nLockTime,
159
+ nSequence: sat.nSequence
160
+ };
161
+ }
162
+ // Re-export numberEncodeAsm from scriptUtils
163
+ export { numberEncodeAsm } from './scriptUtils.js';
@@ -0,0 +1,23 @@
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
+ };
17
+ /** Convert our Network to the format expected by @scure/btc-signer */
18
+ export declare function toBtcSignerNetwork(network: Network): {
19
+ bech32: string;
20
+ pubKeyHash: number;
21
+ scriptHash: number;
22
+ wif: number;
23
+ };
@@ -0,0 +1,37 @@
1
+ // Copyright (c) 2025 Jose-Luis Landabaso - https://bitcoinerlab.com
2
+ // Distributed under the MIT software license
3
+ export const networks = {
4
+ bitcoin: {
5
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
6
+ bech32: 'bc',
7
+ bip32: { public: 0x0488b21e, private: 0x0488ade4 },
8
+ pubKeyHash: 0x00,
9
+ scriptHash: 0x05,
10
+ wif: 0x80
11
+ },
12
+ testnet: {
13
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
14
+ bech32: 'tb',
15
+ bip32: { public: 0x043587cf, private: 0x04358394 },
16
+ pubKeyHash: 0x6f,
17
+ scriptHash: 0xc4,
18
+ wif: 0xef
19
+ },
20
+ regtest: {
21
+ messagePrefix: '\x18Bitcoin Signed Message:\n',
22
+ bech32: 'bcrt',
23
+ bip32: { public: 0x043587cf, private: 0x04358394 },
24
+ pubKeyHash: 0x6f,
25
+ scriptHash: 0xc4,
26
+ wif: 0xef
27
+ }
28
+ };
29
+ /** Convert our Network to the format expected by @scure/btc-signer */
30
+ export function toBtcSignerNetwork(network) {
31
+ return {
32
+ bech32: network.bech32,
33
+ pubKeyHash: network.pubKeyHash,
34
+ scriptHash: network.scriptHash,
35
+ wif: network.wif
36
+ };
37
+ }
package/dist/psbt.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ import * as btc from '@scure/btc-signer';
2
+ import type { KeyInfo } from './types.js';
3
+ type PsbtLike = InstanceType<typeof btc.Transaction>;
4
+ /**
5
+ * Computes final scripts for miniscript-based inputs.
6
+ *
7
+ * @param scriptSatisfaction - The script satisfaction (input script data)
8
+ * @param lockingScript - The "meaningful" locking script (witnessScript or redeemScript)
9
+ * @param isSegwit - Whether this is a segwit input
10
+ * @param isP2SH - Whether this is a P2SH input
11
+ * @param network - The network
12
+ * @returns Object with finalScriptSig and finalScriptWitness (as Uint8Array[])
13
+ */
14
+ export declare function computeFinalScripts(scriptSatisfaction: Uint8Array, lockingScript: Uint8Array, isSegwit: boolean, isP2SH: boolean, redeemScript?: Uint8Array): {
15
+ finalScriptSig: Uint8Array | undefined;
16
+ finalScriptWitness: Uint8Array[] | undefined;
17
+ };
18
+ /**
19
+ * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
20
+ */
21
+ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, tapInternalKey, witnessScript, redeemScript, rbf }: {
22
+ psbt: PsbtLike;
23
+ vout: number;
24
+ txHex?: string;
25
+ txId?: string;
26
+ value?: number | bigint;
27
+ sequence: number | undefined;
28
+ locktime: number | undefined;
29
+ keysInfo: KeyInfo[];
30
+ scriptPubKey: Uint8Array;
31
+ isSegwit: boolean;
32
+ /** for taproot **/
33
+ tapInternalKey?: Uint8Array | undefined;
34
+ witnessScript: Uint8Array | undefined;
35
+ redeemScript: Uint8Array | undefined;
36
+ rbf: boolean;
37
+ }): number;
38
+ export type { PsbtLike };