From 6e5f06693d9d0f9f2c5ecf98f41c3c8649e4902d Mon Sep 17 00:00:00 2001 From: Matias Alejo Garcia Date: Wed, 30 Jul 2014 21:19:39 -0300 Subject: [PATCH] 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); });