|
|
|
'use strict';
|
|
|
|
|
|
|
|
var Message = require('./Message');
|
|
|
|
var ECIES = require('./ECIES');
|
|
|
|
var preconditions = require('preconditions').singleton();
|
|
|
|
var Key = require('./Key');
|
|
|
|
|
|
|
|
|
|
|
|
var majorVersion = 1;
|
|
|
|
var minorVersion = 0;
|
|
|
|
|
|
|
|
/* Encrypted, authenticated messages to be shared between copayers */
|
|
|
|
var AuthMessage = function() {};
|
|
|
|
|
|
|
|
AuthMessage.setVersion = function(major, minor) {
|
|
|
|
majorVersion = major;
|
|
|
|
minorVersion = minor;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage.encode = function(topubkey, fromkey, payload, opts) {
|
|
|
|
preconditions.checkArgument(fromkey instanceof Key, 'fromkey');
|
|
|
|
if (typeof topubkey === 'string') {
|
|
|
|
topubkey = new Buffer(topubkey, 'hex');
|
|
|
|
}
|
|
|
|
if (!(payload instanceof Buffer)) {
|
|
|
|
payload = new Buffer(JSON.stringify(payload));
|
|
|
|
}
|
|
|
|
//peers should reject messges containing bigger major version
|
|
|
|
//i.e., increment to prevent communications with old clients
|
|
|
|
var version1 = new Buffer([majorVersion]);
|
|
|
|
|
|
|
|
//peers should not reject messages containing not-understood minorversion
|
|
|
|
//i.e., increment to allow communication with old clients, but signal new clients
|
|
|
|
var version2 = new Buffer([minorVersion]);
|
|
|
|
|
|
|
|
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 toencrypthexbuf = new Buffer(toencrypt.toString('hex')); //due to bug in sjcl/bitcore, must use hex string
|
|
|
|
var encrypted = AuthMessage._encrypt(topubkey, toencrypthexbuf);
|
|
|
|
var sig = AuthMessage._sign(fromkey, encrypted);
|
|
|
|
var encoded = {
|
|
|
|
pubkey: fromkey.public.toString('hex'),
|
|
|
|
sig: sig.toString('hex'),
|
|
|
|
encrypted: encrypted.toString('hex'),
|
|
|
|
to: topubkey.toString('hex')
|
|
|
|
};
|
|
|
|
return encoded;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage.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) {
|
|
|
|
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 = AuthMessage._verify(frompubkey, sig, encrypted);
|
|
|
|
} catch (e) {
|
|
|
|
throw new Error('Error verifying signature: ' + e);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!v) {
|
|
|
|
throw new Error('Invalid signature');
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
var decryptedhexbuf = AuthMessage._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 !== majorVersion) {
|
|
|
|
throw new Error('Invalid version number');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (version2 !== minorVersion) {
|
|
|
|
//put special version2 handling code here, if ever needed
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!AuthMessage._noncegt(nonce, prevnonce) && prevnonce.toString('hex') !== '0000000000000000') {
|
|
|
|
throw new Error('Nonce not equal to zero and not greater than the previous nonce');
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
payload = JSON.parse(payload);
|
|
|
|
} catch (e) {
|
|
|
|
if (e instanceof SyntaxError) {
|
|
|
|
// if we can't parse a JSON, just return what we found
|
|
|
|
} else {
|
|
|
|
throw e;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var decoded = {
|
|
|
|
version1: version1,
|
|
|
|
version2: version2,
|
|
|
|
nonce: nonce,
|
|
|
|
payload: payload
|
|
|
|
};
|
|
|
|
|
|
|
|
return decoded;
|
|
|
|
};
|
|
|
|
|
|
|
|
//return true if nonce > prevnonce; false otherwise
|
|
|
|
AuthMessage._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;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage._encrypt = function(topubkey, payload, r, iv) {
|
|
|
|
var encrypted = ECIES.encrypt(topubkey, payload, r, iv);
|
|
|
|
return encrypted;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage._decrypt = function(privkey, encrypted) {
|
|
|
|
var decrypted = ECIES.decrypt(privkey, encrypted);
|
|
|
|
return decrypted;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage._sign = function(key, payload) {
|
|
|
|
var sig = Message.sign(payload, key);
|
|
|
|
return sig;
|
|
|
|
};
|
|
|
|
|
|
|
|
AuthMessage._verify = function(pubkey, signature, payload) {
|
|
|
|
var v = Message.verifyWithPubKey(pubkey, payload, signature);
|
|
|
|
return v;
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = AuthMessage;
|