Skip to content

Commit

Permalink
Merge branch 'main' into update-codeowners
Browse files Browse the repository at this point in the history
  • Loading branch information
nplasterer authored Dec 16, 2023
2 parents c02667b + e02a8c6 commit 0e03b8d
Show file tree
Hide file tree
Showing 14 changed files with 152 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +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 @@ -85,6 +89,7 @@ class ClientTest {
}

@Test
@Ignore("CI Issues")
fun testPublicCanMessage() {
val aliceWallet = PrivateKeyBuilder()
val notOnNetwork = PrivateKeyBuilder()
Expand All @@ -98,4 +103,50 @@ class ClientTest {
assert(canMessage)
assert(!cannotMessage)
}

@Test
@Ignore("CI Issues")
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")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,7 @@ class ConversationTest {
bobClient.contacts.refreshConsentList()

val isDenied = bobConversation.consentState() == ConsentState.DENIED
assertEquals(bobClient.contacts.consentList.entries.size, 1)
assertTrue(isDenied)

val aliceConversation = aliceClient.conversations.list()[0]
Expand Down
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
23 changes: 13 additions & 10 deletions library/src/main/java/org/xmtp/android/library/Contacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ data class ConsentListEntry(
}

class ConsentList(val client: Client) {
private val entries: MutableMap<String, ConsentState> = mutableMapOf()
val entries: MutableMap<String, ConsentListEntry> = mutableMapOf()
private val publicKey =
client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes
private val privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes
Expand All @@ -60,7 +60,7 @@ class ConsentList(val client: Client) {
val preferences: MutableList<PrivatePreferencesAction> = mutableListOf()

for (envelope in envelopes.envelopesList) {
val payload = uniffi.xmtp_dh.eciesDecryptK256Sha3256(
val payload = uniffi.xmtp_dh.userPreferencesDecrypt(
publicKey.toByteArray().toUByteArray().toList(),
privateKey.toByteArray().toUByteArray().toList(),
envelope.message.toByteArray().toUByteArray().toList()
Expand Down Expand Up @@ -101,7 +101,7 @@ class ConsentList(val client: Client) {
}
}.build()

val message = uniffi.xmtp_dh.eciesEncryptK256Sha3256(
val message = uniffi.xmtp_dh.userPreferencesEncrypt(
publicKey.toByteArray().toUByteArray().toList(),
privateKey.toByteArray().toUByteArray().toList(),
payload.toByteArray().toUByteArray().toList()
Expand All @@ -117,21 +117,23 @@ class ConsentList(val client: Client) {
}

fun allow(address: String): ConsentListEntry {
entries[ConsentListEntry.address(address).key] = ConsentState.ALLOWED
val entry = ConsentListEntry.address(address, ConsentState.ALLOWED)
entries[ConsentListEntry.address(address).key] = entry

return ConsentListEntry.address(address, ConsentState.ALLOWED)
return entry
}

fun deny(address: String): ConsentListEntry {
entries[ConsentListEntry.address(address).key] = ConsentState.DENIED
val entry = ConsentListEntry.address(address, ConsentState.DENIED)
entries[ConsentListEntry.address(address).key] = entry

return ConsentListEntry.address(address, ConsentState.DENIED)
return entry
}

fun state(address: String): ConsentState {
val state = entries[ConsentListEntry.address(address).key]
val entry = entries[ConsentListEntry.address(address).key]

return state ?: ConsentState.UNKNOWN
return entry?.consentType ?: ConsentState.UNKNOWN
}
}

Expand All @@ -143,10 +145,11 @@ data class Contacts(

var consentList: ConsentList = ConsentList(client)

fun refreshConsentList() {
fun refreshConsentList(): ConsentList {
runBlocking {
consentList = ConsentList(client).load()
}
return consentList
}

fun isAllowed(address: String): Boolean {
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ sealed class Topic {
}

is directMessageV2 -> wrap("m-$addresses")
is preferenceList -> wrap("pppp-$identifier")
is preferenceList -> wrap("userpreferences-$identifier")
}
}

Expand Down
Loading

0 comments on commit 0e03b8d

Please sign in to comment.