mirror of https://github.com/BTCPrivate/copay.git
commit
88a7fca8d8
59
README.md
59
README.md
|
@ -36,19 +36,19 @@ About Copay
|
|||
General
|
||||
-------
|
||||
|
||||
*Copay* implements a multisig wallet using p2sh addresses. It support multiple wallet configurations, like 3-of-5
|
||||
(3 required signatures from 5 participant peers) or 2-of-3. To generate addresses to receive coins,
|
||||
*Copay* needs the public keys of all the participat peers in the wallet. Those public keys, among the
|
||||
*Copay* implements a multisig wallet using p2sh addresses. It supports multiple wallet configurations, like
|
||||
3-of-5 (3 required signatures from 5 participant peers) or 2-of-3. To generate addresses to receive coins,
|
||||
*Copay* needs the public keys of all the participant peers in the wallet. Those public keys, among the
|
||||
wallet configuration, are combined to generate a single address to receive a payment.
|
||||
|
||||
To unlock the payment, and spend the wallet's funds, the needed signatures need to be collected an put togheter
|
||||
in the transaction. Each peer manage her own private key, and that key is never transmited to other
|
||||
peers. Once a transaction proposal is created, the proposal is distributed among the peers and each peer
|
||||
can sign the transaction locally. Once the transaction is complete, the last signing peer will broadcast the
|
||||
To unlock the payment, and spend the wallet's funds, the needed signatures need to be collected and put
|
||||
togheter in the transaction. Each peer manages her own private key, and that key is never transmited to
|
||||
other peers. Once a transaction proposal is created, the proposal is distributed among the peers and each peer
|
||||
can sign the transaction locally. Once the signing is complete, the last signing peer will broadcast the
|
||||
transaction to the bitcoin network, using a public API for that (Insight API by default in *Copay*)..
|
||||
|
||||
*Copay* also implements BIP32 to generate new addresses for the peers. This mean that the actual piece of
|
||||
information shared between the peers is an extended public key, from which is possible to derive more
|
||||
*Copay* also implements BIP32 to generate new addresses for the peers. This means that the actual piece of
|
||||
information shared between the peers is an extended public key, from which it is possible to derive more
|
||||
public keys so the wallet can use them. Each peer holds for himself his extended private key, to be able
|
||||
to sign the incoming transaction proposals.
|
||||
|
||||
|
@ -57,41 +57,40 @@ Serverless web
|
|||
*Copay* software does not need an application server to run. All the software is implemented in client-side
|
||||
Javascript. For persistent storage, the client browser's *localStorage* is used. This information is
|
||||
stored encryped using the peer's password. Also it is possible (and recommended) to backup that information
|
||||
with using one of the options provided by *Copay*, like file downloading. Without a proper backup, all
|
||||
with using one of the options provided by *Copay*, like file downloading. Without a proper backup, all
|
||||
wallets funds can be lost if the browser's localStorage is deleted, or the browser installation deleted.
|
||||
|
||||
Peer communications
|
||||
-------------------
|
||||
*Copay* use peer-to-peer (p2p) networking to comunicate the parties. Parties exchange transaction
|
||||
proposals, public keys, nicknames and some wallet options. As mentioned above, private keys are *no*
|
||||
*Copay* use peer-to-peer (p2p) networking for comunication between the parties. Parties exchange transaction
|
||||
proposals, public keys, nicknames and some wallet options. As mentioned above, private keys are *not*
|
||||
sent to the network.
|
||||
|
||||
webRTC is the used protocol. A p2p facilitator server is needed to allow the peers to find each other.
|
||||
*Copay* uses the open-sourced *peerjs*
|
||||
server implementation. Wallet participants can use a public peerjs server or install their own. Once the peers
|
||||
find each other, a true p2p connection is established and there is no flow of information to the
|
||||
server, only between the peers.
|
||||
webRTC is the used protocol for p2p communication. A p2p facilitator server is needed to allow the peers
|
||||
to find each other. *Copay* uses the open-sourced *peerjs* server implementation. Wallet participants
|
||||
can use a public peerjs server or install their own. Once the peers find each other, a true p2p
|
||||
connection is established and there is no flow of information to the server, only between the peers.
|
||||
|
||||
webRTC uses DTLS to secure communications between the peers, and each peer use a self-signed
|
||||
webRTC uses DTLS to secure communications between the peers, and each peer uses a self-signed
|
||||
certificate.
|
||||
|
||||
Security model
|
||||
--------------
|
||||
On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) by 2 factors: An identity
|
||||
On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) by 2 factors: An identity
|
||||
key and a network key.
|
||||
|
||||
The *identity key* is a ECDSA public key derived from their extended public
|
||||
key using a specific BIP32 branch. This special public key is never used for Bitcoin address creation, and
|
||||
should only be know by members of the WR.
|
||||
In *Copay* this special public key is named *copayerId*. To register into the peerjs server, while not
|
||||
reveling its copayerId to an entity outside the WR, each peer hash the copayerId and pass a SIN
|
||||
to the server. peer discovery is then entirely done using peer's SINs. Note that all copayers in the WR
|
||||
know the complete copayerIDs of the peers.
|
||||
The *identity key* is a ECDSA public key derived from peers' extended public key using a specific BIP32
|
||||
branch. This special public key is never used for Bitcoin address creation, and should only be know by
|
||||
members of the WR.
|
||||
In *Copay* this special public key is named *copayerId*. To register into the peerjs server, while not
|
||||
reveling its copayerId to an entity outside the WR, each peer hashes the copayerId and sends a SIN
|
||||
to the server. peer discovery is then entirely done using the peer's SINs. Note that all copayers in
|
||||
the WR know the complete copayerIDs of the peers.
|
||||
|
||||
The *network key* is a random key generated when the wallet is created an shared in the initial
|
||||
'secret string' that peers distribute while the wallet is been created. The network key is then stored
|
||||
by each peer on the wallet configuration. The network key is used for establishing a CCM/AES
|
||||
authenticated encrypted channel between all peers, on top of webRTC. The main reason of implementing
|
||||
by each peer in their wallet configuration. The network key is used for establishing a CCM/AES
|
||||
authenticated encrypted channel between all peers, on top of webRTC. The main reason for implementing
|
||||
the *network key* is to prevent man-in-the-middle attacks from a compromised peerjs server.
|
||||
|
||||
Secret String
|
||||
|
@ -99,7 +98,7 @@ Secret String
|
|||
When a wallet is created, a secret string is provided to invite new peers to the new wallet. This string
|
||||
has the following format:
|
||||
|
||||
- CopayerId of the peer generating the string. This is a 33 bytes ECDSA public key, as explained above.
|
||||
- CopayerId of the peer generating the string. This is a 33 byte ECDSA public key, as explained above.
|
||||
This allow the receiving peer to locate the generating peer.
|
||||
- Network Key. A 8 byte string to encrypt and sign the peers communication.
|
||||
|
||||
|
@ -109,7 +108,7 @@ Peer authentication
|
|||
-------------------
|
||||
|
||||
It is important to note that all data in the wallet is shared between *all peers*, with the exception of each
|
||||
peer's private key, with are never transmited throught the network. There is not private messages or, in general,
|
||||
peer's private key, which are never transmited throught the network. There are no private messages or, in general,
|
||||
information that belongs to a subset of the WR.
|
||||
|
||||
|
||||
|
|
|
@ -9,18 +9,77 @@ var config = {
|
|||
//host: 'localhost',
|
||||
//port: 10009,
|
||||
//path: '/',
|
||||
//
|
||||
//
|
||||
key: 'g23ihfh82h35rf', // api key for the peerjs server
|
||||
host:'162.242.219.26', // peerjs server
|
||||
port:10009,
|
||||
host: '162.242.219.26', // peerjs server
|
||||
port: 10009,
|
||||
path: '/',
|
||||
maxPeers: 15,
|
||||
// debug: 3,
|
||||
debug: 3,
|
||||
sjclParams: {
|
||||
salt: 'mjuBtGybi/4=', // choose your own salt (base64)
|
||||
iter:1000,
|
||||
mode:'ccm',
|
||||
ts:parseInt(64),
|
||||
iter: 1000,
|
||||
mode: 'ccm',
|
||||
ts: parseInt(64),
|
||||
},
|
||||
config: {
|
||||
'iceServers': [
|
||||
// Pass in STUN and TURN servers for maximum network compatibility
|
||||
{
|
||||
url: 'stun:stun01.sipphone.com'
|
||||
}, {
|
||||
url: 'stun:stun.ekiga.net'
|
||||
}, {
|
||||
url: 'stun:stun.fwdnet.net'
|
||||
}, {
|
||||
url: 'stun:stun.ideasip.com'
|
||||
}, {
|
||||
url: 'stun:stun.iptel.org'
|
||||
}, {
|
||||
url: 'stun:stun.rixtelecom.se'
|
||||
}, {
|
||||
url: 'stun:stun.schlund.de'
|
||||
}, {
|
||||
url: 'stun:stun.l.google.com:19302'
|
||||
}, {
|
||||
url: 'stun:stun1.l.google.com:19302'
|
||||
}, {
|
||||
url: 'stun:stun2.l.google.com:19302'
|
||||
}, {
|
||||
url: 'stun:stun3.l.google.com:19302'
|
||||
}, {
|
||||
url: 'stun:stun4.l.google.com:19302'
|
||||
}, {
|
||||
url: 'stun:stunserver.org'
|
||||
}, {
|
||||
url: 'stun:stun.softjoys.com'
|
||||
}, {
|
||||
url: 'stun:stun.voiparound.com'
|
||||
}, {
|
||||
url: 'stun:stun.voipbuster.com'
|
||||
}, {
|
||||
url: 'stun:stun.voipstunt.com'
|
||||
}, {
|
||||
url: 'stun:stun.voxgratia.org'
|
||||
}, {
|
||||
url: 'stun:stun.xten.com'
|
||||
}, {
|
||||
url: 'turn:numb.viagenie.ca',
|
||||
credential: 'muazkh',
|
||||
username: 'webrtc@live.com'
|
||||
}, {
|
||||
url: 'turn:192.158.29.39:3478?transport=udp',
|
||||
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
|
||||
username: '28224511:1379330808'
|
||||
}, {
|
||||
url: 'turn:192.158.29.39:3478?transport=tcp',
|
||||
credential: 'JZEOEt2V3Qb0y27GRntt2u2PAYA=',
|
||||
username: '28224511:1379330808'
|
||||
}, {
|
||||
url: 'turn:homeo@turn.bistri.com:80',
|
||||
credential: 'homeo'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
limits: {
|
||||
|
@ -47,7 +106,6 @@ var config = {
|
|||
storageSalt: 'mjuBtGybi/4=', // choose your own salt (base64)
|
||||
};
|
||||
|
||||
var log = function () {
|
||||
var log = function() {
|
||||
if (config.verbose) console.log(arguments);
|
||||
}
|
||||
|
||||
|
|
|
@ -267,9 +267,6 @@ hr { margin: 2.25rem 0;}
|
|||
.lh {line-height: 0;}
|
||||
.oh {overflow:hidden;}
|
||||
.lh {line-height: 0;}
|
||||
.signin input.ng-dirty.ng-invalid {
|
||||
border: 2px red solid;
|
||||
}
|
||||
|
||||
.video-small {
|
||||
width: 80px;
|
||||
|
|
|
@ -168,17 +168,94 @@ button.radius, .button.radius {
|
|||
border-radius: 5px;
|
||||
}
|
||||
|
||||
button.primary, button.secondary, button.primary:hover, button.secondary:hover {color: #fff !important;}
|
||||
/* SECONDARY */
|
||||
button.secondary,
|
||||
.button.secondary {
|
||||
background-color: #1ABC9C;
|
||||
color: #fff;
|
||||
}
|
||||
button.secondary:hover,
|
||||
button.secondary:focus,
|
||||
.button.secondary:hover,
|
||||
.button.secondary:focus {
|
||||
background-color: #16A085;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
button.disabled.secondary,
|
||||
button[disabled].secondary,
|
||||
.button.disabled.secondary,
|
||||
.button[disabled].secondary,
|
||||
button.disabled.secondary:hover,
|
||||
button.disabled.secondary:focus,
|
||||
button[disabled].secondary:hover,
|
||||
button[disabled].secondary:focus,
|
||||
.button.disabled.secondary:hover,
|
||||
.button.disabled.secondary:focus,
|
||||
.button[disabled].secondary:hover,
|
||||
.button[disabled].secondary:focus {
|
||||
background-color: #1ABC9C;
|
||||
color: #E6E6E6;
|
||||
}
|
||||
|
||||
button.primary { background-color: #E67E22; }
|
||||
button.secondary { background-color: #1ABC9C !important; }
|
||||
button.warning { background-color: #C0392A; }
|
||||
/* PRIMARY */
|
||||
button.primary,
|
||||
.button.primary {
|
||||
background-color: #E67E22;
|
||||
color: #fff;
|
||||
}
|
||||
button.primary:hover,
|
||||
button.primary:focus,
|
||||
.button.primary:hover,
|
||||
.button.primary:focus {
|
||||
background-color: #D86601;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
button.disabled.primary,
|
||||
button[disabled].primary,
|
||||
.button.disabled.primary,
|
||||
.button[disabled].primary,
|
||||
button.disabled.primary:hover,
|
||||
button.disabled.primary:focus,
|
||||
button[disabled].primary:hover,
|
||||
button[disabled].primary:focus,
|
||||
.button.disabled.primary:hover,
|
||||
.button.disabled.primary:focus,
|
||||
.button[disabled].primary:hover,
|
||||
.button[disabled].primary:focus {
|
||||
background-color: #E67E22;
|
||||
color: #E6E6E6;
|
||||
}
|
||||
|
||||
button.primary:hover { background-color: #D86601;}
|
||||
button.secondary:hover { background-color: #16A085 !important;}
|
||||
button.warning:hover { background-color: #82251A; }
|
||||
/* WARNING */
|
||||
button.warning,
|
||||
.button.warning {
|
||||
background-color: #C0392A;
|
||||
color: #fff;
|
||||
}
|
||||
button.warning:hover,
|
||||
button.warning:focus,
|
||||
.button.warning:hover,
|
||||
.button.warning:focus {
|
||||
background-color: #82251A;
|
||||
color: #e6e6e6;
|
||||
}
|
||||
button.disabled.warning,
|
||||
button[disabled].warning,
|
||||
.button.disabled.warning,
|
||||
.button[disabled].warning,
|
||||
button.disabled.warning:hover,
|
||||
button.disabled.warning:focus,
|
||||
button[disabled].warning:hover,
|
||||
button[disabled].warning:focus,
|
||||
.button.disabled.warning:hover,
|
||||
.button.disabled.warning:focus,
|
||||
.button[disabled].warning:hover,
|
||||
.button[disabled].warning:focus {
|
||||
background-color: #C0392A;
|
||||
color: #E6E6E6;
|
||||
}
|
||||
|
||||
.text-gray { color: #999 !important;}
|
||||
.text-gray { color: #999;}
|
||||
|
||||
#footer {
|
||||
background: #2C3E50;
|
||||
|
|
80
index.html
80
index.html
|
@ -143,40 +143,46 @@
|
|||
</div>
|
||||
<div ng-show="!loading">
|
||||
<div class="row">
|
||||
<div class="large-6 columns">
|
||||
<div class="box-signin">
|
||||
<h3>Join a Wallet in Creation</h3>
|
||||
<input type="text" class="form-control" placeholder="Paste wallet secret here" ng-model="connectionId" required autofocus>
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="joinPassword">
|
||||
<input type="text" class="form-control" placeholder="Your name (optional)" ng-model="nickname">
|
||||
<button class="button primary expand radius" ng-click="join()" ng-disabled="loading" loading="Joining">Join</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="large-6 columns">
|
||||
<div class="large-6 medium-6 columns">
|
||||
<div class="box-signin">
|
||||
<div ng-show="wallets.length">
|
||||
<h3>Open Wallet</h3>
|
||||
<select class="form-control" ng-model="selectedWalletId" ng-options="w.id as w.show for w in wallets">
|
||||
</select>
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="openPassword">
|
||||
<button class="button secondary expand radius" type="button" ng-click="open()" ng-disabled="loading" loading="Opening">Open</button>
|
||||
<form name="openForm" ng-submit="open(openForm)" novalidate>
|
||||
<select class="form-control" ng-model="selectedWalletId" ng-options="w.id as w.show for w in wallets" required>
|
||||
</select>
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" name="openPassword" ng-model="openPassword" required>
|
||||
<button type="submit" class="button secondary radius" ng-disabled="openForm.$invalid || loading" loading="Opening">Open</button>
|
||||
</form>
|
||||
</div>
|
||||
<div ng-show="!wallets.length">
|
||||
<h3>Create a new wallet</h3>
|
||||
<input type="text" class="form-control" ng-model="walletName" placeholder="Wallet name (optional)">
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="createPassword">
|
||||
<button class="button secondary expand radius" ng-click="create()" ng-disabled="loading" loading="Creating">Create</button>
|
||||
<form name="createForm" ng-submit="create(createForm)" novalidate>
|
||||
<input type="text" class="form-control" ng-model="walletName" name="walletName" placeholder="Wallet name (optional)">
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" name="createPassword" ng-model="createPassword" required>
|
||||
<button type="submit" class="button secondary radius" ng-disabled="createForm.$invalid || loading" loading="Creating">Create</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="large-6 medium-6 columns">
|
||||
<div class="box-signin">
|
||||
<h3>Join a Wallet in Creation</h3>
|
||||
<form name="joinForm" ng-submit="join(joinForm)" novalidate>
|
||||
<input type="text" class="form-control" placeholder="Paste wallet secret here" name="connectionId" ng-model="connectionId" required>
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" name="joinPassword" ng-model="joinPassword" required>
|
||||
<input type="text" class="form-control" placeholder="Your name (optional)" name="nickname" ng-model="nickname">
|
||||
<button type="submit" class="button primary radius" ng-disabled="joinForm.$invalid || loading" loading="Joining">Join</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="large-12 columns text-center line-dashed">
|
||||
<span ng-show="wallets.length">
|
||||
<a ng-click="create()" ng-disabled="loading" loading="Creating">Create a new wallet</a> ·
|
||||
<a href="#/setup">Create a new wallet</a> ·
|
||||
</span>
|
||||
<a ng-href="#import">Import from file</a>
|
||||
<a ng-href="#import">Import a backup</a>
|
||||
</div>
|
||||
</div>
|
||||
</div> <!-- End !loading -->
|
||||
|
@ -185,14 +191,23 @@
|
|||
|
||||
<script type="text/ng-template" id="import.html">
|
||||
<div ng-controller="ImportController">
|
||||
<h3>{{title}}</h3>
|
||||
<div class="large-6 columns">
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" ng-model="password" autofocus required>
|
||||
<h6>Select a backup file</h6>
|
||||
<input type="file" class="form-control" placeholder="Select a backup file" ng-model="backupFile" ng-file-select>
|
||||
<h6>Or just paste the backup text here</h6>
|
||||
<textarea class="form-control" ng-model="backupText"></textarea>
|
||||
<button class="button primary expand radius" ng-click="import()">Import backup</button>
|
||||
<div class="large-6 large-centered medium-6 medium-centered columns" ng-init="choosefile=0; pastetext=0">
|
||||
<h2>{{title}}</h2>
|
||||
<form name="importForm" ng-submit="import(importForm)" novalidate>
|
||||
<fieldset>
|
||||
<legend>Select which method want to use to restore</legend>
|
||||
<label for="backupFile" ng-click="choosefile=!choosefile" class="m10b"><i class="fi-upload"></i> Choose backup file from your computer</label>
|
||||
<input type="file" class="form-control" placeholder="Select a backup file" name="backupFile" ng-model="backupFile" ng-file-select ng-show="choosefile">
|
||||
<label for="backupText" ng-click="pastetext=!pastetext" class="m10b"><i class="fi-paperclip"></i> Paste backup plain text code</label>
|
||||
<textarea class="form-control" name="backupText" ng-model="backupText" rows="5" ng-show="pastetext"></textarea>
|
||||
</fieldset>
|
||||
|
||||
<label for="password">Password <small>Required</small></label>
|
||||
<input type="password" class="form-control" placeholder="Your wallet password" name="password" ng-model="password" required>
|
||||
<button type="submit" class="button primary radius" ng-disabled="importForm.$invalid" loading="Importing">
|
||||
Import backup
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
@ -203,6 +218,7 @@
|
|||
Creating new wallet...
|
||||
</div>
|
||||
<div ng-show="!loading">
|
||||
<form name="setupForm" ng-submit="create(setupForm)" novalidate>
|
||||
<div class="row">
|
||||
<div class="small-12 medium-8 medium-centered large-8 large-centered columns box-setup">
|
||||
<div class="large-6 columns line-dashed-v">
|
||||
|
@ -226,27 +242,27 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m30v">
|
||||
<h6>Wallet Password</h6>
|
||||
<h6>Wallet Password <small>Required</small></h6>
|
||||
<input type="password" class="form-control" ng-model="walletPassword" required>
|
||||
</div>
|
||||
<div class="small-12 medium-6 medium-centered large-6 large-centered columns m30v">
|
||||
<h6>Wallet name <small>(optional)</small></h6>
|
||||
<h6>Wallet name <small>Optional</small></h6>
|
||||
<input type="text" class="form-control" ng-model="walletName">
|
||||
</div>
|
||||
<div class="large-6 large-centered columns m30v">
|
||||
<h6>Your name <small>(optional)</small></h6>
|
||||
<h6>Your name <small>Optional</small></h6>
|
||||
<input type="text" class="form-control" ng-model="myNickname">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="large-12 columns line-dashed">
|
||||
<button class="button primary radius right" type="button"
|
||||
ng-click="create()">
|
||||
<button type="submit" class="button primary radius right" ng-disabled="setupForm.$invalid || loading">
|
||||
Create {{requiredCopayers}}-of-{{totalCopayers}} wallet
|
||||
</button>
|
||||
<a class="button secondary radius" href="#signin">Go back</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
|
|
|
@ -44,6 +44,7 @@ angular.module('copay.header').controller('HeaderController',
|
|||
};
|
||||
|
||||
$scope.refresh = function() {
|
||||
var w = $rootScope.wallet;
|
||||
controllerUtils.updateBalance(function() {
|
||||
w.connectToAll();
|
||||
$rootScope.$digest();
|
||||
|
|
|
@ -21,13 +21,31 @@ angular.module('copay.import').controller('ImportController',
|
|||
};
|
||||
};
|
||||
|
||||
$scope.import = function() {
|
||||
if ($scope.password) {
|
||||
if ($scope.backupText) {
|
||||
_importBackup($scope.backupText);
|
||||
} else {
|
||||
reader.readAsBinaryString($scope.file);
|
||||
}
|
||||
$scope.import = function(form) {
|
||||
if (form.$invalid) {
|
||||
$rootScope.flashMessage = { message: 'There is an error in the form. Please, try again', type: 'error'};
|
||||
return;
|
||||
}
|
||||
|
||||
var backupFile = $scope.file;
|
||||
var backupText = form.backupText.$modelValue;
|
||||
var password = form.password.$modelValue;
|
||||
|
||||
if (!backupFile && !backupText) {
|
||||
$rootScope.flashMessage = { message: 'Please, select your backup file or paste the text', type: 'error'};
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
if (backupFile) {
|
||||
reader.readAsBinaryString(backupFile);
|
||||
}
|
||||
else {
|
||||
_importBackup(backupText);
|
||||
}
|
||||
|
||||
$scope.loading = false;
|
||||
|
||||
};
|
||||
});
|
||||
|
|
|
@ -32,7 +32,12 @@ angular.module('copay.setup').controller('SetupController',
|
|||
updateRCSelect(tc);
|
||||
});
|
||||
|
||||
$scope.create = function() {
|
||||
$scope.create = function(form) {
|
||||
if (form && form.$invalid) {
|
||||
$rootScope.flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
|
||||
var passphrase = Passphrase.getBase64($scope.walletPassword);
|
||||
|
|
|
@ -7,30 +7,40 @@ angular.module('copay.signin').controller('SigninController',
|
|||
$scope.selectedWalletId = $scope.wallets.length ? $scope.wallets[0].id : null;
|
||||
$scope.openPassword = '';
|
||||
|
||||
$scope.create = function() {
|
||||
$scope.loading = true;
|
||||
$scope.create = function(form) {
|
||||
if (form && form.$invalid) {
|
||||
$rootScope.flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
||||
return;
|
||||
}
|
||||
|
||||
$rootScope.walletName = $scope.walletName;
|
||||
$rootScope.walletPassword = $scope.createPassword;
|
||||
$rootScope.walletName = form.walletName.$modelValue;
|
||||
$rootScope.walletPassword = form.createPassword.$modelValue;
|
||||
$location.path('setup');
|
||||
};
|
||||
|
||||
$scope.open = function() {
|
||||
if ($scope.openPassword != '') {
|
||||
$scope.loading = true;
|
||||
|
||||
var passphrase = Passphrase.getBase64($scope.openPassword);
|
||||
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
|
||||
if (!w) {
|
||||
$scope.loading = false;
|
||||
$rootScope.flashMessage = { message: 'Bad password or connection error', type: 'error'};
|
||||
return;
|
||||
}
|
||||
controllerUtils.startNetwork(w);
|
||||
$scope.open = function(form) {
|
||||
if (form && form.$invalid) {
|
||||
$rootScope.flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
||||
return;
|
||||
}
|
||||
|
||||
$scope.loading = true;
|
||||
var password = form.openPassword.$modelValue;
|
||||
var passphrase = Passphrase.getBase64(password);
|
||||
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
|
||||
if (!w) {
|
||||
$scope.loading = false;
|
||||
$rootScope.flashMessage = { message: 'Bad password or connection error', type: 'error'};
|
||||
return;
|
||||
}
|
||||
controllerUtils.startNetwork(w);
|
||||
};
|
||||
|
||||
$scope.join = function() {
|
||||
$scope.join = function(form) {
|
||||
if (form && form.$invalid) {
|
||||
$rootScope.flashMessage = { message: 'Please, enter required fields', type: 'error'};
|
||||
return;
|
||||
}
|
||||
$scope.loading = true;
|
||||
|
||||
walletFactory.network.on('badSecret', function() {
|
||||
|
|
|
@ -65,6 +65,8 @@ WalletFactory.prototype.fromObj = function(obj) {
|
|||
}
|
||||
this.log('### WALLET OPENED:', w.id);
|
||||
|
||||
// store imported wallet
|
||||
w.store();
|
||||
return w;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,11 +28,8 @@ function Network(opts) {
|
|||
mode:'ccm',
|
||||
ts:parseInt(64),
|
||||
};
|
||||
|
||||
|
||||
// For using your own peerJs server
|
||||
self.opts = {};
|
||||
['port', 'host', 'path', 'debug', 'key'].forEach(function(k) {
|
||||
this.opts = {};
|
||||
['config', 'port', 'host', 'path', 'debug', 'key'].forEach(function(k) {
|
||||
if (opts[k]) self.opts[k] = opts[k];
|
||||
});
|
||||
this.cleanUp();
|
||||
|
|
|
@ -30,7 +30,7 @@ var createBundle = function(opts) {
|
|||
opts.dir = opts.dir || 'js/';
|
||||
|
||||
// concat browser vendor files
|
||||
exec('cd ' + opts.dir + 'browser; sh concat.sh', puts);
|
||||
exec('sh concat.sh', puts);
|
||||
|
||||
var bopts = {
|
||||
pack: pack,
|
||||
|
|
Loading…
Reference in New Issue