Adds the Android product alongside the existing Linux TUI, in one repo.
SDK — crates/onionwire-sdk is a UniFFI facade over the very same `onionwire`
crate the TUI runs on. Identity, invite, friend upsert, send/receive, rotate
and wipe all delegate; no protocol is reimplemented, so an Android peer and a
Linux peer interoperate. It is its own Cargo workspace because Arti's TLS
backends are non-additive: the TUI keeps native-tls (OpenSSL), Android needs
rustls + static-sqlite (no OpenSSL, no system libsqlite3 in the NDK).
android/ — Gradle project. :sdk produces the AAR (Kotlin bindings generated at
build time + libonionwire_sdk.so via cargo-ndk), :app is a Kotlin/Compose/M3
messenger depending on :sdk only. minSdk 26, targetSdk/compileSdk 36,
INTERNET-only, data in filesDir, backups excluded.
Root Cargo.toml grows `native-tls` (default) and `rustls` features so exactly
one Arti TLS backend is selected per build graph. The default build is
unchanged: same backend, ratatui still a normal dependency, src/tui.rs
untouched.
Also: Store::self_fingerprint/set_petname + Node wrappers (additive only),
scripts/build-android-local.sh, README sections, .gitignore for local SDK paths.
Wrap a random 32-byte message key with Argon2id+ChaCha20-Poly1305.
sqlite messages.plaintext is nonce||ciphertext; identity keys stay
plaintext. Empty/wrong passphrase fails closed. Ctrl-Q types CLEAR or
QUIT then Enter (Esc cancels).
Arti combined status stays Bootstrapping through a 5 min HsDir upload
round. Treat a successful connect as published, floor cbtinitialtimeout
with cbtmintimeout at 20s, and skip preemptive 80/443 circuits so IPT
and HsDir builds are not starved. PUBLISH_WAIT stays 360s fail-closed.
Signed inv/rcp codecs keyed by identity, payments table with unverified
incoming receipts, optional JSON-RPC sidecar (loopback/.onion HTTP only),
and TUI /pay /tip. OnionWire still holds no spend keys.
prf frames are ed25519-signed, friend-visible only (unknown pubkey is ignored).
/profile edits self; F5 and /who show the selected friend's last profile.
/wipe overwrites the message log and VACUUMs; identity and friends stay.
/wipe-all deletes the data dir after confirm so the next open is a new person.
Neither runs without a typed confirm screen.