Merge pull request #820 from cmgustavo/feature/02-addressbook

Creator's signature for addressbook entry
This commit is contained in:
Matias Alejo Garcia 2014-07-07 20:25:45 -03:00
commit 495647d2ac
6 changed files with 173 additions and 88 deletions

View file

@ -645,4 +645,10 @@ ul.pagination li.current a:hover, ul.pagination li.current a:focus {
display: block; display: block;
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.addressbook-disabled td {
color: #ccc;
}
.addressbook-disabled td a {
color: #7A9FB6;
}

View file

@ -764,7 +764,7 @@
</div> </div>
</form> </form>
</div> </div>
<div class="medium-8 medium-centered large-8 large-centered columns"> <div class="medium-9 medium-centered large-9 large-centered columns">
<hr> <hr>
<h3>Address Book</h3> <h3>Address Book</h3>
<p class="text-gray" ng-hide="showAddressBook()">Empty. Create an alias for your addresses</p> <p class="text-gray" ng-hide="showAddressBook()">Empty. Create an alias for your addresses</p>
@ -775,16 +775,19 @@
<th>Address</td> <th>Address</td>
<th>Creator</td> <th>Creator</td>
<th>Date</td> <th>Date</td>
<th>&nbsp;</td> <th></td>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="(addr, info) in $root.wallet.addressBook" ng-if="info.copayerId != -1"> <tr
ng-repeat="(addr, info) in $root.wallet.addressBook"
ng-class="{'addressbook-disabled': info.hidden}">
<td><a ng-click="copyAddress(addr)" title="Copy address">{{info.label}}</a></td> <td><a ng-click="copyAddress(addr)" title="Copy address">{{info.label}}</a></td>
<td>{{addr}}</td> <td>{{addr}}</td>
<td>{{$root.wallet.publicKeyRing.nicknameForCopayer(info.copayerId)}}</td> <td>{{$root.wallet.publicKeyRing.nicknameForCopayer(info.copayerId)}}</td>
<td><time>{{info.createdTs | amCalendar}}</time></td> <td><time>{{info.createdTs | amCalendar}}</time></td>
<td><a ng-click="deleteAddressBook(addr)"><i class="fi-trash"></i></a></td> <td><a style="text-decoration: initial;" ng-click="toggleAddressBookEntry(addr)">{{info.hidden ?
'Enable' : 'Disable'}}</a></td>
</tr> </tr>
</tbody> </tbody>
</table> </table>

View file

@ -14,7 +14,7 @@ angular.module('copayApp.controllers').controller('SendController',
var flag; var flag;
if (w) { if (w) {
for (var k in w.addressBook) { for (var k in w.addressBook) {
if (w.addressBook[k].copayerId != -1) { if (w.addressBook[k]) {
flag = true; flag = true;
break; break;
} }
@ -194,23 +194,9 @@ angular.module('copayApp.controllers').controller('SendController',
}, 500); }, 500);
}; };
$scope.deleteAddressBook = function(addressBook) { $scope.toggleAddressBookEntry = function(key) {
var w = $rootScope.wallet; var w = $rootScope.wallet;
$timeout(function() { w.toggleAddressBookEntry(key);
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);
}; };
$scope.copyAddress = function(address) { $scope.copyAddress = function(address) {
@ -262,7 +248,6 @@ angular.module('copayApp.controllers').controller('SendController',
} }
$rootScope.$digest(); $rootScope.$digest();
}, 500); }, 500);
$anchorScroll();
// reset fields // reset fields
$scope.newaddress = $scope.newlabel = null; $scope.newaddress = $scope.newlabel = null;
}); });

View file

@ -128,7 +128,7 @@ angular.module('copayApp.directives')
var address = attrs.address; var address = attrs.address;
var contact = scope.wallet.addressBook[address]; var contact = scope.wallet.addressBook[address];
if (contact) { if (contact && !contact.hidden) {
element.append(contact.label); element.append(contact.label);
attrs['tooltip'] = attrs.address; attrs['tooltip'] = attrs.address;
} else { } else {

View file

@ -140,10 +140,8 @@ Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) {
var hasChange; var hasChange;
for (var key in rcv) { for (var key in rcv) {
if (!this.addressBook[key]) { if (!this.addressBook[key]) {
this.addressBook[key] = rcv[key]; var isVerified = this.verifyAddressbookEntry(rcv[key], senderId, key);
hasChange = true; if (isVerified) {
} else {
if (rcv[key].createdTs > this.addressBook[key].createdTs) {
this.addressBook[key] = rcv[key]; this.addressBook[key] = rcv[key];
hasChange = true; hasChange = true;
} }
@ -235,7 +233,6 @@ Wallet.prototype.getMyCopayerIdPriv = function() {
return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key return this.privateKey.getIdPriv(); //copayer idpriv is hex of a private key
}; };
Wallet.prototype.getSecret = function() { Wallet.prototype.getSecret = function() {
var pubkeybuf = new Buffer(this.getMyCopayerId(), 'hex'); var pubkeybuf = new Buffer(this.getMyCopayerId(), 'hex');
var str = Base58Check.encode(pubkeybuf); var str = Base58Check.encode(pubkeybuf);
@ -839,23 +836,42 @@ Wallet.prototype._checkAddressBook = function(key) {
Wallet.prototype.setAddressBook = function(key, label) { Wallet.prototype.setAddressBook = function(key, label) {
this._checkAddressBook(key); this._checkAddressBook(key);
var addressbook = { var copayerId = this.getMyCopayerId();
createdTs: Date.now(), var ts = Date.now();
copayerId: this.getMyCopayerId(), var payload = {
label: label 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.sendAddressBook();
this.store(); this.store();
}; };
Wallet.prototype.deleteAddressBook = function(key) { Wallet.prototype.verifyAddressbookEntry = function(rcvEntry, senderId, key) {
if (key) { if (!key) throw new Error('Keys are required');
this.addressBook[key].copayerId = -1; var signature = rcvEntry.signature;
this.addressBook[key].createdTs = Date.now(); var payload = {
this.sendAddressBook(); address: key,
this.store(); 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() { Wallet.prototype.isReady = function() {
@ -868,4 +884,19 @@ Wallet.prototype.offerBackup = function() {
this.store(); 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); module.exports = require('soop')(Wallet);

View file

@ -79,11 +79,13 @@ describe('Wallet model', function() {
label: 'John', label: 'John',
copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03', copayerId: '026a55261b7c898fff760ebe14fd22a71892295f3b49e0ca66727bc0a0d7f94d03',
createdTs: 1403102115, createdTs: 1403102115,
hidden: false
}, },
'2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': { '2MtP8WyiwG7ZdVWM96CVsk2M1N8zyfiVQsY': {
label: 'Jennifer', label: 'Jennifer',
copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7', copayerId: '032991f836543a492bd6d0bb112552bfc7c5f3b7d5388fcbcbf2fbb893b44770d7',
createdTs: 1403103115, createdTs: 1403103115,
hidden: false
} }
}; };
@ -784,61 +786,119 @@ describe('Wallet model', function() {
done(); done();
}); });
var contacts = [{ describe('#AddressBook', function() {
label: 'Charles', var contacts = [{
address: '2N8pJWpXCAxmNLHKVEhz3TtTcYCtHd43xWU ', label: 'Charles',
}, { address: '2N8pJWpXCAxmNLHKVEhz3TtTcYCtHd43xWU ',
label: 'Linda', }, {
address: '2N4Zq92goYGrf5J4F4SZZq7jnPYbCiyRYT2 ', label: 'Linda',
}]; address: '2N4Zq92goYGrf5J4F4SZZq7jnPYbCiyRYT2 ',
}];
it('should create new entry for address book', function() { it('should create new entry for address book', function() {
var w = createW(); var w = createW();
contacts.forEach(function(c) { contacts.forEach(function(c) {
w.setAddressBook(c.address, c.label); 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() { it('should fail if create a duplicate address', function() {
var w = createW(); var w = createW();
w.setAddressBook(contacts[0].address, contacts[0].label);
(function() {
w.setAddressBook(contacts[0].address, contacts[0].label); w.setAddressBook(contacts[0].address, contacts[0].label);
}).should. (function() {
throw(); 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) { it('should show/hide everywhere', function() {
w.setAddressBook(c.address, c.label); 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() { it('#getNetworkName', function() {