Skip to content

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.

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.

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:

Terminal window
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-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

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.

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.

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 provider

Attestation 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.

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.

  • 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 private tier exists.

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:

  1. Read the on-chain service record for the provider address over the RPC endpoint https://evmrpc.0g.ai. It gives you url, teeSignerAddress, and verifiability. If additionalInfo.targetSeparated is true, use additionalInfo.targetTeeAddress as the signer instead.
  2. Fetch the signature: GET {url}/v1/proxy/signature/{chatID}?model={model} returns {text, signature}.
  3. Verify the signature as an EIP-191 personal_sign against the signer address. Any standard Ethereum library does this.
  4. Compare the signed text against 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.