From 9c2ee46db0f8b9b9ecee2e3de8d614606b7eec98 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 11:25:02 +1100 Subject: [PATCH 1/8] New emoji field --- .../proto/communities/v1/chat_identity.proto | 2 ++ .../src/proto/communities/v1/chat_identity.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/packages/status-communities/proto/communities/v1/chat_identity.proto b/packages/status-communities/proto/communities/v1/chat_identity.proto index c455f278..314fb1b1 100644 --- a/packages/status-communities/proto/communities/v1/chat_identity.proto +++ b/packages/status-communities/proto/communities/v1/chat_identity.proto @@ -22,6 +22,8 @@ message ChatIdentity { string description = 5; string color = 6; + + string emoji = 7; } // ProfileImage represents data associated with a user's profile image diff --git a/packages/status-communities/src/proto/communities/v1/chat_identity.ts b/packages/status-communities/src/proto/communities/v1/chat_identity.ts index 7b311934..c75a7900 100644 --- a/packages/status-communities/src/proto/communities/v1/chat_identity.ts +++ b/packages/status-communities/src/proto/communities/v1/chat_identity.ts @@ -22,6 +22,7 @@ export interface ChatIdentity { /** description is the user set description, valid only for organisations */ description: string; color: string; + emoji: string; } export interface ChatIdentity_ImagesEntry { @@ -98,6 +99,7 @@ const baseChatIdentity: object = { displayName: "", description: "", color: "", + emoji: "", }; export const ChatIdentity = { @@ -126,6 +128,9 @@ export const ChatIdentity = { if (message.color !== "") { writer.uint32(50).string(message.color); } + if (message.emoji !== "") { + writer.uint32(58).string(message.emoji); + } return writer; }, @@ -161,6 +166,9 @@ export const ChatIdentity = { case 6: message.color = reader.string(); break; + case 7: + message.emoji = reader.string(); + break; default: reader.skipType(tag & 7); break; @@ -202,6 +210,11 @@ export const ChatIdentity = { } else { message.color = ""; } + if (object.emoji !== undefined && object.emoji !== null) { + message.emoji = String(object.emoji); + } else { + message.emoji = ""; + } return message; }, @@ -220,6 +233,7 @@ export const ChatIdentity = { message.description !== undefined && (obj.description = message.description); message.color !== undefined && (obj.color = message.color); + message.emoji !== undefined && (obj.emoji = message.emoji); return obj; }, @@ -258,6 +272,11 @@ export const ChatIdentity = { } else { message.color = ""; } + if (object.emoji !== undefined && object.emoji !== null) { + message.emoji = object.emoji; + } else { + message.emoji = ""; + } return message; }, }; From 501d9d50d439a0cedab747aedf856682166dc3d9 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 11:25:31 +1100 Subject: [PATCH 2/8] Add getters --- .../src/wire/chat_identity.ts | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/packages/status-communities/src/wire/chat_identity.ts b/packages/status-communities/src/wire/chat_identity.ts index 90bc9dc7..b43c8ecb 100644 --- a/packages/status-communities/src/wire/chat_identity.ts +++ b/packages/status-communities/src/wire/chat_identity.ts @@ -1,6 +1,7 @@ import { Reader } from "protobufjs"; import * as proto from "../proto/communities/v1/chat_identity"; +import { IdentityImage } from "../proto/communities/v1/chat_identity"; export class ChatIdentity { public constructor(public proto: proto.ChatIdentity) {} @@ -14,4 +15,37 @@ export class ChatIdentity { encode(): Uint8Array { return proto.ChatIdentity.encode(this.proto).finish(); } + + /** Lamport timestamp of the message */ + get clock(): number | undefined { + return this.proto.clock; + } + + /** ens_name is the valid ENS name associated with the chat key */ + get ensName(): string | undefined { + return this.proto.ensName; + } + + /** images is a string indexed mapping of images associated with an identity */ + get images(): { [key: string]: IdentityImage } | undefined { + return this.proto.images; + } + + /** display name is the user set identity, valid only for organisations */ + get displayName(): string | undefined { + return this.proto.displayName; + } + + /** description is the user set description, valid only for organisations */ + get description(): string | undefined { + return this.proto.description; + } + + get color(): string | undefined { + return this.proto.color; + } + + get emoji(): string | undefined { + return this.proto.emoji; + } } From 5cbc4b90fbd527ba9d060dcdd303e033f5686830 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 11:26:09 +1100 Subject: [PATCH 3/8] Fix KDF algorithm --- ...es-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip | Bin 0 -> 2645 bytes packages/status-communities/package.json | 2 ++ .../status-communities/src/encryption.spec.ts | 14 ++++++++++++++ packages/status-communities/src/encryption.ts | 10 ++++++++-- yarn.lock | 13 ++++++++++++- 5 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 .yarn/cache/@types-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip create mode 100644 packages/status-communities/src/encryption.spec.ts diff --git a/.yarn/cache/@types-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip b/.yarn/cache/@types-pbkdf2-npm-3.1.0-9fa74ff7fb-d15024b195.zip new file mode 100644 index 0000000000000000000000000000000000000000..7afb6d838864b55e4fbe5dad23d0d4546b85ab93 GIT binary patch literal 2645 zcmaKudpy(MAIHZgHj()XYo%z+C2Mj?Qn@Xf<}!sankcz#lCt^owdPW;iCl7@%U7jQ z(WKDK-Q;p73$m>SHe-lA=_whKr=lfE^0I{#T za*Wn73Ka$bf@Wm;yZ;DUs%Bp8aF&zWK=o8R$gUJO(xT8WNjKT{!RotNRb=Ta)0TT zMJfpDPNdw^TlO`5vVjYr&x}n=B41i{&B3--YrRV7l(=&!VoptFfR@$( znEyFo<6gMv#rRja%flBMl6f|JT|;JP^%W-jhmVWp^g*Q+uekw>6+2*A1XAMMUbT@? zs=S~49=>MRb|y5FcH?XR+u^OYErXbc!2IU@9=^$hv+ln7QIESa+sslIBE{P`0~&f)~TU0Sa>ZSfx7cGo zk=Uv?*OzInT;Ot)hz{Id29frWE1Rr33wcyJkFE08@%6Yn!gV=!z9m&8cNBfn6>WRE z++8_j6s(+d_Sunw`*vJE{mLV0yfhz0jhK`+ti~?wqos4AeQj>0Agr35o0Dhd( z!||%zjyD|u{b&agt%*F=X6LB=IJ{A!N=Nz{WlwbDaVjNSMclF4kGdo+p`M^F$H#MX z$m;DgOPP_)%e+s$$kZ7Ny@^xy_%mS~8mG9;)`>m>qJZ*k)K zo88%LcuU$-_ttjX0xOzlZ~lt#uPSJEwgj9LsE{itA7E}&O+b(ZI;wDTVk&T3N`(9@d+Dl>d z6FG`vafR3Hy*+n42c5WjxkSABLi4GOITp1X~i)j)VZ?hFsJ>YmJhB@^wg1C+$*Hi zB=P3WqV{{yywqB(3k#dUySbF#a3?&tQm@hlWZ`VcHpB$QMH=FO3MvP6uC_$0B^9SG zCL$bE6*`x0Otz@RHI+ZdS5y_d$*y!UuWH?x5~`JD2Quqwx7YU`j2?UT`pMvlPan4i zyZtI!;npioE&|ay1*wBvUoyg)3Kzo73^en0%QYH#v;m2#xaLmF8w;^J7(rG&v*3mBwK zbQ+#AhS&eBf-UffvFkLO>5JnI7uPHp8PNjR+wy=a$IF=xfx;$>VY@pP)v$%=n6&I9 ztTVsc{KdWw*7cqPq(U);MxnPm<1nZ3e|tCIj63^3RKljAV+0tqzoDs!%?r-ZvKiJb zxygQ-b)~vCH2xC&Y4sE*e0#Py*_el9Bz65!n0Nh#^BH99%EF8qs$S2HzhJ&pk9I7v z_8*v=-A0gMxCp@pqi*+7tq!%q`}h^PixifoBm5G1>TEvcvyusZ_mVfEm0m_T5lEmt zX6+Xt=9DwH%AEn%vtX7mOI3G{EcDtqxM?4*%`Zt2U;(v)szGg@Q0xJ%iW-nqXe#^M z^Lpz=$FO$9&|IyJrI7F;;O`BCK&7v3()dgFiziqw^c!$BRAX)E4?FSu#lIMe)i!BN z5Db4={5#=?z4!)Nt1FpNVA1}aK;@bN4lh`k) qXzLvHXEj@kT$^1#kqH~tMgD)T*;tAQCIbMXf=^12rn|pd$bSJI>Kn8G literal 0 HcmV?d00001 diff --git a/packages/status-communities/package.json b/packages/status-communities/package.json index 3f7d396b..eec56752 100644 --- a/packages/status-communities/package.json +++ b/packages/status-communities/package.json @@ -25,6 +25,7 @@ "devDependencies": { "@types/chai": "^4.2.22", "@types/mocha": "^9.0.0", + "@types/pbkdf2": "^3.1.0", "@types/secp256k1": "^4.0.3", "@typescript-eslint/eslint-plugin": "^4.31.1", "@typescript-eslint/parser": "^4.31.1", @@ -47,6 +48,7 @@ "ecies-geth": "^1.5.3", "js-sha3": "^0.8.0", "js-waku": "^0.13.1", + "pbkdf2": "^3.1.2", "protobufjs": "^6.11.2", "secp256k1": "^4.0.2" } diff --git a/packages/status-communities/src/encryption.spec.ts b/packages/status-communities/src/encryption.spec.ts new file mode 100644 index 00000000..2c16ed94 --- /dev/null +++ b/packages/status-communities/src/encryption.spec.ts @@ -0,0 +1,14 @@ +import { expect } from "chai"; + +import { createSymKeyFromPassword } from "./encryption"; + +describe("Encryption", () => { + it("Generate symmetric key from password", async function () { + const str = "arbitrary data here"; + const symKey = await createSymKeyFromPassword(str); + + expect(Buffer.from(symKey).toString("hex")).to.eq( + "c49ad65ebf2a7b7253bf400e3d27719362a91b2c9b9f54d50a69117021666c33" + ); + }); +}); diff --git a/packages/status-communities/src/encryption.ts b/packages/status-communities/src/encryption.ts index 4aa297b2..e27d06af 100644 --- a/packages/status-communities/src/encryption.ts +++ b/packages/status-communities/src/encryption.ts @@ -1,9 +1,15 @@ -import { kdf } from "ecies-geth"; +import pbkdf2 from "pbkdf2"; const AESKeyLength = 32; // bytes export async function createSymKeyFromPassword( password: string ): Promise { - return kdf(Buffer.from(password, "utf-8"), AESKeyLength); + return pbkdf2.pbkdf2Sync( + Buffer.from(password, "utf-8"), + "", + 65356, + AESKeyLength, + "sha256" + ); } diff --git a/yarn.lock b/yarn.lock index 9e2ff6ec..dc797f0c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -938,6 +938,15 @@ __metadata: languageName: node linkType: hard +"@types/pbkdf2@npm:^3.1.0": + version: 3.1.0 + resolution: "@types/pbkdf2@npm:3.1.0" + dependencies: + "@types/node": "*" + checksum: d15024b1957c21cf3b8887329d9bd8dfde754cf13a09d76ae25f1391cfc62bb8b8d7b760773c5dbaa748172fba8b3e0c3dbe962af6ccbd69b76df12a48dfba40 + languageName: node + linkType: hard + "@types/prettier@npm:^1.19.0": version: 1.19.1 resolution: "@types/prettier@npm:1.19.1" @@ -8755,7 +8764,7 @@ fsevents@~2.3.2: languageName: node linkType: hard -"pbkdf2@npm:^3.0.3": +"pbkdf2@npm:^3.0.3, pbkdf2@npm:^3.1.2": version: 3.1.2 resolution: "pbkdf2@npm:3.1.2" dependencies: @@ -10487,6 +10496,7 @@ resolve@^2.0.0-next.3: dependencies: "@types/chai": ^4.2.22 "@types/mocha": ^9.0.0 + "@types/pbkdf2": ^3.1.0 "@types/secp256k1": ^4.0.3 "@typescript-eslint/eslint-plugin": ^4.31.1 "@typescript-eslint/parser": ^4.31.1 @@ -10503,6 +10513,7 @@ resolve@^2.0.0-next.3: js-waku: ^0.13.1 mocha: ^9.1.1 npm-run-all: ^4.1.5 + pbkdf2: ^3.1.2 prettier: ^2.4.0 protobufjs: ^6.11.2 secp256k1: ^4.0.2 From e25f3c0b44879fd4963d409496f33c38bcb2dcd5 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 11:26:43 +1100 Subject: [PATCH 4/8] Return proto wrapper instead of proto class --- packages/status-communities/src/wire/community_chat.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/status-communities/src/wire/community_chat.ts b/packages/status-communities/src/wire/community_chat.ts index 915dfd5b..474c2b99 100644 --- a/packages/status-communities/src/wire/community_chat.ts +++ b/packages/status-communities/src/wire/community_chat.ts @@ -1,12 +1,13 @@ import { Reader } from "protobufjs"; -import { ChatIdentity } from "../proto/communities/v1/chat_identity"; import * as proto from "../proto/communities/v1/communities"; import { CommunityMember, CommunityPermissions, } from "../proto/communities/v1/communities"; +import { ChatIdentity } from "./chat_identity"; + export class CommunityChat { public constructor(public proto: proto.CommunityChat) {} @@ -41,7 +42,9 @@ export class CommunityChat { } public get identity(): ChatIdentity | undefined { - return this.proto.identity; + if (!this.proto.identity) return; + + return new ChatIdentity(this.proto.identity); } // TODO: Document this From 373603067ca020be99e6c3cdc4147419f62ff6c0 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 11:27:22 +1100 Subject: [PATCH 5/8] Decrypt and correctly unwrap Community Description messages --- .../status-communities/src/community.spec.ts | 29 +++++++++++++++++++ .../src/wire/community_description.ts | 10 ++++++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages/status-communities/src/community.spec.ts diff --git a/packages/status-communities/src/community.spec.ts b/packages/status-communities/src/community.spec.ts new file mode 100644 index 00000000..e1ab10eb --- /dev/null +++ b/packages/status-communities/src/community.spec.ts @@ -0,0 +1,29 @@ +import { expect } from "chai"; +import { Waku } from "js-waku"; + +import { Community } from "./community"; +import { CommunityDescription } from "./wire/community_description"; + +describe("Community", () => { + it.skip("Retrieves community description For DappConnect Test from Waku prod fleet", async function () { + this.timeout(20000); + const waku = await Waku.create({ bootstrap: true }); + + await waku.waitForConnectedPeer(); + + const community = await Community.instantiateCommunity( + "0x0262c65c881f5a9f79343a26faaa02aad3af7c533d9445fb1939ed11b8bf4d2abd", + waku + ); + await community.refreshCommunityDescription(); + const desc = community.description as CommunityDescription; + expect(desc).to.not.be.undefined; + + expect(desc.identity?.displayName).to.eq("DappConnect Test"); + const chats = Array.from(desc.chats.values()).map( + (chat) => chat?.identity?.displayName + ); + expect(chats).to.include("foobar"); + expect(chats).to.include("another-channel!"); + }); +}); diff --git a/packages/status-communities/src/wire/community_description.ts b/packages/status-communities/src/wire/community_description.ts index f86d95f8..b4bef503 100644 --- a/packages/status-communities/src/wire/community_description.ts +++ b/packages/status-communities/src/wire/community_description.ts @@ -3,9 +3,11 @@ import { WakuMessage, WakuStore } from "js-waku"; import { Reader } from "protobufjs"; import { idToContentTopic } from "../contentTopic"; +import { createSymKeyFromPassword } from "../encryption"; import * as proto from "../proto/communities/v1/communities"; import { bufToHex } from "../utils"; +import { ApplicationMetadataMessage } from "./application_metadata_message"; import { ChatIdentity } from "./chat_identity"; import { CommunityChat } from "./community_chat"; @@ -45,8 +47,11 @@ export class CommunityDescription { orderedMessages.forEach((message: WakuMessage) => { if (!message.payload) return; try { + const metadata = ApplicationMetadataMessage.decode(message.payload); + if (!metadata.payload) return; + const _communityDescription = CommunityDescription.decode( - message.payload + metadata.payload ); if (!_communityDescription.identity) return; @@ -61,9 +66,12 @@ export class CommunityDescription { }); }; + const symKey = await createSymKeyFromPassword(hexCommunityPublicKey); + await wakuStore .queryHistory([contentTopic], { callback, + decryptionKeys: [symKey], }) .catch((e) => { dbg( From c4512a08407788f3b0e4521a6831912b89239774 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 14:03:44 +1100 Subject: [PATCH 6/8] Run live data test locally but not in CI Running in CI would mean the CI breaks if there are issues with the waku prod fleet. --- packages/status-communities/src/community.spec.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/status-communities/src/community.spec.ts b/packages/status-communities/src/community.spec.ts index e1ab10eb..667914b7 100644 --- a/packages/status-communities/src/community.spec.ts +++ b/packages/status-communities/src/community.spec.ts @@ -4,8 +4,15 @@ import { Waku } from "js-waku"; import { Community } from "./community"; import { CommunityDescription } from "./wire/community_description"; -describe("Community", () => { - it.skip("Retrieves community description For DappConnect Test from Waku prod fleet", async function () { +describe("Community live data", () => { + before(function () { + if (process.env.CI) { + // Skip live data test in CI + this.skip(); + } + }); + + it("Retrieves community description For DappConnect Test from Waku prod fleet", async function () { this.timeout(20000); const waku = await Waku.create({ bootstrap: true }); From 647eaac7221d8bb52087249bc8c1d4cc1f9801fc Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 14:04:10 +1100 Subject: [PATCH 7/8] `Community.instantiateCommunity` already retrieve description --- packages/status-communities/src/community.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/status-communities/src/community.spec.ts b/packages/status-communities/src/community.spec.ts index 667914b7..770e77a8 100644 --- a/packages/status-communities/src/community.spec.ts +++ b/packages/status-communities/src/community.spec.ts @@ -22,7 +22,6 @@ describe("Community live data", () => { "0x0262c65c881f5a9f79343a26faaa02aad3af7c533d9445fb1939ed11b8bf4d2abd", waku ); - await community.refreshCommunityDescription(); const desc = community.description as CommunityDescription; expect(desc).to.not.be.undefined; From a3ade13a6f7902ee3f8c35e3766b25758e0b4b13 Mon Sep 17 00:00:00 2001 From: Franck Royer Date: Tue, 19 Oct 2021 14:17:51 +1100 Subject: [PATCH 8/8] Instantiate all Chats when importing community --- packages/status-communities/src/chat.ts | 14 +++++-- .../status-communities/src/community.spec.ts | 9 ++++- packages/status-communities/src/community.ts | 39 +++++++++---------- 3 files changed, 37 insertions(+), 25 deletions(-) diff --git a/packages/status-communities/src/chat.ts b/packages/status-communities/src/chat.ts index 54ee5da2..8727fe32 100644 --- a/packages/status-communities/src/chat.ts +++ b/packages/status-communities/src/chat.ts @@ -1,6 +1,7 @@ import { idToContentTopic } from "./contentTopic"; import { createSymKeyFromPassword } from "./encryption"; import { ChatMessage, Content } from "./wire/chat_message"; +import { CommunityChat } from "./wire/community_chat"; /** * Represent a chat room. Only public chats are currently supported. @@ -9,16 +10,23 @@ export class Chat { private lastClockValue?: number; private lastMessage?: ChatMessage; - private constructor(public id: string, public symKey: Uint8Array) {} + private constructor( + public id: string, + public symKey: Uint8Array, + public communityChat?: CommunityChat + ) {} /** * Create a public chat room. * [[Community.instantiateChat]] MUST be used for chats belonging to a community. */ - public static async create(id: string): Promise { + public static async create( + id: string, + communityChat?: CommunityChat + ): Promise { const symKey = await createSymKeyFromPassword(id); - return new Chat(id, symKey); + return new Chat(id, symKey, communityChat); } public get contentTopic(): string { diff --git a/packages/status-communities/src/community.spec.ts b/packages/status-communities/src/community.spec.ts index 770e77a8..bd3490d5 100644 --- a/packages/status-communities/src/community.spec.ts +++ b/packages/status-communities/src/community.spec.ts @@ -26,9 +26,16 @@ describe("Community live data", () => { expect(desc).to.not.be.undefined; expect(desc.identity?.displayName).to.eq("DappConnect Test"); - const chats = Array.from(desc.chats.values()).map( + + const descChats = Array.from(desc.chats.values()).map( (chat) => chat?.identity?.displayName ); + expect(descChats).to.include("foobar"); + expect(descChats).to.include("another-channel!"); + + const chats = Array.from(community.chats.values()).map( + (chat) => chat?.communityChat?.identity?.displayName + ); expect(chats).to.include("foobar"); expect(chats).to.include("another-channel!"); }); diff --git a/packages/status-communities/src/community.ts b/packages/status-communities/src/community.ts index 4ffac4f6..424bd8ac 100644 --- a/packages/status-communities/src/community.ts +++ b/packages/status-communities/src/community.ts @@ -11,12 +11,13 @@ const dbg = debug("communities:community"); export class Community { public publicKey: Uint8Array; private waku: Waku; - + public chats: Map; // Chat id, Chat public description?: CommunityDescription; constructor(publicKey: Uint8Array, waku: Waku) { this.publicKey = publicKey; this.waku = waku; + this.chats = new Map(); } /** @@ -60,6 +61,12 @@ export class Community { } this.description = desc; + + await Promise.all( + Array.from(this.description.chats).map(([chatUuid, communityChat]) => { + return this.instantiateChat(chatUuid, communityChat); + }) + ); } /** @@ -68,28 +75,18 @@ export class Community { * * @throws string If the Community Description is unavailable or the chat is not found; */ - public async instantiateChat(chatName: string): Promise { - if (!this.description) { - await this.refreshCommunityDescription(); - if (!this.description) - throw "Failed to retrieve community description, cannot instantiate chat"; - } + private async instantiateChat( + chatUuid: string, + communityChat: CommunityChat + ): Promise { + if (!this.description) + throw "Failed to retrieve community description, cannot instantiate chat"; - let communityChat: CommunityChat | undefined; - let chatUuid: string | undefined; + const chatId = this.publicKeyStr + chatUuid; + if (this.chats.get(chatId)) return; - this.description.chats.forEach((_chat, _id) => { - if (chatUuid) return; + const chat = await Chat.create(chatId, communityChat); - if (_chat.identity?.displayName === chatName) { - chatUuid = _id; - communityChat = _chat; - } - }); - - if (!communityChat || !chatUuid) - throw `Failed to retrieve community community chat with name ${chatName}`; - - return Chat.create(this.publicKeyStr + chatUuid); + this.chats.set(chatId, chat); } }