bitcore-lib-zcash/lib/TransactionBuilder.js

1041 lines
28 KiB
JavaScript
Raw Normal View History

2014-05-08 12:39:56 -07:00
// TransactionBuilder
// ==================
//
2014-04-29 05:34:26 -07:00
// Creates a bitcore Transaction object
//
//
// Synopsis
// --------
// ```
// var tx = (new TransactionBuilder(opts))
// .setUnspent(utxos)
// .setOutputs(outs)
// .sign(keys)
// .build();
//
//
// var builder = (new TransactionBuilder(opts))
// .setUnspent(spent)
// .setOutputs(outs);
//
// // Uncomplete tx (no signed or partially signed)
// var tx = builder.build();
//
// ..later..
//
// builder.sign(keys);
// while ( builder.isFullySigned() ) {
//
// ... get new keys ...
//
// builder.sign(keys);
// }
//
// var tx = builder.build();
// broadcast(tx.serialize());
//
2014-05-08 14:53:18 -07:00
// //Serialize it and pass it around...
2014-05-08 12:39:56 -07:00
// var string = JSON.stringify(builder.toObj());
2014-04-29 05:34:26 -07:00
// // then...
// var builder = TransactionBuilder.fromObj(JSON.parse(str);
// builder.sign(keys);
// // Also
// var builder2 = TransactionBuilder.fromObj(JSON.parse(str2);
// builder2.merge(builder); // Will merge signatures for p2sh mulsig txs.
//
//
// ```
//
//
//
2014-03-28 17:17:34 -07:00
'use strict';
2014-07-10 08:39:09 -07:00
var Address = require('./Address');
var Script = require('./Script');
var util = require('../util');
var bignum = require('bignum');
var buffertools = require('buffertools');
var networks = require('../networks');
var WalletKey = require('./WalletKey');
var PrivateKey = require('./PrivateKey');
var Key = require('./Key');
var log = require('../util/log');
var Transaction = require('./Transaction');
2014-03-28 17:17:34 -07:00
var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN);
2014-07-24 12:34:57 -07:00
var TOOBJ_VERSION = 1;
2014-03-28 17:17:34 -07:00
2014-04-29 05:34:26 -07:00
// Methods
// -------
//
// TransactionBuilder
// ------------------
// Creates a TransactionBuilder instance
// `opts`
// ```
// {
// remainderOut: null,
// fee: 0.001,
// lockTime: null,
// spendUnconfirmed: false,
// signhash: SIGHASH_ALL
// }
// ```
// Amounts are in BTC. instead of fee and amount; feeSat and amountSat can be given,
// repectively, to provide amounts in satoshis.
//
// If no remainderOut is given, and there are remainder coins, the
// first IN out will be used to return the coins. remainderOut has the form:
// ```
// remainderOut = { address: 1xxxxx}
// ```
// or
// ```
// remainderOut = { pubkeys: ['hex1','hex2',...} for multisig
// ```
2014-03-29 00:01:32 -07:00
function TransactionBuilder(opts) {
opts = opts || {};
2014-07-24 12:34:57 -07:00
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.
2014-07-24 13:35:21 -07:00
this.lockTime = opts.lockTime || 0;
2014-03-29 00:01:32 -07:00
this.spendUnconfirmed = opts.spendUnconfirmed || false;
this.givenFeeSat = typeof(opts.fee) !== 'undefined' ? opts.fee * util.COIN : opts.feeSat;
this.remainderOut = opts.remainderOut;
2014-03-29 00:01:32 -07:00
this.signhash = opts.signhash || Transaction.SIGHASH_ALL;
this.tx = {};
this.inputsSigned = 0;
2014-03-29 00:01:32 -07:00
return this;
2014-03-28 17:17:34 -07:00
}
TransactionBuilder.FEE_PER_1000B_SAT = FEE_PER_1000B_SAT;
TransactionBuilder._scriptForPubkeys = function(out) {
var l = out.pubkeys.length;
var pubKeyBuf = [];
for (var i = 0; i < l; i++) {
pubKeyBuf.push(new Buffer(out.pubkeys[i], 'hex'));
}
return Script.createMultisig(out.nreq, pubKeyBuf);
};
TransactionBuilder._scriptForOut = function(out) {
2014-03-31 20:07:45 -07:00
var ret;
if (out.address)
ret = new Address(out.address).getScriptPubKey();
else if (out.pubkeys || out.nreq || out.nreq > 1)
ret = this._scriptForPubkeys(out);
2014-03-31 20:07:45 -07:00
else
2014-03-31 12:25:43 -07:00
throw new Error('unknown out type');
return ret;
};
2014-03-31 20:07:45 -07:00
TransactionBuilder.infoForP2sh = function(opts, networkName) {
var script = this._scriptForOut(opts);
var hash = util.sha256ripe160(script.getBuffer());
2014-03-31 20:07:45 -07:00
var version = networkName === 'testnet' ?
2014-04-07 14:30:49 -07:00
networks.testnet.P2SHVersion : networks.livenet.P2SHVersion;
2014-03-31 20:07:45 -07:00
var addr = new Address(version, hash);
var addrStr = addr.as('base58');
return {
script: script,
scriptBufHex: script.getBuffer().toString('hex'),
hash: hash,
address: addrStr,
};
};
2014-04-29 05:34:26 -07:00
// setUnspent
// ----------
// Sets the `unspent` available for the transaction. Some (or all)
// of them to fullfil the transaction's outputs and fee.
// The expected format is:
// ```
// [{
// address: "mqSjTad2TKbPcKQ3Jq4kgCkKatyN44UMgZ",
2014-05-08 14:53:18 -07:00
// txid: "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1",
2014-04-29 05:34:26 -07:00
// scriptPubKey: "76a9146ce4e1163eb18939b1440c42844d5f0261c0338288ac",
// vout: 1,
// amount: 0.01,
// confirmations: 3
// }, ...
// ]
// ```
// This is compatible con insight's utxo API.
// 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) {
2014-07-24 12:34:57 -07:00
this.vanilla.utxos = JSON.stringify(unspent);
2014-04-29 05:34:26 -07:00
this.utxos = unspent;
2014-03-28 17:17:34 -07:00
return this;
};
TransactionBuilder.prototype._setInputMap = function() {
var inputMap = [];
var l = this.selectedUtxos.length;
for (var i = 0; i < l; i++) {
var utxo = this.selectedUtxos[i];
var scriptBuf = new Buffer(utxo.scriptPubKey, 'hex');
var scriptPubKey = new Script(scriptBuf);
var scriptType = scriptPubKey.classify();
2014-03-31 10:41:27 -07:00
if (scriptType === Script.TX_UNKNOWN)
throw new Error('Unknown scriptPubKey type at:' + i +
' Type:' + scriptPubKey.getRawOutType());
2014-03-28 17:17:34 -07:00
inputMap.push({
2014-04-01 20:59:26 -07:00
address: utxo.address,
2014-03-31 10:41:27 -07:00
scriptPubKey: scriptPubKey,
scriptType: scriptType,
i: i,
2014-03-28 17:17:34 -07:00
});
}
this.inputMap = inputMap;
return this;
};
2014-04-29 05:34:26 -07:00
// getSelectedUnspent
// ------------------
//
// Returns the selected unspent outputs, to be used in the transaction.
2014-03-31 10:41:27 -07:00
TransactionBuilder.prototype.getSelectedUnspent = function() {
2014-03-28 17:17:34 -07:00
return this.selectedUtxos;
};
/* _selectUnspent
* TODO(?): sort sel (at the end) and check is some inputs can be avoided.
* If the initial utxos are sorted, this step would be necesary only if
* utxos were selected from different minConfirmationSteps.
*/
2014-03-28 17:17:34 -07:00
TransactionBuilder.prototype._selectUnspent = function(neededAmountSat) {
if (!this.utxos || !this.utxos.length)
throw new Error('unspent not set');
var minConfirmationSteps = [6, 1];
if (this.spendUnconfirmed) minConfirmationSteps.push(0);
var sel = [],
totalSat = bignum(0),
fulfill = false,
2014-03-28 17:17:34 -07:00
maxConfirmations = null,
l = this.utxos.length;
2014-03-28 17:17:34 -07:00
do {
var minConfirmations = minConfirmationSteps.shift();
for (var i = 0; i < l; i++) {
var u = this.utxos[i];
var c = u.confirmations || 0;
if (c < minConfirmations || (maxConfirmations && c >= maxConfirmations))
continue;
var sat = u.amountSat || util.parseValue(u.amount);
totalSat = totalSat.add(sat);
sel.push(u);
if (totalSat.cmp(neededAmountSat) >= 0) {
fulfill = true;
break;
}
}
maxConfirmations = minConfirmations;
} while (!fulfill && minConfirmationSteps.length);
if (!fulfill)
throw new Error('not enough unspent tx outputs to fulfill totalNeededAmount [SAT]:' +
neededAmountSat);
2014-03-28 17:17:34 -07:00
this.selectedUtxos = sel;
this._setInputMap();
return this;
};
2014-04-12 16:58:22 -07:00
TransactionBuilder.prototype._setInputs = function(txobj) {
2014-03-28 17:17:34 -07:00
var ins = this.selectedUtxos;
var l = ins.length;
var valueInSat = bignum(0);
txobj.ins = [];
2014-03-28 17:17:34 -07:00
for (var i = 0; i < l; i++) {
valueInSat = valueInSat.add(util.parseValue(ins[i].amount));
var txin = {};
txin.s = util.EMPTY_BUFFER;
txin.q = 0xffffffff;
var hash = new Buffer(ins[i].txid, 'hex');
var hashReversed = buffertools.reverse(hash);
var vout = parseInt(ins[i].vout);
var voutBuf = new Buffer(4);
voutBuf.writeUInt32LE(vout, 0);
txin.o = Buffer.concat([hashReversed, voutBuf]);
2014-04-12 16:58:22 -07:00
txobj.ins.push(txin);
2014-03-28 17:17:34 -07:00
}
this.valueInSat = valueInSat;
return this;
};
TransactionBuilder.prototype._setFee = function(feeSat) {
if (typeof this.valueOutSat === 'undefined')
2014-03-28 17:17:34 -07:00
throw new Error('valueOutSat undefined');
var valueOutSat = this.valueOutSat.add(feeSat);
if (this.valueInSat.cmp(valueOutSat) < 0) {
var inv = this.valueInSat.toString();
var ouv = valueOutSat.toString();
throw new Error('transaction input amount is less than outputs: ' +
inv + ' < ' + ouv + ' [SAT]');
}
this.feeSat = feeSat;
return this;
};
2014-04-12 16:58:22 -07:00
TransactionBuilder.prototype._setRemainder = function(txobj, remainderIndex) {
2014-03-28 17:17:34 -07:00
if (typeof this.valueInSat === 'undefined' ||
typeof this.valueOutSat === 'undefined')
2014-03-28 17:17:34 -07:00
throw new Error('valueInSat / valueOutSat undefined');
2014-04-29 05:34:26 -07:00
/* add remainder (without modifying outs[]) */
2014-03-28 17:17:34 -07:00
var remainderSat = this.valueInSat.sub(this.valueOutSat).sub(this.feeSat);
var l = txobj.outs.length;
2014-03-28 17:17:34 -07:00
this.remainderSat = bignum(0);
2014-04-29 05:34:26 -07:00
/*remove old remainder? */
2014-03-28 17:17:34 -07:00
if (l > remainderIndex) {
2014-04-12 16:58:22 -07:00
txobj.outs.pop();
2014-03-28 17:17:34 -07:00
}
if (remainderSat.cmp(0) > 0) {
var remainderOut = this.remainderOut || this.selectedUtxos[0];
2014-03-28 17:17:34 -07:00
var value = util.bigIntToValue(remainderSat);
var script = TransactionBuilder._scriptForOut(remainderOut);
2014-03-28 17:17:34 -07:00
var txout = {
v: value,
s: script.getBuffer(),
};
2014-04-12 16:58:22 -07:00
txobj.outs.push(txout);
2014-03-28 17:17:34 -07:00
this.remainderSat = remainderSat;
}
return this;
};
2014-04-12 16:58:22 -07:00
TransactionBuilder.prototype._setFeeAndRemainder = function(txobj) {
2014-03-28 17:17:34 -07:00
2014-04-29 05:34:26 -07:00
/* starting size estimation */
var size = 500,
maxSizeK, remainderIndex = txobj.outs.length;
2014-03-28 17:17:34 -07:00
do {
2014-04-29 05:34:26 -07:00
/* based on https://en.bitcoin.it/wiki/Transaction_fees */
2014-03-28 17:17:34 -07:00
maxSizeK = parseInt(size / 1000) + 1;
var feeSat = typeof(this.givenFeeSat) !== 'undefined' ? this.givenFeeSat : maxSizeK * FEE_PER_1000B_SAT;
2014-03-28 17:17:34 -07:00
var neededAmountSat = this.valueOutSat.add(feeSat);
this._selectUnspent(neededAmountSat)
._setInputs(txobj)
._setFee(feeSat)
._setRemainder(txobj, remainderIndex);
2014-03-28 17:17:34 -07:00
2014-04-12 16:58:22 -07:00
size = new Transaction(txobj).getSize();
2014-03-28 17:17:34 -07:00
} while (size > (maxSizeK + 1) * 1000);
return this;
};
2014-04-29 05:34:26 -07:00
// setOutputs
// ----------
// Sets the outputs for the transaction. Format is:
// ```
// an array of [{
// address: xx,
// amount:0.001
// },...]
// ```
//
// Note that only some of this outputs will be selected
// to create the transaction. The selected ones can be checked
2014-07-24 16:02:41 -07:00
// after calling `setOutputs`, with `.getSelectedUnspent`.
// amountSatStr could be used to pass in the amount in satoshis, as a string.
2014-04-29 05:34:26 -07:00
//
2014-03-28 17:17:34 -07:00
TransactionBuilder.prototype.setOutputs = function(outs) {
2014-07-24 12:34:57 -07:00
this.vanilla.outs = JSON.stringify(outs);
2014-03-28 17:17:34 -07:00
2014-07-24 12:34:57 -07:00
var valueOutSat = bignum(0);
var txobj = {};
txobj.version = 1;
txobj.lock_time = this.lockTime || 0;
txobj.ins = [];
2014-04-12 16:58:22 -07:00
txobj.outs = [];
2014-03-28 17:17:34 -07:00
var l = outs.length;
2014-03-28 17:17:34 -07:00
for (var i = 0; i < l; i++) {
2014-07-24 16:02:41 -07:00
var amountSat = outs[i].amountSat || outs[i].amountSatStr ? bignum(outs[i].amountSatStr) : util.parseValue(outs[i].amount);
2014-03-28 17:17:34 -07:00
var value = util.bigIntToValue(amountSat);
var script = TransactionBuilder._scriptForOut(outs[i]);
2014-03-28 17:17:34 -07:00
var txout = {
v: value,
s: script.getBuffer(),
};
2014-04-12 16:58:22 -07:00
txobj.outs.push(txout);
2014-03-28 17:17:34 -07:00
2014-07-24 16:02:41 -07:00
valueOutSat = valueOutSat.add(amountSat);
2014-03-28 17:17:34 -07:00
}
this.valueOutSat = valueOutSat;
2014-04-12 16:58:22 -07:00
this._setFeeAndRemainder(txobj);
2014-03-28 17:17:34 -07:00
2014-04-12 16:58:22 -07:00
this.tx = new Transaction(txobj);
2014-03-28 17:17:34 -07:00
return this;
};
TransactionBuilder._mapKeys = function(keys) {
2014-04-29 05:34:26 -07:00
/* prepare keys */
2014-03-28 17:17:34 -07:00
var walletKeyMap = {};
var l = keys.length;
var wk;
for (var i = 0; i < l; i++) {
var k = keys[i];
if (typeof k === 'string') {
var pk = new PrivateKey(k);
wk = new WalletKey({
network: pk.network()
});
wk.fromObj({
priv: k
});
} else if (k instanceof WalletKey) {
2014-03-28 17:17:34 -07:00
wk = k;
} else {
2014-03-28 17:17:34 -07:00
throw new Error('argument must be an array of strings (WIF format) or WalletKey objects');
}
var addr = wk.storeObj().addr;
walletKeyMap[addr] = wk;
2014-07-24 16:02:41 -07:00
2014-03-28 17:17:34 -07:00
}
return walletKeyMap;
};
TransactionBuilder._signHashAndVerify = function(wk, txSigHash) {
var triesLeft = 10,
sigRaw;
2014-03-28 17:17:34 -07:00
do {
sigRaw = wk.privKey.signSync(txSigHash);
} while (wk.privKey.verifySignatureSync(txSigHash, sigRaw) === false &&
triesLeft--);
2014-03-28 17:17:34 -07:00
if (triesLeft < 0)
2014-03-28 17:17:34 -07:00
throw new Error('could not sign input: verification failed');
return sigRaw;
};
TransactionBuilder.prototype._checkTx = function() {
2014-07-24 13:35:21 -07:00
if (!this.tx || !this.tx.ins || !this.tx.ins.length || !this.tx.outs.length)
2014-03-28 17:17:34 -07:00
throw new Error('tx is not defined');
};
2014-04-01 20:59:26 -07:00
TransactionBuilder.prototype._multiFindKey = function(walletKeyMap, pubKeyHash) {
2014-04-01 20:59:26 -07:00
var wk;
[networks.livenet, networks.testnet].forEach(function(n) {
[n.addressVersion, n.P2SHVersion].forEach(function(v) {
var a = new Address(v, pubKeyHash);
2014-04-01 20:59:26 -07:00
if (!wk && walletKeyMap[a]) {
wk = walletKeyMap[a];
}
});
});
return wk;
};
TransactionBuilder.prototype._findWalletKey = function(walletKeyMap, input) {
var wk;
if (input.address) {
wk = walletKeyMap[input.address];
} else if (input.pubKeyHash) {
wk = this._multiFindKey(walletKeyMap, input.pubKeyHash);
} else if (input.pubKeyBuf) {
2014-04-01 20:59:26 -07:00
var pubKeyHash = util.sha256ripe160(input.pubKeyBuf);
wk = this._multiFindKey(walletKeyMap, pubKeyHash);
2014-04-01 20:59:26 -07:00
} else {
throw new Error('no infomation at input to find keys');
}
return wk;
};
2014-03-31 10:41:27 -07:00
TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHash) {
if (this.tx.ins[input.i].s.length > 0) return {};
var wk = this._findWalletKey(walletKeyMap, input);
2014-03-31 10:41:27 -07:00
if (!wk) return;
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
var sigType = new Buffer(1);
sigType[0] = this.signhash;
var sig = Buffer.concat([sigRaw, sigType]);
2014-03-31 10:41:27 -07:00
var scriptSig = new Script();
scriptSig.chunks.push(sig);
scriptSig.updateBuffer();
return {
inputFullySigned: true,
signaturesAdded: 1,
script: scriptSig.getBuffer()
};
2014-03-31 10:41:27 -07:00
};
TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) {
if (this.tx.ins[input.i].s.length > 0) return {};
var wk = this._findWalletKey(walletKeyMap, input);
2014-03-31 10:41:27 -07:00
if (!wk) return;
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
var sigType = new Buffer(1);
sigType[0] = this.signhash;
var sig = Buffer.concat([sigRaw, sigType]);
2014-03-31 10:41:27 -07:00
var scriptSig = new Script();
scriptSig.chunks.push(sig);
scriptSig.chunks.push(wk.privKey.public);
scriptSig.updateBuffer();
return {
inputFullySigned: true,
signaturesAdded: 1,
script: scriptSig.getBuffer()
};
2014-03-31 10:41:27 -07:00
};
2014-04-29 05:34:26 -07:00
/* FOR TESTING
var _dumpChunks = function (scriptSig, label) {
console.log('## DUMP: ' + label + ' ##');
for(var i=0; i<scriptSig.chunks.length; i++) {
console.log('\tCHUNK ', i, Buffer.isBuffer(scriptSig.chunks[i])
?scriptSig.chunks[i].toString('hex'):scriptSig.chunks[i] );
}
};
*/
TransactionBuilder.prototype._chunkSignedWithKey = function(scriptSig, txSigHash, publicKey) {
var ret;
var k = new Key();
k.public = publicKey;
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
var chunk = scriptSig.chunks[i];
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
if (k.verifySignatureSync(txSigHash, sigRaw)) {
ret = chunk;
}
2014-04-12 14:41:34 -07:00
}
return ret;
2014-04-12 14:41:34 -07:00
};
2014-03-31 10:41:27 -07:00
TransactionBuilder.prototype._getSignatureOrder = function(sigPrio, sigRaw, txSigHash, pubkeys) {
var l = pubkeys.length;
for (var j = 0; j < l; j++) {
var k = new Key();
k.public = new Buffer(pubkeys[j], 'hex');
if (k.verifySignatureSync(txSigHash, sigRaw))
break;
2014-03-31 10:41:27 -07:00
}
return j;
2014-03-31 10:41:27 -07:00
};
TransactionBuilder.prototype._getNewSignatureOrder = function(sigPrio, scriptSig, txSigHash, pubkeys) {
var iPrio;
for (var i = 1; i <= scriptSig.countSignatures(); i++) {
2014-03-31 10:41:27 -07:00
var chunk = scriptSig.chunks[i];
var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1));
iPrio = this._getSignatureOrder(sigPrio, sigRaw, txSigHash, pubkeys);
if (sigPrio <= iPrio) break;
2014-03-31 10:41:27 -07:00
}
return (sigPrio === iPrio ? -1 : i - 1);
2014-03-31 10:41:27 -07:00
};
TransactionBuilder.prototype._chunkIsEmpty = function(chunk) {
return chunk === 0 || // when serializing and back, EMPTY_BUFFER becomes 0
2014-03-31 10:41:27 -07:00
buffertools.compare(chunk, util.EMPTY_BUFFER) === 0;
};
TransactionBuilder.prototype._initMultiSig = function(script) {
var wasUpdated = false;
if (script.chunks[0] !== 0) {
script.prependOp0();
wasUpdated = true;
}
return wasUpdated;
};
2014-04-01 20:59:26 -07:00
TransactionBuilder.prototype._updateMultiSig = function(sigPrio, wk, scriptSig, txSigHash, pubkeys) {
var wasUpdated = this._initMultiSig(scriptSig);
2014-03-31 10:41:27 -07:00
if (this._chunkSignedWithKey(scriptSig, txSigHash, wk.privKey.public))
2014-03-31 10:41:27 -07:00
return null;
// Create signature
var sigRaw = TransactionBuilder._signHashAndVerify(wk, txSigHash);
var sigType = new Buffer(1);
sigType[0] = this.signhash;
var sig = Buffer.concat([sigRaw, sigType]);
// Add signature
var order = this._getNewSignatureOrder(sigPrio, scriptSig, txSigHash, pubkeys);
scriptSig.chunks.splice(order + 1, 0, sig);
scriptSig.updateBuffer();
wasUpdated = true;
2014-03-31 10:41:27 -07:00
return wasUpdated ? scriptSig : null;
};
TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSigHash) {
var pubkeys = input.scriptPubKey.capture(),
nreq = input.scriptPubKey.chunks[0] - 80, //see OP_2-OP_16
2014-03-31 10:41:27 -07:00
l = pubkeys.length,
originalScriptBuf = this.tx.ins[input.i].s;
var scriptSig = new Script(originalScriptBuf);
2014-04-10 15:43:28 -07:00
var signaturesAdded = 0;
2014-03-31 10:41:27 -07:00
for (var j = 0; j < l && scriptSig.countSignatures() < nreq; j++) {
var wk = this._findWalletKey(walletKeyMap, {
pubKeyBuf: pubkeys[j]
});
2014-03-31 10:41:27 -07:00
if (!wk) continue;
var newScriptSig = this._updateMultiSig(j, wk, scriptSig, txSigHash, pubkeys);
2014-04-01 20:59:26 -07:00
if (newScriptSig) {
2014-03-31 10:41:27 -07:00
scriptSig = newScriptSig;
2014-04-10 15:43:28 -07:00
signaturesAdded++;
2014-04-01 20:59:26 -07:00
}
2014-03-31 10:41:27 -07:00
}
var ret = {
inputFullySigned: scriptSig.countSignatures() === nreq,
2014-04-01 20:59:26 -07:00
signaturesAdded: signaturesAdded,
2014-03-31 10:41:27 -07:00
script: scriptSig.getBuffer(),
};
return ret;
2014-03-31 10:41:27 -07:00
};
2014-03-31 20:07:45 -07:00
var fnToSign = {};
2014-04-01 20:59:26 -07:00
TransactionBuilder.prototype._scriptIsAppended = function(script, scriptToAddBuf) {
var len = script.chunks.length;
if (script.chunks[len - 1] === undefined)
2014-04-01 20:59:26 -07:00
return false;
if (typeof script.chunks[len - 1] === 'number')
2014-04-01 20:59:26 -07:00
return false;
if (buffertools.compare(script.chunks[len - 1], scriptToAddBuf) !== 0)
2014-04-01 20:59:26 -07:00
return false;
return true;
};
TransactionBuilder.prototype._addScript = function(scriptBuf, scriptToAddBuf) {
var s = new Script(scriptBuf);
if (!this._scriptIsAppended(s, scriptToAddBuf)) {
s.chunks.push(scriptToAddBuf);
s.updateBuffer();
}
return s.getBuffer();
};
2014-04-01 20:59:26 -07:00
TransactionBuilder.prototype._getInputForP2sh = function(script, index) {
var scriptType = script.classify();
2014-04-29 05:34:26 -07:00
/* pubKeyHash is needed for TX_PUBKEYHASH and TX_PUBKEY to retrieve the keys. */
2014-04-01 20:59:26 -07:00
var pubKeyHash;
switch (scriptType) {
2014-04-01 20:59:26 -07:00
case Script.TX_PUBKEYHASH:
pubKeyHash = script.captureOne();
break;
case Script.TX_PUBKEY:
var chunk = script.captureOne();
2014-04-01 20:59:26 -07:00
pubKeyHash = util.sha256ripe160(chunk);
}
return {
i: index,
pubKeyHash: pubKeyHash,
scriptPubKey: script,
scriptType: scriptType,
isP2sh: true,
2014-04-01 20:59:26 -07:00
};
};
TransactionBuilder.prototype._p2shInput = function(input) {
if (!this.hashToScriptMap)
throw new Error('hashToScriptMap not set');
2014-03-31 20:07:45 -07:00
var scriptHex = this.hashToScriptMap[input.address];
if (!scriptHex) return;
2014-03-31 10:41:27 -07:00
var scriptBuf = new Buffer(scriptHex, 'hex');
var script = new Script(scriptBuf);
var scriptType = script.classify();
2014-03-31 10:41:27 -07:00
2014-04-01 20:59:26 -07:00
if (!fnToSign[scriptType] || scriptType === Script.TX_SCRIPTHASH)
throw new Error('dont know how to sign p2sh script type:' + script.getRawOutType());
2014-03-31 20:07:45 -07:00
return {
input: this._getInputForP2sh(script, input.i),
txSigHash: this.tx.hashForSignature(script, input.i, this.signhash),
scriptType: script.classify(),
scriptBuf: scriptBuf,
};
};
TransactionBuilder.prototype._signScriptHash = function(walletKeyMap, input, txSigHash) {
var p2sh = this._p2shInput(input);
2014-03-31 20:07:45 -07:00
var ret = fnToSign[p2sh.scriptType].call(this, walletKeyMap, p2sh.input, p2sh.txSigHash);
2014-04-01 20:59:26 -07:00
if (ret && ret.script && ret.signaturesAdded) {
ret.script = this._addScript(ret.script, p2sh.scriptBuf);
2014-03-31 20:07:45 -07:00
}
return ret;
};
2014-03-31 10:41:27 -07:00
fnToSign[Script.TX_PUBKEYHASH] = TransactionBuilder.prototype._signPubKeyHash;
fnToSign[Script.TX_PUBKEY] = TransactionBuilder.prototype._signPubKey;
fnToSign[Script.TX_MULTISIG] = TransactionBuilder.prototype._signMultiSig;
2014-03-31 10:41:27 -07:00
fnToSign[Script.TX_SCRIPTHASH] = TransactionBuilder.prototype._signScriptHash;
2014-03-28 17:17:34 -07:00
2014-04-29 05:34:26 -07:00
// sign
// ----
// Signs a transaction.
// `keys`: an array of strings representing private keys to sign the
// transaction in WIF private key format OR bitcore's `WalletKey` objects
//
// If multiple keys are given, each will be tested against the transaction's
// scriptPubKeys. Only the valid private keys will be used to sign.
// This method is fully compatible with *multisig* transactions.
//
// `.isFullySigned` can be queried to check is the transactions have all the needed
// signatures.
//
//
2014-03-28 17:17:34 -07:00
TransactionBuilder.prototype.sign = function(keys) {
2014-07-24 13:35:21 -07:00
if (!(keys instanceof Array))
2014-07-24 12:34:57 -07:00
throw new Error('parameter should be an array');
2014-03-28 17:17:34 -07:00
this._checkTx();
var tx = this.tx,
ins = tx.ins,
l = ins.length,
walletKeyMap = TransactionBuilder._mapKeys(keys);
2014-07-24 16:02:41 -07:00
2014-03-28 17:17:34 -07:00
for (var i = 0; i < l; i++) {
2014-03-31 10:41:27 -07:00
var input = this.inputMap[i];
2014-03-28 17:17:34 -07:00
2014-03-31 10:41:27 -07:00
var txSigHash = this.tx.hashForSignature(
input.scriptPubKey, i, this.signhash);
2014-03-28 17:17:34 -07:00
2014-03-31 10:41:27 -07:00
var ret = fnToSign[input.scriptType].call(this, walletKeyMap, input, txSigHash);
if (ret && ret.script) {
2014-07-24 12:34:57 -07:00
this.vanilla.scriptSig[i] = ret.script.toString('hex');
2014-04-01 20:59:26 -07:00
tx.ins[i].s = ret.script;
if (ret.inputFullySigned) this.inputsSigned++;
2014-03-31 10:41:27 -07:00
}
2014-03-28 17:17:34 -07:00
}
return this;
};
2014-04-29 05:34:26 -07:00
// setHashToScriptMap
// ------------------
// Needed for setup Address to Script maps
// for p2sh transactions. See `.infoForP2sh`
// for generate the input for this call.
//
2014-03-31 10:41:27 -07:00
TransactionBuilder.prototype.setHashToScriptMap = function(hashToScriptMap) {
2014-07-24 12:34:57 -07:00
this.vanilla.hashToScriptMap = JSON.stringify(hashToScriptMap);
this.hashToScriptMap = hashToScriptMap;
2014-03-31 20:07:45 -07:00
return this;
2014-03-31 10:41:27 -07:00
};
2014-04-29 05:34:26 -07:00
// isFullySigned
// -------------
// Checks if the transaction have all the necesary signatures.
//
2014-03-28 17:17:34 -07:00
TransactionBuilder.prototype.isFullySigned = function() {
return this.inputsSigned === this.tx.ins.length;
};
TransactionBuilder.prototype.build = function() {
this._checkTx();
return this.tx;
};
2014-04-29 05:34:26 -07:00
// toObj
// -----
// Returns a plain Javascript object that contains
// the full status of the TransactionBuilder instance,
// suitable for serialization, storage and transmition.
// See `.fromObj`
//
2014-04-12 14:41:34 -07:00
TransactionBuilder.prototype.toObj = function() {
2014-07-24 12:34:57 -07:00
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,
2014-04-12 14:41:34 -07:00
};
2014-07-24 12:34:57 -07:00
if (this.vanilla.hashToScriptMap)
ret.hashToScriptMap = JSON.parse(this.vanilla.hashToScriptMap);
return ret;
2014-04-12 14:41:34 -07:00
};
TransactionBuilder.prototype._setScriptSig = function(inScriptSig) {
this.vanilla.scriptSig = inScriptSig;
for (var i in inScriptSig) {
this.tx.ins[i].s = new Buffer(inScriptSig[i], 'hex');
var scriptSig = new Script(this.tx.ins[i].s);
if (scriptSig.finishedMultiSig() !== false)
this.inputsSigned++;
}
};
2014-04-29 05:34:26 -07:00
// fromObj
// -------
// Returns a TransactionBuilder instance given
// a plain Javascript object created previously
// with `.toObj`. See `.toObj`.
2014-04-12 14:41:34 -07:00
TransactionBuilder.fromObj = function(data) {
2014-07-24 12:34:57 -07:00
if (data.version !== TOOBJ_VERSION)
throw new Error('Incompatible version at TransactionBuilder fromObj');
2014-04-12 14:41:34 -07:00
2014-07-24 12:34:57 -07:00
var b = new TransactionBuilder(data.opts);
if (data.utxos) {
2014-07-24 13:35:21 -07:00
b.setUnspent(data.utxos);
2014-04-12 14:41:34 -07:00
2014-07-24 12:34:57 -07:00
if (data.hashToScriptMap)
b.setHashToScriptMap(data.hashToScriptMap);
2014-04-12 14:41:34 -07:00
2014-07-24 12:34:57 -07:00
if (data.outs) {
b.setOutputs(data.outs);
2014-04-12 14:41:34 -07:00
if (data.scriptSig) {
b._setScriptSig(data.scriptSig);
2014-07-24 12:34:57 -07:00
}
}
2014-04-12 14:41:34 -07:00
}
return b;
};
2014-04-12 22:01:29 -07:00
2014-04-12 22:21:44 -07:00
TransactionBuilder.prototype._checkMergeability = function(b) {
2014-07-24 14:46:12 -07:00
var toCompare = ['opts', 'hashToScriptMap', 'outs', 'uxtos'];
for (var i in toCompare) {
var k = toCompare[i];
if (JSON.stringify(this.vanilla[k]) !== JSON.stringify(b.vanilla[k]))
throw new Error('cannot merge: incompatible builders:' + k)
}
2014-04-12 22:01:29 -07:00
};
2014-04-12 16:58:22 -07:00
2014-05-01 07:53:45 -07:00
// TODO this could be on Script class
TransactionBuilder.prototype._mergeInputSigP2sh = function(input, s0, s1) {
var p2sh = this._p2shInput(input);
var redeemScript = new Script(p2sh.scriptBuf);
var pubkeys = redeemScript.capture();
2014-04-12 22:01:29 -07:00
// Look for differences
var s0keys = {};
var l = pubkeys.length;
for (var j = 0; j < l; j++) {
if (this._chunkSignedWithKey(s0, p2sh.txSigHash, pubkeys[j]))
s0keys[pubkeys[j].toString('hex')] = 1;
}
var diff = [];
for (var j = 0; j < l; j++) {
var chunk = this._chunkSignedWithKey(s1, p2sh.txSigHash, pubkeys[j]);
var pubHex = pubkeys[j].toString('hex');
if (chunk && !s0keys[pubHex]) {
diff.push({
prio: j,
chunk: chunk,
pubHex: pubHex,
});
}
}
// Add signatures
for (var j in diff) {
var newSig = diff[j];
var order = this._getNewSignatureOrder(newSig.prio, s0, p2sh.txSigHash, pubkeys);
s0.chunks.splice(order + 1, 0, newSig.chunk);
2014-04-12 16:58:22 -07:00
}
s0.updateBuffer();
return s0.getBuffer();
};
2014-07-24 12:34:57 -07:00
// TODO: move this to script
TransactionBuilder.prototype._getSighashType = function(sig) {
2014-07-24 13:35:21 -07:00
return sig[sig.length - 1];
2014-07-24 12:34:57 -07:00
};
2014-07-24 13:33:40 -07:00
TransactionBuilder.prototype._checkSignHash = function(s1) {
2014-07-24 13:35:21 -07:00
var l = s1.chunks.length - 1;
2014-07-24 13:33:40 -07:00
2014-07-24 13:35:21 -07:00
for (var i = 0; i < l; i++) {
2014-07-24 13:33:40 -07:00
2014-07-24 13:35:21 -07:00
if (i == 0 && s1.chunks[i] === 0)
2014-07-24 13:33:40 -07:00
continue;
if (this._getSighashType(s1.chunks[i]) !== this.signhash)
throw new Error('signhash type mismatch at merge p2sh');
}
};
2014-05-01 07:53:45 -07:00
// TODO this could be on Script class
TransactionBuilder.prototype._mergeInputSig = function(index, s0buf, s1buf) {
if (buffertools.compare(s0buf, s1buf) === 0)
return s0buf;
2014-04-12 22:01:29 -07:00
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 < 2 && l1 > 2) || (l1 < 2 && l0 > 2)))
2014-04-12 22:01:29 -07:00
throw new Error('TX sig types mismatch in merge');
2014-07-24 13:33:40 -07:00
if ((!l0 && !l1) || (l0 && !l1))
return s0buf;
2014-04-12 22:21:44 -07:00
2014-07-24 13:33:40 -07:00
this._checkSignHash(s1);
2014-07-24 12:34:57 -07:00
2014-07-24 13:33:40 -07:00
if ((!l0 && l1))
return s1buf;
2014-04-12 22:21:44 -07:00
// Get the pubkeys
var input = this.inputMap[index];
var type = input.scriptPubKey.classify();
2014-04-12 22:01:29 -07:00
//p2pubkey or p2pubkeyhash
if (type === Script.TX_PUBKEYHASH || type === Script.TX_PUBKEY) {
2014-07-24 12:34:57 -07:00
var s = new Script(s1buf);
log.debug('Merging two signed inputs type:' +
input.scriptPubKey.getRawOutType() + '. Signatures differs. Using the first version.');
return s0buf;
} else if (type !== Script.TX_SCRIPTHASH) {
// No support for normal multisig or strange txs.
throw new Error('Script type:' + input.scriptPubKey.getRawOutType() + 'not supported at #merge');
2014-04-12 22:21:44 -07:00
}
2014-07-24 12:34:57 -07:00
return this._mergeInputSigP2sh(input, s0, s1);
2014-04-12 22:01:29 -07:00
};
2014-05-01 07:53:45 -07:00
// TODO this could be on Transaction class
TransactionBuilder.prototype._mergeTx = function(tx) {
var v0 = this.tx;
var v1 = tx;
2014-04-12 22:01:29 -07:00
var l = v0.ins.length;
if (l !== v1.ins.length)
throw new Error('TX in length mismatch in merge');
2014-04-12 22:01:29 -07:00
this.inputsSigned = 0;
for (var i = 0; i < l; i++) {
var i0 = v0.ins[i];
var i1 = v1.ins[i];
2014-04-12 22:01:29 -07:00
if (i0.q !== i1.q)
throw new Error('TX sequence ins mismatch in merge. Input:', i);
2014-04-12 22:01:29 -07:00
if (buffertools.compare(i0.o, i1.o) !== 0)
throw new Error('TX .o in mismatch in merge. Input:', i);
2014-04-12 22:01:29 -07:00
i0.s = this._mergeInputSig(i, i0.s, i1.s);
2014-07-24 13:35:21 -07:00
this.vanilla.scriptSig[i] = i0.s.toString('hex');
2014-04-12 22:21:44 -07:00
if (v0.isInputComplete(i)) this.inputsSigned++;
}
2014-04-12 22:01:29 -07:00
};
2014-07-24 12:34:57 -07:00
// clone
// -----
// Clone current TransactionBuilder, regenerate derived fields.
//
TransactionBuilder.prototype.clone = function() {
return new TransactionBuilder.fromObj(this.toObj());
};
2014-04-29 05:34:26 -07:00
// merge
// -----
// Merge to TransactionBuilder objects, merging inputs signatures.
// This function supports multisig p2sh inputs.
2014-07-24 12:34:57 -07:00
TransactionBuilder.prototype.merge = function(inB) {
//
var b = inB.clone();
2014-04-12 22:01:29 -07:00
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'))
2014-04-12 22:01:29 -07:00
throw new Error('mismatch at TransactionBuilder NTXID');
this._mergeTx(b.tx);
2014-04-12 16:58:22 -07:00
}
};
module.exports = TransactionBuilder;