From 7a54600b6c06938e0acab7f642e859513790ddb1 Mon Sep 17 00:00:00 2001 From: Alex Risch Date: Wed, 17 Apr 2024 18:10:49 -0600 Subject: [PATCH] feat: Consent Proof Added handling for consent proof in new conversation Updated protos --- library/build.gradle | 2 +- .../xmtp/android/library/ConversationsTest.kt | 27 +++++++++++++++++++ .../org/xmtp/android/library/Conversations.kt | 5 +++- .../android/library/messages/InvitationV1.kt | 14 ++++++++++ .../library/messages/SealedInvitation.kt | 3 +++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/library/build.gradle b/library/build.gradle index 28ac00a9b..5a9c73fd6 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -86,7 +86,7 @@ dependencies { implementation 'org.web3j:crypto:5.0.0' implementation "net.java.dev.jna:jna:5.13.0@aar" api 'com.google.protobuf:protobuf-kotlin-lite:3.22.3' - api 'org.xmtp:proto-kotlin:3.47.0' + api 'org.xmtp:proto-kotlin:3.51.0' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'app.cash.turbine:turbine:0.12.1' diff --git a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt index 0d65539f2..21eb4c667 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/ConversationsTest.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Test import org.junit.runner.RunWith import org.xmtp.android.library.codecs.TextCodec @@ -172,4 +173,30 @@ class ConversationsTest { sleep(2000) assertEquals(allMessages.size, 2) } + + @Test + fun testSendConversationWithConsentSignature() { + val bo = PrivateKeyBuilder() + val alix = PrivateKeyBuilder() + val clientOptions = + ClientOptions(api = ClientOptions.Api(env = XMTPEnvironment.LOCAL, isSecure = false)) + val boClient = Client().create(bo, clientOptions) + val alixClient = Client().create(alix, clientOptions) + val signedBytes = byteArrayOf(1, 2, 3) + val privateKeyBundle = alixClient.keys + val signedPrivateKey = privateKeyBundle.identityKey + val signature = runBlocking { + val privateKey = PrivateKeyBuilder.buildFromSignedPrivateKey(signedPrivateKey) + PrivateKeyBuilder(privateKey).sign(signedBytes) + } + val boConversation = + runBlocking { boClient.conversations.newConversation(alixClient.address, null, signature.toByteArray().toHex()) } + val alixConversations = runBlocking { + alixClient.conversations.list() + } + val alixConversation = alixConversations.find { + it.topic == boConversation.topic + } + assertNotNull(alixConversation) + } } diff --git a/library/src/main/java/org/xmtp/android/library/Conversations.kt b/library/src/main/java/org/xmtp/android/library/Conversations.kt index b54a55779..6a8a92a53 100644 --- a/library/src/main/java/org/xmtp/android/library/Conversations.kt +++ b/library/src/main/java/org/xmtp/android/library/Conversations.kt @@ -158,6 +158,7 @@ data class Conversations( suspend fun newConversation( peerAddress: String, context: Invitation.InvitationV1.Context? = null, + consentProofSignature: String? = null, ): Conversation { if (peerAddress.lowercase() == client.address.lowercase()) { throw XMTPException("Recipient is sender") @@ -223,7 +224,7 @@ data class Conversations( // We don't have an existing conversation, make a v2 one val recipient = contact.toSignedPublicKeyBundle() val invitation = Invitation.InvitationV1.newBuilder().build() - .createDeterministic(client.keys, recipient, context) + .createDeterministic(client.keys, recipient, context, consentProofSignature) val sealedInvitation = sendInvitation(recipient = recipient, invitation = invitation, created = Date()) val conversationV2 = ConversationV2.create( @@ -260,7 +261,9 @@ data class Conversations( val invitations = listInvitations(pagination = pagination) for (sealedInvitation in invitations) { try { +// TODO: Verify Signature and approve newConversations.add(Conversation.V2(conversation(sealedInvitation))) + if (sealedInvitation.) } catch (e: Exception) { Log.d(TAG, e.message.toString()) } diff --git a/library/src/main/java/org/xmtp/android/library/messages/InvitationV1.kt b/library/src/main/java/org/xmtp/android/library/messages/InvitationV1.kt index f0cd5e57c..7438eb898 100644 --- a/library/src/main/java/org/xmtp/android/library/messages/InvitationV1.kt +++ b/library/src/main/java/org/xmtp/android/library/messages/InvitationV1.kt @@ -5,8 +5,10 @@ import com.google.protobuf.kotlin.toByteString import org.xmtp.android.library.Crypto import org.xmtp.android.library.toHex import org.xmtp.proto.message.contents.Invitation +import org.xmtp.proto.message.contents.Invitation.ConsentProofPayload import org.xmtp.proto.message.contents.Invitation.InvitationV1.Context import java.security.SecureRandom +import java.util.Date typealias InvitationV1 = org.xmtp.proto.message.contents.Invitation.InvitationV1 @@ -16,12 +18,16 @@ class InvitationV1Builder { topic: Topic, context: Context? = null, aes256GcmHkdfSha256: Invitation.InvitationV1.Aes256gcmHkdfsha256, + consentProof: ConsentProofPayload? = null ): InvitationV1 { return InvitationV1.newBuilder().apply { this.topic = topic.description if (context != null) { this.context = context } + if (consentProof != null) { + this.consentProof = consentProof + } this.aes256GcmHkdfSha256 = aes256GcmHkdfSha256 }.build() } @@ -60,6 +66,7 @@ fun InvitationV1.createDeterministic( sender: PrivateKeyBundleV2, recipient: SignedPublicKeyBundle, context: Context? = null, + consentProofSignature: String? = null ): InvitationV1 { val myAddress = sender.toV1().walletAddress val theirAddress = recipient.walletAddress @@ -91,10 +98,17 @@ fun InvitationV1.createDeterministic( this.keyMaterial = keyMaterial.toByteString() }.build() + val consentProof = ConsentProofPayload.newBuilder().apply { + this.signature = consentProofSignature + this.timestamp = Date().time * 1_000_000 + this.payloadVersion = Invitation.ConsentProofPayloadVersion.CONSENT_PROOF_PAYLOAD_VERSION_1 + }.build() + return InvitationV1Builder.buildFromTopic( topic = topic, context = inviteContext, aes256GcmHkdfSha256 = aes256GcmHkdfSha256, + consentProof = consentProof ) } diff --git a/library/src/main/java/org/xmtp/android/library/messages/SealedInvitation.kt b/library/src/main/java/org/xmtp/android/library/messages/SealedInvitation.kt index b54ee2502..ee04fe5b7 100644 --- a/library/src/main/java/org/xmtp/android/library/messages/SealedInvitation.kt +++ b/library/src/main/java/org/xmtp/android/library/messages/SealedInvitation.kt @@ -3,6 +3,7 @@ package org.xmtp.android.library.messages import com.google.protobuf.kotlin.toByteString import org.xmtp.android.library.CipherText import org.xmtp.android.library.Crypto +import org.xmtp.proto.message.contents.consentProofPayload import java.util.Date typealias SealedInvitation = org.xmtp.proto.message.contents.Invitation.SealedInvitation @@ -36,7 +37,9 @@ class SealedInvitationBuilder { v1 = v1.toBuilder().also { it.headerBytes = headerBytes.toByteString() it.ciphertext = ciphertext + }.build() + consentProofPayload { } }.build() } }