From e8ebd2e33295c75ea2e3b57f9ffa872b13ed553c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 21 Apr 2014 23:02:35 -0300 Subject: [PATCH 1/5] add tx validation to test --- test/test.TransactionBuilder.js | 168 +++++++++++++++++++++++++++++--- 1 file changed, 155 insertions(+), 13 deletions(-) diff --git a/test/test.TransactionBuilder.js b/test/test.TransactionBuilder.js index d4671ec..1fdb762 100644 --- a/test/test.TransactionBuilder.js +++ b/test/test.TransactionBuilder.js @@ -8,10 +8,19 @@ var should = chai.should(); var TransactionBuilder = bitcore.TransactionBuilder; var WalletKey = bitcore.WalletKey; +var Script = bitcore.Script; var util = bitcore.util; var networks = bitcore.networks; var testdata = testdata || require('./testdata'); + +var vopts = { + verifyP2SH: true, + dontVerifyStrictEnc: true +}; + + + describe('TransactionBuilder', function() { it('should initialze the main object', function() { should.exist(TransactionBuilder); @@ -224,7 +233,7 @@ describe('TransactionBuilder', function() { .setOutputs(outs); }; - it('should sign a tx (case 1)', function() { + it('should sign a tx (case 1)', function(done) { var b = getBuilder3(); b.isFullySigned().should.equal(false); @@ -236,9 +245,18 @@ describe('TransactionBuilder', function() { tx.isComplete().should.equal(true); tx.ins.length.should.equal(1); tx.outs.length.should.equal(2); + + var shex = testdata.dataUnspentSign.unspent[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); }); - it('should sign a tx (case 2)', function() { + it('should sign a tx (case 2)', function(done) { var b = getBuilder3([{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount: 16 @@ -250,6 +268,16 @@ describe('TransactionBuilder', function() { tx.isComplete().should.equal(true); tx.ins.length.should.equal(3); tx.outs.length.should.equal(2); + + var shex = testdata.dataUnspentSign.unspent[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + }); it('should sign an incomplete tx', function() { @@ -273,7 +301,7 @@ describe('TransactionBuilder', function() { }); - it('should sign a tx in multiple steps (case1)', function() { + it('should sign a tx in multiple steps (case1)', function(done) { var b = getBuilder3([{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', @@ -292,9 +320,20 @@ describe('TransactionBuilder', function() { b.sign(k23); b.isFullySigned().should.equal(true); (b.build()).isComplete().should.equal(true); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspent[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + }); - it('#sign should sign a tx in multiple steps (case2)', function() { + it('#sign should sign a tx in multiple steps (case2)', function(done) { var b = getBuilder3([{ address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', amount: 16 @@ -304,21 +343,67 @@ describe('TransactionBuilder', function() { (b.build()).isComplete().should.equal(false); var k1 = testdata.dataUnspentSign.keyStrings.slice(0, 1); + var k2 = testdata.dataUnspentSign.keyStrings.slice(1, 2); + var k3 = testdata.dataUnspentSign.keyStrings.slice(2, 3); b.sign(k1); b.isFullySigned().should.equal(false); (b.build()).isComplete().should.equal(false); - var k2 = testdata.dataUnspentSign.keyStrings.slice(1, 2); b.sign(k2); b.isFullySigned().should.equal(false); (b.build()).isComplete().should.equal(false); - var k3 = testdata.dataUnspentSign.keyStrings.slice(2, 3); b.sign(k3); b.isFullySigned().should.equal(true); (b.build()).isComplete().should.equal(true); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspent[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); }); + it('#sign should sign a tx in multiple steps (case2) / diff order', function(done) { + var b = getBuilder3([{ + address: 'mrPnbY1yKDBsdgbHbS7kJ8GVm8F66hWHLE', + amount: 16 + }]); + + b.isFullySigned().should.equal(false); + (b.build()).isComplete().should.equal(false); + + var k2 = testdata.dataUnspentSign.keyStrings.slice(0, 1); + var k3 = testdata.dataUnspentSign.keyStrings.slice(1, 2); + var k1 = testdata.dataUnspentSign.keyStrings.slice(2, 3); + b.sign(k1); + b.isFullySigned().should.equal(false); + (b.build()).isComplete().should.equal(false); + + b.sign(k2); + b.isFullySigned().should.equal(false); + (b.build()).isComplete().should.equal(false); + + b.sign(k3); + b.isFullySigned().should.equal(true); + (b.build()).isComplete().should.equal(true); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspent[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + }); + + it('should generate dynamic fee and readjust (and not) the selected UTXOs (case1)', function() { //this cases exceeds the input by 1mbtc AFTEr calculating the dynamic fee, //so, it should trigger adding a new 10BTC utxo @@ -394,7 +479,7 @@ describe('TransactionBuilder', function() { tx.isComplete().should.equal(false); }); - it('should sign a p2pubkey tx', function() { + it('should sign a p2pubkey tx', function(done) { var opts = { remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; @@ -413,10 +498,21 @@ describe('TransactionBuilder', function() { tx.isComplete().should.equal(true); tx.ins.length.should.equal(1); tx.outs.length.should.equal(2); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspentPubKey[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + }); - it('should sign a multisig tx', function() { + it('should sign a multisig tx', function(done) { var opts = { remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; @@ -434,10 +530,20 @@ describe('TransactionBuilder', function() { tx.ins.length.should.equal(1); tx.outs.length.should.equal(2); tx.isComplete().should.equal(true); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspentMulti[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); }); - it('should sign a multisig tx in steps (3-5)', function() { + it('should sign a multisig tx in steps (3-5)', function(done) { var opts = { remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; @@ -464,6 +570,17 @@ describe('TransactionBuilder', function() { tx.ins.length.should.equal(1); tx.outs.length.should.equal(2); tx.isComplete().should.equal(true); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspentMulti[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + }); @@ -506,7 +623,7 @@ describe('TransactionBuilder', function() { }); - it('should avoid siging with the same key twice multisig signs (3-5)', function() { + it('should avoid siging with the same key twice multisig signs (3-5)', function(done) { var opts = { remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; @@ -535,6 +652,18 @@ describe('TransactionBuilder', function() { b.sign(k23); b.isFullySigned().should.equal(true); tx.countInputMissingSignatures(0).should.equal(0); + + var tx = b.build(); + var shex = testdata.dataUnspentSign.unspentMulti[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(err); + should.exist(results); + results.should.equal(true); + done(); + }); + + }); @@ -822,11 +951,11 @@ describe('TransactionBuilder', function() { b2.merge(b, true); }); - it('#merge p2sh/steps', function() { + it('#merge p2sh/steps', function(done) { var b = getP2shBuilder(1); - var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); + var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); - var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(2,3); + var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(2,3); b.isFullySigned().should.equal(false); b.signaturesAdded.should.equal(0); b.sign(k1); @@ -855,5 +984,18 @@ describe('TransactionBuilder', function() { b2.signaturesAdded.should.equal(3); tx = b2.build(); tx.isComplete().should.equal(true); + + var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ +console.log('[test.TransactionBuilder.js.870:err:]',err,results); //TODO +//TODO + // should.not.exist(err); + // should.exist(results); + // results.should.equal(true); + + return done(); + }); + }); }); From 56d81bc0dfce3c0b9b713f836a09aea54fa22b91 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 22 Apr 2014 13:50:41 -0300 Subject: [PATCH 2/5] remove unused code from Script. Update it to do not use scriptSig placeholders --- lib/Script.js | 72 +++++++++++++-------------------- lib/TransactionBuilder.js | 8 +++- test/data/unspentSign.json | 3 +- test/test.Script.js | 47 +++++++++++---------- test/test.TransactionBuilder.js | 56 ++++++++++++++++++++++--- 5 files changed, 113 insertions(+), 73 deletions(-) diff --git a/lib/Script.js b/lib/Script.js index d76e3b8..fcba36b 100644 --- a/lib/Script.js +++ b/lib/Script.js @@ -119,34 +119,37 @@ Script.prototype.isMultiSig = function() { this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); }; -Script.prototype.finishedMultiSig = function() { - var nsigs = 0; - for (var i = 0; i < this.chunks.length - 1; i++) - if (this.chunks[i] !== 0) - nsigs++; +Script.prototype.countMissingSignatures = function() { + if (this.isMultiSig()) { + log.debug("Can not count missing signatures on normal Multisig script"); + return null; + } - var serializedScript = this.chunks[this.chunks.length - 1]; - var script = new Script(serializedScript); - var nreq = script.chunks[0] - 80; //see OP_2-OP_16 - - if (nsigs == nreq) - return true; - else - return false; -}; - -Script.prototype.removePlaceHolders = function() { - var chunks = []; - for (var i in this.chunks) { - if (this.chunks.hasOwnProperty(i)) { - var chunk = this.chunks[i]; - if (chunk != 0) - chunks.push(chunk); + // P2SH? + var l =this.chunks.length; + if (isSmallIntOp(this.chunks[0]) && this.chunks[0] ===0) { + var redeemScript = new Script(this.chunks[l-1]); + if (!isSmallIntOp(redeemScript.chunks[0])) { + log.debug("Unrecognized script type"); + ret = null; + } + else { + var nreq = redeemScript.chunks[0] - 80; //see OP_2-OP_16 + ret = nreq - (l - 2); // 2-> marked 0 + redeemScript } } - this.chunks = chunks; - this.updateBuffer(); - return this; + // p2pubkey or p2pubkeyhash + else { + if (buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER) === 0) { + ret = 1; + } + } + return ret; +}; + + +Script.prototype.finishedMultiSig = function() { + return this.countMissingSignatures() === 0; }; Script.prototype.prependOp0 = function() { @@ -517,25 +520,6 @@ Script.prototype.toHumanReadable = function() { return s; }; -Script.prototype.countMissingSignatures = function() { - var ret = 0; - if (!Buffer.isBuffer(this.chunks[0]) && this.chunks[0] ===0) { - // Multisig, skip first 0x0 - for (var i = 1; i < this.chunks.length; i++) { - if (this.chunks[i]===0 - || buffertools.compare(this.chunks[i], util.EMPTY_BUFFER) === 0){ - ret++; - } - } - } - else { - if (buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER) === 0) { - ret = 1; - } - } - return ret; -}; - Script.stringToBuffer = function(s) { var buf = new Put(); var split = s.split(' '); diff --git a/lib/TransactionBuilder.js b/lib/TransactionBuilder.js index ca27dcc..0466e59 100644 --- a/lib/TransactionBuilder.js +++ b/lib/TransactionBuilder.js @@ -550,6 +550,7 @@ TransactionBuilder.prototype._updateMultiSig = function(wk, scriptSig, txSigHash if (this._isSignedWithKey(wk,scriptSig, txSigHash, nreq)) return null; +console.log('[TransactionBuilder.js.552] ', wk.privKey.public.toString('hex')); //TODO // Find an empty slot and sign for(var i=1; i<=nreq; i++) { var chunk = scriptSig.chunks[i]; @@ -581,8 +582,10 @@ TransactionBuilder.prototype._signMultiSig = function(walletKeyMap, input, txSig var signaturesAdded = 0; for(var j=0; j +// +// { +// "address" : "2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6", +// "redeemScript" : "532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae" +// } +// var getP2shBuilder = function(setMap) { var network = 'testnet'; var opts = { remainderOut: {address: 'mwZabyZXg8JzUtFX1pkGygsMJjnuqiNhgd'}, }; var data = getInfoForP2sh(); +console.log('[test.TransactionBuilder.js.700:data:]',data); //TODO +process.exit(1); // multisig p2sh var p2shOpts = {nreq:3, pubkeys:data.pubkeys}; var info = TransactionBuilder.infoForP2sh(p2shOpts, network); +console.log('[test.TransactionBuilder.js.693:info:]',info, p2shOpts); //TODO var outs = outs || [{ - address: info.address, + address: 'mon1Hqs3jqKTtRSnRwJ3pRYMFos9WYfKb5', amount: 0.08 }]; var b = new TransactionBuilder(opts) @@ -713,14 +726,45 @@ describe('TransactionBuilder', function() { (function() {b.sign(testdata.dataUnspentSign.keyStringsP2sh);}).should.throw(); }); - it('should sign a p2sh/multisign tx', function() { - var b = getP2shBuilder(1); - b.sign(testdata.dataUnspentSign.keyStringsP2sh); + it('should sign a p2sh/multisig tx right order', function(done) { + var b = getP2shBuilder(1); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]); b.isFullySigned().should.equal(true); var tx = b.build(); tx.ins.length.should.equal(1); tx.outs.length.should.equal(2); tx.isComplete().should.equal(true); + + var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.exist(results); + results.should.equal(true); + should.not.exist(err); + done(); + }); + }); + + it('should failed to verify a p2sh/multisig tx wrong order', function(done) { + var b = getP2shBuilder(1); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]); + b.isFullySigned().should.equal(true); + var tx = b.build(); + tx.ins.length.should.equal(1); + tx.outs.length.should.equal(2); + tx.isComplete().should.equal(true); + + var shex = testdata.dataUnspentSign.unspentP2sh[0].scriptPubKey; + var s = new Script(new Buffer(shex,'hex')); + tx.verifyInput(0,s, vopts, function(err, results){ + should.not.exist(results); + should.exist(err); + done(); + }); }); @@ -953,9 +997,9 @@ describe('TransactionBuilder', function() { it('#merge p2sh/steps', function(done) { var b = getP2shBuilder(1); - var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); + var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(0,1); var k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); - var k1 = testdata.dataUnspentSign.keyStringsP2sh.slice(2,3); + var k3 = testdata.dataUnspentSign.keyStringsP2sh.slice(2,3); b.isFullySigned().should.equal(false); b.signaturesAdded.should.equal(0); b.sign(k1); From 9951b55a4fb2bee2e8e77503f5bf28e349c25236 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 22 Apr 2014 21:08:48 -0300 Subject: [PATCH 3/5] signature sorting in .sign and .merge in transaction builder --- lib/Script.js | 55 ++++++- lib/Transaction.js | 14 +- lib/TransactionBuilder.js | 268 ++++++++++++++++++-------------- test/test.Script.js | 4 +- test/test.TransactionBuilder.js | 82 ++++------ 5 files changed, 246 insertions(+), 177 deletions(-) diff --git a/lib/Script.js b/lib/Script.js index fcba36b..7c62f66 100644 --- a/lib/Script.js +++ b/lib/Script.js @@ -119,19 +119,64 @@ Script.prototype.isMultiSig = function() { this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); }; + + + +// FOR TESTING +var _dumpChunks = function (scriptSig, label) { + console.log('## DUMP: ' + label + ' ##'); + for(var i=0; i2) || (l1<2 && l0>2 ))) throw new Error('TX sig types mismatch in merge'); - if (!l0 && !l1) return s0buf; - if ( l0 && !l1) return s0buf; - if (!l0 && l1) return s1buf; + if ((!l0 && !l1) || ( l0 && !l1) || (!l0 && l1)) + return s1buf; - // Look for differences. - for (var i=0; i Date: Tue, 22 Apr 2014 21:10:39 -0300 Subject: [PATCH 4/5] rm spaces --- lib/TransactionBuilder.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/TransactionBuilder.js b/lib/TransactionBuilder.js index ab5ad8d..c4172d1 100644 --- a/lib/TransactionBuilder.js +++ b/lib/TransactionBuilder.js @@ -950,7 +950,6 @@ TransactionBuilder.prototype._mergeTx = function(tx, ignoreConflictingSignatures } }; - TransactionBuilder.prototype.merge = function(b) { this._checkMergeability(b); @@ -964,8 +963,4 @@ TransactionBuilder.prototype.merge = function(b) { } }; - - - module.exports = require('soop')(TransactionBuilder); - From a83309eededc9c5ca4dc2bebf562404b35358219 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 22 Apr 2014 23:11:07 -0300 Subject: [PATCH 5/5] rm log --- lib/Script.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/Script.js b/lib/Script.js index 7c62f66..1a4f858 100644 --- a/lib/Script.js +++ b/lib/Script.js @@ -119,18 +119,6 @@ Script.prototype.isMultiSig = function() { this.chunks[this.chunks.length - 1] == OP_CHECKMULTISIG); }; - - - -// FOR TESTING -var _dumpChunks = function (scriptSig, label) { - console.log('## DUMP: ' + label + ' ##'); - for(var i=0; i