|
|
@ -65,6 +65,7 @@ var log = require('../util/log'); |
|
|
|
var Transaction = require('./Transaction'); |
|
|
|
|
|
|
|
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); |
|
|
|
var TOOBJ_VERSION = 1; |
|
|
|
|
|
|
|
// Methods
|
|
|
|
// -------
|
|
|
@ -97,6 +98,14 @@ var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); |
|
|
|
|
|
|
|
function TransactionBuilder(opts) { |
|
|
|
opts = opts || {}; |
|
|
|
|
|
|
|
this.vanilla = {}; |
|
|
|
this.vanilla.scriptSig = []; |
|
|
|
this.vanilla.opts = JSON.stringify(opts); |
|
|
|
|
|
|
|
// If any default opts is changed, TOOBJ_VERSION should be changed as
|
|
|
|
// a caution measure.
|
|
|
|
|
|
|
|
this.lockTime = opts.lockTime || 0; |
|
|
|
this.spendUnconfirmed = opts.spendUnconfirmed || false; |
|
|
|
|
|
|
@ -108,7 +117,6 @@ function TransactionBuilder(opts) { |
|
|
|
|
|
|
|
this.tx = {}; |
|
|
|
this.inputsSigned = 0; |
|
|
|
this.signaturesAdded = 0; |
|
|
|
|
|
|
|
return this; |
|
|
|
} |
|
|
@ -177,6 +185,7 @@ TransactionBuilder.infoForP2sh = function(opts, networkName) { |
|
|
|
// That amount is in BTCs (as returned in insight and bitcoind).
|
|
|
|
// amountSat (instead of amount) can be given to provide amount in satochis.
|
|
|
|
TransactionBuilder.prototype.setUnspent = function(unspent) { |
|
|
|
this.vanilla.utxos = JSON.stringify(unspent); |
|
|
|
this.utxos = unspent; |
|
|
|
return this; |
|
|
|
}; |
|
|
@ -381,8 +390,9 @@ TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) { |
|
|
|
//
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.setOutputs = function(outs) { |
|
|
|
var valueOutSat = bignum(0); |
|
|
|
this.vanilla.outs = JSON.stringify(outs); |
|
|
|
|
|
|
|
var valueOutSat = bignum(0); |
|
|
|
var txobj = {}; |
|
|
|
txobj.version = 1; |
|
|
|
txobj.lock_time = this.lockTime || 0; |
|
|
@ -456,7 +466,7 @@ TransactionBuilder._signHashAndVerify = function(wk, txSigHash) { |
|
|
|
}; |
|
|
|
|
|
|
|
TransactionBuilder.prototype._checkTx = function() { |
|
|
|
if (!this.tx || !this.tx.ins.length || !this.tx.outs.length) |
|
|
|
if (!this.tx || !this.tx.ins || !this.tx.ins.length || !this.tx.outs.length) |
|
|
|
throw new Error('tx is not defined'); |
|
|
|
}; |
|
|
|
|
|
|
@ -748,6 +758,9 @@ fnToSign[Script.TX_SCRIPTHASH] = TransactionBuilder.prototype._signScriptHash; |
|
|
|
//
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
if (!(keys instanceof Array)) |
|
|
|
throw new Error('parameter should be an array'); |
|
|
|
|
|
|
|
this._checkTx(); |
|
|
|
var tx = this.tx, |
|
|
|
ins = tx.ins, |
|
|
@ -763,9 +776,10 @@ TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
|
|
|
|
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash); |
|
|
|
if (ret && ret.script) { |
|
|
|
this.vanilla.scriptSig[i] = ret.script.toString('hex'); |
|
|
|
|
|
|
|
tx.ins[i].s = ret.script; |
|
|
|
if (ret.inputFullySigned) this.inputsSigned++; |
|
|
|
if (ret.signaturesAdded) this.signaturesAdded += ret.signaturesAdded; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
@ -778,6 +792,8 @@ TransactionBuilder.prototype.sign = function(keys) { |
|
|
|
// for generate the input for this call.
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { |
|
|
|
this.vanilla.hashToScriptMap = JSON.stringify(hashToScriptMap); |
|
|
|
|
|
|
|
this.hashToScriptMap = hashToScriptMap; |
|
|
|
return this; |
|
|
|
}; |
|
|
@ -786,8 +802,6 @@ TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) { |
|
|
|
// isFullySigned
|
|
|
|
// -------------
|
|
|
|
// Checks if the transaction have all the necesary signatures.
|
|
|
|
// Also, `.signaturesAdded` and `.inputsSigned` can be queried
|
|
|
|
// for more information about the transaction signature status.
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.isFullySigned = function() { |
|
|
|
return this.inputsSigned === this.tx.ins.length; |
|
|
@ -806,25 +820,18 @@ TransactionBuilder.prototype.build = function() { |
|
|
|
// See `.fromObj`
|
|
|
|
//
|
|
|
|
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, |
|
|
|
|
|
|
|
signhash: this.signhash, |
|
|
|
spendUnconfirmed: this.spendUnconfirmed, |
|
|
|
var ret = { |
|
|
|
version: TOOBJ_VERSION, |
|
|
|
outs: JSON.parse(this.vanilla.outs), |
|
|
|
utxos: JSON.parse(this.vanilla.utxos), |
|
|
|
opts: JSON.parse(this.vanilla.opts), |
|
|
|
scriptSig: this.vanilla.scriptSig, |
|
|
|
}; |
|
|
|
if (this.tx) { |
|
|
|
data.tx = this.tx.serialize().toString('hex'); |
|
|
|
} |
|
|
|
return data; |
|
|
|
if (this.vanilla.hashToScriptMap) |
|
|
|
ret.hashToScriptMap = JSON.parse(this.vanilla.hashToScriptMap); |
|
|
|
|
|
|
|
return ret; |
|
|
|
}; |
|
|
|
|
|
|
|
// fromObj
|
|
|
@ -834,90 +841,36 @@ TransactionBuilder.prototype.toObj = function() { |
|
|
|
// with `.toObj`. See `.toObj`.
|
|
|
|
|
|
|
|
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; |
|
|
|
if (data.version !== TOOBJ_VERSION) |
|
|
|
throw new Error('Incompatible version at TransactionBuilder fromObj'); |
|
|
|
|
|
|
|
b.inputsSigned = data.inputsSigned; |
|
|
|
b.signaturesAdded = data.signaturesAdded; |
|
|
|
var b = new TransactionBuilder(data.opts); |
|
|
|
if (data.utxos) { |
|
|
|
b.setUnspent(data.utxos); |
|
|
|
|
|
|
|
b.signhash = data.signhash; |
|
|
|
b.spendUnconfirmed = data.spendUnconfirmed; |
|
|
|
if (data.hashToScriptMap) |
|
|
|
b.setHashToScriptMap(data.hashToScriptMap); |
|
|
|
|
|
|
|
b._setInputMap(); |
|
|
|
if (data.outs) { |
|
|
|
b.setOutputs(data.outs); |
|
|
|
|
|
|
|
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; |
|
|
|
for (var i in data.scriptSig) { |
|
|
|
b.tx.ins[i].s = new Buffer(data.scriptSig[i], 'hex'); |
|
|
|
|
|
|
|
var scriptSig = new Script(b.tx.ins[i].s); |
|
|
|
if (scriptSig.finishedMultiSig() !== false) |
|
|
|
b.inputsSigned++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
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); |
|
|
|
|
|
|
|
if (JSON.stringify(this.vanilla) !== JSON.stringify(this.vanilla)) |
|
|
|
throw new Error('cannot merge: incompatible builders') |
|
|
|
}; |
|
|
|
|
|
|
|
// TODO this could be on Script class
|
|
|
@ -952,12 +905,33 @@ TransactionBuilder.prototype._mergeInputSigP2sh = function(input, s0, s1) { |
|
|
|
var newSig = diff[j]; |
|
|
|
var order = this._getNewSignatureOrder(newSig.prio, s0, p2sh.txSigHash, pubkeys); |
|
|
|
s0.chunks.splice(order + 1, 0, newSig.chunk); |
|
|
|
this.signaturesAdded++; |
|
|
|
} |
|
|
|
s0.updateBuffer(); |
|
|
|
return s0.getBuffer(); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
// TODO: move this to script
|
|
|
|
TransactionBuilder.prototype._getSighashType = function(sig) { |
|
|
|
return sig[sig.length - 1]; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
TransactionBuilder.prototype._checkSignHash = function(s1) { |
|
|
|
var l = s1.chunks.length - 1; |
|
|
|
|
|
|
|
for (var i = 0; i < l; i++) { |
|
|
|
|
|
|
|
if (i == 0 && s1.chunks[i] === 0) |
|
|
|
continue; |
|
|
|
|
|
|
|
if (this._getSighashType(s1.chunks[i]) !== this.signhash) |
|
|
|
throw new Error('signhash type mismatch at merge p2sh'); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// TODO this could be on Script class
|
|
|
|
TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) { |
|
|
|
if (buffertools.compare(s0buf, s1buf) === 0) |
|
|
@ -972,7 +946,12 @@ TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) { |
|
|
|
if (l0 && l1 && ((l0 < 2 && l1 > 2) || (l1 < 2 && l0 > 2))) |
|
|
|
throw new Error('TX sig types mismatch in merge'); |
|
|
|
|
|
|
|
if ((!l0 && !l1) || (l0 && !l1) || (!l0 && l1)) |
|
|
|
if ((!l0 && !l1) || (l0 && !l1)) |
|
|
|
return s0buf; |
|
|
|
|
|
|
|
this._checkSignHash(s1); |
|
|
|
|
|
|
|
if ((!l0 && l1)) |
|
|
|
return s1buf; |
|
|
|
|
|
|
|
// Get the pubkeys
|
|
|
@ -981,6 +960,7 @@ TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) { |
|
|
|
|
|
|
|
//p2pubkey or p2pubkeyhash
|
|
|
|
if (type === Script.TX_PUBKEYHASH || type === Script.TX_PUBKEY) { |
|
|
|
var s = new Script(s1buf); |
|
|
|
log.debug('Merging two signed inputs type:' + |
|
|
|
input.scriptPubKey.getRawOutType() + '. Signatures differs. Using the first version.'); |
|
|
|
return s0buf; |
|
|
@ -988,6 +968,7 @@ TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) { |
|
|
|
// No support for normal multisig or strange txs.
|
|
|
|
throw new Error('Script type:' + input.scriptPubKey.getRawOutType() + 'not supported at #merge'); |
|
|
|
} |
|
|
|
|
|
|
|
return this._mergeInputSigP2sh(input, s0, s1); |
|
|
|
}; |
|
|
|
|
|
|
@ -1012,17 +993,29 @@ TransactionBuilder.prototype._mergeTx = function(tx) { |
|
|
|
throw new Error('TX .o in mismatch in merge. Input:', i); |
|
|
|
|
|
|
|
i0.s = this._mergeInputSig(i, i0.s, i1.s); |
|
|
|
this.vanilla.scriptSig[i] = i0.s.toString('hex'); |
|
|
|
|
|
|
|
if (v0.isInputComplete(i)) this.inputsSigned++; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// clone
|
|
|
|
// -----
|
|
|
|
// Clone current TransactionBuilder, regenerate derived fields.
|
|
|
|
//
|
|
|
|
TransactionBuilder.prototype.clone = function() { |
|
|
|
return new TransactionBuilder.fromObj(this.toObj()); |
|
|
|
}; |
|
|
|
|
|
|
|
// merge
|
|
|
|
// -----
|
|
|
|
// Merge to TransactionBuilder objects, merging inputs signatures.
|
|
|
|
// This function supports multisig p2sh inputs.
|
|
|
|
|
|
|
|
TransactionBuilder.prototype.merge = function(b) { |
|
|
|
TransactionBuilder.prototype.merge = function(inB) { |
|
|
|
//
|
|
|
|
var b = inB.clone(); |
|
|
|
|
|
|
|
this._checkMergeability(b); |
|
|
|
|
|
|
|
// Does this tX have any signature already?
|
|
|
|