# onionwire-sdk (Android) Kotlin-facing UniFFI bindings over the same Rust crate the OnionWire Linux TUI runs on. An Android peer and a Linux peer that exchange invites interoperate — there is one protocol, not two. This is **not** a WebView wrapper and **not** a client for a hosted service. Each install runs Arti in-process (`arti-client` 0.46, `onion-service-client` + `onion-service-service`), hosts its own v3 onion service, and dials friends' onions directly. There is no `tor` binary, no torrc, no Orbot as the pipe. ## Locked protocol facts | Piece | Rule | |---|---| | Identity | ed25519 keypair. The pubkey **is** who you are. | | Roster key | `friends.pubkey`, `UNIQUE`. Petnames are local only. | | Onion | A **locator**, not an identity. It rotates. | | Invite | `onionwire:v1:k=…:o=…:spk=…:sig=…` | | Crypto | Noise IK (`Noise_IK_25519_ChaChaPoly_BLAKE2s`). `spk` is x25519. | | Fail closed | Peer onion down → the send fails. No outbox, no spool in v1. | | At rest | Chat bodies encrypted (Argon2id wrap + ChaCha20-Poly1305). Identity keys in sqlite are plaintext — that is honest, not an oversight. | ### Invite string ``` onionwire:v1:k=:o=:spk=:sig= ``` * `sig` is ed25519 over `onionwire-invite-v1` + `0x00`-separated `k`, `o`, `spk` (hex/onion as they appear in the string). Decode still accepts the legacy raw concatenation so old invites verify. See [docs/PROTOCOL.md](../../docs/PROTOCOL.md). * A malformed, unsigned, or tampered invite is rejected before anything is stored. * **Same `k` never creates a second friend.** It updates the stored locator on the existing row. That is the whole point of keying the roster on the pubkey. * The invite carries no display name and no Monero address — those arrive later over the wire as signed `prf` frames, not through the invite. ## Adding the AAR to another Android app The SDK ships as `onionwire-sdk-.aar` on the Forgejo release, with a matching `.sha256`. ```kotlin // settings.gradle.kts dependencyResolutionManagement { repositories { google() mavenCentral() flatDir { dirs("libs") } // or however you vendor the AAR } } // app/build.gradle.kts dependencies { implementation(files("libs/onionwire-sdk-0.1.0.aar")) } ``` Requirements: * `minSdk >= 26`. The AAR is built with the NDK against `android-26`. * `abiFilters` must include an ABI the AAR ships. The release AAR includes `arm64-v8a`. Add `x86_64` at build time with `-Ponionwire.abis=arm64-v8a,x86_64` if you need an emulator. * Keep `uniffi.onionwire_sdk.**` (see the AAR's `consumer-rules.pro`, applied automatically by AGP) if you enable R8. ## Kotlin surface Everything is a `suspend fun` — Arti bootstrap and onion publish take minutes, and a send retries the peer for up to 3 minutes before failing. ```kotlin import uniffi.onionwire_sdk.* // 1. Open (or create) the identity inside app-private storage. // `filesDir`, never external storage. Empty passphrase fails closed. val wire: Wire = openWire(File(context.filesDir, "onionwire").absolutePath, passphrase) // 2. Who you are, and where you are right now. val fingerprint: String = wire.fingerprint() // stable forever val onion: String = wire.onion() // changes on rotate // 3. Invite out / invite in. val mine: String = wire.invite() val preview: InviteInfo = decodeInvite(pasted) // verifies sig, stores nothing val friend: FriendInfo = wire.addFriend(pasted) // 4. Roster and chat. val roster: List = wire.friends() wire.setPetname(friend.pubkeyHex, "ada") wire.send(friend.pubkeyHex, "hello wire") val msgs: List = wire.messages(friend.pubkeyHex) // 5. Locator rotation. Do NOT wire this to a single tap — the app that embeds // the SDK owns the typed confirmation. val out: RotateOutcome = wire.rotateOnion() // out.notified / out.friends ``` All peer references are lowercase hex strings so nothing Rust-shaped leaks into the AAR. ## Building the bindings yourself The Gradle task `:sdk:uniffiBindgen` runs, inside `crates/onionwire-sdk`: ``` cargo build --release --lib # host cdylib cargo run --release --bin uniffi-bindgen -- generate \ --library target/release/libonionwire_sdk.so \ --language kotlin --out-dir ``` Do not hand-edit anything under `android/sdk/build/generated/`; it is a build output. ## Why this crate has its own Cargo workspace Arti's TLS backends are **non-additive**: exactly one of `native-tls` and `rustls` may be enabled in a given feature resolution. The Linux TUI keeps `native-tls` (OpenSSL, as it always has). Android has no OpenSSL in the NDK, so this crate selects `rustls` plus `static-sqlite` (no system `libsqlite3` on Android). Two separate workspaces keep the two resolutions from colliding.