anonrouterdocs

End-to-end encrypted inference

Attest an enclave, encrypt locally, relay provider-bound ciphertext through AnonRouter, and decrypt Chutes, NEAR AI, or Venice responses.

Use E2EE when your application must keep prompts and model output opaque to AnonRouter. Your client verifies fresh enclave evidence, encrypts to the key in that evidence, and sends only ciphertext plus fixed routing metadata. Plaintext is recovered only by the selected model enclave and your client.

E2EE and hardware verification are different claims

A working encrypted transport proves that AnonRouter relayed ciphertext; the attestation level says how strongly the destination key was verified. Current Chutes, NEAR AI, and Venice E2EE routes reach provider-attested, not hardware-verified. Do not encrypt unless the evidence, nonce, route, measurements, and encryption key all pass the checks your policy requires.

Provider support

ProviderAnonRouter E2EE transportCurrent attestation levelStreamingPublic request receipt
ChutesML-KEM-768 whole-body ciphertextprovider-attestedNoProvider does not expose one
NEAR AI directX25519/XChaCha20 field ciphertext, version 2provider-attestedYesNot for the current streaming E2EE call
Venicesecp256k1/AES-GCM field ciphertextprovider-attestedYesYes, including completed streams
TinfoilNot exposed through AnonRouterTEE reaches sdk-verifiedProvider does not expose one

Tinfoil's upstream EHBP transport encrypts the model selector. Its documented outer metadata does not let AnonRouter prove that the encrypted model is the one bound to the ticket and reservation. AnonRouter therefore fails closed instead of advertising Tinfoil as E2EE.

The safe request sequence

Every E2EE request follows the same security order:

  1. Select one exact canonical model and provider route. E2EE never uses Auto.
  2. Generate fresh client key material and a fresh 32-byte attestation nonce.
  3. Fetch enclave evidence through the appropriate flow below.
  4. Verify the raw evidence independently, including its nonce, endpoint, measurements, debug state, and encryption-key binding. Enforce the minimum verification_level your application accepts.
  5. Mint a short-lived inference ticket for that same model and provider with e2ee: true.
  6. Encrypt locally and send the ticket, fixed E2EE headers, and ciphertext to the relay. Do not send your API key to the relay in a split deployment.
  7. Decrypt the response locally. If a request fails, repeat from step 2 with fresh evidence, key material, nonces, and tickets.

AnonRouter never falls back an E2EE request to another provider, endpoint, or plaintext route. That is essential: a key attested for one enclave cannot be safely reused for a different destination.

The examples use this base URL:

export ANONROUTER_BASE_URL="https://api.anonrouter.ai/v1"
export ANONROUTER_API_KEY="ar_REPLACE_WITH_YOUR_API_KEY"

1. Select an exact E2EE route

Read the live catalog before encrypting:

curl "$ANONROUTER_BASE_URL/models" \
  -H "Authorization: Bearer $ANONROUTER_API_KEY"

Choose a route whose privacy_class is e2ee, and retain its canonical model, provider, context window, and maximum output. Use the canonical model in the ticket and public chat request. Never infer E2EE support from a -TEE suffix.

2. Fetch and verify attestation

Public verified attestation

Chutes and Venice clients can fetch a normalized server verdict together with the raw provider evidence:

NONCE="$(openssl rand -hex 32)"

curl --get "$ANONROUTER_BASE_URL/tee/attestation" \
  -H "Authorization: Bearer $ANONROUTER_API_KEY" \
  --data-urlencode "model=qwen/qwen3-32b" \
  --data-urlencode "provider=chutes" \
  --data-urlencode "nonce=$NONCE"

The response binds model, provider, route_id, privacy_class, the normalized attestation result, and the raw evidence. Require at least:

attestation.status == "ok"
attestation.privacy_modality == "e2ee"
attestation.supports_client_opaque_e2ee == true
attestation.nonce == the nonce your client generated
attestation.verification_level is accepted by your policy
every required attestation.checks item passed

The normalized result is AnonRouter's verification verdict. A client claiming independent verification must also run the provider and silicon-vendor checks over evidence; merely reading the normalized fields is not independent verification. See TEE verification for the checks and limits of each level.

NEAR v2 encryption-key attestation

NEAR's direct E2EE key uses an Ed25519 attestation flow. Obtain a single-use, model-bound attestation ticket from the authenticated control endpoint:

ATTESTATION_TICKET="$(curl -fsS \
  "$ANONROUTER_BASE_URL/inference/attestation-tickets" \
  -H "Authorization: Bearer $ANONROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"openai/gpt-oss-120b","provider":"near-ai"}' \
  | jq -r .ticket)"

NONCE="$(openssl rand -hex 32)"

curl -fsS "$ANONROUTER_BASE_URL/tee/attestation" \
  -H "X-AnonRouter-Ticket: $ATTESTATION_TICKET" \
  -H "Content-Type: application/json" \
  -d "{\"nonce\":\"$NONCE\"}"

This relay returns { "evidence": ... } without interpreting it. Independently verify NEAR's TDX and NVIDIA evidence, the direct endpoint, measurements, nonce, and the Ed25519 encryption-key commitment before using the model key. The ticket is short-lived, single-use, and valid only for the selected direct route. The two currently reviewed NEAR E2EE routes are openai/gpt-oss-120b and z-ai/glm-5.2.

Do not substitute X-Model-Pub-Key from an unverified response. That header is for NEAR's cloud gateway and is deliberately rejected on AnonRouter's direct path.

3. Mint the inference ticket

After attestation passes, mint a separate inference ticket:

INFERENCE_TICKET="$(curl -fsS \
  "$ANONROUTER_BASE_URL/inference/tickets" \
  -H "Authorization: Bearer $ANONROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-oss-120b",
    "provider": "near-ai",
    "e2ee": true,
    "operation": "chat",
    "max_tokens": 256
  }' | jq -r .ticket)"

The ticket and encrypted request must name the same model, provider, operation, reasoning controls, and output limit. A ticket expires quickly and can be redeemed only once.

For Chutes whole-body E2EE, set max_tokens to the route's complete published maximum. Token usage is itself encrypted, so AnonRouter reserves and captures the full conservative context ceiling rather than estimating plaintext from ciphertext length.

NEAR AI version 2

NEAR encrypts each text field separately:

  1. Convert the attested model Ed25519 public key to X25519.
  2. Generate a fresh ephemeral X25519 key for every encrypted field.
  3. Derive the shared secret with X25519 ECDH.
  4. Derive 32 bytes with HKDF-SHA256, no salt, and info = "ed25519_encryption".
  5. Encrypt UTF-8 plaintext with XChaCha20-Poly1305 and a fresh 24-byte nonce.
  6. Lowercase-hex encode this wire value:
[ephemeral X25519 public key: 32 bytes]
[XChaCha20-Poly1305 nonce: 24 bytes]
[ciphertext and 16-byte authentication tag]

Generate an Ed25519 client keypair and retain the private key only in your client. Send the public key and encrypted message fields:

curl -N "$ANONROUTER_BASE_URL/chat/completions" \
  -H "X-AnonRouter-Ticket: $INFERENCE_TICKET" \
  -H "X-AnonRouter-E2EE-Provider: near-ai" \
  -H "X-Signing-Algo: ed25519" \
  -H "X-Client-Pub-Key: $CLIENT_ED25519_PUBLIC_KEY_HEX" \
  -H "X-Encryption-Version: 2" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"openai/gpt-oss-120b\",
    \"messages\": [{\"role\":\"user\",\"content\":\"$ENCRYPTED_FIELD_HEX\"}],
    \"max_tokens\": 256,
    \"stream\": true
  }"

Decrypt encrypted content or reasoning_content values in the SSE response with the retained client Ed25519 private key, converted to X25519. Reject a field that is too short, malformed, or fails Poly1305 authentication.

AnonRouter validates only the fixed header vocabulary and ciphertext shape. It does not receive a private key or parse the encrypted message as plaintext. The direct enclave domain must exactly match the reviewed route; a missing or mismatched discovery result fails closed.

See NEAR's E2EE chat-completions guide and model verification guide for the independent client verifier.

Chutes ML-KEM whole-body transport

The verified Chutes evidence contains e2e_instances. Select an instance only after verifying its UUID, ML-KEM-768 public key, short-lived nonce set, and SHA256(caller nonce || ML-KEM public key) commitment in TDX report data.

Build the request with Chutes' transport:

  1. Generate an ML-KEM-768 response keypair.
  2. Add its base64 public key as e2e_response_pk inside the JSON request.
  3. Gzip the JSON.
  4. Encapsulate to the attested instance's ML-KEM-768 public key.
  5. Derive the request key with HKDF-SHA256. The salt is the first 16 bytes of the ML-KEM ciphertext and info = "e2e-req-v1".
  6. Encrypt with ChaCha20-Poly1305 using a fresh 12-byte nonce.
  7. Concatenate [ML-KEM ciphertext: 1088][nonce: 12][ciphertext + tag].

The encrypted inner request uses the provider model identifier, such as Qwen/Qwen3-32B-TEE; the outer ticket stays bound to the canonical qwen/qwen3-32b route. Relay the bytes without JSON or base64 wrapping:

curl "$ANONROUTER_BASE_URL/e2ee/chat/completions" \
  -H "Content-Type: application/octet-stream" \
  -H "X-AnonRouter-Ticket: $INFERENCE_TICKET" \
  -H "X-AnonRouter-E2EE-Provider: chutes" \
  -H "X-Chutes-Instance-Id: $ATTESTED_INSTANCE_ID" \
  -H "X-Chutes-E2E-Nonce: $SINGLE_USE_NONCE" \
  --data-binary @encrypted-request.bin \
  --output encrypted-response.bin

Decapsulate the first 1088 response bytes with the retained response secret key. Derive the response key with the first 16 ciphertext bytes as salt and info = "e2e-resp-v1", authenticate/decrypt with ChaCha20-Poly1305, then gunzip and parse the JSON locally.

The endpoint is non-streaming. Use each Chutes nonce once and retry only after fetching fresh evidence. The official Chutes E2EE transport repository is the reference implementation for framing and crypto.

Venice field encryption

Fetch public verified attestation for the exact Venice E2EE model and use its attestation.attested_encryption_key only after every required check passes. For each field:

  1. Generate an ephemeral secp256k1 keypair.
  2. Derive the ECDH shared secret with the attested model public key.
  3. Derive 32 bytes with HKDF-SHA256 and info = "ecdsa_encryption".
  4. Encrypt with AES-256-GCM and a fresh 12-byte nonce.
  5. Hex encode [uncompressed ephemeral public key: 65][nonce: 12][ciphertext + tag].

Send a streaming request with the exact Venice header vocabulary:

curl -N "$ANONROUTER_BASE_URL/chat/completions" \
  -H "X-AnonRouter-Ticket: $INFERENCE_TICKET" \
  -H "X-AnonRouter-E2EE-Provider: venice" \
  -H "X-Venice-TEE-Client-Pub-Key: $CLIENT_PUBLIC_KEY_HEX" \
  -H "X-Venice-TEE-Model-Pub-Key: $ATTESTED_MODEL_PUBLIC_KEY_HEX" \
  -H "X-Venice-TEE-Signing-Algo: ecdsa" \
  -H "Content-Type: application/json" \
  -d "{
    \"model\": \"venice/e2ee-gpt-oss-20b-p\",
    \"messages\": [{\"role\":\"user\",\"content\":\"$ENCRYPTED_FIELD_HEX\"}],
    \"max_tokens\": 256,
    \"stream\": true
  }"

Decrypt response fields locally with your client private key. Venice requires streaming for its E2EE transport. Follow Venice's TEE and E2EE model guide for provider-side encryption and attestation details.

After a completed Venice stream, you can retrieve its enclave receipt through the public TEE signature service. The receipt proves the provider's signed enclave events; it does not claim that Venice signed AnonRouter's exact public wire bytes.

What AnonRouter can and cannot see

DataControl planeRelayProvider workerVerified enclave
Account/API keyYesNoNoNo
Single-use ticketIssues itRedeems itDispatch fence onlyNo
E2EE ciphertextNoYes, opaqueYes, opaqueDecrypts it
Plaintext prompt/responseNoNoNoYes
Provider credentialNoNoYesProvider-specific

Applications should avoid logging ciphertext too. Although encrypted, it can remain sensitive if a client key is later compromised. Raw prompts, responses, private keys, and decrypted output should never be added to attestation caches or telemetry.

Launch limits and failure behavior

  • Field E2EE accepts text user and system messages, requires a positive explicit output limit, and requires streaming. Tool calls, tool messages, images, and plaintext message fields are rejected.
  • Chutes whole-body E2EE is non-streaming and intended for text chat. The relay never parses the encrypted JSON to infer capabilities or token usage.
  • Automatic routing and transparent provider fallback are disabled. A retry requires fresh attestation and a fresh inference ticket.
  • Reusing an attestation ticket, inference ticket, Chutes nonce, or ephemeral field key is unsafe and rejected where the protocol exposes that state.
  • tee_signature_not_supported means the provider has no compatible request receipt; it does not mean attestation or encrypted inference failed.
  • provider-attested is an honest current ceiling, not an alias for hardware-verified.

Production checklist

  • Pin reviewed provider measurements, endpoint identities, release digests, and verifier versions in your client policy.
  • Use a cryptographically secure random generator for every nonce and key.
  • Compare the attested route and model to the ticket before encryption.
  • Reject expired evidence, debug-enabled evidence, a failed required check, an unexpected verification level, or a changed encryption key.
  • Keep API keys on your trusted control-side backend and private keys on the encrypting client.
  • Treat AEAD authentication failure as a terminal security error; do not return partial plaintext.
  • Never retry through a weaker privacy class.

For the exact attestation checks, measurement policies, request receipts, and provider-by-provider limitations, continue with TEE verification and encrypted inference.

On this page