Commit Graph

11460 Commits

Author SHA1 Message Date
Claude 1290e9151c feat(settings): add toggle to disable voice and video calls
New "Enable voice and video calls" switch at the top of the Call
Settings screen. Default is ON (no behavior change for existing users).
When a user turns it OFF:

- Call buttons in the chat room top bar are hidden. ChatroomScreen
  observes account.settings.callsEnabled as a StateFlow and flips
  onCallClick / onVideoCallClick to null, which RenderRoomTopBar
  already treats as "no buttons".

- Incoming CallOfferEvents are silently dropped in CallManager.
  A new isCallsEnabled: () -> Boolean hook on the CallManager
  constructor gates onIncomingCallEvent *before* the followed-user
  check, so the device never rings and no state transition occurs.
  Existing in-flight call signaling (answers/hangups/ICE) still flows
  through so a call that was active when the user flipped the toggle
  continues to clean up normally.

- The rest of the call-related settings (video quality, max bitrate,
  TURN servers) are hidden on the settings screen since they have no
  effect while calls are disabled — the screen becomes just the single
  meaningful toggle.

The setting is persisted per account via SharedPreferences
(PrefKeys.CALLS_ENABLED = "calls_enabled"), loaded in
LocalPreferences.loadFromEncryptedStorageSync, and exposed as a
MutableStateFlow<Boolean> on AccountSettings so both the chat top bar
and the settings screen react to changes without needing to navigate
away and back.

AccountViewModel wires
  isCallsEnabled = { account.settings.callsEnabled.value }
into the CallManager constructor so the flag is read lazily on every
incoming event.

Tests in CallManagerTest:
- incomingOfferIgnoredWhenCallsDisabledInSettings — disabled flag
  drops the offer, no state change, no published events.
- disablingCallsAfterStartDoesNotTearDownInProgressCall — an active
  call keeps working after the toggle flips; only new offers are
  ignored.
- incomingOfferProcessedWhenCallsEnabled — regression guard for the
  default-enabled path.

https://claude.ai/code/session_01XSPDbahLwHs9sdF5XfRvHB
2026-04-15 01:21:37 +00:00
Vitor Pamplona 235d87a386 Merge pull request #2387 from vitorpamplona/claude/secp256k1-implementation-comparison-ThHC3
Fix secp256k1 field arithmetic carry-fold bugs and optimize squaring
2026-04-14 20:02:37 -04:00
Claude b9d6cefbeb feat(call): per-peer 30s invite timeout and per-peer status in video grid
Two changes that tighten how the caller-side UX handles slow or
unresponsive callees in a group call:

1. Per-peer 30-second invite timeout

   Previously the caller used a single 60-second call-wide timeout: if
   *any* peer answered within the window the timer was cancelled and
   slow peers could remain "ringing" indefinitely. For a mid-call
   invite there was no timeout at all — the invitee stayed in
   pendingPeerPubKeys forever, burning a PeerConnection on the caller
   side and keeping the invitee's device ringing.

   CallManager now schedules an independent 30-second timer for every
   peer in pendingPeerPubKeys (or Offering.peerPubKeys in the initial
   offer phase). The timer is started when the peer is added to
   pending — in beginOffering, initiateCall and invitePeer — and is
   cancelled when the peer answers (onCallAnswered), rejects
   (onCallRejected) or hangs up (onPeerHangup). On expiry the peer is
   dropped from the group, a CallHangup is published to them so their
   device stops ringing, and onPeerLeft fires so CallController can
   dispose the per-peer PeerConnection. If the drop leaves the caller
   with zero connected and zero pending peers, the call ends with
   EndReason.TIMEOUT.

   Callee ringing still uses the 60-second CALL_TIMEOUT_MS in
   onIncomingCallEvent; the two timers now serve distinct roles.

   New constant CallManager.PEER_INVITE_TIMEOUT_MS = 30_000L.

   NIP-AC.md "Event Lifecycle" documents both timers.

2. Per-peer "Calling..." status in the video grid

   The shared "Waiting for others to join…" banner across the top of
   ConnectedCallUI is removed. PeerVideoGrid now takes a
   pendingPeerPubKeys set and, for each peer still pending, routes
   rendering through PeerAvatarCell with a "Calling…" status line
   under the username — so it's obvious *which* participants the call
   is waiting on, not just *that* it's waiting. A peer in pending
   never shows as a video cell even if a stale track is still in the
   map, because they haven't actually answered yet.

   The voice-only path in ConnectedCallUI now also uses PeerVideoGrid
   (with empty tracks/active-video) instead of the single
   GroupCallPictures + GroupCallNames stack, so the per-peer status
   behavior is consistent for audio calls.

Tests added in CallManagerTest:
- perPeerTimeoutEndsP2PCallWhenBobNeverAnswers
- perPeerTimeoutDropsSlowCalleeFromGroupCall
- perPeerTimeoutIsCancelledOnAnswer
- perPeerTimeoutDropsMidCallInviteeWhenNoAnswer
- perPeerTimeoutIsCancelledOnReject
- perPeerTimeoutEndsCallWhenAllCalleesIgnore

All existing CallManagerTest cases still pass unchanged.

https://claude.ai/code/session_01XSPDbahLwHs9sdF5XfRvHB
2026-04-14 23:23:42 +00:00
Claude d087e1ac13 feat(call): full-mesh setup for mid-call invites
When an existing group call participant invites a new peer via
CallController.invitePeer(), the caller connects to the invitee via the
normal offer/answer flow, but the other existing callees were never
establishing their own PeerConnections to the new peer — breaking the
full-mesh invariant.

Root cause: the NIP-AC "callee-to-callee mesh setup" mechanism relies on
each callee observing the others' CallAnswers during its own ringing
window and applying a symmetric "lower pubkey initiates" tiebreaker.
A mid-call invitee's ringing window happens after all existing callees
have already answered, so the invitee's discoveredCalleePeers set is
empty. Worse, if the invitee's pubkey is higher than an existing
callee's, that callee also stays passive (waiting for the invitee to
initiate), and no mesh connection is ever attempted between them.

Fix — break the symmetry on mid-call invites:

- CallManager.onCallAnswered now expands peerPubKeys when an answer
  arrives in Connected (or Connecting) state from a peer that is not
  yet in the tracked group membership. This keeps the UI and state
  consistent with the expanded group and gives CallController a clear
  hook via onAnswerReceived.

- CallController.onCallAnswerReceived splits the NO_SESSION case:
    * Connected state → mid-call invite. Unconditionally initiate a
      mesh CallOffer to the new peer. The invitee stays passive, so
      exactly one side initiates per pair and glare is structurally
      impossible.
    * Connecting state → initial-call mesh observation. Keep the
      existing lower-pubkey tiebreaker via onNewPeerInGroupCall to
      avoid glare with the symmetric peer.

This fix uses the existing event kinds (CallOffer 25050 and CallAnswer
25051) — no new kinds or tags are required. NIP-AC.md is updated with
a new "Mid-Call Mesh Expansion" subsection under "Inviting New Peers"
documenting the asymmetric rule and a full-flow diagram.

Tests in CallManagerTest cover:
- Mid-call invitee's broadcast answer expands existing callee's
  peerPubKeys in Connected state.
- Same expansion works in Connecting state (existing callee still
  handshaking with caller when the invitee joins).
- Initial-call answers from already-tracked peers do NOT trigger the
  expansion branch (regression guard).
- Full end-to-end 3-way flow exercising Alice, Bob, Carol managers.

https://claude.ai/code/session_01XSPDbahLwHs9sdF5XfRvHB
2026-04-14 22:54:07 +00:00
Claude 8390badadc perf(secp256k1/c): keep fe_sqr on fe_mul_asm for FE_MUL_ASM platforms
Measurement via bench_vs_acinq (native C-to-C comparison against ACINQ's
libsecp256k1) revealed that fe_sqr_inline's 10-mul __int128 path regressed
verify by ~10% and batch verify by ~25% on x86_64 GCC+BMI2+ADX. The ASM
fe_mul_asm uses MULX plus dual ADCX/ADOX carry chains that the compiler
cannot reproduce from __int128 arithmetic, so the extra 6 multiplications
are cheaper than losing the ASM's scheduling wins.

Restore fe_sqr to fe_mul_asm(r, a, a) whenever FE_MUL_ASM is set, and
document the measurement in the fe_sqr_inline comment block. The 10-mul
inline still wins for portable builds (clang without the GCC asm blocks,
MSVC, non-x86_64/ARM64 targets), where fe_mul falls back to the generic
__int128 row-schoolbook path and dedicated squaring genuinely cuts muls.

Baseline (HEAD~1) vs branch with this fix, averaged over 3 runs of
`bench_vs_acinq` on x86_64 (µs/op, lower is better):

  Operation          ACINQ  baseline  with fix
  pubkeyCreate        17.4     14.5     14.4
  sign (derive pk)    35.7     28.8     28.2
  sign (cached pk)    18.7     14.3     14.3
  verify (BIP340)     35.0     36.4     36.2
  verifyFast          35.0     33.0     32.4
  ECDH                35.8     31.6     31.5
  batch(32)           35.4      5.6      5.6
  batch(200)          36.5      4.9      4.7

Every measurement is within run-to-run noise of baseline, and our custom
C is ~1.2-1.3x faster than ACINQ on sign/pubkey and ~6-8x faster on batch
verify (which ACINQ has no public API for at all). Correctness verified:
`Cross-verification: ACINQ verifies ours=1, We verify ACINQ=1`, and all
188 Kotlin secp256k1 tests still pass.

https://claude.ai/code/session_01KExJURZATpL59ZKXP6AVP6
2026-04-14 22:48:56 +00:00
Claude 0fbe61274a fix(call): release WebRTC resources synchronously on CallActivity teardown
The normal cleanup path routes through CallManager -> state=Ended -> the
state-collector in viewModelScope -> CallController.cleanup(). If the
Activity is destroyed before the collector finishes (task removal, PiP
dismissal, system kill), the async path can leave PeerConnections, the
camera capturer, the proximity wake lock, and the audio-mode change
alive until AccountViewModel.onCleared runs later.

CallActivity.onDestroy / onStop now call CallController.cleanup()
directly as a synchronous safety net, in addition to publishing the
hangup/reject signaling on a detached scope.

To make that safe, CallController.cleanup() is now truly idempotent
within a call session: the cleanedUp flag stays sticky once set and is
re-armed only when a new call starts via initiateGroupCall() or
acceptIncomingCall(). This prevents a second sequential cleanup() call
from double-disposing WebRTC objects when both the state-collector path
and the Activity safety net run.

https://claude.ai/code/session_01XSPDbahLwHs9sdF5XfRvHB
2026-04-14 22:16:17 +00:00
Vitor Pamplona 08e119ede4 Fixes another permission needed for phone calls in the app 2026-04-14 17:48:46 -04:00
Vitor Pamplona 1dc6c2d0d8 correctly saves channel lists from new users. 2026-04-14 17:33:03 -04:00
Vitor Pamplona 5cd94f98cc Reducing the space between the call option buttons and the bottom user to avoid overlap with each other 2026-04-14 17:32:47 -04:00
Vitor Pamplona c170dd2eb3 Fixes the shadow clipping issue 2026-04-14 10:55:24 -04:00
Vitor Pamplona 6d3582cecb removing log borders 2026-04-14 10:45:08 -04:00
Vitor Pamplona 080be06a2f Fixes messages FAB's position when open 2026-04-14 10:31:13 -04:00
Vitor Pamplona d23f6f8ec1 Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-14 10:30:51 -04:00
Vitor Pamplona 887e0bf302 Improves stability and permission checks for voice calls when the app is in the background. 2026-04-14 10:19:47 -04:00
Vitor Pamplona d2b5508288 Merge pull request #2384 from davotoula/feat/adaptive-video-playback
Add YouTube-style video quality picker for adaptive videos
2026-04-14 07:54:52 -04:00
davotoula 606d976b57 code review fixes:
Close the listener race window by re-snapshotting tracks inside the
DisposableEffect, hoist the openDialog remember above the early returns
so the hook count stays stable when the player loses its video group,
return ImmutableList from buildQualityChoices for Compose stability,
and remember the TextButton ButtonColors copy.

Consolidate VideoQualitySheet into VideoQualityButton using the existing
PlaybackSpeedPopUpButton Popup pattern. Derives selection state from
tracks instead of duplicating it, drops the dead QualityOption.Auto
case, gates listener-driven UI behind early returns, and reuses the
existing call_settings_video_quality string. Net -168 lines.
2026-04-14 10:45:56 +02:00
davotoula 4fc739ad7e feat: add YouTube-style video quality picker
Adds a manual override for HLS/DASH adaptive streams via a gear icon
that opens a ModalBottomSheet with Auto + per-rendition options. The
button is hidden for single-rendition videos. Overrides reset when
players return to the pool so each video starts in Auto.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-14 10:04:37 +02:00
Vitor Pamplona 500bc8e4e7 Puts the user's own video up top 2026-04-13 19:55:11 -04:00
Vitor Pamplona 0b4ba6a6b8 Fixes crash on old samsungs when calling 2026-04-13 19:42:39 -04:00
Vitor Pamplona 05318d5f7d Using better icons for the ear sound 2026-04-13 19:37:16 -04:00
Vitor Pamplona 075ca6ef41 Merge pull request #2383 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-13 18:28:21 -04:00
Crowdin Bot a8e5ea2986 New Crowdin translations by GitHub Action 2026-04-13 21:39:53 +00:00
Vitor Pamplona fc0e24faba Merge pull request #2382 from davotoula/feat/lightcompressor-v2
Update LightCompressor-enhanced to v2.0.0
2026-04-13 17:38:19 -04:00
Vitor Pamplona c00222fa5b Merge branch 'main' of https://github.com/vitorpamplona/amethyst
# Conflicts:
#	gradle/libs.versions.toml
2026-04-13 17:28:28 -04:00
Vitor Pamplona 117ba4318b Updates AGP and other dependencies 2026-04-13 17:26:36 -04:00
davotoula 77cd51ec86 update LightCompressor-enhanced to v2.0.0
Migrates imports from com.abedelazizshe.lightcompressorlibrary to
com.davotoula.lightcompressor due to package rename in the fork.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-04-13 23:23:02 +02:00
Vitor Pamplona cd8957ad7d Merge pull request #2381 from nrobi144/feat/tor-support
feat: Desktop Tor support — embedded kmp-tor, full privacy routing, settings UI
2026-04-13 11:52:42 -04:00
Vitor Pamplona b3e997b6c8 Merge pull request #2377 from mstrofnone/fix/desktop-vlc-runtime
fix: VLC video playback shows Install VLC fallback when running via Gradle (#2062)
2026-04-13 08:27:09 -04:00
nrobi144 c9c0cda2c1 feat(tor): full app rebuild on Tor toggle via key() + bootstrap gate + retry
Complete Tor toggle UX:
- key(appRestartKey) wraps App — full Compose tree rebuild on toggle
- TorManager at Window level — survives rebuild, no stop/start collision
- Tor bootstrap gate at top of App — blocks ALL creation (clients, relays,
  Coil, subscriptions) until Tor proxy is ready. Zero clearnet during bootstrap.
- Coil ImageLoader reinitialized after setInstance on each rebuild
- Connection pool evicted on rebuild
- DesktopTorManager retries start up to 3x (handles previous daemon stopping)
- Settings reload from prefs on rebuild (fixes stale toggle display)
- Confirmation dialog on mode change in both TorSettingsSection and Dialog

Known: 2-4 stale Coil CDN connections may persist briefly after toggle
(image loads, not relay traffic — close after OkHttp 5min keep-alive).
Relay WebSocket connections are fully through Tor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 2df621056e feat(tor): restart confirmation dialog + key() app rebuild on Tor toggle
When user changes Tor settings, shows "Restart Required" confirmation
dialog. On confirm: saves prefs, triggers appRestartKey++ which forces
Compose to unmount/remount entire App tree.

Key behaviors:
- Window stays open, user stays logged in (AccountManager outside App)
- Column layout preserved (DeckState outside App)
- DisposableEffect stops old Tor daemon before new one starts
- TorSettingsSection mode change → confirmation dialog
- TorSettingsDialog Save → confirmation dialog
- Relays, subscriptions, caches all recreated fresh

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 01fc5f3ac6 fix(tor): remove broken runtime reconnect, add evictConnections
Remove the LaunchedEffect/scope.launch reconnect code that didn't
work (relay subscriptions lost after disconnect+connect cycle).
Tor toggle will use app rebuild via key() instead (next commit).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 70356d541e fix(tor): reconnect relays on Tor OFF too, not just ON
Relay reconnect was only triggered when Tor transitioned to Active.
When toggling OFF, relays stayed on the dead SOCKS proxy and never
reconnected over clearnet — feed went blank.

Now triggers reconnect on both Active and Off transitions, so relays
switch between proxy and direct connections when toggling Tor.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 566a4bf452 fix(tor): wire Coil image loading through Tor via custom Fetcher.Factory
Port Android's OkHttpFactory pattern to desktop — creates a custom
Fetcher.Factory<Uri> that calls DesktopHttpClient.currentClient() per
image request, routing all image loads through the SOCKS proxy when
Tor is active.

Avoids the OkHttpNetworkFetcher.factory() import issue by directly
constructing NetworkFetcher with the Tor-aware Call.Factory, matching
the proven Android implementation.

This closes the last network leak — ALL egress paths now route through
Tor when enabled.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 11d0a657ac fix(tor): close 8 network traffic leaks — fail-closed when Tor expected
SECURITY: With Tor ON, all HTTP traffic now routes through SOCKS proxy.
Previously only relay WebSocket connections used Tor; 8 other egress
paths created bare OkHttpClient() instances bypassing Tor entirely.

Fail-closed behavior:
- When Tor expected but bootstrapping → dead SOCKS proxy on port 1
  (requests fail instead of leaking IP)
- lateinit var crashes on misconfiguration (loud failure vs silent leak)

Leak sites fixed (all use DesktopHttpClient.currentClient()):
- AnimatedGifImage: GIF fetch
- SaveMediaAction: media downloads
- EncryptedMediaService: NIP-17 DM media
- ServerHealthCheck: Blossom server probes
- NoteActions: zap/lightning LNURL resolution
- DesktopBlossomClient: media uploads
- AccountManager: NIP-46 bunker relay connections
- Coil image loader: TODO (OkHttpNetworkFetcher import issue)

Also:
- Relay reconnect on Tor Active (prevents stale clearnet connections)
- isTorExpected() for fail-closed logic
- Tests updated for new torTypeProvider parameter

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:45 +03:00
nrobi144 8a23a1cd65 fix(tor): move shield to bottom of SinglePane sidebar to prevent cutoff
Shield was getting pushed off screen when RelayHealthIndicator ("x s ago")
appeared above it. Moved to very last position (after BunkerHeartbeat)
so it's always visible at the bottom edge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 df401c823b fix(tor): add TorStatusIndicator to SinglePaneLayout sidebar
Shield icon was only in DeckSidebar (deck mode) but missing from
SinglePaneLayout's NavigationRail. Added between RelayHealthIndicator
and BunkerHeartbeatIndicator. Clicking navigates to Settings.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 4aa4d39a91 fix(tor): default to Internal + Full Privacy, clickable shield, sticky dialog buttons
UX improvements based on manual testing feedback:

- Default TorType changed from OFF to INTERNAL (Tor on by default)
- Default preset changed to Full Privacy (all routing via Tor)
- Shield icon always visible in sidebar (not hidden when Off)
- Shield icon clickable — opens Settings
- TorSettingsDialog Cancel/Save buttons sticky at bottom (don't scroll away)
- HorizontalDivider above buttons for visual separation

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 95ec190b0d feat(tor): wire per-relay routing, .onion badge, shutdown hook, CompositionLocal
Phase 6: Polish and wiring.

Per-relay routing:
- TorRelayEvaluation wired into DesktopHttpClient with actual settings
- shouldUseTorForRelay uses real evaluation instead of { false }
- Settings changes propagate to torTypeFlow/externalPortFlow reactively

CompositionLocal for Tor state:
- LocalTorState provides TorState (status, settings, onSettingsChanged)
- Avoids threading Tor params through every composable layer
- DeckColumnContainer reads from LocalTorState for RelaySettingsScreen

.onion relay badge:
- RelayStatusCard shows "Requires Tor" (red) when Tor is off
- Shows "via Tor" (green) when Tor is active
- Only on .onion relay URLs

Shutdown hook:
- activeTorManager.stopSync() in existing shutdown hook
- Ensures no orphaned Tor processes on app exit
- Set via volatile var from App composable

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 e6068c0916 feat(tor): desktop Tor settings UI — indicator, toggle, dialog
Phase 5: Desktop settings UI for Tor.

TorStatusIndicator:
- Shield icon in sidebar footer (gray/yellow/green/red)
- Tooltip shows status (no port number for security)
- Only visible when Tor is not Off

TorSettingsSection:
- Inline in settings screen between Media Server and Dev Settings
- Mode selector (Off/Internal/External) with segmented buttons
- External SOCKS port input with validation (1-65535)
- "Advanced..." button opens full dialog

TorSettingsDialog:
- Full DialogWindow with preset selector (radio buttons)
- 4 relay routing toggles + 7 content routing toggles
- Preset auto-detection (whichPreset)
- Save/Cancel with DesktopTorPreferences persistence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 8a149af34b feat(tor): proxy-aware DesktopHttpClient + relay connection wiring
Phase 3: Network plumbing for Tor-routed relay connections.

DesktopHttpClient refactored from static singleton to Tor-aware class:
- Dual client: direct (30s timeouts) + SOCKS proxy (60s, 2x multiplier)
- Per-relay routing: localhost=direct, .onion=proxy, others via evaluator
- Shared ConnectionPool across client rebuilds
- Companion getSimpleHttpClient() for backward compat (NIP-46 bunker)

DesktopRelayConnectionManager now takes DesktopHttpClient instance.
Main.kt wires TorManager → DesktopHttpClient → RelayManager.

Tests: 10 new DesktopHttpClientTest cases covering Tor on/off,
.onion routing, localhost bypass, timeout multipliers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:44 +03:00
nrobi144 7cad30b899 feat(tor): add kmp-tor desktop daemon + preferences
Phase 2: Desktop Tor runtime implementation.

DesktopTorManager:
- Implements ITorManager using kmp-tor (Apache 2.0)
- Reactive: auto-starts/stops Tor based on TorType flow
- Supports INTERNAL (embedded kmp-tor) and EXTERNAL (user SOCKS port)
- NEWNYM/Dormant/Active signal support via TorCmd
- OS-specific data dirs (~/Library/Application Support, ~/.local/share, %APPDATA%)
- Directory permissions set to 700
- stopSync() for shutdown hook integration

DesktopTorPreferences:
- Implements ITorSettingsPersistence using java.util.prefs.Preferences
- Underscore-separated keys matching project convention
- Default TorType.OFF (user must opt-in)
- No explicit flush() (matches existing DesktopPreferences pattern)

Gradle:
- kmp-tor runtime 2.6.0 + resource-exec-tor 409.5.0 in version catalog
- java.management module added to nativeDistributions

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:25:43 +03:00
nrobi144 c564d4532b test: add commons/commonTest for shared Tor logic
Mirror TorSettingsTest and TorRelayEvaluationTest in commons/commonTest
using kotlin.test for KMP compatibility. Tests verify the shared types
directly without going through Android typealiases.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:24:31 +03:00
nrobi144 77f9905b69 feat(tor): add TorServiceStatus, ITorManager, ITorSettingsPersistence to commons
New shared types in commons/commonMain/tor/:
- TorServiceStatus: clean sealed class with Off, Connecting, Active(port),
  Error(message). Uses data object for stateless variants. No TorControlConnection
  dependency (Android keeps its own version for now).
- ITorManager: reactive interface with status flow + dormant/active/newIdentity
- ITorSettingsPersistence: load/save interface for platform persistence

Android TorServiceStatus unchanged — will migrate to commons version when
RelayProxyClientConnector is refactored to use ITorManager signals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:24:31 +03:00
nrobi144 84c3189cea refactor(tor): extract TorRelaySettings, TorRelayEvaluation to commons/commonMain
Move relay routing logic to shared commons module:
- TorRelaySettings data class
- TorRelayEvaluation with useTor() routing logic

Android files replaced with typealiases for backward compatibility.
All tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:24:31 +03:00
nrobi144 1d9c1eb9b6 refactor(tor): extract TorSettings, TorType, TorPresetType to commons/commonMain
Move core Tor data models and preset logic to shared commons module:
- TorSettings data class, TorType enum, TorPresetType enum
- parseTorType, parseTorPresetType, isPreset, whichPreset functions
- All preset constants (torOnlyWhenNeededPreset, etc.)

R.string resource IDs removed from enums in commons. Android retains
resourceId/explainerId as extension properties in ui.tor package.

15 Android files updated to import from commons.tor. All 69 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:24:31 +03:00
nrobi144 ff9093f5cc test: add pre-extraction test coverage for Tor logic
69 tests covering TorSettings/presets, TorRelayEvaluation routing,
and OkHttpClientFactory proxy/timeout behavior. Documents current
behavior before extracting to commons/ for desktop Tor support.

Coverage:
- TorSettings: parsing, preset matching, whichPreset, isPreset
- TorRelayEvaluation: .onion/localhost/DM/trusted/new routing,
  priority order, empty relay lists, Tor OFF/INTERNAL/EXTERNAL
- OkHttpClientFactory: SOCKS proxy creation, timeout multipliers,
  null port fallback (9050 security issue documented)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 07:24:31 +03:00
M 4341137e4a fix: VLC discovery for desktop Gradle run task 2026-04-13 06:17:07 +10:00
Claude 566a0997a2 fix(secp256k1): harden C/Kotlin field and scalar arithmetic; add dedicated fe_sqr
Correctness (C):
- scalar_mul: rewrite with a loop-driven fold so the 512-bit product is
  fully reduced regardless of the pre-existing third-fold carry-drop bug;
  also fixes the portable (!HAVE_INT128) fallback which was returning
  (a*b) mod 2^256 instead of (a*b) mod n.
- fe_mul / reduce_wide: loop the final carry fold in a while(carry) rather
  than a single if(carry), so a secondary carry-out is never silently
  dropped for adversarial or deeply lazy-reduced inputs.
- scalar_add: reuse the precomputed SCALAR_NC constant instead of
  recomputing n's two's-complement arithmetically each call.
- fe_negate: remove the data-dependent early-return for a == 0; compute
  P - a unconditionally and fold P back to 0 with a final fe_normalize,
  matching the Kotlin FieldP.neg path and dropping a branch.

Performance (C):
- Add a dedicated 10-mul fe_sqr_inline using __int128 (4 diagonal + 6
  doubled cross products, three-pass structure mirroring Kotlin
  U256.sqrWide). The previous fe_sqr delegated to fe_mul(a, a) using all
  16 schoolbook products; the new path saves ~37% of the multiplications
  at every squaring, and doublePoint/addPoints do ~9 sqrs each in the hot
  Jacobian loop. Col-3 mixes two products so the accumulator is split
  explicitly to avoid a uint128 overflow; the bug was caught by the C
  benchmark's self-test.
- Enable LTO (CMAKE_INTERPROCEDURAL_OPTIMIZATION) when the toolchain
  supports it, recovering cross-TU inlining of fe_mul/fe_sqr into point.c
  and schnorr.c.
- jni_bridge: replace the pinning GetByteArrayElements path (which blocks
  GC compaction) with a stack-or-heap copy_msg_bytes helper that uses
  GetByteArrayRegion. Short messages (32 B event digests, the common case
  for Nostr) hit a 512 B on-stack buffer.

Correctness (Kotlin):
- FieldP.neg: remove the early-return on zero for the same reasons as the
  C side, with a trailing reduceSelf(out) to collapse the P result back
  to 0 when the input was zero.
- Secp256k1.signSchnorrInternal / privKeyTweakAdd: switch the crypto-
  edge-case failure modes from generic require() to check() with clear
  messages, documenting them as invariants rather than argument errors
  and matching the C side's return-code semantics.

Tests & benchmarks:
- Add Secp256k1CrossValidationTest (jvmTest) that byte-for-byte compares
  Kotlin, ACINQ, and the custom C implementation across pubkey creation,
  Schnorr signing (incl. variable message lengths on the Kotlin side),
  privKeyTweakAdd, and x-only ECDH. This is the strongest parity check we
  can run without a third reference, and it's deterministic for
  reproducibility (fixed LCG seed).
- Add adversarial FieldPTest cases that chain lazy adds into mul/sqr/inv
  to exercise the new fe_mul fold loop and the dedicated fe_sqr path.
- Fix pre-existing FieldPTest/GlvTest failures (addNearP, addNegIsZero,
  halfOfOdd, invMulIsOne, invOfTwo, reduceWideWithMaxValues, betaCubedIsOne)
  that were asserting on raw limbs of lazy-reduced values; they now
  reduceSelf before comparing, consistent with the rest of the suite.
- Secp256k1Benchmark: document the intentional apples-to-apples
  asymmetries (signSchnorrWithPubKey vs signSchnorr, ecdhXOnly vs
  pubKeyTweakMul, privKeyTweakAdd's copyOf() penalty) and add a
  taggedHash benchmark since NIP-44 leans heavily on it.

All 188 secp256k1 Kotlin tests pass; the C library builds cleanly with
LTO enabled and the secp256k1_bench self-verification succeeds.

https://claude.ai/code/session_01KExJURZATpL59ZKXP6AVP6
2026-04-12 19:58:34 +00:00
David Kaspar 6a75fd9240 Merge pull request #2375 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-12 20:52:06 +02:00
Crowdin Bot c1939fd68c New Crowdin translations by GitHub Action 2026-04-12 18:51:24 +00:00