Two re-assessment findings:
- verifyScannedQR checked only staleness (now - ts > maxAge), so a
future-dated timestamp bought a QR a longer validity window than a
fresh one. The freshness check is now symmetric.
- Inbound Nostr DMs had no client-side created_at validation; the age
bound relied entirely on relays honoring the subscription's `since`
filter. Both gift-wrap decrypt paths now drop rumors outside
[now - lookback - skew, now + skew]. The inner rumor timestamp is the
sender's true send time (only the outer gift wrap is randomized per
NIP-17), so the window mirrors exactly what an honest relay already
guarantees — a dishonest relay can no longer inject stale or
future-dated DMs. Existing mitigations (persistent gift-wrap dedup,
relay-side since) are unchanged; this closes the malicious-relay gap.
Co-authored-by: jack <jackjackbits@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Transport callers no longer reach the raw NoiseEncryptionService:
getNoiseService() is deleted in favor of narrow purpose-named Transport
methods (session public key, identity fingerprint, static/signing keys,
sign/verify, callback installation). VerificationService now reaches
crypto through the transport, so it can no longer pin a stale service
across a panic reset. myPeerID/myNickname become private(set); the
existing setNickname mutator is the sole nickname path.
ConversationStore is now the sole owner of private-chat selection:
PrivateChatManager.selectedPeer is a published read-only mirror, and
startChat/endChat mutate through the store intent. The bridge method
and its five call sites are deleted, removing a latent bug where a
stale manager selection pushed back into the store could resurrect a
just-removed conversation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* Expand coverage for relay, identity, and location flows
* Fix macOS SwiftPM CI failures
---------
Co-authored-by: jack <jackjackbits@users.noreply.github.com>