fix(nostr): reserve two-hour DM timestamp margin

This commit is contained in:
wollow 2026-08-11 22:37:44 +03:00
parent bc03d78972
commit acfbacf78f
4 changed files with 8 additions and 8 deletions

View File

@ -22,7 +22,7 @@ import java.math.BigInteger
*/ */
object NostrCrypto { object NostrCrypto {
internal const val NIP17_DEFAULT_MAX_PAST_SECONDS = 85_500 internal const val NIP17_DEFAULT_MAX_PAST_SECONDS = 79_200
private val secureRandom = SecureRandom() private val secureRandom = SecureRandom()
// NIP-44 v2 only // NIP-44 v2 only
@ -319,7 +319,7 @@ object NostrCrypto {
} }
/** /**
* Random timestamp in the past, defaulting to 23h45m to leave slack inside iOS's 24h lookback. * Random timestamp in the past, defaulting to 22 hours to leave 2 hours of slack inside iOS's 24-hour lookback.
*/ */
fun randomizeTimestampUpToPast(maxPastSeconds: Int = NIP17_DEFAULT_MAX_PAST_SECONDS): Int { fun randomizeTimestampUpToPast(maxPastSeconds: Int = NIP17_DEFAULT_MAX_PAST_SECONDS): Int {
val now = (System.currentTimeMillis() / 1000).toInt() val now = (System.currentTimeMillis() / 1000).toInt()

View File

@ -37,7 +37,7 @@ object NostrProtocol {
val rumorId = rumorBase.computeEventIdHex() val rumorId = rumorBase.computeEventIdHex()
val rumor = rumorBase.copy(id = rumorId) val rumor = rumorBase.copy(id = rumorId)
// 2. Seal the rumor with 15 minutes of slack inside iOS's 24-hour lookback. // 2. Seal the rumor with 2 hours of slack inside iOS's 24-hour lookback.
val sealedEvent = createSeal( val sealedEvent = createSeal(
rumor = rumor, rumor = rumor,
recipientPubkey = recipientPubkey, recipientPubkey = recipientPubkey,

View File

@ -79,7 +79,7 @@ class NostrProtocolTest {
afterCreation: Int afterCreation: Int
) { ) {
assertTrue( assertTrue(
"$envelope timestamp must leave 15 minutes inside the iOS lookback", "$envelope timestamp must leave 2 hours inside the iOS lookback",
createdAt >= beforeCreation - MAX_OUTBOUND_BACKDATE_SECONDS createdAt >= beforeCreation - MAX_OUTBOUND_BACKDATE_SECONDS
) )
assertTrue( assertTrue(
@ -132,7 +132,7 @@ class NostrProtocolTest {
private companion object { private companion object {
const val IOS_DM_LOOKBACK_SECONDS = 86_400 const val IOS_DM_LOOKBACK_SECONDS = 86_400
const val TIMESTAMP_SAFETY_SLACK_SECONDS = 900 const val TIMESTAMP_SAFETY_SLACK_SECONDS = 7_200
const val MAX_OUTBOUND_BACKDATE_SECONDS = const val MAX_OUTBOUND_BACKDATE_SECONDS =
IOS_DM_LOOKBACK_SECONDS - TIMESTAMP_SAFETY_SLACK_SECONDS IOS_DM_LOOKBACK_SECONDS - TIMESTAMP_SAFETY_SLACK_SECONDS
} }

View File

@ -17,7 +17,7 @@ The remaining implementation work and milestone progress are tracked in
| Inner payloads | Noise type bytes, private-message TLVs, peer-state TLVs, file-transfer TLVs, live-voice bursts, fragment header, sync request TLVs | `ClientRewriteWireContractTest`, `AuthenticatedPeerStateTest`, `PrivateMediaTransferPreparerTest`, `VoiceBurstPacketTest`, `FragmentManagerTest` | | Inner payloads | Noise type bytes, private-message TLVs, peer-state TLVs, file-transfer TLVs, live-voice bursts, fragment header, sync request TLVs | `ClientRewriteWireContractTest`, `AuthenticatedPeerStateTest`, `PrivateMediaTransferPreparerTest`, `VoiceBurstPacketTest`, `FragmentManagerTest` |
| Identity/security | Announcement extensions, capability bitfield endianness, Noise static-key binding, handshake identity binding, signatures | `IdentityAnnouncementTest`, `NoiseSessionManagerIdentityBindingTest`, `ClientRewritePrimitiveContractTest` | | Identity/security | Announcement extensions, capability bitfield endianness, Noise static-key binding, handshake identity binding, signatures | `IdentityAnnouncementTest`, `NoiseSessionManagerIdentityBindingTest`, `ClientRewritePrimitiveContractTest` |
| Sync/routing | Stable packet IDs, GCS bitstream, replay collapse, TTL handling, relay choice, confirmed graph edges | `ClientRewritePrimitiveContractTest`, `GCSFilterTest`, `PacketRelayManagerTest`, `MeshGraphServiceTest`, `TransportBridgeServiceTest` | | Sync/routing | Stable packet IDs, GCS bitstream, replay collapse, TTL handling, relay choice, confirmed graph edges | `ClientRewritePrimitiveContractTest`, `GCSFilterTest`, `PacketRelayManagerTest`, `MeshGraphServiceTest`, `TransportBridgeServiceTest` |
| Nostr | Bech32, secp256k1 key derivation, NIP-01 event IDs/signatures, NIP-44 authenticated encryption, NIP-13 PoW, authenticated NIP-17 seals, 23h45m outbound envelope randomization | `ClientRewriteNostrContractTest`, `NostrProtocolTest` | | Nostr | Bech32, secp256k1 key derivation, NIP-01 event IDs/signatures, NIP-44 authenticated encryption, NIP-13 PoW, authenticated NIP-17 seals, 22h outbound envelope randomization | `ClientRewriteNostrContractTest`, `NostrProtocolTest` |
| Application state | Peer unions, canonical private conversations, chronological history, delivery/read behavior, media migration policy | `AppStateStoreTest`, `PrivateChatManagerTest`, `MediaSendingManagerMigrationTest` | | Application state | Peer unions, canonical private conversations, chronological history, delivery/read behavior, media migration policy | `AppStateStoreTest`, `PrivateChatManagerTest`, `MediaSendingManagerMigrationTest` |
## Golden-vector policy ## Golden-vector policy
@ -31,9 +31,9 @@ Round-trip tests remain useful but are not sufficient on their own: an encoder
and decoder can share the same defect. Each critical wire format therefore has and decoder can share the same defect. Each critical wire format therefore has
at least one literal vector. at least one literal vector.
NIP-17 receivers must use a lookback at least as long as the maximum timestamp NIP-17 receivers should reserve safety slack beyond the maximum timestamp
randomization used by senders. Android caps outbound seal and gift-wrap randomization used by senders. Android caps outbound seal and gift-wrap
randomization at 23h45m, leaving 15 minutes of slack inside iOS's 24-hour randomization at 22h, leaving 2 hours of slack inside iOS's 24-hour
subscription window, while retaining its 48-hour receive lookback. subscription window, while retaining its 48-hour receive lookback.
## Rewrite acceptance gate ## Rewrite acceptance gate