From 4ca6bf4e8149db2d4212e29fe43cd53d456524d5 Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Wed, 31 Jan 2018 12:46:35 +1100 Subject: [PATCH 1/5] use https://api.dcousens.cloud reg-test server instead of testnet --- package.json | 5 +- test/integration/_mainnet.js | 3 - test/integration/_regtest.js | 78 ++++++++++++++++ test/integration/_testnet.js | 99 -------------------- test/integration/addresses.js | 2 +- test/integration/cltv.js | 150 ++++++++++++++++++++++--------- test/integration/crypto.js | 142 ++++++++++------------------- test/integration/transactions.js | 139 ++++++++++++++-------------- 8 files changed, 303 insertions(+), 315 deletions(-) delete mode 100644 test/integration/_mainnet.js create mode 100644 test/integration/_regtest.js delete mode 100644 test/integration/_testnet.js diff --git a/package.json b/package.json index d7f6e92..fba4e23 100644 --- a/package.json +++ b/package.json @@ -47,12 +47,9 @@ "wif": "^2.0.1" }, "devDependencies": { - "async": "^2.0.1", "bip39": "^2.3.0", "bs58": "^4.0.0", - "cb-http-client": "^0.2.0", - "coinselect": "^3.1.1", - "dhttp": "^2.3.5", + "dhttp": "^2.4.2", "minimaldata": "^1.0.2", "mocha": "^3.1.0", "nyc": "^10.2.0", diff --git a/test/integration/_mainnet.js b/test/integration/_mainnet.js deleted file mode 100644 index 0741ce8..0000000 --- a/test/integration/_mainnet.js +++ /dev/null @@ -1,3 +0,0 @@ -var Blockchain = require('cb-http-client') -var BLOCKTRAIL_API_KEY = process.env.BLOCKTRAIL_API_KEY || 'c0bd8155c66e3fb148bb1664adc1e4dacd872548' -module.exports = new Blockchain('https://api.blocktrail.com/cb/v0.2.1/BTC', { api_key: BLOCKTRAIL_API_KEY }) diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js new file mode 100644 index 0000000..4fc4e93 --- /dev/null +++ b/test/integration/_regtest.js @@ -0,0 +1,78 @@ +var bitcoin = require('../../') +var dhttp = require('dhttp/200') + +var APIPASS = process.env.APIPASS || 'satoshi' +var APIURL = 'https://api.dcousens.cloud/1' + +function broadcast (txHex, callback) { + dhttp({ + method: 'PUT', + url: APIURL + '/t/push', + body: txHex + }, callback) +} + +function mine (count, callback) { + dhttp({ + method: 'POST', + url: APIURL + '/r/generate?count=' + count + '&key=' + APIPASS + }, callback) +} + +function faucet (address, value, callback) { + dhttp({ + method: 'POST', + url: APIURL + '/r/faucet?address=' + address + '&value=' + value + '&key=' + APIPASS + }, function (err, txId) { + if (err) return callback(err) + + unspents(address, function (err, results) { + if (err) return callback(err) + + callback(null, results.filter(x => x.txId === txId).pop()) + }) + }) +} + +function fetch (txId, callback) { + dhttp({ + method: 'GET', + url: APIURL + '/t/' + txId + }, callback) +} + +function unspents (address, callback) { + dhttp({ + method: 'GET', + url: APIURL + '/a/' + address + '/unspents' + }, callback) +} + +function verify (txo, callback) { + let { txId } = txo + + fetch(txId, function (err, txHex) { + if (err) return callback(err) + + // TODO: verify address and value + callback() + }) +} + +function randomAddress () { + return bitcoin.ECPair.makeRandom({ + network: bitcoin.networks.testnet + }).getAddress() +} + +module.exports = { + broadcast: broadcast, + faucet: faucet, + fetch: fetch, + mine: mine, + network: bitcoin.networks.testnet, + unspents: unspents, + verify: verify, + randomAddress: randomAddress, + RANDOM_ADDRESS: randomAddress() +} diff --git a/test/integration/_testnet.js b/test/integration/_testnet.js deleted file mode 100644 index ea3752a..0000000 --- a/test/integration/_testnet.js +++ /dev/null @@ -1,99 +0,0 @@ -var async = require('async') -var bitcoin = require('../../') -var Blockchain = require('cb-http-client') -var coinSelect = require('coinselect') -var dhttp = require('dhttp/200') -var typeforce = require('typeforce') -var types = require('../../src/types') - -var BLOCKTRAIL_API_KEY = process.env.BLOCKTRAIL_API_KEY || 'c0bd8155c66e3fb148bb1664adc1e4dacd872548' -var blockchain = new Blockchain('https://api.blocktrail.com/cb/v0.2.1/tBTC', { api_key: BLOCKTRAIL_API_KEY }) -var kpNetwork = bitcoin.networks.testnet -var keyPair = bitcoin.ECPair.fromWIF('cQqjeq2rxqwnqwMewJhkNtJDixtX8ctA4bYoWHdxY4xRPVvAEjmk', kpNetwork) -var kpAddress = keyPair.getAddress() -var conflicts = {} - -function fundAddress (unspents, outputs, callback) { - // avoid too-long-mempool-chain - unspents = unspents.filter(function (x) { - return x.confirmations > 0 && !conflicts[x.txId + x.vout] - }) - - var result = coinSelect(unspents, outputs, 10) - if (!result.inputs) return callback(new Error('Faucet empty')) - - var txb = new bitcoin.TransactionBuilder(kpNetwork) - result.inputs.forEach(function (x) { - conflicts[x.txId + x.vout] = true - txb.addInput(x.txId, x.vout) - }) - - result.outputs.forEach(function (x) { - if (x.address) console.warn('funding ' + x.address + ' w/ ' + x.value) - txb.addOutput(x.address || kpAddress, x.value) - }) - - result.inputs.forEach(function (_, i) { - txb.sign(i, keyPair) - }) - - var tx = txb.build() - - blockchain.transactions.propagate(tx.toHex(), function (err) { - if (err) return callback(err) - - var txId = tx.getId() - callback(null, outputs.map(function (x, i) { - return { txId: txId, vout: i, value: x.value } - })) - }) -} - -blockchain.faucetMany = function faucetMany (outputs, callback) { - blockchain.addresses.unspents(kpAddress, function (err, unspents) { - if (err) return callback(err) - - typeforce([{ - txId: types.Hex, - vout: types.UInt32, - value: types.Satoshi - }], unspents) - - fundAddress(unspents, outputs, callback) - }) -} - -blockchain.faucet = function faucet (address, value, callback) { - blockchain.faucetMany([{ address: address, value: value }], function (err, unspents) { - callback(err, unspents && unspents[0]) - }) -} - -// verify TX was accepted -blockchain.verify = function verify (address, txId, value, done) { - async.retry(5, function (callback) { - setTimeout(function () { - // check that the above transaction included the intended address - dhttp({ - method: 'POST', - url: 'https://api.ei8ht.com.au:9443/3/txs', - body: [txId] - }, function (err, result) { - if (err) return callback(err) - if (!result[txId]) return callback(new Error('Could not find ' + txId)) - callback() - }) - }, 400) - }, done) -} - -blockchain.transactions.propagate = function broadcast (txHex, callback) { - dhttp({ - method: 'POST', - url: 'https://api.ei8ht.com.au:9443/3/pushtx', - body: txHex - }, callback) -} - -blockchain.RETURN_ADDRESS = kpAddress -module.exports = blockchain diff --git a/test/integration/addresses.js b/test/integration/addresses.js index 70f0ec7..74cd487 100644 --- a/test/integration/addresses.js +++ b/test/integration/addresses.js @@ -97,7 +97,7 @@ describe('bitcoinjs-lib (addresses)', function () { assert.strictEqual(address, '3P4mrxQfmExfhxqjLnR2Ah4WES5EB1KBrN') }) - it('can support the retrieval of transactions for an address (3rd party blockchain)', function (done) { + it('can support the retrieval of transactions for an address (via 3PBP)', function (done) { var keyPair = bitcoin.ECPair.makeRandom() var address = keyPair.getAddress() diff --git a/test/integration/cltv.js b/test/integration/cltv.js index 8372d67..ec1ac8b 100644 --- a/test/integration/cltv.js +++ b/test/integration/cltv.js @@ -2,20 +2,21 @@ var assert = require('assert') var bitcoin = require('../../') -var testnetUtils = require('./_testnet') +var regtestUtils = require('./_regtest') +var regtest = regtestUtils.network +var bip65 = require('bip65') -var testnet = bitcoin.networks.testnet -var alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', testnet) -var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', testnet) +var alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', regtest) +var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', regtest) describe('bitcoinjs-lib (transactions w/ CLTV)', function () { var hashType = bitcoin.Transaction.SIGHASH_ALL - // IF MTP > utcSeconds, aQ can redeem, ELSE bQ, aQ joint redeem - function cltvCheckSigOutput (aQ, bQ, utcSeconds) { + // waitUntil is of the form { blocks: ... } or { utc: ... } + function cltvCheckSigOutput (aQ, bQ, waitUntil) { return bitcoin.script.compile([ bitcoin.opcodes.OP_IF, - bitcoin.script.number.encode(utcSeconds), + bitcoin.script.number.encode(bip65.encode(waitUntil)), bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY, bitcoin.opcodes.OP_DROP, @@ -34,36 +35,92 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { } // expiry past, {Alice's signature} OP_TRUE - it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry', function (done) { + it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the past)', function (done) { this.timeout(30000) - // three hours ago + // 3 hours ago var timeUtc = utcNow() - (3600 * 3) - var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) + var redeemScript = cltvCheckSigOutput(alice, bob, { utc: timeUtc }) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) // fund the P2SH(CLTV) address - testnetUtils.faucet(address, 2e4, function (err, unspent) { + regtestUtils.faucet(address, 1e5, function (err, unspent) { if (err) return done(err) - var tx = new bitcoin.TransactionBuilder(testnet) - tx.setLockTime(timeUtc) - tx.addInput(unspent.txId, 0, 0xfffffffe) - tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4) - - var txRaw = tx.buildIncomplete() - var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) + var txb = new bitcoin.TransactionBuilder(regtest) + txb.setLockTime(timeUtc) + txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) // {Alice's signature} OP_TRUE + var tx = txb.buildIncomplete() + var signatureHash = tx.hashForSignature(0, redeemScript, hashType) var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ alice.sign(signatureHash).toScriptSignature(hashType), bitcoin.opcodes.OP_TRUE ], redeemScript) + tx.setInputScript(0, redeemScriptSig) - txRaw.setInputScript(0, redeemScriptSig) + regtestUtils.broadcast(tx.toHex(), function (err) { + if (err) return done(err) - testnetUtils.transactions.propagate(txRaw.toHex(), done) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }, done) + }) + }) + }) + + // expiry will pass, {Alice's signature} OP_TRUE + it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', function (done) { + this.timeout(30000) + + // 50 blocks from now + var time + var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) + var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) + + // fund the P2SH(CLTV) address + regtestUtils.faucet(address, 1e5, function (err, unspent) { + if (err) return done(err) + + var txb = new bitcoin.TransactionBuilder(regtest) + txb.setLockTime(timeUtc) + txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + + // {Alice's signature} OP_TRUE + var tx = txb.buildIncomplete() + var signatureHash = tx.hashForSignature(0, redeemScript, hashType) + var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ + alice.sign(signatureHash).toScriptSignature(hashType), + bitcoin.opcodes.OP_TRUE + ], redeemScript) + tx.setInputScript(0, redeemScriptSig) + + regtestUtils.broadcast(tx.toHex(), function (err) { + if (err) return done(err) + + // fails before the expiry + assert.throws(function () { + if (err) throw err + }, /Error: 64: non-final/) + + // into the future! + regtestUtils.mine( + + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }, done) + }) }) }) @@ -75,28 +132,37 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { var timeUtc = utcNow() - (3600 * 2) var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) // fund the P2SH(CLTV) address - testnetUtils.faucet(address, 2e4, function (err, unspent) { + regtestUtils.faucet(address, 2e5, function (err, unspent) { if (err) return done(err) - var tx = new bitcoin.TransactionBuilder(testnet) - tx.setLockTime(timeUtc) - tx.addInput(unspent.txId, 0, 0xfffffffe) - tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4) + var txb = new bitcoin.TransactionBuilder(regtest) + txb.setLockTime(timeUtc) + txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 8e4) - var txRaw = tx.buildIncomplete() - var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) + // {Alice's signature} {Bob's signature} OP_FALSE + var tx = txb.buildIncomplete() + var signatureHash = tx.hashForSignature(0, redeemScript, hashType) var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ alice.sign(signatureHash).toScriptSignature(hashType), bob.sign(signatureHash).toScriptSignature(hashType), bitcoin.opcodes.OP_FALSE ], redeemScript) + tx.setInputScript(0, redeemScriptSig) - txRaw.setInputScript(0, redeemScriptSig) + regtestUtils.broadcast(tx.toHex(), function (err) { + if (err) return done(err) - testnetUtils.transactions.propagate(txRaw.toHex(), done) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 8e4 + }, done) + }) }) }) @@ -108,29 +174,27 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { var timeUtc = utcNow() + (3600 * 2) var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) // fund the P2SH(CLTV) address - testnetUtils.faucet(address, 2e4, function (err, unspent) { + regtestUtils.faucet(address, 2e4, function (err, unspent) { if (err) return done(err) - var tx = new bitcoin.TransactionBuilder(testnet) - tx.setLockTime(timeUtc) - tx.addInput(unspent.txId, 0, 0xfffffffe) - tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4) - - var txRaw = tx.buildIncomplete() - var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) + var txb = new bitcoin.TransactionBuilder(regtest) + txb.setLockTime(timeUtc) + txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) // {Alice's signature} OP_TRUE + var tx = txb.buildIncomplete() + var signatureHash = tx.hashForSignature(0, redeemScript, hashType) var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ alice.sign(signatureHash).toScriptSignature(hashType), bitcoin.opcodes.OP_TRUE ], redeemScript) + tx.setInputScript(0, redeemScriptSig) - txRaw.setInputScript(0, redeemScriptSig) - - testnetUtils.transactions.propagate(txRaw.toHex(), function (err) { + regtestUtils.broadcast(tx.toHex(), function (err) { assert.throws(function () { if (err) throw err }, /Error: 64: non-final/) diff --git a/test/integration/crypto.js b/test/integration/crypto.js index 16dec80..716cb50 100644 --- a/test/integration/crypto.js +++ b/test/integration/crypto.js @@ -1,115 +1,69 @@ /* global describe, it */ var assert = require('assert') -var async = require('async') var bigi = require('bigi') var bitcoin = require('../../') -var mainnet = require('./_mainnet') var crypto = require('crypto') var ecurve = require('ecurve') var secp256k1 = ecurve.getCurveByName('secp256k1') describe('bitcoinjs-lib (crypto)', function () { - it('can recover a private key from duplicate R values', function (done) { + it('can recover a private key from duplicate R values', function () { this.timeout(30000) - var inputs = [ - { - txId: 'f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50', - vout: 0 - }, - { - txId: 'f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50', - vout: 1 - } - ] + // https://blockchain.info/tx/f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50 + var tx = bitcoin.Transaction.fromHex('01000000020b668015b32a6178d8524cfef6dc6fc0a4751915c2e9b2ed2d2eab02424341c8000000006a47304402205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022024bf5f506968f5f23f1835574d5afe0e9021b4a5b65cf9742332d5e4acb68f41012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffffa95fa69f11dc1cbb77ef64f25a95d4b12ebda57d19d843333819d95c9172ff89000000006b48304502205e00298dc5265b7a914974c9d0298aa0e69a0ca932cb52a360436d6a622e5cd7022100832176b59e8f50c56631acbc824bcba936c9476c559c42a4468be98975d07562012103fd089f73735129f3d798a657aaaa4aa62a00fa15c76b61fc7f1b27ed1d0f35b8ffffffff02b000eb04000000001976a91472956eed9a8ecb19ae7e3ebd7b06cae4668696a788ac303db000000000001976a9146c0bd55dd2592287cd9992ce3ba3fc1208fb76da88ac00000000') - var txIds = inputs.map(function (x) { return x.txId }) + tx.ins.forEach(function (input, vin) { + var script = input.script + var scriptChunks = bitcoin.script.decompile(script) - // first retrieve the relevant transactions - mainnet.transactions.get(txIds, function (err, results) { - assert.ifError(err) + assert(bitcoin.script.pubKeyHash.input.check(scriptChunks), 'Expected pubKeyHash script') + var prevOutScript = bitcoin.address.toOutputScript('1ArJ9vRaQcoQ29mTWZH768AmRwzb6Zif1z') + var scriptSignature = bitcoin.ECSignature.parseScriptSignature(scriptChunks[0]) + var publicKey = bitcoin.ECPair.fromPublicKeyBuffer(scriptChunks[1]) - var transactions = {} - results.forEach(function (tx) { - transactions[tx.txId] = bitcoin.Transaction.fromHex(tx.txHex) - }) + var m = tx.hashForSignature(vin, prevOutScript, scriptSignature.hashType) + assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m') - var tasks = [] - - // now we need to collect/transform a bit of data from the selected inputs - inputs.forEach(function (input) { - var transaction = transactions[input.txId] - var script = transaction.ins[input.vout].script - var scriptChunks = bitcoin.script.decompile(script) - - assert(bitcoin.script.pubKeyHash.input.check(scriptChunks), 'Expected pubKeyHash script') - - var prevOutTxId = Buffer.from(transaction.ins[input.vout].hash).reverse().toString('hex') - var prevVout = transaction.ins[input.vout].index - - tasks.push(function (callback) { - mainnet.transactions.get(prevOutTxId, function (err, result) { - if (err) return callback(err) - - var prevOut = bitcoin.Transaction.fromHex(result.txHex) - var prevOutScript = prevOut.outs[prevVout].script - - var scriptSignature = bitcoin.ECSignature.parseScriptSignature(scriptChunks[0]) - var publicKey = bitcoin.ECPair.fromPublicKeyBuffer(scriptChunks[1]) - - var m = transaction.hashForSignature(input.vout, prevOutScript, scriptSignature.hashType) - assert(publicKey.verify(m, scriptSignature.signature), 'Invalid m') - - // store the required information - input.signature = scriptSignature.signature - input.z = bigi.fromBuffer(m) - - return callback() - }) - }) - }) - - // finally, run the tasks, then on to the math - async.parallel(tasks, function (err) { - if (err) throw err - - var n = secp256k1.n - - for (var i = 0; i < inputs.length; ++i) { - for (var j = i + 1; j < inputs.length; ++j) { - var inputA = inputs[i] - var inputB = inputs[j] - - // enforce matching r values - assert.strictEqual(inputA.signature.r.toString(), inputB.signature.r.toString()) - var r = inputA.signature.r - var rInv = r.modInverse(n) - - var s1 = inputA.signature.s - var s2 = inputB.signature.s - var z1 = inputA.z - var z2 = inputB.z - - var zz = z1.subtract(z2).mod(n) - var ss = s1.subtract(s2).mod(n) - - // k = (z1 - z2) / (s1 - s2) - // d1 = (s1 * k - z1) / r - // d2 = (s2 * k - z2) / r - var k = zz.multiply(ss.modInverse(n)).mod(n) - var d1 = ((s1.multiply(k).mod(n)).subtract(z1).mod(n)).multiply(rInv).mod(n) - var d2 = ((s2.multiply(k).mod(n)).subtract(z2).mod(n)).multiply(rInv).mod(n) - - // enforce matching private keys - assert.strictEqual(d1.toString(), d2.toString()) - } - } - - done() - }) + // store the required information + input.signature = scriptSignature.signature + input.z = bigi.fromBuffer(m) }) + + // finally, run the tasks, then on to the math + var n = secp256k1.n + + for (var i = 0; i < tx.ins.length; ++i) { + for (var j = i + 1; j < tx.ins.length; ++j) { + var inputA = tx.ins[i] + var inputB = tx.ins[j] + + // enforce matching r values + assert.strictEqual(inputA.signature.r.toString(), inputB.signature.r.toString()) + var r = inputA.signature.r + var rInv = r.modInverse(n) + + var s1 = inputA.signature.s + var s2 = inputB.signature.s + var z1 = inputA.z + var z2 = inputB.z + + var zz = z1.subtract(z2).mod(n) + var ss = s1.subtract(s2).mod(n) + + // k = (z1 - z2) / (s1 - s2) + // d1 = (s1 * k - z1) / r + // d2 = (s2 * k - z2) / r + var k = zz.multiply(ss.modInverse(n)).mod(n) + var d1 = ((s1.multiply(k).mod(n)).subtract(z1).mod(n)).multiply(rInv).mod(n) + var d2 = ((s2.multiply(k).mod(n)).subtract(z2).mod(n)).multiply(rInv).mod(n) + + // enforce matching private keys + assert.strictEqual(d1.toString(), d2.toString()) + } + } }) it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () { diff --git a/test/integration/transactions.js b/test/integration/transactions.js index 609f4b8..56ef033 100644 --- a/test/integration/transactions.js +++ b/test/integration/transactions.js @@ -2,9 +2,8 @@ var assert = require('assert') var bitcoin = require('../../') -var dhttp = require('dhttp/200') -var testnet = bitcoin.networks.testnet -var testnetUtils = require('./_testnet') +var regtestUtils = require('./_regtest') +var regtest = regtestUtils.network function rng () { return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64') @@ -46,69 +45,54 @@ describe('bitcoinjs-lib (transactions)', function () { it('can create (and broadcast via 3PBP) a typical Transaction', function (done) { this.timeout(30000) - var alice1 = bitcoin.ECPair.makeRandom({ network: testnet }) - var alice2 = bitcoin.ECPair.makeRandom({ network: testnet }) - var aliceChange = bitcoin.ECPair.makeRandom({ rng: rng, network: testnet }) + var alice1 = bitcoin.ECPair.makeRandom({ network: regtest }) + var alice2 = bitcoin.ECPair.makeRandom({ network: regtest }) + var aliceChange = bitcoin.ECPair.makeRandom({ network: regtest, rng: rng }) - // "simulate" on testnet that Alice has 2 unspent outputs - testnetUtils.faucetMany([ - { - address: alice1.getAddress(), - value: 5e4 - }, - { - address: alice2.getAddress(), - value: 7e4 - } - ], function (err, unspents) { + // give Alice 2 unspent outputs + regtestUtils.faucet(alice1.getAddress(), 5e4, function (err, unspent0) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(testnet) - txb.addInput(unspents[0].txId, unspents[0].vout) // alice1 unspent - txb.addInput(unspents[1].txId, unspents[1].vout) // alice2 unspent - txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend" - txb.addOutput(aliceChange.getAddress(), 1e4) // Alice's change - // (in)(4e4 + 2e4) - (out)(1e4 + 3e4) = (fee)2e4 = 20000, this is the miner fee + regtestUtils.faucet(alice2.getAddress(), 7e4, function (err, unspent1) { + if (err) return done(err) - // Alice signs each input with the respective private keys - txb.sign(0, alice1) - txb.sign(1, alice2) + var txb = new bitcoin.TransactionBuilder(regtest) + txb.addInput(unspent0.txId, unspent0.vout) // alice1 unspent + txb.addInput(unspent1.txId, unspent1.vout) // alice2 unspent + txb.addOutput('mwCwTceJvYV27KXBc3NJZys6CjsgsoeHmf', 8e4) // the actual "spend" + txb.addOutput(aliceChange.getAddress(), 1e4) // Alice's change + // (in)(4e4 + 2e4) - (out)(1e4 + 3e4) = (fee)2e4 = 20000, this is the miner fee - // build and broadcast to the Bitcoin Testnet network - dhttp({ - method: 'POST', - url: 'https://api.ei8ht.com.au:9443/3/pushtx', -// url: 'http://tbtc.blockr.io/api/v1/tx/push', - body: txb.build().toHex() - }, done) - // to build and broadcast to the actual Bitcoin network, see https://github.com/bitcoinjs/bitcoinjs-lib/issues/839 + // Alice signs each input with the respective private keys + txb.sign(0, alice1) + txb.sign(1, alice2) + + // build and broadcast our RegTest network + regtestUtils.broadcast(txb.build().toHex(), done) + // 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', function (done) { this.timeout(30000) - var keyPair = bitcoin.ECPair.makeRandom({ network: testnet }) - var address = keyPair.getAddress() + var keyPair = bitcoin.ECPair.makeRandom({ network: regtest }) - testnetUtils.faucet(address, 5e4, function (err, unspent) { + regtestUtils.faucet(keyPair.getAddress(), 2e5, function (err, unspent) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(testnet) + var txb = new bitcoin.TransactionBuilder(regtest) var data = Buffer.from('bitcoinjs-lib', 'utf8') var dataScript = bitcoin.script.nullData.output.encode(data) txb.addInput(unspent.txId, unspent.vout) txb.addOutput(dataScript, 1000) - txb.addOutput(testnetUtils.RETURN_ADDRESS, 4e4) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e5) txb.sign(0, keyPair) - // build and broadcast to the Bitcoin Testnet network - dhttp({ - method: 'POST', - url: 'https://api.ei8ht.com.au:9443/3/pushtx', - body: txb.build().toHex() - }, done) + // build and broadcast to the RegTest network + regtestUtils.broadcast(txb.build().toHex(), done) }) }) @@ -120,30 +104,34 @@ describe('bitcoinjs-lib (transactions)', function () { '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT', '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe', '91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx9rcrL7' - ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, testnet) }) + ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, regtest) }) var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() }) var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) - testnetUtils.faucet(address, 2e4, function (err, unspent) { + regtestUtils.faucet(address, 2e4, function (err, unspent) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(testnet) + var txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout) - txb.addOutput(testnetUtils.RETURN_ADDRESS, 1e4) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 1e4) txb.sign(0, keyPairs[0], redeemScript) txb.sign(0, keyPairs[2], redeemScript) - var tx = txb.build() - // build and broadcast to the Bitcoin Testnet network - testnetUtils.transactions.propagate(tx.toHex(), function (err) { + // build and broadcast to the Bitcoin RegTest network + regtestUtils.broadcast(tx.toHex(), function (err) { if (err) return done(err) - testnetUtils.verify(address, tx.getId(), 1e4, done) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 1e4 + }, done) }) }) }) @@ -151,31 +139,35 @@ describe('bitcoinjs-lib (transactions)', function () { it('can create (and broadcast via 3PBP) a Transaction with a SegWit P2SH(P2WPKH) input', function (done) { this.timeout(30000) - var keyPair = bitcoin.ECPair.fromWIF('cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA', testnet) + var keyPair = bitcoin.ECPair.fromWIF('cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87JcbXMTcA', regtest) var pubKey = keyPair.getPublicKeyBuffer() var pubKeyHash = bitcoin.crypto.hash160(pubKey) var redeemScript = bitcoin.script.witnessPubKeyHash.output.encode(pubKeyHash) var redeemScriptHash = bitcoin.crypto.hash160(redeemScript) - var scriptPubKey = bitcoin.script.scriptHash.output.encode(redeemScriptHash) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) - testnetUtils.faucet(address, 5e4, function (err, unspent) { + regtestUtils.faucet(address, 5e4, function (err, unspent) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(testnet) + var txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout) - txb.addOutput(testnetUtils.RETURN_ADDRESS, 4e4) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 2e4) txb.sign(0, keyPair, redeemScript, null, unspent.value) var tx = txb.build() - // build and broadcast to the Bitcoin Testnet network - testnetUtils.transactions.propagate(tx.toHex(), function (err) { + // build and broadcast to the Bitcoin RegTest network + regtestUtils.broadcast(tx.toHex(), function (err) { if (err) return done(err) - testnetUtils.verify(address, tx.getId(), 1e4, done) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 2e4 + }, done) }) }) }) @@ -188,31 +180,36 @@ describe('bitcoinjs-lib (transactions)', function () { 'cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87K7XCyj5v', 'cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87KcLPVfXz', 'cMahea7zqjxrtgAbB7LSGbcQUr1uX1ojuat9jZodMN87L7FgDCKE' - ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, testnet) }) + ].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, regtest) }) var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() }) var witnessScript = bitcoin.script.multisig.output.encode(3, pubKeys) var redeemScript = bitcoin.script.witnessScriptHash.output.encode(bitcoin.crypto.sha256(witnessScript)) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) - testnetUtils.faucet(address, 6e4, function (err, unspent) { + regtestUtils.faucet(address, 6e4, function (err, unspent) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(testnet) + var txb = new bitcoin.TransactionBuilder(regtest) txb.addInput(unspent.txId, unspent.vout) - txb.addOutput(testnetUtils.RETURN_ADDRESS, 4e4) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 3e4) txb.sign(0, keyPairs[0], redeemScript, null, unspent.value, witnessScript) txb.sign(0, keyPairs[2], redeemScript, null, unspent.value, witnessScript) txb.sign(0, keyPairs[3], redeemScript, null, unspent.value, witnessScript) var tx = txb.build() - // build and broadcast to the Bitcoin Testnet network - testnetUtils.transactions.propagate(tx.toHex(), function (err) { + // build and broadcast to the Bitcoin RegTest network + regtestUtils.broadcast(tx.toHex(), function (err) { if (err) return done(err) - testnetUtils.verify(address, tx.getId(), 4e4, done) + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 3e4 + }, done) }) }) }) From 31703e2e40ee9b166d30f83e4fc468c999605a4a Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 1 Feb 2018 12:54:20 +1100 Subject: [PATCH 2/5] tests: add into-the-future CLTV test --- package.json | 1 + test/integration/_regtest.js | 8 ++++ test/integration/cltv.js | 87 +++++++++++++++++++----------------- 3 files changed, 56 insertions(+), 40 deletions(-) diff --git a/package.json b/package.json index fba4e23..1fedcae 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ }, "devDependencies": { "bip39": "^2.3.0", + "bip65": "^1.0.1", "bs58": "^4.0.0", "dhttp": "^2.4.2", "minimaldata": "^1.0.2", diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js index 4fc4e93..c06cbef 100644 --- a/test/integration/_regtest.js +++ b/test/integration/_regtest.js @@ -19,6 +19,13 @@ function mine (count, callback) { }, callback) } +function height (callback) { + dhttp({ + method: 'GET', + url: APIURL + '/b/best/height' + }, callback) +} + function faucet (address, value, callback) { dhttp({ method: 'POST', @@ -69,6 +76,7 @@ module.exports = { broadcast: broadcast, faucet: faucet, fetch: fetch, + height: height, mine: mine, network: bitcoin.networks.testnet, unspents: unspents, diff --git a/test/integration/cltv.js b/test/integration/cltv.js index ec1ac8b..8f1488f 100644 --- a/test/integration/cltv.js +++ b/test/integration/cltv.js @@ -12,11 +12,10 @@ var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLw describe('bitcoinjs-lib (transactions w/ CLTV)', function () { var hashType = bitcoin.Transaction.SIGHASH_ALL - // waitUntil is of the form { blocks: ... } or { utc: ... } - function cltvCheckSigOutput (aQ, bQ, waitUntil) { + function cltvCheckSigOutput (aQ, bQ, lockTime) { return bitcoin.script.compile([ bitcoin.opcodes.OP_IF, - bitcoin.script.number.encode(bip65.encode(waitUntil)), + bitcoin.script.number.encode(lockTime), bitcoin.opcodes.OP_CHECKLOCKTIMEVERIFY, bitcoin.opcodes.OP_DROP, @@ -39,8 +38,8 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { this.timeout(30000) // 3 hours ago - var timeUtc = utcNow() - (3600 * 3) - var redeemScript = cltvCheckSigOutput(alice, bob, { utc: timeUtc }) + var lockTime = bip65.encode({ utc: utcNow() - (3600 * 3) }) + var redeemScript = cltvCheckSigOutput(alice, bob, lockTime) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) @@ -49,7 +48,7 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { if (err) return done(err) var txb = new bitcoin.TransactionBuilder(regtest) - txb.setLockTime(timeUtc) + txb.setLockTime(lockTime) txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) @@ -79,47 +78,55 @@ describe('bitcoinjs-lib (transactions w/ CLTV)', function () { it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry (in the future)', function (done) { this.timeout(30000) - // 50 blocks from now - var time - var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) - var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) - var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) - - // fund the P2SH(CLTV) address - regtestUtils.faucet(address, 1e5, function (err, unspent) { + regtestUtils.height(function (err, height) { if (err) return done(err) - var txb = new bitcoin.TransactionBuilder(regtest) - txb.setLockTime(timeUtc) - txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) - txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) + // 50 blocks from now + var lockTime = bip65.encode({ blocks: height + 50 }) + var redeemScript = cltvCheckSigOutput(alice, bob, lockTime) + var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) + var address = bitcoin.address.fromOutputScript(scriptPubKey, regtest) - // {Alice's signature} OP_TRUE - var tx = txb.buildIncomplete() - var signatureHash = tx.hashForSignature(0, redeemScript, hashType) - var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ - alice.sign(signatureHash).toScriptSignature(hashType), - bitcoin.opcodes.OP_TRUE - ], redeemScript) - tx.setInputScript(0, redeemScriptSig) - - regtestUtils.broadcast(tx.toHex(), function (err) { + // fund the P2SH(CLTV) address + regtestUtils.faucet(address, 1e5, function (err, unspent) { if (err) return done(err) - // fails before the expiry - assert.throws(function () { - if (err) throw err - }, /Error: 64: non-final/) + var txb = new bitcoin.TransactionBuilder(regtest) + txb.setLockTime(lockTime) + txb.addInput(unspent.txId, unspent.vout, 0xfffffffe) + txb.addOutput(regtestUtils.RANDOM_ADDRESS, 7e4) - // into the future! - regtestUtils.mine( + // {Alice's signature} OP_TRUE + var tx = txb.buildIncomplete() + var signatureHash = tx.hashForSignature(0, redeemScript, hashType) + var redeemScriptSig = bitcoin.script.scriptHash.input.encode([ + alice.sign(signatureHash).toScriptSignature(hashType), + bitcoin.opcodes.OP_TRUE + ], redeemScript) + tx.setInputScript(0, redeemScriptSig) - regtestUtils.verify({ - txId: tx.getId(), - address: regtestUtils.RANDOM_ADDRESS, - vout: 0, - value: 7e4 - }, done) + regtestUtils.broadcast(tx.toHex(), function (err) { + // fails before the expiry + assert.throws(function () { + if (err) throw err + }, /Error: 64: non-final/) + + // into the future! + regtestUtils.mine(51, function (err) { + if (err) return done(err) + + regtestUtils.broadcast(tx.toHex(), function (err) { + if (err) return done(err) + + regtestUtils.verify({ + txId: tx.getId(), + address: regtestUtils.RANDOM_ADDRESS, + vout: 0, + value: 7e4 + }, done) + }) + }) + }) }) }) }) From 6fc1273b7e2b2bf0b859b209669a045c2f600c9c Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 1 Feb 2018 13:14:34 +1100 Subject: [PATCH 3/5] fix ES6 issue, and actually verify the TXO --- test/integration/_regtest.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/test/integration/_regtest.js b/test/integration/_regtest.js index c06cbef..0bb1a40 100644 --- a/test/integration/_regtest.js +++ b/test/integration/_regtest.js @@ -1,3 +1,4 @@ +var assert = require('assert') var bitcoin = require('../../') var dhttp = require('dhttp/200') @@ -44,7 +45,7 @@ function faucet (address, value, callback) { function fetch (txId, callback) { dhttp({ method: 'GET', - url: APIURL + '/t/' + txId + url: APIURL + '/t/' + txId + '/json' }, callback) } @@ -56,12 +57,12 @@ function unspents (address, callback) { } function verify (txo, callback) { - let { txId } = txo - - fetch(txId, function (err, txHex) { + fetch(txo.txId, function (err, tx) { if (err) return callback(err) - // TODO: verify address and value + var txoActual = tx.outs[txo.vout] + if (txo.address) assert.strictEqual(txoActual.address, txo.address) + if (txo.value) assert.strictEqual(txoActual.value, txo.value) callback() }) } From e6f3aaf5b1712106a7fa9de149da226a8e8bf87e Mon Sep 17 00:00:00 2001 From: junderw Date: Thu, 1 Feb 2018 11:24:19 +0900 Subject: [PATCH 4/5] Add use strict --- test/integration/blocks.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/integration/blocks.js b/test/integration/blocks.js index 648a321..810bcb0 100644 --- a/test/integration/blocks.js +++ b/test/integration/blocks.js @@ -1,4 +1,5 @@ /* global describe, it */ +'use strict'; var assert = require('assert') var bitcoin = require('../../') From e57d0a8fdf6a0d2c31bab9a5e29c6723c0c098bc Mon Sep 17 00:00:00 2001 From: Daniel Cousens Date: Thu, 1 Feb 2018 13:33:23 +1100 Subject: [PATCH 5/5] fix standard issue --- test/integration/blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/blocks.js b/test/integration/blocks.js index 810bcb0..58faab2 100644 --- a/test/integration/blocks.js +++ b/test/integration/blocks.js @@ -1,5 +1,5 @@ /* global describe, it */ -'use strict'; +'use strict' var assert = require('assert') var bitcoin = require('../../')