diff --git a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt index 4376d1e9..5682a365 100644 --- a/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt +++ b/app/src/main/java/com/bitchat/android/nostr/NostrProtocol.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.withContext object NostrProtocol { private const val TAG = "NostrProtocol" + private const val NIP17_IOS_COMPATIBLE_LOOKBACK_SECONDS = 86_400 private val gson = Gson() /** @@ -37,7 +38,8 @@ object NostrProtocol { val rumorId = rumorBase.computeEventIdHex() val rumor = rumorBase.copy(id = rumorId) - // 2. Seal the rumor (kind 13) signed by sender, timestamp randomized up to 2 days + // 2. Seal the rumor (kind 13) signed by sender, timestamp randomized within the + // receiver subscription window used by iOS. val sealedEvent = createSeal( rumor = rumor, recipientPubkey = recipientPubkey, @@ -231,7 +233,9 @@ object NostrProtocol { val seal = NostrEvent( pubkey = senderPublicKey, - createdAt = NostrCrypto.randomizeTimestampUpToPast(), + createdAt = NostrCrypto.randomizeTimestampUpToPast( + NIP17_IOS_COMPATIBLE_LOOKBACK_SECONDS + ), kind = NostrKind.SEAL, tags = emptyList(), content = encrypted @@ -260,7 +264,9 @@ object NostrProtocol { val giftWrap = NostrEvent( pubkey = wrapPublicKey, - createdAt = NostrCrypto.randomizeTimestampUpToPast(), + createdAt = NostrCrypto.randomizeTimestampUpToPast( + NIP17_IOS_COMPATIBLE_LOOKBACK_SECONDS + ), kind = NostrKind.GIFT_WRAP, tags = listOf(listOf("p", recipientPubkey)), // Tag recipient content = encrypted diff --git a/app/src/test/kotlin/com/bitchat/android/nostr/NostrProtocolTest.kt b/app/src/test/kotlin/com/bitchat/android/nostr/NostrProtocolTest.kt index a5bd9561..fa454b3b 100644 --- a/app/src/test/kotlin/com/bitchat/android/nostr/NostrProtocolTest.kt +++ b/app/src/test/kotlin/com/bitchat/android/nostr/NostrProtocolTest.kt @@ -3,10 +3,12 @@ package com.bitchat.android.nostr import com.google.gson.Gson import org.junit.Assert.assertEquals import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Test class NostrProtocolTest { private val gson = Gson() + private val nip17IosCompatibleLookbackSeconds = 86_400 @Test fun decryptPrivateMessage_acceptsAuthenticatedSeal() { @@ -41,6 +43,23 @@ class NostrProtocolTest { assertNull(decrypted) } + @Test + fun createPrivateMessage_keepsGiftWrapTimestampInsideIosLookbackWindow() { + val sender = NostrIdentity.generate() + val recipient = NostrIdentity.generate() + val before = (System.currentTimeMillis() / 1000).toInt() + + val giftWrap = NostrProtocol.createPrivateMessage( + content = "bitchat1:timestamp-window", + recipientPubkey = recipient.publicKeyHex, + senderIdentity = sender + ).single() + val after = (System.currentTimeMillis() / 1000).toInt() + + assertTrue(giftWrap.createdAt <= after) + assertTrue(giftWrap.createdAt >= before - nip17IosCompatibleLookbackSeconds) + } + private fun forgedGiftWrap( content: String, claimedSender: NostrIdentity, diff --git a/gradle/verification-metadata.xml b/gradle/verification-metadata.xml index a62cbff6..8d9b2acb 100644 --- a/gradle/verification-metadata.xml +++ b/gradle/verification-metadata.xml @@ -2147,6 +2147,9 @@ + + +