Merge pull request #7 from status-im/waku-version-1
This commit is contained in:
commit
98bf7f7e3b
|
@ -44,6 +44,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"ecies-geth": "^1.5.3",
|
||||||
"js-sha3": "^0.8.0",
|
"js-sha3": "^0.8.0",
|
||||||
"js-waku": "^0.12.0",
|
"js-waku": "^0.12.0",
|
||||||
"protobufjs": "^6.11.2",
|
"protobufjs": "^6.11.2",
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
|
import { keccak256 } from "js-sha3";
|
||||||
|
import { utils } from "js-waku";
|
||||||
import { Reader } from "protobufjs";
|
import { Reader } from "protobufjs";
|
||||||
|
import secp256k1 from "secp256k1";
|
||||||
|
|
||||||
import { ChatMessage } from "./chat_message";
|
import { ChatMessage } from "./chat_message";
|
||||||
import { Identity } from "./identity";
|
import { Identity } from "./identity";
|
||||||
|
@ -58,4 +61,14 @@ export class ApplicationMetadataMessage {
|
||||||
|
|
||||||
return ChatMessage.decode(this.payload);
|
return ChatMessage.decode(this.payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get signer(): Uint8Array | undefined {
|
||||||
|
if (!this.signature || !this.payload) return;
|
||||||
|
|
||||||
|
const signature = this.signature.slice(0, 64);
|
||||||
|
const recid = this.signature.slice(64)[0];
|
||||||
|
const hash = keccak256(this.payload);
|
||||||
|
|
||||||
|
return secp256k1.ecdsaRecover(signature, recid, utils.hexToBuf(hash));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,23 @@
|
||||||
import { ChatMessage } from "./chat_message";
|
import { ChatMessage } from "./chat_message";
|
||||||
import { chatIdToContentTopic } from "./contentTopic";
|
import { chatIdToContentTopic } from "./contentTopic";
|
||||||
|
import { createSymKeyFromPassword } from "./encryption";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represent a chat room. Only public chats are currently supported.
|
||||||
|
*/
|
||||||
export class Chat {
|
export class Chat {
|
||||||
private lastClockValue?: number;
|
private lastClockValue?: number;
|
||||||
private lastMessage?: ChatMessage;
|
private lastMessage?: ChatMessage;
|
||||||
public id: string;
|
|
||||||
|
|
||||||
constructor(id: string) {
|
private constructor(public id: string, public symKey: Uint8Array) {}
|
||||||
this.id = id;
|
|
||||||
|
/**
|
||||||
|
* Create a public chat room.
|
||||||
|
*/
|
||||||
|
public static async create(id: string) {
|
||||||
|
const symKey = await createSymKeyFromPassword(id);
|
||||||
|
|
||||||
|
return new Chat(id, symKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public get contentTopic(): string {
|
public get contentTopic(): string {
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { kdf } from "ecies-geth";
|
||||||
|
|
||||||
|
const AESKeyLength = 32; // bytes
|
||||||
|
|
||||||
|
export async function createSymKeyFromPassword(
|
||||||
|
password: string
|
||||||
|
): Promise<Uint8Array> {
|
||||||
|
return kdf(Buffer.from(password, "utf-8"), AESKeyLength);
|
||||||
|
}
|
|
@ -6,7 +6,7 @@ import { utils } from "js-waku";
|
||||||
import * as secp256k1 from "secp256k1";
|
import * as secp256k1 from "secp256k1";
|
||||||
|
|
||||||
export class Identity {
|
export class Identity {
|
||||||
public constructor(private privateKey: Uint8Array) {}
|
public constructor(public privateKey: Uint8Array) {}
|
||||||
|
|
||||||
public static generate(): Identity {
|
public static generate(): Identity {
|
||||||
const privateKey = generatePrivateKey();
|
const privateKey = generatePrivateKey();
|
||||||
|
@ -26,4 +26,11 @@ export class Identity {
|
||||||
|
|
||||||
return Buffer.concat([signature, Buffer.from([recid])]);
|
return Buffer.concat([signature, Buffer.from([recid])]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the compressed public key.
|
||||||
|
*/
|
||||||
|
public get publicKey(): Uint8Array {
|
||||||
|
return secp256k1.publicKeyCreate(this.privateKey, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
|
import { utils } from "js-waku";
|
||||||
|
|
||||||
import { ApplicationMetadataMessage } from "./application_metadata_message";
|
import { ApplicationMetadataMessage } from "./application_metadata_message";
|
||||||
import { Identity } from "./identity";
|
import { Identity } from "./identity";
|
||||||
|
@ -9,12 +10,14 @@ const testChatId = "test-chat-id";
|
||||||
describe("Messenger", () => {
|
describe("Messenger", () => {
|
||||||
let messengerAlice: Messenger;
|
let messengerAlice: Messenger;
|
||||||
let messengerBob: Messenger;
|
let messengerBob: Messenger;
|
||||||
|
let identityAlice: Identity;
|
||||||
|
let identityBob: Identity;
|
||||||
|
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
|
|
||||||
const identityAlice = Identity.generate();
|
identityAlice = Identity.generate();
|
||||||
const identityBob = Identity.generate();
|
identityBob = Identity.generate();
|
||||||
|
|
||||||
[messengerAlice, messengerBob] = await Promise.all([
|
[messengerAlice, messengerBob] = await Promise.all([
|
||||||
Messenger.create(identityAlice),
|
Messenger.create(identityAlice),
|
||||||
|
@ -44,11 +47,11 @@ describe("Messenger", () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Sends & Receive message in public chat", async function () {
|
it("Sends & Receive public chat messages", async function () {
|
||||||
this.timeout(10_000);
|
this.timeout(10_000);
|
||||||
|
|
||||||
messengerAlice.joinChat(testChatId);
|
await messengerAlice.joinChat(testChatId);
|
||||||
messengerBob.joinChat(testChatId);
|
await messengerBob.joinChat(testChatId);
|
||||||
|
|
||||||
const text = "This is a message.";
|
const text = "This is a message.";
|
||||||
|
|
||||||
|
@ -66,6 +69,30 @@ describe("Messenger", () => {
|
||||||
expect(receivedMessage.chatMessage?.text).to.eq(text);
|
expect(receivedMessage.chatMessage?.text).to.eq(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("public chat messages have signers", async function () {
|
||||||
|
this.timeout(10_000);
|
||||||
|
|
||||||
|
await messengerAlice.joinChat(testChatId);
|
||||||
|
await messengerBob.joinChat(testChatId);
|
||||||
|
|
||||||
|
const text = "This is a message.";
|
||||||
|
|
||||||
|
const receivedMessagePromise: Promise<ApplicationMetadataMessage> =
|
||||||
|
new Promise((resolve) => {
|
||||||
|
messengerBob.addObserver((message) => {
|
||||||
|
resolve(message);
|
||||||
|
}, testChatId);
|
||||||
|
});
|
||||||
|
|
||||||
|
await messengerAlice.sendMessage(text, testChatId);
|
||||||
|
|
||||||
|
const receivedMessage = await receivedMessagePromise;
|
||||||
|
|
||||||
|
expect(utils.bufToHex(receivedMessage.signer!)).to.eq(
|
||||||
|
utils.bufToHex(identityAlice.publicKey)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(async function () {
|
afterEach(async function () {
|
||||||
this.timeout(5000);
|
this.timeout(5000);
|
||||||
await messengerAlice.stop();
|
await messengerAlice.stop();
|
||||||
|
|
|
@ -39,10 +39,12 @@ export class Messenger {
|
||||||
*
|
*
|
||||||
* Use `addListener` to get messages received on this chat.
|
* Use `addListener` to get messages received on this chat.
|
||||||
*/
|
*/
|
||||||
public joinChat(chatId: string) {
|
public async joinChat(chatId: string) {
|
||||||
if (this.chatsById.has(chatId)) throw "Chat already joined";
|
if (this.chatsById.has(chatId)) throw "Chat already joined";
|
||||||
|
|
||||||
const chat = new Chat(chatId);
|
const chat = await Chat.create(chatId);
|
||||||
|
|
||||||
|
this.waku.relay.addDecryptionKey(chat.symKey);
|
||||||
|
|
||||||
this.waku.relay.addObserver(
|
this.waku.relay.addObserver(
|
||||||
(wakuMessage: WakuMessage) => {
|
(wakuMessage: WakuMessage) => {
|
||||||
|
@ -72,7 +74,7 @@ export class Messenger {
|
||||||
*/
|
*/
|
||||||
public async sendMessage(text: string, chatId: string): Promise<void> {
|
public async sendMessage(text: string, chatId: string): Promise<void> {
|
||||||
const chat = this.chatsById.get(chatId);
|
const chat = this.chatsById.get(chatId);
|
||||||
if (!chat) throw `Chat not joined: ${chatId}`;
|
if (!chat) throw `Failed to send message, chat not joined: ${chatId}`;
|
||||||
|
|
||||||
const chatMessage = chat.createMessage(text);
|
const chatMessage = chat.createMessage(text);
|
||||||
|
|
||||||
|
@ -82,10 +84,10 @@ export class Messenger {
|
||||||
this.identity
|
this.identity
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Use version 1 with signature
|
|
||||||
const wakuMessage = await WakuMessage.fromBytes(
|
const wakuMessage = await WakuMessage.fromBytes(
|
||||||
appMetadataMessage.encode(),
|
appMetadataMessage.encode(),
|
||||||
chat.contentTopic
|
chat.contentTopic,
|
||||||
|
{ symKey: chat.symKey, sigPrivKey: this.identity.privateKey }
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.waku.relay.send(wakuMessage);
|
await this.waku.relay.send(wakuMessage);
|
||||||
|
|
|
@ -1734,7 +1734,7 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"ecies-geth@npm:^1.5.2":
|
"ecies-geth@npm:^1.5.2, ecies-geth@npm:^1.5.3":
|
||||||
version: 1.5.3
|
version: 1.5.3
|
||||||
resolution: "ecies-geth@npm:1.5.3"
|
resolution: "ecies-geth@npm:1.5.3"
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5635,6 +5635,7 @@ fsevents@~2.3.2:
|
||||||
"@typescript-eslint/parser": ^4.31.1
|
"@typescript-eslint/parser": ^4.31.1
|
||||||
buffer: ^6.0.3
|
buffer: ^6.0.3
|
||||||
chai: ^4.3.4
|
chai: ^4.3.4
|
||||||
|
ecies-geth: ^1.5.3
|
||||||
eslint: ^7.32.0
|
eslint: ^7.32.0
|
||||||
eslint-config-prettier: ^8.3.0
|
eslint-config-prettier: ^8.3.0
|
||||||
eslint-import-resolver-node: ^0.3.6
|
eslint-import-resolver-node: ^0.3.6
|
||||||
|
|
Loading…
Reference in New Issue