diff --git a/config.js b/config.js index 4da6227cc..5c658ea68 100644 --- a/config.js +++ b/config.js @@ -1,7 +1,7 @@ 'use strict'; var defaultConfig = { // DEFAULT network (livenet or testnet) - networkName: 'livenet', + networkName: 'testnet', forceNetwork: false, // DEFAULT unit: Bit @@ -15,71 +15,11 @@ var defaultConfig = { minAmountSatoshi: 5400, }, - // network layer (PeerJS) config + // network layer config network: { - // Use this to run your own local PeerJS server - // with params: ./peerjs -p 10009 -k '6d6d751ea61e26f2' - /* -key: '6d6d751ea61e26f2', -host: 'localhost', -port: 10009, -path: '/', -*/ - - // Use this to connect to bitpay's PeerJS server - key: 'satoshirocks', - // host: '162.242.219.26', - // port: 10000, - // secure: false, - host: 'live.copay.io', - port: 9000, - secure: true, - path: '/', - - // other PeerJS config - maxPeers: 15, - debug: 2, - - // PeerJS internal config object - config: { - 'iceServers': [ - // Pass in STUN and TURN servers for maximum network compatibility - { - url: 'stun:162.242.219.26' - }, { - url: 'turn:162.242.219.26', - username: 'bitcore', - credential: 'bitcore', - } - // { - // 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' - // } - // // Options fot TURN servers with p2p communications are not possible. - // { - // 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' - // } - ] - } + host: 'test-insight.bitpay.com', + port: 443, + schema: 'https' }, // wallet default config @@ -96,14 +36,14 @@ path: '/', // blockchain service API config blockchain: { schema: 'https', - host: 'insight.bitpay.com', + host: 'test-insight.bitpay.com', port: 443, retryDelay: 1000, }, // socket service API config socket: { schema: 'https', - host: 'insight.bitpay.com', + host: 'test-insight.bitpay.com', port: 443, reconnectDelay: 1000, }, diff --git a/copay.js b/copay.js index d9c855cce..35178e9a3 100644 --- a/copay.js +++ b/copay.js @@ -9,7 +9,7 @@ module.exports.HDParams = require('./js/models/core/HDParams'); // components -var WebRTC = module.exports.WebRTC = require('./js/models/network/WebRTC'); +var Async = module.exports.Async = require('./js/models/network/Async'); var Insight = module.exports.Insight = require('./js/models/blockchain/Insight'); var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('./js/models/storage/LocalEncrypted'); diff --git a/js/controllers/settings.js b/js/controllers/settings.js index 6ff26a035..c8761aa17 100644 --- a/js/controllers/settings.js +++ b/js/controllers/settings.js @@ -52,10 +52,9 @@ angular.module('copayApp.controllers').controller('SettingsController', function $scope.save = function() { var network = config.network; - network.key = $scope.networkKey; - network.host = $scope.networkHost; - network.port = $scope.networkPort; - network.secure = $scope.networkSecure; + network.host = $scope.insightHost; + network.port = $scope.insightPort; + network.schema = $scope.insightSecure ? 'https' : 'http'; localStorage.setItem('config', JSON.stringify({ networkName: $scope.networkName, diff --git a/js/models/blockchain/Insight.js b/js/models/blockchain/Insight.js index b0a0b7654..610be2ebe 100644 --- a/js/models/blockchain/Insight.js +++ b/js/models/blockchain/Insight.js @@ -256,7 +256,7 @@ Insight.prototype._requestBrowser = function(options, callback) { errTxt = 'CRITICAL: Wrong response from insight' + e2; } } else if (request.status >= 400 && request.status < 499) { - errTxt = 'CRITICAL: Bad request to insight. Probably wrong transaction to broadcast?.'; + errTxt = 'CRITICAL: Bad request to insight: '+request.status; } else { errTxt = 'Error code: ' + request.status + ' - Status: ' + request.statusText + ' - Description: ' + request.responseText; setTimeout(function() { diff --git a/js/models/core/Message.js b/js/models/core/Message.js deleted file mode 100644 index ad6324440..000000000 --- a/js/models/core/Message.js +++ /dev/null @@ -1,148 +0,0 @@ -'use strict'; - -var bitcore = require('bitcore'); - -/* Encrypted, authenticated messages to be shared between copayers */ -var Message = function() { -}; - -Message.encode = function(topubkey, fromkey, payload, opts) { - var version1 = new Buffer([1]); //peers will reject messges containing not-understood version1 - //i.e., increment version1 to prevent communications with old clients - var version2 = new Buffer([0]); //peers will not reject messages containing not-understood version2 - //i.e., increment version2 to allow communication with old clients, but signal new clients - var nonce; - if (opts && opts.nonce && Buffer.isBuffer(opts.nonce) && opts.nonce.length == 8) { - nonce = opts.nonce; - } else { - nonce = new Buffer(8); - nonce.fill(0); //nonce is a big endian 8 byte number - } - - var toencrypt = Buffer.concat([version1, version2, nonce, payload]); - var toencrypthexbuf = new Buffer(toencrypt.toString('hex')); //due to bug in sjcl/bitcore, must use hex string - var encrypted = Message._encrypt(topubkey, toencrypthexbuf); - 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, opts) { - var prevnonce; - if (opts && opts.prevnonce && Buffer.isBuffer(opts.prevnonce) && opts.prevnonce.length == 8) { - prevnonce = opts.prevnonce; - } else { - prevnonce = new Buffer(8); - prevnonce.fill(0); //nonce is a big endian 8 byte number - } - - 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 decryptedhexbuf = Message._decrypt(key.private, encrypted); - var decrypted = new Buffer(decryptedhexbuf.toString(), 'hex'); //workaround for bug in bitcore/sjcl - } catch (e) { - throw new Error('Cannot decrypt data: ' + e); - } - - try { - var version1 = decrypted[0]; - var version2 = decrypted[1]; - var nonce = decrypted.slice(2, 10); - var payload = decrypted.slice(10); - } catch (e) { - throw new Error('Cannot parse decrypted data: ' + e); - } - - if (payload.length === 0) { - throw new Error('No data present'); - } - - if (version1 !== 1) { - throw new Error('Invalid version number'); - } - - if (version2 !== 0) { - //put special version2 handling code here, if ever needed - } - - if (!Message._noncegt(nonce, prevnonce) && prevnonce.toString('hex') !== '0000000000000000') { - throw new Error('Nonce not equal to zero and not greater than the previous nonce'); - } - - var decoded = { - version1: version1, - version2: version2, - nonce: nonce, - payload: payload - }; - - return decoded; -}; - -//return true if nonce > prevnonce; false otherwise -Message._noncegt = function(nonce, prevnonce) { - var noncep1 = nonce.slice(0, 4).readUInt32BE(0); - var prevnoncep1 = prevnonce.slice(0, 4).readUInt32BE(0); - - if (noncep1 > prevnoncep1) - return true; - - if (noncep1 < prevnoncep1) - return false; - - var noncep2 = nonce.slice(4, 8).readUInt32BE(0); - var prevnoncep2 = prevnonce.slice(4, 8).readUInt32BE(0); - - if (noncep2 > prevnoncep2) - return true; - - return false; -}; - -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 = Message; diff --git a/js/models/core/PublicKeyRing.js b/js/models/core/PublicKeyRing.js index b86f18277..9a734e5ac 100644 --- a/js/models/core/PublicKeyRing.js +++ b/js/models/core/PublicKeyRing.js @@ -1,6 +1,5 @@ 'use strict'; - var preconditions = require('preconditions').instance(); var bitcore = require('bitcore'); var HK = bitcore.HierarchicalKey; diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js index f2bb28c6b..a85aadc43 100644 --- a/js/models/core/Wallet.js +++ b/js/models/core/Wallet.js @@ -33,20 +33,16 @@ function Wallet(opts) { 'publicKeyRing', 'txProposals', 'privateKey', 'version', 'reconnectDelay' ].forEach(function(k) { - if (typeof opts[k] === 'undefined') - throw new Error('missing required option for Wallet: ' + k); + preconditions.checkArgument(typeof opts[k] !== 'undefined', + 'missing required option for Wallet: ' + k); self[k] = opts[k]; }); - if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName) - throw new Error('Network forced to ' + copayConfig.networkName + - ' and tried to create a Wallet with network ' + this.getNetworkName()); - - this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet'); + preconditions.checkArgument(!copayConfig.forceNetwork || this.getNetworkName() === copayConfig.networkName, + 'Network forced to ' + copayConfig.networkName + + ' and tried to create a Wallet with network ' + this.getNetworkName()); this.id = opts.id || Wallet.getRandomId(); this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin); - - this.name = opts.name; this.verbose = opts.verbose; @@ -56,6 +52,7 @@ function Wallet(opts) { this.registeredPeerIds = []; this.addressBook = opts.addressBook || {}; this.publicKey = this.privateKey.publicHex; + this.lastTimestamp = opts.lastTimestamp || undefined; this.paymentRequests = opts.paymentRequests || {}; @@ -89,7 +86,10 @@ Wallet.prototype.seedCopayer = function(pubKey) { this.seededCopayerId = pubKey; }; +// not being used now Wallet.prototype.connectToAll = function() { + // not being used now + return; var all = this.publicKeyRing.getAllCopayerIds(); this.network.connectToCopayers(all); @@ -99,7 +99,7 @@ Wallet.prototype.connectToAll = function() { } }; -Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { +Wallet.prototype._onIndexes = function(senderId, data) { this.log('RECV INDEXES:', data); var inIndexes = HDParams.fromList(data.indexes); var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes); @@ -109,7 +109,7 @@ Wallet.prototype._handleIndexes = function(senderId, data, isInbound) { } }; -Wallet.prototype._handlePublicKeyRing = function(senderId, data, isInbound) { +Wallet.prototype._onPublicKeyRing = function(senderId, data) { this.log('RECV PUBLICKEYRING:', data); var inPKR = PublicKeyRing.fromObj(data.publicKeyRing); @@ -217,7 +217,7 @@ Wallet.prototype._checkSentTx = function(ntxid, cb) { }; -Wallet.prototype._handleTxProposal = function(senderId, data) { +Wallet.prototype._onTxProposal = function(senderId, data) { var self = this; this.log('RECV TXPROPOSAL: ', data); var m; @@ -254,7 +254,7 @@ Wallet.prototype._handleTxProposal = function(senderId, data) { }; -Wallet.prototype._handleReject = function(senderId, data, isInbound) { +Wallet.prototype._onReject = function(senderId, data) { preconditions.checkState(data.ntxid); this.log('RECV REJECT:', data); @@ -277,7 +277,7 @@ Wallet.prototype._handleReject = function(senderId, data, isInbound) { }); }; -Wallet.prototype._handleSeen = function(senderId, data, isInbound) { +Wallet.prototype._onSeen = function(senderId, data) { preconditions.checkState(data.ntxid); this.log('RECV SEEN:', data); @@ -295,7 +295,7 @@ Wallet.prototype._handleSeen = function(senderId, data, isInbound) { -Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) { +Wallet.prototype._onAddressBook = function(senderId, data) { preconditions.checkState(data.addressBook); this.log('RECV ADDRESSBOOK:', data); var rcv = data.addressBook; @@ -315,13 +315,25 @@ Wallet.prototype._handleAddressBook = function(senderId, data, isInbound) { } }; -Wallet.prototype._handleData = function(senderId, data, isInbound) { - // TODO check message signature +Wallet.prototype.updateTimestamp = function(ts) { + preconditions.checkArgument(ts); + preconditions.checkArgument(typeof ts === 'number'); + this.lastTimestamp = ts; + this.store(); +}; + +Wallet.prototype._onData = function(senderId, data, ts) { + preconditions.checkArgument(senderId); + preconditions.checkArgument(data); + preconditions.checkArgument(data.type); + preconditions.checkArgument(ts); + preconditions.checkArgument(typeof ts === 'number'); + + this.updateTimestamp(ts); if (data.type !== 'walletId' && this.id !== data.walletId) { - this.emit('badMessage', senderId); - this.log('badMessage FROM:', senderId); + this.emit('corrupt', senderId); return; } @@ -336,27 +348,31 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) { this.sendAllTxProposals(senderId); // send old txps break; case 'publicKeyRing': - this._handlePublicKeyRing(senderId, data, isInbound); + this._onPublicKeyRing(senderId, data); break; case 'reject': - this._handleReject(senderId, data, isInbound); + this._onReject(senderId, data); break; case 'seen': - this._handleSeen(senderId, data, isInbound); + this._onSeen(senderId, data); break; case 'txProposal': - this._handleTxProposal(senderId, data, isInbound); + this._onTxProposal(senderId, data); break; case 'indexes': - this._handleIndexes(senderId, data, isInbound); + this._onIndexes(senderId, data); break; case 'addressbook': - this._handleAddressBook(senderId, data, isInbound); + this._onAddressBook(senderId, data); + break; + case 'disconnect': + this._onDisconnect(senderId, data); break; } + }; -Wallet.prototype._handleConnect = function(newCopayerId) { +Wallet.prototype._onConnect = function(newCopayerId) { if (newCopayerId) { this.log('#### Setting new COPAYER:', newCopayerId); this.sendWalletId(newCopayerId); @@ -365,7 +381,7 @@ Wallet.prototype._handleConnect = function(newCopayerId) { this.emit('connect', peerID); }; -Wallet.prototype._handleDisconnect = function(peerID) { +Wallet.prototype._onDisconnect = function(peerID) { this.currentDelay = null; this.emit('disconnect', peerID); }; @@ -427,15 +443,8 @@ Wallet.prototype.netStart = function(callback) { var net = this.network; net.removeAllListeners(); - net.on('connect', self._handleConnect.bind(self)); - net.on('disconnect', self._handleDisconnect.bind(self)); - net.on('data', self._handleData.bind(self)); - net.on('close', function() { - self.emit('close'); - }); - net.on('serverError', function(msg) { - self.emit('serverError', msg); - }); + net.on('connect', self._onConnect.bind(self)); + net.on('data', self._onData.bind(self)); var myId = self.getMyCopayerId(); var myIdPriv = self.getMyCopayerIdPriv(); @@ -443,7 +452,8 @@ Wallet.prototype.netStart = function(callback) { var startOpts = { copayerId: myId, privkey: myIdPriv, - maxPeers: self.totalCopayers + maxPeers: self.totalCopayers, + lastTimestamp: this.lastTimestamp, }; if (this.publicKeyRing.isComplete()) { @@ -454,12 +464,15 @@ Wallet.prototype.netStart = function(callback) { self.emit('ready', net.getPeer()); setTimeout(function() { self.emit('publicKeyRingUpdated', true); - self.scheduleConnect(); + //self.scheduleConnect(); + // no connection logic for now self.emit('txProposalsUpdated'); }, 10); }); }; + +// not being used now Wallet.prototype.scheduleConnect = function() { var self = this; if (self.network.isOnline()) { @@ -533,6 +546,7 @@ Wallet.prototype.toObj = function() { txProposals: this.txProposals.toObj(), privateKey: this.privateKey ? this.privateKey.toObj() : undefined, addressBook: this.addressBook, + lastTimestamp: this.lastTimestamp, }; return walletObj; @@ -544,16 +558,17 @@ Wallet.fromObj = function(o, storage, network, blockchain) { opts.addressBook = o.addressBook; - if (o.privateKey) + if (o.privateKey) { opts.privateKey = PrivateKey.fromObj(o.privateKey); - else + } else { opts.privateKey = new PrivateKey({ networkName: opts.networkName }); + } - if (o.publicKeyRing) + if (o.publicKeyRing) { opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing); - else { + } else { opts.publicKeyRing = new PublicKeyRing({ networkName: opts.networkName, requiredCopayers: opts.requiredCopayers, @@ -565,12 +580,15 @@ Wallet.fromObj = function(o, storage, network, blockchain) { ); } - if (o.txProposals) + if (o.txProposals) { opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts); - else + } else { opts.txProposals = new TxProposals({ networkName: this.networkName, }); + } + + opts.lastTimestamp = o.lastTimestamp; opts.storage = storage; opts.network = network; @@ -627,7 +645,9 @@ Wallet.prototype.sendReject = function(ntxid) { }); }; + Wallet.prototype.sendWalletReady = function(recipients) { + preconditions.checkArgument(recipients); this.log('### SENDING WalletReady TO:', recipients); this.send(recipients, { @@ -707,7 +727,7 @@ Wallet.prototype.getTxProposals = function() { txp.finallyRejected = true; } - if (txp.readonly && !txp.finallyRejected && !txp.sentTs) {} else { + if (!txp.readonly || txp.finallyRejected || txp.sentTs) { ret.push(txp); } } @@ -1724,7 +1744,7 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) function _while() { return hasActivity; }, - function _finnaly(err) { + function _finally(err) { if (err) return cb(err); cb(null, lastActive); } @@ -1735,7 +1755,12 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb) Wallet.prototype.disconnect = function() { this.log('## DISCONNECTING'); this.lock.release(); - this.network.disconnect(); + var self = this; + self.send(null, { + type: 'disconnect', + walletId: this.id, + }); + self.network.cleanUp(); }; Wallet.prototype.getNetwork = function() { diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js index 909fba68f..3b4d9d77e 100644 --- a/js/models/core/WalletFactory.js +++ b/js/models/core/WalletFactory.js @@ -5,7 +5,7 @@ var PublicKeyRing = require('./PublicKeyRing'); var PrivateKey = require('./PrivateKey'); var Wallet = require('./Wallet'); -var WebRTC = module.exports.WebRTC = require('../network/WebRTC'); +var Async = module.exports.Async = require('../network/Async'); var Insight = module.exports.Insight = require('../blockchain/Insight'); var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../storage/LocalEncrypted'); @@ -19,7 +19,7 @@ function WalletFactory(config, version) { config = config || {}; this.Storage = config.Storage || StorageLocalEncrypted; - this.Network = config.Network || WebRTC; + this.Network = config.Network || Async; this.Blockchain = config.Blockchain || Insight; this.storage = new this.Storage(config.storage); @@ -104,6 +104,7 @@ WalletFactory.prototype.read = function(walletId, skipFields) { obj.privateKey = s.get(walletId, 'privateKey'); obj.addressBook = s.get(walletId, 'addressBook'); obj.backupOffered = s.get(walletId, 'backupOffered'); + obj.lastTimestamp = s.get(walletId, 'lastTimestamp'); var w = this.fromObj(obj, skipFields); return w; @@ -249,16 +250,13 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras self.network.on('connected', function(sender, data) { connectedOnce = true; }); - self.network.on('onlyYou', function(sender, data) { - return cb(connectedOnce ? 'walletFull' : 'joinError'); - }); self.network.on('serverError', function() { return cb('joinError'); }); self.network.start(opts, function() { - self.network.connectTo(s.pubKey); + self.network.greet(s.pubKey); self.network.on('data', function(sender, data) { if (data.type === 'walletId') { @@ -271,7 +269,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras data.opts.passphrase = passphrase; data.opts.id = data.walletId; var w = self.create(data.opts); - w.seedCopayer(s.pubKey); + w.sendWalletReady(s.pubKey); + //w.seedCopayer(s.pubKey); return cb(null, w); } }); diff --git a/js/models/network/Async.js b/js/models/network/Async.js new file mode 100644 index 000000000..e63b2ecfc --- /dev/null +++ b/js/models/network/Async.js @@ -0,0 +1,382 @@ +'use strict'; + +var EventEmitter = require('events').EventEmitter; +var bitcore = require('bitcore'); +var AuthMessage = bitcore.AuthMessage; +var util = bitcore.util; +var nodeUtil = require('util'); +var extend = nodeUtil._extend; +var io = require('socket.io-client'); +var preconditions = require('preconditions').singleton(); + +/* + * Emits + * 'connect' + * when network layout has change (new/lost peers, etc) + * + * 'data' + * when an unknown data type arrives + * + * Provides + * send(toPeerIds, {data}, cb?) + * + */ + +function Network(opts) { + var self = this; + opts = opts || {}; + this.maxPeers = opts.maxPeers || 12; + this.host = opts.host || 'localhost'; + this.port = opts.port || 3001; + this.schema = opts.schema || 'https'; + this.cleanUp(); +} + +nodeUtil.inherits(Network, EventEmitter); + +Network.prototype.cleanUp = function() { + this.started = false; + this.connectedPeers = []; + this.peerId = null; + this.privkey = null; + this.key = null; + this.copayerId = null; + this.allowedCopayerIds = null; + this.isInboundPeerAuth = []; + this.copayerForPeer = {}; + this.connections = {}; + this.criticalErr = ''; + this.removeAllListeners(); + if (this.socket) { + this.socket.disconnect(); + this.socket = null; + } +}; + +Network.parent = EventEmitter; + +// Array helpers +Network._inArray = function(el, array) { + return array.indexOf(el) > -1; +}; + +Network._arrayPushOnce = function(el, array) { + var ret = false; + if (!Network._inArray(el, array)) { + array.push(el); + ret = true; + } + return ret; +}; + +Network._arrayRemove = function(el, array) { + var pos = array.indexOf(el); + if (pos >= 0) array.splice(pos, 1); + return array; +}; + +Network.prototype.connectedCopayers = function() { + var ret = []; + for (var i in this.connectedPeers) { + var copayerId = this.copayerForPeer[this.connectedPeers[i]]; + if (copayerId) ret.push(copayerId); + } + return ret; +}; + +Network.prototype._sendHello = function(copayerId) { + this.send(copayerId, { + type: 'hello', + copayerId: this.copayerId, + }); +}; + +Network.prototype._deletePeer = function(peerId) { + delete this.isInboundPeerAuth[peerId]; + delete this.copayerForPeer[peerId]; + + if (this.connections[peerId]) { + this.connections[peerId].close(); + } + delete this.connections[peerId]; + this.connectedPeers = Network._arrayRemove(peerId, this.connectedPeers); +}; + +Network.prototype._addConnectedCopayer = function(copayerId) { + var peerId = this.peerFromCopayer(copayerId); + this._addCopayerMap(peerId, copayerId); + Network._arrayPushOnce(peerId, this.connectedPeers); + this.emit('connect', copayerId); +}; + +Network.prototype.getKey = function() { + preconditions.checkState(this.privkey || this.key); + if (!this.key) { + var key = new bitcore.Key(); + key.private = new Buffer(this.privkey, 'hex'); + key.regenerateSync(); + this.key = key; + } + return this.key; +}; + +//hex version of one's own nonce +Network.prototype.setHexNonce = function(networkNonce) { + if (networkNonce) { + if (networkNonce.length !== 16) + throw new Error('incorrect length of hex nonce'); + this.networkNonce = new Buffer(networkNonce, 'hex'); + } else + this.iterateNonce(); +}; + +//hex version of copayers' nonces +Network.prototype.setHexNonces = function(networkNonces) { + for (var i in networkNonces) { + if (!this.networkNonces) + this.networkNonces = {}; + if (networkNonces[i].length === 16) + this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); + } +}; + +//for oneself +Network.prototype.getHexNonce = function() { + return this.networkNonce.toString('hex'); +}; + +//for copayers +Network.prototype.getHexNonces = function() { + var networkNoncesHex = []; + for (var i in this.networkNonces) { + networkNoncesHex[i] = this.networkNonces[i].toString('hex'); + } + return networkNoncesHex; +}; + +Network.prototype.iterateNonce = function() { + if (!this.networkNonce || this.networkNonce.length !== 8) { + this.networkNonce = new Buffer(8); + this.networkNonce.fill(0); + } + //the first 4 bytes of a nonce is a unix timestamp in seconds + //the second 4 bytes is just an iterated "sub" nonce + //the whole thing is interpreted as one big endian number + var noncep1 = this.networkNonce.slice(0, 4); + noncep1.writeUInt32BE(Math.floor(Date.now() / 1000), 0); + var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); + var noncep2 = this.networkNonce.slice(4, 8); + noncep2.writeUInt32BE(noncep2uint + 1, 0); + return this.networkNonce; +}; + +Network.prototype.decode = function(enc) { + var sender = enc.pubkey; + var key = this.getKey(); + var prevnonce = this.networkNonces ? this.networkNonces[sender] : undefined; + var opts = { + prevnonce: prevnonce + }; + var decoded = AuthMessage.decode(key, enc, opts); + + //if no error thrown in the last step, we can set the copayer's nonce + if (!this.networkNonces) + this.networkNonces = {}; + this.networkNonces[sender] = decoded.nonce; + + var payload = decoded.payload; + return payload; +}; + +Network.prototype._onMessage = function(enc) { + var sender = enc.pubkey; + try { + var payload = this.decode(enc); + } catch (e) { + this._deletePeer(sender); + return; + } + + //console.log('receiving ' + JSON.stringify(payload)); + + var self = this; + switch (payload.type) { + case 'hello': + // if we locked allowed copayers, check if it belongs + if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) { + this._deletePeer(sender); + return; + } + //ensure claimed public key is actually the public key of the peer + //e.g., their public key should hash to be their peerId + if (sender !== payload.copayerId) { + this._deletePeer(enc.pubkey, 'incorrect pubkey for peerId'); + return; + } + this._addConnectedCopayer(payload.copayerId); + break; + default: + this.emit('data', sender, payload, enc.ts); + } +}; + +Network.prototype._setupConnectionHandlers = function(cb) { + preconditions.checkState(this.socket); + var self = this; + + self.socket.on('connect', function() { + self.socket.on('disconnect', function() { + self.cleanUp(); + }); + if (typeof cb === 'function') cb(); + }); + self.socket.on('message', function(m) { + // delay execution, to improve error handling + setTimeout(function() { + self._onMessage(m); + }, 1); + }); + self.socket.on('error', self._onError.bind(self)); + +}; + +Network.prototype._onError = function(err) { + console.log('RECV ERROR: ', err); + console.log(err.stack); + this.criticalError = err.message; +}; + +Network.prototype.greet = function(copayerId) { + this._sendHello(copayerId); + var peerId = this.peerFromCopayer(copayerId); + this._addCopayerMap(peerId, copayerId); +}; + +Network.prototype._addCopayerMap = function(peerId, copayerId) { + if (!this.copayerForPeer[peerId]) { + if (Object.keys(this.copayerForPeer).length < this.maxPeers) { + this.copayerForPeer[peerId] = copayerId; + } + } +}; + +Network.prototype._setInboundPeerAuth = function(peerId) { + this.isInboundPeerAuth[peerId] = true; +}; + +Network.prototype.setCopayerId = function(copayerId) { + preconditions.checkState(!this.started, 'network already started: can not change peerId'); + + this.copayerId = copayerId; + this.copayerIdBuf = new Buffer(copayerId, 'hex'); + this.peerId = this.peerFromCopayer(this.copayerId); + this._addCopayerMap(this.peerId, copayerId); +}; + + +// TODO cache this. +Network.prototype.peerFromCopayer = function(hex) { + var SIN = bitcore.SIN; + return new SIN(new Buffer(hex, 'hex')).toString(); +}; + +Network.prototype.start = function(opts, openCallback) { + preconditions.checkArgument(opts); + preconditions.checkArgument(opts.privkey); + preconditions.checkArgument(opts.copayerId); + + preconditions.checkState(this.connectedPeers && this.connectedPeers.length === 0); + + if (this.started) return openCallback(); + + this.privkey = opts.privkey; + var pubkey = this.getKey().public.toString('hex'); + this.setCopayerId(opts.copayerId); + this.maxPeers = opts.maxPeers || this.maxPeers; + + this.socket = this.createSocket(); + this._setupConnectionHandlers(openCallback); + this.socket.emit('subscribe', pubkey); + this.socket.emit('sync', opts.lastTimestamp); + this.started = true; + +}; + +Network.prototype.createSocket = function() { + var hostPort = this.schema + '://' + this.host + ':' + this.port; + return io.connect(hostPort, { + reconnection: true, + 'force new connection': true, + 'secure': this.schema === 'https', + }); +}; + +Network.prototype.getOnlinePeerIDs = function() { + return this.connectedPeers; +}; + +Network.prototype.getPeer = function() { + return this.peer; +}; + + +Network.prototype.getCopayerIds = function() { + if (this.allowedCopayerIds) { + return Object.keys(this.allowedCopayerIds); + } else { + var copayerIds = []; + for (var peerId in this.copayerForPeer) { + copayerIds.push(this.copayerForPeer[peerId]); + } + return copayerIds; + } +}; + + +Network.prototype.send = function(dest, payload, cb) { + preconditions.checkArgument(payload); + + var self = this; + if (!dest) { + dest = this.getCopayerIds(); + payload.isBroadcast = 1; + } + + if (typeof dest === 'string') + dest = [dest]; + + var l = dest.length; + var i = 0; + //console.log('sending ' + JSON.stringify(payload)); + dest.forEach(function(to) { + //console.log('\t to ' + to); + var message = self.encode(to, payload); + self.socket.emit('message', message); + }); + if (typeof cb === 'function') cb(); +}; + + +Network.prototype.encode = function(copayerId, payload, nonce) { + this.iterateNonce(); + var opts = { + nonce: nonce || this.networkNonce + }; + var copayerIdBuf = new Buffer(copayerId, 'hex'); + var message = AuthMessage.encode(copayerIdBuf, this.getKey(), payload, opts); + return message; +}; + +Network.prototype.isOnline = function() { + return !!this.socket; +}; + + +Network.prototype.lockIncommingConnections = function(allowedCopayerIdsArray) { + this.allowedCopayerIds = {}; + for (var i in allowedCopayerIdsArray) { + this.allowedCopayerIds[allowedCopayerIdsArray[i]] = true; + } +}; + +module.exports = Network; diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js deleted file mode 100644 index 256bc3ec7..000000000 --- a/js/models/network/WebRTC.js +++ /dev/null @@ -1,525 +0,0 @@ -'use strict'; - -var EventEmitter = require('events').EventEmitter; -var bitcore = require('bitcore'); -var util = bitcore.util; -var nodeUtil = require('util'); -var Message = require('../core/Message'); -/* - * Emits - * 'connect' - * when network layout has change (new/lost peers, etc) - * - * 'data' - * when an unknown data type arrives - * - * Provides - * send(toPeerIds, {data}, cb?) - * - */ - -function Network(opts) { - var self = this; - opts = opts || {}; - this.apiKey = opts.apiKey || 'lwjd5qra8257b9'; - this.debug = opts.debug || 3; - this.maxPeers = opts.maxPeers || 10; - this.reconnectAttempts = opts.reconnectAttempts || 3; - this.sjclParams = opts.sjclParams || { - salt: 'f28bfb49ef70573c', - iter: 500, - mode: 'ccm', - ts: parseInt(64), - }; - this.opts = {}; - ['config', 'port', 'host', 'path', 'debug', 'key', 'secure'].forEach(function(k) { - if (opts.hasOwnProperty(k)) self.opts[k] = opts[k]; - }); - this.cleanUp(); -} - -nodeUtil.inherits(Network, EventEmitter); - -Network.prototype.cleanUp = function() { - this.started = false; - this.connectedPeers = []; - this.peerId = null; - this.privkey = null; //TODO: hide privkey in a closure - this.key = null; - this.copayerId = null; - this.allowedCopayerIds = null; - this.isInboundPeerAuth = []; - this.copayerForPeer = {}; - this.connections = {}; - this.criticalErr = ''; - if (this.peer) { - this.peer.disconnect(); - this.peer.destroy(); - this.peer.removeAllListeners(); - this.peer = null; - } - this.closing = 0; - this.tries = 0; - this.removeAllListeners(); -}; - - -// Array helpers -Network._arrayDiff = function(a, b) { - var seen = []; - var diff = []; - - for (var i = 0; i < b.length; i++) - seen[b[i]] = true; - - for (var j = 0; j < a.length; j++) - if (!seen[a[j]]) - diff.push(a[j]); - - return diff; -}; - -Network._inArray = function(el, array) { - return array.indexOf(el) > -1; -}; - -Network._arrayPushOnce = function(el, array) { - var ret = false; - if (!Network._inArray(el, array)) { - array.push(el); - ret = true; - } - return ret; -}; - -Network._arrayRemove = function(el, array) { - var pos = array.indexOf(el); - if (pos >= 0) array.splice(pos, 1); - return array; -}; - -Network.prototype.connectedCopayers = function() { - var ret = []; - for (var i in this.connectedPeers) { - var copayerId = this.copayerForPeer[this.connectedPeers[i]]; - if (copayerId) ret.push(copayerId); - } - return ret; -}; - -Network.prototype._deletePeer = function(peerId) { - - delete this.isInboundPeerAuth[peerId]; - delete this.copayerForPeer[peerId]; - - if (this.connections[peerId]) { - this.connections[peerId].close(); - } - delete this.connections[peerId]; - this.connectedPeers = Network._arrayRemove(peerId, this.connectedPeers); -}; - -Network.prototype._onClose = function(peerID) { - this._deletePeer(peerID); - this.emit('disconnect', peerID); -}; - -Network.prototype.connectToCopayers = function(copayerIds) { - var self = this; - var arrayDiff = Network._arrayDiff(copayerIds, self.connectedCopayers()); - - arrayDiff.forEach(function(copayerId) { - if (self.allowedCopayerIds && !self.allowedCopayerIds[copayerId]) { - self._deletePeer(self.peerFromCopayer(copayerId)); - } else { - self.connectTo(copayerId); - } - }); -}; - -Network.prototype._sendHello = function(copayerId) { - this.send(copayerId, { - type: 'hello', - copayerId: this.copayerId, - }); -}; - -Network.prototype._addConnectedCopayer = function(copayerId, isInbound) { - var peerId = this.peerFromCopayer(copayerId); - this._addCopayerMap(peerId, copayerId); - Network._arrayPushOnce(peerId, this.connectedPeers); - this.emit('connect', copayerId); -}; - -Network.prototype.getKey = function() { - if (!this.key) { - var key = new bitcore.Key(); - key.private = new Buffer(this.privkey, 'hex'); - key.regenerateSync(); - this.key = key; - } - return this.key; -}; - -//hex version of one's own nonce -Network.prototype.setHexNonce = function(networkNonce) { - if (networkNonce) { - if (networkNonce.length !== 16) - throw new Error('incorrect length of hex nonce'); - this.networkNonce = new Buffer(networkNonce, 'hex'); - } - else - this.iterateNonce(); -}; - -//hex version of copayers' nonces -Network.prototype.setHexNonces = function(networkNonces) { - for (var i in networkNonces) { - if (!this.networkNonces) - this.networkNonces = {}; - if (networkNonces[i].length === 16) - this.networkNonces[i] = new Buffer(networkNonces[i], 'hex'); - } -}; - -//for oneself -Network.prototype.getHexNonce = function() { - return this.networkNonce.toString('hex'); -}; - -//for copayers -Network.prototype.getHexNonces = function() { - var networkNoncesHex = []; - for (var i in this.networkNonces) { - networkNoncesHex[i] = this.networkNonces[i].toString('hex'); - } - return networkNoncesHex; -}; - -Network.prototype.iterateNonce = function() { - if (!this.networkNonce || this.networkNonce.length !== 8) { - this.networkNonce = new Buffer(8); - this.networkNonce.fill(0); - } - //the first 4 bytes of a nonce is a unix timestamp in seconds - //the second 4 bytes is just an iterated "sub" nonce - //the whole thing is interpreted as one big endian number - var noncep1 = this.networkNonce.slice(0, 4); - noncep1.writeUInt32BE(Math.floor(Date.now()/1000), 0); - var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); - var noncep2 = this.networkNonce.slice(4, 8); - noncep2.writeUInt32BE(noncep2uint + 1, 0); - return this.networkNonce; -}; - -Network.prototype._onData = function(enc, isInbound, peerId) { - var encUint8Array = new Uint8Array(enc); - var encbuf = new Buffer(encUint8Array); - var encstr = encbuf.toString(); - - var privkey = this.privkey; - var key = this.getKey(); - - try { - var encoded = JSON.parse(encstr); - var prevnonce = this.networkNonces ? this.networkNonces[peerId] : undefined; - var opts = {prevnonce: prevnonce}; - var decoded = this._decode(key, encoded, opts); - - //if no error thrown in the last step, we can set the copayer's nonce - if (!this.networkNonces) - this.networkNonces = {}; - this.networkNonces[peerId] = decoded.nonce; - - var databuf = decoded.payload; - var datastr = databuf.toString(); - var payload = JSON.parse(datastr); - } catch (e) { - this._deletePeer(peerId); - return; - } - - if (isInbound && payload.type === 'hello') { - 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]) { - this._deletePeer(peerId); - return; - } - - this._addConnectedCopayer(payload.copayerId, isInbound); - this._setInboundPeerAuth(peerId, true); - return; - } - - if (!this.copayerForPeer[peerId] || (isInbound && !this.isInboundPeerAuth[peerId])) { - this._deletePeer(peerId); - return; - } - - var self = this; - switch (payload.type) { - case 'disconnect': - this._onClose(peerId); - break; - default: - this.emit('data', self.copayerForPeer[peerId], payload, isInbound); - } -}; - -Network.prototype._checkAnyPeer = function(msg) { - if (this.connectedPeers.length === 1) { - this.emit('onlyYou'); - } -}; - -Network.prototype._setupConnectionHandlers = function(dataConn, toCopayerId) { - var self = this; - - var isInbound = toCopayerId ? false : true; - - dataConn.on('open', function() { - if (!Network._inArray(dataConn.peer, self.connectedPeers) && - !self.connections[dataConn.peer]) { - - self.connections[dataConn.peer] = dataConn; - - // The connecting peer send hello - if (toCopayerId) { - self.emit('connected'); - self._sendHello(toCopayerId); - self._addConnectedCopayer(toCopayerId); - } - } - }); - - dataConn.on('data', function(data) { - self._onData(data, isInbound, dataConn.peer); - }); - - dataConn.on('error', function(e) { - self._onClose(dataConn.peer); - self._checkAnyPeer(); - self.emit('dataError'); - }); - - dataConn.on('close', function() { - if (self.closing) return; - - self._onClose(dataConn.peer); - self._checkAnyPeer(); - }); -}; - -Network.prototype._handlePeerOpen = function(openCallback) { - this.connectedPeers = [this.peerId]; - this.copayerForPeer[this.peerId] = this.copayerId; - return openCallback(); -}; - -Network.prototype._handlePeerError = function(err) { - console.log('RECV ERROR: ', err); - if (err.message.match(/Could\snot\sconnect\sto peer/)) { - this._checkAnyPeer(); - } else { - this.criticalError = err.message; - } -}; - -Network.prototype._handlePeerConnection = function(dataConn) { - if (this.connectedPeers.length >= self.maxPeers) { - dataConn.on('open', function() { - dataConn.close(); - }); - } else { - this._setInboundPeerAuth(dataConn.peer, false); - this._setupConnectionHandlers(dataConn); - } -}; - -Network.prototype._setupPeerHandlers = function(openCallback) { - var p = this.peer; - p.on('open', this._handlePeerOpen.bind(this, openCallback)); - p.on('error', this._handlePeerError.bind(this)); - p.on('connection', this._handlePeerConnection.bind(this)); -}; - -Network.prototype._addCopayerMap = function(peerId, copayerId) { - if (!this.copayerForPeer[peerId]) { - if (Object.keys(this.copayerForPeer).length < this.maxPeers) { - this.copayerForPeer[peerId] = copayerId; - } else {} - } -}; - -Network.prototype._setInboundPeerAuth = function(peerId, isAuthenticated) { - this.isInboundPeerAuth[peerId] = isAuthenticated; -}; - -Network.prototype.setCopayerId = function(copayerId) { - if (this.started) { - throw new Error('network already started: can not change peerId') - } - this.copayerId = copayerId; - this.copayerIdBuf = new Buffer(copayerId, 'hex'); - this.peerId = this.peerFromCopayer(this.copayerId); - this._addCopayerMap(this.peerId, copayerId); -}; - - -// TODO cache this. -Network.prototype.peerFromCopayer = function(hex) { - var SIN = bitcore.SIN; - return new SIN(new Buffer(hex, 'hex')).toString(); -}; - -Network.prototype.start = function(opts, openCallback) { - opts = opts || {}; - - if (this.started) return openCallback(); - - if (!this.privkey) - this.privkey = opts.privkey; - - this.maxPeers = opts.maxPeers || this.maxPeers; - - if (opts.token) - this.opts.token = opts.token; - - if (!this.copayerId) - this.setCopayerId(opts.copayerId); - - var self = this; - var setupPeer = function() { - if (self.connectedPeers.length > 0) return; // Already connected! - if (self.peer) { - self.peer.destroy(); - self.peer.removeAllListeners(); - } - - if (!self.criticalError && self.tries < self.reconnectAttempts) { - self.tries++; - self.opts.token = util.sha256(self.peerId).toString('hex'); - self.peer = new Peer(self.peerId, self.opts); - self.started = true; - self._setupPeerHandlers(openCallback); - - setTimeout(setupPeer, 3000); // Schedule retry - return; - } - if (self.criticalError && self.criticalError.match(/taken/)) { - self.criticalError = ' Looks like you are already connected to this wallet please close all other Copay Wallets ' - } - - self.emit('serverError', self.criticalError); - self.cleanUp(); - } - - this.tries = 0; - setupPeer(); -}; - -Network.prototype.getOnlinePeerIDs = function() { - return this.connectedPeers; -}; - -Network.prototype.getPeer = function() { - return this.peer; -}; - -Network.prototype._encode = function(topubkey, fromkey, payload, opts) { - var encoded = Message.encode(topubkey, fromkey, payload, opts); - return encoded; -}; - - -Network.prototype._decode = function(key, encoded, opts) { - var decoded = Message.decode(key, encoded, opts); - return decoded; -}; - -Network.prototype._sendToOne = function(copayerId, payload, cb) { - if (!Buffer.isBuffer(payload)) - throw new Error('payload must be a buffer'); - - var peerId = this.peerFromCopayer(copayerId); - if (peerId !== this.peerId) { - var dataConn = this.connections[peerId]; - if (dataConn) { - dataConn.send(payload); - } - } - if (typeof cb === 'function') cb(); -}; - -Network.prototype.send = function(copayerIds, payload, cb) { - if (!payload) return cb(); - - var self = this; - if (!copayerIds) { - copayerIds = this.connectedCopayers(); - payload.isBroadcast = 1; - } - - if (typeof copayerIds === 'string') - copayerIds = [copayerIds]; - - var payloadStr = JSON.stringify(payload); - var payloadBuf = new Buffer(payloadStr); - - var l = copayerIds.length; - var i = 0; - copayerIds.forEach(function(copayerId) { - self.iterateNonce(); - var opts = {nonce: self.networkNonce}; - var copayerIdBuf = new Buffer(copayerId, 'hex'); - var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf, opts); - var enc = new Buffer(JSON.stringify(encPayload)); - self._sendToOne(copayerId, enc, function() { - if (++i === l && typeof cb === 'function') cb(); - }); - }); -}; - - -Network.prototype.isOnline = function() { - return !!this.peer; -}; - -Network.prototype.connectTo = function(copayerId) { - var self = this; - - var peerId = this.peerFromCopayer(copayerId); - var dataConn = this.peer.connect(peerId, { - serialization: 'none', - reliable: true, - }); - self._setupConnectionHandlers(dataConn, copayerId); -}; - -Network.prototype.lockIncommingConnections = function(allowedCopayerIdsArray) { - this.allowedCopayerIds = {}; - for (var i in allowedCopayerIdsArray) { - this.allowedCopayerIds[allowedCopayerIdsArray[i]] = true; - } -}; - -Network.prototype.disconnect = function(cb, forced) { - var self = this; - self.closing = 1; - self.send(null, { - type: 'disconnect' - }, function() { - self.cleanUp(); - if (typeof cb === 'function') cb(); - }); -}; - -module.exports = Network; diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index dfbe4bb45..197b53d11 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -2,382 +2,380 @@ var bitcore = require('bitcore'); angular.module('copayApp.services') -.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) { - var root = {}; - root.getVideoMutedStatus = function(copayer) { - if (!$rootScope.videoInfo) return; + .factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) { + var root = {}; + root.getVideoMutedStatus = function(copayer) { + if (!$rootScope.videoInfo) return; - var vi = $rootScope.videoInfo[copayer] - if (!vi) { - return; - } - return vi.muted; - }; - - root.redirIfLogged = function() { - if ($rootScope.wallet) { - $rootScope.wallet.path('receive'); - } - }; - - root.logout = function() { - if ($rootScope.wallet) - $rootScope.wallet.disconnect(); - - Socket.removeAllListeners(); - - $rootScope.wallet = null; - delete $rootScope['wallet']; - - video.close(); - // Clear rootScope - for (var i in $rootScope) { - if (i.charAt(0) != '$') { - delete $rootScope[i]; - } - } - - $location.path('/'); - }; - - root.onError = function(scope) { - if (scope) scope.loading = false; - root.logout(); - } - - root.onErrorDigest = function(scope, msg) { - root.onError(scope); - if (msg) { - notification.error('Error', msg); - } - $rootScope.$digest(); - }; - - root.installStartupHandlers = function(wallet, $scope) { - wallet.on('connectionError', function() { - var message = "Looks like you are already connected to this wallet, please logout and try importing it again."; - notification.error('PeerJS Error', message); - root.onErrorDigest($scope); - }); - wallet.on('serverError', function(m) { - var message = m || 'The PeerJS server is not responding, please try again'; - $location.path('receive'); - root.onErrorDigest($scope, message); - }); - wallet.on('ready', function() { - $scope.loading = false; - }); - }; - - root.setupRootVariables = function() { - uriHandler.register(); - $rootScope.unitName = config.unitName; - $rootScope.txAlertCount = 0; - $rootScope.insightError = 0; - $rootScope.isCollapsed = true; - $rootScope.$watch('txAlertCount', function(txAlertCount) { - if (txAlertCount && txAlertCount > 0) { - - notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); - } - }); - - - $rootScope.$watch('receivedFund', function(receivedFund) { - if (receivedFund) { - var currentAddr; - for (var i = 0; i < $rootScope.addrInfos.length; i++) { - var addrinfo = $rootScope.addrInfos[i]; - if (addrinfo.address.toString() == receivedFund[1] && !addrinfo.isChange) { - currentAddr = addrinfo.address.toString(); - break; - } - } - if (currentAddr) { - //var beep = new Audio('sound/transaction.mp3'); - notification.funds('Received fund', currentAddr, receivedFund); - //beep.play(); - } - } - }); - - }; - - - root.startNetwork = function(w, $scope) { - Socket.removeAllListeners(); - - root.setupRootVariables(); - root.installStartupHandlers(w, $scope); - root.setSocketHandlers(); - - var handlePeerVideo = function(err, peerID, url) { - if (err) { - delete $rootScope.videoInfo[peerID]; + var vi = $rootScope.videoInfo[copayer] + if (!vi) { return; } - $rootScope.videoInfo[peerID] = { - url: encodeURI(url), - muted: peerID === w.network.peerId - }; + return vi.muted; + }; + + root.redirIfLogged = function() { + if ($rootScope.wallet) { + $rootScope.wallet.path('receive'); + } + }; + + root.logout = function() { + if ($rootScope.wallet) + $rootScope.wallet.disconnect(); + + Socket.removeAllListeners(); + + $rootScope.wallet = null; + delete $rootScope['wallet']; + + video.close(); + // Clear rootScope + for (var i in $rootScope) { + if (i.charAt(0) != '$') { + delete $rootScope[i]; + } + } + + $location.path('/'); + }; + + root.onError = function(scope) { + if (scope) scope.loading = false; + root.logout(); + } + + root.onErrorDigest = function(scope, msg) { + root.onError(scope); + if (msg) { + notification.error('Error', msg); + } $rootScope.$digest(); }; - notification.enableHtml5Mode(); // for chrome: if support, enable it + root.installStartupHandlers = function(wallet, $scope) { + wallet.on('connectionError', function() { + var message = "Looks like you are already connected to this wallet, please logout and try importing it again."; + notification.error('PeerJS Error', message); + root.onErrorDigest($scope); + }); + wallet.on('ready', function() { + $scope.loading = false; + }); + }; - w.on('badMessage', function(peerId) { - notification.error('Error', 'Received wrong message from peer ' + peerId); - }); - w.on('ready', function(myPeerID) { - $rootScope.wallet = w; - if ($rootScope.pendingPayment) { - $location.path('send'); - } else { - $location.path('receive'); - } - if (!config.disableVideo) - video.setOwnPeer(myPeerID, w, handlePeerVideo); - }); + root.setupRootVariables = function() { + uriHandler.register(); + $rootScope.unitName = config.unitName; + $rootScope.txAlertCount = 0; + $rootScope.insightError = 0; + $rootScope.isCollapsed = true; + $rootScope.$watch('txAlertCount', function(txAlertCount) { + if (txAlertCount && txAlertCount > 0) { - w.on('publicKeyRingUpdated', function(dontDigest) { - root.setSocketHandlers(); - if (!dontDigest) { - $rootScope.$digest(); - } - }); - w.on('txProposalsUpdated', function(dontDigest) { - root.updateTxs(); - // give sometime to the tx to propagate. - $timeout(function() { - root.updateBalance(function() { - if (!dontDigest) { - $rootScope.$digest(); + notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount); + } + }); + + + $rootScope.$watch('receivedFund', function(receivedFund) { + if (receivedFund) { + var currentAddr; + for (var i = 0; i < $rootScope.addrInfos.length; i++) { + var addrinfo = $rootScope.addrInfos[i]; + if (addrinfo.address.toString() == receivedFund[1] && !addrinfo.isChange) { + currentAddr = addrinfo.address.toString(); + break; + } } - }); - }, 3000); - }); - w.on('txProposalEvent', function(e) { - var user = w.publicKeyRing.nicknameForCopayer(e.cId); - switch (e.type) { - case 'signed': - notification.info('Transaction Update', 'A transaction was signed by ' + user); - break; - case 'rejected': - notification.info('Transaction Update', 'A transaction was rejected by ' + user); - break; - case 'corrupt': - notification.error('Transaction Error', 'Received corrupt transaction from '+user); - break; - } - }); - w.on('addressBookUpdated', function(dontDigest) { - if (!dontDigest) { + if (currentAddr) { + //var beep = new Audio('sound/transaction.mp3'); + notification.funds('Received fund', currentAddr, receivedFund); + //beep.play(); + } + } + }); + + }; + + + root.startNetwork = function(w, $scope) { + Socket.removeAllListeners(); + + root.setupRootVariables(); + root.installStartupHandlers(w, $scope); + root.setSocketHandlers(); + + var handlePeerVideo = function(err, peerID, url) { + if (err) { + delete $rootScope.videoInfo[peerID]; + return; + } + $rootScope.videoInfo[peerID] = { + url: encodeURI(url), + muted: peerID === w.network.peerId + }; $rootScope.$digest(); - } - }); - w.on('connectionError', function(msg) { - root.onErrorDigest(null, msg); - }); - w.on('connect', function(peerID) { - if (peerID && !config.disableVideo) { - video.callPeer(peerID, handlePeerVideo); - } - $rootScope.$digest(); - }); - w.on('disconnect', function(peerID) { - $rootScope.$digest(); - }); - w.on('close', root.onErrorDigest); - w.on('locked', root.onErrorDigest.bind(this)); - w.netStart(); - }; + }; - root.updateAddressList = function() { - var w = $rootScope.wallet; - if (w && w.isReady()) - $rootScope.addrInfos = w.getAddressesInfo(); - }; + notification.enableHtml5Mode(); // for chrome: if support, enable it - root.updateBalance = function(cb) { - var w = $rootScope.wallet; - if (!w) return root.onErrorDigest(); - if (!w.isReady()) return; + w.on('corrupt', function(peerId) { + notification.error('Error', 'Received corrupt message from ' + peerId); + }); + w.on('ready', function(myPeerID) { + $rootScope.wallet = w; + if ($rootScope.pendingPayment) { + $location.path('send'); + } else { + $location.path('receive'); + } + if (!config.disableVideo) + video.setOwnPeer(myPeerID, w, handlePeerVideo); + }); - $rootScope.balanceByAddr = {}; - $rootScope.updatingBalance = true; + w.on('publicKeyRingUpdated', function(dontDigest) { + root.setSocketHandlers(); + if (!dontDigest) { + $rootScope.$digest(); + } + }); + w.on('txProposalsUpdated', function(dontDigest) { + root.updateTxs(); + // give sometime to the tx to propagate. + $timeout(function() { + root.updateBalance(function() { + if (!dontDigest) { + $rootScope.$digest(); + } + }); + }, 3000); + }); + w.on('txProposalEvent', function(e) { + var user = w.publicKeyRing.nicknameForCopayer(e.cId); + switch (e.type) { + case 'signed': + notification.info('Transaction Update', 'A transaction was signed by ' + user); + break; + case 'rejected': + notification.info('Transaction Update', 'A transaction was rejected by ' + user); + break; + case 'corrupt': + notification.error('Transaction Error', 'Received corrupt transaction from ' + user); + break; + } + }); + w.on('addressBookUpdated', function(dontDigest) { + if (!dontDigest) { + $rootScope.$digest(); + } + }); + w.on('connectionError', function(msg) { + root.onErrorDigest(null, msg); + }); + w.on('connect', function(peerID) { + if (peerID && !config.disableVideo) { + video.callPeer(peerID, handlePeerVideo); + } + $rootScope.$digest(); + }); + w.on('disconnect', function(peerID) { + $rootScope.$digest(); + }); + w.on('close', root.onErrorDigest); + w.on('locked', root.onErrorDigest.bind(this)); + w.netStart(); + }; - w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) { - if (err) { - console.error('Error: ' + err.message); //TODO - root._setCommError(); - return null; - } else { - root._clearCommError(); - } + root.updateAddressList = function() { + var w = $rootScope.wallet; + if (w && w.isReady()) + $rootScope.addrInfos = w.getAddressesInfo(); + }; + + root.updateBalance = function(cb) { + var w = $rootScope.wallet; + if (!w) return root.onErrorDigest(); + if (!w.isReady()) return; + + $rootScope.balanceByAddr = {}; + $rootScope.updatingBalance = true; + + w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) { + if (err) { + console.error('Error: ' + err.message); //TODO + root._setCommError(); + return null; + } else { + root._clearCommError(); + } + + var satToUnit = 1 / config.unitToSatoshi; + var COIN = bitcore.util.COIN; + + $rootScope.totalBalance = balanceSat * satToUnit; + $rootScope.totalBalanceBTC = (balanceSat / COIN); + $rootScope.availableBalance = safeBalanceSat * satToUnit; + $rootScope.availableBalanceBTC = (safeBalanceSat / COIN); + + $rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit; + $rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN; + + var balanceByAddr = {}; + for (var ii in balanceByAddrSat) { + balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit; + } + $rootScope.balanceByAddr = balanceByAddr; + root.updateAddressList(); + $rootScope.updatingBalance = false; + return cb ? cb() : null; + }); + }; + + root.updateTxs = function(opts) { + var w = $rootScope.wallet; + if (!w) return; + opts = opts || $rootScope.txsOpts || {}; var satToUnit = 1 / config.unitToSatoshi; - var COIN = bitcore.util.COIN; + var myCopayerId = w.getMyCopayerId(); + var pendingForUs = 0; + var inT = w.getTxProposals().sort(function(t1, t2) { + return t2.createdTs - t1.createdTs + }); + var txs = []; - $rootScope.totalBalance = balanceSat * satToUnit; - $rootScope.totalBalanceBTC = (balanceSat / COIN); - $rootScope.availableBalance = safeBalanceSat * satToUnit; - $rootScope.availableBalanceBTC = (safeBalanceSat / COIN); + inT.forEach(function(i, index) { + if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) { + return txs.push(null); + } - $rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit; - $rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN; + if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) { + pendingForUs++; + } + if (!i.finallyRejected && !i.sentTs) { + i.isPending = 1; + } - var balanceByAddr = {}; - for (var ii in balanceByAddrSat) { - balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit; + if (!!opts.pending == !!i.isPending) { + var tx = i.builder.build(); + var outs = []; + tx.outs.forEach(function(o) { + var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString(); + if (!w.addressIsOwn(addr, { + excludeMain: true + })) { + outs.push({ + address: addr, + value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit, + }); + } + }); + // extra fields + i.outs = outs; + i.fee = i.builder.feeSat * satToUnit; + i.missingSignatures = tx.countInputMissingSignatures(0); + i.actionList = getActionList(i.peerActions); + txs.push(i); + } + }); + + $rootScope.txs = txs; + $rootScope.txsOpts = opts; + if ($rootScope.pendingTxCount < pendingForUs) { + $rootScope.txAlertCount = pendingForUs; } - $rootScope.balanceByAddr = balanceByAddr; + $rootScope.pendingTxCount = pendingForUs; + }; + + function getActionList(actions) { + var peers = Object.keys(actions).map(function(i) { + return { + cId: i, + actions: actions[i] + } + }); + + return peers.sort(function(a, b) { + return !!b.actions.create - !!a.actions.create; + }); + } + + var connectionLost = false; + $rootScope.$watch('insightError', function(status) { + if (!status) return; + + // Reconnected + if (status === -1) { + if (!connectionLost) return; // Skip on first reconnect + connectionLost = false; + notification.success('Networking restored', 'Connection to Insight re-established'); + return; + } + + // Retry + if (status == 1) return; // Skip the first try + connectionLost = true; + notification.error('Networking problem', 'Connection to Insight lost, reconnecting (attempt number ' + (status - 1) + ')'); + }); + + root._setCommError = function(e) { + if ($rootScope.insightError < 0) + $rootScope.insightError = 0; + $rootScope.insightError++; + }; + + root._clearCommError = function(e) { + if ($rootScope.insightError > 0) + $rootScope.insightError = -1; + else + $rootScope.insightError = 0; + }; + + root.setSocketHandlers = function() { root.updateAddressList(); - $rootScope.updatingBalance = false; - return cb ? cb() : null; - }); - }; - - root.updateTxs = function(opts) { - var w = $rootScope.wallet; - if (!w) return; - opts = opts || $rootScope.txsOpts || {}; - - var satToUnit = 1 / config.unitToSatoshi; - var myCopayerId = w.getMyCopayerId(); - var pendingForUs = 0; - var inT = w.getTxProposals().sort(function(t1, t2) { - return t2.createdTs - t1.createdTs - }); - var txs = []; - - inT.forEach(function(i, index) { - if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) { - return txs.push(null); + if (!Socket.sysEventsSet) { + Socket.sysOn('error', root._setCommError); + Socket.sysOn('reconnect_error', root._setCommError); + Socket.sysOn('reconnect_failed', root._setCommError); + Socket.sysOn('connect', root._clearCommError); + Socket.sysOn('reconnect', root._clearCommError); + Socket.sysEventsSet = true; } + if (!$rootScope.wallet) return; - if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) { - pendingForUs++; - } - if (!i.finallyRejected && !i.sentTs) { - i.isPending = 1; - } + var currentAddrs = Socket.getListeners(); + var allAddrs = $rootScope.addrInfos; - if (!!opts.pending == !!i.isPending) { - var tx = i.builder.build(); - var outs = []; - tx.outs.forEach(function(o) { - var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString(); - if (!w.addressIsOwn(addr, { - excludeMain: true - })) { - outs.push({ - address: addr, - value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit, - }); - } + var newAddrs = []; + for (var i in allAddrs) { + var a = allAddrs[i]; + if (!currentAddrs[a.addressStr]) + newAddrs.push(a); + } + for (var i = 0; i < newAddrs.length; i++) { + Socket.emit('subscribe', newAddrs[i].addressStr); + } + newAddrs.forEach(function(a) { + Socket.on(a.addressStr, function(txid) { + + if (!a.isChange) + notification.funds('Funds received!', a.addressStr); + + root.updateBalance(function() { + $rootScope.$digest(); + }); + }); + }); + + if (!$rootScope.wallet.spendUnconfirmed && !Socket.isListeningBlocks()) { + Socket.emit('subscribe', 'inv'); + Socket.on('block', function(block) { + root.updateBalance(function() { + $rootScope.$digest(); + }); }); - // extra fields - i.outs = outs; - i.fee = i.builder.feeSat * satToUnit; - i.missingSignatures = tx.countInputMissingSignatures(0); - i.actionList = getActionList(i.peerActions); - txs.push(i); } - }); - - $rootScope.txs = txs; - $rootScope.txsOpts = opts; - if ($rootScope.pendingTxCount < pendingForUs) { - $rootScope.txAlertCount = pendingForUs; - } - $rootScope.pendingTxCount = pendingForUs; - }; - - function getActionList(actions) { - var peers = Object.keys(actions).map(function(i) { - return {cId: i, actions: actions[i] } - }); - - return peers.sort(function(a, b) { - return !!b.actions.create - !!a.actions.create; - }); - } - - var connectionLost = false; - $rootScope.$watch('insightError', function(status) { - if (!status) return; - - // Reconnected - if (status === -1) { - if (!connectionLost) return; // Skip on first reconnect - connectionLost = false; - notification.success('Networking restored', 'Connection to Insight re-established'); - return; - } - - // Retry - if (status == 1) return; // Skip the first try - connectionLost = true; - notification.error('Networking problem', 'Connection to Insight lost, reconnecting (attempt number ' + (status-1) + ')'); + }; + return root; }); - - root._setCommError = function(e) { - if ($rootScope.insightError < 0) - $rootScope.insightError = 0; - $rootScope.insightError++; - }; - - root._clearCommError = function(e) { - if ($rootScope.insightError > 0) - $rootScope.insightError = -1; - else - $rootScope.insightError = 0; - }; - - root.setSocketHandlers = function() { - root.updateAddressList(); - if (!Socket.sysEventsSet) { - Socket.sysOn('error', root._setCommError); - Socket.sysOn('reconnect_error', root._setCommError); - Socket.sysOn('reconnect_failed', root._setCommError); - Socket.sysOn('connect', root._clearCommError); - Socket.sysOn('reconnect', root._clearCommError); - Socket.sysEventsSet = true; - } - if (!$rootScope.wallet) return; - - var currentAddrs = Socket.getListeners(); - var allAddrs = $rootScope.addrInfos; - - var newAddrs = []; - for (var i in allAddrs) { - var a = allAddrs[i]; - if (!currentAddrs[a.addressStr]) - newAddrs.push(a); - } - for (var i = 0; i < newAddrs.length; i++) { - Socket.emit('subscribe', newAddrs[i].addressStr); - } - newAddrs.forEach(function(a) { - Socket.on(a.addressStr, function(txid) { - - if (!a.isChange) - notification.funds('Funds received!', a.addressStr); - - root.updateBalance(function() { - $rootScope.$digest(); - }); - }); - }); - - if (!$rootScope.wallet.spendUnconfirmed && !Socket.isListeningBlocks()) { - Socket.emit('subscribe', 'inv'); - Socket.on('block', function(block) { - root.updateBalance(function() { - $rootScope.$digest(); - }); - }); - } - }; - return root; -}); diff --git a/package.json b/package.json index 61b315609..8e9e91402 100644 --- a/package.json +++ b/package.json @@ -53,10 +53,7 @@ "grunt-contrib-uglify": "^0.5.1", "grunt-contrib-watch": "0.5.3", "grunt-markdown": "0.5.0", - "browser-pack": "2.0.1", "bitcore": "0.1.35", - "node-cryptojs-aes": "0.4.0", - "blanket": "1.1.6", "grunt-mocha-test": "0.8.2", "grunt-shell": "0.6.4", "istanbul": "0.2.10", @@ -69,6 +66,7 @@ "mocha-lcov-reporter": "0.0.1", "mock-fs": "^2.3.1", "node-cryptojs-aes": "0.4.0", + "socket.io-client": "1.0.6", "travis-cov": "0.2.5", "uglifyify": "1.2.3" }, diff --git a/test/index.html b/test/index.html index 6d75f29c7..e5aa76a6c 100644 --- a/test/index.html +++ b/test/index.html @@ -15,8 +15,7 @@ - - + diff --git a/test/mocks/FakeNetwork.js b/test/mocks/FakeNetwork.js index 4368e8de5..d1b8fee6c 100644 --- a/test/mocks/FakeNetwork.js +++ b/test/mocks/FakeNetwork.js @@ -6,7 +6,6 @@ function Network(opts) {} util.inherits(Network, EventEmitter); Network.prototype.start = function(opts, cb) { - // start! :D this.peer = { options: { token: "asd" @@ -15,18 +14,12 @@ Network.prototype.start = function(opts, cb) { if (cb) cb(); }; -Network.prototype.send = function(peerIds, data, cb) { - // send! c: -}; +Network.prototype.send = function(peerIds, data, cb) {}; -Network.prototype.connectTo = function(peerId) { - // connect C: -}; +Network.prototype.connectTo = function(peerId) {}; -Network.prototype.disconnect = function(cb) { - // disconect :c -}; +Network.prototype.disconnect = function(cb) {}; Network.prototype.lockIncommingConnections = function() { @@ -43,7 +36,7 @@ Network.prototype.peerFromCopayer = function(copayerId) { //hex version of one's own nonce Network.prototype.setHexNonce = function(networkNonce) { - if (networkNonce && networkNonce.length === 16) + if (networkNonce && networkNonce.length === 16) this.networkNonce = new Buffer(networkNonce, 'hex'); else this.iterateNonce(); @@ -84,7 +77,7 @@ Network.prototype.iterateNonce = function() { //the second 4 bytes is just an iterated "sub" nonce //the whole thing is interpreted as one big endian number var noncep1 = this.networkNonce.slice(0, 4); - noncep1.writeUInt32BE(Math.floor(Date.now()/1000), 0); + noncep1.writeUInt32BE(Math.floor(Date.now() / 1000), 0); var noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0); var noncep2 = this.networkNonce.slice(4, 8); noncep2.writeUInt32BE(noncep2uint + 1, 0); @@ -92,4 +85,9 @@ Network.prototype.iterateNonce = function() { }; +Network.prototype.cleanUp = function() { + return; +}; + + module.exports = Network; diff --git a/test/test.Message.js b/test/test.Message.js deleted file mode 100644 index 9bad687d1..000000000 --- a/test/test.Message.js +++ /dev/null @@ -1,159 +0,0 @@ -'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); - var payload = decoded.payload; - payload.toString('hex').should.equal(messagehex); - }); - - it('should decode an encoded message with proper prevnonce', function() { - var message = new Buffer('message'); - var messagehex = message.toString('hex'); - var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 2]); - var opts = {nonce: nonce}; - var encoded = Message.encode(key2.public, key, message, opts); - - var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); - opts = {prevnonce: prevnonce}; - var decoded = Message.decode(key2, encoded, opts); - var payload = decoded.payload; - payload.toString('hex').should.equal(messagehex); - }); - - it('should decode an encoded message with proper prevnonce - for first part', function() { - var message = new Buffer('message'); - var messagehex = message.toString('hex'); - var nonce = new Buffer([0, 0, 0, 2, 0, 0, 0, 0]); - var opts = {nonce: nonce}; - var encoded = Message.encode(key2.public, key, message, opts); - - var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); - opts = {prevnonce: prevnonce}; - var decoded = Message.decode(key2, encoded, opts); - var payload = decoded.payload; - payload.toString('hex').should.equal(messagehex); - }); - - it('should fail if prevnonce is too high', function() { - var message = new Buffer('message'); - var messagehex = message.toString('hex'); - var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); - var opts = {nonce: nonce}; - var encoded = Message.encode(key2.public, key, message, opts); - - var prevnonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 1]); - opts = {prevnonce: prevnonce}; - (function() {Message.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); - }); - - it('should fail if prevnonce is too high - for first part', function() { - var message = new Buffer('message'); - var messagehex = message.toString('hex'); - var nonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); - var opts = {nonce: nonce}; - var encoded = Message.encode(key2.public, key, message, opts); - - var prevnonce = new Buffer([0, 0, 0, 1, 0, 0, 0, 0]); - opts = {prevnonce: prevnonce}; - (function() {Message.decode(key2, encoded, opts)}).should.throw('Nonce not equal to zero and not greater than the previous nonce'); - }); - - it('should fail if the version number is incorrect', function() { - var payload = new Buffer('message'); - var fromkey = key; - var topubkey = key2.public; - var version1 = new Buffer([2]); - var version2 = new Buffer([0]); - var nonce = new Buffer([0, 0, 0, 0, 0, 0, 0, 0]); - var toencrypt = Buffer.concat([version1, version2, nonce, payload]); - var toencrypt_workaround = new Buffer(toencrypt.toString('hex')); - var encrypted = Message._encrypt(topubkey, toencrypt_workaround); - 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); - }); - - }); - -}); diff --git a/test/test.Wallet.js b/test/test.Wallet.js index e46c1c3c3..3406cf42c 100644 --- a/test/test.Wallet.js +++ b/test/test.Wallet.js @@ -441,7 +441,7 @@ describe('Wallet model', function() { receiveIndex: 2 }] }; - w._handleIndexes('senderID', aiObj, true); + w._onIndexes('senderID', aiObj, true); w.publicKeyRing.getHDParams(0).getReceiveIndex(2); w.publicKeyRing.getHDParams(0).getChangeIndex(3); }); @@ -467,7 +467,7 @@ describe('Wallet model', function() { copayersExtPubKeys: cepk, nicknameFor: {}, }; - w._handlePublicKeyRing('senderID', { + w._onPublicKeyRing('senderID', { publicKeyRing: pkrObj }, true); w.publicKeyRing.getHDParams(0).getReceiveIndex(2); @@ -512,7 +512,7 @@ describe('Wallet model', function() { var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({ '027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe' }); - w._handleTxProposal('senderID', txp, true); + w._onTxProposal('senderID', txp, true); Object.keys(w.txProposals.txps).length.should.equal(1); w.getTxProposals().length.should.equal(1); //stub.restore(); @@ -525,7 +525,7 @@ describe('Wallet model', function() { id.should.equal(newId); done(); }); - w._handleConnect(newId); + w._onConnect(newId); }); it('handle disconnections', function(done) { @@ -534,7 +534,7 @@ describe('Wallet model', function() { id.should.equal(newId); done(); }); - w._handleDisconnect(newId); + w._onDisconnect(newId); }); it('should register new copayers correctly', function() { @@ -993,7 +993,7 @@ describe('Wallet model', function() { var senderId = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb"; Object.keys(w.addressBook).length.should.equal(2); - w._handleAddressBook(senderId, data, true); + w._onAddressBook(senderId, data, true); Object.keys(w.addressBook).length.should.equal(3); }); @@ -1094,7 +1094,7 @@ describe('Wallet model', function() { var backup = copayConfig.forceNetwork; copayConfig.forceNetwork = true; config.networkName = 'livenet'; - cachedCreateW2.should.throw(Error); + createW2.should.throw(Error); copayConfig.forceNetwork = backup; }); }); @@ -1252,7 +1252,7 @@ describe('Wallet model', function() { - describe('_handleTxProposal', function() { + describe('_onTxProposal', function() { var testValidate = function(response, result, done) { var w = cachedCreateW(); @@ -1278,7 +1278,7 @@ describe('Wallet model', function() { }; }); - w._handleTxProposal('senderID', txp); + w._onTxProposal('senderID', txp); spy.callCount.should.equal(1); merge.restore(); }; @@ -1299,11 +1299,11 @@ describe('Wallet model', function() { }); - describe('_handleReject', function() { + describe('_onReject', function() { it('should fails if unknown tx', function() { var w = cachedCreateW(); (function() { - w._handleReject(1, { + w._onReject(1, { ntxid: 1 }, 1); }).should.throw('Unknown TXP'); @@ -1316,7 +1316,7 @@ describe('Wallet model', function() { } }; (function() { - w._handleReject('john', { + w._onReject('john', { ntxid: 'qwerty' }, 1); }).should.throw('already signed'); @@ -1337,7 +1337,7 @@ describe('Wallet model', function() { var spy2 = sinon.spy(w, 'emit'); w.txProposals.txps['qwerty'] = new txp(); w.txProposals.txps['qwerty'].ok.should.equal(0); - w._handleReject('john', { + w._onReject('john', { ntxid: 'qwerty' }, 1); w.txProposals.txps['qwerty'].ok.should.equal(1); @@ -1353,11 +1353,11 @@ describe('Wallet model', function() { }); - describe('_handleSeen', function() { + describe('_onSeen', function() { it('should fails if unknown tx', function() { var w = cachedCreateW(); (function() { - w._handleReject(1, { + w._onReject(1, { ntxid: 1 }, 1); }).should.throw('Unknown TXP'); @@ -1378,7 +1378,7 @@ describe('Wallet model', function() { var spy2 = sinon.spy(w, 'emit'); w.txProposals.txps['qwerty'] = new txp(); w.txProposals.txps['qwerty'].ok.should.equal(0); - w._handleSeen('john', { + w._onSeen('john', { ntxid: 'qwerty' }, 1); w.txProposals.txps['qwerty'].ok.should.equal(1); @@ -1402,9 +1402,9 @@ describe('Wallet model', function() { it('#disconnect', function() { var w = cachedCreateW(); - var spy1 = sinon.spy(w.network, 'disconnect'); + var spy1 = sinon.spy(w, 'send'); w.disconnect(); - spy1.callCount.should.equal(1); + spy1.calledOnce.should.be.true; }); }); diff --git a/test/test.blockchain.Insight.js b/test/test.blockchain.Insight.js index e80d628f8..9eb9af6f1 100644 --- a/test/test.blockchain.Insight.js +++ b/test/test.blockchain.Insight.js @@ -9,6 +9,7 @@ try { } catch (e) { var copay = require('../copay'); //node } +var Buffer = bitcore.Buffer; var Insight = copay.Insight || require('../js/models/blockchain/Insight'); var ID = '933bf321393459b7'; diff --git a/test/test.network.Async.js b/test/test.network.Async.js new file mode 100644 index 000000000..4d46dabb3 --- /dev/null +++ b/test/test.network.Async.js @@ -0,0 +1,285 @@ +'use strict'; + +var chai = chai || require('chai'); +var should = chai.should(); +var expect = chai.expect; +var sinon = sinon || require('sinon'); +var bitcore = bitcore || require('bitcore'); +var copay = copay || require('../copay'); +var Async = copay.Async; +var EventEmitter = require('events').EventEmitter; + +describe('Network / Async', function() { + + + var createN = function(pk) { + var n = new Async(); + var fakeSocket = {}; + fakeSocket.emit = function() {}; + fakeSocket.on = function() {}; + fakeSocket.disconnect = function() {}; + n.createSocket = function() { + return fakeSocket; + }; + var opts = { + copayerId: '03b51d01d798522cf61211b4dfcdd6db219ee33cf166e1cb7f43d836ab00ccddee', + privkey: pk || '31701118abde096d166607115ed00ce74a2231f68f43144406c863f5ebf06c32', + lastTimestamp: 1, + }; + n.start(opts); + return n; + }; + + it('should create an instance', function() { + var n = createN(); + should.exist(n); + }); + + describe('#cleanUp', function() { + + it('should not set netKey', function() { + var n = createN(); + (n.netKey === undefined).should.equal(true); + }); + + it('should set privkey to null', function() { + var n = createN(); + n.cleanUp(); + expect(n.privkey).to.equal(null); + }); + + it('should remove handlers', function() { + var n = createN(); + var save = Async.prototype.removeAllListeners; + var spy = Async.prototype.removeAllListeners = sinon.spy(); + n.cleanUp(); + spy.calledOnce.should.equal(true); + Async.prototype.removeAllListeners = save; + }); + }); + + describe('#send', function() { + + it('should be able to broadcast', function() { + var n = createN('9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'); + var spy = sinon.spy(); + n.send(null, 'hello', spy); + spy.calledOnce.should.be.true; + }); + it('should call _sendToOne for a copayer', function(done) { + var n = createN('9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'); + var data = 'my data to send'; + + var copayerId = '03b51d01d798522cf61211b4dfcdd6d01020304cf166e1cb7f43d836abc5c18b23'; + n._sendToOne = function(a, b, cb) { + cb(); + }; + var opts = {}; + n.send(copayerId, data, done); + + }); + + it('should call _sendToOne with encrypted data for a copayer', function(done) { + var n = createN('9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'); + var data = new bitcore.Buffer('my data to send'); + + var copayerId = '03b51d01d798522cf61001b4dfcdd6db219ee33cf166e1cb7f43d836abc5c18b23'; + n._sendToOne = function(a1, enc, cb) { + var encPayload = JSON.parse(enc.toString()); + encPayload.sig.length.should.be.greaterThan(0); + cb(); + }; + var opts = {}; + n.send(copayerId, data, function() { + done(); + }); + + }); + + it('should call _sendToOne for a list of copayers', function(done) { + var n = createN('9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08'); + var data = new bitcore.Buffer('my data to send'); + var copayerIds = ['03b51d01d798522cf61211b4dfcdd6db219ee33cf166e1cb7f43d836abc5c18b23']; + n._sendToOne = function(a1, a2, cb) { + cb(); + }; + var opts = {}; + n.send(copayerIds, data, function() { + done(); + }); + + }); + }); + + describe('#_onMessage', function() { + var pk1 = 'fb23b9074ca5e7163719b86b41c7ce8348cf3d2839bb5f6125ef6efd5d40d7d3'; + var cid1 = '0311a10109320efb3646c832d3e140c6d9c4f69b16e73fc3f0c23b3d014ec77828'; + + var pk2 = '89073fe4d3fdef2c5f2909bcda92e4470633f08640d1a62acc464327d611577e'; + var cid2 = '03ceefb9dbcf7410411e5c1268d9d8e850ffd3a55da764a8377f3212571a52c01b'; + + var pk3 = 'a2ae2c7029c6a4136d7fe60c4d078a2e9d5af8a246bf2d5fee3410e273a5d430'; + var cid3 = '034d3dd2054234737c1cff9d973c9c7e0fb5902c8e56c9d57a699b7842cedfe984'; + + it('should not reject data sent from a peer with hijacked pubkey', function() { + var n1 = createN(pk1); + var n2 = createN(pk2); + n2._deletePeer = sinon.spy(); + + var message = { + type: 'hello', + copayerId: cid1 + }; + var enc = n1.encode(cid2, message); + n2._onMessage(enc); + n2._deletePeer.calledOnce.should.equal(false); + }); + + it('should reject data sent from a peer with hijacked pubkey', function() { + var n = createN(pk2); + + var message = { + type: 'hello', + copayerId: cid3 // MITM + }; + + var enc = n.encode(cid2, message); + + n._deletePeer = sinon.spy(); + + n._onMessage(enc); + n._deletePeer.calledOnce.should.equal(true); + n._deletePeer.getCall(0).args[1].should.equal('incorrect pubkey for peerId'); + }); + + it('should not reject data sent from a peer with no previously set nonce but who is setting one now', function() { + var n1 = createN(pk1); + var n2 = createN(pk2); + n2._deletePeer = sinon.spy(); + + var message = { + type: 'hello', + copayerId: cid1 + }; + var nonce = new Buffer('0000000000000001', 'hex'); + var enc = n1.encode(cid2, message, nonce); + n2._onMessage(enc); + n2._deletePeer.calledOnce.should.equal(false); + n2.getHexNonces()[cid1].toString('hex').should.equal('0000000000000001'); + }); + + it('should not reject data sent from a peer with a really big new nonce', function() { + var n1 = createN(pk1); + var n2 = createN(pk2); + n2._deletePeer = sinon.spy(); + + var message = { + type: 'hello', + copayerId: cid1 + }; + n2.networkNonces = {}; + n2.networkNonces[cid1] = new Buffer('5000000000000001', 'hex'); + var nonce = new Buffer('5000000000000002', 'hex') + var enc = n1.encode(cid2, message, nonce); + n2._onMessage(enc); + n2._deletePeer.calledOnce.should.equal(false); + n2.getHexNonces()[cid1].toString('hex').should.equal('5000000000000002'); + n2._deletePeer.calledOnce.should.equal(false); + }); + + it('should reject data sent from a peer with an outdated nonce', function() { + var n1 = createN(pk1); + var n2 = createN(pk2); + n2._deletePeer = sinon.spy(); + + var message = { + type: 'hello', + copayerId: cid1 + }; + n2.networkNonces = {}; + n2.networkNonces[cid1] = new Buffer('0000000000000002', 'hex'); + var nonce = new Buffer('0000000000000001', 'hex'); + var enc = n1.encode(cid2, message, nonce); + n2._onMessage(enc); + n2._deletePeer.calledOnce.should.equal(true); + }); + + }); + + describe('#setHexNonce', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = createN(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + n.networkNonce.toString('hex').should.equal(hex); + }); + + }); + + describe('#setHexNonces', function() { + + it('should set a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = createN(); + n.setHexNonces({ + fakeid: hex + }); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#getHexNonce', function() { + + it('should get a nonce hex value', function() { + var hex = '0000000000000000'; + var n = createN(); + n.setHexNonce(hex); + n.getHexNonce().should.equal(hex); + }); + + }); + + describe('#getHexNonces', function() { + + it('should get a nonce from a hex value', function() { + var hex = '0000000000000000'; + var n = createN(); + n.setHexNonces({ + fakeid: hex + }); + n.getHexNonces().fakeid.should.equal(hex); + }); + + }); + + describe('#iterateNonce', function() { + + it('should set a nonce not already set', function() { + var n = createN(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.networkNonce.slice(0, 4).toString('hex').should.not.equal('00000000'); + }); + + it('called twice should increment', function() { + var n = createN(); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); + n.iterateNonce(); + n.networkNonce.slice(4, 8).toString('hex').should.equal('00000002'); + }); + + it('should set the first byte to the most significant "now" digit', function() { + var n = createN(); + n.iterateNonce(); + var buf = new Buffer(4); + buf.writeUInt32BE(Math.floor(Date.now() / 1000), 0); + n.networkNonce[0].should.equal(buf[0]); + }); + + }); + +}); diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js deleted file mode 100644 index 8231991dc..000000000 --- a/test/test.network.WebRTC.js +++ /dev/null @@ -1,507 +0,0 @@ -'use strict'; - -var chai = chai || require('chai'); -var should = chai.should(); -var expect = chai.expect; -var sinon = sinon || require('sinon'); -var bitcore = bitcore || require('bitcore'); -var WebRTC = require('../js/models/network/WebRTC'); - -describe('Network / WebRTC', function() { - - it('should create an instance', function() { - var n = new WebRTC(); - should.exist(n); - }); - - describe('#WebRTC constructor', function() { - - it('should set reconnect attempts', function() { - var n = new WebRTC(); - n.reconnectAttempts.should.equal(3); - }); - - it('should call cleanUp', function() { - var save = WebRTC.prototype.cleanUp; - WebRTC.prototype.cleanUp = sinon.spy(); - var n = new WebRTC(); - n.cleanUp.calledOnce.should.equal(true); - WebRTC.prototype.cleanUp = save; - }); - }); - - describe('#cleanUp', function() { - - it('should not set netKey', function() { - var n = new WebRTC(); - (n.netKey === undefined).should.equal(true); - }); - - it('should set privkey to null', function() { - var n = new WebRTC(); - n.cleanUp(); - expect(n.privkey).to.equal(null); - }); - - it('should remove handlers', function() { - var n = new WebRTC(); - var save = WebRTC.prototype.removeAllListeners; - var spy = WebRTC.prototype.removeAllListeners = sinon.spy(); - n.cleanUp(); - spy.calledOnce.should.equal(true); - WebRTC.prototype.removeAllListeners = save; - }); - }); - - - describe('#_setupPeerHandlers', function() { - var n = new WebRTC(); - n.peer = {}; - var spy = n.peer.on = sinon.spy(); - it('should setup handlers', function() { - n._setupPeerHandlers(); - spy.calledWith('connection').should.equal(true); - spy.calledWith('open').should.equal(true); - spy.calledWith('error').should.equal(true); - }); - }); - - describe('#_handlePeerOpen', function() { - var n = new WebRTC(); - it('should call openCallback handler', function(done) { - n.peerId = 1; - n.copayerId = 2; - n._handlePeerOpen(function() { - n.connectedPeers.should.deep.equal([1]); - n.copayerForPeer.should.deep.equal({ - 1: 2 - }); - done(); - }); - }); - }); - - describe('#_handlePeerError', function() { - var log = console.log; - var n = new WebRTC(); - it('should call _checkAnyPeer on could not connect error', function() { - var save = n._checkAnyPeer; - var spy = n._checkAnyPeer = sinon.spy(); - var logSpy = console.log = sinon.spy(); - n._handlePeerError({ - message: 'Could not connect to peer xxx' - }); - console.log = log; - spy.called.should.equal(true); - logSpy.called.should.equal(true); - n._checkAnyPeer = save; - }); - - it('should call not call _checkAnyPeer other error', function() { - var save = n._checkAnyPeer; - var spy = n._checkAnyPeer = sinon.spy(); - var otherMessage = 'Could connect to peer xxx'; - var logSpy = console.log = sinon.spy(); - n._handlePeerError({ - message: otherMessage, - }); - console.log = log; - spy.called.should.equal(false); - n.criticalError.should.equal(otherMessage); - logSpy.called.should.equal(true); - n._checkAnyPeer = save; - }); - - }); - - - - describe('#_encode', 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 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 encoded = n._encode(key.public, key, data); - var decoded = n._decode(key, encoded); - encoded.sig.should.not.equal(0); - decoded.payload.toString().should.equal('my data to encrypt'); - }); - - }); - - describe('#send', function() { - - it('should call _sendToOne for a copayer', function(done) { - var n = new WebRTC(); - n.privkey = bitcore.util.sha256('test'); - - var data = new bitcore.Buffer('my data to send'); - - var privkeystr = new bitcore.Buffer('test privkey'); - var privkey = bitcore.util.sha256(privkeystr); - var key = new bitcore.Key(); - key.private = privkey; - key.regenerateSync(); - - var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, a2, cb) { - cb(); - }; - var opts = {}; - n.send(copayerId, data, function() { - done(); - }); - - }); - - it('should call _sendToOne with encrypted data for a copayer', function(done) { - var n = new WebRTC(); - n.privkey = bitcore.util.sha256('test'); - - var data = new bitcore.Buffer('my data to send'); - - var privkeystr = new bitcore.Buffer('test privkey'); - var privkey = bitcore.util.sha256(privkeystr); - var key = new bitcore.Key(); - key.private = privkey; - key.regenerateSync(); - - var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, enc, cb) { - var encPayload = JSON.parse(enc.toString()); - encPayload.sig.length.should.be.greaterThan(0); - cb(); - }; - var opts = {}; - n.send(copayerId, data, function() { - done(); - }); - - }); - - it('should call _sendToOne for a list of copayers', function(done) { - var n = new WebRTC(); - n.privkey = bitcore.util.sha256('test'); - - var data = new bitcore.Buffer('my data to send'); - - var privkeystr = new bitcore.Buffer('test privkey'); - var privkey = bitcore.util.sha256(privkeystr); - var key = new bitcore.Key(); - key.private = privkey; - key.regenerateSync(); - - var copayerIds = [key.public.toString('hex')]; - n._sendToOne = function(a1, a2, cb) { - cb(); - }; - var opts = {}; - n.send(copayerIds, data, function() { - done(); - }); - - }); - }); - - 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'); - }); - - it('should not reject data sent from a peer with no previously set nonce but who is setting one now', function() { - var n = new WebRTC(); - n.privkey = key2.private.toString('hex'); - //n.networkNonces = {}; - //n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000001', 'hex'); //previously used nonce - - var message = { - type: 'hello', - copayerId: key1.public.toString('hex') - }; - var messagestr = JSON.stringify(message); - var messagebuf = new Buffer(messagestr); - - var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with new nonce - var encoded = n._encode(key2.public, key1, messagebuf, opts); - 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); - n.getHexNonces()[(new bitcore.SIN(key1.public)).toString()].toString('hex').should.equal('0000000000000001'); - }); - - it('should not reject data sent from a peer with a really big new nonce', function() { - var n = new WebRTC(); - n.privkey = key2.private.toString('hex'); - n.networkNonces = {}; - n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce - - var message = { - type: 'hello', - copayerId: key1.public.toString('hex') - }; - var messagestr = JSON.stringify(message); - var messagebuf = new Buffer(messagestr); - - var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce - var encoded = n._encode(key2.public, key1, messagebuf, opts); - 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 not reject data sent from a peer with a really big new nonce', function() { - var n = new WebRTC(); - n.privkey = key2.private.toString('hex'); - n.networkNonces = {}; - n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000001', 'hex'); //previously used nonce - - var message = { - type: 'hello', - copayerId: key1.public.toString('hex') - }; - var messagestr = JSON.stringify(message); - var messagebuf = new Buffer(messagestr); - - var opts = {nonce: new Buffer('5000000000000002', 'hex')}; //message send with new nonce - var encoded = n._encode(key2.public, key1, messagebuf, opts); - 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 an outdated nonce', function() { - var n = new WebRTC(); - n.privkey = key2.private.toString('hex'); - n.networkNonces = {}; - n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('0000000000000002', 'hex'); //previously used nonce - - var message = { - type: 'hello', - copayerId: key1.public.toString('hex') - }; - var messagestr = JSON.stringify(message); - var messagebuf = new Buffer(messagestr); - - var opts = {nonce: new Buffer('0000000000000001', 'hex')}; //message send with old nonce - var encoded = n._encode(key2.public, key1, messagebuf, opts); - 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); - }); - - it('should reject data sent from a peer with a really big outdated nonce', function() { - var n = new WebRTC(); - n.privkey = key2.private.toString('hex'); - n.networkNonces = {}; - n.networkNonces[(new bitcore.SIN(key1.public)).toString()] = new Buffer('5000000000000002', 'hex'); //previously used nonce - - var message = { - type: 'hello', - copayerId: key1.public.toString('hex') - }; - var messagestr = JSON.stringify(message); - var messagebuf = new Buffer(messagestr); - - var opts = {nonce: new Buffer('5000000000000001', 'hex')}; //message send with old nonce - var encoded = n._encode(key2.public, key1, messagebuf, opts); - 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); - }); - - }); - - describe('#setHexNonce', function() { - - it('should set a nonce from a hex value', function() { - var hex = '0000000000000000'; - var n = new WebRTC(); - n.setHexNonce(hex); - n.getHexNonce().should.equal(hex); - n.networkNonce.toString('hex').should.equal(hex); - }); - - }); - - describe('#setHexNonces', function() { - - it('should set a nonce from a hex value', function() { - var hex = '0000000000000000'; - var n = new WebRTC(); - n.setHexNonces({fakeid: hex}); - n.getHexNonces().fakeid.should.equal(hex); - }); - - }); - - describe('#getHexNonce', function() { - - it('should get a nonce hex value', function() { - var hex = '0000000000000000'; - var n = new WebRTC(); - n.setHexNonce(hex); - n.getHexNonce().should.equal(hex); - }); - - }); - - describe('#getHexNonces', function() { - - it('should get a nonce from a hex value', function() { - var hex = '0000000000000000'; - var n = new WebRTC(); - n.setHexNonces({fakeid: hex}); - n.getHexNonces().fakeid.should.equal(hex); - }); - - }); - - describe('#iterateNonce', function() { - - it('should set a nonce not already set', function() { - var n = new WebRTC(); - n.iterateNonce(); - n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); - n.networkNonce.slice(0, 4).toString('hex').should.not.equal('00000000'); - }); - - it('called twice should increment', function() { - var n = new WebRTC(); - n.iterateNonce(); - n.networkNonce.slice(4, 8).toString('hex').should.equal('00000001'); - n.iterateNonce(); - n.networkNonce.slice(4, 8).toString('hex').should.equal('00000002'); - }); - - it('should set the first byte to the most significant "now" digit', function() { - var n = new WebRTC(); - n.iterateNonce(); - var buf = new Buffer(4); - buf.writeUInt32BE(Math.floor(Date.now()/1000), 0); - n.networkNonce[0].should.equal(buf[0]); - }); - - }); - -}); diff --git a/test/unit/controllers/controllersSpec.js b/test/unit/controllers/controllersSpec.js index 02ae10eb9..c056246c8 100644 --- a/test/unit/controllers/controllersSpec.js +++ b/test/unit/controllers/controllersSpec.js @@ -175,12 +175,12 @@ describe("Unit: Controllers", function() { }); it('should validate address with network', function() { - form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); expect(form.newaddress.$invalid).to.equal(false); }); it('should not validate address with other network', function() { - form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); + form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); expect(form.newaddress.$invalid).to.equal(true); }); @@ -199,7 +199,7 @@ describe("Unit: Controllers", function() { }); it('should create a transaction proposal with given values', function() { - sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.amount.$setViewValue(1000); var spy = sinon.spy(scope.wallet, 'createTx'); @@ -210,7 +210,7 @@ describe("Unit: Controllers", function() { sinon.assert.callCount(spy, 1); sinon.assert.callCount(spy2, 0); sinon.assert.callCount(scope.loadTxs, 1); - spy.getCall(0).args[0].should.equal('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + spy.getCall(0).args[0].should.equal('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); spy.getCall(0).args[1].should.equal(1000 * config.unitToSatoshi); (typeof spy.getCall(0).args[2]).should.equal('undefined'); }); @@ -219,7 +219,7 @@ describe("Unit: Controllers", function() { it('should handle big values in 100 BTC', function() { var old = config.unitToSatoshi; config.unitToSatoshi = 100000000;; - sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.amount.$setViewValue(100); var spy = sinon.spy(scope.wallet, 'createTx'); scope.loadTxs = sinon.spy(); @@ -232,7 +232,7 @@ describe("Unit: Controllers", function() { it('should handle big values in 5000 BTC', function() { var old = config.unitToSatoshi; config.unitToSatoshi = 100000000;; - sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.amount.$setViewValue(5000); var spy = sinon.spy(scope.wallet, 'createTx'); scope.loadTxs = sinon.spy(); @@ -245,7 +245,7 @@ describe("Unit: Controllers", function() { it('should create and send a transaction proposal', function() { - sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm'); + sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy'); sendForm.amount.$setViewValue(1000); scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1; var spy = sinon.spy(scope.wallet, 'createTx'); @@ -322,7 +322,7 @@ describe("Unit: Controllers", function() { it('should return networkName', function() { $httpBackend.flush(); // need flush var networkName = scope.networkName; - expect(networkName).equal('livenet'); + expect(networkName).equal('testnet'); }); }); diff --git a/util/build.js b/util/build.js index 1a7f0a9c1..5495a04a2 100644 --- a/util/build.js +++ b/util/build.js @@ -44,9 +44,6 @@ var createBundle = function(opts) { b.require('./js/models/core/WalletLock', { expose: '../js/models/core/WalletLock' }); - b.require('./js/models/network/WebRTC', { - expose: '../js/models/network/WebRTC' - }); b.require('./js/models/blockchain/Insight', { expose: '../js/models/blockchain/Insight' }); @@ -79,9 +76,6 @@ var createBundle = function(opts) { b.require('./test/mocks/FakeLocalStorage', { expose: './mocks/FakeLocalStorage' }); - b.require('./js/models/core/Message', { - expose: '../js/models/core/Message' - }); b.require('./test/mocks/FakeBlockchain', { expose: './mocks/FakeBlockchain' }); diff --git a/views/includes/video.html b/views/includes/video.html index 11e04ccb6..9c1b7e7b2 100644 --- a/views/includes/video.html +++ b/views/includes/video.html @@ -3,13 +3,13 @@ {{copayer}} diff --git a/views/settings.html b/views/settings.html index 47f92935a..2b231322b 100644 --- a/views/settings.html +++ b/views/settings.html @@ -47,22 +47,6 @@ -
- PeerJS server - - - - - - - - - -

- PeerJS Server is open-source software. You can run your own instance, or use PeerJS Server cloud. Check PeerJS Server -

-
-
« Back