mirror of https://github.com/BTCPrivate/copay.git
commit
bc5c473752
44
CHANGES.md
44
CHANGES.md
|
@ -1,12 +1,42 @@
|
||||||
CHANGES
|
CHANGES
|
||||||
=======
|
=======
|
||||||
|
|
||||||
v0.1.2 (TODO / DRAFT!)
|
v0.2.1 (DRAFT!)
|
||||||
------
|
------
|
||||||
|
|
||||||
* Test coverage from xx to xx
|
New features
|
||||||
* (Encryption and signing) [detail]
|
------------
|
||||||
* New wallet settings: Unit, etc [detail]
|
* Smart backup restore procedure: now it is possible to restore all wallet's fund with an old backup (thanks Ian for the feedback).
|
||||||
* Smart backup restore procedure: [explain]
|
* Transaction proposals can now have a short note attached for reference (thanks Gentry for the feedback).
|
||||||
* Default to SSL insight [explain]
|
* New wallet settings: Bitcoin Unit, defaults to *bits* (thanks Eric for the suggestion)
|
||||||
* ...
|
* Backup is now autogenerated on wallet completion (thanks Eric for the suggestion)
|
||||||
|
* A wallet now can be removed from a particular system from the UX.
|
||||||
|
|
||||||
|
Security
|
||||||
|
--------
|
||||||
|
* Asymetric encryption and signing using ECIES. Details at https://gist.github.com/ryanxcharles/c29fc94d31de7c8c89dc
|
||||||
|
* Default SSL connection to Insight servers
|
||||||
|
|
||||||
|
Code quality
|
||||||
|
------------
|
||||||
|
* Test coverage from 60.9% to 72% (1) (thanks Ryan for insisting on this)
|
||||||
|
* Mayor refactoring of Angular servicies (backupService, controllerUtils, wallet's Indexes handling, txProposal merge related functions,
|
||||||
|
* Add +30 karma tests for Angular controllers and services
|
||||||
|
* Unified js-beautifier format throw all the code
|
||||||
|
|
||||||
|
Other
|
||||||
|
-----
|
||||||
|
* Backup to email have been removed
|
||||||
|
* Performance improvements when signing transactions
|
||||||
|
* Minor UX and wording fixes (address list on receiving funds, notifications fixes, error handling on bad passwords, network timeouts, etc).
|
||||||
|
|
||||||
|
Next steps
|
||||||
|
----------
|
||||||
|
* Make Copay available in other platforms (update Gordon's Atom shell packages, Android bundle, Firefox / Chrome extensions).
|
||||||
|
* Implement Copay 2.0 design: http://invis.io/FWZGJWUS (please take a look at leave comments)
|
||||||
|
|
||||||
|
|
||||||
|
Please check https://github.com/bitpay/copay/wiki/Copay-Known-issues before using Copay.
|
||||||
|
|
||||||
|
|
||||||
|
(1) not including Karma tests, not included on Coveralls yet.
|
||||||
|
|
|
@ -635,3 +635,9 @@ ul.pagination li.current a:hover, ul.pagination li.current a:focus {
|
||||||
color:white;
|
color:white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-note {
|
||||||
|
margin-top: -10px;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
65
index.html
65
index.html
|
@ -20,7 +20,7 @@
|
||||||
<div class="large-3 medium-3 small-5 columns">
|
<div class="large-3 medium-3 small-5 columns">
|
||||||
<span class="logo"></span>
|
<span class="logo"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="large-9 medium-9 small-7 columns text-center p10t" ng-show="$root.wallet && $root.wallet.publicKeyRing.isComplete()">
|
<div class="large-9 medium-9 small-7 columns text-center p10t" ng-show="$root.wallet && $root.wallet.isReady()">
|
||||||
<div class="large-4 medium-4 columns line-dashed-v">
|
<div class="large-4 medium-4 columns line-dashed-v">
|
||||||
<a href="#/addresses" class="has-tip" tooltip-placement="bottom" tooltip="ID: {{$root.wallet.id}}">
|
<a href="#/addresses" class="has-tip" tooltip-placement="bottom" tooltip="ID: {{$root.wallet.id}}">
|
||||||
<strong><span>{{$root.wallet.getName()}}</span></strong>
|
<strong><span>{{$root.wallet.getName()}}</span></strong>
|
||||||
|
@ -60,7 +60,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="top-bar" data-topbar ng-show="$root.wallet && $root.wallet.publicKeyRing.isComplete()">
|
<nav class="top-bar" data-topbar ng-show="$root.wallet && $root.wallet.isReady()">
|
||||||
<ul class="title-area">
|
<ul class="title-area">
|
||||||
<li class="name"></li>
|
<li class="name"></li>
|
||||||
<li class="toggle-topbar menu-icon">
|
<li class="toggle-topbar menu-icon">
|
||||||
|
@ -100,19 +100,10 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row" ng-if='$root.$flashMessage.message' notification>
|
<div ng-if='$root.wallet && !$root.wallet.isReady() && !loading'>
|
||||||
<div class="small-8 large-centered columns">
|
|
||||||
<div data-alert class="alert-box radius {{$root.$flashMessage.type}}">
|
|
||||||
{{$root.$flashMessage.message}}
|
|
||||||
<a ng-click="clearFlashMessage()" class="close">×</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div ng-if='$root.wallet && !$root.wallet.publicKeyRing.isComplete() && !loading'>
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="large-12 medium-12 small-12 columns">
|
<div class="large-12 medium-12 small-12 columns">
|
||||||
<div class="alert-box secondary radius" data-alert>
|
<div class="alert-box secondary radius" ng-if="!$root.wallet.publicKeyRing.isComplete()" data-alert>
|
||||||
<i class="fi-info"></i>
|
<i class="fi-info"></i>
|
||||||
Not all copayers have joined your wallet yet.
|
Not all copayers have joined your wallet yet.
|
||||||
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()>1">
|
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()>1">
|
||||||
|
@ -123,10 +114,14 @@
|
||||||
</span>
|
</span>
|
||||||
yet to join.
|
yet to join.
|
||||||
</div>
|
</div>
|
||||||
|
<div class="alert-box success radius" ng-if="$root.wallet.publicKeyRing.isComplete()" data-alert>
|
||||||
|
<i class="fi-check"></i>
|
||||||
|
All copayers have joined the wallet, it's ready for use!
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row" ng-if="!$root.wallet.publicKeyRing.isComplete()">
|
||||||
<div class="large-12 medium-12 small-12 columns ">
|
<div class="large-12 medium-12 small-12 columns ">
|
||||||
<div class="panel radius m30v">
|
<div class="panel radius m30v">
|
||||||
<h3 class="m15b">Share this secret with your other copayers
|
<h3 class="m15b">Share this secret with your other copayers
|
||||||
|
@ -150,7 +145,18 @@
|
||||||
<div class="large-12 medium-12 small-12 columns ">
|
<div class="large-12 medium-12 small-12 columns ">
|
||||||
<div class="box-setup-copayers">
|
<div class="box-setup-copayers">
|
||||||
<div class="box-setup-copayers-fix">
|
<div class="box-setup-copayers-fix">
|
||||||
<img class="box-setup-copay" ng-repeat="i in getNumber($root.wallet.totalCopayers) track by $index" src="./img/satoshi.gif" alt="Copayer {{$index+1}}-{{totalCopayers}}" ng-class="{'box-setup-copay-required': ($index+1) <= $root.wallet.publicKeyRing.registeredCopayers()}">
|
<img class="box-setup-copay"
|
||||||
|
ng-repeat="i in getNumber($root.wallet.totalCopayers) track by $index"
|
||||||
|
src="./img/satoshi.gif"
|
||||||
|
title="Copayer {{$index+1}}-{{totalCopayers}}"
|
||||||
|
ng-class="{'box-setup-copay-required': ($index+1) <= $root.wallet.publicKeyRing.registeredCopayers()}">
|
||||||
|
|
||||||
|
<button class="button primary radius right"
|
||||||
|
ng-click="backupAndOpen()"
|
||||||
|
ng-disabled="!$root.wallet.publicKeyRing.isComplete()">
|
||||||
|
Backup keys and continue
|
||||||
|
</button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -161,12 +167,12 @@
|
||||||
|
|
||||||
<div notifications="middle right"></div>
|
<div notifications="middle right"></div>
|
||||||
|
|
||||||
<div id="main" class="row" ng-class="{'main-home': !$root.wallet || !$root.wallet.publicKeyRing.isComplete()}">
|
<div id="main" class="row" ng-class="{'main-home': !$root.wallet || !$root.wallet.isReady()}">
|
||||||
<div class="large-12 columns" ng-view></div>
|
<div class="large-12 columns" ng-view></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="footer" data-ng-controller="FooterController" ng-class="{'footer-home': !$root.wallet || !$root.wallet.publicKeyRing.isComplete()}">
|
<div id="footer" data-ng-controller="FooterController" ng-class="{'footer-home': !$root.wallet || !$root.wallet.isReady()}">
|
||||||
<link rel="stylesheet" ng-href="{{theme}}">
|
<link rel="stylesheet" ng-href="{{theme}}">
|
||||||
<div ng-show="!$root.wallet">
|
<div ng-show="!$root.wallet">
|
||||||
<div class="large-12 columns text-left">
|
<div class="large-12 columns text-left">
|
||||||
|
@ -174,7 +180,7 @@
|
||||||
<small>v{{version}}</small>
|
<small>v{{version}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="$root.wallet && !$root.wallet.publicKeyRing.isComplete()">
|
<div ng-show="$root.wallet && !$root.wallet.isReady()">
|
||||||
<div class="large-6 medium-6 small-6 columns">
|
<div class="large-6 medium-6 small-6 columns">
|
||||||
<strong>{{$root.wallet.getName()}}</strong>
|
<strong>{{$root.wallet.getName()}}</strong>
|
||||||
{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}
|
{{$root.wallet.requiredCopayers}}-of-{{$root.wallet.totalCopayers}}
|
||||||
|
@ -186,7 +192,7 @@
|
||||||
<small>v{{version}}</small>
|
<small>v{{version}}</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ng-show="$root.wallet && $root.wallet.publicKeyRing.isComplete()">
|
<div ng-show="$root.wallet && $root.wallet.isReady()">
|
||||||
<div class="large-3 medium-4 hide-for-small columns">
|
<div class="large-3 medium-4 hide-for-small columns">
|
||||||
<div>
|
<div>
|
||||||
<strong>{{$root.wallet.getName()}}</strong>
|
<strong>{{$root.wallet.getName()}}</strong>
|
||||||
|
@ -285,7 +291,7 @@
|
||||||
<div ng-controller="ImportController">
|
<div ng-controller="ImportController">
|
||||||
<div data-alert class="alert-box info radius" ng-show="loading">
|
<div data-alert class="alert-box info radius" ng-show="loading">
|
||||||
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
|
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
|
||||||
Importing wallet...
|
{{ importStatus }}
|
||||||
</div>
|
</div>
|
||||||
<div ng-init="choosefile=0; pastetext=0" ng-show="!loading">
|
<div ng-init="choosefile=0; pastetext=0" ng-show="!loading">
|
||||||
<h3>{{title}}</h3>
|
<h3>{{title}}</h3>
|
||||||
|
@ -372,7 +378,10 @@
|
||||||
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m10b">
|
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m10b">
|
||||||
<div class="box-setup-copayers">
|
<div class="box-setup-copayers">
|
||||||
<div class="box-setup-copayers-fix">
|
<div class="box-setup-copayers-fix">
|
||||||
<img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index" src="./img/satoshi.gif" alt="Copayer {{$index+1}}-{{totalCopayers}}" ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}">
|
<img class="box-setup-copay" ng-repeat="i in getNumber(totalCopayers) track by $index"
|
||||||
|
src="./img/satoshi.gif"
|
||||||
|
title="Copayer {{$index+1}}-{{totalCopayers}}"
|
||||||
|
ng-class="{'box-setup-copay-required': ($index+1) <= requiredCopayers}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -396,7 +405,7 @@
|
||||||
<!-- ADDRESS -->
|
<!-- ADDRESS -->
|
||||||
<script type="text/ng-template" id="addresses.html">
|
<script type="text/ng-template" id="addresses.html">
|
||||||
<div class="addresses" ng-controller="AddressesController">
|
<div class="addresses" ng-controller="AddressesController">
|
||||||
<div ng-show='$root.wallet.publicKeyRing.isComplete()'>
|
<div ng-show='$root.wallet.isReady()'>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="large-9 medium-12 columns" ng-if="addresses[0]">
|
<div class="large-9 medium-12 columns" ng-if="addresses[0]">
|
||||||
<div class="large-8 medium-8 columns" ng-init="showAll=0">
|
<div class="large-8 medium-8 columns" ng-init="showAll=0">
|
||||||
|
@ -459,7 +468,7 @@
|
||||||
<!-- TRANSACTIONS -->
|
<!-- TRANSACTIONS -->
|
||||||
<script type="text/ng-template" id="transactions.html">
|
<script type="text/ng-template" id="transactions.html">
|
||||||
<div class="transactions" data-ng-controller="TransactionsController">
|
<div class="transactions" data-ng-controller="TransactionsController">
|
||||||
<div class="row" ng-show='$root.wallet.publicKeyRing.isComplete()'>
|
<div class="row" ng-show='$root.wallet.isReady()'>
|
||||||
<div class="large-12 columns" ng-show="wallet.totalCopayers > 1">
|
<div class="large-12 columns" ng-show="wallet.totalCopayers > 1">
|
||||||
<h4> Transaction proposals <small>({{txs.length}})</small></h4>
|
<h4> Transaction proposals <small>({{txs.length}})</small></h4>
|
||||||
|
|
||||||
|
@ -639,7 +648,7 @@
|
||||||
<!-- SEND -->
|
<!-- SEND -->
|
||||||
<script type="text/ng-template" id="send.html">
|
<script type="text/ng-template" id="send.html">
|
||||||
<div class="send" data-ng-controller="SendController">
|
<div class="send" data-ng-controller="SendController">
|
||||||
<div class="row" ng-show='$root.wallet.publicKeyRing.isComplete()'>
|
<div class="row" ng-show='$root.wallet.isReady()'>
|
||||||
<div class="medium-6 medium-centered large-6 large-centered columns">
|
<div class="medium-6 medium-centered large-6 large-centered columns">
|
||||||
<h3>{{title}}</h3>
|
<h3>{{title}}</h3>
|
||||||
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
|
||||||
|
@ -688,7 +697,6 @@
|
||||||
<div class="row collapse">
|
<div class="row collapse">
|
||||||
<label for="amount">Amount
|
<label for="amount">Amount
|
||||||
<small ng-hide="!sendForm.amount.$pristine">required</small>
|
<small ng-hide="!sendForm.amount.$pristine">required</small>
|
||||||
<i class="fi-arrow-up" title="Send all funds" ng-click="topAmount()"></i>
|
|
||||||
<small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small>
|
<small class="is-valid" ng-show="!sendForm.amount.$invalid && !sendForm.amount.$pristine">Valid</small>
|
||||||
<small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount">
|
<small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine && !notEnoughAmount">
|
||||||
Not valid
|
Not valid
|
||||||
|
@ -698,9 +706,14 @@
|
||||||
<div class="small-9 columns">
|
<div class="small-9 columns">
|
||||||
<input type="number" id="amount" ng-disabled="loading"
|
<input type="number" id="amount" ng-disabled="loading"
|
||||||
name="amount" placeholder="Amount" ng-model="amount"
|
name="amount" placeholder="Amount" ng-model="amount"
|
||||||
min="1" max="10000000000" enough-amount required
|
min="0.0001" max="10000000000" enough-amount required
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
>
|
>
|
||||||
|
<a class="small input-note" title="Send all funds"
|
||||||
|
ng-show="$root.availableBalance > 0"
|
||||||
|
ng-click="topAmount(sendForm)">
|
||||||
|
Use all funds ({{getAvailableAmount()}} {{$root.unitName}})
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-3 columns">
|
<div class="small-3 columns">
|
||||||
<span class="postfix">{{$root.unitName}}</span>
|
<span class="postfix">{{$root.unitName}}</span>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('HeaderController',
|
angular.module('copayApp.controllers').controller('HeaderController',
|
||||||
function($scope, $rootScope, $location, notification, $http, controllerUtils) {
|
function($scope, $rootScope, $location, notification, $http, controllerUtils, backupService) {
|
||||||
$scope.menu = [{
|
$scope.menu = [{
|
||||||
'title': 'Addresses',
|
'title': 'Addresses',
|
||||||
'icon': 'fi-address-book',
|
'icon': 'fi-address-book',
|
||||||
|
@ -51,10 +51,7 @@ angular.module('copayApp.controllers').controller('HeaderController',
|
||||||
|
|
||||||
$rootScope.$watch('insightError', function(status) {
|
$rootScope.$watch('insightError', function(status) {
|
||||||
if (status === -1) {
|
if (status === -1) {
|
||||||
$rootScope.$flashMessage = {
|
notification.success('Networking restored', 'Connection to insight restored');
|
||||||
type: 'success',
|
|
||||||
message: 'Networking Restored :)',
|
|
||||||
};
|
|
||||||
$rootScope.insightError = 0;
|
$rootScope.insightError = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -98,7 +95,6 @@ angular.module('copayApp.controllers').controller('HeaderController',
|
||||||
|
|
||||||
$scope.signout = function() {
|
$scope.signout = function() {
|
||||||
logout();
|
logout();
|
||||||
$scope.clearFlashMessage();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.refresh = function() {
|
$scope.refresh = function() {
|
||||||
|
@ -111,10 +107,6 @@ angular.module('copayApp.controllers').controller('HeaderController',
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.clearFlashMessage = function() {
|
|
||||||
$rootScope.$flashMessage = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
$rootScope.isCollapsed = true;
|
$rootScope.isCollapsed = true;
|
||||||
|
|
||||||
$scope.toggleCollapse = function() {
|
$scope.toggleCollapse = function() {
|
||||||
|
@ -135,4 +127,11 @@ angular.module('copayApp.controllers').controller('HeaderController',
|
||||||
$scope.$on('$destroy', function() {
|
$scope.$on('$destroy', function() {
|
||||||
window.onbeforeunload = undefined;
|
window.onbeforeunload = undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$scope.backupAndOpen = function() {
|
||||||
|
var w = $rootScope.wallet;
|
||||||
|
w.offerBackup();
|
||||||
|
backupService.download(w);
|
||||||
|
};
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,24 +1,34 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('ImportController',
|
angular.module('copayApp.controllers').controller('ImportController',
|
||||||
function($scope, $rootScope, walletFactory, controllerUtils, Passphrase) {
|
function($scope, $rootScope, walletFactory, controllerUtils, Passphrase, notification) {
|
||||||
$scope.title = 'Import a backup';
|
$scope.title = 'Import a backup';
|
||||||
|
$scope.importStatus = 'Importing wallet - Reading backup...';
|
||||||
|
|
||||||
var reader = new FileReader();
|
var reader = new FileReader();
|
||||||
|
|
||||||
|
var updateStatus = function(status) {
|
||||||
|
$scope.importStatus = status;
|
||||||
|
$scope.$digest();
|
||||||
|
}
|
||||||
|
|
||||||
var _importBackup = function(encryptedObj) {
|
var _importBackup = function(encryptedObj) {
|
||||||
Passphrase.getBase64Async($scope.password, function(passphrase) {
|
Passphrase.getBase64Async($scope.password, function(passphrase) {
|
||||||
walletFactory.import(encryptedObj, passphrase, function(err, w) {
|
updateStatus('Importing wallet - Setting things up...');
|
||||||
|
var w = walletFactory.import(encryptedObj, passphrase, function(err, w) {
|
||||||
if (err) {
|
if (err) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', err.errMsg || 'Wrong password');
|
||||||
message: err.errMsg || 'Wrong password',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$rootScope.wallet = w;
|
$rootScope.wallet = w;
|
||||||
controllerUtils.startNetwork($rootScope.wallet, $scope);
|
controllerUtils.startNetwork($rootScope.wallet, $scope);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
w.on('updatingIndexes', function(){
|
||||||
|
updateStatus('Importing wallet - We are almost there...');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,10 +56,7 @@ angular.module('copayApp.controllers').controller('ImportController',
|
||||||
$scope.import = function(form) {
|
$scope.import = function(form) {
|
||||||
if (form.$invalid) {
|
if (form.$invalid) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'There is an error in the form.');
|
||||||
message: 'There is an error in the form. Please, try again',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,10 +66,7 @@ angular.module('copayApp.controllers').controller('ImportController',
|
||||||
|
|
||||||
if (!backupFile && !backupText) {
|
if (!backupFile && !backupText) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'Please, select your backup file or paste the file contents');
|
||||||
message: 'Please, select your backup file or paste the text',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SendController',
|
angular.module('copayApp.controllers').controller('SendController',
|
||||||
function($scope, $rootScope, $window, $location, $timeout, $anchorScroll, $modal, isMobile) {
|
function($scope, $rootScope, $window, $location, $timeout, $anchorScroll, $modal, isMobile, notification) {
|
||||||
$scope.title = 'Send';
|
$scope.title = 'Send';
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
var satToUnit = 1 / config.unitToSatoshi;
|
var satToUnit = 1 / config.unitToSatoshi;
|
||||||
|
@ -32,10 +32,8 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.submitForm = function(form) {
|
$scope.submitForm = function(form) {
|
||||||
if (form.$invalid) {
|
if (form.$invalid) {
|
||||||
$rootScope.$flashMessage = {
|
var message = 'Unable to send transaction proposal.';
|
||||||
message: 'Unable to send a transaction proposal. Please, try again',
|
notification.error('Error', message);
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,20 +48,16 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
w.createTx(address, amount, commentText, function(ntxid) {
|
w.createTx(address, amount, commentText, function(ntxid) {
|
||||||
if (w.totalCopayers > 1) {
|
if (w.totalCopayers > 1) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = {
|
var message = 'The transaction proposal has been created';
|
||||||
message: 'The transaction proposal has been created',
|
notification.success('Success!', message);
|
||||||
type: 'success'
|
|
||||||
};
|
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
} else {
|
} else {
|
||||||
w.sendTx(ntxid, function(txid) {
|
w.sendTx(ntxid, function(txid) {
|
||||||
$rootScope.$flashMessage = txid ? {
|
if (txid) {
|
||||||
type: 'success',
|
notification.success('Transaction broadcast', 'Transaction id: ' + txid);
|
||||||
message: 'Transaction broadcasted. txid: ' + txid
|
} else {
|
||||||
} : {
|
notification.error('Error', 'There was an error sending the transaction.');
|
||||||
type: 'error',
|
}
|
||||||
message: 'There was an error sending the Transaction'
|
|
||||||
};
|
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -202,10 +196,11 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
errorMsg = e.message;
|
errorMsg = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rootScope.$flashMessage = {
|
if (errorMsg) {
|
||||||
message: errorMsg ? errorMsg : 'Entry removed successful',
|
notification.error('Error', errorMsg);
|
||||||
type: errorMsg ? 'error' : 'success'
|
} else {
|
||||||
};
|
notification.success('Success', 'Entry removed successfully');
|
||||||
|
}
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
@ -223,10 +218,7 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
|
|
||||||
$scope.submitAddressBook = function(form) {
|
$scope.submitAddressBook = function(form) {
|
||||||
if (form.$invalid) {
|
if (form.$invalid) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Form Error', 'Please complete required fields');
|
||||||
message: 'Complete required fields, please',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var entry = {
|
var entry = {
|
||||||
|
@ -255,10 +247,11 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
errorMsg = e.message;
|
errorMsg = e.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
$rootScope.$flashMessage = {
|
if (errorMsg) {
|
||||||
message: errorMsg ? errorMsg : 'New entry has been created',
|
notification.error('Error', errorMsg);
|
||||||
type: errorMsg ? 'error' : 'success'
|
} else {
|
||||||
};
|
notification.success('Success', 'New entry has been created');
|
||||||
|
}
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
}, 500);
|
}, 500);
|
||||||
$anchorScroll();
|
$anchorScroll();
|
||||||
|
@ -267,8 +260,12 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.topAmount = function() {
|
$scope.getAvailableAmount = function() {
|
||||||
var maxSat = ($rootScope.availableBalance * config.unitToSatoshi).toFixed(0) - bitcore.TransactionBuilder.FEE_PER_1000B_SAT;
|
return ((($rootScope.availableBalance * config.unitToSatoshi).toFixed(0) - bitcore.TransactionBuilder.FEE_PER_1000B_SAT) / config.unitToSatoshi);
|
||||||
$scope.amount = maxSat / config.unitToSatoshi;
|
};
|
||||||
|
|
||||||
|
$scope.topAmount = function(form) {
|
||||||
|
$scope.amount = $scope.getAvailableAmount();
|
||||||
|
form.amount.$pristine = false;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
|
@ -33,7 +33,7 @@ var valid_pairs = {
|
||||||
};
|
};
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SetupController',
|
angular.module('copayApp.controllers').controller('SetupController',
|
||||||
function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase, backupService) {
|
function($scope, $rootScope, $location, $timeout, walletFactory, controllerUtils, Passphrase, backupService, notification) {
|
||||||
|
|
||||||
$rootScope.videoInfo = {};
|
$rootScope.videoInfo = {};
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
@ -68,10 +68,7 @@ angular.module('copayApp.controllers').controller('SetupController',
|
||||||
|
|
||||||
$scope.create = function(form) {
|
$scope.create = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'Please enter the required fields');
|
||||||
message: 'Please, enter required fields',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$scope.loading = true;
|
$scope.loading = true;
|
||||||
|
@ -84,7 +81,6 @@ angular.module('copayApp.controllers').controller('SetupController',
|
||||||
passphrase: passphrase,
|
passphrase: passphrase,
|
||||||
};
|
};
|
||||||
var w = walletFactory.create(opts);
|
var w = walletFactory.create(opts);
|
||||||
backupService.download(w);
|
|
||||||
controllerUtils.startNetwork(w, $scope);
|
controllerUtils.startNetwork(w, $scope);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SigninController',
|
angular.module('copayApp.controllers').controller('SigninController',
|
||||||
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, backupService) {
|
function($scope, $rootScope, $location, walletFactory, controllerUtils, Passphrase, backupService, notification) {
|
||||||
var cmp = function(o1, o2) {
|
var cmp = function(o1, o2) {
|
||||||
var v1 = o1.show.toLowerCase(),
|
var v1 = o1.show.toLowerCase(),
|
||||||
v2 = o2.show.toLowerCase();
|
v2 = o2.show.toLowerCase();
|
||||||
|
@ -15,10 +15,7 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
|
|
||||||
$scope.open = function(form) {
|
$scope.open = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'Please enter the required fields');
|
||||||
message: 'Please, enter required fields',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,10 +33,7 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
};
|
};
|
||||||
if (!w) {
|
if (!w) {
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', errMsg || 'Wrong password');
|
||||||
message: errMsg || 'Wrong password',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -49,10 +43,7 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
|
|
||||||
$scope.join = function(form) {
|
$scope.join = function(form) {
|
||||||
if (form && form.$invalid) {
|
if (form && form.$invalid) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'Please enter the required fields');
|
||||||
message: 'Please, enter required fields',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,32 +55,17 @@ angular.module('copayApp.controllers').controller('SigninController',
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
if (err || !w) {
|
if (err || !w) {
|
||||||
if (err === 'joinError')
|
if (err === 'joinError')
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Can\'t find peer.');
|
||||||
message: 'Can\'t find peer'
|
|
||||||
};
|
|
||||||
else if (err === 'walletFull')
|
else if (err === 'walletFull')
|
||||||
$rootScope.$flashMessage = {
|
notification.error('The wallet is full');
|
||||||
message: 'The wallet is full',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
else if (err === 'badNetwork')
|
else if (err === 'badNetwork')
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Network Error', 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.');
|
||||||
message: 'The wallet your are trying to join uses a different Bitcoin Network. Check your settings.',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
else if (err === 'badSecret')
|
else if (err === 'badSecret')
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Bad secret', 'The secret string you entered is invalid');
|
||||||
message: 'Bad secret secret string',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
else
|
else
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Unknown error');
|
||||||
message: 'Unknown error',
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
controllerUtils.onErrorDigest();
|
controllerUtils.onErrorDigest();
|
||||||
} else {
|
} else {
|
||||||
backupService.download(w);
|
|
||||||
controllerUtils.startNetwork(w, $scope);
|
controllerUtils.startNetwork(w, $scope);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('TransactionsController',
|
angular.module('copayApp.controllers').controller('TransactionsController',
|
||||||
function($scope, $rootScope, $timeout, controllerUtils) {
|
function($scope, $rootScope, $timeout, controllerUtils, notification) {
|
||||||
|
|
||||||
$scope.title = 'Transactions';
|
$scope.title = 'Transactions';
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
@ -107,13 +107,11 @@ angular.module('copayApp.controllers').controller('TransactionsController',
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
var w = $rootScope.wallet;
|
var w = $rootScope.wallet;
|
||||||
w.sendTx(ntxid, function(txid) {
|
w.sendTx(ntxid, function(txid) {
|
||||||
$rootScope.$flashMessage = txid ? {
|
if (!txid) {
|
||||||
type: 'success',
|
notification.error('Error', 'There was an error sending the transaction');
|
||||||
message: 'Transaction broadcasted. txid: ' + txid
|
} else {
|
||||||
} : {
|
notification.success('Transaction broadcast', 'Transaction id: '+txid);
|
||||||
type: 'error',
|
}
|
||||||
message: 'There was an error sending the Transaction'
|
|
||||||
};
|
|
||||||
if (cb) return cb();
|
if (cb) return cb();
|
||||||
else $scope.update();
|
else $scope.update();
|
||||||
});
|
});
|
||||||
|
@ -124,10 +122,7 @@ angular.module('copayApp.controllers').controller('TransactionsController',
|
||||||
var w = $rootScope.wallet;
|
var w = $rootScope.wallet;
|
||||||
w.sign(ntxid, function(ret) {
|
w.sign(ntxid, function(ret) {
|
||||||
if (!ret) {
|
if (!ret) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'There was an error signing the transaction');
|
||||||
type: 'error',
|
|
||||||
message: 'There was an error signing the Transaction',
|
|
||||||
};
|
|
||||||
$scope.update();
|
$scope.update();
|
||||||
} else {
|
} else {
|
||||||
var p = w.txProposals.getTxProposal(ntxid);
|
var p = w.txProposals.getTxProposal(ntxid);
|
||||||
|
@ -180,10 +175,7 @@ angular.module('copayApp.controllers').controller('TransactionsController',
|
||||||
$rootScope.txAlertCount = 0;
|
$rootScope.txAlertCount = 0;
|
||||||
var w = $rootScope.wallet;
|
var w = $rootScope.wallet;
|
||||||
w.reject(ntxid);
|
w.reject(ntxid);
|
||||||
$rootScope.$flashMessage = {
|
notification.warning('Transaction rejected', 'You rejected the transaction successfully');
|
||||||
type: 'warning',
|
|
||||||
message: 'Transaction rejected by you'
|
|
||||||
};
|
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,6 @@ angular.module('copayApp.directives')
|
||||||
link: function(scope, element, attrs, ctrl) {
|
link: function(scope, element, attrs, ctrl) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
scope.$apply(function() {
|
scope.$apply(function() {
|
||||||
$rootScope.$flashMessage = {};
|
|
||||||
});
|
});
|
||||||
}, 5000);
|
}, 5000);
|
||||||
}
|
}
|
||||||
|
@ -40,12 +39,12 @@ angular.module('copayApp.directives')
|
||||||
.directive('enoughAmount', ['$rootScope',
|
.directive('enoughAmount', ['$rootScope',
|
||||||
function($rootScope) {
|
function($rootScope) {
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var feeSat = bitcore.TransactionBuilder.FEE_PER_1000B_SAT;
|
var feeSat = Number(bitcore.TransactionBuilder.FEE_PER_1000B_SAT);
|
||||||
return {
|
return {
|
||||||
require: 'ngModel',
|
require: 'ngModel',
|
||||||
link: function(scope, element, attrs, ctrl) {
|
link: function(scope, element, attrs, ctrl) {
|
||||||
var val = function(value) {
|
var val = function(value) {
|
||||||
var availableBalanceNum = ($rootScope.availableBalance * config.unitToSatoshi).toFixed(0);
|
var availableBalanceNum = Number(($rootScope.availableBalance * config.unitToSatoshi).toFixed(0));
|
||||||
var vNum = Number((value * config.unitToSatoshi).toFixed(0)) + feeSat;
|
var vNum = Number((value * config.unitToSatoshi).toFixed(0)) + feeSat;
|
||||||
if (typeof vNum == "number" && vNum > 0) {
|
if (typeof vNum == "number" && vNum > 0) {
|
||||||
if (availableBalanceNum < vNum) {
|
if (availableBalanceNum < vNum) {
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var imports = require('soop').imports();
|
||||||
|
var bitcore = require('bitcore');
|
||||||
|
|
||||||
|
/* Encrypted, authenticated messages to be shared between copayers */
|
||||||
|
var Message = function() {
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.encode = function(topubkey, fromkey, payload) {
|
||||||
|
var version = new Buffer([0]);
|
||||||
|
var toencrypt = Buffer.concat([version, payload]);
|
||||||
|
var encrypted = Message._encrypt(topubkey, toencrypt);
|
||||||
|
var sig = Message._sign(fromkey, encrypted);
|
||||||
|
var encoded = {
|
||||||
|
pubkey: fromkey.public.toString('hex'),
|
||||||
|
sig: sig.toString('hex'),
|
||||||
|
encrypted: encrypted.toString('hex')
|
||||||
|
};
|
||||||
|
return encoded;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message.decode = function(key, encoded) {
|
||||||
|
try {
|
||||||
|
var frompubkey = new Buffer(encoded.pubkey, 'hex');
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Error decoding public key: ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var sig = new Buffer(encoded.sig, 'hex');
|
||||||
|
var encrypted = new Buffer(encoded.encrypted, 'hex');
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Error decoding data: ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
var v = Message._verify(frompubkey, sig, encrypted);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Error verifying signature: ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!v)
|
||||||
|
throw new Error('Invalid signature');
|
||||||
|
|
||||||
|
try {
|
||||||
|
var decrypted = Message._decrypt(key.private, encrypted);
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error('Cannot decrypt data: ' + e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (decrypted[0] !== 0)
|
||||||
|
throw new Error('Invalid version number');
|
||||||
|
|
||||||
|
if (decrypted.length === 0)
|
||||||
|
throw new Error('No data present');
|
||||||
|
|
||||||
|
var payload = decrypted.slice(1);
|
||||||
|
return payload;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message._encrypt = function(topubkey, payload, r, iv) {
|
||||||
|
var encrypted = bitcore.ECIES.encrypt(topubkey, payload, r, iv);
|
||||||
|
return encrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message._decrypt = function(privkey, encrypted) {
|
||||||
|
var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
|
||||||
|
return decrypted;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message._sign = function(key, payload) {
|
||||||
|
var sig = bitcore.Message.sign(payload, key);
|
||||||
|
return sig;
|
||||||
|
};
|
||||||
|
|
||||||
|
Message._verify = function(pubkey, signature, payload) {
|
||||||
|
var v = bitcore.Message.verifyWithPubKey(pubkey, payload, signature);
|
||||||
|
return v;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = require('soop')(Message);
|
|
@ -32,9 +32,17 @@ PrivateKey.prototype.getIdPriv = function() {
|
||||||
return this.idpriv;
|
return this.idpriv;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
PrivateKey.prototype.getIdKey = function() {
|
||||||
|
if (!this.idkey) {
|
||||||
|
this.cacheId();
|
||||||
|
}
|
||||||
|
return this.idkey;
|
||||||
|
};
|
||||||
|
|
||||||
PrivateKey.prototype.cacheId = function() {
|
PrivateKey.prototype.cacheId = function() {
|
||||||
var path = Structure.IdFullBranch;
|
var path = Structure.IdFullBranch;
|
||||||
var idhk = this.bip.derive(path);
|
var idhk = this.bip.derive(path);
|
||||||
|
this.idkey = idhk.eckey;
|
||||||
this.id = idhk.eckey.public.toString('hex');
|
this.id = idhk.eckey.public.toString('hex');
|
||||||
this.idpriv = idhk.eckey.private.toString('hex');
|
this.idpriv = idhk.eckey.private.toString('hex');
|
||||||
};
|
};
|
||||||
|
|
|
@ -50,6 +50,7 @@ function Wallet(opts) {
|
||||||
this.network.maxPeers = this.totalCopayers;
|
this.network.maxPeers = this.totalCopayers;
|
||||||
this.registeredPeerIds = [];
|
this.registeredPeerIds = [];
|
||||||
this.addressBook = opts.addressBook || {};
|
this.addressBook = opts.addressBook || {};
|
||||||
|
this.backupOffered = opts.backupOffered || false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wallet.parent = EventEmitter;
|
Wallet.parent = EventEmitter;
|
||||||
|
@ -279,6 +280,7 @@ Wallet.prototype.netStart = function(callback) {
|
||||||
|
|
||||||
var myId = self.getMyCopayerId();
|
var myId = self.getMyCopayerId();
|
||||||
var myIdPriv = self.getMyCopayerIdPriv();
|
var myIdPriv = self.getMyCopayerIdPriv();
|
||||||
|
|
||||||
var startOpts = {
|
var startOpts = {
|
||||||
copayerId: myId,
|
copayerId: myId,
|
||||||
privkey: myIdPriv,
|
privkey: myIdPriv,
|
||||||
|
@ -355,7 +357,8 @@ Wallet.prototype.toObj = function() {
|
||||||
publicKeyRing: this.publicKeyRing.toObj(),
|
publicKeyRing: this.publicKeyRing.toObj(),
|
||||||
txProposals: this.txProposals.toObj(),
|
txProposals: this.txProposals.toObj(),
|
||||||
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
|
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
|
||||||
addressBook: this.addressBook
|
addressBook: this.addressBook,
|
||||||
|
backupOffered: this.backupOffered,
|
||||||
};
|
};
|
||||||
|
|
||||||
return walletObj;
|
return walletObj;
|
||||||
|
@ -364,6 +367,8 @@ Wallet.prototype.toObj = function() {
|
||||||
Wallet.fromObj = function(o, storage, network, blockchain) {
|
Wallet.fromObj = function(o, storage, network, blockchain) {
|
||||||
var opts = JSON.parse(JSON.stringify(o.opts));
|
var opts = JSON.parse(JSON.stringify(o.opts));
|
||||||
opts.addressBook = o.addressBook;
|
opts.addressBook = o.addressBook;
|
||||||
|
opts.backupOffered = o.backupOffered;
|
||||||
|
|
||||||
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
||||||
opts.txProposals = TxProposals.fromObj(o.txProposals);
|
opts.txProposals = TxProposals.fromObj(o.txProposals);
|
||||||
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
||||||
|
@ -755,6 +760,7 @@ Wallet.prototype.updateIndexes = function(callback) {
|
||||||
if (changeIndex != -1)
|
if (changeIndex != -1)
|
||||||
self.publicKeyRing.indexes.changeIndex = changeIndex + 1;
|
self.publicKeyRing.indexes.changeIndex = changeIndex + 1;
|
||||||
|
|
||||||
|
self.emit('updatingIndexes');
|
||||||
start = self.publicKeyRing.indexes.receiveIndex;
|
start = self.publicKeyRing.indexes.receiveIndex;
|
||||||
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
|
self.indexDiscovery(start, false, 20, function(err, receiveIndex) {
|
||||||
if (err) return callback(err);
|
if (err) return callback(err);
|
||||||
|
@ -850,4 +856,14 @@ Wallet.prototype.deleteAddressBook = function(key) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.isReady = function() {
|
||||||
|
var ret = this.publicKeyRing.isComplete() && this.backupOffered;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
Wallet.prototype.offerBackup = function() {
|
||||||
|
this.backupOffered = true;
|
||||||
|
this.store();
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = require('soop')(Wallet);
|
module.exports = require('soop')(Wallet);
|
||||||
|
|
|
@ -84,6 +84,7 @@ WalletFactory.prototype.import = function(base64, password, cb) {
|
||||||
self.log('Indexes updated');
|
self.log('Indexes updated');
|
||||||
cb(null, w);
|
cb(null, w);
|
||||||
});
|
});
|
||||||
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletFactory.prototype.read = function(walletId) {
|
WalletFactory.prototype.read = function(walletId) {
|
||||||
|
@ -99,6 +100,7 @@ WalletFactory.prototype.read = function(walletId) {
|
||||||
obj.txProposals = s.get(walletId, 'txProposals');
|
obj.txProposals = s.get(walletId, 'txProposals');
|
||||||
obj.privateKey = s.get(walletId, 'privateKey');
|
obj.privateKey = s.get(walletId, 'privateKey');
|
||||||
obj.addressBook = s.get(walletId, 'addressBook');
|
obj.addressBook = s.get(walletId, 'addressBook');
|
||||||
|
obj.backupOffered = s.get(walletId, 'backupOffered');
|
||||||
|
|
||||||
var w = this.fromObj(obj);
|
var w = this.fromObj(obj);
|
||||||
return w;
|
return w;
|
||||||
|
@ -222,7 +224,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
|
||||||
this.log('\t### PrivateKey Initialized');
|
this.log('\t### PrivateKey Initialized');
|
||||||
var opts = {
|
var opts = {
|
||||||
copayerId: privateKey.getId(),
|
copayerId: privateKey.getId(),
|
||||||
privkey: privateKey.getIdPriv()
|
privkey: privateKey.getIdPriv(),
|
||||||
|
key: privateKey.getIdKey()
|
||||||
};
|
};
|
||||||
self.network.cleanUp();
|
self.network.cleanUp();
|
||||||
self.network.start(opts, function() {
|
self.network.start(opts, function() {
|
||||||
|
|
|
@ -5,6 +5,7 @@ var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var util = bitcore.util;
|
var util = bitcore.util;
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
|
var Message = require('../core/Message');
|
||||||
/*
|
/*
|
||||||
* Emits
|
* Emits
|
||||||
* 'connect'
|
* 'connect'
|
||||||
|
@ -45,6 +46,7 @@ Network.prototype.cleanUp = function() {
|
||||||
this.connectedPeers = [];
|
this.connectedPeers = [];
|
||||||
this.peerId = null;
|
this.peerId = null;
|
||||||
this.privkey = null; //TODO: hide privkey in a closure
|
this.privkey = null; //TODO: hide privkey in a closure
|
||||||
|
this.key = null;
|
||||||
this.copayerId = null;
|
this.copayerId = null;
|
||||||
this.signingKey = null;
|
this.signingKey = null;
|
||||||
this.allowedCopayerIds = null;
|
this.allowedCopayerIds = null;
|
||||||
|
@ -151,16 +153,29 @@ Network.prototype._addConnectedCopayer = function(copayerId, isInbound) {
|
||||||
this.emit('connect', copayerId);
|
this.emit('connect', copayerId);
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._onData = function(enchex, isInbound, peerId) {
|
Network.prototype.getKey = function() {
|
||||||
var sig, payload;
|
if (!this.key) {
|
||||||
var encUint8Array = new Uint8Array(enchex);
|
var key = new bitcore.Key();
|
||||||
|
key.private = new Buffer(this.privkey, 'hex');
|
||||||
|
key.regenerateSync();
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
return this.key;
|
||||||
|
};
|
||||||
|
|
||||||
|
Network.prototype._onData = function(enc, isInbound, peerId) {
|
||||||
|
var encUint8Array = new Uint8Array(enc);
|
||||||
var encbuf = new Buffer(encUint8Array);
|
var encbuf = new Buffer(encUint8Array);
|
||||||
|
var encstr = encbuf.toString();
|
||||||
|
|
||||||
var privkey = this.privkey;
|
var privkey = this.privkey;
|
||||||
|
var key = this.getKey();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var data = this._decrypt(privkey, encbuf);
|
var encoded = JSON.parse(encstr);
|
||||||
payload = JSON.parse(data);
|
var databuf = this._decode(key, encoded);
|
||||||
|
var datastr = databuf.toString();
|
||||||
|
var payload = JSON.parse(datastr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._deletePeer(peerId);
|
this._deletePeer(peerId);
|
||||||
return;
|
return;
|
||||||
|
@ -169,6 +184,13 @@ Network.prototype._onData = function(enchex, isInbound, peerId) {
|
||||||
if (isInbound && payload.type === 'hello') {
|
if (isInbound && payload.type === 'hello') {
|
||||||
var payloadStr = JSON.stringify(payload);
|
var payloadStr = JSON.stringify(payload);
|
||||||
|
|
||||||
|
//ensure claimed public key is actually the public key of the peer
|
||||||
|
//e.g., their public key should hash to be their peerId
|
||||||
|
if (peerId.toString() !== this.peerFromCopayer(payload.copayerId) || peerId.toString() !== this.peerFromCopayer(encoded.pubkey)) {
|
||||||
|
this._deletePeer(peerId, 'incorrect pubkey for peerId');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
|
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
|
||||||
this._deletePeer(peerId);
|
this._deletePeer(peerId);
|
||||||
return;
|
return;
|
||||||
|
@ -349,18 +371,20 @@ Network.prototype.getPeer = function() {
|
||||||
return this.peer;
|
return this.peer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._encrypt = function(pubkey, payload) {
|
Network.prototype._encode = function(topubkey, fromkey, payload) {
|
||||||
var encrypted = bitcore.ECIES.encrypt(pubkey, payload);
|
var encoded = Message.encode(topubkey, fromkey, payload);
|
||||||
return encrypted;
|
return encoded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Network.prototype._decrypt = function(privkey, encrypted) {
|
Network.prototype._decode = function(key, encoded) {
|
||||||
var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
|
var payload = Message.decode(key, encoded);
|
||||||
return decrypted;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
|
Network.prototype._sendToOne = function(copayerId, payload, cb) {
|
||||||
|
if (!Buffer.isBuffer(payload))
|
||||||
|
throw new Error('payload must be a buffer');
|
||||||
var peerId = this.peerFromCopayer(copayerId);
|
var peerId = this.peerFromCopayer(copayerId);
|
||||||
if (peerId !== this.peerId) {
|
if (peerId !== this.peerId) {
|
||||||
var dataConn = this.connections[peerId];
|
var dataConn = this.connections[peerId];
|
||||||
|
@ -383,7 +407,6 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
if (typeof copayerIds === 'string')
|
if (typeof copayerIds === 'string')
|
||||||
copayerIds = [copayerIds];
|
copayerIds = [copayerIds];
|
||||||
|
|
||||||
var sig;
|
|
||||||
var payloadStr = JSON.stringify(payload);
|
var payloadStr = JSON.stringify(payload);
|
||||||
var payloadBuf = new Buffer(payloadStr);
|
var payloadBuf = new Buffer(payloadStr);
|
||||||
|
|
||||||
|
@ -391,8 +414,9 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
var i = 0;
|
var i = 0;
|
||||||
copayerIds.forEach(function(copayerId) {
|
copayerIds.forEach(function(copayerId) {
|
||||||
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
||||||
var encPayload = self._encrypt(copayerIdBuf, payloadBuf);
|
var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
|
||||||
self._sendToOne(copayerId, encPayload, sig, function() {
|
var enc = new Buffer(JSON.stringify(encPayload));
|
||||||
|
self._sendToOne(copayerId, enc, function() {
|
||||||
if (++i === l && typeof cb === 'function') cb();
|
if (++i === l && typeof cb === 'function') cb();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,35 +36,27 @@ angular.module('copayApp.services')
|
||||||
|
|
||||||
root.onErrorDigest = function(scope, msg) {
|
root.onErrorDigest = function(scope, msg) {
|
||||||
root.onError(scope);
|
root.onError(scope);
|
||||||
if (msg) $rootScope.$flashMessage = {
|
if (msg) {
|
||||||
type: 'error',
|
notification.error('Error', msg);
|
||||||
message: msg
|
}
|
||||||
};
|
|
||||||
$rootScope.$digest();
|
$rootScope.$digest();
|
||||||
};
|
};
|
||||||
|
|
||||||
root.installStartupHandlers = function(wallet, $scope) {
|
root.installStartupHandlers = function(wallet, $scope) {
|
||||||
wallet.on('serverError', function(msg) {
|
wallet.on('serverError', function(msg) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('PeerJS Error', 'There was an error connecting to the PeerJS server.'
|
||||||
message: 'There was an error connecting to the PeerJS server.' + (msg || 'Check you settings and Internet connection.'),
|
+ (msg || 'Check you settings and Internet connection.'));
|
||||||
type: 'error',
|
|
||||||
};
|
|
||||||
root.onErrorDigest($scope);
|
root.onErrorDigest($scope);
|
||||||
$location.path('addresses');
|
$location.path('addresses');
|
||||||
});
|
});
|
||||||
wallet.on('connectionError', function() {
|
wallet.on('connectionError', function() {
|
||||||
var message = "Looks like you are already connected to this wallet, please logout from it and try importing it again.";
|
var message = "Looks like you are already connected to this wallet, please logout and try importing it again.";
|
||||||
$rootScope.$flashMessage = {
|
notification.error('PeerJS Error', message);
|
||||||
message: message,
|
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
root.onErrorDigest($scope);
|
root.onErrorDigest($scope);
|
||||||
});
|
});
|
||||||
wallet.on('serverError', function() {
|
wallet.on('serverError', function() {
|
||||||
$rootScope.$flashMessage = {
|
var message = 'The PeerJS server is not responding, please try again';
|
||||||
message: 'The PeerJS server is not responding, please try again',
|
notification.error('PeerJS Error', message);
|
||||||
type: 'error'
|
|
||||||
};
|
|
||||||
root.onErrorDigest($scope);
|
root.onErrorDigest($scope);
|
||||||
});
|
});
|
||||||
wallet.on('ready', function() {
|
wallet.on('ready', function() {
|
||||||
|
@ -79,10 +71,7 @@ angular.module('copayApp.services')
|
||||||
$rootScope.isCollapsed = true;
|
$rootScope.isCollapsed = true;
|
||||||
$rootScope.$watch('insightError', function(status) {
|
$rootScope.$watch('insightError', function(status) {
|
||||||
if (status === -1) {
|
if (status === -1) {
|
||||||
$rootScope.$flashMessage = {
|
notification.success('Networking restored', 'Connection to Insight re-established');
|
||||||
type: 'success',
|
|
||||||
message: 'Networking Restored :)',
|
|
||||||
};
|
|
||||||
$rootScope.insightError = 0;
|
$rootScope.insightError = 0;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -117,10 +106,7 @@ angular.module('copayApp.services')
|
||||||
notification.enableHtml5Mode(); // for chrome: if support, enable it
|
notification.enableHtml5Mode(); // for chrome: if support, enable it
|
||||||
|
|
||||||
w.on('badMessage', function(peerId) {
|
w.on('badMessage', function(peerId) {
|
||||||
$rootScope.$flashMessage = {
|
notification.error('Error', 'Received wrong message from peer ' + peerId);
|
||||||
type: 'error',
|
|
||||||
message: 'Received wrong message from peer id:' + peerId
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
w.on('ready', function(myPeerID) {
|
w.on('ready', function(myPeerID) {
|
||||||
$rootScope.wallet = w;
|
$rootScope.wallet = w;
|
||||||
|
|
|
@ -21,7 +21,7 @@ factory('notification', ['$timeout',
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
error: {
|
error: {
|
||||||
duration: 1e10,
|
duration: 5000,
|
||||||
enabled: true
|
enabled: true
|
||||||
},
|
},
|
||||||
success: {
|
success: {
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
<script src="../lib/bitcore/browser/bundle.js"></script>
|
<script src="../lib/bitcore/browser/bundle.js"></script>
|
||||||
<script src="../js/copayBundle.js"></script>
|
<script src="../js/copayBundle.js"></script>
|
||||||
<script src="test.blockchain.Insight.js"></script>
|
<script src="test.blockchain.Insight.js"></script>
|
||||||
|
<script src="test.Message.js"></script>
|
||||||
<script src="test.network.WebRTC.js"></script>
|
<script src="test.network.WebRTC.js"></script>
|
||||||
<script src="test.PrivateKey.js"></script>
|
<script src="test.PrivateKey.js"></script>
|
||||||
<script src="test.PublicKeyRing.js"></script>
|
<script src="test.PublicKeyRing.js"></script>
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var chai = chai || require('chai');
|
||||||
|
var should = chai.should();
|
||||||
|
var sinon = require('sinon');
|
||||||
|
var Message = require('../js/models/core/Message');
|
||||||
|
var bitcore = bitcore || require('bitcore');
|
||||||
|
var Buffer = bitcore.Buffer;
|
||||||
|
|
||||||
|
describe('Message model', function() {
|
||||||
|
var key = new bitcore.Key();
|
||||||
|
key.private = bitcore.util.sha256(new Buffer('test'));
|
||||||
|
key.regenerateSync();
|
||||||
|
|
||||||
|
var key2 = new bitcore.Key();
|
||||||
|
key2.private = bitcore.util.sha256(new Buffer('test 2'));
|
||||||
|
key2.regenerateSync();
|
||||||
|
|
||||||
|
describe('#encode', function() {
|
||||||
|
|
||||||
|
it('should encode a message', function() {
|
||||||
|
var message = new Buffer('message');
|
||||||
|
var encoded = Message.encode(key2.public, key, message);
|
||||||
|
should.exist(encoded.pubkey);
|
||||||
|
should.exist(encoded.sig);
|
||||||
|
should.exist(encoded.encrypted);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#decode', function() {
|
||||||
|
|
||||||
|
it('should decode an encoded message', function() {
|
||||||
|
var message = new Buffer('message');
|
||||||
|
var messagehex = message.toString('hex');
|
||||||
|
var encoded = Message.encode(key2.public, key, message);
|
||||||
|
|
||||||
|
var decoded = Message.decode(key2, encoded);
|
||||||
|
decoded.toString('hex').should.equal(messagehex);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fail if the version number is incorrect', function() {
|
||||||
|
var payload = new Buffer('message');
|
||||||
|
var fromkey = key;
|
||||||
|
var topubkey = key2.public;
|
||||||
|
var version = new Buffer([1]);
|
||||||
|
var toencrypt = Buffer.concat([version, payload]);
|
||||||
|
var encrypted = Message._encrypt(topubkey, toencrypt);
|
||||||
|
var sig = Message._sign(fromkey, encrypted);
|
||||||
|
var encoded = {
|
||||||
|
pubkey: fromkey.public.toString('hex'),
|
||||||
|
sig: sig.toString('hex'),
|
||||||
|
encrypted: encrypted.toString('hex')
|
||||||
|
};
|
||||||
|
|
||||||
|
(function() {Message.decode(key2, encoded);}).should.throw('Invalid version number');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_encrypt', function() {
|
||||||
|
|
||||||
|
it('should encrypt data', function() {
|
||||||
|
var payload = new Buffer('payload');
|
||||||
|
var encrypted = Message._encrypt(key.public, payload);
|
||||||
|
encrypted.length.should.equal(129);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_decrypt', function() {
|
||||||
|
var payload = new Buffer('payload');
|
||||||
|
var payloadhex = payload.toString('hex');
|
||||||
|
|
||||||
|
it('should decrypt encrypted data', function() {
|
||||||
|
var encrypted = Message._encrypt(key.public, payload);
|
||||||
|
var decrypted = Message._decrypt(key.private, encrypted);
|
||||||
|
decrypted.toString('hex').should.equal(payloadhex);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_sign', function() {
|
||||||
|
|
||||||
|
it('should sign data', function() {
|
||||||
|
var payload = new Buffer('payload');
|
||||||
|
var sig = Message._sign(key, payload);
|
||||||
|
sig.length.should.be.greaterThan(60);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_verify', function() {
|
||||||
|
var payload = new Buffer('payload');
|
||||||
|
var sig = Message._sign(key, payload);
|
||||||
|
|
||||||
|
it('should verify signed data', function() {
|
||||||
|
Message._verify(key.public, sig, payload).should.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
|
@ -331,7 +331,6 @@ describe('Wallet model', function() {
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
var spy = sinon.spy(w, 'scheduleConnect');
|
var spy = sinon.spy(w, 'scheduleConnect');
|
||||||
var callCount = 3;
|
var callCount = 3;
|
||||||
w.reconnectDelay = 25;
|
|
||||||
w.netStart();
|
w.netStart();
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
sinon.assert.callCount(spy, callCount);
|
sinon.assert.callCount(spy, callCount);
|
||||||
|
@ -534,11 +533,12 @@ describe('Wallet model', function() {
|
||||||
},
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
var roundWallet = cachedCreateW2();
|
||||||
|
|
||||||
roundErrorChecks.forEach(function(c) {
|
roundErrorChecks.forEach(function(c) {
|
||||||
it('check rounding errors ' + c.unspent[0], function(done) {
|
it('#getBalance should handle rounding errors: ' + c.unspent[0], function(done) {
|
||||||
var w = cachedCreateW2();
|
var w = roundWallet;
|
||||||
w.generateAddress();
|
//w.generateAddress();
|
||||||
w.blockchain.fixUnspent(c.unspent.map(function(u) {
|
w.blockchain.fixUnspent(c.unspent.map(function(u) {
|
||||||
return {
|
return {
|
||||||
amount: u
|
amount: u
|
||||||
|
@ -743,7 +743,7 @@ describe('Wallet model', function() {
|
||||||
var spyEmit = sinon.spy(w, 'emit');
|
var spyEmit = sinon.spy(w, 'emit');
|
||||||
w.updateIndexes(function(err) {
|
w.updateIndexes(function(err) {
|
||||||
sinon.assert.callCount(spyStore, 1);
|
sinon.assert.callCount(spyStore, 1);
|
||||||
sinon.assert.callCount(spyEmit, 1);
|
sinon.assert.callCount(spyEmit, 2);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -826,7 +826,6 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
describe('#getMyCopayerId', function() {
|
describe('#getMyCopayerId', function() {
|
||||||
it('should call getCopayerId', function() {
|
it('should call getCopayerId', function() {
|
||||||
//this.timeout(10000);
|
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
w.getCopayerId = sinon.spy();
|
w.getCopayerId = sinon.spy();
|
||||||
w.getMyCopayerId();
|
w.getMyCopayerId();
|
||||||
|
@ -836,7 +835,6 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
describe('#getMyCopayerIdPriv', function() {
|
describe('#getMyCopayerIdPriv', function() {
|
||||||
it('should call privateKey.getIdPriv', function() {
|
it('should call privateKey.getIdPriv', function() {
|
||||||
//this.timeout(10000);
|
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
w.privateKey.getIdPriv = sinon.spy();
|
w.privateKey.getIdPriv = sinon.spy();
|
||||||
w.getMyCopayerIdPriv();
|
w.getMyCopayerIdPriv();
|
||||||
|
@ -845,9 +843,7 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#netStart', function() {
|
describe('#netStart', function() {
|
||||||
|
|
||||||
it('should call Network.start', function() {
|
it('should call Network.start', function() {
|
||||||
//this.timeout(10000);
|
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
w.network.start = sinon.spy();
|
w.network.start = sinon.spy();
|
||||||
w.netStart();
|
w.netStart();
|
||||||
|
@ -855,13 +851,21 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call Network.start with a private key', function() {
|
it('should call Network.start with a private key', function() {
|
||||||
//this.timeout(10000);
|
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
w.network.start = sinon.spy();
|
w.network.start = sinon.spy();
|
||||||
w.netStart();
|
w.netStart();
|
||||||
w.network.start.getCall(0).args[0].privkey.length.should.equal(64);
|
w.network.start.getCall(0).args[0].privkey.length.should.equal(64);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#offerBackup', function() {
|
||||||
|
it('should work', function() {
|
||||||
|
var w = cachedCreateW2();
|
||||||
|
w.store = sinon.spy();
|
||||||
|
w.offerBackup();
|
||||||
|
w.backupOffered.should.equal(true);
|
||||||
|
w.store.calledOnce.should.equal(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -80,7 +80,7 @@ describe('WalletFactory model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('#fromObj #toObj round trip', function() {
|
it('#fromObj #toObj round trip', function() {
|
||||||
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{}}';
|
var o = '{"opts":{"id":"dbfe10c3fae71cea","spendUnconfirmed":1,"requiredCopayers":3,"totalCopayers":5,"version":"0.0.5"},"publicKeyRing":{"walletId":"dbfe10c3fae71cea","networkName":"testnet","requiredCopayers":3,"totalCopayers":5,"indexes":{"changeIndex":0,"receiveIndex":0},"copayersExtPubKeys":["tpubD6NzVbkrYhZ4YGK8ZhZ8WVeBXNAAoTYjjpw9twCPiNGrGQYFktP3iVQkKmZNiFnUcAFMJRxJVJF6Nq9MDv2kiRceExJaHFbxUCGUiRhmy97","tpubD6NzVbkrYhZ4YKGDJkzWdQsQV3AcFemaQKiwNhV4RL8FHnBFvinidGdQtP8RKj3h34E65RkdtxjrggZYqsEwJ8RhhN2zz9VrjLnrnwbXYNc","tpubD6NzVbkrYhZ4YkDiewjb32Pp3Sz9WK2jpp37KnL7RCrHAyPpnLfgdfRnTdpn6DTWmPS7niywfgWiT42aJb1J6CjWVNmkgsMCxuw7j9DaGKB","tpubD6NzVbkrYhZ4XEtUAz4UUTWbprewbLTaMhR8NUvSJUEAh4Sidxr6rRPFdqqVRR73btKf13wUjds2i8vVCNo8sbKrAnyoTr3o5Y6QSbboQjk","tpubD6NzVbkrYhZ4Yj9AAt6xUVuGPVd8jXCrEE6V2wp7U3PFh8jYYvVad31b4VUXEYXzSnkco4fktu8r4icBsB2t3pCR3WnhVLedY2hxGcPFLKD"],"nicknameFor":{},"publicKeysCache":{"m/0/1/0":["0314368b8efa07e8c7dad30498d0a7e3aa575db1fef833347c6d381c1a33a17b17","02cfd95f89ab46bd3bd86954dd9f83dbab0cd2e4466dee587e8e4d8d733fc0d748","02568969eb6212fe946450be6c5b3353fc754a40b2cdc4aed501a8976fec371da8","0360f870a088ae0ef1c37035a9b6a462ca8dcdd5da275f4e2dcd19f44b81d3e7e4","0300ad8f1bded838b02e127bb25961fbcee718db2df81f680f889692acdcbdd73d"],"m/0/1/1":["024f97a9adb2fa9306c4e3d9244f5e5355c7e2c6b3dd4122ba804e17dc9729df5d","0214834a5adcbc4ad0f3bbbc1c280b8ac480387fcc9a1fd988c1526ed496d923c4","024e72338bd5e976375d076bd71a9649e9141b4cbfc9e16cb7109b354b3e913a05","0322045ea35c3118aa7ab9f2c9f182b0120956b0aa65cc72b9d093f145327a4b17","030dc2450c72df366c1960739c577a2efd4451070bd78effcb6f71d1bcd7dfc7a8"],"m/0/1/2":["0247de59deb66783b8f9b0c326234a9569d00866c2a73f599e77a4d0cab5cbce8f","0376e49f0ac3647404034aae0dc8dd927c34a634ef24ea36f56a272f75fce9539b","032fbaa2593bd1eea4a46e7ac15f15802cdd1eb65a7d5bc4364ddd9d52f0838234","03a81f2a7e1f7191aa0b0c6e0a4ccefc71edd3564e86014972fe338045f68d5a5a","02eb8a012ea9a709392502cacda6ef5115d6d2319ab470d546d9068ab941621a99"],"m/0/0/0":["036dcbd378b4352120d6b720b6294dd2d0dd02801fcf010bb69dadbec1f3999279","022089eedb85dc45d1efa418e1ea226588deedebc1d85acca15ff72783e33636c0","0388aa5fd432b74c56427396f350d236c3ca8f7b2f62da513ce4c2e6ff04a67e9c","02fc4caa7449db7483d2e1fccdacac6fa2f736278c758af9966402589b5632f13e","02e4a15b885d8b2d586f82fa85d16179644e60a154674bde0ec3004810b1bdab99"],"m/0/0/1":["039afa26b2f341c76c7b3c3d0672438f35ac6ebb67b1ddfefac9cd79b7b24418c1","021acaaf500d431ebc396f50630767b01c91ce98ae48e968775ceaad932b7e3b8e","022a947259c4a9f76d5e95c0849df31d01233df41d0d75d631b89317a48d8cddce","03d38d9f94217da780303d9a8987c86d737ef39683febc0cd6632cddbfa62186fd","0394d2581b307fe2af19721888d922aab58ab198ef88cedf9506177e30d807811e"],"m/0/0/2":["037825ffce15d34f9bd6c02bcda7701826706471a4d6ab5004eb965f98811c2098","023768dd6d3c71b7df5733ccda5b2d8b454d5b4c4179d91a6fda74db8b869a2406","021a79e91f003f308764d43039e9b5d56bc8f33ca2f4d30ec6cc5a37c0d09dc273","02437f1e388b273936319f79a5d22958ef5ebff9c8cd7b6f6f72518445b1e30867","0373b0881cb4fd02baa62589023fdfe9739c6148cf104d907549f2528eb80146f5"]}},"txProposals":{"txps":[],"walletId":"dbfe10c3fae71cea","networkName":"testnet"},"privateKey":{"extendedPrivateKeyString":"tprv8ZgxMBicQKsPeoHLg3tY75z4xLeEe8MqAXLNcRA6J6UTRvHV8VZTXznt9eoTmSk1fwSrwZtMhY3XkNsceJ14h6sCXHSWinRqMSSbY8tfhHi","networkName":"testnet","privateKeyCache":{}},"addressBook":{},"backupOffered":false}';
|
||||||
|
|
||||||
var wf = new WalletFactory(config, '0.0.5');
|
var wf = new WalletFactory(config, '0.0.5');
|
||||||
var w = wf.fromObj(JSON.parse(o));
|
var w = wf.fromObj(JSON.parse(o));
|
||||||
|
@ -94,6 +94,29 @@ describe('WalletFactory model', function() {
|
||||||
JSON.stringify(w.toObj()).should.equal(o);
|
JSON.stringify(w.toObj()).should.equal(o);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should create wallet from encrypted object', function() {
|
||||||
|
var wf = new WalletFactory(config, '0.0.1');
|
||||||
|
wf.storage._setPassphrase = sinon.spy();
|
||||||
|
wf.storage.import = sinon.spy();
|
||||||
|
|
||||||
|
var w = wf.fromEncryptedObj("encrypted object", "password");
|
||||||
|
should.exist(w);
|
||||||
|
wf.storage._setPassphrase.called.should.be.true;
|
||||||
|
wf.storage.import.called.should.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should import and update indexes', function() {
|
||||||
|
var wf = new WalletFactory(config, '0.0.1');
|
||||||
|
var wallet = {id: "fake wallet", updateIndexes: function(cb) { cb(); }};
|
||||||
|
wf.fromEncryptedObj = sinon.stub().returns(wallet);
|
||||||
|
var callback = sinon.spy();
|
||||||
|
|
||||||
|
var w = wf.import("encrypted", "password", callback);
|
||||||
|
|
||||||
|
should.exist(w);
|
||||||
|
wallet.should.equal(w);
|
||||||
|
sinon.assert.callCount(callback, 1);
|
||||||
|
});
|
||||||
|
|
||||||
it('BIP32 length problem', function() {
|
it('BIP32 length problem', function() {
|
||||||
var sconfig = {
|
var sconfig = {
|
||||||
|
|
|
@ -46,9 +46,28 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#_encrypt', function() {
|
describe('#_encode', function() {
|
||||||
|
|
||||||
it('should encrypt data successfully', function() {
|
it('should encode data successfully', function() {
|
||||||
|
var n = new WebRTC();
|
||||||
|
var data = new bitcore.Buffer('my data to encode');
|
||||||
|
var privkeystr = new bitcore.Buffer('test privkey');
|
||||||
|
var privkey = bitcore.util.sha256(privkeystr);
|
||||||
|
var key = new bitcore.Key();
|
||||||
|
key.private = privkey;
|
||||||
|
key.regenerateSync();
|
||||||
|
var encoded = n._encode(key.public, key, data);
|
||||||
|
should.exist(encoded);
|
||||||
|
encoded.sig.length.should.not.equal(0);
|
||||||
|
encoded.pubkey.length.should.not.equal(0);
|
||||||
|
encoded.encrypted.length.should.not.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('#_decode', function() {
|
||||||
|
|
||||||
|
it('should decode that which was encoded', function() {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
var data = new bitcore.Buffer('my data to encrypt');
|
var data = new bitcore.Buffer('my data to encrypt');
|
||||||
var privkeystr = new bitcore.Buffer('test privkey');
|
var privkeystr = new bitcore.Buffer('test privkey');
|
||||||
|
@ -56,27 +75,10 @@ describe('Network / WebRTC', function() {
|
||||||
var key = new bitcore.Key();
|
var key = new bitcore.Key();
|
||||||
key.private = privkey;
|
key.private = privkey;
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
var encrypted = n._encrypt(key.public, data);
|
var encoded = n._encode(key.public, key, data);
|
||||||
encrypted.length.should.not.equal(0);
|
var decoded = n._decode(key, encoded);
|
||||||
encrypted.length.should.equal(145);
|
encoded.sig.should.not.equal(0);
|
||||||
});
|
decoded.toString().should.equal('my data to encrypt');
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#_decrypt', function() {
|
|
||||||
|
|
||||||
it('should decrypt that which was encrypted', function() {
|
|
||||||
var n = new WebRTC();
|
|
||||||
var data = new bitcore.Buffer('my data to encrypt');
|
|
||||||
var privkeystr = new bitcore.Buffer('test privkey');
|
|
||||||
var privkey = bitcore.util.sha256(privkeystr);
|
|
||||||
var key = new bitcore.Key();
|
|
||||||
key.private = privkey;
|
|
||||||
key.regenerateSync();
|
|
||||||
var encrypted = n._encrypt(key.public, data);
|
|
||||||
var decrypted = n._decrypt(key.private, encrypted);
|
|
||||||
encrypted.length.should.not.equal(0);
|
|
||||||
decrypted.toString().should.equal('my data to encrypt');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -85,6 +87,7 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
it('should call _sendToOne for a copayer', function(done) {
|
it('should call _sendToOne for a copayer', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
var data = new bitcore.Buffer('my data to send');
|
||||||
|
|
||||||
|
@ -95,7 +98,7 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
var copayerId = key.public.toString('hex');
|
||||||
n._sendToOne = function(a1, a2, a3, cb) {
|
n._sendToOne = function(a1, a2, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var sig = undefined;
|
||||||
|
@ -107,6 +110,7 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
it('should call _sendToOne with encrypted data for a copayer', function(done) {
|
it('should call _sendToOne with encrypted data for a copayer', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
var data = new bitcore.Buffer('my data to send');
|
||||||
|
|
||||||
|
@ -117,8 +121,9 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
var copayerId = key.public.toString('hex');
|
||||||
n._sendToOne = function(a1, encPayload, a3, cb) {
|
n._sendToOne = function(a1, enc, cb) {
|
||||||
encPayload.length.should.be.greaterThan(0);
|
var encPayload = JSON.parse(enc.toString());
|
||||||
|
encPayload.sig.length.should.be.greaterThan(0);
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var sig = undefined;
|
||||||
|
@ -130,6 +135,7 @@ describe('Network / WebRTC', function() {
|
||||||
|
|
||||||
it('should call _sendToOne for a list of copayers', function(done) {
|
it('should call _sendToOne for a list of copayers', function(done) {
|
||||||
var n = new WebRTC();
|
var n = new WebRTC();
|
||||||
|
n.privkey = bitcore.util.sha256('test');
|
||||||
|
|
||||||
var data = new bitcore.Buffer('my data to send');
|
var data = new bitcore.Buffer('my data to send');
|
||||||
|
|
||||||
|
@ -140,7 +146,7 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerIds = [key.public.toString('hex')];
|
var copayerIds = [key.public.toString('hex')];
|
||||||
n._sendToOne = function(a1, a2, a3, cb) {
|
n._sendToOne = function(a1, a2, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var sig = undefined;
|
||||||
|
@ -151,4 +157,73 @@ describe('Network / WebRTC', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#_onData', function() {
|
||||||
|
var privkey1 = bitcore.util.sha256('test privkey 1');
|
||||||
|
var privkey2 = bitcore.util.sha256('test privkey 2');
|
||||||
|
var privkey3 = bitcore.util.sha256('test privkey 2');
|
||||||
|
|
||||||
|
var key1 = new bitcore.Key();
|
||||||
|
key1.private = privkey1;
|
||||||
|
key1.regenerateSync();
|
||||||
|
|
||||||
|
var key2 = new bitcore.Key();
|
||||||
|
key2.private = privkey2;
|
||||||
|
key2.regenerateSync();
|
||||||
|
|
||||||
|
var key3 = new bitcore.Key();
|
||||||
|
key3.private = privkey3;
|
||||||
|
key3.regenerateSync();
|
||||||
|
|
||||||
|
it('should not reject data sent from a peer with hijacked pubkey', function() {
|
||||||
|
var n = new WebRTC();
|
||||||
|
n.privkey = key2.private.toString('hex');
|
||||||
|
|
||||||
|
var message = {
|
||||||
|
type: 'hello',
|
||||||
|
copayerId: key1.public.toString('hex')
|
||||||
|
};
|
||||||
|
var messagestr = JSON.stringify(message);
|
||||||
|
var messagebuf = new Buffer(messagestr);
|
||||||
|
|
||||||
|
var encoded = n._encode(key2.public, key1, messagebuf);
|
||||||
|
var encodedstr = JSON.stringify(encoded);
|
||||||
|
var encodeduint = new Buffer(encodedstr);
|
||||||
|
|
||||||
|
var isInbound = true;
|
||||||
|
var peerId = new bitcore.SIN(key1.public);
|
||||||
|
|
||||||
|
n._deletePeer = sinon.spy();
|
||||||
|
|
||||||
|
n._onData(encodeduint, isInbound, peerId);
|
||||||
|
n._deletePeer.calledOnce.should.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject data sent from a peer with hijacked pubkey', function() {
|
||||||
|
var n = new WebRTC();
|
||||||
|
n.privkey = key2.private.toString('hex');
|
||||||
|
|
||||||
|
var message = {
|
||||||
|
type: 'hello',
|
||||||
|
copayerId: key3.public.toString('hex') //MITM pubkey 3
|
||||||
|
};
|
||||||
|
var messagestr = JSON.stringify(message);
|
||||||
|
var messagebuf = new Buffer(messagestr);
|
||||||
|
|
||||||
|
var encoded = n._encode(key2.public, key1, messagebuf);
|
||||||
|
var encodedstr = JSON.stringify(encoded);
|
||||||
|
var encodeduint = new Buffer(encodedstr);
|
||||||
|
|
||||||
|
var isInbound = true;
|
||||||
|
var peerId = new bitcore.SIN(key1.public);
|
||||||
|
|
||||||
|
n._deletePeer = sinon.spy();
|
||||||
|
|
||||||
|
n._onData(encodeduint, isInbound, peerId);
|
||||||
|
n._deletePeer.calledOnce.should.equal(true);
|
||||||
|
n._deletePeer.getCall(0).args[0].should.equal(peerId);
|
||||||
|
n._deletePeer.getCall(0).args[1].should.equal('incorrect pubkey for peerId');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -12,6 +12,9 @@ saveAs = function(o) {
|
||||||
|
|
||||||
|
|
||||||
describe("Unit: Controllers", function() {
|
describe("Unit: Controllers", function() {
|
||||||
|
var invalidForm = {
|
||||||
|
$invalid: true
|
||||||
|
};
|
||||||
|
|
||||||
var scope;
|
var scope;
|
||||||
|
|
||||||
|
@ -73,6 +76,11 @@ describe("Unit: Controllers", function() {
|
||||||
expect(array.length).equal(n);
|
expect(array.length).equal(n);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('#create', function() {
|
||||||
|
it('should work with invalid form', function() {
|
||||||
|
scope.create(invalidForm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -237,7 +245,6 @@ describe("Unit: Controllers", function() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
afterEach(function() {
|
afterEach(function() {
|
||||||
$httpBackend.verifyNoOutstandingExpectation();
|
$httpBackend.verifyNoOutstandingExpectation();
|
||||||
$httpBackend.verifyNoOutstandingRequest();
|
$httpBackend.verifyNoOutstandingRequest();
|
||||||
|
@ -283,10 +290,23 @@ describe("Unit: Controllers", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Send Controller', function() {
|
describe('Send Controller', function() {
|
||||||
var sendCtrl;
|
var sendCtrl, form;
|
||||||
beforeEach(inject(function($controller, $rootScope) {
|
beforeEach(inject(function($compile, $rootScope, $controller) {
|
||||||
scope = $rootScope.$new();
|
scope = $rootScope.$new();
|
||||||
$rootScope.availableBalance = 123456;
|
$rootScope.availableBalance = 123456;
|
||||||
|
|
||||||
|
var element = angular.element(
|
||||||
|
'<form name="form">' +
|
||||||
|
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' +
|
||||||
|
'</form>'
|
||||||
|
);
|
||||||
|
scope.model = {
|
||||||
|
amount: null
|
||||||
|
};
|
||||||
|
$compile(element)(scope);
|
||||||
|
scope.$digest();
|
||||||
|
form = scope.form;
|
||||||
|
|
||||||
sendCtrl = $controller('SendController', {
|
sendCtrl = $controller('SendController', {
|
||||||
$scope: scope,
|
$scope: scope,
|
||||||
$modal: {},
|
$modal: {},
|
||||||
|
@ -297,8 +317,53 @@ describe("Unit: Controllers", function() {
|
||||||
expect(scope.isMobile).not.to.equal(null);
|
expect(scope.isMobile).not.to.equal(null);
|
||||||
});
|
});
|
||||||
it('should autotop balance correctly', function() {
|
it('should autotop balance correctly', function() {
|
||||||
scope.topAmount();
|
scope.topAmount(form);
|
||||||
|
form.amount.$setViewValue(123356);
|
||||||
expect(scope.amount).to.equal(123356);
|
expect(scope.amount).to.equal(123356);
|
||||||
|
expect(form.amount.$invalid).to.equal(false);
|
||||||
|
expect(form.amount.$pristine).to.equal(false);
|
||||||
|
});
|
||||||
|
it('should return available amount', function() {
|
||||||
|
var amount = scope.getAvailableAmount();
|
||||||
|
expect(amount).to.equal(123356);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Import Controller', function() {
|
||||||
|
var what;
|
||||||
|
beforeEach(inject(function($controller, $rootScope) {
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
what = $controller('ImportController', {
|
||||||
|
$scope: scope,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should exist', function() {
|
||||||
|
should.exist(what);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Signin Controller', function() {
|
||||||
|
var what;
|
||||||
|
beforeEach(inject(function($controller, $rootScope) {
|
||||||
|
scope = $rootScope.$new();
|
||||||
|
what = $controller('SigninController', {
|
||||||
|
$scope: scope,
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should exist', function() {
|
||||||
|
should.exist(what);
|
||||||
|
});
|
||||||
|
describe('#open', function() {
|
||||||
|
it('should work with invalid form', function() {
|
||||||
|
scope.open(invalidForm);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#join', function() {
|
||||||
|
it('should work with invalid form', function() {
|
||||||
|
scope.join(invalidForm);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ describe("Unit: Testing Directives", function() {
|
||||||
|
|
||||||
beforeEach(module('copayApp.directives'));
|
beforeEach(module('copayApp.directives'));
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
config.unitToSatoshi = 100;
|
||||||
|
config.unitName = 'bits';
|
||||||
|
});
|
||||||
|
|
||||||
describe('Check config', function() {
|
describe('Check config', function() {
|
||||||
it('unit should be set to BITS in config.js', function() {
|
it('unit should be set to BITS in config.js', function() {
|
||||||
|
@ -43,6 +47,7 @@ describe("Unit: Testing Directives", function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Validate Amount', function() {
|
describe('Validate Amount', function() {
|
||||||
|
describe('Unit: bits', function() {
|
||||||
beforeEach(inject(function($compile, $rootScope) {
|
beforeEach(inject(function($compile, $rootScope) {
|
||||||
$scope = $rootScope;
|
$scope = $rootScope;
|
||||||
$rootScope.availableBalance = 1000;
|
$rootScope.availableBalance = 1000;
|
||||||
|
@ -58,14 +63,13 @@ describe("Unit: Testing Directives", function() {
|
||||||
$scope.$digest();
|
$scope.$digest();
|
||||||
form = $scope.form;
|
form = $scope.form;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
it('should validate', function() {
|
it('should validate', function() {
|
||||||
form.amount.$setViewValue(100);
|
form.amount.$setViewValue(100);
|
||||||
expect(form.amount.$invalid).to.equal(false);
|
expect(form.amount.$invalid).to.equal(false);
|
||||||
form.amount.$setViewValue(900);
|
form.amount.$setViewValue(800);
|
||||||
expect(form.amount.$invalid).to.equal(false);
|
expect(form.amount.$invalid).to.equal(false);
|
||||||
|
form.amount.$setViewValue(900);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not validate', function() {
|
it('should not validate', function() {
|
||||||
|
@ -77,9 +81,57 @@ describe("Unit: Testing Directives", function() {
|
||||||
expect(form.amount.$invalid).to.equal(true);
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
form.amount.$setViewValue(1000);
|
form.amount.$setViewValue(1000);
|
||||||
expect(form.amount.$invalid).to.equal(true);
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
|
form.amount.$setViewValue(901);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Unit: BTC', function() {
|
||||||
|
beforeEach(inject(function($compile, $rootScope) {
|
||||||
|
config.unitToSatoshi = 100000000;
|
||||||
|
config.unitName = 'BTC';
|
||||||
|
$scope = $rootScope;
|
||||||
|
$rootScope.availableBalance = 0.04;
|
||||||
|
var element = angular.element(
|
||||||
|
'<form name="form">' +
|
||||||
|
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.0001" max="10000000" enough-amount required>' +
|
||||||
|
'</form>'
|
||||||
|
);
|
||||||
|
$scope.model = {
|
||||||
|
amount: null
|
||||||
|
};
|
||||||
|
$compile(element)($scope);
|
||||||
|
$scope.$digest();
|
||||||
|
form = $scope.form;
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should validate', function() {
|
||||||
|
form.amount.$setViewValue(0.01);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(null);
|
||||||
|
expect(form.amount.$invalid).to.equal(false);
|
||||||
|
form.amount.$setViewValue(0.039);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(null);
|
||||||
|
expect(form.amount.$invalid).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not validate', function() {
|
||||||
|
form.amount.$setViewValue(0.03999);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(true);
|
||||||
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
|
form.amount.$setViewValue(0);
|
||||||
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
|
form.amount.$setViewValue(0.0);
|
||||||
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
|
form.amount.$setViewValue(0.05);
|
||||||
|
expect($scope.notEnoughAmount).to.equal(true);
|
||||||
|
expect(form.amount.$invalid).to.equal(true);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('Contact directive', function() {
|
describe('Contact directive', function() {
|
||||||
var element1, element2;
|
var element1, element2;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,13 @@
|
||||||
//
|
//
|
||||||
var sinon = require('sinon');
|
var sinon = require('sinon');
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
config.unitToSatoshi = 100;
|
||||||
|
config.unitName = 'bits';
|
||||||
|
});
|
||||||
|
|
||||||
describe('Check config', function() {
|
describe('Check config', function() {
|
||||||
|
|
||||||
it('unit should be set to BITS in config.js', function() {
|
it('unit should be set to BITS in config.js', function() {
|
||||||
expect(config.unitToSatoshi).to.equal(100);
|
expect(config.unitToSatoshi).to.equal(100);
|
||||||
expect(config.unitName).to.equal('bits');
|
expect(config.unitName).to.equal('bits');
|
||||||
|
|
|
@ -84,6 +84,9 @@ var createBundle = function(opts) {
|
||||||
b.require('./js/models/core/Passphrase', {
|
b.require('./js/models/core/Passphrase', {
|
||||||
expose: '../js/models/core/Passphrase'
|
expose: '../js/models/core/Passphrase'
|
||||||
});
|
});
|
||||||
|
b.require('./js/models/core/Message', {
|
||||||
|
expose: '../js/models/core/Message'
|
||||||
|
});
|
||||||
|
|
||||||
if (opts.dontminify) {
|
if (opts.dontminify) {
|
||||||
//include dev dependencies
|
//include dev dependencies
|
||||||
|
|
Loading…
Reference in New Issue