From a37e7b140c8f972228f56dfee7a015e3b4543c4c Mon Sep 17 00:00:00 2001 From: eordano Date: Wed, 11 Mar 2015 15:38:14 -0300 Subject: [PATCH 1/2] Add functionality to randomize outputs --- lib/transaction/transaction.js | 34 ++++++++++++++++++++++++ package.json | 3 ++- test/transaction/transaction.js | 47 +++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index ca24533cd..cbf054d06 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -805,6 +805,40 @@ Transaction.prototype.removeOutput = function(index) { this._updateChangeOutput(); }; +/** + * Randomize this transaction's outputs ordering. The shuffling algorithm is a + * version of the Fisher-Yates shuffle, provided by lodash's _.shuffle(). + * + * @return {Transaction} this + */ +Transaction.prototype.shuffleOutputs = function() { + return this.sortOutputs(_.shuffle); +}; + +/** + * Sort this transaction's outputs, according to a given sorting function that + * takes an array as argument and returns a new array, with the same elements + * but with a different order. The argument function MUST NOT modify the order + * of the original array + * + * @param {Function} sortingFunction + * @return {Transaction} this + */ +Transaction.prototype.sortOutputs = function(sortingFunction) { + var outs = sortingFunction(this.outputs); + return this._newOutputOrder(outs); +}; + +Transaction.prototype._newOutputOrder = function(newOutputs) { + var changeIndex = 0; + while (this.outputs[this._changeIndex] !== newOutputs[changeIndex]) { + changeIndex++; + } + this.outputs = newOutputs; + this._changeIndex = changeIndex; + return this; +}; + Transaction.prototype.removeInput = function(txId, outputIndex) { var index; if (!outputIndex && _.isNumber(txId)) { diff --git a/package.json b/package.json index f7c316f1e..11b3d697b 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,8 @@ "bitcore-build": "bitpay/bitcore-build", "brfs": "^1.2.0", "chai": "^1.10.0", - "gulp": "^3.8.10" + "gulp": "^3.8.10", + "sinon": "^1.13.0" }, "license": "MIT" } diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 771a94882..be4a3300d 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -5,6 +5,7 @@ var should = require('chai').should(); var expect = require('chai').expect; var _ = require('lodash'); +var sinon = require('sinon'); var bitcore = require('../..'); var Transaction = bitcore.Transaction; @@ -619,6 +620,52 @@ describe('Transaction', function() { }); }); + describe('output ordering', function() { + + var tenth = 1e7; + var fourth = 25e6; + var half = 5e7; + var transaction, out1, out2, out3, out4; + + beforeEach(function() { + transaction = new Transaction() + .from(simpleUtxoWith1BTC) + .to(toAddress, tenth) + .to(toAddress, fourth) + .to(toAddress, half) + .change(changeAddress); + out1 = transaction.outputs[0]; + out2 = transaction.outputs[1]; + out3 = transaction.outputs[2]; + out4 = transaction.outputs[3]; + }); + + it('allows the user to sort outputs according to a criteria', function() { + var sorting = function(array) { + return [array[3], array[2], array[1], array[0]]; + }; + transaction.sortOutputs(sorting); + transaction.outputs[0].should.equal(out4); + transaction.outputs[1].should.equal(out3); + transaction.outputs[2].should.equal(out2); + transaction.outputs[3].should.equal(out1); + }); + + it('allows the user to randomize the output order', function() { + var shuffle = sinon.stub(_, 'shuffle'); + shuffle.onFirstCall().returns([out2, out1, out4, out3]); + + transaction._changeIndex.should.equal(3); + transaction.shuffleOutputs(); + transaction.outputs[0].should.equal(out2); + transaction.outputs[1].should.equal(out1); + transaction.outputs[2].should.equal(out4); + transaction.outputs[3].should.equal(out3); + transaction._changeIndex.should.equal(2); + + _.shuffle.restore(); + }); + }); }); var tx_empty_hex = '01000000000000000000'; From 294ff097a18f747db3c66f193080b653a497b7c1 Mon Sep 17 00:00:00 2001 From: eordano Date: Wed, 11 Mar 2015 15:49:42 -0300 Subject: [PATCH 2/2] Add error if shuffle function doesnt return an expected result --- lib/errors/spec.js | 3 +++ lib/transaction/transaction.js | 6 +++++- test/transaction/transaction.js | 10 ++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/errors/spec.js b/lib/errors/spec.js index 4ce9390cd..3270c3f17 100644 --- a/lib/errors/spec.js +++ b/lib/errors/spec.js @@ -63,6 +63,9 @@ module.exports = [{ }, { name: 'NeedMoreInfo', message: '{0}' + }, { + name: 'InvalidSorting', + message: 'The sorting function provided did not return the change output as one of the array elements' }, { name: 'InvalidOutputAmountSum', message: '{0}' diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index cbf054d06..618cc5ccf 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -831,9 +831,13 @@ Transaction.prototype.sortOutputs = function(sortingFunction) { Transaction.prototype._newOutputOrder = function(newOutputs) { var changeIndex = 0; - while (this.outputs[this._changeIndex] !== newOutputs[changeIndex]) { + var length = this.outputs.length; + while (changeIndex < length && this.outputs[this._changeIndex] !== newOutputs[changeIndex]) { changeIndex++; } + if (changeIndex === length) { + throw new errors.Transaction.InvalidSorting(); + } this.outputs = newOutputs; this._changeIndex = changeIndex; return this; diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index be4a3300d..26842c449 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -665,6 +665,16 @@ describe('Transaction', function() { _.shuffle.restore(); }); + + it('fails if the provided function does not work as expected', function() { + var sorting = function(array) { + return []; + }; + expect(function() { + transaction.sortOutputs(sorting); + }).to.throw(errors.Transaction.InvalidSorting); + }); + }); });