Privacy improvement by sorting inputs and outputs

See BIP69 for more details:
https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki
This commit is contained in:
Esteban Ordano 2015-09-22 16:07:12 -07:00
parent a419a1b037
commit fb65145ba7
2 changed files with 108 additions and 0 deletions

View File

@ -905,6 +905,41 @@ Transaction.prototype.removeOutput = function(index) {
this._updateChangeOutput();
};
/**
* Sort a transaction's inputs and outputs according to BIP69
*
* @see {https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki}
* @return {Transaction} this
*/
Transaction.prototype.sort = function() {
this.sortOutputs(function(outputs) {
var copy = Array.prototype.concat.apply([], outputs);
copy.sort(function compare(first, second) {
if (first.satoshis < second.satoshis) {
return -1;
} else if (first.satoshis === second.satoshis) {
return Buffer.compare(first.script.toBuffer(), second.script.toBuffer());
} else {
return 1;
}
});
return copy;
});
this.sortInputs(function(inputs) {
var copy = Array.prototype.concat.apply([], inputs);
copy.sort(function compare(first, second) {
var compareTx = Buffer.compare(first.script.toBuffer(), second.script.toBuffer());
if (compareTx === 0) {
return first.outputIndex < second.outputIndex ? -1 : 1;
} else {
return compareTx;
}
});
return copy;
});
return this;
};
/**
* Randomize this transaction's outputs ordering. The shuffling algorithm is a
* version of the Fisher-Yates shuffle, provided by lodash's _.shuffle().
@ -929,6 +964,20 @@ Transaction.prototype.sortOutputs = function(sortingFunction) {
return this._newOutputOrder(outs);
};
/**
* Sort this transaction's inputs, 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.
*
* @param {Function} sortingFunction
* @return {Transaction} this
*/
Transaction.prototype.sortInputs = function(sortingFunction) {
this.inputs = sortingFunction(this.inputs);
this._clearSignatures();
return this;
};
Transaction.prototype._newOutputOrder = function(newOutputs) {
var isInvalidSorting = (this.outputs.length !== newOutputs.length ||
_.difference(this.outputs, newOutputs).length !== 0);

View File

@ -14,6 +14,7 @@ var PrivateKey = bitcore.PrivateKey;
var Script = bitcore.Script;
var Address = bitcore.Address;
var Networks = bitcore.Networks;
var Opcode = bitcore.Opcode;
var errors = bitcore.errors;
var transactionVector = require('../data/tx_creation');
@ -919,6 +920,64 @@ describe('Transaction', function() {
});
});
describe('BIP69 Sorting', function() {
it('sorts inputs correctly', function() {
var from1 = {
txId: '0000000000000000000000000000000000000000000000000000000000000000',
outputIndex: 0,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 100000
};
var from2 = {
txId: '0000000000000000000000000000000000000000000000000000000000000001',
outputIndex: 0,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 100000
};
var from3 = {
txId: '0000000000000000000000000000000000000000000000000000000000000001',
outputIndex: 1,
script: Script.buildPublicKeyHashOut(fromAddress).toString(),
satoshis: 100000
};
var tx = new Transaction()
.from(from3)
.from(from2)
.from(from1);
tx.sort();
tx.inputs[0].prevTxId.toString('hex').should.equal(from1.txId);
tx.inputs[1].prevTxId.toString('hex').should.equal(from2.txId);
tx.inputs[2].prevTxId.toString('hex').should.equal(from3.txId);
tx.inputs[0].outputIndex.should.equal(from1.outputIndex);
tx.inputs[1].outputIndex.should.equal(from2.outputIndex);
tx.inputs[2].outputIndex.should.equal(from3.outputIndex);
});
it('sorts outputs correctly', function() {
var tx = new Transaction()
.addOutput(new Transaction.Output({
script: new Script().add(Opcode(0)),
satoshis: 2
}))
.addOutput(new Transaction.Output({
script: new Script().add(Opcode(1)),
satoshis: 2
}))
.addOutput(new Transaction.Output({
script: new Script().add(Opcode(0)),
satoshis: 1
}));
tx.sort();
tx.outputs[0].satoshis.should.equal(1);
tx.outputs[1].satoshis.should.equal(2);
tx.outputs[2].satoshis.should.equal(2);
tx.outputs[0].script.toString().should.equal('OP_0');
tx.outputs[1].script.toString().should.equal('OP_0');
tx.outputs[2].script.toString().should.equal('0x01');
});
});
});
var tx_empty_hex = '01000000000000000000';