diff --git a/lib/Script.js b/lib/Script.js index d76e3b8..1a4f858 100644 --- a/lib/Script.js +++ b/lib/Script.js @@ -119,34 +119,72 @@ 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++; - - 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 +Script.prototype.isP2shScriptSig = function() { + if( !isSmallIntOp(this.chunks[0]) || this.chunks[0] !==0 ) return false; + + var redeemScript = new Script(this.chunks[this.chunks.length-1]); + var type=redeemScript.classify(); + return type !== TX_UNKNOWN; }; -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); +Script.prototype.isMultiSigScriptSig = function() { + if( !isSmallIntOp(this.chunks[0]) || this.chunks[0] !==0 ) + return false; + return !this.isP2shScriptSig(); +}; + +Script.prototype.countSignatures = function() { + var ret = 0; + var l =this.chunks.length; + + // Multisig? + if (this.isMultiSigScriptSig()){ + ret = l - 1; + } + else if (this.isP2shScriptSig()) { + ret = l - 2; + } + // p2pubkey or p2pubkeyhash + else { + ret = buffertools.compare(this.getBuffer(), util.EMPTY_BUFFER)===0?0:1; + } + return ret; +}; + +Script.prototype.countMissingSignatures = function() { + if (this.isMultiSig()) { + log.debug("Can not count missing signatures on normal Multisig script"); + return null; + } + + var ret = 0; + var l =this.chunks.length; + // P2SH? + 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"); + } + 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() { + var missing = this.countMissingSignatures(); + if (missing === null) return null; + + return missing === 0; }; Script.prototype.prependOp0 = function() { @@ -517,25 +555,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/Transaction.js b/lib/Transaction.js index 7b03cdf..d79b627 100644 --- a/lib/Transaction.js +++ b/lib/Transaction.js @@ -593,18 +593,27 @@ Transaction.prototype.getSize = function () { return this.size; }; +Transaction.prototype.countInputSignatures = function(index) { + var ret = 0; + var script = new Script(this.ins[index].s); + return script.countSignatures(); +}; +// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig) Transaction.prototype.countInputMissingSignatures = function(index) { var ret = 0; var script = new Script(this.ins[index].s); return script.countMissingSignatures(); }; - +// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig) Transaction.prototype.isInputComplete = function(index) { - return this.countInputMissingSignatures(index)===0; + var m = this.countInputMissingSignatures(index); + if (m===null) return null; + return m === 0; }; +// Works on p2pubkey, p2pubkeyhash & p2sh (no normal multisig) Transaction.prototype.isComplete = function() { var ret = true; var l = this.ins.length; @@ -615,7 +624,6 @@ Transaction.prototype.isComplete = function() { break; } } - return ret; }; diff --git a/lib/TransactionBuilder.js b/lib/TransactionBuilder.js index ca27dcc..c4172d1 100644 --- a/lib/TransactionBuilder.js +++ b/lib/TransactionBuilder.js @@ -88,6 +88,8 @@ var buffertools = imports.buffertools || require('buffertools'); var networks = imports.networks || require('../networks'); var WalletKey = imports.WalletKey || require('./WalletKey'); var PrivateKey = imports.PrivateKey || require('./PrivateKey'); +var Key = imports.Key || require('./Key'); +var log = imports.log || require('../util/log'); var Transaction = imports.Transaction || require('./Transaction'); var FEE_PER_1000B_SAT = parseInt(0.0001 * util.COIN); @@ -481,7 +483,7 @@ TransactionBuilder.prototype._signPubKey = function(walletKeyMap, input, txSigHa var scriptSig = new Script(); scriptSig.chunks.push(sig); scriptSig.updateBuffer(); - return {isFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; + return {inputFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; }; TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txSigHash) { @@ -500,73 +502,89 @@ TransactionBuilder.prototype._signPubKeyHash = function(walletKeyMap, input, txS scriptSig.chunks.push(sig); scriptSig.chunks.push(wk.privKey.public); scriptSig.updateBuffer(); - return {isFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; + return {inputFullySigned: true, signaturesAdded: 1, script: scriptSig.getBuffer()}; }; // 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 +// +// { +// "address" : "2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6", +// "redeemScript" : "532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae" +// } +// var getP2shBuilder = function(setMap) { var network = 'testnet'; var opts = { @@ -564,7 +702,7 @@ describe('TransactionBuilder', function() { var info = TransactionBuilder.infoForP2sh(p2shOpts, network); var outs = outs || [{ - address: info.address, + address: 'mon1Hqs3jqKTtRSnRwJ3pRYMFos9WYfKb5', amount: 0.08 }]; var b = new TransactionBuilder(opts) @@ -584,16 +722,34 @@ 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); + + var _checkOK = function(b, done) { 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(); + }); + + }; + + [[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) { + var b = getP2shBuilder(1); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[3]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[1]]); + b.sign([testdata.dataUnspentSign.keyStringsP2sh[2]]); + _checkOK(b, done); + }); + }); it('should sign in steps a p2sh/multisign tx', function() { var b = getP2shBuilder(1); @@ -818,11 +974,12 @@ describe('TransactionBuilder', function() { amount: 16 }]) .sign(testdata.dataUnspentSign.keyStrings); - (function() {b2.merge(b);}).should.throw(); - b2.merge(b, true); + b2.isFullySigned().should.equal(true); + b2.merge(b); + b2.isFullySigned().should.equal(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 k2 = testdata.dataUnspentSign.keyStringsP2sh.slice(1,2); @@ -835,6 +992,9 @@ describe('TransactionBuilder', function() { var tx = b.build(); tx.isComplete().should.equal(false); + b = TransactionBuilder.fromObj(b.toObj()); + + // TODO TO OBJ! var b2 = getP2shBuilder(1); b2.sign(k2); b2.signaturesAdded.should.equal(1); @@ -843,6 +1003,7 @@ describe('TransactionBuilder', function() { tx = b2.build(); tx.isComplete().should.equal(false); + b2 = TransactionBuilder.fromObj(b2.toObj()); var b3 = getP2shBuilder(1); b3.sign(k3); b3.signaturesAdded.should.equal(1); @@ -851,9 +1012,20 @@ describe('TransactionBuilder', function() { tx = b3.build(); tx.isComplete().should.equal(true); + 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; + 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(); + }); + }); });