Skip to content

Commit

Permalink
feat: add method to get conversation HMAC keys
Browse files Browse the repository at this point in the history
  • Loading branch information
rygine committed Mar 12, 2024
1 parent acee946 commit 4b6289a
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 7 deletions.
2 changes: 1 addition & 1 deletion src/crypto/encryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ async function hkdf(secret: Uint8Array, salt: Uint8Array): Promise<CryptoKey> {
)
}

async function hkdfHmacKey(
export async function hkdfHmacKey(
secret: Uint8Array,
salt: Uint8Array
): Promise<CryptoKey> {
Expand Down
57 changes: 51 additions & 6 deletions src/keystore/InMemoryKeystore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {
generateUserPreferencesTopic,
} from '../crypto/selfEncryption'
import type { KeystoreInterface } from '..'
import { generateHmacSignature } from '../crypto/encryption'
import { generateHmacSignature, hkdfHmacKey } from '../crypto/encryption'

const { ErrorCode } = keystore

Expand Down Expand Up @@ -298,14 +298,19 @@ export default class InMemoryKeystore implements KeystoreInterface {

const keyMaterial = getKeyMaterial(topicData.invitation)
const ciphertext = await encryptV2(payload, keyMaterial, headerBytes)
const thirtyDayPeriodsSinceEpoch = Math.floor(
Date.now() / 1000 / 60 / 60 / 24 / 30
)
const salt = `${thirtyDayPeriodsSinceEpoch}-${this.accountAddress}`
const hmac = await generateHmacSignature(
keyMaterial,
new TextEncoder().encode(salt),
headerBytes
)

return {
encrypted: ciphertext,
senderHmac: await generateHmacSignature(
keyMaterial,
new TextEncoder().encode(contentTopic),
headerBytes
),
senderHmac: hmac,
}
},
ErrorCode.ERROR_CODE_INVALID_INPUT
Expand Down Expand Up @@ -585,4 +590,44 @@ export default class InMemoryKeystore implements KeystoreInterface {
lookupTopic(topic: string) {
return this.v2Store.lookup(topic)
}

async getV2ConversationHmacKeys(): Promise<keystore.GetConversationHmacKeysResponse> {
const thirtyDayPeriodsSinceEpoch = Math.floor(
Date.now() / 1000 / 60 / 60 / 24 / 30
)

const hmacKeys: keystore.GetConversationHmacKeysResponse['hmacKeys'] = {}

this.v2Store.topics.forEach(async (topicData) => {
if (topicData.invitation?.topic) {
const keyMaterial = getKeyMaterial(topicData.invitation)
const values = await Promise.all(
[
thirtyDayPeriodsSinceEpoch - 1,
thirtyDayPeriodsSinceEpoch,
thirtyDayPeriodsSinceEpoch + 1,
].map(async (value) => {
const salt = `${value}-${this.accountAddress}`
const hmacKey = await hkdfHmacKey(
keyMaterial,
new TextEncoder().encode(salt)
)
return {
thirtyDayPeriodsSinceEpoch: value,
// convert CryptoKey to Uint8Array to match the proto
hmacKey: new Uint8Array(
await crypto.subtle.exportKey('raw', hmacKey)
),
}
})
)

hmacKeys[topicData.invitation.topic] = {
values,
}
}
})

return { hmacKeys }
}
}
5 changes: 5 additions & 0 deletions src/keystore/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ export interface Keystore {
* Get the private preferences topic identifier
*/
getPrivatePreferencesTopicIdentifier(): Promise<keystore.GetPrivatePreferencesTopicIdentifierResponse>
/**
* Returns the conversation HMAC keys for the current, previous, and next
* 30 day periods since the epoch
*/
getV2ConversationHmacKeys(): Promise<keystore.GetConversationHmacKeysResponse>
}

export type TopicData = WithoutUndefined<keystore.TopicMap_TopicData>
4 changes: 4 additions & 0 deletions src/keystore/rpcDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@ export const apiDefs = {
req: null,
res: keystore.GetPrivatePreferencesTopicIdentifierResponse,
},
getV2ConversationHmacKeys: {
req: null,
res: keystore.GetConversationHmacKeysResponse,
},
}

export type KeystoreApiDefs = typeof apiDefs
Expand Down

0 comments on commit 4b6289a

Please sign in to comment.