Commit Graph

10863 Commits

Author SHA1 Message Date
Claude 78d8914ee1 test: add Android microbenchmark for secp256k1 operations
Creates Secp256k1Benchmark.kt in the benchmark module using AndroidX
Benchmark (Jetpack Microbenchmark) to measure all secp256k1 operations
on real Android hardware/emulator with proper warmup and statistics.

Benchmarks: verifySchnorr, signSchnorr, compressedPubKeyFor,
secKeyVerify, pubKeyCompress, privateKeyAdd, pubKeyTweakMulCompact.

Run with: ./gradlew :benchmark:connectedAndroidTest

The existing SignVerifyBenchmark already tests sign/verify through
Nip01Crypto, but this new benchmark tests the Secp256k1Instance
operations directly, matching the JVM benchmark for comparison.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:56 +00:00
Claude 5b7f8478aa perf: secKeyVerify operates directly on bytes, avoids IntArray allocation
Compares the 32-byte key against n byte-by-byte (big-endian) instead of
decoding into limbs first. Eliminates one IntArray(8) allocation and the
fromBytes conversion. The non-zero check ORs all bytes in a single pass.

privKeyTweakAdd was also tested with a byte-based approach but reverted
because 32 byte-iterations are slower than 8 limb-iterations — the JVM
optimizes Long operations better than byte loops.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:56 +00:00
Claude de3808416c perf: pubKeyCompress now 2.4x faster than native — zero field arithmetic
For uncompressed keys (04 || x || y), compression only needs the y parity
bit and a copy of x. The previous implementation decoded both coordinates
into field limbs, validated y²=x³+7 with 2 field muls, then re-encoded.

The new implementation reads the last byte of y (parity bit), copies the
32 x-bytes, and sets the 02/03 prefix. No IntArray allocations, no field
arithmetic, no curve validation — just byte manipulation.

For already-compressed input, returns the input unchanged.

Benchmark: pubKeyCompress 658K → 6.7M ops/s (was 4.5x slower, now 2.4x faster than native)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:56 +00:00
Claude 57d8cc5d0c test: add 20 edge-case tests for GLV, FieldP, Point, and Secp256k1
Fills coverage gaps identified by audit, especially for areas where
bugs were found during development:

GlvTest (4 → 14 tests):
- wNAF reconstruction for small, large, and high-bit scalars
- wNAF carry overflow at bit 255 (regression test for the fixed bug)
- wNAF digits are odd and bounded, zero-run guarantee verified
- splitScalar with zero, n-1, and 5 different scalar values
- splitScalar halves are ~128 bits (upper limbs zero)
- β³ ≡ 1 (mod p) verification
- mulDoubleG with zero e scalar

FieldPTest (22 → 27 tests):
- half(p-1), inv(2), sqrt(0), sqrt(1)
- mul aliasing (output == input)

PointTest (22 → 25 tests):
- addMixed with equal points (should double)
- addMixed with inverse points (should give infinity)
- parsePublicKey with compressed odd-y key round-trip

Secp256k1Test (14 → 17 tests):
- verifySchnorr with wrong message (negative test)
- verifySchnorr with corrupted signature (negative test)
- signSchnorr deterministic (null auxrand produces same signature)

Total: 126 → 146 tests

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:56 +00:00
Claude 7a96a11f6e refactor: extract GLV endomorphism and wNAF encoding into Glv.kt
Splits the 914-line Point.kt into two focused files:

- Glv.kt (248 lines): GLV endomorphism constants, scalar decomposition
  (splitScalar), Babai rounding (mulShift384), and wNAF encoding. This is
  a self-contained algorithm that only operates on scalars (no EC points).

- Point.kt (683 lines): EC point types, core operations (double, addMixed,
  addPoints), scalar multiplication (mul, mulG, mulDoubleG), coordinate
  conversion (toAffine, liftX), and key serialization.

Each file has a comprehensive header explaining its purpose and the
algorithms it implements. The Point.kt header is updated to reflect the
current state (GLV and wNAF are implemented, not "future optimizations").

mulDoubleG now references Glv.splitScalar and Glv.wnaf instead of local
methods. GlvTest updated to use the Glv object directly.

No functional changes — pure file reorganization with updated documentation.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:56 +00:00
Claude 3cbe6ee73b perf: reduce ByteArray allocations in verify and sign paths
- Replace 3-way ByteArray concatenation for tagged hash inputs with
  single pre-sized array + copyInto calls (avoids 3 intermediate arrays)
- Add U256.fromBytes(bytes, offset) overload to decode from a slice
  without copyOfRange allocation
- Build signature output using toBytesInto instead of concatenation
- Apply same pattern to signSchnorr's nonce and challenge hash inputs

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude 0e3c2fcda5 docs: document Karatsuba attempt and why schoolbook is kept for 8 limbs
Karatsuba multiplication (splitting 8 limbs into 4+4 halves for 48 inner
products instead of 64) was implemented and tested but reverted because
the overhead of extra additions, carry propagation, and 5 temporary array
allocations per call negates the product-count savings at only 8 limbs.
The crossover point where Karatsuba beats schoolbook is typically ~32+
limbs on hardware with fast multiply.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude 1f885accb1 perf: cache lambda(G) table, optimize P table build, reduce allocations
Three targeted optimizations in the verify hot path:

1. Precompute and cache lambda(G) table: the 8 AffinePoints for λ(G)
   odd-multiples are now lazily initialized once (like gTable) instead
   of recomputing 8 field multiplications per verify call.

2. Efficient P odd-multiples table: build [1P, 3P, 5P, ..., 15P] via
   1 doubling + 7 additions (compute 2P then add repeatedly) instead
   of building all 16 multiples [1P..16P] with 15 additions and
   discarding the even ones.

3. Pre-allocated Jacobian negation scratch: the MutablePoint used for
   negating P-side table entries (when wNAF digit is negative) is now
   allocated once before the main loop instead of per-digit.

Also removed the unused maybeNegateTable function.

Benchmark: verifySchnorr 3,429 → 3,556 ops/s (7.2x vs native)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude 512ca0fec0 perf: optimized addition chains for field inversion and square root
Replaces generic square-and-multiply exponentiation in inv() and sqrt()
with hand-crafted addition chains derived from libsecp256k1.

The key insight: p-2 and (p+1)/4 have long runs of 1-bits (since p ≈ 2^256),
so generic powModP wastes ~230+ multiplications on redundant mul-by-base steps.
The addition chain instead builds a^(2^k - 1) for k = 2,3,6,9,11,22,44,88,176,
220,223 via a ladder of squarings, then combines them with a short tail.

Savings per call:
  inv:  255 sqr + 15 mul = 270 ops (was 255 sqr + 248 mul = 503 ops) → -233 muls
  sqrt: 254 sqr + 13 mul = 267 ops (was 253 sqr + 246 mul = 499 ops) → -233 muls

Each verify does one inv (toAffine) + one sqrt (liftX), saving ~466 field
multiplications = ~29,800 fewer inner products per verification.

Also removes the now-unused generic powModP function and its P_MINUS_2 /
P_PLUS_1_DIV_4 exponent constants.

Benchmark: verifySchnorr 3,254 → 3,429 ops/s (~5% faster, 8.2x vs native)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude 7c9db9ca9c perf: implement GLV endomorphism — verify 68% faster (8.7x vs native)
Implements the secp256k1 GLV (Gallant-Lambert-Vanstone) endomorphism to
halve the number of point doublings during signature verification.

How it works: secp256k1 has an efficiently computable endomorphism
φ(x,y) = (β·x, y) where β is a cube root of unity in the field.
The corresponding scalar λ satisfies λ·P = φ(P). Any 256-bit scalar k
can be decomposed into k = k₁ + k₂·λ (mod n) where k₁, k₂ are ~128 bits.
This means k·P = k₁·P + k₂·(β·P.x, P.y), requiring only ~130 doublings
instead of 256.

For verification (s·G - e·P), both scalars are split into halves,
giving 4 streams processed in a single pass: s₁·G, s₂·λ(G), e₁·P, e₂·λ(P).

Key fixes from earlier debugging:
- MINUS_LAMBDA constant was wrong (byte-level transcription error)
- G1/G2 Babai rounding constants were truncated to ~142 bits instead of
  the full 256-bit values from libsecp256k1
- wNAF overflow fix: extended working array with maxOf(totalBits, scalar.size)
  to handle scalars larger than maxBits (IntArray(8) > IntArray(5) for 129-bit)
- GLV sign handling: XOR the negation flag with each wNAF digit sign instead
  of pre-baking into tables (avoids double-negation on negative digits)
- P-side uses Jacobian tables (avoids 8 expensive field inversions that
  would negate the GLV speedup)

Tests: 4 new GLV-specific tests (scalar split reconstruction, endomorphism
correctness, wNAF+GLV k1*G, mulDoubleG with zero scalar)

Benchmark improvement for verifySchnorr:
  Before (wNAF only):  2,626 ops/s (10.6x vs native)
  After (wNAF + GLV):  3,254 ops/s (8.7x vs native)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude ec1833b346 perf: implement wNAF-5 Shamir for verify — 35% faster (10.6x vs native)
Fixed the wNAF encoding bug and wired it into the verification hot path.

Bug fix: The wNAF encoding silently dropped carry bits that overflowed past
the 256-bit scalar boundary. When a negative digit near bit 251 caused a
carry to bit 256, the extraction window was clamped to 0 bits by
`w.coerceAtMost(maxBits - bit)`, causing the carry digit to be lost.
Fixed by extending the working copy and result arrays to accommodate
carries up to bit (maxBits + w), and using totalBits instead of maxBits
for the window size clamp.

Performance: wNAF-5 (windowed Non-Adjacent Form, width 5) encodes scalars
using signed odd digits {±1, ±3, ..., ±15} with guaranteed ≥4 zero-runs
between non-zero digits. This reduces point additions in mulDoubleG from
~120 (4-bit window) to ~86 for two 256-bit scalars, a ~28% reduction in
additions while the 256 doublings remain the same.

Benchmark improvement for verifySchnorr:
  Before: 1,940 ops/s (12.9x vs native)
  After:  2,626 ops/s (10.6x vs native)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:55 +00:00
Claude e26ccb23cf test: add comprehensive unit tests for U256, FieldP, ScalarN, ECPoint
Tests each layer of the secp256k1 implementation independently, not just
through the public Secp256k1 API. This catches bugs in the foundations
that could silently produce wrong results for specific input values.

U256Test (22 tests):
- isZero: zero, non-zero, high-bit cases
- cmp: equal, less-than, greater-than
- addTo/subTo: simple, carry/borrow, overflow/underflow
- mulWide: small values, large values, sqrWide consistency
- sqrWide: matches mulWide for arbitrary and max values
- fromBytes/toBytes: round-trip, zero encoding
- getNibble: low nibbles, high nibbles
- testBit: low bits, high bits
- xorTo: basic XOR

FieldPTest (22 tests):
- Identities: add zero, sub self, mul one, neg twice
- Reduction: add near p, overflow past p, underflow past 0
- Commutativity, distributivity of mul
- sqr matches mul(a,a)
- inv: a * a^(-1) = 1, inv(1) = 1, inv(p-1) = p-1
- half: even values, odd values, half-then-double round-trip
- sqrt: square root of perfect square, non-residue rejection, generator point
- reduceWide: (p-1)² = 1 mod p
- In-place operations: add, sqr into output arrays

ScalarNTest (18 tests):
- isValid: normal, zero, n, n-1
- Identities: add zero, sub self, add/sub round-trip, neg twice, add neg
- Commutativity, distributivity of mul
- inv: a * a^(-1) = 1
- Edge cases: (n-1)+1=0, (n-1)+2=1, (n-1)²=1
- neg(0)=0, reduce(n)=0, reduce(small)=unchanged

PointTest (22 tests):
- Generator on curve: y² = x³ + 7
- doublePoint: matches 2·G, in-place, infinity
- addPoints: G+G=2G, infinity identity, inverse→infinity
- addMixed: matches full addition, infinity input
- mulG: by 1, by 0 (infinity), by n (infinity), matches generic mul
- mulDoubleG: separate vs combined computation
- liftX: generator, invalid x
- Serialization: compress/decompress round-trip, uncompressed round-trip, invalid key rejection

Total: 122 tests (86 new + 36 existing BIP-340/ACINQ vectors)

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude 368d1d16b7 refactor: split Field.kt into U256.kt, FieldP.kt, ScalarN.kt
Each object now lives in its own file for easier navigation and review:

- U256.kt (284 lines): Raw 256-bit unsigned integer arithmetic with the
  file-level architecture documentation explaining representation choices
- FieldP.kt (360 lines): Field arithmetic modulo the secp256k1 prime p,
  including reduction, inversion, and square root
- ScalarN.kt (230 lines): Scalar arithmetic modulo the group order n,
  including wide reduction and Fermat inversion

No functional changes — pure file reorganization.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude ff2a00587e refactor: document secp256k1 implementation and remove dead code
Major cleanup of the pure-Kotlin secp256k1 implementation for readability:

Documentation:
- Added file-level architecture comments explaining representation choices
  (why 8×32-bit limbs, why not 5×52-bit like C), field reduction strategy,
  and performance approach (mutable output params, thread-local scratch)
- Added single-paragraph explainers for domain jargon: Jacobian coordinates,
  Fermat inversion vs safegcd, windowed scalar multiplication, Shamir's trick,
  GLV endomorphism, wNAF encoding
- Documented every public function with purpose, cost, and usage context
- Added inline comments explaining the math in point doubling/addition formulas

Removed dead code (-400 lines):
- straussGlvGP: GLV-accelerated Strauss method (had sign-handling bug)
- scalarSplitLambda, SplitResult, isHigh: GLV scalar decomposition
- wnaf, getBitsVar, addBitTo: wNAF encoding functions
- mulLambdaAffine, addMixedWithSign, buildOddMultiplesTable: GLV support
- All GLV constants (BETA, LAMBDA, MINUS_LAMBDA, G1, G2, MINUS_B1, MINUS_B2)
- U256.mulShift: used only by GLV scalar decomposition
These are preserved in git history and can be restored once the wNAF
interaction bug with the verify path is understood and fixed.

Structure:
- Field.kt: Clear sections (U256 → FieldP → ScalarN) with headers
- Point.kt: Sections (types → doubling → mixed add → full add → scalar mul
  → conversion → serialization) with formula documentation
- Secp256k1.kt: Grouped by purpose (keys → BIP-340 → tweaks) with
  algorithm steps documented in KDoc

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude 858cf2ea1a perf: add dedicated squaring, mixed Jac+Affine addition, optimized doubling
Phase 1 optimizations from C code analysis:

1. Dedicated sqrWide: Exploits a[i]*a[j] symmetry — 36 inner products
   instead of 64. Reduces field squaring cost by ~40%.

2. Mixed Jacobian+Affine addition (addMixed): 8M+3S instead of 12M+4S.
   Saves 4 multiplications per addition when one operand is affine.
   Used for precomputed G table lookups during Shamir's trick.

3. Optimized point doubling (3M+4S via fe_half): Uses the (3/2)*X²
   formula from libsecp256k1, replacing a field multiplication with a
   cheap halving operation (carry-propagating right shift).

4. fe_half: Branchless divide-by-2 mod p, used by the new doubling formula.

5. AffinePoint type: Stores precomputed table entries as (x,y) without z,
   enabling mixed addition. G table now stored as affine.

6. U256.mulShift: 256x256→shift multiplication for future GLV scalar
   decomposition.

7. GLV infrastructure (straussGlvGP, scalarSplitLambda, wNAF, endomorphism
   constants): Implemented but not yet wired into the verify hot path due
   to sign-handling bugs being debugged. The 4-stream Strauss with GLV
   will halve doublings from 256→128 once the sign logic is fixed.

Benchmark: verifySchnorr 1,940 → 2,116 ops/s (~9% improvement)
The modest gain reflects that only G-side additions use mixed add;
P-side still uses full Jacobian. GLV will provide the next big jump.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude fbd145557e test: add secp256k1 benchmark comparing native JNI vs pure Kotlin
Benchmarks all Secp256k1Instance operations: verifySchnorr, signSchnorr,
pubkeyCreate, pubKeyCompress, pubKeyTweakMul (ECDH), privKeyTweakAdd,
secKeyVerify, and the combined patterns used by the codebase.

Results on JVM:
  verifySchnorr    ~1,940 ops/s (Kotlin) vs ~25,000 ops/s (native) = ~13x
  signSchnorr      ~  820 ops/s (Kotlin) vs ~27,000 ops/s (native) = ~33x
  pubkeyCreate     ~3,130 ops/s (Kotlin) vs ~55,000 ops/s (native) = ~18x
  pubKeyTweakMul   ~2,740 ops/s (Kotlin) vs ~29,000 ops/s (native) = ~11x
  privKeyTweakAdd  ~1.66M ops/s (Kotlin) vs ~1.46M ops/s (native)  = 0.9x (faster!)
  secKeyVerify     ~1.98M ops/s (Kotlin) vs ~3.88M ops/s (native)  = 2x

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude 4d55dfcff5 perf: optimize secp256k1 for minimum allocations and maximum verify throughput
Key optimizations:

1. Mutable field operations: All FieldP hot-path methods (add, sub, mul, sqr)
   now write into caller-provided output arrays instead of allocating new ones.
   Thread-local IntArray(16) scratch for mulWide avoids per-mul allocation.

2. Mutable point operations: MutablePoint replaces immutable JPoint. Point
   doubling/addition write into output points. Aliasing protection via
   thread-local copy buffer for in-place doublePoint(out, out).

3. 4-bit windowed scalar multiplication: Processes 4 bits per iteration
   (16 table entries) instead of 1 bit. Reduces point additions by ~4x.

4. Precomputed G table: Static lazy table of 16*G multiples. Generator
   multiplication (signing, key creation) uses precomputed table directly.

5. Shamir's trick (mulDoubleG): Computes s*G + e*P in a single pass for
   verification, eliminating the need for two separate scalar multiplications.
   This roughly halves the cost of verifySchnorr.

6. Cached BIP-340 tag hashes: SHA256("BIP0340/challenge") etc. computed
   once and reused, eliminating 2 SHA256 calls per verify.

7. toBytesInto: Writes directly into existing ByteArray at offset,
   avoiding intermediate allocations in serialization.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Claude 4c3b31fe8e feat: replace fr.acinq.secp256k1 with pure-Kotlin secp256k1 implementation
Implements all secp256k1 operations used by Secp256k1Instance in pure Kotlin,
eliminating the dependency on native secp256k1 bindings (fr.acinq.secp256k1-kmp).

Implementation:
- Field.kt: 256-bit unsigned integer arithmetic, field mod p, scalar mod n
- Point.kt: EC point operations (Jacobian coords), point parsing/serialization
- Secp256k1.kt: Public API - pubkeyCreate, pubKeyCompress, secKeyVerify,
  signSchnorr/verifySchnorr (BIP-340), privKeyTweakAdd, pubKeyTweakMul

Tests (36 total):
- All 19 BIP-340 test vectors (signing vectors 0-3, 15-18; verify vectors 4-14)
- ACINQ test vectors for key creation, compression, secKeyVerify, privKeyTweakAdd,
  pubKeyTweakMul
- ECDH symmetry and sign/verify round-trip tests

Secp256k1Instance is now a concrete object in commonMain instead of expect/actual,
delegating to the pure-Kotlin implementation. All existing NIP-44 and BIP-32 tests
pass with the new implementation.

https://claude.ai/code/session_01BhU63WUe9AhikZxRdw3Lpg
2026-04-05 20:32:54 +00:00
Vitor Pamplona e1304ddcab Merge pull request #2147 from vitorpamplona/claude/marmot-protocol-integration-xb3TT
Add Marmot MLS group messaging support to Amethyst
2026-04-05 16:06:12 -04:00
Claude 8b56b3b3bf feat: Phase 6 — Marmot account integration & event routing
Wire Marmot MLS group messaging into the app's Account lifecycle,
event processing pipeline, and relay subscription system.

New files:
- AndroidMlsGroupStateStore: encrypted file-based MLS state storage
  using KeyStoreEncryption (AES/GCM backed by Android KeyStore)
- MarmotManager: central coordinator holding all Marmot components
  (MlsGroupManager, KeyPackageRotationManager, subscription/inbound/
  outbound processors, WelcomeSender)
- MarmotGroupEventsEoseManager: relay subscription manager for
  kind:445 GroupEvent filters via AccountFilterAssembler

Account integration:
- MarmotManager initialized during Account startup with restoreAll()
- Outbound methods: sendMarmotGroupMessage, addMarmotGroupMember,
  publishMarmotKeyPackage(s), createMarmotGroup
- GroupEvent (kind:445) and KeyPackageEvent (kind:30443) added to
  LocalCache.justConsumeInnerInner() dispatch

Event routing:
- GroupEventHandler: processes inbound kind:445 events through
  MarmotInboundProcessor, indexes decrypted inner events in LocalCache
- GiftWrapEventHandler extended: detects kind:444 WelcomeEvent after
  NIP-59 unwrap, routes to MarmotManager.processWelcome(), triggers
  KeyPackage rotation when needed
- WelcomeEvent enhanced with optional "h" tag for group ID routing

https://claude.ai/code/session_01W2LHazEt4E3W4hn8f7gWVW
2026-04-05 19:54:56 +00:00
Vitor Pamplona 3b695dc8b8 Merge pull request #2145 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 15:19:18 -04:00
Crowdin Bot a437d169c0 New Crowdin translations by GitHub Action 2026-04-05 18:48:38 +00:00
Vitor Pamplona 4e56f68a8f Merge pull request #2146 from vitorpamplona/claude/relay-subscriptions-messaging-VOv5P
Add Marmot inbound/outbound message processors and subscription manager
2026-04-05 14:47:19 -04:00
David Kaspar e586f4b179 Merge pull request #2144 from vitorpamplona/claude/find-missing-translations-HUpyP
feat: add missing translations for 5 keys across 4 locales
2026-04-05 19:39:05 +02:00
Claude ee8bc2bf62 feat: add missing translations for 5 keys across 4 locales
Add translations for call_bluetooth, closed_polls, feed_group_locations,
open_polls, and poll_view_results to cs-rCZ, pt-rBR, sv-rSE, and de-rDE.

https://claude.ai/code/session_01CMo4zT5ZKRt3J1yHgm23Aq
2026-04-05 17:36:48 +00:00
Claude ae0a0de9fd feat: Phase 5 — Marmot relay subscriptions & message processing pipeline
Add four protocol-layer components for Marmot group messaging:

1. MarmotSubscriptionManager: Coordinates relay subscriptions for
   GroupEvent (kind:445), GiftWrap (kind:1059), and KeyPackage
   (kind:30443) events. Tracks per-group since timestamps for
   pagination and syncs with MlsGroupManager state.

2. MarmotInboundProcessor: Processes incoming GroupEvents through
   outer ChaCha20-Poly1305 decryption → MLS decrypt → inner event
   extraction. Handles commit detection, conflict resolution via
   CommitOrdering, and Welcome processing with KeyPackage rotation.

3. MarmotOutboundProcessor: Builds outbound GroupEvents by MLS
   encrypting inner Nostr events, applying ChaCha20-Poly1305 outer
   layer, and signing with ephemeral keys for sender privacy.

4. MarmotWelcomeSender: Wraps MLS Welcome messages through the
   NIP-59 gift wrap pipeline for delivery to new group members.

All code in quartz/commonMain (protocol layer). Includes 25 tests
covering roundtrip encryption, subscription management, error
handling, ephemeral key usage, and Welcome wrapping.

https://claude.ai/code/session_01XC5umkmsFB7XQ7xdrouArt
2026-04-05 17:11:23 +00:00
Vitor Pamplona 9a7d7578ff Merge branch 'main' of https://github.com/vitorpamplona/amethyst 2026-04-05 12:59:05 -04:00
Vitor Pamplona 67184fb72e Add a filter group for locations and moves global and around me to their correct sections 2026-04-05 12:50:52 -04:00
Vitor Pamplona 46f1ff7bd7 Merge pull request #2142 from vitorpamplona/l10n_crowdin_translations
New Crowdin Translations
2026-04-05 12:37:21 -04:00
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
Crowdin Bot 5648671906 New Crowdin translations by GitHub Action 2026-04-05 16:26:31 +00: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