- Keep max lastSeen per user so relay's newest-first delivery doesn't
overwrite a fresh timestamp with a stale one
- Serialize event processing on Main dispatcher to eliminate
ConcurrentModificationException on geohashParticipants maps
- Immediately refresh people list on channel switch so sampling data
is reflected before the first relay response arrives
This change prevents the app's content from being visible in the recents screen on Android 13 (Tiramisu) and newer devices.
It achieves this by calling `setRecentsScreenshotEnabled(false)` in the `onCreate` method of `MainActivity` after checking the device's SDK version. This enhances user privacy by hiding potentially sensitive information from the system's app overview.
When a URL contains a # fragment (e.g. https://example.com/page#section),
the hashtag regex matches the fragment as a hashtag. The overlap removal
logic already removes hashtags overlapping geohashes, and geohashes
overlapping URLs, but did not remove hashtags overlapping URLs. Add the
missing overlapsUrl check to the hashtag condition.
* Pause Nostr geohash firehose in background to cut mobile data
The Bluetooth mesh uses no mobile data, but the Nostr relay layer ran
unbounded in the background: the live geohash channel subscription kept
streaming across 5 relays, the presence heartbeat broadcast every ~60s,
and a participant-refresh timer polled every 30s. Combined with
reconnect/re-subscribe replay on public relays, this could burn gigabytes
while idle.
GeohashViewModel now tears down the high-volume subscriptions when the app
is backgrounded (onStop) and restores them on foreground (onStart):
- drop the live channel message stream (currentGeohashSubId)
- drop sampling subscriptions
- cancel the presence heartbeat and participant-refresh timer
Gift-wrap DM subscriptions are intentionally kept alive in the background
since they are filtered to our pubkey (lightweight) so DMs still arrive.
The selected channel is tracked in activeChannelGeohash so the stream can
be rebuilt on foreground without losing the user's selection. The channel
subscription logic is extracted into subscribeChannelStream() /
subscribeChannelDM() helpers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* separate the heartbeat firehose from message delivery
* correct comment
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
* wifi aware wip
* wifi aware wip
* starting to work
* werk
* dms work
* wip
* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork
* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding
* Fix Wi-Fi Aware connectivity and UI integration post-merge
- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.
* Enhance Wi-Fi Aware robustness and debug UI display
- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.
* Share GossipSyncManager across transports to prevent redundant message synchronization
- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.
* Restore VPN acquisition logic and improve peer cleanup
- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.
* Fix wifi aware socket binding 2 (#536)
* Background persistence (#505)
* persistence step 1
* fix build
* messages in the background work, notifications not yet
* app state store
* DM icon shows up
* notification launches when app is closed!
* keep ui updated
* lifecycle fixes
* extensive logging, maybe revert later
* send nickname in announcement
* quit in notification
* setting in about sheet
* fix quit bitchat
* lifecycle fixes
* power mode based on background state
* stats for both direciotns
* fix graph persistence
* better counting
* count per device
* only compute when debug sheet is open? untested
* fix read receipts
* fix read receipts fully
* fix unread badge if messages have been read in focus
* foreground promotion fix
* fix app kill in notification
* adjust to new tor
* nice
* about sheet design
* bump version 1.6.0 (#524)
* Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025
* bump targetSdk (#526)
* Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025
* Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025
* Prevent quit notification from reappearing (#530)
* shutdown sequence
* Prevent quit notification from reappearing
* Restrict force-finish broadcast
* Cancel quit shutdown on relaunch
* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork
* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding
* Fix Wi-Fi Aware connectivity and UI integration post-merge
- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.
* Enhance Wi-Fi Aware robustness and debug UI display
- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.
* Share GossipSyncManager across transports to prevent redundant message synchronization
- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.
* Restore VPN acquisition logic and improve peer cleanup
- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalue <>
* Fix wifi aware routing (#534)
* Background persistence (#505)
* persistence step 1
* fix build
* messages in the background work, notifications not yet
* app state store
* DM icon shows up
* notification launches when app is closed!
* keep ui updated
* lifecycle fixes
* extensive logging, maybe revert later
* send nickname in announcement
* quit in notification
* setting in about sheet
* fix quit bitchat
* lifecycle fixes
* power mode based on background state
* stats for both direciotns
* fix graph persistence
* better counting
* count per device
* only compute when debug sheet is open? untested
* fix read receipts
* fix read receipts fully
* fix unread badge if messages have been read in focus
* foreground promotion fix
* fix app kill in notification
* adjust to new tor
* nice
* about sheet design
* bump version 1.6.0 (#524)
* Automated update of relay data - Sun Dec 14 06:06:53 UTC 2025
* bump targetSdk (#526)
* Automated update of relay data - Sun Dec 21 06:06:56 UTC 2025
* Automated update of relay data - Sun Dec 28 06:07:12 UTC 2025
* Prevent quit notification from reappearing (#530)
* shutdown sequence
* Prevent quit notification from reappearing
* Restrict force-finish broadcast
* Cancel quit shutdown on relaunch
* fix(wifi-aware): use bindSocket and scoped IPv6 instead of bindProcessToNetwork
* Merge branch 'upstream/main' into fix/wifi-aware-socket-binding
* Fix Wi-Fi Aware connectivity and UI integration post-merge
- Replace bindProcessToNetwork with bindSocket for VPN compatibility.
- Implement Scoped IPv6 address resolution (aware0) for mesh routing.
- Bridge Wi-Fi Aware incoming messages to AppStateStore for UI visibility.
- Fix syntax errors and variable name conflicts in Debug UI.
* Enhance Wi-Fi Aware robustness and debug UI display
- Clean up transport resources (sockets, server sockets, network callbacks) immediately on peer disconnection.
- Implement resolveScopedAddress to show scoped IPv6 (e.g., %aware0) in Debug UI.
- Fix Map type mismatch warning in ChatViewModel bridge.
- Filter self-ID from peer cleanup tables to prevent recursive self-removal.
* Share GossipSyncManager across transports to prevent redundant message synchronization
- Registered BluetoothMeshService's GossipSyncManager as a singleton in MeshServiceHolder.
- Modified WifiAwareMeshService to use the shared GossipSyncManager if available.
- Added background cleanup for peer mappings on socket disconnection.
- Fixed Kotlin type mismatch during nickname map merging.
* Restore VPN acquisition logic and improve peer cleanup
- Revert removal of NET_CAPABILITY_NOT_VPN to allow hardware handle acquisition while VPN is active.
- Refactor handlePeerDisconnection to more reliably cleanup initial and routed IDs.
- Switch cleanup logging to debug level to reduce log noise.
* fix wifi aware routing
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalue <>
* unify connection tracker for ble and wifi (#537)
* unify connection tracker for ble and wifi
* cleanup nicely
* Wifi aware skip bluetooth (#538)
* unify connection tracker for ble and wifi
* bluetooth optional
* refactor mesh core
* tests
* share gossip
* refresh peer list more often
* patches: wifi aware mesh refactor core (#549)
* Refactor WifiAware memory management and lifecycle handling
* Fix Wi-Fi Aware reconnection by ensuring callback unregistration and session watchdog
* fix(wifiaware): allow WifiAwareController to restart service if session drops
* Enhance WiFi Aware logging with network request timeouts and detailed callback status
* Ensure explicit release of WiFi Aware network callbacks on failure or disconnection
---------
Co-authored-by: aidenvalue <>
* fix: wifi aware mesh private dms (#561)
* fix(wifi-aware): restore peer check and remove aggressive session restarts
* synchronized access to sockets.
* clear old peer socket `onClientConnected` if one was found.
* address second round of review
* fix: Restart publish session on termination in WifiAwareMeshService
* fix: cleanup connection tracker resources when handling peer disconnection without active socket
---------
Co-authored-by: aidenvalu3 <>
* fix(mesh): address memory leaks and background persistence issues in Wi-Fi Aware
- Move message persistence and background notifications for Wi-Fi Aware into the service layer via MeshCore hooks.
- Fix memory leak in MainActivity by properly detaching WifiAwareMeshDelegate on pause.
- Ensure BluetoothMeshService is initialized before WifiAwareMeshService to guarantee shared GossipSyncManager instance.
- Deduplicate persistence logic from MainActivity delegate.
* wifi aware working
* more robust
* fragments work
* new files
* wifi improvements
* handshake works
* improve
* wifi mesh more robust
* fix review
* check for support
* wifi aware fixes
---------
Co-authored-by: aidenvalue <>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: aidenvalu3 <erendentman@gmail.com>
Co-authored-by: a1denvalu3 <43107113+a1denvalu3@users.noreply.github.com>
Co-authored-by: CC <cc@ggg.local>
* fix(sync): resolve hash alignment and early break bugs on GCS filter trimming
* fix(sync): filter out zero bucket and deduplicate buckets before encoding
* fix(sync): preserve zero-bucket mapping by clamping 0 to 1 during encoding and decoding
Missed by the prior idiomatic-polish pass: SymmetricState had 13 Log.d
calls still using "..." + bytesToHex(...) instead of string templates.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address IDE/compiler warnings across the protocol/ package after the
Java to Kotlin migration:
- Replace reference-identity ByteArray checks `!=` with `!==` in
ChaChaPoly/AESGCM*CipherState (in-place encrypt/decrypt guards) to
silence the "dangerous array comparison" warning while preserving
the original semantics.
- Drop `java.util.Arrays.fill` and `Arrays`-style helpers in favour of
ByteArray.fill / copyInto / copyOfRange where appropriate. Cold paths
in NewHopeDHState, Curve25519DHState, Curve448DHState and parts of
SymmetricState now use Kotlin extensions; tight crypto inner loops
(HMAC body, ChaChaPoly) keep System.arraycopy intentionally.
- Use property access (`digestLength`) instead of `getDigestLength()`.
- Fold redundant if/else into `if-expression`, drop redundant
`val space: Int` + assignment, redundant `Companion.` qualifiers,
and unused `val cipher` re-assign in fork().
- Rename `prev_h` to `prevH` (style).
- Replace string concatenation with templates in log statements and
exception messages.
- Convert unused catch parameters `e` to `_` and remove stale local
`var index: Int` in hmac().
- Strip stale `@param secondaryKey/offset/length` KDoc block above
SymmetricState.split (the params moved to the merged doc).
- Drop dead `check(!(local == null || remote == null))` from
HandshakeState.mixDH — Kotlin types are already non-null.
Crypto behaviour is unchanged; output of every method matches the
prior Kotlin/Java implementation byte-for-byte.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
External callers must use the property form for HandshakeState
and CipherStatePair members that the protocol package now exposes
as `val`s:
- handshakeState.getLocalKeyPair() -> handshakeState.localKeyPair
- handshakeState.getAction() -> handshakeState.action
- handshakeState.getRemotePublicKey() -> handshakeState.remotePublicKey
- handshakeState.getHandshakeHash() -> handshakeState.handshakeHash
- cipherPair.getSender() / getReceiver() -> .sender / .receiver
DH operations with arguments (`setPrivateKey`, `getPublicKey`, etc.)
remain regular method calls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Use Kotlin properties (`protocolName`, `handshakeHash`) and update
every getter call (`getDHName/getPublicKeyLength/getMACLength/
getSharedKeyLength/getProtocolName/getHandshakeHash`) to the new
property names exposed by the migrated DHState / SymmetricState /
CipherState interfaces.
- Initialize `pattern` non-null via `requireNotNull(lookup(...))`,
and give the other late-init `var` fields (`patternIndex`,
`preSharedKey`, `prologue`) sensible defaults so the class
compiles cleanly.
- Resolve smart-cast errors on mutable nullable fields (`prologue`,
`fixedEphemeral`, `fixedHybrid`, `localHybrid`) by copying to
local `val`s before use.
- Replace `preSharedKey != null` / `mixPreSharedKey(preSharedKey)`
guards with `?.let` blocks, and pass `localKeyPair!!` etc. into
`mixPublicKey` to satisfy its now-non-null parameter.
- Tighten the cipher/hash name locals in the protocol-name parser
from `String?` to `String` so the SymmetricState constructor
accepts them.
- Token-processing loop, fallback bookkeeping and message buffer
layout are unchanged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Expose `protocolName` as a non-null Kotlin property and rename the
IDE's `mACLength` accessor to `macLength` (delegates to
`cipher.macLength`).
- Update internal `dh.getPublicKeyLength()` calls to use the new
property form.
- Rewrite the HMAC inner/outer-pad XOR loops as `for`-in-range with
an explicit `.toByte()` on the result, satisfying Kotlin's
stricter Int-vs-Byte rules.
- Keep `split()`'s `secondaryKey` parameter nullable to match the
original Java contract; an empty buffer is substituted when null
so the HKDF helper signature can stay non-null.
- HKDF/HMAC/handshake-hash logic is otherwise unchanged, including
the iOS-parity debug logging.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Override CipherState properties; initialize `val` fields inline.
- Rewrite the big-endian 4-byte CTR-counter increment at iv[12..15]
as an explicit `(iv[i] + 1).toByte()` chain — Kotlin rejects the
`++(iv[15])` form on Byte array elements.
- Replace `Arrays.fill(arr, 0.toByte())` with `arr.fill(0)` and fix
the GHASH-tag XOR loop to cast back to Byte explicitly.
- Rijndael CTR keystream and GHASH paths preserved byte-for-byte.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Override CipherState properties; tighten `initializeKey` parameter
type to non-null `ByteArray`.
- Promote the constructor to a `@Throws(NoSuchAlgorithmException)`
primary constructor with the `Cipher` instance assigned once as a
non-null `val`, removing the bogus `cipher: Cipher? = null`
initializer the IDE emitted.
- Replace `Arrays.fill(arr, 0.toByte())` with `arr.fill(0)`.
- Inline-byte XOR loop in `encryptWithAd` now casts back to `Byte`
explicitly to satisfy Kotlin's stricter type rules.
- `fork()` rethrows the (impossible) NoSuchAlgorithmException as
IllegalStateException instead of returning null — matches the
non-null contract on `CipherState.fork`.
- AES/CTR keystream and GHASH paths preserved byte-for-byte.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Override CipherState properties (`cipherName`, `keyLength`,
`macLength`) instead of Java-style getters.
- Initialize `val` fields inline and drop the now-empty constructor
`init` block.
- Replace `Arrays.fill(arr, 0.toByte())` with `arr.fill(0)` and
`Arrays.fill(intArr, 0)` with `intArr.fill(0)`.
- Tighten counter increments (`++input[12]`) — Kotlin no longer needs
the parenthesized form the IDE emitted.
- Crypto path (ChaCha20 keystream, Poly1305 MAC, AD padding) is
unchanged byte-for-byte.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Override DHState/DHStateHybrid properties idiomatically; collapse
branchy length getters via single-expression `if`.
- Replace `Arrays.fill` and `arraycopy` of in-memory key buffers
with `ByteArray.copyOf()` where the intent is "duplicate buffer".
- Use safe `?.let` / local `val` patterns for the nullable `nh`,
`publicKey`, `privateKey` fields to satisfy Kotlin smart-cast rules.
- Restructure the if/else-if chains in `calculate` and `copyFrom`
as `when` blocks for readability.
- Crypto parity preserved: `shareda` and the BobCalculated arraycopy
intentionally use offset=0 to match the original Java, which never
honored the caller's `offset` parameter; documented in a comment.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Override DHState properties instead of Java-style getters.
- Use `ByteArray.fill(0)` in place of `Arrays.fill(arr, 0.toByte())`.
- Preserve bit-identical crypto behavior (56-byte key sizes,
Curve448.eval call signature, constant-time null-key scan).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Match the tightened `copyFrom` signature on DHState by making the
primary `other` parameter non-null while keeping `remote` nullable
(a missing peer is meaningful for hybrid algorithms).
- Note the inheritance relationship between the single- and two-arg
copyFrom overloads.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Convert getDHName/getPublicKeyLength/getPrivateKeyLength/getSharedKeyLength
from Java getters into idiomatic Kotlin `val` properties.
- Tighten nullability: key buffers and DH peer parameters are non-null
per the original Java @param contract; isNullPublicKey kept as a
function so existing call sites (`isNullPublicKey()`) keep resolving.
- Trim redundant KDoc; preserve upstream license header.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Remove `@get:JvmName("getMACLength")` — Kotlin rejects @JvmName on
interface getters and all remaining callers in this module are Kotlin.
- macLength is now exposed idiomatically as a Kotlin property.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Migrate `Noise.java` to `Noise.kt`.
- Refactor factory methods using Kotlin `when` expressions and destructuring.
- Use `ByteArray.fill()` and `copyOfRange()` for internal utility functions.
- Update reflection logic in `throwBadTagException` for better readability.
- Apply `@JvmStatic` and `const` to maintain compatibility with existing Java callers.
- Ported the NewHope and NewHopeTor key exchange algorithms from Java to idiomatic Kotlin.
- Maintained strict functional parity with the original verified Java implementation.
- Preserved all necessary cryptographic lookup tables (NTT Montgomery, bit-reversal) to ensure mathematical correctness.
- Converted Keccak-f[1600] and ChaCha20 core algorithms into more compact Kotlin structures while retaining constant-time execution safety.
- Ensured seamless Java interoperability by using `const val` for protocol constants like `SENDABYTES` and `SENDBBYTES`.
- Improved overall code readability and removed legacy/redundant constructs.