SHORTWAVE

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                  later

The 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 rnsd process 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 localStorage for 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 15 app/manifest.ts route. 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 intercept wss:// connections — they flow through the page's JS context directly).
  • SW strategy: NetworkFirst for page routes (live content preferred over cache); static assets precached. skipWaiting + clientsClaim for 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-translucent status 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.