# blixt

[![CI](https://github.com/server321/blixt/actions/workflows/ci.yml/badge.svg)](https://github.com/server321/blixt/actions/workflows/ci.yml)

blixt (Swedish for "lightning") is a fast, secure peer-to-peer file transfer
system in the spirit of Aspera and Signiant. It moves files over QUIC (UDP +
TLS 1.3) using many parallel streams, with resumable transfers, per-chunk and
whole-file BLAKE3 integrity, and STUN/ICE NAT traversal coordinated by a tiny
signaling server. You drive it from a local browser UI, a native desktop GUI, or
a headless CLI.

The implementation is **all Rust**. A code
identifies the **sender**, which offers a **catalog** of one or more files; the
receiver sees the list and picks which to accept (or "receive all"). Codes can
be random or human-readable 12-word (BIP39) phrases.

## What you get

- `blixtd` — local agent daemon. Serves an embedded single-page web UI and a
  local JSON/WebSocket API on `127.0.0.1:7777`, and runs the QUIC transfer engine.
- `blixt-signal` — public signaling server. WebSocket rooms keyed by a transfer
  code; relays ICE offers/answers/candidates and TLS fingerprints between the two
  agents. Hardened with origin allowlist, per-IP/global limits, read+rate limits,
  idle-room reaping, role-bound room composition, and default-off metered relay.
- `blixt` — thin CLI for headless send/receive without a browser:
  `blixt send <file...>` offers a catalog, printing a generated 12-word room
  code unless you pass `--code`; `blixt recv --code <code>` shows the catalog
  and accepts `--all` (default), `--files 0,2`, or `--pick` (interactive);
  `blixt code [--words]` prints a fresh room code on its own.
- `blixt-gui` — native desktop app (egui): the same catalog send + file picker.

![blixt web UI: drag-and-drop send, a completed transfer, and the SAS verification code](docs/ui-screenshot.png)

## Easiest start (one command)

```powershell
.\start.ps1
```

This builds the binaries if needed, runs a single `blixtd` that **also hosts the
signaling server in-process** (`-embed-signal`), and opens the web UI in your
browser (`-open`). In the UI you **drag a file onto the Send card** (or click to
pick one), share the generated code, and the receiver pastes it. The signaling
URL is pre-filled and the destination defaults to your Downloads folder, so the
common case needs no typing beyond the code.

(Equivalent without the script: `blixtd --embed-signal --open`. To play both
sides on one machine, start a second receiver UI:
`blixtd --addr 127.0.0.1:7788 --signal-url ws://127.0.0.1:8080/ws`.)

## Building a distributable

```bash
cd rust
cargo build --release -p blixt-webd -p blixt-cli -p blixt-signal-server -p blixt-gui
```

This produces `blixtd` (web daemon), `blixt` (CLI), `blixt-signal` (signaling
server) and `blixt-gui` (desktop app) under `rust/target/release/`.

On Windows, `.\dist.ps1` builds a local release bundle into `dist/`:
`blixt-<version>-windows-amd64.zip` (all four binaries + README + double-click
`start.cmd` launchers), standalone `.exe`s, and
`blixt-<version>-windows-amd64-setup.exe` — an Inno Setup installer with
components, a `blixt://` handler and an optional Explorer "Send with Blixt" verb
(see [rust/docs/INSTALLER.md](rust/docs/INSTALLER.md); `-NoInstaller` skips it).
Everything is labelled with the workspace
version from `rust/Cargo.toml` (override with `-Version`). The recipient unzips
and runs `start.cmd` (or `blixtd --embed-signal --open`). Pushing a `vX.Y.Z`
tag also runs `.github/workflows/release.yml`, which cross-builds the same
bundle for Windows / Linux / macOS (arm64 + amd64).

## How it works

1. The sender shares a high-entropy code that identifies them. Both peers join
   the signaling room; the sender offers a catalog of files and the receiver
   selects which to accept. Files still being hashed appear in the catalog right
   away — marked "hashing…" with an ETA and not yet downloadable — and become
   selectable once their BLAKE3 root hash is ready.
2. The agents exchange self-signed TLS-cert fingerprints (and, for QUIC, ICE
   candidates) through the signaling server, then establish a path. The default
   **Auto** transport tries paths in order of speed — QUIC/ICE (UDP P2P), then
   HTTPS-direct, then the relays — and uses the first that connects.
3. QUIC runs over that path; each side FAIL-CLOSED pins the other's certificate
   fingerprint, and both compute a 6-digit verification code (SAS) from the TLS
   handshake secret. Compare the code out-of-band: matching codes prove no relay
   is sitting in the middle.
4. The sender streams BLAKE3-verified chunks over N parallel streams; the
   receiver validates every frame against the manifest, writes only inside the
   chosen destination directory, journals progress (MAC-protected) so an
   interrupted transfer resumes, and runs a final whole-file BLAKE3 check.

## Ports

- `127.0.0.1:7777` — daemon local API + web UI (HTTP/WebSocket).
- `:8080` — signaling server (WebSocket at `/ws`; also carries the QUIC WS relay).
- `:8443` — HTTPS TCP rendezvous relay (when the hub runs with `-enable-relay`).
- UDP (ephemeral) — QUIC data path, negotiated via ICE or a direct address.

## Quick start

See "Run instructions" below or run `cargo test` in `rust/` for the in-process
loopback integration (real QUIC), catalog/selection, and deterministic resume
tests (no network required).

To validate **real cross-internet NAT traversal** (ICE hole-punch) and the
relay fallback between two machines behind different NATs, follow
[docs/TWO-MACHINE-TEST.md](docs/TWO-MACHINE-TEST.md).

### Transport paths

The transport is chosen in **Settings → Transport** (web/GUI) or with
`--transport auto|quic|https` (CLI). **Auto is the default**; a forced `quic` or
`https` must match on both peers.

- **Auto (default).** Tries paths in order of speed and uses the first that
  connects: **QUIC/ICE** (UDP hole-punch, P2P) → **HTTPS-direct** (a LAN /
  port-forwarded receiver) → **QUIC over the WebSocket relay** (tunnelled through
  the signaling connection on 443/WSS) → **HTTPS rendezvous relay** (TCP `:8443`).
  Because the WS-relay leg rides the same port signaling already uses, Auto
  connects even where UDP and the non-standard `:8443` are blocked — Cloudflare
  WARP, cloud egress, strict firewalls.
- **QUIC (force) — fastest.** UDP + TLS 1.3: **ICE/STUN** hole-punch, then the
  **WS relay** (`--relay` skips ICE) for symmetric NAT.
- **HTTPS (force) — firewall-friendly.** N parallel TCP + TLS-1.3 connections
  (one per stream — the parallel-TCP technique MASV uses), for UDP-blocked
  networks. **direct** (the receiver is reachable on LAN / port-forward / public
  IP and the sender dials it) or, with `--relay` / "Force relay", a **rendezvous
  relay** on `:8443` (both peers dial OUT; works behind symmetric NAT). If `:8443`
  is unreachable, forced HTTPS **falls back to the QUIC WS relay** on 443 so it
  still connects.

The relays need the hub run with `blixt-signal -enable-relay [-relay-tcp-addr
:8443]`; `--embed-signal` enables it automatically. **The WS QUIC relay is
byte-metered — the public relay defaults to 1 MiB/s (`-relay-bytes-per-sec`,
`1<<20`); a relayed transfer that outruns the cap is throttled (blixt logs a
one-time `[relay] throttled …` warning), not broken.** For full-speed relayed
transfers run your own hub with a higher `-relay-bytes-per-sec N` (0 = unlimited).
Direct/ICE and HTTPS transfers are unaffected. All paths are TLS 1.3 with
fail-closed fingerprint pinning and an out-of-band SAS. **Honest expectation:**
prefer QUIC / Auto; the HTTPS legs exist to traverse UDP-blocking firewalls and
match QUIC on a clean link but trail it on lossy / high-RTT links (TCP's
congestion control isn't the app-tunable BBR QUIC uses).

### Multiconnect — link aggregation

On the **HTTPS direct** path you can **bond several local interfaces** (LAN +
Wi-Fi + tethered mobile, or several mobile links) so their bandwidths sum, à la
[MASV Multiconnect](https://massive.io/product/multiconnect/). Each parallel data
connection is pinned to a different NIC and the work-stealing chunk scheduler
sends more over the faster links; a dropped link's chunks are re-sent by the
survivors. Enable it in **Settings → Multiconnect** (web/GUI, with per-link live
rates) or `blixt send --transport https --multiconnect --interface <ip> --interface <ip>`
(see `blixt interfaces` / `blixt egress-check` / `blixt speed-check`). The links
must be genuinely separate uplinks. Details: [rust/docs/MULTICONNECT.md](rust/docs/MULTICONNECT.md).

## Security

Encryption and authentication come from QUIC's TLS 1.3 plus fail-closed
fingerprint pinning AND an out-of-band 6-digit verification code (SAS) that
detects a malicious signaling relay. The signaling server never sees plaintext
file data and is trusted only for liveness, not authenticity. Destinations are
path-traversal-safe, manifests are bounded and validated, the resume journal is
MAC-protected, and the local API is CSRF/origin-guarded and bound to localhost.
Content authenticity beyond "you received what the sender sent" requires
comparing the displayed root hash out-of-band.
