Skip to content

Commit

Permalink
Merge pull request #279 from xmtp/np/client-encryption-key
Browse files Browse the repository at this point in the history
Ability to set encryption key
  • Loading branch information
cameronvoell authored Feb 23, 2024
2 parents ea710dc + 8afe5e8 commit ea461fb
Show file tree
Hide file tree
Showing 8 changed files with 161 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ class XMTPModule : Module() {
//
// Auth functions
//
AsyncFunction("auth") { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean? ->
AsyncFunction("auth") { address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean?, dbEncryptionKey: List<Int>?, dbPath: String? ->
logV("auth")
requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment)
val reactSigner = ReactNativeSigner(module = this@XMTPModule, address = address)
Expand All @@ -185,13 +185,19 @@ class XMTPModule : Module() {
val preEnableIdentityCallback: PreEventCallback? =
preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true }
val context = if (enableAlphaMls == true) context else null
val encryptionKeyBytes =
dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v ->
a.apply { set(i, v.toByte()) }
}

val options = ClientOptions(
api = apiEnvironments(environment, appVersion),
preCreateIdentityCallback = preCreateIdentityCallback,
preEnableIdentityCallback = preEnableIdentityCallback,
enableAlphaMls = enableAlphaMls == true,
appContext = context
appContext = context,
dbEncryptionKey = encryptionKeyBytes,
dbPath = dbPath
)
clients[address] = Client().create(account = reactSigner, options = options)
ContentJson.Companion
Expand All @@ -205,7 +211,7 @@ class XMTPModule : Module() {
}

// Generate a random wallet and set the client to that
AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean? ->
AsyncFunction("createRandom") { environment: String, appVersion: String?, hasCreateIdentityCallback: Boolean?, hasEnableIdentityCallback: Boolean?, enableAlphaMls: Boolean?, dbEncryptionKey: List<Int>?, dbPath: String? ->
logV("createRandom")
requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment)
val privateKey = PrivateKeyBuilder()
Expand All @@ -219,29 +225,42 @@ class XMTPModule : Module() {
val preEnableIdentityCallback: PreEventCallback? =
preEnableIdentityCallback.takeIf { hasEnableIdentityCallback == true }
val context = if (enableAlphaMls == true) context else null
val encryptionKeyBytes =
dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v ->
a.apply { set(i, v.toByte()) }
}

val options = ClientOptions(
api = apiEnvironments(environment, appVersion),
preCreateIdentityCallback = preCreateIdentityCallback,
preEnableIdentityCallback = preEnableIdentityCallback,
enableAlphaMls = enableAlphaMls == true,
appContext = context
appContext = context,
dbEncryptionKey = encryptionKeyBytes,
dbPath = dbPath
)
val randomClient = Client().create(account = privateKey, options = options)
ContentJson.Companion
clients[randomClient.address] = randomClient
randomClient.address
}

AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableAlphaMls: Boolean? ->
AsyncFunction("createFromKeyBundle") { keyBundle: String, environment: String, appVersion: String?, enableAlphaMls: Boolean?, dbEncryptionKey: List<Int>?, dbPath: String? ->
logV("createFromKeyBundle")
requireNotProductionEnvForAlphaMLS(enableAlphaMls, environment)

try {
val context = if (enableAlphaMls == true) context else null
val encryptionKeyBytes =
dbEncryptionKey?.foldIndexed(ByteArray(dbEncryptionKey.size)) { i, a, v ->
a.apply { set(i, v.toByte()) }
}
val options = ClientOptions(
api = apiEnvironments(environment, appVersion),
enableAlphaMls = enableAlphaMls == true,
appContext = context
appContext = context,
dbEncryptionKey = encryptionKeyBytes,
dbPath = dbPath
)
val bundle =
PrivateKeyOuterClass.PrivateKeyBundle.parseFrom(
Expand Down
12 changes: 12 additions & 0 deletions example/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,8 @@ PODS:
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-sqlite-storage (6.0.1):
- React-Core
- React-perflogger (0.71.14)
- React-RCTActionSheet (0.71.14):
- React-Core/RCTActionSheetHeaders (= 0.71.14)
Expand Down Expand Up @@ -430,6 +432,8 @@ PODS:
- React-perflogger (= 0.71.14)
- RNCAsyncStorage (1.21.0):
- React-Core
- RNFS (2.20.0):
- React-Core
- RNScreens (3.20.0):
- React-Core
- React-RCTImage
Expand Down Expand Up @@ -502,6 +506,7 @@ DEPENDENCIES:
- react-native-quick-crypto (from `../node_modules/react-native-quick-crypto`)
- react-native-randombytes (from `../node_modules/react-native-randombytes`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
- React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`)
- React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`)
Expand All @@ -516,6 +521,7 @@ DEPENDENCIES:
- React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
- "RNCAsyncStorage (from `../node_modules/@react-native-async-storage/async-storage`)"
- RNFS (from `../node_modules/react-native-fs`)
- RNScreens (from `../node_modules/react-native-screens`)
- RNSVG (from `../node_modules/react-native-svg`)
- XMTPReactNative (from `../../ios`)
Expand Down Expand Up @@ -630,6 +636,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-randombytes"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-sqlite-storage:
:path: "../node_modules/react-native-sqlite-storage"
React-perflogger:
:path: "../node_modules/react-native/ReactCommon/reactperflogger"
React-RCTActionSheet:
Expand Down Expand Up @@ -658,6 +666,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native/ReactCommon"
RNCAsyncStorage:
:path: "../node_modules/@react-native-async-storage/async-storage"
RNFS:
:path: "../node_modules/react-native-fs"
RNScreens:
:path: "../node_modules/react-native-screens"
RNSVG:
Expand Down Expand Up @@ -725,6 +735,7 @@ SPEC CHECKSUMS:
react-native-quick-crypto: 455c1b411db006dba1026a30681ececb19180187
react-native-randombytes: 421f1c7d48c0af8dbcd471b0324393ebf8fe7846
react-native-safe-area-context: 39c2d8be3328df5d437ac1700f4f3a4f75716acc
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
React-perflogger: 4987ad83731c23d11813c84263963b0d3028c966
React-RCTActionSheet: 5ad952b2a9740d87a5bd77280c4bc23f6f89ea0c
React-RCTAnimation: d2de22af3f536cc80bb5b3918e1a455114d1b985
Expand All @@ -739,6 +750,7 @@ SPEC CHECKSUMS:
React-runtimeexecutor: ffe826b7b1cfbc32a35ed5b64d5886c0ff75f501
ReactCommon: 7f3dd5e98a9ec627c6b03d26c062bf37ea9fc888
RNCAsyncStorage: 618d03a5f52fbccb3d7010076bc54712844c18ef
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f
RNSVG: d00c8f91c3cbf6d476451313a18f04d220d4f396
secp256k1.swift: a7e7a214f6db6ce5db32cc6b2b45e5c4dd633634
Expand Down
2 changes: 2 additions & 0 deletions example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"react-native-config": "^1.5.1",
"react-native-crypto": "^2.2.0",
"react-native-encrypted-storage": "^4.0.3",
"react-native-fs": "^2.20.0",
"react-native-get-random-values": "^1.10.0",
"react-native-mmkv": "^2.8.0",
"react-native-modal-selector": "^2.1.2",
Expand All @@ -38,6 +39,7 @@
"react-native-randombytes": "^3.6.1",
"react-native-safe-area-context": "4.5.0",
"react-native-screens": "~3.20.0",
"react-native-sqlite-storage": "^6.0.1",
"react-native-svg": "^13.9.0",
"react-native-url-polyfill": "^2.0.0",
"react-query": "^3.39.3",
Expand Down
56 changes: 56 additions & 0 deletions example/src/tests/groupTests.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import RNFS from 'react-native-fs'
import { DecodedMessage } from 'xmtp-react-native-sdk/lib/DecodedMessage'

import { Test, assert, delayToPropogate, isIos } from './tests'
Expand Down Expand Up @@ -26,6 +27,61 @@ test('can make a MLS V3 client', async () => {
return true
})

test('can make a MLS V3 client with encryption key and database path', async () => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const dbDirPath = `${RNFS.DocumentDirectoryPath}/xmtp_db`
const directoryExists = await RNFS.exists(dbDirPath)
if (!directoryExists) {
await RNFS.mkdir(dbDirPath)
}
const timestamp = Date.now().toString()
const dbPath = `${dbDirPath}/myCoolApp${timestamp}.db3`

const key = new Uint8Array([
233, 120, 198, 96, 154, 65, 132, 17, 132, 96, 250, 40, 103, 35, 125, 64,
166, 83, 208, 224, 254, 44, 205, 227, 175, 49, 234, 129, 74, 252, 135, 145,
])
const client = await Client.createRandom({
env: 'local',
appVersion: 'Testing/0.0.0',
enableAlphaMls: true,
dbEncryptionKey: key,
dbPath,
})

const anotherClient = await Client.createRandom({
env: 'local',
appVersion: 'Testing/0.0.0',
enableAlphaMls: true,
})

await client.conversations.newGroup([anotherClient.address])
assert(
(await client.conversations.listGroups()).length === 1,
`should have a group size of 1 but was ${(await client.conversations.listGroups()).length}`
)

const bundle = await client.exportKeyBundle()
const clientFromBundle = await Client.createFromKeyBundle(bundle, {
env: 'local',
appVersion: 'Testing/0.0.0',
enableAlphaMls: true,
dbEncryptionKey: key,
dbPath,
})

assert(
clientFromBundle.address === client.address,
`clients dont match ${client.address} and ${clientFromBundle.address}`
)

assert(
(await clientFromBundle.conversations.listGroups()).length === 1,
`should have a group size of 1 but was ${(await clientFromBundle.conversations.listGroups()).length}`
)
return true
})

test('can make a MLS V3 client from bundle', async () => {
const client = await Client.createRandom({
env: 'local',
Expand Down
17 changes: 15 additions & 2 deletions example/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7325,7 +7325,7 @@ balanced-match@^1.0.0:
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==

[email protected]:
[email protected], base-64@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/base-64/-/base-64-0.1.0.tgz"
integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
Expand Down Expand Up @@ -13432,6 +13432,14 @@ react-native-encrypted-storage@^4.0.3:
resolved "https://registry.npmjs.org/react-native-encrypted-storage/-/react-native-encrypted-storage-4.0.3.tgz"
integrity sha512-0pJA4Aj2S1PIJEbU7pN/Qvf7JIJx3hFywx+i+bLHtgK0/6Zryf1V2xVsWcrD50dfiu3jY1eN2gesQ5osGxE7jA==

react-native-fs@^2.20.0:
version "2.20.0"
resolved "https://registry.yarnpkg.com/react-native-fs/-/react-native-fs-2.20.0.tgz#05a9362b473bfc0910772c0acbb73a78dbc810f6"
integrity sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==
dependencies:
base-64 "^0.1.0"
utf8 "^3.0.0"

react-native-get-random-values@^1.10.0, react-native-get-random-values@^1.4.0:
version "1.10.0"
resolved "https://registry.npmjs.org/react-native-get-random-values/-/react-native-get-random-values-1.10.0.tgz"
Expand Down Expand Up @@ -13531,6 +13539,11 @@ react-native-screens@~3.20.0:
react-freeze "^1.0.0"
warn-once "^0.1.0"

react-native-sqlite-storage@^6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/react-native-sqlite-storage/-/react-native-sqlite-storage-6.0.1.tgz#ce6a6b852f07abbea68658d5363818c8bef45dfb"
integrity sha512-1tDFjrint6X6qSYKf3gDyz+XB+X79jfiL6xTugKHPRtF0WvqMtVgdLuNqZunIXjNEvNtNVEbXaeZ6MsguFu00A==

react-native-svg@^13.9.0:
version "13.14.0"
resolved "https://registry.npmjs.org/react-native-svg/-/react-native-svg-13.14.0.tgz"
Expand Down Expand Up @@ -15273,7 +15286,7 @@ utf-8-validate@^5.0.2:
dependencies:
node-gyp-build "^4.3.0"

[email protected]:
[email protected], utf8@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz"
integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
Expand Down
25 changes: 14 additions & 11 deletions ios/XMTPModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public class XMTPModule: Module {
//
// Auth functions
//
AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableAlphaMls: Bool?) in
AsyncFunction("auth") { (address: String, environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableAlphaMls: Bool?, dbEncryptionKey: [UInt8]?, dbPath: String?) in
try requireNotProductionEnvForAlphaMLS(enableAlphaMls: enableAlphaMls, environment: environment)

let signer = ReactNativeSigner(module: self, address: address)
Expand All @@ -91,7 +91,9 @@ public class XMTPModule: Module {
}
let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil
let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil
let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: enableAlphaMls == true)
let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!)

let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: enableAlphaMls == true, encryptionKey: encryptionKeyData, dbPath: dbPath)
try await clientsManager.updateClient(key: address, client: await XMTP.Client.create(account: signer, options: options))
self.signer = nil
sendEvent("authed")
Expand All @@ -102,7 +104,7 @@ public class XMTPModule: Module {
}

// Generate a random wallet and set the client to that
AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableAlphaMls: Bool?) -> String in
AsyncFunction("createRandom") { (environment: String, appVersion: String?, hasCreateIdentityCallback: Bool?, hasEnableIdentityCallback: Bool?, enableAlphaMls: Bool?, dbEncryptionKey: [UInt8]?, dbPath: String?) -> String in
try requireNotProductionEnvForAlphaMLS(enableAlphaMls: enableAlphaMls, environment: environment)

let privateKey = try PrivateKey.generate()
Expand All @@ -114,16 +116,17 @@ public class XMTPModule: Module {
}
let preCreateIdentityCallback: PreEventCallback? = hasCreateIdentityCallback ?? false ? self.preCreateIdentityCallback : nil
let preEnableIdentityCallback: PreEventCallback? = hasEnableIdentityCallback ?? false ? self.preEnableIdentityCallback : nil
let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!)

let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: enableAlphaMls == true)
let options = createClientConfig(env: environment, appVersion: appVersion, preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: enableAlphaMls == true, encryptionKey: encryptionKeyData, dbPath: dbPath)
let client = try await Client.create(account: privateKey, options: options)

await clientsManager.updateClient(key: client.address, client: client)
return client.address
}

// Create a client using its serialized key bundle.
AsyncFunction("createFromKeyBundle") { (keyBundle: String, environment: String, appVersion: String?, enableAlphaMls: Bool?) -> String in
AsyncFunction("createFromKeyBundle") { (keyBundle: String, environment: String, appVersion: String?, enableAlphaMls: Bool?, dbEncryptionKey: [UInt8]?, dbPath: String?) -> String in
try requireNotProductionEnvForAlphaMLS(enableAlphaMls: enableAlphaMls, environment: environment)

do {
Expand All @@ -132,8 +135,8 @@ public class XMTPModule: Module {
else {
throw Error.invalidKeyBundle
}

let options = createClientConfig(env: environment, appVersion: appVersion, mlsAlpha: enableAlphaMls == true)
let encryptionKeyData = dbEncryptionKey == nil ? nil : Data(dbEncryptionKey!)
let options = createClientConfig(env: environment, appVersion: appVersion, mlsAlpha: enableAlphaMls == true, encryptionKey: encryptionKeyData, dbPath: dbPath)
let client = try await Client.from(bundle: bundle, options: options)
await clientsManager.updateClient(key: client.address, client: client)
return client.address
Expand Down Expand Up @@ -847,27 +850,27 @@ public class XMTPModule: Module {
// Helpers
//

func createClientConfig(env: String, appVersion: String?, preEnableIdentityCallback: PreEventCallback? = nil, preCreateIdentityCallback: PreEventCallback? = nil, mlsAlpha: Bool = false) -> XMTP.ClientOptions {
func createClientConfig(env: String, appVersion: String?, preEnableIdentityCallback: PreEventCallback? = nil, preCreateIdentityCallback: PreEventCallback? = nil, mlsAlpha: Bool = false, encryptionKey: Data? = nil, dbPath: String? = nil) -> XMTP.ClientOptions {
// Ensure that all codecs have been registered.
switch env {
case "local":
return XMTP.ClientOptions(api: XMTP.ClientOptions.Api(
env: XMTP.XMTPEnvironment.local,
isSecure: false,
appVersion: appVersion
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: mlsAlpha)
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: mlsAlpha, mlsEncryptionKey: encryptionKey, mlsDbPath: dbPath)
case "production":
return XMTP.ClientOptions(api: XMTP.ClientOptions.Api(
env: XMTP.XMTPEnvironment.production,
isSecure: true,
appVersion: appVersion
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: false)
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: false, mlsEncryptionKey: encryptionKey, mlsDbPath: dbPath)
default:
return XMTP.ClientOptions(api: XMTP.ClientOptions.Api(
env: XMTP.XMTPEnvironment.dev,
isSecure: true,
appVersion: appVersion
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: mlsAlpha)
), preEnableIdentityCallback: preEnableIdentityCallback, preCreateIdentityCallback: preCreateIdentityCallback, mlsAlpha: mlsAlpha, mlsEncryptionKey: encryptionKey, mlsDbPath: dbPath)
}
}

Expand Down
Loading

0 comments on commit ea461fb

Please sign in to comment.