⚠️ Work in progress. APIs may change.
A minimal Oblivious HTTP (RFC 9458) client. It handles the parts every OHTTP client needs: BHTTP inner message construction and parsing, request encapsulation / response decapsulation, and key config parsing (RFC 9540), so you can tunnel requests through a relay to a gateway without exposing them to either.
The client uses whichever KEM a gateway advertises, so it works with every KEM
ohttp implements: DHKEM(X25519, HKDF-SHA256),
DHKEM(P-256, HKDF-SHA256), and X-Wing (ML-KEM-768 + X25519).
The experimental secp256k1 feature adds DHKEM(secp256k1, HKDF-SHA256) via
bitcoin-ohttp, alongside the KEMs
above. Each key config is handled by the backend that supports its KEM.
By default the crate does no network IO. You encapsulate a request, send the outer request with whatever HTTP client you like, then feed the raw response bytes back in to decapsulate:
use ohttp_client::{OhttpClient, Url, parse_key_config};
let client = OhttpClient::new(
Url::parse("https://relay.example/")?,
Url::parse("https://target.example/")?,
parse_key_config(&key_bytes)?, // GET the gateway's key endpoint yourself
);
let (req, ctx) = client.encapsulate("POST", "/resource",
&[("content-type", "text/plain")], &[], Some(b"hello"))?;
let outer_response_body = send(&req); // your HTTP client
let response = ctx.decapsulate(&outer_response_body)?;This keeps the crate portable. It works anywhere, including in the browser.
-
bitreq: opt into doing the IO for you viabitreq.OhttpClient::from_gatewayfetches the key config (tunneled through the relay) and a request builder handles encapsulate / send / decapsulate:client.post("/resource").body("hello").send().await?. -
wasm:wasm-bindgenexports for browser hosts, plus the JS RNG backend. Keep using the sans-IO flow from JS: encapsulate,fetchthe outer request yourself, then decapsulate. Seejs/for the wrapper and an example. -
harness: exportsohttp_client::harness::TestHarness— a local target, OHTTP gateway, and OHTTP relay (which also tunnels key fetches viaCONNECT) for integration tests. Also builds theohttp-test-harnessbinary used by the JS e2e. Pull it in as adev-dependency:[dev-dependencies] ohttp-client = { version = "0.0.2", features = ["harness"] }
use ohttp_client::harness::TestHarness; let h = TestHarness::start(); // h.relay_url(), h.gateway_url(), h.target_url()
The crate builds for wasm32-unknown-unknown. Enable wasm for wasm-bindgen
exports, or wasm_js alone if you bind the Rust API without wasm-bindgen.
WASI targets need no extra feature.
Common tasks are wrapped in the justfile:
just test # cargo test --all-features
just check-wasm # verify the wasm32 build
just build-wasm # build the web package into js/pkg/
just test-js # build wasm and run the JS e2e
just check # full suite: fmt, clippy, tests, wasm, js e2e, auditOr directly with cargo:
cargo test --all-features
cargo check --target wasm32-unknown-unknown --features wasmSee LICENSE.