# waste-go Protocol Extensions These are additive extensions to [YAW/2](PROTOCOL.md) implemented by waste-go. They do **not** break compatibility — YAW/2-only peers silently ignore all new fields. Where a waste-go peer connects to a YAW/2-only peer, the extension simply has no effect on that peer. --- ## EXT-001 — Signed Invites **Status:** implemented **Affects:** `waste:` invite format, `hello` DataChannel message ### Motivation The base YAW/2 network model is open to anyone who knows the anchor URL and network name (or hash). This extension adds opt-in cryptographic membership gating: invites are signed by an existing peer, and peers that enforce `RequireInvite` reject hellos that carry no valid signed invite. ### Invite format changes The `waste:` invite payload (base64-encoded JSON) gains two optional fields: ```json { "anchor": "wss://...", "network": "friends", "net": "<64-hex SHA-256(yaw2-net:name)>", "inviter": "<64-hex Ed25519 pubkey of signing peer>", "sig": "" } ``` The signature covers the following bytes (null-separated): ``` anchor \x00 network \x00 net \x00 inviter ``` Unsigned invites (`inviter`/`sig` absent) remain valid for backward compat. ### Hello message extension The YAW/2 §6 hello message gains one optional field: ```json { "type": "hello", "id": "", "nick": "alice", "caps": ["chat", "file"], "sig": "", "invite": "waste:eyJ..." } ``` `invite` carries the full `waste:` string the connecting peer used to join. YAW/2-only peers ignore this field. ### Enforcement Per-network flag `RequireInvite` (set via `join_network` IPC command). When enabled: 1. A peer that presents no `invite` in hello is disconnected immediately. 2. A peer that presents an invite with no signature is disconnected. 3. A peer whose invite signature is invalid is disconnected. 4. A peer whose invite was signed by an unknown peer ID (not in the store or currently connected) is disconnected. The inviter's key must be a **known peer** — i.e. previously connected and stored in the per-network SQLite store, or currently connected. This forms a chain of trust: Alice (founder) invites Bob; Bob's key is now known; Bob can invite Carol, whose invite Alice will also accept. **Default:** off. Networks opt in. Existing networks with no RequireInvite behave exactly as before. --- ## EXT-002 — Hash-based Hang Link **Status:** implemented **Affects:** web UI URL handling only, no wire changes ### Motivation A shareable URL that pre-fills the join form without conveying cryptographic membership. Suitable for public announcements ("come hang out here"). The fragment is never sent to the server, keeping the network name opaque to server logs and HTTP intermediaries. ### Format ``` https://host/#waste:eyJ... ``` The fragment payload is the standard `waste:` base64 JSON with only `network` and `anchor` fields — no `inviter`, no `sig`. This does **not** grant membership on networks with `RequireInvite` enabled; it only pre-fills the join form. The web UI generates hang links via the 🔗 button in the Networks sidebar section. Arriving users see the join form pre-populated and still need a proper signed invite (if the network enforces it) to be accepted by peers. --- ## EXT-003 — Multi-Share Configuration **Status:** implemented **Affects:** IPC protocol only, no peer-to-peer wire changes ### New IPC commands ```jsonc {"type":"add_share","path":"/home/alice/Music"} // global {"type":"add_share","path":"/home/alice/Docs","network_ids":["abc123"]} // scoped {"type":"remove_share","path":"/home/alice/Music"} {"type":"list_shares"} ``` ### New IPC event ```jsonc {"type":"shares_list","shares":[{"path":"...","networks":["*"]}]} ``` ### Persistence `shares.json` in the data directory (next to `identity.json`). Each entry: ```json { "path": "/absolute/path", "networks": ["*"] } ``` `networks: ["*"]` = global (all networks). Specific network IDs = scoped. Coexists with the legacy `set_share_dir` single-dir mechanism. File listings returned by `get_file_list` and `MsgFileListReq` include entries from all applicable share roots, with relative `path` fields (e.g. `"path": "docs/report.pdf"`). --- ## EXT-004 — TURN Relay (browser mode) **Status:** implemented (browser mode + daemon mode) **Affects:** ICE server configuration only, no wire changes The browser adapter reads `WASTE_CONFIG.turnURL` and `WASTE_CONFIG.turnSecret` and adds a TURN server to the WebRTC `ICEServers` list. Credentials are generated using HMAC-SHA1 of the username (coturn `use-auth-secret` scheme). YAW/2 §0 explicitly declines TURN ("No relay (TURN)"). This extension is opt-in via server configuration and does not affect peers that omit it. --- ## EXT-005 — Per-Network Path in FileEntry **Status:** implemented **Affects:** `MsgFileListResp` wire message (additive field) `FileEntry` gains an optional `path` field carrying the file's relative path within its share root (e.g. `"docs/report.pdf"`). Peers that don't understand this field continue to use `name` for display and download requests. `MsgFileListReq` / `get` requests use `path` as the lookup key when present, falling back to `name` for backward compat with peers that don't send `path`. --- ## EXT-006 — File Transfer Resume **Status:** implemented (daemon mode) **Affects:** `file-accept` wire message (additive field) ### Motivation A transfer interrupted mid-stream (peer disconnect, network drop) can be continued from where it left off on the next offer of the same file, avoiding a full re-download. ### Protocol change `file-accept` gains one optional field: ```json { "type": "file-accept", "xid": "...", "resume_offset": 65536 } ``` `resume_offset` is the number of bytes the receiver already has on disk. When non-zero, the sender seeks to that byte position before streaming. Peers that don't understand this field ignore it and send from the start — the receiver detects this by comparing incoming data to expected offset and will still verify the final SHA-256, but the partial bytes from the interrupted session will be overwritten (YAW/2-only interop degrades gracefully to a full re-download, not corruption). ### Receiver behaviour 1. On `file-offer`, the receiver scans its download directory for a `.tmp.meta` sidecar whose `sha256` matches the offer. 2. If found, the corresponding `.tmp` file's size is the resume offset. This is sent back in `file-accept`. 3. On DC open, the receiver opens the existing `.tmp` in append mode and re-hashes its existing bytes to restore the SHA-256 state. 4. On DC close with all bytes received, SHA-256 is verified. Success → sidecar removed, file renamed to final path. Hash mismatch → both files removed. 5. On DC close with fewer bytes than expected (interrupted again) → both files kept for the next resume attempt. ### Sidecar format Each in-progress `.tmp` file has a corresponding `.tmp.meta` JSON sidecar: ```json { "name": "archive.zip", "sha256": "abc...", "from": "", "size": 1048576 } ``` The sidecar is written when the transfer starts and removed on completion or corruption. Interrupted transfers keep the sidecar indefinitely.