@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.
- package/README.draft.md +84 -0
- package/README.md +74 -0
- package/dist/checksum.d.ts +2 -0
- package/dist/checksum.js +54 -0
- package/dist/descriptors.d.ts +15 -0
- package/dist/descriptors.js +627 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +53 -0
- package/dist/keyExpressions.d.ts +29 -0
- package/dist/keyExpressions.js +196 -0
- package/dist/ledger.d.ts +94 -0
- package/dist/ledger.js +283 -0
- package/dist/miniscript.d.ts +109 -0
- package/dist/miniscript.js +239 -0
- package/dist/psbt.d.ts +38 -0
- package/dist/psbt.js +197 -0
- package/dist/re.d.ts +21 -0
- package/dist/re.js +65 -0
- package/dist/scriptExpressions.d.ts +58 -0
- package/dist/scriptExpressions.js +52 -0
- package/dist/signers.d.ts +37 -0
- package/dist/signers.js +112 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +4 -0
- package/package.json +83 -0
|
@@ -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)));
|