KPS Demo

Connection Freedom in the Browser

Browser vendors migrated the web from http to https in the mid-2010s. That 's' adds encryption via TLS, which is great.

However, https also adds:

  1. You must use a domain controlled by a domain registrar.
  2. You must present a certificate signed by a certificate authority.

KPS (key-pinned stream) restores the freedom to talk to cryptographic identities instead of just those with authority-controlled names.

The trick

  • The address you dial is just <ip>:<port>:<keyhash>. The keyhash is the SHA-256 of the server's self-signed TLS cert, multibase-encoded (e.g. uEi…).
  • KPS is built on the same wire trick as libp2p's WebRTC Direct: the server listens on UDP, both sides synthesize the SDP from the address, the browser accepts the DTLS handshake only if the cert hashes to the pinned value. No signalling server.
  • Unlike the libp2p version, KPS drops everything above DTLS: no Noise XX, no multistream-select, no peer store. Stream name is the data-channel label; the cert hash is the only pinned key.

One dependency left: hosting the client

Everything above removes authority requirements from the server. But the browser app itself is still hosted somewhere — typically on GitHub Pages — which requires https, which requires a CA-signed cert for a registered domain. That's the last authority-controlled link in the chain between you and the server.

A Chromium extension build of the same app loads from chrome-extension://<id>/: trusted at install time rather than by a public CA. Build it yourself with npm run build:extension in demo/web/ and load dist-extension/ as an unpacked extension. Now neither the page nor its connection to the server depends on a domain registrar or a certificate authority.

What runs on top

The transport is the point; this demo bolts two example services onto it, one stream per service.

  • Chat (chat stream) — a public bulletin plus end-to-end encrypted direct messages. Each client mints a fresh Ed25519 identity; their DM key is signed by it, so the server can route messages but can't read or forge DMs.
  • Block explorer (eth-rpc stream) — the server proxies JSON-RPC calls to curated public endpoints for Ethereum, Arbitrum, Optimism, Base, and Polygon; the browser renders a small live explorer from the responses. The server sees your queries; the public RPC endpoints only see the server's IP.

How the trust works

  1. The server generates a self-signed TLS certificate at first start, persisted to kps.key.
  2. It encodes the SHA-256 of that certificate into its address: <ip>:<port>:<keyhash>.
  3. You paste that address into the browser. The browser now knows the endpoint and the exact certificate fingerprint to expect.
  4. The browser dials the IP and UDP port directly. It accepts the DTLS handshake only if the cert hashes to the pinned value.

No domain. No certificate authority. No signalling relay. If the address reached you intact, the connection cannot be intercepted.

Run a server

Needs Go 1.24+. From the repo root:

cd demo/server
go run .

Output looks like:

listening; dial from the browser:
  192.168.1.50:41108:uEi...

On first run the server writes its TLS key to kps.key and the chosen UDP port to state.json. Both are reused on subsequent starts so the address stays byte-identical across restarts.

Pick the right address

KPS is just UDP underneath. The browser must reach the server's UDP port directly:

  • Same machine — use the 127.0.0.1 address.
  • Same LAN — start the server with -ip 192.168.x.x.
  • Across the internet — public IP, or forward the UDP port.