End-to-end encrypted messaging for iOS, Android, and Desktop, built on MLS over Nostr.
Warning
Alpha software. This project was largely vibe-coded and likely contains privacy and security flaws. Do not use it for sensitive or production workloads.
Features
| Feature | iOS | Android | Desktop |
|---|---|---|---|
| 1:1 encrypted messaging | ✅ | ✅ | ✅ |
| Group chats (MLS) | ✅ | ✅ | ✅ |
| Voice calls (1:1) | ✅ | ✅ | ✅ |
| Video calls (1:1) | ✅ | ✅ | |
| Push notifications | ✅ | ||
| Emoji reactions | ✅ | ✅ | |
| Typing indicators | ✅ | ✅ | |
| @mention autocomplete | ✅ | ✅ | |
| Markdown rendering | ✅ | ✅ | |
| Polls | ✅ | ✅ | |
| Interactive widgets (HTML) | ✅ | ||
| QR code scan / display | ✅ | ✅ | |
| Encrypted media upload/download | ✅ | ||
| Profile photo upload | ✅ | ✅ | |
| Follow / unfollow contacts | ✅ | ✅ | ✅ |
How it works
Pika uses the Marmot protocol to layer MLS group encryption on top of Nostr relays. Messages are encrypted client-side using MLS, then published as Nostr events. Nostr relays handle transport and delivery without ever seeing plaintext.
┌─────────┐ UniFFI / JNI ┌────────────┐ Nostr events ┌───────────┐
│ iOS / │ ─── actions ────────▶ │ Rust core │ ── encrypted msgs ──▶ │ Nostr │
│ Android │ ◀── state snapshots ── │ (pika_core)│ ◀─ encrypted msgs ── │ relays │
│ Desktop │ │ │ │ │
└─────────┘ └────────────┘ └───────────┘
│
▼
┌────────────┐
│ MDK │
│ (MLS lib) │
└────────────┘
- Rust core owns all business logic: MLS state, message encryption/decryption, Nostr transport, and app state
- iOS (SwiftUI), Android (Kotlin), and Desktop (Iced) are thin UI layers that render state snapshots from Rust and dispatch user actions back
- MDK (Marmot Development Kit) provides the MLS implementation
- nostr-sdk handles relay connections and event publishing/subscribing
Project structure
pika/
├── rust/ Rust core library (pika_core) — MLS, Nostr, app state
├── ios/ iOS app (SwiftUI, XcodeGen)
├── android/ Android app (Kotlin, Gradle)
├── cli/ pikachat — command-line tool for testing and automation
├── cmd/pika-relay/ Local relay + Blossom server for development
├── crates/
│ ├── pika-desktop/ Desktop app (Iced)
│ ├── pikachat-sidecar/ Pikachat daemon engine (shared library)
│ ├── pika-media/ Media handling (audio, etc.)
│ ├── pika-news/ Browser-first PR/news feed generator
│ ├── pika-share/ Shared Rust core for the mobile share extension flow
│ ├── pika-tls/ TLS / certificate utilities
│ ├── pikaci/ CI orchestration and staged lane runner
│ └── rmp-cli/ RMP scaffolding CLI
├── uniffi-bindgen/ UniFFI binding generator
├── docs/ Architecture and design docs
├── todos/ Active implementation plans and workstream notes
├── tools/ Build and run tooling (pika-run, etc.)
├── scripts/ Developer scripts
└── justfile Task runner recipes
Prerequisites
- Rust (stable toolchain with cross-compilation targets)
- just (task runner used throughout the repo)
- Nix (optional) —
nix developprovides a complete dev environment - iOS: Xcode, XcodeGen
- Android: Android SDK, NDK
The Nix flake (flake.nix) pins all dependencies including Rust toolchains and Android SDK components. This is the recommended way to get a reproducible environment.
Getting started
Start by checking the repo's platform commands and target-selection help:
Build the Rust core
iOS
just ios-rust # Cross-compile Rust for iOS targets just ios-xcframework # Build PikaCore.xcframework just ios-xcodeproj # Generate Xcode project just ios-build-sim # Build for simulator just run-ios # Build, install, and launch on simulator
Android
just android-local-properties # Write local.properties with SDK path just android-rust # Cross-compile Rust for Android targets just gen-kotlin # Generate Kotlin bindings via UniFFI just android-assemble # Build debug APK just run-android # Build, install, and launch on device/emulator
Desktop
just desktop-check # Build-check the Iced desktop app just run-desktop # Run the desktop app just desktop-ui-test # Run desktop tests
pikachat
A command-line interface for testing the Marmot protocol directly:
just cli-build cargo run -p pikachat -- --relay ws://127.0.0.1:7777 identity cargo run -p pikachat -- --relay ws://127.0.0.1:7777 groups
Development
just fmt # Format Rust code just clippy # Lint just test # Run pika_core tests just qa # Full QA: fmt + clippy + test + platform builds just pre-merge-pika # CI-safe app lane (Rust + Android + desktop) just pre-merge # CI entrypoint for the whole repo
See all available recipes with just --list.
Testing
just test # Unit tests just cli-smoke # CLI smoke test (requires local Nostr relay) just e2e-local-relay # Deterministic E2E with local relay + local bot just e2e-public # E2E against public relays (nondeterministic) just ios-ui-test # iOS UI tests on simulator just android-ui-test # Android instrumentation tests just desktop-ui-test # Desktop UI/manager tests
Architecture
Pika follows a unidirectional data flow pattern:
- UI dispatches an
AppActionto Rust (fire-and-forget, never blocks) - Rust mutates state in a single-threaded actor (
AppCore) - Rust emits an
AppUpdatewith a monotonic revision number - iOS/Android applies the update on the main thread and re-renders
State is transferred as full snapshots over UniFFI (Swift) and JNI (Kotlin). This keeps the system simple and eliminates partial-state consistency bugs.
See docs/architecture.md for the full design.
Contributor Docs
Useful starting points for new contributors:
docs/rmp.md— Rust-first ownership model and native capability bridge rulesdocs/state.md—AppState/AppUpdateflowdocs/shared-cargo-target.md— faster worktree setupdocs/android-parity-report-feb-26.md— current Android gap audittodos/android-parity-plan-feb-26.md— Android implementation plancrates/rmp-cli/README.md— RMP CLI purpose and commandscrates/pika-news/README.md—pika-newslocal and hosted modes