diff --git a/js/models/core/Message.js b/js/models/core/Message.js index b281eecd8..7c9de9c27 100644 --- a/js/models/core/Message.js +++ b/js/models/core/Message.js @@ -7,9 +7,20 @@ var bitcore = require('bitcore'); var Message = function() { }; -Message.encode = function(topubkey, fromkey, payload) { - var version = new Buffer([0]); - var toencrypt = Buffer.concat([version, payload]); +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 + + if (opts && opts.nonce && Buffer.isBuffer(opts.nonce) && opts.nonce.length == 8) { + var nonce = opts.nonce; + } else { + var nonce = new Buffer(8); + nonce.fill(0); //nonce is a big endian 8 byte number + } + + var toencrypt = Buffer.concat([version1, version2, nonce, payload]); var encrypted = Message._encrypt(topubkey, toencrypt); var sig = Message._sign(fromkey, encrypted); var encoded = { @@ -20,7 +31,14 @@ Message.encode = function(topubkey, fromkey, payload) { return encoded; }; -Message.decode = function(key, encoded) { +Message.decode = function(key, encoded, opts) { + if (opts && opts.prevnonce && Buffer.isBuffer(opts.prevnonce) && opts.prevnonce.length == 8) { + var prevnonce = opts.prevnonce; + } else { + var 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) { @@ -40,8 +58,9 @@ Message.decode = function(key, encoded) { throw new Error('Error verifying signature: ' + e); } - if (!v) + if (!v) { throw new Error('Invalid signature'); + } try { var decrypted = Message._decrypt(key.private, encrypted); @@ -49,14 +68,59 @@ Message.decode = function(key, encoded) { throw new Error('Cannot decrypt data: ' + e); } - if (decrypted[0] !== 0) - throw new Error('Invalid version number'); + 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 (decrypted.length === 0) + if (payload.length === 0) { throw new Error('No data present'); + } - var payload = decrypted.slice(1); - return payload; + 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) { diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js index 91d4092d7..462c258a5 100644 --- a/js/models/network/WebRTC.js +++ b/js/models/network/WebRTC.js @@ -376,18 +376,19 @@ Network.prototype.getPeer = function() { return this.peer; }; -Network.prototype._encode = function(topubkey, fromkey, payload) { - var encoded = Message.encode(topubkey, fromkey, payload); +Network.prototype._encode = function(topubkey, fromkey, payload, opts) { + var encoded = Message.encode(topubkey, fromkey, payload, opts); return encoded; }; -Network.prototype._decode = function(key, encoded) { - var payload = Message.decode(key, encoded); +Network.prototype._decode = function(key, encoded, opts) { + var decoded = Message.decode(key, encoded, opts); + var payload = decoded.payload; return payload; }; -Network.prototype._sendToOne = function(copayerId, payload, cb) { +Network.prototype._sendToOne = function(copayerId, payload, opts, cb) { if (!Buffer.isBuffer(payload)) throw new Error('payload must be a buffer'); var peerId = this.peerFromCopayer(copayerId); @@ -400,7 +401,7 @@ Network.prototype._sendToOne = function(copayerId, payload, cb) { if (typeof cb === 'function') cb(); }; -Network.prototype.send = function(copayerIds, payload, cb) { +Network.prototype.send = function(copayerIds, payload, opts, cb) { if (!payload) return cb(); var self = this; @@ -421,7 +422,7 @@ Network.prototype.send = function(copayerIds, payload, cb) { var copayerIdBuf = new Buffer(copayerId, 'hex'); var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf); var enc = new Buffer(JSON.stringify(encPayload)); - self._sendToOne(copayerId, enc, function() { + self._sendToOne(copayerId, enc, opts, function() { if (++i === l && typeof cb === 'function') cb(); }); }); diff --git a/test/test.Message.js b/test/test.Message.js index 6abe1e3e3..10134b736 100644 --- a/test/test.Message.js +++ b/test/test.Message.js @@ -36,15 +36,70 @@ describe('Message model', function() { var encoded = Message.encode(key2.public, key, message); var decoded = Message.decode(key2, encoded); - decoded.toString('hex').should.equal(messagehex); + 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 version = new Buffer([1]); - var toencrypt = Buffer.concat([version, payload]); + 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 encrypted = Message._encrypt(topubkey, toencrypt); var sig = Message._sign(fromkey, encrypted); var encoded = { diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js index 1acbb32c5..24f414c12 100644 --- a/test/test.network.WebRTC.js +++ b/test/test.network.WebRTC.js @@ -168,11 +168,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, a2, cb) { + n._sendToOne = function(a1, a2, a3, cb) { cb(); }; - var sig = undefined; - n.send(copayerId, data, function() { + var opts = {}; + n.send(copayerId, data, opts, function() { done(); }); @@ -191,13 +191,13 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerId = key.public.toString('hex'); - n._sendToOne = function(a1, enc, cb) { + n._sendToOne = function(a1, enc, opts, cb) { var encPayload = JSON.parse(enc.toString()); encPayload.sig.length.should.be.greaterThan(0); cb(); }; - var sig = undefined; - n.send(copayerId, data, function() { + var opts = {}; + n.send(copayerId, data, opts, function() { done(); }); @@ -216,11 +216,11 @@ describe('Network / WebRTC', function() { key.regenerateSync(); var copayerIds = [key.public.toString('hex')]; - n._sendToOne = function(a1, a2, cb) { + n._sendToOne = function(a1, a2, a3, cb) { cb(); }; - var sig = undefined; - n.send(copayerIds, data, function() { + var opts = {}; + n.send(copayerIds, data, opts, function() { done(); });