Architecture
Transport-agnostic core, internet tier (Nostr), radio tier (Reticulum/LoRa), one codebase → web / native / Pi device.
Architecture
One system, multiple surfaces
┌───────────────────────── clients ──────────────────────────┐
│ Shortwave web app │ native (CLI/TUI) │ Pi appliance │
│ (this repo, Next) │ (resilient-comms) │ (headless) │
└──────────────────────┴─────────┬──────────┴────────────────┘
│
┌──────────────▼──────── RUST CORE ─────────────────┐
│ identity (keypair) · crypto (NIP-44 / MLS) │
│ message engine (ephemeral, framing, dedup) │
│ transport manager (prefer T1 → fall to T3) │
└───┬──────────────────────┬──────────────┬─────────┘
Nostr (T1) Wi-Fi/BT mesh (T2) Reticulum/LoRa (T3)
INTERNET local fallback radio fallback
build first later laterThe Rust core (~/Code/resilient-comms) is the single authoritative engine. The web app (this repo) is a browser-friendly surface layered on top, implementing the same NIP-17/NIP-44/NIP-59 protocol entirely client-side.
Transport-agnostic design
The engine never knows which physical medium is carrying the message. It hands a sealed, self-contained ciphertext blob to whichever Transport is available. Carriers register as plugins (Briar-style):
trait Transport {
fn send(blob: SealedBlob, dest: Addr) -> Result<(), TransportError>;
fn recv() -> Stream<SealedBlob>;
}This is non-negotiable: blobs must survive radically different delivery semantics (Nostr's ~64 KB async store-and-forward vs LoRa's ~500 byte MTU, seconds-to-minutes latency, lossy).
Tier 1 — Internet transport (Nostr)
PRIMARY tier. Built first. In production on the Rust core today.
- Protocol: Nostr NIP-17 (private direct messages) over NIP-44 encryption (ChaCha20-Poly1305, Cure53-audited Dec 2023) wrapped in NIP-59 gift-wrap.
- Relays:
wss://relay.damus.io,wss://nos.lol(public defaults). Self-hostable via strfry or any NIP-compliant relay. - Censorship resistance: publish to multiple relays simultaneously; if one is blocked, others deliver.
- Store-and-forward: offline peers get messages when they reconnect — the decisive feature over pure P2P.
Tier 2 — Wi-Fi/BT mesh (future)
Local fallback when internet is unreachable but devices are in proximity. Not yet built.
Tier 3 — Radio (LoRa / Reticulum)
Infrastructure-independent fallback. Built in the Rust core (software-proven, hardware test pending).
- Radio stack: Reticulum Network Stack (rnsd sidecar) + LXMF for store-and-forward over LoRa.
- Integration pattern: the Rust transport plugin hands sealed blobs to a local
rnsdprocess via TCP; rnsd handles LoRa PHY. This keeps the Rust core free of Python/unaudited code. - Phase 0 feel: Meshtastic LoRa nodes (2 × ~A$50) for hands-on encrypted text over radio before the full Reticulum bridge.
See ~/Code/resilient-comms/docs/radio-prototype.md for the hardware runbook.
The #1 technical risk: the transport seam
Nostr (stored, ~64 KB, async) vs LoRa/Reticulum (~500 byte MTU, lossy, seconds-minutes latency) vs live mesh (online-only) have wildly different delivery semantics. The engine must handle:
- Fragmentation and reassembly per-transport MTU
- Deduplication across transports (same message arrives via two routes)
- Ordering and retries without assumptions about latency
- Stateful group crypto (MLS epochs) that can desync over lossy LoRa
Mitigation: self-contained sealed blobs carry their own framing/sequence/dedup ID. Crypto is kept simplest where the transport is lossiest (NIP-44 1:1 over radio; MLS for internet-tier groups only).
Web app — client-side crypto discipline
The web surface has an additional constraint: no server ever sees plaintext or secret keys.
- All encryption/decryption happens in the browser via Web Crypto API (used by nostr-tools internally).
- Secret keys live in
localStoragefor MVP (acknowledged weak point — see Threat Model). - No API routes. No server-side sessions. No plaintext in network requests.
- Never auto-fetch link previews on encrypted content (known NIP-44 attack vector, Black Hat 2025).
PWA architecture (M6)
Shortwave is an installable Progressive Web App (iOS + Android).
- Manifest (
/manifest.webmanifest) — Next.js 15app/manifest.tsroute.display: standalone, dark theme, maskable icons. - Service worker (
/sw.js) — Serwist 9.x (MIT, maintained). App-shell cache only. Message data is never cached; Nostr WebSocket connections bypass the SW entirely (SW cannot interceptwss://connections — they flow through the page's JS context directly). - SW strategy:
NetworkFirstfor page routes (live content preferred over cache); static assets precached.skipWaiting + clientsClaimfor immediate activation. - Offline behaviour: app shell loads (identity + UI). Messages require an active internet connection (by design — ephemeral, relay-routed).
- iOS safe areas:
viewport-fit=cover+env(safe-area-inset-*)in the compose bar and identity strip.apple-mobile-web-app-capable+black-translucentstatus bar. - Responsive breakpoint: 600px CSS media query in
ChatInstrument.tsx(window.matchMedia) — switches between desktop (forged-2 columnar ledger) and mobile (mobile-1 stacked cards) layouts.