testing/integration/examples: isolate to addresses/transactions

examples, use public broadcast endpoints
This commit is contained in:
Daniel Cousens 2017-08-09 16:03:12 +10:00 committed by Daniel Cousens
parent 798ec3512c
commit a4fe3d3139
12 changed files with 358 additions and 295 deletions

View file

@ -100,8 +100,8 @@ The definitions are complete and up to date with version 2.2.0. The definitions
The below examples are implemented as integration tests, they should be very easy to understand. Otherwise, pull requests are appreciated. The below examples are implemented as integration tests, they should be very easy to understand. Otherwise, pull requests are appreciated.
- [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L9) - [Generate a random address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L9)
- [Generate a address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L20) - [Generate an address from a SHA256 hash](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L20)
- [Generate a address and WIF for Litecoin](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L30) - [Generate an address and WIF for Litecoin](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L30)
- [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L43) - [Import an address via WIF](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L43)
- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L50) - [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/basic.js#L50)
- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/advanced.js#L24) - [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/d853806/test/integration/advanced.js#L24)

View file

@ -62,7 +62,7 @@
"pushdata-bitcoin": "^1.0.1", "pushdata-bitcoin": "^1.0.1",
"randombytes": "^2.0.1", "randombytes": "^2.0.1",
"safe-buffer": "^5.0.1", "safe-buffer": "^5.0.1",
"typeforce": "^1.8.7", "typeforce": "^1.11.3",
"varuint-bitcoin": "^1.0.4", "varuint-bitcoin": "^1.0.4",
"wif": "^2.0.1" "wif": "^2.0.1"
}, },
@ -72,6 +72,7 @@
"bs58": "^4.0.0", "bs58": "^4.0.0",
"cb-http-client": "^0.2.0", "cb-http-client": "^0.2.0",
"coinselect": "^3.1.1", "coinselect": "^3.1.1",
"dhttp": "^2.3.5",
"minimaldata": "^1.0.2", "minimaldata": "^1.0.2",
"mocha": "^3.1.0", "mocha": "^3.1.0",
"nyc": "^10.2.0", "nyc": "^10.2.0",

View file

@ -0,0 +1,3 @@
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 })

View file

@ -1,13 +1,12 @@
var async = require('async')
var bitcoin = require('../../') var bitcoin = require('../../')
var Blockchain = require('cb-http-client') var Blockchain = require('cb-http-client')
var BLOCKTRAIL_API_KEY = process.env.BLOCKTRAIL_API_KEY || 'c0bd8155c66e3fb148bb1664adc1e4dacd872548'
var coinSelect = require('coinselect') var coinSelect = require('coinselect')
var typeforce = require('typeforce') var typeforce = require('typeforce')
var types = require('../../src/types') var types = require('../../src/types')
var mainnet = new Blockchain('https://api.blocktrail.com/cb/v0.2.1/BTC', { api_key: BLOCKTRAIL_API_KEY }) var BLOCKTRAIL_API_KEY = process.env.BLOCKTRAIL_API_KEY || 'c0bd8155c66e3fb148bb1664adc1e4dacd872548'
var testnet = new Blockchain('https://api.blocktrail.com/cb/v0.2.1/tBTC', { api_key: BLOCKTRAIL_API_KEY }) var blockchain = new Blockchain('https://api.blocktrail.com/cb/v0.2.1/tBTC', { api_key: BLOCKTRAIL_API_KEY })
var kpNetwork = bitcoin.networks.testnet var kpNetwork = bitcoin.networks.testnet
var keyPair = bitcoin.ECPair.fromWIF('cQqjeq2rxqwnqwMewJhkNtJDixtX8ctA4bYoWHdxY4xRPVvAEjmk', kpNetwork) var keyPair = bitcoin.ECPair.fromWIF('cQqjeq2rxqwnqwMewJhkNtJDixtX8ctA4bYoWHdxY4xRPVvAEjmk', kpNetwork)
var kpAddress = keyPair.getAddress() var kpAddress = keyPair.getAddress()
@ -37,11 +36,11 @@ function fundAddress (unspents, outputs, callback) {
var tx = txb.build() var tx = txb.build()
var txId = tx.getId() var txId = tx.getId()
testnet.transactions.propagate(tx.toHex(), function (err) { blockchain.transactions.propagate(tx.toHex(), function (err) {
if (err) return callback(err) if (err) return callback(err)
// FIXME: @blocktrail can be very slow, give it time // FIXME: @blocktrail can be very slow, give it time
setTimeout(() => { setTimeout(function () {
callback(null, outputs.map(function (_, i) { callback(null, outputs.map(function (_, i) {
return { txId: txId, vout: i } return { txId: txId, vout: i }
})) }))
@ -49,8 +48,8 @@ function fundAddress (unspents, outputs, callback) {
}) })
} }
testnet.faucetMany = function faucetMany (outputs, callback) { blockchain.faucetMany = function faucetMany (outputs, callback) {
testnet.addresses.unspents(kpAddress, function (err, unspents) { blockchain.addresses.unspents(kpAddress, function (err, unspents) {
if (err) return callback(err) if (err) return callback(err)
typeforce([{ typeforce([{
@ -63,15 +62,28 @@ testnet.faucetMany = function faucetMany (outputs, callback) {
}) })
} }
testnet.faucet = function faucet (address, value, callback) { blockchain.faucet = function faucet (address, value, callback) {
testnet.faucetMany([{ address: address, value: value }], function (err, unspents) { blockchain.faucetMany([{ address: address, value: value }], function (err, unspents) {
callback(err, unspents && unspents[0]) callback(err, unspents && unspents[0])
}) })
} }
testnet.RETURN = kpAddress // verify TX was accepted
blockchain.verify = function (address, txId, value, done) {
async.retry(5, function (callback) {
setTimeout(function () {
// check that the above transaction included the intended address
blockchain.addresses.unspents(blockchain.RETURN_ADDRESS, function (err, unspents) {
if (err) return callback(err)
if (!unspents.some(function (x) {
return x.txId === txId && x.value === value
})) return callback(new Error('Could not find unspent'))
module.exports = { callback()
m: mainnet, })
t: testnet }, 600)
}, done)
} }
blockchain.RETURN_ADDRESS = kpAddress
module.exports = blockchain

View file

@ -0,0 +1,92 @@
/* global describe, it */
var assert = require('assert')
var bigi = require('bigi')
var bitcoin = require('../../')
var dhttp = require('dhttp/200')
// deterministic RNG for testing only
function rng () { return Buffer.from('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz') }
describe('bitcoinjs-lib (addresses)', function () {
it('can generate a random address', function () {
var keyPair = bitcoin.ECPair.makeRandom({ rng: rng })
var address = keyPair.getAddress()
assert.strictEqual(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64')
})
it('can generate an address from a SHA256 hash', function () {
var hash = bitcoin.crypto.sha256('correct horse battery staple')
var d = bigi.fromBuffer(hash)
var keyPair = new bitcoin.ECPair(d)
var address = keyPair.getAddress()
assert.strictEqual(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8')
})
it('can import an address via WIF', function () {
var keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
var address = keyPair.getAddress()
assert.strictEqual(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31')
})
it('can generate a 2-of-3 multisig P2SH address', function () {
var pubKeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
].map(function (hex) {
return Buffer.from(hex, 'hex')
})
var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 3
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey)
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
})
it('can support the retrieval of transactions for an address (3rd party blockchain)', function (done) {
var keyPair = bitcoin.ECPair.makeRandom()
var address = keyPair.getAddress()
dhttp({
method: 'POST',
url: 'https://api.ei8ht.com.au/3/addrtxs',
body: {
addrs: [address],
height: 0
}
}, function (err, transactions) {
if (err) return done(err)
// random private keys [probably] have no transactions
assert.strictEqual(Object.keys(transactions).length, 0)
done()
})
})
// other networks
it('can generate a Testnet address', function () {
var testnet = bitcoin.networks.testnet
var keyPair = bitcoin.ECPair.makeRandom({ network: testnet, rng: rng })
var wif = keyPair.toWIF()
var address = keyPair.getAddress()
assert.strictEqual(address, 'mubSzQNtZfDj1YdNP6pNDuZy6zs6GDn61L')
assert.strictEqual(wif, 'cRgnQe9MUu1JznntrLaoQpB476M8PURvXVQB5R2eqms5tXnzNsrr')
})
it('can generate a Litecoin address', function () {
var litecoin = bitcoin.networks.litecoin
var keyPair = bitcoin.ECPair.makeRandom({ network: litecoin, rng: rng })
var wif = keyPair.toWIF()
var address = keyPair.getAddress()
assert.strictEqual(address, 'LZJSxZbjqJ2XVEquqfqHg1RQTDdfST5PTn')
assert.strictEqual(wif, 'T7A4PUSgTDHecBxW1ZiYFrDNRih2o7M8Gf9xpoCgudPF9gDiNvuS')
})
})

View file

@ -1,30 +0,0 @@
/* global describe, it */
var bitcoin = require('../../')
var blockchain = require('./_blockchain')
describe('bitcoinjs-lib (advanced)', function () {
it('can create an OP_RETURN transaction', function (done) {
this.timeout(30000)
var network = bitcoin.networks.testnet
var keyPair = bitcoin.ECPair.makeRandom({ network: network })
var address = keyPair.getAddress()
blockchain.t.faucet(address, 5e4, function (err, unspent) {
if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(network)
var data = Buffer.from('bitcoinjs-lib')
var dataScript = bitcoin.script.nullData.output.encode(data)
tx.addInput(unspent.txId, unspent.vout)
tx.addOutput(dataScript, 1000)
tx.addOutput(blockchain.t.RETURN, 4e4)
tx.sign(0, keyPair)
var txRaw = tx.build()
blockchain.t.transactions.propagate(txRaw.toHex(), done)
})
})
})

View file

@ -1,94 +0,0 @@
/* global describe, it */
var assert = require('assert')
var bigi = require('bigi')
var bitcoin = require('../../')
var blockchain = require('./_blockchain')
describe('bitcoinjs-lib (basic)', function () {
it('can generate a random bitcoin address', function () {
// for testing only
function rng () { return Buffer.from('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz') }
// generate random keyPair
var keyPair = bitcoin.ECPair.makeRandom({ rng: rng })
var address = keyPair.getAddress()
assert.strictEqual(address, '1F5VhMHukdnUES9kfXqzPzMeF1GPHKiF64')
})
it('can generate an address from a SHA256 hash', function () {
var hash = bitcoin.crypto.sha256('correct horse battery staple')
var d = bigi.fromBuffer(hash)
var keyPair = new bitcoin.ECPair(d)
var address = keyPair.getAddress()
assert.strictEqual(address, '1C7zdTfnkzmr13HfA2vNm5SJYRK6nEKyq8')
})
it('can generate a random keypair for alternative networks', function () {
// for testing only
function rng () { return Buffer.from('zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz') }
var litecoin = bitcoin.networks.litecoin
var keyPair = bitcoin.ECPair.makeRandom({ network: litecoin, rng: rng })
var wif = keyPair.toWIF()
var address = keyPair.getAddress()
assert.strictEqual(address, 'LZJSxZbjqJ2XVEquqfqHg1RQTDdfST5PTn')
assert.strictEqual(wif, 'T7A4PUSgTDHecBxW1ZiYFrDNRih2o7M8Gf9xpoCgudPF9gDiNvuS')
})
it('can import an address via WIF', function () {
var keyPair = bitcoin.ECPair.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')
var address = keyPair.getAddress()
assert.strictEqual(address, '19AAjaTUbRjQCMuVczepkoPswiZRhjtg31')
})
it('can create a Transaction', function () {
var keyPair = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
var tx = new bitcoin.TransactionBuilder()
tx.addInput('aa94ab02c182214f090e99a0d57021caffd0f195a81c24602b1028b130b63e31', 0)
tx.addOutput('1Gokm82v6DmtwKEB8AiVhm82hyFSsEvBDK', 15000)
tx.sign(0, keyPair)
assert.strictEqual(tx.build().toHex(), '0100000001313eb630b128102b60241ca895f1d0ffca2170d5a0990e094f2182c102ab94aa000000006b483045022100aefbcf847900b01dd3e3debe054d3b6d03d715d50aea8525f5ea3396f168a1fb022013d181d05b15b90111808b22ef4f9ebe701caf2ab48db269691fdf4e9048f4f60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01983a0000000000001976a914ad618cf4333b3b248f9744e8e81db2964d0ae39788ac00000000')
})
it('can create a [complex] Transaction', function (done) {
this.timeout(30000)
var testnet = bitcoin.networks.testnet
var alice = bitcoin.ECPair.makeRandom({ network: testnet })
var bob = bitcoin.ECPair.makeRandom({ network: testnet })
var alicesAddress = alice.getAddress()
var bobsAddress = bob.getAddress()
blockchain.t.faucetMany([
{
address: alicesAddress,
value: 4e4
},
{
address: bobsAddress,
value: 2e4
}
], function (err, unspents) {
if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(testnet)
tx.addInput(unspents[0].txId, unspents[0].vout)
tx.addInput(unspents[1].txId, unspents[1].vout)
tx.addOutput(blockchain.t.RETURN, 3e4)
tx.addOutput('mvGVHWi6gbkBZZPaqBVRcxvKVPYd9r3fp7', 1e4)
tx.sign(0, alice)
tx.sign(1, bob)
blockchain.t.transactions.propagate(tx.build().toHex(), done)
})
})
})

View file

@ -1,13 +1,8 @@
/* global describe, it */ /* global describe, it */
var assert = require('assert') var assert = require('assert')
var bigi = require('bigi')
var bip39 = require('bip39') var bip39 = require('bip39')
var bitcoin = require('../../') var bitcoin = require('../../')
var crypto = require('crypto')
var ecurve = require('ecurve')
var secp256k1 = ecurve.getCurveByName('secp256k1')
describe('bitcoinjs-lib (BIP32)', function () { describe('bitcoinjs-lib (BIP32)', function () {
it('can import a BIP32 testnet xpriv and export to WIF', function () { it('can import a BIP32 testnet xpriv and export to WIF', function () {
@ -66,53 +61,6 @@ describe('bitcoinjs-lib (BIP32)', function () {
assert.equal(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2') assert.equal(address, '2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2')
}) })
it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () {
function recoverParent (master, child) {
assert(!master.keyPair.d, 'You already have the parent private key')
assert(child.keyPair.d, 'Missing child private key')
var curve = secp256k1
var QP = master.keyPair.Q
var serQP = master.keyPair.getPublicKeyBuffer()
var d1 = child.keyPair.d
var d2
var data = Buffer.alloc(37)
serQP.copy(data, 0)
// search index space until we find it
for (var i = 0; i < bitcoin.HDNode.HIGHEST_BIT; ++i) {
data.writeUInt32BE(i, 33)
// calculate I
var I = crypto.createHmac('sha512', master.chainCode).update(data).digest()
var IL = I.slice(0, 32)
var pIL = bigi.fromBuffer(IL)
// See hdnode.js:273 to understand
d2 = d1.subtract(pIL).mod(curve.n)
var Qp = new bitcoin.ECPair(d2).Q
if (Qp.equals(QP)) break
}
var node = new bitcoin.HDNode(new bitcoin.ECPair(d2), master.chainCode, master.network)
node.depth = master.depth
node.index = master.index
node.masterFingerprint = master.masterFingerprint
return node
}
var seed = crypto.randomBytes(32)
var master = bitcoin.HDNode.fromSeedBuffer(seed)
var child = master.derive(6) // m/6
// now for the recovery
var neuteredMaster = master.neutered()
var recovered = recoverParent(neuteredMaster, child)
assert.strictEqual(recovered.toBase58(), master.toBase58())
})
it('can use BIP39 to generate BIP32 wallet address', function () { it('can use BIP39 to generate BIP32 wallet address', function () {
// var mnemonic = bip39.generateMnemonic() // var mnemonic = bip39.generateMnemonic()
var mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost' var mnemonic = 'praise you muffin lion enable neck grocery crumble super myself license ghost'

View file

@ -2,15 +2,16 @@
var assert = require('assert') var assert = require('assert')
var bitcoin = require('../../') var bitcoin = require('../../')
var blockchain = require('./_blockchain') var testnetUtils = require('./_testnet')
var network = bitcoin.networks.testnet var testnet = bitcoin.networks.testnet
var alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', network) var alice = bitcoin.ECPair.fromWIF('cScfkGjbzzoeewVWmU2hYPUHeVGJRDdFt7WhmrVVGkxpmPP8BHWe', testnet)
var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', network) var bob = bitcoin.ECPair.fromWIF('cMkopUXKWsEzAjfa1zApksGRwjVpJRB3831qM9W4gKZsLwjHXA9x', testnet)
describe('bitcoinjs-lib (CLTV)', function () { describe('bitcoinjs-lib (transactions w/ CLTV)', function () {
var hashType = bitcoin.Transaction.SIGHASH_ALL var hashType = bitcoin.Transaction.SIGHASH_ALL
// IF MTP > utcSeconds, aQ can redeem, ELSE bQ, aQ joint redeem
function cltvCheckSigOutput (aQ, bQ, utcSeconds) { function cltvCheckSigOutput (aQ, bQ, utcSeconds) {
return bitcoin.script.compile([ return bitcoin.script.compile([
bitcoin.opcodes.OP_IF, bitcoin.opcodes.OP_IF,
@ -33,23 +34,23 @@ describe('bitcoinjs-lib (CLTV)', function () {
} }
// expiry past, {Alice's signature} OP_TRUE // expiry past, {Alice's signature} OP_TRUE
it('where Alice can redeem after the expiry is past', function (done) { it('can create (and broadcast via 3PBP) a Transaction where Alice can redeem the output after the expiry', function (done) {
this.timeout(30000) this.timeout(30000)
// three hours ago // three hours ago
var timeUtc = utcNow() - (3600 * 3) var timeUtc = utcNow() - (3600 * 3)
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey, network) var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet)
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
blockchain.t.faucet(address, 2e4, function (err, unspent) { testnetUtils.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err) if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(network) var tx = new bitcoin.TransactionBuilder(testnet)
tx.setLockTime(timeUtc) tx.setLockTime(timeUtc)
tx.addInput(unspent.txId, 0, 0xfffffffe) tx.addInput(unspent.txId, 0, 0xfffffffe)
tx.addOutput(blockchain.t.RETURN, 1e4) tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4)
var txRaw = tx.buildIncomplete() var txRaw = tx.buildIncomplete()
var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
@ -62,27 +63,28 @@ describe('bitcoinjs-lib (CLTV)', function () {
txRaw.setInputScript(0, redeemScriptSig) txRaw.setInputScript(0, redeemScriptSig)
blockchain.t.transactions.propagate(txRaw.toHex(), done) testnetUtils.transactions.propagate(txRaw.toHex(), done)
}) })
}) })
// expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE // expiry ignored, {Bob's signature} {Alice's signature} OP_FALSE
it('where Alice and Bob can redeem at any time', function (done) { it('can create (and broadcast via 3PBP) a Transaction where Alice and Bob can redeem the output at any time', function (done) {
this.timeout(30000) this.timeout(30000)
// two hours ago // two hours ago
var timeUtc = utcNow() - (3600 * 2) var timeUtc = utcNow() - (3600 * 2)
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey, network) var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet)
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
blockchain.t.faucet(address, 2e4, function (err, unspent) { testnetUtils.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err) if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(network) var tx = new bitcoin.TransactionBuilder(testnet)
tx.setLockTime(timeUtc)
tx.addInput(unspent.txId, 0, 0xfffffffe) tx.addInput(unspent.txId, 0, 0xfffffffe)
tx.addOutput(blockchain.t.RETURN, 1e4) tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4)
var txRaw = tx.buildIncomplete() var txRaw = tx.buildIncomplete()
var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
@ -94,28 +96,28 @@ describe('bitcoinjs-lib (CLTV)', function () {
txRaw.setInputScript(0, redeemScriptSig) txRaw.setInputScript(0, redeemScriptSig)
blockchain.t.transactions.propagate(txRaw.toHex(), done) testnetUtils.transactions.propagate(txRaw.toHex(), done)
}) })
}) })
// expiry in the future, {Alice's signature} OP_TRUE // expiry in the future, {Alice's signature} OP_TRUE
it('fails when still time-locked', function (done) { it('can create (but fail to broadcast via 3PBP) a Transaction where Alice attempts to redeem before the expiry', function (done) {
this.timeout(30000) this.timeout(30000)
// two hours from now // two hours from now
var timeUtc = utcNow() + (3600 * 2) var timeUtc = utcNow() + (3600 * 2)
var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc) var redeemScript = cltvCheckSigOutput(alice, bob, timeUtc)
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript)) var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey, network) var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet)
// fund the P2SH(CLTV) address // fund the P2SH(CLTV) address
blockchain.t.faucet(address, 2e4, function (err, unspent) { testnetUtils.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err) if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(network) var tx = new bitcoin.TransactionBuilder(testnet)
tx.setLockTime(timeUtc) tx.setLockTime(timeUtc)
tx.addInput(unspent.txId, 0, 0xfffffffe) tx.addInput(unspent.txId, 0, 0xfffffffe)
tx.addOutput(blockchain.t.RETURN, 1e4) tx.addOutput(testnetUtils.RETURN_ADDRESS, 1e4)
var txRaw = tx.buildIncomplete() var txRaw = tx.buildIncomplete()
var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType) var signatureHash = txRaw.hashForSignature(0, redeemScript, hashType)
@ -128,7 +130,7 @@ describe('bitcoinjs-lib (CLTV)', function () {
txRaw.setInputScript(0, redeemScriptSig) txRaw.setInputScript(0, redeemScriptSig)
blockchain.t.transactions.propagate(txRaw.toHex(), function (err) { testnetUtils.transactions.propagate(txRaw.toHex(), function (err) {
assert.throws(function () { assert.throws(function () {
if (err) throw err if (err) throw err
}, /Error: 64: non-final/) }, /Error: 64: non-final/)

View file

@ -4,7 +4,8 @@ var assert = require('assert')
var async = require('async') var async = require('async')
var bigi = require('bigi') var bigi = require('bigi')
var bitcoin = require('../../') var bitcoin = require('../../')
var blockchain = require('./_blockchain') var mainnet = require('./_mainnet')
var crypto = require('crypto')
var ecurve = require('ecurve') var ecurve = require('ecurve')
var secp256k1 = ecurve.getCurveByName('secp256k1') var secp256k1 = ecurve.getCurveByName('secp256k1')
@ -27,7 +28,7 @@ describe('bitcoinjs-lib (crypto)', function () {
var txIds = inputs.map(function (x) { return x.txId }) var txIds = inputs.map(function (x) { return x.txId })
// first retrieve the relevant transactions // first retrieve the relevant transactions
blockchain.m.transactions.get(txIds, function (err, results) { mainnet.transactions.get(txIds, function (err, results) {
assert.ifError(err) assert.ifError(err)
var transactions = {} var transactions = {}
@ -49,7 +50,7 @@ describe('bitcoinjs-lib (crypto)', function () {
var prevVout = transaction.ins[input.vout].index var prevVout = transaction.ins[input.vout].index
tasks.push(function (callback) { tasks.push(function (callback) {
blockchain.m.transactions.get(prevOutTxId, function (err, result) { mainnet.transactions.get(prevOutTxId, function (err, result) {
if (err) return callback(err) if (err) return callback(err)
var prevOut = bitcoin.Transaction.fromHex(result.txHex) var prevOut = bitcoin.Transaction.fromHex(result.txHex)
@ -110,4 +111,51 @@ describe('bitcoinjs-lib (crypto)', function () {
}) })
}) })
}) })
it('can recover a BIP32 parent private key from the parent public key, and a derived, non-hardened child private key', function () {
function recoverParent (master, child) {
assert(!master.keyPair.d, 'You already have the parent private key')
assert(child.keyPair.d, 'Missing child private key')
var curve = secp256k1
var QP = master.keyPair.Q
var serQP = master.keyPair.getPublicKeyBuffer()
var d1 = child.keyPair.d
var d2
var data = Buffer.alloc(37)
serQP.copy(data, 0)
// search index space until we find it
for (var i = 0; i < bitcoin.HDNode.HIGHEST_BIT; ++i) {
data.writeUInt32BE(i, 33)
// calculate I
var I = crypto.createHmac('sha512', master.chainCode).update(data).digest()
var IL = I.slice(0, 32)
var pIL = bigi.fromBuffer(IL)
// See hdnode.js:273 to understand
d2 = d1.subtract(pIL).mod(curve.n)
var Qp = new bitcoin.ECPair(d2).Q
if (Qp.equals(QP)) break
}
var node = new bitcoin.HDNode(new bitcoin.ECPair(d2), master.chainCode, master.network)
node.depth = master.depth
node.index = master.index
node.masterFingerprint = master.masterFingerprint
return node
}
var seed = crypto.randomBytes(32)
var master = bitcoin.HDNode.fromSeedBuffer(seed)
var child = master.derive(6) // m/6
// now for the recovery
var neuteredMaster = master.neutered()
var recovered = recoverParent(neuteredMaster, child)
assert.strictEqual(recovered.toBase58(), master.toBase58())
})
}) })

View file

@ -1,76 +0,0 @@
/* global describe, it */
var async = require('async')
var assert = require('assert')
var bitcoin = require('../../')
var blockchain = require('./_blockchain')
describe('bitcoinjs-lib (multisig)', function () {
it('can create a 2-of-3 multisig P2SH address', function () {
var pubKeys = [
'026477115981fe981a6918a6297d9803c4dc04f328f22041bedff886bbc2962e01',
'02c96db2302d19b43d4c69368babace7854cc84eb9e061cde51cfa77ca4a22b8b9',
'03c6103b3b83e4a24a0e33a4df246ef11772f9992663db0c35759a5e2ebf68d8e9'
].map(function (hex) {
return Buffer.from(hex, 'hex')
})
var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 3
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey)
assert.strictEqual(address, '36NUkt6FWUi3LAWBqWRdDmdTWbt91Yvfu7')
})
it('can spend from a 2-of-4 multsig P2SH address', function (done) {
this.timeout(30000)
var keyPairs = [
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx9rcrL7'
].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, bitcoin.networks.testnet) })
var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })
var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 4
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet)
// attempt to send funds to the source address
blockchain.t.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err)
var txb = new bitcoin.TransactionBuilder(bitcoin.networks.testnet)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(blockchain.t.RETURN, 1e4)
// sign with 1st and 3rd key
txb.sign(0, keyPairs[0], redeemScript)
txb.sign(0, keyPairs[2], redeemScript)
// broadcast our transaction
var tx = txb.build()
var txId = tx.getId()
blockchain.t.transactions.propagate(tx.toHex(), function (err) {
if (err) return done(err)
// wait for TX to be accepted
async.retry(5, function (callback) {
setTimeout(function () {
// check that the above transaction included the intended address
blockchain.t.addresses.unspents(blockchain.t.RETURN, function (err, unspents) {
if (err) return callback(err)
if (!unspents.some(function (x) {
return x.txId === txId && x.value === 1e4
})) return callback(new Error('Could not find unspent after broadcast'))
callback()
})
}, 600)
}, done)
})
})
})
})

View file

@ -0,0 +1,157 @@
/* global describe, it */
var assert = require('assert')
var bitcoin = require('../../')
var dhttp = require('dhttp/200')
var testnet = bitcoin.networks.testnet
var testnetUtils = require('./_testnet')
function rng () {
return Buffer.from('YT8dAtK4d16A3P1z+TpwB2jJ4aFH3g9M1EioIBkLEV4=', 'base64')
}
describe('bitcoinjs-lib (transactions)', function () {
it('can create a 1-to-1 Transaction', function () {
var alice = bitcoin.ECPair.fromWIF('L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy')
var tx = new bitcoin.TransactionBuilder()
tx.addInput('61d520ccb74288c96bc1a2b20ea1c0d5a704776dd0164a396efec3ea7040349d', 0) // Alice's previous transaction output, has 15000 satoshis
tx.addOutput('1cMh228HTCiwS8ZsaakH8A8wze1JR5ZsP', 12000)
// (in)15000 - (out)12000 = (fee)3000, this is the miner fee
tx.sign(0, alice)
// build, prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
assert.strictEqual(tx.build().toHex(), '01000000019d344070eac3fe6e394a16d06d7704a7d5c0a10eb2a2c16bc98842b7cc20d561000000006b48304502210088828c0bdfcdca68d8ae0caeb6ec62cd3fd5f9b2191848edae33feb533df35d302202e0beadd35e17e7f83a733f5277028a9b453d525553e3f5d2d7a7aa8010a81d60121029f50f51d63b345039a290c94bffd3180c99ed659ff6ea6b1242bca47eb93b59fffffffff01e02e0000000000001976a91406afd46bcdfd22ef94ac122aa11f241244a37ecc88ac00000000')
})
it('can create a 2-to-2 Transaction', function () {
var alice = bitcoin.ECPair.fromWIF('L1Knwj9W3qK3qMKdTvmg3VfzUs3ij2LETTFhxza9LfD5dngnoLG1')
var bob = bitcoin.ECPair.fromWIF('KwcN2pT3wnRAurhy7qMczzbkpY5nXMW2ubh696UBc1bcwctTx26z')
var tx = new bitcoin.TransactionBuilder()
tx.addInput('b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c', 6) // Alice's previous transaction output, has 200000 satoshis
tx.addInput('7d865e959b2466918c9863afca942d0fb89d7c9ac0c99bafc3749504ded97730', 0) // Bob's previous transaction output, has 300000 satoshis
tx.addOutput('1CUNEBjYrCn2y1SdiUMohaKUi4wpP326Lb', 180000)
tx.addOutput('1JtK9CQw1syfWj1WtFMWomrYdV3W2tWBF9', 170000)
// (in)(200000 + 300000) - (out)(180000 + 150000) = (fee)170000, this is the miner fee
tx.sign(1, bob) // Bob signs his input, which was the second input (1th)
tx.sign(0, alice) // Alice signs her input, which was the first input (0th)
// build, prepare for broadcast to the Bitcoin network, see "can broadcast a Transaction" below
assert.strictEqual(tx.build().toHex(), '01000000024c94e48a870b85f41228d33cf25213dfcc8dd796e7211ed6b1f9a014809dbbb5060000006a473044022041450c258ce7cac7da97316bf2ea1ce66d88967c4df94f3e91f4c2a30f5d08cb02203674d516e6bb2b0afd084c3551614bd9cec3c2945231245e891b145f2d6951f0012103e05ce435e462ec503143305feb6c00e06a3ad52fbf939e85c65f3a765bb7baacffffffff3077d9de049574c3af9bc9c09a7c9db80f2d94caaf63988c9166249b955e867d000000006b483045022100aeb5f1332c79c446d3f906e4499b2e678500580a3f90329edf1ba502eec9402e022072c8b863f8c8d6c26f4c691ac9a6610aa4200edc697306648ee844cfbc089d7a012103df7940ee7cddd2f97763f67e1fb13488da3fbdd7f9c68ec5ef0864074745a289ffffffff0220bf0200000000001976a9147dd65592d0ab2fe0d0257d571abf032cd9db93dc88ac10980200000000001976a914c42e7ef92fdb603af844d064faad95db9bcdfd3d88ac00000000')
})
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 })
// "simulate" on testnet that Alice has 2 unspent outputs
testnetUtils.faucetMany([
{
address: alice1.getAddress(),
value: 4e4
},
{
address: alice2.getAddress(),
value: 2e4
}
], function (err, unspents) {
if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(testnet)
tx.addInput(unspents[0].txId, unspents[0].vout) // alice1 unspent
tx.addInput(unspents[1].txId, unspents[1].vout) // alice2 unspent
tx.addOutput('mvGVHWi6gbkBZZPaqBVRcxvKVPYd9r3fp7', 1e4) // the actual "spend"
tx.addOutput(aliceChange.getAddress(), 3e4) // Alice's change
// (in)(4e4 + 2e4) - (out)(1e4 + 3e4) = (fee)2e4 = 20000, this is the miner fee
// Alice signs each input with the respective private keys
tx.sign(0, alice1)
tx.sign(1, alice2)
// 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: tx.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()
testnetUtils.faucet(address, 5e4, function (err, unspent) {
if (err) return done(err)
var tx = new bitcoin.TransactionBuilder(testnet)
var data = Buffer.from('bitcoinjs-lib', 'utf8')
var dataScript = bitcoin.script.nullData.output.encode(data)
tx.addInput(unspent.txId, unspent.vout)
tx.addOutput(dataScript, 1000)
tx.addOutput(testnetUtils.RETURN_ADDRESS, 4e4)
tx.sign(0, keyPair)
// build and broadcast to the Bitcoin Testnet network
dhttp({
method: 'POST',
url: 'https://api.ei8ht.com.au:9443/3/pushtx',
body: tx.build().toHex()
}, done)
})
})
it('can create (and broadcast via 3PBP) a Transaction with a 2-of-4 multisig P2SH input', function (done) {
this.timeout(30000)
var keyPairs = [
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgwmaKkrx',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgww7vXtT',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx3cTMqe',
'91avARGdfge8E4tZfYLoxeJ5sGBdNJQH4kvjJoQFacbgx9rcrL7'
].map(function (wif) { return bitcoin.ECPair.fromWIF(wif, testnet) })
var pubKeys = keyPairs.map(function (x) { return x.getPublicKeyBuffer() })
var redeemScript = bitcoin.script.multisig.output.encode(2, pubKeys) // 2 of 4
var scriptPubKey = bitcoin.script.scriptHash.output.encode(bitcoin.crypto.hash160(redeemScript))
var address = bitcoin.address.fromOutputScript(scriptPubKey, testnet)
// attempt to send funds to the source address
testnetUtils.faucet(address, 2e4, function (err, unspent) {
if (err) return done(err)
var txb = new bitcoin.TransactionBuilder(testnet)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(testnetUtils.RETURN_ADDRESS, 1e4)
// sign with 1st and 3rd key
txb.sign(0, keyPairs[0], redeemScript)
txb.sign(0, keyPairs[2], redeemScript)
// broadcast our transaction
var tx = txb.build()
var txId = tx.getId()
dhttp({
method: 'POST',
url: 'https://api.ei8ht.com.au:9443/3/pushtx',
body: tx.toHex()
}, function (err) {
if (err) return done(err)
testnetUtils.verify(address, txId, 1e4, done)
})
})
})
})