mirror of https://github.com/BTCPrivate/copay.git
add support for 8 byte big endian nonce to Message
...network protocol is backwards incompatible with previous network protocl. also includes a second version number for backwards-compatible changes (the original version number was for backwards-incompatible changes).
This commit is contained in:
parent
9b3ce1ebe9
commit
a1155c2798
|
@ -7,9 +7,20 @@ var bitcore = require('bitcore');
|
||||||
var Message = function() {
|
var Message = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
Message.encode = function(topubkey, fromkey, payload) {
|
Message.encode = function(topubkey, fromkey, payload, opts) {
|
||||||
var version = new Buffer([0]);
|
var version1 = new Buffer([1]); //peers will reject messges containing not-understood version1
|
||||||
var toencrypt = Buffer.concat([version, payload]);
|
//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 encrypted = Message._encrypt(topubkey, toencrypt);
|
||||||
var sig = Message._sign(fromkey, encrypted);
|
var sig = Message._sign(fromkey, encrypted);
|
||||||
var encoded = {
|
var encoded = {
|
||||||
|
@ -20,7 +31,14 @@ Message.encode = function(topubkey, fromkey, payload) {
|
||||||
return encoded;
|
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 {
|
try {
|
||||||
var frompubkey = new Buffer(encoded.pubkey, 'hex');
|
var frompubkey = new Buffer(encoded.pubkey, 'hex');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -40,8 +58,9 @@ Message.decode = function(key, encoded) {
|
||||||
throw new Error('Error verifying signature: ' + e);
|
throw new Error('Error verifying signature: ' + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!v)
|
if (!v) {
|
||||||
throw new Error('Invalid signature');
|
throw new Error('Invalid signature');
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var decrypted = Message._decrypt(key.private, encrypted);
|
var decrypted = Message._decrypt(key.private, encrypted);
|
||||||
|
@ -49,14 +68,59 @@ Message.decode = function(key, encoded) {
|
||||||
throw new Error('Cannot decrypt data: ' + e);
|
throw new Error('Cannot decrypt data: ' + e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (decrypted[0] !== 0)
|
try {
|
||||||
throw new Error('Invalid version number');
|
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');
|
throw new Error('No data present');
|
||||||
|
}
|
||||||
|
|
||||||
var payload = decrypted.slice(1);
|
if (version1 !== 1) {
|
||||||
return payload;
|
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) {
|
Message._encrypt = function(topubkey, payload, r, iv) {
|
||||||
|
|
|
@ -376,18 +376,19 @@ Network.prototype.getPeer = function() {
|
||||||
return this.peer;
|
return this.peer;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._encode = function(topubkey, fromkey, payload) {
|
Network.prototype._encode = function(topubkey, fromkey, payload, opts) {
|
||||||
var encoded = Message.encode(topubkey, fromkey, payload);
|
var encoded = Message.encode(topubkey, fromkey, payload, opts);
|
||||||
return encoded;
|
return encoded;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
Network.prototype._decode = function(key, encoded) {
|
Network.prototype._decode = function(key, encoded, opts) {
|
||||||
var payload = Message.decode(key, encoded);
|
var decoded = Message.decode(key, encoded, opts);
|
||||||
|
var payload = decoded.payload;
|
||||||
return payload;
|
return payload;
|
||||||
};
|
};
|
||||||
|
|
||||||
Network.prototype._sendToOne = function(copayerId, payload, cb) {
|
Network.prototype._sendToOne = function(copayerId, payload, opts, cb) {
|
||||||
if (!Buffer.isBuffer(payload))
|
if (!Buffer.isBuffer(payload))
|
||||||
throw new Error('payload must be a buffer');
|
throw new Error('payload must be a buffer');
|
||||||
var peerId = this.peerFromCopayer(copayerId);
|
var peerId = this.peerFromCopayer(copayerId);
|
||||||
|
@ -400,7 +401,7 @@ Network.prototype._sendToOne = function(copayerId, payload, cb) {
|
||||||
if (typeof cb === 'function') 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();
|
if (!payload) return cb();
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -421,7 +422,7 @@ Network.prototype.send = function(copayerIds, payload, cb) {
|
||||||
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
var copayerIdBuf = new Buffer(copayerId, 'hex');
|
||||||
var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
|
var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
|
||||||
var enc = new Buffer(JSON.stringify(encPayload));
|
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();
|
if (++i === l && typeof cb === 'function') cb();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -36,15 +36,70 @@ describe('Message model', function() {
|
||||||
var encoded = Message.encode(key2.public, key, message);
|
var encoded = Message.encode(key2.public, key, message);
|
||||||
|
|
||||||
var decoded = Message.decode(key2, encoded);
|
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() {
|
it('should fail if the version number is incorrect', function() {
|
||||||
var payload = new Buffer('message');
|
var payload = new Buffer('message');
|
||||||
var fromkey = key;
|
var fromkey = key;
|
||||||
var topubkey = key2.public;
|
var topubkey = key2.public;
|
||||||
var version = new Buffer([1]);
|
var version1 = new Buffer([2]);
|
||||||
var toencrypt = Buffer.concat([version, payload]);
|
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 encrypted = Message._encrypt(topubkey, toencrypt);
|
||||||
var sig = Message._sign(fromkey, encrypted);
|
var sig = Message._sign(fromkey, encrypted);
|
||||||
var encoded = {
|
var encoded = {
|
||||||
|
|
|
@ -168,11 +168,11 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
var copayerId = key.public.toString('hex');
|
||||||
n._sendToOne = function(a1, a2, cb) {
|
n._sendToOne = function(a1, a2, a3, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var opts = {};
|
||||||
n.send(copayerId, data, function() {
|
n.send(copayerId, data, opts, function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -191,13 +191,13 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerId = key.public.toString('hex');
|
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());
|
var encPayload = JSON.parse(enc.toString());
|
||||||
encPayload.sig.length.should.be.greaterThan(0);
|
encPayload.sig.length.should.be.greaterThan(0);
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var opts = {};
|
||||||
n.send(copayerId, data, function() {
|
n.send(copayerId, data, opts, function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -216,11 +216,11 @@ describe('Network / WebRTC', function() {
|
||||||
key.regenerateSync();
|
key.regenerateSync();
|
||||||
|
|
||||||
var copayerIds = [key.public.toString('hex')];
|
var copayerIds = [key.public.toString('hex')];
|
||||||
n._sendToOne = function(a1, a2, cb) {
|
n._sendToOne = function(a1, a2, a3, cb) {
|
||||||
cb();
|
cb();
|
||||||
};
|
};
|
||||||
var sig = undefined;
|
var opts = {};
|
||||||
n.send(copayerIds, data, function() {
|
n.send(copayerIds, data, opts, function() {
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue