@bitcoinerlab/descriptors 0.2.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.
@@ -0,0 +1,109 @@
1
+ /// <reference types="node" />
2
+ import { Network } from 'bitcoinjs-lib';
3
+ import type { ECPairAPI } from 'ecpair';
4
+ import type { BIP32API } from 'bip32';
5
+ import type { PartialSig } from 'bip174/src/lib/interfaces';
6
+ import type { Preimage, TimeConstraints, ExpansionMap } from './types';
7
+ /**
8
+ * Expand a miniscript to a generalized form using variables instead of key
9
+ * expressions. Variables will be of this form: @0, @1, ...
10
+ * This is done so that it can be compiled with compileMiniscript and
11
+ * satisfied with satisfier.
12
+ * Also compute pubkeys from descriptors to use them later.
13
+ */
14
+ export declare function expandMiniscript({ miniscript, isSegwit, network, ECPair, BIP32 }: {
15
+ miniscript: string;
16
+ isSegwit: 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
+ }): Buffer;
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: Buffer;
52
+ nLockTime: number | undefined;
53
+ nSequence: number | undefined;
54
+ };
55
+ /**
56
+ *
57
+ * Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
58
+ * when encoding numbers to be compiled with `fromASM` to avoid problems.
59
+ *
60
+ * Motivation:
61
+ *
62
+ * Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
63
+ * Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
64
+ *
65
+ * This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
66
+ * can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
67
+ * does.
68
+ *
69
+ * Note that `fromASM` already converts integers from 1 to 16 to
70
+ * OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
71
+ * This is done in Bitcoin to save some bits.
72
+ *
73
+ * Neither this function nor `script.number.encode` convert numbers to
74
+ * their op code equivalent since this is done later in `fromASM`.
75
+ *
76
+ * Both functions simply convert numbers to Little Endian.
77
+ *
78
+ * However, the `0` number is an edge case that we specially handle with this
79
+ * function.
80
+ *
81
+ * bitcoinjs-lib's `bscript.number.encode(0)` produces an empty Buffer.
82
+ * This is what the Bitcoin interpreter does and it is what `script.number.encode` was
83
+ * implemented to do.
84
+ *
85
+ * The problem is `bscript.number.encode(0).toString('hex')` produces an
86
+ * empty string and thus it should not be used to serialize number zero before `fromASM`.
87
+ *
88
+ * A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
89
+ * compiled).
90
+ *
91
+ * So, this function will produce a string in hex format in Little Endian
92
+ * encoding for integers not equal to `0` and it will return `OP_0` for `0`.
93
+ *
94
+ * Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
95
+ *
96
+ * Use it in combination with `fromASM` like this:
97
+ *
98
+ * ```javascript
99
+ * //To produce "0 1 OP_ADD":
100
+ * fromASM(
101
+ * `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
102
+ * .trim().replace(/\s+/g, ' ')
103
+ * )
104
+ * ```
105
+ *
106
+ * @param {number} number An integer.
107
+ * @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
108
+ */
109
+ export declare function numberEncodeAsm(number: number): string;
@@ -0,0 +1,239 @@
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 (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.numberEncodeAsm = exports.satisfyMiniscript = exports.miniscript2Script = exports.expandMiniscript = void 0;
29
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
30
+ const keyExpressions_1 = require("./keyExpressions");
31
+ const RE = __importStar(require("./re"));
32
+ const miniscript_1 = require("@bitcoinerlab/miniscript");
33
+ /**
34
+ * Expand a miniscript to a generalized form using variables instead of key
35
+ * expressions. Variables will be of this form: @0, @1, ...
36
+ * This is done so that it can be compiled with compileMiniscript and
37
+ * satisfied with satisfier.
38
+ * Also compute pubkeys from descriptors to use them later.
39
+ */
40
+ function expandMiniscript({ miniscript, isSegwit, network = bitcoinjs_lib_1.networks.bitcoin, ECPair, BIP32 }) {
41
+ const expansionMap = {};
42
+ const expandedMiniscript = miniscript.replace(RegExp(RE.reKeyExp, 'g'), (keyExpression) => {
43
+ const key = '@' + Object.keys(expansionMap).length;
44
+ expansionMap[key] = (0, keyExpressions_1.parseKeyExpression)({
45
+ keyExpression,
46
+ isSegwit,
47
+ network,
48
+ ECPair,
49
+ BIP32
50
+ });
51
+ return key;
52
+ });
53
+ //Do some assertions. Miniscript must not have duplicate keys, also all
54
+ //keyExpressions must produce a valid pubkey
55
+ const pubkeysHex = Object.values(expansionMap).map(keyInfo => {
56
+ if (!keyInfo.pubkey)
57
+ throw new Error(`Error: keyExpression ${keyInfo.keyExpression} does not have a pubkey`);
58
+ return keyInfo.pubkey.toString('hex');
59
+ });
60
+ if (new Set(pubkeysHex).size !== pubkeysHex.length) {
61
+ throw new Error(`Error: miniscript ${miniscript} is not sane: contains duplicate public keys.`);
62
+ }
63
+ return { expandedMiniscript, expansionMap };
64
+ }
65
+ exports.expandMiniscript = expandMiniscript;
66
+ /**
67
+ * Particularize an expanded ASM expression using the variables in
68
+ * expansionMap.
69
+ * This is the kind of the opposite to what expandMiniscript does.
70
+ * Signatures and preimages are already subsituted by the satisfier calling
71
+ * this function.
72
+ */
73
+ function substituteAsm({ expandedAsm, expansionMap }) {
74
+ //Replace back variables into the pubkeys previously computed.
75
+ let asm = Object.keys(expansionMap).reduce((accAsm, key) => {
76
+ const pubkey = expansionMap[key]?.pubkey;
77
+ if (!pubkey) {
78
+ throw new Error(`Error: invalid expansionMap for ${key}`);
79
+ }
80
+ return accAsm
81
+ .replaceAll(`<${key}>`, `<${pubkey.toString('hex')}>`)
82
+ .replaceAll(`<HASH160(${key})>`, `<${bitcoinjs_lib_1.crypto.hash160(pubkey).toString('hex')}>`);
83
+ }, expandedAsm);
84
+ //Now clean it and prepare it so that fromASM can be called:
85
+ asm = asm
86
+ .trim()
87
+ //Replace one or more consecutive whitespace characters (spaces, tabs,
88
+ //or line breaks) with a single space.
89
+ .replace(/\s+/g, ' ')
90
+ //Now encode numbers to little endian hex. Note that numbers are not
91
+ //enclosed in <>, since <> represents hex code already encoded.
92
+ //The regex below will match one or more digits within a string,
93
+ //except if the sequence is surrounded by "<" and ">"
94
+ .replace(/(?<![<])\b\d+\b(?![>])/g, (num) => numberEncodeAsm(Number(num)))
95
+ //we don't have numbers anymore, now it's safe to remove < and > since we
96
+ //know that every remaining is either an op_code or a hex encoded number
97
+ .replace(/[<>]/g, '');
98
+ return asm;
99
+ }
100
+ function miniscript2Script({ expandedMiniscript, expansionMap }) {
101
+ const compiled = (0, miniscript_1.compileMiniscript)(expandedMiniscript);
102
+ if (compiled.issane !== true) {
103
+ throw new Error(`Error: Miniscript ${expandedMiniscript} is not sane`);
104
+ }
105
+ return bitcoinjs_lib_1.script.fromASM(substituteAsm({ expandedAsm: compiled.asm, expansionMap }));
106
+ }
107
+ exports.miniscript2Script = miniscript2Script;
108
+ /**
109
+ * Assumptions:
110
+ * The attacker does not have access to any of the private keys of public keys
111
+ * that participate in the Script.
112
+ *
113
+ * The attacker only has access to hash preimages that honest users have access
114
+ * to as well.
115
+ *
116
+ * Pass timeConstraints to search for the first solution with this nLockTime and
117
+ * nSequence. Throw if no solution is possible using these constraints.
118
+ *
119
+ * Don't pass timeConstraints (this is the default) if you want to get the
120
+ * smallest size solution altogether.
121
+ *
122
+ * If a solution is not found this function throws.
123
+ */
124
+ function satisfyMiniscript({ expandedMiniscript, expansionMap, signatures = [], preimages = [], timeConstraints }) {
125
+ //convert 'sha256(6c...33)' to: { ['<sha256_preimage(6c...33)>']: '10...5f'}
126
+ const preimageMap = {};
127
+ preimages.forEach(preimage => {
128
+ preimageMap['<' + preimage.digest.replace('(', '_preimage(') + '>'] =
129
+ '<' + preimage.preimage + '>';
130
+ });
131
+ //convert the pubkeys in signatures into [{['<sig(@0)>']: '30450221'}, ...]
132
+ //get the keyExpressions: @0, @1 from the keys in expansionMap
133
+ const expandedSignatureMap = {};
134
+ signatures.forEach(signature => {
135
+ const pubkeyHex = signature.pubkey.toString('hex');
136
+ const keyExpression = Object.keys(expansionMap).find(k => expansionMap[k]?.pubkey.toString('hex') === pubkeyHex);
137
+ expandedSignatureMap['<sig(' + keyExpression + ')>'] =
138
+ '<' + signature.signature.toString('hex') + '>';
139
+ });
140
+ const expandedKnownsMap = { ...preimageMap, ...expandedSignatureMap };
141
+ const knowns = Object.keys(expandedKnownsMap);
142
+ //satisfier verifies again internally whether expandedKnownsMap with given knowns is sane
143
+ const { nonMalleableSats } = (0, miniscript_1.satisfier)(expandedMiniscript, { knowns });
144
+ if (!Array.isArray(nonMalleableSats) || !nonMalleableSats[0])
145
+ throw new Error(`Error: unresolvable miniscript ${expandedMiniscript}`);
146
+ let sat;
147
+ if (!timeConstraints) {
148
+ sat = nonMalleableSats[0];
149
+ }
150
+ else {
151
+ sat = nonMalleableSats.find(nonMalleableSat => nonMalleableSat.nSequence === timeConstraints.nSequence &&
152
+ nonMalleableSat.nLockTime === timeConstraints.nLockTime);
153
+ if (sat === undefined) {
154
+ 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?`);
155
+ }
156
+ }
157
+ //substitute signatures and preimages:
158
+ let expandedAsm = sat.asm;
159
+ //replace in expandedAsm all the <sig(@0)> and <sha256_preimage(6c...33)>
160
+ //to <304...01> and <107...5f> ...
161
+ for (const search in expandedKnownsMap) {
162
+ const replace = expandedKnownsMap[search];
163
+ if (!replace || replace === '<>')
164
+ throw new Error(`Error: invalid expandedKnownsMap`);
165
+ expandedAsm = expandedAsm.replaceAll(search, replace);
166
+ }
167
+ const scriptSatisfaction = bitcoinjs_lib_1.script.fromASM(substituteAsm({ expandedAsm, expansionMap }));
168
+ return {
169
+ scriptSatisfaction,
170
+ nLockTime: sat.nLockTime,
171
+ nSequence: sat.nSequence
172
+ };
173
+ }
174
+ exports.satisfyMiniscript = satisfyMiniscript;
175
+ /**
176
+ *
177
+ * Use this function instead of bitcoinjs-lib's equivalent `script.number.encode`
178
+ * when encoding numbers to be compiled with `fromASM` to avoid problems.
179
+ *
180
+ * Motivation:
181
+ *
182
+ * Numbers in Bitcoin assembly code are represented in hex and in Little Endian.
183
+ * Decimal: 32766 - Big endian: 0x7FFE - Little Endian: 0xFE7F.
184
+ *
185
+ * This function takes an integer and encodes it so that bitcoinjs-lib `fromASM`
186
+ * can compile it. This is basically what bitcoinjs-lib's `script.number.encode`
187
+ * does.
188
+ *
189
+ * Note that `fromASM` already converts integers from 1 to 16 to
190
+ * OP_1 ... OP_16 {@link https://github.com/bitcoinjs/bitcoinjs-lib/blob/59b21162a2c4645c64271ca004c7a3755a3d72fb/src/script.js#L33 here}.
191
+ * This is done in Bitcoin to save some bits.
192
+ *
193
+ * Neither this function nor `script.number.encode` convert numbers to
194
+ * their op code equivalent since this is done later in `fromASM`.
195
+ *
196
+ * Both functions simply convert numbers to Little Endian.
197
+ *
198
+ * However, the `0` number is an edge case that we specially handle with this
199
+ * function.
200
+ *
201
+ * bitcoinjs-lib's `bscript.number.encode(0)` produces an empty Buffer.
202
+ * This is what the Bitcoin interpreter does and it is what `script.number.encode` was
203
+ * implemented to do.
204
+ *
205
+ * The problem is `bscript.number.encode(0).toString('hex')` produces an
206
+ * empty string and thus it should not be used to serialize number zero before `fromASM`.
207
+ *
208
+ * A zero should produce the OP_0 ASM symbolic code (corresponding to a `0` when
209
+ * compiled).
210
+ *
211
+ * So, this function will produce a string in hex format in Little Endian
212
+ * encoding for integers not equal to `0` and it will return `OP_0` for `0`.
213
+ *
214
+ * Read more about the this {@link https://github.com/bitcoinjs/bitcoinjs-lib/issues/1799#issuecomment-1122591738 here}.
215
+ *
216
+ * Use it in combination with `fromASM` like this:
217
+ *
218
+ * ```javascript
219
+ * //To produce "0 1 OP_ADD":
220
+ * fromASM(
221
+ * `${numberEncodeAsm(0)} ${numberEncodeAsm(1)} OP_ADD`
222
+ * .trim().replace(/\s+/g, ' ')
223
+ * )
224
+ * ```
225
+ *
226
+ * @param {number} number An integer.
227
+ * @returns {string} Returns `"OP_0"` for `number === 0` and a hex string representing other numbers in Little Endian encoding.
228
+ */
229
+ function numberEncodeAsm(number) {
230
+ if (Number.isSafeInteger(number) === false) {
231
+ throw new Error(`Error: invalid number ${number}`);
232
+ }
233
+ if (number === 0) {
234
+ return 'OP_0';
235
+ }
236
+ else
237
+ return bitcoinjs_lib_1.script.number.encode(number).toString('hex');
238
+ }
239
+ exports.numberEncodeAsm = numberEncodeAsm;
package/dist/psbt.d.ts ADDED
@@ -0,0 +1,38 @@
1
+ /// <reference types="node" />
2
+ import type { PsbtInput } from 'bip174/src/lib/interfaces';
3
+ import type { KeyInfo } from './types';
4
+ import { Network, Psbt } from 'bitcoinjs-lib';
5
+ /**
6
+ * This function must do two things:
7
+ * 1. Check if the `input` can be finalized. If it can not be finalized, throw.
8
+ * ie. `Can not finalize input #${inputIndex}`
9
+ * 2. Create the finalScriptSig and finalScriptWitness Buffers.
10
+ */
11
+ type FinalScriptsFunc = (inputIndex: number, // Which input is it?
12
+ input: PsbtInput, // The PSBT input contents
13
+ script: Buffer, // The "meaningful" locking script Buffer (redeemScript for P2SH etc.)
14
+ isSegwit: boolean, // Is it segwit?
15
+ isP2SH: boolean, // Is it P2SH?
16
+ isP2WSH: boolean) => {
17
+ finalScriptSig: Buffer | undefined;
18
+ finalScriptWitness: Buffer | undefined;
19
+ };
20
+ export declare function finalScriptsFuncFactory(scriptSatisfaction: Buffer, network: Network): FinalScriptsFunc;
21
+ /**
22
+ * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
23
+ */
24
+ export declare function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript }: {
25
+ psbt: Psbt;
26
+ vout: number;
27
+ txHex?: string;
28
+ txId?: string;
29
+ value?: number;
30
+ sequence: number | undefined;
31
+ locktime: number | undefined;
32
+ keysInfo: KeyInfo[];
33
+ scriptPubKey: Buffer;
34
+ isSegwit: boolean;
35
+ witnessScript: Buffer | undefined;
36
+ redeemScript: Buffer | undefined;
37
+ }): number;
38
+ export {};
package/dist/psbt.js ADDED
@@ -0,0 +1,197 @@
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 (mod) {
21
+ if (mod && mod.__esModule) return mod;
22
+ var result = {};
23
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
24
+ __setModuleDefault(result, mod);
25
+ return result;
26
+ };
27
+ Object.defineProperty(exports, "__esModule", { value: true });
28
+ exports.updatePsbt = exports.finalScriptsFuncFactory = void 0;
29
+ const bitcoinjs_lib_1 = require("bitcoinjs-lib");
30
+ const varuint = __importStar(require("bip174/src/lib/converter/varint"));
31
+ function reverseBuffer(buffer) {
32
+ if (buffer.length < 1)
33
+ return buffer;
34
+ let j = buffer.length - 1;
35
+ let tmp = 0;
36
+ for (let i = 0; i < buffer.length / 2; i++) {
37
+ tmp = buffer[i];
38
+ buffer[i] = buffer[j];
39
+ buffer[j] = tmp;
40
+ j--;
41
+ }
42
+ return buffer;
43
+ }
44
+ function witnessStackToScriptWitness(witness) {
45
+ let buffer = Buffer.allocUnsafe(0);
46
+ function writeSlice(slice) {
47
+ buffer = Buffer.concat([buffer, Buffer.from(slice)]);
48
+ }
49
+ function writeVarInt(i) {
50
+ const currentLen = buffer.length;
51
+ const varintLen = varuint.encodingLength(i);
52
+ buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]);
53
+ varuint.encode(i, buffer, currentLen);
54
+ }
55
+ function writeVarSlice(slice) {
56
+ writeVarInt(slice.length);
57
+ writeSlice(slice);
58
+ }
59
+ function writeVector(vector) {
60
+ writeVarInt(vector.length);
61
+ vector.forEach(writeVarSlice);
62
+ }
63
+ writeVector(witness);
64
+ return buffer;
65
+ }
66
+ function finalScriptsFuncFactory(scriptSatisfaction, network) {
67
+ const finalScriptsFunc = (_index, _input, lockingScript /*witnessScript or redeemScript*/, isSegwit, isP2SH, _isP2WSH) => {
68
+ let finalScriptWitness;
69
+ let finalScriptSig;
70
+ //p2wsh
71
+ if (isSegwit && !isP2SH) {
72
+ const payment = bitcoinjs_lib_1.payments.p2wsh({
73
+ redeem: { input: scriptSatisfaction, output: lockingScript },
74
+ network
75
+ });
76
+ if (!payment.witness)
77
+ throw new Error(`Error: p2wsh failed producing a witness`);
78
+ finalScriptWitness = witnessStackToScriptWitness(payment.witness);
79
+ }
80
+ //p2sh-p2wsh
81
+ else if (isSegwit && isP2SH) {
82
+ const payment = bitcoinjs_lib_1.payments.p2sh({
83
+ redeem: bitcoinjs_lib_1.payments.p2wsh({
84
+ redeem: { input: scriptSatisfaction, output: lockingScript },
85
+ network
86
+ }),
87
+ network
88
+ });
89
+ if (!payment.witness)
90
+ throw new Error(`Error: p2sh-p2wsh failed producing a witness`);
91
+ finalScriptWitness = witnessStackToScriptWitness(payment.witness);
92
+ finalScriptSig = payment.input;
93
+ }
94
+ //p2sh
95
+ else {
96
+ finalScriptSig = bitcoinjs_lib_1.payments.p2sh({
97
+ redeem: { input: scriptSatisfaction, output: lockingScript },
98
+ network
99
+ }).input;
100
+ }
101
+ return {
102
+ finalScriptWitness,
103
+ finalScriptSig
104
+ };
105
+ };
106
+ return finalScriptsFunc;
107
+ }
108
+ exports.finalScriptsFuncFactory = finalScriptsFuncFactory;
109
+ /**
110
+ * Important: Read comments on descriptor.updatePsbt regarding not passing txHex
111
+ */
112
+ function updatePsbt({ psbt, vout, txHex, txId, value, sequence, locktime, keysInfo, scriptPubKey, isSegwit, witnessScript, redeemScript }) {
113
+ //Some data-sanity checks:
114
+ if (!isSegwit && txHex === undefined)
115
+ throw new Error(`Error: txHex is mandatory for Non-Segwit inputs`);
116
+ if (isSegwit &&
117
+ txHex === undefined &&
118
+ (txId === undefined || value === undefined))
119
+ throw new Error(`Error: pass txHex or txId+value for Segwit inputs`);
120
+ if (txHex !== undefined) {
121
+ const tx = bitcoinjs_lib_1.Transaction.fromHex(txHex);
122
+ const out = tx?.outs?.[vout];
123
+ if (!out)
124
+ throw new Error(`Error: tx ${txHex} does not have vout ${vout}`);
125
+ const outputScript = out.script;
126
+ if (!outputScript)
127
+ throw new Error(`Error: could not extract outputScript for txHex ${txHex} and vout ${vout}`);
128
+ if (Buffer.compare(outputScript, scriptPubKey) !== 0)
129
+ throw new Error(`Error: txHex ${txHex} for vout ${vout} does not correspond to scriptPubKey ${scriptPubKey}`);
130
+ if (txId !== undefined) {
131
+ if (tx.getId() !== txId)
132
+ throw new Error(`Error: txId for ${txHex} and vout ${vout} does not correspond to ${txId}`);
133
+ }
134
+ else {
135
+ txId = tx.getId();
136
+ }
137
+ if (value !== undefined) {
138
+ if (out.value !== value)
139
+ throw new Error(`Error: value for ${txHex} and vout ${vout} does not correspond to ${value}`);
140
+ }
141
+ else {
142
+ value = out.value;
143
+ }
144
+ }
145
+ if (txId === undefined || !value)
146
+ throw new Error(`Error: txHex+vout required. Alternatively, but ONLY for Segwit inputs, txId+value can also be passed.`);
147
+ if (locktime !== undefined) {
148
+ if (psbt.locktime !== 0 && psbt.locktime !== undefined)
149
+ throw new Error(`Error: transaction locktime has already been set: ${psbt.locktime}`);
150
+ psbt.setLocktime(locktime);
151
+ }
152
+ let inputSequence;
153
+ if (locktime !== undefined) {
154
+ if (sequence === undefined) {
155
+ // for CTV nSequence MUST be <= 0xfffffffe otherwise OP_CHECKLOCKTIMEVERIFY will fail.
156
+ inputSequence = 0xfffffffe;
157
+ }
158
+ else if (sequence > 0xfffffffe) {
159
+ throw new Error(`Error: incompatible sequence: ${inputSequence} and locktime: ${locktime}`);
160
+ }
161
+ else {
162
+ inputSequence = sequence;
163
+ }
164
+ }
165
+ else {
166
+ inputSequence = sequence;
167
+ }
168
+ const input = {
169
+ hash: reverseBuffer(Buffer.from(txId, 'hex')),
170
+ index: vout
171
+ };
172
+ if (txHex !== undefined) {
173
+ input.nonWitnessUtxo = bitcoinjs_lib_1.Transaction.fromHex(txHex).toBuffer();
174
+ }
175
+ const bip32Derivation = keysInfo
176
+ .filter((keyInfo) => keyInfo.pubkey && keyInfo.masterFingerprint && keyInfo.path)
177
+ .map((keyInfo) => ({
178
+ masterFingerprint: keyInfo.masterFingerprint,
179
+ pubkey: keyInfo.pubkey,
180
+ path: keyInfo.path
181
+ }));
182
+ if (bip32Derivation.length)
183
+ input.bip32Derivation = bip32Derivation;
184
+ if (isSegwit && txHex !== undefined) {
185
+ //There's no need to put both witnessUtxo and nonWitnessUtxo
186
+ input.witnessUtxo = { script: scriptPubKey, value };
187
+ }
188
+ if (inputSequence !== undefined)
189
+ input.sequence = inputSequence;
190
+ if (witnessScript)
191
+ input.witnessScript = witnessScript;
192
+ if (redeemScript)
193
+ input.redeemScript = redeemScript;
194
+ psbt.addInput(input);
195
+ return psbt.data.inputs.length - 1;
196
+ }
197
+ exports.updatePsbt = updatePsbt;
package/dist/re.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ export declare const reOriginPath: string;
2
+ export declare const reMasterFingerprint: string;
3
+ export declare const reOrigin: string;
4
+ export declare const reChecksum: string;
5
+ export declare const rePubKey: string;
6
+ export declare const reWIF: string;
7
+ export declare const reXpub: string;
8
+ export declare const reXprv: string;
9
+ export declare const rePath: string;
10
+ export declare const reXpubKey: string;
11
+ export declare const reXprvKey: string;
12
+ export declare const reKeyExp: string;
13
+ export declare const anchorStartAndEnd: (re: string) => string;
14
+ export declare const rePkAnchored: string;
15
+ export declare const reAddrAnchored: string;
16
+ export declare const rePkhAnchored: string;
17
+ export declare const reWpkhAnchored: string;
18
+ export declare const reShWpkhAnchored: string;
19
+ export declare const reShMiniscriptAnchored: string;
20
+ export declare const reShWshMiniscriptAnchored: string;
21
+ export declare const reWshMiniscriptAnchored: string;
package/dist/re.js ADDED
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ // Copyright (c) 2023 Jose-Luis Landabaso - https://bitcoinerlab.com
3
+ // Distributed under the MIT software license
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.reWshMiniscriptAnchored = exports.reShWshMiniscriptAnchored = exports.reShMiniscriptAnchored = exports.reShWpkhAnchored = exports.reWpkhAnchored = exports.rePkhAnchored = exports.reAddrAnchored = exports.rePkAnchored = exports.anchorStartAndEnd = exports.reKeyExp = exports.reXprvKey = exports.reXpubKey = exports.rePath = exports.reXprv = exports.reXpub = exports.reWIF = exports.rePubKey = exports.reChecksum = exports.reOrigin = exports.reMasterFingerprint = exports.reOriginPath = void 0;
6
+ const checksum_1 = require("./checksum");
7
+ //Regular expressions cheat sheet:
8
+ //https://www.keycdn.com/support/regex-cheat-sheet
9
+ //hardened characters
10
+ const reHardened = String.raw `(['hH])`;
11
+ //a level is a series of integers followed (optional) by a hardener char
12
+ const reLevel = String.raw `(\d+${reHardened}?)`;
13
+ //a path component is a level followed by a slash "/" char
14
+ const rePathComponent = String.raw `(${reLevel}\/)`;
15
+ //A path formed by a series of path components that can be hardened: /2'/23H/23
16
+ exports.reOriginPath = String.raw `(\/${rePathComponent}*${reLevel})`; //The "*" means: "match 0 or more of the previous"
17
+ //an origin is something like this: [d34db33f/44'/0'/0'] where the path is optional. The fingerPrint is 8 chars hex
18
+ exports.reMasterFingerprint = String.raw `[0-9a-fA-F]{8}`;
19
+ exports.reOrigin = String.raw `(\[${exports.reMasterFingerprint}(${exports.reOriginPath})?\])`;
20
+ exports.reChecksum = String.raw `(#[${checksum_1.CHECKSUM_CHARSET}]{8})`;
21
+ //Something like this: 0252972572d465d016d4c501887b8df303eee3ed602c056b1eb09260dfa0da0ab2
22
+ //as explained here: github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md#reference
23
+ const reCompressedPubKey = String.raw `((02|03)[0-9a-fA-F]{64})`;
24
+ const reUncompressedPubKey = String.raw `(04[0-9a-fA-F]{128})`;
25
+ exports.rePubKey = String.raw `(${reCompressedPubKey}|${reUncompressedPubKey})`;
26
+ //https://learnmeabitcoin.com/technical/wif
27
+ //5, K, L for mainnet, 5: uncompressed, {K, L}: compressed
28
+ //c, 9, testnet, c: compressed, 9: uncompressed
29
+ exports.reWIF = String.raw `([5KLc9][1-9A-HJ-NP-Za-km-z]{50,51})`;
30
+ //x for mainnet, t for testnet
31
+ exports.reXpub = String.raw `([xXtT]pub[1-9A-HJ-NP-Za-km-z]{79,108})`;
32
+ exports.reXprv = String.raw `([xXtT]prv[1-9A-HJ-NP-Za-km-z]{79,108})`;
33
+ //reRangeLevel is like reLevel but using a wildcard "*"
34
+ const reRangeLevel = String.raw `(\*(${reHardened})?)`;
35
+ //A path can be finished with stuff like this: /23 or /23h or /* or /*'
36
+ exports.rePath = String.raw `(\/(${rePathComponent})*(${reRangeLevel}|${reLevel}))`;
37
+ //rePath is optional (note the "zero"): Followed by zero or more /NUM or /NUM' path elements to indicate unhardened or hardened derivation steps between the fingerprint and the key or xpub/xprv root that follows
38
+ exports.reXpubKey = String.raw `(${exports.reXpub})(${exports.rePath})?`;
39
+ exports.reXprvKey = String.raw `(${exports.reXprv})(${exports.rePath})?`;
40
+ //actualKey is the keyExpression without optional origin
41
+ const reActualKey = String.raw `(${exports.reXpubKey}|${exports.reXprvKey}|${exports.rePubKey}|${exports.reWIF})`;
42
+ //reOrigin is optional: Optionally, key origin information, consisting of:
43
+ //Matches a key expression: wif, xpub, xprv or pubkey:
44
+ exports.reKeyExp = String.raw `(${exports.reOrigin})?(${reActualKey})`;
45
+ const rePk = String.raw `pk\((.*?)\)`; //Matches anything. We assert later in the code that the pubkey is valid.
46
+ const reAddr = String.raw `addr\((.*?)\)`; //Matches anything. We assert later in the code that the address is valid.
47
+ const rePkh = String.raw `pkh\(${exports.reKeyExp}\)`;
48
+ const reWpkh = String.raw `wpkh\(${exports.reKeyExp}\)`;
49
+ const reShWpkh = String.raw `sh\(wpkh\(${exports.reKeyExp}\)\)`;
50
+ const reMiniscript = String.raw `(.*?)`; //Matches anything. We assert later in the code that miniscripts are valid and sane.
51
+ //RegExp makers:
52
+ const makeReSh = (re) => String.raw `sh\(${re}\)`;
53
+ const makeReWsh = (re) => String.raw `wsh\(${re}\)`;
54
+ const makeReShWsh = (re) => makeReSh(makeReWsh(re));
55
+ const anchorStartAndEnd = (re) => String.raw `^${re}$`; //starts and finishes like re (not composable)
56
+ exports.anchorStartAndEnd = anchorStartAndEnd;
57
+ const composeChecksum = (re) => String.raw `${re}(${exports.reChecksum})?`; //it's optional (note the "?")
58
+ exports.rePkAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(rePk));
59
+ exports.reAddrAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reAddr));
60
+ exports.rePkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(rePkh));
61
+ exports.reWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reWpkh));
62
+ exports.reShWpkhAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(reShWpkh));
63
+ exports.reShMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReSh(reMiniscript)));
64
+ exports.reShWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReShWsh(reMiniscript)));
65
+ exports.reWshMiniscriptAnchored = (0, exports.anchorStartAndEnd)(composeChecksum(makeReWsh(reMiniscript)));