#530 derived the Nostr private key as SHA-256(sub || index). The Google
sub claim is not a secret — any app the user signs into with Google
gets the same sub value for that account/OAuth-client pair, and even
across clients it leaks via id_token introspection. That meant any
third party with the sub could regenerate the user's nsec offline. A
non-starter.
Reverts to the #528 design:
- GoogleSignInManager re-acquires the drive.appdata OAuth scope via
AuthorizationClient
- DriveBackupService reads/writes wisp_nsec_<npub>.bin blobs in the
per-app appDataFolder (other apps cannot see this folder)
- BackupCrypto already provided HMAC-SHA256(sub)-derived key + NIP-44
encryption; that file was not touched by #530 and is reused as-is
- GoogleAuthViewModel lists backups, surfaces a chooser with avatar +
display name fetched from kind-0 events, and decrypts only on
explicit Restore
- "Create new account" generates a fresh keypair, encrypts, and
uploads a new blob
The encryption key is still derived from sub, but the attack surface
is fundamentally different: an attacker now needs both the sub AND
read access to Wisp's appDataFolder in the user's Drive. The folder
is sandboxed per-app, so the only paths in are the user's own Google
account or a Wisp app compromise. That's the same trust boundary as
"can sign in to the user's Google account."
Splash button styling kept as-is (dark variant from #530 retained — a
cosmetic choice independent of the security model).
Reverts: 3d3c6e1, de88bdd
Replaces the Google Drive backup flow (#528) with deterministic key
derivation. The user's Nostr identity IS their Google account — no
encrypted blobs to store, no backup events to publish, nothing for
Google or any third party to retain.
privkey = SHA-256("wisp-account-v1:" || sub || ":" || accountIndex)
Properties:
- Same Google account always derives the same nsec on any device
- No backup to lose: signing in regenerates the keys
- No `drive.appdata` OAuth scope, no scary Drive consent dialog
- Anyone with access to the Google account can derive every nsec.
Bounded by Google account security — same trade-off as #528, with
a much simpler attack surface and no third-party storage layer
Discovery on sign-in:
- Derive candidate keypairs for indices 0..15 from the user's `sub`
- One REQ to relay.damus.io, relay.primal.net, nos.lol, nostr.wine,
relay.wisp.talk, relay.ditto.pub asking for kind 0/3/10002 events
from those pubkeys
- Pubkeys with any activity = "in use" accounts that go in the chooser;
avatar + display name come from the same kind-0 events
- "Create another account" derives the next-unused index
Code shrinkage: DriveBackupService is gone, BackupCrypto's encryption
helpers are gone, the play-services-auth dependency is gone, and the
Drive-related ProGuard rules are gone. The whole flow is ~200 fewer
lines than #528 and easier to audit — the derivation is one line of
SHA-256.
Splash button switches to Google's dark-mode brand variant (#131314
container, full-color G, #8E918F stroke) per Sign in with Google spec.
No migration needed: nobody is on the #528 flow yet.
Adds a third splash entry point alongside Sign Up / Log In:
- Credential Manager + Google Identity for sign-in; AuthorizationClient
scopes drive.appdata so Wisp can read/write a hidden per-app folder
- Per-account backup files named wisp_nsec_<npub>.bin in appDataFolder.
One Google account can back up many Nostr identities; sign-in lists
every backup found and the chooser shows each with avatar + display
name fetched from relay.damus.io / relay.primal.net (kind 0)
- Encryption key derived via HMAC-SHA256 from the Google sub claim,
reusing Nip44's XChaCha20 + HMAC building blocks for the payload.
Plaintext nsec never leaves the device
- "Create another account" is always available, even when prior backups
exist, so users can keep adding identities without overwriting
- Google-branded splash button (white, full-color G logo) per Sign in
with Google brand spec
Drops the embedded kmp-tor client, all .onion relay support, the Tor
toggle UI on splash/auth/drawer screens, the SOCKS proxy plumbing in
HttpClientFactory, all Tor-related strings across 11 locales, the
TorManager singleton, and the kmp-tor dependencies and ProGuard rules.
Relay URL validation now accepts only wss:// (with hostname, no port,
non-localhost, non-IP). The cleartextTrafficPermitted network security
flag stays — still needed for local relays on RFC1918 / loopback.
Disable legacy jniLibs packaging so native libraries are stored
uncompressed and page-aligned in the APK. Bump secp256k1-kmp to 0.19.0
and kmp-tor-resource to 408.16.4 since the prior versions shipped
ELF binaries with 4 KB LOAD alignment. All 13 .so files in the release
APK now report 2**14 alignment.
Prevent the device from sleeping during fullscreen video playback with
FLAG_KEEP_SCREEN_ON. Add a MediaSessionService so Android shows media
controls in the notification shade and lock screen when the app is
backgrounded, covering both fullscreen and PiP video modes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add CameraX + ML Kit barcode scanning to send flow (scan QR / import from gallery)
- Show fee for outgoing transactions in history
- Associate zap counterparties (sender/recipient) with transactions using:
- Persisted ZapSender map (SharedPreferences, survives restarts)
- ObjectBox zap receipt queries (kind 9735)
- One-shot relay fetch for user's incoming zap receipts (#p filter)
- Show counterparty avatar and display name in transaction rows
- Enable Spark SDK LNURL verify support (NIP-57 zap receipts)
- Sync wallet before listing transactions for freshest data
- Cache zap receipt counterparty maps to avoid repeated bolt11 parsing
- Move heavy transaction processing to Dispatchers.IO
Integrate Breez SDK Spark as a second wallet backend alongside NWC.
Users can create/restore a non-custodial Lightning wallet directly
in the app with seamless onboarding (no forced backup on first use).
- WalletProvider interface abstracts NWC and Spark backends
- WalletModeRepository persists active wallet mode
- SparkRepository wraps Breez SDK: connect, send, receive, transactions
- Wallet mode selection UI with back navigation
- Receive flow auto-detects payment via sync polling + SDK events
- Payment received success screen with auto-navigation
- Dynamic wallet provider resolution for zaps (no stale captures)
- Proper SDK lifecycle: disconnect on app exit, cleanup on teardown
- 60s connection timeout for Spark (vs 20s for NWC)
- Wallet-connected indicator works for both providers
- BIP39 mnemonic generation with bundled English wordlist
- ProGuard rules for UniFFI/JNA bindings
ExoPlayer was using its default HTTP stack, bypassing the SOCKS proxy
entirely and leaking the user's IP to video servers even with Tor active.
Added media3-datasource-okhttp to pipe video traffic through the same
Tor-aware OkHttpClient used by image loading and relay connections.
Add ObjectBox as a write-behind persistence layer for Nostr events.
Events flowing through the relay pipeline are asynchronously batched
and written to ObjectBox on Dispatchers.IO. The LRU cache remains the
hot read path — no startup seeding or feed changes in this PR.
What's persisted: user's own events (all kinds), notes (kind 1),
profiles (kind 0), reposts (kind 6), reactions (kind 7), and zap
receipts (kind 9735).
Search now merges LRU cache results with ObjectBox for cross-session
note search. Old events are pruned on startup (50k cap, 90-day TTL,
user's own events exempt).
Add a Media tab to user profile screens showing an Instagram-style grid
of images and videos extracted from the user's notes. Videos display
actual frame thumbnails via Coil's VideoFrameDecoder with a play icon
overlay. The grid supports infinite scroll.
Also adds ICQ-style flower burst animation for reply notifications,
restores the shared OkHttpClient for image loading (fixes GC thrashing
from per-request client creation), and separates reply vs generic
notification sounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add profileinstaller library so baseline profiles are applied on
sideloaded APKs (not just Play Store installs). Add app-specific
baseline-prof.txt covering startup-critical paths. Enable resource
shrinking for release builds.
Use Google ML Kit for on-device translation with no API keys required.
Adds a "Translate" option to the post overflow menu that detects the
source language, downloads the model if needed, and displays the
translated text inline below the original content.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Route all network traffic through Tor when enabled — relay WebSockets,
NIP-05, NIP-11, image loading, media uploads, and zaps. Uses kmp-tor
to embed the Tor runtime with automatic SOCKS5 port discovery.
- Add TorManager singleton for Tor lifecycle management
- Add HttpClientFactory to centralize OkHttpClient creation with
Tor proxy and DNS leak prevention (custom Dns resolver forces
hostname resolution through SOCKS5 tunnel)
- Migrate all OkHttpClient creation sites to use HttpClientFactory
- Add Tor-aware Coil image loading via Call.Factory
- Support .onion relay addresses when Tor is active
- Add onion icon toggle in navigation drawer with status indication
- Persist Tor enabled state across app restarts
- RelayPool hot-swaps clients on Tor state changes
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>