|
|
@ -94,12 +94,7 @@ var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); |
|
|
|
|
|
|
|
function TransactionBuilder(opts) { |
|
|
|
opts = opts || {}; |
|
|
|
this.txobj = {}; |
|
|
|
this.txobj.version = 1; |
|
|
|
this.txobj.lock_time = opts.lockTime || 0; |
|
|
|
this.txobj.ins = []; |
|
|
|
this.txobj.outs = []; |
|
|
|
|
|
|
|
this.lockTime = opts.lockTime || 0; |
|
|
|
this.spendUnconfirmed = opts.spendUnconfirmed || false; |
|
|
|
|
|
|
|
if (opts.fee || opts.feeSat) { |
|
|
@ -265,12 +260,12 @@ TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._setInputs = function() { |
|
|
|
TransactionBuilder.prototype._setInputs = function(txobj) { |
|
|
|
var ins = this.selectedUtxos; |
|
|
|
var l = ins.length; |
|
|
|
var valueInSat = bignum(0); |
|
|
|
|
|
|
|
this.txobj.ins=[]; |
|
|
|
txobj.ins=[]; |
|
|
|
for (var i = 0; i < l; i++) { |
|
|
|
valueInSat = valueInSat.add(util.parseValue(ins[i].amount)); |
|
|
|
|
|
|
@ -286,7 +281,7 @@ TransactionBuilder.prototype._setInputs = function() { |
|
|
|
voutBuf.writeUInt32LE(vout, 0); |
|
|
|
|
|
|
|
txin.o = Buffer.concat([hashReversed, voutBuf]); |
|
|
|
this.txobj.ins.push(txin); |
|
|
|
txobj.ins.push(txin); |
|
|
|
} |
|
|
|
this.valueInSat = valueInSat; |
|
|
|
return this; |
|
|
@ -309,7 +304,7 @@ TransactionBuilder.prototype._setFee = function(feeSat) { |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._setRemainder = function(remainderIndex) { |
|
|
|
TransactionBuilder.prototype._setRemainder = function(txobj, remainderIndex) { |
|
|
|
|
|
|
|
if ( typeof this.valueInSat === 'undefined' || |
|
|
|
typeof this.valueOutSat === 'undefined') |
|
|
@ -317,12 +312,12 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) { |
|
|
|
|
|
|
|
// add remainder (without modifying outs[])
|
|
|
|
var remainderSat = this.valueInSat.sub(this.valueOutSat).sub(this.feeSat); |
|
|
|
var l =this.txobj.outs.length; |
|
|
|
var l =txobj.outs.length; |
|
|
|
this.remainderSat = bignum(0); |
|
|
|
|
|
|
|
//remove old remainder?
|
|
|
|
if (l > remainderIndex) { |
|
|
|
this.txobj.outs.pop(); |
|
|
|
txobj.outs.pop(); |
|
|
|
} |
|
|
|
|
|
|
|
if (remainderSat.cmp(0) > 0) { |
|
|
@ -333,18 +328,17 @@ TransactionBuilder.prototype._setRemainder = function(remainderIndex) { |
|
|
|
v: value, |
|
|
|
s: script.getBuffer(), |
|
|
|
}; |
|
|
|
this.txobj.outs.push(txout); |
|
|
|
txobj.outs.push(txout); |
|
|
|
this.remainderSat = remainderSat; |
|
|
|
} |
|
|
|
|
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._setFeeAndRemainder = function() { |
|
|
|
TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) { |
|
|
|
|
|
|
|
//starting size estimation
|
|
|
|
var size = 500, maxSizeK, remainderIndex = this.txobj.outs.length; |
|
|
|
|
|
|
|
var size = 500, maxSizeK, remainderIndex = txobj.outs.length; |
|
|
|
do { |
|
|
|
// based on https://en.bitcoin.it/wiki/Transaction_fees
|
|
|
|
maxSizeK = parseInt(size / 1000) + 1; |
|
|
@ -355,12 +349,12 @@ TransactionBuilder.prototype._setFeeAndRemainder = function() { |
|
|
|
var neededAmountSat = this.valueOutSat.add(feeSat); |
|
|
|
|
|
|
|
this._selectUnspent(neededAmountSat) |
|
|
|
._setInputs() |
|
|
|
._setInputs(txobj) |
|
|
|
._setFee(feeSat) |
|
|
|
._setRemainder(remainderIndex); |
|
|
|
._setRemainder(txobj, remainderIndex); |
|
|
|
|
|
|
|
|
|
|
|
size = new Transaction(this.txobj).getSize(); |
|
|
|
size = new Transaction(txobj).getSize(); |
|
|
|
} while (size > (maxSizeK + 1) * 1000); |
|
|
|
return this; |
|
|
|
}; |
|
|
@ -368,9 +362,13 @@ TransactionBuilder.prototype._setFeeAndRemainder = function() { |
|
|
|
TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
var valueOutSat = bignum(0); |
|
|
|
|
|
|
|
this.txobj.outs = []; |
|
|
|
var l =outs.length; |
|
|
|
var txobj = {}; |
|
|
|
txobj.version = 1; |
|
|
|
txobj.lock_time = this.lockTime || 0; |
|
|
|
txobj.ins = []; |
|
|
|
txobj.outs = []; |
|
|
|
|
|
|
|
var l =outs.length; |
|
|
|
for (var i = 0; i < l; i++) { |
|
|
|
var amountSat = outs[i].amountSat || util.parseValue(outs[i].amount); |
|
|
|
var value = util.bigIntToValue(amountSat); |
|
|
@ -379,7 +377,7 @@ TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
v: value, |
|
|
|
s: script.getBuffer(), |
|
|
|
}; |
|
|
|
this.txobj.outs.push(txout); |
|
|
|
txobj.outs.push(txout); |
|
|
|
|
|
|
|
var sat = outs[i].amountSat || util.parseValue(outs[i].amount); |
|
|
|
valueOutSat = valueOutSat.add(sat); |
|
|
@ -387,9 +385,9 @@ TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
|
|
|
|
this.valueOutSat = valueOutSat; |
|
|
|
|
|
|
|
this._setFeeAndRemainder(); |
|
|
|
this._setFeeAndRemainder(txobj); |
|
|
|
|
|
|
|
this.tx = new Transaction(this.txobj); |
|
|
|
this.tx = new Transaction(txobj); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
|
|
|
@ -506,12 +504,12 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS |
|
|
|
}; |
|
|
|
|
|
|
|
// FOR TESTING
|
|
|
|
// var _dumpChunks = function (scriptSig, label) {
|
|
|
|
// console.log('## DUMP: ' + label + ' ##');
|
|
|
|
// for(var i=0; i<scriptSig.chunks.length; i++) {
|
|
|
|
// console.log('\tCHUNK ', i, scriptSig.chunks[i]);
|
|
|
|
// }
|
|
|
|
// };
|
|
|
|
var _dumpChunks = function (scriptSig, label) { |
|
|
|
console.log('## DUMP: ' + label + ' ##'); |
|
|
|
for(var i=0; i<scriptSig.chunks.length; i++) { |
|
|
|
console.log('\tCHUNK ', i, scriptSig.chunks[i]); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._initMultiSig = function(scriptSig, nreq) { |
|
|
|
var wasUpdated = false; |
|
|
@ -526,7 +524,8 @@ TransactionBuilder.prototype._initMultiSig = function(scriptSig, nreq) { |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._isSignedWithKey = function(wk, scriptSig, txSigHash, nreq) { |
|
|
|
var ret=0; |
|
|
|
var ret=false; |
|
|
|
// _dumpChunks(scriptSig);
|
|
|
|
for(var i=1; i<=nreq; i++) { |
|
|
|
var chunk = scriptSig.chunks[i]; |
|
|
|
if (chunk ===0 || chunk.length === 0) continue; |
|
|
@ -567,6 +566,7 @@ TransactionBuilder.prototype._updateMultiSig = function(wk, scriptSig, txSigHash |
|
|
|
wasUpdated=true; |
|
|
|
break; |
|
|
|
} |
|
|
|
// _dumpChunks(scriptSig); // TODO
|
|
|
|
return wasUpdated ? scriptSig : null; |
|
|
|
}; |
|
|
|
|
|
|
@ -683,8 +683,6 @@ TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
l = ins.length, |
|
|
|
walletKeyMap = TransactionBuilder._mapKeys(keys); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (var i = 0; i < l; i++) { |
|
|
|
var input = this.inputMap[i]; |
|
|
|
|
|
|
@ -717,5 +715,223 @@ TransactionBuilder.prototype.build = function() { |
|
|
|
return this.tx; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.toObj = function() { |
|
|
|
var data = { |
|
|
|
valueInSat : this.valueInSat.toString(), |
|
|
|
valueOutSat : this.valueOutSat.toString(), |
|
|
|
feeSat : this.feeSat.toString(), |
|
|
|
remainderSat : this.remainderSat.toString(), |
|
|
|
|
|
|
|
hashToScriptMap : this.hashToScriptMap, |
|
|
|
selectedUtxos : this.selectedUtxos, |
|
|
|
|
|
|
|
inputsSigned : this.inputsSigned, |
|
|
|
signaturesAdded : this.signaturesAdded, |
|
|
|
|
|
|
|
//opts :
|
|
|
|
signhash : this.signhash, |
|
|
|
spendUnconfirmed : this.spendUnconfirmed, |
|
|
|
}; |
|
|
|
if (this.tx) { |
|
|
|
data.tx =this.tx.serialize().toString('hex'); |
|
|
|
} |
|
|
|
return data; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.fromObj = function(data) { |
|
|
|
var b = new TransactionBuilder(); |
|
|
|
b.valueInSat = data.valueInSat.toString(); |
|
|
|
b.valueOutSat = data.valueOutSat.toString(); |
|
|
|
b.feeSat = data.feeSat.toString(); |
|
|
|
b.remainderSat = data.remainderSat.toString(); |
|
|
|
|
|
|
|
b.hashToScriptMap = data.hashToScriptMap; |
|
|
|
b.selectedUtxos = data.selectedUtxos; |
|
|
|
|
|
|
|
b.inputsSigned = data.inputsSigned; |
|
|
|
b.signaturesAdded = data.signaturesAdded; |
|
|
|
|
|
|
|
b.signhash = data.signhash; |
|
|
|
b.spendUnconfirmed = data.spendUnconfirmed; |
|
|
|
|
|
|
|
b._setInputMap(); |
|
|
|
|
|
|
|
if (data.tx) { |
|
|
|
// Tx may have signatures, that are not on txobj
|
|
|
|
var t = new Transaction(); |
|
|
|
t.parse(new Buffer(data.tx,'hex')); |
|
|
|
b.tx = t; |
|
|
|
} |
|
|
|
return b; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._checkMergeability = function(b) { |
|
|
|
var self=this; |
|
|
|
|
|
|
|
// Builder should have the same params
|
|
|
|
['valueInSat', 'valueOutSat', 'feeSat', 'remainderSat', 'signhash', 'spendUnconfirmed'] |
|
|
|
.forEach(function (k) { |
|
|
|
|
|
|
|
if (self[k].toString() !== b[k].toString()) { |
|
|
|
throw new Error('mismatch at TransactionBuilder match: ' |
|
|
|
+ k + ': ' + self[k] + ' vs. ' + b[k]); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
if (self.hashToScriptMap) { |
|
|
|
var err = 0; |
|
|
|
if(! b.hashToScriptMap) err=1; |
|
|
|
Object.keys(self.hashToScriptMap).forEach(function(k) { |
|
|
|
if (!b.hashToScriptMap[k]) err=1; |
|
|
|
if (self.hashToScriptMap[k] !== b.hashToScriptMap[k]) err=1; |
|
|
|
}); |
|
|
|
if (err) |
|
|
|
throw new Error('mismatch at TransactionBuilder hashToScriptMap'); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var err = 0, i=0;; |
|
|
|
self.selectedUtxos.forEach(function(u) { |
|
|
|
if (!err) { |
|
|
|
var v=b.selectedUtxos[i++]; |
|
|
|
if (!v) err=1; |
|
|
|
// confirmations could differ
|
|
|
|
['address', 'hash', 'scriptPubKey', 'vout', 'amount'].forEach(function(k) { |
|
|
|
if (u[k] !== v[k]) |
|
|
|
err=k; |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
if (err) |
|
|
|
throw new Error('mismatch at TransactionBuilder selectedUtxos #' + i-1+ ' Key:' + err); |
|
|
|
|
|
|
|
|
|
|
|
err = 0; i=0;; |
|
|
|
self.inputMap.forEach(function(u) { |
|
|
|
if (!err) { |
|
|
|
var v=b.inputMap[i++]; |
|
|
|
if (!v) err=1; |
|
|
|
// confirmations could differ
|
|
|
|
['address', 'scriptType', 'scriptPubKey', 'i'].forEach(function(k) { |
|
|
|
if (u[k].toString() !== v[k].toString()) |
|
|
|
err=k; |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
if (err) |
|
|
|
throw new Error('mismatch at TransactionBuilder inputMap #' + i-1 + ' Key:' + err); |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// this assumes that the same signature can not be v0 / v1 (which shouldnt be!)
|
|
|
|
TransactionBuilder.prototype._mergeInputSig = function(s0buf, s1buf, ignoreConflictingSignatures) { |
|
|
|
if (buffertools.compare(s0buf,s1buf) === 0) { |
|
|
|
//console.log('BUFFERS .s MATCH'); //TODO
|
|
|
|
return s0buf; |
|
|
|
} |
|
|
|
// Is multisig?
|
|
|
|
var s0 = new Script(s0buf); |
|
|
|
var s1 = new Script(s1buf); |
|
|
|
var l0 = s0.chunks.length; |
|
|
|
var l1 = s1.chunks.length; |
|
|
|
var s0map = {}; |
|
|
|
|
|
|
|
if (l0 && l1 && l0 !== l1) |
|
|
|
throw new Error('TX sig types mismatch in merge'); |
|
|
|
|
|
|
|
if (!l0 && !l1) return s0buf; |
|
|
|
if ( l0 && !l1) return s0buf; |
|
|
|
if (!l0 && l1) return s1buf; |
|
|
|
|
|
|
|
// Look for differences.
|
|
|
|
for (var i=0; i<l0; i++) { |
|
|
|
if (!this._chunkIsEmpty(s0.chunks[i])) |
|
|
|
s0map[s0.chunks[i]] = 1; |
|
|
|
}; |
|
|
|
|
|
|
|
var diff = []; |
|
|
|
for (var i=0; i<l1; i++) { |
|
|
|
if ( !this._chunkIsEmpty(s1.chunks[i]) && !s0map[s1.chunks[i]]) { |
|
|
|
diff.push(s1.chunks[i]); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
if (!diff) { |
|
|
|
console.log('[TransactionBuilder.js.857: NO DIFF FOUND, just ORDER DIFF]'); //TODO
|
|
|
|
return s0.getBuffer(); |
|
|
|
} |
|
|
|
|
|
|
|
var emptySlots = []; |
|
|
|
for (var i=1; i<l0; i++) { |
|
|
|
if (this._chunkIsEmpty(s0.chunks[i])) { |
|
|
|
emptySlots.push(i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (emptySlots.length<diff.length) { |
|
|
|
if (!ignoreConflictingSignatures) { |
|
|
|
throw new Error( |
|
|
|
'no enough empty slots to merge Txs: Check ignoreConflictingSignatures option'); |
|
|
|
} |
|
|
|
} |
|
|
|
else { |
|
|
|
for (var i=0; i<diff.length; i++) { |
|
|
|
s0.chunks[emptySlots[i]] = diff[i]; |
|
|
|
this.signaturesAdded++; |
|
|
|
} |
|
|
|
s0.updateBuffer(); |
|
|
|
} |
|
|
|
return s0.getBuffer(); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures) { |
|
|
|
var v0 = this.tx; |
|
|
|
var v1 = tx; |
|
|
|
|
|
|
|
var l = v0.ins.length; |
|
|
|
if (l !== v1.ins.length) |
|
|
|
throw new Error('TX in length mismatch in merge'); |
|
|
|
|
|
|
|
this.inputsSigned =0; |
|
|
|
for(var i=0; i<l; i++) { |
|
|
|
var i0 = v0.ins[i]; |
|
|
|
var i1 = v1.ins[i]; |
|
|
|
|
|
|
|
if (i0.q !== i1.q) |
|
|
|
throw new Error('TX sequence ins mismatch in merge. Input:',i); |
|
|
|
|
|
|
|
if (buffertools.compare(i0.o,i1.o) !== 0) |
|
|
|
throw new Error('TX .o in mismatch in merge. Input:',i); |
|
|
|
|
|
|
|
i0.s=this._mergeInputSig(i0.s,i1.s, ignoreConflictingSignatures); |
|
|
|
|
|
|
|
if (v0.isInputComplete(i)) this.inputsSigned++; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.merge = function(b, ignoreConflictingSignatures) { |
|
|
|
this._checkMergeability(b); |
|
|
|
|
|
|
|
|
|
|
|
// Does this tX have any signature already?
|
|
|
|
if (this.tx || b.tx) { |
|
|
|
if (this.tx.getNormalizedHash().toString('hex') |
|
|
|
!== b.tx.getNormalizedHash().toString('hex')) |
|
|
|
throw new Error('mismatch at TransactionBuilder NTXID'); |
|
|
|
|
|
|
|
this._mergeTx(b.tx, ignoreConflictingSignatures); |
|
|
|
// TODO UPDATE: signaturesAdded, inputsSigned
|
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
module.exports = require('soop')(TransactionBuilder); |
|
|
|
|
|
|
|