onionwire/crates/onionwire-sdk/README.md

123 lines
4.8 KiB
Markdown
Raw Normal View History

# 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=<ed25519 pubkey hex64>:o=<v3 onion address>:spk=<x25519 prekey hex64>:sig=<ed25519 sig hex128>
```
* `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-<version>.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<FriendInfo> = wire.friends()
wire.setPetname(friend.pubkeyHex, "ada")
wire.send(friend.pubkeyHex, "hello wire")
val msgs: List<ChatMessage> = 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 <generated 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.