hello's fingerprint-bound signature was the actual gate for `verified`
on both the CLI and PWA, and dtlsFP() only ever parses sha-256
fingerprints. Different WebRTC stacks report the DTLS cert hash under
different algorithms (WebKit: sha-512, pion/Chromium: sha-256), so a
legitimate cross-stack peer could never produce a verifiable hello —
degrading to permanently "unverified", or on the Go side, never
sending hello at all. Matches an interop gotcha waste-go's yaw2 docs
recently called out explicitly.
Real authentication already happens over the sealed signaling channel
during the ekey exchange (crypto.Verify), same as waste-go. Added
peerAuthed to the Go session (mirroring the PWA's existing field),
set once a sealed box from the peer opens successfully, and gate
`verified` on peerAuthed + hello.id matching instead. The fingerprint
signature is still sent/checked when both sides have a parseable
fingerprint, but only logged on mismatch — never blocking.
No behavior change for today's pion<->Chromium pairings (both sha-256).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
npm ci was failing with "Missing: @emnapi/runtime@1.11.1 from lock
file" — a stale lockfile entry for rolldown's optional wasm32-wasi
binding's transitive deps, unrelated to any feature work. Blocked
deploy-pwa.sh, which uses npm ci for a clean install.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Uses the waste-go anchor's new EXT-009 presence_query to check each
known device's reachability without a full connect attempt. Each
pairing already has its own deterministic anchor room, so this is one
short-lived query per device (auto-refreshed every 15s while the
Known Devices tab is open), not a persistent connection held per
pairing while idle.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Closing the file DataChannel can trigger SCTP/ICE cleanup before the
file-done control message is flushed, causing a 15 s timeout on the
receiver. Send file-done first on both the Go and TS sender paths.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>