status-go/static/tests/whisper.js

755 lines
30 KiB
JavaScript

var chai = require("chai");
var expect = chai.expect;
var assert = chai.assert;
var Web3 = require('web3');
describe('Whisper Tests', function () {
var node1 = new Web3();
var node2 = new Web3();
var web3 = node1;
node1.setProvider(new web3.providers.HttpProvider('http://localhost:8645'));
node2.setProvider(new web3.providers.HttpProvider('http://localhost:8745'));
console.log('Node is expected: statusd --datadir app1 --http --httpport 8645 wnode');
console.log('Node is expected: statusd --datadir app2 --http --httpport 8745 wnode');
console.log('Node is expected: statusd --datadir wnode1 wnode --notify --injectaccounts=false --identity ./static/keys/wnodekey --firebaseauth ./static/keys/firebaseauthkey');
// some common vars
var topic1 = '0xdeadbeef'; // each topic 4 bytes, as hex
var topic2 = '0xbeefdead'; // each topic 4 bytes, as hex
var topic3 = '0xbebebebe'; // each topic 4 bytes, as hex
var topic4 = '0xdadadada'; // each topic 4 bytes, as hex
var identity1 = '0x04eedbaafd6adf4a9233a13e7b1c3c14461fffeba2e9054b8d456ce5f6ebeafadcbf3dce3716253fbc391277fa5a086b60b283daf61fb5b1f26895f456c2f31ae3';
var identity2 = '0x0490161b00f2c47542d28c2e8908e77159b1720dccceb6393d7c001850122efc3b1709bcea490fd8f5634ba1a145aa0722d86b9330b0e39a8d493cb981fd459da2';
// watchFilter makes sure that we halt the filter on first message received
var watchFilter = function (filter, done) {
var messageReceived = false;
filter.watch(function (error, message) {
if (messageReceived) return; // avoid double calling
messageReceived = true; // no need to watch for the filter any more
filter.stopWatching();
done(error, message);
});
};
// makeTopic generates random topic (4 bytes, in hex)
var makeTopic = function () {
var min = 1;
var max = Math.pow(16, 8);
var randInt = Math.floor(Math.random() * (max - min + 1)) + min;
return web3.toHex(randInt);
};
context('shh/5 API verification', function () {
it('statusd node is running', function () {
var web3 = new Web3();
var provider = new web3.providers.HttpProvider('http://localhost:8645');
var result = provider.send({});
assert.equal(typeof result, 'object');
});
it('shh.version()', function () {
var version = node1.shh.version();
assert.equal(version, '0x5', 'Whisper version does not match');
});
it('shh.info()', function () {
var info = node1.shh.info();
if (info == "") {
throw new Error('no Whisper info provided')
}
});
context('symmetric key management', function () {
var keyId = ''; // symmetric key ID (to be populated)
var keyVal = ''; // symmetric key value (to be populated)
it('shh.generateSymmetricKey()', function () {
keyId = node1.shh.generateSymmetricKey();
assert.lengthOf(keyId, 64, 'invalid keyId length');
});
it('shh.getSymmetricKey(keyId)', function () {
keyVal = node1.shh.getSymmetricKey(keyId);
assert.lengthOf(keyVal, 66, 'invalid key value length'); // 2 bytes for "0x"
});
it('shh.hasSymmetricKey(keyId)', function () {
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(true);
});
it('shh.deleteSymmetricKey(keyId)', function () {
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(true);
node1.shh.deleteSymmetricKey(keyId);
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(false);
});
it('shh.addSymmetricKeyDirect(keyVal)', function () {
keyIdOriginal = keyId;
keyId = node1.shh.addSymmetricKeyDirect(keyVal);
assert.notEqual(keyId, keyIdOriginal);
assert.lengthOf(keyId, 64, 'invalid keyId length');
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(true);
});
it('shh.addSymmetricKeyFromPassword(password)', function () {
var password = 'foobar';
var keyId = node1.shh.addSymmetricKeyFromPassword(password);
var keyVal = node1.shh.getSymmetricKey(keyId);
assert.lengthOf(keyId, 64, 'invalid keyId length');
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(true);
assert.equal(keyVal, '0xa582720d74d463589df14c11538189a1c07778c47e86f70bab7b5ba27e2de3cc');
});
});
context('assymmetric key management', function () {
var keyId = ''; // to be populated
var pubKey = ''; // to be populated
it('shh.newKeyPair()', function () {
keyId = node1.shh.newKeyPair();
assert.lengthOf(keyId, 64);
});
it('shh.hasKeyPair(id)', function () {
expect(node1.shh.hasKeyPair(keyId)).to.equal(true);
});
it('shh.getPublicKey(id)', function () {
pubKey = node1.shh.getPublicKey(keyId);
assert.lengthOf(pubKey, 132);
});
it('shh.hasKeyPair(pubKey)', function () {
expect(node1.shh.hasKeyPair(pubKey)).to.equal(true);
});
it('shh.getPrivateKey(id)', function () {
var prvkey = node1.shh.getPrivateKey(keyId);
assert.lengthOf(prvkey, 66);
});
it('shh.deleteKeyPair(id)', function () {
expect(node1.shh.hasKeyPair(pubKey)).to.equal(true);
expect(node1.shh.hasKeyPair(keyId)).to.equal(true);
node1.shh.deleteKeyPair(keyId);
expect(node1.shh.hasKeyPair(pubKey)).to.equal(false);
expect(node1.shh.hasKeyPair(keyId)).to.equal(false);
// re-create
keyId = node1.shh.newKeyPair();
assert.lengthOf(keyId, 64);
pubKey = node1.shh.getPublicKey(keyId);
assert.lengthOf(pubKey, 132);
});
it('shh.deleteKeyPair(pubKey)', function () {
expect(node1.shh.hasKeyPair(pubKey)).to.equal(true);
expect(node1.shh.hasKeyPair(keyId)).to.equal(true);
node1.shh.deleteKeyPair(pubKey);
expect(node1.shh.hasKeyPair(pubKey)).to.equal(false);
expect(node1.shh.hasKeyPair(keyId)).to.equal(false);
// re-create
keyId = node1.shh.newKeyPair();
assert.lengthOf(keyId, 64);
pubKey = node1.shh.getPublicKey(keyId);
assert.lengthOf(pubKey, 132);
});
});
context('subscribe and manually get messages', function () {
// NOTE: you can still use shh.filter to poll for messages automatically, see other examples
var filterid1 = ''; // sym filter, to be populated
var filterid2 = ''; // asym filter, to be populated
var keyId = ''; // symkey, to be populated
var uniqueTopic = makeTopic();
var payloadBeforeSymFilter = 'sent before filter was active (symmetric)';
var payloadAfterSymFilter = 'sent after filter was active (symmetric)';
var payloadBeforeAsymFilter = 'sent before filter was active (asymmetric)';
var payloadAfterAsymFilter = 'sent after filter was active (asymmetric)';
it('shh.subscribe(filterParams) - symmetric filter', function () {
keyId = node1.shh.generateSymmetricKey();
assert.lengthOf(keyId, 64);
// send message, which will be floating around *before* filter is even created
var message = {
type: "sym",
key: keyId,
topic: uniqueTopic,
payload: payloadBeforeSymFilter
};
expect(node1.shh.post(message)).to.equal(null);
// symmetric filter
filterid1 = node1.shh.subscribe({
type: "sym",
key: keyId,
sig: identity1,
topics: [topic1, topic2, uniqueTopic]
});
assert.lengthOf(filterid1, 64);
});
it('shh.subscribe(filterParams) - asymmetric filter', function () {
// send message, which will be floating around *before* filter is even created
var message = {
type: "asym",
key: identity2,
topic: uniqueTopic,
payload: payloadBeforeAsymFilter
};
expect(node1.shh.post(message)).to.equal(null);
// asymmetric filter
filterid2 = node1.shh.subscribe({
type: "asym",
key: identity2,
sig: identity1,
topics: [topic1, topic2, uniqueTopic]
});
assert.lengthOf(filterid1, 64);
});
it('shh.getMessages(filterID) - symmetric filter', function () {
// let's try to capture message that was there *before* filter is created
var messages = node1.shh.getMessages(filterid1);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 1);
assert.equal(web3.toAscii(messages[0].payload), payloadBeforeSymFilter);
// send message, after the filter has been already installed
var message = {
type: "sym",
key: keyId,
topic: uniqueTopic,
payload: payloadAfterSymFilter
};
expect(node1.shh.post(message)).to.equal(null);
});
it('shh.getMessages(filterID) - asymmetric filter', function () {
// let's try to capture message that was there *before* filter is created
var messages = node1.shh.getMessages(filterid2);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 1);
assert.equal(web3.toAscii(messages[0].payload), payloadBeforeAsymFilter);
// send message, after the filter has been already installed
var message = {
type: "asym",
key: identity2,
topic: uniqueTopic,
payload: payloadAfterAsymFilter
};
expect(node1.shh.post(message)).to.equal(null);
});
it('shh.getSubscriptionMessages(filterID) - symmetric filter', function (done) {
// allow some time for message to propagate
setTimeout(function () {
// now let's try to capture new messages from our last capture
var messages = node1.shh.getSubscriptionMessages(filterid1);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 1);
assert.equal(web3.toAscii(messages[0].payload), payloadAfterSymFilter);
// no more messages should be returned
messages = node1.shh.getSubscriptionMessages(filterid1);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 0);
done();
}, 200);
});
it('shh.getSubscriptionMessages(filterID) - asymmetric filter', function () {
// allow some time for message to propagate
setTimeout(function () {
// now let's try to capture new messages from our last capture
var messages = node1.shh.getSubscriptionMessages(filterid2);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 1);
assert.equal(web3.toAscii(messages[0].payload), payloadAfterAsymFilter);
// no more messages should be returned
messages = node1.shh.getSubscriptionMessages(filterid2);
assert.typeOf(messages, 'array');
assert.lengthOf(messages, 0);
done();
}, 200);
});
it('shh.unsubscribe(filterID)', function () {
node1.shh.unsubscribe(filterid1);
node1.shh.unsubscribe(filterid2);
});
});
});
context('symmetrically encrypted messages send/recieve', function () {
this.timeout(0);
var keyId = ''; // symmetric key ID (to be populated)
var keyVal = ''; // symmetric key value (to be populated)
var payload = 'here come the dragons';
it('default test identity is present', function () {
if (!node1.shh.hasKeyPair(identity1)) {
throw new Error('identity not found in whisper: ' + identity1);
}
});
it('ensure symkey exists', function () {
keyId = node1.shh.generateSymmetricKey();
assert.lengthOf(keyId, 64);
expect(node1.shh.hasSymmetricKey(keyId)).to.equal(true);
});
it('read the generated symkey', function () {
keyVal = node1.shh.getSymmetricKey(keyId);
assert.lengthOf(keyVal, 66); // 2 bytes for "0x"
});
it('send/receive symmetrically encrypted message', function (done) {
// start watching for messages
watchFilter(node1.shh.filter({
type: "sym",
key: keyId,
sig: identity1,
topics: [topic1, topic2]
}), function (err, message) {
done(err);
});
// send message
var message = {
type: "sym",
key: keyId,
sig: identity1,
topic: topic1,
payload: web3.fromAscii(payload),
ttl: 20,
powTime: 2,
powTarget: 0.001
};
expect(node1.shh.post(message)).to.equal(null);
});
it('send the minimal symmetric message possible', function (done) {
var uniqueTopic = makeTopic();
// start watching for messages
watchFilter(node1.shh.filter({
type: "sym",
key: keyId,
topics: [uniqueTopic]
}), function (err, message) {
done(err);
});
// send message
var message = {
type: "sym",
key: keyId,
topic: uniqueTopic
};
expect(node1.shh.post(message)).to.equal(null);
});
});
context('message travelling from one node to another', function () {
this.timeout(0);
var keyId1 = ''; // symmetric key ID on node 1 (to be populated)
var keyId2 = ''; // symmetric key ID on node 2 (to be populated)
it('statusd node1 is running', function () {
var web3 = new Web3();
var provider = new web3.providers.HttpProvider('http://localhost:8645');
var result = provider.send({});
assert.equal(typeof result, 'object');
});
it('statusd node2 is running', function () {
var web3 = new Web3();
var provider = new web3.providers.HttpProvider('http://localhost:8745');
var result = provider.send({});
assert.equal(typeof result, 'object');
});
it('test identities injected', function () {
if (!node1.shh.hasKeyPair(identity1)) {
throw new Error('identity not found in whisper (node1): ' + identity1);
}
if (!node1.shh.hasKeyPair(identity2)) {
throw new Error('identity not found in whisper (node1): ' + identity2);
}
if (!node2.shh.hasKeyPair(identity1)) {
throw new Error('identity not found in whisper (node2): ' + identity1);
}
if (!node2.shh.hasKeyPair(identity2)) {
throw new Error('identity not found in whisper (node2): ' + identity2);
}
});
it('ensure symkey exists', function () {
keyId1 = node1.shh.generateSymmetricKey();
assert.lengthOf(keyId1, 64);
expect(node1.shh.hasSymmetricKey(keyId1)).to.equal(true);
// obtain key value
var keyVal = node1.shh.getSymmetricKey(keyId1);
assert.lengthOf(keyVal, 66); // 2 bytes of "0x"
// share the value with the node2
keyId2 = node2.shh.addSymmetricKeyDirect(keyVal);
assert.lengthOf(keyId2, 64);
expect(node2.shh.hasSymmetricKey(keyId2)).to.equal(true);
});
it('send symmetrically encrypted, signed message (node1 -> node2)', function (done) {
var payload = 'send symmetrically encrypted, signed message (node1 -> node2)';
var topic = makeTopic();
// start watching for messages
watchFilter(node2.shh.filter({
type: "sym",
sig: identity1,
key: keyId2,
topics: [topic]
}), function (err, message) {
done(err);
});
// send message
var message = {
type: "sym",
sig: identity1,
key: keyId1,
topic: topic,
payload: payload,
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
it('send asymmetrically encrypted, signed message (node1.id1 -> node2.id2)', function (done) {
var payload = 'send asymmetrically encrypted, signed message (node1.id1 -> node2.id2)';
var topic = makeTopic();
// start watching for messages
watchFilter(node2.shh.filter({
type: "asym",
sig: identity1,
key: identity2
}), function (err, message) {
done(err);
});
// send message
var message = {
type: "asym",
sig: identity1,
key: identity2,
topic: topic,
payload: payload,
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
});
context('push notifications', function () {
this.timeout(5000);
var discoveryPubKey = '0x040edb0d71a3dbe928e154fcb696ffbda359b153a90efc2b46f0043ce9f5dbe55b77b9328fd841a1db5273758624afadd5b39638d4c35b36b3a96e1a586c1b4c2a';
var discoverServerTopic = '0x268302f3'; // DISCOVER_NOTIFICATION_SERVER
var proposeServerTopic = '0x08e3d8c0'; // PROPOSE_NOTIFICATION_SERVER
var acceptServerTopic = '0x04f7dea6'; // ACCEPT_NOTIFICATION_SERVER
var ackClientSubscriptionTopic = '0x93dafe28'; // ACK_NOTIFICATION_SERVER_SUBSCRIPTION
var sendNotificationTopic = '0x69915296'; // SEND_NOTIFICATION
var newChatSessionTopic = '0x509579a2'; // NEW_CHAT_SESSION
var ackNewChatSessionTopic = '0xd012aae8'; // ACK_NEW_CHAT_SESSION
var newDeviceRegistrationTopic = '0x14621a51'; // NEW_DEVICE_REGISTRATION
var ackDeviceRegistrationTopic = '0x424358d6'; // ACK_DEVICE_REGISTRATION
var checkClientSessionTopic = '0x8745d931'; // CHECK_CLIENT_SESSION
var confirmClientSessionTopic = '0xd3202c5f'; // CONFIRM_CLIENT_SESSION
var dropClientSessionTopic = '0x3a6656bb'; // DROP_CLIENT_SESSION
// ensures that message had payload (which is HEX-encoded JSON)
var extractPayload = function (message) {
expect(message).to.have.property('payload');
return JSON.parse(web3.toAscii(message.payload));
};
var identity1 = ''; // pub key of device 1
var identity2 = ''; // pub key of device 2
var chatKeySharingTopic = makeTopic(); // topic used by device1 to send chat key to device 2
context('prepare devices', function () {
it('create key pair to be used as main identity on device1', function () {
var keyId = node1.shh.newKeyPair();
assert.lengthOf(keyId, 64);
identity1 = node1.shh.getPublicKey(keyId);
assert.lengthOf(identity1, 132);
expect(node1.shh.hasKeyPair(identity1)).to.equal(true);
expect(node1.shh.hasKeyPair(identity2)).to.equal(false);
});
it('create key pair to be used as main identity on device2', function () {
var keyId = node2.shh.newKeyPair();
assert.lengthOf(keyId, 64);
identity2 = node2.shh.getPublicKey(keyId);
assert.lengthOf(identity1, 132);
expect(node2.shh.hasKeyPair(identity1)).to.equal(false);
expect(node2.shh.hasKeyPair(identity2)).to.equal(true);
});
});
context('run device1', function () {
var serverId = ''; // accepted/selected server id
var subscriptionKeyId = ''; // symkey provided by server, and used to configure client-server subscription
var chatKeyId = ''; // symkey provided by server, and shared among clients so that they can trigger notifications
var appChatId = ''; // chat id that identifies device1-device2 interaction session on RN app level
it('start discovery by sending discovery request', function () {
var message = {
type: "asym",
sig: identity1,
key: discoveryPubKey,
topic: discoverServerTopic,
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
it('watch for server proposals', function (done) {
watchFilter(node1.shh.filter({
type: "asym",
sig: discoveryPubKey,
key: identity1,
topics: [proposeServerTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('server');
serverId = payload.server;
done();
});
});
it('client accepts server', function () {
var message = {
type: "asym",
sig: identity1,
key: discoveryPubKey,
topic: acceptServerTopic,
payload: '{"server": "' + serverId + '"}',
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
it('watch for server ACK response and save provided subscription key', function (done) {
watchFilter(node1.shh.filter({
type: "asym",
key: identity1,
topics: [ackClientSubscriptionTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('server');
expect(payload).to.have.property('key');
// save subscription key
subscriptionKeyId = node1.shh.addSymmetricKeyDirect(payload.key);
assert.lengthOf(subscriptionKeyId, 64);
expect(node1.shh.hasSymmetricKey(subscriptionKeyId)).to.equal(true);
done();
});
});
it('create chat session', function () {
appChatId = makeTopic(); // globally unique chat id
var message = {
type: "sym",
sig: identity1,
key: subscriptionKeyId,
topic: newChatSessionTopic,
payload: '{"chat": "' + appChatId + '"}',
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
it('watch for server to respond with chat key', function (done) {
watchFilter(node1.shh.filter({
type: "asym",
key: identity1,
topics: [ackNewChatSessionTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('server');
expect(payload).to.have.property('key');
// save subscription key
chatKeyId = node1.shh.addSymmetricKeyDirect(payload.key);
assert.lengthOf(chatKeyId, 64);
expect(node1.shh.hasSymmetricKey(chatKeyId)).to.equal(true);
done();
});
});
it('register device with a given chat', function (done) {
// this obtained from https://status-sandbox-c1b34.firebaseapp.com/
var deviceId = 'ca5pRJc6L8s:APA91bHpYFtpxvXx6uOayGmnNVnktA4PEEZdquCCt3fWR5ldLzSy1A37Tsbzk5Gavlmk1d_fvHRVnK7xPAhFFl-erF7O87DnIEstW6DEyhyiKZYA4dXFh6uy323f9A3uw5hEtT_kQVhT';
var message = {
type: "sym",
sig: identity1,
key: chatKeyId,
topic: newDeviceRegistrationTopic,
payload: '{"device": "' + deviceId + '"}',
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
// watch for server server ACK
watchFilter(node1.shh.filter({
type: "asym",
key: identity1,
topics: [ackDeviceRegistrationTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('server');
done();
});
});
it('share chat key, so that another device can send us notifications', function () {
var chatKey = node1.shh.getSymmetricKey(chatKeyId);
assert.lengthOf(chatKey, 66);
var message = {
type: "asym",
sig: identity1,
key: identity2,
topic: chatKeySharingTopic,
payload: '{"chat": "' + appChatId + '", "key": "' + chatKey + '"}',
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
});
context('run device2', function () {
var chatKeyId = '';
it('watch for device1 to send us chat key', function (done) {
watchFilter(node2.shh.filter({
type: "asym",
key: identity2,
topics: [chatKeySharingTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('chat');
expect(payload).to.have.property('key');
// persist chat key
chatKeyId = node2.shh.addSymmetricKeyDirect(payload.key);
assert.lengthOf(chatKeyId, 64);
expect(node2.shh.hasSymmetricKey(chatKeyId)).to.equal(true);
done();
});
});
it('trigger notification (from device2, on device1)', function () {
var message = {
type: "sym",
sig: identity2,
key: chatKeyId,
topic: sendNotificationTopic,
payload: '{' // see https://firebase.google.com/docs/cloud-messaging/http-server-ref
+ '"notification": {'
+ '"title": "status.im notification",'
+ '"body": "Hello this is test notification!",'
+ '"icon": "https://status.im/img/logo.png",'
+ '"click_action": "https://status.im"'
+ '},'
+ '"to": "{{ ID }}"' // this get replaced by device id your've registered
+ '}',
ttl: 20
};
expect(node2.shh.post(message)).to.equal(null);
});
});
context('misc methods and cleanup', function () {
it('check client session', function (done) {
// request status
var message = {
type: "asym",
sig: identity1,
key: discoveryPubKey,
topic: checkClientSessionTopic,
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
// process server's response
watchFilter(node1.shh.filter({
type: "asym",
key: identity1,
topics: [confirmClientSessionTopic]
}), function (err, message) {
if (err) return done(err);
// process payload
var payload = extractPayload(message);
expect(payload).to.have.property('server');
expect(payload).to.have.property('key');
done();
});
});
it('remove client session', function () {
var message = {
type: "asym",
sig: identity1,
key: discoveryPubKey,
topic: dropClientSessionTopic,
ttl: 20
};
expect(node1.shh.post(message)).to.equal(null);
});
});
});
});