Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

End-to-end encryption protocol

A Verified-tier provider runs on an ordinary GPU with no enclave, so the network gives you confidentiality from cryptography instead of trusted hardware: the client seals its request to the provider’s public key, every relay (gateway, router, reverse tunnel) sees only ciphertext, and only the provider can open it. The provider seals the reply back to a key the client generated per request.

This page is the wire protocol, so anyone can write a client, including a browser “personal mode” one. The canonical implementation is the Rust crates ogong-e2ee (crypto), ogong-client (consumer), and ogong-provider (the provider’s xwing_e2ee interceptor); this page matches them byte for byte.

Primitives

  • KEM: X-Wing — the IETF hybrid that runs X25519 and ML-KEM-768 together and combines their secrets, so an attacker must break both the classical and the post-quantum part (harvest-now, decrypt-later is covered). A provider publishes its X-Wing encapsulation (public) key in its attested advertisement.
  • AEAD: ChaCha20-Poly1305 — 32-byte key, 12-byte nonce, 16-byte tag.
  • KDF: SHA-256, domain-separated. The one-shot AEAD key is SHA-256("ogong-e2ee-xwing-v1" || shared_secret); the streaming session key is SHA-256("ogong-e2ee-xwing-session-v1" || shared_secret) (a different domain, so the two can never collide).

Model binding (AAD)

Every AEAD operation authenticates an associated-data value that binds the served model, so an on-path relay that rewrites the cleartext x-ogong-model header breaks the tag instead of silently steering you to a different model:

aad = "ogong-e2ee-inference-aad-v1" || u32_le(len(model)) || model_utf8

Request

The client generates a fresh reply keypair per request and seals an envelope to the provider’s X-Wing public key. The reply key rides inside the seal (not a cleartext header), so a relay cannot substitute its own to intercept the response.

envelope   = u32_le(len(reply_pub)) || reply_pub || request_json
sealed_req = xwing_seal(provider_pub, envelope, aad(model))
           = xwing_ciphertext || chacha20poly1305(key = KDF("...xwing-v1", ss), nonce = 0, envelope, aad)

POST it to /v1/chat/completions with:

headervalue
x-ogong-e2eexwing
x-ogong-modelthe model id (so a gateway can route the opaque body)
content-typeapplication/octet-stream
x-ogong-e2ee-reply-tobase64(reply_pub) — legacy/back-compat; the provider trusts the copy inside the seal

The one-shot request nonce is all-zeros: safe because each seal makes a fresh X-Wing encapsulation, so the AEAD key is single-use.

Trusting the provider key. On the Verified tier the client must authenticate the advertised X-Wing key against the provider’s pinned record_signer (a signed key-binding with an expiry) before sealing; on the TEE tier the key is bound into the enclave’s attestation quote. See Trust tiers. Skipping this turns E2EE into blind-seal, which a relay can MITM, so a real client fails closed without a pin.

One-shot response

For a non-streaming reply the provider seals the whole response as one envelope to reply_pub:

header  x-ogong-e2ee: xwing
body    xwing_seal(reply_pub, response_json, aad(model))

The client opens it with its reply secret key.

Streaming response

For a streaming (SSE) reply the provider does one X-Wing encapsulation to reply_pub to establish a shared session key, then seals each chunk under it with cheap symmetric crypto — one KEM, not a seal per chunk. The response is a sequence of length-delimited frames:

header  x-ogong-e2ee: xwing-stream
body    frame*  where each frame = u32_le(len) || payload

  frame 0        payload = xwing_ciphertext            (the KEM; client decapsulates -> session key)
  frame 1..N     payload = chacha20poly1305(
                             key   = KDF("...xwing-session-v1", ss),
                             nonce = u64_le(i) padded to 12 bytes,   // i = 0,1,2,... per chunk
                             chunk_i, aad(model))
  final frame    len = 0                               (clean end-of-stream marker)

The per-chunk nonce is the chunk counter (64-bit little-endian in the low 8 bytes of the 12-byte nonce). Because the session key is fresh per response, no (key, nonce) pair ever repeats — the AEAD safety condition. The counter also authenticates order: a relay that reorders, drops, or splices chunks makes an open fail, and a missing terminal frame is detectable truncation.

To consume: read frame 0, decapsulate it to the session key, then open frames at counter 0,1,2,... until the zero-length terminal, concatenating the plaintext (the original SSE byte stream).

Who runs a client

This protocol is the consumer path, and a consumer is anyone who speaks it — not only a personal app. Two common shapes:

  • A person, for themselves — a CLI or a browser “personal mode” app. A browser client is just this protocol in JS/WASM: the gateway/router blind-relay the opaque sealed bytes (they never hold plaintext), so the browser does all the crypto itself — seal the request to the provider’s authenticated X-Wing key, send the opaque body through the gateway, and open the sealed reply (reassembling the stream with the frame format above). No server-side change is needed.
  • A service or business — a backend that uses OGONG for inference and wraps it in its own product (a chat app, an API, an agent). The operator is the OGONG consumer: it holds the token and pays for the work, so its users never touch OGONG — they sign in and pay however the operator chooses. This is the standard “wrap an inference API in your product” model.

Where the seal happens decides who is shut out of the plaintext

The encryption is identical in both shapes; what differs is which client performs the seal:

  • The end user’s browser seals (the operator only blind-relays): genuinely end-to-end from that user to the GPU — even the operator cannot read it. Use this for a “we can’t see your prompts” product.
  • The operator’s backend seals (users send plaintext to the backend over ordinary TLS, the backend does the OGONG E2EE outward): the OGONG network, relays, and reverse tunnels see only ciphertext, but the operator — being the consumer — does see the plaintext. Normal for any app wrapping an inference API; it is just not end-to-end from the user.

Both are valid; it is the operator’s choice of where the crypto terminates. A client can embed the ogong-client logic directly, or point at a gateway (ogong-gatewayd) that handles tier trust, E2EE, and settlement — so a service can resell verified inference without implementing the low-level protocol, and its customers never deal with the token.

Whichever client seals, it MUST authenticate the provider’s X-Wing key first (Verified: the pinned record_signer binding; TEE: the attestation quote) and fail closed without it — otherwise an on-path relay can hand over its own key and read everything.