Browse Source

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

psbt-tx-getters
junderw 6 years ago
parent
commit
f376913a4c
No known key found for this signature in database GPG Key ID: B256185D3A971908
  1. 11
      README.md
  2. 45
      test/integration/cltv.spec.ts
  3. 49
      test/integration/csv.spec.ts
  4. 45
      test/integration/payments.spec.ts
  5. 669
      test/integration/transactions-psbt.spec.ts
  6. 866
      test/integration/transactions.spec.ts

11
README.md

@ -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)

45
test/integration/cltv.spec.ts

@ -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: {

49
test/integration/csv.spec.ts

@ -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!,

45
test/integration/payments.spec.ts

@ -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 => {

669
test/integration/transactions-psbt.spec.ts

@ -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,
};
}

866
test/integration/transactions.spec.ts

@ -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.
txb.setVersion(1); psbt.setLocktime(0); // These are defaults. This line is not needed.
txb.addInput( psbt.addInput({
'61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', // if hash is string, txid, if hash is Buffer, is reversed compared to txid
0, hash: '7d067b4a697a09d2c3cff7d4d9506c9955e93bff41bf82d439da7d030382bc3e',
); // Alice's previous transaction output, has 15000 satoshis index: 0,
txb.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000); sequence: 0xffffffff, // These are defaults. This line is not needed.
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
// non-segwit inputs now require passing the whole previous tx as Buffer
txb.sign({ nonWitnessUtxo: Buffer.from(
prevOutScriptType: 'p2pkh', '0200000001f9f34e95b9d5c8abcd20fc5bd4a825d1517be62f0f775e5f36da944d9' +
vin: 0, '452e550000000006b483045022100c86e9a111afc90f64b4904bd609e9eaed80d48' +
keyPair: alice, '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({
// 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' +
'fda5ec16b261ec1056f455ffffffff0180380100000000001976a914ca0d36044e0dc' +
'08a22724efa6f6a07b0ec4c79aa88ac00000000',
); );
}); });
it('can create a 2-to-2 Transaction', () => { it('can create (and broadcast via 3PBP) a typical Transaction', async () => {
const alice = bitcoin.ECPair.fromWIF( // these are { payment: Payment; keys: ECPair[] }
'L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1', const alice1 = createPayment('p2pkh');
const alice2 = createPayment('p2pkh');
// give Alice 2 unspent outputs
const inputData1 = await getInputData(
5e4,
alice1.payment,
false,
'noredeem',
); );
const bob = bitcoin.ECPair.fromWIF( const inputData2 = await getInputData(
'KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z', 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
const txb = new bitcoin.TransactionBuilder(); // Let's show a new feature with PSBT.
txb.setVersion(1); // We can have multiple signers sign in parrallel and combine them.
txb.addInput( // (this is not necessary, but a nice feature)
'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 () => { // encode to send out to the signers
const alice1 = bitcoin.ECPair.makeRandom({ network: regtest }); const psbtBaseText = psbt.toBase64();
const alice2 = bitcoin.ECPair.makeRandom({ network: regtest });
const aliceChange = bitcoin.ECPair.makeRandom({
network: regtest,
rng: rng,
});
const alice1pkh = bitcoin.payments.p2pkh({ // each signer imports
pubkey: alice1.publicKey, const signer1 = bitcoin.Psbt.fromBase64(psbtBaseText);
network: regtest, const signer2 = bitcoin.Psbt.fromBase64(psbtBaseText);
});
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 // Alice signs each input with the respective private keys
const unspent0 = await regtestUtils.faucet(alice1pkh.address!, 5e4); // 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]);
const unspent1 = await regtestUtils.faucet(alice2pkh.address!, 7e4); // If your signer object's sign method returns a promise, use the following
// await signer2.signAllInputsAsync(alice2.keys[0])
const txb = new bitcoin.TransactionBuilder(regtest); // encode to send back to combiner (signer 1 and 2 are not near each other)
txb.addInput(unspent0.txId, unspent0.vout); // alice1 unspent const s1text = signer1.toBase64();
txb.addInput(unspent1.txId, unspent1.vout); // alice2 unspent const s2text = signer2.toBase64();
txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4); // the actual "spend"
txb.addOutput(aliceCpkh.address!, 1e4); // Alice's change
// (in)(5e4 + 7e4) - (out)(8e4 + 1e4) = (fee)3e4 = 30000, this is the miner fee
// Alice signs each input with the respective private keys const final1 = bitcoin.Psbt.fromBase64(s1text);
txb.sign({ const final2 = bitcoin.Psbt.fromBase64(s2text);
prevOutScriptType: 'p2pkh',
vin: 0, // final1.combine(final2) would give the exact same result
keyPair: alice1, psbt.combine(final1, final2);
});
txb.sign({ // Finalizer wants to check all signatures are valid before finalizing.
prevOutScriptType: 'p2pkh', // If the finalizer wants to check for specific pubkeys, the second arg
vin: 1, // can be passed. See the first multisig example below.
keyPair: alice2, 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 })
const txb = new bitcoin.TransactionBuilder(regtest); .addInput(inputData1)
txb.addInput(unspent.txId, unspent.vout); .addOutput({
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4); 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();
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,61 +239,103 @@ 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 {
const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest }); hash,
index,
const unspent = await regtestUtils.faucet(p2sh.address!, 5e4); witnessUtxo, // NEW: this is an object of the output being spent { script: Buffer; value: Satoshis; }
redeemScript,
const txb = new bitcoin.TransactionBuilder(regtest); } = inputData;
txb.addInput(unspent.txId, unspent.vout); assert.deepStrictEqual(
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); { hash, index, witnessUtxo, redeemScript },
txb.sign({ inputData,
prevOutScriptType: 'p2sh-p2wpkh', );
vin: 0, }
keyPair: keyPair, const keyPair = p2sh.keys[0];
redeemScript: p2sh.redeem!.output, const outputData = {
witnessValue: unspent.value, 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 = txb.build(); const tx = new bitcoin.Psbt()
.addInputs([inputData, inputData2])
.addOutputs([outputData, outputData2])
.signAllInputs(keyPair)
.finalizeAllInputs()
.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());
await regtestUtils.verify({ await regtestUtils.verify({
txId: tx.getId(), txId: tx.getId(),
address: regtestUtils.RANDOM_ADDRESS, address: p2sh.payment.address,
vout: 0, vout: 0,
value: 2e4, value: 2e4,
}); });
}); });
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 P2SH(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 p2sh = createPayment('p2sh-p2wpkh');
network: regtest, 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 unspent = await regtestUtils.faucetComplex(p2wpkh.output!, 5e4); const tx = psbt.extractTransaction();
// XXX: build the Transaction w/ a P2WPKH input // build and broadcast to the Bitcoin RegTest network
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, undefined, p2wpkh.output); // NOTE: provide the prevOutScript!
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4);
txb.sign({
prevOutScriptType: 'p2wpkh',
vin: 0,
keyPair: keyPair,
witnessValue: unspent.value,
}); // NOTE: no redeem script
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({
@ -264,32 +346,26 @@ 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 P2WPKH input with nonWitnessUtxo', async () => {
const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }); // For learning purposes, ignore this test.
const p2pk = bitcoin.payments.p2pk({ // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
pubkey: keyPair.publicKey, const p2wpkh = createPayment('p2wpkh');
network: regtest, const inputData = await getInputData(
}); 5e4,
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest }); p2wpkh.payment,
false,
const unspent = await regtestUtils.faucetComplex(p2wsh.output!, 5e4); 'noredeem',
);
// XXX: build the Transaction w/ a P2WSH input const psbt = new bitcoin.Psbt({ network: regtest })
const txb = new bitcoin.TransactionBuilder(regtest); .addInput(inputData)
txb.addInput(unspent.txId, unspent.vout, undefined, p2wsh.output); // NOTE: provide the prevOutScript! .addOutput({
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4); address: regtestUtils.RANDOM_ADDRESS,
txb.sign({ value: 2e4,
prevOutScriptType: 'p2wsh-p2pk', })
vin: 0, .signInput(0, p2wpkh.keys[0]);
keyPair: keyPair, psbt.finalizeAllInputs();
witnessValue: 5e4, const tx = psbt.extractTransaction();
witnessScript: p2wsh.redeem!.output,
}); // NOTE: provide a witnessScript!
const tx = txb.build();
// build and broadcast (the P2WSH 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,
@ -298,50 +374,34 @@ describe('bitcoinjs-lib (transactions)', () => {
}); });
}); });
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 P2WSH(P2PK) input', async () => {
const keyPairs = [ const p2wsh = createPayment('p2wsh-p2pk');
bitcoin.ECPair.makeRandom({ network: regtest }), const inputData = await getInputData(5e4, p2wsh.payment, true, '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,
witnessScript, // NEW: A Buffer of the witnessScript
const p2ms = bitcoin.payments.p2ms({ m: 3, pubkeys, network: regtest }); } = inputData;
const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network: regtest }); assert.deepStrictEqual(
const p2sh = bitcoin.payments.p2sh({ redeem: p2wsh, network: regtest }); { hash, index, witnessUtxo, witnessScript },
inputData,
const unspent = await regtestUtils.faucet(p2sh.address!, 6e4); );
}
const txb = new bitcoin.TransactionBuilder(regtest);
txb.addInput(unspent.txId, unspent.vout, undefined, p2sh.output); const psbt = new bitcoin.Psbt({ network: regtest })
txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4); .addInput(inputData)
txb.sign({ .addOutput({
prevOutScriptType: 'p2sh-p2wsh-p2ms', address: regtestUtils.RANDOM_ADDRESS,
vin: 0, value: 2e4,
keyPair: keyPairs[0], })
redeemScript: p2sh.redeem!.output, .signInput(0, p2wsh.keys[0]);
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(); 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());
@ -350,72 +410,260 @@ 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 P2WSH(P2PK) 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 p2wsh = createPayment('p2wsh-p2pk');
'032b4c06c06c3ec0b7fa29519dfa5aae193ee2cc35ca127f29f14ec605d62fb63d', const inputData = await getInputData(5e4, p2wsh.payment, false, 'p2wsh');
'0216c92abe433106491bdeb4a261226f20f5a4ac86220cc6e37655aac6bf3c1f2a', const psbt = new bitcoin.Psbt({ network: regtest })
'039e05da8b8ea4f9868ecebb25998c7701542986233f4401799551fbecf316b18f', .addInput(inputData)
].map(q => { .addOutput({
return bitcoin.ECPair.fromPublicKey(Buffer.from(q, 'hex')); 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,
}); });
});
const tx = bitcoin.Transaction.fromHex(txHex); 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();
tx.ins.forEach((input, i) => { const tx = psbt.extractTransaction();
const keyPair = keyPairs[i];
const p2pkh = bitcoin.payments.p2pkh({
pubkey: keyPair.publicKey,
input: input.script,
});
const ss = bitcoin.script.signature.decode(p2pkh.signature!); // build and broadcast to the Bitcoin RegTest network
const hash = tx.hashForSignature(i, p2pkh.output!, ss.hashType); await regtestUtils.broadcast(tx.toHex());
assert.strictEqual(keyPair.verify(hash, ss.signature), true); await regtestUtils.verify({
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 P2SH(P2WSH(P2MS(3 of 4))) (SegWit multisig) input with nonWitnessUtxo', async () => {
const utxos = { // For learning purposes, ignore this test.
'f72d1d83ac40fcedd01415751556a905844ab5f44bbb7728565ebb91b1590109:0': { // REPEATING ABOVE BUT WITH nonWitnessUtxo by passing false to getInputData
value: 50000, 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,
});
});
const txHex = it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input using HD', async () => {
'02000000000101090159b191bb5e562877bb4bf4b54a8405a95615751514d0edfc40ac831d2df7000000001716001435a179e5516947a39ae9c8a25e9fe62c0fc598edffffffff01204e0000000000001976a91431d43308d3c886d53e9ae8a45728370571ff456988ac0247304402206ec41f685b997a51f325b07ee852e82a535f6b52ef54485cc133e05168aa052a022070bafa86108acb51c77b2b259ae8fb7fd1efa10fef804fcfe9b13c2db719acf5012103fb03e9d0a9af86cbed94225dbb8bb70f6b82109bce0a61ddcf41dab6cbb4871100000000'; const hdRoot = bip32.fromSeed(rng(64));
const tx = bitcoin.Transaction.fromHex(txHex); 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();
tx.ins.forEach((input, i) => { const tx = psbt.extractTransaction();
const txId = (Buffer.from(input.hash).reverse() as Buffer).toString(
'hex',
);
const utxo = (utxos as any)[`${txId}:${i}`];
if (!utxo) throw new Error('Missing utxo');
const p2sh = bitcoin.payments.p2sh({ // build and broadcast to the Bitcoin RegTest network
input: input.script, await regtestUtils.broadcast(tx.toHex());
witness: input.witness,
});
const p2wpkh = bitcoin.payments.p2wpkh(p2sh.redeem!);
const p2pkh = bitcoin.payments.p2pkh({ pubkey: p2wpkh.pubkey }); // because P2WPKH is annoying
const ss = bitcoin.script.signature.decode(p2wpkh.signature!);
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); 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,
};
}

Loading…
Cancel
Save