Skip to content

Commit e2585a4

Browse files
committed
write a test for it
1 parent 17a12a2 commit e2585a4

File tree

2 files changed

+99
-0
lines changed
  • library/src

2 files changed

+99
-0
lines changed

library/src/androidTest/java/org/xmtp/android/library/CodecTest.kt

+83
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,31 @@ package org.xmtp.android.library
33
import androidx.test.ext.junit.runners.AndroidJUnit4
44
import com.google.protobuf.kotlin.toByteStringUtf8
55
import org.junit.Assert.assertEquals
6+
import org.junit.Assert.assertTrue
67
import org.junit.Test
78
import org.junit.runner.RunWith
9+
import org.xmtp.android.library.Crypto.Companion.calculateMac
10+
import org.xmtp.android.library.Crypto.Companion.verifyHmacSignature
811
import org.xmtp.android.library.codecs.CompositeCodec
912
import org.xmtp.android.library.codecs.ContentCodec
1013
import org.xmtp.android.library.codecs.ContentTypeId
1114
import org.xmtp.android.library.codecs.ContentTypeIdBuilder
1215
import org.xmtp.android.library.codecs.DecodedComposite
1316
import org.xmtp.android.library.codecs.EncodedContent
1417
import org.xmtp.android.library.codecs.TextCodec
18+
import org.xmtp.android.library.messages.InvitationV1
1519
import org.xmtp.android.library.messages.MessageV2Builder
20+
import org.xmtp.android.library.messages.PrivateKeyBuilder
21+
import org.xmtp.android.library.messages.PrivateKeyBundleV1
22+
import org.xmtp.android.library.messages.SealedInvitationBuilder
23+
import org.xmtp.android.library.messages.createDeterministic
24+
import org.xmtp.android.library.messages.generate
25+
import org.xmtp.android.library.messages.getPublicKeyBundle
26+
import org.xmtp.android.library.messages.toV2
1627
import org.xmtp.android.library.messages.walletAddress
28+
import java.security.Key
29+
import java.time.Instant
30+
import java.util.Date
1731

1832
data class NumberCodec(
1933
override var contentType: ContentTypeId = ContentTypeIdBuilder.builderFromAuthorityId(
@@ -141,4 +155,73 @@ class CodecTest {
141155
assertEquals(true, message.senderHmac?.isNotEmpty())
142156
val keys = aliceClient.conversations.getHmacKeys()
143157
}
158+
159+
@Test
160+
fun testReturnsAllHMACKeys() {
161+
val baseTime = Instant.now()
162+
val timestamps = List(5) { i -> baseTime.plusSeconds(i.toLong()) }
163+
val fixtures = fixtures()
164+
165+
val invites = timestamps.map { createdAt ->
166+
val fakeWallet = FakeWallet.generate()
167+
val recipient = PrivateKeyBundleV1.newBuilder().build().generate(wallet = fakeWallet)
168+
InvitationV1.newBuilder().build().createDeterministic(
169+
sender = fixtures.aliceClient.privateKeyBundleV1.toV2(),
170+
recipient = recipient.toV2().getPublicKeyBundle()
171+
)
172+
}
173+
174+
val thirtyDayPeriodsSinceEpoch = Instant.now().epochSecond / 60 / 60 / 24 / 30
175+
176+
val periods = listOf(
177+
thirtyDayPeriodsSinceEpoch - 1,
178+
thirtyDayPeriodsSinceEpoch,
179+
thirtyDayPeriodsSinceEpoch + 1
180+
)
181+
182+
val hmacKeys = fixtures.aliceClient.conversations.getHmacKeys()
183+
184+
val topics = hmacKeys.hmacKeysMap.keys
185+
invites.forEach { invite ->
186+
assertTrue(topics.contains(invite.topic))
187+
}
188+
189+
val topicHmacs = mutableMapOf<String, ByteArray>()
190+
val headerBytes = ByteArray(10)
191+
192+
invites.map { invite ->
193+
val topic = invite.topic
194+
val payload = TextCodec().encode(content = "Hello, world!")
195+
196+
val message = MessageV2Builder.buildEncode(
197+
client = fixtures.aliceClient,
198+
encodedContent = payload,
199+
topic = topic,
200+
keyMaterial = headerBytes,
201+
codec = TextCodec()
202+
)
203+
204+
val conversation = fixtures.aliceClient.fetchConversation(topic)
205+
val keyMaterial = conversation?.keyMaterial
206+
val info = "$thirtyDayPeriodsSinceEpoch-${fixtures.aliceClient.address}"
207+
val hmac = Crypto.calculateMac(
208+
Crypto.deriveKey(keyMaterial!!, ByteArray(0), info.toByteArray()),
209+
headerBytes
210+
)
211+
212+
topicHmacs[topic] = hmac
213+
}
214+
215+
hmacKeys.hmacKeysMap.forEach { (topic, hmacData) ->
216+
hmacData.valuesList.forEachIndexed { idx, hmacKeyThirtyDayPeriod ->
217+
val valid = verifyHmacSignature(
218+
hmacKeyThirtyDayPeriod.hmacKey.toByteArray(),
219+
topicHmacs[topic]!!,
220+
headerBytes
221+
)
222+
assertTrue(valid == (idx == 1))
223+
}
224+
}
225+
226+
}
144227
}

library/src/main/java/org/xmtp/android/library/Crypto.kt

+16
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.bouncycastle.crypto.generators.HKDFBytesGenerator
88
import org.bouncycastle.crypto.params.HKDFParameters
99
import org.xmtp.proto.message.contents.CiphertextOuterClass
1010
import java.security.GeneralSecurityException
11+
import java.security.Key
1112
import java.security.SecureRandom
1213
import javax.crypto.Cipher
1314
import javax.crypto.Mac
@@ -98,5 +99,20 @@ class Crypto {
9899
hkdfGenerator.generateBytes(hkdf, 0, hkdf.size)
99100
return hkdf
100101
}
102+
103+
fun verifyHmacSignature(
104+
key: ByteArray,
105+
signature: ByteArray,
106+
message: ByteArray
107+
): Boolean {
108+
return try {
109+
val mac = Mac.getInstance("HmacSHA256")
110+
mac.init(SecretKeySpec(key, "HmacSHA256"))
111+
val computedSignature = mac.doFinal(message)
112+
java.util.Arrays.equals(signature, computedSignature)
113+
} catch (e: Exception) {
114+
false
115+
}
116+
}
101117
}
102118
}

0 commit comments

Comments
 (0)