mirror of https://github.com/BTCPrivate/copay.git
Merge pull request #1065 from maraoz/ref/broker
Refactor networking, use insight message broker
This commit is contained in:
commit
f63e17eaf3
74
config.js
74
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,
|
||||
},
|
||||
|
|
2
copay.js
2
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');
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
|
@ -1,6 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
|
||||
var preconditions = require('preconditions').instance();
|
||||
var bitcore = require('bitcore');
|
||||
var HK = bitcore.HierarchicalKey;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
},
|
||||
|
|
|
@ -15,8 +15,7 @@
|
|||
<script src="../lib/bitcore.js"></script>
|
||||
<script src="../js/copayBundle.js"></script>
|
||||
<script src="test.blockchain.Insight.js"></script>
|
||||
<script src="test.Message.js"></script>
|
||||
<script src="test.network.WebRTC.js"></script>
|
||||
<script src="test.network.Async.js"></script>
|
||||
<script src="test.PayPro.js"></script>
|
||||
<script src="test.PrivateKey.js"></script>
|
||||
<script src="test.PublicKeyRing.js"></script>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -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'
|
||||
});
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<video
|
||||
ng-if="hasVideo(copayer)"
|
||||
peer="{{copayer}}" avatar autoplay
|
||||
ng-class="isConnected(copayer) ? 'online' : 'offline'"
|
||||
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
|
||||
ng-src="{{getVideoURL(copayer)}}"></video>
|
||||
|
||||
<img
|
||||
class="br100 no-video"
|
||||
ng-if="!hasVideo(copayer)"
|
||||
ng-class="isConnected(copayer) ? 'online' : 'offline'"
|
||||
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
|
||||
src="./img/satoshi.gif"
|
||||
alt="{{copayer}}"
|
||||
width="70">
|
||||
|
|
|
@ -47,22 +47,6 @@
|
|||
|
||||
</fieldset>
|
||||
|
||||
<fieldset>
|
||||
<legend>PeerJS server</legend>
|
||||
<label for="peerjs-key">Key</label>
|
||||
<input type="text" ng-model="networkKey" class="form-control" name="peerjs-key">
|
||||
<label for="peerjs-host">Host</label>
|
||||
<input type="text" ng-model="networkHost" class="form-control" name="peerjs-host">
|
||||
<label for="peerjs-port">Port</label>
|
||||
<input type="number" ng-model="networkPort" class="form-control" name="peerjs-port">
|
||||
<input id="peerjs-secure" type="checkbox" ng-model="networkSecure" class="form-control">
|
||||
<label for="peerjs-secure">Use SSL</label>
|
||||
|
||||
<p class="small">
|
||||
PeerJS Server is open-source software. You can run your own instance, or use PeerJS Server cloud. Check <a href="http://peerjs.com" target="_blank">PeerJS Server</a>
|
||||
</p>
|
||||
</fieldset>
|
||||
|
||||
<div class="text-right">
|
||||
<a class="back-button text-white m20r" href="#!/">« Back</a>
|
||||
<button type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">
|
||||
|
|
Loading…
Reference in New Issue