0G TeeML inference

July 29, 2026 · View on GitHub

✅ UPDATE — fully recomputed on a genuine enclave (0G Compute mainnet, glm-5.2). 0G gave us mainnet access; we ran the broker path (processResponse) on a real glm-5.2 action and recomputed the entire chain — no tee_verified flag. node verify-enclave.mjs (reads glm52-enclave.json) reproduces all of it: EIP-191 signer recovery → 0xA46EA4…, sha256(request)==H(req) and sha256(response)==H(resp) (both client-recomputable — a genuine enclave signs the plain H(req):H(resp), unlike the relay), the TDX quote's RTMR event-log replay (SHA-384, 4/4), and report_data == signer == on-chain registry teeSignerAddress. The chain: live response ← signed by → 0xA46EA4 ← attested by → the enclave's TDX quote ← declared by → the on-chain 0G registry. Frozen as conformance profile tee-inference-enclave.v0 (recompute-kit#2, 9/9). The relay analysis below stands as the honest baseline; this is what a genuine enclave lets you do.

✅ + the quote's HARDWARE root of trust, recomputed too (Intel PCS lane, dcap-qvl). node verify-dcap.mjs parses the PCK cert chain embedded in the quote and verifies each signature up to the Intel SGX Root CA — pinned to Intel's known key 44a0196b…74d3 — then the QE-report signature (PCK leaf), the attestation-key↔QE binding (report_data), and the TD-quote signature (att key). So the quote is provably produced by a genuine Intel-provisioned TDX part, checked to Intel's root — no vendor SDK, no trusted verifier. Same core runs client-side in the browser on the live panel (@peculiar/x509 + Web Crypto). The /verify enclave panel is now 9 recomputed-green + 1 residual — the only residual left is the known-good image (MRTD == 0G's published glm-5.2 measurement), pending 0G publishing that measurement. TCB-status / CRL freshness (Intel PCS collateral) stays a documented residual.


Part 1 — the relay baseline (Galileo)

One real signed inference on the 0G Galileo TeeML provider, pulled from the SDK's actual processResponse source path (@0gfoundation/0g-compute-ts-sdkbroker/response.jsbroker/verifier.js) — not a spec written from the outside. verify-check1.mjs re-runs the recovery from teeml-sample.json so you can reproduce it yourself: don't trust — recompute.

  • provider 0xa48f01287233509FD694a22Bf840225062E67836 (qwen/qwen2.5-omni-7b)
  • signer 0x83df4B8EbA7c0B3B740019b8c9a77ffF77D508cF
  • chain 0G Galileo (16602)

The verify path (from source)

// response.js — ResponseProcessor.processResponse
const ResponseSignature = await Verifier.fetchSignatureByChatID(svc.url, chatID, svc.model); // GET {url}/v1/proxy/signature/{chatID}?model={model} -> { text, signature }
return Verifier.verifySignature(ResponseSignature.text, ResponseSignature.signature, signingAddress);

// verifier.js
static verifySignature(message, signature, expectedAddress) {
  const messageHash      = ethers.hashMessage(message);              // EIP-191 personal_sign prefix
  const recoveredAddress = ethers.recoverAddress(messageHash, signature);
  return recoveredAddress === expectedAddress;
}

So: preimage = ResponseSignature.text, hashed EIP-191 (ethers.hashMessage), signature is 65-byte secp256k1 (r,s,v), recovered address must equal the TEE signer.

The signed preimage is a structured commitment (NOT the answer)

H(request) : H(response) : providerType : providerIdentity : H(session)

captured value:

c8d4932769c4be14a5c4614ff1c31dee79aef1bd842d656fe1f39992d247fb51:d2fe539f9eaec3aef9f01c8a19a6dd43816d74a1d5e189e418c906b1769fef4a:centralized:aliyun:cc7c08805b2b0c44f53faae5d917ac07367fd9112971ca2405b8fa2e4b0acd51
  • encoding: ASCII, :-joined; hashes are lowercase-hex sha256.
  • domain separation: only EIP-191's "\x19Ethereum Signed Message:\n"+len prefix. No app-specific tag.
  • anti-replay: chatID / zg_res_key is NOT in the signed bytes — it is only the fetch key for the signature. Replay-binding is transport-level (the sig is retrievable only by the chatID holder, briefly), not cryptographic.
  • canonicalization: the broker hashes as sha256(JSON), not RFC-8785 JCS — confirmed H(response) == sha256(JSON.stringify(completion)). So the TEE's digests are in the broker's scheme, distinct from the request_jcs_sha256 / response_jcs_sha256 fields in the sample. Bind through the broker scheme, or prove the JCS↔broker-hash correspondence over the same bytes.

Preimage construction — traced to the SERVER source (not inferred)

The client SDK only verifies hashMessage(text); the text is built server-side (0gfoundation/0g-serving-broker api/inference/internal/ctrl/signing.go signCentralizedRoutingProofapi/common/tee/tls.go FormatRoutingProofText):

text = requestSha256 : responseSha256 : providerType : providerIdentity : tlsCertFingerprint
sig  = crypto.Sign(accounts.TextHash([]byte(text)), providerSigner)   // EIP-191
  • requestSha256 = sha256(reqBody)reqBody is the body the broker forwards UPSTREAM (DashScope), enclave-internal bytes. Confirmed: no client canonicalization reproduces it (wire body / JCS / sorted-keys / +stream all miss). The broker signature over hash1 is established, but the client cannot verify the hidden forwarded body is byte-equal to its request → request binding is broker-asserted, not independently recomputable from the client vantage (distinct from a hardware attestation).
  • responseSha256 = sha256(respData) = sha256(JSON(completion)) (established).
  • tlsCertFingerprint = SHA256(upstream leaf TLS cert) — a routing proof, constant across independent inferences (same DashScope endpoint cert), NOT a session nonce. So it provides no anti-replay; combined with chatID being absent from the preimage, there is no cryptographic replay binding at all.

Evidence class per check

#checkstatus on this provider
1ecrecover(EIP-191(preimage)) == signerRECOMPUTED ✅verify-check1.mjs recovers 0x83df…508cF
2request/response digest bindingresponse RECOMPUTED ✅ (H(resp)=sha256(JSON(completion))); request needs the verbatim outgoing HTTP body (one capture detail)
3asigner ↔ provider identityrecomputable via the 0G provider registry (0x83df0xa48f)
3braw dstack enclave quoteNOT AVAILABLE — this provider is a relay (see below)

The finding that matters: this provider has no enclave attestation

The provider's own /v1/proxy/attestation/report returns, verbatim:

"LLM attestation report is not available for this provider. This service forwards to an upstream API without local TEE attestation."

The response headers confirm it (x-dashscope-call-gateway: true): the model runs on Alibaba Cloud DashScope; 0xa48f is a signing relay, not an enclave. The preimage says so itself: …:centralized:aliyun:….

A scan of the entire Galileo TeeML population = 2 providers, neither an enclave: the aliyun relay above, and 0x4b2a (image-edit) whose attestation endpoint is a plain 404. So the raw dstack quote / independent attestation parsing is not obtainable on Galileo today — blocked at the network level, not by effort. It needs 0G to point at (or stand up) a genuine-enclave TeeML provider / the Galileo TEE oracle.

So, stated precisely:

  • TEE execution reported: yes. SDK verification reported: yes.
  • Independent signature verification: ESTABLISHED (check 1 — reproduce with verify-check1.mjs).
  • Independent request/response digest binding: response established, request pending the verbatim body.
  • Independent attestation parsing: not establishable on Galileo today (no provider emits a quote).

For this provider, "TeeML" = a broker signature over its own digests of (request, response) + identity — not a hardware quote that the model ran in a sealed enclave. The recompute discipline caught a real overclaim in the wild. A conformance vector can carry checks 1–3 without collapsing them, shipping check-3b as an explicit "attestation unavailable (relay provider)" amber — never a green — until a genuine enclave provider exists.

Files

  • teeml-sample.json — the full artifact: preimage, 65-byte signature, the endpoint's raw {text, signature}, the JCS canonical forms + hashes, raw completion + response headers, and the "not available" attestation body.
  • verify-check1.mjsnode verify-check1.mjs → independently recovers the signer from the sample (check 1).
  • capture-teeml.ts / scan-teeml.ts — how the sample and the provider scan were produced.