Skip to content

Commit f222447

Browse files
committed
Add CSV example for custom finalizer
1 parent 4b5a519 commit f222447

File tree

1 file changed

+109
-27
lines changed

1 file changed

+109
-27
lines changed

test/integration/csv.spec.ts

+109-27
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import * as assert from 'assert';
2+
import { PsbtInput } from 'bip174/src/lib/interfaces';
23
import { before, describe, it } from 'mocha';
34
import * as bitcoin from '../..';
45
import { regtestUtils } from './_regtest';
56
const regtest = regtestUtils.network;
67
const bip68 = require('bip68');
8+
const varuint = require('varuint-bitcoin');
79

810
function toOutputScript(address: string): Buffer {
911
return bitcoin.address.toOutputScript(address, regtest);
@@ -129,33 +131,28 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
129131

130132
// fund the P2SH(CSV) address
131133
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();
159156

160157
// TODO: test that it failures _prior_ to expiry, unfortunately, race conditions when run concurrently
161158
// ...
@@ -430,3 +427,88 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
430427
},
431428
);
432429
});
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

Comments
 (0)