Fix Conflicts:

index.html
This commit is contained in:
Gustavo Cortez 2014-06-02 22:19:15 -03:00
commit 7aebf032ef
16 changed files with 225 additions and 165 deletions

View File

@ -24,7 +24,7 @@ grunt shell --target=dev
Open Copay: Open Copay:
``` ```
node app.js npm start
``` ```
Then visit localhost:3000 in your browser. Then visit localhost:3000 in your browser.
@ -33,16 +33,16 @@ Then visit localhost:3000 in your browser.
## Running copay ## Running copay
To run on a different port: To run on a different port:
``` ```
PORT=3001 node app.js PORT=3001 npm start
``` ```
To open up five different instances to test 3-of-5 multisig with yourself, then run this in 5 different terminals: To open up five different instances to test 3-of-5 multisig with yourself, then run this in 5 different terminals:
``` ```
PORT=3001 node app.js PORT=3001 npm start
PORT=3002 node app.js PORT=3002 npm start
PORT=3003 node app.js PORT=3003 npm start
PORT=3004 node app.js PORT=3004 npm start
PORT=3005 node app.js PORT=3005 npm start
``` ```
To open n different instances more easily, just run: To open n different instances more easily, just run:
@ -51,12 +51,22 @@ n=5
node launch.js $n & node launch.js $n &
``` ```
To require Copay as a module for use within you application:
```js
require('copay').start(3000, function(location) {
console.log('Copay server running at: ' + location);
});
```
## Configuration ## Configuration
Default configuration can be found in the config.js file. Default configuration can be found in the config.js file.
See config.js for more info on configuration options. See config.js for more info on configuration options.
# About Copay # About Copay
General General
@ -64,22 +74,22 @@ General
*Copay* implements a multisig wallet using p2sh addresses. It supports multiple wallet configurations, such as 3-of-5 *Copay* implements a multisig wallet using p2sh addresses. It supports multiple wallet configurations, such as 3-of-5
(3 required signatures from 5 participant peers) or 2-of-3. To create a multisig wallet shared between multiple participants, (3 required signatures from 5 participant peers) or 2-of-3. To create a multisig wallet shared between multiple participants,
*Copay* needs the public keys of all the wallet participants. Those public keys are incorporated into the *Copay* needs the public keys of all the wallet participants. Those public keys are incorporated into the
wallet configuration and are combined to generate a payment address with which funds can be sent into the wallet. wallet configuration and are combined to generate a payment address with which funds can be sent into the wallet.
To unlock the payment and spend the wallet's funds, a quorum of participant signatures must be collected To unlock the payment and spend the wallet's funds, a quorum of participant signatures must be collected
and assembled in the transaction. The funds cannot be spent without at least the minimum number of and assembled in the transaction. The funds cannot be spent without at least the minimum number of
signatures required by the wallet configuration (2 of 3, 3 of 5, 6 of 6, etc). signatures required by the wallet configuration (2 of 3, 3 of 5, 6 of 6, etc).
Each participant manages their own private key, and that private key is never transmitted anywhere. Each participant manages their own private key, and that private key is never transmitted anywhere.
Once a transaction proposal is created, the proposal is distributed among the Once a transaction proposal is created, the proposal is distributed among the
wallet participants for each participant to sign the transaction locally. wallet participants for each participant to sign the transaction locally.
Once the transaction is signed, the last signing participant will broadcast the Once the transaction is signed, the last signing participant will broadcast the
transaction to the Bitcoin network using a public API (defaults to the Insight API). transaction to the Bitcoin network using a public API (defaults to the Insight API).
*Copay* also implements BIP32 to generate new addresses for the peers. The public key each participant contributes *Copay* also implements BIP32 to generate new addresses for the peers. The public key each participant contributes
to the wallet is a BIP32 extended public key. As additional public keys are needed for wallet operations (to produce to the wallet is a BIP32 extended public key. As additional public keys are needed for wallet operations (to produce
new addresses to receive payments into the wallet, for example) new public keys can be derived from the participants' new addresses to receive payments into the wallet, for example) new public keys can be derived from the participants'
original extended public keys. Each participant keeps their own private keys locally. Private keys are not shared. original extended public keys. Each participant keeps their own private keys locally. Private keys are not shared.
Private keys are used to sign transaction proposals to make a payment from the shared wallet. Private keys are used to sign transaction proposals to make a payment from the shared wallet.
Serverless web Serverless web
@ -88,17 +98,17 @@ Serverless web
JavaScript. For persistent storage, the client browser's *localStorage* is used. Locally stored data is JavaScript. For persistent storage, the client browser's *localStorage* is used. Locally stored data is
encrypted using a password provided by the local user. Data kept in browser local storage should be encrypted using a password provided by the local user. Data kept in browser local storage should be
backed up for safekeeping using one of the methods provided by *Copay*, such as downloading the data into a file. backed up for safekeeping using one of the methods provided by *Copay*, such as downloading the data into a file.
Without a proper backup of the user's private key data, all funds stored in the Without a proper backup of the user's private key data, all funds stored in the
wallet may be lost or inaccessible if the browser's localStorage is deleted, the browser uninstalled, wallet may be lost or inaccessible if the browser's localStorage is deleted, the browser uninstalled,
the local hard disk fails, etc. the local hard disk fails, etc.
Peer communications Peer communications
------------------- -------------------
*Copay* uses peer-to-peer (p2p) networking to communicate between wallet participants. Participants exchange transaction *Copay* uses peer-to-peer (p2p) networking to communicate between wallet participants. Participants exchange transaction
proposals, public keys, nicknames and information about the wallet configuration. Private keys are *not* shared with anyone. proposals, public keys, nicknames and information about the wallet configuration. Private keys are *not* shared with anyone.
*Copay* network communications use the webRTC protocol. A p2p facilitator server is needed to enable the peers to find each other. *Copay* network communications use the webRTC protocol. A p2p facilitator server is needed to enable the peers to find each other.
*Copay* uses the open-sourced *peerjs* server implementation for p2p discovery. Wallet participants can use a *Copay* uses the open-sourced *peerjs* server implementation for p2p discovery. Wallet participants can use a
public peerjs server or install their own. Once the peers find each other, a true p2p connection is established between the public peerjs server or install their own. Once the peers find each other, a true p2p connection is established between the
peers and there is no further flow of information to the p2p discovery server. peers and there is no further flow of information to the p2p discovery server.
@ -107,19 +117,19 @@ certificate.
Security model Security model
-------------- --------------
On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) using an identity On top of webRTC, *Copay* peers authenticate as part of the "wallet ring"(WR) using an identity
key and a network key. key and a network key.
The *identity key* is a ECDSA public key derived from the participant's extended public The *identity key* is a ECDSA public key derived from the participant's extended public
key using a specific BIP32 branch. This special public key is never used for Bitcoin address creation, and key using a specific BIP32 branch. This special public key is never used for Bitcoin address creation, and
should only be known by members of the WR. should only be known by members of the WR.
In *Copay* this special public key is named *copayerId*. The copayerId is hashed and the hash is used to In *Copay* this special public key is named *copayerId*. The copayerId is hashed and the hash is used to
register with the peerjs server. Registering with a hash avoids disclosing the copayerId to parties outside of the WR. register with the peerjs server. Registering with a hash avoids disclosing the copayerId to parties outside of the WR.
Peer discovery is accomplished using only the hashes of the WR members' copayerIds. All members of the WR Peer discovery is accomplished using only the hashes of the WR members' copayerIds. All members of the WR
know the full copayerIds of all the other members of the WR. know the full copayerIds of all the other members of the WR.
The *network key* is a random key generated and distributed among the wallet members during wallet creation. The *network key* is a random key generated and distributed among the wallet members during wallet creation.
The network key is stored by each peer in the wallet configuration. The network key is used in establishing a CCM/AES The network key is stored by each peer in the wallet configuration. The network key is used in establishing a CCM/AES
authenticated encrypted channel between all members of the WR, on top of webRTC. Use of this authenticated encrypted channel between all members of the WR, on top of webRTC. Use of this
*network key* prevents man-in-the-middle attacks from a compromised peerjs server. *network key* prevents man-in-the-middle attacks from a compromised peerjs server.
@ -137,16 +147,6 @@ The string is encoded using Bitcoin's Base58Check encoding, to prevent transmiss
Peer Authentication Peer Authentication
------------------- -------------------
It is important to note that - except for private keys - *all data* in the wallet is shared with *all members of the wallet*. It is important to note that - except for private keys - *all data* in the wallet is shared with *all members of the wallet*.
Private keys are never shared with anyone and are never sent over the network. There are no *private* messages between Private keys are never shared with anyone and are never sent over the network. There are no *private* messages between
individual members of the wallet. All members of a wallet see everything that happens in that wallet. individual members of the wallet. All members of a wallet see everything that happens in that wallet.

21
app.js
View File

@ -1,12 +1,15 @@
var express=require("express"); var express = require('express');
var http=require("http"); var http = require('http');
var app = express();
var app=express(); app.start = function(port, callback) {
var port = process.env.PORT || 3000; app.set('port', port);
app.set("port", port); app.use(express.static(__dirname));
app.use(express.static(__dirname));
app.listen(port, function(){ app.listen(port, function() {
console.log("Listening at: http://localhost:" + port); callback('http://localhost:' + port);
}); });
};
module.exports = app;

View File

@ -14,7 +14,7 @@
<body ng-cloak class="ng-cloak"> <body ng-cloak class="ng-cloak">
<div id="wrap"> <div id="wrap">
<div data-ng-init="init()" data-ng-controller="HeaderController"> <div data-ng-init="init()" data-ng-controller="HeaderController">
<div class="header"> <div class="header">
<div class="header-content"> <div class="header-content">
<div class="large-3 medium-3 small-3 columns"> <div class="large-3 medium-3 small-3 columns">
<span class="logo"></span> <span class="logo"></span>
@ -23,15 +23,15 @@
<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="{{$root.wallet.id}}"> <a href="#/addresses" class="has-tip" tooltip-placement="bottom" tooltip="{{$root.wallet.id}}">
<strong><span>{{$root.wallet.getName()}}</span></strong> <strong><span>{{$root.wallet.getName()}}</span></strong>
</a> </a>
<a class="button radius small-icon" title="Manual Refresh" <a class="button radius small-icon" title="Manual Refresh"
ng-disabled="$root.loading" ng-disabled="$root.loading"
ng-click="refresh()"><i class="fi-refresh"></i></a> ng-click="refresh()"><i class="fi-refresh"></i></a>
<a class="button radius small-icon" title="Signout" <a class="button radius small-icon" title="Signout"
ng-click="signout()"><i class="fi-power"></i></a> ng-click="signout()"><i class="fi-power"></i></a>
</div> </div>
<div class="large-4 medium-4 columns line-dashed-v"> <div class="large-4 medium-4 columns line-dashed-v">
Balance: Balance:
<span ng-if="$root.updatingBalance"> <span ng-if="$root.updatingBalance">
<i class="fi-bitcoin-circle icon-rotate spinner"></i> <i class="fi-bitcoin-circle icon-rotate spinner"></i>
</span> </span>
@ -48,7 +48,7 @@
<i class="fi-bitcoin"></i> <i class="fi-bitcoin"></i>
</span> </span>
</div> </div>
</div> </div>
</div> </div>
@ -63,14 +63,14 @@
<section class="top-bar-section {{isCollapsed && 'hide_menu' || 'show_menu'}}"> <section class="top-bar-section {{isCollapsed && 'hide_menu' || 'show_menu'}}">
<ul> <ul>
<li data-ng-repeat="item in menu" ui-route="/{{item.link}}" class="text-center" data-ng-class="{active: isActive(item)}"> <li data-ng-repeat="item in menu" ui-route="/{{item.link}}" class="text-center" data-ng-class="{active: isActive(item)}">
<a href="{{item.link}}" ng-click="toggleCollapse()"> <i class="{{item.icon}}"></i> {{item.title}} <a href="{{item.link}}" ng-click="toggleCollapse()"> <i class="{{item.icon}}"></i> {{item.title}}
<span class="label alert round" ng-if="item.link=='#/transactions' && $root.pendingTxCount > 0">{{$root.pendingTxCount}}</span> <span class="label alert round" ng-if="item.link=='#/transactions' && $root.pendingTxCount > 0">{{$root.pendingTxCount}}</span>
</a> </a>
</li> </li>
</ul> </ul>
</section> </section>
</nav> </nav>
</div> </div>
<div class="row" ng-if="updateVersion"> <div class="row" ng-if="updateVersion">
@ -95,19 +95,19 @@
<div ng-if='$root.wallet && !$root.wallet.publicKeyRing.isComplete() && !loading'> <div ng-if='$root.wallet && !$root.wallet.publicKeyRing.isComplete() && !loading'>
<div class="medium-12 small-12 columns"> <div class="medium-12 small-12 columns">
<div class="alert-box secondary radius" data-alert> <div class="alert-box secondary radius" 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">
{{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have {{$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers() }} people have
</span> </span>
<span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()==1"> <span ng-show="$root.wallet.publicKeyRing.totalCopayers - $root.wallet.publicKeyRing.registeredCopayers()==1">
One person has One person has
</span> </span>
yet to join. yet to join.
</div> </div>
</div> </div>
<div class="medium-12 small-12 columns "> <div class="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
<small> for them to join your wallet</small> <small> for them to join your wallet</small>
@ -141,7 +141,7 @@
<link rel="stylesheet" ng-href="{{theme}}"> <link rel="stylesheet" ng-href="{{theme}}">
<div class="row" ng-show="!$root.wallet"> <div class="row" ng-show="!$root.wallet">
<div class="large-12 columns text-right"> <div class="large-12 columns text-right">
Copay Copay
<small>v{{version}}</small> <small>v{{version}}</small>
</div> </div>
</div> </div>
@ -159,18 +159,18 @@
</div> </div>
<div class="large-9 medium-9 small-9 columns"> <div class="large-9 medium-9 small-9 columns">
<a href="#/addresses" > </a> <a href="#/addresses" > </a>
<div class="bottom-copay" <div class="bottom-copay"
ng-repeat="c in $root.wallet.getRegisteredPeerIds()" class="has-tip" tooltip-popup-delay="1000" tooltip-placement="top" tooltip="{{c.nick}}"> ng-repeat="c in $root.wallet.getRegisteredPeerIds()" class="has-tip" tooltip-popup-delay="1000" tooltip-placement="top" tooltip="{{c.nick}}">
<video ng-if="$root.videoInfo[c.peerId]" <video ng-if="$root.videoInfo[c.peerId]"
avatar peer="{{c}}" avatar peer="{{c}}"
autoplay autoplay
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'" ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
ng-src="{{getVideoURL(c.peerId)}}" ng-src="{{getVideoURL(c.peerId)}}"
></video> ></video>
<img ng-if="!$root.videoInfo[c.peerId]" <img ng-if="!$root.videoInfo[c.peerId]"
avatar peer="{{c}}" avatar peer="{{c}}"
ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'" ng-class="($root.wallet.getOnlinePeerIDs().indexOf(c.peerId) != -1) ? 'online' : 'offline'"
src="./img/satoshi.gif" src="./img/satoshi.gif"
/> />
</div> </div>
</div> </div>
@ -182,7 +182,7 @@
<div class="signin" ng-controller="SigninController"> <div class="signin" ng-controller="SigninController">
<div data-alert class="alert-box info radius" ng-show="loading && !failure"> <div data-alert class="alert-box info radius" ng-show="loading && !failure">
<i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i> <i class="size-21 fi-bitcoin-circle icon-rotate spinner"></i>
Authenticating and Looking for peers... Authenticating and looking for peers...
</div> </div>
<div class="alert-box error radius" ng-show="failure"> <div class="alert-box error radius" ng-show="failure">
Oops, we had an error! Looks like you are already connected to this wallet, Oops, we had an error! Looks like you are already connected to this wallet,
@ -235,7 +235,7 @@
</div> <!-- End !loading --> </div> <!-- End !loading -->
</div> </div>
</script> </script>
<script type="text/ng-template" id="import.html"> <script type="text/ng-template" id="import.html">
<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">
@ -347,7 +347,7 @@
</div> </div>
</div> </div>
</script> </script>
<!-- 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">
@ -355,11 +355,11 @@
<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">
<a class="panel radius db" ng-repeat="addr in addresses | limitAddress:showAll" <a class="panel radius db" ng-repeat="addr in addresses | limitAddress:showAll"
ng-click="selectAddress(addr)" ng-click="selectAddress(addr)"
ng-class="{selected : addr.address == selectedAddr.address}"> ng-class="{selected : addr.address == selectedAddr.address}">
<span>{{addr.address}}</span> <span>{{addr.address}}</span>
<small ng-if="addr.isChange">change</small> <small ng-if="addr.isChange">change</small>
<span class="right"> <span class="right">
@ -379,7 +379,7 @@
</span> </span>
</a> </a>
<a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|withoutFunds) > 1"> <a class="secondary radius" ng-click="showAll=!showAll" ng-show="(addresses|withoutFunds) > 1">
<span ng-if="!showAll">Show all</span> <span ng-if="!showAll">Show all</span>
<span ng-if="showAll">Show less</span> <span ng-if="showAll">Show less</span>
</a> </a>
@ -409,9 +409,9 @@
<button class="secondary radius expandi new-address" ng-click="newAddr()" <button class="secondary radius expandi new-address" ng-click="newAddr()"
ng-disabled="loading" loading="Creating"> Create </button> ng-disabled="loading" loading="Creating"> Create </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</script> </script>
<!-- TRANSACTIONS --> <!-- TRANSACTIONS -->
@ -439,7 +439,7 @@
</div> </div>
</div> </div>
<div class="tx-copayers"> <div class="tx-copayers">
<div class="box-copayers" ng-repeat="(cId, actions) in tx.peerActions"> <div class="box-copayers" ng-repeat="(cId, actions) in tx.peerActions">
<figure class="left"> <figure class="left">
@ -467,17 +467,17 @@
<div class="text-center" style="margin-right:16px; color:#999; font-size:12px"> <div class="text-center" style="margin-right:16px; color:#999; font-size:12px">
{{$root.wallet.publicKeyRing.nicknameForCopayer(cId)}} {{$root.wallet.publicKeyRing.nicknameForCopayer(cId)}}
</div> </div>
</div> </div>
</div> </div>
<div class="row m10"> <div class="row m10">
<div class="large-5 medium-5 columns" ng-show="!tx.sentTs"> <div class="large-5 medium-5 columns" ng-show="!tx.sentTs">
<div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures"> <div ng-show="!tx.signedByUs && !tx.rejectedByUs && !tx.finallyRejected && tx.missingSignatures">
<button class="secondary radius m10r" ng-click="sign(tx.ntxid)" ng-disabled="loading" loading="Signing"> <button class="secondary radius m10r" ng-click="sign(tx.ntxid)" ng-disabled="loading" loading="Signing">
<i class="fi-check"></i> Sign <i class="fi-check"></i> Sign
</button> </button>
<button class="warning radius" ng-click="reject(tx.ntxid)" ng-disabled="loading" loading="Rejecting"> <button class="warning radius" ng-click="reject(tx.ntxid)" ng-disabled="loading" loading="Rejecting">
<i class="fi-x" ></i> Reject <i class="fi-x" ></i> Reject
</button> </button>
</div> </div>
<div ng-show="!tx.missingSignatures && !tx.sentTs"> <div ng-show="!tx.missingSignatures && !tx.sentTs">
@ -486,7 +486,7 @@
</button> </button>
</div> </div>
</div> </div>
<div class="large-7 medium-7 columns text-right"> <div class="large-7 medium-7 columns text-right">
<div ng-show="tx.finallyRejected" class="text-warning m10b"> <div ng-show="tx.finallyRejected" class="text-warning m10b">
Transaction finally rejected Transaction finally rejected
@ -496,20 +496,20 @@
<strong>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span> <strong>Sent</strong> <span class="text-gray" am-time-ago="tx.sentTs"></span>
</div> </div>
<div class="ellipsis small"> <div class="ellipsis small">
Transaction ID: Transaction ID:
<a href="http://{{getShortNetworkName()}}.insight.is/tx/{{tx.sentTxid}}" target="blank"> <a href="http://{{getShortNetworkName()}}.insight.is/tx/{{tx.sentTxid}}" target="blank">
{{tx.sentTxid}} {{tx.sentTxid}}
</a> </a>
</div> </div>
</div> </div>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1"> <p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures==1">
One signature missing One signature missing
</p> </p>
<p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1"> <p class="text-gray m5b" ng-show="!tx.finallyRejected && tx.missingSignatures>1">
{{tx.missingSignatures}} signatures missing</p> {{tx.missingSignatures}} signatures missing</p>
<div class="ellipsis small text-gray"> <div class="ellipsis small text-gray">
<strong>Fee:</strong> <i class="fi-bitcoin"></i> {{tx.fee}} <strong>Fee:</strong> <i class="fi-bitcoin"></i> {{tx.fee}}
<strong>Proposal ID:</strong> {{tx.ntxid}} <strong>Proposal ID:</strong> {{tx.ntxid}}
</div> </div>
</div> </div>
</div> </div>
@ -572,7 +572,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</script> </script>
@ -626,11 +626,11 @@
<div class="row"> <div class="row">
<div class="large-6 medium-6 columns"> <div class="large-6 medium-6 columns">
<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>
<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"> <small class="has-error" ng-show="sendForm.amount.$invalid && !sendForm.amount.$pristine">
not valid.</small> not valid.</small>
<small ng-show="notEnoughAmount">{{notEnoughAmount}}</small> <small ng-show="notEnoughAmount">{{notEnoughAmount}}</small>
</label> </label>
<div class="small-9 columns"> <div class="small-9 columns">
@ -648,7 +648,7 @@
<div class="large-5 columns"> <div class="large-5 columns">
<button type="submit" class="button secondary radius text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending"> <button type="submit" class="button secondary radius text-center" ng-disabled="sendForm.$invalid || loading" loading="Sending">
Send Send
</button> </button>
</div> </div>
</div> </div>
</form> </form>
@ -732,10 +732,10 @@
<script type="text/ng-template" id="unsupported.html"> <script type="text/ng-template" id="unsupported.html">
<h2 class="text-center">Browser unsupported</h2> <h2 class="text-center">Browser unsupported</h2>
<h3 class="text-center"> <h3 class="text-center">
Copay uses webRTC for peer-to-peer communications, Copay uses webRTC for peer-to-peer communications,
but your browser does not support it. but your browser does not support it.
Please use Please use
a current version of Google Chrome, Mozilla Firefox, or Opera. a current version of Google Chrome, Mozilla Firefox, or Opera.
<br><br> <br><br>
For more information For more information
@ -753,7 +753,7 @@ on supported browsers please check <a href="http://www.webrtc.org/">http://www.w
<script src="config.js"></script> <script src="config.js"></script>
<script src="js/shell.js"></script>
<script src="lib/angular/angular.min.js"></script> <script src="lib/angular/angular.min.js"></script>
<script src="lib/moment/moment.js"></script> <script src="lib/moment/moment.js"></script>
<script src="lib/angular-moment/angular-moment.js"></script> <script src="lib/angular-moment/angular-moment.js"></script>

View File

@ -59,4 +59,3 @@ angular.module('copay.video', []);
angular.module('copay.import', []); angular.module('copay.import', []);
angular.module('copay.passphrase', []); angular.module('copay.passphrase', []);
angular.module('copay.settings', []); angular.module('copay.settings', []);

View File

@ -6,7 +6,15 @@ angular.module('copay.import').controller('ImportController',
var reader = new FileReader(); var reader = new FileReader();
var _importBackup = function(encryptedObj) { var _importBackup = function(encryptedObj) {
Passphrase.getBase64Async($scope.password, function(passphrase){ Passphrase.getBase64Async($scope.password, function(passphrase){
$rootScope.wallet = walletFactory.fromEncryptedObj(encryptedObj, passphrase); var w = walletFactory.fromEncryptedObj(encryptedObj, passphrase);
if (!w) {
$scope.loading = false;
$rootScope.$flashMessage = { message: 'Wrong password', type: 'error'};
$rootScope.$digest();
return;
}
$rootScope.wallet = w;
controllerUtils.startNetwork($rootScope.wallet); controllerUtils.startNetwork($rootScope.wallet);
}); });
}; };
@ -33,6 +41,7 @@ angular.module('copay.import').controller('ImportController',
var password = form.password.$modelValue; var password = form.password.$modelValue;
if (!backupFile && !backupText) { if (!backupFile && !backupText) {
$scope.loading = false;
$rootScope.$flashMessage = { message: 'Please, select your backup file or paste the text', type: 'error'}; $rootScope.$flashMessage = { message: 'Please, select your backup file or paste the text', type: 'error'};
$scope.loading = false; $scope.loading = false;
return; return;

View File

@ -23,17 +23,15 @@ angular.module('copay.signin').controller('SigninController',
console.log('## Obtaining passphrase...'); console.log('## Obtaining passphrase...');
Passphrase.getBase64Async(password, function(passphrase){ Passphrase.getBase64Async(password, function(passphrase){
console.log('## Done.'); console.log('## Passphrase obtained');
var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase}); var w = walletFactory.open($scope.selectedWalletId, { passphrase: passphrase});
if (!w) { if (!w) {
$scope.loading = $scope.failure = false; $scope.loading = $scope.failure = false;
$rootScope.$flashMessage = { message: 'Bad password or connection error', type: 'error'}; $rootScope.$flashMessage = { message: 'Wrong password', type: 'error'};
$rootScope.$digest(); $rootScope.$digest();
return; return;
} }
console.log('[signin.js.49]'); //TODO
installStartupHandlers(w); installStartupHandlers(w);
console.log('[signin.js.52]'); //TODO
controllerUtils.startNetwork(w); controllerUtils.startNetwork(w);
}); });
}; };

View File

@ -63,9 +63,7 @@ Wallet.prototype.seedCopayer = function(pubKey) {
Wallet.prototype.connectToAll = function() { Wallet.prototype.connectToAll = function() {
console.log('[Wallet.js.57]'); //TODO
var all = this.publicKeyRing.getAllCopayerIds(); var all = this.publicKeyRing.getAllCopayerIds();
console.log('[Wallet.js.58] connecting'); //TODO console.log('[Wallet.js.58] connecting'); //TODO
this.network.connectToCopayers(all); this.network.connectToCopayers(all);
if (this.seededCopayerId) { if (this.seededCopayerId) {
@ -224,8 +222,8 @@ Wallet.prototype.netStart = function() {
self.log('[Wallet.js.132:openError:] GOT openError'); //TODO self.log('[Wallet.js.132:openError:] GOT openError'); //TODO
self.emit('openError'); self.emit('openError');
}); });
net.on('error', function(){ net.on('error', function() {
self.emit('connectionError'); // Bubble the error self.emit('connectionError');
}); });
net.on('close', function() { net.on('close', function() {
self.emit('close'); self.emit('close');

View File

@ -39,14 +39,11 @@ WalletFactory.prototype.log = function(){
WalletFactory.prototype._checkRead = function(walletId) { WalletFactory.prototype._checkRead = function(walletId) {
var s = this.storage; var s = this.storage;
var ret = var ret =
(
s.get(walletId, 'publicKeyRing') && s.get(walletId, 'publicKeyRing') &&
s.get(walletId, 'txProposals') && s.get(walletId, 'txProposals') &&
s.get(walletId, 'opts') && s.get(walletId, 'opts') &&
s.get(walletId, 'privateKey') s.get(walletId, 'privateKey');
)?true:false; return !!ret;
;
return ret?true:false;
}; };
WalletFactory.prototype.fromObj = function(obj) { WalletFactory.prototype.fromObj = function(obj) {
@ -60,8 +57,9 @@ WalletFactory.prototype.fromObj = function(obj) {
WalletFactory.prototype.fromEncryptedObj = function(base64, password) { WalletFactory.prototype.fromEncryptedObj = function(base64, password) {
this.storage._setPassphrase(password); this.storage._setPassphrase(password);
var walletObj = this.storage.import(base64); var walletObj = this.storage.import(base64);
var w= this.fromObj(walletObj); if (!walletObj) return false;
w.store(); var w = this.fromObj(walletObj);
if (!w) return false;
return w; return w;
}; };
@ -149,7 +147,6 @@ WalletFactory.prototype.open = function(walletId, opts) {
var w = this.read(walletId); var w = this.read(walletId);
if (w) { if (w) {
this._checkVersion(w.version); this._checkVersion(w.version);
w.store(); w.store();

View File

@ -269,7 +269,6 @@ Network.prototype._setupPeerHandlers = function(openCallback) {
self._checkAnyPeer(); self._checkAnyPeer();
}); });
p.on('connection', function(dataConn) { p.on('connection', function(dataConn) {
console.log('### NEW INBOUND CONNECTION %d/%d', self.connectedPeers.length, self.maxPeers); console.log('### NEW INBOUND CONNECTION %d/%d', self.connectedPeers.length, self.maxPeers);
if (self.connectedPeers.length >= self.maxPeers) { if (self.connectedPeers.length >= self.maxPeers) {

View File

@ -3,6 +3,7 @@
var imports = require('soop').imports(); var imports = require('soop').imports();
var id = 0; var id = 0;
function Storage(opts) { function Storage(opts) {
opts = opts || {}; opts = opts || {};
@ -33,12 +34,16 @@ Storage.prototype._encryptObj = function(obj) {
}; };
Storage.prototype._decrypt = function(base64) { Storage.prototype._decrypt = function(base64) {
var decryptedStr=null; var decryptedStr = null;
var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase()); try {
var decrypted = CryptoJS.AES.decrypt(base64, this._getPassphrase());
if (decrypted)
decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
if (decrypted)
decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
} catch (e) {
console.log('Error while decrypting ' + base64);
return null;
}
return decryptedStr; return decryptedStr;
}; };
@ -49,22 +54,16 @@ Storage.prototype._decryptObj = function(base64) {
Storage.prototype._read = function(k) { Storage.prototype._read = function(k) {
var ret; var ret;
try { ret = localStorage.getItem(k);
ret = localStorage.getItem(k); if (!ret) return null;
if (ret){ ret = this._decrypt(ret);
ret = this._decrypt(ret); if (!ret) return null;
ret = ret.toString(CryptoJS.enc.Utf8); ret = ret.toString(CryptoJS.enc.Utf8);
ret = JSON.parse(ret); ret = JSON.parse(ret);
}
} catch (e) {
console.log('Error while decrypting: '+e);
return null;
};
return ret; return ret;
}; };
Storage.prototype._write = function(k,v) { Storage.prototype._write = function(k, v) {
v = JSON.stringify(v); v = JSON.stringify(v);
v = this._encrypt(v); v = this._encrypt(v);
@ -78,7 +77,7 @@ Storage.prototype.getGlobal = function(k) {
}; };
// set value for key // set value for key
Storage.prototype.setGlobal = function(k,v) { Storage.prototype.setGlobal = function(k, v) {
localStorage.setItem(k, JSON.stringify(v)); localStorage.setItem(k, JSON.stringify(v));
}; };
@ -92,46 +91,45 @@ Storage.prototype._key = function(walletId, k) {
}; };
// get value by key // get value by key
Storage.prototype.get = function(walletId, k) { Storage.prototype.get = function(walletId, k) {
var ret = this._read(this._key(walletId,k)); var ret = this._read(this._key(walletId, k));
return ret; return ret;
}; };
// set value for key // set value for key
Storage.prototype.set = function(walletId, k,v) { Storage.prototype.set = function(walletId, k, v) {
this._write(this._key(walletId,k), v); this._write(this._key(walletId, k), v);
}; };
// remove value for key // remove value for key
Storage.prototype.remove = function(walletId, k) { Storage.prototype.remove = function(walletId, k) {
this.removeGlobal(this._key(walletId,k)); this.removeGlobal(this._key(walletId, k));
}; };
Storage.prototype.setName = function(walletId, name) { Storage.prototype.setName = function(walletId, name) {
this.setGlobal('nameFor::'+walletId, name); this.setGlobal('nameFor::' + walletId, name);
}; };
Storage.prototype.getName = function(walletId) { Storage.prototype.getName = function(walletId) {
return this.getGlobal('nameFor::'+walletId); return this.getGlobal('nameFor::' + walletId);
}; };
Storage.prototype.getWalletIds = function() { Storage.prototype.getWalletIds = function() {
var walletIds = []; var walletIds = [];
var uniq = {}; var uniq = {};
for (var i = 0; i < localStorage.length; i++) { for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i); var key = localStorage.key(i);
var split = key.split('::'); var split = key.split('::');
if (split.length == 2) { if (split.length == 2) {
var walletId = split[0]; var walletId = split[0];
if (walletId === 'nameFor') continue; if (walletId === 'nameFor') continue;
if (typeof uniq[walletId] === 'undefined' ) { if (typeof uniq[walletId] === 'undefined') {
walletIds.push(walletId); walletIds.push(walletId);
uniq[walletId] = 1; uniq[walletId] = 1;
} }
} }
} }
return walletIds; return walletIds;
}; };
@ -140,9 +138,9 @@ Storage.prototype.getWallets = function() {
var uniq = {}; var uniq = {};
var ids = this.getWalletIds(); var ids = this.getWalletIds();
for (var i in ids){ for (var i in ids) {
wallets.push({ wallets.push({
id:ids[i], id: ids[i],
name: this.getName(ids[i]), name: this.getName(ids[i]),
}); });
} }

View File

@ -62,35 +62,29 @@ angular.module('copay.controllerUtils')
$rootScope.wallet = w; $rootScope.wallet = w;
$location.path('addresses'); $location.path('addresses');
video.setOwnPeer(myPeerID, w, handlePeerVideo); video.setOwnPeer(myPeerID, w, handlePeerVideo);
console.log('# Done ready handler');
}); });
w.on('publicKeyRingUpdated', function(dontDigest) { w.on('publicKeyRingUpdated', function(dontDigest) {
console.log('[start publicKeyRing handler]'); //TODO
root.setSocketHandlers(); root.setSocketHandlers();
root.updateAddressList(); root.updateAddressList();
if (!dontDigest) { if (!dontDigest) {
console.log('[pkr digest]');
$rootScope.$digest(); $rootScope.$digest();
console.log('[done digest]');
} }
}); });
w.on('txProposalsUpdated', function(dontDigest) { w.on('txProposalsUpdated', function(dontDigest) {
root.updateTxs({onlyPending:true}); root.updateTxs({onlyPending:true});
root.updateBalance(function(){ root.updateBalance(function(){
if (!dontDigest) { if (!dontDigest) {
console.log('[txp digest]');
$rootScope.$digest(); $rootScope.$digest();
console.log('[done digest]');
} }
}); });
}); });
w.on('openError', root.onErrorDigest); w.on('openError', root.onErrorDigest);
w.on('connectionError', root.onErrorDigest);
w.on('connect', function(peerID) { w.on('connect', function(peerID) {
if (peerID) { if (peerID) {
video.callPeer(peerID, handlePeerVideo); video.callPeer(peerID, handlePeerVideo);
} }
console.log('[digest]');
$rootScope.$digest(); $rootScope.$digest();
}); });
w.on('disconnect', function(peerID) { w.on('disconnect', function(peerID) {
@ -129,7 +123,6 @@ angular.module('copay.controllerUtils')
$rootScope.availableBalance = safeBalance; $rootScope.availableBalance = safeBalance;
root.updateAddressList(); root.updateAddressList();
$rootScope.updatingBalance = false; $rootScope.updatingBalance = false;
console.log('Done updating balance.'); //TODO
return cb?cb():null; return cb?cb():null;
}); });
}; };
@ -145,7 +138,6 @@ angular.module('copay.controllerUtils')
var inT = w.getTxProposals().sort(function(t1, t2) { return t1.createdTs < t2.createdTs }); var inT = w.getTxProposals().sort(function(t1, t2) { return t1.createdTs < t2.createdTs });
var txs = []; var txs = [];
console.log('[START LOOP]'); //TODO
inT.forEach(function(i, index){ inT.forEach(function(i, index){
if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) { if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) {
return txs.push(null); return txs.push(null);

60
js/shell.js Normal file
View File

@ -0,0 +1,60 @@
/*
** copay-shell integration
*/
(function() {
/*
** This is a monkey patch for when Copay is running from
** within Copay-Shell (atom-shell). Since the renderer (the frontend)
** receives context from Node.js, we get a `module.exports` contruct
** available to us. Because of this, some libs (specifically Moment.js)
** attempt to assume their CommonJS form and bind to this. This causes
** there to be no references in the window to these libs, so let's trick
** the renderer into thinking that we are _not_ in a CommonJS environment.
*/
if (typeof module !== 'undefined') module = { exports: null };
// are we running in copay shell?
if (process && process.type === 'renderer') initCopayShellBindings();
function controller(name) {
return angular.element(
document.querySelectorAll(
'[ng-controller="' + name + '"], [data-ng-controller="' + name + '"]'
)
).scope();
};
function initCopayShellBindings() {
var ipc = require('ipc');
ipc.on('address:create', function(data) {
location.href = '#/addresses';
controller('AddressesController').newAddr();
});
ipc.on('transactions:send', function(data) {
location.href = '#/send';
});
ipc.on('transactions:all', function(data) {
location.href = '#/transactions';
controller('TransactionsController').show();
});
ipc.on('transactions:pending', function(data) {
location.href = '#/transactions';
controller('TransactionsController').show(true);
});
ipc.on('backup:download', function(data) {
});
ipc.on('backup:email', function(data) {
});
};
})();

View File

@ -26,7 +26,6 @@ var DEFAULT_PORT = process.env.DEFAULT_PORT || 3000;
for (var i=0; i<N; i++) { for (var i=0; i<N; i++) {
var port =(i+DEFAULT_PORT); var port =(i+DEFAULT_PORT);
console.log('Simulating copayer #'+(i+1)+' at http://localhost:'+port); console.log('Simulating copayer #'+(i+1)+' at http://localhost:'+port);
var command = 'PORT='+port+' node app.js &' var command = 'PORT='+port+' npm start &'
exec(command, puts); exec(command, puts);
} }

View File

@ -17,7 +17,9 @@
"bugs": { "bugs": {
"url": "https://github.com/bitpay/copay/issues" "url": "https://github.com/bitpay/copay/issues"
}, },
"main": "app.js",
"scripts": { "scripts": {
"start": "node server.js",
"test": "mocha" "test": "mocha"
}, },
"homepage": "https://github.com/bitpay/copay", "homepage": "https://github.com/bitpay/copay",

6
server.js Normal file
View File

@ -0,0 +1,6 @@
var server = require('./app');
var port = process.env.PORT || 3000;
server.start(port, function(loc){
console.log('Listening at: ' + loc);
});