Update README: hosting guide, browser vs daemon mode, NPM setup
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
376
README.md
376
README.md
@@ -9,7 +9,7 @@ friend-to-friend encrypted mesh networking with chat and file sharing. Written i
|
|||||||
waste-go/
|
waste-go/
|
||||||
├── cmd/
|
├── cmd/
|
||||||
│ ├── daemon/ The peer process — run one on each friend's machine
|
│ ├── daemon/ The peer process — run one on each friend's machine
|
||||||
│ ├── anchor/ WebSocket signaling server — run this on your Hetzner VPS
|
│ ├── anchor/ WebSocket signaling server — run this on your VPS
|
||||||
│ └── tui/ Bubble Tea terminal UI (connects to a running daemon)
|
│ └── tui/ Bubble Tea terminal UI (connects to a running daemon)
|
||||||
└── internal/
|
└── internal/
|
||||||
├── proto/ All wire types (shared by daemon and anchor)
|
├── proto/ All wire types (shared by daemon and anchor)
|
||||||
@@ -19,235 +19,225 @@ waste-go/
|
|||||||
└── ipc/ Local JSON API (UI talks to daemon here, port 17337)
|
└── ipc/ Local JSON API (UI talks to daemon here, port 17337)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Prerequisites
|
---
|
||||||
|
|
||||||
- Go 1.24+ → https://go.dev/dl/
|
## Hosting on a VPS
|
||||||
- VS Code with the Go extension (`golang.go`)
|
|
||||||
|
|
||||||
On first open VS Code will prompt you to install `gopls`, `dlv`, and `goimports` — accept all of them.
|
You need two things on the server: the **anchor** (signaling process) and the **web UI** (static files). Both are served through the same domain via Nginx Proxy Manager.
|
||||||
|
|
||||||
## Getting started
|
### 1. Build and run the anchor
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Fetch dependencies
|
# On your local machine — cross-compile for Linux
|
||||||
go mod tidy
|
GOOS=linux GOARCH=amd64 go build -o bin/waste-anchor ./cmd/anchor
|
||||||
|
|
||||||
# Build everything (confirms it compiles)
|
# Copy to VPS
|
||||||
go build ./...
|
scp bin/waste-anchor user@your-vps:~/waste-anchor
|
||||||
|
```
|
||||||
|
|
||||||
# Terminal 1 — anchor (required for peers to find each other)
|
On the VPS, run the anchor and keep it alive (systemd, screen, whatever you use):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./waste-anchor -bind 127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
The anchor listens locally on port 8080 — Nginx Proxy Manager will expose it over TLS.
|
||||||
|
|
||||||
|
### 2. Build and upload the web UI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On your local machine
|
||||||
|
cd web
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
# Produces web/dist/
|
||||||
|
|
||||||
|
# Copy to VPS
|
||||||
|
rsync -az web/dist/ user@your-vps:/var/www/waste-web/
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a `/var/www/waste-web/config.js` on the VPS (not in git — this is host-specific):
|
||||||
|
|
||||||
|
```js
|
||||||
|
window.WASTE_CONFIG = {
|
||||||
|
signalURL: 'wss://your-domain.com/ws',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells the browser where to connect for signaling. Without it the join form shows a blank signal server field and the user must fill it in manually.
|
||||||
|
|
||||||
|
### 3. Nginx Proxy Manager setup
|
||||||
|
|
||||||
|
Create one **Proxy Host** for your domain (e.g. `waste.example.com`) with TLS enabled. You need two locations:
|
||||||
|
|
||||||
|
**Location 1 — WebSocket signaling (`/ws`)**
|
||||||
|
- Location: `/ws`
|
||||||
|
- Forward hostname/IP: `127.0.0.1`
|
||||||
|
- Forward port: `8080`
|
||||||
|
- Enable: WebSockets Support
|
||||||
|
|
||||||
|
**Location 2 — Web UI (catch-all)**
|
||||||
|
- Location: `/`
|
||||||
|
- Choose "Serve Static Files" (or point to a local HTTP server serving `/var/www/waste-web`)
|
||||||
|
- Enable the SPA fallback so unknown paths return `index.html` — this is required for invite links to work
|
||||||
|
|
||||||
|
If NPM doesn't support static file serving directly, run a small static server on a spare port (e.g. `npx serve -s /var/www/waste-web -l 3000`) and proxy `/` to `127.0.0.1:3000`. The key requirements:
|
||||||
|
|
||||||
|
- `/ws` → anchor process (WebSocket, keep-alive)
|
||||||
|
- `/*` → static file server (SPA fallback: return `index.html` for unknown paths)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How it works: daemon vs browser mode
|
||||||
|
|
||||||
|
There are two ways to use the web UI.
|
||||||
|
|
||||||
|
### Browser mode (for anyone with just a URL)
|
||||||
|
|
||||||
|
When the web UI is served from a non-localhost origin it runs entirely in the browser — no daemon, no install. Crypto (Ed25519/X25519) runs via libsodium compiled to WebAssembly. The identity seed is stored in `localStorage` and persists across sessions.
|
||||||
|
|
||||||
|
A user visits your domain, enters their name and a network name, and joins. Invite links (`waste:…` or `?n=name&a=wss://…`) pre-fill the join form.
|
||||||
|
|
||||||
|
**Identity note:** browser mode uses the master identity directly (same keypair on all networks, compatible with yaw2). The daemon derives a separate keypair per network via HKDF. A browser user and a daemon user on the same network will see each other and can chat — they just appear as different peers even if they're the same person.
|
||||||
|
|
||||||
|
### Daemon mode (for users running the daemon locally)
|
||||||
|
|
||||||
|
`launch-web.sh` starts the Go daemon and the Vite dev server. The web UI connects to the local daemon over WebSocket IPC (`ws://127.0.0.1:17338`). The daemon handles all crypto and connects to the anchor.
|
||||||
|
|
||||||
|
When the web UI is loaded from `localhost`, it defaults to daemon mode. A "Switch to browser mode" button is available in the join screen if the daemon is not running.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.24+ — https://go.dev/dl/
|
||||||
|
- Node.js 20+
|
||||||
|
|
||||||
|
### Quick start (three peers in one terminal session)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Terminal 1 — local anchor
|
||||||
go run ./cmd/anchor -bind 127.0.0.1:17339
|
go run ./cmd/anchor -bind 127.0.0.1:17339
|
||||||
|
|
||||||
# Terminal 2 — peer A
|
# Terminal 2 — peer A
|
||||||
go run ./cmd/daemon -alias alice -data-dir /tmp/waste-alice -ipc-port 17337 -anchor ws://127.0.0.1:17339/ws
|
go run ./cmd/daemon -alias alice -data-dir /tmp/waste-alice -ipc-port 17337 -anchor ws://127.0.0.1:17339/ws
|
||||||
|
|
||||||
# Terminal 3 — peer B (or use --join with an invite from peer A)
|
# Terminal 3 — peer B
|
||||||
go run ./cmd/daemon -alias bob -data-dir /tmp/waste-bob -ipc-port 17341 -anchor ws://127.0.0.1:17339/ws
|
go run ./cmd/daemon -alias bob -data-dir /tmp/waste-bob -ipc-port 17341 -anchor ws://127.0.0.1:17339/ws
|
||||||
```
|
```
|
||||||
|
|
||||||
Both peers join the same named network via IPC:
|
Join both to a network:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Join peer A to a network called "friends"
|
|
||||||
echo '{"type":"join_network","network_name":"friends"}' | nc 127.0.0.1 17337
|
echo '{"type":"join_network","network_name":"friends"}' | nc 127.0.0.1 17337
|
||||||
|
|
||||||
# Join peer B to the same network
|
|
||||||
echo '{"type":"join_network","network_name":"friends"}' | nc 127.0.0.1 17341
|
echo '{"type":"join_network","network_name":"friends"}' | nc 127.0.0.1 17341
|
||||||
|
|
||||||
# Subscribe to peer A's events (in a separate terminal)
|
|
||||||
nc 127.0.0.1 17337 &
|
|
||||||
|
|
||||||
# Send a message from B
|
|
||||||
echo '{"type":"send_message","room":"general","body":"hello from bob"}' | nc 127.0.0.1 17341
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**On Windows** — use PowerShell's built-in TCP client instead of `nc`:
|
### Web UI (daemon mode)
|
||||||
|
|
||||||
```powershell
|
|
||||||
$c = [System.Net.Sockets.TcpClient]::new('127.0.0.1', 17341)
|
|
||||||
$w = [System.IO.StreamWriter]::new($c.GetStream()); $w.AutoFlush = $true
|
|
||||||
|
|
||||||
$w.WriteLine('{"type":"join_network","network_name":"friends"}')
|
|
||||||
$w.WriteLine('{"type":"send_message","room":"general","body":"hello from bob"}')
|
|
||||||
|
|
||||||
# In a separate terminal — subscribe to peer A's events
|
|
||||||
$r = [System.Net.Sockets.TcpClient]::new('127.0.0.1', 17337)
|
|
||||||
$reader = [System.IO.StreamReader]::new($r.GetStream())
|
|
||||||
while ($true) { $reader.ReadLine() }
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deploying the anchor on your Hetzner VPS
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
GOOS=linux GOARCH=amd64 go build -o bin/waste-anchor ./cmd/anchor
|
# Requires a running daemon on port 17337
|
||||||
scp bin/waste-anchor user@your-vps:~/
|
./launch-web.sh
|
||||||
|
|
||||||
# On the VPS (also run coturn in STUN-only mode on port 3478)
|
# Or with a custom alias and network:
|
||||||
./waste-anchor -bind 0.0.0.0:17339
|
ALIAS=alice NETWORK=friends ./launch-web.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Then start daemons with `-anchor ws://your-vps-ip:17339/ws` and they'll connect via WebRTC
|
### Automated test
|
||||||
with ICE (STUN-assisted hole punching) through the anchor for signaling.
|
|
||||||
|
|
||||||
## IPC protocol (plain JSON over TCP)
|
|
||||||
|
|
||||||
Everything is newline-delimited JSON. You can test with `nc 127.0.0.1 17337`.
|
|
||||||
|
|
||||||
**Commands you send:**
|
|
||||||
```jsonc
|
|
||||||
{"type":"join_network","network_name":"friends"}
|
|
||||||
{"type":"leave_network"}
|
|
||||||
{"type":"send_message","room":"general","body":"hi"}
|
|
||||||
{"type":"send_message","room":"dm:<peer-hex>","body":"hey","to":"<peer-hex>"}
|
|
||||||
{"type":"get_state"}
|
|
||||||
{"type":"get_file_list"} // own share dir
|
|
||||||
{"type":"get_file_list","peer_id":"<64-hex>"} // remote peer's share dir
|
|
||||||
{"type":"send_file","peer_id":"<64-hex>","path":"notes.txt"} // offer a file from share dir
|
|
||||||
{"type":"generate_invite"}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Events the daemon pushes:**
|
|
||||||
```jsonc
|
|
||||||
// Sent immediately on connect and in response to get_state
|
|
||||||
{"type":"state_snapshot","local_peer":{"id":"<64-hex>","alias":"alice","public_key":"<64-hex>","created_at":"..."},"connected_peers":[...],"rooms":["general"]}
|
|
||||||
|
|
||||||
// Peer lifecycle
|
|
||||||
{"type":"peer_connected","peer":{"id":"<64-hex>","alias":"bob",...}}
|
|
||||||
{"type":"session_ready","peer_id":"<64-hex>","nick":"bob"}
|
|
||||||
{"type":"peer_disconnected","peer_id":"<64-hex>"}
|
|
||||||
|
|
||||||
// Incoming message — mid is a 32-hex dedup token, to is set for DMs
|
|
||||||
{"type":"message_received","message":{"mid":"<32-hex>","from":"<64-hex>","room":"general","text":"hi","ts":1700000000000}}
|
|
||||||
|
|
||||||
// File events
|
|
||||||
{"type":"incoming_file","peer_id":"<64-hex>","offer":{"xid":"<32-hex>","name":"notes.txt","size":1024,"sha256":"<64-hex>"}}
|
|
||||||
{"type":"file_progress","transfer_id":"<32-hex>","bytes_received":65536,"total_bytes":1048576}
|
|
||||||
{"type":"file_complete","transfer_id":"<32-hex>","path":"/data-dir/downloads-<netid>/notes.txt"}
|
|
||||||
{"type":"file_list","peer_id":"<64-hex>","files":[{"name":"notes.txt","size_bytes":1024}]}
|
|
||||||
|
|
||||||
// Invite generation response
|
|
||||||
{"type":"invite_generated","invite":"waste:<base64>"}
|
|
||||||
|
|
||||||
// Error
|
|
||||||
{"type":"error","error_message":"..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Crypto choices
|
|
||||||
|
|
||||||
| Purpose | Algorithm | Notes |
|
|
||||||
|---|---|---|
|
|
||||||
| Identity | Ed25519 | Fast, small keys, standard |
|
|
||||||
| Peer ID | Hex-encoded Ed25519 pubkey | 64 lowercase hex chars (YAW/2 §2) |
|
|
||||||
| Signaling encryption (2.0) | XSalsa20-Poly1305 (`nacl/box`) | X25519 keys derived from Ed25519 identity (YAW/2 §3) |
|
|
||||||
| **Signaling encryption (2.1)** | **XSalsa20-Poly1305, ephemeral X25519** | **Per-session keypair; `esk` wiped on close → forward secrecy** |
|
|
||||||
| Transport | WebRTC DataChannels (DTLS+SCTP) | pion/webrtc — ICE, hole punching included |
|
|
||||||
| Hashing | SHA-256 | File integrity, network name hashing |
|
|
||||||
|
|
||||||
Replaces WASTE's original Blowfish/PCBC (broken cipher mode) + RSA.
|
|
||||||
|
|
||||||
### Forward-secret signaling (YAW/2.1)
|
|
||||||
|
|
||||||
By default waste-go speaks **YAW/2.1**: before sending an offer each peer generates a fresh
|
|
||||||
X25519 keypair (`esk`/`epk`), broadcasts its `epk` in a signed `ekey` message, then seals
|
|
||||||
`offer`/`answer`/`candidate` payloads with the *ephemeral* keys. `esk` is zeroed when the
|
|
||||||
session ends. Recorded signaling traffic cannot be decrypted even if the long-term Ed25519
|
|
||||||
keys later leak.
|
|
||||||
|
|
||||||
A 2.0 peer ignores the `ekey` message (unknown type → silently dropped) and the offerer
|
|
||||||
falls back to static-key sealing after a 2 s timeout, so **2.1 ↔ 2.0 sessions work** — the
|
|
||||||
session just isn't forward-secret. The log line `anchor: 2.0 fallback offer to …` flags this.
|
|
||||||
|
|
||||||
> Peer IDs are 64-char lowercase hex (Ed25519 public key). Existing `identity.json` files
|
|
||||||
> on disk are unaffected — only the over-the-wire representation changed from base64url.
|
|
||||||
|
|
||||||
## Onboarding a new peer
|
|
||||||
|
|
||||||
Alice is already on the network and wants to add Bob.
|
|
||||||
|
|
||||||
**Alice generates an invite** (from the TUI with `Ctrl+I`, or via IPC directly):
|
|
||||||
```bash
|
|
||||||
echo '{"type":"generate_invite"}' | nc 127.0.0.1 17337
|
|
||||||
# → {"type":"invite_generated","invite":"waste:eyJhbmNob3IiOiJ3czovL..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Bob starts his daemon using the invite** — the `--join` flag sets the anchor URL and auto-joins the network:
|
|
||||||
```bash
|
|
||||||
go run ./cmd/daemon -alias bob -data-dir ~/.waste-bob --join 'waste:eyJhbmNob3IiOiJ3czovL...'
|
|
||||||
```
|
|
||||||
|
|
||||||
**Bob opens the TUI** — `--join` also accepts the invite to skip the `-network` flag:
|
|
||||||
```bash
|
|
||||||
go run ./cmd/tui --join 'waste:eyJhbmNob3IiOiJ3czovL...'
|
|
||||||
```
|
|
||||||
|
|
||||||
The invite encodes the anchor URL and network name as a `waste:` URI. Share it over Signal, email, or any side channel — the anchor never sees plaintext messages, so the invite leaking to a third party only lets them join the same network (which is by design: same network = mutual trust).
|
|
||||||
|
|
||||||
## Terminal UI
|
|
||||||
|
|
||||||
Start the daemon first (see Getting started above), then:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go run ./cmd/tui -network friends
|
|
||||||
```
|
|
||||||
|
|
||||||
Options:
|
|
||||||
|
|
||||||
| Flag | Default | Description |
|
|
||||||
|---|---|---|
|
|
||||||
| `-network` | *(required unless -join)* | Network name to join on startup |
|
|
||||||
| `-join` | — | `waste:` invite string — sets the network name automatically |
|
|
||||||
| `-ipc` | `17337` | Daemon IPC port |
|
|
||||||
|
|
||||||
**Layout:**
|
|
||||||
|
|
||||||
```
|
|
||||||
╭─ Rooms ──────╮╭─── #general ────────────────╮╭─ Peers ──────╮
|
|
||||||
│ ▶ #general ││ 15:04 alice hey everyone ││ ◉ alice (me) │
|
|
||||||
│ @ bob ││ 15:04 bob hi alice! ││ ● bob │
|
|
||||||
│ ││ 15:05 charlie the mesh works ││ ● charlie │
|
|
||||||
╰──────────────╯╰─────────────────────────────╯╰──────────────╯
|
|
||||||
╭─────────────────────────────────────────────────────────────╮
|
|
||||||
│ Type a message… │
|
|
||||||
╰─────────────────────────────────────────────────────────────╯
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key bindings:** `Tab` / `Shift+Tab` — switch rooms · `PgUp` / `PgDn` — scroll · `Enter` — send · `Ctrl+I` — generate invite · `Esc` — close invite overlay · `Ctrl+C` — quit
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
A self-contained test script boots anchor + three peers, joins them to a named network, exchanges group messages and DMs, and verifies SQLite persistence:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./test-network.sh
|
./test-network.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Data lands at `/tmp/waste-test` (wiped on each run). Inspect after a run:
|
Boots anchor + three peers, joins them to a network, sends group messages and DMs, verifies SQLite persistence.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Onboarding a new peer
|
||||||
|
|
||||||
|
Alice generates an invite (TUI: `Ctrl+I`, or via IPC):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# DB name includes the network ID (first 8 hex chars of sha256("yaw2-net:"+name))
|
echo '{"type":"generate_invite"}' | nc 127.0.0.1 17337
|
||||||
sqlite3 /tmp/waste-test/alice/messages-<netid>.db
|
# → {"type":"invite_generated","invite":"waste:eyJ..."}
|
||||||
.headers on
|
|
||||||
SELECT room, from_peer, text, sent_at FROM messages;
|
|
||||||
SELECT peer_id, alias, last_seen FROM peers;
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There is also a TUI integration test that boots the same three-peer network and
|
Bob joins using the invite:
|
||||||
launches the Bubble Tea UI as alice:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
./test-tui.sh
|
go run ./cmd/daemon -alias bob -data-dir ~/.waste-bob --join 'waste:eyJ...'
|
||||||
|
go run ./cmd/tui --join 'waste:eyJ...'
|
||||||
```
|
```
|
||||||
|
|
||||||
## Roadmap
|
The invite encodes the anchor URL and network name. Sharing it only lets the recipient join the same network — the anchor never sees plaintext messages.
|
||||||
|
|
||||||
- [x] **Crypto layer** — hex peer IDs, `nacl/box` signaling, Ed25519→X25519 key derivation
|
Invite links also work in the web UI. Share `https://your-domain.com/?invite=waste:eyJ...` and the join form is pre-filled.
|
||||||
- [x] **Proto additions** — `mid` dedup field, signaling types, anchor wire types, `hello` message
|
|
||||||
- [x] **Anchor server** (`cmd/anchor`) — WebSocket signaling server replacing TCP relay
|
---
|
||||||
- [x] **WebRTC peer connections** — pion/webrtc DataChannels; ICE hole-punching via STUN
|
|
||||||
- [x] **Anchor client** (`internal/anchor`) — offer/answer/candidate lifecycle, `nacl/box` sealing
|
## Terminal UI
|
||||||
- [x] **IPC updates** — `join_network`/`leave_network`; `session_ready` event; DMs via `to` field
|
|
||||||
- [x] **Message persistence** — SQLite (`internal/store`); messages and peer alias cache
|
```bash
|
||||||
- [x] **TUI** — Bubble Tea terminal UI (`cmd/tui`); three-pane layout with room switching and DMs
|
go run ./cmd/tui -network friends
|
||||||
- [x] **File transfer** — chunked binary DataChannel (`f:<xid>`); SHA-256 verified; backpressure; auto-accept
|
```
|
||||||
- [x] **Forward-secret signaling (YAW/2.1)** — ephemeral X25519 per session; `esk` wiped on close; 2.0 fallback
|
|
||||||
- [ ] **Native UI** — web frontend with native packaging (Tauri-style)
|
| Flag | Default | Description |
|
||||||
|
|---|---|---|
|
||||||
|
| `-network` | *(required unless -join)* | Network name to join on startup |
|
||||||
|
| `-join` | — | `waste:` invite string |
|
||||||
|
| `-ipc` | `17337` | Daemon IPC port |
|
||||||
|
|
||||||
|
**Key bindings:** `Tab`/`Shift+Tab` — switch rooms · `PgUp`/`PgDn` — scroll · `Enter` — send · `Ctrl+I` — generate invite · `Esc` — close overlay · `Ctrl+C` — quit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## IPC protocol
|
||||||
|
|
||||||
|
Newline-delimited JSON on TCP port 17337 (or WebSocket on 17338).
|
||||||
|
|
||||||
|
**Commands:**
|
||||||
|
```jsonc
|
||||||
|
{"type":"join_network","network_name":"friends"}
|
||||||
|
{"type":"send_message","room":"general","body":"hi"}
|
||||||
|
{"type":"send_message","to":"<64-hex>","body":"hey"} // DM
|
||||||
|
{"type":"generate_invite"}
|
||||||
|
{"type":"get_state"}
|
||||||
|
{"type":"get_file_list"}
|
||||||
|
{"type":"get_file_list","peer_id":"<64-hex>"}
|
||||||
|
{"type":"send_file","peer_id":"<64-hex>","path":"notes.txt"}
|
||||||
|
{"type":"export_identity","passphrase":"..."}
|
||||||
|
{"type":"import_identity","passphrase":"...","backup":"..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Events:**
|
||||||
|
```jsonc
|
||||||
|
{"type":"state_snapshot","local_peer":{...},"connected_peers":[...],"master_alias":"alice","master_id":"<64-hex>"}
|
||||||
|
{"type":"peer_connected","peer":{"id":"<64-hex>","alias":"bob"}}
|
||||||
|
{"type":"session_ready","peer_id":"<64-hex>","nick":"bob"}
|
||||||
|
{"type":"peer_disconnected","peer_id":"<64-hex>"}
|
||||||
|
{"type":"message_received","message":{"mid":"<32-hex>","from":"<64-hex>","room":"general","text":"hi","ts":1700000000000}}
|
||||||
|
{"type":"network_joined","network_id":"...","network_name":"friends"}
|
||||||
|
{"type":"invite_generated","invite":"waste:<base64>"}
|
||||||
|
{"type":"incoming_file","peer_id":"<64-hex>","offer":{"xid":"...","name":"notes.txt","size":1024,"sha256":"..."}}
|
||||||
|
{"type":"file_complete","transfer_id":"...","path":"/downloads/notes.txt"}
|
||||||
|
{"type":"identity_exported","backup":"..."}
|
||||||
|
{"type":"error","error_message":"..."}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Crypto
|
||||||
|
|
||||||
|
| Purpose | Algorithm |
|
||||||
|
|---|---|
|
||||||
|
| Identity | Ed25519 |
|
||||||
|
| Signaling (2.0) | XSalsa20-Poly1305, X25519 keys derived from Ed25519 |
|
||||||
|
| Signaling (2.1) | XSalsa20-Poly1305, ephemeral X25519 per session (forward secrecy) |
|
||||||
|
| Transport | WebRTC DataChannels (DTLS+SCTP via pion/webrtc) |
|
||||||
|
| File integrity | SHA-256 |
|
||||||
|
|
||||||
|
### Forward-secret signaling (YAW/2.1)
|
||||||
|
|
||||||
|
Each peer generates a fresh X25519 keypair per session and broadcasts the public half in a signed `ekey` message before sending an offer. The `esk` is zeroed when the session ends. A 2.0 peer ignores `ekey` and the offerer falls back to static-key sealing after 2 s — so 2.1↔2.0 sessions work, just without forward secrecy.
|
||||||
|
|||||||
Reference in New Issue
Block a user