Protocol & wire explorer
Run a query and see every WebSocket frame the SDK sends and receives. The explorer monkey-patches window.WebSocket so it observes every frame the vendored DPF/HarmonyPIR WASM clients produce, then checks the captured traffic against the four privacy invariants documented in the main repo's CLAUDE.md.
1. Pick a backend & run a query
Each box is one PBC group query. Every group is byte-indistinguishable to the server — real queries and dummy padding are colour-equal here because they are colour-equal on the wire.
| seq | t (ms) | sock | dir | label | size | group×kpg | hex preview |
|---|---|---|---|---|---|---|---|
| No frames captured yet. Run a query to start the tap. | |||||||
2. Found vs. not-found wire-shape diff
Run two queries — one known-found, one known-not-found — and check whether the wire shape (frame counts, byte counts, group counts) differs. The privacy guarantee is that they are identical.
The explorer wraps window.WebSocket with a subclass that records every send() call and every received message event before forwarding them to the real socket. The vendored DPF/HarmonyPIR WASM client and the OnionPIR TS client both call the global new WebSocket(url) constructor at connect-time, so the patched subclass picks up every frame they exchange with the server. The frame body is parsed against the documented wire layout ([4B len LE][1B opcode][payload]) to extract count, keysPerGroup, and the opcode label without decrypting any payload bytes — all four privacy invariants are observable on the unencrypted wire-format envelope.
The DPF and HarmonyPIR clients normally upgrade to an encrypted channel after attestation. The explorer skips that upgrade intentionally — with ChaCha20 ciphertext on the wire the invariants would still hold but would no longer be parseable from JS. Production wallets MUST enable the channel; the plaintext mode here is observation-only.