Skip to content
20 changes: 10 additions & 10 deletions src/bufferutils.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
var assert = require('assert')
var opcodes = require('./opcodes')

// https://github.com/feross/buffer/blob/master/index.js#L1127
function verifuint(value, max) {
assert(typeof value === 'number', 'cannot write a non-number as a number')
assert(value >= 0, 'specified a negative value for writing an unsigned value')
assert(value <= max, 'value is larger than maximum value for type')
assert(Math.floor(value) === value, 'value has a fractional component')
}

function pushDataSize(i) {
return i < opcodes.OP_PUSHDATA1 ? 1
: i < 0xff ? 2
Expand Down Expand Up @@ -47,9 +55,7 @@ function readUInt64LE(buffer, offset) {
var b = buffer.readUInt32LE(offset + 4)
b *= 0x100000000

// Javascript Safe Integer limitation
// assert(Number.isSafeInteger(value), 'value must be < 2^53')
assert(b + a < 0x0020000000000000, 'value must be < 2^53')
verifuint(b + a, 0x001fffffffffffff)

return b + a
}
Expand Down Expand Up @@ -104,10 +110,6 @@ function writePushDataInt(buffer, number, offset) {

// 32 bit
} else {
// Javascript Safe Integer limitation
// assert(Number.isSafeInteger(value), 'value must be < 2^53')
assert(number < 0x0020000000000000, 'value must be < 2^53')

buffer.writeUInt8(opcodes.OP_PUSHDATA4, offset)
buffer.writeUInt32LE(number, offset + 1)

Expand All @@ -117,9 +119,7 @@ function writePushDataInt(buffer, number, offset) {
}

function writeUInt64LE(buffer, value, offset) {
// Javascript Safe Integer limitation
// assert(Number.isSafeInteger(value), 'value must be < 2^53')
assert(value < 0x0020000000000000, 'value must be < 2^53')
verifuint(value, 0x001fffffffffffff)

buffer.writeInt32LE(value & -1, offset)
buffer.writeUInt32LE(Math.floor(value / 0x100000000), offset + 4)
Expand Down
28 changes: 13 additions & 15 deletions src/hdwallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ var networks = require('./networks')
var sec = require('./sec')
var ecparams = sec("secp256k1")

function HDWallet(seed, networkString) {
function HDWallet(seed, network) {
if (seed == undefined) return; // FIXME: Boo, should be stricter

this.network = networkString || 'bitcoin'

if(!networks.hasOwnProperty(this.network)) {
throw new Error("Unknown network: " + this.network)
}
network = network || networks.bitcoin
assert(network.bip32, 'Unknown BIP32 constants for network')

var I = crypto.HmacSHA512(seed, HDWallet.MASTER_SECRET)
var IL = I.slice(0, 32)
Expand All @@ -28,6 +25,7 @@ function HDWallet(seed, networkString) {
// In case IL is 0 or >= n, the master key is invalid (handled by ECKey.fromBuffer)
var pIL = BigInteger.fromBuffer(IL)

this.network = network
this.priv = new ECKey(pIL, true)
this.pub = this.priv.pub

Expand All @@ -40,8 +38,8 @@ HDWallet.MASTER_SECRET = new Buffer('Bitcoin seed')
HDWallet.HIGHEST_BIT = 0x80000000
HDWallet.LENGTH = 78

HDWallet.fromSeedHex = function(hex, networkString) {
return new HDWallet(new Buffer(hex, 'hex'), networkString)
HDWallet.fromSeedHex = function(hex, network) {
return new HDWallet(new Buffer(hex, 'hex'), network)
}

HDWallet.fromBase58 = function(string) {
Expand All @@ -51,8 +49,8 @@ HDWallet.fromBase58 = function(string) {
var checksum = buffer.slice(-4)
var newChecksum = crypto.hash256(payload).slice(0, 4)

assert.deepEqual(newChecksum, checksum)
assert.equal(payload.length, HDWallet.LENGTH)
assert.deepEqual(newChecksum, checksum, 'Invalid checksum')
assert.equal(payload.length, HDWallet.LENGTH, 'Invalid BIP32 string')

return HDWallet.fromBuffer(payload)
}
Expand All @@ -66,14 +64,14 @@ HDWallet.fromBuffer = function(input) {
var version = input.readUInt32BE(0)

var type
for(var name in networks) {
for (var name in networks) {
var network = networks[name]

for(var t in network.bip32) {
for (var t in network.bip32) {
if (version != network.bip32[t]) continue

type = t
hd.network = name
hd.network = network
}
}

Expand Down Expand Up @@ -127,7 +125,7 @@ HDWallet.prototype.getAddress = function() {

HDWallet.prototype.toBuffer = function(priv) {
// Version
var version = networks[this.network].bip32[priv ? 'priv' : 'pub']
var version = this.network.bip32[priv ? 'priv' : 'pub']
var buffer = new Buffer(HDWallet.LENGTH)

// 4 bytes: version bytes
Expand Down Expand Up @@ -254,7 +252,7 @@ HDWallet.prototype.derivePrivate = function(index) {
}

HDWallet.prototype.getKeyVersion = function() {
return networks[this.network].pubKeyHash
return this.network.pubKeyHash
}

HDWallet.prototype.toString = HDWallet.prototype.toBase58
Expand Down
118 changes: 39 additions & 79 deletions src/wallet.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
var Address = require('./address')
var convert = require('./convert')
var HDNode = require('./hdwallet.js')
var assert = require('assert')
var networks = require('./networks')
var rng = require('secure-random')
var Transaction = require('./transaction').Transaction

function Wallet(seed, options) {
if (!(this instanceof Wallet)) { return new Wallet(seed, options); }
var Address = require('./address')
var HDNode = require('./hdwallet')
var Transaction = require('./transaction').Transaction

var options = options || {}
var network = options.network || 'bitcoin'
function Wallet(seed, network) {
network = network || networks.bitcoin

// Stored in a closure to make accidental serialization less likely
var masterkey = null
Expand All @@ -22,11 +20,14 @@ function Wallet(seed, options) {
this.addresses = []
this.changeAddresses = []

// Dust value
this.dustThreshold = 5430

// Transaction output data
this.outputs = {}

// Make a new master key
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed network here as it is implied by the closure it resides in. If a user was to specify otherwise, it would produce a HDWallet node with a different network type to the master wallet. This seems erroneous and just plain confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. Good call

this.newMasterKey = function(seed, network) {
this.newMasterKey = function(seed) {
seed = seed || new Buffer(rng(32))
masterkey = new HDNode(seed, network)

Expand All @@ -41,7 +42,8 @@ function Wallet(seed, options) {

me.outputs = {}
}
this.newMasterKey(seed, network)

this.newMasterKey(seed)

this.generateAddress = function() {
var key = externalAccount.derive(this.addresses.length)
Expand Down Expand Up @@ -84,31 +86,19 @@ function Wallet(seed, options) {
this.outputs = outputs
}

this.setUnspentOutputsAsync = function(utxo, callback) {
var error = null
try {
this.setUnspentOutputs(utxo)
} catch(err) {
error = err
} finally {
process.nextTick(function(){ callback(error) })
}
}

function outputToUnspentOutput(output){
var hashAndIndex = output.receive.split(":")

return {
hash: hashAndIndex[0],
hashLittleEndian: convert.reverseEndian(hashAndIndex[0]),
outputIndex: parseInt(hashAndIndex[1]),
address: output.address,
value: output.value
}
}

function unspentOutputToOutput(o) {
var hash = o.hash || convert.reverseEndian(o.hashLittleEndian)
var hash = o.hash
var key = hash + ":" + o.outputIndex
return {
receive: key,
Expand All @@ -120,8 +110,8 @@ function Wallet(seed, options) {
function validateUnspentOutput(uo) {
var missingField

if (isNullOrUndefined(uo.hash) && isNullOrUndefined(uo.hashLittleEndian)) {
missingField = "hash(or hashLittleEndian)"
if (isNullOrUndefined(uo.hash)) {
missingField = "hash"
}

var requiredKeys = ['outputIndex', 'address', 'value']
Expand All @@ -137,7 +127,7 @@ function Wallet(seed, options) {
'A valid unspent output must contain'
]
message.push(requiredKeys.join(', '))
message.push("and hash(or hashLittleEndian)")
message.push("and hash")
throw new Error(message.join(' '))
}
}
Expand All @@ -153,7 +143,7 @@ function Wallet(seed, options) {
var address

try {
address = Address.fromScriptPubKey(txOut.script, networks[network]).toString()
address = Address.fromScriptPubKey(txOut.script, network).toString()
} catch(e) {
if (!(e.message.match(/has no matching Address/))) throw e
}
Expand All @@ -171,73 +161,50 @@ function Wallet(seed, options) {

tx.ins.forEach(function(txIn, i){
var op = txIn.outpoint
var o = me.outputs[op.hash+':'+op.index]

var o = me.outputs[op.hash + ':' + op.index]
if (o) {
o.spend = txhash + ':' +i
o.spend = txhash + ':' + i
}
})
}

this.createTx = function(to, value, fixedFee, changeAddress) {
checkDust(value)
assert(value > this.dustThreshold, value + ' must be above dust threshold (' + this.dustThreshold + ' Satoshis)')

var utxos = getCandidateOutputs(value)
var accum = 0
var subTotal = value

var tx = new Transaction()
tx.addOutput(to, value)

var utxo = getCandidateOutputs(value)
var totalInValue = 0
for(var i=0; i<utxo.length; i++){
var output = utxo[i]
tx.addInput(output.receive)
for (var i = 0; i < utxos.length; ++i) {
var utxo = utxos[i]

totalInValue += output.value
if(totalInValue < value) continue
tx.addInput(utxo.receive)
accum += utxo.value

var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee
if(totalInValue < value + fee) continue

var change = totalInValue - value - fee
if(change > 0 && !isDust(change)) {
tx.addOutput(changeAddress || getChangeAddress(), change)
subTotal = value + fee
if (accum >= subTotal) {
var change = accum - subTotal

if (change > this.dustThreshold) {
tx.addOutput(changeAddress || getChangeAddress(), change)
}

break
}
break
}

checkInsufficientFund(totalInValue, value, fee)
assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal)

this.sign(tx)

return tx
}

this.createTxAsync = function(to, value, fixedFee, callback){
if(fixedFee instanceof Function) {
callback = fixedFee
fixedFee = undefined
}
var tx = null
var error = null

try {
tx = this.createTx(to, value, fixedFee)
} catch(err) {
error = err
} finally {
process.nextTick(function(){ callback(error, tx) })
}
}

this.dustThreshold = 5430
function isDust(amount) {
return amount <= me.dustThreshold
}

function checkDust(value){
if (isNullOrUndefined(value) || isDust(value)) {
throw new Error("Value must be above dust threshold")
}
}

function getCandidateOutputs(value){
var unspent = []
for (var key in me.outputs){
Expand All @@ -263,13 +230,6 @@ function Wallet(seed, options) {
return me.changeAddresses[me.changeAddresses.length - 1]
}

function checkInsufficientFund(totalInValue, value, fee) {
if(totalInValue < value + fee) {
throw new Error('Not enough money to send funds including transaction fee. Have: ' +
totalInValue + ', needed: ' + (value + fee))
}
}

this.sign = function(tx) {
tx.ins.forEach(function(inp,i) {
var output = me.outputs[inp.outpoint.hash + ':' + inp.outpoint.index]
Expand Down
Loading