The web can only talk to names
Browsers migrated the web from http to https in the mid-2010s. That s adds TLS encryption — but it means you need permission from a certificate authority for your origin (usually a domain from another authority). KPS restores the freedom to talk to a cryptographic identity directly.
The certhash is the identity
The certhash is the SHA-256 of the server's self-signed certificate, multibase-encoded. Pin it, and the connection can't be intercepted.
Encode the key into the address
The server hashes its self-signed cert and publishes ip:port:certhash. If that string reaches you intact, you know exactly which key to expect.
Synthesize the handshake
Both sides derive the SDP (and the ICE password) from the address alone — no signalling server. The browser just starts a WebRTC/DTLS handshake at the UDP endpoint.
Verify certificate hash or hang up
The client accepts the handshake only if the presented certificate hashes to the pinned value — the browser over DTLS, native clients over QUIC's TLS 1.3.
One wire, three languages
KPS has a client and a server in TypeScript, Go, and Rust — all speaking the identical wire protocol. Any client dials any server, browser included, verified end-to-end in CI.
TypeScript
Browser client over WebRTC, Node client over QUIC, and a Node server. Typed, published to npm.
npm install @kpstreams/webrtc-client
Go
Client and server, both transports on one UDP port. A Go module you can go get.
go get github.com/privacy-ethereum/kps/libs/go
Rust
Client and server, both transports, async on Tokio. The kps crate.
| Server | |||
|---|---|---|---|
| Client | TS | Go | Rust |
| Browser WebRTC | ✓ | ✓ | ✓ |
| Go QUIC / WebRTC | ✓ | ✓ | ✓ |
| Rust QUIC / WebRTC | ✓ | ✓ | ✓ |
Same ip:port:certhash address, same UDP port. Browsers dial over WebRTC; native clients default to QUIC.
import { dial } from '@kpstreams/webrtc-client'; const conn = await dial('203.0.113.5:41108:uEiA…9Qw'); const stream = await conn.openStream(); // reliable, multiplexed bytes
import kps "github.com/privacy-ethereum/kps/libs/go" conn, _ := kps.Dial(ctx, "203.0.113.5:41108:uEiA…9Qw") stream, _ := conn.OpenStream(ctx) // reliable, multiplexed bytes
use kps::dial; let conn = dial("203.0.113.5:41108:uEiA…9Qw").await?; let stream = conn.open_stream().await?; // reliable, multiplexed bytes
import { listen } from '@kpstreams/server'; const srv = await listen({ port: 41108 }); console.log(srv.address('203.0.113.5')); // ip:port:certhash const conn = await srv.accept(); const stream = await conn.acceptStream();
import kps "github.com/privacy-ethereum/kps/libs/go" ln, _ := kps.Listen(ctx, ":41108", kps.Options{}) fmt.Println(ln.Address("203.0.113.5")) // ip:port:certhash conn, _ := ln.Accept(ctx) stream, _ := conn.AcceptStream(ctx)
use kps::{listen, ListenOptions}; let ln = listen(":41108", ListenOptions::default()).await?; println!("{}", ln.address("203.0.113.5")); // ip:port:certhash let conn = ln.accept().await?; let stream = conn.accept_stream().await?;
A transport, stripped to the essentials
Key-pinned identity
Talk to a cryptographic identity, not an authority-controlled name. The cert hash is the only pinned key — no CA, no domain.
Two transports, one port
Browsers speak WebRTC; native clients (Go today, Rust next) dial the same address over QUIC on the same UDP port. One listener accepts both.
Unnamed byte streams
No Noise, no multistream-select, no peer store. Reliable, multiplexed byte streams — applications frame and route inside the bytes.
Datagrams too
Every connection also carries unreliable, unordered, size-limited datagrams alongside its reliable streams.
Hard to fingerprint
The ICE password derives from the certhash, the QUIC ALPN carries no KPS marker, and the certificate exposes no identifying metadata.
Extension build
Ship the browser client as a Chromium extension — trusted at install time, so even the page stops depending on a public CA.
Nothing in the middle
A browser dials the server's UDP port directly. No registrar names it, no authority vouches for it, no relay carries the handshake.
Derives the handshake from the address; pins the cert hash
WebRTC/DTLS and QUIC/TLS 1.3 on the same listener
One self-signed cert; its hash is its identity
The full details live in the protocol spec and security model.