@scure/btc-signer 0.5.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/LICENSE +21 -0
- package/README.md +836 -0
- package/index.d.ts +1277 -0
- package/index.d.ts.map +1 -0
- package/index.js +2552 -0
- package/index.js.map +1 -0
- package/index.ts +2571 -0
- package/package.json +64 -0
package/index.js
ADDED
|
@@ -0,0 +1,2552 @@
|
|
|
1
|
+
/*! micro-btc-signer - MIT License (c) 2022 Paul Miller (paulmillr.com) */
|
|
2
|
+
import { secp256k1 as _secp, schnorr } from '@noble/curves/secp256k1';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
4
|
+
import { ripemd160 } from '@noble/hashes/ripemd160';
|
|
5
|
+
import { hex, base58, base58check as _b58, bech32, bech32m } from '@scure/base';
|
|
6
|
+
import * as P from 'micro-packed';
|
|
7
|
+
const { ProjectivePoint: ProjPoint, sign: _signECDSA, getPublicKey: _pubECDSA } = _secp;
|
|
8
|
+
const CURVE_ORDER = _secp.CURVE.n;
|
|
9
|
+
// Same as value || def, but doesn't overwrites zero ('0', 0, 0n, etc)
|
|
10
|
+
const def = (value, def) => (value === undefined ? def : value);
|
|
11
|
+
const isBytes = P.isBytes;
|
|
12
|
+
const hash160 = (msg) => ripemd160(sha256(msg));
|
|
13
|
+
const sha256x2 = (...msgs) => sha256(sha256(concat(...msgs)));
|
|
14
|
+
const concat = P.concatBytes;
|
|
15
|
+
// Make base58check work
|
|
16
|
+
export const base58check = _b58(sha256);
|
|
17
|
+
var PubT;
|
|
18
|
+
(function (PubT) {
|
|
19
|
+
PubT[PubT["ecdsa"] = 0] = "ecdsa";
|
|
20
|
+
PubT[PubT["schnorr"] = 1] = "schnorr";
|
|
21
|
+
})(PubT || (PubT = {}));
|
|
22
|
+
function validatePubkey(pub, type) {
|
|
23
|
+
const len = pub.length;
|
|
24
|
+
if (type === PubT.ecdsa) {
|
|
25
|
+
if (len === 32)
|
|
26
|
+
throw new Error('Expected non-Schnorr key');
|
|
27
|
+
ProjPoint.fromHex(pub); // does assertValidity
|
|
28
|
+
return pub;
|
|
29
|
+
}
|
|
30
|
+
else if (type === PubT.schnorr) {
|
|
31
|
+
if (len !== 32)
|
|
32
|
+
throw new Error('Expected 32-byte Schnorr key');
|
|
33
|
+
schnorr.utils.lift_x(schnorr.utils.bytesToNumberBE(pub));
|
|
34
|
+
return pub;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error('Unknown key type');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function isValidPubkey(pub, type) {
|
|
41
|
+
try {
|
|
42
|
+
validatePubkey(pub, type);
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
catch (e) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
// low-r signature grinding. Used to reduce tx size by 1 byte.
|
|
50
|
+
// noble/secp256k1 does not support the feature: it is not used outside of BTC.
|
|
51
|
+
// We implement it manually, because in BTC it's common.
|
|
52
|
+
// Not best way, but closest to bitcoin implementation (easier to check)
|
|
53
|
+
const hasLowR = (sig) => sig.r < CURVE_ORDER / 2n;
|
|
54
|
+
function signECDSA(hash, privateKey, lowR = false) {
|
|
55
|
+
let sig = _signECDSA(hash, privateKey);
|
|
56
|
+
if (lowR && !hasLowR(sig)) {
|
|
57
|
+
const extraEntropy = new Uint8Array(32);
|
|
58
|
+
for (let cnt = 0; cnt < Number.MAX_SAFE_INTEGER; cnt++) {
|
|
59
|
+
extraEntropy.set(P.U32LE.encode(cnt));
|
|
60
|
+
sig = _signECDSA(hash, privateKey, { extraEntropy });
|
|
61
|
+
if (hasLowR(sig))
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return sig.toDERRawBytes();
|
|
66
|
+
}
|
|
67
|
+
function tapTweak(a, b) {
|
|
68
|
+
const u = schnorr.utils;
|
|
69
|
+
const t = u.taggedHash('TapTweak', a, b);
|
|
70
|
+
const tn = u.bytesToNumberBE(t);
|
|
71
|
+
if (tn >= CURVE_ORDER)
|
|
72
|
+
throw new Error('tweak higher than curve order');
|
|
73
|
+
return tn;
|
|
74
|
+
}
|
|
75
|
+
export function taprootTweakPrivKey(privKey, merkleRoot = new Uint8Array()) {
|
|
76
|
+
const u = schnorr.utils;
|
|
77
|
+
const seckey0 = u.bytesToNumberBE(privKey); // seckey0 = int_from_bytes(seckey0)
|
|
78
|
+
const P = ProjPoint.fromPrivateKey(seckey0); // P = point_mul(G, seckey0)
|
|
79
|
+
// seckey = seckey0 if has_even_y(P) else SECP256K1_ORDER - seckey0
|
|
80
|
+
const seckey = P.hasEvenY() ? seckey0 : u.mod(-seckey0, CURVE_ORDER);
|
|
81
|
+
const xP = u.pointToBytes(P);
|
|
82
|
+
// t = int_from_bytes(tagged_hash("TapTweak", bytes_from_int(x(P)) + h)); >= SECP256K1_ORDER check
|
|
83
|
+
const t = tapTweak(xP, merkleRoot);
|
|
84
|
+
// bytes_from_int((seckey + t) % SECP256K1_ORDER)
|
|
85
|
+
return u.numberToBytesBE(u.mod(seckey + t, CURVE_ORDER), 32);
|
|
86
|
+
}
|
|
87
|
+
export function taprootTweakPubkey(pubKey, h) {
|
|
88
|
+
const u = schnorr.utils;
|
|
89
|
+
const t = tapTweak(pubKey, h); // t = int_from_bytes(tagged_hash("TapTweak", pubkey + h))
|
|
90
|
+
const P = u.lift_x(u.bytesToNumberBE(pubKey)); // P = lift_x(int_from_bytes(pubkey))
|
|
91
|
+
const Q = P.add(ProjPoint.fromPrivateKey(t)); // Q = point_add(P, point_mul(G, t))
|
|
92
|
+
const parity = Q.hasEvenY() ? 0 : 1; // 0 if has_even_y(Q) else 1
|
|
93
|
+
return [u.pointToBytes(Q), parity]; // bytes_from_int(x(Q))
|
|
94
|
+
}
|
|
95
|
+
// Can be 33 or 64 bytes
|
|
96
|
+
const PubKeyECDSA = P.validate(P.bytes(null), (pub) => validatePubkey(pub, PubT.ecdsa));
|
|
97
|
+
const PubKeySchnorr = P.validate(P.bytes(32), (pub) => validatePubkey(pub, PubT.schnorr));
|
|
98
|
+
const SignatureSchnorr = P.validate(P.bytes(null), (sig) => {
|
|
99
|
+
if (sig.length !== 64 && sig.length !== 65)
|
|
100
|
+
throw new Error('Schnorr signature should be 64 or 65 bytes long');
|
|
101
|
+
return sig;
|
|
102
|
+
});
|
|
103
|
+
function uniqPubkey(pubkeys) {
|
|
104
|
+
const map = {};
|
|
105
|
+
for (const pub of pubkeys) {
|
|
106
|
+
const key = hex.encode(pub);
|
|
107
|
+
if (map[key])
|
|
108
|
+
throw new Error(`Multisig: non-uniq pubkey: ${pubkeys.map(hex.encode)}`);
|
|
109
|
+
map[key] = true;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
export const NETWORK = {
|
|
113
|
+
bech32: 'bc',
|
|
114
|
+
pubKeyHash: 0x00,
|
|
115
|
+
scriptHash: 0x05,
|
|
116
|
+
wif: 0x80,
|
|
117
|
+
};
|
|
118
|
+
export const TEST_NETWORK = {
|
|
119
|
+
bech32: 'tb',
|
|
120
|
+
pubKeyHash: 0x6f,
|
|
121
|
+
scriptHash: 0xc4,
|
|
122
|
+
wif: 0xef,
|
|
123
|
+
};
|
|
124
|
+
export const PRECISION = 8;
|
|
125
|
+
export const DEFAULT_VERSION = 2;
|
|
126
|
+
export const DEFAULT_LOCKTIME = 0;
|
|
127
|
+
export const DEFAULT_SEQUENCE = 4294967295;
|
|
128
|
+
const EMPTY32 = new Uint8Array(32);
|
|
129
|
+
// Utils
|
|
130
|
+
export const Decimal = P.coders.decimal(PRECISION);
|
|
131
|
+
// Exported for tests, internal method
|
|
132
|
+
export function _cmpBytes(a, b) {
|
|
133
|
+
if (!isBytes(a) || !isBytes(b))
|
|
134
|
+
throw new Error(`cmp: wrong type a=${typeof a} b=${typeof b}`);
|
|
135
|
+
// -1 -> a<b, 0 -> a==b, 1 -> a>b
|
|
136
|
+
const len = Math.min(a.length, b.length);
|
|
137
|
+
for (let i = 0; i < len; i++)
|
|
138
|
+
if (a[i] != b[i])
|
|
139
|
+
return Math.sign(a[i] - b[i]);
|
|
140
|
+
return Math.sign(a.length - b.length);
|
|
141
|
+
}
|
|
142
|
+
// Coders
|
|
143
|
+
// prettier-ignore
|
|
144
|
+
export var OP;
|
|
145
|
+
(function (OP) {
|
|
146
|
+
OP[OP["OP_0"] = 0] = "OP_0";
|
|
147
|
+
OP[OP["PUSHDATA1"] = 76] = "PUSHDATA1";
|
|
148
|
+
OP[OP["PUSHDATA2"] = 77] = "PUSHDATA2";
|
|
149
|
+
OP[OP["PUSHDATA4"] = 78] = "PUSHDATA4";
|
|
150
|
+
OP[OP["1NEGATE"] = 79] = "1NEGATE";
|
|
151
|
+
OP[OP["RESERVED"] = 80] = "RESERVED";
|
|
152
|
+
OP[OP["OP_1"] = 81] = "OP_1";
|
|
153
|
+
OP[OP["OP_2"] = 82] = "OP_2";
|
|
154
|
+
OP[OP["OP_3"] = 83] = "OP_3";
|
|
155
|
+
OP[OP["OP_4"] = 84] = "OP_4";
|
|
156
|
+
OP[OP["OP_5"] = 85] = "OP_5";
|
|
157
|
+
OP[OP["OP_6"] = 86] = "OP_6";
|
|
158
|
+
OP[OP["OP_7"] = 87] = "OP_7";
|
|
159
|
+
OP[OP["OP_8"] = 88] = "OP_8";
|
|
160
|
+
OP[OP["OP_9"] = 89] = "OP_9";
|
|
161
|
+
OP[OP["OP_10"] = 90] = "OP_10";
|
|
162
|
+
OP[OP["OP_11"] = 91] = "OP_11";
|
|
163
|
+
OP[OP["OP_12"] = 92] = "OP_12";
|
|
164
|
+
OP[OP["OP_13"] = 93] = "OP_13";
|
|
165
|
+
OP[OP["OP_14"] = 94] = "OP_14";
|
|
166
|
+
OP[OP["OP_15"] = 95] = "OP_15";
|
|
167
|
+
OP[OP["OP_16"] = 96] = "OP_16";
|
|
168
|
+
// Control
|
|
169
|
+
OP[OP["NOP"] = 97] = "NOP";
|
|
170
|
+
OP[OP["VER"] = 98] = "VER";
|
|
171
|
+
OP[OP["IF"] = 99] = "IF";
|
|
172
|
+
OP[OP["NOTIF"] = 100] = "NOTIF";
|
|
173
|
+
OP[OP["VERIF"] = 101] = "VERIF";
|
|
174
|
+
OP[OP["VERNOTIF"] = 102] = "VERNOTIF";
|
|
175
|
+
OP[OP["ELSE"] = 103] = "ELSE";
|
|
176
|
+
OP[OP["ENDIF"] = 104] = "ENDIF";
|
|
177
|
+
OP[OP["VERIFY"] = 105] = "VERIFY";
|
|
178
|
+
OP[OP["RETURN"] = 106] = "RETURN";
|
|
179
|
+
// Stack
|
|
180
|
+
OP[OP["TOALTSTACK"] = 107] = "TOALTSTACK";
|
|
181
|
+
OP[OP["FROMALTSTACK"] = 108] = "FROMALTSTACK";
|
|
182
|
+
OP[OP["2DROP"] = 109] = "2DROP";
|
|
183
|
+
OP[OP["2DUP"] = 110] = "2DUP";
|
|
184
|
+
OP[OP["3DUP"] = 111] = "3DUP";
|
|
185
|
+
OP[OP["2OVER"] = 112] = "2OVER";
|
|
186
|
+
OP[OP["2ROT"] = 113] = "2ROT";
|
|
187
|
+
OP[OP["2SWAP"] = 114] = "2SWAP";
|
|
188
|
+
OP[OP["IFDUP"] = 115] = "IFDUP";
|
|
189
|
+
OP[OP["DEPTH"] = 116] = "DEPTH";
|
|
190
|
+
OP[OP["DROP"] = 117] = "DROP";
|
|
191
|
+
OP[OP["DUP"] = 118] = "DUP";
|
|
192
|
+
OP[OP["NIP"] = 119] = "NIP";
|
|
193
|
+
OP[OP["OVER"] = 120] = "OVER";
|
|
194
|
+
OP[OP["PICK"] = 121] = "PICK";
|
|
195
|
+
OP[OP["ROLL"] = 122] = "ROLL";
|
|
196
|
+
OP[OP["ROT"] = 123] = "ROT";
|
|
197
|
+
OP[OP["SWAP"] = 124] = "SWAP";
|
|
198
|
+
OP[OP["TUCK"] = 125] = "TUCK";
|
|
199
|
+
// Splice
|
|
200
|
+
OP[OP["CAT"] = 126] = "CAT";
|
|
201
|
+
OP[OP["SUBSTR"] = 127] = "SUBSTR";
|
|
202
|
+
OP[OP["LEFT"] = 128] = "LEFT";
|
|
203
|
+
OP[OP["RIGHT"] = 129] = "RIGHT";
|
|
204
|
+
OP[OP["SIZE"] = 130] = "SIZE";
|
|
205
|
+
// Boolean logic
|
|
206
|
+
OP[OP["INVERT"] = 131] = "INVERT";
|
|
207
|
+
OP[OP["AND"] = 132] = "AND";
|
|
208
|
+
OP[OP["OR"] = 133] = "OR";
|
|
209
|
+
OP[OP["XOR"] = 134] = "XOR";
|
|
210
|
+
OP[OP["EQUAL"] = 135] = "EQUAL";
|
|
211
|
+
OP[OP["EQUALVERIFY"] = 136] = "EQUALVERIFY";
|
|
212
|
+
OP[OP["RESERVED1"] = 137] = "RESERVED1";
|
|
213
|
+
OP[OP["RESERVED2"] = 138] = "RESERVED2";
|
|
214
|
+
// Numbers
|
|
215
|
+
OP[OP["1ADD"] = 139] = "1ADD";
|
|
216
|
+
OP[OP["1SUB"] = 140] = "1SUB";
|
|
217
|
+
OP[OP["2MUL"] = 141] = "2MUL";
|
|
218
|
+
OP[OP["2DIV"] = 142] = "2DIV";
|
|
219
|
+
OP[OP["NEGATE"] = 143] = "NEGATE";
|
|
220
|
+
OP[OP["ABS"] = 144] = "ABS";
|
|
221
|
+
OP[OP["NOT"] = 145] = "NOT";
|
|
222
|
+
OP[OP["0NOTEQUAL"] = 146] = "0NOTEQUAL";
|
|
223
|
+
OP[OP["ADD"] = 147] = "ADD";
|
|
224
|
+
OP[OP["SUB"] = 148] = "SUB";
|
|
225
|
+
OP[OP["MUL"] = 149] = "MUL";
|
|
226
|
+
OP[OP["DIV"] = 150] = "DIV";
|
|
227
|
+
OP[OP["MOD"] = 151] = "MOD";
|
|
228
|
+
OP[OP["LSHIFT"] = 152] = "LSHIFT";
|
|
229
|
+
OP[OP["RSHIFT"] = 153] = "RSHIFT";
|
|
230
|
+
OP[OP["BOOLAND"] = 154] = "BOOLAND";
|
|
231
|
+
OP[OP["BOOLOR"] = 155] = "BOOLOR";
|
|
232
|
+
OP[OP["NUMEQUAL"] = 156] = "NUMEQUAL";
|
|
233
|
+
OP[OP["NUMEQUALVERIFY"] = 157] = "NUMEQUALVERIFY";
|
|
234
|
+
OP[OP["NUMNOTEQUAL"] = 158] = "NUMNOTEQUAL";
|
|
235
|
+
OP[OP["LESSTHAN"] = 159] = "LESSTHAN";
|
|
236
|
+
OP[OP["GREATERTHAN"] = 160] = "GREATERTHAN";
|
|
237
|
+
OP[OP["LESSTHANOREQUAL"] = 161] = "LESSTHANOREQUAL";
|
|
238
|
+
OP[OP["GREATERTHANOREQUAL"] = 162] = "GREATERTHANOREQUAL";
|
|
239
|
+
OP[OP["MIN"] = 163] = "MIN";
|
|
240
|
+
OP[OP["MAX"] = 164] = "MAX";
|
|
241
|
+
OP[OP["WITHIN"] = 165] = "WITHIN";
|
|
242
|
+
// Crypto
|
|
243
|
+
OP[OP["RIPEMD160"] = 166] = "RIPEMD160";
|
|
244
|
+
OP[OP["SHA1"] = 167] = "SHA1";
|
|
245
|
+
OP[OP["SHA256"] = 168] = "SHA256";
|
|
246
|
+
OP[OP["HASH160"] = 169] = "HASH160";
|
|
247
|
+
OP[OP["HASH256"] = 170] = "HASH256";
|
|
248
|
+
OP[OP["CODESEPARATOR"] = 171] = "CODESEPARATOR";
|
|
249
|
+
OP[OP["CHECKSIG"] = 172] = "CHECKSIG";
|
|
250
|
+
OP[OP["CHECKSIGVERIFY"] = 173] = "CHECKSIGVERIFY";
|
|
251
|
+
OP[OP["CHECKMULTISIG"] = 174] = "CHECKMULTISIG";
|
|
252
|
+
OP[OP["CHECKMULTISIGVERIFY"] = 175] = "CHECKMULTISIGVERIFY";
|
|
253
|
+
// Expansion
|
|
254
|
+
OP[OP["NOP1"] = 176] = "NOP1";
|
|
255
|
+
OP[OP["CHECKLOCKTIMEVERIFY"] = 177] = "CHECKLOCKTIMEVERIFY";
|
|
256
|
+
OP[OP["CHECKSEQUENCEVERIFY"] = 178] = "CHECKSEQUENCEVERIFY";
|
|
257
|
+
OP[OP["NOP4"] = 179] = "NOP4";
|
|
258
|
+
OP[OP["NOP5"] = 180] = "NOP5";
|
|
259
|
+
OP[OP["NOP6"] = 181] = "NOP6";
|
|
260
|
+
OP[OP["NOP7"] = 182] = "NOP7";
|
|
261
|
+
OP[OP["NOP8"] = 183] = "NOP8";
|
|
262
|
+
OP[OP["NOP9"] = 184] = "NOP9";
|
|
263
|
+
OP[OP["NOP10"] = 185] = "NOP10";
|
|
264
|
+
// BIP 342
|
|
265
|
+
OP[OP["CHECKSIGADD"] = 186] = "CHECKSIGADD";
|
|
266
|
+
// Invalid
|
|
267
|
+
OP[OP["INVALID"] = 255] = "INVALID";
|
|
268
|
+
})(OP || (OP = {}));
|
|
269
|
+
// Converts script bytes to parsed script
|
|
270
|
+
// 5221030000000000000000000000000000000000000000000000000000000000000001210300000000000000000000000000000000000000000000000000000000000000022103000000000000000000000000000000000000000000000000000000000000000353ae
|
|
271
|
+
// =>
|
|
272
|
+
// OP_2
|
|
273
|
+
// 030000000000000000000000000000000000000000000000000000000000000001
|
|
274
|
+
// 030000000000000000000000000000000000000000000000000000000000000002
|
|
275
|
+
// 030000000000000000000000000000000000000000000000000000000000000003
|
|
276
|
+
// OP_3
|
|
277
|
+
// CHECKMULTISIG
|
|
278
|
+
export const Script = P.wrap({
|
|
279
|
+
encodeStream: (w, value) => {
|
|
280
|
+
for (let o of value) {
|
|
281
|
+
if (typeof o === 'string') {
|
|
282
|
+
if (OP[o] === undefined)
|
|
283
|
+
throw new Error(`Unknown opcode=${o}`);
|
|
284
|
+
w.byte(OP[o]);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
else if (typeof o === 'number') {
|
|
288
|
+
if (o === 0x00) {
|
|
289
|
+
w.byte(0x00);
|
|
290
|
+
continue;
|
|
291
|
+
}
|
|
292
|
+
else if (1 <= o && o <= 16) {
|
|
293
|
+
w.byte(OP.OP_1 - 1 + o);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
// Encode big numbers
|
|
298
|
+
if (typeof o === 'number')
|
|
299
|
+
o = ScriptNum().encode(BigInt(o));
|
|
300
|
+
if (!isBytes(o))
|
|
301
|
+
throw new Error(`Wrong Script OP=${o} (${typeof o})`);
|
|
302
|
+
// Bytes
|
|
303
|
+
const len = o.length;
|
|
304
|
+
if (len < OP.PUSHDATA1)
|
|
305
|
+
w.byte(len);
|
|
306
|
+
else if (len <= 0xff) {
|
|
307
|
+
w.byte(OP.PUSHDATA1);
|
|
308
|
+
w.byte(len);
|
|
309
|
+
}
|
|
310
|
+
else if (len <= 0xffff) {
|
|
311
|
+
w.byte(OP.PUSHDATA2);
|
|
312
|
+
w.bytes(P.U16LE.encode(len));
|
|
313
|
+
}
|
|
314
|
+
else {
|
|
315
|
+
w.byte(OP.PUSHDATA4);
|
|
316
|
+
w.bytes(P.U32LE.encode(len));
|
|
317
|
+
}
|
|
318
|
+
w.bytes(o);
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
decodeStream: (r) => {
|
|
322
|
+
const out = [];
|
|
323
|
+
while (!r.isEnd()) {
|
|
324
|
+
const cur = r.byte();
|
|
325
|
+
// if 0 < cur < 78
|
|
326
|
+
if (OP.OP_0 < cur && cur <= OP.PUSHDATA4) {
|
|
327
|
+
let len;
|
|
328
|
+
if (cur < OP.PUSHDATA1)
|
|
329
|
+
len = cur;
|
|
330
|
+
else if (cur === OP.PUSHDATA1)
|
|
331
|
+
len = P.U8.decodeStream(r);
|
|
332
|
+
else if (cur === OP.PUSHDATA2)
|
|
333
|
+
len = P.U16LE.decodeStream(r);
|
|
334
|
+
else if (cur === OP.PUSHDATA4)
|
|
335
|
+
len = P.U32LE.decodeStream(r);
|
|
336
|
+
else
|
|
337
|
+
throw new Error('Should be not possible');
|
|
338
|
+
out.push(r.bytes(len));
|
|
339
|
+
}
|
|
340
|
+
else if (cur === 0x00) {
|
|
341
|
+
out.push(0);
|
|
342
|
+
}
|
|
343
|
+
else if (OP.OP_1 <= cur && cur <= OP.OP_16) {
|
|
344
|
+
out.push(cur - (OP.OP_1 - 1));
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
const op = OP[cur];
|
|
348
|
+
if (op === undefined)
|
|
349
|
+
throw new Error(`Unknown opcode=${cur.toString(16)}`);
|
|
350
|
+
out.push(op);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return out;
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
// We can encode almost any number as ScriptNum, however, parsing will be a problem
|
|
357
|
+
// since we can't know if buffer is a number or something else.
|
|
358
|
+
export function ScriptNum(bytesLimit = 6, forceMinimal = false) {
|
|
359
|
+
return P.wrap({
|
|
360
|
+
encodeStream: (w, value) => {
|
|
361
|
+
if (value === 0n)
|
|
362
|
+
return;
|
|
363
|
+
const neg = value < 0;
|
|
364
|
+
const val = BigInt(value);
|
|
365
|
+
const nums = [];
|
|
366
|
+
for (let abs = neg ? -val : val; abs; abs >>= 8n)
|
|
367
|
+
nums.push(Number(abs & 0xffn));
|
|
368
|
+
if (nums[nums.length - 1] >= 0x80)
|
|
369
|
+
nums.push(neg ? 0x80 : 0);
|
|
370
|
+
else if (neg)
|
|
371
|
+
nums[nums.length - 1] |= 0x80;
|
|
372
|
+
w.bytes(new Uint8Array(nums));
|
|
373
|
+
},
|
|
374
|
+
decodeStream: (r) => {
|
|
375
|
+
const len = r.leftBytes;
|
|
376
|
+
if (len > bytesLimit)
|
|
377
|
+
throw new Error(`ScriptNum: number (${len}) bigger than limit=${bytesLimit}`);
|
|
378
|
+
if (len === 0)
|
|
379
|
+
return 0n;
|
|
380
|
+
if (forceMinimal) {
|
|
381
|
+
// MSB is zero (without sign bit) -> not minimally encoded
|
|
382
|
+
if ((r.data[len - 1] & 0x7f) === 0) {
|
|
383
|
+
// exception
|
|
384
|
+
if (len <= 1 || (r.data[len - 2] & 0x80) === 0)
|
|
385
|
+
throw new Error('Non-minimally encoded ScriptNum');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
let last = 0;
|
|
389
|
+
let res = 0n;
|
|
390
|
+
for (let i = 0; i < len; ++i) {
|
|
391
|
+
last = r.byte();
|
|
392
|
+
res |= BigInt(last) << (8n * BigInt(i));
|
|
393
|
+
}
|
|
394
|
+
if (last >= 0x80) {
|
|
395
|
+
res &= (2n ** BigInt(len * 8) - 1n) >> 1n;
|
|
396
|
+
res = -res;
|
|
397
|
+
}
|
|
398
|
+
return res;
|
|
399
|
+
},
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
export function OpToNum(op, bytesLimit = 4, forceMinimal = true) {
|
|
403
|
+
if (typeof op === 'number')
|
|
404
|
+
return op;
|
|
405
|
+
if (isBytes(op)) {
|
|
406
|
+
try {
|
|
407
|
+
const val = ScriptNum(bytesLimit, forceMinimal).decode(op);
|
|
408
|
+
if (val > Number.MAX_SAFE_INTEGER)
|
|
409
|
+
return;
|
|
410
|
+
return Number(val);
|
|
411
|
+
}
|
|
412
|
+
catch (e) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
// BTC specific variable length integer encoding
|
|
418
|
+
// https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
|
|
419
|
+
const CSLimits = {
|
|
420
|
+
0xfd: [0xfd, 2, 253n, 65535n],
|
|
421
|
+
0xfe: [0xfe, 4, 65536n, 4294967295n],
|
|
422
|
+
0xff: [0xff, 8, 4294967296n, 18446744073709551615n],
|
|
423
|
+
};
|
|
424
|
+
export const CompactSize = P.wrap({
|
|
425
|
+
encodeStream: (w, value) => {
|
|
426
|
+
if (typeof value === 'number')
|
|
427
|
+
value = BigInt(value);
|
|
428
|
+
if (0n <= value && value <= 252n)
|
|
429
|
+
return w.byte(Number(value));
|
|
430
|
+
for (const [flag, bytes, start, stop] of Object.values(CSLimits)) {
|
|
431
|
+
if (start > value || value > stop)
|
|
432
|
+
continue;
|
|
433
|
+
w.byte(flag);
|
|
434
|
+
for (let i = 0; i < bytes; i++)
|
|
435
|
+
w.byte(Number((value >> (8n * BigInt(i))) & 0xffn));
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
throw w.err(`VarInt too big: ${value}`);
|
|
439
|
+
},
|
|
440
|
+
decodeStream: (r) => {
|
|
441
|
+
const b0 = r.byte();
|
|
442
|
+
if (b0 <= 0xfc)
|
|
443
|
+
return BigInt(b0);
|
|
444
|
+
const [_, bytes, start] = CSLimits[b0];
|
|
445
|
+
let num = 0n;
|
|
446
|
+
for (let i = 0; i < bytes; i++)
|
|
447
|
+
num |= BigInt(r.byte()) << (8n * BigInt(i));
|
|
448
|
+
if (num < start)
|
|
449
|
+
throw r.err(`Wrong CompactSize(${8 * bytes})`);
|
|
450
|
+
return num;
|
|
451
|
+
},
|
|
452
|
+
});
|
|
453
|
+
// Same thing, but in number instead of bigint. Checks for safe integer inside
|
|
454
|
+
const CompactSizeLen = P.apply(CompactSize, P.coders.number);
|
|
455
|
+
// Array of size <CompactSize>
|
|
456
|
+
export const BTCArray = (t) => P.array(CompactSize, t);
|
|
457
|
+
// ui8a of size <CompactSize>
|
|
458
|
+
export const VarBytes = P.bytes(CompactSize);
|
|
459
|
+
export const RawInput = P.struct({
|
|
460
|
+
txid: P.bytes(32, true),
|
|
461
|
+
index: P.U32LE,
|
|
462
|
+
finalScriptSig: VarBytes,
|
|
463
|
+
sequence: P.U32LE, // ?
|
|
464
|
+
});
|
|
465
|
+
export const RawOutput = P.struct({ amount: P.U64LE, script: VarBytes });
|
|
466
|
+
const EMPTY_OUTPUT = {
|
|
467
|
+
amount: 0xffffffffffffffffn,
|
|
468
|
+
script: P.EMPTY,
|
|
469
|
+
};
|
|
470
|
+
// SegWit v0 stack of witness buffers
|
|
471
|
+
export const RawWitness = P.array(CompactSizeLen, VarBytes);
|
|
472
|
+
// https://en.bitcoin.it/wiki/Protocol_documentation#tx
|
|
473
|
+
const _RawTx = P.struct({
|
|
474
|
+
version: P.I32LE,
|
|
475
|
+
segwitFlag: P.flag(new Uint8Array([0x00, 0x01])),
|
|
476
|
+
inputs: BTCArray(RawInput),
|
|
477
|
+
outputs: BTCArray(RawOutput),
|
|
478
|
+
witnesses: P.flagged('segwitFlag', P.array('inputs/length', RawWitness)),
|
|
479
|
+
// < 500000000 Block number at which this transaction is unlocked
|
|
480
|
+
// >= 500000000 UNIX timestamp at which this transaction is unlocked
|
|
481
|
+
// Handled as part of PSBTv2
|
|
482
|
+
lockTime: P.U32LE,
|
|
483
|
+
});
|
|
484
|
+
function validateRawTx(tx) {
|
|
485
|
+
if (tx.segwitFlag && tx.witnesses && !tx.witnesses.length)
|
|
486
|
+
throw new Error('Segwit flag with empty witnesses array');
|
|
487
|
+
return tx;
|
|
488
|
+
}
|
|
489
|
+
export const RawTx = P.validate(_RawTx, validateRawTx);
|
|
490
|
+
function PSBTKeyInfo(info) {
|
|
491
|
+
const [type, kc, vc, reqInc, allowInc, silentIgnore] = info;
|
|
492
|
+
return { type, kc, vc, reqInc, allowInc, silentIgnore };
|
|
493
|
+
}
|
|
494
|
+
const BIP32Der = P.struct({
|
|
495
|
+
fingerprint: P.U32BE,
|
|
496
|
+
path: P.array(null, P.U32LE),
|
|
497
|
+
});
|
|
498
|
+
// Complex structure for PSBT fields
|
|
499
|
+
// <control byte with leaf version and parity bit> <internal key p> <C> <E> <AB>
|
|
500
|
+
const _TaprootControlBlock = P.struct({
|
|
501
|
+
version: P.U8,
|
|
502
|
+
internalKey: P.bytes(32),
|
|
503
|
+
merklePath: P.array(null, P.bytes(32)),
|
|
504
|
+
});
|
|
505
|
+
export const TaprootControlBlock = P.validate(_TaprootControlBlock, (cb) => {
|
|
506
|
+
if (cb.merklePath.length > 128)
|
|
507
|
+
throw new Error('TaprootControlBlock: merklePath should be of length 0..128 (inclusive)');
|
|
508
|
+
return cb;
|
|
509
|
+
});
|
|
510
|
+
const TaprootBIP32Der = P.struct({
|
|
511
|
+
hashes: P.array(CompactSizeLen, P.bytes(32)),
|
|
512
|
+
der: BIP32Der,
|
|
513
|
+
});
|
|
514
|
+
// The 78 byte serialized extended public key as defined by BIP 32.
|
|
515
|
+
const GlobalXPUB = P.bytes(78);
|
|
516
|
+
const tapScriptSigKey = P.struct({ pubKey: PubKeySchnorr, leafHash: P.bytes(32) });
|
|
517
|
+
// {<8-bit uint depth> <8-bit uint leaf version> <compact size uint scriptlen> <bytes script>}*
|
|
518
|
+
const tapTree = P.array(null, P.struct({
|
|
519
|
+
depth: P.U8,
|
|
520
|
+
version: P.U8,
|
|
521
|
+
script: VarBytes,
|
|
522
|
+
}));
|
|
523
|
+
const BytesInf = P.bytes(null); // Bytes will conflict with Bytes type
|
|
524
|
+
const Bytes20 = P.bytes(20);
|
|
525
|
+
const Bytes32 = P.bytes(32);
|
|
526
|
+
// versionsRequiringExclusing = !versionsAllowsInclusion (as set)
|
|
527
|
+
// {name: [tag, keyCoder, valueCoder, versionsRequiringInclusion, versionsRequiringExclusing, versionsAllowsInclusion, silentIgnore]}
|
|
528
|
+
// SilentIgnore: we use some v2 fields for v1 representation too, so we just clean them before serialize
|
|
529
|
+
// Tables from BIP-0174 (https://github.com/bitcoin/bips/blob/master/bip-0174.mediawiki)
|
|
530
|
+
// prettier-ignore
|
|
531
|
+
const PSBTGlobal = {
|
|
532
|
+
unsignedTx: [0x00, false, RawTx, [0], [0], false],
|
|
533
|
+
xpub: [0x01, GlobalXPUB, BIP32Der, [], [0, 2], false],
|
|
534
|
+
txVersion: [0x02, false, P.U32LE, [2], [2], false],
|
|
535
|
+
fallbackLocktime: [0x03, false, P.U32LE, [], [2], false],
|
|
536
|
+
inputCount: [0x04, false, CompactSizeLen, [2], [2], false],
|
|
537
|
+
outputCount: [0x05, false, CompactSizeLen, [2], [2], false],
|
|
538
|
+
txModifiable: [0x06, false, P.U8, [], [2], false],
|
|
539
|
+
version: [0xfb, false, P.U32LE, [], [0, 2], false],
|
|
540
|
+
propietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
|
|
541
|
+
};
|
|
542
|
+
// prettier-ignore
|
|
543
|
+
const PSBTInput = {
|
|
544
|
+
nonWitnessUtxo: [0x00, false, RawTx, [], [0, 2], false],
|
|
545
|
+
witnessUtxo: [0x01, false, RawOutput, [], [0, 2], false],
|
|
546
|
+
partialSig: [0x02, PubKeyECDSA, BytesInf, [], [0, 2], false],
|
|
547
|
+
sighashType: [0x03, false, P.U32LE, [], [0, 2], false],
|
|
548
|
+
redeemScript: [0x04, false, BytesInf, [], [0, 2], false],
|
|
549
|
+
witnessScript: [0x05, false, BytesInf, [], [0, 2], false],
|
|
550
|
+
bip32Derivation: [0x06, PubKeyECDSA, BIP32Der, [], [0, 2], false],
|
|
551
|
+
finalScriptSig: [0x07, false, BytesInf, [], [0, 2], false],
|
|
552
|
+
finalScriptWitness: [0x08, false, RawWitness, [], [0, 2], false],
|
|
553
|
+
porCommitment: [0x09, false, BytesInf, [], [0, 2], false],
|
|
554
|
+
ripemd160: [0x0a, Bytes20, BytesInf, [], [0, 2], false],
|
|
555
|
+
sha256: [0x0b, Bytes32, BytesInf, [], [0, 2], false],
|
|
556
|
+
hash160: [0x0c, Bytes20, BytesInf, [], [0, 2], false],
|
|
557
|
+
hash256: [0x0d, Bytes32, BytesInf, [], [0, 2], false],
|
|
558
|
+
txid: [0x0e, false, Bytes32, [2], [2], true],
|
|
559
|
+
index: [0x0f, false, P.U32LE, [2], [2], true],
|
|
560
|
+
sequence: [0x10, false, P.U32LE, [], [2], true],
|
|
561
|
+
requiredTimeLocktime: [0x11, false, P.U32LE, [], [2], false],
|
|
562
|
+
requiredHeightLocktime: [0x12, false, P.U32LE, [], [2], false],
|
|
563
|
+
tapKeySig: [0x13, false, SignatureSchnorr, [], [0, 2], false],
|
|
564
|
+
tapScriptSig: [0x14, tapScriptSigKey, SignatureSchnorr, [], [0, 2], false],
|
|
565
|
+
tapLeafScript: [0x15, TaprootControlBlock, BytesInf, [], [0, 2], false],
|
|
566
|
+
tapBip32Derivation: [0x16, Bytes32, TaprootBIP32Der, [], [0, 2], false],
|
|
567
|
+
tapInternalKey: [0x17, false, PubKeySchnorr, [], [0, 2], false],
|
|
568
|
+
tapMerkleRoot: [0x18, false, Bytes32, [], [0, 2], false],
|
|
569
|
+
propietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
|
|
570
|
+
};
|
|
571
|
+
// All other keys removed when finalizing
|
|
572
|
+
const PSBTInputFinalKeys = [
|
|
573
|
+
'txid',
|
|
574
|
+
'sequence',
|
|
575
|
+
'index',
|
|
576
|
+
'witnessUtxo',
|
|
577
|
+
'nonWitnessUtxo',
|
|
578
|
+
'finalScriptSig',
|
|
579
|
+
'finalScriptWitness',
|
|
580
|
+
'unknown',
|
|
581
|
+
];
|
|
582
|
+
// Can be modified even on signed input
|
|
583
|
+
const PSBTInputUnsignedKeys = [
|
|
584
|
+
'partialSig',
|
|
585
|
+
'finalScriptSig',
|
|
586
|
+
'finalScriptWitness',
|
|
587
|
+
'tapKeySig',
|
|
588
|
+
'tapScriptSig',
|
|
589
|
+
];
|
|
590
|
+
// prettier-ignore
|
|
591
|
+
const PSBTOutput = {
|
|
592
|
+
redeemScript: [0x00, false, BytesInf, [], [0, 2], false],
|
|
593
|
+
witnessScript: [0x01, false, BytesInf, [], [0, 2], false],
|
|
594
|
+
bip32Derivation: [0x02, PubKeyECDSA, BIP32Der, [], [0, 2], false],
|
|
595
|
+
amount: [0x03, false, P.I64LE, [2], [2], true],
|
|
596
|
+
script: [0x04, false, BytesInf, [2], [2], true],
|
|
597
|
+
tapInternalKey: [0x05, false, PubKeySchnorr, [], [0, 2], false],
|
|
598
|
+
tapTree: [0x06, false, tapTree, [], [0, 2], false],
|
|
599
|
+
tapBip32Derivation: [0x07, PubKeySchnorr, TaprootBIP32Der, [], [0, 2], false],
|
|
600
|
+
propietary: [0xfc, BytesInf, BytesInf, [], [0, 2], false],
|
|
601
|
+
};
|
|
602
|
+
// Can be modified even on signed input
|
|
603
|
+
const PSBTOutputUnsignedKeys = [];
|
|
604
|
+
const PSBTKeyPair = P.array(P.NULL, P.struct({
|
|
605
|
+
// <key> := <keylen> <keytype> <keydata> WHERE keylen = len(keytype)+len(keydata)
|
|
606
|
+
key: P.prefix(CompactSizeLen, P.struct({ type: CompactSizeLen, key: P.bytes(null) })),
|
|
607
|
+
// <value> := <valuelen> <valuedata>
|
|
608
|
+
value: P.bytes(CompactSizeLen),
|
|
609
|
+
}));
|
|
610
|
+
const PSBTUnknownKey = P.struct({ type: CompactSizeLen, key: P.bytes(null) });
|
|
611
|
+
// Key cannot be 'unknown', value coder cannot be array for elements with empty key
|
|
612
|
+
function PSBTKeyMap(psbtEnum) {
|
|
613
|
+
// -> Record<type, [keyName, ...coders]>
|
|
614
|
+
const byType = {};
|
|
615
|
+
for (const k in psbtEnum) {
|
|
616
|
+
const [num, kc, vc] = psbtEnum[k];
|
|
617
|
+
byType[num] = [k, kc, vc];
|
|
618
|
+
}
|
|
619
|
+
return P.wrap({
|
|
620
|
+
encodeStream: (w, value) => {
|
|
621
|
+
let out = [];
|
|
622
|
+
// Because we use order of psbtEnum, keymap is sorted here
|
|
623
|
+
for (const name in psbtEnum) {
|
|
624
|
+
const val = value[name];
|
|
625
|
+
if (val === undefined)
|
|
626
|
+
continue;
|
|
627
|
+
const [type, kc, vc] = psbtEnum[name];
|
|
628
|
+
if (!kc)
|
|
629
|
+
out.push({ key: { type, key: P.EMPTY }, value: vc.encode(val) });
|
|
630
|
+
else {
|
|
631
|
+
// Low level interface, returns keys as is (with duplicates). Useful for debug
|
|
632
|
+
const kv = val.map(([k, v]) => [
|
|
633
|
+
kc.encode(k),
|
|
634
|
+
vc.encode(v),
|
|
635
|
+
]);
|
|
636
|
+
// sort by keys
|
|
637
|
+
kv.sort((a, b) => _cmpBytes(a[0], b[0]));
|
|
638
|
+
for (const [key, value] of kv)
|
|
639
|
+
out.push({ key: { key, type }, value });
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
if (value.unknown) {
|
|
643
|
+
value.unknown.sort((a, b) => _cmpBytes(a[0].key, b[0].key));
|
|
644
|
+
for (const [k, v] of value.unknown)
|
|
645
|
+
out.push({ key: k, value: v });
|
|
646
|
+
}
|
|
647
|
+
PSBTKeyPair.encodeStream(w, out);
|
|
648
|
+
},
|
|
649
|
+
decodeStream: (r) => {
|
|
650
|
+
const raw = PSBTKeyPair.decodeStream(r);
|
|
651
|
+
const out = {};
|
|
652
|
+
const noKey = {};
|
|
653
|
+
for (const elm of raw) {
|
|
654
|
+
let name = 'unknown';
|
|
655
|
+
let key = elm.key.key;
|
|
656
|
+
let value = elm.value;
|
|
657
|
+
if (byType[elm.key.type]) {
|
|
658
|
+
const [_name, kc, vc] = byType[elm.key.type];
|
|
659
|
+
name = _name;
|
|
660
|
+
if (!kc && key.length) {
|
|
661
|
+
throw new Error(`PSBT: Non-empty key for ${name} (key=${hex.encode(key)} value=${hex.encode(value)}`);
|
|
662
|
+
}
|
|
663
|
+
key = kc ? kc.decode(key) : undefined;
|
|
664
|
+
value = vc.decode(value);
|
|
665
|
+
if (!kc) {
|
|
666
|
+
if (out[name])
|
|
667
|
+
throw new Error(`PSBT: Same keys: ${name} (key=${key} value=${value})`);
|
|
668
|
+
out[name] = value;
|
|
669
|
+
noKey[name] = true;
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else {
|
|
674
|
+
// For unknown: add key type inside key
|
|
675
|
+
key = { type: elm.key.type, key: elm.key.key };
|
|
676
|
+
}
|
|
677
|
+
// Only keyed elements at this point
|
|
678
|
+
if (noKey[name])
|
|
679
|
+
throw new Error(`PSBT: Key type with empty key and no key=${name} val=${value}`);
|
|
680
|
+
if (!out[name])
|
|
681
|
+
out[name] = [];
|
|
682
|
+
out[name].push([key, value]);
|
|
683
|
+
}
|
|
684
|
+
return out;
|
|
685
|
+
},
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
// Basic sanity check for scripts
|
|
689
|
+
function checkWSH(s, witnessScript) {
|
|
690
|
+
if (!P.equalBytes(s.hash, sha256(witnessScript)))
|
|
691
|
+
throw new Error('checkScript: wsh wrong witnessScript hash');
|
|
692
|
+
const w = OutScript.decode(witnessScript);
|
|
693
|
+
if (w.type === 'tr' || w.type === 'tr_ns' || w.type === 'tr_ms')
|
|
694
|
+
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2SH`);
|
|
695
|
+
if (w.type === 'wpkh' || w.type === 'sh')
|
|
696
|
+
throw new Error(`checkScript: P2${w.type} cannot be wrapped in P2WSH`);
|
|
697
|
+
}
|
|
698
|
+
function checkScript(script, redeemScript, witnessScript) {
|
|
699
|
+
if (script) {
|
|
700
|
+
const s = OutScript.decode(script);
|
|
701
|
+
// ms||pk maybe work, but there will be no address, hard to spend
|
|
702
|
+
if (s.type === 'tr_ns' || s.type === 'tr_ms' || s.type === 'ms' || s.type == 'pk')
|
|
703
|
+
throw new Error(`checkScript: non-wrapped ${s.type}`);
|
|
704
|
+
if (s.type === 'sh' && redeemScript) {
|
|
705
|
+
if (!P.equalBytes(s.hash, hash160(redeemScript)))
|
|
706
|
+
throw new Error('checkScript: sh wrong redeemScript hash');
|
|
707
|
+
const r = OutScript.decode(redeemScript);
|
|
708
|
+
if (r.type === 'tr' || r.type === 'tr_ns' || r.type === 'tr_ms')
|
|
709
|
+
throw new Error(`checkScript: P2${r.type} cannot be wrapped in P2SH`);
|
|
710
|
+
// Not sure if this unspendable, but we cannot represent this via PSBT
|
|
711
|
+
if (r.type === 'sh')
|
|
712
|
+
throw new Error('checkScript: P2SH cannot be wrapped in P2SH');
|
|
713
|
+
}
|
|
714
|
+
if (s.type === 'wsh' && witnessScript)
|
|
715
|
+
checkWSH(s, witnessScript);
|
|
716
|
+
}
|
|
717
|
+
if (redeemScript) {
|
|
718
|
+
const r = OutScript.decode(redeemScript);
|
|
719
|
+
if (r.type === 'wsh' && witnessScript)
|
|
720
|
+
checkWSH(r, witnessScript);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
const PSBTInputCoder = P.validate(PSBTKeyMap(PSBTInput), (i) => {
|
|
724
|
+
if (i.finalScriptWitness && !i.finalScriptWitness.length)
|
|
725
|
+
throw new Error('validateInput: wmpty finalScriptWitness');
|
|
726
|
+
//if (i.finalScriptSig && !i.finalScriptSig.length) throw new Error('validateInput: empty finalScriptSig');
|
|
727
|
+
if (i.partialSig && !i.partialSig.length)
|
|
728
|
+
throw new Error('Empty partialSig');
|
|
729
|
+
if (i.partialSig)
|
|
730
|
+
for (const [k, v] of i.partialSig)
|
|
731
|
+
validatePubkey(k, PubT.ecdsa);
|
|
732
|
+
if (i.bip32Derivation)
|
|
733
|
+
for (const [k, v] of i.bip32Derivation)
|
|
734
|
+
validatePubkey(k, PubT.ecdsa);
|
|
735
|
+
// Locktime = unsigned little endian integer greater than or equal to 500000000 representing
|
|
736
|
+
if (i.requiredTimeLocktime !== undefined && i.requiredTimeLocktime < 500000000)
|
|
737
|
+
throw new Error(`validateInput: wrong timeLocktime=${i.requiredTimeLocktime}`);
|
|
738
|
+
// unsigned little endian integer greater than 0 and less than 500000000
|
|
739
|
+
if (i.requiredHeightLocktime !== undefined &&
|
|
740
|
+
(i.requiredHeightLocktime <= 0 || i.requiredHeightLocktime >= 500000000))
|
|
741
|
+
throw new Error(`validateInput: wrong heighLocktime=${i.requiredHeightLocktime}`);
|
|
742
|
+
if (i.nonWitnessUtxo && i.index !== undefined) {
|
|
743
|
+
const last = i.nonWitnessUtxo.outputs.length - 1;
|
|
744
|
+
if (i.index > last)
|
|
745
|
+
throw new Error(`validateInput: index(${i.index}) not in nonWitnessUtxo`);
|
|
746
|
+
const prevOut = i.nonWitnessUtxo.outputs[i.index];
|
|
747
|
+
if (i.witnessUtxo &&
|
|
748
|
+
(!P.equalBytes(i.witnessUtxo.script, prevOut.script) ||
|
|
749
|
+
i.witnessUtxo.amount !== prevOut.amount))
|
|
750
|
+
throw new Error('validateInput: witnessUtxo different from nonWitnessUtxo');
|
|
751
|
+
}
|
|
752
|
+
if (i.tapLeafScript) {
|
|
753
|
+
// tap leaf version appears here twice: in control block and at the end of script
|
|
754
|
+
for (const [k, v] of i.tapLeafScript) {
|
|
755
|
+
if ((k.version & 254) !== v[v.length - 1])
|
|
756
|
+
throw new Error('validateInput: tapLeafScript version mimatch');
|
|
757
|
+
if (v[v.length - 1] & 1)
|
|
758
|
+
throw new Error('validateInput: tapLeafScript version has parity bit!');
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// Validate txid for nonWitnessUtxo is correct
|
|
762
|
+
if (i.nonWitnessUtxo && i.index && i.txid) {
|
|
763
|
+
const outputs = i.nonWitnessUtxo.outputs;
|
|
764
|
+
if (outputs.length - 1 < i.index)
|
|
765
|
+
throw new Error('nonWitnessUtxo: incorect output index');
|
|
766
|
+
const tx = Transaction.fromRaw(RawTx.encode(i.nonWitnessUtxo));
|
|
767
|
+
const txid = hex.encode(i.txid);
|
|
768
|
+
if (tx.id !== txid)
|
|
769
|
+
throw new Error(`nonWitnessUtxo: wrong txid, exp=${txid} got=${tx.id}`);
|
|
770
|
+
}
|
|
771
|
+
return i;
|
|
772
|
+
});
|
|
773
|
+
const PSBTOutputCoder = P.validate(PSBTKeyMap(PSBTOutput), (o) => {
|
|
774
|
+
if (o.bip32Derivation)
|
|
775
|
+
for (const [k, v] of o.bip32Derivation)
|
|
776
|
+
validatePubkey(k, PubT.ecdsa);
|
|
777
|
+
return o;
|
|
778
|
+
});
|
|
779
|
+
const PSBTGlobalCoder = P.validate(PSBTKeyMap(PSBTGlobal), (g) => {
|
|
780
|
+
const version = g.version || 0;
|
|
781
|
+
if (version === 0) {
|
|
782
|
+
if (!g.unsignedTx)
|
|
783
|
+
throw new Error('PSBTv0: missing unsignedTx');
|
|
784
|
+
if (g.unsignedTx.segwitFlag || g.unsignedTx.witnesses)
|
|
785
|
+
throw new Error('PSBTv0: witness in unsingedTx');
|
|
786
|
+
for (const inp of g.unsignedTx.inputs)
|
|
787
|
+
if (inp.finalScriptSig && inp.finalScriptSig.length)
|
|
788
|
+
throw new Error('PSBTv0: input scriptSig found in unsignedTx');
|
|
789
|
+
}
|
|
790
|
+
return g;
|
|
791
|
+
});
|
|
792
|
+
export const _RawPSBTV0 = P.struct({
|
|
793
|
+
magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
|
|
794
|
+
global: PSBTGlobalCoder,
|
|
795
|
+
inputs: P.array('global/unsignedTx/inputs/length', PSBTInputCoder),
|
|
796
|
+
outputs: P.array(null, PSBTOutputCoder),
|
|
797
|
+
});
|
|
798
|
+
export const _RawPSBTV2 = P.struct({
|
|
799
|
+
magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
|
|
800
|
+
global: PSBTGlobalCoder,
|
|
801
|
+
inputs: P.array('global/inputCount', PSBTInputCoder),
|
|
802
|
+
outputs: P.array('global/outputCount', PSBTOutputCoder),
|
|
803
|
+
});
|
|
804
|
+
export const _DebugPSBT = P.struct({
|
|
805
|
+
magic: P.magic(P.string(new Uint8Array([0xff])), 'psbt'),
|
|
806
|
+
items: P.array(null, P.apply(P.array(P.NULL, P.tuple([P.hex(CompactSizeLen), P.bytes(CompactSize)])), P.coders.dict())),
|
|
807
|
+
});
|
|
808
|
+
function validatePSBTFields(version, info, lst) {
|
|
809
|
+
for (const k in lst) {
|
|
810
|
+
if (k === 'unknown')
|
|
811
|
+
continue;
|
|
812
|
+
if (!info[k])
|
|
813
|
+
continue;
|
|
814
|
+
const { allowInc } = PSBTKeyInfo(info[k]);
|
|
815
|
+
if (!allowInc.includes(version))
|
|
816
|
+
throw new Error(`PSBTv${version}: field ${k} is not allowed`);
|
|
817
|
+
}
|
|
818
|
+
for (const k in info) {
|
|
819
|
+
const { reqInc } = PSBTKeyInfo(info[k]);
|
|
820
|
+
if (reqInc.includes(version) && lst[k] === undefined)
|
|
821
|
+
throw new Error(`PSBTv${version}: missing required field ${k}`);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
function cleanPSBTFields(version, info, lst) {
|
|
825
|
+
const out = {};
|
|
826
|
+
for (const _k in lst) {
|
|
827
|
+
const k = _k;
|
|
828
|
+
if (k !== 'unknown') {
|
|
829
|
+
if (!info[k])
|
|
830
|
+
continue;
|
|
831
|
+
const { allowInc, silentIgnore } = PSBTKeyInfo(info[k]);
|
|
832
|
+
if (!allowInc.includes(version)) {
|
|
833
|
+
if (silentIgnore)
|
|
834
|
+
continue;
|
|
835
|
+
throw new Error(`Failed to serialize in PSBTv${version}: ${k} but versions allows inclusion=${allowInc}`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
out[k] = lst[k];
|
|
839
|
+
}
|
|
840
|
+
return out;
|
|
841
|
+
}
|
|
842
|
+
function validatePSBT(tx) {
|
|
843
|
+
const version = (tx && tx.global && tx.global.version) || 0;
|
|
844
|
+
validatePSBTFields(version, PSBTGlobal, tx.global);
|
|
845
|
+
for (const i of tx.inputs)
|
|
846
|
+
validatePSBTFields(version, PSBTInput, i);
|
|
847
|
+
for (const o of tx.outputs)
|
|
848
|
+
validatePSBTFields(version, PSBTOutput, o);
|
|
849
|
+
// We allow only one empty element at the end of map (compat with bitcoinjs-lib bug)
|
|
850
|
+
const inputCount = !version ? tx.global.unsignedTx.inputs.length : tx.global.inputCount;
|
|
851
|
+
if (tx.inputs.length < inputCount)
|
|
852
|
+
throw new Error('Not enough inputs');
|
|
853
|
+
const inputsLeft = tx.inputs.slice(inputCount);
|
|
854
|
+
if (inputsLeft.length > 1 || (inputsLeft.length && Object.keys(inputsLeft[0]).length))
|
|
855
|
+
throw new Error(`Unexpected inputs left in tx=${inputsLeft}`);
|
|
856
|
+
// Same for inputs
|
|
857
|
+
const outputCount = !version ? tx.global.unsignedTx.outputs.length : tx.global.outputCount;
|
|
858
|
+
if (tx.outputs.length < outputCount)
|
|
859
|
+
throw new Error('Not outputs inputs');
|
|
860
|
+
const outputsLeft = tx.outputs.slice(outputCount);
|
|
861
|
+
if (outputsLeft.length > 1 || (outputsLeft.length && Object.keys(outputsLeft[0]).length))
|
|
862
|
+
throw new Error(`Unexpected outputs left in tx=${outputsLeft}`);
|
|
863
|
+
return tx;
|
|
864
|
+
}
|
|
865
|
+
function mergeKeyMap(psbtEnum, val, cur, allowedFields) {
|
|
866
|
+
const res = { ...cur, ...val };
|
|
867
|
+
// All arguments can be provided as hex
|
|
868
|
+
for (const k in psbtEnum) {
|
|
869
|
+
const key = k;
|
|
870
|
+
const [_, kC, vC] = psbtEnum[key];
|
|
871
|
+
const cannotChange = allowedFields && !allowedFields.includes(k);
|
|
872
|
+
if (val[k] === undefined && k in val) {
|
|
873
|
+
if (cannotChange)
|
|
874
|
+
throw new Error(`Cannot remove signed field=${k}`);
|
|
875
|
+
delete res[k];
|
|
876
|
+
}
|
|
877
|
+
else if (kC) {
|
|
878
|
+
const oldKV = (cur && cur[k] ? cur[k] : []);
|
|
879
|
+
let newKV = val[key];
|
|
880
|
+
if (newKV) {
|
|
881
|
+
if (!Array.isArray(newKV))
|
|
882
|
+
throw new Error(`keyMap(${k}): KV pairs should be [k, v][]`);
|
|
883
|
+
// Decode hex in k-v
|
|
884
|
+
newKV = newKV.map((val) => {
|
|
885
|
+
if (val.length !== 2)
|
|
886
|
+
throw new Error(`keyMap(${k}): KV pairs should be [k, v][]`);
|
|
887
|
+
return [
|
|
888
|
+
typeof val[0] === 'string' ? kC.decode(hex.decode(val[0])) : val[0],
|
|
889
|
+
typeof val[1] === 'string' ? vC.decode(hex.decode(val[1])) : val[1],
|
|
890
|
+
];
|
|
891
|
+
});
|
|
892
|
+
const map = {};
|
|
893
|
+
const add = (kStr, k, v) => {
|
|
894
|
+
if (map[kStr] === undefined) {
|
|
895
|
+
map[kStr] = [k, v];
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const oldVal = hex.encode(vC.encode(map[kStr][1]));
|
|
899
|
+
const newVal = hex.encode(vC.encode(v));
|
|
900
|
+
if (oldVal !== newVal)
|
|
901
|
+
throw new Error(`keyMap(${key}): same key=${kStr} oldVal=${oldVal} newVal=${newVal}`);
|
|
902
|
+
};
|
|
903
|
+
for (const [k, v] of oldKV) {
|
|
904
|
+
const kStr = hex.encode(kC.encode(k));
|
|
905
|
+
add(kStr, k, v);
|
|
906
|
+
}
|
|
907
|
+
for (const [k, v] of newKV) {
|
|
908
|
+
const kStr = hex.encode(kC.encode(k));
|
|
909
|
+
// undefined removes previous value
|
|
910
|
+
if (v === undefined) {
|
|
911
|
+
if (cannotChange)
|
|
912
|
+
throw new Error(`Cannot remove signed field=${key}/${k}`);
|
|
913
|
+
delete map[kStr];
|
|
914
|
+
}
|
|
915
|
+
else
|
|
916
|
+
add(kStr, k, v);
|
|
917
|
+
}
|
|
918
|
+
res[key] = Object.values(map);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
else if (typeof res[k] === 'string') {
|
|
922
|
+
res[k] = vC.decode(hex.decode(res[k]));
|
|
923
|
+
}
|
|
924
|
+
else if (cannotChange && k in val && cur && cur[k] !== undefined) {
|
|
925
|
+
if (!P.equalBytes(vC.encode(val[k]), vC.encode(cur[k])))
|
|
926
|
+
throw new Error(`Cannot change signed field=${k}`);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// Remove unknown keys
|
|
930
|
+
for (const k in res)
|
|
931
|
+
if (!psbtEnum[k])
|
|
932
|
+
delete res[k];
|
|
933
|
+
return res;
|
|
934
|
+
}
|
|
935
|
+
export const RawPSBTV0 = P.validate(_RawPSBTV0, validatePSBT);
|
|
936
|
+
export const RawPSBTV2 = P.validate(_RawPSBTV2, validatePSBT);
|
|
937
|
+
// (TxHash, Idx)
|
|
938
|
+
const TxHashIdx = P.struct({ txid: P.bytes(32, true), index: P.U32LE });
|
|
939
|
+
const OutPK = {
|
|
940
|
+
encode(from) {
|
|
941
|
+
if (from.length !== 2 ||
|
|
942
|
+
!isBytes(from[0]) ||
|
|
943
|
+
!isValidPubkey(from[0], PubT.ecdsa) ||
|
|
944
|
+
from[1] !== 'CHECKSIG')
|
|
945
|
+
return;
|
|
946
|
+
return { type: 'pk', pubkey: from[0] };
|
|
947
|
+
},
|
|
948
|
+
decode: (to) => (to.type === 'pk' ? [to.pubkey, 'CHECKSIG'] : undefined),
|
|
949
|
+
};
|
|
950
|
+
export const p2pk = (pubkey, network = NETWORK) => {
|
|
951
|
+
if (!isValidPubkey(pubkey, PubT.ecdsa))
|
|
952
|
+
throw new Error('P2PK: invalid publicKey');
|
|
953
|
+
return {
|
|
954
|
+
type: 'pk',
|
|
955
|
+
script: OutScript.encode({ type: 'pk', pubkey }),
|
|
956
|
+
};
|
|
957
|
+
};
|
|
958
|
+
const OutPKH = {
|
|
959
|
+
encode(from) {
|
|
960
|
+
if (from.length !== 5 || from[0] !== 'DUP' || from[1] !== 'HASH160' || !isBytes(from[2]))
|
|
961
|
+
return;
|
|
962
|
+
if (from[3] !== 'EQUALVERIFY' || from[4] !== 'CHECKSIG')
|
|
963
|
+
return;
|
|
964
|
+
return { type: 'pkh', hash: from[2] };
|
|
965
|
+
},
|
|
966
|
+
decode: (to) => to.type === 'pkh' ? ['DUP', 'HASH160', to.hash, 'EQUALVERIFY', 'CHECKSIG'] : undefined,
|
|
967
|
+
};
|
|
968
|
+
export const p2pkh = (publicKey, network = NETWORK) => {
|
|
969
|
+
if (!isValidPubkey(publicKey, PubT.ecdsa))
|
|
970
|
+
throw new Error('P2PKH: invalid publicKey');
|
|
971
|
+
const hash = hash160(publicKey);
|
|
972
|
+
return {
|
|
973
|
+
type: 'pkh',
|
|
974
|
+
script: OutScript.encode({ type: 'pkh', hash }),
|
|
975
|
+
address: Address(network).encode({ type: 'pkh', hash }),
|
|
976
|
+
};
|
|
977
|
+
};
|
|
978
|
+
const OutSH = {
|
|
979
|
+
encode(from) {
|
|
980
|
+
if (from.length !== 3 || from[0] !== 'HASH160' || !isBytes(from[1]) || from[2] !== 'EQUAL')
|
|
981
|
+
return;
|
|
982
|
+
return { type: 'sh', hash: from[1] };
|
|
983
|
+
},
|
|
984
|
+
decode: (to) => to.type === 'sh' ? ['HASH160', to.hash, 'EQUAL'] : undefined,
|
|
985
|
+
};
|
|
986
|
+
export const p2sh = (child, network = NETWORK) => {
|
|
987
|
+
// It is already tested inside noble-hashes and checkScript
|
|
988
|
+
const cs = child.script;
|
|
989
|
+
if (!isBytes(cs))
|
|
990
|
+
throw new Error(`Wrong script: ${typeof child.script}, expected Uint8Array`);
|
|
991
|
+
const hash = hash160(cs);
|
|
992
|
+
const script = OutScript.encode({ type: 'sh', hash });
|
|
993
|
+
checkScript(script, cs, child.witnessScript);
|
|
994
|
+
const res = {
|
|
995
|
+
type: 'sh',
|
|
996
|
+
redeemScript: cs,
|
|
997
|
+
script: OutScript.encode({ type: 'sh', hash }),
|
|
998
|
+
address: Address(network).encode({ type: 'sh', hash }),
|
|
999
|
+
};
|
|
1000
|
+
if (child.witnessScript)
|
|
1001
|
+
res.witnessScript = child.witnessScript;
|
|
1002
|
+
return res;
|
|
1003
|
+
};
|
|
1004
|
+
const OutWSH = {
|
|
1005
|
+
encode(from) {
|
|
1006
|
+
if (from.length !== 2 || from[0] !== 0 || !isBytes(from[1]))
|
|
1007
|
+
return;
|
|
1008
|
+
if (from[1].length !== 32)
|
|
1009
|
+
return;
|
|
1010
|
+
return { type: 'wsh', hash: from[1] };
|
|
1011
|
+
},
|
|
1012
|
+
decode: (to) => (to.type === 'wsh' ? [0, to.hash] : undefined),
|
|
1013
|
+
};
|
|
1014
|
+
export const p2wsh = (child, network = NETWORK) => {
|
|
1015
|
+
const cs = child.script;
|
|
1016
|
+
if (!isBytes(cs))
|
|
1017
|
+
throw new Error(`Wrong script: ${typeof cs}, expected Uint8Array`);
|
|
1018
|
+
const hash = sha256(cs);
|
|
1019
|
+
const script = OutScript.encode({ type: 'wsh', hash });
|
|
1020
|
+
checkScript(script, undefined, cs);
|
|
1021
|
+
return {
|
|
1022
|
+
type: 'wsh',
|
|
1023
|
+
witnessScript: cs,
|
|
1024
|
+
script: OutScript.encode({ type: 'wsh', hash }),
|
|
1025
|
+
address: Address(network).encode({ type: 'wsh', hash }),
|
|
1026
|
+
};
|
|
1027
|
+
};
|
|
1028
|
+
const OutWPKH = {
|
|
1029
|
+
encode(from) {
|
|
1030
|
+
if (from.length !== 2 || from[0] !== 0 || !isBytes(from[1]))
|
|
1031
|
+
return;
|
|
1032
|
+
if (from[1].length !== 20)
|
|
1033
|
+
return;
|
|
1034
|
+
return { type: 'wpkh', hash: from[1] };
|
|
1035
|
+
},
|
|
1036
|
+
decode: (to) => (to.type === 'wpkh' ? [0, to.hash] : undefined),
|
|
1037
|
+
};
|
|
1038
|
+
export const p2wpkh = (publicKey, network = NETWORK) => {
|
|
1039
|
+
if (!isValidPubkey(publicKey, PubT.ecdsa))
|
|
1040
|
+
throw new Error('P2WPKH: invalid publicKey');
|
|
1041
|
+
if (publicKey.length === 65)
|
|
1042
|
+
throw new Error('P2WPKH: uncompressed public key');
|
|
1043
|
+
const hash = hash160(publicKey);
|
|
1044
|
+
return {
|
|
1045
|
+
type: 'wpkh',
|
|
1046
|
+
script: OutScript.encode({ type: 'wpkh', hash }),
|
|
1047
|
+
address: Address(network).encode({ type: 'wpkh', hash }),
|
|
1048
|
+
};
|
|
1049
|
+
};
|
|
1050
|
+
const OutMS = {
|
|
1051
|
+
encode(from) {
|
|
1052
|
+
const last = from.length - 1;
|
|
1053
|
+
if (from[last] !== 'CHECKMULTISIG')
|
|
1054
|
+
return;
|
|
1055
|
+
const m = from[0];
|
|
1056
|
+
const n = from[last - 1];
|
|
1057
|
+
if (typeof m !== 'number' || typeof n !== 'number')
|
|
1058
|
+
return;
|
|
1059
|
+
const pubkeys = from.slice(1, -2);
|
|
1060
|
+
if (n !== pubkeys.length)
|
|
1061
|
+
return;
|
|
1062
|
+
for (const pub of pubkeys)
|
|
1063
|
+
if (!isBytes(pub))
|
|
1064
|
+
return;
|
|
1065
|
+
return { type: 'ms', m, pubkeys: pubkeys }; // we don't need n, since it is the same as pubkeys
|
|
1066
|
+
},
|
|
1067
|
+
// checkmultisig(n, ..pubkeys, m)
|
|
1068
|
+
decode: (to) => to.type === 'ms' ? [to.m, ...to.pubkeys, to.pubkeys.length, 'CHECKMULTISIG'] : undefined,
|
|
1069
|
+
};
|
|
1070
|
+
export const p2ms = (m, pubkeys, allowSamePubkeys = false) => {
|
|
1071
|
+
if (!allowSamePubkeys)
|
|
1072
|
+
uniqPubkey(pubkeys);
|
|
1073
|
+
return { type: 'ms', script: OutScript.encode({ type: 'ms', pubkeys, m }) };
|
|
1074
|
+
};
|
|
1075
|
+
const OutTR = {
|
|
1076
|
+
encode(from) {
|
|
1077
|
+
if (from.length !== 2 || from[0] !== 1 || !isBytes(from[1]))
|
|
1078
|
+
return;
|
|
1079
|
+
return { type: 'tr', pubkey: from[1] };
|
|
1080
|
+
},
|
|
1081
|
+
decode: (to) => (to.type === 'tr' ? [1, to.pubkey] : undefined),
|
|
1082
|
+
};
|
|
1083
|
+
// Helper for generating binary tree from list, with weights
|
|
1084
|
+
export function taprootListToTree(taprootList) {
|
|
1085
|
+
// Clone input in order to not corrupt it
|
|
1086
|
+
const lst = Array.from(taprootList);
|
|
1087
|
+
// We have at least 2 elements => can create branch
|
|
1088
|
+
while (lst.length >= 2) {
|
|
1089
|
+
// Sort: elements with smallest weight are in the end of queue
|
|
1090
|
+
lst.sort((a, b) => (b.weight || 1) - (a.weight || 1));
|
|
1091
|
+
const b = lst.pop();
|
|
1092
|
+
const a = lst.pop();
|
|
1093
|
+
const weight = (a?.weight || 1) + (b?.weight || 1);
|
|
1094
|
+
lst.push({
|
|
1095
|
+
weight,
|
|
1096
|
+
// Unwrap children array
|
|
1097
|
+
// TODO: Very hard to remove any here
|
|
1098
|
+
childs: [a?.childs || a, b?.childs || b],
|
|
1099
|
+
});
|
|
1100
|
+
}
|
|
1101
|
+
// At this point there is always 1 element in lst
|
|
1102
|
+
const last = lst[0];
|
|
1103
|
+
return (last?.childs || last);
|
|
1104
|
+
}
|
|
1105
|
+
function checkTaprootScript(script, allowUnknowOutput = false) {
|
|
1106
|
+
const out = OutScript.decode(script);
|
|
1107
|
+
if (out.type === 'unknown' && allowUnknowOutput)
|
|
1108
|
+
return;
|
|
1109
|
+
if (!['tr_ns', 'tr_ms'].includes(out.type))
|
|
1110
|
+
throw new Error(`P2TR: invalid leaf script=${out.type}`);
|
|
1111
|
+
}
|
|
1112
|
+
function taprootHashTree(tree, allowUnknowOutput = false) {
|
|
1113
|
+
if (!tree)
|
|
1114
|
+
throw new Error('taprootHashTree: empty tree');
|
|
1115
|
+
if (Array.isArray(tree) && tree.length === 1)
|
|
1116
|
+
tree = tree[0];
|
|
1117
|
+
// Terminal node (leaf)
|
|
1118
|
+
if (!Array.isArray(tree)) {
|
|
1119
|
+
const { leafVersion: version, script: leafScript, tapInternalKey } = tree;
|
|
1120
|
+
// Earliest tree walk where we can validate tapScripts
|
|
1121
|
+
if (tree.tapLeafScript || (tree.tapMerkleRoot && !P.equalBytes(tree.tapMerkleRoot, P.EMPTY)))
|
|
1122
|
+
throw new Error('P2TR: tapRoot leafScript cannot have tree');
|
|
1123
|
+
// Just to be sure that it is spendable
|
|
1124
|
+
if (tapInternalKey && P.equalBytes(tapInternalKey, TAPROOT_UNSPENDABLE_KEY))
|
|
1125
|
+
throw new Error('P2TR: tapRoot leafScript cannot have unspendble key');
|
|
1126
|
+
const script = typeof leafScript === 'string' ? hex.decode(leafScript) : leafScript;
|
|
1127
|
+
if (!isBytes(script))
|
|
1128
|
+
throw new Error(`checkScript: wrong script type=${script}`);
|
|
1129
|
+
checkTaprootScript(script, allowUnknowOutput);
|
|
1130
|
+
return {
|
|
1131
|
+
type: 'leaf',
|
|
1132
|
+
tapInternalKey,
|
|
1133
|
+
version,
|
|
1134
|
+
script,
|
|
1135
|
+
hash: tapLeafHash(script, version),
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
// If tree / branch is not binary tree, convert it
|
|
1139
|
+
if (tree.length !== 2)
|
|
1140
|
+
tree = taprootListToTree(tree);
|
|
1141
|
+
if (tree.length !== 2)
|
|
1142
|
+
throw new Error('hashTree: non binary tree!');
|
|
1143
|
+
// branch
|
|
1144
|
+
// Both nodes should exist
|
|
1145
|
+
const left = taprootHashTree(tree[0], allowUnknowOutput);
|
|
1146
|
+
const right = taprootHashTree(tree[1], allowUnknowOutput);
|
|
1147
|
+
// We cannot swap left/right here, since it will change structure of tree
|
|
1148
|
+
let [lH, rH] = [left.hash, right.hash];
|
|
1149
|
+
if (_cmpBytes(rH, lH) === -1)
|
|
1150
|
+
[lH, rH] = [rH, lH];
|
|
1151
|
+
return { type: 'branch', left, right, hash: schnorr.utils.taggedHash('TapBranch', lH, rH) };
|
|
1152
|
+
}
|
|
1153
|
+
function taprootAddPath(tree, path = []) {
|
|
1154
|
+
if (!tree)
|
|
1155
|
+
throw new Error(`taprootAddPath: empty tree`);
|
|
1156
|
+
if (tree.type === 'leaf')
|
|
1157
|
+
return { ...tree, path };
|
|
1158
|
+
if (tree.type !== 'branch')
|
|
1159
|
+
throw new Error(`taprootAddPath: wrong type=${tree}`);
|
|
1160
|
+
return {
|
|
1161
|
+
...tree,
|
|
1162
|
+
path,
|
|
1163
|
+
// Left element has right hash in path and otherwise
|
|
1164
|
+
left: taprootAddPath(tree.left, [tree.right.hash, ...path]),
|
|
1165
|
+
right: taprootAddPath(tree.right, [tree.left.hash, ...path]),
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
function taprootWalkTree(tree) {
|
|
1169
|
+
if (!tree)
|
|
1170
|
+
throw new Error(`taprootAddPath: empty tree`);
|
|
1171
|
+
if (tree.type === 'leaf')
|
|
1172
|
+
return [tree];
|
|
1173
|
+
if (tree.type !== 'branch')
|
|
1174
|
+
throw new Error(`taprootWalkTree: wrong type=${tree}`);
|
|
1175
|
+
return [...taprootWalkTree(tree.left), ...taprootWalkTree(tree.right)];
|
|
1176
|
+
}
|
|
1177
|
+
// Another stupid decision, where lack of standard affects security.
|
|
1178
|
+
// Multisig needs to be generated with some key.
|
|
1179
|
+
// We are using approach from BIP 341/bitcoinjs-lib: SHA256(uncompressedDER(SECP256K1_GENERATOR_POINT))
|
|
1180
|
+
// It is possible to switch SECP256K1_GENERATOR_POINT with some random point;
|
|
1181
|
+
// but it's too complex to prove.
|
|
1182
|
+
// Also used by bitcoin-core and bitcoinjs-lib
|
|
1183
|
+
export const TAPROOT_UNSPENDABLE_KEY = sha256(ProjPoint.BASE.toRawBytes(false));
|
|
1184
|
+
// Works as key OR tree.
|
|
1185
|
+
// If we only have tree, need to add unspendable key, otherwise
|
|
1186
|
+
// complex multisig wallet can be spent by owner of key only. See TAPROOT_UNSPENDABLE_KEY
|
|
1187
|
+
export function p2tr(internalPubKey, tree, network = NETWORK, allowUnknowOutput = false) {
|
|
1188
|
+
// Unspendable
|
|
1189
|
+
if (!internalPubKey && !tree)
|
|
1190
|
+
throw new Error('p2tr: should have pubKey or scriptTree (or both)');
|
|
1191
|
+
const pubKey = typeof internalPubKey === 'string'
|
|
1192
|
+
? hex.decode(internalPubKey)
|
|
1193
|
+
: internalPubKey || TAPROOT_UNSPENDABLE_KEY;
|
|
1194
|
+
if (!isValidPubkey(pubKey, PubT.schnorr))
|
|
1195
|
+
throw new Error('p2tr: non-schnorr pubkey');
|
|
1196
|
+
let hashedTree = tree ? taprootAddPath(taprootHashTree(tree, allowUnknowOutput)) : undefined;
|
|
1197
|
+
const tapMerkleRoot = hashedTree ? hashedTree.hash : undefined;
|
|
1198
|
+
const [tweakedPubkey, parity] = taprootTweakPubkey(pubKey, tapMerkleRoot || P.EMPTY);
|
|
1199
|
+
let leaves;
|
|
1200
|
+
if (hashedTree) {
|
|
1201
|
+
leaves = taprootWalkTree(hashedTree).map((l) => ({
|
|
1202
|
+
...l,
|
|
1203
|
+
controlBlock: TaprootControlBlock.encode({
|
|
1204
|
+
version: (l.version || TAP_LEAF_VERSION) + parity,
|
|
1205
|
+
internalKey: l.tapInternalKey || pubKey,
|
|
1206
|
+
merklePath: l.path,
|
|
1207
|
+
}),
|
|
1208
|
+
}));
|
|
1209
|
+
}
|
|
1210
|
+
let tapLeafScript;
|
|
1211
|
+
if (leaves) {
|
|
1212
|
+
tapLeafScript = leaves.map((l) => [
|
|
1213
|
+
TaprootControlBlock.decode(l.controlBlock),
|
|
1214
|
+
concat(l.script, new Uint8Array([l.version || TAP_LEAF_VERSION])),
|
|
1215
|
+
]);
|
|
1216
|
+
}
|
|
1217
|
+
const res = {
|
|
1218
|
+
type: 'tr',
|
|
1219
|
+
script: OutScript.encode({ type: 'tr', pubkey: tweakedPubkey }),
|
|
1220
|
+
address: Address(network).encode({ type: 'tr', pubkey: tweakedPubkey }),
|
|
1221
|
+
// For tests
|
|
1222
|
+
tweakedPubkey,
|
|
1223
|
+
// PSBT stuff
|
|
1224
|
+
tapInternalKey: pubKey,
|
|
1225
|
+
};
|
|
1226
|
+
// Just in case someone would want to select a specific script
|
|
1227
|
+
if (leaves)
|
|
1228
|
+
res.leaves = leaves;
|
|
1229
|
+
if (tapLeafScript)
|
|
1230
|
+
res.tapLeafScript = tapLeafScript;
|
|
1231
|
+
if (tapMerkleRoot)
|
|
1232
|
+
res.tapMerkleRoot = tapMerkleRoot;
|
|
1233
|
+
return res;
|
|
1234
|
+
}
|
|
1235
|
+
const OutTRNS = {
|
|
1236
|
+
encode(from) {
|
|
1237
|
+
const last = from.length - 1;
|
|
1238
|
+
if (from[last] !== 'CHECKSIG')
|
|
1239
|
+
return;
|
|
1240
|
+
const pubkeys = [];
|
|
1241
|
+
// On error return, since it can be different script
|
|
1242
|
+
for (let i = 0; i < last; i++) {
|
|
1243
|
+
const elm = from[i];
|
|
1244
|
+
if (i & 1) {
|
|
1245
|
+
if (elm !== 'CHECKSIGVERIFY' || i === last - 1)
|
|
1246
|
+
return;
|
|
1247
|
+
continue;
|
|
1248
|
+
}
|
|
1249
|
+
if (!isBytes(elm))
|
|
1250
|
+
return;
|
|
1251
|
+
pubkeys.push(elm);
|
|
1252
|
+
}
|
|
1253
|
+
return { type: 'tr_ns', pubkeys };
|
|
1254
|
+
},
|
|
1255
|
+
decode: (to) => {
|
|
1256
|
+
if (to.type !== 'tr_ns')
|
|
1257
|
+
return;
|
|
1258
|
+
const out = [];
|
|
1259
|
+
for (let i = 0; i < to.pubkeys.length - 1; i++)
|
|
1260
|
+
out.push(to.pubkeys[i], 'CHECKSIGVERIFY');
|
|
1261
|
+
out.push(to.pubkeys[to.pubkeys.length - 1], 'CHECKSIG');
|
|
1262
|
+
return out;
|
|
1263
|
+
},
|
|
1264
|
+
};
|
|
1265
|
+
// Returns all combinations of size M from lst
|
|
1266
|
+
export function combinations(m, list) {
|
|
1267
|
+
const res = [];
|
|
1268
|
+
if (!Array.isArray(list))
|
|
1269
|
+
throw new Error('combinations: lst arg should be array');
|
|
1270
|
+
const n = list.length;
|
|
1271
|
+
if (m > n)
|
|
1272
|
+
throw new Error('combinations: m > lst.length, no combinations possible');
|
|
1273
|
+
/*
|
|
1274
|
+
Basically works as M nested loops like:
|
|
1275
|
+
for (;idx[0]<lst.length;idx[0]++) for (idx[1]=idx[0]+1;idx[1]<lst.length;idx[1]++)
|
|
1276
|
+
but since we cannot create nested loops dynamically, we unroll it to a single loop
|
|
1277
|
+
*/
|
|
1278
|
+
const idx = Array.from({ length: m }, (_, i) => i);
|
|
1279
|
+
const last = idx.length - 1;
|
|
1280
|
+
main: for (;;) {
|
|
1281
|
+
res.push(idx.map((i) => list[i]));
|
|
1282
|
+
idx[last] += 1;
|
|
1283
|
+
let i = last;
|
|
1284
|
+
// Propagate increment
|
|
1285
|
+
// idx[i] cannot be bigger than n-m+i, otherwise last elements in right part will overflow
|
|
1286
|
+
for (; i >= 0 && idx[i] > n - m + i; i--) {
|
|
1287
|
+
idx[i] = 0;
|
|
1288
|
+
// Overflow in idx[0], break
|
|
1289
|
+
if (i === 0)
|
|
1290
|
+
break main;
|
|
1291
|
+
idx[i - 1] += 1;
|
|
1292
|
+
}
|
|
1293
|
+
// Propagate: idx[i+1] = idx[idx]+1
|
|
1294
|
+
for (i += 1; i < idx.length; i++)
|
|
1295
|
+
idx[i] = idx[i - 1] + 1;
|
|
1296
|
+
}
|
|
1297
|
+
return res;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* M-of-N multi-leaf wallet via p2tr_ns. If m == n, single script is emitted.
|
|
1301
|
+
* Takes O(n^2) if m != n. 99-of-100 is ok, 5-of-100 is not.
|
|
1302
|
+
* `2-of-[A,B,C] => [A,B] | [A,C] | [B,C]`
|
|
1303
|
+
*/
|
|
1304
|
+
export const p2tr_ns = (m, pubkeys, allowSamePubkeys = false) => {
|
|
1305
|
+
if (!allowSamePubkeys)
|
|
1306
|
+
uniqPubkey(pubkeys);
|
|
1307
|
+
return combinations(m, pubkeys).map((i) => ({
|
|
1308
|
+
type: 'tr_ns',
|
|
1309
|
+
script: OutScript.encode({ type: 'tr_ns', pubkeys: i }),
|
|
1310
|
+
}));
|
|
1311
|
+
};
|
|
1312
|
+
// Taproot public key (case of p2tr_ns)
|
|
1313
|
+
export const p2tr_pk = (pubkey) => p2tr_ns(1, [pubkey], undefined)[0];
|
|
1314
|
+
const OutTRMS = {
|
|
1315
|
+
encode(from) {
|
|
1316
|
+
const last = from.length - 1;
|
|
1317
|
+
if (from[last] !== 'NUMEQUAL' || from[1] !== 'CHECKSIG')
|
|
1318
|
+
return;
|
|
1319
|
+
const pubkeys = [];
|
|
1320
|
+
const m = OpToNum(from[last - 1]);
|
|
1321
|
+
if (typeof m !== 'number')
|
|
1322
|
+
return;
|
|
1323
|
+
for (let i = 0; i < last - 1; i++) {
|
|
1324
|
+
const elm = from[i];
|
|
1325
|
+
if (i & 1) {
|
|
1326
|
+
if (elm !== (i === 1 ? 'CHECKSIG' : 'CHECKSIGADD'))
|
|
1327
|
+
throw new Error('OutScript.encode/tr_ms: wrong element');
|
|
1328
|
+
continue;
|
|
1329
|
+
}
|
|
1330
|
+
if (!isBytes(elm))
|
|
1331
|
+
throw new Error('OutScript.encode/tr_ms: wrong key element');
|
|
1332
|
+
pubkeys.push(elm);
|
|
1333
|
+
}
|
|
1334
|
+
return { type: 'tr_ms', pubkeys, m };
|
|
1335
|
+
},
|
|
1336
|
+
decode: (to) => {
|
|
1337
|
+
if (to.type !== 'tr_ms')
|
|
1338
|
+
return;
|
|
1339
|
+
const out = [to.pubkeys[0], 'CHECKSIG'];
|
|
1340
|
+
for (let i = 1; i < to.pubkeys.length; i++)
|
|
1341
|
+
out.push(to.pubkeys[i], 'CHECKSIGADD');
|
|
1342
|
+
out.push(to.m, 'NUMEQUAL');
|
|
1343
|
+
return out;
|
|
1344
|
+
},
|
|
1345
|
+
};
|
|
1346
|
+
export function p2tr_ms(m, pubkeys, allowSamePubkeys = false) {
|
|
1347
|
+
if (!allowSamePubkeys)
|
|
1348
|
+
uniqPubkey(pubkeys);
|
|
1349
|
+
return {
|
|
1350
|
+
type: 'tr_ms',
|
|
1351
|
+
script: OutScript.encode({ type: 'tr_ms', pubkeys, m }),
|
|
1352
|
+
};
|
|
1353
|
+
}
|
|
1354
|
+
const OutUnknown = {
|
|
1355
|
+
encode(from) {
|
|
1356
|
+
return { type: 'unknown', script: Script.encode(from) };
|
|
1357
|
+
},
|
|
1358
|
+
decode: (to) => to.type === 'unknown' ? Script.decode(to.script) : undefined,
|
|
1359
|
+
};
|
|
1360
|
+
// /Payments
|
|
1361
|
+
const OutScripts = [
|
|
1362
|
+
OutPK,
|
|
1363
|
+
OutPKH,
|
|
1364
|
+
OutSH,
|
|
1365
|
+
OutWSH,
|
|
1366
|
+
OutWPKH,
|
|
1367
|
+
OutMS,
|
|
1368
|
+
OutTR,
|
|
1369
|
+
OutTRNS,
|
|
1370
|
+
OutTRMS,
|
|
1371
|
+
OutUnknown,
|
|
1372
|
+
];
|
|
1373
|
+
// TODO: we can support user supplied output scripts now
|
|
1374
|
+
// - addOutScript
|
|
1375
|
+
// - removeOutScript
|
|
1376
|
+
// - We can do that as log we modify array in-place
|
|
1377
|
+
// - Actually is very hard, since there is sign/finalize logic
|
|
1378
|
+
const _OutScript = P.apply(Script, P.coders.match(OutScripts));
|
|
1379
|
+
// We can validate this once, because of packed & coders
|
|
1380
|
+
export const OutScript = P.validate(_OutScript, (i) => {
|
|
1381
|
+
if (i.type === 'pk' && !isValidPubkey(i.pubkey, PubT.ecdsa))
|
|
1382
|
+
throw new Error('OutScript/pk: wrong key');
|
|
1383
|
+
if ((i.type === 'pkh' || i.type === 'sh' || i.type === 'wpkh') &&
|
|
1384
|
+
(!isBytes(i.hash) || i.hash.length !== 20))
|
|
1385
|
+
throw new Error(`OutScript/${i.type}: wrong hash`);
|
|
1386
|
+
if (i.type === 'wsh' && (!isBytes(i.hash) || i.hash.length !== 32))
|
|
1387
|
+
throw new Error(`OutScript/wsh: wrong hash`);
|
|
1388
|
+
if (i.type === 'tr' && (!isBytes(i.pubkey) || !isValidPubkey(i.pubkey, PubT.schnorr)))
|
|
1389
|
+
throw new Error('OutScript/tr: wrong taproot public key');
|
|
1390
|
+
if (i.type === 'ms' || i.type === 'tr_ns' || i.type === 'tr_ms')
|
|
1391
|
+
if (!Array.isArray(i.pubkeys))
|
|
1392
|
+
throw new Error('OutScript/multisig: wrong pubkeys array');
|
|
1393
|
+
if (i.type === 'ms') {
|
|
1394
|
+
const n = i.pubkeys.length;
|
|
1395
|
+
for (const p of i.pubkeys)
|
|
1396
|
+
if (!isValidPubkey(p, PubT.ecdsa))
|
|
1397
|
+
throw new Error('OutScript/multisig: wrong pubkey');
|
|
1398
|
+
if (i.m <= 0 || n > 16 || i.m > n)
|
|
1399
|
+
throw new Error('OutScript/multisig: invalid params');
|
|
1400
|
+
}
|
|
1401
|
+
if (i.type === 'tr_ns' || i.type === 'tr_ms') {
|
|
1402
|
+
for (const p of i.pubkeys)
|
|
1403
|
+
if (!isValidPubkey(p, PubT.schnorr))
|
|
1404
|
+
throw new Error(`OutScript/${i.type}: wrong pubkey`);
|
|
1405
|
+
}
|
|
1406
|
+
if (i.type === 'tr_ms') {
|
|
1407
|
+
const n = i.pubkeys.length;
|
|
1408
|
+
if (i.m <= 0 || n > 999 || i.m > n)
|
|
1409
|
+
throw new Error('OutScript/tr_ms: invalid params');
|
|
1410
|
+
}
|
|
1411
|
+
return i;
|
|
1412
|
+
});
|
|
1413
|
+
// Address
|
|
1414
|
+
function validateWitness(version, data) {
|
|
1415
|
+
if (data.length < 2 || data.length > 40)
|
|
1416
|
+
throw new Error('Witness: invalid length');
|
|
1417
|
+
if (version > 16)
|
|
1418
|
+
throw new Error('Witness: invalid version');
|
|
1419
|
+
if (version === 0 && !(data.length === 20 || data.length === 32))
|
|
1420
|
+
throw new Error('Witness: invalid length for version');
|
|
1421
|
+
}
|
|
1422
|
+
export function programToWitness(version, data, network = NETWORK) {
|
|
1423
|
+
validateWitness(version, data);
|
|
1424
|
+
const coder = version === 0 ? bech32 : bech32m;
|
|
1425
|
+
return coder.encode(network.bech32, [version].concat(coder.toWords(data)));
|
|
1426
|
+
}
|
|
1427
|
+
function formatKey(hashed, prefix) {
|
|
1428
|
+
return base58check.encode(concat(Uint8Array.from(prefix), hashed));
|
|
1429
|
+
}
|
|
1430
|
+
export function WIF(network = NETWORK) {
|
|
1431
|
+
return {
|
|
1432
|
+
encode(privKey) {
|
|
1433
|
+
const compressed = concat(privKey, new Uint8Array([0x01]));
|
|
1434
|
+
return formatKey(compressed.subarray(0, 33), [network.wif]);
|
|
1435
|
+
},
|
|
1436
|
+
decode(wif) {
|
|
1437
|
+
let parsed = base58check.decode(wif);
|
|
1438
|
+
if (parsed[0] !== network.wif)
|
|
1439
|
+
throw new Error('Wrong WIF prefix');
|
|
1440
|
+
parsed = parsed.subarray(1);
|
|
1441
|
+
// Check what it is. Compressed flag?
|
|
1442
|
+
if (parsed.length !== 33)
|
|
1443
|
+
throw new Error('Wrong WIF length');
|
|
1444
|
+
if (parsed[32] !== 0x01)
|
|
1445
|
+
throw new Error('Wrong WIF postfix');
|
|
1446
|
+
return parsed.subarray(0, -1);
|
|
1447
|
+
},
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
// Returns OutType, which can be used to create outscript
|
|
1451
|
+
export function Address(network = NETWORK) {
|
|
1452
|
+
return {
|
|
1453
|
+
encode(from) {
|
|
1454
|
+
const { type } = from;
|
|
1455
|
+
if (type === 'wpkh')
|
|
1456
|
+
return programToWitness(0, from.hash, network);
|
|
1457
|
+
else if (type === 'wsh')
|
|
1458
|
+
return programToWitness(0, from.hash, network);
|
|
1459
|
+
else if (type === 'tr')
|
|
1460
|
+
return programToWitness(1, from.pubkey, network);
|
|
1461
|
+
else if (type === 'pkh')
|
|
1462
|
+
return formatKey(from.hash, [network.pubKeyHash]);
|
|
1463
|
+
else if (type === 'sh')
|
|
1464
|
+
return formatKey(from.hash, [network.scriptHash]);
|
|
1465
|
+
throw new Error(`Unknown address type=${type}`);
|
|
1466
|
+
},
|
|
1467
|
+
decode(address) {
|
|
1468
|
+
if (address.length < 14 || address.length > 74)
|
|
1469
|
+
throw new Error('Invalid address length');
|
|
1470
|
+
// Bech32
|
|
1471
|
+
if (network.bech32 && address.toLowerCase().startsWith(network.bech32)) {
|
|
1472
|
+
let res;
|
|
1473
|
+
try {
|
|
1474
|
+
res = bech32.decode(address);
|
|
1475
|
+
if (res.words[0] !== 0)
|
|
1476
|
+
throw new Error(`bech32: wrong version=${res.words[0]}`);
|
|
1477
|
+
}
|
|
1478
|
+
catch (_) {
|
|
1479
|
+
// Starting from version 1 it is decoded as bech32m
|
|
1480
|
+
res = bech32m.decode(address);
|
|
1481
|
+
if (res.words[0] === 0)
|
|
1482
|
+
throw new Error(`bech32m: wrong version=${res.words[0]}`);
|
|
1483
|
+
}
|
|
1484
|
+
if (res.prefix !== network.bech32)
|
|
1485
|
+
throw new Error(`wrong bech32 prefix=${res.prefix}`);
|
|
1486
|
+
const [version, ...program] = res.words;
|
|
1487
|
+
const data = bech32.fromWords(program);
|
|
1488
|
+
validateWitness(version, data);
|
|
1489
|
+
if (version === 0 && data.length === 32)
|
|
1490
|
+
return { type: 'wsh', hash: data };
|
|
1491
|
+
else if (version === 0 && data.length === 20)
|
|
1492
|
+
return { type: 'wpkh', hash: data };
|
|
1493
|
+
else if (version === 1 && data.length === 32)
|
|
1494
|
+
return { type: 'tr', pubkey: data };
|
|
1495
|
+
else
|
|
1496
|
+
throw new Error('Unkown witness program');
|
|
1497
|
+
}
|
|
1498
|
+
const data = base58.decode(address);
|
|
1499
|
+
if (data.length !== 25)
|
|
1500
|
+
throw new Error('Invalid base58 address');
|
|
1501
|
+
// Pay To Public Key Hash
|
|
1502
|
+
if (data[0] === network.pubKeyHash) {
|
|
1503
|
+
const bytes = base58.decode(address);
|
|
1504
|
+
return { type: 'pkh', hash: bytes.slice(1, bytes.length - 4) };
|
|
1505
|
+
}
|
|
1506
|
+
else if (data[0] === network.scriptHash) {
|
|
1507
|
+
const bytes = base58.decode(address);
|
|
1508
|
+
return {
|
|
1509
|
+
type: 'sh',
|
|
1510
|
+
hash: base58.decode(address).slice(1, bytes.length - 4),
|
|
1511
|
+
};
|
|
1512
|
+
}
|
|
1513
|
+
throw new Error(`Invalid address prefix=${data[0]}`);
|
|
1514
|
+
},
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
// /Address
|
|
1518
|
+
export var SignatureHash;
|
|
1519
|
+
(function (SignatureHash) {
|
|
1520
|
+
SignatureHash[SignatureHash["DEFAULT"] = 0] = "DEFAULT";
|
|
1521
|
+
SignatureHash[SignatureHash["ALL"] = 1] = "ALL";
|
|
1522
|
+
SignatureHash[SignatureHash["NONE"] = 2] = "NONE";
|
|
1523
|
+
SignatureHash[SignatureHash["SINGLE"] = 3] = "SINGLE";
|
|
1524
|
+
SignatureHash[SignatureHash["ANYONECANPAY"] = 128] = "ANYONECANPAY";
|
|
1525
|
+
})(SignatureHash || (SignatureHash = {}));
|
|
1526
|
+
export const SigHashCoder = P.apply(P.U32LE, P.coders.tsEnum(SignatureHash));
|
|
1527
|
+
function unpackSighash(hashType) {
|
|
1528
|
+
const masked = hashType & 0b0011111;
|
|
1529
|
+
return {
|
|
1530
|
+
isAny: !!(hashType & SignatureHash.ANYONECANPAY),
|
|
1531
|
+
isNone: masked === SignatureHash.NONE,
|
|
1532
|
+
isSingle: masked === SignatureHash.SINGLE,
|
|
1533
|
+
};
|
|
1534
|
+
}
|
|
1535
|
+
export const _sortPubkeys = (pubkeys) => Array.from(pubkeys).sort(_cmpBytes);
|
|
1536
|
+
// Force check index/txid/sequence
|
|
1537
|
+
function inputBeforeSign(i) {
|
|
1538
|
+
if (i.txid === undefined || i.index === undefined)
|
|
1539
|
+
throw new Error('Transaction/input: txid and index required');
|
|
1540
|
+
return {
|
|
1541
|
+
txid: i.txid,
|
|
1542
|
+
index: i.index,
|
|
1543
|
+
sequence: def(i.sequence, DEFAULT_SEQUENCE),
|
|
1544
|
+
finalScriptSig: def(i.finalScriptSig, P.EMPTY),
|
|
1545
|
+
};
|
|
1546
|
+
}
|
|
1547
|
+
function cleanFinalInput(i) {
|
|
1548
|
+
for (const _k in i) {
|
|
1549
|
+
const k = _k;
|
|
1550
|
+
if (!PSBTInputFinalKeys.includes(k))
|
|
1551
|
+
delete i[k];
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
// Force check amount/script
|
|
1555
|
+
function outputBeforeSign(i) {
|
|
1556
|
+
if (i.script === undefined || i.amount === undefined)
|
|
1557
|
+
throw new Error('Transaction/output: script and amount required');
|
|
1558
|
+
return { script: i.script, amount: i.amount };
|
|
1559
|
+
}
|
|
1560
|
+
export const TAP_LEAF_VERSION = 0xc0;
|
|
1561
|
+
export const tapLeafHash = (script, version = TAP_LEAF_VERSION) => schnorr.utils.taggedHash('TapLeaf', new Uint8Array([version]), VarBytes.encode(script));
|
|
1562
|
+
function getTaprootKeys(privKey, pubKey, internalKey, merkleRoot = P.EMPTY) {
|
|
1563
|
+
if (P.equalBytes(internalKey, pubKey)) {
|
|
1564
|
+
privKey = taprootTweakPrivKey(privKey, merkleRoot);
|
|
1565
|
+
pubKey = schnorr.getPublicKey(privKey);
|
|
1566
|
+
}
|
|
1567
|
+
return { privKey, pubKey };
|
|
1568
|
+
}
|
|
1569
|
+
// Check if object doens't have custom constructor (like Uint8Array/Array)
|
|
1570
|
+
const isPlainObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]' && obj.constructor === Object;
|
|
1571
|
+
function validateOpts(opts) {
|
|
1572
|
+
if (!isPlainObject(opts))
|
|
1573
|
+
throw new Error(`Wrong object type for transaction options: ${opts}`);
|
|
1574
|
+
const _opts = {
|
|
1575
|
+
...opts,
|
|
1576
|
+
version: def(opts.version, DEFAULT_VERSION),
|
|
1577
|
+
lockTime: def(opts.lockTime, 0),
|
|
1578
|
+
PSBTVersion: def(opts.PSBTVersion, 0),
|
|
1579
|
+
}; // Defaults
|
|
1580
|
+
// 0 and -1 happens in tests
|
|
1581
|
+
if (![-1, 0, 1, 2].includes(_opts.version))
|
|
1582
|
+
throw new Error(`Unknown version: ${_opts.version}`);
|
|
1583
|
+
if (typeof _opts.lockTime !== 'number')
|
|
1584
|
+
throw new Error('Transaction lock time should be number');
|
|
1585
|
+
P.U32LE.encode(_opts.lockTime); // Additional range checks that lockTime
|
|
1586
|
+
// There is no PSBT v1, and any new version will probably have fields which we don't know how to parse, which
|
|
1587
|
+
// can lead to constructing broken transactions
|
|
1588
|
+
if (_opts.PSBTVersion !== 0 && _opts.PSBTVersion !== 2)
|
|
1589
|
+
throw new Error(`Unknown PSBT version ${_opts.PSBTVersion}`);
|
|
1590
|
+
// Flags
|
|
1591
|
+
for (const k of [
|
|
1592
|
+
'allowUnknowOutput',
|
|
1593
|
+
'allowUnknowInput',
|
|
1594
|
+
'disableScriptCheck',
|
|
1595
|
+
'bip174jsCompat',
|
|
1596
|
+
'allowLegacyWitnessUtxo',
|
|
1597
|
+
'lowR',
|
|
1598
|
+
]) {
|
|
1599
|
+
const v = _opts[k];
|
|
1600
|
+
if (v === undefined)
|
|
1601
|
+
continue; // optional
|
|
1602
|
+
if (typeof v !== 'boolean')
|
|
1603
|
+
throw new Error(`Transation options wrong type: ${k}=${v} (${typeof v})`);
|
|
1604
|
+
}
|
|
1605
|
+
return Object.freeze(_opts);
|
|
1606
|
+
}
|
|
1607
|
+
export class Transaction {
|
|
1608
|
+
constructor(opts = {}) {
|
|
1609
|
+
this.global = {};
|
|
1610
|
+
this.inputs = [];
|
|
1611
|
+
this.outputs = [];
|
|
1612
|
+
const _opts = (this.opts = validateOpts(opts));
|
|
1613
|
+
// Merge with global structure of PSBTv2
|
|
1614
|
+
if (_opts.lockTime !== DEFAULT_LOCKTIME)
|
|
1615
|
+
this.global.fallbackLocktime = _opts.lockTime;
|
|
1616
|
+
this.global.txVersion = _opts.version;
|
|
1617
|
+
}
|
|
1618
|
+
// Import
|
|
1619
|
+
static fromRaw(raw, opts = {}) {
|
|
1620
|
+
const parsed = RawTx.decode(raw);
|
|
1621
|
+
const tx = new Transaction({ ...opts, version: parsed.version, lockTime: parsed.lockTime });
|
|
1622
|
+
for (const o of parsed.outputs)
|
|
1623
|
+
tx.addOutput(o);
|
|
1624
|
+
tx.outputs = parsed.outputs;
|
|
1625
|
+
tx.inputs = parsed.inputs;
|
|
1626
|
+
if (parsed.witnesses) {
|
|
1627
|
+
for (let i = 0; i < parsed.witnesses.length; i++)
|
|
1628
|
+
tx.inputs[i].finalScriptWitness = parsed.witnesses[i];
|
|
1629
|
+
}
|
|
1630
|
+
return tx;
|
|
1631
|
+
}
|
|
1632
|
+
// PSBT
|
|
1633
|
+
static fromPSBT(psbt, opts = {}) {
|
|
1634
|
+
let parsed;
|
|
1635
|
+
try {
|
|
1636
|
+
parsed = RawPSBTV0.decode(psbt);
|
|
1637
|
+
}
|
|
1638
|
+
catch (e0) {
|
|
1639
|
+
try {
|
|
1640
|
+
parsed = RawPSBTV2.decode(psbt);
|
|
1641
|
+
}
|
|
1642
|
+
catch (e2) {
|
|
1643
|
+
// Throw error for v0 parsing, since it popular, otherwise it would be shadowed by v2 error
|
|
1644
|
+
throw e0;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
const PSBTVersion = parsed.global.version || 0;
|
|
1648
|
+
if (PSBTVersion !== 0 && PSBTVersion !== 2)
|
|
1649
|
+
throw new Error(`Wrong PSBT version=${PSBTVersion}`);
|
|
1650
|
+
const unsigned = parsed.global.unsignedTx;
|
|
1651
|
+
const version = PSBTVersion === 0 ? unsigned?.version : parsed.global.txVersion;
|
|
1652
|
+
const lockTime = PSBTVersion === 0 ? unsigned?.lockTime : parsed.global.fallbackLocktime;
|
|
1653
|
+
const tx = new Transaction({ ...opts, version, lockTime, PSBTVersion });
|
|
1654
|
+
// We need slice here, because otherwise
|
|
1655
|
+
const inputCount = PSBTVersion === 0 ? unsigned?.inputs.length : parsed.global.inputCount;
|
|
1656
|
+
tx.inputs = parsed.inputs.slice(0, inputCount).map((i, j) => ({
|
|
1657
|
+
finalScriptSig: P.EMPTY,
|
|
1658
|
+
...parsed.global.unsignedTx?.inputs[j],
|
|
1659
|
+
...i,
|
|
1660
|
+
}));
|
|
1661
|
+
const outputCount = PSBTVersion === 0 ? unsigned?.outputs.length : parsed.global.outputCount;
|
|
1662
|
+
tx.outputs = parsed.outputs.slice(0, outputCount).map((i, j) => ({
|
|
1663
|
+
...i,
|
|
1664
|
+
...parsed.global.unsignedTx?.outputs[j],
|
|
1665
|
+
}));
|
|
1666
|
+
tx.global = { ...parsed.global, txVersion: version }; // just in case propietary/unknown fields
|
|
1667
|
+
if (lockTime !== DEFAULT_LOCKTIME)
|
|
1668
|
+
tx.global.fallbackLocktime = lockTime;
|
|
1669
|
+
return tx;
|
|
1670
|
+
}
|
|
1671
|
+
toPSBT(PSBTVersion = this.opts.PSBTVersion) {
|
|
1672
|
+
if (PSBTVersion !== 0 && PSBTVersion !== 2)
|
|
1673
|
+
throw new Error(`Wrong PSBT version=${PSBTVersion}`);
|
|
1674
|
+
const inputs = this.inputs.map((i) => cleanPSBTFields(PSBTVersion, PSBTInput, i));
|
|
1675
|
+
for (const inp of inputs) {
|
|
1676
|
+
// Don't serialize empty fields
|
|
1677
|
+
if (inp.partialSig && !inp.partialSig.length)
|
|
1678
|
+
delete inp.partialSig;
|
|
1679
|
+
if (inp.finalScriptSig && !inp.finalScriptSig.length)
|
|
1680
|
+
delete inp.finalScriptSig;
|
|
1681
|
+
if (inp.finalScriptWitness && !inp.finalScriptWitness.length)
|
|
1682
|
+
delete inp.finalScriptWitness;
|
|
1683
|
+
}
|
|
1684
|
+
const outputs = this.outputs.map((i) => cleanPSBTFields(PSBTVersion, PSBTOutput, i));
|
|
1685
|
+
const global = { ...this.global };
|
|
1686
|
+
if (PSBTVersion === 0) {
|
|
1687
|
+
global.unsignedTx = RawTx.decode(this.unsignedTx);
|
|
1688
|
+
delete global.fallbackLocktime;
|
|
1689
|
+
delete global.txVersion;
|
|
1690
|
+
}
|
|
1691
|
+
else {
|
|
1692
|
+
global.version = PSBTVersion;
|
|
1693
|
+
global.txVersion = this.version;
|
|
1694
|
+
global.inputCount = this.inputs.length;
|
|
1695
|
+
global.outputCount = this.outputs.length;
|
|
1696
|
+
if (global.fallbackLocktime && global.fallbackLocktime === DEFAULT_LOCKTIME)
|
|
1697
|
+
delete global.fallbackLocktime;
|
|
1698
|
+
}
|
|
1699
|
+
if (this.opts.bip174jsCompat) {
|
|
1700
|
+
if (!inputs.length)
|
|
1701
|
+
inputs.push({});
|
|
1702
|
+
if (!outputs.length)
|
|
1703
|
+
outputs.push({});
|
|
1704
|
+
}
|
|
1705
|
+
return (PSBTVersion === 0 ? RawPSBTV0 : RawPSBTV2).encode({
|
|
1706
|
+
global,
|
|
1707
|
+
inputs,
|
|
1708
|
+
outputs,
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
// BIP370 lockTime (https://github.com/bitcoin/bips/blob/master/bip-0370.mediawiki#determining-lock-time)
|
|
1712
|
+
get lockTime() {
|
|
1713
|
+
let height = DEFAULT_LOCKTIME;
|
|
1714
|
+
let heightCnt = 0;
|
|
1715
|
+
let time = DEFAULT_LOCKTIME;
|
|
1716
|
+
let timeCnt = 0;
|
|
1717
|
+
for (const i of this.inputs) {
|
|
1718
|
+
if (i.requiredHeightLocktime) {
|
|
1719
|
+
height = Math.max(height, i.requiredHeightLocktime);
|
|
1720
|
+
heightCnt++;
|
|
1721
|
+
}
|
|
1722
|
+
if (i.requiredTimeLocktime) {
|
|
1723
|
+
time = Math.max(time, i.requiredTimeLocktime);
|
|
1724
|
+
timeCnt++;
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
if (heightCnt && heightCnt >= timeCnt)
|
|
1728
|
+
return height;
|
|
1729
|
+
if (time !== DEFAULT_LOCKTIME)
|
|
1730
|
+
return time;
|
|
1731
|
+
return this.global.fallbackLocktime || DEFAULT_LOCKTIME;
|
|
1732
|
+
}
|
|
1733
|
+
get version() {
|
|
1734
|
+
// Should be not possible
|
|
1735
|
+
if (this.global.txVersion === undefined)
|
|
1736
|
+
throw new Error('No global.txVersion');
|
|
1737
|
+
return this.global.txVersion;
|
|
1738
|
+
}
|
|
1739
|
+
inputStatus(idx) {
|
|
1740
|
+
this.checkInputIdx(idx);
|
|
1741
|
+
const input = this.inputs[idx];
|
|
1742
|
+
// Finalized
|
|
1743
|
+
if (input.finalScriptSig && input.finalScriptSig.length)
|
|
1744
|
+
return 'finalized';
|
|
1745
|
+
if (input.finalScriptWitness && input.finalScriptWitness.length)
|
|
1746
|
+
return 'finalized';
|
|
1747
|
+
// Signed taproot
|
|
1748
|
+
if (input.tapKeySig)
|
|
1749
|
+
return 'signed';
|
|
1750
|
+
if (input.tapScriptSig && input.tapScriptSig.length)
|
|
1751
|
+
return 'signed';
|
|
1752
|
+
// Signed
|
|
1753
|
+
if (input.partialSig && input.partialSig.length)
|
|
1754
|
+
return 'signed';
|
|
1755
|
+
return 'unsigned';
|
|
1756
|
+
}
|
|
1757
|
+
// Cannot replace unpackSighash, tests rely on very generic implemenetation with signing inputs outside of range
|
|
1758
|
+
// We will lose some vectors -> smaller test coverage of preimages (very important!)
|
|
1759
|
+
inputSighash(idx) {
|
|
1760
|
+
this.checkInputIdx(idx);
|
|
1761
|
+
const sighash = this.inputType(this.inputs[idx]).sighash;
|
|
1762
|
+
// ALL or DEFAULT -- everything signed
|
|
1763
|
+
// NONE -- all inputs + no outputs
|
|
1764
|
+
// SINGLE -- all inputs + output with same index
|
|
1765
|
+
// ALL + ANYONE -- specific input + all outputs
|
|
1766
|
+
// NONE + ANYONE -- specific input + no outputs
|
|
1767
|
+
// SINGLE -- specific inputs + output with same index
|
|
1768
|
+
const sigOutputs = sighash === SignatureHash.DEFAULT ? SignatureHash.ALL : sighash & 0b11;
|
|
1769
|
+
const sigInputs = sighash & SignatureHash.ANYONECANPAY;
|
|
1770
|
+
return { sigInputs, sigOutputs };
|
|
1771
|
+
}
|
|
1772
|
+
// Very nice for debug purposes, but slow. If there is too much inputs/outputs to add, will be quadratic.
|
|
1773
|
+
// Some cache will be nice, but there chance to have bugs with cache invalidation
|
|
1774
|
+
signStatus() {
|
|
1775
|
+
// if addInput or addOutput is not possible, then all inputs or outputs are signed
|
|
1776
|
+
let addInput = true, addOutput = true;
|
|
1777
|
+
let inputs = [], outputs = [];
|
|
1778
|
+
for (let idx = 0; idx < this.inputs.length; idx++) {
|
|
1779
|
+
const status = this.inputStatus(idx);
|
|
1780
|
+
// Unsigned input doesn't affect anything
|
|
1781
|
+
if (status === 'unsigned')
|
|
1782
|
+
continue;
|
|
1783
|
+
const { sigInputs, sigOutputs } = this.inputSighash(idx);
|
|
1784
|
+
// Input type
|
|
1785
|
+
if (sigInputs === SignatureHash.ANYONECANPAY)
|
|
1786
|
+
inputs.push(idx);
|
|
1787
|
+
else
|
|
1788
|
+
addInput = false;
|
|
1789
|
+
// Output type
|
|
1790
|
+
if (sigOutputs === SignatureHash.ALL)
|
|
1791
|
+
addOutput = false;
|
|
1792
|
+
else if (sigOutputs === SignatureHash.SINGLE)
|
|
1793
|
+
outputs.push(idx);
|
|
1794
|
+
else if (sigOutputs === SignatureHash.NONE) {
|
|
1795
|
+
// Doesn't affect any outputs at all
|
|
1796
|
+
}
|
|
1797
|
+
else
|
|
1798
|
+
throw new Error(`Wrong signature hash output type: ${sigOutputs}`);
|
|
1799
|
+
}
|
|
1800
|
+
return { addInput, addOutput, inputs, outputs };
|
|
1801
|
+
}
|
|
1802
|
+
get isFinal() {
|
|
1803
|
+
for (let idx = 0; idx < this.inputs.length; idx++)
|
|
1804
|
+
if (this.inputStatus(idx) !== 'finalized')
|
|
1805
|
+
return false;
|
|
1806
|
+
return true;
|
|
1807
|
+
}
|
|
1808
|
+
// Info utils
|
|
1809
|
+
get hasWitnesses() {
|
|
1810
|
+
let out = false;
|
|
1811
|
+
for (const i of this.inputs)
|
|
1812
|
+
if (i.finalScriptWitness && i.finalScriptWitness.length)
|
|
1813
|
+
out = true;
|
|
1814
|
+
return out;
|
|
1815
|
+
}
|
|
1816
|
+
// https://en.bitcoin.it/wiki/Weight_units
|
|
1817
|
+
get weight() {
|
|
1818
|
+
if (!this.isFinal)
|
|
1819
|
+
throw new Error('Transaction is not finalized');
|
|
1820
|
+
// TODO: Can we find out how much witnesses/script will be used before signing?
|
|
1821
|
+
let out = 32;
|
|
1822
|
+
const outputs = this.outputs.map(outputBeforeSign);
|
|
1823
|
+
if (this.hasWitnesses)
|
|
1824
|
+
out += 2;
|
|
1825
|
+
out += 4 * CompactSizeLen.encode(this.inputs.length).length;
|
|
1826
|
+
out += 4 * CompactSizeLen.encode(this.outputs.length).length;
|
|
1827
|
+
for (const i of this.inputs)
|
|
1828
|
+
if (i.finalScriptSig)
|
|
1829
|
+
out += 160 + 4 * VarBytes.encode(i.finalScriptSig).length;
|
|
1830
|
+
for (const o of outputs)
|
|
1831
|
+
out += 32 + 4 * VarBytes.encode(o.script).length;
|
|
1832
|
+
if (this.hasWitnesses) {
|
|
1833
|
+
for (const i of this.inputs)
|
|
1834
|
+
if (i.finalScriptWitness)
|
|
1835
|
+
out += RawWitness.encode(i.finalScriptWitness).length;
|
|
1836
|
+
}
|
|
1837
|
+
return out;
|
|
1838
|
+
}
|
|
1839
|
+
get vsize() {
|
|
1840
|
+
return Math.ceil(this.weight / 4);
|
|
1841
|
+
}
|
|
1842
|
+
toBytes(withScriptSig = false, withWitness = false) {
|
|
1843
|
+
return RawTx.encode({
|
|
1844
|
+
version: this.version,
|
|
1845
|
+
lockTime: this.lockTime,
|
|
1846
|
+
inputs: this.inputs.map(inputBeforeSign).map((i) => ({
|
|
1847
|
+
...i,
|
|
1848
|
+
finalScriptSig: (withScriptSig && i.finalScriptSig) || P.EMPTY,
|
|
1849
|
+
})),
|
|
1850
|
+
outputs: this.outputs.map(outputBeforeSign),
|
|
1851
|
+
witnesses: this.inputs.map((i) => i.finalScriptWitness || []),
|
|
1852
|
+
segwitFlag: withWitness && this.hasWitnesses,
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
get unsignedTx() {
|
|
1856
|
+
return this.toBytes(false, false);
|
|
1857
|
+
}
|
|
1858
|
+
get hex() {
|
|
1859
|
+
return hex.encode(this.toBytes(true, this.hasWitnesses));
|
|
1860
|
+
}
|
|
1861
|
+
get hash() {
|
|
1862
|
+
if (!this.isFinal)
|
|
1863
|
+
throw new Error('Transaction is not finalized');
|
|
1864
|
+
return hex.encode(sha256x2(this.toBytes(true)));
|
|
1865
|
+
}
|
|
1866
|
+
get id() {
|
|
1867
|
+
if (!this.isFinal)
|
|
1868
|
+
throw new Error('Transaction is not finalized');
|
|
1869
|
+
return hex.encode(sha256x2(this.toBytes(true)).reverse());
|
|
1870
|
+
}
|
|
1871
|
+
// Input stuff
|
|
1872
|
+
checkInputIdx(idx) {
|
|
1873
|
+
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.inputs.length)
|
|
1874
|
+
throw new Error(`Wrong input index=${idx}`);
|
|
1875
|
+
}
|
|
1876
|
+
// Modification
|
|
1877
|
+
normalizeInput(i, cur, allowedFields) {
|
|
1878
|
+
let { nonWitnessUtxo, txid } = i;
|
|
1879
|
+
// String support for common fields. We usually prefer Uint8Array to avoid errors (like hex looking string accidentally passed),
|
|
1880
|
+
// however in case of nonWitnessUtxo it is better to expect string, since constructing this complex object will be difficult for user
|
|
1881
|
+
if (typeof nonWitnessUtxo === 'string')
|
|
1882
|
+
nonWitnessUtxo = hex.decode(nonWitnessUtxo);
|
|
1883
|
+
if (isBytes(nonWitnessUtxo))
|
|
1884
|
+
nonWitnessUtxo = RawTx.decode(nonWitnessUtxo);
|
|
1885
|
+
if (nonWitnessUtxo === undefined)
|
|
1886
|
+
nonWitnessUtxo = cur?.nonWitnessUtxo;
|
|
1887
|
+
if (typeof txid === 'string')
|
|
1888
|
+
txid = hex.decode(txid);
|
|
1889
|
+
if (txid === undefined)
|
|
1890
|
+
txid = cur?.txid;
|
|
1891
|
+
let res = { ...cur, ...i, nonWitnessUtxo, txid };
|
|
1892
|
+
if (res.nonWitnessUtxo === undefined)
|
|
1893
|
+
delete res.nonWitnessUtxo;
|
|
1894
|
+
if (res.sequence === undefined)
|
|
1895
|
+
res.sequence = DEFAULT_SEQUENCE;
|
|
1896
|
+
if (res.tapMerkleRoot === null)
|
|
1897
|
+
delete res.tapMerkleRoot;
|
|
1898
|
+
res = mergeKeyMap(PSBTInput, res, cur, allowedFields);
|
|
1899
|
+
PSBTInputCoder.encode(res); // Validates that everything is correct at this point
|
|
1900
|
+
let prevOut;
|
|
1901
|
+
if (res.nonWitnessUtxo && res.index !== undefined)
|
|
1902
|
+
prevOut = res.nonWitnessUtxo.outputs[res.index];
|
|
1903
|
+
else if (res.witnessUtxo)
|
|
1904
|
+
prevOut = res.witnessUtxo;
|
|
1905
|
+
if (prevOut && !this.opts.disableScriptCheck)
|
|
1906
|
+
checkScript(prevOut && prevOut.script, res.redeemScript, res.witnessScript);
|
|
1907
|
+
return res;
|
|
1908
|
+
}
|
|
1909
|
+
addInput(input, _ignoreSignStatus = false) {
|
|
1910
|
+
if (!_ignoreSignStatus && !this.signStatus().addInput)
|
|
1911
|
+
throw new Error('Tx has signed inputs, cannot add new one');
|
|
1912
|
+
this.inputs.push(this.normalizeInput(input));
|
|
1913
|
+
return this.inputs.length - 1;
|
|
1914
|
+
}
|
|
1915
|
+
updateInput(idx, input, _ignoreSignStatus = false) {
|
|
1916
|
+
this.checkInputIdx(idx);
|
|
1917
|
+
let allowedFields = undefined;
|
|
1918
|
+
if (!_ignoreSignStatus) {
|
|
1919
|
+
const status = this.signStatus();
|
|
1920
|
+
if (!status.addInput || status.inputs.includes(idx))
|
|
1921
|
+
allowedFields = PSBTInputUnsignedKeys;
|
|
1922
|
+
}
|
|
1923
|
+
this.inputs[idx] = this.normalizeInput(input, this.inputs[idx], allowedFields);
|
|
1924
|
+
}
|
|
1925
|
+
// Output stuff
|
|
1926
|
+
checkOutputIdx(idx) {
|
|
1927
|
+
if (!Number.isSafeInteger(idx) || 0 > idx || idx >= this.outputs.length)
|
|
1928
|
+
throw new Error(`Wrong output index=${idx}`);
|
|
1929
|
+
}
|
|
1930
|
+
normalizeOutput(o, cur, allowedFields) {
|
|
1931
|
+
let { amount, script } = o;
|
|
1932
|
+
if (amount === undefined)
|
|
1933
|
+
amount = cur?.amount;
|
|
1934
|
+
if (typeof amount !== 'bigint')
|
|
1935
|
+
throw new Error('amount must be bigint sats');
|
|
1936
|
+
if (typeof script === 'string')
|
|
1937
|
+
script = hex.decode(script);
|
|
1938
|
+
if (script === undefined)
|
|
1939
|
+
script = cur?.script;
|
|
1940
|
+
let res = { ...cur, ...o, amount, script };
|
|
1941
|
+
if (res.amount === undefined)
|
|
1942
|
+
delete res.amount;
|
|
1943
|
+
res = mergeKeyMap(PSBTOutput, res, cur, allowedFields);
|
|
1944
|
+
PSBTOutputCoder.encode(res);
|
|
1945
|
+
if (res.script &&
|
|
1946
|
+
!this.opts.allowUnknowOutput &&
|
|
1947
|
+
OutScript.decode(res.script).type === 'unknown') {
|
|
1948
|
+
throw new Error('Transaction/output: unknown output script type, there is a chance that input is unspendable. Pass allowUnkownScript=true, if you sure');
|
|
1949
|
+
}
|
|
1950
|
+
if (!this.opts.disableScriptCheck)
|
|
1951
|
+
checkScript(res.script, res.redeemScript, res.witnessScript);
|
|
1952
|
+
return res;
|
|
1953
|
+
}
|
|
1954
|
+
addOutput(o, _ignoreSignStatus = false) {
|
|
1955
|
+
if (!_ignoreSignStatus && !this.signStatus().addOutput)
|
|
1956
|
+
throw new Error('Tx has signed outputs, cannot add new one');
|
|
1957
|
+
this.outputs.push(this.normalizeOutput(o));
|
|
1958
|
+
return this.outputs.length - 1;
|
|
1959
|
+
}
|
|
1960
|
+
updateOutput(idx, output, _ignoreSignStatus = false) {
|
|
1961
|
+
this.checkOutputIdx(idx);
|
|
1962
|
+
let allowedFields = undefined;
|
|
1963
|
+
if (!_ignoreSignStatus) {
|
|
1964
|
+
const status = this.signStatus();
|
|
1965
|
+
if (!status.addOutput || status.outputs.includes(idx))
|
|
1966
|
+
allowedFields = PSBTOutputUnsignedKeys;
|
|
1967
|
+
}
|
|
1968
|
+
this.outputs[idx] = this.normalizeOutput(output, this.outputs[idx], allowedFields);
|
|
1969
|
+
}
|
|
1970
|
+
addOutputAddress(address, amount, network = NETWORK) {
|
|
1971
|
+
return this.addOutput({ script: OutScript.encode(Address(network).decode(address)), amount });
|
|
1972
|
+
}
|
|
1973
|
+
// Utils
|
|
1974
|
+
get fee() {
|
|
1975
|
+
let res = 0n;
|
|
1976
|
+
for (const i of this.inputs) {
|
|
1977
|
+
const prevOut = this.prevOut(i);
|
|
1978
|
+
if (!prevOut)
|
|
1979
|
+
throw new Error('Empty input amount');
|
|
1980
|
+
res += prevOut.amount;
|
|
1981
|
+
}
|
|
1982
|
+
const outputs = this.outputs.map(outputBeforeSign);
|
|
1983
|
+
for (const o of outputs)
|
|
1984
|
+
res -= o.amount;
|
|
1985
|
+
return res;
|
|
1986
|
+
}
|
|
1987
|
+
// Signing
|
|
1988
|
+
// Based on https://github.com/bitcoin/bitcoin/blob/5871b5b5ab57a0caf9b7514eb162c491c83281d5/test/functional/test_framework/script.py#L624
|
|
1989
|
+
// There is optimization opportunity to re-use hashes for multiple inputs for witness v0/v1,
|
|
1990
|
+
// but we are trying to be less complicated for audit purpose for now.
|
|
1991
|
+
preimageLegacy(idx, prevOutScript, hashType) {
|
|
1992
|
+
const { isAny, isNone, isSingle } = unpackSighash(hashType);
|
|
1993
|
+
if (idx < 0 || !Number.isSafeInteger(idx))
|
|
1994
|
+
throw new Error(`Invalid input idx=${idx}`);
|
|
1995
|
+
if ((isSingle && idx >= this.outputs.length) || idx >= this.inputs.length)
|
|
1996
|
+
return P.U256BE.encode(1n);
|
|
1997
|
+
prevOutScript = Script.encode(Script.decode(prevOutScript).filter((i) => i !== 'CODESEPARATOR'));
|
|
1998
|
+
let inputs = this.inputs
|
|
1999
|
+
.map(inputBeforeSign)
|
|
2000
|
+
.map((input, inputIdx) => ({
|
|
2001
|
+
...input,
|
|
2002
|
+
finalScriptSig: inputIdx === idx ? prevOutScript : P.EMPTY,
|
|
2003
|
+
}));
|
|
2004
|
+
if (isAny)
|
|
2005
|
+
inputs = [inputs[idx]];
|
|
2006
|
+
else if (isNone || isSingle) {
|
|
2007
|
+
inputs = inputs.map((input, inputIdx) => ({
|
|
2008
|
+
...input,
|
|
2009
|
+
sequence: inputIdx === idx ? input.sequence : 0,
|
|
2010
|
+
}));
|
|
2011
|
+
}
|
|
2012
|
+
let outputs = this.outputs.map(outputBeforeSign);
|
|
2013
|
+
if (isNone)
|
|
2014
|
+
outputs = [];
|
|
2015
|
+
else if (isSingle) {
|
|
2016
|
+
outputs = outputs.slice(0, idx).fill(EMPTY_OUTPUT).concat([outputs[idx]]);
|
|
2017
|
+
}
|
|
2018
|
+
const tmpTx = RawTx.encode({
|
|
2019
|
+
lockTime: this.lockTime,
|
|
2020
|
+
version: this.version,
|
|
2021
|
+
segwitFlag: false,
|
|
2022
|
+
inputs,
|
|
2023
|
+
outputs,
|
|
2024
|
+
});
|
|
2025
|
+
return sha256x2(tmpTx, P.I32LE.encode(hashType));
|
|
2026
|
+
}
|
|
2027
|
+
preimageWitnessV0(idx, prevOutScript, hashType, amount) {
|
|
2028
|
+
const { isAny, isNone, isSingle } = unpackSighash(hashType);
|
|
2029
|
+
let inputHash = EMPTY32;
|
|
2030
|
+
let sequenceHash = EMPTY32;
|
|
2031
|
+
let outputHash = EMPTY32;
|
|
2032
|
+
const inputs = this.inputs.map(inputBeforeSign);
|
|
2033
|
+
const outputs = this.outputs.map(outputBeforeSign);
|
|
2034
|
+
if (!isAny)
|
|
2035
|
+
inputHash = sha256x2(...inputs.map(TxHashIdx.encode));
|
|
2036
|
+
if (!isAny && !isSingle && !isNone)
|
|
2037
|
+
sequenceHash = sha256x2(...inputs.map((i) => P.U32LE.encode(i.sequence)));
|
|
2038
|
+
if (!isSingle && !isNone) {
|
|
2039
|
+
outputHash = sha256x2(...outputs.map(RawOutput.encode));
|
|
2040
|
+
}
|
|
2041
|
+
else if (isSingle && idx < outputs.length)
|
|
2042
|
+
outputHash = sha256x2(RawOutput.encode(outputs[idx]));
|
|
2043
|
+
const input = inputs[idx];
|
|
2044
|
+
return sha256x2(P.I32LE.encode(this.version), inputHash, sequenceHash, P.bytes(32, true).encode(input.txid), P.U32LE.encode(input.index), VarBytes.encode(prevOutScript), P.U64LE.encode(amount), P.U32LE.encode(input.sequence), outputHash, P.U32LE.encode(this.lockTime), P.U32LE.encode(hashType));
|
|
2045
|
+
}
|
|
2046
|
+
preimageWitnessV1(idx, prevOutScript, hashType, amount, codeSeparator = -1, leafScript, leafVer = 0xc0, annex) {
|
|
2047
|
+
if (!Array.isArray(amount) || this.inputs.length !== amount.length)
|
|
2048
|
+
throw new Error(`Invalid amounts array=${amount}`);
|
|
2049
|
+
if (!Array.isArray(prevOutScript) || this.inputs.length !== prevOutScript.length)
|
|
2050
|
+
throw new Error(`Invalid prevOutScript array=${prevOutScript}`);
|
|
2051
|
+
const out = [
|
|
2052
|
+
P.U8.encode(0),
|
|
2053
|
+
P.U8.encode(hashType),
|
|
2054
|
+
P.I32LE.encode(this.version),
|
|
2055
|
+
P.U32LE.encode(this.lockTime),
|
|
2056
|
+
];
|
|
2057
|
+
const outType = hashType === SignatureHash.DEFAULT ? SignatureHash.ALL : hashType & 0b11;
|
|
2058
|
+
const inType = hashType & SignatureHash.ANYONECANPAY;
|
|
2059
|
+
const inputs = this.inputs.map(inputBeforeSign);
|
|
2060
|
+
const outputs = this.outputs.map(outputBeforeSign);
|
|
2061
|
+
if (inType !== SignatureHash.ANYONECANPAY) {
|
|
2062
|
+
out.push(...[
|
|
2063
|
+
inputs.map(TxHashIdx.encode),
|
|
2064
|
+
amount.map(P.U64LE.encode),
|
|
2065
|
+
prevOutScript.map(VarBytes.encode),
|
|
2066
|
+
inputs.map((i) => P.U32LE.encode(i.sequence)),
|
|
2067
|
+
].map((i) => sha256(concat(...i))));
|
|
2068
|
+
}
|
|
2069
|
+
if (outType === SignatureHash.ALL) {
|
|
2070
|
+
out.push(sha256(concat(...outputs.map(RawOutput.encode))));
|
|
2071
|
+
}
|
|
2072
|
+
const spendType = (annex ? 1 : 0) | (leafScript ? 2 : 0);
|
|
2073
|
+
out.push(new Uint8Array([spendType]));
|
|
2074
|
+
if (inType === SignatureHash.ANYONECANPAY) {
|
|
2075
|
+
const inp = inputs[idx];
|
|
2076
|
+
out.push(TxHashIdx.encode(inp), P.U64LE.encode(amount[idx]), VarBytes.encode(prevOutScript[idx]), P.U32LE.encode(inp.sequence));
|
|
2077
|
+
}
|
|
2078
|
+
else
|
|
2079
|
+
out.push(P.U32LE.encode(idx));
|
|
2080
|
+
if (spendType & 1)
|
|
2081
|
+
out.push(sha256(VarBytes.encode(annex || P.EMPTY)));
|
|
2082
|
+
if (outType === SignatureHash.SINGLE)
|
|
2083
|
+
out.push(idx < outputs.length ? sha256(RawOutput.encode(outputs[idx])) : EMPTY32);
|
|
2084
|
+
if (leafScript)
|
|
2085
|
+
out.push(tapLeafHash(leafScript, leafVer), P.U8.encode(0), P.I32LE.encode(codeSeparator));
|
|
2086
|
+
return schnorr.utils.taggedHash('TapSighash', ...out);
|
|
2087
|
+
}
|
|
2088
|
+
// Utils for sign/finalize
|
|
2089
|
+
// Used pretty often, should be fast
|
|
2090
|
+
prevOut(input) {
|
|
2091
|
+
if (input.nonWitnessUtxo) {
|
|
2092
|
+
if (input.index === undefined)
|
|
2093
|
+
throw new Error('Uknown input index');
|
|
2094
|
+
return input.nonWitnessUtxo.outputs[input.index];
|
|
2095
|
+
}
|
|
2096
|
+
else if (input.witnessUtxo)
|
|
2097
|
+
return input.witnessUtxo;
|
|
2098
|
+
else
|
|
2099
|
+
throw new Error('Cannot find previous output info.');
|
|
2100
|
+
}
|
|
2101
|
+
inputType(input) {
|
|
2102
|
+
let txType = 'legacy';
|
|
2103
|
+
let defaultSighash = SignatureHash.ALL;
|
|
2104
|
+
const prevOut = this.prevOut(input);
|
|
2105
|
+
const first = OutScript.decode(prevOut.script);
|
|
2106
|
+
let type = first.type;
|
|
2107
|
+
let cur = first;
|
|
2108
|
+
const stack = [first];
|
|
2109
|
+
if (first.type === 'tr') {
|
|
2110
|
+
defaultSighash = SignatureHash.DEFAULT;
|
|
2111
|
+
return {
|
|
2112
|
+
txType: 'taproot',
|
|
2113
|
+
type: 'tr',
|
|
2114
|
+
last: first,
|
|
2115
|
+
lastScript: prevOut.script,
|
|
2116
|
+
defaultSighash,
|
|
2117
|
+
sighash: input.sighashType || defaultSighash,
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
if (first.type === 'wpkh' || first.type === 'wsh')
|
|
2122
|
+
txType = 'segwit';
|
|
2123
|
+
if (first.type === 'sh') {
|
|
2124
|
+
if (!input.redeemScript)
|
|
2125
|
+
throw new Error('inputType: sh without redeemScript');
|
|
2126
|
+
let child = OutScript.decode(input.redeemScript);
|
|
2127
|
+
if (child.type === 'wpkh' || child.type === 'wsh')
|
|
2128
|
+
txType = 'segwit';
|
|
2129
|
+
stack.push(child);
|
|
2130
|
+
cur = child;
|
|
2131
|
+
type += `-${child.type}`;
|
|
2132
|
+
}
|
|
2133
|
+
// wsh can be inside sh
|
|
2134
|
+
if (cur.type === 'wsh') {
|
|
2135
|
+
if (!input.witnessScript)
|
|
2136
|
+
throw new Error('inputType: wsh without witnessScript');
|
|
2137
|
+
let child = OutScript.decode(input.witnessScript);
|
|
2138
|
+
if (child.type === 'wsh')
|
|
2139
|
+
txType = 'segwit';
|
|
2140
|
+
stack.push(child);
|
|
2141
|
+
cur = child;
|
|
2142
|
+
type += `-${child.type}`;
|
|
2143
|
+
}
|
|
2144
|
+
const last = stack[stack.length - 1];
|
|
2145
|
+
if (last.type === 'sh' || last.type === 'wsh')
|
|
2146
|
+
throw new Error('inputType: sh/wsh cannot be terminal type');
|
|
2147
|
+
const lastScript = OutScript.encode(last);
|
|
2148
|
+
const res = {
|
|
2149
|
+
type,
|
|
2150
|
+
txType,
|
|
2151
|
+
last,
|
|
2152
|
+
lastScript,
|
|
2153
|
+
defaultSighash,
|
|
2154
|
+
sighash: input.sighashType || defaultSighash,
|
|
2155
|
+
};
|
|
2156
|
+
if (txType === 'legacy' && !this.opts.allowLegacyWitnessUtxo && !input.nonWitnessUtxo) {
|
|
2157
|
+
throw new Error(`Transaction/sign: legacy input without nonWitnessUtxo, can result in attack that forces paying higher fees. Pass allowLegacyWitnessUtxo=true, if you sure`);
|
|
2158
|
+
}
|
|
2159
|
+
return res;
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
// Signer can be privateKey OR instance of bip32 HD stuff
|
|
2163
|
+
signIdx(privateKey, idx, allowedSighash, _auxRand) {
|
|
2164
|
+
this.checkInputIdx(idx);
|
|
2165
|
+
const input = this.inputs[idx];
|
|
2166
|
+
const inputType = this.inputType(input);
|
|
2167
|
+
// Handle BIP32 HDKey
|
|
2168
|
+
if (!isBytes(privateKey)) {
|
|
2169
|
+
if (!input.bip32Derivation || !input.bip32Derivation.length)
|
|
2170
|
+
throw new Error('bip32Derivation: empty');
|
|
2171
|
+
const signers = input.bip32Derivation
|
|
2172
|
+
.filter((i) => i[1].fingerprint == privateKey.fingerprint)
|
|
2173
|
+
.map(([pubKey, { path }]) => {
|
|
2174
|
+
let s = privateKey;
|
|
2175
|
+
for (const i of path)
|
|
2176
|
+
s = s.deriveChild(i);
|
|
2177
|
+
if (!P.equalBytes(s.publicKey, pubKey))
|
|
2178
|
+
throw new Error('bip32Derivation: wrong pubKey');
|
|
2179
|
+
if (!s.privateKey)
|
|
2180
|
+
throw new Error('bip32Derivation: no privateKey');
|
|
2181
|
+
return s;
|
|
2182
|
+
});
|
|
2183
|
+
if (!signers.length)
|
|
2184
|
+
throw new Error(`bip32Derivation: no items with fingerprint=${privateKey.fingerprint}`);
|
|
2185
|
+
let signed = false;
|
|
2186
|
+
for (const s of signers)
|
|
2187
|
+
if (this.signIdx(s.privateKey, idx))
|
|
2188
|
+
signed = true;
|
|
2189
|
+
return signed;
|
|
2190
|
+
}
|
|
2191
|
+
// Sighash checks
|
|
2192
|
+
// Just for compat with bitcoinjs-lib, so users won't face unexpected behaviour.
|
|
2193
|
+
if (!allowedSighash)
|
|
2194
|
+
allowedSighash = [inputType.defaultSighash];
|
|
2195
|
+
const sighash = inputType.sighash;
|
|
2196
|
+
if (!allowedSighash.includes(sighash)) {
|
|
2197
|
+
throw new Error(`Input with not allowed sigHash=${sighash}. Allowed: ${allowedSighash.join(', ')}`);
|
|
2198
|
+
}
|
|
2199
|
+
// It is possible to sign these inputs for legacy/segwit v0 (but no taproot!),
|
|
2200
|
+
// however this was because of bug in bitcoin-core, which remains here because of consensus.
|
|
2201
|
+
// If this is absolutely neccessary for your case, please open issue.
|
|
2202
|
+
// We disable it to avoid complicated workflow where SINGLE will block adding new outputs
|
|
2203
|
+
const { sigInputs, sigOutputs } = this.inputSighash(idx);
|
|
2204
|
+
if (sigOutputs === SignatureHash.SINGLE && idx >= this.outputs.length) {
|
|
2205
|
+
throw new Error(`Input with sighash SINGLE, but there is no output with corresponding index=${idx}`);
|
|
2206
|
+
}
|
|
2207
|
+
// Actual signing
|
|
2208
|
+
// Taproot
|
|
2209
|
+
const prevOut = this.prevOut(input);
|
|
2210
|
+
if (inputType.txType === 'taproot') {
|
|
2211
|
+
if (input.tapBip32Derivation)
|
|
2212
|
+
throw new Error('tapBip32Derivation unsupported');
|
|
2213
|
+
const prevOuts = this.inputs.map(this.prevOut);
|
|
2214
|
+
const prevOutScript = prevOuts.map((i) => i.script);
|
|
2215
|
+
const amount = prevOuts.map((i) => i.amount);
|
|
2216
|
+
let signed = false;
|
|
2217
|
+
let schnorrPub = schnorr.getPublicKey(privateKey);
|
|
2218
|
+
let merkleRoot = input.tapMerkleRoot || P.EMPTY;
|
|
2219
|
+
if (input.tapInternalKey) {
|
|
2220
|
+
// internal + tweak = tweaked key
|
|
2221
|
+
// if internal key == current public key, we need to tweak private key,
|
|
2222
|
+
// otherwise sign as is. bitcoinjs implementation always wants tweaked
|
|
2223
|
+
// priv key to be provided
|
|
2224
|
+
const { pubKey, privKey } = getTaprootKeys(privateKey, schnorrPub, input.tapInternalKey, merkleRoot);
|
|
2225
|
+
const [taprootPubKey, parity] = taprootTweakPubkey(input.tapInternalKey, merkleRoot);
|
|
2226
|
+
if (P.equalBytes(taprootPubKey, pubKey)) {
|
|
2227
|
+
const hash = this.preimageWitnessV1(idx, prevOutScript, sighash, amount);
|
|
2228
|
+
const sig = concat(schnorr.sign(hash, privKey, _auxRand), sighash !== SignatureHash.DEFAULT ? new Uint8Array([sighash]) : P.EMPTY);
|
|
2229
|
+
this.updateInput(idx, { tapKeySig: sig }, true);
|
|
2230
|
+
signed = true;
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
if (input.tapLeafScript) {
|
|
2234
|
+
input.tapScriptSig = input.tapScriptSig || [];
|
|
2235
|
+
for (const [cb, _script] of input.tapLeafScript) {
|
|
2236
|
+
const script = _script.subarray(0, -1);
|
|
2237
|
+
const scriptDecoded = Script.decode(script);
|
|
2238
|
+
const ver = _script[_script.length - 1];
|
|
2239
|
+
const hash = tapLeafHash(script, ver);
|
|
2240
|
+
const { pubKey, privKey } = getTaprootKeys(privateKey, schnorrPub, cb.internalKey, P.EMPTY // Because we cannot have nested taproot tree
|
|
2241
|
+
);
|
|
2242
|
+
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, pubKey));
|
|
2243
|
+
// Skip if there is no public key in tapLeafScript
|
|
2244
|
+
if (pos === -1)
|
|
2245
|
+
continue;
|
|
2246
|
+
const msg = this.preimageWitnessV1(idx, prevOutScript, sighash, amount, undefined, script, ver);
|
|
2247
|
+
const sig = concat(schnorr.sign(msg, privKey, _auxRand), sighash !== SignatureHash.DEFAULT ? new Uint8Array([sighash]) : P.EMPTY);
|
|
2248
|
+
this.updateInput(idx, { tapScriptSig: [[{ pubKey: pubKey, leafHash: hash }, sig]] }, true);
|
|
2249
|
+
signed = true;
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
if (!signed)
|
|
2253
|
+
throw new Error('No taproot scripts signed');
|
|
2254
|
+
return true;
|
|
2255
|
+
}
|
|
2256
|
+
else {
|
|
2257
|
+
// only compressed keys are supported for now
|
|
2258
|
+
const pubKey = _pubECDSA(privateKey);
|
|
2259
|
+
// TODO: replace with explicit checks
|
|
2260
|
+
// Check if script has public key or its has inside
|
|
2261
|
+
let hasPubkey = false;
|
|
2262
|
+
const pubKeyHash = hash160(pubKey);
|
|
2263
|
+
for (const i of Script.decode(inputType.lastScript)) {
|
|
2264
|
+
if (isBytes(i) && (P.equalBytes(i, pubKey) || P.equalBytes(i, pubKeyHash)))
|
|
2265
|
+
hasPubkey = true;
|
|
2266
|
+
}
|
|
2267
|
+
if (!hasPubkey)
|
|
2268
|
+
throw new Error(`Input script doesn't have pubKey: ${inputType.lastScript}`);
|
|
2269
|
+
let hash;
|
|
2270
|
+
if (inputType.txType === 'legacy') {
|
|
2271
|
+
hash = this.preimageLegacy(idx, inputType.lastScript, sighash);
|
|
2272
|
+
}
|
|
2273
|
+
else if (inputType.txType === 'segwit') {
|
|
2274
|
+
let script = inputType.lastScript;
|
|
2275
|
+
// If wpkh OR sh-wpkh, wsh-wpkh is impossible, so looks ok
|
|
2276
|
+
if (inputType.last.type === 'wpkh')
|
|
2277
|
+
script = OutScript.encode({ type: 'pkh', hash: inputType.last.hash });
|
|
2278
|
+
hash = this.preimageWitnessV0(idx, script, sighash, prevOut.amount);
|
|
2279
|
+
}
|
|
2280
|
+
else
|
|
2281
|
+
throw new Error(`Transaction/sign: unknown tx type: ${inputType.txType}`);
|
|
2282
|
+
const sig = signECDSA(hash, privateKey, this.opts.lowR);
|
|
2283
|
+
this.updateInput(idx, {
|
|
2284
|
+
partialSig: [[pubKey, concat(sig, new Uint8Array([sighash]))]],
|
|
2285
|
+
}, true);
|
|
2286
|
+
}
|
|
2287
|
+
return true;
|
|
2288
|
+
}
|
|
2289
|
+
// This is bad API. Will work if user creates and signs tx, but if
|
|
2290
|
+
// there is some complex workflow with exchanging PSBT and signing them,
|
|
2291
|
+
// then it is better to validate which output user signs. How could a better API look like?
|
|
2292
|
+
// Example: user adds input, sends to another party, then signs received input (mixer etc),
|
|
2293
|
+
// another user can add different input for same key and user will sign it.
|
|
2294
|
+
// Even worse: another user can add bip32 derivation, and spend money from different address.
|
|
2295
|
+
// Better api: signIdx
|
|
2296
|
+
sign(privateKey, allowedSighash, _auxRand) {
|
|
2297
|
+
let num = 0;
|
|
2298
|
+
for (let i = 0; i < this.inputs.length; i++) {
|
|
2299
|
+
try {
|
|
2300
|
+
if (this.signIdx(privateKey, i, allowedSighash, _auxRand))
|
|
2301
|
+
num++;
|
|
2302
|
+
}
|
|
2303
|
+
catch (e) { }
|
|
2304
|
+
}
|
|
2305
|
+
if (!num)
|
|
2306
|
+
throw new Error('No inputs signed');
|
|
2307
|
+
return num;
|
|
2308
|
+
}
|
|
2309
|
+
finalizeIdx(idx) {
|
|
2310
|
+
this.checkInputIdx(idx);
|
|
2311
|
+
if (this.fee < 0n)
|
|
2312
|
+
throw new Error('Outputs spends more than inputs amount');
|
|
2313
|
+
const input = this.inputs[idx];
|
|
2314
|
+
const inputType = this.inputType(input);
|
|
2315
|
+
// Taproot finalize
|
|
2316
|
+
if (inputType.txType === 'taproot') {
|
|
2317
|
+
if (input.tapKeySig)
|
|
2318
|
+
input.finalScriptWitness = [input.tapKeySig];
|
|
2319
|
+
else if (input.tapLeafScript && input.tapScriptSig) {
|
|
2320
|
+
// Sort leafs by control block length.
|
|
2321
|
+
const leafs = input.tapLeafScript.sort((a, b) => TaprootControlBlock.encode(a[0]).length - TaprootControlBlock.encode(b[0]).length);
|
|
2322
|
+
for (const [cb, _script] of leafs) {
|
|
2323
|
+
// Last byte is version
|
|
2324
|
+
const script = _script.slice(0, -1);
|
|
2325
|
+
const ver = _script[_script.length - 1];
|
|
2326
|
+
const outScript = OutScript.decode(script);
|
|
2327
|
+
const hash = tapLeafHash(script, ver);
|
|
2328
|
+
const scriptSig = input.tapScriptSig.filter((i) => P.equalBytes(i[0].leafHash, hash));
|
|
2329
|
+
let signatures = [];
|
|
2330
|
+
if (outScript.type === 'tr_ms') {
|
|
2331
|
+
const m = outScript.m;
|
|
2332
|
+
const pubkeys = outScript.pubkeys;
|
|
2333
|
+
let added = 0;
|
|
2334
|
+
for (const pub of pubkeys) {
|
|
2335
|
+
const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
|
|
2336
|
+
// Should have exact amount of signatures (more -- will fail)
|
|
2337
|
+
if (added === m || sigIdx === -1) {
|
|
2338
|
+
signatures.push(P.EMPTY);
|
|
2339
|
+
continue;
|
|
2340
|
+
}
|
|
2341
|
+
signatures.push(scriptSig[sigIdx][1]);
|
|
2342
|
+
added++;
|
|
2343
|
+
}
|
|
2344
|
+
// Should be exact same as m
|
|
2345
|
+
if (added !== m)
|
|
2346
|
+
continue;
|
|
2347
|
+
}
|
|
2348
|
+
else if (outScript.type === 'tr_ns') {
|
|
2349
|
+
for (const pub of outScript.pubkeys) {
|
|
2350
|
+
const sigIdx = scriptSig.findIndex((i) => P.equalBytes(i[0].pubKey, pub));
|
|
2351
|
+
if (sigIdx === -1)
|
|
2352
|
+
continue;
|
|
2353
|
+
signatures.push(scriptSig[sigIdx][1]);
|
|
2354
|
+
}
|
|
2355
|
+
if (signatures.length !== outScript.pubkeys.length)
|
|
2356
|
+
continue;
|
|
2357
|
+
}
|
|
2358
|
+
else if (outScript.type === 'unknown' && this.opts.allowUnknowInput) {
|
|
2359
|
+
// Trying our best to sign what we can
|
|
2360
|
+
const scriptDecoded = Script.decode(script);
|
|
2361
|
+
signatures = scriptSig
|
|
2362
|
+
.map(([{ pubKey }, signature]) => {
|
|
2363
|
+
const pos = scriptDecoded.findIndex((i) => isBytes(i) && P.equalBytes(i, pubKey));
|
|
2364
|
+
if (pos === -1)
|
|
2365
|
+
throw new Error('finalize/taproot: cannot find position of pubkey in script');
|
|
2366
|
+
return { signature, pos };
|
|
2367
|
+
})
|
|
2368
|
+
// Reverse order (because witness is stack and we take last element first from it)
|
|
2369
|
+
.sort((a, b) => a.pos - b.pos)
|
|
2370
|
+
.map((i) => i.signature);
|
|
2371
|
+
if (!signatures.length)
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
else
|
|
2375
|
+
throw new Error('Finalize: Unknown tapLeafScript');
|
|
2376
|
+
// Witness is stack, so last element will be used first
|
|
2377
|
+
input.finalScriptWitness = signatures
|
|
2378
|
+
.reverse()
|
|
2379
|
+
.concat([script, TaprootControlBlock.encode(cb)]);
|
|
2380
|
+
break;
|
|
2381
|
+
}
|
|
2382
|
+
if (!input.finalScriptWitness)
|
|
2383
|
+
throw new Error('finalize/taproot: empty witness');
|
|
2384
|
+
}
|
|
2385
|
+
else
|
|
2386
|
+
throw new Error('finalize/taproot: unknown input');
|
|
2387
|
+
input.finalScriptSig = P.EMPTY;
|
|
2388
|
+
cleanFinalInput(input);
|
|
2389
|
+
return;
|
|
2390
|
+
}
|
|
2391
|
+
if (!input.partialSig || !input.partialSig.length)
|
|
2392
|
+
throw new Error('Not enough partial sign');
|
|
2393
|
+
let inputScript = P.EMPTY;
|
|
2394
|
+
let witness = [];
|
|
2395
|
+
// TODO: move input scripts closer to payments/output scripts
|
|
2396
|
+
// Multisig
|
|
2397
|
+
if (inputType.last.type === 'ms') {
|
|
2398
|
+
const m = inputType.last.m;
|
|
2399
|
+
const pubkeys = inputType.last.pubkeys;
|
|
2400
|
+
let signatures = [];
|
|
2401
|
+
// partial: [pubkey, sign]
|
|
2402
|
+
for (const pub of pubkeys) {
|
|
2403
|
+
const sign = input.partialSig.find((s) => P.equalBytes(pub, s[0]));
|
|
2404
|
+
if (!sign)
|
|
2405
|
+
continue;
|
|
2406
|
+
signatures.push(sign[1]);
|
|
2407
|
+
}
|
|
2408
|
+
signatures = signatures.slice(0, m);
|
|
2409
|
+
if (signatures.length !== m) {
|
|
2410
|
+
throw new Error(`Multisig: wrong signatures count, m=${m} n=${pubkeys.length} signatures=${signatures.length}`);
|
|
2411
|
+
}
|
|
2412
|
+
inputScript = Script.encode([0, ...signatures]);
|
|
2413
|
+
}
|
|
2414
|
+
else if (inputType.last.type === 'pk') {
|
|
2415
|
+
inputScript = Script.encode([input.partialSig[0][1]]);
|
|
2416
|
+
}
|
|
2417
|
+
else if (inputType.last.type === 'pkh') {
|
|
2418
|
+
inputScript = Script.encode([input.partialSig[0][1], input.partialSig[0][0]]);
|
|
2419
|
+
}
|
|
2420
|
+
else if (inputType.last.type === 'wpkh') {
|
|
2421
|
+
inputScript = P.EMPTY;
|
|
2422
|
+
witness = [input.partialSig[0][1], input.partialSig[0][0]];
|
|
2423
|
+
}
|
|
2424
|
+
else if (inputType.last.type === 'unknown' && !this.opts.allowUnknowInput)
|
|
2425
|
+
throw new Error('Unknown inputs not allowed');
|
|
2426
|
+
// Create final scripts (generic part)
|
|
2427
|
+
let finalScriptSig, finalScriptWitness;
|
|
2428
|
+
if (inputType.type.includes('wsh-')) {
|
|
2429
|
+
// P2WSH
|
|
2430
|
+
if (inputScript.length && inputType.lastScript.length) {
|
|
2431
|
+
witness = Script.decode(inputScript).map((i) => {
|
|
2432
|
+
if (i === 0)
|
|
2433
|
+
return P.EMPTY;
|
|
2434
|
+
if (isBytes(i))
|
|
2435
|
+
return i;
|
|
2436
|
+
throw new Error(`Wrong witness op=${i}`);
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2439
|
+
witness = witness.concat(inputType.lastScript);
|
|
2440
|
+
}
|
|
2441
|
+
if (inputType.txType === 'segwit')
|
|
2442
|
+
finalScriptWitness = witness;
|
|
2443
|
+
if (inputType.type.startsWith('sh-wsh-')) {
|
|
2444
|
+
finalScriptSig = Script.encode([Script.encode([0, sha256(inputType.lastScript)])]);
|
|
2445
|
+
}
|
|
2446
|
+
else if (inputType.type.startsWith('sh-')) {
|
|
2447
|
+
finalScriptSig = Script.encode([...Script.decode(inputScript), inputType.lastScript]);
|
|
2448
|
+
}
|
|
2449
|
+
else if (inputType.type.startsWith('wsh-')) {
|
|
2450
|
+
}
|
|
2451
|
+
else if (inputType.txType !== 'segwit')
|
|
2452
|
+
finalScriptSig = inputScript;
|
|
2453
|
+
if (!finalScriptSig && !finalScriptWitness)
|
|
2454
|
+
throw new Error('Unknown error finalizing input');
|
|
2455
|
+
if (finalScriptSig)
|
|
2456
|
+
input.finalScriptSig = finalScriptSig;
|
|
2457
|
+
if (finalScriptWitness)
|
|
2458
|
+
input.finalScriptWitness = finalScriptWitness;
|
|
2459
|
+
cleanFinalInput(input);
|
|
2460
|
+
}
|
|
2461
|
+
finalize() {
|
|
2462
|
+
for (let i = 0; i < this.inputs.length; i++)
|
|
2463
|
+
this.finalizeIdx(i);
|
|
2464
|
+
}
|
|
2465
|
+
extract() {
|
|
2466
|
+
if (!this.isFinal)
|
|
2467
|
+
throw new Error('Transaction has unfinalized inputs');
|
|
2468
|
+
if (!this.outputs.length)
|
|
2469
|
+
throw new Error('Transaction has no outputs');
|
|
2470
|
+
if (this.fee < 0n)
|
|
2471
|
+
throw new Error('Outputs spends more than inputs amount');
|
|
2472
|
+
return this.toBytes(true, true);
|
|
2473
|
+
}
|
|
2474
|
+
combine(other) {
|
|
2475
|
+
for (const k of ['PSBTVersion', 'version', 'lockTime']) {
|
|
2476
|
+
if (this.opts[k] !== other.opts[k]) {
|
|
2477
|
+
throw new Error(`Transaction/combine: different ${k} this=${this.opts[k]} other=${other.opts[k]}`);
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
for (const k of ['inputs', 'outputs']) {
|
|
2481
|
+
if (this[k].length !== other[k].length) {
|
|
2482
|
+
throw new Error(`Transaction/combine: different ${k} length this=${this[k].length} other=${other[k].length}`);
|
|
2483
|
+
}
|
|
2484
|
+
}
|
|
2485
|
+
const thisUnsigned = this.global.unsignedTx ? RawTx.encode(this.global.unsignedTx) : P.EMPTY;
|
|
2486
|
+
const otherUnsigned = other.global.unsignedTx ? RawTx.encode(other.global.unsignedTx) : P.EMPTY;
|
|
2487
|
+
if (!P.equalBytes(thisUnsigned, otherUnsigned))
|
|
2488
|
+
throw new Error(`Transaction/combine: different unsigned tx`);
|
|
2489
|
+
this.global = mergeKeyMap(PSBTGlobal, this.global, other.global);
|
|
2490
|
+
for (let i = 0; i < this.inputs.length; i++)
|
|
2491
|
+
this.updateInput(i, other.inputs[i], true);
|
|
2492
|
+
for (let i = 0; i < this.outputs.length; i++)
|
|
2493
|
+
this.updateOutput(i, other.outputs[i], true);
|
|
2494
|
+
return this;
|
|
2495
|
+
}
|
|
2496
|
+
clone() {
|
|
2497
|
+
// deepClone probably faster, but this enforces that encoding is valid
|
|
2498
|
+
return Transaction.fromPSBT(this.toPSBT(2), this.opts);
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
// User facing API?
|
|
2502
|
+
// Simple pubkey address, without complex scripts
|
|
2503
|
+
export function getAddress(type, privKey, network = NETWORK) {
|
|
2504
|
+
if (type === 'tr') {
|
|
2505
|
+
return p2tr(schnorr.getPublicKey(privKey), undefined, network).address;
|
|
2506
|
+
}
|
|
2507
|
+
const pubKey = _pubECDSA(privKey);
|
|
2508
|
+
if (type === 'pkh')
|
|
2509
|
+
return p2pkh(pubKey, network).address;
|
|
2510
|
+
if (type === 'wpkh')
|
|
2511
|
+
return p2wpkh(pubKey, network).address;
|
|
2512
|
+
throw new Error(`getAddress: unknown type=${type}`);
|
|
2513
|
+
}
|
|
2514
|
+
export function multisig(m, pubkeys, sorted = false, witness = false) {
|
|
2515
|
+
const ms = p2ms(m, sorted ? _sortPubkeys(pubkeys) : pubkeys);
|
|
2516
|
+
return witness ? p2wsh(ms) : p2sh(ms);
|
|
2517
|
+
}
|
|
2518
|
+
export function sortedMultisig(m, pubkeys, witness = false) {
|
|
2519
|
+
return multisig(m, pubkeys, true, witness);
|
|
2520
|
+
}
|
|
2521
|
+
// Copy-pasted from bip32 derive, maybe do something like 'bip32.parsePath'?
|
|
2522
|
+
const HARDENED_OFFSET = 0x80000000;
|
|
2523
|
+
export function bip32Path(path) {
|
|
2524
|
+
const out = [];
|
|
2525
|
+
if (!/^[mM]'?/.test(path))
|
|
2526
|
+
throw new Error('Path must start with "m" or "M"');
|
|
2527
|
+
if (/^[mM]'?$/.test(path))
|
|
2528
|
+
return out;
|
|
2529
|
+
const parts = path.replace(/^[mM]'?\//, '').split('/');
|
|
2530
|
+
for (const c of parts) {
|
|
2531
|
+
const m = /^(\d+)('?)$/.exec(c);
|
|
2532
|
+
if (!m || m.length !== 3)
|
|
2533
|
+
throw new Error(`Invalid child index: ${c}`);
|
|
2534
|
+
let idx = +m[1];
|
|
2535
|
+
if (!Number.isSafeInteger(idx) || idx >= HARDENED_OFFSET)
|
|
2536
|
+
throw new Error('Invalid index');
|
|
2537
|
+
// hardened key
|
|
2538
|
+
if (m[2] === "'")
|
|
2539
|
+
idx += HARDENED_OFFSET;
|
|
2540
|
+
out.push(idx);
|
|
2541
|
+
}
|
|
2542
|
+
return out;
|
|
2543
|
+
}
|
|
2544
|
+
export function PSBTCombine(psbts) {
|
|
2545
|
+
if (!psbts || !Array.isArray(psbts) || !psbts.length)
|
|
2546
|
+
throw new Error('PSBTCombine: wrong PSBT list');
|
|
2547
|
+
const tx = Transaction.fromPSBT(psbts[0]);
|
|
2548
|
+
for (let i = 1; i < psbts.length; i++)
|
|
2549
|
+
tx.combine(Transaction.fromPSBT(psbts[i]));
|
|
2550
|
+
return tx.toPSBT();
|
|
2551
|
+
}
|
|
2552
|
+
//# sourceMappingURL=index.js.map
|