From df357e4ea703242902730450dcc3a2da78f8be98 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 29 Jul 2014 00:05:36 -0300 Subject: [PATCH 01/31] add metadata checks --- js/models/core/TxProposals.js | 115 +++++++++++++++++++++++++--------- 1 file changed, 85 insertions(+), 30 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 77462cca8..d5fb5a503 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -8,8 +8,12 @@ var Transaction = bitcore.Transaction; var BuilderMockV0 = require('./BuilderMockV0');; var TransactionBuilder = bitcore.TransactionBuilder; var Script = bitcore.Script; +var Key = bitcore.Key; var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); +var COPAY_EPOCH = 1400000000000; + + function TxProposal(opts) { this.creator = opts.creator; @@ -73,6 +77,21 @@ TxProposal.prototype.isValid = function() { return false; } } + + // Check that has valid signatures + var tx = this.builder.build(); + + for (var i = 0; i < tx.ins.length; i++) { + + var txSigHash = this.tx.hashForSignature( + this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); + + + return false; + } + + + return true; }; @@ -80,9 +99,9 @@ TxProposal.getSentTs = function() { return this.sentTs; }; -TxProposal.prototype.merge = function(other, author) { +TxProposal.prototype.merge = function(incoming, author) { var ret = {}; - ret.events = this.mergeMetadata(other, author); + ret.events = this.mergeMetadata(incoming, author); ret.hasChanged = this.mergeBuilder(other); return ret; }; @@ -91,49 +110,86 @@ TxProposal.prototype.mergeBuilder = function(other) { var b0 = this.builder; var b1 = other.builder; - // TODO: improve this comparison var before = JSON.stringify(b0.toObj()); b0.merge(b1); var after = JSON.stringify(b0.toObj()); return after !== before; }; -TxProposal.prototype.mergeMetadata = function(v1, author) { + +TxProposal.prototype._verifyTimestamp = function(ts, limits) { + if (ts < limits.min || ts > limits.max) + throw new Error('Invalid metadata at transaction proposal: skiping'); + return true; +}; + +TxProposal.prototype._isSignedBy = function(incoming,author) { + var status = false;; + + var builder = incoming.builder.clone(); + var sHex = builder.vanilla.scriptSig[0]; + if (!sHex) return ret; + + var sBuf = new Buffer(sHex,'hex'); + var s = new Script(new Buffer(sHex,'hex')); + + var k = new Key(); + k.public = author; + + + console.log('[TxProposals.js.124]', builder.vanilla); //TODO + + for (var i = 1; i <= s.countSignatures(); i++) { + var chunk = s.chunks[i]; + + var txSigHash = builder.build().hashForSignature(builder.vanilla, i-1, Transaction.SIGHASH_ALL); + + var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1)); + if (k.verifySignatureSync(txSigHash, sigRaw)) { + ret = true; + break; + } + } + return ret; +}; + +TxProposal.prototype.mergeMetadata = function(incoming, author) { var events = []; - var v0 = this; var ntxid = this.getID(); + var limits = { + min: COPAY_EPOCH, + max: Date.now(), + }; + var status = this._isSignedBy(incoming, author); - Object.keys(v1.seenBy).forEach(function(k) { - if (!v0.seenBy[k]) { - // TODO: uncomment below and change protocol to make this work - //if (k != author) throw new Error('Non authoritative seenBy change by ' + author); - v0.seenBy[k] = v1.seenBy[k]; - events.push({ - type: 'seen', - cId: k, - txId: ntxid - }); - } - }); + // Only use author's metadata + if (!this.seenBy[author] && incoming.seenBy[k]) { + this._validateTimestamp(incoming.seenBy[k], limits); + this.seenBy[author] = incoming.seenBy[author]; + events.push({ + type: 'seen', + cId: k, + txId: ntxid + }); + }; - Object.keys(v1.signedBy).forEach(function(k) { - if (!v0.signedBy[k]) { - // TODO: uncomment below and change protocol to make this work - //if (k != author) throw new Error('Non authoritative signedBy change by ' + author); - v0.signedBy[k] = v1.signedBy[k]; + + if (this.seenBy[author]) { + limits.min = this.seenBy[author]; + + + if (!this.signedBy[author] && incoming.signedBy[author]) { + this._validateTimestamp(incoming.seenBy[k], this.seenBy[author], now); + this.signedBy[k] = incoming.signedBy[k]; events.push({ type: 'signed', cId: k, txId: ntxid }); } - }); - - Object.keys(v1.rejectedBy).forEach(function(k) { - if (!v0.rejectedBy[k]) { - // TODO: uncomment below and change protocol to make this work - //if (k != author) throw new Error('Non authoritative rejectedBy change by ' + author); + if (!this.rejectedBy[author] && incoming.signedBy[author]) { + this._validateTimestamp(incoming.rejectedBy[k], this.seenBy[author], now); v0.rejectedBy[k] = v1.rejectedBy[k]; events.push({ type: 'rejected', @@ -141,7 +197,7 @@ TxProposal.prototype.mergeMetadata = function(v1, author) { txId: ntxid }); } - }); + } if (!v0.sentTxid && v1.sentTxid) { v0.sentTs = v1.sentTs; @@ -151,7 +207,6 @@ TxProposal.prototype.mergeMetadata = function(v1, author) { txId: ntxid }); } - return events; }; From 723cb189ccd06922184b7e76512e3a8e6df2ee95 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 29 Jul 2014 14:39:02 -0300 Subject: [PATCH 02/31] ignore .sig files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7f0f7a730..74839fb3b 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ lib-cov *.pid *.gz *.swp +*.sig tags pids logs From c19eac6a4e70d2e304ecf78939aec26f0cbfc81e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 09:45:56 -0300 Subject: [PATCH 03/31] WIP --- js/models/core/TxProposals.js | 116 ++++++---------------------------- 1 file changed, 21 insertions(+), 95 deletions(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index d5fb5a503..de40ca324 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -87,7 +87,7 @@ TxProposal.prototype.isValid = function() { this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); - return false; + return false; } @@ -101,7 +101,27 @@ TxProposal.getSentTs = function() { TxProposal.prototype.merge = function(incoming, author) { var ret = {}; + + /* TODO */ + + /* + events.push({ + type: 'seen', + cId: k, + txId: ntxid + }); + events.push({ + type: 'signed', + cId: k, + txId: ntxid + }); + events.push({ + type: 'rejected', + cId: k, + txId: ntxid + }); ret.events = this.mergeMetadata(incoming, author); + */ ret.hasChanged = this.mergeBuilder(other); return ret; }; @@ -117,100 +137,6 @@ TxProposal.prototype.mergeBuilder = function(other) { }; -TxProposal.prototype._verifyTimestamp = function(ts, limits) { - if (ts < limits.min || ts > limits.max) - throw new Error('Invalid metadata at transaction proposal: skiping'); - return true; -}; - -TxProposal.prototype._isSignedBy = function(incoming,author) { - var status = false;; - - var builder = incoming.builder.clone(); - var sHex = builder.vanilla.scriptSig[0]; - if (!sHex) return ret; - - var sBuf = new Buffer(sHex,'hex'); - var s = new Script(new Buffer(sHex,'hex')); - - var k = new Key(); - k.public = author; - - - console.log('[TxProposals.js.124]', builder.vanilla); //TODO - - for (var i = 1; i <= s.countSignatures(); i++) { - var chunk = s.chunks[i]; - - var txSigHash = builder.build().hashForSignature(builder.vanilla, i-1, Transaction.SIGHASH_ALL); - - var sigRaw = new Buffer(chunk.slice(0, chunk.length - 1)); - if (k.verifySignatureSync(txSigHash, sigRaw)) { - ret = true; - break; - } - } - return ret; -}; - -TxProposal.prototype.mergeMetadata = function(incoming, author) { - var events = []; - - var ntxid = this.getID(); - var limits = { - min: COPAY_EPOCH, - max: Date.now(), - }; - var status = this._isSignedBy(incoming, author); - - // Only use author's metadata - if (!this.seenBy[author] && incoming.seenBy[k]) { - this._validateTimestamp(incoming.seenBy[k], limits); - this.seenBy[author] = incoming.seenBy[author]; - events.push({ - type: 'seen', - cId: k, - txId: ntxid - }); - }; - - - if (this.seenBy[author]) { - limits.min = this.seenBy[author]; - - - if (!this.signedBy[author] && incoming.signedBy[author]) { - this._validateTimestamp(incoming.seenBy[k], this.seenBy[author], now); - this.signedBy[k] = incoming.signedBy[k]; - events.push({ - type: 'signed', - cId: k, - txId: ntxid - }); - } - if (!this.rejectedBy[author] && incoming.signedBy[author]) { - this._validateTimestamp(incoming.rejectedBy[k], this.seenBy[author], now); - v0.rejectedBy[k] = v1.rejectedBy[k]; - events.push({ - type: 'rejected', - cId: k, - txId: ntxid - }); - } - } - - if (!v0.sentTxid && v1.sentTxid) { - v0.sentTs = v1.sentTs; - v0.sentTxid = v1.sentTxid; - events.push({ - type: 'broadcast', - txId: ntxid - }); - } - return events; - -}; - //This should be on bitcore / Transaction TxProposal.prototype.countSignatures = function() { var tx = this.builder.build(); From 4e6d572de05150c38d8773cb2fb6bc1ce865c923 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 11:47:43 -0300 Subject: [PATCH 04/31] add cache to pubkeyring test. Speedup 2x --- js/models/core/PublicKeyRing.js | 85 +++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 36 deletions(-) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index ad7fe6f15..2a25010a1 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -23,14 +23,13 @@ function PublicKeyRing(opts) { this.copayersHK = opts.copayersHK || []; - this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) - : HDParams.init(this.totalCopayers); + this.indexes = opts.indexes ? HDParams.fromList(opts.indexes) : HDParams.init(this.totalCopayers); - this.publicKeysCache = opts.publicKeysCache || {}; - this.nicknameFor = opts.nicknameFor || {}; - this.copayerIds = []; - this.copayersBackup = opts.copayersBackup || []; - this.addressToPath = {}; + this.publicKeysCache = opts.publicKeysCache || {}; + this.nicknameFor = opts.nicknameFor || {}; + this.copayerIds = []; + this.copayersBackup = opts.copayersBackup || []; + this.addressToPath = {}; } PublicKeyRing.fromObj = function(data) { @@ -192,7 +191,9 @@ PublicKeyRing.prototype.getAddress = function(index, isChange, id) { // Overloaded to receive a PubkeyString or a consigner index PublicKeyRing.prototype.getHDParams = function(id) { var copayerIndex = this.getCosigner(id); - var index = this.indexes.filter(function(i) { return i.copayerIndex == copayerIndex }); + var index = this.indexes.filter(function(i) { + return i.copayerIndex == copayerIndex + }); if (index.length != 1) throw new Error('no index for copayerIndex'); return index[0]; @@ -231,9 +232,11 @@ PublicKeyRing.prototype.getCosigner = function(pubKey) { if (typeof pubKey == 'undefined') return HDPath.SHARED_INDEX; if (typeof pubKey == 'number') return pubKey; - var sorted = this.copayersHK.map(function(h, i){ + var sorted = this.copayersHK.map(function(h, i) { return h.eckey.public.toString('hex'); - }).sort(function(h1, h2){ return h1.localeCompare(h2); }); + }).sort(function(h1, h2) { + return h1.localeCompare(h2); + }); var index = sorted.indexOf(pubKey); if (index == -1) throw new Error('no public key in ring'); @@ -255,41 +258,51 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts, pubkey) { PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayerIndex) { opts = opts || {}; - var isOwned = index.copayerIndex == HDPath.SHARED_INDEX - || index.copayerIndex == copayerIndex; + var isOwned = index.copayerIndex == HDPath.SHARED_INDEX || index.copayerIndex == copayerIndex; - var ret = []; - if (!opts.excludeChange) { - for (var i = 0; i < index.changeIndex; i++) { - var a = this.getAddress(i, true, index.copayerIndex); - ret.unshift({ - address: a, - addressStr: a.toString(), - isChange: true, - owned: isOwned - }); - } + var ret = []; + if (!opts.excludeChange) { + for (var i = 0; i < index.changeIndex; i++) { + var a = this.getAddress(i, true, index.copayerIndex); + ret.unshift({ + address: a, + addressStr: a.toString(), + isChange: true, + owned: isOwned + }); } + } - if (!opts.excludeMain) { - for (var i = 0; i < index.receiveIndex; i++) { - var a = this.getAddress(i, false, index.copayerIndex); - ret.unshift({ - address: a, - addressStr: a.toString(), - isChange: false, - owned: isOwned - }); - } + if (!opts.excludeMain) { + for (var i = 0; i < index.receiveIndex; i++) { + var a = this.getAddress(i, false, index.copayerIndex); + ret.unshift({ + address: a, + addressStr: a.toString(), + isChange: false, + owned: isOwned + }); } + } - return ret; + return ret; }; +PublicKeyRing.prototype.getForPaths = function(paths) { + return paths.map(this.getForPath.bind(this)); +}; + +PublicKeyRing.prototype.getForPath = function(path) { + var p = HDPath.indexesForPath(path); + var pubKeys = this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex); + return pubKeys; +}; + + // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { - var p = HDPath.indicesForPath(path); - var script = this.getRedeemScript(p.index, p.isChange, p.copayerIndex); + var p = HDPath.indexesForPath(path); + var script = this.getRedeemScript(p.addressIndex, p.isChange, p.copayerIndex); map[Address.fromScript(script, this.network.name).toString()] = script.getBuffer().toString('hex'); }; From a10e6f4908527c7b740c244a226d93b35694127e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 11:54:11 -0300 Subject: [PATCH 05/31] add getForPath with tests --- js/models/core/PublicKeyRing.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 2a25010a1..2d626cf0e 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -288,16 +288,15 @@ PublicKeyRing.prototype.getAddressesInfoForIndex = function(index, opts, copayer return ret; }; -PublicKeyRing.prototype.getForPaths = function(paths) { - return paths.map(this.getForPath.bind(this)); -}; - PublicKeyRing.prototype.getForPath = function(path) { var p = HDPath.indexesForPath(path); var pubKeys = this.getPubKeys(p.addressIndex, p.isChange, p.copayerIndex); return pubKeys; }; +PublicKeyRing.prototype.getForPaths = function(paths) { + return paths.map(this.getForPath.bind(this)); +}; // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { From 96a5f0aeb8d74ae211b404e80a5d78780493df40 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 11:59:07 -0300 Subject: [PATCH 06/31] add forPath and tests --- js/models/core/PublicKeyRing.js | 9 +++++++++ test/test.PublicKeyRing.js | 23 +++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 2d626cf0e..823315ac9 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -298,6 +298,15 @@ PublicKeyRing.prototype.getForPaths = function(paths) { return paths.map(this.getForPath.bind(this)); }; + +PublicKeyRing.prototype.forPaths = function(paths) { + return { + pubKeys: paths.map(this.getForPath.bind(this)), + copayerIds: this.copayerIds, + } +}; + + // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { var p = HDPath.indexesForPath(path); diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index 41d874676..fcf179691 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -489,4 +489,27 @@ describe('PublicKeyRing model', function() { }); }); + it('#getForPath should return 5 pubkeys', function() { + var w = getCachedW().w; + var pubkeys = w.getForPath('m/45\'/2147483647/1/0'); + pubkeys.length.should.equal(5); + }); + + it('#getForPaths should return 2 arrays of 5 pubkey ', function() { + var w = getCachedW().w; + var pubkeys = w.getForPaths([ 'm/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1'] ); + pubkeys.length.should.equal(2); + pubkeys[0].length.should.equal(5); + pubkeys[1].length.should.equal(5); + }); + + it('#forPaths should return copayers and pubkeys ', function() { + var w = getCachedW().w; + var ret = w.forPaths([ 'm/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1'] ); + ret.copayerIds.length.should.equal(5); + ret.pubKeys.length.should.equal(2); + ret.pubKeys[0].length.should.equal(5); + ret.pubKeys[1].length.should.equal(5); + }); + }); From 6e5f06693d9d0f9f2c5ecf98f41c3c8649e4902d Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 21:19:39 -0300 Subject: [PATCH 07/31] separate TxProposal to a different class. new tests --- js/models/core/TxProposal.js | 226 ++++++ js/models/core/TxProposals.js | 297 ------- test/mocks/FakeBuilder.js | 37 + test/test.TxProposal.js | 170 ++++ ....TxProposals.js => test.TxProposalsSet.js} | 766 +++++++++--------- 5 files changed, 821 insertions(+), 675 deletions(-) create mode 100644 js/models/core/TxProposal.js delete mode 100644 js/models/core/TxProposals.js create mode 100644 test/mocks/FakeBuilder.js create mode 100644 test/test.TxProposal.js rename test/{test.TxProposals.js => test.TxProposalsSet.js} (51%) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js new file mode 100644 index 000000000..68d786311 --- /dev/null +++ b/js/models/core/TxProposal.js @@ -0,0 +1,226 @@ +'use strict'; + +var bitcore = require('bitcore'); +var util = bitcore.util; +var Transaction = bitcore.Transaction; +var BuilderMockV0 = require('./BuilderMockV0');; +var TransactionBuilder = bitcore.TransactionBuilder; +var Script = bitcore.Script; +var Key = bitcore.Key; +var buffertools = bitcore.buffertools; +var preconditions = require('preconditions').instance(); + + +function TxProposal(opts) { + preconditions.checkArgument(opts); + preconditions.checkArgument(opts.inputChainPaths); + preconditions.checkArgument(opts.creator); + preconditions.checkArgument(opts.createdTs); + preconditions.checkArgument(opts.builder); + + + this.creator = opts.creator; + this.createdTs = opts.createdTs; + this.builder = opts.builder; + this.inputChainPaths = opts.inputChainPaths; + + this._inputSignedBy = []; + this.seenBy = opts.seenBy || {}; + this.signedBy = opts.signedBy || {}; + this.rejectedBy = opts.rejectedBy || {}; + this.sentTs = opts.sentTs || null; + this.sentTxid = opts.sentTxid || null; + this.comment = opts.comment || null; + this.readonly = opts.readonly || null; + this._updateSignedBy(); +} + +TxProposal.prototype.getId = function() { + return this.builder.build().getNormalizedHash().toString('hex'); +}; + +TxProposal.prototype.toObj = function() { + var o = JSON.parse(JSON.stringify(this)); + delete o['builder']; + o.builderObj = this.builder.toObj(); + return o; +}; + + +TxProposal.prototype.setSent = function(sentTxid) { + this.sentTxid = sentTxid; + this.sentTs = Date.now(); +}; + +TxProposal.fromObj = function(o, forceOpts) { + preconditions.checkArgument(o.builderObj); + delete o['builder']; + + try { + // force opts is requested. + for (var k in forceOpts) { + o.builderObj.opts[k] = forceOpts[k]; + } + o.builder = TransactionBuilder.fromObj(o.builderObj); + } catch (e) { + if (!o.version) { + o.builder = new BuilderMockV0(o.builderObj); + o.readonly = 1; + }; + } + var t = new TxProposal(o); + t._throwIfInvalid(); + t._updateSignedBy(); + return t; +}; + + + +TxProposal.prototype._formatKeys = function(allowedPubKeys) { + var keys = []; + for (var i in allowedPubKeys) { + if (!Buffer.isBuffer(allowedPubKeys[i])) + throw new Error('allowedPubKeys must be buffers'); + + var k = new Key(); + k.public = allowedPubKeys[i]; + keys.push[k]; + }; +}; + +TxProposal.prototype._verifySignatures = function(inKeys, scriptSig, txSigHash) { + preconditions.checkArgument(Buffer.isBuffer(txSigHash)); + + if (scriptSig.chunks[0] !== 0) + throw new Error('Invalid scriptSig'); + + var keys = this._formatKeys(inKeys); + var ret = []; + for (var i = 1; typeof ret === 'undefined' && i <= scriptSig.countSignatures(); i++) { + var chunk = scriptSig.chunks[i]; + 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(j); + } + } + } + return ret; +}; + +TxProposal.prototype._keysFromRedeemScript = function(s) { + var redeemScript = new Script(s.chunks[s.chunks.length-1]); + if (!redeemScript) + throw new Error('Bad scriptSig'); + var pubkeys = redeemScript.capture(); + if (!pubkeys || !pubkeys.length) + throw new Error('Bad scriptSig'); + + return pubkeys; +}; + +TxProposal.prototype._updateSignedBy = function() { + this._inputSignedBy = []; + + var tx = this.builder.build(); + for (var i in tx.ins) { + var scriptSig = new Script(tx.ins[i].s); + + var keys = this._keysFromRedeemScript(scriptSig); + var txSigHash = tx.hashForSignature(this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); + var copayerIndex = this._verifySignatures(keys, scriptSig, txSigHash); + if (typeof copayerIndex === 'undefined') + throw new Error('Invalid signature'); + this._inputSignedBy[i] = this._inputSignedBy[i] || {}; + this._inputSignedBy[i][copayerIndex] = true; + }; +}; + +TxProposal.prototype.isValid = function() { + + if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { + return false; + } + + var tx = this.builder.build(); + if (!tx.ins.length) + return false; + + for (var i = 0; i < tx.ins.length; i++) { + var hashType = tx.getHashType(i); + if (hashType && hashType !== Transaction.SIGHASH_ALL) { + return false; + } + } + + console.log('[TxProposal.js.145]'); //TODO + + //Should be signed + var scriptSigs = this.builder.vanilla.scriptSigs; + if (!scriptSigs) + return false; + + + console.log('[TxProposal.js.153]'); //TODO + return true; +}; + + +TxProposal.prototype._throwIfInvalid = function(allowedPubKeys) { + if (!this.isValid(allowedPubKeys)) + throw new Error('Invalid tx proposal'); +}; + + +TxProposal.prototype.merge = function(incoming, allowedPubKeys) { + var ret = {}; + ret.events = []; + incoming._throwIfInvalid(allowedPubKeys); + + /* TODO */ + + /* + events.push({ +type: 'seen', +cId: k, +txId: ntxid +}); +events.push({ +type: 'signed', +cId: k, +txId: ntxid +}); +events.push({ +type: 'rejected', +cId: k, +txId: ntxid +}); +ret.events = this.mergeMetadata(incoming); +*/ + ret.hasChanged = this.mergeBuilder(incoming); + return ret; +}; + +TxProposal.prototype.mergeBuilder = function(incoming) { + var b0 = this.builder; + var b1 = incoming.builder; + + var before = JSON.stringify(b0.toObj()); + b0.merge(b1); + var after = JSON.stringify(b0.toObj()); + return after !== before; +}; + + +//This should be on bitcore / Transaction +TxProposal.prototype.countSignatures = function() { + var tx = this.builder.build(); + + var ret = 0; + for (var i in tx.ins) { + ret += tx.countInputSignatures(i); + } + return ret; +}; +module.exports = TxProposal; diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js deleted file mode 100644 index de40ca324..000000000 --- a/js/models/core/TxProposals.js +++ /dev/null @@ -1,297 +0,0 @@ -'use strict'; - - -var imports = require('soop').imports(); -var bitcore = require('bitcore'); -var util = bitcore.util; -var Transaction = bitcore.Transaction; -var BuilderMockV0 = require('./BuilderMockV0');; -var TransactionBuilder = bitcore.TransactionBuilder; -var Script = bitcore.Script; -var Key = bitcore.Key; -var buffertools = bitcore.buffertools; -var preconditions = require('preconditions').instance(); -var COPAY_EPOCH = 1400000000000; - - - -function TxProposal(opts) { - this.creator = opts.creator; - this.createdTs = opts.createdTs; - this.seenBy = opts.seenBy || {}; - this.signedBy = opts.signedBy || {}; - this.rejectedBy = opts.rejectedBy || {}; - this.builder = opts.builder; - this.sentTs = opts.sentTs || null; - this.sentTxid = opts.sentTxid || null; - this.inputChainPaths = opts.inputChainPaths || []; - this.comment = opts.comment || null; -} - -TxProposal.prototype.getID = function() { - return this.builder.build().getNormalizedHash().toString('hex'); -}; - -TxProposal.prototype.toObj = function() { - var o = JSON.parse(JSON.stringify(this)); - delete o['builder']; - o.builderObj = this.builder.toObj(); - return o; -}; - - -TxProposal.prototype.setSent = function(sentTxid) { - this.sentTxid = sentTxid; - this.sentTs = Date.now(); -}; - -TxProposal.fromObj = function(o, forceOpts) { - var t = new TxProposal(o); - - try { - // force opts is requested. - for (var k in forceOpts) { - o.builderObj.opts[k] = forceOpts[k]; - } - t.builder = TransactionBuilder.fromObj(o.builderObj); - - } catch (e) { - if (!o.version) { - t.builder = new BuilderMockV0(o.builderObj); - t.readonly = 1; - }; - } - - return t; -}; - - -TxProposal.prototype.isValid = function() { - if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { - return false; - } - var tx = this.builder.build(); - for (var i = 0; i < tx.ins.length; i++) { - var hashType = tx.getHashType(i); - if (hashType && hashType !== Transaction.SIGHASH_ALL) { - return false; - } - } - - // Check that has valid signatures - var tx = this.builder.build(); - - for (var i = 0; i < tx.ins.length; i++) { - - var txSigHash = this.tx.hashForSignature( - this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); - - - return false; - } - - - - return true; -}; - -TxProposal.getSentTs = function() { - return this.sentTs; -}; - -TxProposal.prototype.merge = function(incoming, author) { - var ret = {}; - - /* TODO */ - - /* - events.push({ - type: 'seen', - cId: k, - txId: ntxid - }); - events.push({ - type: 'signed', - cId: k, - txId: ntxid - }); - events.push({ - type: 'rejected', - cId: k, - txId: ntxid - }); - ret.events = this.mergeMetadata(incoming, author); - */ - ret.hasChanged = this.mergeBuilder(other); - return ret; -}; - -TxProposal.prototype.mergeBuilder = function(other) { - var b0 = this.builder; - var b1 = other.builder; - - var before = JSON.stringify(b0.toObj()); - b0.merge(b1); - var after = JSON.stringify(b0.toObj()); - return after !== before; -}; - - -//This should be on bitcore / Transaction -TxProposal.prototype.countSignatures = function() { - var tx = this.builder.build(); - - var ret = 0; - for (var i in tx.ins) { - ret += tx.countInputSignatures(i); - } - return ret; -}; - -module.exports = require('soop')(TxProposal); - - -function TxProposals(opts) { - opts = opts || {}; - this.walletId = opts.walletId; - this.network = opts.networkName === 'livenet' ? - bitcore.networks.livenet : bitcore.networks.testnet; - this.txps = {}; -} - -TxProposals.fromObj = function(o, forceOpts) { - var ret = new TxProposals({ - networkName: o.networkName, - walletId: o.walletId, - }); - - o.txps.forEach(function(o2) { - var t = TxProposal.fromObj(o2, forceOpts); - if (t.builder) { - var id = t.getID(); - ret.txps[id] = t; - } - }); - return ret; -}; - -TxProposals.prototype.getNtxids = function() { - return Object.keys(this.txps); -}; - -TxProposals.prototype.toObj = function(onlyThisNtxid) { - if (onlyThisNtxid) throw new Error(); - var ret = []; - for (var id in this.txps) { - - if (onlyThisNtxid && id != onlyThisNtxid) - continue; - - var t = this.txps[id]; - if (!t.sent) - ret.push(t.toObj()); - } - return { - txps: ret, - walletId: this.walletId, - networkName: this.network.name, - }; -}; - -TxProposals.prototype.merge = function(inTxp, author) { - var myTxps = this.txps; - - var ntxid = inTxp.getID(); - var ret = {}; - ret.events = []; - ret.events.hasChanged = false; - - if (myTxps[ntxid]) { - var v0 = myTxps[ntxid]; - var v1 = inTxp; - ret = v0.merge(v1, author); - } else { - this.txps[ntxid] = inTxp; - ret.hasChanged = true; - ret.events.push({ - type: 'new', - cid: inTxp.creator, - tx: ntxid - }); - } - return ret; -}; - -TxProposals.prototype.add = function(data) { - preconditions.checkArgument(data.inputChainPaths); - preconditions.checkArgument(data.signedBy); - preconditions.checkArgument(data.creator); - preconditions.checkArgument(data.createdTs); - preconditions.checkArgument(data.builder); - var txp = new TxProposal(data); - var ntxid = txp.getID(); - this.txps[ntxid] = txp; - return ntxid; -}; - -TxProposals.prototype.setSent = function(ntxid, txid) { - //sent TxProposals are local an not broadcasted. - this.txps[ntxid].setSent(txid); -}; - - -TxProposals.prototype.getTxProposal = function(ntxid, copayers) { - var txp = this.txps[ntxid]; - var i = JSON.parse(JSON.stringify(txp)); - i.builder = txp.builder; - i.ntxid = ntxid; - i.peerActions = {}; - - if (copayers) { - for (var j = 0; j < copayers.length; j++) { - var p = copayers[j]; - i.peerActions[p] = {}; - } - } - - for (var p in txp.seenBy) { - i.peerActions[p] = { - seen: txp.seenBy[p] - }; - } - for (var p in txp.signedBy) { - i.peerActions[p] = i.peerActions[p] || {}; - i.peerActions[p].sign = txp.signedBy[p]; - } - var r = 0; - for (var p in txp.rejectedBy) { - i.peerActions[p] = i.peerActions[p] || {}; - i.peerActions[p].rejected = txp.rejectedBy[p]; - r++; - } - i.rejectCount = r; - - var c = txp.creator; - i.peerActions[c] = i.peerActions[c] || {}; - i.peerActions[c].create = txp.createdTs; - return i; -}; - -//returns the unspent txid-vout used in PENDING Txs -TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { - var ret = {}; - for (var i in this.txps) { - var u = this.txps[i].builder.getSelectedUnspent(); - var p = this.getTxProposal(i); - if (p.rejectCount > maxRejectCount || p.sentTxid) - continue; - - for (var j in u) { - ret[u[j].txid + ',' + u[j].vout] = 1; - } - } - return ret; -}; - -TxProposals.TxProposal = TxProposal; -module.exports = require('soop')(TxProposals); diff --git a/test/mocks/FakeBuilder.js b/test/mocks/FakeBuilder.js new file mode 100644 index 000000000..40fcdf5e7 --- /dev/null +++ b/test/mocks/FakeBuilder.js @@ -0,0 +1,37 @@ +'use scrict'; +var bitcore = bitcore || require('bitcore'); +var Script = bitcore.Script; + + +function Tx() { + this.ins = [{s: new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae','hex')}]; +}; + +Tx.prototype.getNormalizedHash = function() { + return '123456'; +}; +Tx.prototype.hashForSignature = function() { + return new Buffer('72403de587240b85855da2ebd29b7dab5d170a9662d4bf7c4980358b14091696','hex'); +}; + + +function FakeBuilder() { + this.test = 1; + this.tx = new Tx(); + this.signhash = 1; + this.inputMap = [{ address: '2NDJbzwzsmRgD2o5HHXPhuq5g6tkKTjYkd6', + scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')), + scriptType: 4, + i: 0 }]; +} + +FakeBuilder.prototype.build = function() { + return this.tx; +}; + + +FakeBuilder.prototype.toObj = function() { + return this; +}; + +module.exports = FakeBuilder; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js new file mode 100644 index 000000000..fc7d1f8fe --- /dev/null +++ b/test/test.TxProposal.js @@ -0,0 +1,170 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var bitcore = bitcore || require('bitcore'); +var Transaction = bitcore.Transaction; +var buffertools = bitcore.buffertools; +var WalletKey = bitcore.WalletKey; +var Key = bitcore.Key; +var bignum = bitcore.Bignum; +var Script = bitcore.Script; +var TransactionBuilder = bitcore.TransactionBuilder; +var util = bitcore.util; +var networks = bitcore.networks; +var sinon = require('sinon'); +try { + var copay = require('copay'); //browser +} catch (e) { + var copay = require('../copay'); //node +} + +var FakeBuilder = require('./mocks/FakeBuilder'); +var TxProposal = copay.TxProposal; + +var dummyProposal = new TxProposal({ + creator: 1, + createdTs: 1, + builder: new FakeBuilder(), + inputChainPaths: ['m/1'], +}); + +var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5","022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"]; + +describe('TxProposal', function() { + describe('new', function() { + it('should fail to create an instance with wrong arguments', function() { + + (function() { + var txp = new TxProposal(); + }).should.throw('Illegal Argument'); + + (function() { + var txp = new TxProposal({ + creator: 1 + }); + }).should.throw('Illegal Argument'); + + }); + + + it('should create an instance', function() { + var txp = new TxProposal({ + creator: 1, + createdTs: 1, + builder: new FakeBuilder(), + inputChainPaths: 'm/1', + }); + should.exist(txp); + + txp.creator.should.equal(1); + should.exist(txp.builder); + txp.inputChainPaths.should.equal('m/1'); + }); + }); + describe('#getId', function() { + it('should return id',function() { + var b = new FakeBuilder(); + var spy = sinon.spy(b.tx, 'getNormalizedHash'); + var txp = new TxProposal({ + creator: 1, + createdTs: 1, + builder: b, + inputChainPaths: 'm/1', + }); + txp.getId().should.equal('123456');; + sinon.assert.callCount(spy,1); + }); + }); + describe('#toObj', function() { + it('should return an object and remove builder',function() { + var b = new FakeBuilder(); + var txp = new TxProposal({ + creator: 1, + createdTs: 1, + builder: b, + inputChainPaths: 'm/1', + }); + var o = txp.toObj(); + should.exist(o); + o.creator.should.equal(1); + should.not.exist(o.builder); + should.exist(o.builderObj); + }); + }); + describe('#fromObj', function() { + it.skip('should create from Object',function() { + var b = new FakeBuilder(); + var txp = TxProposal.fromObj({ + creator: 1, + createdTs: 1, + builderObj: b.toObj(), + inputChainPaths: ['m/1'] , + }); + should.exist(txp); + }); + + + it('should fail to create from wrong object',function() { + var b = new FakeBuilder(); + (function(){var txp = TxProposal.fromObj({ + creator: 1, + createdTs: 1, + builderObj: b.toObj(), + inputChainPaths: ['m/1'] , + });}).should.throw('Invalid'); + }); + + + }); + + describe('#setSent', function() { + it('should set txid and timestamp',function() { + var now = Date.now(); + var txp = dummyProposal; + txp.setSent('3a42'); + txp.sentTs.should.gte(now); + txp.sentTxid.should.equal('3a42'); + }); + }); + + + describe('Signature verification', function() { + var validScriptSig = '00483045022100a35a5cbe37e39caa62bf1c347eae9c72be827c190b31494b184943b3012757a8022008a1ff72a34a5bf2fc955aa5b6f8a4c32cb0fab7e54c212a5f6f645bb95b8ef10149304602210092347916c3c3e6f1692bf9447b973779c28ce9985baaa3940b483af573f464b4022100ab91062796ab8acb32a0fa90e00627db5be77d9722400b3ecfd9c5f34a8092b1014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'; + + var keyBuf = someKeys.map(function(hex){ + return new Buffer(hex,'hex'); + }); + it('#_formatKeys',function() { + var txp = dummyProposal; + (function(){txp._formatKeys(someKeys);}).should.throw('buffers'); + var res = txp._formatKeys(keyBuf); + }); + it('#_verifyScriptSig arg checks',function() { + var txp = dummyProposal; + ( function() { txp._verifySignatures( + keyBuf, + new bitcore.Script(new Buffer('112233','hex')), + new Buffer('1a','hex')); } ).should.throw('script'); + }); + it('#_verifyScriptSig, no signatures',function() { + var txp = dummyProposal; + var ret = txp._verifySignatures( + keyBuf, + new bitcore.Script(new Buffer(validScriptSig,'hex')), + new Buffer('1a','hex') + ); + ret.length.should.equal(0); + }); + it('#_verifyScriptSig, one signature',function() { + var txp = dummyProposal; + var ret = txp._verifySignatures( + [new Buffer('0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127','hex')], + new bitcore.Script(new Buffer(validScriptSig,'hex')), + new Buffer('1a','hex') + ); + ret.length.should.equal(0); + }); + + }); +}); diff --git a/test/test.TxProposals.js b/test/test.TxProposalsSet.js similarity index 51% rename from test/test.TxProposals.js rename to test/test.TxProposalsSet.js index 4ce94e9f6..4872bd569 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposalsSet.js @@ -26,6 +26,8 @@ var PublicKeyRing = is_browser ? copay.PublicKeyRing : Storage: fakeStorage }); +var isChange = false; +var addressIndex = 0; var config = { networkName: 'testnet', }; @@ -69,12 +71,88 @@ var vopts = { dontVerifyStrictEnc: true }; +var createTx = function(toAddress, amountSatStr, utxos, opts, priv, pkr) { + opts = opts || {}; + + var pub = priv.publicHex; + + if (!pkr.isComplete()) { + throw new Error('publicKeyRing is not complete'); + } + + if (!opts.remainderOut) { + opts.remainderOut = { + address: pkr.generateAddress(true, pub).toString() + }; + }; + + var b = new TransactionBuilder(opts) + .setUnspent(utxos) + .setOutputs([{ + address: toAddress, + amountSatStr: amountSatStr, + }]); + var selectedUtxos = b.getSelectedUnspent(); + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + + var selectedUtxos = b.getSelectedUnspent(); + + var inputChainPaths = selectedUtxos.map(function(utxo) { + return pkr.pathForAddress(utxo.address); + }); + + b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); + var signRet; + if (priv) { + var pkeys = priv.getForPaths(inputChainPaths); + b.sign(pkeys); + } + var me = {}; + if (priv) me[priv.getId()] = Date.now(); + var tx = b.build(); + + return { + inputChainPaths: inputChainPaths, + creator: priv.getId(), + createdTs: new Date(), + signedBy: priv && tx.countInputSignatures(0) ? {priv.getId(): true} : {}, + seenBy: priv ? me : {}, + builder: b, + }; +}; + + +var cachedTxProposals; +var getCachedTxProposals = function() { + if (!cachedTxProposals){ + var priv = new PrivateKey(config); + var pub = priv.publicHex; + var w = new TxProposals({ + networkName: config.networkName, + }); + var start = new Date().getTime(); + var pkr = createPKR([priv]); + var ts = Date.now(); + + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, {}, + priv, + pkr + )); + cachedTxProposals = { tObj:w.toObj(), priv: priv}; + } + cachedTxProposals.txp = TxProposals.fromObj(cachedTxProposals.tObj); + return cachedTxProposals; +}; describe('TxProposals model', function() { - var isChange = false; - var addressIndex = 0; - it('verify TXs', function(done) { var priv = new PrivateKey(config); @@ -136,59 +214,6 @@ describe('TxProposals model', function() { w.network.name.should.equal(config.networkName); }); - var createTx = function(toAddress, amountSatStr, utxos, opts, priv, pkr) { - opts = opts || {}; - - var pub = priv.publicHex; - - if (!pkr.isComplete()) { - throw new Error('publicKeyRing is not complete'); - } - - if (!opts.remainderOut) { - opts.remainderOut = { - address: pkr.generateAddress(true, pub).toString() - }; - }; - - var b = new TransactionBuilder(opts) - .setUnspent(utxos) - .setOutputs([{ - address: toAddress, - amountSatStr: amountSatStr, - }]); - var selectedUtxos = b.getSelectedUnspent(); - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - - var selectedUtxos = b.getSelectedUnspent(); - - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - - b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - var signRet; - if (priv) { - var pkeys = priv.getForPaths(inputChainPaths); - b.sign(pkeys); - } - var me = {}; - if (priv) me[priv.getId()] = Date.now(); - - var tx = b.build(); - - return { - inputChainPaths: inputChainPaths, - creator: priv.getId(), - createdTs: new Date(), - signedBy: priv && tx.countInputSignatures(0) ? me : {}, - seenBy: priv ? me : {}, - builder: b, - }; - }; - it('#getUsedUnspend', function() { var priv = new PrivateKey(config); @@ -215,362 +240,347 @@ describe('TxProposals model', function() { uuk[0].split(',')[0].should.equal(unspentTest[0].txid); }); - it('#merge with self', function() { - var priv = new PrivateKey(config); - var pub = priv.publicHex; - - var w = new TxProposals({ - networkName: config.networkName, - }); - var start = new Date().getTime(); - var pkr = createPKR([priv]); - var ts = Date.now(); - - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, {}, - priv, - pkr - )); - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - var x = priv.getId(); - (w.txps[ntxid].signedBy[priv.getId()] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - var info = w.merge(w.txps[ntxid], pkr.getCopayerId(0)); - info.events.length.should.equal(0); - - Object.keys(w.txps).length.should.equal(1); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + it.only('should create a correct uncomplete txp', function() { + var c = getCachedTxProposals(); + var x = c.priv.getId(); + var ntxid = Object.keys(c.txp.txps)[0]; + c.txp.txps[ntxid].signedBy[c.priv.getId()].should.equal(true); + (c.txp.txps[ntxid].seenBy[c.priv.id] - ts > 0).should.equal(true); }); + describe('#merge', function() { + + it('with self', function() { + var t = txps.fromObj(txpsObj); + var info = w.merge(w.txps[ntxid], pkr.getCopayerId(0)); + info.events.length.should.equal(0); + + Object.keys(w.txps).length.should.equal(1); + + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + }); - it('#merge, merge signatures case 1', function() { - var priv2 = new PrivateKey(config); - var priv = new PrivateKey(config); - var pub = priv.publicHex; - var ts = Date.now(); - var pkr = createPKR([priv]); - var opts = { - remainderOut: { - address: pkr.generateAddress(true, pub).toString() + it('#merge, merge signatures case 1', function() { + var priv2 = new PrivateKey(config); + var priv = new PrivateKey(config); + var pub = priv.publicHex; + + var ts = Date.now(); + var pkr = createPKR([priv]); + var opts = { + remainderOut: { + address: pkr.generateAddress(true, pub).toString() + } + }; + + + var w = new TxProposals({ + networkName: config.networkName, + }); + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv2, + pkr + )); + + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputSignatures(0).should.equal(0); + tx.countInputMissingSignatures(0).should.equal(1); + + Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); + Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); + + + var w2 = new TxProposals({ + networkName: config.networkName, + publicKeyRing: w.publicKeyRing, + }); + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w2.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv, + pkr + )); + + var ntxid = Object.keys(w.txps)[0]; + var tx = w2.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + + + (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true, 'asdsd'); + (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + + var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); + console.log('[test.TxProposals.js.323:info:]',info); //TODO + info.events.length.should.equal(2); + info.events[0].type.should.equal('seen'); + info.events[1].type.should.equal('signed'); + + Object.keys(w.txps).length.should.equal(1); + + var tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + + }); + + var _dumpChunks = function(scriptSig, label) { + console.log('## DUMP: ' + label + ' ##'); + for (var i = 0; i < scriptSig.chunks.length; i++) { + console.log('\tCHUNK ', i, scriptSig.chunks[i]); } }; - var w = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); + it('#merge, merge signatures case 2', function() { - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputSignatures(0).should.equal(0); - tx.countInputMissingSignatures(0).should.equal(1); - - Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); - Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); + var o1 = { + extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdSF1avR6mXyDj5Uv1XY2UyUHSDpAXQ5TvPN7prGeDppjy4562rBB9gMMAhRfFdJrNDpQ4t69kkqHNEEen3PX1zBJqSehJDH', + networkName: 'testnet', + privateKeyCache: {} + }; + var o2 = { + extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdVeB5RzuxS9JQcACueZYgUaM5eWzaEBkHjW5Pg6Mqez1APSqoUP1jUdbT8WVG7ZJYTXvUL7XtPzFYBXjmdKuwSor1dcNQ8j', + networkName: 'testnet', + privateKeyCache: {} + }; + var o3 = { + extendedPrivateKeyString: 'tprv8ZgxMBicQKsPeHWNrPVZtQVgcCtXBr5TACNbDQ56rwqNJce9MEc64US6DJKxpWsrebEomxxWZFDtkvkZGkzA43uLvdF4XHiWqoNaL6Dq2Gd', + networkName: 'testnet', + privateKeyCache: {} + }; - var w2 = new TxProposals({ - networkName: config.networkName, - publicKeyRing: w.publicKeyRing, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); + var priv = PrivateKey.fromObj(o1); + var priv2 = PrivateKey.fromObj(o2); + var priv3 = PrivateKey.fromObj(o3); + var pub = priv.publicHex; - var ntxid = Object.keys(w.txps)[0]; - var tx = w2.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); + var ts = Date.now(); + var pkr = createPKR([priv, priv2]); + var opts = { + remainderOut: { + address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr' + } + }; + var addressToSign = pkr.generateAddress(false, pub); + unspentTest[0].address = addressToSign.toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + var tx, txb; + + var w = new TxProposals({ + networkName: config.networkName, + }); + + w.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv3, + pkr + )); + + var ntxid = Object.keys(w.txps)[0]; + txb = w.txps[ntxid].builder; + tx = txb.build(); + + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(1); + + Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); + Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); + + var w2 = new TxProposals({ + networkName: config.networkName, + }); - (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true, 'asdsd'); - (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); - info.events.length.should.equal(2); - info.events[0].type.should.equal('seen'); - info.events[1].type.should.equal('signed'); + w2.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv, + pkr + )); + var ntxid = Object.keys(w2.txps)[0]; + txb = w2.txps[ntxid].builder; + tx = txb.build(); - Object.keys(w.txps).length.should.equal(1); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - }); + var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); + info.events.length.should.equal(2); + info.events[0].type.should.equal('seen'); + info.events[1].type.should.equal('signed'); - var _dumpChunks = function(scriptSig, label) { - console.log('## DUMP: ' + label + ' ##'); - for (var i = 0; i < scriptSig.chunks.length; i++) { - console.log('\tCHUNK ', i, scriptSig.chunks[i]); - } - }; + tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - it('#merge, merge signatures case 2', function() { + var w3 = new TxProposals({ + networkName: config.networkName, + publicKeyRing: pkr, + }); + w3.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv2, + pkr + )); + tx = w3.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); - var o1 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdSF1avR6mXyDj5Uv1XY2UyUHSDpAXQ5TvPN7prGeDppjy4562rBB9gMMAhRfFdJrNDpQ4t69kkqHNEEen3PX1zBJqSehJDH', - networkName: 'testnet', - privateKeyCache: {} - }; - var o2 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdVeB5RzuxS9JQcACueZYgUaM5eWzaEBkHjW5Pg6Mqez1APSqoUP1jUdbT8WVG7ZJYTXvUL7XtPzFYBXjmdKuwSor1dcNQ8j', - networkName: 'testnet', - privateKeyCache: {} - }; - var o3 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPeHWNrPVZtQVgcCtXBr5TACNbDQ56rwqNJce9MEc64US6DJKxpWsrebEomxxWZFDtkvkZGkzA43uLvdF4XHiWqoNaL6Dq2Gd', - networkName: 'testnet', - privateKeyCache: {} - }; + (w3.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w3.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(1)); - var priv = PrivateKey.fromObj(o1); - var priv2 = PrivateKey.fromObj(o2); - var priv3 = PrivateKey.fromObj(o3); - var pub = priv.publicHex; + Object.keys(w.txps).length.should.equal(1); - var ts = Date.now(); - var pkr = createPKR([priv, priv2]); - var opts = { - remainderOut: { - address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr' - } - }; - var addressToSign = pkr.generateAddress(false, pub); - unspentTest[0].address = addressToSign.toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - var tx, txb; + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - var w = new TxProposals({ - networkName: config.networkName, - }); - - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv3, - pkr - )); - - var ntxid = Object.keys(w.txps)[0]; - txb = w.txps[ntxid].builder; - tx = txb.build(); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - - Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); - Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); - - var w2 = new TxProposals({ - networkName: config.networkName, + tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(1); }); + it('#merge, merge signatures case 3', function() { - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - var ntxid = Object.keys(w2.txps)[0]; - txb = w2.txps[ntxid].builder; - tx = txb.build(); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); - info.events.length.should.equal(2); - info.events[0].type.should.equal('seen'); - info.events[1].type.should.equal('signed'); - - tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + var priv = new PrivateKey(config); + var priv2 = new PrivateKey(config); + var priv3 = new PrivateKey(config); + var pub = priv.publicHex; - var w3 = new TxProposals({ - networkName: config.networkName, - publicKeyRing: pkr, + var ts = Date.now(); + var pkr = createPKR([priv, priv2, priv3]); + var opts = { + remainderOut: { + address: pkr.generateAddress(true, pub).toString() + } + }; + + var w = new TxProposals({ + networkName: config.networkName, + }); + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv, + pkr + )); + var ntxid = Object.keys(w.txps)[0]; + var tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + + + var w2 = new TxProposals({ + networkName: config.networkName, + }); + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w2.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv2, + pkr + )); + var tx = w2.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + (w2.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w2.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + + var w3 = new TxProposals({ + networkName: config.networkName, + }); + unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); + unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); + w3.add(createTx( + '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', + '123456789', + unspentTest, + opts, + priv3, + pkr + )); + var tx = w3.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(2); + (w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); + (w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); + + var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(1)); + + Object.keys(w.txps).length.should.equal(1); + var tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(false); + tx.countInputMissingSignatures(0).should.equal(1); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + + + var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(2)); + + var tx = w.txps[ntxid].builder.build(); + tx.isComplete().should.equal(true); + tx.countInputMissingSignatures(0).should.equal(0); + Object.keys(w.txps).length.should.equal(1); + (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); + (w.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); }); - w3.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); - tx = w3.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w3.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w3.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(1)); - - Object.keys(w.txps).length.should.equal(1); - - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - }); - - - it('#merge, merge signatures case 3', function() { - - var priv = new PrivateKey(config); - var priv2 = new PrivateKey(config); - var priv3 = new PrivateKey(config); - var pub = priv.publicHex; - - - var ts = Date.now(); - var pkr = createPKR([priv, priv2, priv3]); - var opts = { - remainderOut: { - address: pkr.generateAddress(true, pub).toString() - } - }; - - var w = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - - var w2 = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); - var tx = w2.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w2.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w2.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - var w3 = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w3.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv3, - pkr - )); - var tx = w3.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); - (w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); - - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(1)); - - Object.keys(w.txps).length.should.equal(1); - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - - - var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(2)); - - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(true); - tx.countInputMissingSignatures(0).should.equal(0); - Object.keys(w.txps).length.should.equal(1); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); }); From 72e1dfc114fe68e18ea5dd6ee8e2d6ff8cd09195 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 21:20:08 -0300 Subject: [PATCH 08/31] WIP --- copay.js | 3 +- js/models/core/HDPath.js | 4 +- js/models/core/TxProposalsSet.js | 160 +++++++++++++++++++++++++++++++ js/models/core/Wallet.js | 103 ++++++++++---------- js/models/core/WalletFactory.js | 6 +- test/test.HDPath.js | 6 +- 6 files changed, 220 insertions(+), 62 deletions(-) create mode 100644 js/models/core/TxProposalsSet.js diff --git a/copay.js b/copay.js index 8e8b03089..3a816a42c 100644 --- a/copay.js +++ b/copay.js @@ -1,6 +1,7 @@ // core module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing'); -module.exports.TxProposals = require('./js/models/core/TxProposals'); +module.exports.TxProposal = require('./js/models/core/TxProposal'); +module.exports.TxProposalsSet = require('./js/models/core/TxProposalsSet'); module.exports.PrivateKey = require('./js/models/core/PrivateKey'); module.exports.Passphrase = require('./js/models/core/Passphrase'); module.exports.HDPath = require('./js/models/core/HDPath'); diff --git a/js/models/core/HDPath.js b/js/models/core/HDPath.js index 2e32ade7e..126b84a0a 100644 --- a/js/models/core/HDPath.js +++ b/js/models/core/HDPath.js @@ -33,12 +33,12 @@ HDPath.FullBranch = function(addressIndex, isChange, copayerIndex) { return BIP45_PUBLIC_PREFIX + '/' + sub; }; -HDPath.indicesForPath = function(path) { +HDPath.indexesForPath = function(path) { preconditions.shouldBeString(path); var s = path.split('/'); return { isChange: s[3] === '1', - index: parseInt(s[4]), + addressIndex: parseInt(s[4]), copayerIndex: parseInt(s[2]) }; }; diff --git a/js/models/core/TxProposalsSet.js b/js/models/core/TxProposalsSet.js new file mode 100644 index 000000000..22d444303 --- /dev/null +++ b/js/models/core/TxProposalsSet.js @@ -0,0 +1,160 @@ +'use strict'; + +var BuilderMockV0 = require('./BuilderMockV0');; +var bitcore = require('bitcore'); +var util = bitcore.util; +var Transaction = bitcore.Transaction; +var BuilderMockV0 = require('./BuilderMockV0');; +var Script = bitcore.Script; +var Key = bitcore.Key; +var buffertools = bitcore.buffertools; +var preconditions = require('preconditions').instance(); + + +function TxProposalsSet(opts) { + opts = opts || {}; + this.walletId = opts.walletId; + this.network = opts.networkName === 'livenet' ? + bitcore.networks.livenet : bitcore.networks.testnet; + this.txps = {}; +} + +TxProposalsSet.fromObj = function(o, forceOpts) { + var ret = new TxProposalsSet({ + networkName: o.networkName, + walletId: o.walletId, + }); + + o.txps.forEach(function(o2) { + var t = TxProposal.fromObj(o2, forceOpts); + if (t.builder) { + var id = t.getID(); + ret.txps[id] = t; + } + }); + return ret; +}; + +TxProposalsSet.prototype.getNtxids = function() { + return Object.keys(this.txps); +}; + +TxProposalsSet.prototype.toObj = function(onlyThisNtxid) { + if (onlyThisNtxid) throw new Error(); + var ret = []; + for (var id in this.txps) { + + if (onlyThisNtxid && id != onlyThisNtxid) + continue; + + var t = this.txps[id]; + if (!t.sent) + ret.push(t.toObj()); + } + return { + txps: ret, + walletId: this.walletId, + networkName: this.network.name, + }; +}; + + +TxProposalsSet.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) { + var inTxp = TxProposal.fromObj(txProposalObj, opts); + var mergeInfo = this.txProposals.merge(inTxp, allowedPubKeys); + mergeInfo.inTxp = inTxp; + return mergeInfo; +}; + + +TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { + var myTxps = this.txps; + + var ntxid = inTxp.getID(); + var ret = {}; + ret.events = []; + ret.events.hasChanged = false; + + if (myTxps[ntxid]) { + var v0 = myTxps[ntxid]; + var v1 = inTxp; + ret = v0.merge(v1, allowedPubKeys); + } else { + this.txps[ntxid] = inTxp; + ret.hasChanged = true; + ret.events.push({ + type: 'new', + cid: inTxp.creator, + tx: ntxid + }); + } + return ret; +}; + +// Add a LOCALLY CREATED (trusted) tx proposal +TxProposalsSet.prototype.add = function(data) { + var txp = new TxProposal(data); + var ntxid = txp.getID(); + this.txps[ntxid] = txp; + return ntxid; +}; + +TxProposalsSet.prototype.setSent = function(ntxid, txid) { + //sent TxProposalsSet are local an not broadcasted. + this.txps[ntxid].setSent(txid); +}; + + +TxProposalsSet.prototype.getTxProposal = function(ntxid, copayers) { + var txp = this.txps[ntxid]; + var i = JSON.parse(JSON.stringify(txp)); + i.builder = txp.builder; + i.ntxid = ntxid; + i.peerActions = {}; + + if (copayers) { + for (var j = 0; j < copayers.length; j++) { + var p = copayers[j]; + i.peerActions[p] = {}; + } + } + + for (var p in txp.seenBy) { + i.peerActions[p] = { + seen: txp.seenBy[p] + }; + } + for (var p in txp.signedBy) { + i.peerActions[p] = i.peerActions[p] || {}; + i.peerActions[p].sign = txp.signedBy[p]; + } + var r = 0; + for (var p in txp.rejectedBy) { + i.peerActions[p] = i.peerActions[p] || {}; + i.peerActions[p].rejected = txp.rejectedBy[p]; + r++; + } + i.rejectCount = r; + + var c = txp.creator; + i.peerActions[c] = i.peerActions[c] || {}; + i.peerActions[c].create = txp.createdTs; + return i; +}; + +//returns the unspent txid-vout used in PENDING Txs +TxProposalsSet.prototype.getUsedUnspent = function(maxRejectCount) { + var ret = {}; + for (var i in this.txps) { + var u = this.txps[i].builder.getSelectedUnspent(); + var p = this.getTxProposal(i); + if (p.rejectCount > maxRejectCount || p.sentTxid) + continue; + + for (var j in u) { + ret[u[j].txid + ',' + u[j].vout] = 1; + } + } + return ret; +}; + diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index c7ae6403d..856e5cb43 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -17,7 +17,7 @@ var Address = bitcore.Address; var HDParams = require('./HDParams'); var PublicKeyRing = require('./PublicKeyRing'); -var TxProposals = require('./TxProposals'); +var TxProposalsSet = require('./TxProposalsSet'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); @@ -36,7 +36,7 @@ function Wallet(opts) { }); if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName) throw new Error('Network forced to ' + copayConfig.networkName + - ' and tried to create a Wallet with network ' + this.getNetworkName()); + ' and tried to create a Wallet with network ' + this.getNetworkName()); this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet'); @@ -59,10 +59,10 @@ function Wallet(opts) { Wallet.builderOpts = { - lockTime: null, - signhash: bitcore.Transaction.SIGNHASH_ALL, - fee: null, - feeSat: null, + lockTime: null, + signhash: bitcore.Transaction.SIGNHASH_ALL, + fee: null, + feeSat: null, }; Wallet.parent = EventEmitter; @@ -129,28 +129,25 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { }; - Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL: ', data); - var inTxp = TxProposals.TxProposal.fromObj(data.txProposal, Wallet.builderOpts); + var mergeInfo; - - - var valid = inTxp.isValid(); - if (!valid) { + try { + mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts); + } catch (e) { var corruptEvent = { type: 'corrupt', - cId: inTxp.creator + cId: mergeInfo.inTxp.creator }; this.emit('txProposalEvent', corruptEvent); return; } - var mergeInfo = this.txProposals.merge(inTxp, senderId); - var added = this.addSeenToTxProposals(); + var added = this.addSeenToTxProposals(); if (added) { this.log('### BROADCASTING txProposals with my seenBy updated.'); - this.sendTxProposal(inTxp.getID()); + this.sendTxProposal(mergeInfo.inTxp.getID()); } this.emit('txProposalsUpdated'); @@ -193,24 +190,24 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { // This handler is repeaded on WalletFactory (#join). TODO case 'walletId': this.sendWalletReady(senderId); - break; + break; case 'walletReady': this.sendPublicKeyRing(senderId); - this.sendAddressBook(senderId); - this.sendAllTxProposals(senderId); // send old txps - break; + this.sendAddressBook(senderId); + this.sendAllTxProposals(senderId); // send old txps + break; case 'publicKeyRing': this._handlePublicKeyRing(senderId, data, isInbound); - break; + break; case 'txProposal': this._handleTxProposal(senderId, data, isInbound); - break; + break; case 'indexes': this._handleIndexes(senderId, data, isInbound); - break; + break; case 'addressbook': this._handleAddressBook(senderId, data, isInbound); - break; + break; } }; @@ -389,7 +386,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) { opts.addressBook = o.addressBook; opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); - opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); + opts.txProposals = TxProposalsSet.fromObj(o.txProposals, Wallet.builderOpts); opts.privateKey = PrivateKey.fromObj(o.privateKey); opts.storage = storage; @@ -497,7 +494,7 @@ Wallet.prototype.generateAddress = function(isChange, cb) { }; -Wallet.prototype.getTxProposals = function() { +Wallet.prototype.getTxProposalsSet = function() { var ret = []; var copayers = this.getRegisteredCopayerIds(); for (var ntxid in this.txProposals.txps) { @@ -734,11 +731,11 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos } var b = new Builder(opts) - .setUnspent(utxos) - .setOutputs([{ - address: toAddress, - amountSatStr: amountSatStr, - }]); + .setUnspent(utxos) + .setOutputs([{ + address: toAddress, + amountSatStr: amountSatStr, + }]); var selectedUtxos = b.getSelectedUnspent(); var inputChainPaths = selectedUtxos.map(function(utxo) { @@ -831,29 +828,29 @@ Wallet.prototype.indexDiscovery = function(start, change, cosigner, gap, cb) { var self = this; async.doWhilst( function _do(next) { - // Optimize window to minimize the derivations. - var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; - var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner); - self.blockchain.checkActivity(addresses, function(err, actives) { - if (err) throw err; + // Optimize window to minimize the derivations. + var scanWindow = (lastActive == -1) ? gap : gap - (scanIndex - lastActive) + 1; + var addresses = self.deriveAddresses(scanIndex, scanWindow, change, cosigner); + self.blockchain.checkActivity(addresses, function(err, actives) { + if (err) throw err; - // Check for new activities in the newlly scanned addresses - var recentActive = actives.reduce(function(r, e, i) { - return e ? scanIndex + i : r; - }, lastActive); - hasActivity = lastActive != recentActive; - lastActive = recentActive; - scanIndex += scanWindow; - next(); - }); - }, - function _while() { - return hasActivity; - }, - function _finnaly(err) { - if (err) return cb(err); - cb(null, lastActive); - } + // Check for new activities in the newlly scanned addresses + var recentActive = actives.reduce(function(r, e, i) { + return e ? scanIndex + i : r; + }, lastActive); + hasActivity = lastActive != recentActive; + lastActive = recentActive; + scanIndex += scanWindow; + next(); + }); + }, + function _while() { + return hasActivity; + }, + function _finnaly(err) { + if (err) return cb(err); + cb(null, lastActive); + } ); } diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 6f335ed0f..cd89d30da 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -2,7 +2,7 @@ var imports = require('soop').imports(); -var TxProposals = require('./TxProposals'); +var TxProposalsSet = require('./TxProposalsSet'); var PublicKeyRing = require('./PublicKeyRing'); var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); @@ -124,10 +124,10 @@ WalletFactory.prototype.create = function(opts) { opts.nickname); this.log('\t### PublicKeyRing Initialized'); - opts.txProposals = opts.txProposals || new TxProposals({ + opts.txProposals = opts.txProposals || new TxProposalsSet({ networkName: this.networkName, }); - this.log('\t### TxProposals Initialized'); + this.log('\t### TxProposalsSet Initialized'); this.storage._setPassphrase(opts.passphrase); diff --git a/test/test.HDPath.js b/test/test.HDPath.js index 500ba348d..1bf5bf0b0 100644 --- a/test/test.HDPath.js +++ b/test/test.HDPath.js @@ -70,9 +70,9 @@ describe('HDPath model', function() { ].forEach(function(datum) { var path = datum[0]; var result = datum[1]; - it('should get the correct indices for path ' + path, function() { - var i = HDPath.indicesForPath(path); - i.index.should.equal(result.index); + it('should get the correct indexes for path ' + path, function() { + var i = HDPath.indexesForPath(path); + i.addressIndex.should.equal(result.index); i.isChange.should.equal(result.isChange); }); }); From 05aefc17a5980d70d835432a545118c74f1d47c4 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 31 Jul 2014 10:42:09 -0300 Subject: [PATCH 09/31] tests for txProposal --- js/models/core/TxProposal.js | 18 +++++--- test/test.TxProposal.js | 83 ++++++++++++++++++++---------------- test/test.TxProposalsSet.js | 4 +- 3 files changed, 60 insertions(+), 45 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 68d786311..6842a2449 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -32,7 +32,7 @@ function TxProposal(opts) { this.sentTxid = opts.sentTxid || null; this.comment = opts.comment || null; this.readonly = opts.readonly || null; - this._updateSignedBy(); + // this._updateSignedBy(); } TxProposal.prototype.getId = function() { @@ -84,33 +84,37 @@ TxProposal.prototype._formatKeys = function(allowedPubKeys) { var k = new Key(); k.public = allowedPubKeys[i]; - keys.push[k]; + keys.push(k); }; + + return keys; }; TxProposal.prototype._verifySignatures = function(inKeys, scriptSig, txSigHash) { preconditions.checkArgument(Buffer.isBuffer(txSigHash)); + preconditions.checkArgument(inKeys); + preconditions.checkState(Buffer.isBuffer(inKeys[0])); if (scriptSig.chunks[0] !== 0) throw new Error('Invalid scriptSig'); - var keys = this._formatKeys(inKeys); var ret = []; - for (var i = 1; typeof ret === 'undefined' && i <= scriptSig.countSignatures(); i++) { + for (var i = 1; i <= scriptSig.countSignatures(); i++) { var chunk = scriptSig.chunks[i]; 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(j); - } + ret.push(parseInt(j)); + break; + } } } return ret; }; TxProposal.prototype._keysFromRedeemScript = function(s) { - var redeemScript = new Script(s.chunks[s.chunks.length-1]); + var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) throw new Error('Bad scriptSig'); var pubkeys = redeemScript.capture(); diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index fc7d1f8fe..2fcaccc18 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -29,7 +29,7 @@ var dummyProposal = new TxProposal({ inputChainPaths: ['m/1'], }); -var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5","022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"]; +var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"]; describe('TxProposal', function() { describe('new', function() { @@ -63,7 +63,7 @@ describe('TxProposal', function() { }); }); describe('#getId', function() { - it('should return id',function() { + it('should return id', function() { var b = new FakeBuilder(); var spy = sinon.spy(b.tx, 'getNormalizedHash'); var txp = new TxProposal({ @@ -73,11 +73,11 @@ describe('TxProposal', function() { inputChainPaths: 'm/1', }); txp.getId().should.equal('123456');; - sinon.assert.callCount(spy,1); + sinon.assert.callCount(spy, 1); }); }); describe('#toObj', function() { - it('should return an object and remove builder',function() { + it('should return an object and remove builder', function() { var b = new FakeBuilder(); var txp = new TxProposal({ creator: 1, @@ -93,33 +93,35 @@ describe('TxProposal', function() { }); }); describe('#fromObj', function() { - it.skip('should create from Object',function() { + it.skip('should create from Object', function() { var b = new FakeBuilder(); var txp = TxProposal.fromObj({ creator: 1, createdTs: 1, builderObj: b.toObj(), - inputChainPaths: ['m/1'] , + inputChainPaths: ['m/1'], }); should.exist(txp); }); - it('should fail to create from wrong object',function() { + it('should fail to create from wrong object', function() { var b = new FakeBuilder(); - (function(){var txp = TxProposal.fromObj({ - creator: 1, - createdTs: 1, - builderObj: b.toObj(), - inputChainPaths: ['m/1'] , - });}).should.throw('Invalid'); + (function() { + var txp = TxProposal.fromObj({ + creator: 1, + createdTs: 1, + builderObj: b.toObj(), + inputChainPaths: ['m/1'], + }); + }).should.throw('Invalid'); }); }); describe('#setSent', function() { - it('should set txid and timestamp',function() { + it('should set txid and timestamp', function() { var now = Date.now(); var txp = dummyProposal; txp.setSent('3a42'); @@ -132,38 +134,47 @@ describe('TxProposal', function() { describe('Signature verification', function() { var validScriptSig = '00483045022100a35a5cbe37e39caa62bf1c347eae9c72be827c190b31494b184943b3012757a8022008a1ff72a34a5bf2fc955aa5b6f8a4c32cb0fab7e54c212a5f6f645bb95b8ef10149304602210092347916c3c3e6f1692bf9447b973779c28ce9985baaa3940b483af573f464b4022100ab91062796ab8acb32a0fa90e00627db5be77d9722400b3ecfd9c5f34a8092b1014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'; - var keyBuf = someKeys.map(function(hex){ - return new Buffer(hex,'hex'); + var pubkeys = [ + '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', + '0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127', + '0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03', + '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3', + '03e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e4' + ].map(function(hex) { + return new Buffer(hex, 'hex'); }); - it('#_formatKeys',function() { + var keyBuf = someKeys.map(function(hex) { + return new Buffer(hex, 'hex'); + }); + it('#_formatKeys', function() { var txp = dummyProposal; - (function(){txp._formatKeys(someKeys);}).should.throw('buffers'); + (function() { + txp._formatKeys(someKeys); + }).should.throw('buffers'); var res = txp._formatKeys(keyBuf); }); - it('#_verifyScriptSig arg checks',function() { + it('#_verifyScriptSig arg checks', function() { var txp = dummyProposal; - ( function() { txp._verifySignatures( - keyBuf, - new bitcore.Script(new Buffer('112233','hex')), - new Buffer('1a','hex')); } ).should.throw('script'); + (function() { + txp._verifySignatures( + keyBuf, + new bitcore.Script(new Buffer('112233', 'hex')), + new Buffer('1a', 'hex')); + }).should.throw('script'); }); - it('#_verifyScriptSig, no signatures',function() { + it('#_verifyScriptSig, no signatures', function() { var txp = dummyProposal; - var ret = txp._verifySignatures( - keyBuf, - new bitcore.Script(new Buffer(validScriptSig,'hex')), - new Buffer('1a','hex') - ); + var ret = txp._verifySignatures( keyBuf, new bitcore.Script(new Buffer(validScriptSig, 'hex')), new Buffer(32)); ret.length.should.equal(0); }); - it('#_verifyScriptSig, one signature',function() { + it('#_verifyScriptSig, one signature', function() { + // Data taken from bitcore's TransactionBuilder test var txp = dummyProposal; - var ret = txp._verifySignatures( - [new Buffer('0380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127','hex')], - new bitcore.Script(new Buffer(validScriptSig,'hex')), - new Buffer('1a','hex') - ); - ret.length.should.equal(0); + var ret = txp._verifySignatures(pubkeys, + new bitcore.Script(new Buffer(validScriptSig, 'hex')), + new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex') + ); + ret.should.deep.equal([0, 3]); }); }); diff --git a/test/test.TxProposalsSet.js b/test/test.TxProposalsSet.js index 4872bd569..2c6dfcd86 100644 --- a/test/test.TxProposalsSet.js +++ b/test/test.TxProposalsSet.js @@ -19,7 +19,7 @@ try { } var fakeStorage = copay.FakeStorage; var PrivateKey = copay.PrivateKey || require('../js/models/PrivateKey'); -var TxProposals = copay.TxProposals || require('../js/models/TxProposal'); +var TxProposalsSet = copay.TxProposalsSet || require('../js/models/TxProposalsSet'); var is_browser = (typeof process == 'undefined' || typeof process.versions === 'undefined') var PublicKeyRing = is_browser ? copay.PublicKeyRing : require('soop').load('../js/models/core/PublicKeyRing', { @@ -117,7 +117,7 @@ var createTx = function(toAddress, amountSatStr, utxos, opts, priv, pkr) { inputChainPaths: inputChainPaths, creator: priv.getId(), createdTs: new Date(), - signedBy: priv && tx.countInputSignatures(0) ? {priv.getId(): true} : {}, + signedBy: priv && tx.countInputSignatures(0) ? me : {}, seenBy: priv ? me : {}, builder: b, }; From 0e211426d7aca76d344bc27b771233e3e753a802 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 31 Jul 2014 10:45:50 -0300 Subject: [PATCH 10/31] tests for txProposal --- js/models/core/TxProposal.js | 8 ++++---- test/test.TxProposal.js | 32 ++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 6842a2449..ff560d84c 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -76,7 +76,7 @@ TxProposal.fromObj = function(o, forceOpts) { -TxProposal.prototype._formatKeys = function(allowedPubKeys) { +TxProposal._formatKeys = function(allowedPubKeys) { var keys = []; for (var i in allowedPubKeys) { if (!Buffer.isBuffer(allowedPubKeys[i])) @@ -97,7 +97,7 @@ TxProposal.prototype._verifySignatures = function(inKeys, scriptSig, txSigHash) if (scriptSig.chunks[0] !== 0) throw new Error('Invalid scriptSig'); - var keys = this._formatKeys(inKeys); + var keys = TxProposal._formatKeys(inKeys); var ret = []; for (var i = 1; i <= scriptSig.countSignatures(); i++) { var chunk = scriptSig.chunks[i]; @@ -113,7 +113,7 @@ TxProposal.prototype._verifySignatures = function(inKeys, scriptSig, txSigHash) return ret; }; -TxProposal.prototype._keysFromRedeemScript = function(s) { +TxProposal._keysFromRedeemScript = function(s) { var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) throw new Error('Bad scriptSig'); @@ -131,7 +131,7 @@ TxProposal.prototype._updateSignedBy = function() { for (var i in tx.ins) { var scriptSig = new Script(tx.ins[i].s); - var keys = this._keysFromRedeemScript(scriptSig); + var keys = TxProposal._keysFromRedeemScript(scriptSig); var txSigHash = tx.hashForSignature(this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); var copayerIndex = this._verifySignatures(keys, scriptSig, txSigHash); if (typeof copayerIndex === 'undefined') diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 2fcaccc18..78561e278 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -132,7 +132,7 @@ describe('TxProposal', function() { describe('Signature verification', function() { - var validScriptSig = '00483045022100a35a5cbe37e39caa62bf1c347eae9c72be827c190b31494b184943b3012757a8022008a1ff72a34a5bf2fc955aa5b6f8a4c32cb0fab7e54c212a5f6f645bb95b8ef10149304602210092347916c3c3e6f1692bf9447b973779c28ce9985baaa3940b483af573f464b4022100ab91062796ab8acb32a0fa90e00627db5be77d9722400b3ecfd9c5f34a8092b1014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae'; + var validScriptSig = new bitcore.Script(new Buffer('00483045022100a35a5cbe37e39caa62bf1c347eae9c72be827c190b31494b184943b3012757a8022008a1ff72a34a5bf2fc955aa5b6f8a4c32cb0fab7e54c212a5f6f645bb95b8ef10149304602210092347916c3c3e6f1692bf9447b973779c28ce9985baaa3940b483af573f464b4022100ab91062796ab8acb32a0fa90e00627db5be77d9722400b3ecfd9c5f34a8092b1014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae', 'hex')); var pubkeys = [ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', @@ -147,35 +147,39 @@ describe('TxProposal', function() { return new Buffer(hex, 'hex'); }); it('#_formatKeys', function() { - var txp = dummyProposal; (function() { - txp._formatKeys(someKeys); + TxProposal._formatKeys(someKeys); }).should.throw('buffers'); - var res = txp._formatKeys(keyBuf); + var res = TxProposal._formatKeys(keyBuf); }); it('#_verifyScriptSig arg checks', function() { - var txp = dummyProposal; (function() { - txp._verifySignatures( + TxProposal._verifySignatures( keyBuf, new bitcore.Script(new Buffer('112233', 'hex')), new Buffer('1a', 'hex')); }).should.throw('script'); }); it('#_verifyScriptSig, no signatures', function() { - var txp = dummyProposal; - var ret = txp._verifySignatures( keyBuf, new bitcore.Script(new Buffer(validScriptSig, 'hex')), new Buffer(32)); + var ret = TxProposal._verifySignatures( keyBuf, validScriptSig, new Buffer(32)); ret.length.should.equal(0); }); - it('#_verifyScriptSig, one signature', function() { + it('#_verifyScriptSig, two signatures', function() { // Data taken from bitcore's TransactionBuilder test var txp = dummyProposal; - var ret = txp._verifySignatures(pubkeys, - new bitcore.Script(new Buffer(validScriptSig, 'hex')), - new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex') - ); - ret.should.deep.equal([0, 3]); + var ret = TxProposal._verifySignatures(pubkeys,validScriptSig, new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex')); + ret.should.deep.equal([0, 3]); }); + it('#_keysFromRedeemScript', function() { + var txp = dummyProposal; + var keys = + // Data taken from bitcore's TransactionBuilder test + var txp = dummyProposal; + var ret = txp._updateSignedBy(pubkeys, new bitcore.Script(new Buffer(validScriptSig, 'hex')), new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex')); + ret.should.deep.equal([0, 3]); + }); + + }); }); From 2fe421b14f27dc8e9a7fc7783a231a33c894c51c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 31 Jul 2014 10:50:00 -0300 Subject: [PATCH 11/31] tests for txProposal 1 --- js/models/core/TxProposal.js | 2 +- test/test.TxProposal.js | 14 +++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index ff560d84c..5ee3432b4 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -90,7 +90,7 @@ TxProposal._formatKeys = function(allowedPubKeys) { return keys; }; -TxProposal.prototype._verifySignatures = function(inKeys, scriptSig, txSigHash) { +TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { preconditions.checkArgument(Buffer.isBuffer(txSigHash)); preconditions.checkArgument(inKeys); preconditions.checkState(Buffer.isBuffer(inKeys[0])); diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 78561e278..138439ba2 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -171,15 +171,11 @@ describe('TxProposal', function() { ret.should.deep.equal([0, 3]); }); it('#_keysFromRedeemScript', function() { - var txp = dummyProposal; - var keys = - // Data taken from bitcore's TransactionBuilder test - var txp = dummyProposal; - var ret = txp._updateSignedBy(pubkeys, new bitcore.Script(new Buffer(validScriptSig, 'hex')), new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex')); - ret.should.deep.equal([0, 3]); + var keys = TxProposal._keysFromRedeemScript(validScriptSig); + keys.length.should.equal(5); + for(var i in keys){ + keys[i].toString('hex').should.equal(pubkeys[i].toString('hex')); + } }); - - - }); }); From ae042a8292642f4fd799a6595bd7ef17c90da5ca Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 31 Jul 2014 16:04:41 -0300 Subject: [PATCH 12/31] TxProposal fully testes --- js/models/core/TxProposal.js | 135 +++++++++++++++++++---------------- test/mocks/FakeBuilder.js | 20 +++++- test/test.TxProposal.js | 79 ++++++++++++++++++-- 3 files changed, 166 insertions(+), 68 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 5ee3432b4..3efad2b89 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -24,7 +24,7 @@ function TxProposal(opts) { this.builder = opts.builder; this.inputChainPaths = opts.inputChainPaths; - this._inputSignedBy = []; + this._inputSignatures = []; this.seenBy = opts.seenBy || {}; this.signedBy = opts.signedBy || {}; this.rejectedBy = opts.rejectedBy || {}; @@ -69,25 +69,24 @@ TxProposal.fromObj = function(o, forceOpts) { }; } var t = new TxProposal(o); - t._throwIfInvalid(); + t._check(); t._updateSignedBy(); return t; }; -TxProposal._formatKeys = function(allowedPubKeys) { - var keys = []; - for (var i in allowedPubKeys) { - if (!Buffer.isBuffer(allowedPubKeys[i])) - throw new Error('allowedPubKeys must be buffers'); +TxProposal._formatKeys = function(keys) { + var ret = []; + for (var i in keys) { + if (!Buffer.isBuffer(keys[i])) + throw new Error('keys must be buffers'); var k = new Key(); - k.public = allowedPubKeys[i]; - keys.push(k); + k.public = keys[i]; + ret.push(k); }; - - return keys; + return ret; }; TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { @@ -107,13 +106,13 @@ TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { if (k.verifySignatureSync(txSigHash, sigRaw)) { ret.push(parseInt(j)); break; - } + } } } return ret; }; -TxProposal._keysFromRedeemScript = function(s) { +TxProposal._infoFromRedeemScript = function(s) { var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) throw new Error('Bad scriptSig'); @@ -121,71 +120,66 @@ TxProposal._keysFromRedeemScript = function(s) { if (!pubkeys || !pubkeys.length) throw new Error('Bad scriptSig'); - return pubkeys; + return { + keys: pubkeys, + scriptBuf: redeemScript.getBuffer() + }; }; TxProposal.prototype._updateSignedBy = function() { - this._inputSignedBy = []; + this._inputSignatures = []; var tx = this.builder.build(); for (var i in tx.ins) { var scriptSig = new Script(tx.ins[i].s); - - var keys = TxProposal._keysFromRedeemScript(scriptSig); - var txSigHash = tx.hashForSignature(this.builder.inputMap[i].scriptPubKey, i, Transaction.SIGHASH_ALL); - var copayerIndex = this._verifySignatures(keys, scriptSig, txSigHash); - if (typeof copayerIndex === 'undefined') + var signatureCount = scriptSig.countSignatures(); + var info = TxProposal._infoFromRedeemScript(scriptSig); + var txSigHash = tx.hashForSignature(info.scriptBuf, i, Transaction.SIGHASH_ALL); + var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); + if (signatureIndexes.length !== signatureCount) throw new Error('Invalid signature'); - this._inputSignedBy[i] = this._inputSignedBy[i] || {}; - this._inputSignedBy[i][copayerIndex] = true; + this._inputSignatures[i] = signatureIndexes.map(function(i) { + return info.keys[i].toString('hex'); + }); }; }; -TxProposal.prototype.isValid = function() { +TxProposal.prototype._check = function() { if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { - return false; + throw new Error('Invalid tx proposal'); } var tx = this.builder.build(); if (!tx.ins.length) - return false; + throw new Error('Invalid tx proposal: no ins'); + + var scriptSigs = this.builder.vanilla.scriptSigs; + if (!scriptSigs || !scriptSigs.length) { + throw new Error('Invalid tx proposal: no signatures'); + } for (var i = 0; i < tx.ins.length; i++) { var hashType = tx.getHashType(i); - if (hashType && hashType !== Transaction.SIGHASH_ALL) { - return false; - } + if (hashType && hashType !== Transaction.SIGHASH_ALL) + throw new Error('Invalid tx proposal: bad signatures'); } - - console.log('[TxProposal.js.145]'); //TODO - - //Should be signed - var scriptSigs = this.builder.vanilla.scriptSigs; - if (!scriptSigs) - return false; - - - console.log('[TxProposal.js.153]'); //TODO - return true; }; -TxProposal.prototype._throwIfInvalid = function(allowedPubKeys) { - if (!this.isValid(allowedPubKeys)) - throw new Error('Invalid tx proposal'); +TxProposal.prototype.mergeBuilder = function(incoming) { + var b0 = this.builder; + var b1 = incoming.builder; + + var before = JSON.stringify(b0.toObj()); + b0.merge(b1); + var after = JSON.stringify(b0.toObj()); + return after !== before; }; -TxProposal.prototype.merge = function(incoming, allowedPubKeys) { - var ret = {}; - ret.events = []; - incoming._throwIfInvalid(allowedPubKeys); - - /* TODO */ - - /* - events.push({ +/* OTDO + events.push({ type: 'seen', cId: k, txId: ntxid @@ -202,29 +196,48 @@ txId: ntxid }); ret.events = this.mergeMetadata(incoming); */ - ret.hasChanged = this.mergeBuilder(incoming); + + +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; + return ret; }; -TxProposal.prototype.mergeBuilder = function(incoming) { - var b0 = this.builder; - var b1 = incoming.builder; +TxProposal.prototype.merge = function(incoming) { + var ret = {}; + var newSignatures = []; - var before = JSON.stringify(b0.toObj()); - b0.merge(b1); - var after = JSON.stringify(b0.toObj()); - return after !== before; + incoming._check(); + incoming._updateSignedBy(); + + var prevInputSignatures = this._allSignatures(); + + ret.hasChanged = this.mergeBuilder(incoming); + this._updateSignedBy(); + + if (ret.hasChanged) + for(var i in this._inputSignatures) + for (var j in this._inputSignatures[i]) + if (!prevInputSignatures[this._inputSignatures[i][j]]) + newSignatures.push(this._inputSignatures[i][j]); + + ret.newSignatures = newSignatures; + + return ret; }; - //This should be on bitcore / Transaction TxProposal.prototype.countSignatures = function() { var tx = this.builder.build(); - var ret = 0; for (var i in tx.ins) { ret += tx.countInputSignatures(i); } return ret; }; + module.exports = TxProposal; diff --git a/test/mocks/FakeBuilder.js b/test/mocks/FakeBuilder.js index 40fcdf5e7..15e38d5e5 100644 --- a/test/mocks/FakeBuilder.js +++ b/test/mocks/FakeBuilder.js @@ -2,16 +2,22 @@ var bitcore = bitcore || require('bitcore'); var Script = bitcore.Script; +var VALID_SCRIPTSIG_BUF = new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae','hex'); function Tx() { - this.ins = [{s: new Buffer('0048304502200708a381dde585ef7fdfaeaeb5da9b451d3e22b01eac8a5e3d03b959e24a7478022100c90e76e423523a54a9e9c43858337ebcef1a539a7fc685c2698dd8648fcf1b9101473044022030a77c9613d6ee010717c1abc494668d877e3fa0ae4c520f65cc3b308754c98c02205219d387bcb291bd44805b9468439e4168b02a6a180cdbcc24d84d71d696c1ae014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae','hex')}]; + this.ins = [{s: VALID_SCRIPTSIG_BUF }]; +}; + + +Tx.prototype.getHashType = function() { + return 1; }; Tx.prototype.getNormalizedHash = function() { return '123456'; }; Tx.prototype.hashForSignature = function() { - return new Buffer('72403de587240b85855da2ebd29b7dab5d170a9662d4bf7c4980358b14091696','hex'); + return new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex'); }; @@ -23,8 +29,16 @@ function FakeBuilder() { scriptPubKey: new Script(new Buffer('a914dc0623476aefb049066b09b0147a022e6eb8429187', 'hex')), scriptType: 4, i: 0 }]; + + this.vanilla = { + scriptSigs: [VALID_SCRIPTSIG_BUF], + } } + +FakeBuilder.prototype.merge = function() { +}; + FakeBuilder.prototype.build = function() { return this.tx; }; @@ -33,5 +47,5 @@ FakeBuilder.prototype.build = function() { FakeBuilder.prototype.toObj = function() { return this; }; - +FakeBuilder.VALID_SCRIPTSIG_BUF = VALID_SCRIPTSIG_BUF; module.exports = FakeBuilder; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 138439ba2..b62ce7bf0 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -132,7 +132,7 @@ describe('TxProposal', function() { describe('Signature verification', function() { - var validScriptSig = new bitcore.Script(new Buffer('00483045022100a35a5cbe37e39caa62bf1c347eae9c72be827c190b31494b184943b3012757a8022008a1ff72a34a5bf2fc955aa5b6f8a4c32cb0fab7e54c212a5f6f645bb95b8ef10149304602210092347916c3c3e6f1692bf9447b973779c28ce9985baaa3940b483af573f464b4022100ab91062796ab8acb32a0fa90e00627db5be77d9722400b3ecfd9c5f34a8092b1014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae', 'hex')); + var validScriptSig = new bitcore.Script(FakeBuilder.VALID_SCRIPTSIG_BUF); var pubkeys = [ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', @@ -167,15 +167,86 @@ describe('TxProposal', function() { it('#_verifyScriptSig, two signatures', function() { // Data taken from bitcore's TransactionBuilder test var txp = dummyProposal; - var ret = TxProposal._verifySignatures(pubkeys,validScriptSig, new Buffer('31103626e162f1cbfab6b95b08c9f6e78aae128523261cb37f8dfd4783cb09a7', 'hex')); + var tx = dummyProposal.builder.build(); + var ret = TxProposal._verifySignatures(pubkeys,validScriptSig, tx.hashForSignature()); ret.should.deep.equal([0, 3]); }); - it('#_keysFromRedeemScript', function() { - var keys = TxProposal._keysFromRedeemScript(validScriptSig); + it('#_infoFromRedeemScript', function() { + var info = TxProposal._infoFromRedeemScript(validScriptSig); + var keys = info.keys; keys.length.should.equal(5); for(var i in keys){ keys[i].toString('hex').should.equal(pubkeys[i].toString('hex')); } + Buffer.isBuffer(info.scriptBuf).should.equal(true); }); + it('#_updateSignedBy', function() { + var txp = dummyProposal; + txp._inputSignatures.should.deep.equal([]); + txp._updateSignedBy(); + txp._inputSignatures.should.deep.equal([[ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3' ]]); + }); + describe('#_check', function() { + var txp = dummyProposal; + var backup = txp.builder.tx.ins; + + it('OK', function() { + txp._check(); + }); + it('FAIL ins', function() { + txp.builder.tx.ins = []; + (function() { txp._check();} ).should.throw('no ins'); + txp.builder.tx.ins = backup; + }); + it('FAIL signhash', function() { + sinon.stub(txp.builder.tx,'getHashType').returns(2); + (function() { txp._check();} ).should.throw('signatures'); + txp.builder.tx.getHashType.restore(); + }); + it('FAIL no signatures', function() { + var backup = txp.builder.vanilla.scriptSigs; + txp.builder.vanilla.scriptSigs = []; + (function() { txp._check();} ).should.throw('no signatures'); + txp.builder.vanilla.scriptSigs = backup; + }); + }); + describe('#merge', function() { + var txp = dummyProposal; + var backup = txp.builder.tx.ins; + it('with self', function() { + var ret = txp.merge(txp); + ret.newSignatures.length.should.equal(0); + ret.hasChanged.should.equal(false); + }); + + it('with less signatures', function() { + var backup = txp.builder.vanilla.scriptSigs[0]; + txp.builder.merge = function() { + // 3 signatures. + this.vanilla.scriptSigs=['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s=new Buffer(this.vanilla.scriptSigs[0],'hex'); + }; + var ret = txp.merge(txp); + ret.hasChanged.should.equal(true); + ret.newSignatures.length.should.equal(0); + + txp.builder.vanilla.scriptSigs = [backup]; + txp.builder.tx.ins[0].s = new Buffer(backup,'hex'); + }); + + + it('with more signatures', function() { + txp.builder.merge = function() { + // 3 signatures. + this.vanilla.scriptSigs=['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s=new Buffer(this.vanilla.scriptSigs[0],'hex'); + }; + var ret = txp.merge(txp); + ret.hasChanged.should.equal(true); + ret.newSignatures.length.should.equal(1); + ret.newSignatures[0].should.equal('0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03'); + }); + }); + }); }); From 42c73f9a0144c3e1a79a181b840a9aa4d3481bc7 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Thu, 31 Jul 2014 23:32:24 -0300 Subject: [PATCH 13/31] rewrite from scratch tests for TxProposalSet --- js/models/core/TxProposal.js | 10 +- js/models/core/TxProposalsSet.js | 15 +- js/models/core/Wallet.js | 2 +- test/mocks/FakeBuilder.js | 2 +- test/test.TxProposal.js | 18 +- test/test.TxProposalsSet.js | 777 +++---------------------------- 6 files changed, 97 insertions(+), 727 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 3efad2b89..107394ff6 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -96,6 +96,7 @@ TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { if (scriptSig.chunks[0] !== 0) throw new Error('Invalid scriptSig'); + var keys = TxProposal._formatKeys(inKeys); var ret = []; for (var i = 1; i <= scriptSig.countSignatures(); i++) { @@ -116,13 +117,14 @@ TxProposal._infoFromRedeemScript = function(s) { var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) throw new Error('Bad scriptSig'); + var pubkeys = redeemScript.capture(); if (!pubkeys || !pubkeys.length) throw new Error('Bad scriptSig'); return { keys: pubkeys, - scriptBuf: redeemScript.getBuffer() + script: redeemScript, }; }; @@ -134,7 +136,7 @@ TxProposal.prototype._updateSignedBy = function() { var scriptSig = new Script(tx.ins[i].s); var signatureCount = scriptSig.countSignatures(); var info = TxProposal._infoFromRedeemScript(scriptSig); - var txSigHash = tx.hashForSignature(info.scriptBuf, i, Transaction.SIGHASH_ALL); + var txSigHash = tx.hashForSignature(info.script, parseInt(i), Transaction.SIGHASH_ALL); var signatureIndexes = TxProposal._verifySignatures(info.keys, scriptSig, txSigHash); if (signatureIndexes.length !== signatureCount) throw new Error('Invalid signature'); @@ -154,8 +156,8 @@ TxProposal.prototype._check = function() { if (!tx.ins.length) throw new Error('Invalid tx proposal: no ins'); - var scriptSigs = this.builder.vanilla.scriptSigs; - if (!scriptSigs || !scriptSigs.length) { + var scriptSig = this.builder.vanilla.scriptSig; + if (!scriptSig || !scriptSig.length) { throw new Error('Invalid tx proposal: no signatures'); } diff --git a/js/models/core/TxProposalsSet.js b/js/models/core/TxProposalsSet.js index 22d444303..517cdf370 100644 --- a/js/models/core/TxProposalsSet.js +++ b/js/models/core/TxProposalsSet.js @@ -5,6 +5,7 @@ var bitcore = require('bitcore'); var util = bitcore.util; var Transaction = bitcore.Transaction; var BuilderMockV0 = require('./BuilderMockV0');; +var TxProposal = require('./TxProposal');; var Script = bitcore.Script; var Key = bitcore.Key; var buffertools = bitcore.buffertools; @@ -28,7 +29,7 @@ TxProposalsSet.fromObj = function(o, forceOpts) { o.txps.forEach(function(o2) { var t = TxProposal.fromObj(o2, forceOpts); if (t.builder) { - var id = t.getID(); + var id = t.getId(); ret.txps[id] = t; } }); @@ -39,14 +40,9 @@ TxProposalsSet.prototype.getNtxids = function() { return Object.keys(this.txps); }; -TxProposalsSet.prototype.toObj = function(onlyThisNtxid) { - if (onlyThisNtxid) throw new Error(); +TxProposalsSet.prototype.toObj = function() { var ret = []; for (var id in this.txps) { - - if (onlyThisNtxid && id != onlyThisNtxid) - continue; - var t = this.txps[id]; if (!t.sent) ret.push(t.toObj()); @@ -70,7 +66,7 @@ TxProposalsSet.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { var myTxps = this.txps; - var ntxid = inTxp.getID(); + var ntxid = inTxp.getId(); var ret = {}; ret.events = []; ret.events.hasChanged = false; @@ -94,7 +90,7 @@ TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { // Add a LOCALLY CREATED (trusted) tx proposal TxProposalsSet.prototype.add = function(data) { var txp = new TxProposal(data); - var ntxid = txp.getID(); + var ntxid = txp.getId(); this.txps[ntxid] = txp; return ntxid; }; @@ -158,3 +154,4 @@ TxProposalsSet.prototype.getUsedUnspent = function(maxRejectCount) { return ret; }; +module.exports = TxProposalsSet; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 856e5cb43..a3392b16e 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -147,7 +147,7 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { var added = this.addSeenToTxProposals(); if (added) { this.log('### BROADCASTING txProposals with my seenBy updated.'); - this.sendTxProposal(mergeInfo.inTxp.getID()); + this.sendTxProposal(mergeInfo.inTxp.getId()); } this.emit('txProposalsUpdated'); diff --git a/test/mocks/FakeBuilder.js b/test/mocks/FakeBuilder.js index 15e38d5e5..ace53be70 100644 --- a/test/mocks/FakeBuilder.js +++ b/test/mocks/FakeBuilder.js @@ -31,7 +31,7 @@ function FakeBuilder() { i: 0 }]; this.vanilla = { - scriptSigs: [VALID_SCRIPTSIG_BUF], + scriptSig: [VALID_SCRIPTSIG_BUF], } } diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index b62ce7bf0..63174343f 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -204,10 +204,10 @@ describe('TxProposal', function() { txp.builder.tx.getHashType.restore(); }); it('FAIL no signatures', function() { - var backup = txp.builder.vanilla.scriptSigs; - txp.builder.vanilla.scriptSigs = []; + var backup = txp.builder.vanilla.scriptSig; + txp.builder.vanilla.scriptSig = []; (function() { txp._check();} ).should.throw('no signatures'); - txp.builder.vanilla.scriptSigs = backup; + txp.builder.vanilla.scriptSig = backup; }); }); describe('#merge', function() { @@ -220,17 +220,17 @@ describe('TxProposal', function() { }); it('with less signatures', function() { - var backup = txp.builder.vanilla.scriptSigs[0]; + var backup = txp.builder.vanilla.scriptSig[0]; txp.builder.merge = function() { // 3 signatures. - this.vanilla.scriptSigs=['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; - this.tx.ins[0].s=new Buffer(this.vanilla.scriptSigs[0],'hex'); + this.vanilla.scriptSig=['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex'); }; var ret = txp.merge(txp); ret.hasChanged.should.equal(true); ret.newSignatures.length.should.equal(0); - txp.builder.vanilla.scriptSigs = [backup]; + txp.builder.vanilla.scriptSig = [backup]; txp.builder.tx.ins[0].s = new Buffer(backup,'hex'); }); @@ -238,8 +238,8 @@ describe('TxProposal', function() { it('with more signatures', function() { txp.builder.merge = function() { // 3 signatures. - this.vanilla.scriptSigs=['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; - this.tx.ins[0].s=new Buffer(this.vanilla.scriptSigs[0],'hex'); + this.vanilla.scriptSig=['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex'); }; var ret = txp.merge(txp); ret.hasChanged.should.equal(true); diff --git a/test/test.TxProposalsSet.js b/test/test.TxProposalsSet.js index 2c6dfcd86..58cff2198 100644 --- a/test/test.TxProposalsSet.js +++ b/test/test.TxProposalsSet.js @@ -12,727 +12,98 @@ var Script = bitcore.Script; var TransactionBuilder = bitcore.TransactionBuilder; var util = bitcore.util; var networks = bitcore.networks; +var sinon = require('sinon'); try { var copay = require('copay'); //browser } catch (e) { var copay = require('../copay'); //node } -var fakeStorage = copay.FakeStorage; -var PrivateKey = copay.PrivateKey || require('../js/models/PrivateKey'); -var TxProposalsSet = copay.TxProposalsSet || require('../js/models/TxProposalsSet'); -var is_browser = (typeof process == 'undefined' || typeof process.versions === 'undefined') -var PublicKeyRing = is_browser ? copay.PublicKeyRing : - require('soop').load('../js/models/core/PublicKeyRing', { - Storage: fakeStorage + +var FakeBuilder = require('./mocks/FakeBuilder'); +var TxProposal = copay.TxProposal; +var TxProposalsSet = copay.TxProposalsSet; + +var dummyProposal = new TxProposal({ + creator: 1, + createdTs: 1, + builder: new FakeBuilder(), + inputChainPaths: ['m/1'], }); -var isChange = false; -var addressIndex = 0; -var config = { - networkName: 'testnet', -}; +var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"]; -var unspentTest = [{ - "address": "dummy", - "scriptPubKey": "dummy", - "txid": "2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1", - "vout": 1, - "amount": 10, - "confirmations": 7 -}]; - -var createPKR = function(bip32s) { - var w = new PublicKeyRing(config); - should.exist(w); - - for (var i = 0; i < 5; i++) { - if (bip32s && i < bip32s.length) { - var b = bip32s[i]; - w.addCopayer(b.deriveBIP45Branch().extendedPublicKeyString()); - } else { - w.addCopayer(); - } - } - - var pubkey = bip32s[0].publicHex; - - w.generateAddress(false, pubkey); - w.generateAddress(false, pubkey); - w.generateAddress(false, pubkey); - w.generateAddress(true, pubkey); - w.generateAddress(true, pubkey); - w.generateAddress(true, pubkey); - - return w; -}; - -var vopts = { - verifyP2SH: true, - dontVerifyStrictEnc: true -}; - -var createTx = function(toAddress, amountSatStr, utxos, opts, priv, pkr) { - opts = opts || {}; - - var pub = priv.publicHex; - - if (!pkr.isComplete()) { - throw new Error('publicKeyRing is not complete'); - } - - if (!opts.remainderOut) { - opts.remainderOut = { - address: pkr.generateAddress(true, pub).toString() - }; - }; - - var b = new TransactionBuilder(opts) - .setUnspent(utxos) - .setOutputs([{ - address: toAddress, - amountSatStr: amountSatStr, - }]); - var selectedUtxos = b.getSelectedUnspent(); - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - - var selectedUtxos = b.getSelectedUnspent(); - - var inputChainPaths = selectedUtxos.map(function(utxo) { - return pkr.pathForAddress(utxo.address); - }); - - b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - var signRet; - if (priv) { - var pkeys = priv.getForPaths(inputChainPaths); - b.sign(pkeys); - } - var me = {}; - if (priv) me[priv.getId()] = Date.now(); - var tx = b.build(); - - return { - inputChainPaths: inputChainPaths, - creator: priv.getId(), - createdTs: new Date(), - signedBy: priv && tx.countInputSignatures(0) ? me : {}, - seenBy: priv ? me : {}, - builder: b, - }; -}; - - -var cachedTxProposals; -var getCachedTxProposals = function() { - if (!cachedTxProposals){ - var priv = new PrivateKey(config); - var pub = priv.publicHex; - var w = new TxProposals({ - networkName: config.networkName, - }); - var start = new Date().getTime(); - var pkr = createPKR([priv]); - var ts = Date.now(); - - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, {}, - priv, - pkr - )); - cachedTxProposals = { tObj:w.toObj(), priv: priv}; - } - cachedTxProposals.txp = TxProposals.fromObj(cachedTxProposals.tObj); - return cachedTxProposals; -}; - -describe('TxProposals model', function() { - - it('verify TXs', function(done) { - - var priv = new PrivateKey(config); - var priv2 = new PrivateKey(config); - var priv3 = new PrivateKey(config); - var pub = priv.publicHex; - - var ts = Date.now(); - var pkr = createPKR([priv, priv2, priv3]); - var opts = { - remainderOut: { - address: pkr.generateAddress(true, pub).toString() - } - }; - - var w = new TxProposals({ - networkName: config.networkName, - }); - - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - var ntxid = Object.keys(w.txps)[0]; - var b = w.txps[ntxid].builder; - var tx = b.build(); - tx.isComplete().should.equal(false); - - var ringIndex = pkr.getHDParams(pub); - b.sign(priv2.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.copayerIndex)); - b.sign(priv3.getAll(ringIndex.getReceiveIndex(), ringIndex.getChangeIndex(), ringIndex.copayerIndex)); - tx = b.build(); - tx.isComplete().should.equal(true); - - var s = new Script(new bitcore.Buffer(unspentTest[0].scriptPubKey, 'hex')); - - tx.verifyInput(0, s, { - verifyP2SH: true, - dontVerifyStrictEnc: true - }, function(err, results) { - should.not.exist(err); - results.should.equal(true); - done(); +describe('TxProposalsSet', function() { + describe('constructor', function() { + it('should create an instance', function() { + var txps = new TxProposalsSet(); + should.exist(txps); + txps.network.name.should.equal('testnet'); }); }); - - - it('should create an instance', function() { - var w = new TxProposals({ - networkName: config.networkName + describe('#fromObj', function() { + it('should create an instance from an Object', function() { + var txps = TxProposalsSet.fromObj({ + networkName:'livenet', + walletId: '123a12', + txps: [], + }); + should.exist(txps); + txps.network.name.should.equal('livenet'); }); - should.exist(w); - w.network.name.should.equal(config.networkName); - }); - - - it('#getUsedUnspend', function() { - var priv = new PrivateKey(config); - var pub = priv.publicHex; - - var w = new TxProposals({ - networkName: config.networkName, + it('should fail create an instance from an Object with errors', function() { + (function() {var txps = TxProposalsSet.fromObj({ + networkName:'livenet', + walletId: '123a12', + txps: [ { a: 1 }], + }) }).should.throw('Illegal'); }); - var start = new Date().getTime(); - var pkr = createPKR([priv]); - var ts = Date.now(); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, {}, - priv, - pkr - )); - var uu = w.getUsedUnspent(); - var uuk = Object.keys(uu); - uuk.length.should.equal(1); - uuk[0].split(',')[0].should.equal(unspentTest[0].txid); }); - - it.only('should create a correct uncomplete txp', function() { - var c = getCachedTxProposals(); - var x = c.priv.getId(); - var ntxid = Object.keys(c.txp.txps)[0]; - c.txp.txps[ntxid].signedBy[c.priv.getId()].should.equal(true); - (c.txp.txps[ntxid].seenBy[c.priv.id] - ts > 0).should.equal(true); - }); - - describe('#merge', function() { - - it('with self', function() { - var t = txps.fromObj(txpsObj); - var info = w.merge(w.txps[ntxid], pkr.getCopayerId(0)); - info.events.length.should.equal(0); - - Object.keys(w.txps).length.should.equal(1); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); + describe('#getNtxids', function() { + it('should return keys', function() { + var txps = new TxProposalsSet(); + txps.txps = {a:1, b:2}; + txps.getNtxids().should.deep.equal(['a','b']); }); - - - - it('#merge, merge signatures case 1', function() { - var priv2 = new PrivateKey(config); - var priv = new PrivateKey(config); - var pub = priv.publicHex; - - var ts = Date.now(); - var pkr = createPKR([priv]); - var opts = { - remainderOut: { - address: pkr.generateAddress(true, pub).toString() - } + }); + describe('#toObj', function() { + it('should an object', function() { + var txps = TxProposalsSet.fromObj({ + networkName:'livenet', + walletId: '123a12', + txps: [], + }); + var o = txps.toObj(); + o.walletId.should.equal('123a12'); + o.networkName.should.equal('livenet'); + }); + it('should export txps', function() { + var txps = TxProposalsSet.fromObj({ + networkName:'livenet', + walletId: '123a12', + txps: [], + }); + txps.txps = { + 'hola' : dummyProposal, + 'chau' : dummyProposal, }; - - - var w = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); - - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputSignatures(0).should.equal(0); - tx.countInputMissingSignatures(0).should.equal(1); - - Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); - Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); - - - var w2 = new TxProposals({ - networkName: config.networkName, - publicKeyRing: w.publicKeyRing, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - - var ntxid = Object.keys(w.txps)[0]; - var tx = w2.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - - (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true, 'asdsd'); - (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); - console.log('[test.TxProposals.js.323:info:]',info); //TODO - info.events.length.should.equal(2); - info.events[0].type.should.equal('seen'); - info.events[1].type.should.equal('signed'); - - Object.keys(w.txps).length.should.equal(1); - - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - + var o = txps.toObj(); + o.txps.length.should.equal(2); }); - - var _dumpChunks = function(scriptSig, label) { - console.log('## DUMP: ' + label + ' ##'); - for (var i = 0; i < scriptSig.chunks.length; i++) { - console.log('\tCHUNK ', i, scriptSig.chunks[i]); - } - }; - - - it('#merge, merge signatures case 2', function() { - - var o1 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdSF1avR6mXyDj5Uv1XY2UyUHSDpAXQ5TvPN7prGeDppjy4562rBB9gMMAhRfFdJrNDpQ4t69kkqHNEEen3PX1zBJqSehJDH', - networkName: 'testnet', - privateKeyCache: {} + it('should filter sent txp', function() { + var txps = TxProposalsSet.fromObj({ + networkName:'livenet', + walletId: '123a12', + txps: [], + }); + var d = JSON.parse(JSON.stringify(dummyProposal)); + d.sent=1; + txps.txps = { + 'hola' : dummyProposal, + 'chau' : d, }; - var o2 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPdVeB5RzuxS9JQcACueZYgUaM5eWzaEBkHjW5Pg6Mqez1APSqoUP1jUdbT8WVG7ZJYTXvUL7XtPzFYBXjmdKuwSor1dcNQ8j', - networkName: 'testnet', - privateKeyCache: {} - }; - var o3 = { - extendedPrivateKeyString: 'tprv8ZgxMBicQKsPeHWNrPVZtQVgcCtXBr5TACNbDQ56rwqNJce9MEc64US6DJKxpWsrebEomxxWZFDtkvkZGkzA43uLvdF4XHiWqoNaL6Dq2Gd', - networkName: 'testnet', - privateKeyCache: {} - }; - - - var priv = PrivateKey.fromObj(o1); - var priv2 = PrivateKey.fromObj(o2); - var priv3 = PrivateKey.fromObj(o3); - var pub = priv.publicHex; - - var ts = Date.now(); - var pkr = createPKR([priv, priv2]); - var opts = { - remainderOut: { - address: '2MxK2m7cPtEwjZBB8Ksq7ppjkgJyFPJGemr' - } - }; - var addressToSign = pkr.generateAddress(false, pub); - unspentTest[0].address = addressToSign.toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - var tx, txb; - - var w = new TxProposals({ - networkName: config.networkName, - }); - - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv3, - pkr - )); - - var ntxid = Object.keys(w.txps)[0]; - txb = w.txps[ntxid].builder; - tx = txb.build(); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - - Object.keys(w.txps[ntxid].signedBy).length.should.equal(0); - Object.keys(w.txps[ntxid].seenBy).length.should.equal(1); - - var w2 = new TxProposals({ - networkName: config.networkName, - }); - - - - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - var ntxid = Object.keys(w2.txps)[0]; - txb = w2.txps[ntxid].builder; - tx = txb.build(); - - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(0)); - info.events.length.should.equal(2); - info.events[0].type.should.equal('seen'); - info.events[1].type.should.equal('signed'); - - tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - - var w3 = new TxProposals({ - networkName: config.networkName, - publicKeyRing: pkr, - }); - w3.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); - tx = w3.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - - (w3.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w3.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(1)); - - Object.keys(w.txps).length.should.equal(1); - - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - }); - - - it('#merge, merge signatures case 3', function() { - - var priv = new PrivateKey(config); - var priv2 = new PrivateKey(config); - var priv3 = new PrivateKey(config); - var pub = priv.publicHex; - - - var ts = Date.now(); - var pkr = createPKR([priv, priv2, priv3]); - var opts = { - remainderOut: { - address: pkr.generateAddress(true, pub).toString() - } - }; - - var w = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv, - pkr - )); - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - - var w2 = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w2.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv2, - pkr - )); - var tx = w2.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w2.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w2.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - - var w3 = new TxProposals({ - networkName: config.networkName, - }); - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w3.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, - opts, - priv3, - pkr - )); - var tx = w3.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w3.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); - (w3.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); - - var info = w.merge(w2.txps[ntxid], pkr.getCopayerId(1)); - - Object.keys(w.txps).length.should.equal(1); - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(1); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - - - var info = w.merge(w3.txps[ntxid], pkr.getCopayerId(2)); - - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(true); - tx.countInputMissingSignatures(0).should.equal(0); - Object.keys(w.txps).length.should.equal(1); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv3.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv2.id] - ts > 0).should.equal(true); - (w.txps[ntxid].signedBy[priv3.id] - ts > 0).should.equal(true); + var o = txps.toObj(); + o.txps.length.should.equal(1); }); }); - - - it('#fromObj stored (hardcoded) data', function() { - var txp = TxProposals.TxProposal.fromObj(txpv1); - txp.getID().should.equal('5cae6e225335acd2725856c71ef1ca61c42f118967102c5d0ed6710343e4a19f'); - var tx = txp.builder.build(); - tx.countInputSignatures(0).should.equal(2); - tx.countInputMissingSignatures(0).should.equal(0); - }); - - it('#toObj #fromObj roundtrip', function() { - - var priv = new PrivateKey(config); - var pub = priv.publicHex; - - var pkr = createPKR([priv]); - var w = new TxProposals({ - walletId: 'qwerty', - networkName: config.networkName, - }); - var ts = Date.now(); - - unspentTest[0].address = pkr.getAddress(addressIndex, isChange, pub).toString(); - unspentTest[0].scriptPubKey = pkr.getScriptPubKeyHex(addressIndex, isChange, pub); - w.add(createTx( - '15q6HKjWHAksHcH91JW23BJEuzZgFwydBt', - '123456789', - unspentTest, {}, - priv, - pkr - )); - var ntxid = Object.keys(w.txps)[0]; - var tx = w.txps[ntxid].builder.build(); - tx.isComplete().should.equal(false); - tx.countInputMissingSignatures(0).should.equal(2); - (w.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - - var o = w.toObj(); - should.exist(o); - o.txps.length.should.equal(1); - - should.exist(o.txps[0]); - should.exist(o.txps[0].signedBy); - should.exist(o.txps[0].seenBy); - should.exist(o.txps[0].builderObj); - should.exist(o.txps[0].signedBy[priv.id]); - - var o2 = JSON.parse(JSON.stringify(o)); - var w2 = TxProposals.fromObj(o2); - w2.walletId.should.equal(w.walletId); - - var tx2 = w2.txps[ntxid].builder.build(); - tx2.isComplete().should.equal(false); - tx2.countInputMissingSignatures(0).should.equal(2); - (w2.txps[ntxid].signedBy[priv.id] - ts > 0).should.equal(true); - (w2.txps[ntxid].seenBy[priv.id] - ts > 0).should.equal(true); - should.exist(w2.txps[ntxid].builder); - should.exist(w2.txps[ntxid].builder.valueInSat); - - w2.merge(w.txps[ntxid], pkr.getCopayerId(0)); - Object.keys(w2.txps).length.should.equal(1); - }); - - describe('TxProposal model', function() { - var createMockTxp = function(raw) { - var tx = new Transaction(); - tx.parse(new Buffer(raw, 'hex')); - var txb = new TransactionBuilder(); - var txp = new TxProposals.TxProposal({ - builder: txb - }); - txb.build = function() { - return tx; - }; - return txp; - }; - - it('should validate for no signatures yet in tx', function() { - // taken from https://gist.github.com/gavinandresen/3966071 - var raw = '010000000189632848f99722915727c5c75da8db2dbf194342a0429828f66ff88fab2af7d60000000000ffffffff0140420f000000000017a914f815b036d9bbbce5e9f2a00abd1bf3dc91e955108700000000'; - var txp = createMockTxp(raw); - txp.isValid().should.equal(true); - }); - it('should validate for no signatures yet in copay generated tx', function() { - // taken from copay incomplete tx proposal - var raw = '0100000001e205297fd05e4504d72761dc7a16e5cc9f4ab89877f28aee97c1cc66b3f07d690100000000ffffffff01706f9800000000001976a91473707e88f79c9c616b44bc766a25efcb9f49346688ac00000000'; - var txp = createMockTxp(raw); - txp.isValid().should.equal(true); - }); - it('should validate for a SIGHASH_NONE tx in builder', function() { - var raw = '010000000145c3bf51ced6cefaea8c6578a645316270dbf8600f46969d31136e1e06829598000000007000483045022100877c715e0f3bd6377086c96d4757b2c983682a1934d9e3f894941f4f1e18d4710220272ed81758d7a391ee4c15a29246f3fe75efbddeaf1118e4c0d3bb14f57cdba601255121022f58491a833933a9bea80d8e820e66bee91bd8c71bfa972fe70482360b48129951aeffffffff01706f9800000000001976a91408328947f0caf8728729d740cbecdfe3c2327db588ac00000000'; - var txp = createMockTxp(raw); - txp.isValid().should.equal(true); - }); - it('should not validate for a non SIGHASH_NONE tx in builder with 1 input', function() { - var raw = '0100000001eaf08f93f895127fbf000128ac74f6e8c7f003854e5ee1f02a5fd820cb689beb00000000fdfe00004730440220778f3174393e9ee6b0bfa876b4150db6f12a4da9715044ead5e345c2781ceee002203aab31f1e1d3dcf77ca780d9af798139719891917c9a09123dba54483ef462bc02493046022100dd93b64b30580029605dbba09d7fa34194d9ff38fda0c4fa187c52bf7f79ae98022100dd7b056762087b9aa8ccfde328d7067fa1753b78c0ee25577122569ff9de1d57024c695221039f847c24f09d7299c10bba4e41b24dc78e47bbb05fd7c1d209c994899d6881062103d363476e634fc5cdc11e9330c05a141c1e0c7f8b616817bdb83e7579bbf870942103fb2072953ceab87c6da450ac661685a881ddb661002d2ec1d60bfd33e3ec807d53aeffffffff01d06bf5050000000017a914db682f579cf6ca483880460fcf4ab63e223dc07e8700000000'; - var txp = createMockTxp(raw); - txp.isValid().should.equal(false); - }); - it('should not validate for a non SIGHASH_NONE tx in builder with 1 input', function() { - var raw = '0100000002d903852d223b3100fcc01e0b02d73a76a0787cdff7d000e9cba0e931917f407501000000fdfe0000493046022100b232e994fdca7fd61fcf8ffe4a7f746ff8f8baf2667ac80841de0250f521c402022100862c0783ca7eafcbd2786b9444ed6e83ae941dcc2248bea4db12b7815d15de050247304402200189fe0cde9d1dd192553f4dddb6764df3eb643f9f71be8aa015f41f2d4fd11f02205513b8ca985c3b5b936f814c7eba92e2e2985c83927ca06c41081d264c0be7a7024c695221026fa1a3ed0c820c1053c8ba101f3c96f85c55624a902a82cf6b2896ed5f9b3d1521035a3383c13dd346a5784adfe3ec3026ab31d519fdfae2740497b10bdfb994e6442103c7477a6668d5bc250fe727e358d951b9e05f1d7c02059bf59ecbb335f1eeec7953aeffffffffd903852d223b3100fcc01e0b02d73a76a0787cdff7d000e9cba0e931917f407500000000fdfd0000483045022100bdb9d14569af66d84af63416d77296ace24a96f1720d30e74bc6e316a4b3727502206ed54d532467393488889d72edbb667d075de491a89e8e496fee8791b943fa37024730440220379c30c884a21a949d8ec32d6934ffa9faf86add4d839de0f5fbd2b90f8ef1e802204048df2ec0035ce5e4bf01e9d70fd93a45a41ce2630100d692cd908cdaa61fc0024c69522102203938ef947327edce2cf2997c55b433be3d3ffcf3284c10d6fcdf4b01c6221f21033b60c3363a226ce9b850af655c6e1470d9a0936d7f56ea4a07ab84005f91cd1b210385755bc813fe7f92577b93bf689bf0d9b2118e6bbb7fee5d3d16976f4f7271af53aeffffffff01c02d9a3b0000000017a914db682f579cf6ca483880460fcf4ab63e223dc07e8700000000'; - var txp = createMockTxp(raw); - txp.isValid().should.equal(false); - }); - }); - }); - -var txpv1 = { - "creator": "0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0", - "createdTs": 1406310417996, - "seenBy": { - "0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0": 1406310417996, - "02ba1599c64da4d80e25985be46c50e944b65f02e2b48c930528ce763d6710158f": 1406310418162 - }, - "signedBy": { - "0361fb4252367715405a0d27f99cc74a671133292e8d725e009536d7257c8c01b0": 1406310417996, - "02ba1599c64da4d80e25985be46c50e944b65f02e2b48c930528ce763d6710158f": 1406310645549 - }, - "rejectedBy": {}, - "sentTs": 1406310645873, - "sentTxid": "87296c50e8601437d63d556afb27c3b8e3819214be0a9d756d401a8286c0ec43", - "inputChainPaths": ["m/45'/0/1/1"], - "comment": "test 6", - "builderObj": { - "version": 1, - "outs": [{ - "address": "mph66bnLvcn9KUSMrpikUBUZZkN2C1Z5tg", - "amountSatStr": 100 - }], - "utxos": [{ - "address": "2NEodmgBa4SH3VwE2asgW34vMYe8VThBZNo", - "txid": "8f8deda12dad6248e655054632a27f6891ebb37e8d2b3dd1bff87e71fd451ac7", - "vout": 1, - "ts": 1406312717, - "scriptPubKey": "a914ec7bce12d0e82a7d2b5431f6d89ca70af317f5a187", - "amount": 0.009798, - "confirmations": 0, - "confirmationsFromCache": false - }], - "opts": { - "spendUnconfirmed": true, - "remainderOut": { - "address": "2N74XAozMH3JB3XgeBkRvRw1J8TtfLTtvny" - } - }, - "scriptSig": ["00483045022100f167ad33b8bef4c65af8d19c1a849d1770cc8d1e35bffebe6b5459dcbe655c7802207b37370b308ba668fe19f8e8bc462c9fbdc6c67f79900670758d228d83ea96da014730440220038ad3f4cc7b0738b593454ec189913ae4b442bc83da153d68d9a0077bd1b09102202b5728a08f302e97de61ea37280b48ccdd575f0d235c22f5e0ecac6a4ab0f46401475221024739614847d5233a46913482c17c6860194ad78abb3bf47de46223047d8a0b5821024c6dc65a52c5eaaa080b96888091544f8ab8712caa7e0b69ea4b45f6f059557452ae"], - "hashToScriptMap": { - "2NEodmgBa4SH3VwE2asgW34vMYe8VThBZNo": "5221024739614847d5233a46913482c17c6860194ad78abb3bf47de46223047d8a0b5821024c6dc65a52c5eaaa080b96888091544f8ab8712caa7e0b69ea4b45f6f059557452ae" - } - } -}; From 5d2b50f77f85640d3666ae0b5605cee02ea8015c Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 1 Aug 2014 01:09:46 -0300 Subject: [PATCH 14/31] test in Txproposals working again --- copay.js | 2 +- js/models/core/TxProposal.js | 24 +++++++--- .../{TxProposalsSet.js => TxProposals.js} | 45 ++++++++++--------- js/models/core/Wallet.js | 11 ++--- js/models/core/WalletFactory.js | 2 +- test/test.TxProposal.js | 2 +- ....TxProposalsSet.js => test.TxProposals.js} | 24 ++++++---- test/test.Wallet.js | 9 ++-- 8 files changed, 72 insertions(+), 47 deletions(-) rename js/models/core/{TxProposalsSet.js => TxProposals.js} (79%) rename test/{test.TxProposalsSet.js => test.TxProposals.js} (83%) diff --git a/copay.js b/copay.js index 3a816a42c..5b2711dc9 100644 --- a/copay.js +++ b/copay.js @@ -1,7 +1,7 @@ // core module.exports.PublicKeyRing = require('./js/models/core/PublicKeyRing'); module.exports.TxProposal = require('./js/models/core/TxProposal'); -module.exports.TxProposalsSet = require('./js/models/core/TxProposalsSet'); +module.exports.TxProposals = require('./js/models/core/TxProposals'); module.exports.PrivateKey = require('./js/models/core/PrivateKey'); module.exports.Passphrase = require('./js/models/core/Passphrase'); module.exports.HDPath = require('./js/models/core/HDPath'); diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 107394ff6..39b89ccc9 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -13,10 +13,10 @@ var preconditions = require('preconditions').instance(); function TxProposal(opts) { preconditions.checkArgument(opts); - preconditions.checkArgument(opts.inputChainPaths); - preconditions.checkArgument(opts.creator); - preconditions.checkArgument(opts.createdTs); - preconditions.checkArgument(opts.builder); + preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths'); + preconditions.checkArgument(opts.creator,'no creator'); + preconditions.checkArgument(opts.createdTs,'no createdTs'); + preconditions.checkArgument(opts.builder,'no builder'); this.creator = opts.creator; @@ -53,9 +53,14 @@ TxProposal.prototype.setSent = function(sentTxid) { }; TxProposal.fromObj = function(o, forceOpts) { + + console.log('[TxProposal.js.56]'); //TODO preconditions.checkArgument(o.builderObj); + + console.log('[TxProposal.js.59]'); //TODO delete o['builder']; + console.log('[TxProposal.js.62]'); //TODO try { // force opts is requested. for (var k in forceOpts) { @@ -63,14 +68,20 @@ TxProposal.fromObj = function(o, forceOpts) { } o.builder = TransactionBuilder.fromObj(o.builderObj); } catch (e) { + + console.log('[TxProposal.js.71]'); //TODO if (!o.version) { o.builder = new BuilderMockV0(o.builderObj); o.readonly = 1; }; } + + console.log('[TxProposal.js.78]', o); //TODO var t = new TxProposal(o); t._check(); t._updateSignedBy(); + + console.log('[TxProposal.js.78]'); //TODO return t; }; @@ -114,13 +125,14 @@ TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { }; TxProposal._infoFromRedeemScript = function(s) { + console.log('[TxProposal.js.127]',s.getBuffer().toString('hex')); //TODO var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) - throw new Error('Bad scriptSig'); + throw new Error('Bad scriptSig (no redeemscript)'); var pubkeys = redeemScript.capture(); if (!pubkeys || !pubkeys.length) - throw new Error('Bad scriptSig'); + throw new Error('Bad scriptSig (no pubkeys)'); return { keys: pubkeys, diff --git a/js/models/core/TxProposalsSet.js b/js/models/core/TxProposals.js similarity index 79% rename from js/models/core/TxProposalsSet.js rename to js/models/core/TxProposals.js index 517cdf370..5565cac77 100644 --- a/js/models/core/TxProposalsSet.js +++ b/js/models/core/TxProposals.js @@ -12,7 +12,7 @@ var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); -function TxProposalsSet(opts) { +function TxProposals(opts) { opts = opts || {}; this.walletId = opts.walletId; this.network = opts.networkName === 'livenet' ? @@ -20,8 +20,8 @@ function TxProposalsSet(opts) { this.txps = {}; } -TxProposalsSet.fromObj = function(o, forceOpts) { - var ret = new TxProposalsSet({ +TxProposals.fromObj = function(o, forceOpts) { + var ret = new TxProposals({ networkName: o.networkName, walletId: o.walletId, }); @@ -36,11 +36,11 @@ TxProposalsSet.fromObj = function(o, forceOpts) { return ret; }; -TxProposalsSet.prototype.getNtxids = function() { +TxProposals.prototype.getNtxids = function() { return Object.keys(this.txps); }; -TxProposalsSet.prototype.toObj = function() { +TxProposals.prototype.toObj = function() { var ret = []; for (var id in this.txps) { var t = this.txps[id]; @@ -55,15 +55,7 @@ TxProposalsSet.prototype.toObj = function() { }; -TxProposalsSet.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) { - var inTxp = TxProposal.fromObj(txProposalObj, opts); - var mergeInfo = this.txProposals.merge(inTxp, allowedPubKeys); - mergeInfo.inTxp = inTxp; - return mergeInfo; -}; - - -TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { +TxProposals.prototype.merge = function(inTxp, allowedPubKeys) { var myTxps = this.txps; var ntxid = inTxp.getId(); @@ -75,7 +67,8 @@ TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { var v0 = myTxps[ntxid]; var v1 = inTxp; ret = v0.merge(v1, allowedPubKeys); - } else { + } + else { this.txps[ntxid] = inTxp; ret.hasChanged = true; ret.events.push({ @@ -87,21 +80,31 @@ TxProposalsSet.prototype.merge = function(inTxp, allowedPubKeys) { return ret; }; +TxProposals.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) { + var inTxp = TxProposal.fromObj(txProposalObj, opts); + var mergeInfo = this.merge(inTxp, allowedPubKeys); + mergeInfo.inTxp = inTxp; + return mergeInfo; +}; + + + + // Add a LOCALLY CREATED (trusted) tx proposal -TxProposalsSet.prototype.add = function(data) { +TxProposals.prototype.add = function(data) { var txp = new TxProposal(data); var ntxid = txp.getId(); this.txps[ntxid] = txp; return ntxid; }; -TxProposalsSet.prototype.setSent = function(ntxid, txid) { - //sent TxProposalsSet are local an not broadcasted. +TxProposals.prototype.setSent = function(ntxid, txid) { + //sent TxProposals are local an not broadcasted. this.txps[ntxid].setSent(txid); }; -TxProposalsSet.prototype.getTxProposal = function(ntxid, copayers) { +TxProposals.prototype.getTxProposal = function(ntxid, copayers) { var txp = this.txps[ntxid]; var i = JSON.parse(JSON.stringify(txp)); i.builder = txp.builder; @@ -139,7 +142,7 @@ TxProposalsSet.prototype.getTxProposal = function(ntxid, copayers) { }; //returns the unspent txid-vout used in PENDING Txs -TxProposalsSet.prototype.getUsedUnspent = function(maxRejectCount) { +TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { var ret = {}; for (var i in this.txps) { var u = this.txps[i].builder.getSelectedUnspent(); @@ -154,4 +157,4 @@ TxProposalsSet.prototype.getUsedUnspent = function(maxRejectCount) { return ret; }; -module.exports = TxProposalsSet; +module.exports = TxProposals; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index a3392b16e..8ce22c0bb 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -17,7 +17,7 @@ var Address = bitcore.Address; var HDParams = require('./HDParams'); var PublicKeyRing = require('./PublicKeyRing'); -var TxProposalsSet = require('./TxProposalsSet'); +var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); @@ -132,13 +132,14 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL: ', data); var mergeInfo; - try { mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts); } catch (e) { +console.log('[Wallet.js.141]',e); //TODO var corruptEvent = { type: 'corrupt', - cId: mergeInfo.inTxp.creator + cId: senderId, + error: e, }; this.emit('txProposalEvent', corruptEvent); return; @@ -386,7 +387,7 @@ Wallet.fromObj = function(o, storage, network, blockchain) { opts.addressBook = o.addressBook; opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); - opts.txProposals = TxProposalsSet.fromObj(o.txProposals, Wallet.builderOpts); + opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); opts.privateKey = PrivateKey.fromObj(o.privateKey); opts.storage = storage; @@ -494,7 +495,7 @@ Wallet.prototype.generateAddress = function(isChange, cb) { }; -Wallet.prototype.getTxProposalsSet = function() { +Wallet.prototype.getTxProposals = function() { var ret = []; var copayers = this.getRegisteredCopayerIds(); for (var ntxid in this.txProposals.txps) { diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index cd89d30da..322975221 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -2,7 +2,7 @@ var imports = require('soop').imports(); -var TxProposalsSet = require('./TxProposalsSet'); +var TxProposals = require('./TxProposals'); var PublicKeyRing = require('./PublicKeyRing'); var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 63174343f..9456e841a 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -178,7 +178,7 @@ describe('TxProposal', function() { for(var i in keys){ keys[i].toString('hex').should.equal(pubkeys[i].toString('hex')); } - Buffer.isBuffer(info.scriptBuf).should.equal(true); + Buffer.isBuffer(info.script.getBuffer()).should.equal(true); }); it('#_updateSignedBy', function() { var txp = dummyProposal; diff --git a/test/test.TxProposalsSet.js b/test/test.TxProposals.js similarity index 83% rename from test/test.TxProposalsSet.js rename to test/test.TxProposals.js index 58cff2198..a27258c5f 100644 --- a/test/test.TxProposalsSet.js +++ b/test/test.TxProposals.js @@ -21,7 +21,7 @@ try { var FakeBuilder = require('./mocks/FakeBuilder'); var TxProposal = copay.TxProposal; -var TxProposalsSet = copay.TxProposalsSet; +var TxProposals = copay.TxProposals; var dummyProposal = new TxProposal({ creator: 1, @@ -32,17 +32,17 @@ var dummyProposal = new TxProposal({ var someKeys = ["03b39d61dc9a504b13ae480049c140dcffa23a6cc9c09d12d6d1f332fee5e18ca5", "022929f515c5cf967474322468c3bd945bb6f281225b2c884b465680ef3052c07e"]; -describe('TxProposalsSet', function() { +describe('TxProposals', function() { describe('constructor', function() { it('should create an instance', function() { - var txps = new TxProposalsSet(); + var txps = new TxProposals(); should.exist(txps); txps.network.name.should.equal('testnet'); }); }); describe('#fromObj', function() { it('should create an instance from an Object', function() { - var txps = TxProposalsSet.fromObj({ + var txps = TxProposals.fromObj({ networkName:'livenet', walletId: '123a12', txps: [], @@ -51,7 +51,7 @@ describe('TxProposalsSet', function() { txps.network.name.should.equal('livenet'); }); it('should fail create an instance from an Object with errors', function() { - (function() {var txps = TxProposalsSet.fromObj({ + (function() {var txps = TxProposals.fromObj({ networkName:'livenet', walletId: '123a12', txps: [ { a: 1 }], @@ -60,14 +60,14 @@ describe('TxProposalsSet', function() { }); describe('#getNtxids', function() { it('should return keys', function() { - var txps = new TxProposalsSet(); + var txps = new TxProposals(); txps.txps = {a:1, b:2}; txps.getNtxids().should.deep.equal(['a','b']); }); }); describe('#toObj', function() { it('should an object', function() { - var txps = TxProposalsSet.fromObj({ + var txps = TxProposals.fromObj({ networkName:'livenet', walletId: '123a12', txps: [], @@ -77,7 +77,7 @@ describe('TxProposalsSet', function() { o.networkName.should.equal('livenet'); }); it('should export txps', function() { - var txps = TxProposalsSet.fromObj({ + var txps = TxProposals.fromObj({ networkName:'livenet', walletId: '123a12', txps: [], @@ -90,7 +90,7 @@ describe('TxProposalsSet', function() { o.txps.length.should.equal(2); }); it('should filter sent txp', function() { - var txps = TxProposalsSet.fromObj({ + var txps = TxProposals.fromObj({ networkName:'livenet', walletId: '123a12', txps: [], @@ -105,5 +105,11 @@ describe('TxProposalsSet', function() { o.txps.length.should.equal(1); }); }); + describe.skip('#merge', function() { + it('mergeFromObj', function() { + var txps = new TxProposals(); + txps.mergeFromObj(dummyProposal.toObj()); + }); + }); }); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 92810302b..92258666a 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -1064,7 +1064,10 @@ describe('Wallet model', function() { ]); var txp = { 'txProposal': { - 'builderObj': txb.toObj() + builderObj: txb.toObj(), + inputChainPaths: 'm/1', + creator: '1234', + createdTs: Date.now(), } }; w._handleTxProposal('senderID', txp, true); @@ -1072,11 +1075,11 @@ describe('Wallet model', function() { }; it('should validate for undefined', function(done) { - var result = 'new'; + var result = 'corrupt'; var signhash; testValidate(signhash, result, done); }); - it('should validate for SIGHASH_ALL', function(done) { + it.only('should validate for SIGHASH_ALL', function(done) { var result = 'new'; var signhash = Transaction.SIGHASH_ALL; testValidate(signhash, result, done); From dc3bbb885971e32e11e162ec5f17e62da982a808 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 1 Aug 2014 10:51:46 -0300 Subject: [PATCH 15/31] wallet test passings --- js/models/core/TxProposal.js | 9 ----- js/models/core/Wallet.js | 1 - test/test.TxProposal.js | 14 ++++++-- test/test.Wallet.js | 69 ++++++------------------------------ 4 files changed, 23 insertions(+), 70 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 39b89ccc9..cf2b60f4e 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -53,14 +53,9 @@ TxProposal.prototype.setSent = function(sentTxid) { }; TxProposal.fromObj = function(o, forceOpts) { - - console.log('[TxProposal.js.56]'); //TODO preconditions.checkArgument(o.builderObj); - - console.log('[TxProposal.js.59]'); //TODO delete o['builder']; - console.log('[TxProposal.js.62]'); //TODO try { // force opts is requested. for (var k in forceOpts) { @@ -69,19 +64,16 @@ TxProposal.fromObj = function(o, forceOpts) { o.builder = TransactionBuilder.fromObj(o.builderObj); } catch (e) { - console.log('[TxProposal.js.71]'); //TODO if (!o.version) { o.builder = new BuilderMockV0(o.builderObj); o.readonly = 1; }; } - console.log('[TxProposal.js.78]', o); //TODO var t = new TxProposal(o); t._check(); t._updateSignedBy(); - console.log('[TxProposal.js.78]'); //TODO return t; }; @@ -125,7 +117,6 @@ TxProposal._verifySignatures = function(inKeys, scriptSig, txSigHash) { }; TxProposal._infoFromRedeemScript = function(s) { - console.log('[TxProposal.js.127]',s.getBuffer().toString('hex')); //TODO var redeemScript = new Script(s.chunks[s.chunks.length - 1]); if (!redeemScript) throw new Error('Bad scriptSig (no redeemscript)'); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8ce22c0bb..8851e94ba 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -135,7 +135,6 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { try { mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts); } catch (e) { -console.log('[Wallet.js.141]',e); //TODO var corruptEvent = { type: 'corrupt', cId: senderId, diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 9456e841a..0a8ebfec7 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -198,8 +198,18 @@ describe('TxProposal', function() { (function() { txp._check();} ).should.throw('no ins'); txp.builder.tx.ins = backup; }); - it('FAIL signhash', function() { - sinon.stub(txp.builder.tx,'getHashType').returns(2); + it('FAIL signhash SINGLE', function() { + sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_SINGLE); + (function() { txp._check();} ).should.throw('signatures'); + txp.builder.tx.getHashType.restore(); + }); + it('FAIL signhash NONE', function() { + sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_NONE); + (function() { txp._check();} ).should.throw('signatures'); + txp.builder.tx.getHashType.restore(); + }); + it('FAIL signhash ANYONECANPAY', function() { + sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY); (function() { txp._check();} ).should.throw('signatures'); txp.builder.tx.getHashType.restore(); }); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 92258666a..144f323f5 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -1023,30 +1023,7 @@ describe('Wallet model', function() { }); describe('validate txProposals', function() { - var a1 = 'n1pKARYYUnZwxBuGj3y7WqVDu6VLN7n971'; - var a2 = 'mtxYYJXZJmQc2iJRHQ4RZkfxU5K7TE2qMJ'; - var utxos = [{ - address: a1, - txid: '2ac165fa7a3a2b535d106a0041c7568d03b531e58aeccdd3199d7289ab12cfc1', - vout: 1, - scriptPubKey: Address.getScriptPubKeyFor(a1).serialize().toString('hex'), - amount: 0.5, - confirmations: 200 - }, { - address: a2, - txid: '88c4520ffd97ea565578afe0b40919120be704b36561c71ba4e450e83cb3c9fd', - vout: 1, - scriptPubKey: Address.getScriptPubKeyFor(a2).serialize().toString('hex'), - amount: 0.5001, - confirmations: 200 - }]; - var destAddress = 'myuAQcCc1REUgXGsCTiYhZvPPc3XxZ36G1'; - var outs = [{ - address: destAddress, - amount: 1.0 - }]; - - var testValidate = function(signhash, result, done) { + var testValidate = function(shouldThrow, result, done) { var w = cachedCreateW(); var spy = sinon.spy(); w.on('txProposalEvent', spy); @@ -1054,50 +1031,26 @@ describe('Wallet model', function() { e.type.should.equal(result); done(); }); - var opts = {}; - opts.signhash = signhash; - var txb = new TransactionBuilder(opts) - .setUnspent(utxos) - .setOutputs(outs) - .sign(['cVBtNonMyTydnS3NnZyipbduXo9KZfF1aUZ3uQHcvJB6UARZbiWG', - 'cRVF68hhZp1PUQCdjr2k6aVYb2cn6uabbySDPBizAJ3PXF7vDXTL' - ]); var txp = { - 'txProposal': { - builderObj: txb.toObj(), - inputChainPaths: 'm/1', - creator: '1234', - createdTs: Date.now(), - } + 'txProposal': { dummy: 1} }; + var merge = sinon.stub(w.txProposals, 'mergeFromObj', function() { + if (shouldThrow) throw new Error(); + return {events: [{type:'new'}]}; + }); + w._handleTxProposal('senderID', txp, true); spy.callCount.should.equal(1); + merge.restore(); }; it('should validate for undefined', function(done) { var result = 'corrupt'; - var signhash; - testValidate(signhash, result, done); + testValidate(1, result, done); }); - it.only('should validate for SIGHASH_ALL', function(done) { + it('should validate for SIGHASH_ALL', function(done) { var result = 'new'; - var signhash = Transaction.SIGHASH_ALL; - testValidate(signhash, result, done); - }); - it('should not validate for different SIGHASH_NONE', function(done) { - var result = 'corrupt'; - var signhash = Transaction.SIGHASH_NONE; - testValidate(signhash, result, done); - }); - it('should not validate for different SIGHASH_SINGLE', function(done) { - var result = 'corrupt'; - var signhash = Transaction.SIGHASH_SINGLE; - testValidate(signhash, result, done); - }); - it('should not validate for different SIGHASH_ANYONECANPAY', function(done) { - var result = 'corrupt'; - var signhash = Transaction.SIGHASH_ANYONECANPAY; - testValidate(signhash, result, done); + testValidate(0, result, done); }); }); }); From 25631e641a2bcaf2cbd465b5e40975b6af214cae Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 1 Aug 2014 10:53:06 -0300 Subject: [PATCH 16/31] wallet test passings --- test/test.TxProposal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 0a8ebfec7..fa393c0da 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -43,7 +43,7 @@ describe('TxProposal', function() { var txp = new TxProposal({ creator: 1 }); - }).should.throw('Illegal Argument'); + }).should.throw('no inputChainPaths'); }); From d0c43f9bfb41e8ef7a4eb30b17fb17485281bb01 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 1 Aug 2014 11:24:16 -0300 Subject: [PATCH 17/31] ALL test passing --- js/models/core/TxProposal.js | 8 +++++--- js/models/core/WalletFactory.js | 8 +++----- test/test.TxProposal.js | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index cf2b60f4e..13e04c3b1 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -159,9 +159,11 @@ TxProposal.prototype._check = function() { if (!tx.ins.length) throw new Error('Invalid tx proposal: no ins'); - var scriptSig = this.builder.vanilla.scriptSig; - if (!scriptSig || !scriptSig.length) { - throw new Error('Invalid tx proposal: no signatures'); + for(var i in tx.ins){ + var scriptSig = tx.ins[i].s; + if (!scriptSig || !scriptSig.length) { + throw new Error('Invalid tx proposal: no signatures'); + } } for (var i = 0; i < tx.ins.length; i++) { diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 322975221..8858ab2f1 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -102,10 +102,7 @@ WalletFactory.prototype.read = function(walletId) { WalletFactory.prototype.create = function(opts) { opts = opts || {}; - this.log('### CREATING NEW WALLET.' + - (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + - (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey') - ); + this.log('### CREATING NEW WALLET.' + (opts.id ? ' USING ID: ' + opts.id : ' NEW ID') + (opts.privateKey ? ' USING PrivateKey: ' + opts.privateKey.getId() : ' NEW PrivateKey')); opts.privateKey = opts.privateKey || new PrivateKey({ networkName: this.networkName @@ -121,7 +118,8 @@ WalletFactory.prototype.create = function(opts) { }); opts.publicKeyRing.addCopayer( opts.privateKey.deriveBIP45Branch().extendedPublicKeyString(), - opts.nickname); + opts.nickname + ); this.log('\t### PublicKeyRing Initialized'); opts.txProposals = opts.txProposals || new TxProposalsSet({ diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index fa393c0da..ed0032b20 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -214,10 +214,10 @@ describe('TxProposal', function() { txp.builder.tx.getHashType.restore(); }); it('FAIL no signatures', function() { - var backup = txp.builder.vanilla.scriptSig; - txp.builder.vanilla.scriptSig = []; + var backup = txp.builder.tx.ins[0].s; + txp.builder.tx.ins[0].s = undefined; (function() { txp._check();} ).should.throw('no signatures'); - txp.builder.vanilla.scriptSig = backup; + txp.builder.tx.ins[0].s = backup; }); }); describe('#merge', function() { From c8917fccd6ed054d0800988f2d87aa62480894a8 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Fri, 1 Aug 2014 11:26:22 -0300 Subject: [PATCH 18/31] karma + mocha browser passing also --- js/models/core/TxProposal.js | 1 - 1 file changed, 1 deletion(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 13e04c3b1..cbebce268 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -173,7 +173,6 @@ TxProposal.prototype._check = function() { } }; - TxProposal.prototype.mergeBuilder = function(incoming) { var b0 = this.builder; var b1 = incoming.builder; From 966818c53ad1bb689174d3a85df19bb7ddf81176 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sat, 2 Aug 2014 20:51:31 -0300 Subject: [PATCH 19/31] add different toObj/fromObj fn for networking --- js/models/core/PublicKeyRing.js | 38 +++++--- js/models/core/TxProposal.js | 78 +++++++++++++---- js/models/core/TxProposals.js | 22 +---- js/models/core/Wallet.js | 149 +++++++++++++++++++++++--------- test/test.TxProposal.js | 2 - test/test.Wallet.js | 62 +++++++------ 6 files changed, 234 insertions(+), 117 deletions(-) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 823315ac9..c89c2f0ac 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -99,14 +99,6 @@ PublicKeyRing.prototype._checkKeys = function() { throw new Error('dont have required keys yet'); }; -PublicKeyRing.prototype._newExtendedPublicKey = function() { - return new PrivateKey({ - networkName: this.network.name - }) - .deriveBIP45Branch() - .extendedPublicKeyString(); -}; - PublicKeyRing.prototype._updateBip = function(index) { var hk = this.copayersHK[index].derive(HDPath.IdBranch); this.copayerIds[index] = hk.eckey.public.toString('hex'); @@ -125,6 +117,8 @@ PublicKeyRing.prototype.nicknameForCopayer = function(copayerId) { }; PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) { + preconditions.checkArgument(newEpk); + if (this.isComplete()) throw new Error('PKR already has all required key:' + this.totalCopayers); @@ -133,10 +127,6 @@ PublicKeyRing.prototype.addCopayer = function(newEpk, nickname) { throw new Error('PKR already has that key'); }); - if (!newEpk) { - newEpk = this._newExtendedPublicKey(); - } - var i = this.copayersHK.length; var bip = new HK(newEpk); this.copayersHK.push(bip); @@ -307,6 +297,30 @@ PublicKeyRing.prototype.forPaths = function(paths) { }; +PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { + + var inKeyMap = {}, ret = []; + for(var i in pubkeys ){ + inKeyMap[pubkeys[i]] = 1; + }; + + var keys = this.getForPaths(paths); + for(var i in keys ){ + for(var copayerIndex in keys[i] ){ + var kHex = keys[i][copayerIndex].toString('hex'); + if (inKeyMap[kHex]) { + ret.push(this.copayerIds[copayerIndex]); + delete inKeyMap[kHex]; + } + } + } + for(var i in inKeyMap) + throw new Error('Pubkey not identified') + + return ret; +}; + + // TODO this could be cached PublicKeyRing.prototype._addScriptMap = function(map, path) { var p = HDPath.indexesForPath(path); diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index cbebce268..da5181d0b 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -10,6 +10,9 @@ var Key = bitcore.Key; var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); +var VERSION = 1; +var CORE_FIELDS = ['builderObj','inputChainPaths', 'version']; + function TxProposal(opts) { preconditions.checkArgument(opts); @@ -17,22 +20,26 @@ function TxProposal(opts) { preconditions.checkArgument(opts.creator,'no creator'); preconditions.checkArgument(opts.createdTs,'no createdTs'); preconditions.checkArgument(opts.builder,'no builder'); + preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths'); - - this.creator = opts.creator; - this.createdTs = opts.createdTs; - this.builder = opts.builder; this.inputChainPaths = opts.inputChainPaths; - + this.version = opts.version; + this.builder = opts.builder; + this.createdTs = opts.createdTs; + this.createdTs = opts.createdTs; this._inputSignatures = []; - this.seenBy = opts.seenBy || {}; + + // CopayerIds + this.creator = opts.creator; this.signedBy = opts.signedBy || {}; + this.seenBy = opts.seenBy || {}; this.rejectedBy = opts.rejectedBy || {}; this.sentTs = opts.sentTs || null; this.sentTxid = opts.sentTxid || null; this.comment = opts.comment || null; this.readonly = opts.readonly || null; - // this._updateSignedBy(); + + this.sync(); } TxProposal.prototype.getId = function() { @@ -47,11 +54,24 @@ TxProposal.prototype.toObj = function() { }; -TxProposal.prototype.setSent = function(sentTxid) { - this.sentTxid = sentTxid; - this.sentTs = Date.now(); +TxProposal.prototype.toObjForNetwork = function() { + var o = this.toObj; + + var newOutput = {}; + CORE_FIELDS.forEach(function(k){ + newOutput[k] = o[k]; + }); + return newOutput; }; +TxProposal.prototype.sync = function() { + this._check(); + this._updateSignedBy(); + return this; +} + + +// fromObj => from a trusted source TxProposal.fromObj = function(o, forceOpts) { preconditions.checkArgument(o.builderObj); delete o['builder']; @@ -64,17 +84,24 @@ TxProposal.fromObj = function(o, forceOpts) { o.builder = TransactionBuilder.fromObj(o.builderObj); } catch (e) { + // backwards (V0) compatatibility fix. if (!o.version) { o.builder = new BuilderMockV0(o.builderObj); o.readonly = 1; }; } + return new TxProposal(o); +}; - var t = new TxProposal(o); - t._check(); - t._updateSignedBy(); +TxProposal.fromObjUntrusted = function(o, forceOpts, senderId) { + var newInput = {}; + CORE_FIELDS.forEach(function(k){ + newInput[k] = o[k]; + }); + if (newInput.version !== VERSION) + throw new Error('Peer using different version'); - return t; + return TxProposal.fromObj(newInput, forceOpts, senderId); }; @@ -144,7 +171,8 @@ TxProposal.prototype._updateSignedBy = function() { if (signatureIndexes.length !== signatureCount) throw new Error('Invalid signature'); this._inputSignatures[i] = signatureIndexes.map(function(i) { - return info.keys[i].toString('hex'); + var r = info.keys[i].toString('hex'); + return r; }); }; }; @@ -184,6 +212,21 @@ TxProposal.prototype.mergeBuilder = function(incoming) { }; +TxProposal.prototype.setSeen = function(copayerId) { + if (!this.seenBy[copayerId]) + this.seenBy[copayerId] = Date.now(); +}; + +TxProposal.prototype.setRejected = function(copayerId) { + if (!this.rejectedBy[copayerId] && !this.signedBy) + this.rejectedBy[copayerId] = Date.now(); +}; + +TxProposal.prototype.setSent = function(sentTxid) { + this.sentTxid = sentTxid; + this.sentTs = Date.now(); +}; + /* OTDO events.push({ type: 'seen', @@ -213,12 +256,11 @@ TxProposal.prototype._allSignatures = function() { return ret; }; +// merge will not merge any metadata. TxProposal.prototype.merge = function(incoming) { var ret = {}; var newSignatures = []; - - incoming._check(); - incoming._updateSignedBy(); + incoming.sync(); var prevInputSignatures = this._allSignatures(); diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 5565cac77..71cfb443b 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -20,6 +20,7 @@ function TxProposals(opts) { this.txps = {}; } +// fromObj => from a trusted source TxProposals.fromObj = function(o, forceOpts) { var ret = new TxProposals({ networkName: o.networkName, @@ -60,8 +61,6 @@ TxProposals.prototype.merge = function(inTxp, allowedPubKeys) { var ntxid = inTxp.getId(); var ret = {}; - ret.events = []; - ret.events.hasChanged = false; if (myTxps[ntxid]) { var v0 = myTxps[ntxid]; @@ -70,12 +69,7 @@ TxProposals.prototype.merge = function(inTxp, allowedPubKeys) { } else { this.txps[ntxid] = inTxp; - ret.hasChanged = true; - ret.events.push({ - type: 'new', - cid: inTxp.creator, - tx: ntxid - }); + ret.new = 1; } return ret; }; @@ -88,22 +82,14 @@ TxProposals.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opt }; - - // Add a LOCALLY CREATED (trusted) tx proposal -TxProposals.prototype.add = function(data) { - var txp = new TxProposal(data); +TxProposals.prototype.add = function(txp) { + txp.sync(); var ntxid = txp.getId(); this.txps[ntxid] = txp; return ntxid; }; -TxProposals.prototype.setSent = function(ntxid, txid) { - //sent TxProposals are local an not broadcasted. - this.txps[ntxid].setSent(txid); -}; - - TxProposals.prototype.getTxProposal = function(ntxid, copayers) { var txp = this.txps[ntxid]; var i = JSON.parse(JSON.stringify(txp)); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8851e94ba..3f7583643 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -17,6 +17,7 @@ var Address = bitcore.Address; var HDParams = require('./HDParams'); var PublicKeyRing = require('./PublicKeyRing'); +var TxProposal = require('./TxProposal'); var TxProposals = require('./TxProposals'); var PrivateKey = require('./PrivateKey'); var copayConfig = require('../../../config'); @@ -129,11 +130,39 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { }; +Wallet.prototype._processProposalEvents = function(mergeInfo) { + var ev = []; + if (mergeInfo.new) { + ev = { + type: 'new', + cid: senderId + } + } else { + for (var i in mergeInfo.newCopayers) { + var copayerId = mergeInfo.newCopayers[i]; + ev.push({ + type: 'signed', + cid: copayerId + }); + } + } + if (ev) + this.emit('txProposalEvent',ev); +}; + + Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL: ', data); - var mergeInfo; + var mergeInfo, ntxid; + try { mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts); + mergeInfo.newCopayers=[]; + for (var i in mergeInfo.newSignatures) { + var k = mergeInfo.newSignatures[i]; + mergeInfo.newCopayers.push(this.getCopayerIdFromPubKey(k)); + }; + ntxid = mergeInfo.inTxp.getId(); } catch (e) { var corruptEvent = { type: 'corrupt', @@ -143,21 +172,36 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { this.emit('txProposalEvent', corruptEvent); return; } + this.sendSeen(ntxid); - var added = this.addSeenToTxProposals(); - if (added) { - this.log('### BROADCASTING txProposals with my seenBy updated.'); - this.sendTxProposal(mergeInfo.inTxp.getId()); - } + if (mergeInfo.hasChanged) + this.sendTxProposal(ntxid); this.emit('txProposalsUpdated'); this.store(); - - for (var i = 0; i < mergeInfo.events.length; i++) { - this.emit('txProposalEvent', mergeInfo.events[i]); - } + this._processProposalEvents(senderId, mergeInfo); }; + +Wallet.prototype._handleReject = function(senderId, data, isInbound) { + this.log('RECV REJECT:', data); + // TODO check that has not signed. + // + this.txProposals.txps[data.ntxid].setRejected(senderId); + this.emit('txProposalsUpdated'); + this.store(); + +}; + +Wallet.prototype._handleSeen = function(senderId, data, isInbound) { + this.log('RECV SEEN:', data); + this.txProposals.txps[data.ntxid].setSeen(senderId); + this.emit('txProposalsUpdated'); + this.store(); +}; + + + Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) { this.log('RECV ADDRESSBOOK:', data); var rcv = data.addressBook; @@ -199,6 +243,10 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { case 'publicKeyRing': this._handlePublicKeyRing(senderId, data, isInbound); break; + case 'reject': + this._handleReject(senderId, data, isInbound); + case 'seen': + this._handleReject(senderId, data, isInbound); case 'txProposal': this._handleTxProposal(senderId, data, isInbound); break; @@ -381,6 +429,7 @@ Wallet.prototype.toObj = function() { return walletObj; }; +// fromObj => from a trusted source Wallet.fromObj = function(o, storage, network, blockchain) { var opts = JSON.parse(JSON.stringify(o.opts)); opts.addressBook = o.addressBook; @@ -424,6 +473,26 @@ Wallet.prototype.sendTxProposal = function(ntxid, recipients) { }); }; +Wallet.prototype.sendSeen = function(ntxid) { + preconditions.checkArgument(ntxid); + this.log('### SENDING seen: ' + ntxid + ' TO: All'); + this.send(null, { + type: 'seen', + ntxid: ntxid, + walletId: this.id, + }); +}; + +Wallet.prototype.sendReject = function(ntxid) { + preconditions.checkArgument(ntxid); + this.log('### SENDING reject: ' + ntxid + ' TO: All'); + this.send(null, { + type: 'reject', + ntxid: ntxid, + walletId: this.id, + }); +}; + Wallet.prototype.sendWalletReady = function(recipients) { this.log('### SENDING WalletReady TO:', recipients); @@ -521,7 +590,7 @@ Wallet.prototype.reject = function(ntxid) { } txp.rejectedBy[myId] = Date.now(); - this.sendTxProposal(ntxid); + this.sendReject(ntxid); this.store(); this.emit('txProposalsUpdated'); }; @@ -534,10 +603,10 @@ Wallet.prototype.sign = function(ntxid, cb) { setTimeout(function() { var myId = self.getMyCopayerId(); var txp = self.txProposals.txps[ntxid]; - if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { - if (cb) cb(false); - } - + // if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { + // if (cb) cb(false); + // } + // var keys = self.privateKey.getForPaths(txp.inputChainPaths); var b = txp.builder; @@ -574,7 +643,7 @@ Wallet.prototype.sendTx = function(ntxid, cb) { this.blockchain.sendRawTransaction(txHex, function(txid) { self.log('BITCOIND txid:', txid); if (txid) { - self.txProposals.setSent(ntxid, txid); + self.txProposals.txps[ntxid].setSent(txid); self.sendTxProposal(ntxid); self.store(); } @@ -582,20 +651,20 @@ Wallet.prototype.sendTx = function(ntxid, cb) { }); }; -Wallet.prototype.addSeenToTxProposals = function() { - var ret = false; - var myId = this.getMyCopayerId(); - - for (var k in this.txProposals.txps) { - var txp = this.txProposals.txps[k]; - if (!txp.seenBy[myId]) { - - txp.seenBy[myId] = Date.now(); - ret = true; - } - } - return ret; -}; +// Wallet.prototype.addSeenToTxProposals = function() { +// var ret = false; +// var myId = this.getMyCopayerId(); +// +// for (var k in this.txProposals.txps) { +// var txp = this.txProposals.txps[k]; +// if (!txp.seenBy[myId]) { +// +// txp.seenBy[myId] = Date.now(); +// ret = true; +// } +// } +// return ret; +// }; // TODO: remove this method and use getAddressesInfo everywhere Wallet.prototype.getAddresses = function(opts) { @@ -718,6 +787,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName()); preconditions.checkState(pkr.isComplete()); + preconditions.checkState(priv); if (comment) preconditions.checkArgument(comment.length <= 100); if (!opts.remainderOut) { @@ -744,22 +814,23 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos b = b.setHashToScriptMap(pkr.getRedeemScriptMap(inputChainPaths)); - if (priv) { - var keys = priv.getForPaths(inputChainPaths); - var signed = b.sign(keys); - } + var keys = priv.getForPaths(inputChainPaths); + var signed = b.sign(keys); var myId = this.getMyCopayerId(); var now = Date.now(); - var me = {}; var tx = b.build(); - if (priv && tx.countInputSignatures(0)) me[myId] = now; + if (!tx.countInputSignatures(0)) + throw new Error ('Could not sign generated tx'); + + var me = {}; + me[myId] = now; var meSeen = {}; if (priv) meSeen[myId] = now; - var data = { + var ntxid = this.txProposals.add(new TxProposal({ inputChainPaths: inputChainPaths, signedBy: me, seenBy: meSeen, @@ -767,9 +838,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos createdTs: now, builder: b, comment: comment - }; - - var ntxid = this.txProposals.add(data); + })); return ntxid; }; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index ed0032b20..79435a318 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -182,8 +182,6 @@ describe('TxProposal', function() { }); it('#_updateSignedBy', function() { var txp = dummyProposal; - txp._inputSignatures.should.deep.equal([]); - txp._updateSignedBy(); txp._inputSignatures.should.deep.equal([[ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3' ]]); }); describe('#_check', function() { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 144f323f5..91e2a2573 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -10,7 +10,7 @@ try { } var copayConfig = require('../config'); var Wallet = require('../js/models/core/Wallet'); -var Structure = copay.Structure; +var PrivateKey = copay.PrivateKey; var Storage = require('./mocks/FakeStorage'); var Network = require('./mocks/FakeNetwork'); var Blockchain = require('./mocks/FakeBlockchain'); @@ -19,22 +19,30 @@ var TransactionBuilder = bitcore.TransactionBuilder; var Transaction = bitcore.Transaction; var Address = bitcore.Address; +var config = { + requiredCopayers: 3, + totalCopayers: 5, + spendUnconfirmed: true, + reconnectDelay: 100, + networkName: 'testnet', +}; + +var getNewEpk = function() { + return new PrivateKey({ + networkName: config.networkName, + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); +} + var addCopayers = function(w) { for (var i = 0; i < 4; i++) { - w.publicKeyRing.addCopayer(); + w.publicKeyRing.addCopayer(getNewEpk()); } }; describe('Wallet model', function() { - var config = { - requiredCopayers: 3, - totalCopayers: 5, - spendUnconfirmed: true, - reconnectDelay: 100, - networkName: 'testnet', - }; - it('should fail to create an instance', function() { (function() { new Wallet(config) @@ -47,12 +55,11 @@ describe('Wallet model', function() { }); - var createW = function(netKey, N, conf) { + var createW = function(N, conf) { var c = JSON.parse(JSON.stringify(conf || config)); if (!N) N = c.totalCopayers; - if (netKey) c.netKey = netKey; var mainPrivateKey = new copay.PrivateKey({ networkName: config.networkName }); @@ -148,8 +155,7 @@ describe('Wallet model', function() { var createW2 = function(privateKeys, N, conf) { if (!N) N = 3; - var netKey = 'T0FbU2JLby0='; - var w = createW(netKey, N, conf); + var w = createW(N, conf); should.exist(w); var pkr = w.publicKeyRing; @@ -157,9 +163,9 @@ describe('Wallet model', function() { for (var i = 0; i < N - 1; i++) { if (privateKeys) { var k = privateKeys[i]; - pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : null); + pkr.addCopayer(k ? k.deriveBIP45Branch().extendedPublicKeyString() : getNewEpk()); } else { - pkr.addCopayer(); + pkr.addCopayer(getNewEpk()); } } @@ -212,12 +218,12 @@ describe('Wallet model', function() { var t = w.txProposals; var txp = t.txps[ntxid]; + Object.keys(txp._inputSignatures).length.should.equal(1); var tx = txp.builder.build(); should.exist(tx); chai.expect(txp.comment).to.be.null; tx.isComplete().should.equal(false); Object.keys(txp.seenBy).length.should.equal(1); - Object.keys(txp.signedBy).length.should.equal(1); }); it('#create with comment', function() { @@ -502,7 +508,8 @@ describe('Wallet model', function() { var w = createW(); var r = w.getRegisteredCopayerIds(); r.length.should.equal(1); - w.publicKeyRing.addCopayer(); + w.publicKeyRing.addCopayer(getNewEpk()); + r = w.getRegisteredCopayerIds(); r.length.should.equal(2); r[0].should.not.equal(r[1]); @@ -512,7 +519,7 @@ describe('Wallet model', function() { var w = createW(); var r = w.getRegisteredPeerIds(); r.length.should.equal(1); - w.publicKeyRing.addCopayer(); + w.publicKeyRing.addCopayer(getNewEpk()); r = w.getRegisteredPeerIds(); r.length.should.equal(2); r[0].should.not.equal(r[1]); @@ -642,10 +649,11 @@ describe('Wallet model', function() { }); }); it('should create & sign transaction from received funds', function(done) { - this.timeout(10000); - var w = cachedCreateW2(); - var pk = w.privateKey; - w.privateKey = null; + var k2 = new PrivateKey({ + networkName: config.networkName + }); + + var w = createW2([k2]); var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { @@ -654,7 +662,7 @@ describe('Wallet model', function() { w.getTxProposals()[0].rejectedByUs.should.equal(false); done(); }); - w.privateKey = pk; + w.privateKey = k2; w.sign(ntxid, function(success) { success.should.equal(true); }); @@ -1031,9 +1039,9 @@ describe('Wallet model', function() { e.type.should.equal(result); done(); }); - var txp = { - 'txProposal': { dummy: 1} - }; + var txp = {dummy:1}; + // txp.prototype.getId = function() {return 'aa'}; + var txp = { 'txProposal': txp }; var merge = sinon.stub(w.txProposals, 'mergeFromObj', function() { if (shouldThrow) throw new Error(); return {events: [{type:'new'}]}; From 753b89065887d1cb34a2652a12dfe5d02780400f Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 3 Aug 2014 22:34:47 -0300 Subject: [PATCH 20/31] txp* test passing --- js/models/core/PublicKeyRing.js | 5 +- js/models/core/TxProposal.js | 243 +++++++++++++++++--------------- js/models/core/TxProposals.js | 27 ++-- js/models/core/Wallet.js | 137 ++++++++++++------ test/test.TxProposal.js | 206 +++++++++++++++++++++++---- 5 files changed, 421 insertions(+), 197 deletions(-) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index c89c2f0ac..09a38d903 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -297,9 +297,10 @@ PublicKeyRing.prototype.forPaths = function(paths) { }; +// returns pubkey -> copayerId. PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { - var inKeyMap = {}, ret = []; + var inKeyMap = {}, ret = {}; for(var i in pubkeys ){ inKeyMap[pubkeys[i]] = 1; }; @@ -309,7 +310,7 @@ PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { for(var copayerIndex in keys[i] ){ var kHex = keys[i][copayerIndex].toString('hex'); if (inKeyMap[kHex]) { - ret.push(this.copayerIds[copayerIndex]); + ret[kHex] =this.copayerIds[copayerIndex]; delete inKeyMap[kHex]; } } diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index da5181d0b..38fd61a43 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -11,16 +11,16 @@ var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); var VERSION = 1; -var CORE_FIELDS = ['builderObj','inputChainPaths', 'version']; +var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version']; function TxProposal(opts) { preconditions.checkArgument(opts); - preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths'); - preconditions.checkArgument(opts.creator,'no creator'); - preconditions.checkArgument(opts.createdTs,'no createdTs'); - preconditions.checkArgument(opts.builder,'no builder'); - preconditions.checkArgument(opts.inputChainPaths,'no inputChainPaths'); + preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); + preconditions.checkArgument(opts.creator, 'no creator'); + preconditions.checkArgument(opts.createdTs, 'no createdTs'); + preconditions.checkArgument(opts.builder, 'no builder'); + preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); this.inputChainPaths = opts.inputChainPaths; this.version = opts.version; @@ -38,11 +38,63 @@ function TxProposal(opts) { this.sentTxid = opts.sentTxid || null; this.comment = opts.comment || null; this.readonly = opts.readonly || null; - - this.sync(); + this._sync(); } + +TxProposal.prototype._check = function() { + + if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { + throw new Error('Invalid tx proposal'); + } + + var tx = this.builder.build(); + if (!tx.ins.length) + throw new Error('Invalid tx proposal: no ins'); + + for (var i in tx.ins) { + var scriptSig = tx.ins[i].s; + if (!scriptSig || !scriptSig.length) { + throw new Error('Invalid tx proposal: no signatures'); + } + } + + for (var i = 0; i < tx.ins.length; i++) { + var hashType = tx.getHashType(i); + if (hashType && hashType !== Transaction.SIGHASH_ALL) + throw new Error('Invalid tx proposal: bad signatures'); + } +}; + + +TxProposal.prototype._updateSignedBy = function() { + this._inputSignatures = []; + + 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) + throw new Error('Invalid signature'); + this._inputSignatures[i] = signatureIndexes.map(function(i) { + var r = info.keys[i].toString('hex'); + return r; + }); + }; +}; + +TxProposal.prototype._sync = function() { + this._check(); + this._updateSignedBy(); + return this; +} + + TxProposal.prototype.getId = function() { + preconditions.checkState(this.builder); return this.builder.build().getNormalizedHash().toString('hex'); }; @@ -54,23 +106,15 @@ TxProposal.prototype.toObj = function() { }; -TxProposal.prototype.toObjForNetwork = function() { - var o = this.toObj; - - var newOutput = {}; - CORE_FIELDS.forEach(function(k){ - newOutput[k] = o[k]; +TxProposal.trim = function() { + var o = this.toObj(); + var ret = {}; + CORE_FIELDS.forEach(function(k) { + ret[k] = o[k]; }); - return newOutput; + return ret; }; -TxProposal.prototype.sync = function() { - this._check(); - this._updateSignedBy(); - return this; -} - - // fromObj => from a trusted source TxProposal.fromObj = function(o, forceOpts) { preconditions.checkArgument(o.builderObj); @@ -93,17 +137,6 @@ TxProposal.fromObj = function(o, forceOpts) { return new TxProposal(o); }; -TxProposal.fromObjUntrusted = function(o, forceOpts, senderId) { - var newInput = {}; - CORE_FIELDS.forEach(function(k){ - newInput[k] = o[k]; - }); - if (newInput.version !== VERSION) - throw new Error('Peer using different version'); - - return TxProposal.fromObj(newInput, forceOpts, senderId); -}; - TxProposal._formatKeys = function(keys) { @@ -158,49 +191,6 @@ TxProposal._infoFromRedeemScript = function(s) { }; }; -TxProposal.prototype._updateSignedBy = function() { - this._inputSignatures = []; - - 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) - throw new Error('Invalid signature'); - this._inputSignatures[i] = signatureIndexes.map(function(i) { - var r = info.keys[i].toString('hex'); - return r; - }); - }; -}; - -TxProposal.prototype._check = function() { - - if (this.builder.signhash && this.builder.signhash !== Transaction.SIGHASH_ALL) { - throw new Error('Invalid tx proposal'); - } - - var tx = this.builder.build(); - if (!tx.ins.length) - throw new Error('Invalid tx proposal: no ins'); - - for(var i in tx.ins){ - var scriptSig = tx.ins[i].s; - if (!scriptSig || !scriptSig.length) { - throw new Error('Invalid tx proposal: no signatures'); - } - } - - for (var i = 0; i < tx.ins.length; i++) { - var hashType = tx.getHashType(i); - if (hashType && hashType !== Transaction.SIGHASH_ALL) - throw new Error('Invalid tx proposal: bad signatures'); - } -}; - TxProposal.prototype.mergeBuilder = function(incoming) { var b0 = this.builder; var b1 = incoming.builder; @@ -213,12 +203,12 @@ TxProposal.prototype.mergeBuilder = function(incoming) { TxProposal.prototype.setSeen = function(copayerId) { - if (!this.seenBy[copayerId]) + if (!this.seenBy[copayerId]) this.seenBy[copayerId] = Date.now(); }; TxProposal.prototype.setRejected = function(copayerId) { - if (!this.rejectedBy[copayerId] && !this.signedBy) + if (!this.rejectedBy[copayerId] && !this.signedBy) this.rejectedBy[copayerId] = Date.now(); }; @@ -227,55 +217,78 @@ TxProposal.prototype.setSent = function(sentTxid) { this.sentTs = Date.now(); }; -/* OTDO - events.push({ -type: 'seen', -cId: k, -txId: ntxid -}); -events.push({ -type: 'signed', -cId: k, -txId: ntxid -}); -events.push({ -type: 'rejected', -cId: k, -txId: ntxid -}); -ret.events = this.mergeMetadata(incoming); -*/ TxProposal.prototype._allSignatures = function() { var ret = {}; - for(var i in this._inputSignatures) + for (var i in this._inputSignatures) for (var j in this._inputSignatures[i]) ret[this._inputSignatures[i][j]] = true; return ret; }; + +TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { + var newCopayers = {}, + oldCopayers = {}, newSignedBy = {}, readOnlyPeers = {}, isNew = 1; + + for(var k in this.signedBy) { + oldCopayers[k] = 1; + isNew = 0; + }; + + if (isNew == 0 && (!this.creator || !this.createdTs)) + throw new Error('Existing TX has no creator'); + + if (isNew == 0 && (!this.signedBy[this.creator])) + throw new Error('Existing TX is not signed by creator'); + + var iSig = this._inputSignatures[0]; + for(var i in iSig){ + var copayerId = keyMap[iSig[i]]; + if (!copayerId) + throw new Error('Found unknown signature') + + if (oldCopayers[copayerId]) { + //Already have it. Do nothing + } else { + newCopayers[copayerId] = Date.now(); + delete oldCopayers[i]; + } + } + + if (!newCopayers[senderId] && !readOnlyPeers[senderId]) + throw new Error('TX must have a (new) senders signature') + + if (isNew && Object.keys(newCopayers).length>1) + throw new Error('New TX must have only 1 signature'); + + // Handler creator / createdTs. + // from senderId, and must be signed by senderId + if (isNew) { + this.creator = Object.keys(newCopayers)[0]; + this.createdTs = Date.now(); + } + + //Ended. Update this. + for(var i in newCopayers) { + this.signedBy[i] = newCopayers[i]; + } + + // signedBy has preference over rejectedBy + for(var i in this.signedBy) { + delete this.rejectedBy[i]; + } + + return Object.keys(newCopayers); +}; + // merge will not merge any metadata. TxProposal.prototype.merge = function(incoming) { - var ret = {}; - var newSignatures = []; - incoming.sync(); - - var prevInputSignatures = this._allSignatures(); - - ret.hasChanged = this.mergeBuilder(incoming); - this._updateSignedBy(); - - if (ret.hasChanged) - for(var i in this._inputSignatures) - for (var j in this._inputSignatures[i]) - if (!prevInputSignatures[this._inputSignatures[i][j]]) - newSignatures.push(this._inputSignatures[i][j]); - - ret.newSignatures = newSignatures; - - return ret; + var hasChanged = this.mergeBuilder(incoming); + this._sync(); + return hasChanged; }; //This should be on bitcore / Transaction diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 71cfb443b..e12c0b9db 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -56,21 +56,30 @@ TxProposals.prototype.toObj = function() { }; -TxProposals.prototype.merge = function(inTxp, allowedPubKeys) { - var myTxps = this.txps; +TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, builderOpts) { + var safeObj = inObj.trimUntrustedObj(); + var incomingTx = TxProposal.fromObj(safeObj, builderOpts); + incomingTx._sync(); + var myTxps = this.txps; var ntxid = inTxp.getId(); - var ret = {}; + var ret = { + ntxid: ntxid + }; if (myTxps[ntxid]) { - var v0 = myTxps[ntxid]; - var v1 = inTxp; - ret = v0.merge(v1, allowedPubKeys); - } - else { - this.txps[ntxid] = inTxp; + + // Merge an existing txProposal + ret.hasChanged = myTxps[ntxid].merge(inTxp, allowedPubKeys); + + + } else { + // Create a new one ret.new = 1; + this.txps[ntxid] = inTxp; } + + ret.txp = this.txps[ntxid]; return ret; }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3f7583643..8646d5924 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -59,7 +59,7 @@ function Wallet(opts) { } -Wallet.builderOpts = { +Wallet.builderOpts = { lockTime: null, signhash: bitcore.Transaction.SIGNHASH_ALL, fee: null, @@ -132,72 +132,119 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { Wallet.prototype._processProposalEvents = function(mergeInfo) { var ev = []; - if (mergeInfo.new) { - ev = { - type: 'new', - cid: senderId + if (mergeInfo) { + if (mergeInfo.new) { + ev = { + type: 'new', + cid: senderId + } + } else { + for (var i in mergeInfo.newCopayers) { + var copayerId = mergeInfo.newCopayers[i]; + ev.push({ + type: 'signed', + cid: copayerId + }); + } } } else { - for (var i in mergeInfo.newCopayers) { - var copayerId = mergeInfo.newCopayers[i]; - ev.push({ - type: 'signed', - cid: copayerId - }); - } + ev = { + type: 'corrupt', + cId: senderId, + error: e, + }; + } + if (ev) + this.emit('txProposalEvent', ev); +}; + + + +/* OTDO + events.push({ +type: 'signed', +cId: k, +txId: ntxid +}); +*/ +Wallet.prototype._getKeyMap = function(tpx, senderId) { + + this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.paths); + + var keyMapStr = JSON.stringify(keyMap); + // All inputs must be signed with the same copayers + for (var i in m.txp._inputSignatures) { + if (!i) continue; + var inputKeyMapStr = JSON.stringify( + this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[i], txp.paths)); + + if (inputKeyMapStr !== keyMapStr) + throw new Error('found inputs with different signatures in Tx from:' + senderId); } - if (ev) - this.emit('txProposalEvent',ev); }; Wallet.prototype._handleTxProposal = function(senderId, data) { this.log('RECV TXPROPOSAL: ', data); - var mergeInfo, ntxid; + var m; try { - mergeInfo = this.txProposals.mergeFromObj(data.txProposal, senderId, Wallet.builderOpts); - mergeInfo.newCopayers=[]; - for (var i in mergeInfo.newSignatures) { - var k = mergeInfo.newSignatures[i]; - mergeInfo.newCopayers.push(this.getCopayerIdFromPubKey(k)); - }; - ntxid = mergeInfo.inTxp.getId(); + m = this.txProposals.mergeObj(senderId, data.txProposal, Wallet.builderOpts); + + var keyMap = this._getKeyMap(m.tpx,senderId); + ret.newCopayers = m.txp.setCopayers(senderId, keyMap); + } catch (e) { - var corruptEvent = { - type: 'corrupt', - cId: senderId, - error: e, - }; - this.emit('txProposalEvent', corruptEvent); - return; + this.log('Corrupt TX proposal received', senderId, e); //TODO } - this.sendSeen(ntxid); - if (mergeInfo.hasChanged) - this.sendTxProposal(ntxid); + if (m) { + this.emit('txProposalsUpdated'); + this.store(); - this.emit('txProposalsUpdated'); - this.store(); - this._processProposalEvents(senderId, mergeInfo); + this.sendSeen(m.ntxid); + + if (m.hasChanged) + this.sendTxProposal(m.ntxid); + } + + this._processProposalEvents(senderId, m); }; Wallet.prototype._handleReject = function(senderId, data, isInbound) { this.log('RECV REJECT:', data); - // TODO check that has not signed. - // - this.txProposals.txps[data.ntxid].setRejected(senderId); - this.emit('txProposalsUpdated'); + + var txp = this.txProposals.txps[data.ntxid]; + + if (!txp) + throw new Error('Received Reject for an unkwown TX from:' + senderId); + + if (txp.signedBy[senderId]) + throw new Error('Received Reject for an already signed TX from:' + senderId); + + txp.setRejected(senderId); this.store(); + this.emit('txProposalsUpdated'); + this.emit('txProposalEvent', { + type: 'rejected', + cId: senderId, + txId: data.ntxid, + }); }; Wallet.prototype._handleSeen = function(senderId, data, isInbound) { this.log('RECV SEEN:', data); this.txProposals.txps[data.ntxid].setSeen(senderId); - this.emit('txProposalsUpdated'); this.store(); + this.emit('txProposalsUpdated'); + this.emit('txProposalEvent', { + type: 'seen', + cId: senderId, + txId: data.ntxid, + }); + }; @@ -245,8 +292,10 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { break; case 'reject': this._handleReject(senderId, data, isInbound); + break; case 'seen': - this._handleReject(senderId, data, isInbound); + this._handleSeen(senderId, data, isInbound); + break; case 'txProposal': this._handleTxProposal(senderId, data, isInbound); break; @@ -796,7 +845,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos }; } - for (var k in Wallet.builderOpts){ + for (var k in Wallet.builderOpts) { opts[k] = Wallet.builderOpts[k]; } @@ -821,8 +870,8 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos var tx = b.build(); - if (!tx.countInputSignatures(0)) - throw new Error ('Could not sign generated tx'); + if (!tx.countInputSignatures(0)) + throw new Error('Could not sign generated tx'); var me = {}; me[myId] = now; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 79435a318..33c671519 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -161,28 +161,30 @@ describe('TxProposal', function() { }).should.throw('script'); }); it('#_verifyScriptSig, no signatures', function() { - var ret = TxProposal._verifySignatures( keyBuf, validScriptSig, new Buffer(32)); + var ret = TxProposal._verifySignatures(keyBuf, validScriptSig, new Buffer(32)); ret.length.should.equal(0); }); it('#_verifyScriptSig, two signatures', function() { // Data taken from bitcore's TransactionBuilder test var txp = dummyProposal; var tx = dummyProposal.builder.build(); - var ret = TxProposal._verifySignatures(pubkeys,validScriptSig, tx.hashForSignature()); + var ret = TxProposal._verifySignatures(pubkeys, validScriptSig, tx.hashForSignature()); ret.should.deep.equal([0, 3]); }); it('#_infoFromRedeemScript', function() { var info = TxProposal._infoFromRedeemScript(validScriptSig); var keys = info.keys; keys.length.should.equal(5); - for(var i in keys){ + for (var i in keys) { keys[i].toString('hex').should.equal(pubkeys[i].toString('hex')); } Buffer.isBuffer(info.script.getBuffer()).should.equal(true); }); it('#_updateSignedBy', function() { var txp = dummyProposal; - txp._inputSignatures.should.deep.equal([[ '03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3' ]]); + txp._inputSignatures.should.deep.equal([ + ['03197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d', '03a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e3'] + ]); }); describe('#_check', function() { var txp = dummyProposal; @@ -193,28 +195,38 @@ describe('TxProposal', function() { }); it('FAIL ins', function() { txp.builder.tx.ins = []; - (function() { txp._check();} ).should.throw('no ins'); + (function() { + txp._check(); + }).should.throw('no ins'); txp.builder.tx.ins = backup; }); it('FAIL signhash SINGLE', function() { - sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_SINGLE); - (function() { txp._check();} ).should.throw('signatures'); + sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_SINGLE); + (function() { + txp._check(); + }).should.throw('signatures'); txp.builder.tx.getHashType.restore(); }); it('FAIL signhash NONE', function() { - sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_NONE); - (function() { txp._check();} ).should.throw('signatures'); + sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_NONE); + (function() { + txp._check(); + }).should.throw('signatures'); txp.builder.tx.getHashType.restore(); }); it('FAIL signhash ANYONECANPAY', function() { - sinon.stub(txp.builder.tx,'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY); - (function() { txp._check();} ).should.throw('signatures'); + sinon.stub(txp.builder.tx, 'getHashType').returns(Transaction.SIGHASH_ANYONECANPAY); + (function() { + txp._check(); + }).should.throw('signatures'); txp.builder.tx.getHashType.restore(); }); it('FAIL no signatures', function() { var backup = txp.builder.tx.ins[0].s; txp.builder.tx.ins[0].s = undefined; - (function() { txp._check();} ).should.throw('no signatures'); + (function() { + txp._check(); + }).should.throw('no signatures'); txp.builder.tx.ins[0].s = backup; }); }); @@ -222,39 +234,179 @@ describe('TxProposal', function() { var txp = dummyProposal; var backup = txp.builder.tx.ins; it('with self', function() { - var ret = txp.merge(txp); - ret.newSignatures.length.should.equal(0); - ret.hasChanged.should.equal(false); + var hasChanged = txp.merge(txp); + hasChanged.should.equal(false); }); it('with less signatures', function() { var backup = txp.builder.vanilla.scriptSig[0]; txp.builder.merge = function() { // 3 signatures. - this.vanilla.scriptSig=['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; - this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex'); + this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex'); }; - var ret = txp.merge(txp); - ret.hasChanged.should.equal(true); - ret.newSignatures.length.should.equal(0); + var hasChanged = txp.merge(txp); + hasChanged.should.equal(true); txp.builder.vanilla.scriptSig = [backup]; - txp.builder.tx.ins[0].s = new Buffer(backup,'hex'); + txp.builder.tx.ins[0].s = new Buffer(backup, 'hex'); }); it('with more signatures', function() { txp.builder.merge = function() { // 3 signatures. - this.vanilla.scriptSig=['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; - this.tx.ins[0].s=new Buffer(this.vanilla.scriptSig[0],'hex'); + this.vanilla.scriptSig = ['00483045022100f75bd3eb92d8c9be9a94d848bbd1985fc0eaf4c47fb470a0b222881802a1f03802204eb239ae3082779b1ec4f2e69baa0362494071e707e1696c14ad23c8f2e184e20148304502201981482db0f369ce943293b6fec06a0347918663c766a79d4cbd0457801768d1022100aedf8d7c51d55a9ddbdcc0067ed6b648b77ce9660447bbcf4e2c209698efa0a30148304502203f0ddad47757f8705cb40e7c706590d2e2028a7027ffdb26dd208fd6155e0d28022100ccd206f9b969ab7f88ee4c5c6cee48c800a62dda024c5a8de7eb8612b833a0c0014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; + this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex'); }; - var ret = txp.merge(txp); - ret.hasChanged.should.equal(true); - ret.newSignatures.length.should.equal(1); - ret.newSignatures[0].should.equal('0392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed03'); + var hasChanged = txp.merge(txp); + hasChanged.should.equal(true); }); }); + describe('#setCopayers', function() { + it("should fails if Tx has no creator", function() { + var txp = dummyProposal; + txp.signedBy = { + 'hugo': 1 + }; + delete txp['creator']; + (function() { + txp.setCopayers('juan', { + pk1: 'pepe' + }) + }).should.throw('no creator'); + }); + it("should fails if Tx is not signed by creator", function() { + var txp = dummyProposal; + txp.creator = 'creator'; + txp.signedBy = { + 'hugo': 1 + }; + txp._inputSignatures = [ + ['pkX'] + ]; + (function() { + txp.setCopayers('juan', { + pk1: 'pepe' + }) + }).should.throw('creator'); + }); + + + it("should fails if Tx has unmapped signatures", function() { + var txp = dummyProposal; + txp.creator = 'creator'; + txp.signedBy = { + creator: 1 + }; + txp._inputSignatures = [ + ['pk0', 'pkX'] + ]; + (function() { + txp.setCopayers('juan', { + pk1: 'pepe' + }) + }).should.throw('unknown sig'); + }); + + it("should be signed by sender", function() { + var txp = dummyProposal; + var ts = Date.now(); + txp._inputSignatures = [ + ['pk1', 'pk0'] + ]; + txp.signedBy = { + 'creator': Date.now() + }; + (function() { + txp.setCopayers('juan', { + pk0: 'creator', + pk1: 'pepe', + pk2: 'john' + }) + }).should.throw('senders sig'); + }); + + + it("should set signedBy (trivial case)", function() { + var txp = dummyProposal; + var ts = Date.now(); + txp._inputSignatures = [ + ['pk1', 'pk0'] + ]; + txp.signedBy = { + 'creator': Date.now() + }; + txp.setCopayers('pepe', { + pk0: 'creator', + pk1: 'pepe', + pk2: 'john' + }) + Object.keys(txp.signedBy).length.should.equal(2); + txp.signedBy['pepe'].should.gte(ts); + txp.signedBy['creator'].should.gte(ts); + }); + it("should assign creator", function() { + var txp = dummyProposal; + var ts = Date.now(); + txp._inputSignatures = [ + ['pk0'] + ]; + txp.signedBy = {}; + delete txp['creator']; + delete txp['creatorTs']; + txp.setCopayers('creator', { + pk0: 'creator', + pk1: 'pepe', + pk2: 'john' + }) + Object.keys(txp.signedBy).length.should.equal(1); + txp.creator.should.equal('creator'); + txp.createdTs.should.gte(ts); + }) + it("New tx should have only 1 signature", function() { + var txp = dummyProposal; + var ts = Date.now(); + txp.signedBy = {}; + delete txp['creator']; + delete txp['creatorTs']; + txp._inputSignatures = [ + ['pk0', 'pk1'] + ]; + (function() { + txp.setCopayers( + 'creator', { + pk0: 'creator', + pk1: 'pepe', + pk2: 'john' + }, { + 'creator2': 1 + } + ); + }).should.throw('only 1'); + }) + + it("if signed, should not change ts", function() { + var txp = dummyProposal; + var ts = Date.now(); + txp._inputSignatures = [ + ['pk0', 'pk1'] + ]; + txp.creator = 'creator'; + txp.signedBy = { + 'creator': 1 + }; + txp.setCopayers('pepe', { + pk0: 'creator', + pk1: 'pepe', + pk2: 'john' + }) + Object.keys(txp.signedBy).length.should.equal(2); + txp.creator.should.equal('creator'); + txp.signedBy['creator'].should.equal(1); + txp.signedBy['pepe'].should.gte(ts); + }) + }); }); }); From f5f9848ff10aa36523996dbde9441296512f26e1 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Sun, 3 Aug 2014 23:57:23 -0300 Subject: [PATCH 21/31] all test passing! --- js/models/core/TxProposal.js | 34 ++++++++++++---------- js/models/core/TxProposals.js | 44 +++++++++++++++++----------- js/models/core/Wallet.js | 46 +++++++++++------------------ test/test.Wallet.js | 54 +++++++++++++++++++++++------------ test/test.performance.js | 11 ++++++- 5 files changed, 109 insertions(+), 80 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 38fd61a43..85420245f 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -17,8 +17,6 @@ var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version']; function TxProposal(opts) { preconditions.checkArgument(opts); preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); - preconditions.checkArgument(opts.creator, 'no creator'); - preconditions.checkArgument(opts.createdTs, 'no createdTs'); preconditions.checkArgument(opts.builder, 'no builder'); preconditions.checkArgument(opts.inputChainPaths, 'no inputChainPaths'); @@ -106,8 +104,7 @@ TxProposal.prototype.toObj = function() { }; -TxProposal.trim = function() { - var o = this.toObj(); +TxProposal._trim = function(o) { var ret = {}; CORE_FIELDS.forEach(function(k) { ret[k] = o[k]; @@ -115,7 +112,6 @@ TxProposal.trim = function() { return ret; }; -// fromObj => from a trusted source TxProposal.fromObj = function(o, forceOpts) { preconditions.checkArgument(o.builderObj); delete o['builder']; @@ -137,6 +133,9 @@ TxProposal.fromObj = function(o, forceOpts) { return new TxProposal(o); }; +TxProposal.fromUntrustedObj = function(o, forceOpts) { + return TxProposal.fromObj(TxProposal._trim(o),forceOpts); +}; TxProposal._formatKeys = function(keys) { @@ -208,7 +207,11 @@ TxProposal.prototype.setSeen = function(copayerId) { }; TxProposal.prototype.setRejected = function(copayerId) { - if (!this.rejectedBy[copayerId] && !this.signedBy) + + if (this.signedBy[copayerId]) + throw new Error('Can not reject a signed TX'); + + if (!this.rejectedBy[copayerId]) this.rejectedBy[copayerId] = Date.now(); }; @@ -230,7 +233,7 @@ TxProposal.prototype._allSignatures = function() { TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { - var newCopayers = {}, + var newCopayer = {}, oldCopayers = {}, newSignedBy = {}, readOnlyPeers = {}, isNew = 1; for(var k in this.signedBy) { @@ -253,35 +256,36 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { if (oldCopayers[copayerId]) { //Already have it. Do nothing } else { - newCopayers[copayerId] = Date.now(); + newCopayer[copayerId] = Date.now(); delete oldCopayers[i]; } } - if (!newCopayers[senderId] && !readOnlyPeers[senderId]) + if (!newCopayer[senderId] && !readOnlyPeers[senderId]) throw new Error('TX must have a (new) senders signature') - if (isNew && Object.keys(newCopayers).length>1) - throw new Error('New TX must have only 1 signature'); + if (Object.keys(newCopayer).length>1) + throw new Error('New TX must have only 1 new signature'); // Handler creator / createdTs. // from senderId, and must be signed by senderId if (isNew) { - this.creator = Object.keys(newCopayers)[0]; + this.creator = Object.keys(newCopayer)[0]; this.createdTs = Date.now(); } //Ended. Update this. - for(var i in newCopayers) { - this.signedBy[i] = newCopayers[i]; + for(var i in newCopayer) { + this.signedBy[i] = newCopayer[i]; } // signedBy has preference over rejectedBy for(var i in this.signedBy) { delete this.rejectedBy[i]; } + console.log('[TxProposal.js.287:newCopayer:]',newCopayer); //TODO - return Object.keys(newCopayers); + return Object.keys(newCopayer); }; // merge will not merge any metadata. diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index e12c0b9db..7fe68b7b9 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -56,13 +56,12 @@ TxProposals.prototype.toObj = function() { }; -TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, builderOpts) { - var safeObj = inObj.trimUntrustedObj(); - var incomingTx = TxProposal.fromObj(safeObj, builderOpts); +TxProposals.prototype.merge = function(inObj, builderOpts) { + var incomingTx = TxProposal.fromUntrustedObj(inObj, builderOpts); incomingTx._sync(); var myTxps = this.txps; - var ntxid = inTxp.getId(); + var ntxid = incomingTx.getId(); var ret = { ntxid: ntxid }; @@ -70,37 +69,39 @@ TxProposals.prototype.merge = function(inObj, senderId, copayersForPubkeys, buil if (myTxps[ntxid]) { // Merge an existing txProposal - ret.hasChanged = myTxps[ntxid].merge(inTxp, allowedPubKeys); + ret.hasChanged = myTxps[ntxid].merge(incomingTx, allowedPubKeys); } else { // Create a new one ret.new = 1; - this.txps[ntxid] = inTxp; + this.txps[ntxid] = incomingTx; } ret.txp = this.txps[ntxid]; return ret; }; -TxProposals.prototype.mergeFromObj = function(txProposalObj, allowedPubKeys, opts) { - var inTxp = TxProposal.fromObj(txProposalObj, opts); - var mergeInfo = this.merge(inTxp, allowedPubKeys); - mergeInfo.inTxp = inTxp; - return mergeInfo; -}; - - // Add a LOCALLY CREATED (trusted) tx proposal TxProposals.prototype.add = function(txp) { - txp.sync(); + txp._sync(); var ntxid = txp.getId(); this.txps[ntxid] = txp; return ntxid; }; + +TxProposals.prototype._getTxp = function(ntxid) { + var ret = this.txps[ntxid]; + if (!ret) + throw new Error('Could not find txp: '+ntxid); + + return ret; +}; + TxProposals.prototype.getTxProposal = function(ntxid, copayers) { - var txp = this.txps[ntxid]; + var txp = this._getTxp(ntxid); + var i = JSON.parse(JSON.stringify(txp)); i.builder = txp.builder; i.ntxid = ntxid; @@ -136,6 +137,17 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) { return i; }; + +TxProposals.prototype.reject = function(ntxid, copayerId) { + var txp = this._getTxp(ntxid); + txp.setRejected(copayerId); +}; + +TxProposals.prototype.seen = function(ntxid, copayerId) { + var txp = this._getTxp(ntxid); + txp.setSeen(copayerId); +}; + //returns the unspent txid-vout used in PENDING Txs TxProposals.prototype.getUsedUnspent = function(maxRejectCount) { var ret = {}; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8646d5924..e2e57379f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -130,30 +130,27 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { }; -Wallet.prototype._processProposalEvents = function(mergeInfo) { - var ev = []; - if (mergeInfo) { - if (mergeInfo.new) { +Wallet.prototype._processProposalEvents = function(senderId, m) { + var ev; + if (m) { + if (m.new) { ev = { type: 'new', cid: senderId } - } else { - for (var i in mergeInfo.newCopayers) { - var copayerId = mergeInfo.newCopayers[i]; - ev.push({ - type: 'signed', - cid: copayerId - }); - } + } else if(m.newCopayer){ + ev={ + type: 'signed', + cid: m.newCopayer + }; } } else { ev = { type: 'corrupt', cId: senderId, - error: e, }; } + if (ev) this.emit('txProposalEvent', ev); }; @@ -189,13 +186,12 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { var m; try { - m = this.txProposals.mergeObj(senderId, data.txProposal, Wallet.builderOpts); - + m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); var keyMap = this._getKeyMap(m.tpx,senderId); - ret.newCopayers = m.txp.setCopayers(senderId, keyMap); + ret.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { - this.log('Corrupt TX proposal received', senderId, e); //TODO + this.log('Corrupt TX proposal received', senderId, e); } if (m) { @@ -632,20 +628,12 @@ Wallet.prototype.getTxProposals = function() { Wallet.prototype.reject = function(ntxid) { - var myId = this.getMyCopayerId(); - var txp = this.txProposals.txps[ntxid]; - if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { - throw new Error('Invalid transaction to reject: ' + ntxid); - } - - txp.rejectedBy[myId] = Date.now(); + var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()) ; this.sendReject(ntxid); this.store(); this.emit('txProposalsUpdated'); }; - - Wallet.prototype.sign = function(ntxid, cb) { preconditions.checkState(typeof this.getMyCopayerId() !== 'undefined'); var self = this; @@ -834,9 +822,9 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos var priv = this.privateKey; opts = opts || {}; - preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName()); - preconditions.checkState(pkr.isComplete()); - preconditions.checkState(priv); + preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch'); + preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete'); + preconditions.checkState(priv,'no private key'); if (comment) preconditions.checkArgument(comment.length <= 100); if (!opts.remainderOut) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 91e2a2573..b721afb39 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -668,18 +668,28 @@ describe('Wallet model', function() { }); }); }); - it('should create & reject transaction', function(done) { + it('should fail to reject a signed transaction', function() { var w = cachedCreateW2(); - w.privateKey = null; var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { - w.on('txProposalsUpdated', function() { - w.getTxProposals()[0].signedByUs.should.equal(false); - w.getTxProposals()[0].rejectedByUs.should.equal(true); - done(); - }); + (function() {w.reject(ntxid);}).should.throw('reject a signed'); + }); + }); + + it('should create & reject transaction', function(done) { + var w = cachedCreateW2(); + var oldK = w.privateKey; + var utxo = createUTXO(w); + w.blockchain.fixUnspent(utxo); + w.createTx(toAddress, amountSatStr, null, function(ntxid) { + var s = sinon.stub(w, 'getMyCopayerId').returns('213'); + Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(0); w.reject(ntxid); + Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(1); + w.txProposals._getTxp(ntxid).rejectedBy['213'].should.gt(1); + s.restore(); + done(); }); }); it('should create & sign & send a transaction', function(done) { @@ -1030,8 +1040,9 @@ describe('Wallet model', function() { }); }); - describe('validate txProposals', function() { - var testValidate = function(shouldThrow, result, done) { + describe('_handleTxProposal', function() { + var testValidate = function(response, result, done) { + var w = cachedCreateW(); var spy = sinon.spy(); w.on('txProposalEvent', spy); @@ -1039,26 +1050,31 @@ describe('Wallet model', function() { e.type.should.equal(result); done(); }); - var txp = {dummy:1}; // txp.prototype.getId = function() {return 'aa'}; + var txp = {dummy:1}; var txp = { 'txProposal': txp }; - var merge = sinon.stub(w.txProposals, 'mergeFromObj', function() { - if (shouldThrow) throw new Error(); - return {events: [{type:'new'}]}; + var merge = sinon.stub(w.txProposals, 'merge', function() { + if (response==0) throw new Error(); + return {newCopayer: ['juan'], ntxid:1, new:response==1}; }); - w._handleTxProposal('senderID', txp, true); + w._handleTxProposal('senderID', txp); spy.callCount.should.equal(1); merge.restore(); }; - it('should validate for undefined', function(done) { + it('should handle corrupt', function(done) { var result = 'corrupt'; - testValidate(1, result, done); - }); - it('should validate for SIGHASH_ALL', function(done) { - var result = 'new'; testValidate(0, result, done); }); + it('should handle new', function(done) { + var result = 'new'; + testValidate(1, result, done); + }); + it('should handle signed', function(done) { + var result = 'signed'; + testValidate(2, result, done); + }); + }); }); diff --git a/test/test.performance.js b/test/test.performance.js index d2b2bfb75..9bca46ebc 100644 --- a/test/test.performance.js +++ b/test/test.performance.js @@ -5,6 +5,15 @@ var should = chai.should(); var PrivateKey = require('../js/models/core/PrivateKey'); var PublicKeyRing = require('../js/models/core/PublicKeyRing'); +var getNewEpk = function() { + return new PrivateKey({ + networkName: 'livenet', + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); +} + + describe('Performance tests', function() { describe('PrivateKey', function() { it('should optimize BIP32 private key gen time with cache', function() { @@ -43,7 +52,7 @@ describe('Performance tests', function() { requiredCopayers: M }); for (var i = 0; i < N; i++) { - pkr1.addCopayer(); // add new random ext public key + pkr1.addCopayer(getNewEpk()); // add new random ext public key } var generateN = 5; var generated = []; From 4cf152999aae419a23697a969bb631387a68dab6 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 4 Aug 2014 07:16:09 -0300 Subject: [PATCH 22/31] add seenBy on creator --- js/models/core/TxProposal.js | 3 +-- test/test.TxProposal.js | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 85420245f..e0e5c1674 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -271,7 +271,7 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { // from senderId, and must be signed by senderId if (isNew) { this.creator = Object.keys(newCopayer)[0]; - this.createdTs = Date.now(); + this.seenBy[this.creator] = this.createdTs = Date.now(); } //Ended. Update this. @@ -283,7 +283,6 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { for(var i in this.signedBy) { delete this.rejectedBy[i]; } - console.log('[TxProposal.js.287:newCopayer:]',newCopayer); //TODO return Object.keys(newCopayer); }; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 33c671519..18cc67ccb 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -363,6 +363,7 @@ describe('TxProposal', function() { Object.keys(txp.signedBy).length.should.equal(1); txp.creator.should.equal('creator'); txp.createdTs.should.gte(ts); + txp.seenBy['creator'].should.equal(txp.createdTs); }) it("New tx should have only 1 signature", function() { var txp = dummyProposal; From 768dfefe3af8d16a916381aff4102d27ecedcfb5 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 4 Aug 2014 07:16:22 -0300 Subject: [PATCH 23/31] wallet working again --- js/models/core/TxProposals.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 7fe68b7b9..7885509d1 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -69,7 +69,7 @@ TxProposals.prototype.merge = function(inObj, builderOpts) { if (myTxps[ntxid]) { // Merge an existing txProposal - ret.hasChanged = myTxps[ntxid].merge(incomingTx, allowedPubKeys); + ret.hasChanged = myTxps[ntxid].merge(incomingTx); } else { From 725f79f5f8b7af68206cc559f08c2ddc689ec737 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 4 Aug 2014 09:43:46 -0300 Subject: [PATCH 24/31] fix tests --- test/test.PublicKeyRing.js | 70 ++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 30 deletions(-) diff --git a/test/test.PublicKeyRing.js b/test/test.PublicKeyRing.js index fcf179691..0dbbd447c 100644 --- a/test/test.PublicKeyRing.js +++ b/test/test.PublicKeyRing.js @@ -13,11 +13,20 @@ try { } catch (e) { var copay = require('../copay'); //node } +var PrivateKey = copay.PrivateKey; var PublicKeyRing = copay.PublicKeyRing; var aMasterPubKey = 'tprv8ZgxMBicQKsPdSVTiWXEqCCzqRaRr9EAQdn5UVMpT9UHX67Dh1FmzEMbavPumpAicsUm2XvC6NTdcWB89yN5DUWx5HQ7z3KByUg7Ht74VRZ'; +var getNewEpk = function() { + return new PrivateKey({ + networkName: 'livenet', + }) + .deriveBIP45Branch() + .extendedPublicKeyString(); +} + var createW = function(networkName) { var config = { networkName: networkName || 'livenet', @@ -29,8 +38,8 @@ var createW = function(networkName) { var copayers = []; for (var i = 0; i < 5; i++) { w.isComplete().should.equal(false); - w.remainingCopayers().should.equal(5-i); - var newEpk = w.addCopayer(); + w.remainingCopayers().should.equal(5 - i); + var newEpk = w.addCopayer(getNewEpk()); copayers.push(newEpk); } w.isComplete().should.equal(true); @@ -43,6 +52,14 @@ var createW = function(networkName) { }; }; +var cachedW; +var getCachedW = function() { + if (!cachedW) { + cachedW = createW(); + } + return cachedW; +}; + describe('PublicKeyRing model', function() { it('should create an instance (livenet)', function() { @@ -78,7 +95,7 @@ describe('PublicKeyRing model', function() { }); it('should add and check when adding shared pub keys', function() { - var k = createW(); + var k = getCachedW(); var w = k.w; var copayers = k.copayers; @@ -92,7 +109,7 @@ describe('PublicKeyRing model', function() { }); it('should be able to to store and read', function() { - var k = createW(); + var k = getCachedW(); var w = k.w; var copayers = k.copayers; var changeN = 2; @@ -124,10 +141,10 @@ describe('PublicKeyRing model', function() { it('should generate some p2sh addresses', function() { - var k = createW(); + var k = getCachedW(); var w = k.w; - [true, false].forEach(function(isChange){ + [true, false].forEach(function(isChange) { for (var i = 0; i < 2; i++) { var a = w.generateAddress(isChange, k.pub); a.isValid().should.equal(true); @@ -148,7 +165,7 @@ describe('PublicKeyRing model', function() { var a = w.getAddresses(); a.length.should.equal(1); - [true, false].forEach(function(isChange){ + [true, false].forEach(function(isChange) { for (var i = 0; i < 2; i++) { w.generateAddress(isChange, k.pub); } @@ -185,18 +202,12 @@ describe('PublicKeyRing model', function() { }); it('should set backup ready', function() { - var w = createW().w; + var w = getCachedW().w; w.isBackupReady().should.equal(false); w.setBackupReady(); w.isBackupReady().should.equal(true); }); - it('should set backup ready', function() { - var w = createW().w; - w.isBackupReady().should.equal(false); - w.setBackupReady(); - w.isBackupReady().should.equal(true); - }); it('should check for other backups', function() { var w = createW().w; @@ -213,7 +224,7 @@ describe('PublicKeyRing model', function() { }); it('should merge backup', function() { - var w = createW().w; + var w = getCachedW().w; w.copayersBackup = ["a", "b"]; var hasChanged = w.mergeBackups(["b", "c"]); @@ -313,11 +324,10 @@ describe('PublicKeyRing model', function() { var w0 = new PublicKeyRing({ networkName: 'livenet', }); - w0.addCopayer(); - w0.addCopayer(); - w0.addCopayer(); - w0.addCopayer(); - w0.addCopayer(); + + for (var i = 0; i < 5; i++) + w0.addCopayer(getNewEpk()); + (function() { w0.merge(w); }).should.throw(); @@ -327,7 +337,7 @@ describe('PublicKeyRing model', function() { var wx = new PublicKeyRing({ networkName: 'livenet', }); - wx.addCopayer(); + wx.addCopayer(getNewEpk()); (function() { w.merge(wx); }).should.throw(); @@ -343,7 +353,7 @@ describe('PublicKeyRing model', function() { var copayers = []; for (var i = 0; i < 2; i++) { w.isComplete().should.equal(false); - w.addCopayer(); + w.addCopayer(getNewEpk()); } var w2 = new PublicKeyRing({ @@ -354,7 +364,7 @@ describe('PublicKeyRing model', function() { var copayers = []; for (var i = 0; i < 3; i++) { w2.isComplete().should.equal(false); - w2.addCopayer(); + w2.addCopayer(getNewEpk()); } w2.merge(w).should.equal(true); w2.isComplete().should.equal(true); @@ -379,7 +389,7 @@ describe('PublicKeyRing model', function() { networkName: 'livenet', id: w.id, }); - w2.addCopayer(); + w2.addCopayer(getNewEpk()); w.merge(w2).should.equal(true); } w.isComplete().should.equal(true); @@ -393,7 +403,7 @@ describe('PublicKeyRing model', function() { var w = new PublicKeyRing(config); should.exist(w); for (var i = 0; i < 3; i++) { - w.addCopayer(); + w.addCopayer(getNewEpk()); }; w._setNicknameForIndex(0, 'pepe0'); w._setNicknameForIndex(1, 'pepe1'); @@ -409,7 +419,7 @@ describe('PublicKeyRing model', function() { networkName: 'livenet', id: w.id, }); - w2.addCopayer(); + w2.addCopayer(getNewEpk()); w2._setNicknameForIndex(0, 'juan' + i); w.merge(w2).should.equal(true); } @@ -430,7 +440,7 @@ describe('PublicKeyRing model', function() { var w = new PublicKeyRing(config); should.exist(w); for (var i = 0; i < 3; i++) { - w.addCopayer(null, 'tito' + i); + w.addCopayer(getNewEpk(), 'tito' + i); }; w.nicknameForIndex(0).should.equal('tito0'); w.nicknameForIndex(1).should.equal('tito1'); @@ -468,7 +478,7 @@ describe('PublicKeyRing model', function() { }); it('#getRedeemScriptMap check tests', function() { - var k = createW(); + var k = getCachedW(); var w = k.w; var amount = 2; @@ -497,7 +507,7 @@ describe('PublicKeyRing model', function() { it('#getForPaths should return 2 arrays of 5 pubkey ', function() { var w = getCachedW().w; - var pubkeys = w.getForPaths([ 'm/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1'] ); + var pubkeys = w.getForPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']); pubkeys.length.should.equal(2); pubkeys[0].length.should.equal(5); pubkeys[1].length.should.equal(5); @@ -505,7 +515,7 @@ describe('PublicKeyRing model', function() { it('#forPaths should return copayers and pubkeys ', function() { var w = getCachedW().w; - var ret = w.forPaths([ 'm/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1'] ); + var ret = w.forPaths(['m/45\'/2147483647/1/0', 'm/45\'/2147483647/1/1']); ret.copayerIds.length.should.equal(5); ret.pubKeys.length.should.equal(2); ret.pubKeys[0].length.should.equal(5); From 36fcd6882f7c5c22b1da3dd311b063d9014bb881 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 4 Aug 2014 12:43:21 -0300 Subject: [PATCH 25/31] add more tests to wallet --- js/models/core/Wallet.js | 36 +++++-- test/test.Wallet.js | 211 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 231 insertions(+), 16 deletions(-) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index e2e57379f..8c069a93b 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -164,20 +164,25 @@ cId: k, txId: ntxid }); */ -Wallet.prototype._getKeyMap = function(tpx, senderId) { +Wallet.prototype._getKeyMap = function(txp) { - this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.paths); + var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.paths); + + var inSig = JSON.stringify(txp._inputSignatures[0].sort()); + + if (JSON.stringify(Object.keys(keyMap).sort()) !== inSig) { + throw new Error('inputSignatures dont match know copayers pubkeys'); + } var keyMapStr = JSON.stringify(keyMap); // All inputs must be signed with the same copayers - for (var i in m.txp._inputSignatures) { + for (var i in txp._inputSignatures) { if (!i) continue; - var inputKeyMapStr = JSON.stringify( - this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[i], txp.paths)); - - if (inputKeyMapStr !== keyMapStr) - throw new Error('found inputs with different signatures in Tx from:' + senderId); + var inSigX = JSON.stringify(txp._inputSignatures[i].sort()); + if (inSigX !== inSig) + throw new Error('found inputs with different signatures:'); } + return keyMap; }; @@ -187,7 +192,7 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { try { m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); - var keyMap = this._getKeyMap(m.tpx,senderId); + var keyMap = this._getKeyMap(m.tpx); ret.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { @@ -209,12 +214,13 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { Wallet.prototype._handleReject = function(senderId, data, isInbound) { + preconditions.checkState(data.ntxid); this.log('RECV REJECT:', data); var txp = this.txProposals.txps[data.ntxid]; if (!txp) - throw new Error('Received Reject for an unkwown TX from:' + senderId); + throw new Error('Received Reject for an unknown TX from:' + senderId); if (txp.signedBy[senderId]) throw new Error('Received Reject for an already signed TX from:' + senderId); @@ -231,8 +237,15 @@ Wallet.prototype._handleReject = function(senderId, data, isInbound) { }; Wallet.prototype._handleSeen = function(senderId, data, isInbound) { + preconditions.checkState(data.ntxid); this.log('RECV SEEN:', data); - this.txProposals.txps[data.ntxid].setSeen(senderId); + + var txp = this.txProposals.txps[data.ntxid]; + + if (!txp) + throw new Error('Received Reject for an unknown TX from:' + senderId); + + txp.setSeen(senderId); this.store(); this.emit('txProposalsUpdated'); this.emit('txProposalEvent', { @@ -246,6 +259,7 @@ Wallet.prototype._handleSeen = function(senderId, data, isInbound) { Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) { + preconditions.checkState(data.addressBook); this.log('RECV ADDRESSBOOK:', data); var rcv = data.addressBook; var hasChange; diff --git a/test/test.Wallet.js b/test/test.Wallet.js index b721afb39..3f33e7fe5 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -673,7 +673,9 @@ describe('Wallet model', function() { var utxo = createUTXO(w); w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { - (function() {w.reject(ntxid);}).should.throw('reject a signed'); + (function() { + w.reject(ntxid); + }).should.throw('reject a signed'); }); }); @@ -1039,6 +1041,87 @@ describe('Wallet model', function() { copayConfig.forceNetwork = backup; }); }); + describe('_getKeymap', function() { + var w = cachedCreateW(); + + it('should set keymap', function() { + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return { + '123': 'juan' + }; + }); + var txp = { + _inputSignatures: [ + ['123'] + ], + paths: ['/m/1'], + }; + var map = w._getKeyMap(txp); + Object.keys(map).length.should.equal(1); + map['123'].should.equal('juan'); + stub.restore(); + }); + + it('should throw if unmatched sigs', function() { + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return { + '123': 'juan' + }; + }); + var txp = { + _inputSignatures: [ + ['234'] + ], + paths: ['/m/1'], + }; + (function() { + w._getKeyMap(txp); + }).should.throw('dont match know copayers'); + stub.restore(); + }); + + it('should set keymap with multiple signatures', function() { + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return { + '123': 'juan', + '234': 'pepe', + }; + }); + var txp = { + _inputSignatures: [ + ['234', '123'] + ], + paths: ['/m/1'], + }; + var map = w._getKeyMap(txp); + Object.keys(map).length.should.equal(2); + map['123'].should.equal('juan'); + map['234'].should.equal('pepe'); + stub.restore(); + }); + + it('should throw is one inputs has missing sigs', function() { + var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys', function() { + return { + '123': 'juan', + '234': 'pepe', + }; + }); + var txp = { + _inputSignatures: [ + ['234', '123'], + ['234'] + ], + paths: ['/m/1'], + }; + (function() { + w._getKeyMap(txp); + }).should.throw('different sig'); + stub.restore(); + }); + }); + + describe('_handleTxProposal', function() { var testValidate = function(response, result, done) { @@ -1051,11 +1134,19 @@ describe('Wallet model', function() { done(); }); // txp.prototype.getId = function() {return 'aa'}; - var txp = {dummy:1}; - var txp = { 'txProposal': txp }; + var txp = { + dummy: 1 + }; + var txp = { + 'txProposal': txp + }; var merge = sinon.stub(w.txProposals, 'merge', function() { - if (response==0) throw new Error(); - return {newCopayer: ['juan'], ntxid:1, new:response==1}; + if (response == 0) throw new Error(); + return { + newCopayer: ['juan'], + ntxid: 1, + new: response == 1 + }; }); w._handleTxProposal('senderID', txp); @@ -1077,4 +1168,114 @@ describe('Wallet model', function() { }); }); + + + describe('_handleReject', function() { + it('should fails if unknown tx', function() { + var w = cachedCreateW(); + (function() { + w._handleReject(1, { + ntxid: 1 + }, 1); + }).should.throw('unknown TX'); + }); + it('should fail to reject a signed tx', function() { + var w = cachedCreateW(); + w.txProposals.txps['qwerty'] = { + signedBy: { + john: 1 + } + }; + (function() { + w._handleReject('john', { + ntxid: 'qwerty' + }, 1); + }).should.throw('already signed'); + }); + it('should reject a tx', function() { + var w = cachedCreateW(); + function txp() { + this.ok=0; + this.signedBy = {}; + }; + txp.prototype.setRejected = function() { + this.ok=1; + }; + txp.prototype.toObj = function() { + }; + + var spy1 = sinon.spy(w,'store'); + var spy2 = sinon.spy(w,'emit'); + w.txProposals.txps['qwerty'] = new txp(); + w.txProposals.txps['qwerty'].ok.should.equal(0); + w._handleReject('john', { + ntxid: 'qwerty' + }, 1); + w.txProposals.txps['qwerty'].ok.should.equal(1); + spy1.calledOnce.should.equal(true); + spy2.callCount.should.equal(2); + spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']); + spy2.secondCall.args.should.deep.equal(['txProposalEvent',{ + type:'rejected', + cId: 'john', + txId: 'qwerty', + }]); + }); + }); + + + describe('_handleSeen', function() { + it('should fails if unknown tx', function() { + var w = cachedCreateW(); + (function() { + w._handleReject(1, { + ntxid: 1 + }, 1); + }).should.throw('unknown TX'); + }); + it('should set seen a tx', function() { + var w = cachedCreateW(); + function txp() { + this.ok=0; + this.signedBy = {}; + }; + txp.prototype.setSeen = function() { + this.ok=1; + }; + txp.prototype.toObj = function() { + }; + + var spy1 = sinon.spy(w,'store'); + var spy2 = sinon.spy(w,'emit'); + w.txProposals.txps['qwerty'] = new txp(); + w.txProposals.txps['qwerty'].ok.should.equal(0); + w._handleSeen('john', { + ntxid: 'qwerty' + }, 1); + w.txProposals.txps['qwerty'].ok.should.equal(1); + spy1.calledOnce.should.equal(true); + spy2.callCount.should.equal(2); + spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']); + spy2.secondCall.args.should.deep.equal(['txProposalEvent',{ + type:'seen', + cId: 'john', + txId: 'qwerty', + }]); + }); + }); + + it('getNetwork', function() { + var w = cachedCreateW(); + var n = w.getNetwork(); + n.maxPeers.should.equal(5); + should.exist(n.networkNonce); + }); + + it('#disconnect', function() { + var w = cachedCreateW(); + var spy1 = sinon.spy(w.network,'disconnect'); + w.disconnect(); + spy1.callCount.should.equal(1); + }); + }); From 91829f841070eb2502e88316f879daa643f716a9 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Mon, 4 Aug 2014 17:12:53 -0300 Subject: [PATCH 26/31] remove sender sig check --- js/models/core/PublicKeyRing.js | 3 ++ js/models/core/TxProposal.js | 46 +++++++++++++-------- js/models/core/Wallet.js | 14 ++++--- test/test.TxProposal.js | 18 ++++++++- test/test.Wallet.js | 72 +++++++++++++++------------------ 5 files changed, 90 insertions(+), 63 deletions(-) diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index 09a38d903..c61cea9f2 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -285,6 +285,7 @@ PublicKeyRing.prototype.getForPath = function(path) { }; PublicKeyRing.prototype.getForPaths = function(paths) { + preconditions.checkArgument(paths); return paths.map(this.getForPath.bind(this)); }; @@ -299,6 +300,8 @@ PublicKeyRing.prototype.forPaths = function(paths) { // returns pubkey -> copayerId. PublicKeyRing.prototype.copayersForPubkeys = function(pubkeys, paths) { + preconditions.checkArgument(pubkeys); + preconditions.checkArgument(paths); var inKeyMap = {}, ret = {}; for(var i in pubkeys ){ diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index e0e5c1674..739283b47 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -134,9 +134,12 @@ TxProposal.fromObj = function(o, forceOpts) { }; TxProposal.fromUntrustedObj = function(o, forceOpts) { - return TxProposal.fromObj(TxProposal._trim(o),forceOpts); + return TxProposal.fromObj(TxProposal._trim(o), forceOpts); }; +TxProposal.prototype.toObjTrim = function() { + return TxProposal._trim(this.toObj()); +}; TxProposal._formatKeys = function(keys) { var ret = []; @@ -234,21 +237,31 @@ 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) { + for (var k in this.signedBy) { oldCopayers[k] = 1; isNew = 0; }; - if (isNew == 0 && (!this.creator || !this.createdTs)) - throw new Error('Existing TX has no creator'); + if (isNew == 0) { + if (!this.creator || !this.createdTs) + throw new Error('Existing TX has no creator'); + + if (!this.signedBy[this.creator]) + throw new Error('Existing TX is not signed by creator'); + + + if (Object.keys(this.signedBy).length === 0) + throw new Error('Existing TX has no signatures'); + } - if (isNew == 0 && (!this.signedBy[this.creator])) - throw new Error('Existing TX is not signed by creator'); var iSig = this._inputSignatures[0]; - for(var i in iSig){ + for (var i in iSig) { var copayerId = keyMap[iSig[i]]; if (!copayerId) throw new Error('Found unknown signature') @@ -256,15 +269,16 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { if (oldCopayers[copayerId]) { //Already have it. Do nothing } else { - newCopayer[copayerId] = Date.now(); + newCopayer[copayerId] = Date.now(); delete oldCopayers[i]; } } - if (!newCopayer[senderId] && !readOnlyPeers[senderId]) - throw new Error('TX must have a (new) senders signature') + // Seems unncessary to check this: + // if (!newCopayer[senderId] && !readOnlyPeers[senderId]) + // throw new Error('TX must have a (new) senders signature') - if (Object.keys(newCopayer).length>1) + if (Object.keys(newCopayer).length > 1) throw new Error('New TX must have only 1 new signature'); // Handler creator / createdTs. @@ -272,16 +286,16 @@ TxProposal.prototype.setCopayers = function(senderId, keyMap, readOnlyPeers) { if (isNew) { this.creator = Object.keys(newCopayer)[0]; this.seenBy[this.creator] = this.createdTs = Date.now(); - } + } //Ended. Update this. - for(var i in newCopayer) { + for (var i in newCopayer) { this.signedBy[i] = newCopayer[i]; } // signedBy has preference over rejectedBy - for(var i in this.signedBy) { - delete this.rejectedBy[i]; + for (var i in this.signedBy) { + delete this.rejectedBy[i]; } return Object.keys(newCopayer); diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 8c069a93b..bf68497f8 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -165,8 +165,9 @@ txId: ntxid }); */ Wallet.prototype._getKeyMap = function(txp) { + preconditions.checkArgument(txp); - var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.paths); + var keyMap = this.publicKeyRing.copayersForPubkeys(txp._inputSignatures[0], txp.inputChainPaths); var inSig = JSON.stringify(txp._inputSignatures[0].sort()); @@ -191,12 +192,12 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { var m; try { - m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); - var keyMap = this._getKeyMap(m.tpx); - ret.newCopayer = m.txp.setCopayers(senderId, keyMap); + m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); + var keyMap = this._getKeyMap(m.txp); + ret.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { - this.log('Corrupt TX proposal received', senderId, e); + this.log('Corrupt TX proposal received from:', senderId, e); } if (m) { @@ -524,10 +525,11 @@ Wallet.prototype.sendAllTxProposals = function(recipients) { Wallet.prototype.sendTxProposal = function(ntxid, recipients) { preconditions.checkArgument(ntxid); preconditions.checkState(this.txProposals.txps[ntxid]); + this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals); this.send(recipients, { type: 'txProposal', - txProposal: this.txProposals.txps[ntxid].toObj(), + txProposal: this.txProposals.txps[ntxid].toObjTrim(), walletId: this.id, }); }; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 18cc67ccb..046930695 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -91,6 +91,21 @@ describe('TxProposal', function() { should.not.exist(o.builder); should.exist(o.builderObj); }); + it('toObjTrim', function() { + var b = new FakeBuilder(); + var txp = new TxProposal({ + creator: 1, + createdTs: 1, + builder: b, + inputChainPaths: 'm/1', + }); + var o = txp.toObjTrim(); + should.exist(o); + should.not.exist(o.creator); + should.not.exist(o.builder); + should.exist(o.builderObj); + }); + }); describe('#fromObj', function() { it.skip('should create from Object', function() { @@ -309,7 +324,8 @@ describe('TxProposal', function() { }).should.throw('unknown sig'); }); - it("should be signed by sender", function() { + // This was disabled. Unnecessary to check this. + it.skip("should be signed by sender", function() { var txp = dummyProposal; var ts = Date.now(); txp._inputSignatures = [ diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 3f33e7fe5..01b1dbbe7 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -440,19 +440,7 @@ describe('Wallet model', function() { var w = createW(); var txp = { 'txProposal': { - creator: '02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96', - createdTs: '2014-07-24T23:54:26.682Z', - seenBy: { - '02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96': 1406246066682 - }, - signedBy: { - '02c643ef43c14481fa8e81e61438c2cbc39a59024663f8cab575d28a248fe53d96': 1406246066682 - }, - rejectedBy: {}, - sentTs: null, - sentTxid: null, - inputChainPaths: ['m/45\'/2/0/0'], - comment: null, + inputChainPaths: ['m/1'], builderObj: { version: 1, outs: [{ @@ -480,9 +468,13 @@ describe('Wallet model', function() { } }; + 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); + //stub.restore(); }); var newId = '00bacacafe'; @@ -1054,7 +1046,7 @@ describe('Wallet model', function() { _inputSignatures: [ ['123'] ], - paths: ['/m/1'], + inputChainPaths: ['/m/1'], }; var map = w._getKeyMap(txp); Object.keys(map).length.should.equal(1); @@ -1072,7 +1064,7 @@ describe('Wallet model', function() { _inputSignatures: [ ['234'] ], - paths: ['/m/1'], + inputChainPaths: ['/m/1'], }; (function() { w._getKeyMap(txp); @@ -1091,7 +1083,7 @@ describe('Wallet model', function() { _inputSignatures: [ ['234', '123'] ], - paths: ['/m/1'], + inputChainPaths: ['/m/1'], }; var map = w._getKeyMap(txp); Object.keys(map).length.should.equal(2); @@ -1112,7 +1104,7 @@ describe('Wallet model', function() { ['234', '123'], ['234'] ], - paths: ['/m/1'], + inputChainPaths: ['/m/1'], }; (function() { w._getKeyMap(txp); @@ -1194,29 +1186,29 @@ describe('Wallet model', function() { }); it('should reject a tx', function() { var w = cachedCreateW(); + function txp() { - this.ok=0; + this.ok = 0; this.signedBy = {}; }; - txp.prototype.setRejected = function() { - this.ok=1; - }; - txp.prototype.toObj = function() { + txp.prototype.setRejected = function() { + this.ok = 1; }; + txp.prototype.toObj = function() {}; - var spy1 = sinon.spy(w,'store'); - var spy2 = sinon.spy(w,'emit'); + var spy1 = sinon.spy(w, 'store'); + var spy2 = sinon.spy(w, 'emit'); w.txProposals.txps['qwerty'] = new txp(); - w.txProposals.txps['qwerty'].ok.should.equal(0); + w.txProposals.txps['qwerty'].ok.should.equal(0); w._handleReject('john', { ntxid: 'qwerty' }, 1); - w.txProposals.txps['qwerty'].ok.should.equal(1); + w.txProposals.txps['qwerty'].ok.should.equal(1); spy1.calledOnce.should.equal(true); spy2.callCount.should.equal(2); spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']); - spy2.secondCall.args.should.deep.equal(['txProposalEvent',{ - type:'rejected', + spy2.secondCall.args.should.deep.equal(['txProposalEvent', { + type: 'rejected', cId: 'john', txId: 'qwerty', }]); @@ -1235,29 +1227,29 @@ describe('Wallet model', function() { }); it('should set seen a tx', function() { var w = cachedCreateW(); + function txp() { - this.ok=0; + this.ok = 0; this.signedBy = {}; }; - txp.prototype.setSeen = function() { - this.ok=1; - }; - txp.prototype.toObj = function() { + txp.prototype.setSeen = function() { + this.ok = 1; }; + txp.prototype.toObj = function() {}; - var spy1 = sinon.spy(w,'store'); - var spy2 = sinon.spy(w,'emit'); + var spy1 = sinon.spy(w, 'store'); + var spy2 = sinon.spy(w, 'emit'); w.txProposals.txps['qwerty'] = new txp(); - w.txProposals.txps['qwerty'].ok.should.equal(0); + w.txProposals.txps['qwerty'].ok.should.equal(0); w._handleSeen('john', { ntxid: 'qwerty' }, 1); - w.txProposals.txps['qwerty'].ok.should.equal(1); + w.txProposals.txps['qwerty'].ok.should.equal(1); spy1.calledOnce.should.equal(true); spy2.callCount.should.equal(2); spy2.firstCall.args.should.deep.equal(['txProposalsUpdated']); - spy2.secondCall.args.should.deep.equal(['txProposalEvent',{ - type:'seen', + spy2.secondCall.args.should.deep.equal(['txProposalEvent', { + type: 'seen', cId: 'john', txId: 'qwerty', }]); @@ -1273,7 +1265,7 @@ describe('Wallet model', function() { it('#disconnect', function() { var w = cachedCreateW(); - var spy1 = sinon.spy(w.network,'disconnect'); + var spy1 = sinon.spy(w.network, 'disconnect'); w.disconnect(); spy1.callCount.should.equal(1); }); From 5a2dfe690da8633679bf2155556a7ce41876cd23 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 5 Aug 2014 16:25:02 -0300 Subject: [PATCH 27/31] add rebroadcast test --- js/models/blockchain/Insight.js | 30 ++++++++-- js/models/core/TxProposals.js | 12 ++-- js/models/core/Wallet.js | 102 ++++++++++++++++++-------------- test/test.Wallet.js | 10 ++-- test/test.blockchain.Insight.js | 43 ++++++++++++-- 5 files changed, 131 insertions(+), 66 deletions(-) diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 863863e70..04f007c74 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -2,6 +2,7 @@ var imports = require('soop').imports(); var bitcore = require('bitcore'); +var coinUtil = bitcore.util; var preconditions = require('preconditions').singleton(); var http; @@ -39,9 +40,9 @@ function _asyncForEach(array, fn, callback) { function removeRepeatedElements(ar) { var ya = false, - v = "", - aux = [].concat(ar), - r = Array(); + v = "", + aux = [].concat(ar), + r = Array(); for (var i in aux) { // v = aux[i]; ya = false; @@ -78,6 +79,25 @@ Insight.prototype._getOptions = function(method, path, data) { }; }; + +// This is vulneable to txid maneability +// TODO: if ret = false, +// check output address from similar transactions. +// +Insight.prototype.checkSentTx = function(tx, cb) { + var hash = coinUtil.formatHashFull(tx.getHash()); + var options = this._getOptions('GET', '/api/tx/' + hash); + + this._request(options, function(err, res) { + if (err) return cb(err); + var ret = false; + if (res && res.txid === hash) { + ret = hash; + } + return cb(err, ret); + }); +}; + Insight.prototype.getTransactions = function(addresses, cb) { preconditions.shouldBeArray(addresses); preconditions.shouldBeFunction(cb); @@ -164,8 +184,8 @@ Insight.prototype.checkActivity = function(addresses, cb) { var getOutputs = function(t) { return flatArray( t.vout.map(function(vout) { - return vout.scriptPubKey.addresses; - }) + return vout.scriptPubKey.addresses; + }) ); }; diff --git a/js/models/core/TxProposals.js b/js/models/core/TxProposals.js index 7885509d1..f0933afd6 100644 --- a/js/models/core/TxProposals.js +++ b/js/models/core/TxProposals.js @@ -74,7 +74,7 @@ TxProposals.prototype.merge = function(inObj, builderOpts) { } else { // Create a new one - ret.new = 1; + ret.new = ret.hasChanged = 1; this.txps[ntxid] = incomingTx; } @@ -91,16 +91,16 @@ TxProposals.prototype.add = function(txp) { }; -TxProposals.prototype._getTxp = function(ntxid) { +TxProposals.prototype.get = function(ntxid) { var ret = this.txps[ntxid]; if (!ret) - throw new Error('Could not find txp: '+ntxid); + throw new Error('Unknown TXP: '+ntxid); return ret; }; TxProposals.prototype.getTxProposal = function(ntxid, copayers) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); var i = JSON.parse(JSON.stringify(txp)); i.builder = txp.builder; @@ -139,12 +139,12 @@ TxProposals.prototype.getTxProposal = function(ntxid, copayers) { TxProposals.prototype.reject = function(ntxid, copayerId) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); txp.setRejected(copayerId); }; TxProposals.prototype.seen = function(ntxid, copayerId) { - var txp = this._getTxp(ntxid); + var txp = this.get(ntxid); txp.setSeen(copayerId); }; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index bf68497f8..55649c296 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -138,10 +138,10 @@ Wallet.prototype._processProposalEvents = function(senderId, m) { type: 'new', cid: senderId } - } else if(m.newCopayer){ - ev={ + } else if (m.newCopayer) { + ev = { type: 'signed', - cid: m.newCopayer + cid: m.newCopayer }; } } else { @@ -187,29 +187,54 @@ Wallet.prototype._getKeyMap = function(txp) { }; +Wallet.prototype._checkSentTx = function(ntxid, cb) { + var txp = this.txProposals.get(ntxid); + var tx = txp.builder.build(); + + this.blockchain.checkSentTx(tx, function(err, txid) { + var ret = false; + if (txid) { + txp.setSent(txid); + ret = txid; + } + return cb(ret); + }); +}; + + Wallet.prototype._handleTxProposal = function(senderId, data) { + var self = this; this.log('RECV TXPROPOSAL: ', data); var m; try { - m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); - var keyMap = this._getKeyMap(m.txp); - ret.newCopayer = m.txp.setCopayers(senderId, keyMap); + m = this.txProposals.merge(data.txProposal, Wallet.builderOpts); + var keyMap = this._getKeyMap(m.txp); + ret.newCopayer = m.txp.setCopayers(senderId, keyMap); } catch (e) { this.log('Corrupt TX proposal received from:', senderId, e); } if (m) { + + if (m.hasChanged) { + this.sendSeen(m.ntxid); + var tx = m.txp.builder.build(); + if (tx.isComplete()) { + this._checkSentTx(m.ntxid, function(ret) { + if (ret) { + self.emit('txProposalsUpdated'); + self.store(); + } + }); + } else { + this.sendTxProposal(m.ntxid); + } + } this.emit('txProposalsUpdated'); this.store(); - - this.sendSeen(m.ntxid); - - if (m.hasChanged) - this.sendTxProposal(m.ntxid); } - this._processProposalEvents(senderId, m); }; @@ -218,7 +243,7 @@ Wallet.prototype._handleReject = function(senderId, data, isInbound) { preconditions.checkState(data.ntxid); this.log('RECV REJECT:', data); - var txp = this.txProposals.txps[data.ntxid]; + var txp = this.txProposals.get(data.ntxid); if (!txp) throw new Error('Received Reject for an unknown TX from:' + senderId); @@ -241,11 +266,7 @@ Wallet.prototype._handleSeen = function(senderId, data, isInbound) { preconditions.checkState(data.ntxid); this.log('RECV SEEN:', data); - var txp = this.txProposals.txps[data.ntxid]; - - if (!txp) - throw new Error('Received Reject for an unknown TX from:' + senderId); - + var txp = this.txProposals.get(data.ntxid); txp.setSeen(senderId); this.store(); this.emit('txProposalsUpdated'); @@ -524,12 +545,11 @@ Wallet.prototype.sendAllTxProposals = function(recipients) { Wallet.prototype.sendTxProposal = function(ntxid, recipients) { preconditions.checkArgument(ntxid); - preconditions.checkState(this.txProposals.txps[ntxid]); this.log('### SENDING txProposal ' + ntxid + ' TO:', recipients || 'All', this.txProposals); this.send(recipients, { type: 'txProposal', - txProposal: this.txProposals.txps[ntxid].toObjTrim(), + txProposal: this.txProposals.get(ntxid).toObjTrim(), walletId: this.id, }); }; @@ -644,7 +664,7 @@ Wallet.prototype.getTxProposals = function() { Wallet.prototype.reject = function(ntxid) { - var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()) ; + var txp = this.txProposals.reject(ntxid, this.getMyCopayerId()); this.sendReject(ntxid); this.store(); this.emit('txProposalsUpdated'); @@ -655,7 +675,7 @@ Wallet.prototype.sign = function(ntxid, cb) { var self = this; setTimeout(function() { var myId = self.getMyCopayerId(); - var txp = self.txProposals.txps[ntxid]; + var txp = self.txProposals.get(ntxid); // if (!txp || txp.rejectedBy[myId] || txp.signedBy[myId]) { // if (cb) cb(false); // } @@ -678,14 +698,13 @@ Wallet.prototype.sign = function(ntxid, cb) { }, 10); }; + Wallet.prototype.sendTx = function(ntxid, cb) { - var txp = this.txProposals.txps[ntxid]; - if (!txp) return; - + var txp = this.txProposals.get(ntxid); var tx = txp.builder.build(); - if (!tx.isComplete()) return; + if (!tx.isComplete()) + throw new Error('Tx is not complete. Can not broadcast'); this.log('Broadcasting Transaction'); - var scriptSig = tx.ins[0].getScript(); var size = scriptSig.serialize().length; @@ -696,28 +715,23 @@ Wallet.prototype.sendTx = function(ntxid, cb) { this.blockchain.sendRawTransaction(txHex, function(txid) { self.log('BITCOIND txid:', txid); if (txid) { - self.txProposals.txps[ntxid].setSent(txid); + self.txProposals.get(ntxid).setSent(txid); self.sendTxProposal(ntxid); self.store(); + return cb(txid); + } else { + self.log('Sent failed. Checking is the TX was sent already'); + self._checkSentTx(ntxid, function(txid) { + console.log('[Wallet.js.730:txid:]', txid); //TODO + if (txid) + self.store(); + + return cb(txid); + }); } - return cb(txid); }); }; -// Wallet.prototype.addSeenToTxProposals = function() { -// var ret = false; -// var myId = this.getMyCopayerId(); -// -// for (var k in this.txProposals.txps) { -// var txp = this.txProposals.txps[k]; -// if (!txp.seenBy[myId]) { -// -// txp.seenBy[myId] = Date.now(); -// ret = true; -// } -// } -// return ret; -// }; // TODO: remove this method and use getAddressesInfo everywhere Wallet.prototype.getAddresses = function(opts) { @@ -840,7 +854,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos preconditions.checkArgument(new Address(toAddress).network().name === this.getNetworkName(), 'networkname mismatch'); preconditions.checkState(pkr.isComplete(), 'pubkey ring incomplete'); - preconditions.checkState(priv,'no private key'); + preconditions.checkState(priv, 'no private key'); if (comment) preconditions.checkArgument(comment.length <= 100); if (!opts.remainderOut) { diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 01b1dbbe7..fad38aff6 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -678,10 +678,10 @@ describe('Wallet model', function() { w.blockchain.fixUnspent(utxo); w.createTx(toAddress, amountSatStr, null, function(ntxid) { var s = sinon.stub(w, 'getMyCopayerId').returns('213'); - Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(0); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(0); w.reject(ntxid); - Object.keys(w.txProposals._getTxp(ntxid).rejectedBy).length.should.equal(1); - w.txProposals._getTxp(ntxid).rejectedBy['213'].should.gt(1); + Object.keys(w.txProposals.get(ntxid).rejectedBy).length.should.equal(1); + w.txProposals.get(ntxid).rejectedBy['213'].should.gt(1); s.restore(); done(); }); @@ -1169,7 +1169,7 @@ describe('Wallet model', function() { w._handleReject(1, { ntxid: 1 }, 1); - }).should.throw('unknown TX'); + }).should.throw('Unknown TXP'); }); it('should fail to reject a signed tx', function() { var w = cachedCreateW(); @@ -1223,7 +1223,7 @@ describe('Wallet model', function() { w._handleReject(1, { ntxid: 1 }, 1); - }).should.throw('unknown TX'); + }).should.throw('Unknown TXP'); }); it('should set seen a tx', function() { var w = cachedCreateW(); diff --git a/test/test.blockchain.Insight.js b/test/test.blockchain.Insight.js index 18a35471f..b5451c99a 100644 --- a/test/test.blockchain.Insight.js +++ b/test/test.blockchain.Insight.js @@ -82,9 +82,9 @@ describe('Insight model', function() { sinon - .stub(http, 'request') - .returns(req) - .yields(request); + .stub(http, 'request') + .returns(req) + .yields(request); i.getUnspent(['2MuD5LnZSViZZYwZbpVsagwrH8WWvCztdmV', '2NBSLoMvsHsf2Uv3LA17zV4beH6Gze6RovA'], function(e, ret) { should.not.exist(e); @@ -113,9 +113,9 @@ describe('Insight model', function() { req.end = function() {}; sinon - .stub(http, 'request') - .returns(req) - .yields(request); + .stub(http, 'request') + .returns(req) + .yields(request); i.sendRawTransaction(rawtx, function(a) { should.exist(a); @@ -200,5 +200,36 @@ describe('Insight model', function() { }); }); + describe("#checkSentTx", function() { + it('should return true if Tx is found', function(done) { + var w = new Insight(); + w._request = sinon.stub().yields(null, { + txid: "414142", + }); + var tx = function() {}; + tx.prototype.getHash = function(){return new Buffer('BAA')}; + w.checkSentTx(new tx(), function(err, ret) { + should.not.exist(err); + ret.should.equal('414142'); + done(); + }); + }); + it('should return false if Tx is not found', function(done) { + var w = new Insight(); + w._request = sinon.stub().yields(null, { + txid: "414142", + }); + var tx = function() {}; + tx.prototype.getHash = function(){return new Buffer('ABC')}; + w.checkSentTx(new tx(), function(err, ret) { + should.not.exist(err); + ret.should.equal(false); + done(); + }); + }); + }); + + + }); From 260adc85877937c1595ad5e7cd11064f2c9ab2a6 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 5 Aug 2014 16:42:51 -0300 Subject: [PATCH 28/31] fix tests --- js/models/core/WalletFactory.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 8858ab2f1..cc56cd976 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -122,10 +122,10 @@ WalletFactory.prototype.create = function(opts) { ); this.log('\t### PublicKeyRing Initialized'); - opts.txProposals = opts.txProposals || new TxProposalsSet({ + opts.txProposals = opts.txProposals || new TxProposals({ networkName: this.networkName, }); - this.log('\t### TxProposalsSet Initialized'); + this.log('\t### TxProposals Initialized'); this.storage._setPassphrase(opts.passphrase); @@ -155,9 +155,9 @@ WalletFactory.prototype._checkVersion = function(inVersion) { //We only check for major version differences if (thisV0 < inV0) { throw new Error('Major difference in software versions' + - '. Received:' + inVersion + - '. Current version:' + this.version + - '. Aborting.'); + '. Received:' + inVersion + + '. Current version:' + this.version + + '. Aborting.'); } }; From 91a05a896edbfa12b5b4a5ee162430e1fa3de9d9 Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Tue, 5 Aug 2014 17:18:02 -0300 Subject: [PATCH 29/31] rm removeRepeated fn --- js/models/blockchain/Insight.js | 38 ++++++--------------------------- test/test.blockchain.Insight.js | 3 --- 2 files changed, 7 insertions(+), 34 deletions(-) diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 04f007c74..22e1bce7d 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -38,33 +38,6 @@ function _asyncForEach(array, fn, callback) { } }; -function removeRepeatedElements(ar) { - var ya = false, - v = "", - aux = [].concat(ar), - r = Array(); - for (var i in aux) { // - v = aux[i]; - ya = false; - for (var a in aux) { - if (v == aux[a]) { - if (ya == false) { - ya = true; - } else { - aux[a] = ""; - } - } - } - } - for (var a in aux) { - if (aux[a] != "") { - r.push(aux[a]); - } - } - return r; -} - - Insight.prototype._getOptions = function(method, path, data) { return { host: this.host, @@ -121,8 +94,11 @@ Insight.prototype.getTransactions = function(addresses, cb) { callback(); }); }, function() { - var clean_txids = removeRepeatedElements(txids); - _asyncForEach(clean_txids, function(txid, callback2) { + var uniqueTxids = {}; + for (var k in txids) { + uniqueTxids[k] = 1; + } + _asyncForEach(Object.keys(uniqueTxids), function(txid, callback2) { var options = self._getOptions('GET', '/api/tx/' + txid); self._request(options, function(err, res) { txs.push(res); @@ -184,8 +160,8 @@ Insight.prototype.checkActivity = function(addresses, cb) { var getOutputs = function(t) { return flatArray( t.vout.map(function(vout) { - return vout.scriptPubKey.addresses; - }) + return vout.scriptPubKey.addresses; + }) ); }; diff --git a/test/test.blockchain.Insight.js b/test/test.blockchain.Insight.js index b5451c99a..e80d628f8 100644 --- a/test/test.blockchain.Insight.js +++ b/test/test.blockchain.Insight.js @@ -229,7 +229,4 @@ describe('Insight model', function() { }); }); - - - }); From afde77e5755f2946c89afbbec928ee31c3cd57eb Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 6 Aug 2014 15:30:47 -0300 Subject: [PATCH 30/31] fix last transaction retrieval --- js/models/blockchain/Insight.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index 22e1bce7d..bafde9861 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -96,7 +96,7 @@ Insight.prototype.getTransactions = function(addresses, cb) { }, function() { var uniqueTxids = {}; for (var k in txids) { - uniqueTxids[k] = 1; + uniqueTxids[txids[k]] = 1; } _asyncForEach(Object.keys(uniqueTxids), function(txid, callback2) { var options = self._getOptions('GET', '/api/tx/' + txid); @@ -160,8 +160,8 @@ Insight.prototype.checkActivity = function(addresses, cb) { var getOutputs = function(t) { return flatArray( t.vout.map(function(vout) { - return vout.scriptPubKey.addresses; - }) + return vout.scriptPubKey.addresses; + }) ); }; From ddc8649bd34681814fd3fc00c2f25a88b3c2011e Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 6 Aug 2014 15:56:17 -0300 Subject: [PATCH 31/31] fix `comment` handling --- js/models/core/TxProposal.js | 2 +- test/mocks/FakeBlockchain.js | 3 +-- test/test.TxProposal.js | 4 +++- test/test.TxProposals.js | 5 +++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/js/models/core/TxProposal.js b/js/models/core/TxProposal.js index 739283b47..3ddeac22e 100644 --- a/js/models/core/TxProposal.js +++ b/js/models/core/TxProposal.js @@ -11,7 +11,7 @@ var buffertools = bitcore.buffertools; var preconditions = require('preconditions').instance(); var VERSION = 1; -var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version']; +var CORE_FIELDS = ['builderObj', 'inputChainPaths', 'version', 'comment']; function TxProposal(opts) { diff --git a/test/mocks/FakeBlockchain.js b/test/mocks/FakeBlockchain.js index d47b44af6..e4c3997bc 100644 --- a/test/mocks/FakeBlockchain.js +++ b/test/mocks/FakeBlockchain.js @@ -1,6 +1,5 @@ 'use strict'; -var imports = require('soop').imports(); var bitcore = require('bitcore'); function FakeBlockchain(opts) { @@ -47,4 +46,4 @@ FakeBlockchain.prototype.sendRawTransaction = function(rawtx, cb) { return cb(txid); }; -module.exports = require('soop')(FakeBlockchain); +module.exports = FakeBlockchain; diff --git a/test/test.TxProposal.js b/test/test.TxProposal.js index 046930695..3ac2f310d 100644 --- a/test/test.TxProposal.js +++ b/test/test.TxProposal.js @@ -98,11 +98,13 @@ describe('TxProposal', function() { createdTs: 1, builder: b, inputChainPaths: 'm/1', + comment: 'hola', }); var o = txp.toObjTrim(); should.exist(o); should.not.exist(o.creator); should.not.exist(o.builder); + should.exist(o.comment); should.exist(o.builderObj); }); @@ -256,7 +258,7 @@ describe('TxProposal', function() { it('with less signatures', function() { var backup = txp.builder.vanilla.scriptSig[0]; txp.builder.merge = function() { - // 3 signatures. + // 2 signatures. this.vanilla.scriptSig = ['0048304502207d8e832bd576c93300e53ab6cbd68641961bec60690c358fd42d8e42b7d7d687022100a1daa89923efdb4c9b615d065058d9e1644f67000694a7d0806759afa7bef19b014cad532103197599f6e209cefef07da2fddc6fe47715a70162c531ffff8e611cef23dfb70d210380a29968851f93af55e581c43d9ef9294577a439a3ca9fc2bc47d1ca2b3e9127210392dccb2ed470a45984811d6402fdca613c175f8f3e4eb8e2306e8ccd7d0aed032103a94351fecc4328bb683bf93a1aa67378374904eac5980c7966723a51897c56e32103e085eb6fa1f20b2722c16161144314070a2c316a9cae2489fd52ce5f63fff6e455ae']; this.tx.ins[0].s = new Buffer(this.vanilla.scriptSig[0], 'hex'); }; diff --git a/test/test.TxProposals.js b/test/test.TxProposals.js index a27258c5f..a7a25e727 100644 --- a/test/test.TxProposals.js +++ b/test/test.TxProposals.js @@ -106,9 +106,10 @@ describe('TxProposals', function() { }); }); describe.skip('#merge', function() { - it('mergeFromObj', function() { + it('should merge', function() { var txps = new TxProposals(); - txps.mergeFromObj(dummyProposal.toObj()); + var d = dummyProposal; + txps.merge(d.toObj(),{}); }); }); });