Verification and TEE
This page answers one question: what can you check about a response, rather than take on faith? Every provider on the network runs inside a Trusted Execution Environment, and every claim below has a matching way for you to verify it.
Why verification matters
Section titled “Why verification matters”With a centralized API you have the provider’s word and nothing else. Two claims in particular are unfalsifiable from the outside:
- “We ran the model you asked for.” A provider can take payment for a large model and quietly serve a smaller, cheaper one. Nothing in an ordinary response distinguishes the two.
- “We do not look at your data.” Whether anyone reads your prompt is a policy statement, not a property of the system.
Here both claims turn into artifacts you can inspect. Every response is signed inside an enclave that attests the exact model it serves, so a substitution cannot produce a valid signature. And with TeeML, the plaintext never leaves the enclave, so no operator is in a position to read it.
These map onto two trust modes: verified for authenticity, private for authenticity plus confidentiality. See Trust Modes for how to require them.
What every response carries
Section titled “What every response carries”Every Router response includes an x_0g_trace block. When you send verify_tee: true, the Router also validates the provider’s signature and reports the outcome in the same block:
{ "x_0g_trace": { "request_id": "<REQUEST_ID>", "provider": "<PROVIDER_ADDRESS>", "billing": { "input_cost": "85600000000000", "output_cost": "285760000000000", "total_cost": "371360000000000" }, "tee_verified": true }}| Field | What it is |
|---|---|
request_id |
Identifier for this request, useful in support and logs |
provider |
The serving provider’s on-chain address |
billing |
input_cost, output_cost, total_cost for this request |
tee_verified |
Present only when verification was requested |
Requesting verification is one field:
curl https://router-api.0g.ai/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $ZG_API_KEY" \ -d '{"model":"glm-5.2","messages":[{"role":"user","content":"Hello"}],"verify_tee":true}'verify_tee is a 0G extension and is stripped before the request reaches the provider. On multipart/form-data endpoints there is no JSON body to carry it, so pass it as a query parameter: POST /v1/audio/transcriptions?verify_tee=true.
tee_verified has three states, not two:
tee_verified |
Meaning |
|---|---|
true |
The signature was validated successfully |
false |
A signature was present but did not verify — treat the response as untrusted |
| absent | Verification was not requested for this response |
One more artifact lives outside the body: the ZG-Res-Key response header carries the chatID you need to re-verify a response yourself. The body’s id field is a fallback when the header is absent. Because it is primarily a header, you need access to raw HTTP response headers — fetch exposes them directly, and OpenAI SDKs expose them through their raw-response helper.
How to check it yourself: tee_verified: true says the Router reports that it verified the signature. It does not carry the raw signature back to you, so it is a strictly weaker claim than a cryptographic proof in your hands. For most applications that is a reasonable assumption — you already trust the Router to route and bill correctly. If it is not enough for yours, see Verify it yourself.
TEE attestation
Section titled “TEE attestation”“TEE-backed” is not one guarantee. Two methods exist, and the difference is where your plaintext lives.
| TeeML | TeeTLS | |
|---|---|---|
| What runs in the enclave | The model itself | The 0G broker, which relays to an upstream |
| Who sees your prompt in plaintext | Nobody — not 0G, not the hardware operator | The upstream provider |
| Response signed in the enclave | Yes | Yes |
| Verifiable execution | Yes | Yes |
| Sealed inference | Yes | No |
| Satisfies trust mode | standard, verified, private |
standard, verified |
TeeML: the model is inside the enclave
Section titled “TeeML: the model is inside the enclave”The model weights and the inference process run inside the enclave. Your prompt enters encrypted, is decrypted only inside, and the response is generated and signed inside by a key that never leaves. The host machine sees only encrypted traffic.
The consequence is the strong claim: neither 0G nor the operator of the hardware can see your inference data or the inference process. The isolation is enforced by the hardware, so the operator running the box is outside the boundary like everyone else.
This is what private trust mode routes to, and it is the only method that can serve it.
How to check it yourself: read the model’s verifiability field in GET /v1/models, then verify a response independently as described below. The signature ties the response to a signer key that only exists inside an attested enclave.
TeeTLS: an attested relay
Section titled “TeeTLS: an attested relay”The 0G broker, itself running inside a TEE, relays your request to an upstream provider over attested TLS. The broker cannot read your request in transit, and the attested channel means you are talking to a verified enclave rather than something impersonating one.
What TeeTLS does not claim: the upstream provider terminates the request and processes your prompt in plaintext, under its own data policy. The broker being blind does not make the destination blind.
So TeeTLS is a real guarantee about the path and about authenticity, and no guarantee about confidentiality at the endpoint. If your requirement is that no third party ever sees plaintext, TeeTLS does not meet it. Use private.
How to check it yourself: the same independent verification applies. It proves authenticity, not confidentiality — which is the point of the distinction.
Hardware attestation
Section titled “Hardware attestation”An enclave’s claims are worth nothing unless you can check that it is a real enclave. Enclaves run on Intel TDX, with TEE-enabled GPUs for TeeML, and publish a hardware attestation: a signed measurement, rooted in the CPU vendor’s chain of trust, describing what code is running inside.
The trust chain has three links, each independently checkable:
Intel TDX hardware root of trust -> attestation: this is a genuine enclave running this exact code -> enclave signing key: this response came from that enclave -> on-chain teeSignerAddress: and that enclave is this providerAttestation covers the enclave; the per-response signature covers the response. You need both — a real enclave that did not produce your response proves nothing about your response.
How to check it yourself: attestations can be verified independently with the dstack verifier tooling.
For the protocol and network mechanics behind attestation and the on-chain service registry, see the 0G network documentation.
Verification is not privacy
Section titled “Verification is not privacy”A verified response proves authenticity: the real model produced this. It says nothing about who saw your prompt.
A TeeTLS provider produces perfectly verifiable responses while the upstream processes your prompt in plaintext under its own data policy. If your requirement is confidentiality rather than authenticity, verification is the wrong tool: you need private trust mode, which routes only to TeeML providers.
The two compose. private mode plus verify_tee: true gives you a sealed prompt and a checked signature.
What TEE does not cover
Section titled “What TEE does not cover”- Your own client. The prompt is plaintext in your process before you send it. The enclave boundary starts at the provider.
- What you do with the response. Once it is back in your application, it is ordinary data.
- The upstream, under TeeTLS. Covered above; this is why the
privatetier exists.
Verify it yourself
Section titled “Verify it yourself”Trust here comes in three layers, and you choose where to stop.
| Layer | What it is | You trust | Cost to you |
|---|---|---|---|
| 1. Provider signature | The provider signs every response inside its TEE | The TEE hardware and the on-chain signer record | Nothing — always on |
| 2. Router-side check | verify_tee: true makes the Router validate the signature and report tee_verified |
The above, plus the Router’s honesty | One request field |
| 3. Independent verification | You fetch the signature and verify it yourself | The above, minus the Router | An extra call and a chain read |
Most applications stop at layer 1 or 2. Layer 3 exists so that trusting the Router is a choice you make, not one imposed on you.
Layer 1 is always on and costs nothing. Every provider signs its responses with a key that lives inside the enclave, and the corresponding signer address is published in the provider’s on-chain service record. But layer 1 is passive — a signature exists, and nobody has checked it unless someone asks. A signature you never fetch proves nothing. Layers 2 and 3 are two ways of asking. Layer 2 is the verify_tee field described above.
Layer 3: check without trusting the Router
Section titled “Layer 3: check without trusting the Router”Every input the Router uses is public, so you can reproduce the check with no trust in the Router at all. You need two things from the response: x_0g_trace.provider (the provider’s on-chain address) and chatID (from the ZG-Res-Key header, with the body id as fallback).
With the TypeScript SDK, one call reads the chain and contacts the provider directly, with the Router out of the path. Point an ethers provider at https://evmrpc.0g.ai, create a broker with createZGComputeNetworkBroker from @0gfoundation/0g-compute-ts-sdk, and call:
broker.inference.processResponse(providerAddress, chatID)Any wallet works, including a freshly generated one: processResponse only reads the chain and calls the provider’s public signature endpoint, so no funds and no signing are required. It returns three values:
| Result | Meaning |
|---|---|
true |
Independently verified |
false |
Verification failed — treat the response as untrusted |
null |
The provider has no verifiable TEE service, so there is nothing to check |
null is a third state, not a failure: there was nothing to verify. Decide deliberately whether your application accepts that.
From a language with no 0G SDK, the same check is four steps:
- Read the on-chain service record for the
provideraddress over the RPC endpointhttps://evmrpc.0g.ai. It gives youurl,teeSignerAddress, andverifiability. IfadditionalInfo.targetSeparatedis true, useadditionalInfo.targetTeeAddressas the signer instead. - Fetch the signature:
GET {url}/v1/proxy/signature/{chatID}?model={model}returns{text, signature}. - Verify the signature as an EIP-191
personal_signagainst the signer address. Any standard Ethereum library does this. - Compare the signed
textagainst the response content you actually received from the Router.
Step 4 is the one people skip, and it is the one that closes the loop. Steps 1 to 3 prove that a valid signed response exists for that chatID. Only step 4 proves it is the same content you were handed.
Working, runnable code for layers 2 and 3 is in Verify a response.
Related
Section titled “Related”- Verify a response — runnable code for layers 2 and 3
- Trust Modes — turn TeeML into an enforced routing constraint
- Headers — routing headers, including
X-0G-Provider-Trust-Mode - Model catalog — find models by
verifiability