KPS · Key-Pinned Streams

Connection freedom
in the browser.

KPS (Key-Pinned Streams) opens secure, multiplexed byte streams to a server identified by a pinned certificate hash — no domain registrar, no certificate authority, no signalling relay. From the browser over WebRTC, and from native code over QUIC, on one UDP port.

client & server in TypeScript · Go · Rust
Browser dials a KPS server over UDP directly — no signalling server, CA, or domain registrar required.
Why

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.

How it works

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.

1

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.

2

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.

3

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.

Implementations

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.

clientserverWebRTCQUIC
npm install @kpstreams/webrtc-client

Go

Client and server, both transports on one UDP port. A Go module you can go get.

clientserverWebRTCQUIC
go get github.com/privacy-ethereum/kps/libs/go

Rust

Client and server, both transports, async on Tokio. The kps crate.

clientserverWebRTCQUIC
git dependency + vendored patches → libs/rust README
Every client dials every server
Server
ClientTSGoRust
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.

Client — dial an address, open a stream
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
Server — accept connections and their streams
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?;
What you get

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.

Architecture

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.

Browser / native client

Derives the handshake from the address; pins the cert hash

One UDP port

WebRTC/DTLS and QUIC/TLS 1.3 on the same listener

KPS server

One self-signed cert; its hash is its identity

The full details live in the protocol spec and security model.

Dial a key, from anywhere