ipc: CmdSendMessage now routes to the named recipient only (m.SendTo) when the "to" field is set, rather than broadcasting to all peers. Group messages continue to use Broadcast. This is the correct privacy behaviour for DMs. test-network.sh: - Resolve each peer's hex id from get_state before joining the network, using a retry loop with timeout to handle slow daemon startup. - Added a DM section: alice→bob, bob→alice, charlie→alice, alice→charlie. DMs use the "to" + "room":"dm:<peer_id>" convention. - Fixed jq -cn (compact) instead of jq -n (pretty) so the IPC newline- delimited protocol receives a single JSON line per command. - pretty() now renders DMs as 📨 DM <from→to> rather than 💬 #room. - Persistence check now breaks down totals by group vs DM message count. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
waste-go
A modern reimagining of WASTE — decentralized, friend-to-friend encrypted mesh networking with chat and file sharing. Written in Go.
Project layout
waste-go/
├── cmd/
│ ├── daemon/ The peer process — run one on each friend's machine
│ └── anchor/ WebSocket signaling server — run this on your Hetzner VPS
└── internal/
├── proto/ All wire types (shared by daemon and anchor)
├── crypto/ Ed25519 identity, nacl/box signaling, ChaCha20-Poly1305
<20><><EFBFBD>── mesh/ Connected peer state + DataChannel helpers
├── anchor/ Anchor client — WebRTC signaling via the anchor server
└── ipc/ Local JSON API (UI talks to daemon here, port 17337)
Prerequisites
- Go 1.24+ → https://go.dev/dl/
- 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.
Getting started
# Fetch dependencies
go mod tidy
# Build everything (confirms it compiles)
go build ./...
# Terminal 1 — anchor (required for peers to find each other)
go run ./cmd/anchor -bind 127.0.0.1:17339
# 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
# 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
Both peers join the same named network via IPC:
# Join peer A to a network called "friends"
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
# 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:
$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
GOOS=linux GOARCH=amd64 go build -o bin/waste-anchor ./cmd/anchor
scp bin/waste-anchor user@your-vps:~/
# On the VPS (also run coturn in STUN-only mode on port 3478)
./waste-anchor -bind 0.0.0.0:17339
Then start daemons with -anchor ws://your-vps-ip:17339/ws and they'll connect via WebRTC
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:
{"type":"join_network","network_name":"friends"}
{"type":"leave_network"}
{"type":"send_message","room":"general","body":"hi"}
{"type":"get_state"}
Events the daemon pushes:
{"type":"state_snapshot","local_peer":{...},"connected_peers":[...]}
{"type":"peer_connected","peer":{...}}
{"type":"session_ready","peer_id":"<hex>","nick":"alice"}
{"type":"message_received","message":{"from":"<hex>","body":"hi","room":"general"}}
{"type":"peer_disconnected","peer_id":"<hex>"}
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 | XSalsa20-Poly1305 (nacl/box) |
X25519 keys derived from Ed25519 identity (YAW/2 §3) |
| 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.
Peer IDs are 64-char lowercase hex (Ed25519 public key). Existing
identity.jsonfiles on disk are unaffected — only the over-the-wire representation changed from base64url.
Roadmap
- Crypto layer — hex peer IDs,
nacl/boxsignaling, Ed25519→X25519 key derivation - Proto additions —
midfield, signaling types, anchor wire types,hellomessage,FileDone - Anchor server (
cmd/anchor) — WebSocket signaling server (replaces TCP relay) - WebRTC peer connections — pion/webrtc DataChannels replace raw TCP in
internal/mesh - Anchor client (
internal/anchor) — ICE offer/answer/candidate lifecycle,nacl/boxsealing - IPC updates —
join_network/leave_networkreplaceconnect;session_readyevent added - File transfer — chunked binary DataChannel (
f:<xid>) - Message persistence — SQLite via
modernc.org/sqlite - TUI — Bubble Tea client consuming the IPC port
- Native UI — web frontend with native packaging (Tauri-style)