1 | 'use strict';
|
2 | Object.defineProperty(exports, '__esModule', { value: true });
|
3 | exports.Psbt = void 0;
|
4 | const bip174_1 = require('bip174');
|
5 | const varuint = require('bip174/src/lib/converter/varint');
|
6 | const utils_1 = require('bip174/src/lib/utils');
|
7 | const address_1 = require('./address');
|
8 | const bufferutils_1 = require('./bufferutils');
|
9 | const networks_1 = require('./networks');
|
10 | const payments = require('./payments');
|
11 | const bip341_1 = require('./payments/bip341');
|
12 | const bscript = require('./script');
|
13 | const transaction_1 = require('./transaction');
|
14 | const bip371_1 = require('./psbt/bip371');
|
15 | const psbtutils_1 = require('./psbt/psbtutils');
|
16 |
|
17 |
|
18 |
|
19 | const DEFAULT_OPTS = {
|
20 | |
21 |
|
22 |
|
23 |
|
24 | network: networks_1.bitcoin,
|
25 | |
26 |
|
27 |
|
28 |
|
29 |
|
30 | maximumFeeRate: 5000,
|
31 | };
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | class Psbt {
|
65 | static fromBase64(data, opts = {}) {
|
66 | const buffer = Buffer.from(data, 'base64');
|
67 | return this.fromBuffer(buffer, opts);
|
68 | }
|
69 | static fromHex(data, opts = {}) {
|
70 | const buffer = Buffer.from(data, 'hex');
|
71 | return this.fromBuffer(buffer, opts);
|
72 | }
|
73 | static fromBuffer(buffer, opts = {}) {
|
74 | const psbtBase = bip174_1.Psbt.fromBuffer(buffer, transactionFromBuffer);
|
75 | const psbt = new Psbt(opts, psbtBase);
|
76 | checkTxForDupeIns(psbt.__CACHE.__TX, psbt.__CACHE);
|
77 | return psbt;
|
78 | }
|
79 | constructor(opts = {}, data = new bip174_1.Psbt(new PsbtTransaction())) {
|
80 | this.data = data;
|
81 |
|
82 | this.opts = Object.assign({}, DEFAULT_OPTS, opts);
|
83 | this.__CACHE = {
|
84 | __NON_WITNESS_UTXO_TX_CACHE: [],
|
85 | __NON_WITNESS_UTXO_BUF_CACHE: [],
|
86 | __TX_IN_CACHE: {},
|
87 | __TX: this.data.globalMap.unsignedTx.tx,
|
88 |
|
89 |
|
90 |
|
91 |
|
92 |
|
93 |
|
94 |
|
95 |
|
96 | __UNSAFE_SIGN_NONSEGWIT: false,
|
97 | };
|
98 | if (this.data.inputs.length === 0) this.setVersion(2);
|
99 |
|
100 | const dpew = (obj, attr, enumerable, writable) =>
|
101 | Object.defineProperty(obj, attr, {
|
102 | enumerable,
|
103 | writable,
|
104 | });
|
105 | dpew(this, '__CACHE', false, true);
|
106 | dpew(this, 'opts', false, true);
|
107 | }
|
108 | get inputCount() {
|
109 | return this.data.inputs.length;
|
110 | }
|
111 | get version() {
|
112 | return this.__CACHE.__TX.version;
|
113 | }
|
114 | set version(version) {
|
115 | this.setVersion(version);
|
116 | }
|
117 | get locktime() {
|
118 | return this.__CACHE.__TX.locktime;
|
119 | }
|
120 | set locktime(locktime) {
|
121 | this.setLocktime(locktime);
|
122 | }
|
123 | get txInputs() {
|
124 | return this.__CACHE.__TX.ins.map(input => ({
|
125 | hash: (0, bufferutils_1.cloneBuffer)(input.hash),
|
126 | index: input.index,
|
127 | sequence: input.sequence,
|
128 | }));
|
129 | }
|
130 | get txOutputs() {
|
131 | return this.__CACHE.__TX.outs.map(output => {
|
132 | let address;
|
133 | try {
|
134 | address = (0, address_1.fromOutputScript)(
|
135 | output.script,
|
136 | this.opts.network,
|
137 | );
|
138 | } catch (_) {}
|
139 | return {
|
140 | script: (0, bufferutils_1.cloneBuffer)(output.script),
|
141 | value: output.value,
|
142 | address,
|
143 | };
|
144 | });
|
145 | }
|
146 | combine(...those) {
|
147 | this.data.combine(...those.map(o => o.data));
|
148 | return this;
|
149 | }
|
150 | clone() {
|
151 |
|
152 | const res = Psbt.fromBuffer(this.data.toBuffer());
|
153 | res.opts = JSON.parse(JSON.stringify(this.opts));
|
154 | return res;
|
155 | }
|
156 | setMaximumFeeRate(satoshiPerByte) {
|
157 | check32Bit(satoshiPerByte);
|
158 | this.opts.maximumFeeRate = satoshiPerByte;
|
159 | }
|
160 | setVersion(version) {
|
161 | check32Bit(version);
|
162 | checkInputsForPartialSig(this.data.inputs, 'setVersion');
|
163 | const c = this.__CACHE;
|
164 | c.__TX.version = version;
|
165 | c.__EXTRACTED_TX = undefined;
|
166 | return this;
|
167 | }
|
168 | setLocktime(locktime) {
|
169 | check32Bit(locktime);
|
170 | checkInputsForPartialSig(this.data.inputs, 'setLocktime');
|
171 | const c = this.__CACHE;
|
172 | c.__TX.locktime = locktime;
|
173 | c.__EXTRACTED_TX = undefined;
|
174 | return this;
|
175 | }
|
176 | setInputSequence(inputIndex, sequence) {
|
177 | check32Bit(sequence);
|
178 | checkInputsForPartialSig(this.data.inputs, 'setInputSequence');
|
179 | const c = this.__CACHE;
|
180 | if (c.__TX.ins.length <= inputIndex) {
|
181 | throw new Error('Input index too high');
|
182 | }
|
183 | c.__TX.ins[inputIndex].sequence = sequence;
|
184 | c.__EXTRACTED_TX = undefined;
|
185 | return this;
|
186 | }
|
187 | addInputs(inputDatas) {
|
188 | inputDatas.forEach(inputData => this.addInput(inputData));
|
189 | return this;
|
190 | }
|
191 | addInput(inputData) {
|
192 | if (
|
193 | arguments.length > 1 ||
|
194 | !inputData ||
|
195 | inputData.hash === undefined ||
|
196 | inputData.index === undefined
|
197 | ) {
|
198 | throw new Error(
|
199 | `Invalid arguments for Psbt.addInput. ` +
|
200 | `Requires single object with at least [hash] and [index]`,
|
201 | );
|
202 | }
|
203 | (0, bip371_1.checkTaprootInputFields)(inputData, inputData, 'addInput');
|
204 | checkInputsForPartialSig(this.data.inputs, 'addInput');
|
205 | if (inputData.witnessScript) checkInvalidP2WSH(inputData.witnessScript);
|
206 | const c = this.__CACHE;
|
207 | this.data.addInput(inputData);
|
208 | const txIn = c.__TX.ins[c.__TX.ins.length - 1];
|
209 | checkTxInputCache(c, txIn);
|
210 | const inputIndex = this.data.inputs.length - 1;
|
211 | const input = this.data.inputs[inputIndex];
|
212 | if (input.nonWitnessUtxo) {
|
213 | addNonWitnessTxCache(this.__CACHE, input, inputIndex);
|
214 | }
|
215 | c.__FEE = undefined;
|
216 | c.__FEE_RATE = undefined;
|
217 | c.__EXTRACTED_TX = undefined;
|
218 | return this;
|
219 | }
|
220 | addOutputs(outputDatas) {
|
221 | outputDatas.forEach(outputData => this.addOutput(outputData));
|
222 | return this;
|
223 | }
|
224 | addOutput(outputData) {
|
225 | if (
|
226 | arguments.length > 1 ||
|
227 | !outputData ||
|
228 | outputData.value === undefined ||
|
229 | (outputData.address === undefined && outputData.script === undefined)
|
230 | ) {
|
231 | throw new Error(
|
232 | `Invalid arguments for Psbt.addOutput. ` +
|
233 | `Requires single object with at least [script or address] and [value]`,
|
234 | );
|
235 | }
|
236 | checkInputsForPartialSig(this.data.inputs, 'addOutput');
|
237 | const { address } = outputData;
|
238 | if (typeof address === 'string') {
|
239 | const { network } = this.opts;
|
240 | const script = (0, address_1.toOutputScript)(address, network);
|
241 | outputData = Object.assign(outputData, { script });
|
242 | }
|
243 | (0, bip371_1.checkTaprootOutputFields)(outputData, outputData, 'addOutput');
|
244 | const c = this.__CACHE;
|
245 | this.data.addOutput(outputData);
|
246 | c.__FEE = undefined;
|
247 | c.__FEE_RATE = undefined;
|
248 | c.__EXTRACTED_TX = undefined;
|
249 | return this;
|
250 | }
|
251 | extractTransaction(disableFeeCheck) {
|
252 | if (!this.data.inputs.every(isFinalized)) throw new Error('Not finalized');
|
253 | const c = this.__CACHE;
|
254 | if (!disableFeeCheck) {
|
255 | checkFees(this, c, this.opts);
|
256 | }
|
257 | if (c.__EXTRACTED_TX) return c.__EXTRACTED_TX;
|
258 | const tx = c.__TX.clone();
|
259 | inputFinalizeGetAmts(this.data.inputs, tx, c, true);
|
260 | return tx;
|
261 | }
|
262 | getFeeRate() {
|
263 | return getTxCacheValue(
|
264 | '__FEE_RATE',
|
265 | 'fee rate',
|
266 | this.data.inputs,
|
267 | this.__CACHE,
|
268 | );
|
269 | }
|
270 | getFee() {
|
271 | return getTxCacheValue('__FEE', 'fee', this.data.inputs, this.__CACHE);
|
272 | }
|
273 | finalizeAllInputs() {
|
274 | (0, utils_1.checkForInput)(this.data.inputs, 0);
|
275 | range(this.data.inputs.length).forEach(idx => this.finalizeInput(idx));
|
276 | return this;
|
277 | }
|
278 | finalizeInput(inputIndex, finalScriptsFunc) {
|
279 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
280 | if ((0, bip371_1.isTaprootInput)(input))
|
281 | return this._finalizeTaprootInput(
|
282 | inputIndex,
|
283 | input,
|
284 | undefined,
|
285 | finalScriptsFunc,
|
286 | );
|
287 | return this._finalizeInput(inputIndex, input, finalScriptsFunc);
|
288 | }
|
289 | finalizeTaprootInput(
|
290 | inputIndex,
|
291 | tapLeafHashToFinalize,
|
292 | finalScriptsFunc = bip371_1.tapScriptFinalizer,
|
293 | ) {
|
294 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
295 | if ((0, bip371_1.isTaprootInput)(input))
|
296 | return this._finalizeTaprootInput(
|
297 | inputIndex,
|
298 | input,
|
299 | tapLeafHashToFinalize,
|
300 | finalScriptsFunc,
|
301 | );
|
302 | throw new Error(`Cannot finalize input #${inputIndex}. Not Taproot.`);
|
303 | }
|
304 | _finalizeInput(inputIndex, input, finalScriptsFunc = getFinalScripts) {
|
305 | const { script, isP2SH, isP2WSH, isSegwit } = getScriptFromInput(
|
306 | inputIndex,
|
307 | input,
|
308 | this.__CACHE,
|
309 | );
|
310 | if (!script) throw new Error(`No script found for input #${inputIndex}`);
|
311 | checkPartialSigSighashes(input);
|
312 | const { finalScriptSig, finalScriptWitness } = finalScriptsFunc(
|
313 | inputIndex,
|
314 | input,
|
315 | script,
|
316 | isSegwit,
|
317 | isP2SH,
|
318 | isP2WSH,
|
319 | );
|
320 | if (finalScriptSig) this.data.updateInput(inputIndex, { finalScriptSig });
|
321 | if (finalScriptWitness)
|
322 | this.data.updateInput(inputIndex, { finalScriptWitness });
|
323 | if (!finalScriptSig && !finalScriptWitness)
|
324 | throw new Error(`Unknown error finalizing input #${inputIndex}`);
|
325 | this.data.clearFinalizedInput(inputIndex);
|
326 | return this;
|
327 | }
|
328 | _finalizeTaprootInput(
|
329 | inputIndex,
|
330 | input,
|
331 | tapLeafHashToFinalize,
|
332 | finalScriptsFunc = bip371_1.tapScriptFinalizer,
|
333 | ) {
|
334 | if (!input.witnessUtxo)
|
335 | throw new Error(
|
336 | `Cannot finalize input #${inputIndex}. Missing withness utxo.`,
|
337 | );
|
338 |
|
339 | if (input.tapKeySig) {
|
340 | const payment = payments.p2tr({
|
341 | output: input.witnessUtxo.script,
|
342 | signature: input.tapKeySig,
|
343 | });
|
344 | const finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
|
345 | payment.witness,
|
346 | );
|
347 | this.data.updateInput(inputIndex, { finalScriptWitness });
|
348 | } else {
|
349 | const { finalScriptWitness } = finalScriptsFunc(
|
350 | inputIndex,
|
351 | input,
|
352 | tapLeafHashToFinalize,
|
353 | );
|
354 | this.data.updateInput(inputIndex, { finalScriptWitness });
|
355 | }
|
356 | this.data.clearFinalizedInput(inputIndex);
|
357 | return this;
|
358 | }
|
359 | getInputType(inputIndex) {
|
360 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
361 | const script = getScriptFromUtxo(inputIndex, input, this.__CACHE);
|
362 | const result = getMeaningfulScript(
|
363 | script,
|
364 | inputIndex,
|
365 | 'input',
|
366 | input.redeemScript || redeemFromFinalScriptSig(input.finalScriptSig),
|
367 | input.witnessScript ||
|
368 | redeemFromFinalWitnessScript(input.finalScriptWitness),
|
369 | );
|
370 | const type = result.type === 'raw' ? '' : result.type + '-';
|
371 | const mainType = classifyScript(result.meaningfulScript);
|
372 | return type + mainType;
|
373 | }
|
374 | inputHasPubkey(inputIndex, pubkey) {
|
375 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
376 | return pubkeyInInput(pubkey, input, inputIndex, this.__CACHE);
|
377 | }
|
378 | inputHasHDKey(inputIndex, root) {
|
379 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
380 | const derivationIsMine = bip32DerivationIsMine(root);
|
381 | return (
|
382 | !!input.bip32Derivation && input.bip32Derivation.some(derivationIsMine)
|
383 | );
|
384 | }
|
385 | outputHasPubkey(outputIndex, pubkey) {
|
386 | const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex);
|
387 | return pubkeyInOutput(pubkey, output, outputIndex, this.__CACHE);
|
388 | }
|
389 | outputHasHDKey(outputIndex, root) {
|
390 | const output = (0, utils_1.checkForOutput)(this.data.outputs, outputIndex);
|
391 | const derivationIsMine = bip32DerivationIsMine(root);
|
392 | return (
|
393 | !!output.bip32Derivation && output.bip32Derivation.some(derivationIsMine)
|
394 | );
|
395 | }
|
396 | validateSignaturesOfAllInputs(validator) {
|
397 | (0, utils_1.checkForInput)(this.data.inputs, 0);
|
398 | const results = range(this.data.inputs.length).map(idx =>
|
399 | this.validateSignaturesOfInput(idx, validator),
|
400 | );
|
401 | return results.reduce((final, res) => res === true && final, true);
|
402 | }
|
403 | validateSignaturesOfInput(inputIndex, validator, pubkey) {
|
404 | const input = this.data.inputs[inputIndex];
|
405 | if ((0, bip371_1.isTaprootInput)(input))
|
406 | return this.validateSignaturesOfTaprootInput(
|
407 | inputIndex,
|
408 | validator,
|
409 | pubkey,
|
410 | );
|
411 | return this._validateSignaturesOfInput(inputIndex, validator, pubkey);
|
412 | }
|
413 | _validateSignaturesOfInput(inputIndex, validator, pubkey) {
|
414 | const input = this.data.inputs[inputIndex];
|
415 | const partialSig = (input || {}).partialSig;
|
416 | if (!input || !partialSig || partialSig.length < 1)
|
417 | throw new Error('No signatures to validate');
|
418 | if (typeof validator !== 'function')
|
419 | throw new Error('Need validator function to validate signatures');
|
420 | const mySigs = pubkey
|
421 | ? partialSig.filter(sig => sig.pubkey.equals(pubkey))
|
422 | : partialSig;
|
423 | if (mySigs.length < 1) throw new Error('No signatures for this pubkey');
|
424 | const results = [];
|
425 | let hashCache;
|
426 | let scriptCache;
|
427 | let sighashCache;
|
428 | for (const pSig of mySigs) {
|
429 | const sig = bscript.signature.decode(pSig.signature);
|
430 | const { hash, script } =
|
431 | sighashCache !== sig.hashType
|
432 | ? getHashForSig(
|
433 | inputIndex,
|
434 | Object.assign({}, input, { sighashType: sig.hashType }),
|
435 | this.__CACHE,
|
436 | true,
|
437 | )
|
438 | : { hash: hashCache, script: scriptCache };
|
439 | sighashCache = sig.hashType;
|
440 | hashCache = hash;
|
441 | scriptCache = script;
|
442 | checkScriptForPubkey(pSig.pubkey, script, 'verify');
|
443 | results.push(validator(pSig.pubkey, hash, sig.signature));
|
444 | }
|
445 | return results.every(res => res === true);
|
446 | }
|
447 | validateSignaturesOfTaprootInput(inputIndex, validator, pubkey) {
|
448 | const input = this.data.inputs[inputIndex];
|
449 | const tapKeySig = (input || {}).tapKeySig;
|
450 | const tapScriptSig = (input || {}).tapScriptSig;
|
451 | if (!input && !tapKeySig && !(tapScriptSig && !tapScriptSig.length))
|
452 | throw new Error('No signatures to validate');
|
453 | if (typeof validator !== 'function')
|
454 | throw new Error('Need validator function to validate signatures');
|
455 | pubkey = pubkey && (0, bip371_1.toXOnly)(pubkey);
|
456 | const allHashses = pubkey
|
457 | ? getTaprootHashesForSig(
|
458 | inputIndex,
|
459 | input,
|
460 | this.data.inputs,
|
461 | pubkey,
|
462 | this.__CACHE,
|
463 | )
|
464 | : getAllTaprootHashesForSig(
|
465 | inputIndex,
|
466 | input,
|
467 | this.data.inputs,
|
468 | this.__CACHE,
|
469 | );
|
470 | if (!allHashses.length) throw new Error('No signatures for this pubkey');
|
471 | const tapKeyHash = allHashses.find(h => !h.leafHash);
|
472 | let validationResultCount = 0;
|
473 | if (tapKeySig && tapKeyHash) {
|
474 | const isValidTapkeySig = validator(
|
475 | tapKeyHash.pubkey,
|
476 | tapKeyHash.hash,
|
477 | trimTaprootSig(tapKeySig),
|
478 | );
|
479 | if (!isValidTapkeySig) return false;
|
480 | validationResultCount++;
|
481 | }
|
482 | if (tapScriptSig) {
|
483 | for (const tapSig of tapScriptSig) {
|
484 | const tapSigHash = allHashses.find(h => tapSig.pubkey.equals(h.pubkey));
|
485 | if (tapSigHash) {
|
486 | const isValidTapScriptSig = validator(
|
487 | tapSig.pubkey,
|
488 | tapSigHash.hash,
|
489 | trimTaprootSig(tapSig.signature),
|
490 | );
|
491 | if (!isValidTapScriptSig) return false;
|
492 | validationResultCount++;
|
493 | }
|
494 | }
|
495 | }
|
496 | return validationResultCount > 0;
|
497 | }
|
498 | signAllInputsHD(
|
499 | hdKeyPair,
|
500 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
501 | ) {
|
502 | if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
503 | throw new Error('Need HDSigner to sign input');
|
504 | }
|
505 | const results = [];
|
506 | for (const i of range(this.data.inputs.length)) {
|
507 | try {
|
508 | this.signInputHD(i, hdKeyPair, sighashTypes);
|
509 | results.push(true);
|
510 | } catch (err) {
|
511 | results.push(false);
|
512 | }
|
513 | }
|
514 | if (results.every(v => v === false)) {
|
515 | throw new Error('No inputs were signed');
|
516 | }
|
517 | return this;
|
518 | }
|
519 | signAllInputsHDAsync(
|
520 | hdKeyPair,
|
521 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
522 | ) {
|
523 | return new Promise((resolve, reject) => {
|
524 | if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
525 | return reject(new Error('Need HDSigner to sign input'));
|
526 | }
|
527 | const results = [];
|
528 | const promises = [];
|
529 | for (const i of range(this.data.inputs.length)) {
|
530 | promises.push(
|
531 | this.signInputHDAsync(i, hdKeyPair, sighashTypes).then(
|
532 | () => {
|
533 | results.push(true);
|
534 | },
|
535 | () => {
|
536 | results.push(false);
|
537 | },
|
538 | ),
|
539 | );
|
540 | }
|
541 | return Promise.all(promises).then(() => {
|
542 | if (results.every(v => v === false)) {
|
543 | return reject(new Error('No inputs were signed'));
|
544 | }
|
545 | resolve();
|
546 | });
|
547 | });
|
548 | }
|
549 | signInputHD(
|
550 | inputIndex,
|
551 | hdKeyPair,
|
552 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
553 | ) {
|
554 | if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
555 | throw new Error('Need HDSigner to sign input');
|
556 | }
|
557 | const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
558 | signers.forEach(signer => this.signInput(inputIndex, signer, sighashTypes));
|
559 | return this;
|
560 | }
|
561 | signInputHDAsync(
|
562 | inputIndex,
|
563 | hdKeyPair,
|
564 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
565 | ) {
|
566 | return new Promise((resolve, reject) => {
|
567 | if (!hdKeyPair || !hdKeyPair.publicKey || !hdKeyPair.fingerprint) {
|
568 | return reject(new Error('Need HDSigner to sign input'));
|
569 | }
|
570 | const signers = getSignersFromHD(inputIndex, this.data.inputs, hdKeyPair);
|
571 | const promises = signers.map(signer =>
|
572 | this.signInputAsync(inputIndex, signer, sighashTypes),
|
573 | );
|
574 | return Promise.all(promises)
|
575 | .then(() => {
|
576 | resolve();
|
577 | })
|
578 | .catch(reject);
|
579 | });
|
580 | }
|
581 | signAllInputs(keyPair, sighashTypes) {
|
582 | if (!keyPair || !keyPair.publicKey)
|
583 | throw new Error('Need Signer to sign input');
|
584 |
|
585 |
|
586 |
|
587 | const results = [];
|
588 | for (const i of range(this.data.inputs.length)) {
|
589 | try {
|
590 | this.signInput(i, keyPair, sighashTypes);
|
591 | results.push(true);
|
592 | } catch (err) {
|
593 | results.push(false);
|
594 | }
|
595 | }
|
596 | if (results.every(v => v === false)) {
|
597 | throw new Error('No inputs were signed');
|
598 | }
|
599 | return this;
|
600 | }
|
601 | signAllInputsAsync(keyPair, sighashTypes) {
|
602 | return new Promise((resolve, reject) => {
|
603 | if (!keyPair || !keyPair.publicKey)
|
604 | return reject(new Error('Need Signer to sign input'));
|
605 |
|
606 |
|
607 |
|
608 | const results = [];
|
609 | const promises = [];
|
610 | for (const [i] of this.data.inputs.entries()) {
|
611 | promises.push(
|
612 | this.signInputAsync(i, keyPair, sighashTypes).then(
|
613 | () => {
|
614 | results.push(true);
|
615 | },
|
616 | () => {
|
617 | results.push(false);
|
618 | },
|
619 | ),
|
620 | );
|
621 | }
|
622 | return Promise.all(promises).then(() => {
|
623 | if (results.every(v => v === false)) {
|
624 | return reject(new Error('No inputs were signed'));
|
625 | }
|
626 | resolve();
|
627 | });
|
628 | });
|
629 | }
|
630 | signInput(inputIndex, keyPair, sighashTypes) {
|
631 | if (!keyPair || !keyPair.publicKey)
|
632 | throw new Error('Need Signer to sign input');
|
633 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
634 | if ((0, bip371_1.isTaprootInput)(input)) {
|
635 | return this._signTaprootInput(
|
636 | inputIndex,
|
637 | input,
|
638 | keyPair,
|
639 | undefined,
|
640 | sighashTypes,
|
641 | );
|
642 | }
|
643 | return this._signInput(inputIndex, keyPair, sighashTypes);
|
644 | }
|
645 | signTaprootInput(inputIndex, keyPair, tapLeafHashToSign, sighashTypes) {
|
646 | if (!keyPair || !keyPair.publicKey)
|
647 | throw new Error('Need Signer to sign input');
|
648 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
649 | if ((0, bip371_1.isTaprootInput)(input))
|
650 | return this._signTaprootInput(
|
651 | inputIndex,
|
652 | input,
|
653 | keyPair,
|
654 | tapLeafHashToSign,
|
655 | sighashTypes,
|
656 | );
|
657 | throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
658 | }
|
659 | _signInput(
|
660 | inputIndex,
|
661 | keyPair,
|
662 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
663 | ) {
|
664 | const { hash, sighashType } = getHashAndSighashType(
|
665 | this.data.inputs,
|
666 | inputIndex,
|
667 | keyPair.publicKey,
|
668 | this.__CACHE,
|
669 | sighashTypes,
|
670 | );
|
671 | const partialSig = [
|
672 | {
|
673 | pubkey: keyPair.publicKey,
|
674 | signature: bscript.signature.encode(keyPair.sign(hash), sighashType),
|
675 | },
|
676 | ];
|
677 | this.data.updateInput(inputIndex, { partialSig });
|
678 | return this;
|
679 | }
|
680 | _signTaprootInput(
|
681 | inputIndex,
|
682 | input,
|
683 | keyPair,
|
684 | tapLeafHashToSign,
|
685 | allowedSighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT],
|
686 | ) {
|
687 | const hashesForSig = this.checkTaprootHashesForSig(
|
688 | inputIndex,
|
689 | input,
|
690 | keyPair,
|
691 | tapLeafHashToSign,
|
692 | allowedSighashTypes,
|
693 | );
|
694 | const tapKeySig = hashesForSig
|
695 | .filter(h => !h.leafHash)
|
696 | .map(h =>
|
697 | (0, bip371_1.serializeTaprootSignature)(
|
698 | keyPair.signSchnorr(h.hash),
|
699 | input.sighashType,
|
700 | ),
|
701 | )[0];
|
702 | const tapScriptSig = hashesForSig
|
703 | .filter(h => !!h.leafHash)
|
704 | .map(h => ({
|
705 | pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey),
|
706 | signature: (0, bip371_1.serializeTaprootSignature)(
|
707 | keyPair.signSchnorr(h.hash),
|
708 | input.sighashType,
|
709 | ),
|
710 | leafHash: h.leafHash,
|
711 | }));
|
712 | if (tapKeySig) {
|
713 | this.data.updateInput(inputIndex, { tapKeySig });
|
714 | }
|
715 | if (tapScriptSig.length) {
|
716 | this.data.updateInput(inputIndex, { tapScriptSig });
|
717 | }
|
718 | return this;
|
719 | }
|
720 | signInputAsync(inputIndex, keyPair, sighashTypes) {
|
721 | return Promise.resolve().then(() => {
|
722 | if (!keyPair || !keyPair.publicKey)
|
723 | throw new Error('Need Signer to sign input');
|
724 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
725 | if ((0, bip371_1.isTaprootInput)(input))
|
726 | return this._signTaprootInputAsync(
|
727 | inputIndex,
|
728 | input,
|
729 | keyPair,
|
730 | undefined,
|
731 | sighashTypes,
|
732 | );
|
733 | return this._signInputAsync(inputIndex, keyPair, sighashTypes);
|
734 | });
|
735 | }
|
736 | signTaprootInputAsync(inputIndex, keyPair, tapLeafHash, sighashTypes) {
|
737 | return Promise.resolve().then(() => {
|
738 | if (!keyPair || !keyPair.publicKey)
|
739 | throw new Error('Need Signer to sign input');
|
740 | const input = (0, utils_1.checkForInput)(this.data.inputs, inputIndex);
|
741 | if ((0, bip371_1.isTaprootInput)(input))
|
742 | return this._signTaprootInputAsync(
|
743 | inputIndex,
|
744 | input,
|
745 | keyPair,
|
746 | tapLeafHash,
|
747 | sighashTypes,
|
748 | );
|
749 | throw new Error(`Input #${inputIndex} is not of type Taproot.`);
|
750 | });
|
751 | }
|
752 | _signInputAsync(
|
753 | inputIndex,
|
754 | keyPair,
|
755 | sighashTypes = [transaction_1.Transaction.SIGHASH_ALL],
|
756 | ) {
|
757 | const { hash, sighashType } = getHashAndSighashType(
|
758 | this.data.inputs,
|
759 | inputIndex,
|
760 | keyPair.publicKey,
|
761 | this.__CACHE,
|
762 | sighashTypes,
|
763 | );
|
764 | return Promise.resolve(keyPair.sign(hash)).then(signature => {
|
765 | const partialSig = [
|
766 | {
|
767 | pubkey: keyPair.publicKey,
|
768 | signature: bscript.signature.encode(signature, sighashType),
|
769 | },
|
770 | ];
|
771 | this.data.updateInput(inputIndex, { partialSig });
|
772 | });
|
773 | }
|
774 | async _signTaprootInputAsync(
|
775 | inputIndex,
|
776 | input,
|
777 | keyPair,
|
778 | tapLeafHash,
|
779 | sighashTypes = [transaction_1.Transaction.SIGHASH_DEFAULT],
|
780 | ) {
|
781 | const hashesForSig = this.checkTaprootHashesForSig(
|
782 | inputIndex,
|
783 | input,
|
784 | keyPair,
|
785 | tapLeafHash,
|
786 | sighashTypes,
|
787 | );
|
788 | const signaturePromises = [];
|
789 | const tapKeyHash = hashesForSig.filter(h => !h.leafHash)[0];
|
790 | if (tapKeyHash) {
|
791 | const tapKeySigPromise = Promise.resolve(
|
792 | keyPair.signSchnorr(tapKeyHash.hash),
|
793 | ).then(sig => {
|
794 | return {
|
795 | tapKeySig: (0, bip371_1.serializeTaprootSignature)(
|
796 | sig,
|
797 | input.sighashType,
|
798 | ),
|
799 | };
|
800 | });
|
801 | signaturePromises.push(tapKeySigPromise);
|
802 | }
|
803 | const tapScriptHashes = hashesForSig.filter(h => !!h.leafHash);
|
804 | if (tapScriptHashes.length) {
|
805 | const tapScriptSigPromises = tapScriptHashes.map(tsh => {
|
806 | return Promise.resolve(keyPair.signSchnorr(tsh.hash)).then(
|
807 | signature => {
|
808 | const tapScriptSig = [
|
809 | {
|
810 | pubkey: (0, bip371_1.toXOnly)(keyPair.publicKey),
|
811 | signature: (0, bip371_1.serializeTaprootSignature)(
|
812 | signature,
|
813 | input.sighashType,
|
814 | ),
|
815 | leafHash: tsh.leafHash,
|
816 | },
|
817 | ];
|
818 | return { tapScriptSig };
|
819 | },
|
820 | );
|
821 | });
|
822 | signaturePromises.push(...tapScriptSigPromises);
|
823 | }
|
824 | return Promise.all(signaturePromises).then(results => {
|
825 | results.forEach(v => this.data.updateInput(inputIndex, v));
|
826 | });
|
827 | }
|
828 | checkTaprootHashesForSig(
|
829 | inputIndex,
|
830 | input,
|
831 | keyPair,
|
832 | tapLeafHashToSign,
|
833 | allowedSighashTypes,
|
834 | ) {
|
835 | if (typeof keyPair.signSchnorr !== 'function')
|
836 | throw new Error(
|
837 | `Need Schnorr Signer to sign taproot input #${inputIndex}.`,
|
838 | );
|
839 | const hashesForSig = getTaprootHashesForSig(
|
840 | inputIndex,
|
841 | input,
|
842 | this.data.inputs,
|
843 | keyPair.publicKey,
|
844 | this.__CACHE,
|
845 | tapLeafHashToSign,
|
846 | allowedSighashTypes,
|
847 | );
|
848 | if (!hashesForSig || !hashesForSig.length)
|
849 | throw new Error(
|
850 | `Can not sign for input #${inputIndex} with the key ${keyPair.publicKey.toString(
|
851 | 'hex',
|
852 | )}`,
|
853 | );
|
854 | return hashesForSig;
|
855 | }
|
856 | toBuffer() {
|
857 | checkCache(this.__CACHE);
|
858 | return this.data.toBuffer();
|
859 | }
|
860 | toHex() {
|
861 | checkCache(this.__CACHE);
|
862 | return this.data.toHex();
|
863 | }
|
864 | toBase64() {
|
865 | checkCache(this.__CACHE);
|
866 | return this.data.toBase64();
|
867 | }
|
868 | updateGlobal(updateData) {
|
869 | this.data.updateGlobal(updateData);
|
870 | return this;
|
871 | }
|
872 | updateInput(inputIndex, updateData) {
|
873 | if (updateData.witnessScript) checkInvalidP2WSH(updateData.witnessScript);
|
874 | (0, bip371_1.checkTaprootInputFields)(
|
875 | this.data.inputs[inputIndex],
|
876 | updateData,
|
877 | 'updateInput',
|
878 | );
|
879 | this.data.updateInput(inputIndex, updateData);
|
880 | if (updateData.nonWitnessUtxo) {
|
881 | addNonWitnessTxCache(
|
882 | this.__CACHE,
|
883 | this.data.inputs[inputIndex],
|
884 | inputIndex,
|
885 | );
|
886 | }
|
887 | return this;
|
888 | }
|
889 | updateOutput(outputIndex, updateData) {
|
890 | const outputData = this.data.outputs[outputIndex];
|
891 | (0, bip371_1.checkTaprootOutputFields)(
|
892 | outputData,
|
893 | updateData,
|
894 | 'updateOutput',
|
895 | );
|
896 | this.data.updateOutput(outputIndex, updateData);
|
897 | return this;
|
898 | }
|
899 | addUnknownKeyValToGlobal(keyVal) {
|
900 | this.data.addUnknownKeyValToGlobal(keyVal);
|
901 | return this;
|
902 | }
|
903 | addUnknownKeyValToInput(inputIndex, keyVal) {
|
904 | this.data.addUnknownKeyValToInput(inputIndex, keyVal);
|
905 | return this;
|
906 | }
|
907 | addUnknownKeyValToOutput(outputIndex, keyVal) {
|
908 | this.data.addUnknownKeyValToOutput(outputIndex, keyVal);
|
909 | return this;
|
910 | }
|
911 | clearFinalizedInput(inputIndex) {
|
912 | this.data.clearFinalizedInput(inputIndex);
|
913 | return this;
|
914 | }
|
915 | }
|
916 | exports.Psbt = Psbt;
|
917 |
|
918 |
|
919 |
|
920 |
|
921 |
|
922 | const transactionFromBuffer = buffer => new PsbtTransaction(buffer);
|
923 |
|
924 |
|
925 |
|
926 |
|
927 | class PsbtTransaction {
|
928 | constructor(buffer = Buffer.from([2, 0, 0, 0, 0, 0, 0, 0, 0, 0])) {
|
929 | this.tx = transaction_1.Transaction.fromBuffer(buffer);
|
930 | checkTxEmpty(this.tx);
|
931 | Object.defineProperty(this, 'tx', {
|
932 | enumerable: false,
|
933 | writable: true,
|
934 | });
|
935 | }
|
936 | getInputOutputCounts() {
|
937 | return {
|
938 | inputCount: this.tx.ins.length,
|
939 | outputCount: this.tx.outs.length,
|
940 | };
|
941 | }
|
942 | addInput(input) {
|
943 | if (
|
944 | input.hash === undefined ||
|
945 | input.index === undefined ||
|
946 | (!Buffer.isBuffer(input.hash) && typeof input.hash !== 'string') ||
|
947 | typeof input.index !== 'number'
|
948 | ) {
|
949 | throw new Error('Error adding input.');
|
950 | }
|
951 | const hash =
|
952 | typeof input.hash === 'string'
|
953 | ? (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash, 'hex'))
|
954 | : input.hash;
|
955 | this.tx.addInput(hash, input.index, input.sequence);
|
956 | }
|
957 | addOutput(output) {
|
958 | if (
|
959 | output.script === undefined ||
|
960 | output.value === undefined ||
|
961 | !Buffer.isBuffer(output.script) ||
|
962 | typeof output.value !== 'number'
|
963 | ) {
|
964 | throw new Error('Error adding output.');
|
965 | }
|
966 | this.tx.addOutput(output.script, output.value);
|
967 | }
|
968 | toBuffer() {
|
969 | return this.tx.toBuffer();
|
970 | }
|
971 | }
|
972 | function canFinalize(input, script, scriptType) {
|
973 | switch (scriptType) {
|
974 | case 'pubkey':
|
975 | case 'pubkeyhash':
|
976 | case 'witnesspubkeyhash':
|
977 | return hasSigs(1, input.partialSig);
|
978 | case 'multisig':
|
979 | const p2ms = payments.p2ms({ output: script });
|
980 | return hasSigs(p2ms.m, input.partialSig, p2ms.pubkeys);
|
981 | default:
|
982 | return false;
|
983 | }
|
984 | }
|
985 | function checkCache(cache) {
|
986 | if (cache.__UNSAFE_SIGN_NONSEGWIT !== false) {
|
987 | throw new Error('Not BIP174 compliant, can not export');
|
988 | }
|
989 | }
|
990 | function hasSigs(neededSigs, partialSig, pubkeys) {
|
991 | if (!partialSig) return false;
|
992 | let sigs;
|
993 | if (pubkeys) {
|
994 | sigs = pubkeys
|
995 | .map(pkey => {
|
996 | const pubkey = compressPubkey(pkey);
|
997 | return partialSig.find(pSig => pSig.pubkey.equals(pubkey));
|
998 | })
|
999 | .filter(v => !!v);
|
1000 | } else {
|
1001 | sigs = partialSig;
|
1002 | }
|
1003 | if (sigs.length > neededSigs) throw new Error('Too many signatures');
|
1004 | return sigs.length === neededSigs;
|
1005 | }
|
1006 | function isFinalized(input) {
|
1007 | return !!input.finalScriptSig || !!input.finalScriptWitness;
|
1008 | }
|
1009 | function bip32DerivationIsMine(root) {
|
1010 | return d => {
|
1011 | if (!d.masterFingerprint.equals(root.fingerprint)) return false;
|
1012 | if (!root.derivePath(d.path).publicKey.equals(d.pubkey)) return false;
|
1013 | return true;
|
1014 | };
|
1015 | }
|
1016 | function check32Bit(num) {
|
1017 | if (
|
1018 | typeof num !== 'number' ||
|
1019 | num !== Math.floor(num) ||
|
1020 | num > 0xffffffff ||
|
1021 | num < 0
|
1022 | ) {
|
1023 | throw new Error('Invalid 32 bit integer');
|
1024 | }
|
1025 | }
|
1026 | function checkFees(psbt, cache, opts) {
|
1027 | const feeRate = cache.__FEE_RATE || psbt.getFeeRate();
|
1028 | const vsize = cache.__EXTRACTED_TX.virtualSize();
|
1029 | const satoshis = feeRate * vsize;
|
1030 | if (feeRate >= opts.maximumFeeRate) {
|
1031 | throw new Error(
|
1032 | `Warning: You are paying around ${(satoshis / 1e8).toFixed(8)} in ` +
|
1033 | `fees, which is ${feeRate} satoshi per byte for a transaction ` +
|
1034 | `with a VSize of ${vsize} bytes (segwit counted as 0.25 byte per ` +
|
1035 | `byte). Use setMaximumFeeRate method to raise your threshold, or ` +
|
1036 | `pass true to the first arg of extractTransaction.`,
|
1037 | );
|
1038 | }
|
1039 | }
|
1040 | function checkInputsForPartialSig(inputs, action) {
|
1041 | inputs.forEach(input => {
|
1042 | const throws = (0, bip371_1.isTaprootInput)(input)
|
1043 | ? (0, bip371_1.checkTaprootInputForSigs)(input, action)
|
1044 | : (0, psbtutils_1.checkInputForSig)(input, action);
|
1045 | if (throws)
|
1046 | throw new Error('Can not modify transaction, signatures exist.');
|
1047 | });
|
1048 | }
|
1049 | function checkPartialSigSighashes(input) {
|
1050 | if (!input.sighashType || !input.partialSig) return;
|
1051 | const { partialSig, sighashType } = input;
|
1052 | partialSig.forEach(pSig => {
|
1053 | const { hashType } = bscript.signature.decode(pSig.signature);
|
1054 | if (sighashType !== hashType) {
|
1055 | throw new Error('Signature sighash does not match input sighash type');
|
1056 | }
|
1057 | });
|
1058 | }
|
1059 | function checkScriptForPubkey(pubkey, script, action) {
|
1060 | if (!(0, psbtutils_1.pubkeyInScript)(pubkey, script)) {
|
1061 | throw new Error(
|
1062 | `Can not ${action} for this input with the key ${pubkey.toString('hex')}`,
|
1063 | );
|
1064 | }
|
1065 | }
|
1066 | function checkTxEmpty(tx) {
|
1067 | const isEmpty = tx.ins.every(
|
1068 | input =>
|
1069 | input.script &&
|
1070 | input.script.length === 0 &&
|
1071 | input.witness &&
|
1072 | input.witness.length === 0,
|
1073 | );
|
1074 | if (!isEmpty) {
|
1075 | throw new Error('Format Error: Transaction ScriptSigs are not empty');
|
1076 | }
|
1077 | }
|
1078 | function checkTxForDupeIns(tx, cache) {
|
1079 | tx.ins.forEach(input => {
|
1080 | checkTxInputCache(cache, input);
|
1081 | });
|
1082 | }
|
1083 | function checkTxInputCache(cache, input) {
|
1084 | const key =
|
1085 | (0, bufferutils_1.reverseBuffer)(Buffer.from(input.hash)).toString('hex') +
|
1086 | ':' +
|
1087 | input.index;
|
1088 | if (cache.__TX_IN_CACHE[key]) throw new Error('Duplicate input detected.');
|
1089 | cache.__TX_IN_CACHE[key] = 1;
|
1090 | }
|
1091 | function scriptCheckerFactory(payment, paymentScriptName) {
|
1092 | return (inputIndex, scriptPubKey, redeemScript, ioType) => {
|
1093 | const redeemScriptOutput = payment({
|
1094 | redeem: { output: redeemScript },
|
1095 | }).output;
|
1096 | if (!scriptPubKey.equals(redeemScriptOutput)) {
|
1097 | throw new Error(
|
1098 | `${paymentScriptName} for ${ioType} #${inputIndex} doesn't match the scriptPubKey in the prevout`,
|
1099 | );
|
1100 | }
|
1101 | };
|
1102 | }
|
1103 | const checkRedeemScript = scriptCheckerFactory(payments.p2sh, 'Redeem script');
|
1104 | const checkWitnessScript = scriptCheckerFactory(
|
1105 | payments.p2wsh,
|
1106 | 'Witness script',
|
1107 | );
|
1108 | function getTxCacheValue(key, name, inputs, c) {
|
1109 | if (!inputs.every(isFinalized))
|
1110 | throw new Error(`PSBT must be finalized to calculate ${name}`);
|
1111 | if (key === '__FEE_RATE' && c.__FEE_RATE) return c.__FEE_RATE;
|
1112 | if (key === '__FEE' && c.__FEE) return c.__FEE;
|
1113 | let tx;
|
1114 | let mustFinalize = true;
|
1115 | if (c.__EXTRACTED_TX) {
|
1116 | tx = c.__EXTRACTED_TX;
|
1117 | mustFinalize = false;
|
1118 | } else {
|
1119 | tx = c.__TX.clone();
|
1120 | }
|
1121 | inputFinalizeGetAmts(inputs, tx, c, mustFinalize);
|
1122 | if (key === '__FEE_RATE') return c.__FEE_RATE;
|
1123 | else if (key === '__FEE') return c.__FEE;
|
1124 | }
|
1125 | function getFinalScripts(inputIndex, input, script, isSegwit, isP2SH, isP2WSH) {
|
1126 | const scriptType = classifyScript(script);
|
1127 | if (!canFinalize(input, script, scriptType))
|
1128 | throw new Error(`Can not finalize input #${inputIndex}`);
|
1129 | return prepareFinalScripts(
|
1130 | script,
|
1131 | scriptType,
|
1132 | input.partialSig,
|
1133 | isSegwit,
|
1134 | isP2SH,
|
1135 | isP2WSH,
|
1136 | );
|
1137 | }
|
1138 | function prepareFinalScripts(
|
1139 | script,
|
1140 | scriptType,
|
1141 | partialSig,
|
1142 | isSegwit,
|
1143 | isP2SH,
|
1144 | isP2WSH,
|
1145 | ) {
|
1146 | let finalScriptSig;
|
1147 | let finalScriptWitness;
|
1148 |
|
1149 | const payment = getPayment(script, scriptType, partialSig);
|
1150 | const p2wsh = !isP2WSH ? null : payments.p2wsh({ redeem: payment });
|
1151 | const p2sh = !isP2SH ? null : payments.p2sh({ redeem: p2wsh || payment });
|
1152 | if (isSegwit) {
|
1153 | if (p2wsh) {
|
1154 | finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
|
1155 | p2wsh.witness,
|
1156 | );
|
1157 | } else {
|
1158 | finalScriptWitness = (0, psbtutils_1.witnessStackToScriptWitness)(
|
1159 | payment.witness,
|
1160 | );
|
1161 | }
|
1162 | if (p2sh) {
|
1163 | finalScriptSig = p2sh.input;
|
1164 | }
|
1165 | } else {
|
1166 | if (p2sh) {
|
1167 | finalScriptSig = p2sh.input;
|
1168 | } else {
|
1169 | finalScriptSig = payment.input;
|
1170 | }
|
1171 | }
|
1172 | return {
|
1173 | finalScriptSig,
|
1174 | finalScriptWitness,
|
1175 | };
|
1176 | }
|
1177 | function getHashAndSighashType(
|
1178 | inputs,
|
1179 | inputIndex,
|
1180 | pubkey,
|
1181 | cache,
|
1182 | sighashTypes,
|
1183 | ) {
|
1184 | const input = (0, utils_1.checkForInput)(inputs, inputIndex);
|
1185 | const { hash, sighashType, script } = getHashForSig(
|
1186 | inputIndex,
|
1187 | input,
|
1188 | cache,
|
1189 | false,
|
1190 | sighashTypes,
|
1191 | );
|
1192 | checkScriptForPubkey(pubkey, script, 'sign');
|
1193 | return {
|
1194 | hash,
|
1195 | sighashType,
|
1196 | };
|
1197 | }
|
1198 | function getHashForSig(inputIndex, input, cache, forValidate, sighashTypes) {
|
1199 | const unsignedTx = cache.__TX;
|
1200 | const sighashType =
|
1201 | input.sighashType || transaction_1.Transaction.SIGHASH_ALL;
|
1202 | checkSighashTypeAllowed(sighashType, sighashTypes);
|
1203 | let hash;
|
1204 | let prevout;
|
1205 | if (input.nonWitnessUtxo) {
|
1206 | const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
|
1207 | cache,
|
1208 | input,
|
1209 | inputIndex,
|
1210 | );
|
1211 | const prevoutHash = unsignedTx.ins[inputIndex].hash;
|
1212 | const utxoHash = nonWitnessUtxoTx.getHash();
|
1213 |
|
1214 | if (!prevoutHash.equals(utxoHash)) {
|
1215 | throw new Error(
|
1216 | `Non-witness UTXO hash for input #${inputIndex} doesn't match the hash specified in the prevout`,
|
1217 | );
|
1218 | }
|
1219 | const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
1220 | prevout = nonWitnessUtxoTx.outs[prevoutIndex];
|
1221 | } else if (input.witnessUtxo) {
|
1222 | prevout = input.witnessUtxo;
|
1223 | } else {
|
1224 | throw new Error('Need a Utxo input item for signing');
|
1225 | }
|
1226 | const { meaningfulScript, type } = getMeaningfulScript(
|
1227 | prevout.script,
|
1228 | inputIndex,
|
1229 | 'input',
|
1230 | input.redeemScript,
|
1231 | input.witnessScript,
|
1232 | );
|
1233 | if (['p2sh-p2wsh', 'p2wsh'].indexOf(type) >= 0) {
|
1234 | hash = unsignedTx.hashForWitnessV0(
|
1235 | inputIndex,
|
1236 | meaningfulScript,
|
1237 | prevout.value,
|
1238 | sighashType,
|
1239 | );
|
1240 | } else if ((0, psbtutils_1.isP2WPKH)(meaningfulScript)) {
|
1241 |
|
1242 | const signingScript = payments.p2pkh({
|
1243 | hash: meaningfulScript.slice(2),
|
1244 | }).output;
|
1245 | hash = unsignedTx.hashForWitnessV0(
|
1246 | inputIndex,
|
1247 | signingScript,
|
1248 | prevout.value,
|
1249 | sighashType,
|
1250 | );
|
1251 | } else {
|
1252 |
|
1253 | if (
|
1254 | input.nonWitnessUtxo === undefined &&
|
1255 | cache.__UNSAFE_SIGN_NONSEGWIT === false
|
1256 | )
|
1257 | throw new Error(
|
1258 | `Input #${inputIndex} has witnessUtxo but non-segwit script: ` +
|
1259 | `${meaningfulScript.toString('hex')}`,
|
1260 | );
|
1261 | if (!forValidate && cache.__UNSAFE_SIGN_NONSEGWIT !== false)
|
1262 | console.warn(
|
1263 | 'Warning: Signing non-segwit inputs without the full parent transaction ' +
|
1264 | 'means there is a chance that a miner could feed you incorrect information ' +
|
1265 | "to trick you into paying large fees. This behavior is the same as Psbt's predecesor " +
|
1266 | '(TransactionBuilder - now removed) when signing non-segwit scripts. You are not ' +
|
1267 | 'able to export this Psbt with toBuffer|toBase64|toHex since it is not ' +
|
1268 | 'BIP174 compliant.\n*********************\nPROCEED WITH CAUTION!\n' +
|
1269 | '*********************',
|
1270 | );
|
1271 | hash = unsignedTx.hashForSignature(
|
1272 | inputIndex,
|
1273 | meaningfulScript,
|
1274 | sighashType,
|
1275 | );
|
1276 | }
|
1277 | return {
|
1278 | script: meaningfulScript,
|
1279 | sighashType,
|
1280 | hash,
|
1281 | };
|
1282 | }
|
1283 | function getAllTaprootHashesForSig(inputIndex, input, inputs, cache) {
|
1284 | const allPublicKeys = [];
|
1285 | if (input.tapInternalKey) {
|
1286 | const key = getPrevoutTaprootKey(inputIndex, input, cache);
|
1287 | if (key) {
|
1288 | allPublicKeys.push(key);
|
1289 | }
|
1290 | }
|
1291 | if (input.tapScriptSig) {
|
1292 | const tapScriptPubkeys = input.tapScriptSig.map(tss => tss.pubkey);
|
1293 | allPublicKeys.push(...tapScriptPubkeys);
|
1294 | }
|
1295 | const allHashes = allPublicKeys.map(pubicKey =>
|
1296 | getTaprootHashesForSig(inputIndex, input, inputs, pubicKey, cache),
|
1297 | );
|
1298 | return allHashes.flat();
|
1299 | }
|
1300 | function getPrevoutTaprootKey(inputIndex, input, cache) {
|
1301 | const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
1302 | return (0, psbtutils_1.isP2TR)(script) ? script.subarray(2, 34) : null;
|
1303 | }
|
1304 | function trimTaprootSig(signature) {
|
1305 | return signature.length === 64 ? signature : signature.subarray(0, 64);
|
1306 | }
|
1307 | function getTaprootHashesForSig(
|
1308 | inputIndex,
|
1309 | input,
|
1310 | inputs,
|
1311 | pubkey,
|
1312 | cache,
|
1313 | tapLeafHashToSign,
|
1314 | allowedSighashTypes,
|
1315 | ) {
|
1316 | const unsignedTx = cache.__TX;
|
1317 | const sighashType =
|
1318 | input.sighashType || transaction_1.Transaction.SIGHASH_DEFAULT;
|
1319 | checkSighashTypeAllowed(sighashType, allowedSighashTypes);
|
1320 | const prevOuts = inputs.map((i, index) =>
|
1321 | getScriptAndAmountFromUtxo(index, i, cache),
|
1322 | );
|
1323 | const signingScripts = prevOuts.map(o => o.script);
|
1324 | const values = prevOuts.map(o => o.value);
|
1325 | const hashes = [];
|
1326 | if (input.tapInternalKey && !tapLeafHashToSign) {
|
1327 | const outputKey =
|
1328 | getPrevoutTaprootKey(inputIndex, input, cache) || Buffer.from([]);
|
1329 | if ((0, bip371_1.toXOnly)(pubkey).equals(outputKey)) {
|
1330 | const tapKeyHash = unsignedTx.hashForWitnessV1(
|
1331 | inputIndex,
|
1332 | signingScripts,
|
1333 | values,
|
1334 | sighashType,
|
1335 | );
|
1336 | hashes.push({ pubkey, hash: tapKeyHash });
|
1337 | }
|
1338 | }
|
1339 | const tapLeafHashes = (input.tapLeafScript || [])
|
1340 | .filter(tapLeaf => (0, psbtutils_1.pubkeyInScript)(pubkey, tapLeaf.script))
|
1341 | .map(tapLeaf => {
|
1342 | const hash = (0, bip341_1.tapleafHash)({
|
1343 | output: tapLeaf.script,
|
1344 | version: tapLeaf.leafVersion,
|
1345 | });
|
1346 | return Object.assign({ hash }, tapLeaf);
|
1347 | })
|
1348 | .filter(
|
1349 | tapLeaf => !tapLeafHashToSign || tapLeafHashToSign.equals(tapLeaf.hash),
|
1350 | )
|
1351 | .map(tapLeaf => {
|
1352 | const tapScriptHash = unsignedTx.hashForWitnessV1(
|
1353 | inputIndex,
|
1354 | signingScripts,
|
1355 | values,
|
1356 | transaction_1.Transaction.SIGHASH_DEFAULT,
|
1357 | tapLeaf.hash,
|
1358 | );
|
1359 | return {
|
1360 | pubkey,
|
1361 | hash: tapScriptHash,
|
1362 | leafHash: tapLeaf.hash,
|
1363 | };
|
1364 | });
|
1365 | return hashes.concat(tapLeafHashes);
|
1366 | }
|
1367 | function checkSighashTypeAllowed(sighashType, sighashTypes) {
|
1368 | if (sighashTypes && sighashTypes.indexOf(sighashType) < 0) {
|
1369 | const str = sighashTypeToString(sighashType);
|
1370 | throw new Error(
|
1371 | `Sighash type is not allowed. Retry the sign method passing the ` +
|
1372 | `sighashTypes array of whitelisted types. Sighash type: ${str}`,
|
1373 | );
|
1374 | }
|
1375 | }
|
1376 | function getPayment(script, scriptType, partialSig) {
|
1377 | let payment;
|
1378 | switch (scriptType) {
|
1379 | case 'multisig':
|
1380 | const sigs = getSortedSigs(script, partialSig);
|
1381 | payment = payments.p2ms({
|
1382 | output: script,
|
1383 | signatures: sigs,
|
1384 | });
|
1385 | break;
|
1386 | case 'pubkey':
|
1387 | payment = payments.p2pk({
|
1388 | output: script,
|
1389 | signature: partialSig[0].signature,
|
1390 | });
|
1391 | break;
|
1392 | case 'pubkeyhash':
|
1393 | payment = payments.p2pkh({
|
1394 | output: script,
|
1395 | pubkey: partialSig[0].pubkey,
|
1396 | signature: partialSig[0].signature,
|
1397 | });
|
1398 | break;
|
1399 | case 'witnesspubkeyhash':
|
1400 | payment = payments.p2wpkh({
|
1401 | output: script,
|
1402 | pubkey: partialSig[0].pubkey,
|
1403 | signature: partialSig[0].signature,
|
1404 | });
|
1405 | break;
|
1406 | }
|
1407 | return payment;
|
1408 | }
|
1409 | function getScriptFromInput(inputIndex, input, cache) {
|
1410 | const unsignedTx = cache.__TX;
|
1411 | const res = {
|
1412 | script: null,
|
1413 | isSegwit: false,
|
1414 | isP2SH: false,
|
1415 | isP2WSH: false,
|
1416 | };
|
1417 | res.isP2SH = !!input.redeemScript;
|
1418 | res.isP2WSH = !!input.witnessScript;
|
1419 | if (input.witnessScript) {
|
1420 | res.script = input.witnessScript;
|
1421 | } else if (input.redeemScript) {
|
1422 | res.script = input.redeemScript;
|
1423 | } else {
|
1424 | if (input.nonWitnessUtxo) {
|
1425 | const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
|
1426 | cache,
|
1427 | input,
|
1428 | inputIndex,
|
1429 | );
|
1430 | const prevoutIndex = unsignedTx.ins[inputIndex].index;
|
1431 | res.script = nonWitnessUtxoTx.outs[prevoutIndex].script;
|
1432 | } else if (input.witnessUtxo) {
|
1433 | res.script = input.witnessUtxo.script;
|
1434 | }
|
1435 | }
|
1436 | if (input.witnessScript || (0, psbtutils_1.isP2WPKH)(res.script)) {
|
1437 | res.isSegwit = true;
|
1438 | }
|
1439 | return res;
|
1440 | }
|
1441 | function getSignersFromHD(inputIndex, inputs, hdKeyPair) {
|
1442 | const input = (0, utils_1.checkForInput)(inputs, inputIndex);
|
1443 | if (!input.bip32Derivation || input.bip32Derivation.length === 0) {
|
1444 | throw new Error('Need bip32Derivation to sign with HD');
|
1445 | }
|
1446 | const myDerivations = input.bip32Derivation
|
1447 | .map(bipDv => {
|
1448 | if (bipDv.masterFingerprint.equals(hdKeyPair.fingerprint)) {
|
1449 | return bipDv;
|
1450 | } else {
|
1451 | return;
|
1452 | }
|
1453 | })
|
1454 | .filter(v => !!v);
|
1455 | if (myDerivations.length === 0) {
|
1456 | throw new Error(
|
1457 | 'Need one bip32Derivation masterFingerprint to match the HDSigner fingerprint',
|
1458 | );
|
1459 | }
|
1460 | const signers = myDerivations.map(bipDv => {
|
1461 | const node = hdKeyPair.derivePath(bipDv.path);
|
1462 | if (!bipDv.pubkey.equals(node.publicKey)) {
|
1463 | throw new Error('pubkey did not match bip32Derivation');
|
1464 | }
|
1465 | return node;
|
1466 | });
|
1467 | return signers;
|
1468 | }
|
1469 | function getSortedSigs(script, partialSig) {
|
1470 | const p2ms = payments.p2ms({ output: script });
|
1471 |
|
1472 | return p2ms.pubkeys
|
1473 | .map(pk => {
|
1474 |
|
1475 | return (
|
1476 | partialSig.filter(ps => {
|
1477 | return ps.pubkey.equals(pk);
|
1478 | })[0] || {}
|
1479 | ).signature;
|
1480 |
|
1481 |
|
1482 | })
|
1483 | .filter(v => !!v);
|
1484 | }
|
1485 | function scriptWitnessToWitnessStack(buffer) {
|
1486 | let offset = 0;
|
1487 | function readSlice(n) {
|
1488 | offset += n;
|
1489 | return buffer.slice(offset - n, offset);
|
1490 | }
|
1491 | function readVarInt() {
|
1492 | const vi = varuint.decode(buffer, offset);
|
1493 | offset += varuint.decode.bytes;
|
1494 | return vi;
|
1495 | }
|
1496 | function readVarSlice() {
|
1497 | return readSlice(readVarInt());
|
1498 | }
|
1499 | function readVector() {
|
1500 | const count = readVarInt();
|
1501 | const vector = [];
|
1502 | for (let i = 0; i < count; i++) vector.push(readVarSlice());
|
1503 | return vector;
|
1504 | }
|
1505 | return readVector();
|
1506 | }
|
1507 | function sighashTypeToString(sighashType) {
|
1508 | let text =
|
1509 | sighashType & transaction_1.Transaction.SIGHASH_ANYONECANPAY
|
1510 | ? 'SIGHASH_ANYONECANPAY | '
|
1511 | : '';
|
1512 | const sigMod = sighashType & 0x1f;
|
1513 | switch (sigMod) {
|
1514 | case transaction_1.Transaction.SIGHASH_ALL:
|
1515 | text += 'SIGHASH_ALL';
|
1516 | break;
|
1517 | case transaction_1.Transaction.SIGHASH_SINGLE:
|
1518 | text += 'SIGHASH_SINGLE';
|
1519 | break;
|
1520 | case transaction_1.Transaction.SIGHASH_NONE:
|
1521 | text += 'SIGHASH_NONE';
|
1522 | break;
|
1523 | }
|
1524 | return text;
|
1525 | }
|
1526 | function addNonWitnessTxCache(cache, input, inputIndex) {
|
1527 | cache.__NON_WITNESS_UTXO_BUF_CACHE[inputIndex] = input.nonWitnessUtxo;
|
1528 | const tx = transaction_1.Transaction.fromBuffer(input.nonWitnessUtxo);
|
1529 | cache.__NON_WITNESS_UTXO_TX_CACHE[inputIndex] = tx;
|
1530 | const self = cache;
|
1531 | const selfIndex = inputIndex;
|
1532 | delete input.nonWitnessUtxo;
|
1533 | Object.defineProperty(input, 'nonWitnessUtxo', {
|
1534 | enumerable: true,
|
1535 | get() {
|
1536 | const buf = self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex];
|
1537 | const txCache = self.__NON_WITNESS_UTXO_TX_CACHE[selfIndex];
|
1538 | if (buf !== undefined) {
|
1539 | return buf;
|
1540 | } else {
|
1541 | const newBuf = txCache.toBuffer();
|
1542 | self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = newBuf;
|
1543 | return newBuf;
|
1544 | }
|
1545 | },
|
1546 | set(data) {
|
1547 | self.__NON_WITNESS_UTXO_BUF_CACHE[selfIndex] = data;
|
1548 | },
|
1549 | });
|
1550 | }
|
1551 | function inputFinalizeGetAmts(inputs, tx, cache, mustFinalize) {
|
1552 | let inputAmount = 0;
|
1553 | inputs.forEach((input, idx) => {
|
1554 | if (mustFinalize && input.finalScriptSig)
|
1555 | tx.ins[idx].script = input.finalScriptSig;
|
1556 | if (mustFinalize && input.finalScriptWitness) {
|
1557 | tx.ins[idx].witness = scriptWitnessToWitnessStack(
|
1558 | input.finalScriptWitness,
|
1559 | );
|
1560 | }
|
1561 | if (input.witnessUtxo) {
|
1562 | inputAmount += input.witnessUtxo.value;
|
1563 | } else if (input.nonWitnessUtxo) {
|
1564 | const nwTx = nonWitnessUtxoTxFromCache(cache, input, idx);
|
1565 | const vout = tx.ins[idx].index;
|
1566 | const out = nwTx.outs[vout];
|
1567 | inputAmount += out.value;
|
1568 | }
|
1569 | });
|
1570 | const outputAmount = tx.outs.reduce((total, o) => total + o.value, 0);
|
1571 | const fee = inputAmount - outputAmount;
|
1572 | if (fee < 0) {
|
1573 | throw new Error('Outputs are spending more than Inputs');
|
1574 | }
|
1575 | const bytes = tx.virtualSize();
|
1576 | cache.__FEE = fee;
|
1577 | cache.__EXTRACTED_TX = tx;
|
1578 | cache.__FEE_RATE = Math.floor(fee / bytes);
|
1579 | }
|
1580 | function nonWitnessUtxoTxFromCache(cache, input, inputIndex) {
|
1581 | const c = cache.__NON_WITNESS_UTXO_TX_CACHE;
|
1582 | if (!c[inputIndex]) {
|
1583 | addNonWitnessTxCache(cache, input, inputIndex);
|
1584 | }
|
1585 | return c[inputIndex];
|
1586 | }
|
1587 | function getScriptFromUtxo(inputIndex, input, cache) {
|
1588 | const { script } = getScriptAndAmountFromUtxo(inputIndex, input, cache);
|
1589 | return script;
|
1590 | }
|
1591 | function getScriptAndAmountFromUtxo(inputIndex, input, cache) {
|
1592 | if (input.witnessUtxo !== undefined) {
|
1593 | return {
|
1594 | script: input.witnessUtxo.script,
|
1595 | value: input.witnessUtxo.value,
|
1596 | };
|
1597 | } else if (input.nonWitnessUtxo !== undefined) {
|
1598 | const nonWitnessUtxoTx = nonWitnessUtxoTxFromCache(
|
1599 | cache,
|
1600 | input,
|
1601 | inputIndex,
|
1602 | );
|
1603 | const o = nonWitnessUtxoTx.outs[cache.__TX.ins[inputIndex].index];
|
1604 | return { script: o.script, value: o.value };
|
1605 | } else {
|
1606 | throw new Error("Can't find pubkey in input without Utxo data");
|
1607 | }
|
1608 | }
|
1609 | function pubkeyInInput(pubkey, input, inputIndex, cache) {
|
1610 | const script = getScriptFromUtxo(inputIndex, input, cache);
|
1611 | const { meaningfulScript } = getMeaningfulScript(
|
1612 | script,
|
1613 | inputIndex,
|
1614 | 'input',
|
1615 | input.redeemScript,
|
1616 | input.witnessScript,
|
1617 | );
|
1618 | return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript);
|
1619 | }
|
1620 | function pubkeyInOutput(pubkey, output, outputIndex, cache) {
|
1621 | const script = cache.__TX.outs[outputIndex].script;
|
1622 | const { meaningfulScript } = getMeaningfulScript(
|
1623 | script,
|
1624 | outputIndex,
|
1625 | 'output',
|
1626 | output.redeemScript,
|
1627 | output.witnessScript,
|
1628 | );
|
1629 | return (0, psbtutils_1.pubkeyInScript)(pubkey, meaningfulScript);
|
1630 | }
|
1631 | function redeemFromFinalScriptSig(finalScript) {
|
1632 | if (!finalScript) return;
|
1633 | const decomp = bscript.decompile(finalScript);
|
1634 | if (!decomp) return;
|
1635 | const lastItem = decomp[decomp.length - 1];
|
1636 | if (
|
1637 | !Buffer.isBuffer(lastItem) ||
|
1638 | isPubkeyLike(lastItem) ||
|
1639 | isSigLike(lastItem)
|
1640 | )
|
1641 | return;
|
1642 | const sDecomp = bscript.decompile(lastItem);
|
1643 | if (!sDecomp) return;
|
1644 | return lastItem;
|
1645 | }
|
1646 | function redeemFromFinalWitnessScript(finalScript) {
|
1647 | if (!finalScript) return;
|
1648 | const decomp = scriptWitnessToWitnessStack(finalScript);
|
1649 | const lastItem = decomp[decomp.length - 1];
|
1650 | if (isPubkeyLike(lastItem)) return;
|
1651 | const sDecomp = bscript.decompile(lastItem);
|
1652 | if (!sDecomp) return;
|
1653 | return lastItem;
|
1654 | }
|
1655 | function compressPubkey(pubkey) {
|
1656 | if (pubkey.length === 65) {
|
1657 | const parity = pubkey[64] & 1;
|
1658 | const newKey = pubkey.slice(0, 33);
|
1659 | newKey[0] = 2 | parity;
|
1660 | return newKey;
|
1661 | }
|
1662 | return pubkey.slice();
|
1663 | }
|
1664 | function isPubkeyLike(buf) {
|
1665 | return buf.length === 33 && bscript.isCanonicalPubKey(buf);
|
1666 | }
|
1667 | function isSigLike(buf) {
|
1668 | return bscript.isCanonicalScriptSignature(buf);
|
1669 | }
|
1670 | function getMeaningfulScript(
|
1671 | script,
|
1672 | index,
|
1673 | ioType,
|
1674 | redeemScript,
|
1675 | witnessScript,
|
1676 | ) {
|
1677 | const isP2SH = (0, psbtutils_1.isP2SHScript)(script);
|
1678 | const isP2SHP2WSH =
|
1679 | isP2SH && redeemScript && (0, psbtutils_1.isP2WSHScript)(redeemScript);
|
1680 | const isP2WSH = (0, psbtutils_1.isP2WSHScript)(script);
|
1681 | if (isP2SH && redeemScript === undefined)
|
1682 | throw new Error('scriptPubkey is P2SH but redeemScript missing');
|
1683 | if ((isP2WSH || isP2SHP2WSH) && witnessScript === undefined)
|
1684 | throw new Error(
|
1685 | 'scriptPubkey or redeemScript is P2WSH but witnessScript missing',
|
1686 | );
|
1687 | let meaningfulScript;
|
1688 | if (isP2SHP2WSH) {
|
1689 | meaningfulScript = witnessScript;
|
1690 | checkRedeemScript(index, script, redeemScript, ioType);
|
1691 | checkWitnessScript(index, redeemScript, witnessScript, ioType);
|
1692 | checkInvalidP2WSH(meaningfulScript);
|
1693 | } else if (isP2WSH) {
|
1694 | meaningfulScript = witnessScript;
|
1695 | checkWitnessScript(index, script, witnessScript, ioType);
|
1696 | checkInvalidP2WSH(meaningfulScript);
|
1697 | } else if (isP2SH) {
|
1698 | meaningfulScript = redeemScript;
|
1699 | checkRedeemScript(index, script, redeemScript, ioType);
|
1700 | } else {
|
1701 | meaningfulScript = script;
|
1702 | }
|
1703 | return {
|
1704 | meaningfulScript,
|
1705 | type: isP2SHP2WSH
|
1706 | ? 'p2sh-p2wsh'
|
1707 | : isP2SH
|
1708 | ? 'p2sh'
|
1709 | : isP2WSH
|
1710 | ? 'p2wsh'
|
1711 | : 'raw',
|
1712 | };
|
1713 | }
|
1714 | function checkInvalidP2WSH(script) {
|
1715 | if (
|
1716 | (0, psbtutils_1.isP2WPKH)(script) ||
|
1717 | (0, psbtutils_1.isP2SHScript)(script)
|
1718 | ) {
|
1719 | throw new Error('P2WPKH or P2SH can not be contained within P2WSH');
|
1720 | }
|
1721 | }
|
1722 | function classifyScript(script) {
|
1723 | if ((0, psbtutils_1.isP2WPKH)(script)) return 'witnesspubkeyhash';
|
1724 | if ((0, psbtutils_1.isP2PKH)(script)) return 'pubkeyhash';
|
1725 | if ((0, psbtutils_1.isP2MS)(script)) return 'multisig';
|
1726 | if ((0, psbtutils_1.isP2PK)(script)) return 'pubkey';
|
1727 | return 'nonstandard';
|
1728 | }
|
1729 | function range(n) {
|
1730 | return [...Array(n).keys()];
|
1731 | }
|