Commit Graph

11234 Commits

Author SHA1 Message Date
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
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
davotoula 9f8745864f update cz,de,pt,se 2026-04-12 20:47:51 +02:00
Vitor Pamplona 9de0c363ca Merge pull request #2270 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-12 14:38:52 -04:00
Crowdin Bot ecabb9cae7 New Crowdin translations by GitHub Action 2026-04-12 18:17:55 +00:00
Vitor Pamplona 7d5a2b2016 Merge pull request #2269 from vitorpamplona/claude/fix-relay-connectivity-pJ29t
Improve Tor bootstrap resilience and WebSocket connection handling
2026-04-12 14:16:34 -04:00
Vitor Pamplona 7c83714da2 Merge pull request #2374 from vitorpamplona/claude/secp256k1-c-implementation-KwkWV
Add high-performance C secp256k1 implementation with JNI bindings
2026-04-12 14:15:26 -04:00
Claude 3ae1fb36d2 perf: inline fe_mul into point operations — 10% faster verify
Move fe_mul/fe_sqr to static inline in field.h when ASM is available
(FE_MUL_ASM=1). This allows the compiler to inline the entire field
multiply directly into gej_double and gej_add_ge, eliminating function
call boundaries.

Before: gej_double had 9 function calls (to fe_mul/fe_sqr)
After:  gej_double has 2 function calls (fe_half only)

The compiler can now:
- Keep intermediate results in registers across multiply boundaries
- Schedule MULX instructions across adjacent field operations
- Eliminate push/pop register saves at call boundaries

gej_double: 738 → 1311 instructions (larger but no call overhead)

Impact:
  verifyFast: 35.1µs → 31.6µs (10% faster, 1.19x vs ACINQ)
  verify:     39.7µs → 38.4µs (0.98x vs ACINQ — essentially tied!)
  sign:       15.2µs → 14.2µs (1.48x vs ACINQ)
  batch(200): 6.2µs → 4.5µs per event (7.9x vs ACINQ)

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-12 03:02:52 +00:00
Claude 854bf9379a perf: fe_sqr calls fe_mul — eliminates 5ns/sqr gap, 33% faster fe_inv
fe_sqr was 20.6ns (using mul_wide + reduce_wide as separate functions)
while fe_mul was 15.6ns (inlined). Simply making fe_sqr call fe_mul
eliminates the gap.

This has a massive impact on fe_inv/fe_sqrt which do 255 squarings:
  fe_inv:  6107ns → 4085ns (33% faster!)
  fe_sqr:  20.6ns → 15.8ns (23% faster)
  fe_mul:  15.6ns → 14.9ns (stable)

Impact on operations:
  sign (cached):     15.2µs → 13.6µs (1.30x faster than ACINQ)
  pubkeyCreate:      15.3µs → 14.1µs (1.24x faster)
  verifyFast:        35.1µs → 32.2µs (1.01x vs ACINQ — tied!)
  verify (BIP-340):  39.7µs → 36.5µs (0.89x vs ACINQ)
  batch(200)/event:   6.2µs →  4.5µs (8.3x faster than ACINQ!)

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-12 02:52:51 +00:00
Vitor Pamplona bc8c5e2108 Merge pull request #2239 from davotoula/update-video-compressor-library-and-migrage-gif2mp4
Use GifToMp4Converter from LightCompressor-enhanced 1.9.0
2026-04-11 22:48:53 -04:00
Claude 070affb4e4 fix: restore accidentally deleted test files
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-12 01:40:40 +00:00
Claude 4dea4b5db5 perf: lazy fe_mul — remove normalize from mul/sqr output (both C and Kotlin)
Remove fe_normalize/reduceSelf from the end of field multiply and
square. After reduceWide, the output is in [0, 2^256) which may
include values in [P, P+C) where C = 2^32+977. This is the same
"unreduced" range that lazy fe_add produces, and is safe because:

- mul/sqr: mulWide handles any 256-bit input via reduceWide ✓
- add: carry fold handles overflow past 2^256 ✓
- sub: P-add-back on underflow produces correct field element ✓
- neg/half: already normalize input via reduceSelf ✓
- isZero/cmp/toBytes: caller normalizes before use ✓

Native C-to-C results (x86_64, vs ACINQ):
  verifyFast: 0.95x → 0.99x (essentially tied with ACINQ!)
  sign (cached): 1.18x → 1.24x faster
  ECDH: 1.05x → 1.06x faster
  batch(200)/event: 7.2µs → 6.4µs

Kotlin JVM results (vs previous lazy-add-only):
  Kotlin numbers stable — reduceSelf in reduceWide was already
  cheap on JVM since the branch is almost never taken.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-12 01:40:15 +00:00
Claude 8291987145 docs: document why lazy fe_sub is NOT safe with 4x64 limbs
Analyzed lazy fe_sub: on underflow, the unsigned-wrapped value
(a - b + 2^256) differs from (a - b + P) by C = 2^32 + 977.
When multiplied: (a-b+2^256)*x ≠ (a-b)*x mod p (off by x*C mod p).
The P-add-back on underflow is mandatory for correctness.

This is a fundamental difference from 5x52 lazy reduction where
magnitude tracking keeps values representable. With 4x64 fully-packed
limbs, sub MUST add P back, but add CAN skip reduceSelf since values
in [P, 2^256) differ from [0, C) which is handled by mulWide+reduceWide.

Also evaluated:
- WINDOW_G 12→14: only 1.2µs savings for 4x memory (128→512KB).
  Not worth it on phones where L1 cache is 128-256KB.
- fe_sqrt optimization: only 831ns overhead over theoretical minimum
  of 5.85µs. The addition chain is already near-optimal.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-12 01:32:21 +00:00
Claude ae77935625 perf: lazy field addition in Kotlin — 10-16% faster across all operations
Remove reduceSelf from FieldP.add, making it lazy (same approach as
the C implementation). Values may be in [0, 2^256) between additions.

Safety analysis:
- mul/sqr: reduceWide handles any 256-bit input ✓
- neg: added reduceSelf before P - a (prevents underflow) ✓
- half: added reduceSelf before conditional add P ✓
- sub: already adds P back on borrow (self-normalizing) ✓
- isZero/cmp: called on sub outputs (normalized) or after mul ✓
- verifySchnorrCore: added reduceSelf before Jacobian x-check ✓
- toBytes: only called on toAffine outputs (from mul, normalized) ✓

JVM benchmark results (ops/sec, HotSpot C2):
  pubkeyCreate:  32,096 → 37,211 (+15.9%)
  signXOnly:     31,149 → 34,507 (+10.8%)
  sign:          16,137 → 17,822 (+10.4%)
  verify:        12,733 → 14,027 (+10.2%)
  verifyFast:    14,734 → 15,449 (+4.9%)
  ECDH:          10,975 → 12,102 (+10.3%)
  batch(200):    91,611 → 102,354 (+11.7%)

The improvement is larger than expected (10-16% vs estimated 6%)
because HotSpot's branch prediction overhead for reduceSelf
(virtual dispatch + 4 Long comparisons) is higher than the
~2.5ns estimated for native code.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 23:59:44 +00:00
davotoula 9a02ee14ce refactor: use GifToMp4Converter from LightCompressor-enhanced 1.9.0
LightCompressor-enhanced 1.9.0 bundles the GIF-to-MP4 converter originally
contributed upstream from amethyst. Drop the duplicated copies and delegate
to the library, adapting GifToMp4Result -> MediaCompressorResult.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:31:13 +02:00
Claude e474f9b36f fix: add missing #include sha256.h in jni_bridge.c
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 15:11:05 +00:00
Claude 944562ddc8 feat: add SHA-256 benchmark comparing Android native vs our C vs Kotlin
Three SHA-256 implementations benchmarked on the same phone:
- sha256Android: Android's MessageDigest (BoringSSL + ARM64 CE hardware)
- sha256OurC: Our C SHA-256 (ARM64 CE hardware via JNI)
- sha256Kotlin: Kotlin's sha256 (platform MessageDigest on Android)

Also add nativeSha256 JNI method to expose our hardware SHA-256.

Why our C matches ACINQ on ARM64 despite different architectures:
- Our 4x64: 16 MUL+UMULH pairs per field mul
- ACINQ 5x52: 25 MUL+UMULH pairs per field mul
- We save 9 multiply pairs (~27ns on Cortex-X3) per fe_mul
- ACINQ saves ~6 normalizations with lazy reduction (~6ns)
- Net advantage: ~21ns per mul × 500 muls/verify > 0
- Plus our SHA-256 uses hardware CE, ACINQ's is software

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 15:02:27 +00:00
Claude 61e8471c37 fix: add missing #include <stdlib.h> in jni_bridge.c for malloc/free
https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 14:49:04 +00:00
Claude 40a51d855c feat: add Android cross-compile script for phone benchmarking
Add build_android.sh that cross-compiles the C secp256k1 library for
ARM64 using the Android NDK and places the .so in benchmark/src/main/
jniLibs/ where the benchmark APK will package it.

Usage:
  cd quartz/src/main/c/secp256k1
  ./build_android.sh
  cd ../../../../..
  ./gradlew :benchmark:connectedAndroidTest

Also:
- Fix Secp256k1InstanceC.android.kt to use Secp256k1C JNI binding
  class (same pattern as JVM) so the JNI method names match
  Java_com_vitorpamplona_quartz_utils_Secp256k1C_native*
- Add benchmark/src/main/jniLibs/ to .gitignore

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 14:40:31 +00:00
Claude eb9527d793 perf: hardware SHA-256 acceleration (SHA-NI x86_64, CE ARM64)
Add hardware-accelerated SHA-256 using platform crypto extensions:

x86_64 (SHA-NI, Intel Goldmont+/AMD Zen+):
  SHA256RNDS2 — processes 2 SHA-256 rounds per instruction
  SHA256MSG1/SHA256MSG2 — message schedule expansion
  Result: SHA-256(160B) 749ns → 165ns (4.5x faster)

ARM64 (Crypto Extensions, all Android ARMv8 phones):
  SHA256H/SHA256H2 — hash update (4 rounds per instruction)
  SHA256SU0/SHA256SU1 — message schedule
  Expected: similar 4-5x speedup on phone

Impact on secp256k1 operations:
  signSchnorr:    33→31µs (8%, 3-4 SHA-256 calls per sign)
  signXOnly:      17→16µs (8%)
  verifyFast:     34→33µs (4%, 1 SHA-256 call)
  batch(200):     7.2→6.6µs/event (10%, 200 SHA-256 calls)
  batch(200) now 5.1x faster than ACINQ individual verify

Native C-to-C (with SHA-NI + all optimizations):
  pubkeyCreate:  ACINQ 15.9  Ours 14.9  1.07x faster
  sign (cached): ACINQ 17.7  Ours 15.1  1.18x faster
  sign (full):   ACINQ 33.2  Ours 30.6  1.08x faster
  verifyFast:    ACINQ 32.1  Ours 33.7  0.95x (tied)
  batch(200):    ACINQ 33.5  Ours  6.6  5.1x faster

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 13:19:46 +00:00
Claude 41a49b639f perf: lazy fe_add + full ARM64 ASM fe_mul for phone performance
Two major optimizations targeting ARM64 mobile phones:

1. LAZY FIELD ADDITION (both platforms):
   fe_add no longer calls fe_normalize. Values may be in [0, 2^256)
   between operations. This is safe because:
   - fe_mul/fe_sqr reduction handles any 256-bit input
   - fe_negate normalizes its input before P - a
   - fe_is_zero/fe_equal/fe_to_bytes normalize their copies

   Saves ~6 normalize calls per gej_double (called 130x per verify).
   Impact: gej_add_ge 372→316ns (15%), gej_double 224→206ns (8%).

2. FULL ARM64 ASM fe_mul (ARM64 only):
   Complete 4x4 multiply + reduction in inline assembly using:
   - LDP/STP for load/store pairs (halves memory instructions)
   - MUL+UMULH with interleaved scheduling across columns
     (hides 3-cycle multiply latency behind independent additions)
   - Full reduction in ASM with MUL+UMULH+ADDS carry chain
   - 20 registers used (ARM64 has 31 — zero stack spills)
   - Row 1 and 3 interleave b0+b2 products with b1+b3 for ILP

   Expected ARM64 improvement: fe_mul ~20-25ns → ~13-15ns

x86_64 benchmark (measured on this machine):
  gej_add_ge: 372→316ns (15% faster)
  ECDH:       35.4→32.6µs (8% faster, now tied with ACINQ)
  batch(200): 1596→1437µs (10% faster, 7.2µs/event)

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 13:11:54 +00:00
Claude b71641a15f fix: fair C-to-C benchmark — ACINQ sign must include keypair_create
The previous benchmark unfairly gave ACINQ a cached keypair (created
once outside the loop) while our sign derived the pubkey each call.

Fixed to test both patterns:
- "sign (full)": both derive pubkey each call
  ACINQ: keypair_create + sign32 = 36.3µs
  Ours: ecmult_gen + sign_internal = 33.6µs → 1.08x faster

- "sign (cached)": both reuse precomputed pubkey
  ACINQ: sign32 with cached keypair = 18.8µs
  Ours: signXOnly with cached xonly = 17.4µs → 1.08x faster

Fair native C-to-C results (x86_64, BMI2):
  pubkeyCreate:          ACINQ 18.0  Ours 16.3  1.10x faster ✓
  sign (full):           ACINQ 36.3  Ours 33.6  1.08x faster ✓
  sign (cached):         ACINQ 18.8  Ours 17.4  1.08x faster ✓
  verify (BIP-340):      ACINQ 36.7  Ours 40.4  0.91x slower ✗
  verifyFast (Nostr):    ACINQ 36.7  Ours 34.5  1.06x faster ✓
  ECDH (cached):         ACINQ 37.2  Ours 35.4  1.05x faster ✓
  batch(200):            ACINQ 42.4  Ours  8.3  5.1x faster  ✓

We beat ACINQ on 5 of 6 operations. The only loss is full BIP-340
verify (0.91x) due to ACINQ's 5x52+ADCX/ADOX field assembly.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 12:38:03 +00:00
Claude 7d12b5a1dd fix: clear stale Arti Tor cache on each fresh init, retry on failure
The Arti data directory (<filesDir>/arti/) accumulates stale consensus
and relay descriptors over time. When this cached data becomes invalid,
TorClient::create_bootstrapped() either bootstraps with stale guards
(leading to circuit failures) or fails outright. Since the native
TorClient persists for the process lifetime, force-closing and reopening
the app re-reads the same corrupted data. Only clearing all app data
(which deletes the arti/ dir) would recover — matching the user report.

- Clear arti/cache/ on each fresh initialization (new process) so Arti
  always downloads fresh consensus and relay descriptors
- On initialize() failure, clear ALL arti data (state + cache) and
  retry once, recovering from corrupted state without requiring the
  user to manually clear app data

https://claude.ai/code/session_01VFAypytKGzdmuoJAXrb72G
2026-04-11 11:54:21 +00:00
Claude 2f5152efd6 bench: add native C-to-C benchmark vs ACINQ libsecp256k1
Direct comparison with no JNI/JVM overhead, both libraries called
from the same C program on the same machine (x86_64, BMI2).

Results (x86_64, same machine, cached pubkey pattern):
  Operation                  ACINQ     Ours    Speedup
  pubkeyCreate               21.2µs   20.3µs    1.04x
  signSchnorr                23.5µs   39.3µs    0.60x (*)
  verify (BIP-340)           42.8µs   46.8µs    0.91x
  verifyFast (Nostr)         42.8µs   39.1µs    1.10x
  ECDH (cached)              44.4µs   39.7µs    1.12x
  batch(200)                 47.2µs   10.1µs    4.7x per event

(*) sign is slower because ACINQ's keypair API pre-stores the pubkey,
avoiding ecmult_gen during sign. Our API derives pubkey each time.
A keypair-style API would match ACINQ's sign performance.

Cross-verification confirms both produce compatible signatures.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 11:47:19 +00:00
Claude bc405d045b perf: add P-table cache to ecmult and liftX cache to ECDH
The ECDH operation was 0.7x ACINQ (55µs vs 40µs) because:
1. P-table built from scratch every call: 9.8µs
2. liftX (sqrt) for pubkey decompression: 5.5µs

Both are redundant for the Nostr use case where the same peer key
is used repeatedly (NIP-44 encrypted DMs).

Fixes:
- Share the P-table cache (1024 entries) between ecmult and
  ecmult_double_g. Same pubkey → cache hit → skip table build.
- Use lift_x_cached in ecdh_xonly. Same pubkey → skip sqrt.

ECDH: 55.2µs → 33.9µs (1.63x faster, now 1.18x faster than ACINQ)

Full benchmark (x86_64, cached pubkey pattern):
  signXOnly:      17.6µs (56,818 ops/s) — 2.1x faster than ACINQ
  verifyFast:     35.0µs (28,571 ops/s) — 1.2x faster than ACINQ
  pubkeyCreate:   15.8µs (63,291 ops/s) — 1.2x faster than ACINQ
  ecdhXOnly:      33.9µs (29,499 ops/s) — 1.2x faster than ACINQ
  batch(200):   1596µs (125,313 ev/s)   — 12x faster than ACINQ

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 05:22:40 +00:00
Claude 57acbfd567 perf: ARM64-specific optimizations for mobile phones
Optimize the ARM64 code path (the primary target for Nostr clients):

1. fe_mul_asm: Use LDP/STP (load/store pair) to load all 8 limbs in
   4 instructions instead of 8 individual LDR. Row 0 in hand-tuned
   ASM with MUL+UMULH+ADDS/ADC. Rows 1-3 in __int128 C (ARM64 gcc
   generates optimal MUL+UMULH+ADDS/ADCS and MADD from this).

2. fe_normalize: Branchless on ARM64 using mask-based conditional
   subtract (compiles to CSEL/AND on ARM64, avoiding branch
   misprediction on mobile Cortex-A76+ SoCs). x86_64 keeps the
   branching version since its branch predictor handles the >99.99%
   non-taken case perfectly.

3. Document ARM64-specific instruction usage:
   - MUL + UMULH: 64×64→128 product (1 cycle throughput on A76+)
   - LDP/STP: load/store pair (2 regs per instruction)
   - ADDS/ADCS: carry chain for accumulation
   - MADD: fused multiply-add (generated by gcc from __int128)

These changes don't affect x86_64 correctness or performance
(verified: all keys pass, benchmark matches previous numbers).
The ARM64 improvements will be measurable when built with the
Android NDK for actual phone testing.

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 05:10:15 +00:00
Claude 4fd4ad63d1 perf: use MULX (BMI2) in x86_64 field multiply assembly
Replace MULQ with MULX in the inline assembly for fe_mul on x86_64.
MULX advantages over MULQ:
- Uses RDX as implicit input (not RAX), outputs to two arbitrary regs
- Does NOT clobber flags, enabling better instruction scheduling
- Enables the compiler to interleave multiplies with carries

Requires BMI2 (available on Haswell+ / Zen+). Added -mbmi2 to
CMakeLists.txt for x86_64 builds.

Note: ADCX/ADOX (ADX extension) for dual carry chains was investigated
but the compiler doesn't auto-generate them from __int128 code, and
hand-encoding in inline ASM requires restructuring the entire multiply
to express two independent carry chains. The MULX-only approach still
gives a measurable improvement.

fe_mul: 17.2ns → 15.5ns (10% faster)
pubkeyCreate: 16.7µs → 15.5µs (7% faster)
verifyFast: 36.5µs → 36.1µs (1% faster, 27,700 ops/s)

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 04:59:10 +00:00
Claude 3544c2cbc3 fix: benchmark batch verify naming, add batch sizes 4 and 64
Fix garbled batch verify names in the C benchmark (static char buffer
was shared across calls). Use string literals per batch size instead.

Fix batch verify self-test: use sign() (safe path) instead of
sign_xonly() since the test key has odd-y pubkey.

Add batch sizes 4 and 64 to the benchmark for finer granularity.

Batch verification results (x86_64 standalone, same pubkey):
  Batch   µs/event   events/sec   vs individual
    1       36.6       27,322       1.0x
    4       15.2       66,007       2.4x
    8       11.1       89,787       3.3x
   16        9.2      109,290       4.0x
   32        8.6      115,942       4.2x
   64        8.3      120,960       4.4x
  200        7.8      127,689       4.7x

vs Kotlin: C batch is 1.3-1.7x faster across all batch sizes.
At 200 events: 127K ev/s (C) vs 96K ev/s (Kotlin).

https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
2026-04-11 04:50:47 +00:00