diff --git a/src/Message.ts b/src/Message.ts index cedfd21e..cd5b83e5 100644 --- a/src/Message.ts +++ b/src/Message.ts @@ -188,26 +188,30 @@ export class MessageV1 extends MessageBase implements proto.MessageV1 { export class MessageV2 extends MessageBase implements proto.MessageV2 { senderAddress: string | undefined - private header: proto.MessageHeaderV2 // eslint-disable-line camelcase + private header: proto.MessageHeaderV2 + senderHmac: Uint8Array constructor( id: string, bytes: Uint8Array, obj: proto.Message, - header: proto.MessageHeaderV2 + header: proto.MessageHeaderV2, + senderHmac: Uint8Array ) { super(id, bytes, obj) this.header = header + this.senderHmac = senderHmac } static async create( obj: proto.Message, header: proto.MessageHeaderV2, - bytes: Uint8Array + bytes: Uint8Array, + senderHmac: Uint8Array ): Promise { const id = bytesToHex(await sha256(bytes)) - return new MessageV2(id, bytes, obj, header) + return new MessageV2(id, bytes, obj, header, senderHmac) } get sent(): Date { diff --git a/src/conversations/Conversation.ts b/src/conversations/Conversation.ts index 13ca2872..478316db 100644 --- a/src/conversations/Conversation.ts +++ b/src/conversations/Conversation.ts @@ -15,7 +15,7 @@ import type { import type Client from '../Client' import type { InvitationContext } from '../Invitation' import { DecodedMessage, MessageV1, MessageV2 } from '../Message' -import type { messageApi, keystore, ciphertext } from '@xmtp/proto' +import type { messageApi, keystore } from '@xmtp/proto' import { message, content as proto } from '@xmtp/proto' import { SignedPublicKey, @@ -658,14 +658,17 @@ export class ConversationV2 } const signedBytes = proto.SignedContent.encode(signed).finish() - const ciphertext = await this.encryptMessage(signedBytes, headerBytes) + const { encrypted: ciphertext, senderHmac } = await this.encryptMessage( + signedBytes, + headerBytes + ) const protoMsg = { v1: undefined, - v2: { headerBytes, ciphertext }, + v2: { headerBytes, ciphertext, senderHmac }, } const bytes = message.Message.encode(protoMsg).finish() - return MessageV2.create(protoMsg, header, bytes) + return MessageV2.create(protoMsg, header, bytes, senderHmac) } private async decryptBatch( @@ -709,10 +712,7 @@ export class ConversationV2 } } - private async encryptMessage( - payload: Uint8Array, - headerBytes: Uint8Array - ): Promise { + private async encryptMessage(payload: Uint8Array, headerBytes: Uint8Array) { const { responses } = await this.client.keystore.encryptV2({ requests: [ { @@ -725,8 +725,8 @@ export class ConversationV2 if (responses.length !== 1) { throw new Error('Invalid response length') } - const { encrypted } = getResultOrThrow(responses[0]) - return encrypted + const { encrypted, senderHmac } = getResultOrThrow(responses[0]) + return { encrypted, senderHmac } } private async buildDecodedMessage( @@ -829,7 +829,7 @@ export class ConversationV2 throw new Error('topic mismatch') } - return MessageV2.create(msg, header, env.message) + return MessageV2.create(msg, header, env.message, msg.v2.senderHmac) } async decodeMessage( diff --git a/src/keystore/InMemoryKeystore.ts b/src/keystore/InMemoryKeystore.ts index 4fa1c9c9..374372ab 100644 --- a/src/keystore/InMemoryKeystore.ts +++ b/src/keystore/InMemoryKeystore.ts @@ -35,6 +35,7 @@ import { generateUserPreferencesTopic, } from '../crypto/selfEncryption' import type { KeystoreInterface } from '..' +import { generateHmacSignature } from '../crypto/encryption' const { ErrorCode } = keystore @@ -295,10 +296,14 @@ export default class InMemoryKeystore implements KeystoreInterface { ) } + const keyMaterial = getKeyMaterial(topicData.invitation) + const ciphertext = await encryptV2(payload, keyMaterial, headerBytes) + return { - encrypted: await encryptV2( - payload, - getKeyMaterial(topicData.invitation), + encrypted: ciphertext, + senderHmac: await generateHmacSignature( + keyMaterial, + new TextEncoder().encode(contentTopic), headerBytes ), }