Remove TransactionBuilder from tests (besides transaction_builder.spec.ts)

This commit is contained in:
junderw 2019-09-12 13:15:52 +09:00
parent 603b0eb544
commit f376913a4c
No known key found for this signature in database
GPG key ID: B256185D3A971908
6 changed files with 623 additions and 1046 deletions

View file

@ -85,14 +85,6 @@ The below examples are implemented as integration tests, they should be very eas
Otherwise, pull requests are appreciated. Otherwise, pull requests are appreciated.
Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP). Some examples interact (via HTTPS) with a 3rd Party Blockchain Provider (3PBP).
### Warning: Currently the tests use TransactionBuilder, which will be removed in the future (v6.x.x or higher)
We will move towards replacing all instances of TransactionBuilder in the tests with the new Psbt.
Currently we have a few examples on how to use the newer Psbt class at the following link:
- [Psbt examples](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions-psbt.spec.ts)
The rest of the examples are below (using TransactionBuilder for Transaction creation)
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a 2-of-3 P2SH multisig address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
@ -104,7 +96,6 @@ The rest of the examples are below (using TransactionBuilder for Transaction cre
- [Generate a Testnet address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a Testnet address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Generate a Litecoin address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts) - [Generate a Litecoin address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/addresses.spec.ts)
- [Create a 1-to-1 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create a 1-to-1 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create a 2-to-2 Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create (and broadcast via 3PBP) a typical Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a typical Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create (and broadcast via 3PBP) a Transaction with an OP\_RETURN output](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with an OP\_RETURN output](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a 2-of-4 P2SH(multisig) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
@ -112,7 +103,7 @@ The rest of the examples are below (using TransactionBuilder for Transaction cre
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2WPKH input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a SegWit P2WPKH input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create (and broadcast via 3PBP) a Transaction with a SegWit P2PK input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a SegWit P2PK input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction with a SegWit 3-of-4 P2SH(P2WSH(multisig)) input](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Verify a Transaction signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts) - [Create (and broadcast via 3PBP) a Transaction and sign with an HDSigner interface (bip32)](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/transactions.spec.ts)
- [Import a BIP32 testnet xpriv and export to WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) - [Import a BIP32 testnet xpriv and export to WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts)
- [Export a BIP32 xpriv, then import it](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) - [Export a BIP32 xpriv, then import it](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts)
- [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts) - [Export a BIP32 xpub](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/bip32.spec.ts)

View file

@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest';
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
const bip65 = require('bip65'); const bip65 = require('bip65');
function toOutputScript(address: string): Buffer {
return bitcoin.address.toOutputScript(address, regtest);
}
function idToHash(txid: string): Buffer {
return Buffer.from(txid, 'hex').reverse() as Buffer;
}
const alice = bitcoin.ECPair.fromWIF( const alice = bitcoin.ECPair.fromWIF(
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
regtest, regtest,
@ -13,7 +21,6 @@ const bob = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x',
regtest, regtest,
); );
console.warn = () => {}; // Silence the Deprecation Warning
describe('bitcoinjs-lib (transactions w/ CLTV)', () => { describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// force update MTP // force update MTP
@ -59,14 +66,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address!, 1e5); const unspent = await regtestUtils.faucet(address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.setLockTime(lockTime); tx.locktime = lockTime;
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// {Alice's signature} OP_TRUE // {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType); const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({ const redeemScriptSig = bitcoin.payments.p2sh({
redeem: { redeem: {
@ -102,14 +108,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address!, 1e5); const unspent = await regtestUtils.faucet(address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.setLockTime(lockTime); tx.locktime = lockTime;
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// {Alice's signature} OP_TRUE // {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType); const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({ const redeemScriptSig = bitcoin.payments.p2sh({
redeem: { redeem: {
@ -147,14 +152,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address!, 2e5); const unspent = await regtestUtils.faucet(address!, 2e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.setLockTime(lockTime); tx.locktime = lockTime;
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 8e4);
// {Alice's signature} {Bob's signature} OP_FALSE // {Alice's signature} {Bob's signature} OP_FALSE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType); const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({ const redeemScriptSig = bitcoin.payments.p2sh({
redeem: { redeem: {
@ -189,14 +193,13 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', () => {
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
const unspent = await regtestUtils.faucet(address!, 2e4); const unspent = await regtestUtils.faucet(address!, 2e4);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.setLockTime(lockTime); tx.locktime = lockTime;
// Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable. // Note: nSequence MUST be <= 0xfffffffe otherwise LockTime is ignored, and is immediately spendable.
txb.addInput(unspent.txId, unspent.vout, 0xfffffffe); tx.addInput(idToHash(unspent.txId), unspent.vout, 0xfffffffe);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4);
// {Alice's signature} OP_TRUE // {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature(0, redeemScript, hashType); const signatureHash = tx.hashForSignature(0, redeemScript, hashType);
const redeemScriptSig = bitcoin.payments.p2sh({ const redeemScriptSig = bitcoin.payments.p2sh({
redeem: { redeem: {

View file

@ -5,6 +5,14 @@ import { regtestUtils } from './_regtest';
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
const bip68 = require('bip68'); const bip68 = require('bip68');
function toOutputScript(address: string): Buffer {
return bitcoin.address.toOutputScript(address, regtest);
}
function idToHash(txid: string): Buffer {
return Buffer.from(txid, 'hex').reverse() as Buffer;
}
const alice = bitcoin.ECPair.fromWIF( const alice = bitcoin.ECPair.fromWIF(
'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', 'cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe',
regtest, regtest,
@ -21,7 +29,6 @@ const dave = bitcoin.ECPair.fromWIF(
'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx', 'cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsMwS4pqnx',
regtest, regtest,
); );
console.warn = () => {}; // Silence the Deprecation Warning
describe('bitcoinjs-lib (transactions w/ CSV)', () => { describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// force update MTP // force update MTP
@ -111,12 +118,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// fund the P2SH(CSV) address // fund the P2SH(CSV) address
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.addInput(unspent.txId, unspent.vout, sequence); tx.version = 2;
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addInput(idToHash(unspent.txId), unspent.vout, sequence);
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// {Alice's signature} OP_TRUE // {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature( const signatureHash = tx.hashForSignature(
0, 0,
p2sh.redeem!.output!, p2sh.redeem!.output!,
@ -164,12 +171,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// fund the P2SH(CSV) address // fund the P2SH(CSV) address
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4); const unspent = await regtestUtils.faucet(p2sh.address!, 2e4);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.addInput(unspent.txId, unspent.vout, sequence); tx.version = 2;
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); tx.addInput(idToHash(unspent.txId), unspent.vout, sequence);
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 1e4);
// {Alice's signature} OP_TRUE // {Alice's signature} OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature( const signatureHash = tx.hashForSignature(
0, 0,
p2sh.redeem!.output!, p2sh.redeem!.output!,
@ -219,12 +226,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// fund the P2SH(CCSV) address // fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.addInput(unspent.txId, unspent.vout); tx.version = 2;
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addInput(idToHash(unspent.txId), unspent.vout);
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE // OP_0 {Bob sig} {Charles sig} OP_TRUE OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature( const signatureHash = tx.hashForSignature(
0, 0,
p2sh.redeem!.output!, p2sh.redeem!.output!,
@ -282,12 +289,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// fund the P2SH(CCSV) address // fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.addInput(unspent.txId, unspent.vout, sequence1); // Set sequence1 for input tx.version = 2;
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addInput(idToHash(unspent.txId), unspent.vout, sequence1); // Set sequence1 for input
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE // OP_0 {Bob sig} {Alice mediator sig} OP_FALSE OP_TRUE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature( const signatureHash = tx.hashForSignature(
0, 0,
p2sh.redeem!.output!, p2sh.redeem!.output!,
@ -345,12 +352,12 @@ describe('bitcoinjs-lib (transactions w/ CSV)', () => {
// fund the P2SH(CCSV) address // fund the P2SH(CCSV) address
const unspent = await regtestUtils.faucet(p2sh.address!, 1e5); const unspent = await regtestUtils.faucet(p2sh.address!, 1e5);
const txb = new bitcoin.TransactionBuilder(regtest); const tx = new bitcoin.Transaction();
txb.addInput(unspent.txId, unspent.vout, sequence2); // Set sequence2 for input tx.version = 2;
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4); tx.addInput(idToHash(unspent.txId), unspent.vout, sequence2); // Set sequence2 for input
tx.addOutput(toOutputScript(regtestUtils.RANDOM_ADDRESS), 7e4);
// {Alice mediator sig} OP_FALSE // {Alice mediator sig} OP_FALSE
const tx = txb.buildIncomplete();
const signatureHash = tx.hashForSignature( const signatureHash = tx.hashForSignature(
0, 0,
p2sh.redeem!.output!, p2sh.redeem!.output!,

View file

@ -6,7 +6,6 @@ const keyPairs = [
bitcoin.ECPair.makeRandom({ network: NETWORK }), bitcoin.ECPair.makeRandom({ network: NETWORK }),
bitcoin.ECPair.makeRandom({ network: NETWORK }), bitcoin.ECPair.makeRandom({ network: NETWORK }),
]; ];
console.warn = () => {}; // Silence the Deprecation Warning
async function buildAndSign( async function buildAndSign(
depends: any, depends: any,
@ -15,37 +14,35 @@ async function buildAndSign(
witnessScript: any, witnessScript: any,
) { ) {
const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4); const unspent = await regtestUtils.faucetComplex(prevOutput, 5e4);
const utx = await regtestUtils.fetch(unspent.txId);
const txb = new bitcoin.TransactionBuilder(NETWORK); const psbt = new bitcoin.Psbt({ network: NETWORK })
txb.addInput(unspent.txId, unspent.vout, undefined, prevOutput); .addInput({
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); hash: unspent.txId,
index: unspent.vout,
const posType = depends.prevOutScriptType; nonWitnessUtxo: Buffer.from(utx.txHex, 'hex'),
const needsValue = !!witnessScript || posType.slice(-6) === 'p2wpkh'; ...(redeemScript ? { redeemScript } : {}),
...(witnessScript ? { witnessScript } : {}),
})
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
});
if (depends.signatures) { if (depends.signatures) {
keyPairs.forEach(keyPair => { keyPairs.forEach(keyPair => {
txb.sign({ psbt.signInput(0, keyPair);
prevOutScriptType: posType,
vin: 0,
keyPair,
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
});
}); });
} else if (depends.signature) { } else if (depends.signature) {
txb.sign({ psbt.signInput(0, keyPairs[0]);
prevOutScriptType: posType,
vin: 0,
keyPair: keyPairs[0],
redeemScript,
witnessValue: needsValue ? unspent.value : undefined,
witnessScript,
});
} }
return regtestUtils.broadcast(txb.build().toHex()); return regtestUtils.broadcast(
psbt
.finalizeAllInputs()
.extractTransaction()
.toHex(),
);
} }
['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => { ['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach(k => {

View file

@ -1,669 +0,0 @@
import * as assert from 'assert';
import { describe, it } from 'mocha';
import * as bitcoin from '../..';
import { regtestUtils } from './_regtest';
import * as bip32 from 'bip32';
const rng = require('randombytes');
const regtest = regtestUtils.network;
// See bottom of file for some helper functions used to make the payment objects needed.
describe('bitcoinjs-lib (transactions with psbt)', () => {
it('can create a 1-to-1 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF(
'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr',
);
const psbt = new bitcoin.Psbt();
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0); // These are defaults. This line is not needed.
psbt.addInput({
// if hash is string, txid, if hash is Buffer, is reversed compared to txid
hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
index: 0,
sequence: 0xffffffff, // These are defaults. This line is not needed.
// non-segwit inputs now require passing the whole previous tx as Buffer
nonWitnessUtxo: Buffer.from(
'0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
'452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
'9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
'631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
// value in satoshis (Int64LE) = 0x015f90 = 90000
'905f010000000000' +
// scriptPubkey length
'19' +
// scriptPubkey
'76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' +
// locktime
'00000000',
'hex',
),
// // If this input was segwit, instead of nonWitnessUtxo, you would add
// // a witnessUtxo as follows. The scriptPubkey and the value only are needed.
// witnessUtxo: {
// script: Buffer.from(
// '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac',
// 'hex',
// ),
// value: 90000,
// },
// Not featured here:
// redeemScript. A Buffer of the redeemScript for P2SH
// witnessScript. A Buffer of the witnessScript for P2WSH
});
psbt.addOutput({
address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
value: 80000,
});
psbt.signInput(0, alice);
psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
assert.strictEqual(
psbt.extractTransaction().toHex(),
'02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
'9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
'08a22724efa6f6a07b0ec4c79aa88ac00000000',
);
});
it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
// these are { payment: Payment; keys: ECPair[] }
const alice1 = createPayment('p2pkh');
const alice2 = createPayment('p2pkh');
// give Alice 2 unspent outputs
const inputData1 = await getInputData(
5e4,
alice1.payment,
false,
'noredeem',
);
const inputData2 = await getInputData(
7e4,
alice2.payment,
false,
'noredeem',
);
{
const {
hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order)
index, // the output index of the txo you are spending
nonWitnessUtxo, // the full previous transaction as a Buffer
} = inputData1;
assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1);
}
// network is only needed if you pass an address to addOutput
// using script (Buffer of scriptPubkey) instead will avoid needed network.
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1) // alice1 unspent
.addInput(inputData2) // alice2 unspent
.addOutput({
address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf',
value: 8e4,
}) // the actual "spend"
.addOutput({
address: alice2.payment.address, // OR script, which is a Buffer.
value: 1e4,
}); // Alice's change
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Let's show a new feature with PSBT.
// We can have multiple signers sign in parrallel and combine them.
// (this is not necessary, but a nice feature)
// encode to send out to the signers
const psbtBaseText = psbt.toBase64();
// each signer imports
const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText);
const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText);
// Alice signs each input with the respective private keys
// signInput and signInputAsync are better
// (They take the input index explicitly as the first arg)
signer1.signAllInputs(alice1.keys[0]);
signer2.signAllInputs(alice2.keys[0]);
// If your signer object's sign method returns a promise, use the following
// await signer2.signAllInputsAsync(alice2.keys[0])
// encode to send back to combiner (signer 1 and 2 are not near each other)
const s1text = signer1.toBase64();
const s2text = signer2.toBase64();
const final1 = bitcoin.Psbt.fromBase64(s1text);
const final2 = bitcoin.Psbt.fromBase64(s2text);
// final1.combine(final2) would give the exact same result
psbt.combine(final1, final2);
// Finalizer wants to check all signatures are valid before finalizing.
// If the finalizer wants to check for specific pubkeys, the second arg
// can be passed. See the first multisig example below.
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(psbt.validateSignaturesOfInput(1), true);
// This step it new. Since we separate the signing operation and
// the creation of the scriptSig and witness stack, we are able to
psbt.finalizeAllInputs();
// build and broadcast our RegTest network
await regtestUtils.broadcast(psbt.extractTransaction().toHex());
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
});
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
const alice1 = createPayment('p2pkh');
const inputData1 = await getInputData(
2e5,
alice1.payment,
false,
'noredeem',
);
const data = Buffer.from('bitcoinjs-lib', 'utf8');
const embed = bitcoin.payments.embed({ data: [data] });
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1)
.addOutput({
script: embed.output!,
value: 1000,
})
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 1e5,
})
.signInput(0, alice1.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
// build and broadcast to the RegTest network
await regtestUtils.broadcast(psbt.extractTransaction().toHex());
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
const multisig = createPayment('p2sh-p2ms(2 of 4)');
const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh');
{
const {
hash,
index,
nonWitnessUtxo,
redeemScript, // NEW: P2SH needs to give redeemScript when adding an input.
} = inputData1;
assert.deepStrictEqual(
{ hash, index, nonWitnessUtxo, redeemScript },
inputData1,
);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 1e4,
})
.signInput(0, multisig.keys[0])
.signInput(0, multisig.keys[2]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey),
true,
);
assert.throws(() => {
psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey);
}, new RegExp('No signatures for this pubkey'));
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 1e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
const p2sh = createPayment('p2sh-p2wpkh');
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh');
const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh');
{
const {
hash,
index,
witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; }
redeemScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, redeemScript },
inputData,
);
}
const keyPair = p2sh.keys[0];
const outputData = {
script: p2sh.payment.output, // sending to myself for fun
value: 2e4,
};
const outputData2 = {
script: p2sh.payment.output, // sending to myself for fun
value: 7e4,
};
const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: p2sh.payment.address,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2sh = createPayment('p2sh-p2wpkh');
const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh');
const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh');
const keyPair = p2sh.keys[0];
const outputData = {
script: p2sh.payment.output,
value: 2e4,
};
const outputData2 = {
script: p2sh.payment.output,
value: 7e4,
};
const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: p2sh.payment.address,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
// the only thing that changes is you don't give a redeemscript for input data
const p2wpkh = createPayment('p2wpkh');
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
{
const { hash, index, witnessUtxo } = inputData;
assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wpkh.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2wpkh = createPayment('p2wpkh');
const inputData = await getInputData(
5e4,
p2wpkh.payment,
false,
'noredeem',
);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wpkh.keys[0]);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
const p2wsh = createPayment('p2wsh-p2pk');
const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh');
{
const {
hash,
index,
witnessUtxo,
witnessScript, // NEW: A Buffer of the witnessScript
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, witnessScript },
inputData,
);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wsh.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2wsh = createPayment('p2wsh-p2pk');
const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh');
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wsh.keys[0]);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh');
{
const {
hash,
index,
witnessUtxo,
redeemScript,
witnessScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, redeemScript, witnessScript },
inputData,
);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2sh.keys[0])
.signInput(0, p2sh.keys[2])
.signInput(0, p2sh.keys[3]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey),
true,
);
assert.throws(() => {
psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey);
}, new RegExp('No signatures for this pubkey'));
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
const inputData = await getInputData(
5e4,
p2sh.payment,
false,
'p2sh-p2wsh',
);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2sh.keys[0])
.signInput(0, p2sh.keys[2])
.signInput(0, p2sh.keys[3]);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
const hdRoot = bip32.fromSeed(rng(64));
const masterFingerprint = hdRoot.fingerprint;
const path = "m/84'/0'/0'/0/0";
const childNode = hdRoot.derivePath(path);
const pubkey = childNode.publicKey;
// This information should be added to your input via updateInput
// You can add multiple bip32Derivation objects for multisig, but
// each must have a unique pubkey.
//
// This is useful because as long as you store the masterFingerprint on
// the PSBT Creator's server, you can have the PSBT Creator do the heavy
// lifting with derivation from your m/84'/0'/0' xpub, (deriving only 0/0 )
// and your signer just needs to pass in an HDSigner interface (ie. bip32 library)
const updateData = {
bip32Derivation: [
{
masterFingerprint,
path,
pubkey,
},
],
};
const p2wpkh = createPayment('p2wpkh', [childNode]);
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
{
const { hash, index, witnessUtxo } = inputData;
assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData);
}
// You can add extra attributes for updateData into the addInput(s) object(s)
Object.assign(inputData, updateData);
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
// .updateInput(0, updateData) // if you didn't merge the bip32Derivation with inputData
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInputHD(0, hdRoot); // must sign with root!!!
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, childNode.publicKey),
true,
);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
});
function createPayment(_type: string, myKeys?: any[], network?: any) {
network = network || regtest;
const splitType = _type.split('-').reverse();
const isMultisig = splitType[0].slice(0, 4) === 'p2ms';
const keys = myKeys || [];
let m: number | undefined;
if (isMultisig) {
const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/);
m = parseInt(match![1]);
let n = parseInt(match![2]);
if (keys.length > 0 && keys.length !== n) {
throw new Error('Need n keys for multisig');
}
while (!myKeys && n > 1) {
keys.push(bitcoin.ECPair.makeRandom({ network }));
n--;
}
}
if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network }));
let payment: any;
splitType.forEach(type => {
if (type.slice(0, 4) === 'p2ms') {
payment = bitcoin.payments.p2ms({
m,
pubkeys: keys.map(key => key.publicKey).sort(),
network,
});
} else if (['p2sh', 'p2wsh'].indexOf(type) > -1) {
payment = (bitcoin.payments as any)[type]({
redeem: payment,
network,
});
} else {
payment = (bitcoin.payments as any)[type]({
pubkey: keys[0].publicKey,
network,
});
}
});
return {
payment,
keys,
};
}
function getWitnessUtxo(out: any) {
delete out.address;
out.script = Buffer.from(out.script, 'hex');
return out;
}
async function getInputData(
amount: number,
payment: any,
isSegwit: boolean,
redeemType: string,
) {
const unspent = await regtestUtils.faucetComplex(payment.output, amount);
const utx = await regtestUtils.fetch(unspent.txId);
// for non segwit inputs, you must pass the full transaction buffer
const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex');
// for segwit inputs, you only need the output script and value as an object.
const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]);
const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo };
const mixin2: any = {};
switch (redeemType) {
case 'p2sh':
mixin2.redeemScript = payment.redeem.output;
break;
case 'p2wsh':
mixin2.witnessScript = payment.redeem.output;
break;
case 'p2sh-p2wsh':
mixin2.witnessScript = payment.redeem.redeem.output;
mixin2.redeemScript = payment.redeem.output;
break;
}
return {
hash: unspent.txId,
index: unspent.vout,
...mixin,
...mixin2,
};
}

View file

@ -2,190 +2,230 @@ import * as assert from 'assert';
import { describe, it } from 'mocha'; import { describe, it } from 'mocha';
import * as bitcoin from '../..'; import * as bitcoin from '../..';
import { regtestUtils } from './_regtest'; import { regtestUtils } from './_regtest';
import * as bip32 from 'bip32';
const rng = require('randombytes');
const regtest = regtestUtils.network; const regtest = regtestUtils.network;
console.warn = () => {}; // Silence the Deprecation Warning
function rng() { // See bottom of file for some helper functions used to make the payment objects needed.
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64');
}
describe('bitcoinjs-lib (transactions)', () => { describe('bitcoinjs-lib (transactions with psbt)', () => {
it('can create a 1-to-1 Transaction', () => { it('can create a 1-to-1 Transaction', () => {
const alice = bitcoin.ECPair.fromWIF( const alice = bitcoin.ECPair.fromWIF(
'L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy', 'L2uPYXe17xSTqbCjZvL2DsyXPCbXspvcu5mHLDYUgzdUbZGSKrSr',
); );
const txb = new bitcoin.TransactionBuilder(); const psbt = new bitcoin.Psbt();
psbt.setVersion(2); // These are defaults. This line is not needed.
psbt.setLocktime(0); // These are defaults. This line is not needed.
psbt.addInput({
// if hash is string, txid, if hash is Buffer, is reversed compared to txid
hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
index: 0,
sequence: 0xffffffff, // These are defaults. This line is not needed.
txb.setVersion(1); // non-segwit inputs now require passing the whole previous tx as Buffer
txb.addInput( nonWitnessUtxo: Buffer.from(
'61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
0, '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
); // Alice's previous transaction output, has 15000 satoshis 'ca17c162b1aca0a788ac3526f002207bb79b60d4fc6526329bf18a77135dc566020' +
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000); '9e761da46e1c2f1152ec013215801210211755115eabf846720f5cb18f248666fec' +
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee '631e5e1e66009ce3710ceea5b1ad13ffffffff01' +
// value in satoshis (Int64LE) = 0x015f90 = 90000
'905f010000000000' +
// scriptPubkey length
'19' +
// scriptPubkey
'76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac' +
// locktime
'00000000',
'hex',
),
txb.sign({ // // If this input was segwit, instead of nonWitnessUtxo, you would add
prevOutScriptType: 'p2pkh', // // a witnessUtxo as follows. The scriptPubkey and the value only are needed.
vin: 0, // witnessUtxo: {
keyPair: alice, // script: Buffer.from(
// '76a9148bbc95d2709c71607c60ee3f097c1217482f518d88ac',
// 'hex',
// ),
// value: 90000,
// },
// Not featured here:
// redeemScript. A Buffer of the redeemScript for P2SH
// witnessScript. A Buffer of the witnessScript for P2WSH
}); });
psbt.addOutput({
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below address: '1KRMKfeZcmosxALVYESdPNez1AP1mEtywp',
value: 80000,
});
psbt.signInput(0, alice);
psbt.validateSignaturesOfInput(0);
psbt.finalizeAllInputs();
assert.strictEqual( assert.strictEqual(
txb.build().toHex(), psbt.extractTransaction().toHex(),
'01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000', '02000000013ebc8203037dda39d482bf41ff3be955996c50d9d4f7cfc3d2097a694a7' +
); 'b067d000000006b483045022100931b6db94aed25d5486884d83fc37160f37f3368c0' +
}); 'd7f48c757112abefec983802205fda64cff98c849577026eb2ce916a50ea70626a766' +
'9f8596dd89b720a26b4d501210365db9da3f8a260078a7e8f8b708a1161468fb2323f' +
it('can create a 2-to-2 Transaction', () => { 'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
const alice = bitcoin.ECPair.fromWIF( '08a22724efa6f6a07b0ec4c79aa88ac00000000',
'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1',
);
const bob = bitcoin.ECPair.fromWIF(
'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z',
);
const txb = new bitcoin.TransactionBuilder();
txb.setVersion(1);
txb.addInput(
'b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c',
6,
); // Alice's previous transaction output, has 200000 satoshis
txb.addInput(
'7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730',
0,
); // Bob's previous transaction output, has 300000 satoshis
txb.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000);
txb.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000);
// (in)(200000 + 300000) - (out)(180000 + 170000) = (fee)150000, this is the miner fee
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 1,
keyPair: bob,
}); // Bob signs his input, which was the second input (1th)
txb.sign({
prevOutScriptType: 'p2pkh',
vin: 0,
keyPair: alice,
}); // Alice signs her input, which was the first input (0th)
// prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
assert.strictEqual(
txb.build().toHex(),
'01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000',
); );
}); });
it('can create (and broadcast via 3PBP) a typical Transaction', async () => { it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest }); // these are { payment: Payment; keys: ECPair[] }
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest }); const alice1 = createPayment('p2pkh');
const aliceChange = bitcoin.ECPair.makeRandom({ const alice2 = createPayment('p2pkh');
network: regtest,
rng: rng,
});
const alice1pkh = bitcoin.payments.p2pkh({
pubkey: alice1.publicKey,
network: regtest,
});
const alice2pkh = bitcoin.payments.p2pkh({
pubkey: alice2.publicKey,
network: regtest,
});
const aliceCpkh = bitcoin.payments.p2pkh({
pubkey: aliceChange.publicKey,
network: regtest,
});
// give Alice 2 unspent outputs // give Alice 2 unspent outputs
const unspent0 = await regtestUtils.faucet(alice1pkh.address!, 5e4); const inputData1 = await getInputData(
5e4,
alice1.payment,
false,
'noredeem',
);
const inputData2 = await getInputData(
7e4,
alice2.payment,
false,
'noredeem',
);
{
const {
hash, // string of txid or Buffer of tx hash. (txid and hash are reverse order)
index, // the output index of the txo you are spending
nonWitnessUtxo, // the full previous transaction as a Buffer
} = inputData1;
assert.deepStrictEqual({ hash, index, nonWitnessUtxo }, inputData1);
}
const unspent1 = await regtestUtils.faucet(alice2pkh.address!, 7e4); // network is only needed if you pass an address to addOutput
// using script (Buffer of scriptPubkey) instead will avoid needed network.
const txb = new bitcoin.TransactionBuilder(regtest); const psbt = new bitcoin.Psbt({ network: regtest })
txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent .addInput(inputData1) // alice1 unspent
txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent .addInput(inputData2) // alice2 unspent
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend" .addOutput({
txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change address: 'mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf',
value: 8e4,
}) // the actual "spend"
.addOutput({
address: alice2.payment.address, // OR script, which is a Buffer.
value: 1e4,
}); // Alice's change
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee // (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Let's show a new feature with PSBT.
// We can have multiple signers sign in parrallel and combine them.
// (this is not necessary, but a nice feature)
// encode to send out to the signers
const psbtBaseText = psbt.toBase64();
// each signer imports
const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText);
const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText);
// Alice signs each input with the respective private keys // Alice signs each input with the respective private keys
txb.sign({ // signInput and signInputAsync are better
prevOutScriptType: 'p2pkh', // (They take the input index explicitly as the first arg)
vin: 0, signer1.signAllInputs(alice1.keys[0]);
keyPair: alice1, signer2.signAllInputs(alice2.keys[0]);
});
txb.sign({ // If your signer object's sign method returns a promise, use the following
prevOutScriptType: 'p2pkh', // await signer2.signAllInputsAsync(alice2.keys[0])
vin: 1,
keyPair: alice2, // encode to send back to combiner (signer 1 and 2 are not near each other)
}); const s1text = signer1.toBase64();
const s2text = signer2.toBase64();
const final1 = bitcoin.Psbt.fromBase64(s1text);
const final2 = bitcoin.Psbt.fromBase64(s2text);
// final1.combine(final2) would give the exact same result
psbt.combine(final1, final2);
// Finalizer wants to check all signatures are valid before finalizing.
// If the finalizer wants to check for specific pubkeys, the second arg
// can be passed. See the first multisig example below.
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(psbt.validateSignaturesOfInput(1), true);
// This step it new. Since we separate the signing operation and
// the creation of the scriptSig and witness stack, we are able to
psbt.finalizeAllInputs();
// build and broadcast our RegTest network // build and broadcast our RegTest network
await regtestUtils.broadcast(txb.build().toHex()); await regtestUtils.broadcast(psbt.extractTransaction().toHex());
// to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839 // to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839
}); });
it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => { it('can create (and broadcast via 3PBP) a Transaction with an OP_RETURN output', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); const alice1 = createPayment('p2pkh');
const p2pkh = bitcoin.payments.p2pkh({ const inputData1 = await getInputData(
pubkey: keyPair.publicKey, 2e5,
network: regtest, alice1.payment,
}); false,
'noredeem',
);
const unspent = await regtestUtils.faucet(p2pkh.address!, 2e5);
const txb = new bitcoin.TransactionBuilder(regtest);
const data = Buffer.from('bitcoinjs-lib', 'utf8'); const data = Buffer.from('bitcoinjs-lib', 'utf8');
const embed = bitcoin.payments.embed({ data: [data] }); const embed = bitcoin.payments.embed({ data: [data] });
txb.addInput(unspent.txId, unspent.vout);
txb.addOutput(embed.output!, 1000); const psbt = new bitcoin.Psbt({ network: regtest })
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5); .addInput(inputData1)
txb.sign({ .addOutput({
prevOutScriptType: 'p2pkh', script: embed.output!,
vin: 0, value: 1000,
keyPair, })
}); .addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 1e5,
})
.signInput(0, alice1.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
// build and broadcast to the RegTest network // build and broadcast to the RegTest network
await regtestUtils.broadcast(txb.build().toHex()); await regtestUtils.broadcast(psbt.extractTransaction().toHex());
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2MS(2 of 4)) (multisig) input', async () => {
const keyPairs = [ const multisig = createPayment('p2sh-p2ms(2 of 4)');
bitcoin.ECPair.makeRandom({ network: regtest }), const inputData1 = await getInputData(2e4, multisig.payment, false, 'p2sh');
bitcoin.ECPair.makeRandom({ network: regtest }), {
bitcoin.ECPair.makeRandom({ network: regtest }), const {
bitcoin.ECPair.makeRandom({ network: regtest }), hash,
]; index,
const pubkeys = keyPairs.map(x => x.publicKey); nonWitnessUtxo,
const p2ms = bitcoin.payments.p2ms({ redeemScript, // NEW: P2SH needs to give redeemScript when adding an input.
m: 2, } = inputData1;
pubkeys: pubkeys, assert.deepStrictEqual(
network: regtest, { hash, index, nonWitnessUtxo, redeemScript },
}); inputData1,
const p2sh = bitcoin.payments.p2sh({ redeem: p2ms, network: regtest }); );
}
const unspent = await regtestUtils.faucet(p2sh.address!, 2e4); const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData1)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 1e4,
})
.signInput(0, multisig.keys[0])
.signInput(0, multisig.keys[2]);
const txb = new bitcoin.TransactionBuilder(regtest); assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
txb.addInput(unspent.txId, unspent.vout); assert.strictEqual(
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); psbt.validateSignaturesOfInput(0, multisig.keys[0].publicKey),
true,
);
assert.throws(() => {
psbt.validateSignaturesOfInput(0, multisig.keys[3].publicKey);
}, new RegExp('No signatures for this pubkey'));
psbt.finalizeAllInputs();
txb.sign({ const tx = psbt.extractTransaction();
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem!.output,
});
const tx = txb.build();
// build and broadcast to the Bitcoin RegTest network // build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex()); await regtestUtils.broadcast(tx.toHex());
@ -199,27 +239,101 @@ describe('bitcoinjs-lib (transactions)', () => {
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); const p2sh = createPayment('p2sh-p2wpkh');
const p2wpkh = bitcoin.payments.p2wpkh({ const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh');
pubkey: keyPair.publicKey, const inputData2 = await getInputData(5e4, p2sh.payment, true, 'p2sh');
network: regtest, {
const {
hash,
index,
witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; }
redeemScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, redeemScript },
inputData,
);
}
const keyPair = p2sh.keys[0];
const outputData = {
script: p2sh.payment.output, // sending to myself for fun
value: 2e4,
};
const outputData2 = {
script: p2sh.payment.output, // sending to myself for fun
value: 7e4,
};
const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: p2sh.payment.address,
vout: 0,
value: 2e4,
}); });
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest }); });
const unspent = await regtestUtils.faucet(p2sh.address!, 5e4); it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WPKH) input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
const txb = new bitcoin.TransactionBuilder(regtest); // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
txb.addInput(unspent.txId, unspent.vout); const p2sh = createPayment('p2sh-p2wpkh');
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); const inputData = await getInputData(5e4, p2sh.payment, false, 'p2sh');
txb.sign({ const inputData2 = await getInputData(5e4, p2sh.payment, false, 'p2sh');
prevOutScriptType: 'p2sh-p2wpkh', const keyPair = p2sh.keys[0];
vin: 0, const outputData = {
keyPair: keyPair, script: p2sh.payment.output,
redeemScript: p2sh.redeem!.output, value: 2e4,
witnessValue: unspent.value, };
const outputData2 = {
script: p2sh.payment.output,
value: 7e4,
};
const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: p2sh.payment.address,
vout: 0,
value: 2e4,
}); });
});
const tx = txb.build(); it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => {
// the only thing that changes is you don't give a redeemscript for input data
const p2wpkh = createPayment('p2wpkh');
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
{
const { hash, index, witnessUtxo } = inputData;
assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData);
}
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wpkh.keys[0]);
assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network // build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex()); await regtestUtils.broadcast(tx.toHex());
@ -232,30 +346,26 @@ describe('bitcoinjs-lib (transactions)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input with nonWitnessUtxo', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); // For learning purposes, ignore this test.
const p2wpkh = bitcoin.payments.p2wpkh({ // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
pubkey: keyPair.publicKey, const p2wpkh = createPayment('p2wpkh');
network: regtest, const inputData = await getInputData(
}); 5e4,
p2wpkh.payment,
const unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4); false,
'noredeem',
// XXX: build the Transaction w/ a P2WPKH input );
const txb = new bitcoin.TransactionBuilder(regtest); const psbt = new bitcoin.Psbt({ network: regtest })
txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript! .addInput(inputData)
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); .addOutput({
txb.sign({ address: regtestUtils.RANDOM_ADDRESS,
prevOutScriptType: 'p2wpkh', value: 2e4,
vin: 0, })
keyPair: keyPair, .signInput(0, p2wpkh.keys[0]);
witnessValue: unspent.value, psbt.finalizeAllInputs();
}); // NOTE: no redeem script const tx = psbt.extractTransaction();
const tx = txb.build();
// build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex()); await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({ await regtestUtils.verify({
txId: tx.getId(), txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS, address: regtestUtils.RANDOM_ADDRESS,
@ -265,29 +375,35 @@ describe('bitcoinjs-lib (transactions)', () => {
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); const p2wsh = createPayment('p2wsh-p2pk');
const p2pk = bitcoin.payments.p2pk({ const inputData = await getInputData(5e4, p2wsh.payment, true, 'p2wsh');
pubkey: keyPair.publicKey, {
network: regtest, const {
}); hash,
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest }); index,
witnessUtxo,
witnessScript, // NEW: A Buffer of the witnessScript
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, witnessScript },
inputData,
);
}
const unspent = await regtestUtils.faucetComplex(p2wsh.output!, 5e4); const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wsh.keys[0]);
// XXX: build the Transaction w/ a P2WSH input assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
const txb = new bitcoin.TransactionBuilder(regtest); psbt.finalizeAllInputs();
txb.addInput(unspent.txId, unspent.vout, undefined, p2wsh.output); // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
txb.sign({
prevOutScriptType: 'p2wsh-p2pk',
vin: 0,
keyPair: keyPair,
witnessValue: 5e4,
witnessScript: p2wsh.redeem!.output,
}); // NOTE: provide a witnessScript!
const tx = txb.build();
// build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network const tx = psbt.extractTransaction();
// build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex()); await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({ await regtestUtils.verify({
@ -298,50 +414,67 @@ describe('bitcoinjs-lib (transactions)', () => {
}); });
}); });
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input with nonWitnessUtxo', async () => {
// For learning purposes, ignore this test.
// REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const p2wsh = createPayment('p2wsh-p2pk');
const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh');
const psbt = new bitcoin.Psbt({ network: regtest })
.addInput(inputData)
.addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2wsh.keys[0]);
psbt.finalizeAllInputs();
const tx = psbt.extractTransaction();
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
});
});
it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input', async () => {
const keyPairs = [ const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
bitcoin.ECPair.makeRandom({ network: regtest }), const inputData = await getInputData(5e4, p2sh.payment, true, 'p2sh-p2wsh');
bitcoin.ECPair.makeRandom({ network: regtest }), {
bitcoin.ECPair.makeRandom({ network: regtest }), const {
bitcoin.ECPair.makeRandom({ network: regtest }), hash,
]; index,
const pubkeys = keyPairs.map(x => x.publicKey); witnessUtxo,
redeemScript,
witnessScript,
} = inputData;
assert.deepStrictEqual(
{ hash, index, witnessUtxo, redeemScript, witnessScript },
inputData,
);
}
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest }); const psbt = new bitcoin.Psbt({ network: regtest })
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest }); .addInput(inputData)
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest }); .addOutput({
address: regtestUtils.RANDOM_ADDRESS,
value: 2e4,
})
.signInput(0, p2sh.keys[0])
.signInput(0, p2sh.keys[2])
.signInput(0, p2sh.keys[3]);
const unspent = await regtestUtils.faucet(p2sh.address!, 6e4); assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
assert.strictEqual(
psbt.validateSignaturesOfInput(0, p2sh.keys[3].publicKey),
true,
);
assert.throws(() => {
psbt.validateSignaturesOfInput(0, p2sh.keys[1].publicKey);
}, new RegExp('No signatures for this pubkey'));
psbt.finalizeAllInputs();
const txb = new bitcoin.TransactionBuilder(regtest); const tx = psbt.extractTransaction();
txb.addInput(unspent.txId, unspent.vout, undefined, p2sh.output);
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4);
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[0],
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[2],
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem!.output,
});
txb.sign({
prevOutScriptType: 'p2sh-p2wsh-p2ms',
vin: 0,
keyPair: keyPairs[3],
redeemScript: p2sh.redeem!.output,
witnessValue: unspent.value,
witnessScript: p2wsh.redeem!.output,
});
const tx = txb.build();
// build and broadcast to the Bitcoin RegTest network // build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex()); await regtestUtils.broadcast(tx.toHex());
@ -350,72 +483,187 @@ describe('bitcoinjs-lib (transactions)', () => {
txId: tx.getId(), txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS, address: regtestUtils.RANDOM_ADDRESS,
vout: 0, vout: 0,
value: 3e4, value: 2e4,
}); });
}); });
it('can verify Transaction (P2PKH) signatures', () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
const txHex = // For learning purposes, ignore this test.
'010000000321c5f7e7bc98b3feda84aad36a5c99a02bcb8823a2f3eccbcd5da209698b5c20000000006b48304502210099e021772830207cf7c55b69948d3b16b4dcbf1f55a9cd80ebf8221a169735f9022064d33f11d62cd28240b3862afc0b901adc9f231c7124dd19bdb30367b61964c50121032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63dffffffff8a75ce85441ddb3f342708ee33cc8ed418b07d9ba9e0e7c4e1cccfe9f52d8a88000000006946304302207916c23dae212c95a920423902fa44e939fb3d542f4478a7b46e9cde53705800021f0d74e9504146e404c1b8f9cba4dff2d4782e3075491c9ed07ce4a7d1c4461a01210216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2affffffffdfef93f69fe32e944fad79fa8f882b3a155d80383252348caba1a77a5abbf7ef000000006b483045022100faa6e9ca289b46c64764a624c59ac30d9abcf1d4a04c4de9089e67cbe0d300a502206930afa683f6807502de5c2431bf9a1fd333c8a2910a76304df0f3d23d83443f0121039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18fffffffff01ff4b0000000000001976a9146c86476d1d85cd60116cd122a274e6a570a5a35c88acc96d0700'; // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
const keyPairs = [ const p2sh = createPayment('p2sh-p2wsh-p2ms(3 of 4)');
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d', const inputData = await getInputData(
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a', 5e4,
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f', p2sh.payment,
].map(q => { false,
return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')); 'p2sh-p2wsh',
}); );
const psbt = new bitcoin.Psbt({ network: regtest })
const tx = bitcoin.Transaction.fromHex(txHex); .addInput(inputData)
.addOutput({
tx.ins.forEach((input, i) => { address: regtestUtils.RANDOM_ADDRESS,
const keyPair = keyPairs[i]; value: 2e4,
const p2pkh = bitcoin.payments.p2pkh({ })
pubkey: keyPair.publicKey, .signInput(0, p2sh.keys[0])
input: input.script, .signInput(0, p2sh.keys[2])
}); .signInput(0, p2sh.keys[3]);
psbt.finalizeAllInputs();
const ss = bitcoin.script.signature.decode(p2pkh.signature!); const tx = psbt.extractTransaction();
const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType); await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
assert.strictEqual(keyPair.verify(hash, ss.signature), true); txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
}); });
}); });
it('can verify Transaction (P2SH(P2WPKH)) signatures', () => { it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
const utxos = { const hdRoot = bip32.fromSeed(rng(64));
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': { const masterFingerprint = hdRoot.fingerprint;
value: 50000, const path = "m/84'/0'/0'/0/0";
}, const childNode = hdRoot.derivePath(path);
const pubkey = childNode.publicKey;
// This information should be added to your input via updateInput
// You can add multiple bip32Derivation objects for multisig, but
// each must have a unique pubkey.
//
// This is useful because as long as you store the masterFingerprint on
// the PSBT Creator's server, you can have the PSBT Creator do the heavy
// lifting with derivation from your m/84'/0'/0' xpub, (deriving only 0/0 )
// and your signer just needs to pass in an HDSigner interface (ie. bip32 library)
const updateData = {
bip32Derivation: [
{
masterFingerprint,
path,
pubkey,
},
],
}; };
const p2wpkh = createPayment('p2wpkh', [childNode]);
const inputData = await getInputData(5e4, p2wpkh.payment, true, 'noredeem');
{
const { hash, index, witnessUtxo } = inputData;
assert.deepStrictEqual({ hash, index, witnessUtxo }, inputData);
}
const txHex = // You can add extra attributes for updateData into the addInput(s) object(s)
'02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'; Object.assign(inputData, updateData);
const tx = bitcoin.Transaction.fromHex(txHex);
tx.ins.forEach((input, i) => { const psbt = new bitcoin.Psbt({ network: regtest })
const txId = (Buffer.from(input.hash).reverse() as Buffer).toString( .addInput(inputData)
'hex', // .updateInput(0, updateData) // if you didn't merge the bip32Derivation with inputData
); .addOutput({
const utxo = (utxos as any)[`${txId}:${i}`]; address: regtestUtils.RANDOM_ADDRESS,
if (!utxo) throw new Error('Missing utxo'); value: 2e4,
})
.signInputHD(0, hdRoot); // must sign with root!!!
const p2sh = bitcoin.payments.p2sh({ assert.strictEqual(psbt.validateSignaturesOfInput(0), true);
input: input.script, assert.strictEqual(
witness: input.witness, psbt.validateSignaturesOfInput(0, childNode.publicKey),
}); true,
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem!); );
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }); // because P2WPKH is annoying psbt.finalizeAllInputs();
const ss = bitcoin.script.signature.decode(p2wpkh.signature!); const tx = psbt.extractTransaction();
const hash = tx.hashForWitnessV0(
i,
p2pkh.output!,
utxo.value,
ss.hashType,
);
const keyPair = bitcoin.ECPair.fromPublicKey(p2wpkh.pubkey!); // aka, cQ3EtF4mApRcogNGSeyPTKbmfxxn3Yfb1wecfKSws9a8bnYuxoAk
assert.strictEqual(keyPair.verify(hash, ss.signature), true); // build and broadcast to the Bitcoin RegTest network
await regtestUtils.broadcast(tx.toHex());
await regtestUtils.verify({
txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS,
vout: 0,
value: 2e4,
}); });
}); });
}); });
function createPayment(_type: string, myKeys?: any[], network?: any) {
network = network || regtest;
const splitType = _type.split('-').reverse();
const isMultisig = splitType[0].slice(0, 4) === 'p2ms';
const keys = myKeys || [];
let m: number | undefined;
if (isMultisig) {
const match = splitType[0].match(/^p2ms\((\d+) of (\d+)\)$/);
m = parseInt(match![1]);
let n = parseInt(match![2]);
if (keys.length > 0 && keys.length !== n) {
throw new Error('Need n keys for multisig');
}
while (!myKeys && n > 1) {
keys.push(bitcoin.ECPair.makeRandom({ network }));
n--;
}
}
if (!myKeys) keys.push(bitcoin.ECPair.makeRandom({ network }));
let payment: any;
splitType.forEach(type => {
if (type.slice(0, 4) === 'p2ms') {
payment = bitcoin.payments.p2ms({
m,
pubkeys: keys.map(key => key.publicKey).sort(),
network,
});
} else if (['p2sh', 'p2wsh'].indexOf(type) > -1) {
payment = (bitcoin.payments as any)[type]({
redeem: payment,
network,
});
} else {
payment = (bitcoin.payments as any)[type]({
pubkey: keys[0].publicKey,
network,
});
}
});
return {
payment,
keys,
};
}
function getWitnessUtxo(out: any) {
delete out.address;
out.script = Buffer.from(out.script, 'hex');
return out;
}
async function getInputData(
amount: number,
payment: any,
isSegwit: boolean,
redeemType: string,
) {
const unspent = await regtestUtils.faucetComplex(payment.output, amount);
const utx = await regtestUtils.fetch(unspent.txId);
// for non segwit inputs, you must pass the full transaction buffer
const nonWitnessUtxo = Buffer.from(utx.txHex, 'hex');
// for segwit inputs, you only need the output script and value as an object.
const witnessUtxo = getWitnessUtxo(utx.outs[unspent.vout]);
const mixin = isSegwit ? { witnessUtxo } : { nonWitnessUtxo };
const mixin2: any = {};
switch (redeemType) {
case 'p2sh':
mixin2.redeemScript = payment.redeem.output;
break;
case 'p2wsh':
mixin2.witnessScript = payment.redeem.output;
break;
case 'p2sh-p2wsh':
mixin2.witnessScript = payment.redeem.redeem.output;
mixin2.redeemScript = payment.redeem.output;
break;
}
return {
hash: unspent.txId,
index: unspent.vout,
...mixin,
...mixin2,
};
}