Skip to content

Commit e3a452c

Browse files
authored
feat: expose explicit decrypt of remote attachments (#107)
1 parent 1183b26 commit e3a452c

File tree

2 files changed

+55
-15
lines changed

2 files changed

+55
-15
lines changed

library/src/main/java/org/xmtp/android/library/codecs/RemoteAttachmentCodec.kt

+31-15
Original file line numberDiff line numberDiff line change
@@ -41,26 +41,42 @@ data class RemoteAttachment(
4141
throw XMTPException("no remote attachment payload")
4242
}
4343

44-
if (Hash.sha256(payload).toHex() != contentDigest) {
45-
throw XMTPException("contentDigest does not match")
46-
}
47-
48-
val aes = Ciphertext.Aes256gcmHkdfsha256.newBuilder().also {
49-
it.hkdfSalt = salt
50-
it.gcmNonce = nonce
51-
it.payload = payload.toByteString()
52-
}.build()
53-
54-
val ciphertext = Ciphertext.newBuilder().also {
55-
it.aes256GcmHkdfSha256 = aes
56-
}.build()
44+
val encrypted = EncryptedEncodedContent(
45+
contentDigest,
46+
secret,
47+
salt,
48+
nonce,
49+
payload.toByteString(),
50+
contentLength,
51+
filename,
52+
)
5753

58-
val decrypted = Crypto.decrypt(secret = secret.toByteArray(), ciphertext = ciphertext)
54+
val decrypted = decryptEncoded(encrypted)
5955

60-
return EncodedContent.parseFrom(decrypted).decoded<T>()
56+
return decrypted.decoded<T>()
6157
}
6258

6359
companion object {
60+
fun decryptEncoded(encrypted: EncryptedEncodedContent): EncodedContent {
61+
if (Hash.sha256(encrypted.payload.toByteArray()).toHex() != encrypted.contentDigest) {
62+
throw XMTPException("contentDigest does not match")
63+
}
64+
65+
val aes = Ciphertext.Aes256gcmHkdfsha256.newBuilder().also {
66+
it.hkdfSalt = encrypted.salt
67+
it.gcmNonce = encrypted.nonce
68+
it.payload = encrypted.payload
69+
}.build()
70+
71+
val ciphertext = Ciphertext.newBuilder().also {
72+
it.aes256GcmHkdfSha256 = aes
73+
}.build()
74+
75+
val decrypted = Crypto.decrypt(encrypted.secret.toByteArray(), ciphertext)
76+
77+
return EncodedContent.parseFrom(decrypted)
78+
}
79+
6480
fun <T> encodeEncrypted(content: T, codec: ContentCodec<T>): EncryptedEncodedContent {
6581
val secret = SecureRandom().generateSeed(32)
6682
val encodedContent = codec.encode(content).toByteArray()

library/src/test/java/org/xmtp/android/library/RemoteAttachmentTest.kt

+24
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,38 @@ import org.junit.Ignore
77
import org.junit.Test
88
import org.xmtp.android.library.codecs.Attachment
99
import org.xmtp.android.library.codecs.AttachmentCodec
10+
import org.xmtp.android.library.codecs.ContentTypeAttachment
1011
import org.xmtp.android.library.codecs.ContentTypeRemoteAttachment
1112
import org.xmtp.android.library.codecs.RemoteAttachment
1213
import org.xmtp.android.library.codecs.RemoteAttachmentCodec
14+
import org.xmtp.android.library.codecs.decoded
15+
import org.xmtp.android.library.codecs.id
1316
import org.xmtp.android.library.messages.walletAddress
1417
import java.io.File
1518
import java.net.URL
1619

1720
class RemoteAttachmentTest {
21+
22+
@Test
23+
fun testEncryptedContentShouldBeDecryptable() {
24+
Client.register(codec = AttachmentCodec())
25+
val attachment = Attachment(
26+
filename = "test.txt",
27+
mimeType = "text/plain",
28+
data = "hello world".toByteStringUtf8(),
29+
)
30+
31+
val encrypted = RemoteAttachment.encodeEncrypted(attachment, AttachmentCodec())
32+
33+
val decrypted = RemoteAttachment.decryptEncoded(encrypted)
34+
Assert.assertEquals(ContentTypeAttachment.id, decrypted.type.id)
35+
36+
val decoded = decrypted.decoded<Attachment>()
37+
Assert.assertEquals("test.txt", decoded?.filename)
38+
Assert.assertEquals("text/plain", decoded?.mimeType)
39+
Assert.assertEquals("hello world", decoded?.data?.toStringUtf8())
40+
}
41+
1842
@Test
1943
@Ignore
2044
fun testCanUseRemoteAttachmentCodec() {

0 commit comments

Comments
 (0)