Architecture Overview
Vauchi is a privacy-focused contact card system. Users exchange contact cards in person via QR code (with NFC and Bluetooth as additional transport options). After exchange, cards update automatically — when you change your phone number, everyone who has your card sees the change.
System Architecture
┌─────────────────────────────────────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────┐
│ CLIENTS │ │ RELAY SERVER │
│ │ │ • Store-and-forward encrypted messages │
│ │ │ • No access to plaintext (oblivious) │
│ ┌───────────────────────────┐ ┌─────────┐ ┌─────────┐ ┌──────┐ ┌──────┐ │ │ ┌───────────•─Rate limiting,─quotas,┐GDPR purge────────────┐ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ iOS │ │ Android │ │ Desktop │ │ CLI │ │ TUI │ │ │ │ Blob Storage │ │ Device Sync │ │ Recovery Store │ │
│ │ SwiftUI │ │ Compose │ │ Native │ │ Rust │ │ Rust │ │ │ │ (encrypted) │ │ (per-device) │ │ (90-day TTL) │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ └─────────────┬─────────────┘ └────┬────┘ └────┬────┘ └───┬──┘ └───┬──┘ │ │ └──────────────┘ └──────────────┘ └────────────────┘ │
│ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ └──────────────────────────────────────────────────────────────┘
│ ├────────────────────────┴───────────────┴──────────────┴────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────┐ │
│ │ │ │
│ │ vauchi-core │ │
│ │ (UniFFI) │ │
│ │ Crypto, storage, protocol │ │
│ │ │ │
│ └─────────────┬─────────────┘ │
│ │ │
└────────OHTTP─encrypted──────────────────────────────────────────────────────────────────┘
│
│
▼
┌───────────────────────────┐
│ │
│ │
│ OHTTP Gateway │
│ (strips client IP) │
│ │
└─────────────┬─────────────┘
│
WebSocket (TLS)
│
│
▼
┌───────────────────────────┐
│ │
│ Relay │
│ │
└───────────────────────────┘
Note: All remote client↔relay traffic flows through an OHTTP gateway per ADR-037 — the relay never sees client IP addresses, and the gateway never sees request content. Sequence diagrams below omit the gateway hop for protocol clarity.
Core Components
vauchi-core
The Rust core library provides all cryptographic and protocol functionality:
| Module | Purpose | Key Files |
|---|---|---|
crypto/ | Encryption, signing, KDF | encryption.rs, signing.rs |
exchange/ | Contact exchange protocol | session.rs, qr.rs, x3dh.rs |
sync/ | Update propagation | device_sync.rs, delta.rs |
recovery/ | Social recovery | mod.rs |
storage/ | Local encrypted database | contacts.rs, identity.rs |
network/ | Relay communication | connection.rs, protocol.rs |
ui/ | Core-driven UI (vauchi-app) | screen.rs, component.rs |
i18n | Internationalization (vauchi-app) | i18n.rs |
vauchi-protocol
Shared protocol message types used by both vauchi-core and the relay:
- Serde-only crate (no crypto, no I/O)
- Defines
MessageEnvelope,MessagePayload, and all variant structs - Provides framing helpers (
encode_message/decode_message) - Ensures wire format consistency between clients and relay
Relay Server
Rust server for message routing (depends on vauchi-protocol for shared types):
- WebSocket-based store-and-forward
- TLS required in production
- No user accounts — just encrypted blobs
- Background cleanup tasks (hourly)
Client Applications
| Platform | Stack | Binding |
|---|---|---|
| iOS | SwiftUI | vauchi-platform-swift (SPM) |
| Android | Kotlin/Compose | Maven AAR from core CI |
| Linux (GTK) | GTK4 (gtk4-rs) | Direct Rust linkage |
| Linux (Qt) | Qt6 (Widgets) | cbindgen C FFI |
| macOS | SwiftUI | UniFFI (shared with iOS) |
| Windows | WinUI3 (C# .NET 8) | C ABI (vauchi-cabi) |
| CLI | Rust | Direct library use |
| TUI | Rust (ratatui) | Direct library use |
Core-Driven UI
Core defines what to show; frontends only decide how to render natively. New workflows are pure Rust — zero frontend code unless a new component type is needed.
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ Core (Rust) │ │ Frontend (per platform) │
│ │ │ │
│ │ │ │
│ ┌────────────────────────────────────────────┐ ┌───────────────────────────────────────────┐ ┌───────────────────────────────────────────────┐ ┌────────────────────────────────────────┐ ┌─────────────────────────────────────────────┐ │ │ ┌───────────────────────────────────────────────────────────┐ ┌───────────────────────────────┐ │ ┌──────┐
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ WorkflowEngine trait │ │ │ │ │ │ │ │ │ │ │ │ "Component Library (one native widget per Component) │ │ ScreenRenderer │ │ │ │
│ │ • current_screen() → ScreenModel │ │ ScreenModel { screen_id, title, subtitle, │ │ Component { TextInput, ToggleList, FieldList, │ │ UserAction { TextChanged, ItemToggled, │ │ ActionResult { UpdateScreen, NavigateTo, │ │ │ │ TextInput → TextField / OutlinedTextField / <input> │ │ Maps ScreenModel → native UI │ │ │ Core │
│ │ • handle_action(UserAction) → ActionResult │ │ components, actions, progress } │ │ CardPreview, InfoPanel, Text, Divider, ... } │ │ ActionPressed, ... } │ │ ValidationError, Complete, ShowToast, ... } │ │ │ │ ToggleList → Toggle list / Checkboxes / [x │ │ Sends UserAction back to core │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ └────────────────────────────────────────────┘ └───────────────────────────────────────────┘ └───────────────────────────────────────────────┘ └────────────────────────────────────────┘ └─────────────────────────────────────────────┘ │ │ └───────────────────────────────────────────────────────────┘ └───────────────────────────────┘ │ └───┬──┘
│ │ │ │ │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────────────────────────────────┘ │
│
│
│
┌────────────────────────────────────────────┐ │
│ │ │
│ Frontend │◄─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────ScreenModel─(JSON─or─direct)───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
│ │ UserAction (JSON or direct)
└────────────────────────────────────────────┘
Each frontend implements a component library
(one native component per Component variant) and
a ScreenRenderer that maps ScreenModel to
native UI. The component library is built once and
reused across all workflows.
| Component | Linux GTK4 | Linux Qt (Widgets) | macOS/iOS (SwiftUI) | Android (Compose) | Windows (WinUI3) | TUI (Ratatui) | CLI |
|---|---|---|---|---|---|---|---|
| TextInput | gtk::Entry | TextField | TextField | OutlinedTextField | TextBox | Input widget | stdin prompt |
| ToggleList | gtk::CheckButton | CheckBox | List + Toggle | LazyColumn + Checkbox | ToggleSwitch | [x]/[ ] list | numbered choice |
| FieldList | gtk::ListBox | ListView | List + chips | LazyColumn + chips | ListView | Table rows | formatted output |
| CardPreview | gtk::Frame | Frame | Card view | Card composable | Border | Box render | text output |
| InfoPanel | gtk::Box | ColumnLayout | VStack | Column | StackPanel | Block | println sections |
Transport: Rust clients (CLI, TUI, Desktop)
call WorkflowEngine directly. Mobile clients
(iOS, Android) use JSON over UniFFI.
Adding workflows: Implement a new
WorkflowEngine in core. All frontends render it
automatically via the existing component library —
no frontend changes needed.
Adding component types: Define a new Component
variant in core, then implement the corresponding
native widget in each frontend's component library.
This is rare — the vocabulary stabilizes quickly.
Data Flow
1. Contact Exchange (In-Person)
┌───────┐ ┌─────┐
│ Alice │ │ Bob │
└───┬───┘ └──┬──┘
│ │
│ Display QR (identity + key) │
│────────────────────────────────▶
│ │
│ Scan QR, verify proximity │
◀────────────────────────────────│
│ │
│ X3DH key agreement │
│────────────────────────────────▶
│ │
│ Exchange encrypted cards │
◀────────────────────────────────│
│ │
┌──────────────────────────────────┐
│ Both now have each other's cards │
└──────────────────────────────────┘
│ │
┌───┴───┐ ┌──┴──┐
│ Alice │ │ Bob │
└───────┘ └─────┘
2. Card Updates (Remote via Relay)
┌──────────────────────────────────────────┐
│ │
│ Alice updates phone number │
│ │
└─────────────────────┬────────────────────┘
│
│
│
│
▼
┌──────────────────────────────────────────┐
│ │
│ │
│ Encrypt delta with CEK │
│ (per-contact shared key, Double Ratchet) │
│ │
└─────────────────────┬────────────────────┘
│
│
│
│
▼
┌──────────────────────────────────────────┐
│ │
│ │
│ Send to relay │
│ (WebSocket) │
│ │
└─────────────────────┬────────────────────┘
│
│
│
│
▼
┌──────────────────────────────────────────┐
│ │
│ │
│ Relay stores encrypted blob │
│ (indexed by recipient_id) │
│ │
└─────────────────────┬────────────────────┘
│
│
│
│
▼
┌──────────────────────────────────────────┐
│ │
│ │
│ Bob connects │
│ (receives pending messages) │
│ │
└─────────────────────┬────────────────────┘
│
│
│
│
▼
┌──────────────────────────────────────────┐
│ │
│ │
│ Decrypt delta │
│ (update Alice's card locally) │
│ │
└──────────────────────────────────────────┘
3. Multi-Device Sync
All devices under one identity share the same master seed. Device-specific keys are derived via HKDF:
┌─────────────┐ ┌───────────────────────┐
│ │ │ │
│ │ │ │
│ Master Seed ├────►│ Device 1 keys │
│ │ │ (HKDF + device_index) │
│ │ │ │
└──────┬──────┘ └───────────────────────┘
│
│
│
│
│
│ ┌───────────────────────┐
│ │ │
│ │ │
├───────────►│ Device 2 keys │
│ │ (HKDF + device_index) │
│ │ │
│ └───────────────────────┘
│
│
│
│
│
│ ┌───────────────────────┐
│ │ │
│ │ │
└───────────►│ Device 3 keys │
│ (HKDF + device_index) │
│ │
└───────────────────────┘
Device linking uses QR code scan with time-limited token.
4. Recovery (Social Vouching)
When all devices are lost:
- Create new identity
- Generate recovery claim (old_pk → new_pk)
- Meet contacts in person, collect signed vouchers
- When threshold (3) met, upload proof to relay
- Other contacts discover proof, verify via mutual contacts
- Accept/reject identity transition
Security Model
End-to-End Encryption
- All card data encrypted with XChaCha20-Poly1305
- Per-contact keys derived via X3DH + Double Ratchet
- Forward secrecy: each message uses unique key
- Relay sees only encrypted blobs
Key Hierarchy
┌───────────────────────────────────────────┐
│ │
│ │
│ Master Seed │
│ (256-bit, generated at identity creation) │
│ │
└───────────────────────────────────────────┘
│
│
├─────────────────────────────────────────┬──────────────────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────┐ ┌───────────────────────────┐ ┌────────────────────────────┐
│ │ │ │ │ │
│ │ │ │ │ │
│ Identity Signing Key │ │ Exchange Key │ │ SMK (Shredding Master Key) │
│ (Ed25519, raw seed) │ │ (X25519, HKDF derived) │ │ (HKDF derived) │
│ │ │ │ │ │
└───────────────────────────────────────────┘ └───────────────────────────┘ └────────────────────────────┘
│
│
┌─────────────────────────────────────────┬──────────────────────────────────┤
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────┐ ┌───────────────────────────┐ ┌────────────────────────────┐
│ │ │ │ │ │
│ │ │ │ │ │
│ SEK │ │ FKEK │ │ Per-Contact CEK │
│ (Storage Encryption Key) │ │ (File Key Encryption Key) │ │ (random 256-bit) │
│ │ │ │ │ │
└───────────────────────────────────────────┘ └───────────────────────────┘ └────────────────────────────┘
Physical Verification
Contact exchange requires in-person presence:
- QR + ultrasonic audio verification (18-20 kHz) — implemented on iOS, planned for Android
- NFC Active tap (planned — centimeters range)
- BLE with RSSI proximity check (planned — GATT transport)
Repository Structure
vauchi/ ← Orchestrator repo
├── core/ ← vauchi-core + vauchi-platform + vauchi-protocol
├── relay/ ← WebSocket relay server (uses vauchi-protocol)
├── linux-gtk/ ← GTK4 Linux desktop app
├── linux-qt/ ← Qt6 (Widgets) Linux desktop app
├── macos/ ← macOS native app (SwiftUI)
├── windows/ ← Windows native app (WinUI3)
├── ios/ ← SwiftUI app
├── android/ ← Kotlin/Compose app
├── cli/ ← Command-line interface
├── tui/ ← Terminal UI
├── features/ ← Gherkin specs
├── locales/ ← i18n JSON files
├── ohttp-relay/ ← OHTTP relay proxy
├── themes/ ← Design tokens
├── e2e/ ← End-to-end tests
└── docs/ ← Documentation
Related Documentation
- GUI Guidelines — Component design rules (toasts, inline editing, confirmations)
- UX Interaction Guidelines — Interaction philosophy (physical-first, local-first, flow design)
- Crypto Reference — Cryptographic operations
- Tech Stack — Technology choices
- Diagrams — Sequence diagrams