Browse Source

Merge pull request #452 from matiu/feature/txproposal10

TransactionBuilder .fromObj .toObj rewrite
patch-2
Ryan X. Charles 11 years ago
parent
commit
552a18b760
  1. 191
      lib/TransactionBuilder.js
  2. 718
      test/test.TransactionBuilder.js

191
lib/TransactionBuilder.js

@ -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?

718
test/test.TransactionBuilder.js

File diff suppressed because it is too large
Loading…
Cancel
Save