Merge pull request #7 from status-im/waku-version-1
This commit is contained in:
commit
98bf7f7e3b
|
@ -44,6 +44,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"buffer": "^6.0.3",
|
||||
"ecies-geth": "^1.5.3",
|
||||
"js-sha3": "^0.8.0",
|
||||
"js-waku": "^0.12.0",
|
||||
"protobufjs": "^6.11.2",
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { keccak256 } from "js-sha3";
|
||||
import { utils } from "js-waku";
|
||||
import { Reader } from "protobufjs";
|
||||
import secp256k1 from "secp256k1";
|
||||
|
||||
import { ChatMessage } from "./chat_message";
|
||||
import { Identity } from "./identity";
|
||||
|
@ -58,4 +61,14 @@ export class ApplicationMetadataMessage {
|
|||
|
||||
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 { chatIdToContentTopic } from "./contentTopic";
|
||||
import { createSymKeyFromPassword } from "./encryption";
|
||||
|
||||
/**
|
||||
* Represent a chat room. Only public chats are currently supported.
|
||||
*/
|
||||
export class Chat {
|
||||
private lastClockValue?: number;
|
||||
private lastMessage?: ChatMessage;
|
||||
public id: string;
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
private constructor(public id: string, public symKey: Uint8Array) {}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
|
|
|
@ -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";
|
||||
|
||||
export class Identity {
|
||||
public constructor(private privateKey: Uint8Array) {}
|
||||
public constructor(public privateKey: Uint8Array) {}
|
||||
|
||||
public static generate(): Identity {
|
||||
const privateKey = generatePrivateKey();
|
||||
|
@ -26,4 +26,11 @@ export class Identity {
|
|||
|
||||
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 { utils } from "js-waku";
|
||||
|
||||
import { ApplicationMetadataMessage } from "./application_metadata_message";
|
||||
import { Identity } from "./identity";
|
||||
|
@ -9,12 +10,14 @@ const testChatId = "test-chat-id";
|
|||
describe("Messenger", () => {
|
||||
let messengerAlice: Messenger;
|
||||
let messengerBob: Messenger;
|
||||
let identityAlice: Identity;
|
||||
let identityBob: Identity;
|
||||
|
||||
beforeEach(async function () {
|
||||
this.timeout(10_000);
|
||||
|
||||
const identityAlice = Identity.generate();
|
||||
const identityBob = Identity.generate();
|
||||
identityAlice = Identity.generate();
|
||||
identityBob = Identity.generate();
|
||||
|
||||
[messengerAlice, messengerBob] = await Promise.all([
|
||||
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);
|
||||
|
||||
messengerAlice.joinChat(testChatId);
|
||||
messengerBob.joinChat(testChatId);
|
||||
await messengerAlice.joinChat(testChatId);
|
||||
await messengerBob.joinChat(testChatId);
|
||||
|
||||
const text = "This is a message.";
|
||||
|
||||
|
@ -66,6 +69,30 @@ describe("Messenger", () => {
|
|||
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 () {
|
||||
this.timeout(5000);
|
||||
await messengerAlice.stop();
|
||||
|
|
|
@ -39,10 +39,12 @@ export class Messenger {
|
|||
*
|
||||
* 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";
|
||||
|
||||
const chat = new Chat(chatId);
|
||||
const chat = await Chat.create(chatId);
|
||||
|
||||
this.waku.relay.addDecryptionKey(chat.symKey);
|
||||
|
||||
this.waku.relay.addObserver(
|
||||
(wakuMessage: WakuMessage) => {
|
||||
|
@ -72,7 +74,7 @@ export class Messenger {
|
|||
*/
|
||||
public async sendMessage(text: string, chatId: string): Promise<void> {
|
||||
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);
|
||||
|
||||
|
@ -82,10 +84,10 @@ export class Messenger {
|
|||
this.identity
|
||||
);
|
||||
|
||||
// TODO: Use version 1 with signature
|
||||
const wakuMessage = await WakuMessage.fromBytes(
|
||||
appMetadataMessage.encode(),
|
||||
chat.contentTopic
|
||||
chat.contentTopic,
|
||||
{ symKey: chat.symKey, sigPrivKey: this.identity.privateKey }
|
||||
);
|
||||
|
||||
await this.waku.relay.send(wakuMessage);
|
||||
|
|
|
@ -1734,7 +1734,7 @@ __metadata:
|
|||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ecies-geth@npm:^1.5.2":
|
||||
"ecies-geth@npm:^1.5.2, ecies-geth@npm:^1.5.3":
|
||||
version: 1.5.3
|
||||
resolution: "ecies-geth@npm:1.5.3"
|
||||
dependencies:
|
||||
|
@ -5635,6 +5635,7 @@ fsevents@~2.3.2:
|
|||
"@typescript-eslint/parser": ^4.31.1
|
||||
buffer: ^6.0.3
|
||||
chai: ^4.3.4
|
||||
ecies-geth: ^1.5.3
|
||||
eslint: ^7.32.0
|
||||
eslint-config-prettier: ^8.3.0
|
||||
eslint-import-resolver-node: ^0.3.6
|
||||
|
|
Loading…
Reference in New Issue