Merge pull request #1065 from maraoz/ref/broker
Refactor networking, use insight message broker
This commit is contained in:
commit
f63e17eaf3
23 changed files with 1126 additions and 1864 deletions
74
config.js
74
config.js
|
|
@ -1,7 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
var defaultConfig = {
|
var defaultConfig = {
|
||||||
// DEFAULT network (livenet or testnet)
|
// DEFAULT network (livenet or testnet)
|
||||||
networkName: 'livenet',
|
networkName: 'testnet',
|
||||||
forceNetwork: false,
|
forceNetwork: false,
|
||||||
|
|
||||||
// DEFAULT unit: Bit
|
// DEFAULT unit: Bit
|
||||||
|
|
@ -15,71 +15,11 @@ var defaultConfig = {
|
||||||
minAmountSatoshi: 5400,
|
minAmountSatoshi: 5400,
|
||||||
},
|
},
|
||||||
|
|
||||||
// network layer (PeerJS) config
|
// network layer config
|
||||||
network: {
|
network: {
|
||||||
// Use this to run your own local PeerJS server
|
host: 'test-insight.bitpay.com',
|
||||||
// with params: ./peerjs -p 10009 -k '6d6d751ea61e26f2'
|
port: 443,
|
||||||
/*
|
schema: 'https'
|
||||||
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'
|
|
||||||
// }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// wallet default config
|
// wallet default config
|
||||||
|
|
@ -96,14 +36,14 @@ path: '/',
|
||||||
// blockchain service API config
|
// blockchain service API config
|
||||||
blockchain: {
|
blockchain: {
|
||||||
schema: 'https',
|
schema: 'https',
|
||||||
host: 'insight.bitpay.com',
|
host: 'test-insight.bitpay.com',
|
||||||
port: 443,
|
port: 443,
|
||||||
retryDelay: 1000,
|
retryDelay: 1000,
|
||||||
},
|
},
|
||||||
// socket service API config
|
// socket service API config
|
||||||
socket: {
|
socket: {
|
||||||
schema: 'https',
|
schema: 'https',
|
||||||
host: 'insight.bitpay.com',
|
host: 'test-insight.bitpay.com',
|
||||||
port: 443,
|
port: 443,
|
||||||
reconnectDelay: 1000,
|
reconnectDelay: 1000,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
2
copay.js
2
copay.js
|
|
@ -9,7 +9,7 @@ module.exports.HDParams = require('./js/models/core/HDParams');
|
||||||
|
|
||||||
|
|
||||||
// components
|
// 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 Insight = module.exports.Insight = require('./js/models/blockchain/Insight');
|
||||||
var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('./js/models/storage/LocalEncrypted');
|
var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('./js/models/storage/LocalEncrypted');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -52,10 +52,9 @@ angular.module('copayApp.controllers').controller('SettingsController', function
|
||||||
|
|
||||||
$scope.save = function() {
|
$scope.save = function() {
|
||||||
var network = config.network;
|
var network = config.network;
|
||||||
network.key = $scope.networkKey;
|
network.host = $scope.insightHost;
|
||||||
network.host = $scope.networkHost;
|
network.port = $scope.insightPort;
|
||||||
network.port = $scope.networkPort;
|
network.schema = $scope.insightSecure ? 'https' : 'http';
|
||||||
network.secure = $scope.networkSecure;
|
|
||||||
|
|
||||||
localStorage.setItem('config', JSON.stringify({
|
localStorage.setItem('config', JSON.stringify({
|
||||||
networkName: $scope.networkName,
|
networkName: $scope.networkName,
|
||||||
|
|
|
||||||
|
|
@ -256,7 +256,7 @@ Insight.prototype._requestBrowser = function(options, callback) {
|
||||||
errTxt = 'CRITICAL: Wrong response from insight' + e2;
|
errTxt = 'CRITICAL: Wrong response from insight' + e2;
|
||||||
}
|
}
|
||||||
} else if (request.status >= 400 && request.status < 499) {
|
} 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 {
|
} else {
|
||||||
errTxt = 'Error code: ' + request.status + ' - Status: ' + request.statusText + ' - Description: ' + request.responseText;
|
errTxt = 'Error code: ' + request.status + ' - Status: ' + request.statusText + ' - Description: ' + request.responseText;
|
||||||
setTimeout(function() {
|
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';
|
'use strict';
|
||||||
|
|
||||||
|
|
||||||
var preconditions = require('preconditions').instance();
|
var preconditions = require('preconditions').instance();
|
||||||
var bitcore = require('bitcore');
|
var bitcore = require('bitcore');
|
||||||
var HK = bitcore.HierarchicalKey;
|
var HK = bitcore.HierarchicalKey;
|
||||||
|
|
|
||||||
|
|
@ -33,20 +33,16 @@ function Wallet(opts) {
|
||||||
'publicKeyRing', 'txProposals', 'privateKey', 'version',
|
'publicKeyRing', 'txProposals', 'privateKey', 'version',
|
||||||
'reconnectDelay'
|
'reconnectDelay'
|
||||||
].forEach(function(k) {
|
].forEach(function(k) {
|
||||||
if (typeof opts[k] === 'undefined')
|
preconditions.checkArgument(typeof opts[k] !== 'undefined',
|
||||||
throw new Error('missing required option for Wallet: ' + k);
|
'missing required option for Wallet: ' + k);
|
||||||
self[k] = opts[k];
|
self[k] = opts[k];
|
||||||
});
|
});
|
||||||
if (copayConfig.forceNetwork && this.getNetworkName() !== copayConfig.networkName)
|
preconditions.checkArgument(!copayConfig.forceNetwork || this.getNetworkName() === copayConfig.networkName,
|
||||||
throw new Error('Network forced to ' + copayConfig.networkName +
|
'Network forced to ' + copayConfig.networkName +
|
||||||
' and tried to create a Wallet with network ' + this.getNetworkName());
|
' and tried to create a Wallet with network ' + this.getNetworkName());
|
||||||
|
|
||||||
this.log('creating ' + opts.requiredCopayers + ' of ' + opts.totalCopayers + ' wallet');
|
|
||||||
|
|
||||||
this.id = opts.id || Wallet.getRandomId();
|
this.id = opts.id || Wallet.getRandomId();
|
||||||
this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
|
this.lock = new WalletLock(this.storage, this.id, opts.lockTimeOutMin);
|
||||||
|
|
||||||
|
|
||||||
this.name = opts.name;
|
this.name = opts.name;
|
||||||
|
|
||||||
this.verbose = opts.verbose;
|
this.verbose = opts.verbose;
|
||||||
|
|
@ -56,6 +52,7 @@ function Wallet(opts) {
|
||||||
this.registeredPeerIds = [];
|
this.registeredPeerIds = [];
|
||||||
this.addressBook = opts.addressBook || {};
|
this.addressBook = opts.addressBook || {};
|
||||||
this.publicKey = this.privateKey.publicHex;
|
this.publicKey = this.privateKey.publicHex;
|
||||||
|
this.lastTimestamp = opts.lastTimestamp || undefined;
|
||||||
|
|
||||||
this.paymentRequests = opts.paymentRequests || {};
|
this.paymentRequests = opts.paymentRequests || {};
|
||||||
|
|
||||||
|
|
@ -89,7 +86,10 @@ Wallet.prototype.seedCopayer = function(pubKey) {
|
||||||
this.seededCopayerId = pubKey;
|
this.seededCopayerId = pubKey;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// not being used now
|
||||||
Wallet.prototype.connectToAll = function() {
|
Wallet.prototype.connectToAll = function() {
|
||||||
|
// not being used now
|
||||||
|
return;
|
||||||
|
|
||||||
var all = this.publicKeyRing.getAllCopayerIds();
|
var all = this.publicKeyRing.getAllCopayerIds();
|
||||||
this.network.connectToCopayers(all);
|
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);
|
this.log('RECV INDEXES:', data);
|
||||||
var inIndexes = HDParams.fromList(data.indexes);
|
var inIndexes = HDParams.fromList(data.indexes);
|
||||||
var hasChanged = this.publicKeyRing.mergeIndexes(inIndexes);
|
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);
|
this.log('RECV PUBLICKEYRING:', data);
|
||||||
|
|
||||||
var inPKR = PublicKeyRing.fromObj(data.publicKeyRing);
|
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;
|
var self = this;
|
||||||
this.log('RECV TXPROPOSAL: ', data);
|
this.log('RECV TXPROPOSAL: ', data);
|
||||||
var m;
|
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);
|
preconditions.checkState(data.ntxid);
|
||||||
this.log('RECV REJECT:', data);
|
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);
|
preconditions.checkState(data.ntxid);
|
||||||
this.log('RECV SEEN:', data);
|
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);
|
preconditions.checkState(data.addressBook);
|
||||||
this.log('RECV ADDRESSBOOK:', data);
|
this.log('RECV ADDRESSBOOK:', data);
|
||||||
var rcv = data.addressBook;
|
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) {
|
if (data.type !== 'walletId' && this.id !== data.walletId) {
|
||||||
this.emit('badMessage', senderId);
|
this.emit('corrupt', senderId);
|
||||||
this.log('badMessage FROM:', senderId);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -336,27 +348,31 @@ Wallet.prototype._handleData = function(senderId, data, isInbound) {
|
||||||
this.sendAllTxProposals(senderId); // send old txps
|
this.sendAllTxProposals(senderId); // send old txps
|
||||||
break;
|
break;
|
||||||
case 'publicKeyRing':
|
case 'publicKeyRing':
|
||||||
this._handlePublicKeyRing(senderId, data, isInbound);
|
this._onPublicKeyRing(senderId, data);
|
||||||
break;
|
break;
|
||||||
case 'reject':
|
case 'reject':
|
||||||
this._handleReject(senderId, data, isInbound);
|
this._onReject(senderId, data);
|
||||||
break;
|
break;
|
||||||
case 'seen':
|
case 'seen':
|
||||||
this._handleSeen(senderId, data, isInbound);
|
this._onSeen(senderId, data);
|
||||||
break;
|
break;
|
||||||
case 'txProposal':
|
case 'txProposal':
|
||||||
this._handleTxProposal(senderId, data, isInbound);
|
this._onTxProposal(senderId, data);
|
||||||
break;
|
break;
|
||||||
case 'indexes':
|
case 'indexes':
|
||||||
this._handleIndexes(senderId, data, isInbound);
|
this._onIndexes(senderId, data);
|
||||||
break;
|
break;
|
||||||
case 'addressbook':
|
case 'addressbook':
|
||||||
this._handleAddressBook(senderId, data, isInbound);
|
this._onAddressBook(senderId, data);
|
||||||
|
break;
|
||||||
|
case 'disconnect':
|
||||||
|
this._onDisconnect(senderId, data);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype._handleConnect = function(newCopayerId) {
|
Wallet.prototype._onConnect = function(newCopayerId) {
|
||||||
if (newCopayerId) {
|
if (newCopayerId) {
|
||||||
this.log('#### Setting new COPAYER:', newCopayerId);
|
this.log('#### Setting new COPAYER:', newCopayerId);
|
||||||
this.sendWalletId(newCopayerId);
|
this.sendWalletId(newCopayerId);
|
||||||
|
|
@ -365,7 +381,7 @@ Wallet.prototype._handleConnect = function(newCopayerId) {
|
||||||
this.emit('connect', peerID);
|
this.emit('connect', peerID);
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype._handleDisconnect = function(peerID) {
|
Wallet.prototype._onDisconnect = function(peerID) {
|
||||||
this.currentDelay = null;
|
this.currentDelay = null;
|
||||||
this.emit('disconnect', peerID);
|
this.emit('disconnect', peerID);
|
||||||
};
|
};
|
||||||
|
|
@ -427,15 +443,8 @@ Wallet.prototype.netStart = function(callback) {
|
||||||
var net = this.network;
|
var net = this.network;
|
||||||
|
|
||||||
net.removeAllListeners();
|
net.removeAllListeners();
|
||||||
net.on('connect', self._handleConnect.bind(self));
|
net.on('connect', self._onConnect.bind(self));
|
||||||
net.on('disconnect', self._handleDisconnect.bind(self));
|
net.on('data', self._onData.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);
|
|
||||||
});
|
|
||||||
|
|
||||||
var myId = self.getMyCopayerId();
|
var myId = self.getMyCopayerId();
|
||||||
var myIdPriv = self.getMyCopayerIdPriv();
|
var myIdPriv = self.getMyCopayerIdPriv();
|
||||||
|
|
@ -443,7 +452,8 @@ Wallet.prototype.netStart = function(callback) {
|
||||||
var startOpts = {
|
var startOpts = {
|
||||||
copayerId: myId,
|
copayerId: myId,
|
||||||
privkey: myIdPriv,
|
privkey: myIdPriv,
|
||||||
maxPeers: self.totalCopayers
|
maxPeers: self.totalCopayers,
|
||||||
|
lastTimestamp: this.lastTimestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.publicKeyRing.isComplete()) {
|
if (this.publicKeyRing.isComplete()) {
|
||||||
|
|
@ -454,12 +464,15 @@ Wallet.prototype.netStart = function(callback) {
|
||||||
self.emit('ready', net.getPeer());
|
self.emit('ready', net.getPeer());
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
self.emit('publicKeyRingUpdated', true);
|
self.emit('publicKeyRingUpdated', true);
|
||||||
self.scheduleConnect();
|
//self.scheduleConnect();
|
||||||
|
// no connection logic for now
|
||||||
self.emit('txProposalsUpdated');
|
self.emit('txProposalsUpdated');
|
||||||
}, 10);
|
}, 10);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// not being used now
|
||||||
Wallet.prototype.scheduleConnect = function() {
|
Wallet.prototype.scheduleConnect = function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
if (self.network.isOnline()) {
|
if (self.network.isOnline()) {
|
||||||
|
|
@ -533,6 +546,7 @@ Wallet.prototype.toObj = function() {
|
||||||
txProposals: this.txProposals.toObj(),
|
txProposals: this.txProposals.toObj(),
|
||||||
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
|
privateKey: this.privateKey ? this.privateKey.toObj() : undefined,
|
||||||
addressBook: this.addressBook,
|
addressBook: this.addressBook,
|
||||||
|
lastTimestamp: this.lastTimestamp,
|
||||||
};
|
};
|
||||||
|
|
||||||
return walletObj;
|
return walletObj;
|
||||||
|
|
@ -544,16 +558,17 @@ Wallet.fromObj = function(o, storage, network, blockchain) {
|
||||||
|
|
||||||
opts.addressBook = o.addressBook;
|
opts.addressBook = o.addressBook;
|
||||||
|
|
||||||
if (o.privateKey)
|
if (o.privateKey) {
|
||||||
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
opts.privateKey = PrivateKey.fromObj(o.privateKey);
|
||||||
else
|
} else {
|
||||||
opts.privateKey = new PrivateKey({
|
opts.privateKey = new PrivateKey({
|
||||||
networkName: opts.networkName
|
networkName: opts.networkName
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (o.publicKeyRing)
|
if (o.publicKeyRing) {
|
||||||
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
opts.publicKeyRing = PublicKeyRing.fromObj(o.publicKeyRing);
|
||||||
else {
|
} else {
|
||||||
opts.publicKeyRing = new PublicKeyRing({
|
opts.publicKeyRing = new PublicKeyRing({
|
||||||
networkName: opts.networkName,
|
networkName: opts.networkName,
|
||||||
requiredCopayers: opts.requiredCopayers,
|
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);
|
opts.txProposals = TxProposals.fromObj(o.txProposals, Wallet.builderOpts);
|
||||||
else
|
} else {
|
||||||
opts.txProposals = new TxProposals({
|
opts.txProposals = new TxProposals({
|
||||||
networkName: this.networkName,
|
networkName: this.networkName,
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
opts.lastTimestamp = o.lastTimestamp;
|
||||||
|
|
||||||
opts.storage = storage;
|
opts.storage = storage;
|
||||||
opts.network = network;
|
opts.network = network;
|
||||||
|
|
@ -627,7 +645,9 @@ Wallet.prototype.sendReject = function(ntxid) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Wallet.prototype.sendWalletReady = function(recipients) {
|
Wallet.prototype.sendWalletReady = function(recipients) {
|
||||||
|
preconditions.checkArgument(recipients);
|
||||||
this.log('### SENDING WalletReady TO:', recipients);
|
this.log('### SENDING WalletReady TO:', recipients);
|
||||||
|
|
||||||
this.send(recipients, {
|
this.send(recipients, {
|
||||||
|
|
@ -707,7 +727,7 @@ Wallet.prototype.getTxProposals = function() {
|
||||||
txp.finallyRejected = true;
|
txp.finallyRejected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txp.readonly && !txp.finallyRejected && !txp.sentTs) {} else {
|
if (!txp.readonly || txp.finallyRejected || txp.sentTs) {
|
||||||
ret.push(txp);
|
ret.push(txp);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1724,7 +1744,7 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
|
||||||
function _while() {
|
function _while() {
|
||||||
return hasActivity;
|
return hasActivity;
|
||||||
},
|
},
|
||||||
function _finnaly(err) {
|
function _finally(err) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
cb(null, lastActive);
|
cb(null, lastActive);
|
||||||
}
|
}
|
||||||
|
|
@ -1735,7 +1755,12 @@ Wallet.prototype.indexDiscovery = function(start, change, copayerIndex, gap, cb)
|
||||||
Wallet.prototype.disconnect = function() {
|
Wallet.prototype.disconnect = function() {
|
||||||
this.log('## DISCONNECTING');
|
this.log('## DISCONNECTING');
|
||||||
this.lock.release();
|
this.lock.release();
|
||||||
this.network.disconnect();
|
var self = this;
|
||||||
|
self.send(null, {
|
||||||
|
type: 'disconnect',
|
||||||
|
walletId: this.id,
|
||||||
|
});
|
||||||
|
self.network.cleanUp();
|
||||||
};
|
};
|
||||||
|
|
||||||
Wallet.prototype.getNetwork = function() {
|
Wallet.prototype.getNetwork = function() {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ var PublicKeyRing = require('./PublicKeyRing');
|
||||||
var PrivateKey = require('./PrivateKey');
|
var PrivateKey = require('./PrivateKey');
|
||||||
var Wallet = require('./Wallet');
|
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 Insight = module.exports.Insight = require('../blockchain/Insight');
|
||||||
var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../storage/LocalEncrypted');
|
var StorageLocalEncrypted = module.exports.StorageLocalEncrypted = require('../storage/LocalEncrypted');
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@ function WalletFactory(config, version) {
|
||||||
config = config || {};
|
config = config || {};
|
||||||
|
|
||||||
this.Storage = config.Storage || StorageLocalEncrypted;
|
this.Storage = config.Storage || StorageLocalEncrypted;
|
||||||
this.Network = config.Network || WebRTC;
|
this.Network = config.Network || Async;
|
||||||
this.Blockchain = config.Blockchain || Insight;
|
this.Blockchain = config.Blockchain || Insight;
|
||||||
|
|
||||||
this.storage = new this.Storage(config.storage);
|
this.storage = new this.Storage(config.storage);
|
||||||
|
|
@ -104,6 +104,7 @@ WalletFactory.prototype.read = function(walletId, skipFields) {
|
||||||
obj.privateKey = s.get(walletId, 'privateKey');
|
obj.privateKey = s.get(walletId, 'privateKey');
|
||||||
obj.addressBook = s.get(walletId, 'addressBook');
|
obj.addressBook = s.get(walletId, 'addressBook');
|
||||||
obj.backupOffered = s.get(walletId, 'backupOffered');
|
obj.backupOffered = s.get(walletId, 'backupOffered');
|
||||||
|
obj.lastTimestamp = s.get(walletId, 'lastTimestamp');
|
||||||
|
|
||||||
var w = this.fromObj(obj, skipFields);
|
var w = this.fromObj(obj, skipFields);
|
||||||
return w;
|
return w;
|
||||||
|
|
@ -249,16 +250,13 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
|
||||||
self.network.on('connected', function(sender, data) {
|
self.network.on('connected', function(sender, data) {
|
||||||
connectedOnce = true;
|
connectedOnce = true;
|
||||||
});
|
});
|
||||||
self.network.on('onlyYou', function(sender, data) {
|
|
||||||
return cb(connectedOnce ? 'walletFull' : 'joinError');
|
|
||||||
});
|
|
||||||
|
|
||||||
self.network.on('serverError', function() {
|
self.network.on('serverError', function() {
|
||||||
return cb('joinError');
|
return cb('joinError');
|
||||||
});
|
});
|
||||||
|
|
||||||
self.network.start(opts, function() {
|
self.network.start(opts, function() {
|
||||||
self.network.connectTo(s.pubKey);
|
self.network.greet(s.pubKey);
|
||||||
|
|
||||||
self.network.on('data', function(sender, data) {
|
self.network.on('data', function(sender, data) {
|
||||||
if (data.type === 'walletId') {
|
if (data.type === 'walletId') {
|
||||||
|
|
@ -271,7 +269,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
|
||||||
data.opts.passphrase = passphrase;
|
data.opts.passphrase = passphrase;
|
||||||
data.opts.id = data.walletId;
|
data.opts.id = data.walletId;
|
||||||
var w = self.create(data.opts);
|
var w = self.create(data.opts);
|
||||||
w.seedCopayer(s.pubKey);
|
w.sendWalletReady(s.pubKey);
|
||||||
|
//w.seedCopayer(s.pubKey);
|
||||||
return cb(null, w);
|
return cb(null, w);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
382
js/models/network/Async.js
Normal file
382
js/models/network/Async.js
Normal file
|
|
@ -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');
|
var bitcore = require('bitcore');
|
||||||
|
|
||||||
angular.module('copayApp.services')
|
angular.module('copayApp.services')
|
||||||
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) {
|
.factory('controllerUtils', function($rootScope, $sce, $location, notification, $timeout, Socket, video, uriHandler) {
|
||||||
var root = {};
|
var root = {};
|
||||||
root.getVideoMutedStatus = function(copayer) {
|
root.getVideoMutedStatus = function(copayer) {
|
||||||
if (!$rootScope.videoInfo) return;
|
if (!$rootScope.videoInfo) return;
|
||||||
|
|
||||||
var vi = $rootScope.videoInfo[copayer]
|
var vi = $rootScope.videoInfo[copayer]
|
||||||
if (!vi) {
|
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];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$rootScope.videoInfo[peerID] = {
|
return vi.muted;
|
||||||
url: encodeURI(url),
|
};
|
||||||
muted: peerID === w.network.peerId
|
|
||||||
};
|
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();
|
$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) {
|
root.setupRootVariables = function() {
|
||||||
notification.error('Error', 'Received wrong message from peer ' + peerId);
|
uriHandler.register();
|
||||||
});
|
$rootScope.unitName = config.unitName;
|
||||||
w.on('ready', function(myPeerID) {
|
$rootScope.txAlertCount = 0;
|
||||||
$rootScope.wallet = w;
|
$rootScope.insightError = 0;
|
||||||
if ($rootScope.pendingPayment) {
|
$rootScope.isCollapsed = true;
|
||||||
$location.path('send');
|
$rootScope.$watch('txAlertCount', function(txAlertCount) {
|
||||||
} else {
|
if (txAlertCount && txAlertCount > 0) {
|
||||||
$location.path('receive');
|
|
||||||
}
|
|
||||||
if (!config.disableVideo)
|
|
||||||
video.setOwnPeer(myPeerID, w, handlePeerVideo);
|
|
||||||
});
|
|
||||||
|
|
||||||
w.on('publicKeyRingUpdated', function(dontDigest) {
|
notification.info('New Transaction', ($rootScope.txAlertCount == 1) ? 'You have a pending transaction proposal' : 'You have ' + $rootScope.txAlertCount + ' pending transaction proposals', txAlertCount);
|
||||||
root.setSocketHandlers();
|
}
|
||||||
if (!dontDigest) {
|
});
|
||||||
$rootScope.$digest();
|
|
||||||
}
|
|
||||||
});
|
$rootScope.$watch('receivedFund', function(receivedFund) {
|
||||||
w.on('txProposalsUpdated', function(dontDigest) {
|
if (receivedFund) {
|
||||||
root.updateTxs();
|
var currentAddr;
|
||||||
// give sometime to the tx to propagate.
|
for (var i = 0; i < $rootScope.addrInfos.length; i++) {
|
||||||
$timeout(function() {
|
var addrinfo = $rootScope.addrInfos[i];
|
||||||
root.updateBalance(function() {
|
if (addrinfo.address.toString() == receivedFund[1] && !addrinfo.isChange) {
|
||||||
if (!dontDigest) {
|
currentAddr = addrinfo.address.toString();
|
||||||
$rootScope.$digest();
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
if (currentAddr) {
|
||||||
}, 3000);
|
//var beep = new Audio('sound/transaction.mp3');
|
||||||
});
|
notification.funds('Received fund', currentAddr, receivedFund);
|
||||||
w.on('txProposalEvent', function(e) {
|
//beep.play();
|
||||||
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;
|
root.startNetwork = function(w, $scope) {
|
||||||
case 'corrupt':
|
Socket.removeAllListeners();
|
||||||
notification.error('Transaction Error', 'Received corrupt transaction from '+user);
|
|
||||||
break;
|
root.setupRootVariables();
|
||||||
}
|
root.installStartupHandlers(w, $scope);
|
||||||
});
|
root.setSocketHandlers();
|
||||||
w.on('addressBookUpdated', function(dontDigest) {
|
|
||||||
if (!dontDigest) {
|
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();
|
$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() {
|
notification.enableHtml5Mode(); // for chrome: if support, enable it
|
||||||
var w = $rootScope.wallet;
|
|
||||||
if (w && w.isReady())
|
|
||||||
$rootScope.addrInfos = w.getAddressesInfo();
|
|
||||||
};
|
|
||||||
|
|
||||||
root.updateBalance = function(cb) {
|
w.on('corrupt', function(peerId) {
|
||||||
var w = $rootScope.wallet;
|
notification.error('Error', 'Received corrupt message from ' + peerId);
|
||||||
if (!w) return root.onErrorDigest();
|
});
|
||||||
if (!w.isReady()) return;
|
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 = {};
|
w.on('publicKeyRingUpdated', function(dontDigest) {
|
||||||
$rootScope.updatingBalance = true;
|
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) {
|
root.updateAddressList = function() {
|
||||||
if (err) {
|
var w = $rootScope.wallet;
|
||||||
console.error('Error: ' + err.message); //TODO
|
if (w && w.isReady())
|
||||||
root._setCommError();
|
$rootScope.addrInfos = w.getAddressesInfo();
|
||||||
return null;
|
};
|
||||||
} else {
|
|
||||||
root._clearCommError();
|
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 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;
|
inT.forEach(function(i, index) {
|
||||||
$rootScope.totalBalanceBTC = (balanceSat / COIN);
|
if (opts.skip && (index < opts.skip[0] || index >= opts.skip[1])) {
|
||||||
$rootScope.availableBalance = safeBalanceSat * satToUnit;
|
return txs.push(null);
|
||||||
$rootScope.availableBalanceBTC = (safeBalanceSat / COIN);
|
}
|
||||||
|
|
||||||
$rootScope.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
|
if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) {
|
||||||
$rootScope.lockedBalanceBTC = (balanceSat - safeBalanceSat) / COIN;
|
pendingForUs++;
|
||||||
|
}
|
||||||
|
if (!i.finallyRejected && !i.sentTs) {
|
||||||
|
i.isPending = 1;
|
||||||
|
}
|
||||||
|
|
||||||
var balanceByAddr = {};
|
if (!!opts.pending == !!i.isPending) {
|
||||||
for (var ii in balanceByAddrSat) {
|
var tx = i.builder.build();
|
||||||
balanceByAddr[ii] = balanceByAddrSat[ii] * satToUnit;
|
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();
|
root.updateAddressList();
|
||||||
$rootScope.updatingBalance = false;
|
if (!Socket.sysEventsSet) {
|
||||||
return cb ? cb() : null;
|
Socket.sysOn('error', root._setCommError);
|
||||||
});
|
Socket.sysOn('reconnect_error', root._setCommError);
|
||||||
};
|
Socket.sysOn('reconnect_failed', root._setCommError);
|
||||||
|
Socket.sysOn('connect', root._clearCommError);
|
||||||
root.updateTxs = function(opts) {
|
Socket.sysOn('reconnect', root._clearCommError);
|
||||||
var w = $rootScope.wallet;
|
Socket.sysEventsSet = true;
|
||||||
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 (!$rootScope.wallet) return;
|
||||||
|
|
||||||
if (myCopayerId != i.creator && !i.finallyRejected && !i.sentTs && !i.rejectedByUs && !i.signedByUs) {
|
var currentAddrs = Socket.getListeners();
|
||||||
pendingForUs++;
|
var allAddrs = $rootScope.addrInfos;
|
||||||
}
|
|
||||||
if (!i.finallyRejected && !i.sentTs) {
|
|
||||||
i.isPending = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!!opts.pending == !!i.isPending) {
|
var newAddrs = [];
|
||||||
var tx = i.builder.build();
|
for (var i in allAddrs) {
|
||||||
var outs = [];
|
var a = allAddrs[i];
|
||||||
tx.outs.forEach(function(o) {
|
if (!currentAddrs[a.addressStr])
|
||||||
var addr = bitcore.Address.fromScriptPubKey(o.getScript(), config.networkName)[0].toString();
|
newAddrs.push(a);
|
||||||
if (!w.addressIsOwn(addr, {
|
}
|
||||||
excludeMain: true
|
for (var i = 0; i < newAddrs.length; i++) {
|
||||||
})) {
|
Socket.emit('subscribe', newAddrs[i].addressStr);
|
||||||
outs.push({
|
}
|
||||||
address: addr,
|
newAddrs.forEach(function(a) {
|
||||||
value: bitcore.util.valueToBigInt(o.getValue()) * satToUnit,
|
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);
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
return root;
|
||||||
$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) + ')');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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-uglify": "^0.5.1",
|
||||||
"grunt-contrib-watch": "0.5.3",
|
"grunt-contrib-watch": "0.5.3",
|
||||||
"grunt-markdown": "0.5.0",
|
"grunt-markdown": "0.5.0",
|
||||||
"browser-pack": "2.0.1",
|
|
||||||
"bitcore": "0.1.35",
|
"bitcore": "0.1.35",
|
||||||
"node-cryptojs-aes": "0.4.0",
|
|
||||||
"blanket": "1.1.6",
|
|
||||||
"grunt-mocha-test": "0.8.2",
|
"grunt-mocha-test": "0.8.2",
|
||||||
"grunt-shell": "0.6.4",
|
"grunt-shell": "0.6.4",
|
||||||
"istanbul": "0.2.10",
|
"istanbul": "0.2.10",
|
||||||
|
|
@ -69,6 +66,7 @@
|
||||||
"mocha-lcov-reporter": "0.0.1",
|
"mocha-lcov-reporter": "0.0.1",
|
||||||
"mock-fs": "^2.3.1",
|
"mock-fs": "^2.3.1",
|
||||||
"node-cryptojs-aes": "0.4.0",
|
"node-cryptojs-aes": "0.4.0",
|
||||||
|
"socket.io-client": "1.0.6",
|
||||||
"travis-cov": "0.2.5",
|
"travis-cov": "0.2.5",
|
||||||
"uglifyify": "1.2.3"
|
"uglifyify": "1.2.3"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,7 @@
|
||||||
<script src="../lib/bitcore.js"></script>
|
<script src="../lib/bitcore.js"></script>
|
||||||
<script src="../js/copayBundle.js"></script>
|
<script src="../js/copayBundle.js"></script>
|
||||||
<script src="test.blockchain.Insight.js"></script>
|
<script src="test.blockchain.Insight.js"></script>
|
||||||
<script src="test.Message.js"></script>
|
<script src="test.network.Async.js"></script>
|
||||||
<script src="test.network.WebRTC.js"></script>
|
|
||||||
<script src="test.PayPro.js"></script>
|
<script src="test.PayPro.js"></script>
|
||||||
<script src="test.PrivateKey.js"></script>
|
<script src="test.PrivateKey.js"></script>
|
||||||
<script src="test.PublicKeyRing.js"></script>
|
<script src="test.PublicKeyRing.js"></script>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ function Network(opts) {}
|
||||||
util.inherits(Network, EventEmitter);
|
util.inherits(Network, EventEmitter);
|
||||||
|
|
||||||
Network.prototype.start = function(opts, cb) {
|
Network.prototype.start = function(opts, cb) {
|
||||||
// start! :D
|
|
||||||
this.peer = {
|
this.peer = {
|
||||||
options: {
|
options: {
|
||||||
token: "asd"
|
token: "asd"
|
||||||
|
|
@ -15,18 +14,12 @@ Network.prototype.start = function(opts, cb) {
|
||||||
if (cb) cb();
|
if (cb) cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype.send = function(peerIds, data, cb) {
|
Network.prototype.send = function(peerIds, data, cb) {};
|
||||||
// send! c:
|
|
||||||
};
|
|
||||||
|
|
||||||
Network.prototype.connectTo = function(peerId) {
|
Network.prototype.connectTo = function(peerId) {};
|
||||||
// connect C:
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
Network.prototype.disconnect = function(cb) {
|
Network.prototype.disconnect = function(cb) {};
|
||||||
// disconect :c
|
|
||||||
};
|
|
||||||
|
|
||||||
Network.prototype.lockIncommingConnections = function() {
|
Network.prototype.lockIncommingConnections = function() {
|
||||||
|
|
||||||
|
|
@ -84,7 +77,7 @@ Network.prototype.iterateNonce = function() {
|
||||||
//the second 4 bytes is just an iterated "sub" nonce
|
//the second 4 bytes is just an iterated "sub" nonce
|
||||||
//the whole thing is interpreted as one big endian number
|
//the whole thing is interpreted as one big endian number
|
||||||
var noncep1 = this.networkNonce.slice(0, 4);
|
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 noncep2uint = this.networkNonce.slice(4, 8).readUInt32BE(0);
|
||||||
var noncep2 = this.networkNonce.slice(4, 8);
|
var noncep2 = this.networkNonce.slice(4, 8);
|
||||||
noncep2.writeUInt32BE(noncep2uint + 1, 0);
|
noncep2.writeUInt32BE(noncep2uint + 1, 0);
|
||||||
|
|
@ -92,4 +85,9 @@ Network.prototype.iterateNonce = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Network.prototype.cleanUp = function() {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
module.exports = Network;
|
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
|
receiveIndex: 2
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
w._handleIndexes('senderID', aiObj, true);
|
w._onIndexes('senderID', aiObj, true);
|
||||||
w.publicKeyRing.getHDParams(0).getReceiveIndex(2);
|
w.publicKeyRing.getHDParams(0).getReceiveIndex(2);
|
||||||
w.publicKeyRing.getHDParams(0).getChangeIndex(3);
|
w.publicKeyRing.getHDParams(0).getChangeIndex(3);
|
||||||
});
|
});
|
||||||
|
|
@ -467,7 +467,7 @@ describe('Wallet model', function() {
|
||||||
copayersExtPubKeys: cepk,
|
copayersExtPubKeys: cepk,
|
||||||
nicknameFor: {},
|
nicknameFor: {},
|
||||||
};
|
};
|
||||||
w._handlePublicKeyRing('senderID', {
|
w._onPublicKeyRing('senderID', {
|
||||||
publicKeyRing: pkrObj
|
publicKeyRing: pkrObj
|
||||||
}, true);
|
}, true);
|
||||||
w.publicKeyRing.getHDParams(0).getReceiveIndex(2);
|
w.publicKeyRing.getHDParams(0).getReceiveIndex(2);
|
||||||
|
|
@ -512,7 +512,7 @@ describe('Wallet model', function() {
|
||||||
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({
|
var stub = sinon.stub(w.publicKeyRing, 'copayersForPubkeys').returns({
|
||||||
'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe'
|
'027445ab3a935dce7aee1dadb0d103ed6147a0f83deb80474a04538b2c5bc4d509': 'pepe'
|
||||||
});
|
});
|
||||||
w._handleTxProposal('senderID', txp, true);
|
w._onTxProposal('senderID', txp, true);
|
||||||
Object.keys(w.txProposals.txps).length.should.equal(1);
|
Object.keys(w.txProposals.txps).length.should.equal(1);
|
||||||
w.getTxProposals().length.should.equal(1);
|
w.getTxProposals().length.should.equal(1);
|
||||||
//stub.restore();
|
//stub.restore();
|
||||||
|
|
@ -525,7 +525,7 @@ describe('Wallet model', function() {
|
||||||
id.should.equal(newId);
|
id.should.equal(newId);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
w._handleConnect(newId);
|
w._onConnect(newId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('handle disconnections', function(done) {
|
it('handle disconnections', function(done) {
|
||||||
|
|
@ -534,7 +534,7 @@ describe('Wallet model', function() {
|
||||||
id.should.equal(newId);
|
id.should.equal(newId);
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
w._handleDisconnect(newId);
|
w._onDisconnect(newId);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should register new copayers correctly', function() {
|
it('should register new copayers correctly', function() {
|
||||||
|
|
@ -993,7 +993,7 @@ describe('Wallet model', function() {
|
||||||
var senderId = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb";
|
var senderId = "03baa45498fee1045fa8f91a2913f638dc3979b455498924d3cf1a11303c679cdb";
|
||||||
|
|
||||||
Object.keys(w.addressBook).length.should.equal(2);
|
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);
|
Object.keys(w.addressBook).length.should.equal(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1094,7 +1094,7 @@ describe('Wallet model', function() {
|
||||||
var backup = copayConfig.forceNetwork;
|
var backup = copayConfig.forceNetwork;
|
||||||
copayConfig.forceNetwork = true;
|
copayConfig.forceNetwork = true;
|
||||||
config.networkName = 'livenet';
|
config.networkName = 'livenet';
|
||||||
cachedCreateW2.should.throw(Error);
|
createW2.should.throw(Error);
|
||||||
copayConfig.forceNetwork = backup;
|
copayConfig.forceNetwork = backup;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -1252,7 +1252,7 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('_handleTxProposal', function() {
|
describe('_onTxProposal', function() {
|
||||||
var testValidate = function(response, result, done) {
|
var testValidate = function(response, result, done) {
|
||||||
|
|
||||||
var w = cachedCreateW();
|
var w = cachedCreateW();
|
||||||
|
|
@ -1278,7 +1278,7 @@ describe('Wallet model', function() {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
w._handleTxProposal('senderID', txp);
|
w._onTxProposal('senderID', txp);
|
||||||
spy.callCount.should.equal(1);
|
spy.callCount.should.equal(1);
|
||||||
merge.restore();
|
merge.restore();
|
||||||
};
|
};
|
||||||
|
|
@ -1299,11 +1299,11 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('_handleReject', function() {
|
describe('_onReject', function() {
|
||||||
it('should fails if unknown tx', function() {
|
it('should fails if unknown tx', function() {
|
||||||
var w = cachedCreateW();
|
var w = cachedCreateW();
|
||||||
(function() {
|
(function() {
|
||||||
w._handleReject(1, {
|
w._onReject(1, {
|
||||||
ntxid: 1
|
ntxid: 1
|
||||||
}, 1);
|
}, 1);
|
||||||
}).should.throw('Unknown TXP');
|
}).should.throw('Unknown TXP');
|
||||||
|
|
@ -1316,7 +1316,7 @@ describe('Wallet model', function() {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(function() {
|
(function() {
|
||||||
w._handleReject('john', {
|
w._onReject('john', {
|
||||||
ntxid: 'qwerty'
|
ntxid: 'qwerty'
|
||||||
}, 1);
|
}, 1);
|
||||||
}).should.throw('already signed');
|
}).should.throw('already signed');
|
||||||
|
|
@ -1337,7 +1337,7 @@ describe('Wallet model', function() {
|
||||||
var spy2 = sinon.spy(w, 'emit');
|
var spy2 = sinon.spy(w, 'emit');
|
||||||
w.txProposals.txps['qwerty'] = new txp();
|
w.txProposals.txps['qwerty'] = new txp();
|
||||||
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
||||||
w._handleReject('john', {
|
w._onReject('john', {
|
||||||
ntxid: 'qwerty'
|
ntxid: 'qwerty'
|
||||||
}, 1);
|
}, 1);
|
||||||
w.txProposals.txps['qwerty'].ok.should.equal(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() {
|
it('should fails if unknown tx', function() {
|
||||||
var w = cachedCreateW();
|
var w = cachedCreateW();
|
||||||
(function() {
|
(function() {
|
||||||
w._handleReject(1, {
|
w._onReject(1, {
|
||||||
ntxid: 1
|
ntxid: 1
|
||||||
}, 1);
|
}, 1);
|
||||||
}).should.throw('Unknown TXP');
|
}).should.throw('Unknown TXP');
|
||||||
|
|
@ -1378,7 +1378,7 @@ describe('Wallet model', function() {
|
||||||
var spy2 = sinon.spy(w, 'emit');
|
var spy2 = sinon.spy(w, 'emit');
|
||||||
w.txProposals.txps['qwerty'] = new txp();
|
w.txProposals.txps['qwerty'] = new txp();
|
||||||
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
w.txProposals.txps['qwerty'].ok.should.equal(0);
|
||||||
w._handleSeen('john', {
|
w._onSeen('john', {
|
||||||
ntxid: 'qwerty'
|
ntxid: 'qwerty'
|
||||||
}, 1);
|
}, 1);
|
||||||
w.txProposals.txps['qwerty'].ok.should.equal(1);
|
w.txProposals.txps['qwerty'].ok.should.equal(1);
|
||||||
|
|
@ -1402,9 +1402,9 @@ describe('Wallet model', function() {
|
||||||
|
|
||||||
it('#disconnect', function() {
|
it('#disconnect', function() {
|
||||||
var w = cachedCreateW();
|
var w = cachedCreateW();
|
||||||
var spy1 = sinon.spy(w.network, 'disconnect');
|
var spy1 = sinon.spy(w, 'send');
|
||||||
w.disconnect();
|
w.disconnect();
|
||||||
spy1.callCount.should.equal(1);
|
spy1.calledOnce.should.be.true;
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ try {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
var copay = require('../copay'); //node
|
var copay = require('../copay'); //node
|
||||||
}
|
}
|
||||||
|
var Buffer = bitcore.Buffer;
|
||||||
var Insight = copay.Insight || require('../js/models/blockchain/Insight');
|
var Insight = copay.Insight || require('../js/models/blockchain/Insight');
|
||||||
|
|
||||||
var ID = '933bf321393459b7';
|
var ID = '933bf321393459b7';
|
||||||
|
|
|
||||||
285
test/test.network.Async.js
Normal file
285
test/test.network.Async.js
Normal file
|
|
@ -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() {
|
it('should validate address with network', function() {
|
||||||
form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
||||||
expect(form.newaddress.$invalid).to.equal(false);
|
expect(form.newaddress.$invalid).to.equal(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not validate address with other network', function() {
|
it('should not validate address with other network', function() {
|
||||||
form.newaddress.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
form.newaddress.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
||||||
expect(form.newaddress.$invalid).to.equal(true);
|
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() {
|
it('should create a transaction proposal with given values', function() {
|
||||||
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
||||||
sendForm.amount.$setViewValue(1000);
|
sendForm.amount.$setViewValue(1000);
|
||||||
|
|
||||||
var spy = sinon.spy(scope.wallet, 'createTx');
|
var spy = sinon.spy(scope.wallet, 'createTx');
|
||||||
|
|
@ -210,7 +210,7 @@ describe("Unit: Controllers", function() {
|
||||||
sinon.assert.callCount(spy, 1);
|
sinon.assert.callCount(spy, 1);
|
||||||
sinon.assert.callCount(spy2, 0);
|
sinon.assert.callCount(spy2, 0);
|
||||||
sinon.assert.callCount(scope.loadTxs, 1);
|
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);
|
spy.getCall(0).args[1].should.equal(1000 * config.unitToSatoshi);
|
||||||
(typeof spy.getCall(0).args[2]).should.equal('undefined');
|
(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() {
|
it('should handle big values in 100 BTC', function() {
|
||||||
var old = config.unitToSatoshi;
|
var old = config.unitToSatoshi;
|
||||||
config.unitToSatoshi = 100000000;;
|
config.unitToSatoshi = 100000000;;
|
||||||
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
||||||
sendForm.amount.$setViewValue(100);
|
sendForm.amount.$setViewValue(100);
|
||||||
var spy = sinon.spy(scope.wallet, 'createTx');
|
var spy = sinon.spy(scope.wallet, 'createTx');
|
||||||
scope.loadTxs = sinon.spy();
|
scope.loadTxs = sinon.spy();
|
||||||
|
|
@ -232,7 +232,7 @@ describe("Unit: Controllers", function() {
|
||||||
it('should handle big values in 5000 BTC', function() {
|
it('should handle big values in 5000 BTC', function() {
|
||||||
var old = config.unitToSatoshi;
|
var old = config.unitToSatoshi;
|
||||||
config.unitToSatoshi = 100000000;;
|
config.unitToSatoshi = 100000000;;
|
||||||
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
||||||
sendForm.amount.$setViewValue(5000);
|
sendForm.amount.$setViewValue(5000);
|
||||||
var spy = sinon.spy(scope.wallet, 'createTx');
|
var spy = sinon.spy(scope.wallet, 'createTx');
|
||||||
scope.loadTxs = sinon.spy();
|
scope.loadTxs = sinon.spy();
|
||||||
|
|
@ -245,7 +245,7 @@ describe("Unit: Controllers", function() {
|
||||||
|
|
||||||
|
|
||||||
it('should create and send a transaction proposal', function() {
|
it('should create and send a transaction proposal', function() {
|
||||||
sendForm.address.$setViewValue('1JqniWpWNA6Yvdivg3y9izLidETnurxRQm');
|
sendForm.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
|
||||||
sendForm.amount.$setViewValue(1000);
|
sendForm.amount.$setViewValue(1000);
|
||||||
scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1;
|
scope.wallet.totalCopayers = scope.wallet.requiredCopayers = 1;
|
||||||
var spy = sinon.spy(scope.wallet, 'createTx');
|
var spy = sinon.spy(scope.wallet, 'createTx');
|
||||||
|
|
@ -322,7 +322,7 @@ describe("Unit: Controllers", function() {
|
||||||
it('should return networkName', function() {
|
it('should return networkName', function() {
|
||||||
$httpBackend.flush(); // need flush
|
$httpBackend.flush(); // need flush
|
||||||
var networkName = scope.networkName;
|
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', {
|
b.require('./js/models/core/WalletLock', {
|
||||||
expose: '../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', {
|
b.require('./js/models/blockchain/Insight', {
|
||||||
expose: '../js/models/blockchain/Insight'
|
expose: '../js/models/blockchain/Insight'
|
||||||
});
|
});
|
||||||
|
|
@ -79,9 +76,6 @@ var createBundle = function(opts) {
|
||||||
b.require('./test/mocks/FakeLocalStorage', {
|
b.require('./test/mocks/FakeLocalStorage', {
|
||||||
expose: './mocks/FakeLocalStorage'
|
expose: './mocks/FakeLocalStorage'
|
||||||
});
|
});
|
||||||
b.require('./js/models/core/Message', {
|
|
||||||
expose: '../js/models/core/Message'
|
|
||||||
});
|
|
||||||
b.require('./test/mocks/FakeBlockchain', {
|
b.require('./test/mocks/FakeBlockchain', {
|
||||||
expose: './mocks/FakeBlockchain'
|
expose: './mocks/FakeBlockchain'
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
<video
|
<video
|
||||||
ng-if="hasVideo(copayer)"
|
ng-if="hasVideo(copayer)"
|
||||||
peer="{{copayer}}" avatar autoplay
|
peer="{{copayer}}" avatar autoplay
|
||||||
ng-class="isConnected(copayer) ? 'online' : 'offline'"
|
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
|
||||||
ng-src="{{getVideoURL(copayer)}}"></video>
|
ng-src="{{getVideoURL(copayer)}}"></video>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
class="br100 no-video"
|
class="br100 no-video"
|
||||||
ng-if="!hasVideo(copayer)"
|
ng-if="!hasVideo(copayer)"
|
||||||
ng-class="isConnected(copayer) ? 'online' : 'offline'"
|
ng-class="true || isConnected(copayer) ? 'online' : 'offline'"
|
||||||
src="./img/satoshi.gif"
|
src="./img/satoshi.gif"
|
||||||
alt="{{copayer}}"
|
alt="{{copayer}}"
|
||||||
width="70">
|
width="70">
|
||||||
|
|
|
||||||
|
|
@ -47,22 +47,6 @@
|
||||||
|
|
||||||
</fieldset>
|
</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">
|
<div class="text-right">
|
||||||
<a class="back-button text-white m20r" href="#!/">« Back</a>
|
<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()">
|
<button type="submit" class="button primary m0 ng-binding" ng-disabled="setupForm.$invalid || loading" disabled="disabled" ng-click="save()">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue