0G Compute providers that advertise verifiability == "TeeML" run inside a Trusted Execution Environment (TEE) and sign every response they produce. The SDK gives you three tools to verify the integrity of a provider and its responses:
  • verify_service — comprehensive provider attestation check before you use it
  • process_response — validate a specific response’s TEE signature
  • Attestation download helpers — fetch raw quotes and signatures for custom verification

The verifiability field

service = broker.inference.get_service(provider_address)
print(service.verifiability)       # "TeeML" or ""
print(service.is_verifiable())     # True / False
  • "TeeML" — provider runs in a TEE (SGX, TDX, DStack, or GPU attestation)
  • Empty string — unverifiable provider; responses are not cryptographically bound
For non-verifiable services, process_response always returns True (nothing to validate). To filter at runtime, use the is_verifiability type guard:
from zerog_py_sdk import is_verifiability

if is_verifiability(service.verifiability):
    ...

Verify a provider end-to-end

verify_service performs a full attestation check and returns a typed VerificationResult dataclass. It is silent by default — pass an on_log callback to stream progress, or iterate result.steps after the call to replay the verification trace.
result = broker.inference.verify_service(
    provider_address = "0xProvider...",
    output_dir       = "./reports",    # optional — save JSON report
)

print(f"Success:         {result.success}")
print(f"Model:           {result.model}")
print(f"Verifiability:   {result.verifiability}")
print(f"TEE signer:      {result.tee_signer}")
print(f"Signer match:    {result.signer_match}")
print(f"Attestation:     {result.attestation_verified}")
if result.reports_generated:
    print(f"Report saved to: {result.reports_generated[0]}")

Stream progress while running

on_log fires once per verification step with a VerificationStep (type, message):
result = broker.inference.verify_service(
    provider_address,
    on_log = lambda step: print(f"[{step.type}] {step.message}"),
)
Or replay the same trace after the fact — result.steps always carries the full sequence, even when on_log is None:
result = broker.inference.verify_service(provider_address)
for step in result.steps:
    print(f"[{step.type}] {step.message}")
The SDK never writes to stdout from verify_service itself — matching the TypeScript SDK’s behaviour. If you want terminal output, opt in via on_log (or result.steps). This makes the method safe to use inside web servers, notebooks, and background workers without polluting their logs.

What it does

  1. Fetches the provider’s current service metadata from the Inference contract.
  2. Requests the TEE quote from <provider_url>/v1/quote.
  3. Extracts the TEE signer address from the attestation report, auto-detecting the format:
    • SGX / TDX — standard report_data field (base64-decoded)
    • DStack — compose-based verification via nested evidence.quote.report_data
    • GPU attestationevidence.gpu_evidence
  4. Optionally verifies the quote against the on-chain Automata contract (non-blocking — unavailable verification is logged, not fatal).
  5. Compares the extracted signer with the signer recorded on-chain (if you’ve acknowledged the provider).
  6. Writes a JSON report to output_dir if provided.

Result structure

VerificationResult is a dataclass — access fields with result.field, not result["field"].
FieldTypeDescription
successboolOverall validity — True when the quote is available and signers match (or the format is DStack/GPU with a valid attestation)
stepslist[VerificationStep]Every progress entry emitted during verification
providerstrProvider address
service_type, model, verifiabilitystrFrom service metadata
tee_signerstr | NoneExtracted from the quote
expected_signerstr | NoneSigner recorded on-chain, if available
signer_matchbool | NoneTrue if extracted and expected signers match (or the format is DStack/GPU)
signer_verificationSignerVerification | NoneStructured signer comparison: contract_address, report_addresses[], all_match
quote_availableboolWhether the /v1/quote request succeeded
quote_datadictRaw quote response
attestation_verifiedbool | NoneAutomata contract verification result (optional)
attestation_formatstr | None"sgx_tdx", "dstack", or "gpu"
attestation_methodstr | NoneE.g. "automata_contract"
is_acknowledgedbool | NoneWhether the provider is acknowledged on-chain by the caller
reports_generatedlist[str]Paths to JSON reports written when output_dir was set
output_directorystr | NoneThe output_dir argument as supplied
errorslist[str]Non-fatal issues encountered during verification
timestampintMilliseconds since epoch
Each VerificationStep has type ("info", "success", "error", "warning", or "step") and message.
verify_service never raises on verification failure — it returns success=False with errors populated. It only raises ServiceNotFoundError if the provider isn’t registered at all.

Validate a single response

After making an inference request, confirm the response was actually produced by the TEE:
import json
import requests

metadata = broker.inference.get_service_metadata(provider_address)
headers  = broker.inference.get_request_headers(provider_address)

resp = requests.post(
    f"{metadata['endpoint']}/chat/completions",
    headers = {"Content-Type": "application/json", **headers},
    json    = {
        "model":    metadata["model"],
        "messages": [{"role": "user", "content": "What is 2 + 2?"}],
    },
)

data    = resp.json()
content = data["choices"][0]["message"]["content"]
chat_id = data["id"]

is_valid = broker.inference.process_response(
    provider_address = provider_address,
    content          = content,
    chat_id          = chat_id,
)

print(f"Response verified: {is_valid}")

How it works

process_response looks up the service’s verifiability. For "TeeML" providers it:
  1. Fetches the TEE-generated signature for this specific chat_id from <provider_url>/v1/proxy/signature/<chat_id>
  2. Extracts the signer’s public key from the service’s additional_info (which may include a TargetTeeAddress override for separated architectures)
  3. Validates the ECDSA signature over the response content
chat_id is required for verifiable services. Without it, process_response raises InvalidResponseError. For non-verifiable services, chat_id can be omitted — the method returns True regardless.

When validation matters

  • Compliance use cases — prove to auditors that AI outputs came from a specific attested TEE
  • Dispute resolution — provide cryptographic evidence that a provider produced a given output
  • Detection of middleboxes — detect if a response was modified between the TEE and your app

Raw attestation artifacts

For custom verification flows, bypass the high-level helpers and fetch the primitives directly.

Download the attestation quote

broker.inference.download_quote_report(
    provider_address = "0xProvider...",
    output_path      = "./quote.json",
)
Saves the provider’s TEE quote as JSON. Includes report_data, quote (hex), and (for DStack) compose_content and evidence.

Attestation report download URL

For distributing the verification link to a third-party system:
url = broker.inference.get_signer_ra_download_link(provider_address)
# http://provider.example.com/v1/proxy/attestation/report

Per-chat signature URL

url = broker.inference.get_chat_signature_download_link(provider_address, chat_id)
# http://provider.example.com/v1/proxy/signature/chatcmpl-abc123
Third parties can fetch this URL and verify the signature against your expected TEE signer without involving the SDK.

Generate a verification report

Archive verification output for compliance:
from pathlib import Path

Path("./audit-reports").mkdir(exist_ok=True)
result = broker.inference.verify_service(
    provider_address = provider_address,
    output_dir       = "./audit-reports",
)

print(f"Report: {result.reports_generated[0]}")
Reports are JSON files named verification_<provider>_<timestamp>.json containing the full result payload.

Acknowledge first, then verify

The expected_signer / signer_match check only works for providers you’ve acknowledged on-chain. Acknowledgement records the provider’s TEE signer to your account:
# First time per provider
broker.inference.acknowledge_provider_signer(provider_address)

# Now verify_service can compare extracted vs expected signer
result = broker.inference.verify_service(provider_address)

Check acknowledgement status

if broker.inference.acknowledged(provider_address):
    print("Already acknowledged")
For a structured snapshot of the provider’s signer state, including whether the account exists and the on-chain TEE signer address, use check_provider_signer_status:
status = broker.inference.check_provider_signer_status(provider_address)
print(status)
# {
#   "is_acknowledged": True,
#   "tee_signer_address": "0xabc...",
#   "account_exists": True,
# }

Owner-side acknowledgement

Contract owners can acknowledge a provider’s TEE signer on behalf of the network with acknowledge_tee_signer_by_owner:
broker.inference.acknowledge_tee_signer_by_owner(provider_address)

Revoke acknowledgement

If you want to stop trusting a provider’s TEE signer (e.g. after suspicious behaviour):
broker.inference.revoke_provider_tee_signer_acknowledgement(provider_address)
You must re-acknowledge before the SDK will generate request headers for that provider again.

API at a glance

Verification

MethodReturns
broker.inference.verify_service(provider, output_dir=None, on_log=None)VerificationResult
broker.inference.process_response(provider, content, chat_id=None)bool
broker.inference.acknowledged(provider)bool
broker.inference.check_provider_signer_status(provider)dict
broker.inference.acknowledge_provider_signer(provider)receipt dict
broker.inference.acknowledge_tee_signer_by_owner(provider)receipt dict
broker.inference.revoke_provider_tee_signer_acknowledgement(provider)receipt dict

Attestation artifacts

MethodReturns
broker.inference.download_quote_report(provider, output_path)None — writes JSON
broker.inference.get_signer_ra_download_link(provider)str — URL
broker.inference.get_chat_signature_download_link(provider, chat_id)str — URL

Verifier types

All exported from zerog_py_sdk: VerificationResult, VerificationStep, VerificationSummary, SignerVerification, SignerReportMatch, SignerRAVerificationResult, AttestationReport, EventLogEntry, ReportsData, ComposeVerification, ComposeVerificationDetail, ComposeVerificationResult, ProviderType, VerificationLogger.

Migrating from earlier versions

verify_service previously returned a plain dict and printed progress to stdout. As of the TS-parity refresh:
BeforeAfter
result["is_valid"]result.success
result["report_path"]result.reports_generated[0]
result["tee_signer"]result.tee_signer
Prints to stdout automaticallySilent — pass on_log=lambda s: print(s.message)

Next steps

Inference

Make requests to verified providers.

Fine-tuning

Fine-tuning providers use the same TEE attestation flow.