Merge pull request #7 from status-im/waku-version-1

This commit is contained in:
Franck Royer 2021-09-24 10:33:32 +10:00 committed by GitHub
commit 98bf7f7e3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 85 additions and 15 deletions

View File

@ -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",

View File

@ -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));
}
} }

View File

@ -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 {

View File

@ -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);
}

View File

@ -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);
}
} }

View File

@ -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();

View File

@ -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);

View File

@ -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