diff --git a/lib/bcoin/tx.js b/lib/bcoin/tx.js index d74a0284..339413bd 100644 --- a/lib/bcoin/tx.js +++ b/lib/bcoin/tx.js @@ -1166,6 +1166,40 @@ TX.prototype.fill = function fill(unspent, address, fee) { return result; }; +// https://github.com/bitcoin/bips/blob/master/bip-0069.mediawiki +TX.prototype.sortMembers = function sortMembers() { + var changeOutput; + + if (this.changeIndex !== -1) { + changeOutput = this.outputs[this.changeIndex]; + assert(changeOutput); + } + + this.inputs = this.inputs.slice().sort(function(a, b) { + var res = new bn(a.prevout.hash, 'hex').cmp(new bn(b.prevout.hash, 'hex')); + if (res !== 0) + return res; + + return a.prevout.index - b.prevout.index; + }); + + this.outputs = this.outputs.slice().sort(function(a, b) { + var res = a.value.cmp(b.value); + if (res !== 0) + return res; + + a = bcoin.script.encode(a.script); + b = bcoin.script.encode(b.script); + + return new bn(a).cmp(b); + }); + + if (this.changeIndex !== -1) { + this.changeIndex = this.outputs.indexOf(changeOutput); + assert(this.changeIndex !== -1); + } +}; + // Legacy TX.prototype.fillUnspent = TX.prototype.fill; TX.prototype.fillInputs = TX.prototype.fill; @@ -1258,7 +1292,7 @@ TX.prototype.getFunds = function getFunds(side) { // Legacy TX.prototype.funds = TX.prototype.getFunds; -TX.prototype.getTargetTime = function getTargetTime() { +TX.prototype.getTargetLocktime = function getTargetLocktime() { var bestValue = 0; var i, locktime, bestType; diff --git a/lib/bcoin/wallet.js b/lib/bcoin/wallet.js index 054996ba..03144d61 100644 --- a/lib/bcoin/wallet.js +++ b/lib/bcoin/wallet.js @@ -392,27 +392,35 @@ Wallet.prototype.createTX = function createTX(outputs, fee) { if (!Array.isArray(outputs)) outputs = [outputs]; + // Add the outputs outputs.forEach(function(output) { tx.addOutput(output); }); + // Fill the inputs with unspents if (!this.fill(tx, null, fee)) return; + // Sort members a la BIP69 + tx.sortMembers(); + // Find the necessary locktime if there is // a checklocktimeverify script in the unspents. - target = tx.getTargetTime(); + target = tx.getTargetLocktime(); // No target value. The unspents have an // incompatible locktime type. if (!target) return; + // Set the locktime to target value or + // `height - whatever` to avoid fee snipping. if (target.value > 0) tx.setLocktime(target.value); else tx.avoidFeeSnipping(); + // Sign the inputs this.sign(tx); return tx;