Commit Graph

10833 Commits

Author SHA1 Message Date
Vitor Pamplona 78010cfd69 Merge pull request #2143 from vitorpamplona/claude/webrtc-nip-ac-tests-f1l1g
Add comprehensive test suite for NIP-AC call state machine
2026-04-05 12:29:30 -04:00
davotoula 9af678bd46 use toast instead of dialogue 2026-04-05 18:27:47 +02:00
davotoula f9c7e3b15c - Added @VisibleForTesting annotation to the three internal methods in ShareHelper.kt — signals test-only intent and triggers lint warnings if called from production code
- Removed redundant deleteOnExit() in test helper — explicit delete() after each test already handles cleanup; the shutdown hook was unnecessary overhead
2026-04-05 18:27:47 +02:00
Vitor Pamplona 6e1526e178 Merge pull request #2137 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 12:26:00 -04:00
Claude 1e105f5195 docs: align NIP-AC test vectors with actual test coverage
Add missing test vector entries for scenarios that have tests but were
not listed in the spec:

- E10-E18: single callee detection, P2P flow sequence, group p-tags
  for all event types, group member union, ICE serialization round-trip
- S22-S29: fresh events, wrong call-id handling, ICE forwarding,
  peer-left callback, reset, call-type preservation, caller cancel
- R7: renegotiation preserves call-id
- B1-B12: new ICE Candidate Buffering section covering both global
  and per-session buffer layers
- W1-W18: new Gift Wrap Round-Trip section covering NIP-44
  encrypt/decrypt for all event types including group calls
- I6, I10: renegotiation answer, renumbered full P2P flow

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:25:00 +00:00
Vitor Pamplona de73d54a08 Merge pull request #2140 from vitorpamplona/claude/asyncimage-size-check-IDdPa
Add thumbnail disk cache for profile pictures
2026-04-05 12:24:59 -04:00
Vitor Pamplona 19421db09a Merge pull request #2139 from vitorpamplona/claude/poll-view-only-results-7VBnX
Add poll results preview feature with persistent viewing state
2026-04-05 12:24:41 -04:00
Crowdin Bot 690f1a393d New Crowdin translations by GitHub Action 2026-04-05 16:20:30 +00:00
Vitor Pamplona ec5265cffd Merge pull request #2141 from vitorpamplona/claude/marmot-protocol-integration-JnU4w
Add MLS group state persistence and KeyPackage rotation management
2026-04-05 12:19:03 -04:00
Claude ec7b1e537f docs: remove Quartz-specific names from NIP-AC spec
Replace implementation-specific class names (EphemeralGiftWrapEvent,
SealedRumorEvent) with protocol-neutral language so the spec reads as
a standalone NIP independent of any particular codebase.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:18:48 +00:00
Claude 86d66673d8 style: rename test methods from snake_case to camelCase
https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 16:12:54 +00:00
Claude 40eb128cb8 feat: Phase 4 — MLS group state persistence & account integration
Add encrypted local storage for MLS group state so groups survive app
restarts, plus key lifecycle management:

- MlsGroupState: TLS-encoded serializable snapshot of complete group state
  (group context, ratchet tree, epoch secrets, private keys, transcript hash)
- MlsGroupStateStore: interface for encrypted per-group storage (platform
  implementations provide EncryptedSharedPreferences / encrypted file)
- MlsGroupManager: high-level coordinator managing group lifecycle, epoch
  secret retention window (N-1 for late messages), state persistence,
  and key export
- RetainedEpochSecrets: bounded window of past epoch decryption secrets
  for out-of-order message handling
- MlsGroup.saveState()/restore(): roundtrip serialization via TLS codec
- MlsGroup.proposeSigningKeyRotation(): Update proposal with fresh Ed25519
  signing key and X25519 encryption key for forward secrecy
- KeyPackageRotationManager: tracks consumed KeyPackages after Welcome
  processing, handles slot rotation and proactive age-based rotation
- Tests: 16 tests covering state serialization roundtrips, manager lifecycle,
  multi-group independence, and signing key rotation persistence

https://claude.ai/code/session_01MuRS2zSVm6A36HNFwG7M5p
2026-04-05 16:02:03 +00:00
Vitor Pamplona ddf500446c Merge pull request #2133 from davotoula/fix-image-sharing-error
Fix image sharing error
2026-04-05 11:42:41 -04:00
Claude 6fbbcbbf3b refactor: clean up PeerSessionManager and CallController integration
- Split PeerSession.kt out of PeerSessionManager.kt (types, interface,
  manager are now in separate files)
- Remove webRtcSessions duplication in CallController — PeerSessionManager
  is now the single source of truth for session tracking; WebRtcCallSession
  is retrieved via the adapter cast when WebRTC-specific APIs are needed
- Initialize PeerSessionManager eagerly with localPubKey (passed to
  CallController constructor) instead of lazy suspend init — fixes early
  ICE candidates being silently dropped before first suspend call
- Extract FakePeerSession into its own file for reuse across test files
- Remove assertion-only glare tiebreaker tests from NipACStateMachineTest
  (now properly tested with real logic in PeerSessionManagerTest)

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 15:38:28 +00:00
Claude c79ef634de fix: store expiration timestamps in viewedPollResultNoteIds to prevent unbounded growth
Changes viewedPollResultNoteIds from Set<String> to Map<String, Long> where
the value is the expiration timestamp. Uses the poll's endsAt date if set and
in the future, otherwise now + 24 hours. Expired entries are pruned on each
new insertion. Serialized as JSON instead of StringSet.

https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
2026-04-05 15:38:08 +00:00
Claude 361ef6798e refactor: simplify thumbnail cache API and remove unused code
- ThumbnailDiskCache: combine get()+decodeThumbnail() into single load()
  method. Move bitmap resize logic into generateFromFile() so callers
  just pass a source file and the cache handles decode+resize+save.
- ProfilePictureFetcher: remove unused `options` field, remove @Stable
  annotation, delegate all resize logic to ThumbnailDiskCache.
- UserAvatar: fix stale doc comment.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:36:34 +00:00
davotoula fd9de754b1 Merge branch 'main' into fix-image-sharing-error
Resolve conflict in ZoomableContentView.kt: keep viewModelScope
from this branch (prevents cancellation on dialog dismiss) and
toast notification from main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-05 17:31:19 +02:00
Claude 0df4c5e19c refactor: use typed ProfilePictureUrl class instead of URI scheme string
Replace profilepic:// string scheme with a ProfilePictureUrl data class.
Coil routes to the fetcher by type via Fetcher.Factory<ProfilePictureUrl>
instead of parsing a URI scheme string on every load. This avoids string
concatenation at the call site and string parsing in the fetcher/keyer.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:22:38 +00:00
Claude e9f1fcbd2a refactor: extract PeerSessionManager for testable ICE buffering and glare handling
Extract the ICE candidate buffering, answer routing, and renegotiation
glare logic from CallController into a platform-independent
PeerSessionManager in commons/. This makes the three most critical
NIP-AC spec requirements testable as JVM unit tests with FakePeerSession:

1. Two-layer ICE candidate buffering (global + per-session)
   - Global buffer: candidates arriving before any PeerConnection exists
   - Per-session buffer: candidates arriving before setRemoteDescription
   - Candidates buffered while ringing are preserved when accepting

2. Renegotiation glare handling (pubkey comparison tiebreaker)
   - Higher pubkey wins, lower pubkey rolls back local offer
   - Full glare scenario test with two PeerSessionManagers

3. Callee-to-callee mesh initiation (lower pubkey initiates)

CallController now delegates to PeerSessionManager via a PeerSession
interface, with WebRtcPeerSessionAdapter bridging to WebRtcCallSession.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 15:17:29 +00:00
Claude 3f032710d2 perf: avoid byte array allocation on first profile picture load
On cache miss, instead of reading the entire downloaded image into a
byte array (which could be 50MB+ for a malicious profile picture),
the fetcher now passes the NetworkFetcher result straight through to
Coil's normal streaming decoder pipeline — zero overhead vs current.

Thumbnail generation runs in background from Coil's disk cache file
using file-based BitmapFactory.decodeFile(), which is seekable
(supports two-pass bounds+decode) and never buffers the full file.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:14:48 +00:00
Claude 6c9f6531cf fix: prevent race conditions in thumbnail disk cache
- Atomic writes: write to temp file then rename, so readers never see
  a partially written JPEG
- Deduplication: ConcurrentHashMap tracks in-flight URLs so multiple
  composables requesting the same profile picture don't trigger
  duplicate decode+save work

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 15:07:17 +00:00
Vitor Pamplona b6507561af Merge pull request #2135 from davotoula/video-share-download-toast
show toast while downloading video for sharing
2026-04-05 10:58:40 -04:00
Claude c2347c6cee test: add NIP-AC gift wrap round-trip tests with real crypto
Verify the full encrypt/decrypt pipeline for all 6 NIP-AC signaling event
types through Ephemeral Gift Wraps (kind 21059):

  sign inner event → NIP-44 encrypt → gift wrap → unwrap → verify

Tests cover:
- Each event kind round-trips (offer, answer, ICE, hangup, reject, renegotiate)
- Third parties cannot decrypt wraps addressed to others
- Group call per-peer wraps are only decryptable by intended recipient
- "Sign once, wrap per recipient" produces identical inner event IDs
- SDP and ICE candidate special characters survive JSON+NIP-44 round-trip
- Ephemeral wrap keys are unique per wrap and differ from sender
- Inner event signatures are verifiable after unwrapping
- Full P2P call flow (all 7 signaling steps) through gift wraps

Uses real secp256k1 keys and NIP-44 encryption — no mocks.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 14:34:19 +00:00
Claude 80ece9ac9d perf: run thumbnail generation in background on first load
On cache miss, the fetcher now returns the decoded bitmap immediately for
display while saving the thumbnail to disk in a background coroutine. This
means the first load is just as fast as the normal Coil pipeline — no
blocking on resize + JPEG compress + disk write. The thumbnail is ready
for the next load.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 13:58:06 +00:00
Claude fd42c7b725 refactor: use quartz event builders in CallManager test helpers
Replace manual tag assembly in test helpers (makeOffer, makeAnswer, etc.)
with the existing build() methods from quartz event classes. This keeps
test tag structures in sync with production code automatically.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-05 13:54:03 +00:00
Claude 9b356d31ba feat: add thumbnail disk cache for profile pictures
Profile pictures are displayed at small fixed sizes (18-100dp) but Coil's
disk cache stores full-size originals, causing expensive re-decode on every
memory cache miss. With 60 avatars on screen, this creates significant I/O.

Adds a ProfilePictureFetcher with a dedicated ThumbnailDiskCache that stores
pre-resized 256px JPEG thumbnails (~5-10KB each). On cache hit, reads a tiny
file instead of re-decoding the full original. The zoomable full-screen dialog
bypasses this cache and loads the original via the normal Coil pipeline.

https://claude.ai/code/session_01PBJS3HDrurLP3i5n4tMsCv
2026-04-05 03:06:52 +00:00
Claude 3a78dd6afe feat: add "View results" option to polls that prevents voting after viewing
Adds a "View results" link below poll voting options. Once a user clicks it,
the poll results are shown and the user can no longer vote on that poll.
The viewed state is persisted in account settings via SharedPreferences.

https://claude.ai/code/session_01EkUYT4giQPUvbAJZ54o1se
2026-04-05 02:22:18 +00:00
Claude 8b944a0c68 feat: show toast while downloading video for sharing
Display a "Downloading video…" toast when the user taps share on a
remote video, so they know the download is in progress before the
share sheet appears. Uses the existing `downloading_video_for_sharing`
string resource that was already translated in multiple locales.

https://claude.ai/code/session_01B7HLXqbnicfj3eg1Wjgz6Z
2026-04-04 16:31:16 +00:00
Claude c88c961488 refactor: simplify scope usage and fix test temp file leak
- Remove unnecessary local alias; use accountViewModel.viewModelScope
  directly at call sites for clarity about scope ownership
- Add deleteOnExit() to test temp files so they're cleaned up even
  if assertions fail

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:56:21 +00:00
Claude 074cde0bd1 test: add media extension detection tests for ShareHelper
Make getImageExtension, getVideoExtension, and getMediaExtension
internal for testing. Add tests covering all supported image formats
(JPEG, PNG, GIF, WebP) and video formats (WebM, AVI, MP4, MOV),
plus edge cases (unknown formats, empty files, short headers).

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:52:21 +00:00
Claude 7a70c98158 fix: image sharing fails due to coroutine scope cancellation
The share media dialog used `rememberCoroutineScope()` which shadowed
the outer `accountViewModel.viewModelScope`. When onDismiss() was called
immediately after launching the share coroutine, the dialog-scoped
coroutine was cancelled, causing CancellationException to be caught
as a generic error and showing "Unable to share image" toast.

Fix: use viewModelScope for share operations so they survive dialog
dismissal, and re-throw CancellationException in shareImageFile.

https://claude.ai/code/session_01Euj5mXfjneNCx9m49YTp92
2026-04-04 15:43:10 +00:00
Claude 9854209843 test: add comprehensive NIP-AC WebRTC call state machine tests
Add 81 tests across two test suites covering the full NIP-AC spec:

- quartz/NipACStateMachineTest (31 tests): Protocol compliance test vectors
  for event structure, tags, P2P/group flows, ICE serialization, staleness,
  renegotiation glare rules, and multi-device support

- commons/CallManagerTest (50 tests): State machine integration tests using
  real NostrSignerInternal with actual crypto, covering:
  * Full call lifecycle (Idle → Offering/IncomingCall → Connecting → Connected → Ended → Idle)
  * Call rejection, busy auto-reject, hangup from any state
  * Self-event filtering (ICE, hangup, answer-elsewhere)
  * Mid-call renegotiation (voice ↔ video)
  * Group calls (mesh discovery, partial disconnect, invite peer)
  * Interface-level tests with real signing + gift wrapping pipeline
  * Full end-to-end P2P flow with two CallManager instances

Also adds test vector tables to NIP-AC.md spec for other implementers.

https://claude.ai/code/session_01AfRYTRCvtKqqDxeKQujUrx
2026-04-04 15:26:20 +00:00
Vitor Pamplona 5349121443 Merge pull request #2131 from vitorpamplona/claude/review-webrtc-spec-4JDNH
NIP-AC: Comprehensive WebRTC call state machine and group call spec
2026-04-04 10:41:46 -04:00
Claude db658f8c96 docs: add group call interoperability details to NIP-AC spec
Add missing protocol details required for interoperable group call
implementations: call state machine, callee-to-callee full-mesh setup
with pubkey tiebreaker, two-layer ICE buffering, renegotiation glare
handling, busy rejection, self-event filtering, group answer broadcast,
and partial disconnect behavior.

https://claude.ai/code/session_01EjVAgjJUuv2NsuqzRiYN2F
2026-04-04 14:39:23 +00:00
Vitor Pamplona c3524d988f Merge pull request #2130 from vitorpamplona/claude/polls-feed-tabs-Cr3Ow
Add tabbed interface to Polls screen for open/closed polls
2026-04-04 09:52:06 -04:00
Vitor Pamplona 8ec6c5e13a Merge pull request #2129 from vitorpamplona/claude/fix-dark-mode-polls-92zS2
Set explicit surface color for Card in notification feed
2026-04-04 09:49:26 -04:00
Vitor Pamplona 19037e8f02 Merge pull request #2128 from vitorpamplona/claude/fix-poll-draft-editing-3j8cG
Add routing support for Poll and ZapPoll draft edits
2026-04-04 09:48:00 -04:00
Vitor Pamplona 53d4fb7910 Merge pull request #2127 from vitorpamplona/claude/faster-broadcast-tracker-disappear-Rm4p9
Improve broadcast completion detection and dismissal timing
2026-04-04 09:47:40 -04:00
Vitor Pamplona ff3e0bbd44 Merge pull request #2126 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-04 09:39:39 -04:00
Crowdin Bot a827c5baba New Crowdin translations by GitHub Action 2026-04-04 13:38:38 +00:00
Vitor Pamplona 13f989cf35 Merge pull request #2026 from davotoula/ci-use-built-in-gradle-cache
fix for 5m hung windows build: Switching to cache: gradle in setup-java.
2026-04-04 09:36:56 -04:00
davotoula caf3202091 Merge branch 'main-upstream' into ci-use-built-in-gradle-cache 2026-04-04 06:02:58 +02:00
Claude 7a3542c4b4 feat: split polls feed into Open and Closed tabs
Adds tabbed navigation to the polls screen, separating polls into
"Open" (active/no end date) and "Closed" (ended) tabs. Uses the same
tab pattern as HomeScreen with HorizontalPager and SecondaryTabRow.

https://claude.ai/code/session_0168P3u8aMmn4HnSBDXR7yv7
2026-04-04 03:27:04 +00:00
Claude 2384d1f58b fix: reduce broadcast tracker dismiss delay from 11s to 3s after completion
Previously the broadcast tracker always waited TIMEOUT_SECONDS + 1 (11s) before
disappearing, even when all relays had already responded. Now it only auto-dismisses
after 3 seconds once all relays have responded, and stays visible while broadcasts
are still in progress.

https://claude.ai/code/session_017KzfovYfAWCgrwMy8RWLb9
2026-04-04 02:46:03 +00:00
Claude 904711f0e7 fix: add routing for poll draft editing
The routeEditDraftTo() function was missing cases for PollEvent and
ZapPollEvent, causing poll drafts to fall through to the else->null
branch and preventing navigation to the poll editor.

https://claude.ai/code/session_01WedCekkgCwNVCq2ybSTvqE
2026-04-04 02:45:06 +00:00
Claude ce91327ebe fix: use surface color for open poll cards in dark mode notifications
The Card wrapping open polls in the notification feed was using the
default Material3 containerColor (surfaceContainerHighest), which is
too bright in dark mode where the app uses pure black backgrounds.
Explicitly set the container color to surface to match the theme.

https://claude.ai/code/session_012ErcSBhXugYCRAwHRXrFr7
2026-04-04 02:13:11 +00:00
Vitor Pamplona e2a7f14c9a Merge pull request #2125 from vitorpamplona/claude/compare-mls-implementations-GbVs5
Add MLS interoperability test vectors and implementations
2026-04-03 21:02:24 -04:00
Claude 477d560d18 feat: implement External Commit flow (RFC 9420 Section 8.3, 12.4.3.2)
Enables non-members to join a group without a Welcome message by using
an external commit with HPKE key encapsulation.

HPKE extensions (Hpke.kt):
- deriveKeyPair(): DHKEM(X25519) key derivation from seed
- setupBaseSExport(): HPKE sender with export-only context
- setupBaseRExport(): HPKE receiver with export-only context
- keyScheduleFull(): Full key schedule returning exporter_secret
- HpkeExportContext: Export secrets via labeled expand

MlsGroup joiner side:
- externalJoin(groupInfoBytes, identity): Static method for joining
  via external commit. Performs HPKE encapsulation to external_pub,
  derives init_secret, adds self to tree, creates Commit with
  ExternalInit proposal and UpdatePath.

MlsGroup member side:
- externalPub(): Returns the group's HPKE public key for external joins
- groupInfo(): Returns GroupInfo with ratchet_tree + external_pub extensions
- deriveExternalInitSecret(): Derives init_secret from ExternalInit kem_output
- processCommit handles ExternalInit proposals by overriding init_secret

RatchetTree fix:
- setLeaf() now expands _leafCount when setting a leaf beyond current size,
  fixing external joins where the new member extends the tree

Test: testExternalJoin verifies Alice creates group, Zara joins via external
commit, Alice processes the commit, both at epoch 1 with 2 members.

All 121 MLS tests pass (41 interop + 80 unit), 0 failures.

https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
2026-04-04 00:55:09 +00:00
Claude db3dadb337 feat: complete security verification - Update sig, extensions, parent hash, replay detection
Closes all remaining security gaps from the RFC 9420 audit:

1. Update proposal signature verification (RFC 9420 Section 12.1.2):
   - Verifies LeafNode signature in Update proposals via verifyLeafNodeSignature()
   - Ensures sender can only update their own leaf with properly signed data

2. GroupContextExtensions validation (RFC 9420 Section 12.1.7):
   - Validates extension types against KNOWN_EXTENSION_TYPES whitelist
   - Supports ratchet_tree (0x0001), required_capabilities (0x0002),
     external_pub (0x0003), external_senders (0x0004), Marmot (0xF2EE)
   - Rejects unknown extension types

3. Parent hash validation (RFC 9420 Section 7.9.2):
   - Full per-node ParentHashInput computation:
     encryption_key || parent_hash || original_sibling_tree_hash
   - Walks the entire direct path verifying hash chain
   - Exposed treeHashNode as internal for sibling hash computation
   - Returns false (rejects) on any chain break

4. Message replay detection (RFC 9420 Section 9.1):
   - Added consumedGenerations tracker (Map<sender, Set<generation>>)
   - applicationKeyNonceForGeneration rejects already-consumed generations
   - Prevents replaying messages within the same epoch

5. Welcome ciphersuite verification (RFC 9420 Section 12.4.3.1):
   - Verifies Welcome.cipherSuite matches KeyPackage.cipherSuite
   - Rejects mismatched ciphersuites before attempting decryption

All 120 MLS tests pass (41 interop + 79 unit), 0 failures.

https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
2026-04-04 00:44:27 +00:00
Claude 9be7cfd27a feat: security hardening - sender auth, epoch/group verification, lifetime checks
Addresses critical security gaps identified in the RFC 9420 audit:

1. Epoch and group ID verification in decrypt (RFC 9420 Section 6.1):
   - PrivateMessage.epoch must match current group epoch
   - PrivateMessage.groupId must match current group ID
   - Rejects messages from wrong epoch/group immediately

2. Remove proposal sender authorization (RFC 9420 Section 12.1.2):
   - Cannot remove yourself via Remove (use SelfRemove)
   - Target leaf index must be in range and non-blank
   - Committer is implicitly authorized for inline proposals

3. KeyPackage lifetime validation (RFC 9420 Section 10.1):
   - Checks notBefore/notAfter against current time on Add proposals
   - Rejects expired or not-yet-valid KeyPackages

4. Unified proposal application in commit():
   - commit() now uses applyProposal() for all validation
   - Same authorization checks apply to both commit() and processCommit()
   - addedMembers tracked before apply for Welcome generation

All 120 MLS tests pass (41 interop + 79 unit), 0 failures.

https://claude.ai/code/session_01NocQDWj2Y92FugjfgazzL3
2026-04-04 00:40:13 +00:00