|
1 | 1 | import * as assert from 'assert';
|
| 2 | +import { PsbtInput } from 'bip174/src/lib/interfaces'; |
2 | 3 | import { before, describe, it } from 'mocha';
|
3 | 4 | import * as bitcoin from '../..';
|
4 | 5 | import { regtestUtils } from './_regtest';
|
5 | 6 | const regtest = regtestUtils.network;
|
6 | 7 | const bip68 = require('bip68');
|
| 8 | +const varuint = require('varuint-bitcoin'); |
7 | 9 |
|
8 | 10 | function toOutputScript(address: string): Buffer {
|
9 | 11 | return bitcoin.address.toOutputScript(address, regtest);
|
@@ -129,33 +131,28 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
129 | 131 |
|
130 | 132 | // fund the P2SH(CSV) address
|
131 | 133 | const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
|
132 |
| - |
133 |
| - const tx = new bitcoin.Transaction(); |
134 |
| - tx.version = 2; |
135 |
| - tx.addInput(idToHash(unspent.txId), unspent.vout, sequence); |
136 |
| - tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4); |
137 |
| - |
138 |
| - // {Alice's signature} OP_TRUE |
139 |
| - const signatureHash = tx.hashForSignature( |
140 |
| - 0, |
141 |
| - p2sh.redeem!.output!, |
142 |
| - hashType, |
143 |
| - ); |
144 |
| - const redeemScriptSig = bitcoin.payments.p2sh({ |
145 |
| - network: regtest, |
146 |
| - redeem: { |
147 |
| - network: regtest, |
148 |
| - output: p2sh.redeem!.output, |
149 |
| - input: bitcoin.script.compile([ |
150 |
| - bitcoin.script.signature.encode( |
151 |
| - alice.sign(signatureHash), |
152 |
| - hashType, |
153 |
| - ), |
154 |
| - bitcoin.opcodes.OP_TRUE, |
155 |
| - ]), |
156 |
| - }, |
157 |
| - }).input; |
158 |
| - tx.setInputScript(0, redeemScriptSig!); |
| 134 | + const utx = await regtestUtils.fetch(unspent.txId); |
| 135 | + // for non segwit inputs, you must pass the full transaction buffer |
| 136 | + const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex'); |
| 137 | + |
| 138 | + // This is an example of using the finalizeInput second parameter to |
| 139 | + // define how you finalize the inputs, allowing for any type of script. |
| 140 | + const tx = new bitcoin.Psbt({ network: regtest }) |
| 141 | + .setVersion(2) |
| 142 | + .addInput({ |
| 143 | + hash: unspent.txId, |
| 144 | + index: unspent.vout, |
| 145 | + sequence, |
| 146 | + redeemScript: p2sh.redeem!.output!, |
| 147 | + nonWitnessUtxo, |
| 148 | + }) |
| 149 | + .addOutput({ |
| 150 | + address: regtestUtils.RANDOM_ADDRESS, |
| 151 | + value: 7e4, |
| 152 | + }) |
| 153 | + .signInput(0, alice) |
| 154 | + .finalizeInput(0, csvGetFinalScripts) // See csvGetFinalScripts below |
| 155 | + .extractTransaction(); |
159 | 156 |
|
160 | 157 | // TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
|
161 | 158 | // ...
|
@@ -430,3 +427,88 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
|
430 | 427 | },
|
431 | 428 | );
|
432 | 429 | });
|
| 430 | + |
| 431 | +// This function is used to finalize a CSV transaction using PSBT. |
| 432 | +// See first test above. |
| 433 | +function csvGetFinalScripts( |
| 434 | + inputIndex: number, |
| 435 | + input: PsbtInput, |
| 436 | + script: Buffer, |
| 437 | + isSegwit: boolean, |
| 438 | + isP2SH: boolean, |
| 439 | + isP2WSH: boolean, |
| 440 | +): { |
| 441 | + finalScriptSig: Buffer | undefined; |
| 442 | + finalScriptWitness: Buffer | undefined; |
| 443 | +} { |
| 444 | + // Step 1: Check to make sure the meaningful script matches what you expect. |
| 445 | + const decompiled = bitcoin.script.decompile(script); |
| 446 | + // Checking if first OP is OP_IF... should do better check in production! |
| 447 | + // You may even want to check the public keys in the script against a |
| 448 | + // whitelist depending on the circumstances!!! |
| 449 | + // You also want to check the contents of the input to see if you have enough |
| 450 | + // info to actually construct the scriptSig and Witnesses. |
| 451 | + if (!decompiled || decompiled[0] !== bitcoin.opcodes.OP_IF) { |
| 452 | + throw new Error(`Can not finalize input #${inputIndex}`); |
| 453 | + } |
| 454 | + |
| 455 | + // Step 2: Create final scripts |
| 456 | + let payment: bitcoin.Payment = { |
| 457 | + network: regtest, |
| 458 | + output: script, |
| 459 | + // This logic should be more strict and make sure the pubkeys in the |
| 460 | + // meaningful script are the ones signing in the PSBT etc. |
| 461 | + input: bitcoin.script.compile([ |
| 462 | + input.partialSig![0].signature, |
| 463 | + bitcoin.opcodes.OP_TRUE, |
| 464 | + ]), |
| 465 | + }; |
| 466 | + if (isP2WSH && isSegwit) |
| 467 | + payment = bitcoin.payments.p2wsh({ |
| 468 | + network: regtest, |
| 469 | + redeem: payment, |
| 470 | + }); |
| 471 | + if (isP2SH) |
| 472 | + payment = bitcoin.payments.p2sh({ |
| 473 | + network: regtest, |
| 474 | + redeem: payment, |
| 475 | + }); |
| 476 | + |
| 477 | + function witnessStackToScriptWitness(witness: Buffer[]): Buffer { |
| 478 | + let buffer = Buffer.allocUnsafe(0); |
| 479 | + |
| 480 | + function writeSlice(slice: Buffer): void { |
| 481 | + buffer = Buffer.concat([buffer, Buffer.from(slice)]); |
| 482 | + } |
| 483 | + |
| 484 | + function writeVarInt(i: number): void { |
| 485 | + const currentLen = buffer.length; |
| 486 | + const varintLen = varuint.encodingLength(i); |
| 487 | + |
| 488 | + buffer = Buffer.concat([buffer, Buffer.allocUnsafe(varintLen)]); |
| 489 | + varuint.encode(i, buffer, currentLen); |
| 490 | + } |
| 491 | + |
| 492 | + function writeVarSlice(slice: Buffer): void { |
| 493 | + writeVarInt(slice.length); |
| 494 | + writeSlice(slice); |
| 495 | + } |
| 496 | + |
| 497 | + function writeVector(vector: Buffer[]): void { |
| 498 | + writeVarInt(vector.length); |
| 499 | + vector.forEach(writeVarSlice); |
| 500 | + } |
| 501 | + |
| 502 | + writeVector(witness); |
| 503 | + |
| 504 | + return buffer; |
| 505 | + } |
| 506 | + |
| 507 | + return { |
| 508 | + finalScriptSig: payment.input, |
| 509 | + finalScriptWitness: |
| 510 | + payment.witness && payment.witness.length > 0 |
| 511 | + ? witnessStackToScriptWitness(payment.witness) |
| 512 | + : undefined, |
| 513 | + }; |
| 514 | +} |
0 commit comments