Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BN #2

Closed
wants to merge 3 commits into from
Closed

BN #2

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
25 changes: 14 additions & 11 deletions accumulative.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,41 @@
var utils = require('./utils')
var ext = require('./bn-extensions')

// add inputs until we reach or surpass the target value (or deplete)
// worst-case: O(n)
module.exports = function accumulative (utxos, outputs, feeRate) {
if (!isFinite(utils.uintOrNaN(feeRate))) return {}
if (!utils.uintOrNull(feeRate)) return {}
var bytesAccum = utils.transactionBytes([], outputs)

var inAccum = 0
var inAccum = ext.BN_ZERO
var inputs = []
var outAccum = utils.sumOrNaN(outputs)

for (var i = 0; i < utxos.length; ++i) {
var utxo = utxos[i]
var utxoBytes = utils.inputBytes(utxo)
var utxoFee = feeRate * utxoBytes
var utxoValue = utils.uintOrNaN(utxo.value)
var utxoFee = ext.mul(feeRate, utxoBytes)
var utxoValue = utils.uintOrNull(utxo.value)

// skip detrimental input
if (utxoFee > utxo.value) {
if (i === utxos.length - 1) return { fee: feeRate * (bytesAccum + utxoBytes) }
if (ext.gt(utxoFee, utxoValue)) {
if (i === utxos.length - 1) {
return { fee: ext.mul(feeRate, ext.add(bytesAccum, utxoBytes)) }
}
continue
}

bytesAccum += utxoBytes
inAccum += utxoValue
bytesAccum = ext.add(bytesAccum, utxoBytes)
inAccum = ext.add(inAccum, utxoValue)
inputs.push(utxo)

var fee = feeRate * bytesAccum
var fee = ext.mul(feeRate, bytesAccum)

// go again?
if (inAccum < outAccum + fee) continue
if (ext.lt(inAccum, ext.add(outAccum, fee))) continue

return utils.finalize(inputs, outputs, feeRate)
}

return { fee: feeRate * bytesAccum }
return { fee: ext.mul(feeRate, bytesAccum) }
}
20 changes: 10 additions & 10 deletions blackjack.js
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
var utils = require('./utils')
var ext = require('./bn-extensions')

// only add inputs if they don't bust the target value (aka, exact match)
// worst-case: O(n)
module.exports = function blackjack (utxos, outputs, feeRate) {
if (!isFinite(utils.uintOrNaN(feeRate))) return {}
if (!utils.uintOrNull(feeRate)) return {}

var bytesAccum = utils.transactionBytes([], outputs)

var inAccum = 0
var inAccum = ext.BN_ZERO
var inputs = []
var outAccum = utils.sumOrNaN(outputs)
var threshold = utils.dustThreshold({}, feeRate)

for (var i = 0; i < utxos.length; ++i) {
var input = utxos[i]
var inputBytes = utils.inputBytes(input)
var fee = feeRate * (bytesAccum + inputBytes)
var inputValue = utils.uintOrNaN(input.value)
var fee = ext.mul(feeRate, ext.add(bytesAccum, inputBytes))
var inputValue = utils.uintOrNull(input.value)

// would it waste value?
if ((inAccum + inputValue) > (outAccum + fee + threshold)) continue
if (ext.gt(ext.add(inAccum, inputValue), ext.add(outAccum, fee, threshold))) continue

bytesAccum += inputBytes
inAccum += inputValue
bytesAccum = ext.add(bytesAccum, inputBytes)
inAccum = ext.add(inAccum, inputValue)
inputs.push(input)

// go again?
if (inAccum < outAccum + fee) continue
if (ext.lt(inAccum, ext.add(outAccum, fee))) continue

return utils.finalize(inputs, outputs, feeRate)
}

return { fee: feeRate * bytesAccum }
return { fee: ext.mul(feeRate, bytesAccum) }
}
79 changes: 79 additions & 0 deletions bn-extensions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
var BN = require('bn.js')
var slice = Array.prototype.slice

var BN_ZERO = new BN(0)
var BN_ONE = new BN(1)

function add () {
var args = slice.call(arguments)
return args.reduce(_add)
}

function _add (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return null
return a.add(b)
}

function sub () {
var args = slice.call(arguments)
return args.reduce(_sub)
}

function _sub (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return null
return a.sub(b)
}

function mul () {
var args = slice.call(arguments)
return args.reduce(_mul)
}

function _mul (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return null
return a.mul(b)
}

function div () {
var args = slice.call(arguments)
return args.reduce(_div)
}

function _div (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return null
if (b.isZero()) return null
return a.div(b)
}

function isZero (v) {
if (!BN.isBN(v)) return false
return v.isZero()
}

function eq (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return false
return a.eq(b)
}

function lt (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return false
return a.lt(b)
}

function gt (a, b) {
if (!BN.isBN(a) || !BN.isBN(b)) return false
return a.gt(b)
}

module.exports = {
mul: mul,
div: div,
add: add,
sub: sub,
isZero: isZero,
eq: eq,
lt: lt,
gt: gt,
BN_ZERO: BN_ZERO,
BN_ONE: BN_ONE
}
22 changes: 11 additions & 11 deletions break.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
var utils = require('./utils')
var ext = require('./bn-extensions')

// break utxos into the maximum number of 'output' possible
module.exports = function broken (utxos, output, feeRate) {
if (!isFinite(utils.uintOrNaN(feeRate))) return {}
if (!utils.uintOrNull(feeRate)) return {}

var bytesAccum = utils.transactionBytes(utxos, [])
var value = utils.uintOrNaN(output.value)
var value = utils.uintOrNull(output.value)
var inAccum = utils.sumOrNaN(utxos)
if (!isFinite(value) ||
!isFinite(inAccum)) return { fee: feeRate * bytesAccum }

if (!value || !inAccum) return { fee: ext.mul(feeRate, bytesAccum) }

var outputBytes = utils.outputBytes(output)
var outAccum = 0
var outAccum = ext.BN_ZERO
var outputs = []

while (true) {
var fee = feeRate * (bytesAccum + outputBytes)
var fee = ext.mul(feeRate, ext.add(bytesAccum, outputBytes))

// did we bust?
if (inAccum < (outAccum + fee + value)) {
if (ext.lt(inAccum, ext.add(outAccum, fee, value))) {
// premature?
if (outAccum === 0) return { fee: fee }

if (ext.isZero(outAccum)) return { fee: fee }
break
}

bytesAccum += outputBytes
outAccum += value
bytesAccum = ext.add(bytesAccum, outputBytes)
outAccum = ext.add(outAccum, value)
outputs.push(output)
}

Expand Down
5 changes: 3 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
var accumulative = require('./accumulative')
var blackjack = require('./blackjack')
var utils = require('./utils')
var ext = require('./bn-extensions')

// order by descending value, minus the inputs approximate fee
function utxoScore (x, feeRate) {
return x.value - (feeRate * utils.inputBytes(x))
return ext.sub(x.value, ext.mul(feeRate, utils.inputBytes(x)))
}

module.exports = function coinSelect (utxos, outputs, feeRate) {
utxos = utxos.concat().sort(function (a, b) {
return utxoScore(b, feeRate) - utxoScore(a, feeRate)
return ext.sub(utxoScore(b, feeRate), utxoScore(a, feeRate))
})

// attempt to use the blackjack strategy first (no change output)
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,7 @@
"standard": "*",
"tape": "^4.5.1"
},
"dependencies": {}
"dependencies": {
"bn.js": "^4.11.8"
}
}
25 changes: 15 additions & 10 deletions split.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,37 @@
var utils = require('./utils')
var BN = require('bn.js')
var ext = require('./bn-extensions')

// split utxos between each output, ignores outputs with .value defined
module.exports = function split (utxos, outputs, feeRate) {
if (!isFinite(utils.uintOrNaN(feeRate))) return {}
if (!utils.uintOrNull(feeRate)) return {}

var bytesAccum = utils.transactionBytes(utxos, outputs)
var fee = feeRate * bytesAccum
var fee = ext.mul(feeRate, bytesAccum)
if (outputs.length === 0) return { fee: fee }

var inAccum = utils.sumOrNaN(utxos)
var outAccum = utils.sumForgiving(outputs)
var remaining = inAccum - outAccum - fee
if (!isFinite(remaining) || remaining < 0) return { fee: fee }
var remaining = ext.sub(inAccum, outAccum, fee)
if (!remaining || remaining < 0) return { fee: fee }

var unspecified = outputs.reduce(function (a, x) {
return a + !isFinite(x.value)
return a + !x.value
}, 0)

if (remaining === 0 && unspecified === 0) return utils.finalize(utxos, outputs, feeRate)
if (ext.isZero(remaining) && unspecified === 0) return utils.finalize(utxos, outputs, feeRate)

var splitOutputsCount = outputs.reduce(function (a, x) {
// Counts the number of split outputs left
var splitOutputsCount = new BN(outputs.reduce(function (a, x) {
return a + !x.value
}, 0)
var splitValue = (remaining / splitOutputsCount) >>> 0
}, 0))

// any number / 0 = infinity (shift right = 0)
var splitValue = ext.div(remaining, splitOutputsCount)

// ensure every output is either user defined, or over the threshold
if (!outputs.every(function (x) {
return x.value !== undefined || (splitValue > utils.dustThreshold(x, feeRate))
return x.value !== undefined || ext.gt(splitValue, utils.dustThreshold(x, feeRate))
})) return { fee: fee }

// assign splitValue to outputs not user defined
Expand Down
7 changes: 4 additions & 3 deletions test/_utils.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
var BN = require('bn.js')

function expand (values, indices) {
if (indices) {
return values.map(function (x, i) {
if (typeof x === 'number') return { i: i, value: x }

if (BN.isBN(x)) { return { i: i, value: x } }
var y = { i: i }
for (var k in x) y[k] = x[k]
return y
})
}

return values.map(function (x, i) {
return typeof x === 'object' ? x : { value: x }
return (typeof x === 'object' && !BN.isBN(x)) ? x : { value: x }
})
}

Expand Down
11 changes: 9 additions & 2 deletions test/accumulative.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,17 @@ fixtures.forEach(function (f) {
var outputs = utils.expand(f.outputs)
var actual = coinAccum(inputs, outputs, f.feeRate)

t.same(actual, f.expected)
t.same(actual.inputs, f.expected.inputs)
t.same(actual.outputs, f.expected.outputs)
if (f.expected.fee) t.ok(actual.fee.eq(f.expected.fee))
else t.ok(actual.fee === f.expected.fee)

if (actual.inputs) {
var feedback = coinAccum(actual.inputs, actual.outputs, f.feeRate)
t.same(feedback, f.expected)
t.same(feedback.inputs, f.expected.inputs)
t.same(feedback.outputs, f.expected.outputs)
if (f.expected.fee) t.ok(feedback.fee.eq(f.expected.fee))
else t.ok(actual.fee === f.expected.fee)
}

t.end()
Expand Down
Loading