mirror of https://github.com/BTCPrivate/copay.git
starting with async networking
This commit is contained in:
parent
dea8d778ee
commit
7dc37a272a
|
@ -0,0 +1,410 @@
|
|||
'use strict';
|
||||
|
||||
var imports = require('soop').imports();
|
||||
var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
|
||||
var bitcore = require('bitcore');
|
||||
var AuthMessage = bitcore.AuthMessage;
|
||||
var util = bitcore.util;
|
||||
var extend = require('util')._extend;
|
||||
var io = require('socket.io-client');
|
||||
|
||||
/*
|
||||
* 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.host = opts.host || 'localhost';
|
||||
this.port = opts.port || 3001;
|
||||
this.retryDelay = opts.retryDelay || 3000;
|
||||
this.reconnectAttempts = opts.reconnectAttempts || 3;
|
||||
this.cleanUp();
|
||||
}
|
||||
|
||||
Network.parent = 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.closing = 0;
|
||||
this.tries = 0;
|
||||
this.removeAllListeners();
|
||||
};
|
||||
|
||||
Network.parent = EventEmitter;
|
||||
|
||||
// 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.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._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, 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._onMessage = function(enc) {
|
||||
var key = this.getKey();
|
||||
|
||||
try {
|
||||
var prevnonce = this.networkNonces ? this.networkNonces[peerId] : 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[peerId] = decoded.nonce;
|
||||
|
||||
var payload = decoded.payload;
|
||||
} catch (e) {
|
||||
this._deletePeer(peerId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.allowedCopayerIds && !this.allowedCopayerIds[payload.copayerId]) {
|
||||
this._deletePeer(peerId);
|
||||
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(toCopayerId) {
|
||||
preconditions.checkState(this.socket);
|
||||
var self = this;
|
||||
|
||||
var isInbound = toCopayerId ? false : true;
|
||||
|
||||
self.socket.on('connect', function() {
|
||||
console.log('CONNECTED!');
|
||||
self.socket.on('disconnect', function() {
|
||||
console.log('DISCONNECTED');
|
||||
self.cleanUp();
|
||||
});
|
||||
});
|
||||
self.socket.on('message', self._onMessage);
|
||||
self.socket.on('error', self._handlePeerError);
|
||||
|
||||
};
|
||||
|
||||
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._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.socket) {
|
||||
self.socket.destroy();
|
||||
self.socket.removeAllListeners();
|
||||
}
|
||||
|
||||
if (!self.criticalError && self.tries < self.reconnectAttempts) {
|
||||
self.tries++;
|
||||
self.opts.token = util.sha256(self.peerId).toString('hex');
|
||||
self.socket = io.connect(self.host, {
|
||||
port: self.port
|
||||
});
|
||||
self.socket.emit('subscribe', pubkey);
|
||||
self.socket.emit('sync', ts);
|
||||
self.started = true;
|
||||
self._setupConnectionHandlers(self.socket, copayerId);
|
||||
|
||||
setTimeout(setupPeer, self.retryDelay); // 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.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 l = copayerIds.length;
|
||||
var i = 0;
|
||||
copayerIds.forEach(function(copayerId) {
|
||||
self.iterateNonce();
|
||||
var opts = {
|
||||
nonce: self.networkNonce
|
||||
};
|
||||
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
||||
var message = AuthMessage.encode(copayerIdBuf, self.getKey(), payload, opts);
|
||||
self.socket.emit('message', message);
|
||||
if (++i === l && typeof cb === 'function') cb();
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Network.prototype.isOnline = function() {
|
||||
return !!this.socket;
|
||||
};
|
||||
|
||||
|
||||
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 = require('soop')(Network);
|
|
@ -53,10 +53,7 @@
|
|||
"grunt-contrib-uglify": "^0.5.1",
|
||||
"grunt-contrib-watch": "0.5.3",
|
||||
"grunt-markdown": "0.5.0",
|
||||
"browser-pack": "2.0.1",
|
||||
"bitcore": "0.1.35",
|
||||
"node-cryptojs-aes": "0.4.0",
|
||||
"blanket": "1.1.6",
|
||||
"grunt-mocha-test": "0.8.2",
|
||||
"grunt-shell": "0.6.4",
|
||||
"istanbul": "0.2.10",
|
||||
|
@ -69,6 +66,8 @@
|
|||
"mocha-lcov-reporter": "0.0.1",
|
||||
"mock-fs": "^2.3.1",
|
||||
"node-cryptojs-aes": "0.4.0",
|
||||
"socket.io-client": "^1.0.6",
|
||||
"soop": "0.1.5",
|
||||
"travis-cov": "0.2.5",
|
||||
"uglifyify": "1.2.3"
|
||||
},
|
||||
|
|
|
@ -0,0 +1,507 @@
|
|||
'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 Async = require('../js/models/network/Async');
|
||||
|
||||
describe('Network / Async', function() {
|
||||
|
||||
it('should create an instance', function() {
|
||||
var n = new Async();
|
||||
should.exist(n);
|
||||
});
|
||||
|
||||
describe('#Async constructor', function() {
|
||||
|
||||
it('should set reconnect attempts', function() {
|
||||
var n = new Async();
|
||||
n.reconnectAttempts.should.equal(3);
|
||||
});
|
||||
|
||||
it('should call cleanUp', function() {
|
||||
var save = Async.prototype.cleanUp;
|
||||
Async.prototype.cleanUp = sinon.spy();
|
||||
var n = new Async();
|
||||
n.cleanUp.calledOnce.should.equal(true);
|
||||
Async.prototype.cleanUp = save;
|
||||
});
|
||||
});
|
||||
|
||||
describe('#cleanUp', function() {
|
||||
|
||||
it('should not set netKey', function() {
|
||||
var n = new Async();
|
||||
(n.netKey === undefined).should.equal(true);
|
||||
});
|
||||
|
||||
it('should set privkey to null', function() {
|
||||
var n = new Async();
|
||||
n.cleanUp();
|
||||
expect(n.privkey).to.equal(null);
|
||||
});
|
||||
|
||||
it('should remove handlers', function() {
|
||||
var n = new Async();
|
||||
var save = Async.prototype.removeAllListeners;
|
||||
var spy = Async.prototype.removeAllListeners = sinon.spy();
|
||||
n.cleanUp();
|
||||
spy.calledOnce.should.equal(true);
|
||||
Async.prototype.removeAllListeners = save;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('#_setupPeerHandlers', function() {
|
||||
var n = new Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
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 Async();
|
||||
n.iterateNonce();
|
||||
var buf = new Buffer(4);
|
||||
buf.writeUInt32BE(Math.floor(Date.now()/1000), 0);
|
||||
n.networkNonce[0].should.equal(buf[0]);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
Loading…
Reference in New Issue