diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 3a71dd0e6..b86f18277 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -328,6 +328,8 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { } } } + + for(var i in inKeyMap) throw new Error('Pubkey not identified') diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 32aa0f38f..2c7b6d02b 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -25,7 +25,7 @@ function TxProposal(opts) { this.builder = opts.builder; this.createdTs = opts.createdTs; this.createdTs = opts.createdTs; - this._inputSignatures = []; + this._inputSigners = []; // CopayerIds this.creator = opts.creator; @@ -77,24 +77,23 @@ TxProposal.prototype.isPending = function(maxRejectCount) { return true; }; - + TxProposal.prototype._updateSignedBy = function() { - this._inputSignatures = []; + this._inputSigners = []; var tx = this.builder.build(); for (var i in tx.ins) { var scriptSig = new Script(tx.ins[i].s); var signatureCount = scriptSig.countSignatures(); + var info = TxProposal._infoFromRedeemScript(scriptSig); var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL); - var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); - if (signatureIndexes.length !== signatureCount) + var signersPubKey = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); + if (signersPubKey.length !== signatureCount) throw new Error('Invalid signature'); - this._inputSignatures[i] = signatureIndexes.map(function(i) { - var r = info.keys[i].toString('hex'); - return r; - }); + + this._inputSigners[i] = signersPubKey; }; }; @@ -163,8 +162,11 @@ TxProposal._formatKeys = function(keys) { var k = new Key(); k.public = keys[i]; - ret.push(k); - }; + ret.push({ + keyObj: k, + keyHex: keys[i].toString('hex'), + }); + } return ret; }; @@ -183,8 +185,8 @@ TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1)); for (var j in keys) { var k = keys[j]; - if (k.verifySignatureSync(txSigHash, sigRaw)) { - ret.push(parseInt(j)); + if (k.keyObj.verifySignatureSync(txSigHash, sigRaw)) { + ret.push(k.keyHex); break; } } @@ -241,9 +243,9 @@ TxProposal.prototype.setSent = function(sentTxid) { TxProposal.prototype._allSignatures = function() { var ret = {}; - for (var i in this._inputSignatures) - for (var j in this._inputSignatures[i]) - ret[this._inputSignatures[i][j]] = true; + for (var i in this._inputSigners) + for (var j in this._inputSigners[i]) + ret[this._inputSigners[i][j]] = true; return ret; }; @@ -251,10 +253,10 @@ TxProposal.prototype._allSignatures = function() { TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { var newCopayer = {}, - oldCopayers = {}, - newSignedBy = {}, - readOnlyPeers = {}, - isNew = 1; + oldCopayers = {}, + newSignedBy = {}, + readOnlyPeers = {}, + isNew = 1; for (var k in this.signedBy) { oldCopayers[k] = 1; @@ -274,9 +276,10 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { } - var iSig = this._inputSignatures[0]; + var iSig = this._inputSigners[0]; for (var i in iSig) { var copayerId = keyMap[iSig[i]]; + if (!copayerId) throw new Error('Found unknown signature') diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e3ca1d872..f2bb28c6b 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -173,24 +173,32 @@ txId: ntxid */ Wallet.prototype._getKeyMap = function(txp) { preconditions.checkArgument(txp); + var inSig0, keyMapAll = {}; - var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.inputChainPaths); + for (var i in txp._inputSigners) { + var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSigners[i], txp.inputChainPaths); - var inSig = JSON.stringify(txp._inputSignatures[0].sort()); + if (Object.keys(keyMap).length !== txp._inputSigners[i].length) + throw new Error('Signature does not match known copayers'); - if (JSON.stringify(Object.keys(keyMap).sort()) !== inSig) { - throw new Error('inputSignatures dont match know copayers pubkeys'); + for (var j in keyMap) { + keyMapAll[j] = keyMap[j]; + } + + // From here -> only to check that all inputs have the same sigs + var inSigArr = []; + Object.keys(keyMap).forEach(function(k) { + inSigArr.push(keyMap[k]); + }); + var inSig = JSON.stringify(inSigArr.sort()); + if (i === '0') { + inSig0 = inSig; + continue; + } + if (inSig !== inSig0) + throw new Error('found inputs with different signatures'); } - - var keyMapStr = JSON.stringify(keyMap); - // All inputs must be signed with the same copayers - for (var i in txp._inputSignatures) { - if (!i) continue; - var inSigX = JSON.stringify(txp._inputSignatures[i].sort()); - if (inSigX !== inSig) - throw new Error('found inputs with different signatures:'); - } - return keyMap; + return keyMapAll; }; @@ -540,8 +548,8 @@ Wallet.fromObj = function(o, storage, network, blockchain) { opts.privateKey = PrivateKey.fromObj(o.privateKey); else opts.privateKey = new PrivateKey({ - networkName: opts.networkName - }); + networkName: opts.networkName + }); if (o.publicKeyRing) opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); @@ -717,7 +725,7 @@ Wallet.prototype.purgeTxProposals = function(deleteAll) { this.store(); var n = this.txProposals.length(); - return m-n; + return m - n; }; Wallet.prototype.reject = function(ntxid) { diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index a8535dd25..9ad7fe2a8 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -189,7 +189,7 @@ describe('TxProposal', function() { var txp = dummyProposal; var tx = dummyProposal.builder.build(); var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature()); - ret.should.deep.equal([0, 3]); + ret.should.deep.equal(['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3']); }); it('#_infoFromRedeemScript', function() { var info = TxProposal._infoFromRedeemScript(validScriptSig); @@ -202,7 +202,7 @@ describe('TxProposal', function() { }); it('#_updateSignedBy', function() { var txp = dummyProposal; - txp._inputSignatures.should.deep.equal([ + txp._inputSigners.should.deep.equal([ ['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3'] ]); }); @@ -302,7 +302,7 @@ describe('TxProposal', function() { txp.signedBy = { 'hugo': 1 }; - txp._inputSignatures = [ + txp._inputSigners = [ ['pkX'] ]; (function() { @@ -319,7 +319,7 @@ describe('TxProposal', function() { txp.signedBy = { creator: 1 }; - txp._inputSignatures = [ + txp._inputSigners = [ ['pk0', 'pkX'] ]; (function() { @@ -333,7 +333,7 @@ describe('TxProposal', function() { it.skip("should be signed by sender", function() { var txp = dummyProposal; var ts = Date.now(); - txp._inputSignatures = [ + txp._inputSigners = [ ['pk1', 'pk0'] ]; txp.signedBy = { @@ -352,7 +352,7 @@ describe('TxProposal', function() { it("should set signedBy (trivial case)", function() { var txp = dummyProposal; var ts = Date.now(); - txp._inputSignatures = [ + txp._inputSigners = [ ['pk1', 'pk0'] ]; txp.signedBy = { @@ -370,7 +370,7 @@ describe('TxProposal', function() { it("should assign creator", function() { var txp = dummyProposal; var ts = Date.now(); - txp._inputSignatures = [ + txp._inputSigners = [ ['pk0'] ]; txp.signedBy = {}; @@ -392,7 +392,7 @@ describe('TxProposal', function() { txp.signedBy = {}; delete txp['creator']; delete txp['creatorTs']; - txp._inputSignatures = [ + txp._inputSigners = [ ['pk0', 'pk1'] ]; (function() { @@ -411,7 +411,7 @@ describe('TxProposal', function() { it("if signed, should not change ts", function() { var txp = dummyProposal; var ts = Date.now(); - txp._inputSignatures = [ + txp._inputSigners = [ ['pk0', 'pk1'] ]; txp.creator = 'creator'; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index b1320d6a0..e46c1c3c3 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -30,10 +30,10 @@ var config = { var getNewEpk = function() { return new PrivateKey({ - networkName: config.networkName, - }) - .deriveBIP45Branch() - .extendedPublicKeyString(); + networkName: config.networkName, + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); } var addCopayers = function(w) { @@ -48,7 +48,7 @@ describe('Wallet model', function() { (function() { new Wallet(config) }).should. - throw(); + throw(); }); it('should getNetworkName', function() { var w = cachedCreateW(); @@ -219,7 +219,7 @@ describe('Wallet model', function() { var t = w.txProposals; var txp = t.txps[ntxid]; - Object.keys(txp._inputSignatures).length.should.equal(1); + Object.keys(txp._inputSigners).length.should.equal(1); var tx = txp.builder.build(); should.exist(tx); chai.expect(txp.comment).to.be.null; @@ -273,7 +273,7 @@ describe('Wallet model', function() { var w = cachedCreateW2(); var l = w.getAddressesStr(); for (var i = 0; i < l.length; i++) - w.addressIsOwn(l[i]).should.equal(true); + w.addressIsOwn(l[i]).should.equal(true); w.addressIsOwn(l[0], { excludeMain: true @@ -322,14 +322,14 @@ describe('Wallet model', function() { o.opts.reconnectDelay = 100; var w2 = Wallet.fromObj(o, - new Storage(config.storage), - new Network(config.network), - new Blockchain(config.blockchain)); - should.exist(w2); - w2.publicKeyRing.requiredCopayers.should.equal(w.publicKeyRing.requiredCopayers); - should.exist(w2.publicKeyRing.getCopayerId); - should.exist(w2.txProposals.toObj); - should.exist(w2.privateKey.toObj); + new Storage(config.storage), + new Network(config.network), + new Blockchain(config.blockchain)); + should.exist(w2); + w2.publicKeyRing.requiredCopayers.should.equal(w.publicKeyRing.requiredCopayers); + should.exist(w2.publicKeyRing.getCopayerId); + should.exist(w2.txProposals.toObj); + should.exist(w2.privateKey.toObj); }); it('#getSecret decodeSecret', function() { @@ -346,15 +346,15 @@ describe('Wallet model', function() { (function() { Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoKM'); }).should.not. - throw(); + throw(); (function() { Wallet.decodeSecret('4fp61K187CsYmjoRQC5iAdC5eGmbCRsAAXfwEwetSQgHvZs27eWKaLaNHRoK'); }).should. - throw(); + throw(); (function() { Wallet.decodeSecret('12345'); }).should. - throw(); + throw(); }); @@ -509,9 +509,9 @@ describe('Wallet model', function() { } }; - var stub = sinon.stub(w.publicKeyRing,'copayersForPubkeys').returns( - {'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509':'pepe'} - ); + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({ + '027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe' + }); w._handleTxProposal('senderID', txp, true); Object.keys(w.txProposals.txps).length.should.equal(1); w.getTxProposals().length.should.equal(1); @@ -605,21 +605,21 @@ describe('Wallet model', function() { }); var roundErrorChecks = [{ - unspent: [1.0001], - balance: 100010000 - }, { - unspent: [1.0002, 1.0003, 1.0004], - balance: 300090000 - }, { - unspent: [0.000002, 1.000003, 2.000004], - balance: 300000900 - }, { - unspent: [0.0001, 0.0003], - balance: 40000 - }, { - unspent: [0.0001, 0.0003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0002], - balance: 110000 - }, + unspent: [1.0001], + balance: 100010000 + }, { + unspent: [1.0002, 1.0003, 1.0004], + balance: 300090000 + }, { + unspent: [0.000002, 1.000003, 2.000004], + balance: 300000900 + }, { + unspent: [0.0001, 0.0003], + balance: 40000 + }, { + unspent: [0.0001, 0.0003, 0.0001, 0.0001, 0.0001, 0.0001, 0.0001, 0.0002], + balance: 110000 + }, ]; var roundWallet = cachedCreateW2(); @@ -765,7 +765,7 @@ describe('Wallet model', function() { }); }); - describe('#createTxSync', function () { + describe('#createTxSync', function() { it('should fail if amount below min value', function() { var w = cachedCreateW2(); var utxo = createUTXO(w); @@ -872,9 +872,9 @@ describe('Wallet model', function() { }); sinon.assert.callCount(updateIndex, 4); - sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[0] ); - sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[1] ); - sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[2] ); + sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[0]); + sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[1]); + sinon.assert.calledWith(updateIndex, w.publicKeyRing.indexes[2]); w.updateIndex.restore(); done(); }); @@ -898,8 +898,8 @@ describe('Wallet model', function() { index.receiveIndex.should.equal(9); index.changeIndex.should.equal(9); indexDiscovery.callCount.should.equal(2); - sinon.assert.calledWith(indexDiscovery, 1, true, 2, 20 ); - sinon.assert.calledWith(indexDiscovery, 2, false, 2, 20 ); + sinon.assert.calledWith(indexDiscovery, 1, true, 2, 20); + sinon.assert.calledWith(indexDiscovery, 2, false, 2, 20); w.indexDiscovery.restore(); done(); }); @@ -957,7 +957,7 @@ describe('Wallet model', function() { (function() { w.setAddressBook(contacts[0].address, contacts[0].label); }).should. - throw(); + throw(); }); it('should show/hide everywhere', function() { @@ -1108,7 +1108,7 @@ describe('Wallet model', function() { }; }); var txp = { - _inputSignatures: [ + _inputSigners: [ ['123'] ], inputChainPaths: ['/m/1'], @@ -1121,19 +1121,34 @@ describe('Wallet model', function() { it('should throw if unmatched sigs', function() { var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { - return { - '123': 'juan' - }; + return {}; }); var txp = { - _inputSignatures: [ + _inputSigners: [ ['234'] ], inputChainPaths: ['/m/1'], }; (function() { w._getKeyMap(txp); - }).should.throw('dont match know copayers'); + }).should.throw('does not match known copayers'); + stub.restore(); + }); + + it('should throw if unmatched sigs (case 2)', function() { + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return {}; + }); + var txp = { + _inputSigners: [ + ['234', '321'], + ['234', '322'] + ], + inputChainPaths: ['/m/1'], + }; + (function() { + w._getKeyMap(txp); + }).should.throw('does not match known copayers'); stub.restore(); }); @@ -1145,7 +1160,7 @@ describe('Wallet model', function() { }; }); var txp = { - _inputSignatures: [ + _inputSigners: [ ['234', '123'] ], inputChainPaths: ['/m/1'], @@ -1157,17 +1172,20 @@ describe('Wallet model', function() { stub.restore(); }); - it('should throw is one inputs has missing sigs', function() { + it('should throw if one inputs has missing sigs', function() { + var call = 0; var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { - return { + return call++ ? { + '555': 'pepe', + } : { '123': 'juan', '234': 'pepe', }; }); var txp = { - _inputSignatures: [ + _inputSigners: [ ['234', '123'], - ['234'] + ['555'] ], inputChainPaths: ['/m/1'], }; @@ -1176,6 +1194,60 @@ describe('Wallet model', function() { }).should.throw('different sig'); stub.restore(); }); + + + it('should throw if one inputs has different sigs', function() { + var call = 0; + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return call++ ? { + '555': 'pepe', + '666': 'pedro', + } : { + '123': 'juan', + '234': 'pepe', + }; + }); + var txp = { + _inputSigners: [ + ['234', '123'], + ['555', '666'] + ], + inputChainPaths: ['/m/1'], + }; + (function() { + w._getKeyMap(txp); + }).should.throw('different sig'); + stub.restore(); + }); + + + it('should not throw if 2 inputs has different pubs, same copayers', function() { + var call = 0; + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return call++ ? { + '555': 'pepe', + '666': 'pedro', + } : { + '123': 'pedro', + '234': 'pepe', + }; + }); + var txp = { + _inputSigners: [ + ['234', '123'], + ['555', '666'] + ], + inputChainPaths: ['/m/1'], + }; + var gk = w._getKeyMap(txp); + gk.should.deep.equal({ + '123': 'pedro', + '234': 'pepe', + '555': 'pepe', + '666': 'pedro' + }); + stub.restore(); + }); });