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. 344
      test/test.TransactionBuilder.js

191
lib/TransactionBuilder.js

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

344
test/test.TransactionBuilder.js

@ -33,7 +33,10 @@ describe('TransactionBuilder', function() {
}); });
it('should be able to create instance with params', function() { it('should be able to create instance with params', function() {
var t = new TransactionBuilder({spendUnconfirmed: true, lockTime: 10}); var t = new TransactionBuilder({
spendUnconfirmed: true,
lockTime: 10
});
should.exist(t); should.exist(t);
should.exist(t.lockTime); should.exist(t.lockTime);
t.spendUnconfirmed.should.equal(true); t.spendUnconfirmed.should.equal(true);
@ -46,7 +49,9 @@ describe('TransactionBuilder', function() {
}); });
var getBuilder = function(spendUnconfirmed) { var getBuilder = function(spendUnconfirmed) {
var t = new TransactionBuilder({spendUnconfirmed: spendUnconfirmed}) var t = new TransactionBuilder({
spendUnconfirmed: spendUnconfirmed
})
.setUnspent(testdata.dataUnspent); .setUnspent(testdata.dataUnspent);
return t; return t;
@ -75,16 +80,22 @@ describe('TransactionBuilder', function() {
}); });
it('#_selectUnspent should return null if not enough utxos', function() { it('#_selectUnspent should return null if not enough utxos', function() {
(function() { f(1.12); }).should.throw(); (function() {
f(1.12);
}).should.throw();
}); });
it('#_selectUnspent should check confirmations', function() { it('#_selectUnspent should check confirmations', function() {
(function() { f(0.9,false); }).should.throw(); (function() {
f(0.9, false);
}).should.throw();
f(0.9).length.should.equal(3); f(0.9).length.should.equal(3);
f(0.11, false).length.should.equal(2); f(0.11, false).length.should.equal(2);
(function() { f(0.111,false); }).should.throw(); (function() {
f(0.111, false);
}).should.throw();
}); });
@ -115,7 +126,9 @@ describe('TransactionBuilder', function() {
var getBuilder2 = function(fee) { var getBuilder2 = function(fee) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
spendUnconfirmed: true, spendUnconfirmed: true,
}; };
@ -144,7 +157,9 @@ describe('TransactionBuilder', function() {
var utxos = testdata.dataUnspent; var utxos = testdata.dataUnspent;
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
spendUnconfirmed: true, spendUnconfirmed: true,
}; };
var outs = [{ var outs = [{
@ -218,11 +233,14 @@ describe('TransactionBuilder', function() {
var getBuilder3 = function (outs) { var getBuilder3 = function(outs, signhash) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
spendUnconfirmed: true, spendUnconfirmed: true,
signhash: signhash,
}; };
var outs = outs || [{ var outs = outs || [{
@ -483,7 +501,9 @@ describe('TransactionBuilder', function() {
it('should sign a p2pubkey tx', function(done) { it('should sign a p2pubkey tx', function(done) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
var outs = outs || [{ var outs = outs || [{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -516,7 +536,9 @@ describe('TransactionBuilder', function() {
it('should sign a multisig tx', function(done) { it('should sign a multisig tx', function(done) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
var outs = outs || [{ var outs = outs || [{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -547,7 +569,9 @@ describe('TransactionBuilder', function() {
it('should sign a multisig tx in steps (3-5)', function(done) { it('should sign a multisig tx in steps (3-5)', function(done) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
var outs = outs || [{ var outs = outs || [{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -588,7 +612,9 @@ describe('TransactionBuilder', function() {
it('should count multisig signs (3-5)', function() { it('should count multisig signs (3-5)', function() {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
var outs = outs || [{ var outs = outs || [{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -626,7 +652,9 @@ describe('TransactionBuilder', function() {
it('should avoid siging with the same key twice multisig signs (3-5)', function(done) { it('should avoid siging with the same key twice multisig signs (3-5)', function(done) {
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
var outs = outs || [{ var outs = outs || [{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -672,8 +700,12 @@ describe('TransactionBuilder', function() {
var privs = testdata.dataUnspentSign.keyStringsP2sh; var privs = testdata.dataUnspentSign.keyStringsP2sh;
var pubkeys = []; var pubkeys = [];
privs.forEach(function(p) { privs.forEach(function(p) {
var wk = new WalletKey({network: networks.testnet}); var wk = new WalletKey({
wk.fromObj({priv: p}); network: networks.testnet
});
wk.fromObj({
priv: p
});
pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public)); pubkeys.push(bitcore.buffertools.toHex(wk.privKey.public));
}); });
@ -693,14 +725,19 @@ describe('TransactionBuilder', function() {
// "redeemScript" : "532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae" // "redeemScript" : "532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae"
// } // }
// //
var getP2shBuilder = function(setMap) { var getP2shBuilder = function(setMap, opts) {
var network = 'testnet'; var network = 'testnet';
var opts = { opts = opts || {};
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, opts.remainderOut = opts.remainderOut || {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
}; };
var data = getInfoForP2sh(); var data = getInfoForP2sh();
// multisig p2sh // multisig p2sh
var p2shOpts = {nreq:3, pubkeys:data.pubkeys}; var p2shOpts = {
nreq: 3,
pubkeys: data.pubkeys
};
var info = TransactionBuilder.infoForP2sh(p2shOpts, network); var info = TransactionBuilder.infoForP2sh(p2shOpts, network);
var outs = outs || [{ var outs = outs || [{
@ -721,7 +758,9 @@ describe('TransactionBuilder', function() {
it('should fail to sign a p2sh/multisign tx if none script map was given', function() { it('should fail to sign a p2sh/multisign tx if none script map was given', function() {
var b = getP2shBuilder(); var b = getP2shBuilder();
(function() {b.sign(testdata.dataUnspentSign.keyStringsP2sh);}).should.throw(); (function() {
b.sign(testdata.dataUnspentSign.keyStringsP2sh);
}).should.throw();
}); });
@ -743,7 +782,14 @@ describe('TransactionBuilder', function() {
}; };
[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,2,1],[3,1,2]].forEach(function(order) { [
[1, 2, 3],
[1, 3, 2],
[2, 1, 3],
[2, 3, 1],
[3, 2, 1],
[3, 1, 2]
].forEach(function(order) {
it('should sign a p2sh/multisig tx in order ' + order.join(','), function(done) { it('should sign a p2sh/multisig tx in order ' + order.join(','), function(done) {
var b = getP2shBuilder(1); var b = getP2shBuilder(1);
b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]); b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]);
@ -760,53 +806,50 @@ describe('TransactionBuilder', function() {
var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1, 2); var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1, 2);
var k5 = testdata.dataUnspentSign.keyStringsP2sh.slice(4, 5); var k5 = testdata.dataUnspentSign.keyStringsP2sh.slice(4, 5);
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
b.signaturesAdded.should.equal(0);
b.sign(k1); b.sign(k1);
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
b.signaturesAdded.should.equal(1);
var tx = b.build(); var tx = b.build();
tx.ins.length.should.equal(1); tx.ins.length.should.equal(1);
tx.outs.length.should.equal(2); tx.outs.length.should.equal(2);
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
b.signaturesAdded.should.equal(1);
// Sign with the same // Sign with the same
b.sign(k1); b.sign(k1);
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
b.signaturesAdded.should.equal(1);
// Sign with k5 // Sign with k5
b.sign(k5); b.sign(k5);
/// ///
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
b.signaturesAdded.should.equal(2);
// Sign with same // Sign with same
b.sign(k5); b.sign(k5);
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
b.signaturesAdded.should.equal(2);
// Sign k2 // Sign k2
b.sign(k2); b.sign(k2);
b.isFullySigned().should.equal(true); b.isFullySigned().should.equal(true);
tx.isComplete().should.equal(true); tx.isComplete().should.equal(true);
b.signaturesAdded.should.equal(3);
}); });
it('should sign a p2sh/p2pubkeyhash tx', function() { it('should sign a p2sh/p2pubkeyhash tx', function() {
var priv = 'cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA'; var priv = 'cMpKwGr5oxEacN95WFKNEq6tTcvi11regFwS3muHvGYVxMPJX8JA';
var network = 'testnet'; var network = 'testnet';
var opts = { var opts = {
remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, remainderOut: {
address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'
},
}; };
// p2hash/ p2sh // p2hash/ p2sh
var p2shOpts = {address:'mgwqzy6pF5BSc72vxHBFSnnhNEBcV4TJzV'}; var p2shOpts = {
address: 'mgwqzy6pF5BSc72vxHBFSnnhNEBcV4TJzV'
};
var info = TransactionBuilder.infoForP2sh(p2shOpts, network); var info = TransactionBuilder.infoForP2sh(p2shOpts, network);
//addr: 2NAwCQ1jPYPrSsyBQvfP6AJ6d6SSxnHsZ4e //addr: 2NAwCQ1jPYPrSsyBQvfP6AJ6d6SSxnHsZ4e
@ -844,6 +887,17 @@ describe('TransactionBuilder', function() {
tx.isComplete().should.equal(true); tx.isComplete().should.equal(true);
}); });
it('should check sign parameters', function() {
var b = getP2shBuilder(1);
(function() {
b.sign(testdata.dataUnspentSign.keyStringsP2sh[0])
}).should.throw('array');
});
describe('serizalization', function() {
it('#toObj #fromObj roundtrip', function() { it('#toObj #fromObj roundtrip', function() {
var b = getBuilder2(); var b = getBuilder2();
@ -868,37 +922,33 @@ describe('TransactionBuilder', function() {
it('#toObj #fromObj roundtrip, step signatures p2sh/p2pubkeyhash', function() { it('#toObj #fromObj roundtrip, step signatures p2sh/p2pubkeyhash', function() {
var b = getP2shBuilder(1); var b = getP2shBuilder(1);
var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); var keys = JSON.parse(JSON.stringify(testdata.dataUnspentSign.keyStringsP2sh));
var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2);
var k5 = testdata.dataUnspentSign.keyStringsP2sh.slice(4,5); var k1 = keys.slice(0, 1);
var k2 = keys.slice(1, 2);
var k5 = keys.slice(4, 5);
b.isFullySigned().should.equal(false); b.isFullySigned().should.equal(false);
b.signaturesAdded.should.equal(0);
var b2 = TransactionBuilder.fromObj(b.toObj()); var b2 = TransactionBuilder.fromObj(b.toObj());
b2.sign(k1); b2.sign(k1);
b2.isFullySigned().should.equal(false); b2.isFullySigned().should.equal(false);
b2.signaturesAdded.should.equal(1);
var tx = b2.build(); var tx = b2.build();
tx.ins.length.should.equal(1); tx.ins.length.should.equal(1);
tx.outs.length.should.equal(2); tx.outs.length.should.equal(2);
tx.isComplete().should.equal(false); tx.isComplete().should.equal(false);
b2.signaturesAdded.should.equal(1);
// Sign with the same // Sign with the same
var b3 = TransactionBuilder.fromObj(b2.toObj()); var b3 = TransactionBuilder.fromObj(b2.toObj());
b3.sign(k1); b3.sign(k1);
b3.isFullySigned().should.equal(false); b3.isFullySigned().should.equal(false);
b3.signaturesAdded.should.equal(1);
// Sign with k5 // Sign with k5
var b4 = TransactionBuilder.fromObj(b3.toObj()); var b4 = TransactionBuilder.fromObj(b3.toObj());
b4.sign(k5); b4.sign(k5);
b4.isFullySigned().should.equal(false); b4.isFullySigned().should.equal(false);
b4.signaturesAdded.should.equal(2);
var b5 = TransactionBuilder.fromObj(b4.toObj()); var b5 = TransactionBuilder.fromObj(b4.toObj());
// Sign k2 // Sign k2
@ -906,10 +956,30 @@ describe('TransactionBuilder', function() {
b5.isFullySigned().should.equal(true); b5.isFullySigned().should.equal(true);
var tx2 = b5.build(); var tx2 = b5.build();
tx2.isComplete().should.equal(true); tx2.isComplete().should.equal(true);
b5.signaturesAdded.should.equal(3);
}); });
it('#merge self', function() {
it('should keep signatures after clone', function() {
var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
var b = getP2shBuilder(1);
var b2 = getP2shBuilder(1);
b.sign([k1]);
b2.sign([k2]);
b2.merge(b);
var tx2 = b2.build();
tx2.countInputSignatures(0).should.equal(2, 'before clone');
tx2 = b2.clone().build();
tx2.countInputSignatures(0).should.equal(2, 'after clone');
});
});
describe('#merge', function() {
it('with self', function() {
var b = getBuilder3([{ var b = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 16 amount: 16
@ -923,6 +993,7 @@ describe('TransactionBuilder', function() {
tx.ins.length.should.equal(3); tx.ins.length.should.equal(3);
tx.outs.length.should.equal(2); tx.outs.length.should.equal(2);
}); });
it('#merge simple', function() { it('#merge simple', function() {
var b = getBuilder3([{ var b = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
@ -945,33 +1016,64 @@ describe('TransactionBuilder', function() {
tx.outs.length.should.equal(2); tx.outs.length.should.equal(2);
}); });
it('#merge checks', function() {
var b = getBuilder3([{ var b = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 16 amount: 16
}]); }]);
it('should check amount', function() {
// bad amount // bad amount
var b2 = getBuilder3([{ var b2 = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 15 amount: 15
}]); }]);
(function() {b2.merge(b);}).should.throw(); (function() {
b2.merge(b);
}).should.throw('NTXID');
});
it('should check addresses', function() {
// bad out // bad out
b2 = getBuilder3([{ var b2 = getBuilder3([{
address: 'muHct3YZ9Nd5Pq7uLYYhXRAxeW4EnpcaLz', address: 'muHct3YZ9Nd5Pq7uLYYhXRAxeW4EnpcaLz',
amount: 16 amount: 16
}]); }]);
(function() {b2.merge(b);}).should.throw(); (function() {
b2.merge(b);
}).should.throw('NTXID');
});
it('should check signhash in p2pubkeyhash', function() {
// bad amount
var b = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 15
}]);
b.sign(testdata.dataUnspentSign.keyStrings);
var b2 = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 15
}], bitcore.Transaction.SIGHASH_NONE);
b2.sign(testdata.dataUnspentSign.keyStrings);
(function() {
b2.merge(b);
}).should.throw('signhash');
});
it('should merge signed signed txs', function() {
// same signature // same signature
// -> this fails: no way to check signatures, since PRIV Keys are not stored // -> keep first signature
b = getBuilder3([{ var b = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 16 amount: 16
}]) }])
.sign(testdata.dataUnspentSign.keyStrings); .sign(testdata.dataUnspentSign.keyStrings);
// merge simple // merge simple
b2 = getBuilder3([{ var b2 = getBuilder3([{
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE',
amount: 16 amount: 16
}]) }])
@ -981,53 +1083,129 @@ describe('TransactionBuilder', function() {
b2.isFullySigned().should.equal(true); b2.isFullySigned().should.equal(true);
}); });
it('#merge p2sh/steps', function(done) {
it('#merge p2sh in 2 steps', function() {
var b = getP2shBuilder(1); var b = getP2shBuilder(1);
var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); var b2 = getP2shBuilder(1);
var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(2,3); var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
b.isFullySigned().should.equal(false); b.sign([k1]);
b.signaturesAdded.should.equal(0); b2.sign([k2]);
b.sign(k1); b.merge(b2);
b.signaturesAdded.should.equal(1);
b.isFullySigned().should.equal(false);
var tx = b.build(); var tx = b.build();
tx.isComplete().should.equal(false); tx.countInputSignatures(0).should.equal(2);
});
it('#merge p2sh in 2 steps, case 2', function() {
var b = getP2shBuilder(1);
var b2 = getP2shBuilder(1);
var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
var k3 = testdata.dataUnspentSign.keyStringsP2sh[2];
b.sign([k1, k2]);
b2.sign([k3]);
b.merge(b2);
var tx = b.build();
tx.countInputSignatures(0).should.equal(3);
});
b = TransactionBuilder.fromObj(b.toObj());
// TODO TO OBJ! it('#merge p2sh sign twice', function() {
var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
var b = getP2shBuilder(1);
var b2 = getP2shBuilder(1); var b2 = getP2shBuilder(1);
b2.sign(k2);
b2.signaturesAdded.should.equal(1); b.sign([k1]);
b2.sign([k1, k2]);
b2.merge(b); b2.merge(b);
b2.signaturesAdded.should.equal(2); var tx = b2.build();
tx = b2.build(); tx.countInputSignatures(0).should.equal(2);
tx.isComplete().should.equal(false); });
it('#merge p2sh sign twice, case2', function() {
var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
b2 = TransactionBuilder.fromObj(b2.toObj()); var b = getP2shBuilder(1);
var b2 = getP2shBuilder(1);
b.sign([k1]);
b2.sign([k1]);
b2.merge(b);
var tx = b2.build();
tx.countInputSignatures(0).should.equal(1);
});
it('#merge p2sh in 3 steps', function() {
var k1 = testdata.dataUnspentSign.keyStringsP2sh[0];
var k2 = testdata.dataUnspentSign.keyStringsP2sh[1];
var k3 = testdata.dataUnspentSign.keyStringsP2sh[2];
var b = getP2shBuilder(1);
var b2 = getP2shBuilder(1);
var b3 = getP2shBuilder(1); var b3 = getP2shBuilder(1);
b3.sign(k3);
b3.signaturesAdded.should.equal(1); b.sign([k1]);
b2.sign([k2]);
b2.merge(b);
var tx = b2.clone().build();
tx.countInputSignatures(0).should.equal(2);
b3.sign([k3]);
b3.merge(b2); b3.merge(b2);
b3.signaturesAdded.should.equal(3);
tx = b3.build(); tx = b3.build();
tx.isComplete().should.equal(true); tx.countInputSignatures(0).should.equal(3);
});
b3 = TransactionBuilder.fromObj(b3.toObj());
b2.merge(b3);
b2.signaturesAdded.should.equal(3);
tx = b2.build();
tx.isComplete().should.equal(true);
var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey; it('should check signhash in p2sh/merge', function() {
var s = new Script(new Buffer(shex,'hex')); var b = getP2shBuilder(1);
tx.verifyInput(0,s, vopts, function(err, results){ var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0, 1);
should.exist(results); var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1, 2);
results.should.equal(true); b.isFullySigned().should.equal(false);
should.not.exist(err); b.sign(k1);
done(); var tx = b.build();
tx.isComplete().should.equal(false);
var b2 = getP2shBuilder(1, {
signhash: bitcore.Transaction.SIGHASH_NONE
}); });
b2.sign(k2);
(function() {
b2.merge(b)
}).should.throw();
});
it('#merge p2sh/steps change return address', function() {
var b = getP2shBuilder(1);
var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0, 1);
var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1, 2);
var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(2, 3);
b.isFullySigned().should.equal(false);
b.sign(k1);
var tx = b.build();
tx.isComplete().should.equal(false);
b = TransactionBuilder.fromObj(b.toObj());
var b2 = getP2shBuilder(1, {
remainderOut: {
address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE'
}
});
b2.sign(k2);
(function() {
b2.merge(b)
}).should.throw('NTXID');
});
}); });
}); });

Loading…
Cancel
Save