diff --git a/js/models/core/Message.js b/js/models/core/Message.js
new file mode 100644
index 000000000..b281eecd8
--- /dev/null
+++ b/js/models/core/Message.js
@@ -0,0 +1,82 @@
+'use strict';
+
+var imports = require('soop').imports();
+var bitcore = require('bitcore');
+
+/* Encrypted, authenticated messages to be shared between copayers */
+var Message = function() {
+};
+
+Message.encode = function(topubkey, fromkey, payload) {
+ var version = new Buffer([0]);
+ var toencrypt = Buffer.concat([version, payload]);
+ var encrypted = Message._encrypt(topubkey, toencrypt);
+ 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) {
+ 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 decrypted = Message._decrypt(key.private, encrypted);
+ } catch (e) {
+ throw new Error('Cannot decrypt data: ' + e);
+ }
+
+ if (decrypted[0] !== 0)
+ throw new Error('Invalid version number');
+
+ if (decrypted.length === 0)
+ throw new Error('No data present');
+
+ var payload = decrypted.slice(1);
+ return payload;
+};
+
+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 = require('soop')(Message);
diff --git a/js/models/core/PrivateKey.js b/js/models/core/PrivateKey.js
index ff8b70e79..b1149f28b 100644
--- a/js/models/core/PrivateKey.js
+++ b/js/models/core/PrivateKey.js
@@ -32,9 +32,17 @@ PrivateKey.prototype.getIdPriv = function() {
return this.idpriv;
};
+PrivateKey.prototype.getIdKey = function() {
+ if (!this.idkey) {
+ this.cacheId();
+ }
+ return this.idkey;
+};
+
PrivateKey.prototype.cacheId = function() {
var path = Structure.IdFullBranch;
var idhk = this.bip.derive(path);
+ this.idkey = idhk.eckey;
this.id = idhk.eckey.public.toString('hex');
this.idpriv = idhk.eckey.private.toString('hex');
};
diff --git a/js/models/core/Wallet.js b/js/models/core/Wallet.js
index 0bfa3dcf4..eb84b1f8c 100644
--- a/js/models/core/Wallet.js
+++ b/js/models/core/Wallet.js
@@ -279,6 +279,7 @@ Wallet.prototype.netStart = function(callback) {
var myId = self.getMyCopayerId();
var myIdPriv = self.getMyCopayerIdPriv();
+
var startOpts = {
copayerId: myId,
privkey: myIdPriv,
diff --git a/js/models/core/WalletFactory.js b/js/models/core/WalletFactory.js
index 03d59e2ea..b7ccc60e0 100644
--- a/js/models/core/WalletFactory.js
+++ b/js/models/core/WalletFactory.js
@@ -222,7 +222,8 @@ WalletFactory.prototype.joinCreateSession = function(secret, nickname, passphras
this.log('\t### PrivateKey Initialized');
var opts = {
copayerId: privateKey.getId(),
- privkey: privateKey.getIdPriv()
+ privkey: privateKey.getIdPriv(),
+ key: privateKey.getIdKey()
};
self.network.cleanUp();
self.network.start(opts, function() {
diff --git a/js/models/network/WebRTC.js b/js/models/network/WebRTC.js
index 98e660a7a..e56134c14 100644
--- a/js/models/network/WebRTC.js
+++ b/js/models/network/WebRTC.js
@@ -5,6 +5,7 @@ var EventEmitter = imports.EventEmitter || require('events').EventEmitter;
var bitcore = require('bitcore');
var util = bitcore.util;
var extend = require('util')._extend;
+var Message = require('../core/Message');
/*
* Emits
* 'connect'
@@ -45,6 +46,7 @@ Network.prototype.cleanUp = function() {
this.connectedPeers = [];
this.peerId = null;
this.privkey = null; //TODO: hide privkey in a closure
+ this.key = null;
this.copayerId = null;
this.signingKey = null;
this.allowedCopayerIds = null;
@@ -151,16 +153,30 @@ Network.prototype._addConnectedCopayer = function(copayerId, isInbound) {
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;
+};
+
Network.prototype._onData = function(enchex, isInbound, peerId) {
var sig, payload;
var encUint8Array = new Uint8Array(enchex);
var encbuf = new Buffer(encUint8Array);
+ var encstr = encbuf.toString();
var privkey = this.privkey;
+ var key = this.getKey();
try {
- var data = this._decrypt(privkey, encbuf);
- payload = JSON.parse(data);
+ var encoded = JSON.parse(encstr);
+ var databuf = this._decode(key, encoded);
+ var datastr = databuf.toString();
+ payload = JSON.parse(datastr);
} catch (e) {
this._deletePeer(peerId);
return;
@@ -349,18 +365,19 @@ Network.prototype.getPeer = function() {
return this.peer;
};
-Network.prototype._encrypt = function(pubkey, payload) {
- var encrypted = bitcore.ECIES.encrypt(pubkey, payload);
- return encrypted;
+Network.prototype._encode = function(topubkey, fromkey, payload) {
+ var encoded = Message.encode(topubkey, fromkey, payload);
+ return encoded;
};
-Network.prototype._decrypt = function(privkey, encrypted) {
- var decrypted = bitcore.ECIES.decrypt(privkey, encrypted);
- return decrypted;
+Network.prototype._decode = function(key, encoded) {
+ var payload = Message.decode(key, encoded);
+ return payload;
};
Network.prototype._sendToOne = function(copayerId, payload, sig, cb) {
+ console.log('payload: ' + payload);
var peerId = this.peerFromCopayer(copayerId);
if (peerId !== this.peerId) {
var dataConn = this.connections[peerId];
@@ -391,7 +408,7 @@ Network.prototype.send = function(copayerIds, payload, cb) {
var i = 0;
copayerIds.forEach(function(copayerId) {
var copayerIdBuf = new Buffer(copayerId, 'hex');
- var encPayload = self._encrypt(copayerIdBuf, payloadBuf);
+ var encPayload = self._encode(copayerIdBuf, self.getKey(), payloadBuf);
self._sendToOne(copayerId, encPayload, sig, function() {
if (++i === l && typeof cb === 'function') cb();
});
diff --git a/test/index.html b/test/index.html
index 127df85f3..120a98b2b 100644
--- a/test/index.html
+++ b/test/index.html
@@ -15,6 +15,7 @@
+
diff --git a/test/test.Message.js b/test/test.Message.js
new file mode 100644
index 000000000..cd71458ca
--- /dev/null
+++ b/test/test.Message.js
@@ -0,0 +1,85 @@
+'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() {
+ var message = new Buffer('message');
+ var messagehex = message.toString('hex');
+ var encoded = Message.encode(key2.public, key, message);
+
+ it('should decode an encoded message', function() {
+ var decoded = Message.decode(key2, encoded);
+ decoded.toString('hex').should.equal(messagehex);
+ });
+
+ });
+
+ 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);
+ });
+
+ });
+
+});
diff --git a/test/test.network.WebRTC.js b/test/test.network.WebRTC.js
index d880d006d..8297762bf 100644
--- a/test/test.network.WebRTC.js
+++ b/test/test.network.WebRTC.js
@@ -46,26 +46,28 @@ describe('Network / WebRTC', function() {
});
- describe('#_encrypt', function() {
+ describe('#_encode', function() {
- it('should encrypt data successfully', function() {
+ it('should encode data successfully', function() {
var n = new WebRTC();
- var data = new bitcore.Buffer('my data to encrypt');
+ 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 encrypted = n._encrypt(key.public, data);
- encrypted.length.should.not.equal(0);
- encrypted.length.should.equal(145);
+ 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('#_decrypt', function() {
+ describe('#_decode', function() {
- it('should decrypt that which was encrypted', 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');
@@ -73,10 +75,10 @@ describe('Network / WebRTC', function() {
var key = new bitcore.Key();
key.private = privkey;
key.regenerateSync();
- var encrypted = n._encrypt(key.public, data);
- var decrypted = n._decrypt(key.private, encrypted);
- encrypted.length.should.not.equal(0);
- decrypted.toString().should.equal('my data to encrypt');
+ var encoded = n._encode(key.public, key, data);
+ var decoded = n._decode(key, encoded);
+ encoded.sig.should.not.equal(0);
+ decoded.toString().should.equal('my data to encrypt');
});
});
@@ -85,6 +87,7 @@ describe('Network / WebRTC', 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');
@@ -107,6 +110,7 @@ describe('Network / WebRTC', function() {
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');
@@ -118,7 +122,7 @@ describe('Network / WebRTC', function() {
var copayerId = key.public.toString('hex');
n._sendToOne = function(a1, encPayload, a3, cb) {
- encPayload.length.should.be.greaterThan(0);
+ encPayload.sig.length.should.be.greaterThan(0);
cb();
};
var sig = undefined;
@@ -130,6 +134,7 @@ describe('Network / WebRTC', function() {
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');
diff --git a/util/build.js b/util/build.js
index 2af0ca892..e7cf06fa5 100755
--- a/util/build.js
+++ b/util/build.js
@@ -84,6 +84,9 @@ var createBundle = function(opts) {
b.require('./js/models/core/Passphrase', {
expose: '../js/models/core/Passphrase'
});
+ b.require('./js/models/core/Message', {
+ expose: '../js/models/core/Message'
+ });
if (opts.dontminify) {
//include dev dependencies