Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add PreEventCallback #144

Merged
merged 16 commits into from
Dec 16, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package org.xmtp.android.library

import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Assert.assertEquals
import org.junit.Assert.fail
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.xmtp.android.library.messages.PrivateKeyBuilder
import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder
import org.xmtp.android.library.messages.generate
import org.xmtp.proto.message.contents.PrivateKeyOuterClass
import java.util.concurrent.CompletableFuture
import java.util.concurrent.TimeUnit

@RunWith(AndroidJUnit4::class)
class ClientTest {
Expand Down Expand Up @@ -100,4 +103,50 @@ class ClientTest {
assert(canMessage)
assert(!cannotMessage)
}

@Test
@Ignore("CI Issues")
kele-leanes marked this conversation as resolved.
Show resolved Hide resolved
fun testPreEnableIdentityCallback() {
val fakeWallet = PrivateKeyBuilder()
val expectation = CompletableFuture<Unit>()

val preEnableIdentityCallback: suspend () -> Unit = {
expectation.complete(Unit)
}

val opts = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
preEnableIdentityCallback = preEnableIdentityCallback
)

try {
Client().create(account = fakeWallet, options = opts)
expectation.get(5, TimeUnit.SECONDS)
} catch (e: Exception) {
fail("Error: $e")
}
}

@Test
@Ignore("CI Issues")
fun testPreCreateIdentityCallback() {
val fakeWallet = PrivateKeyBuilder()
val expectation = CompletableFuture<Unit>()

val preCreateIdentityCallback: suspend () -> Unit = {
expectation.complete(Unit)
}

val opts = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
preCreateIdentityCallback = preCreateIdentityCallback
)

try {
Client().create(account = fakeWallet, options = opts)
expectation.get(5, TimeUnit.SECONDS)
} catch (e: Exception) {
fail("Error: $e")
}
}
}
23 changes: 15 additions & 8 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,13 @@ import java.util.TimeZone

typealias PublishResponse = org.xmtp.proto.message.api.v1.MessageApiOuterClass.PublishResponse
typealias QueryResponse = org.xmtp.proto.message.api.v1.MessageApiOuterClass.QueryResponse
typealias PreEventCallback = suspend () -> Unit

data class ClientOptions(val api: Api = Api()) {
data class ClientOptions(
val api: Api = Api(),
val preCreateIdentityCallback: PreEventCallback? = null,
val preEnableIdentityCallback: PreEventCallback? = null,
) {
data class Api(
val env: XMTPEnvironment = XMTPEnvironment.DEV,
val isSecure: Boolean = true,
Expand Down Expand Up @@ -152,13 +157,13 @@ class Client() {
val clientOptions = options ?: ClientOptions()
val apiClient =
GRPCApiClient(environment = clientOptions.api.env, secure = clientOptions.api.isSecure)
return create(account = account, apiClient = apiClient)
return create(account = account, apiClient = apiClient, options = options)
}

fun create(account: SigningKey, apiClient: ApiClient): Client {
fun create(account: SigningKey, apiClient: ApiClient, options: ClientOptions? = null): Client {
return runBlocking {
try {
val privateKeyBundleV1 = loadOrCreateKeys(account, apiClient)
val privateKeyBundleV1 = loadOrCreateKeys(account, apiClient, options)
val client = Client(account.address, privateKeyBundleV1, apiClient)
client.ensureUserContactPublished()
client
Expand All @@ -182,14 +187,15 @@ class Client() {
private suspend fun loadOrCreateKeys(
account: SigningKey,
apiClient: ApiClient,
options: ClientOptions? = null,
): PrivateKeyBundleV1 {
val keys = loadPrivateKeys(account, apiClient)
val keys = loadPrivateKeys(account, apiClient, options)
return if (keys != null) {
keys
} else {
val v1Keys = PrivateKeyBundleV1.newBuilder().build().generate(account)
val v1Keys = PrivateKeyBundleV1.newBuilder().build().generate(account, options)
val keyBundle = PrivateKeyBundleBuilder.buildFromV1Key(v1Keys)
val encryptedKeys = keyBundle.encrypted(account)
val encryptedKeys = keyBundle.encrypted(account, options?.preEnableIdentityCallback)
authSave(apiClient, keyBundle.v1, encryptedKeys)
v1Keys
}
Expand All @@ -198,11 +204,12 @@ class Client() {
private suspend fun loadPrivateKeys(
account: SigningKey,
apiClient: ApiClient,
options: ClientOptions? = null,
): PrivateKeyBundleV1? {
val encryptedBundles = authCheck(apiClient, account.address)
for (encryptedBundle in encryptedBundles) {
try {
val bundle = encryptedBundle.decrypted(account)
val bundle = encryptedBundle.decrypted(account, options?.preEnableIdentityCallback)
return bundle.v1
} catch (e: Throwable) {
print("Error decoding encrypted private key bundle: $e")
Expand Down
12 changes: 11 additions & 1 deletion library/src/main/java/org/xmtp/android/library/SigningKey.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@ interface SigningKey {
suspend fun sign(message: String): SignatureOuterClass.Signature?
}

fun SigningKey.createIdentity(identity: PrivateKeyOuterClass.PrivateKey): AuthorizedIdentity {
fun SigningKey.createIdentity(
identity: PrivateKeyOuterClass.PrivateKey,
preCreateIdentityCallback: PreEventCallback? = null,
): AuthorizedIdentity {
val slimKey = PublicKeyOuterClass.PublicKey.newBuilder().apply {
timestamp = Date().time
secp256K1Uncompressed = identity.publicKey.secp256K1Uncompressed
}.build()

preCreateIdentityCallback?.let {
runBlocking {
it.invoke()
}
}

val signatureClass = Signature.newBuilder().build()
val signatureText = signatureClass.createIdentityText(key = slimKey.toByteArray())
val digest = signatureClass.ethHash(message = signatureText)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ package org.xmtp.android.library.messages

import kotlinx.coroutines.runBlocking
import org.xmtp.android.library.Crypto
import org.xmtp.android.library.PreEventCallback
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.XMTPException

typealias EncryptedPrivateKeyBundle = org.xmtp.proto.message.contents.PrivateKeyOuterClass.EncryptedPrivateKeyBundle

fun EncryptedPrivateKeyBundle.decrypted(key: SigningKey): PrivateKeyBundle {
fun EncryptedPrivateKeyBundle.decrypted(
key: SigningKey,
preEnableIdentityCallback: PreEventCallback? = null,
): PrivateKeyBundle {
preEnableIdentityCallback?.let {
runBlocking {
it.invoke()
}
}

val signature = runBlocking {
key.sign(
message = Signature.newBuilder().build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.xmtp.android.library.messages
import com.google.protobuf.kotlin.toByteString
import kotlinx.coroutines.runBlocking
import org.xmtp.android.library.Crypto
import org.xmtp.android.library.PreEventCallback
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.XMTPException
import org.xmtp.proto.message.contents.PrivateKeyOuterClass
Expand All @@ -20,9 +21,19 @@ class PrivateKeyBundleBuilder {
}
}

fun PrivateKeyBundle.encrypted(key: SigningKey): EncryptedPrivateKeyBundle {
fun PrivateKeyBundle.encrypted(
key: SigningKey,
preEnableIdentityCallback: PreEventCallback? = null,
): EncryptedPrivateKeyBundle {
val bundleBytes = toByteArray()
val walletPreKey = SecureRandom().generateSeed(32)

preEnableIdentityCallback?.let {
runBlocking {
it.invoke()
}
}

val signature =
runBlocking {
key.sign(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package org.xmtp.android.library.messages
import com.google.crypto.tink.subtle.Base64
import kotlinx.coroutines.runBlocking
import org.web3j.crypto.Hash
import org.xmtp.android.library.ClientOptions
import org.xmtp.android.library.SigningKey
import org.xmtp.android.library.XMTPException
import org.xmtp.android.library.createIdentity
Expand Down Expand Up @@ -31,9 +32,13 @@ class PrivateKeyBundleV1Builder {
}
}

fun PrivateKeyBundleV1.generate(wallet: SigningKey): PrivateKeyBundleV1 {
fun PrivateKeyBundleV1.generate(
wallet: SigningKey,
options: ClientOptions? = null,
): PrivateKeyBundleV1 {
val privateKey = PrivateKeyBuilder()
val authorizedIdentity = wallet.createIdentity(privateKey.getPrivateKey())
val authorizedIdentity =
wallet.createIdentity(privateKey.getPrivateKey(), options?.preCreateIdentityCallback)
var bundle = authorizedIdentity.toBundle
var preKey = PrivateKey.newBuilder().build().generate()
val bytesToSign = UnsignedPublicKeyBuilder.buildFromPublicKey(preKey.publicKey).toByteArray()
Expand Down
Loading