diff --git a/copay.js b/copay.js index 9149fa071..585fc1677 100644 --- a/copay.js +++ b/copay.js @@ -5,6 +5,7 @@ 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.Structure = require('./js/models/core/Structure'); +module.exports.AddressIndex = require('./js/models/core/AddressIndex'); // components diff --git a/js/models/core/AddressIndex.js b/js/models/core/AddressIndex.js new file mode 100644 index 000000000..8bfe2ffb6 --- /dev/null +++ b/js/models/core/AddressIndex.js @@ -0,0 +1,69 @@ +'use strict'; + + +var imports = require('soop').imports(); + +function AddressIndex(opts) { + opts = opts || {}; + this.walletId = opts.walletId; + + this.changeIndex = opts.changeIndex || 0; + this.receiveIndex = opts.receiveIndex || 0; +} + +AddressIndex.fromObj = function(data) { + if (data instanceof AddressIndex) { + throw new Error('bad data format: Did you use .toObj()?'); + } + var ret = new AddressIndex(data); + return ret; +}; + +AddressIndex.prototype.toObj = function() { + return { + walletId: this.walletId, + changeIndex: this.changeIndex, + receiveIndex: this.receiveIndex, + }; +}; + +AddressIndex.prototype.checkRange = function(index, isChange) { + if ((isChange && index > this.changeIndex) || + (!isChange && index > this.receiveIndex)) { + throw new Error('Out of bounds at index %d isChange: %d', index, isChange); + } +}; + + +AddressIndex.prototype.getChangeIndex = function() { + return this.changeIndex; +}; +AddressIndex.prototype.getReceiveIndex = function() { + return this.receiveIndex; +}; + +AddressIndex.prototype.increment = function(isChange) { + if (isChange) { + this.changeIndex++; + } else { + this.receiveIndex++; + } +}; + +AddressIndex.prototype.merge = function(inAddressIndex) { + var hasChanged = false; + + // Indexes + if (inAddressIndex.changeIndex > this.changeIndex) { + this.changeIndex = inAddressIndex.changeIndex; + hasChanged = true; + } + + if (inAddressIndex.receiveIndex > this.receiveIndex) { + this.receiveIndex = inAddressIndex.receiveIndex; + hasChanged = true; + } + return hasChanged; +}; + +module.exports = require('soop')(AddressIndex); diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js index cc2a4d715..7475f9146 100644 --- a/js/models/core/PrivateKey.js +++ b/js/models/core/PrivateKey.js @@ -81,12 +81,15 @@ PrivateKey.prototype.get = function(index,isChange) { return this.getForPath(path); }; -PrivateKey.prototype.getAll = function(addressIndex, changeAddressIndex) { +PrivateKey.prototype.getAll = function(receiveIndex, changeIndex) { + if (typeof receiveIndex === 'undefined' || typeof changeIndex === 'undefined') + throw new Error('Invalid parameters'); + var ret = []; - for(var i=0;i this.changeAddressIndex) || - (!isChange && index > this.addressIndex)) { - throw new Error('Out of bounds at getAddress: Index %d isChange: %d', index, isChange); - } -}; - // TODO this could be cached PublicKeyRing.prototype.getRedeemScript = function (index, isChange) { - this._checkIndexRange(index, isChange); + this.indexes.checkRange(index, isChange); var pubKeys = this.getPubKeys(index, isChange); var script = Script.createMultisig(this.requiredCopayers, pubKeys); @@ -197,14 +186,9 @@ PublicKeyRing.prototype.getScriptPubKeyHex = function (index, isChange) { //generate a new address, update index. PublicKeyRing.prototype.generateAddress = function(isChange) { - var ret = - this.getAddress(isChange ? this.changeAddressIndex : this.addressIndex, isChange); - if (isChange) { - this.changeAddressIndex++; - } else { - this.addressIndex++; - } - + var index = isChange ? this.indexes.getChangeIndex() : this.indexes.getReceiveIndex(); + var ret = this.getAddress(index, isChange); + this.indexes.increment(isChange); return ret; }; @@ -219,7 +203,7 @@ PublicKeyRing.prototype.getAddressesInfo = function(opts) { var ret = []; if (!opts.excludeChange) { - for (var i=0; i this.changeAddressIndex) { - this.changeAddressIndex = inPKR.changeAddressIndex; - hasChanged = true; - } - - if (inPKR.addressIndex > this.addressIndex) { - this.addressIndex = inPKR.addressIndex; - hasChanged = true; - } - return hasChanged; -}; - PublicKeyRing.prototype._mergePubkeys = function(inPKR) { var self = this; var hasChanged = false; @@ -330,7 +298,7 @@ PublicKeyRing.prototype.merge = function(inPKR, ignoreId) { this._checkInPRK(inPKR, ignoreId); - if (this._mergeIndexes(inPKR)) + if (this.indexes.merge(inPKR.indexes)) hasChanged = true; if (this._mergePubkeys(inPKR)) diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 3a6f32138..72f25743f 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -72,6 +72,16 @@ Wallet.prototype.connectToAll = function() { } }; +Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { + this.log('RECV INDEXES:', data); + var inIndexes = copay.AddressIndex.fromObj(data.indexes); + var hasChanged = this.publicKeyRing.indexes.merge(inIndexes); + if (hasChanged) { + this.emit('publicKeyRingUpdated'); + this.store(); + } +}; + Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { this.log('RECV PUBLICKEYRING:', data); @@ -87,9 +97,9 @@ Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { if (this.publicKeyRing.isComplete()) { this._lockIncomming(); } + this.emit('publicKeyRingUpdated'); + this.store(); } - this.emit('publicKeyRingUpdated'); - this.store(); }; @@ -142,6 +152,9 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { case 'txProposals': this._handleTxProposals(senderId, data, isInbound); break; + case 'indexes': + this._handleIndexes(senderId, data, isInbound); + break; } }; @@ -378,6 +391,15 @@ Wallet.prototype.sendPublicKeyRing = function(recipients) { walletId: this.id, }); }; +Wallet.prototype.sendIndexes = function(recipients) { + this.log('### INDEXES TO:', recipients || 'All', this.publicKeyRing.indexes.toObj()); + + this.network.send(recipients, { + type: 'indexes', + indexes: this.publicKeyRing.indexes.toObj(), + walletId: this.id, + }); +}; Wallet.prototype.getName = function() { return this.name || this.id; @@ -390,7 +412,7 @@ Wallet.prototype._doGenerateAddress = function(isChange) { Wallet.prototype.generateAddress = function(isChange, cb) { var addr = this._doGenerateAddress(isChange); - this.sendPublicKeyRing(); + this.sendIndexes(); this.store(); if (cb) return cb(addr); return addr; @@ -596,7 +618,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, opts, cb) { this.getUnspent(function(err, safeUnspent) { var ntxid = self.createTxSync(toAddress, amountSatStr, safeUnspent, opts); if (ntxid) { - self.sendPublicKeyRing(); + self.sendIndexes(); self.sendTxProposals(null, ntxid); self.store(); self.emit('txProposalsUpdated'); diff --git a/test/test.AddressIndex.js b/test/test.AddressIndex.js new file mode 100644 index 000000000..d78ea3407 --- /dev/null +++ b/test/test.AddressIndex.js @@ -0,0 +1,84 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var bitcore = bitcore || require('bitcore'); +var Address = bitcore.Address; +var buffertools = bitcore.buffertools; +var copay = copay || require('../copay'); +var PublicKeyRing = copay.PublicKeyRing; +var AddressIndex = copay.AddressIndex; + + +var config = { + networkName:'livenet', +}; + +var createAI = function () { + var i = new AddressIndex(); + should.exist(i); + + i.walletId = '1234567'; + + return i; +}; + +describe('AddressIndex model', function() { + + it('should create an instance (livenet)', function () { + var i = new AddressIndex(); + should.exist(i); + }); + + it('show be able to tostore and read', function () { + var i = createAI(); + var changeN = 2; + var addressN = 2; + for(var j=0; j