From 732df833466e1094d9a0c436f1acd6b1fa4c972c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 18 Jul 2018 14:00:53 +1000 Subject: [PATCH 1/7] tests/integration: simplify the bare witness examples --- test/integration/_regtest.js | 45 ++++++++++++---- test/integration/transactions.js | 92 +++++++++++--------------------- 2 files changed, 68 insertions(+), 69 deletions(-) diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js index f851d2c..0f39fad 100644 --- a/test/integration/_regtest.js +++ b/test/integration/_regtest.js @@ -4,6 +4,7 @@ const dhttp = require('dhttp/200') const APIPASS = process.env.APIPASS || 'satoshi' const APIURL = 'https://api.dcousens.cloud/1' +const NETWORK = bitcoin.networks.testnet function broadcast (txHex, callback) { dhttp({ @@ -42,6 +43,31 @@ function faucet (address, value, callback) { }) } +function faucetComplex (output, value, callback) { + const keyPair = bitcoin.ECPair.makeRandom({ network: NETWORK }) + const p2pkh = bitcoin.payments.p2pkh({ pubkey: keyPair.publicKey, network: NETWORK }) + + faucet(p2pkh.address, value * 2, (err, unspent) => { + if (err) return callback(err) + + const txvb = new bitcoin.TransactionBuilder(NETWORK) + txvb.addInput(unspent.txId, unspent.vout, null, p2pkh.output) + txvb.addOutput(output, value) + txvb.sign(0, keyPair) + const txv = txvb.build() + + broadcast(txv.toHex(), function (err) { + if (err) return callback(err) + + return callback(null, { + txId: txv.getId(), + vout: 0, + value + }) + }) + }) +} + function fetch (txId, callback) { dhttp({ method: 'GET', @@ -78,14 +104,15 @@ function randomAddress () { } module.exports = { - broadcast: broadcast, - faucet: faucet, - fetch: fetch, - height: height, - mine: mine, - network: bitcoin.networks.testnet, - unspents: unspents, - verify: verify, - randomAddress: randomAddress, + broadcast, + faucet, + faucetComplex, + fetch, + height, + mine, + network: NETWORK, + unspents, + verify, + randomAddress, RANDOM_ADDRESS: randomAddress() } diff --git a/test/integration/transactions.js b/test/integration/transactions.js index 73b83c5..ef95d53 100644 --- a/test/integration/transactions.js +++ b/test/integration/transactions.js @@ -171,91 +171,63 @@ describe('bitcoinjs-lib (transactions)', function () { }) }) - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input (via a P2SH(P2WPKH) transaction)', function (done) { + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WPKH input', function (done) { this.timeout(30000) const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) const p2wpkh = bitcoin.payments.p2wpkh({ pubkey: keyPair.publicKey, network: regtest }) - // prepare a P2SH(P2WPKH) faucet transaction, as Bitcoin-core doesn't support bare P2WPKH outputs (...yet) - const p2sh = bitcoin.payments.p2sh({ redeem: p2wpkh, network: regtest }) - - regtestUtils.faucet(p2sh.address, 10e4, function (err, unspent) { + regtestUtils.faucetComplex(p2wpkh.address, 5e4, function (err, unspent) { if (err) return done(err) - const txvb = new bitcoin.TransactionBuilder(regtest) - txvb.addInput(unspent.txId, unspent.vout) - txvb.addOutput(p2wpkh.address, 6e4) // funds a P2WPKH address - txvb.sign(0, keyPair, p2sh.redeem.output, null, unspent.value) - const txv = txvb.build() + // XXX: build the Transaction w/ a P2WPKH input + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, null, p2wpkh.output) // NOTE: provide the prevOutScript! + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + txb.sign(0, keyPair, null, null, unspent.value) // NOTE: no redeem script + const tx = txb.build() - // build and broadcast (the via transaction) to the Bitcoin RegTest network - regtestUtils.broadcast(txv.toHex(), function (err) { + // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network + regtestUtils.broadcast(tx.toHex(), function (err) { if (err) return done(err) - // XXX: build the Transaction w/ a P2WPKH input - const txb = new bitcoin.TransactionBuilder(regtest) - txb.addInput(txv.getId(), 0, null, p2wpkh.output) // NOTE: provide the prevOutScript! - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) - txb.sign(0, keyPair, null, null, 6e4) // NOTE: no redeem script - const tx = txb.build() - - // build and broadcast (the P2WPKH transaction) to the Bitcoin RegTest network - regtestUtils.broadcast(tx.toHex(), function (err) { - if (err) return done(err) - - regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4 - }, done) - }) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }, done) }) }) }) - it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input (via a P2SH(P2PK) transaction)', function (done) { + it('can create (and broadcast via 3PBP) a Transaction, w/ a P2WSH(P2PK) input', function (done) { this.timeout(30000) const keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) const p2pk = bitcoin.payments.p2pk({ pubkey: keyPair.publicKey, network: regtest }) const p2wsh = bitcoin.payments.p2wsh({ redeem: p2pk, network: regtest }) - // prepare a P2SH(P2PK) faucet transaction, as Bitcoin-core doesn't support bare P2WSH outputs (...yet) - const p2sh = bitcoin.payments.p2sh({ redeem: p2pk, network: regtest }) - - regtestUtils.faucet(p2sh.address, 10e4, function (err, unspent) { + regtestUtils.faucetComplex(p2wsh.address, 5e4, function (err, unspent) { if (err) return done(err) - const txvb = new bitcoin.TransactionBuilder(regtest) - txvb.addInput(unspent.txId, unspent.vout) - txvb.addOutput(p2wsh.address, 6e4) // funds a P2WPKH address - txvb.sign(0, keyPair, p2sh.redeem.output) - const txv = txvb.build() + // XXX: build the Transaction w/ a P2WSH input + const txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent.txId, unspent.vout, null, p2wsh.output) // NOTE: provide the prevOutScript! + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + txb.sign(0, keyPair, null, null, 5e4, p2wsh.redeem.output) // NOTE: provide a witnessScript! + const tx = txb.build() - // build and broadcast (the via transaction) to the Bitcoin RegTest network - regtestUtils.broadcast(txv.toHex(), function (err) { + // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network + regtestUtils.broadcast(tx.toHex(), function (err) { if (err) return done(err) - // XXX: build the Transaction w/ a P2WSH input - const txb = new bitcoin.TransactionBuilder(regtest) - txb.addInput(txv.getId(), 0, null, p2wsh.output) // NOTE: provide the prevOutScript! - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) - txb.sign(0, keyPair, null, null, 6e4, p2wsh.redeem.output) // NOTE: provide a witnessScript! - const tx = txb.build() - - // build and broadcast (the P2WSH transaction) to the Bitcoin RegTest network - regtestUtils.broadcast(tx.toHex(), function (err) { - if (err) return done(err) - - regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 2e4 - }, done) - }) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }, done) }) }) }) From b2d3a2cf3024364eb8e5645188658320e9914a62 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 20 Jul 2018 16:28:14 +1000 Subject: [PATCH 2/7] testing: add payments tests for each standard payment type --- test/integration/payments.js | 71 ++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 test/integration/payments.js diff --git a/test/integration/payments.js b/test/integration/payments.js new file mode 100644 index 0000000..0a164f9 --- /dev/null +++ b/test/integration/payments.js @@ -0,0 +1,71 @@ +/* global describe, it */ + +const bitcoin = require('../../') + +const regtestUtils = require('./_regtest') +const NETWORK = regtestUtils.network +const keyPairs = [ + bitcoin.ECPair.makeRandom({ network: NETWORK }), + bitcoin.ECPair.makeRandom({ network: NETWORK }) +] + +function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) { + regtestUtils.faucetComplex(prevOutput, 5e4, (err, unspent) => { + if (err) return done(err) + + const txb = new bitcoin.TransactionBuilder(NETWORK) + txb.addInput(unspent.txId, unspent.vout, null, prevOutput) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) + + if (depends.signatures) { + keyPairs.forEach((keyPair) => { + txb.sign(0, keyPair, redeemScript, null, unspent.value, witnessScript) + }) + } else if (depends.signature) { + txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript) + } + + regtestUtils.broadcast(txb.build().toHex(), done) + }) +} + +;['p2ms', 'p2pk', 'p2pkh', 'p2wpkh'].forEach((k) => { + const fixtures = require('../fixtures/' + k) + const { depends } = fixtures.dynamic + const fn = bitcoin.payments[k] + + const base = {} + if (depends.pubkey) base.pubkey = keyPairs[0].publicKey + if (depends.pubkeys) base.pubkeys = keyPairs.map(x => x.publicKey) + if (depends.m) base.m = base.pubkeys.length + + const { output } = fn(base) + if (!output) throw new TypeError('Missing output') + + describe('bitcoinjs-lib (payments - ' + k + ')', () => { + it('can broadcast as an output, and be spent as an input', (done) => { + buildAndSign(depends, output, null, null, done) + }) + + it('can (as P2SH(' + k + ')) broadcast as an output, and be spent as an input', (done) => { + const p2sh = bitcoin.payments.p2sh({ redeem: { output }, network: NETWORK }) + buildAndSign(depends, p2sh.output, p2sh.redeem.output, null, done) + }) + + it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', (done) => { + if (k === 'p2wpkh') return done() // skip P2WSH(P2WPKH) + + const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) + buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output, done) + }) + + it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', (done) => { + if (k === 'p2wpkh') return done() // skip P2SH(P2WSH(P2WPKH)) + + const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) + const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK }) + + buildAndSign(depends, p2sh.output, p2sh.redeem.output, p2wsh.redeem.output, done) + }) + }) +}) From faf3645361bb468f4544725911278aee46655937 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 20 Jul 2018 17:02:27 +1000 Subject: [PATCH 3/7] tests/integration: allow more time --- test/integration/payments.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/integration/payments.js b/test/integration/payments.js index 0a164f9..d657f3c 100644 --- a/test/integration/payments.js +++ b/test/integration/payments.js @@ -42,7 +42,9 @@ function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) { const { output } = fn(base) if (!output) throw new TypeError('Missing output') - describe('bitcoinjs-lib (payments - ' + k + ')', () => { + describe('bitcoinjs-lib (payments - ' + k + ')', function () { + this.timeout(30000) + it('can broadcast as an output, and be spent as an input', (done) => { buildAndSign(depends, output, null, null, done) }) From de0259a820cb759c0147da85dc6be79eafaa4830 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 20 Jul 2018 17:30:18 +1000 Subject: [PATCH 4/7] tests/integration/payments: enable failing P2SH(P2WSH(P2WPKH)) tests --- src/transaction_builder.js | 14 ++++++++++++-- test/integration/payments.js | 4 ---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/transaction_builder.js b/src/transaction_builder.js index c4724d0..4545107 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -232,6 +232,11 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri expanded.signatures = input.signatures } + let signScript = witnessScript + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output + } + return { redeemScript, redeemScriptType: SCRIPT_TYPES.P2WSH, @@ -243,7 +248,7 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri prevOutScript: p2sh.output, hasWitness: true, - signScript: witnessScript, + signScript, signType: expanded.type, pubkeys: expanded.pubkeys, @@ -303,6 +308,11 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri expanded.signatures = input.signatures } + let signScript = witnessScript + if (expanded.type === SCRIPT_TYPES.P2WPKH) { + signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output + } + return { witnessScript, witnessScriptType: expanded.type, @@ -311,7 +321,7 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri prevOutScript: p2wsh.output, hasWitness: true, - signScript: witnessScript, + signScript, signType: expanded.type, pubkeys: expanded.pubkeys, diff --git a/test/integration/payments.js b/test/integration/payments.js index d657f3c..c996f55 100644 --- a/test/integration/payments.js +++ b/test/integration/payments.js @@ -55,15 +55,11 @@ function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) { }) it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', (done) => { - if (k === 'p2wpkh') return done() // skip P2WSH(P2WPKH) - const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output, done) }) it('can (as P2SH(P2WSH(' + k + '))) broadcast as an output, and be spent as an input', (done) => { - if (k === 'p2wpkh') return done() // skip P2SH(P2WSH(P2WPKH)) - const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) const p2sh = bitcoin.payments.p2sh({ redeem: { output: p2wsh.output }, network: NETWORK }) From 31ab9bfc03b26fe73454d8c3b2b58cd23caee629 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Fri, 20 Jul 2018 17:35:34 +1000 Subject: [PATCH 5/7] tests/integration: throw verbose error if unspent is missing --- test/integration/_regtest.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js index 0f39fad..059cec2 100644 --- a/test/integration/_regtest.js +++ b/test/integration/_regtest.js @@ -38,7 +38,10 @@ function faucet (address, value, callback) { unspents(address, function (err, results) { if (err) return callback(err) - callback(null, results.filter(x => x.txId === txId).pop()) + const unspents = results.filter(x => x.txId === txId) + if (unspents.length === 0) return callback(new Error('Missing unspent')) + + callback(null, unspents.pop()) }) }) } From ca4c9ca64cf878fb703a0248e226c0076a06c654 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 23 Jul 2018 10:37:31 +1000 Subject: [PATCH 6/7] add note about P2WPKH in P2WSH as a consensus fail --- test/integration/payments.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/integration/payments.js b/test/integration/payments.js index c996f55..d319f60 100644 --- a/test/integration/payments.js +++ b/test/integration/payments.js @@ -54,6 +54,9 @@ function buildAndSign (depends, prevOutput, redeemScript, witnessScript, done) { buildAndSign(depends, p2sh.output, p2sh.redeem.output, null, done) }) + // NOTE: P2WPKH cannot be wrapped in P2WSH, consensus fail + if (k === 'p2wpkh') return + it('can (as P2WSH(' + k + ')) broadcast as an output, and be spent as an input', (done) => { const p2wsh = bitcoin.payments.p2wsh({ redeem: { output }, network: NETWORK }) buildAndSign(depends, p2wsh.output, null, p2wsh.redeem.output, done) From 079d83d887ef56ca4902f151a4c90ed01902623c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Mon, 23 Jul 2018 10:41:01 +1000 Subject: [PATCH 7/7] txbuilder: note consensus issue --- src/transaction_builder.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/transaction_builder.js b/src/transaction_builder.js index 4545107..8706841 100644 --- a/src/transaction_builder.js +++ b/src/transaction_builder.js @@ -233,9 +233,7 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri } let signScript = witnessScript - if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output - } + if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2SH(P2WSH(P2WPKH)) is a consensus failure') return { redeemScript, @@ -309,9 +307,7 @@ function prepareInput (input, ourPubKey, redeemScript, witnessValue, witnessScri } let signScript = witnessScript - if (expanded.type === SCRIPT_TYPES.P2WPKH) { - signScript = payments.p2pkh({ pubkey: expanded.pubkeys[0] }).output - } + if (expanded.type === SCRIPT_TYPES.P2WPKH) throw new Error('P2WSH(P2WPKH) is a consensus failure') return { witnessScript,