diff --git a/css/main.css b/css/main.css index b2ea53654..e3e9db981 100644 --- a/css/main.css +++ b/css/main.css @@ -645,4 +645,10 @@ ul.pagination li.current a:hover, ul.pagination li.current a:focus { display: block; margin-bottom: 1rem; } +.addressbook-disabled td { + color: #ccc; +} +.addressbook-disabled td a { + color: #7A9FB6; +} diff --git a/index.html b/index.html index c5953653e..5a354b627 100644 --- a/index.html +++ b/index.html @@ -764,7 +764,7 @@ -
+

Address Book

Empty. Create an alias for your addresses

@@ -775,16 +775,19 @@ Address Creator Date -   + - + {{info.label}} {{addr}} {{$root.wallet.publicKeyRing.nicknameForCopayer(info.copayerId)}} - + {{info.hidden ? + 'Enable' : 'Disable'}} diff --git a/js/controllers/send.js b/js/controllers/send.js index 0ab3d3858..e9f3d92ec 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('SendController', var flag; if (w) { for (var k in w.addressBook) { - if (w.addressBook[k].copayerId != -1) { + if (w.addressBook[k]) { flag = true; break; } @@ -194,23 +194,9 @@ angular.module('copayApp.controllers').controller('SendController', }, 500); }; - $scope.deleteAddressBook = function(addressBook) { + $scope.toggleAddressBookEntry = function(key) { var w = $rootScope.wallet; - $timeout(function() { - var errorMsg; - try { - w.deleteAddressBook(addressBook); - } catch (e) { - errorMsg = e.message; - } - - if (errorMsg) { - notification.error('Error', errorMsg); - } else { - notification.success('Success', 'Entry removed successfully'); - } - $rootScope.$digest(); - }, 500); + w.toggleAddressBookEntry(key); }; $scope.copyAddress = function(address) { @@ -262,7 +248,6 @@ angular.module('copayApp.controllers').controller('SendController', } $rootScope.$digest(); }, 500); - $anchorScroll(); // reset fields $scope.newaddress = $scope.newlabel = null; }); diff --git a/js/directives.js b/js/directives.js index cac0adb93..e5c0502b2 100644 --- a/js/directives.js +++ b/js/directives.js @@ -128,7 +128,7 @@ angular.module('copayApp.directives') var address = attrs.address; var contact = scope.wallet.addressBook[address]; - if (contact) { + if (contact && !contact.hidden) { element.append(contact.label); attrs['tooltip'] = attrs.address; } else { diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index 5ee65c49b..7e892dd42 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -140,10 +140,8 @@ Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) { var hasChange; for (var key in rcv) { if (!this.addressBook[key]) { - this.addressBook[key] = rcv[key]; - hasChange = true; - } else { - if (rcv[key].createdTs > this.addressBook[key].createdTs) { + var isVerified = this.verifyAddressbookEntry(rcv[key], senderId, key); + if (isVerified) { this.addressBook[key] = rcv[key]; hasChange = true; } @@ -235,7 +233,6 @@ Wallet.prototype.getMyCopayerIdPriv = function() { return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key }; - Wallet.prototype.getSecret = function() { var pubkeybuf = new Buffer(this.getMyCopayerId(), 'hex'); var str = Base58Check.encode(pubkeybuf); @@ -839,23 +836,42 @@ Wallet.prototype._checkAddressBook = function(key) { Wallet.prototype.setAddressBook = function(key, label) { this._checkAddressBook(key); - var addressbook = { - createdTs: Date.now(), - copayerId: this.getMyCopayerId(), - label: label + var copayerId = this.getMyCopayerId(); + var ts = Date.now(); + var payload = { + address: key, + label: label, + copayerId: copayerId, + createdTs: ts }; - this.addressBook[key] = addressbook; + var newEntry = { + hidden: false, + createdTs: ts, + copayerId: copayerId, + label: label, + signature: this.signJson(payload) + }; + this.addressBook[key] = newEntry; this.sendAddressBook(); this.store(); }; -Wallet.prototype.deleteAddressBook = function(key) { - if (key) { - this.addressBook[key].copayerId = -1; - this.addressBook[key].createdTs = Date.now(); - this.sendAddressBook(); - this.store(); - } +Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) { + if (!key) throw new Error('Keys are required'); + var signature = rcvEntry.signature; + var payload = { + address: key, + label: rcvEntry.label, + copayerId: rcvEntry.copayerId, + createdTs: rcvEntry.createdTs + }; + return this.verifySignedJson(senderId, payload, signature); +} + +Wallet.prototype.toggleAddressBookEntry = function(key) { + if (!key) throw new Error('Key is required'); + this.addressBook[key].hidden = !this.addressBook[key].hidden; + this.store(); }; Wallet.prototype.isReady = function() { @@ -868,4 +884,19 @@ Wallet.prototype.offerBackup = function() { this.store(); }; +Wallet.prototype.signJson = function(payload) { + var key = new bitcore.Key(); + key.private = new Buffer(this.getMyCopayerIdPriv(), 'hex'); + key.regenerateSync(); + var sign = bitcore.Message.sign(JSON.stringify(payload), key); + return sign.toString('hex'); +} + +Wallet.prototype.verifySignedJson = function(senderId, payload, signature) { + var pubkey = new Buffer(senderId, 'hex'); + var sign = new Buffer(signature, 'hex'); + var v = bitcore.Message.verifyWithPubKey(pubkey, JSON.stringify(payload), sign); + return v; +} + module.exports = require('soop')(Wallet); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index 3ba333d43..42991ed57 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -79,11 +79,13 @@ describe('Wallet model', function() { label: 'John', copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', createdTs: 1403102115, + hidden: false }, '2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': { label: 'Jennifer', copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7', createdTs: 1403103115, + hidden: false } }; @@ -784,61 +786,119 @@ describe('Wallet model', function() { done(); }); - var contacts = [{ - label: 'Charles', - address: '2N8pJWpXCAxmNLHKVEhz3TtTcYCtHd43xWU ', - }, { - label: 'Linda', - address: '2N4Zq92goYGrf5J4F4SZZq7jnPYbCiyRYT2 ', - }]; + describe('#AddressBook', function() { + var contacts = [{ + label: 'Charles', + address: '2N8pJWpXCAxmNLHKVEhz3TtTcYCtHd43xWU ', + }, { + label: 'Linda', + address: '2N4Zq92goYGrf5J4F4SZZq7jnPYbCiyRYT2 ', + }]; - it('should create new entry for address book', function() { - var w = createW(); - contacts.forEach(function(c) { - w.setAddressBook(c.address, c.label); + it('should create new entry for address book', function() { + var w = createW(); + contacts.forEach(function(c) { + w.setAddressBook(c.address, c.label); + }); + Object.keys(w.addressBook).length.should.equal(4); }); - Object.keys(w.addressBook).length.should.equal(4); - }); - it('should fail if create a duplicate address', function() { - var w = createW(); - w.setAddressBook(contacts[0].address, contacts[0].label); - (function() { + it('should fail if create a duplicate address', function() { + var w = createW(); w.setAddressBook(contacts[0].address, contacts[0].label); - }).should. - throw(); - }); - - it('should delete an entry for address book', function() { - var w = createW(); - contacts.forEach(function(c) { - w.setAddressBook(c.address, c.label); + (function() { + w.setAddressBook(contacts[0].address, contacts[0].label); + }).should. + throw(); + }); + + it('should show/hide everywhere', function() { + var w = createW(); + var key = '2NFR2kzH9NUdp8vsXTB4wWQtTtzhpKxsyoJ'; + w.toggleAddressBookEntry(key); + w.addressBook[key].hidden.should.equal(true); + w.toggleAddressBookEntry(key); + w.addressBook[key].hidden.should.equal(false); + (function() { + w.toggleAddressBookEntry(); + }).should.throw(); + }); + + it('handle network addressBook correctly', function() { + var w = createW(); + + var data = { + type: "addressbook", + addressBook: { + "3Ae1ieAYNXznm7NkowoFTu5MkzgrTfDz8Z" : { + copayerId: "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb", + createdTs: 1404769393509, + hidden: false, + label: "adsf", + signature: "3046022100d4cdefef66ab8cea26031d5df03a38fc9ec9b09b0fb31d3a26b6e204918e9e78022100ecdbbd889ec99ea1bfd471253487af07a7fa7c0ac6012ca56e10e66f335e4586" + } + }, + walletId: "11d23e638ed84c06", + isBroadcast: 1 + }; + + var senderId = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb"; + + Object.keys(w.addressBook).length.should.equal(2); + w._handleAddressBook(senderId, data, true); + Object.keys(w.addressBook).length.should.equal(3); + }); + + it('should return signed object', function() { + var w = createW(); + var payload = { + address: 'msj42CCGruhRsFrGATiUuh25dtxYtnpbTx', + label: 'Faucet', + copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', + createdTs: 1403102115 + }; + should.exist(w.signJson(payload)); + }); + + it('should verify signed object', function() { + var w = createW(); + + var payload = { + address: "3Ae1ieAYNXznm7NkowoFTu5MkzgrTfDz8Z", + label: "adsf", + copayerId: "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb", + createdTs: 1404769393509 + } + + var signature = "3046022100d4cdefef66ab8cea26031d5df03a38fc9ec9b09b0fb31d3a26b6e204918e9e78022100ecdbbd889ec99ea1bfd471253487af07a7fa7c0ac6012ca56e10e66f335e4586"; + + var pubKey = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb"; + + w.verifySignedJson(pubKey, payload, signature).should.equal(true); + payload.label = 'Another'; + w.verifySignedJson(pubKey, payload, signature).should.equal(false); + }); + + it('should verify signed addressbook entry', function() { + var w = createW(); + var key = "3Ae1ieAYNXznm7NkowoFTu5MkzgrTfDz8Z"; + var pubKey = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb"; + w.addressBook[key] = { + copayerId: pubKey, + createdTs: 1404769393509, + hidden: false, + label: "adsf", + signature: "3046022100d4cdefef66ab8cea26031d5df03a38fc9ec9b09b0fb31d3a26b6e204918e9e78022100ecdbbd889ec99ea1bfd471253487af07a7fa7c0ac6012ca56e10e66f335e4586" + }; + + w.verifyAddressbookEntry(w.addressBook[key], pubKey, key).should.equal(true); + w.addressBook[key].label = 'Another'; + w.verifyAddressbookEntry(w.addressBook[key], pubKey, key).should.equal(false); + (function() { + w.verifyAddressbookEntry(); + }).should.throw(); }); - Object.keys(w.addressBook).length.should.equal(4); - var key = contacts[0].address; - w.deleteAddressBook(key); - w.addressBook[key].copayerId.should.equal(-1); - }); - it('handle network addressBook correctly', function() { - var w = createW(); - var data = { - walletId: w.id, - addressBook: { - 'msj42CCGruhRsFrGATiUuh25dtxYtnpbTx': { - label: 'Faucet', - copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', - createdTs: 1403102115, - } - }, - type: 'addressbook' - }; - Object.keys(w.addressBook).length.should.equal(2); - w._handleAddressBook('senderID', data, true); - Object.keys(w.addressBook).length.should.equal(3); - data.addressBook['msj42CCGruhRsFrGATiUuh25dtxYtnpbTx'].createdTs = 1403102215; - w._handleAddressBook('senderID', data, true); - Object.keys(w.addressBook).length.should.equal(3); }); it('#getNetworkName', function() {