Skip to content

Commit

Permalink
update the code to allow v3 only client and write a test
Browse files Browse the repository at this point in the history
  • Loading branch information
nplasterer committed Sep 16, 2024
1 parent adc267d commit b68cb66
Show file tree
Hide file tree
Showing 7 changed files with 116 additions and 73 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,31 @@ class ClientTest {
assertEquals(inboxId, client.inboxId)
}

@Test
fun testCreatesAV3OnlyClient() {
val key = SecureRandom().generateSeed(32)
val context = InstrumentationRegistry.getInstrumentation().targetContext
val fakeWallet = PrivateKeyBuilder()
val options = ClientOptions(
ClientOptions.Api(XMTPEnvironment.LOCAL, false),
enableV3 = true,
appContext = context,
dbEncryptionKey = key
)
val inboxId = runBlocking { Client.getOrCreateInboxId(options, fakeWallet.address) }
val client = runBlocking {
Client().createOrBuild(
account = fakeWallet,
options = options
)
}
runBlocking {
client.canMessageV3(listOf(client.address))[client.address]?.let { assert(it) }
}
assert(client.installationId.isNotEmpty())
assertEquals(inboxId, client.inboxId)
}

@Test
fun testCanDeleteDatabase() {
val key = SecureRandom().generateSeed(32)
Expand Down
30 changes: 17 additions & 13 deletions library/src/main/java/org/xmtp/android/library/Client.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,10 @@ data class ClientOptions(

class Client() {
lateinit var address: String
lateinit var privateKeyBundleV1: PrivateKeyBundleV1
lateinit var apiClient: ApiClient
lateinit var contacts: Contacts
lateinit var conversations: Conversations
var privateKeyBundleV1: PrivateKeyBundleV1? = null
var apiClient: ApiClient? = null
var logger: XMTPLogger = XMTPLogger()
val libXMTPVersion: String = getVersionInfo()
var installationId: String = ""
Expand Down Expand Up @@ -489,7 +489,7 @@ class Client() {
if (legacy) {
val contactBundle = ContactBundle.newBuilder().also {
it.v1 = it.v1.toBuilder().also { v1Builder ->
v1Builder.keyBundle = privateKeyBundleV1.toPublicKeyBundle()
v1Builder.keyBundle = v1keys.toPublicKeyBundle()
}.build()
}.build()

Expand Down Expand Up @@ -529,11 +529,13 @@ class Client() {
}

suspend fun query(topic: Topic, pagination: Pagination? = null): QueryResponse {
return apiClient.queryTopic(topic = topic, pagination = pagination)
val client = apiClient ?: throw XMTPException("V2 only function")
return client.queryTopic(topic = topic, pagination = pagination)
}

suspend fun batchQuery(requests: List<QueryRequest>): BatchQueryResponse {
return apiClient.batchQuery(requests)
val client = apiClient ?: throw XMTPException("V2 only function")
return client.batchQuery(requests)
}

suspend fun subscribe(
Expand All @@ -547,7 +549,8 @@ class Client() {
request: FfiV2SubscribeRequest,
callback: FfiV2SubscriptionCallback,
): FfiV2Subscription {
return apiClient.subscribe(request, callback)
val client = apiClient ?: throw XMTPException("V2 only function")
return client.subscribe(request, callback)
}

suspend fun fetchConversation(
Expand Down Expand Up @@ -585,13 +588,14 @@ class Client() {
suspend fun publish(envelopes: List<Envelope>) {
val authorized = AuthorizedIdentity(
address = address,
authorized = privateKeyBundleV1.identityKey.publicKey,
identity = privateKeyBundleV1.identityKey,
authorized = v1keys.identityKey.publicKey,
identity = v1keys.identityKey,
)
val client = apiClient ?: throw XMTPException("V2 only function")
val authToken = authorized.createAuthToken()
apiClient.setAuthToken(authToken)
client.setAuthToken(authToken)

apiClient.publish(envelopes = envelopes)
client.publish(envelopes = envelopes)
}

suspend fun ensureUserContactPublished() {
Expand Down Expand Up @@ -722,11 +726,11 @@ class Client() {
}

val privateKeyBundle: PrivateKeyBundle
get() = PrivateKeyBundleBuilder.buildFromV1Key(privateKeyBundleV1)
get() = PrivateKeyBundleBuilder.buildFromV1Key(v1keys)

val v1keys: PrivateKeyBundleV1
get() = privateKeyBundleV1
get() = privateKeyBundleV1 ?: throw XMTPException("V2 only function")

val keys: PrivateKeyBundleV2
get() = privateKeyBundleV1.toV2()
get() = v1keys.toV2()
}
108 changes: 58 additions & 50 deletions library/src/main/java/org/xmtp/android/library/Contacts.kt
Original file line number Diff line number Diff line change
Expand Up @@ -98,67 +98,67 @@ class ConsentList(
val entries: MutableMap<String, ConsentListEntry> = mutableMapOf(),
) {
private var lastFetched: Date? = null
private val publicKey =
client.privateKeyBundleV1.identityKey.publicKey.secp256K1Uncompressed.bytes
private val privateKey = client.privateKeyBundleV1.identityKey.secp256K1.bytes

private val identifier: String =
uniffi.xmtpv3.generatePrivatePreferencesTopicIdentifier(
privateKey.toByteArray(),
)

@OptIn(ExperimentalUnsignedTypes::class)
suspend fun load(): List<ConsentListEntry> {
val newDate = Date()
val envelopes =
client.apiClient.envelopes(
Topic.preferenceList(identifier).description,
Pagination(
after = lastFetched,
direction = MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING,
limit = 500
),
)

lastFetched = newDate
val preferences: MutableList<PrivatePreferencesAction> = mutableListOf()
for (envelope in envelopes) {
val payload =
uniffi.xmtpv3.userPreferencesDecrypt(
publicKey.toByteArray(),
if (client.hasV2Client) {
val newDate = Date()
val publicKey =
client.v1keys.identityKey.publicKey.secp256K1Uncompressed.bytes
val privateKey = client.v1keys.identityKey.secp256K1.bytes
val identifier: String =
uniffi.xmtpv3.generatePrivatePreferencesTopicIdentifier(
privateKey.toByteArray(),
envelope.message.toByteArray(),
)

preferences.add(
PrivatePreferencesAction.parseFrom(
payload.toUByteArray().toByteArray(),
val envelopes =
client.apiClient!!.envelopes(
Topic.preferenceList(identifier).description,
Pagination(
after = lastFetched,
direction = MessageApiOuterClass.SortDirection.SORT_DIRECTION_ASCENDING,
limit = 500
),
)
)
}

preferences.iterator().forEach { preference ->
preference.allowAddress?.walletAddressesList?.forEach { address ->
allow(address)
}
preference.denyAddress?.walletAddressesList?.forEach { address ->
deny(address)
}
preference.allowGroup?.groupIdsList?.forEach { groupId ->
allowGroup(groupId)
}
preference.denyGroup?.groupIdsList?.forEach { groupId ->
denyGroup(groupId)
lastFetched = newDate
val preferences: MutableList<PrivatePreferencesAction> = mutableListOf()
for (envelope in envelopes) {
val payload =
uniffi.xmtpv3.userPreferencesDecrypt(
publicKey.toByteArray(),
privateKey.toByteArray(),
envelope.message.toByteArray(),
)

preferences.add(
PrivatePreferencesAction.parseFrom(
payload.toUByteArray().toByteArray(),
)
)
}

preference.allowInboxId?.inboxIdsList?.forEach { inboxId ->
allowInboxId(inboxId)
}
preference.denyInboxId?.inboxIdsList?.forEach { inboxId ->
denyInboxId(inboxId)
preferences.iterator().forEach { preference ->
preference.allowAddress?.walletAddressesList?.forEach { address ->
allow(address)
}
preference.denyAddress?.walletAddressesList?.forEach { address ->
deny(address)
}
preference.allowGroup?.groupIdsList?.forEach { groupId ->
allowGroup(groupId)
}
preference.denyGroup?.groupIdsList?.forEach { groupId ->
denyGroup(groupId)
}

preference.allowInboxId?.inboxIdsList?.forEach { inboxId ->
allowInboxId(inboxId)
}
preference.denyInboxId?.inboxIdsList?.forEach { inboxId ->
denyInboxId(inboxId)
}
}
}

return entries.values.toList()
}

Expand Down Expand Up @@ -204,6 +204,14 @@ class ConsentList(
}
}.build()

val publicKey =
client.v1keys.identityKey.publicKey.secp256K1Uncompressed.bytes
val privateKey = client.v1keys.identityKey.secp256K1.bytes
val identifier: String =
uniffi.xmtpv3.generatePrivatePreferencesTopicIdentifier(
privateKey.toByteArray(),
)

val message =
uniffi.xmtpv3.userPreferencesEncrypt(
publicKey.toByteArray(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ data class ConversationV1(
): List<DecodedMessage> {
val pagination =
Pagination(limit = limit, before = before, after = after, direction = direction)
val result = client.apiClient.envelopes(topic = topic.description, pagination = pagination)
val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val result = apiClient.envelopes(topic = topic.description, pagination = pagination)

return result.mapNotNull { envelope ->
decodeOrNull(envelope = envelope)
Expand Down Expand Up @@ -105,8 +106,9 @@ data class ConversationV1(
val pagination =
Pagination(limit = limit, before = before, after = after, direction = direction)

val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val envelopes =
client.apiClient.envelopes(
apiClient.envelopes(
topic = Topic.directMessageV1(client.address, peerAddress).description,
pagination = pagination,
)
Expand Down Expand Up @@ -238,7 +240,7 @@ data class ConversationV1(
}
val date = Date()
val message = MessageV1Builder.buildEncode(
sender = client.privateKeyBundleV1,
sender = client.v1keys,
recipient = recipient,
message = encodedContent.toByteArray(),
timestamp = date,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,9 @@ data class ConversationV2(
): List<DecodedMessage> {
val pagination =
Pagination(limit = limit, before = before, after = after, direction = direction)
val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val result =
client.apiClient.envelopes(
apiClient.envelopes(
topic = topic,
pagination = pagination,
)
Expand Down Expand Up @@ -117,7 +118,8 @@ data class ConversationV2(
): List<DecryptedMessage> {
val pagination =
Pagination(limit = limit, before = before, after = after, direction = direction)
val envelopes = client.apiClient.envelopes(topic, pagination)
val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val envelopes = apiClient.envelopes(topic, pagination)

return envelopes.map { envelope ->
decrypt(envelope)
Expand Down
10 changes: 6 additions & 4 deletions library/src/main/java/org/xmtp/android/library/Conversations.kt
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ data class Conversations(
groupDescription: String = "",
groupPinnedFrameUrl: String = "",

): Group {
): Group {
return newGroupInternal(
accountAddresses,
FfiGroupPermissionsOptions.CUSTOM_POLICY,
Expand All @@ -156,7 +156,7 @@ data class Conversations(
groupImageUrlSquare: String,
groupDescription: String,
groupPinnedFrameUrl: String,
permissionsPolicySet: FfiPermissionPolicySet?
permissionsPolicySet: FfiPermissionPolicySet?,
): Group {
if (accountAddresses.size == 1 &&
accountAddresses.first().lowercase() == client.address.lowercase()
Expand Down Expand Up @@ -435,7 +435,8 @@ data class Conversations(
}

private suspend fun listIntroductionPeers(pagination: Pagination? = null): Map<String, Date> {
val envelopes = client.apiClient.queryTopic(
val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val envelopes = apiClient.queryTopic(
topic = Topic.userIntro(client.address),
pagination = pagination,
).envelopesList
Expand Down Expand Up @@ -475,8 +476,9 @@ data class Conversations(
* @return List of [SealedInvitation] that are inside of the range specified by [pagination]
*/
private suspend fun listInvitations(pagination: Pagination? = null): List<SealedInvitation> {
val apiClient = client.apiClient ?: throw XMTPException("V2 only function")
val envelopes =
client.apiClient.envelopes(Topic.userInvite(client.address).description, pagination)
apiClient.envelopes(Topic.userInvite(client.address).description, pagination)
return envelopes.map { envelope ->
SealedInvitation.parseFrom(envelope.message)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ class MessageV2Builder(val senderHmac: ByteArray? = null, val shouldPush: Boolea
val digest = Hash.sha256(headerBytes + payload)
val preKey = client.keys.preKeysList?.get(0)
val signature = preKey?.sign(digest)
val bundle = client.privateKeyBundleV1.toV2().getPublicKeyBundle()
val bundle = client.v1keys.toV2().getPublicKeyBundle()
val signedContent = SignedContentBuilder.builderFromPayload(payload, bundle, signature)
val signedBytes = signedContent.toByteArray()
val ciphertext = Crypto.encrypt(keyMaterial, signedBytes, additionalData = headerBytes)
Expand Down

0 comments on commit b68cb66

Please sign in to comment.