testing/integration/examples: isolate to addresses/transactions
examples, use public broadcast endpoints
This commit is contained in:
parent
798ec3512c
commit
a4fe3d3139
12 changed files with 358 additions and 295 deletions
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
3
test/integration/_mainnet.js
Normal file
3
test/integration/_mainnet.js
Normal 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 })
|
|
@ -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
|
92
test/integration/addresses.js
Normal file
92
test/integration/addresses.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -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'
|
||||||
|
|
|
@ -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/)
|
||||||
|
|
|
@ -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())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
157
test/integration/transactions.js
Normal file
157
test/integration/transactions.js
Normal 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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
Loading…
Reference in a new issue