The anchor keys its clients map by peer ID. With the daemon joining one
room per trusted peer, both sessions authenticated with the same real
Ed25519 ID — the second register() overwrote the first, orphaning the
earlier room's WS connection.
Fix mirrors waste-go/internal/netmgr: derive a deterministic per-room
Ed25519 keypair from HKDF(master_private_key, room_hash). Same inputs
always produce the same derived ID; different rooms produce different
IDs. No anchor changes required.
cli/internal/crypto: add DeriveForNetwork (HKDF-SHA256, same KDF as
waste-go) so the daemon can derive stable per-room signaling identities.
cli/internal/transport: Join derives a per-room signaling identity
before calling dialSignaling. Real identity is still used for hello
verification and seal/open crypto inside the DataChannel.
pwa/src/transport/flit.ts: split PeerConn.peerId into signalingId
(anchor routing) and cryptoId (seal/open, hello). In pair-room mode
the daemon's signaling ID differs from its real ID, so connectTo and
_onFrom now use trustedPeerId as the cryptoId regardless of what the
anchor reports as the sender. offerByOrder comparison uses real IDs
(trustedPeerId ?? signalingId) so both sides agree. hello verification
now also checks the Ed25519 signature, not just the claimed ID.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- wireFileRecv: replace OnMessage/OnClose channel approach with pion
Detach API so SCTP buffers data regardless of callback timing — fixes
0-byte files caused by pion dispatching data goroutines before OnMessage
was registered
- yaw DC: same Detach API treatment for the control channel
- connectTo: use DetachDataChannels SettingEngine, replace stale PCs
(Connecting/Failed/Disconnected state) instead of returning early;
isCurrent guard in OnConnectionStateChange prevents stale-PC close
from firing OnDisconnected
- offerByOrder: gate maybeOffer on peer ID order so only one side
creates the yaw DC and SDP offer — fixes signaling glare causing
triple connected (verified) logs
- pendingRecvs: promote single pendingRecv slot to map[string]*recvState
so concurrent file offers don't overwrite each other
- peer-left: stop calling resetPeer() so active file DCs aren't closed
before data arrives
- signaling ping: wrap conn.Ping with 10s timeout context so a dead TCP
connection is detected within 30s rather than hanging forever
- signaling read: 3-minute read deadline forces reconnect if anchor
silently evicts the peer from the room (idle timeout)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Auto-accept toggle per trusted peer (keyring.autoAccept); only
activates when peer is cryptographically verified — unverified
connections always prompt regardless of setting
- Disconnect button on connected screen returns to idle with full
state reset
- Copy snip: copies own peer ID as a flit-peer:<base64> string for
sharing out-of-band (no QR needed for initial pairing)
- Add peer by ID: paste a raw hex peer ID or flit-peer: snip in
Invite new tab to add directly to keyring and connect without QR
- Own peer ID always visible at bottom of idle screen
- Restructured known-device row for mobile: secondary actions
(Auto/Rename/Forget) on top line, Connect full-width below
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Ports the yaw/2.1 identity/signaling/WebRTC transport from waste-go to
both a browser PWA (with QR pairing and Web Share Target) and a headless
Go CLI, trimmed to 1:1 ephemeral file transfer only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>