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:
parent
a419a1b037
commit
fb65145ba7
|
@ -905,6 +905,41 @@ Transaction.prototype.removeOutput = function(index) {
|
||||||
this._updateChangeOutput();
|
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
|
* Randomize this transaction's outputs ordering. The shuffling algorithm is a
|
||||||
* version of the Fisher-Yates shuffle, provided by lodash's _.shuffle().
|
* version of the Fisher-Yates shuffle, provided by lodash's _.shuffle().
|
||||||
|
@ -929,6 +964,20 @@ Transaction.prototype.sortOutputs = function(sortingFunction) {
|
||||||
return this._newOutputOrder(outs);
|
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) {
|
Transaction.prototype._newOutputOrder = function(newOutputs) {
|
||||||
var isInvalidSorting = (this.outputs.length !== newOutputs.length ||
|
var isInvalidSorting = (this.outputs.length !== newOutputs.length ||
|
||||||
_.difference(this.outputs, newOutputs).length !== 0);
|
_.difference(this.outputs, newOutputs).length !== 0);
|
||||||
|
|
|
@ -14,6 +14,7 @@ var PrivateKey = bitcore.PrivateKey;
|
||||||
var Script = bitcore.Script;
|
var Script = bitcore.Script;
|
||||||
var Address = bitcore.Address;
|
var Address = bitcore.Address;
|
||||||
var Networks = bitcore.Networks;
|
var Networks = bitcore.Networks;
|
||||||
|
var Opcode = bitcore.Opcode;
|
||||||
var errors = bitcore.errors;
|
var errors = bitcore.errors;
|
||||||
|
|
||||||
var transactionVector = require('../data/tx_creation');
|
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';
|
var tx_empty_hex = '01000000000000000000';
|
||||||
|
|
Loading…
Reference in New Issue