Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ The below examples are implemented as integration tests, they should be very eas
- [Create a Transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/basic.js#L36)
- [Sign a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L9)
- [Verify a Bitcoin message](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L17)
- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L25)
- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L58)
- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L60)
- [Create an OP RETURN transaction](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/advanced.js#L24)
- [Create a 2-of-3 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L8)
- [Spend from a 2-of-2 multisig P2SH address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/multisig.js#L22)
- [Generate a single-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L7)
- [Generate a dual-key stealth address](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L40)
- [Recover a BIP32 parent private key from the parent public key and a derived non-hardened child private key](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L42)
- [Recover a Private key from duplicate R values in a signature](https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/crypto.js#L90)


## Projects utilizing BitcoinJS
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,11 @@
"ecurve": "1.0.0"
},
"devDependencies": {
"async": "^0.9.0",
"browserify": "^5.12.0",
"bs58": "^2.0.0",
"cb-helloblock": "^0.4.7",
"coveralls": "^2.11.2",
"helloblock-js": "^0.2.5",
"istanbul": "^0.3.2",
"jshint": "^2.5.6",
"mocha": "^1.21.4",
Expand Down
4 changes: 2 additions & 2 deletions src/hdnode.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ function HDNode(K, chainCode, network) {
this.chainCode = chainCode
this.depth = 0
this.index = 0
this.parentFingerprint = 0x00000000
this.network = network

if (K instanceof BigInteger) {
Expand Down Expand Up @@ -196,8 +197,7 @@ HDNode.prototype.toBuffer = function(isPrivate, __ignoreDeprecation) {
buffer.writeUInt8(this.depth, 4)

// 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
var fingerprint = (this.depth === 0) ? 0x00000000 : this.parentFingerprint
buffer.writeUInt32BE(fingerprint, 5)
buffer.writeUInt32BE(this.parentFingerprint, 5)

// 4 bytes: child number. This is the number i in xi = xpar/i, with xi the key being serialized.
// This is encoded in Big endian. (0x00000000 if master key)
Expand Down
55 changes: 8 additions & 47 deletions test/integration/advanced.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
var assert = require('assert')
var bigi = require('bigi')
var bitcoin = require('../../')
var helloblock = require('helloblock-js')({
network: 'testnet'
})
var blockchain = new (require('cb-helloblock'))('testnet')

describe('bitcoinjs-lib (advanced)', function() {
it('can sign a Bitcoin message', function() {
Expand All @@ -22,51 +19,16 @@ describe('bitcoinjs-lib (advanced)', function() {
assert(bitcoin.Message.verify(address, signature, message))
})

it('can generate a single-key stealth address', function() {
var receiver = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss')

// XXX: ephemeral, must be random (and secret to sender) to preserve privacy
var sender = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')

var G = bitcoin.ECKey.curve.G
var d = receiver.d // secret (receiver only)
var Q = receiver.pub.Q // shared

var e = sender.d // secret (sender only)
var P = sender.pub.Q // shared

// derived shared secret
var eQ = Q.multiply(e) // sender
var dP = P.multiply(d) // receiver
assert.deepEqual(eQ.getEncoded(), dP.getEncoded())

var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
var cG = G.multiply(c)

// derived public key
var QprimeS = Q.add(cG)
var QprimeR = G.multiply(d.add(c))
assert.deepEqual(QprimeR.getEncoded(), QprimeS.getEncoded())

// derived shared-secret address
var address = new bitcoin.ECPubKey(QprimeS).getAddress().toString()

assert.equal(address, '1EwCNJNZM5q58YPPTnjR1H5BvYRNeyZi47')
})

// TODO
it.skip('can generate a dual-key stealth address', function() {})

it('can create an OP_RETURN transaction', function(done) {
this.timeout(20000)

var key = bitcoin.ECKey.fromWIF("L1uyy5qTuGrVXrmrsvHWHgVzW9kKdrp27wBC7Vs6nZDTF2BRUVwy")
var address = key.pub.getAddress(bitcoin.networks.testnet).toString()

helloblock.faucet.withdraw(address, 2e4, function(err) {
blockchain.addresses.__faucetWithdraw(address, 2e4, function(err) {
if (err) return done(err)

helloblock.addresses.getUnspents(address, function(err, _, unspents) {
blockchain.addresses.unspents(address, function(err, unspents) {
if (err) return done(err)

// filter small unspents
Expand All @@ -80,20 +42,19 @@ describe('bitcoinjs-lib (advanced)', function() {
var data = new Buffer('cafedeadbeef', 'hex')
var dataScript = bitcoin.scripts.nullDataOutput(data)

tx.addInput(unspent.txHash, unspent.index)
tx.addInput(unspent.txId, unspent.vout)
tx.addOutput(dataScript, 1000)
tx.sign(0, key)

helloblock.transactions.propagate(tx.build().toHex(), function(err) {
blockchain.transactions.propagate(tx.build().toHex(), function(err) {
if (err) return done(err)

// check that the message was propagated
helloblock.addresses.getTransactions(address, function(err, res, transactions) {
blockchain.addresses.transactions(address, function(err, transactions) {
if (err) return done(err)

var transaction = transactions[0]
var output = transaction.outputs[0]
var dataScript2 = bitcoin.Script.fromHex(output.scriptPubKey)
var transaction = bitcoin.Transaction.fromHex(transactions[0].txHex)
var dataScript2 = transaction.outs[0].script
var data2 = dataScript2.chunks[1]

assert.deepEqual(dataScript, dataScript2)
Expand Down
183 changes: 183 additions & 0 deletions test/integration/crypto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
var assert = require('assert')
var async = require('async')
var bigi = require('bigi')
var bitcoin = require('../../')
var blockchain = new (require('cb-helloblock'))('bitcoin')
var crypto = require('crypto')

describe('bitcoinjs-lib (crypto)', function() {
it('can generate a single-key stealth address', function() {
var receiver = bitcoin.ECKey.fromWIF('5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss')

// XXX: ephemeral, must be random (and secret to sender) to preserve privacy
var sender = bitcoin.ECKey.fromWIF('Kxr9tQED9H44gCmp6HAdmemAzU3n84H3dGkuWTKvE23JgHMW8gct')

var G = bitcoin.ECKey.curve.G
var d = receiver.d // secret (receiver only)
var Q = receiver.pub.Q // shared

var e = sender.d // secret (sender only)
var P = sender.pub.Q // shared

// derived shared secret
var eQ = Q.multiply(e) // sender
var dP = P.multiply(d) // receiver
assert.deepEqual(eQ.getEncoded(), dP.getEncoded())

var c = bigi.fromBuffer(bitcoin.crypto.sha256(eQ.getEncoded()))
var cG = G.multiply(c)

// derived public key
var QprimeS = Q.add(cG)
var QprimeR = G.multiply(d.add(c))
assert.deepEqual(QprimeR.getEncoded(), QprimeS.getEncoded())

// derived shared-secret address
var address = new bitcoin.ECPubKey(QprimeS).getAddress().toString()

assert.equal(address, '1EwCNJNZM5q58YPPTnjR1H5BvYRNeyZi47')
})

// TODO
it.skip('can generate a dual-key stealth address', function() {})

it('can recover a parent private key from the parent\'s public key and a derived non-hardened child private key', function() {
function recoverParent(master, child) {
assert(!master.privKey, 'You already have the parent private key')
assert(child.privKey, 'Missing child private key')

var curve = bitcoin.ECKey.curve
var QP = master.pubKey.toBuffer()
var QP64 = QP.toString('base64')
var d1 = child.privKey.d
var d2
var indexBuffer = new Buffer(4)

// search index space until we find it
for (var i = 0; i < bitcoin.HDNode.HIGHEST_BIT; ++i) {
indexBuffer.writeUInt32BE(i, 0)

// calculate I
var data = Buffer.concat([QP, indexBuffer])
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.ECKey(d2, true).pub.toBuffer()
if (Qp.toString('base64') === QP64) break
}

var node = new bitcoin.HDNode(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.equal(recovered.toBase58(), master.toBase58())
})

it('can recover a private key from duplicate R values', function() {
var inputs = [
{
txId: "f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50",
vout: 0
},
{
txId: "f4c16475f2a6e9c602e4a287f9db3040e319eb9ece74761a4b84bc820fbeef50",
vout: 1
}
]

var txIds = inputs.map(function(x) { return x.txId })

// first retrieve the relevant transactions
blockchain.transactions.get(txIds, function(err, results) {
assert.ifError(err)

var transactions = {}
results.forEach(function(tx) {
transactions[tx.txId] = bitcoin.Transaction.fromHex(tx.txHex)
})

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
assert(bitcoin.scripts.isPubKeyHashInput(script), 'Expected pubKeyHash script')

var prevOutTxId = bitcoin.bufferutils.reverse(transaction.ins[input.vout].hash).toString('hex')
var prevVout = transaction.ins[input.vout].index

tasks.push(function(callback) {
blockchain.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(script.chunks[0])
var publicKey = bitcoin.ECPubKey.fromBuffer(script.chunks[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 = bitcoin.ECKey.curve.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.equal(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.equal(d1.toString(), d2.toString())
}
}
})
})
})
})
16 changes: 7 additions & 9 deletions test/integration/multisig.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
var assert = require('assert')
var bitcoin = require('../../')
var helloblock = require('helloblock-js')({
network: 'testnet'
})
var blockchain = new (require('cb-helloblock'))('testnet')

describe('bitcoinjs-lib (multisig)', function() {
it('can create a 2-of-3 multisig P2SH address', function() {
Expand Down Expand Up @@ -33,11 +31,11 @@ describe('bitcoinjs-lib (multisig)', function() {
var address = bitcoin.Address.fromOutputScript(scriptPubKey, bitcoin.networks.testnet).toString()

// Attempt to send funds to the source address
helloblock.faucet.withdraw(address, 2e4, function(err) {
blockchain.addresses.__faucetWithdraw(address, 2e4, function(err) {
if (err) return done(err)

// get latest unspents from the address
helloblock.addresses.getUnspents(address, function(err, _, unspents) {
blockchain.addresses.unspents(address, function(err, unspents) {
if (err) return done(err)

// filter small unspents
Expand All @@ -50,7 +48,7 @@ describe('bitcoinjs-lib (multisig)', function() {
var targetAddress = bitcoin.ECKey.makeRandom().pub.getAddress(bitcoin.networks.testnet).toString()

var txb = new bitcoin.TransactionBuilder()
txb.addInput(unspent.txHash, unspent.index)
txb.addInput(unspent.txId, unspent.vout)
txb.addOutput(targetAddress, 1e4)

// sign w/ each private key
Expand All @@ -59,14 +57,14 @@ describe('bitcoinjs-lib (multisig)', function() {
})

// broadcast our transaction
helloblock.transactions.propagate(txb.build().toHex(), function(err) {
blockchain.transactions.propagate(txb.build().toHex(), function(err) {
if (err) return done(err)

// check that the funds (1e4 Satoshis) indeed arrived at the intended address
helloblock.addresses.get(targetAddress, function(err, res, addrInfo) {
blockchain.addresses.summary(targetAddress, function(err, result) {
if (err) return done(err)

assert.equal(addrInfo.balance, 1e4)
assert.equal(result.balance, 1e4)
done()
})
})
Expand Down