diff --git a/library/build.gradle b/library/build.gradle index bfbe72be0..8fd7d3223 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.31.0' + api 'org.xmtp:proto-kotlin:3.39.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/CodecTest.kt b/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt index 4b04fe815..5859a46ab 100644 --- a/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt +++ b/library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt @@ -19,8 +19,8 @@ data class NumberCodec( authorityId = "example.com", typeId = "number", versionMajor = 1, - versionMinor = 1 - ) + versionMinor = 1, + ), ) : ContentCodec { override fun encode(content: Double): EncodedContent { return EncodedContent.newBuilder().also { @@ -28,7 +28,7 @@ data class NumberCodec( authorityId = "example.com", typeId = "number", versionMajor = 1, - versionMinor = 1 + versionMinor = 1, ) it.content = mapOf(Pair("number", content)).toString().toByteStringUtf8() }.build() @@ -37,10 +37,13 @@ data class NumberCodec( override fun decode(content: EncodedContent): Double = content.content.toStringUtf8().filter { it.isDigit() || it == '.' }.toDouble() + override fun shouldPush(): Boolean = false + override fun fallback(content: Double): String? { return "Error: This app does not support numbers." } } + @RunWith(AndroidJUnit4::class) class CodecTest { @@ -53,7 +56,7 @@ class CodecTest { aliceClient.conversations.newConversation(fixtures.bob.walletAddress) aliceConversation.send( content = 3.14, - options = SendOptions(contentType = NumberCodec().contentType) + options = SendOptions(contentType = NumberCodec().contentType), ) val messages = aliceConversation.messages() assertEquals(messages.size, 1) @@ -75,7 +78,7 @@ class CodecTest { val source = DecodedComposite(encodedContent = textContent) aliceConversation.send( content = source, - options = SendOptions(contentType = CompositeCodec().contentType) + options = SendOptions(contentType = CompositeCodec().contentType), ) val messages = aliceConversation.messages() val decoded: DecodedComposite? = messages[0].content() @@ -95,12 +98,12 @@ class CodecTest { val source = DecodedComposite( parts = listOf( DecodedComposite(encodedContent = textContent), - DecodedComposite(parts = listOf(DecodedComposite(encodedContent = numberContent))) - ) + DecodedComposite(parts = listOf(DecodedComposite(encodedContent = numberContent))), + ), ) aliceConversation.send( content = source, - options = SendOptions(contentType = CompositeCodec().contentType) + options = SendOptions(contentType = CompositeCodec().contentType), ) val messages = aliceConversation.messages() val decoded: DecodedComposite? = messages[0].content() diff --git a/library/src/main/java/org/xmtp/android/library/Crypto.kt b/library/src/main/java/org/xmtp/android/library/Crypto.kt index b91af0fa9..681f2d33e 100644 --- a/library/src/main/java/org/xmtp/android/library/Crypto.kt +++ b/library/src/main/java/org/xmtp/android/library/Crypto.kt @@ -77,26 +77,35 @@ class Crypto { null } } - } - fun calculateMac(secret: ByteArray, message: ByteArray): ByteArray { - val sha256HMAC: Mac = Mac.getInstance("HmacSHA256") - val secretKey = SecretKeySpec(secret, "HmacSHA256") - sha256HMAC.init(secretKey) - return sha256HMAC.doFinal(message) - } + fun calculateMac(secret: ByteArray, message: ByteArray): ByteArray { + val sha256HMAC: Mac = Mac.getInstance("HmacSHA256") + val secretKey = SecretKeySpec(secret, "HmacSHA256") + sha256HMAC.init(secretKey) + return sha256HMAC.doFinal(message) + } - fun deriveKey( - secret: ByteArray, - salt: ByteArray, - info: ByteArray, - ): ByteArray { - val derivationParameters = HKDFParameters(secret, salt, info) - val digest = SHA256Digest() - val hkdfGenerator = HKDFBytesGenerator(digest) - hkdfGenerator.init(derivationParameters) - val hkdf = ByteArray(32) - hkdfGenerator.generateBytes(hkdf, 0, hkdf.size) - return hkdf + fun deriveKey( + secret: ByteArray, + salt: ByteArray, + info: ByteArray, + ): ByteArray { + val derivationParameters = HKDFParameters(secret, salt, info) + val digest = SHA256Digest() + val hkdfGenerator = HKDFBytesGenerator(digest) + hkdfGenerator.init(derivationParameters) + val hkdf = ByteArray(32) + hkdfGenerator.generateBytes(hkdf, 0, hkdf.size) + return hkdf + } + + fun generateHmacSignature( + secret: ByteArray, + info: ByteArray, + message: ByteArray, + ): ByteArray { + val hkdfKey = deriveKey(secret, message, info) + return calculateMac(secret, hkdfKey) + } } } diff --git a/library/src/main/java/org/xmtp/android/library/codecs/AttachmentCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/AttachmentCodec.kt index 21c9e872c..f2273f14b 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/AttachmentCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/AttachmentCodec.kt @@ -36,4 +36,6 @@ data class AttachmentCodec(override var contentType: ContentTypeId = ContentType override fun fallback(content: Attachment): String? { return "Can’t display \"${content.filename}”. This app doesn’t support attachments." } + + override fun shouldPush(): Boolean = true } diff --git a/library/src/main/java/org/xmtp/android/library/codecs/Composite.kt b/library/src/main/java/org/xmtp/android/library/codecs/Composite.kt index 02241e6a3..d508437cc 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/Composite.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/Composite.kt @@ -52,6 +52,8 @@ class CompositeCodec : ContentCodec { return null } + override fun shouldPush(): Boolean = false + private fun toComposite(decodedComposite: DecodedComposite): Composite { return Composite.newBuilder().also { val content = decodedComposite.encodedContent diff --git a/library/src/main/java/org/xmtp/android/library/codecs/ContentCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/ContentCodec.kt index 1282e3655..732d96b44 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/ContentCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/ContentCodec.kt @@ -67,6 +67,7 @@ interface ContentCodec { fun encode(content: T): EncodedContent fun decode(content: EncodedContent): T fun fallback(content: T): String? + fun shouldPush(): Boolean } val id: String diff --git a/library/src/main/java/org/xmtp/android/library/codecs/ReactionCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/ReactionCodec.kt index f15f366d4..fbb917b7f 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/ReactionCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/ReactionCodec.kt @@ -95,6 +95,8 @@ data class ReactionCodec(override var contentType: ContentTypeId = ContentTypeRe else -> null } } + + override fun shouldPush(): Boolean = true } private class ReactionSerializer : JsonSerializer { diff --git a/library/src/main/java/org/xmtp/android/library/codecs/ReadReceiptCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/ReadReceiptCodec.kt index 6e51e77ae..c07b66d40 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/ReadReceiptCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/ReadReceiptCodec.kt @@ -28,4 +28,6 @@ data class ReadReceiptCodec(override var contentType: ContentTypeId = ContentTyp override fun fallback(content: ReadReceipt): String? { return null } + + override fun shouldPush(): Boolean = false } diff --git a/library/src/main/java/org/xmtp/android/library/codecs/RemoteAttachmentCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/RemoteAttachmentCodec.kt index 2fdebe0a2..29ebfce1b 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/RemoteAttachmentCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/RemoteAttachmentCodec.kt @@ -171,4 +171,6 @@ data class RemoteAttachmentCodec(override var contentType: ContentTypeId = Conte override fun fallback(content: RemoteAttachment): String? { return "Can’t display \"${content.filename}”. This app doesn’t support attachments." } + + override fun shouldPush(): Boolean = true } diff --git a/library/src/main/java/org/xmtp/android/library/codecs/ReplyCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/ReplyCodec.kt index f61c20400..61fe61c0a 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/ReplyCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/ReplyCodec.kt @@ -49,6 +49,8 @@ data class ReplyCodec(override var contentType: ContentTypeId = ContentTypeReply return "Replied with “${content.content}” to an earlier message" } + override fun shouldPush(): Boolean = true + private fun , T> encodeReply( codec: Codec, content: Any, diff --git a/library/src/main/java/org/xmtp/android/library/codecs/TextCodec.kt b/library/src/main/java/org/xmtp/android/library/codecs/TextCodec.kt index 378d6f060..86b15c02d 100644 --- a/library/src/main/java/org/xmtp/android/library/codecs/TextCodec.kt +++ b/library/src/main/java/org/xmtp/android/library/codecs/TextCodec.kt @@ -36,4 +36,6 @@ data class TextCodec(override var contentType: ContentTypeId = ContentTypeText) override fun fallback(content: String): String? { return null } + + override fun shouldPush(): Boolean = true } 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 347847ed7..f0cd5e57c 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 @@ -52,7 +52,7 @@ fun InvitationV1.createRandom(context: Context? = null): InvitationV1 { return InvitationV1Builder.buildFromTopic( topic = topic, context = inviteContext, - aes256GcmHkdfSha256 = aes256GcmHkdfSha256 + aes256GcmHkdfSha256 = aes256GcmHkdfSha256, ) } @@ -68,7 +68,7 @@ fun InvitationV1.createDeterministic( val secret = sender.sharedSecret( peer = recipient, myPreKey = sender.preKeysList[0].publicKey, - isRecipient = myAddress < theirAddress + isRecipient = myAddress < theirAddress, ) val addresses = arrayOf(myAddress, theirAddress) @@ -80,12 +80,12 @@ fun InvitationV1.createDeterministic( addresses.joinToString(separator = ",") } - val topicId = Crypto().calculateMac(secret = secret, message = msg.toByteArray()).toHex() + val topicId = Crypto.calculateMac(secret = secret, message = msg.toByteArray()).toHex() val topic = Topic.directMessageV2(topicId) - val keyMaterial = Crypto().deriveKey( + val keyMaterial = Crypto.deriveKey( secret = secret, salt = "__XMTP__INVITATION__SALT__XMTP__".toByteArray(), - info = listOf("0").plus(addresses).joinToString(separator = "|").toByteArray() + info = listOf("0").plus(addresses).joinToString(separator = "|").toByteArray(), ) val aes256GcmHkdfSha256 = Invitation.InvitationV1.Aes256gcmHkdfsha256.newBuilder().apply { this.keyMaterial = keyMaterial.toByteString() @@ -94,7 +94,7 @@ fun InvitationV1.createDeterministic( return InvitationV1Builder.buildFromTopic( topic = topic, context = inviteContext, - aes256GcmHkdfSha256 = aes256GcmHkdfSha256 + aes256GcmHkdfSha256 = aes256GcmHkdfSha256, ) }