diff --git a/README.md b/README.md index 551b552..0c3386c 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,20 @@ OpenGradient operates two networks: For current network RPC endpoints, contract addresses, and deployment information, refer to the [Network Deployment Documentation](https://docs.opengradient.ai/learn/network/deployment.html). +## Trust Model + +OpenGradient's verifiable inference relies on three distinct layers. Understanding which layer enforces what makes it easier to reason about what an SDK response does and does not guarantee. + +1. **TEE attestation (network-side, at registration).** Each TEE proves its identity and code measurement (PCR hash) to the OpenGradient network when it registers in the on-chain `TEERegistry` contract. The network verifies the attestation and stores the TEE's public key, PCR hash, and TLS certificate on-chain. SDK clients do not re-run attestation themselves. + +2. **TLS certificate pinning (SDK-side, at request time).** When the SDK resolves a TEE through `RegistryTEEConnection` (the production path), it reads the registered TLS certificate directly from the registry and pins it as the *only* trust anchor for the connection (`CERT_REQUIRED`, no system-CA fallback). A successful response is therefore, by TLS construction, from a TEE the network has already attested. This is the live trust binding for SDK responses. + +3. **Signature verification (settlement-side, on-chain).** The `tee_signature` and `tee_timestamp` returned with each response are durable proof material. Signature verification happens during on-chain settlement, not in the SDK at return time. These fields are also useful for offline / auditor verification when a response leaves the original TLS session. + +> **Note:** The SDK does **not** verify `tee_signature` client-side at return time. A non-erroring response means the TLS-pinned channel was honored; signature checking lives at the settlement layer. If you archive a response and want to re-verify it offline, use the registry's stored public key for that TEE. +> +> The `StaticTEEConnection` mode (used for self-hosted dev with `verify=False`) bypasses both the registry and TLS pinning, and is **not a production trust path**. + ## Getting Started ### Prerequisites @@ -142,7 +156,7 @@ See [Payment Settlement](#payment-settlement) for details on settlement modes. ### TEE-Secured LLM Chat -OpenGradient provides secure, verifiable inference through Trusted Execution Environments. All supported models include cryptographic attestation verified by the OpenGradient network. LLM methods are async: +OpenGradient provides secure, verifiable inference through Trusted Execution Environments. All supported models run in TEEs whose attestation is verified by the OpenGradient network at registration; the SDK binds each request to an attested TEE via registry-pinned TLS, and signed proof material is verified at on-chain settlement (see [Trust Model](#trust-model)). LLM methods are async: ```python completion = await llm.chat( model=og.TEE_LLM.GPT_5, diff --git a/src/opengradient/client/tee_connection.py b/src/opengradient/client/tee_connection.py index 2807b88..6afd2d2 100644 --- a/src/opengradient/client/tee_connection.py +++ b/src/opengradient/client/tee_connection.py @@ -46,8 +46,18 @@ async def close(self) -> None: ... class StaticTEEConnection: """TEE connection with a hardcoded endpoint URL. - No registry lookup, no background refresh. TLS certificate verification - is disabled because self-hosted TEE servers typically use self-signed certs. + Intended for self-hosted development only. No registry lookup, no + background refresh, and TLS certificate verification is disabled + (``verify=False``) because self-hosted TEE servers typically use + self-signed certs. + + Warning: + This is **not a production trust path**. Because TLS is unverified, + the connection is not bound to a network-attested TEE: there is no + registry cert pinning and no PCR-attested identity. Responses + received over this connection should not be treated as TEE-verified. + For production use, resolve TEEs via ``RegistryTEEConnection``, + which pins the on-chain registry's TLS certificate. Args: x402_client: Configured x402 payment client for creating HTTP clients. diff --git a/src/opengradient/types.py b/src/opengradient/types.py index 7e4c18b..dce3775 100644 --- a/src/opengradient/types.py +++ b/src/opengradient/types.py @@ -232,7 +232,10 @@ class StreamChunk: model: Model identifier usage: Token usage information (only in final chunk) is_final: Whether this is the final chunk (before [DONE]) - tee_signature: RSA-PSS signature over the response, present on the final chunk + tee_signature: RSA-PSS signature over the response, present on the final chunk. + Forwarded as-is; verified at on-chain settlement, not by the SDK. Live + trust comes from the registry-pinned TLS channel — see + ``TextGenerationOutput`` for the trust model. tee_timestamp: ISO timestamp from the TEE at signing time, present on the final chunk tee_id: On-chain TEE registry ID of the enclave that served this request (final chunk only) tee_endpoint: Endpoint URL of the TEE that served this request (final chunk only) @@ -403,9 +406,21 @@ class TextGenerationOutput: **completion** requests it is in ``completion_output``. Only the field that matches the request type will be populated. - Every response includes a ``tee_signature`` and ``tee_timestamp`` - that can be used to cryptographically verify the inference was - performed inside a TEE enclave. + Trust model: + Live trust in the response comes from the **TLS channel** the SDK + used to obtain it: when the TEE is resolved via the on-chain + registry, the SDK pins the registry-attested TLS certificate, so + a successful response is, by construction, from a network-attested + TEE enclave. See ``opengradient.client.tee_registry`` for the + pinning logic. + + ``tee_signature`` and ``tee_timestamp`` are durable proof material + intended for **on-chain settlement verification** and offline / + auditor use (e.g. when a response is archived and re-checked + outside the original TLS session). The SDK does not verify the + signature at return time, and a non-erroring response does not + imply client-side signature verification has occurred — only that + the TLS-pinned channel was honored. Attributes: data_settlement_transaction_hash: Blockchain transaction hash for @@ -422,10 +437,12 @@ class TextGenerationOutput: optionally ``tool_calls``. completion_output: Raw text returned by a completion request. payment_hash: Payment hash for the x402 transaction. - tee_signature: RSA-PSS signature over the response produced - by the TEE enclave. + tee_signature: RSA-PSS signature over the response produced by + the TEE enclave. Forwarded as-is from the server; verified at + settlement on-chain, not by the SDK at return time. See the + class-level "Trust model" note above. tee_timestamp: ISO-8601 timestamp from the TEE at signing - time. + time. Forwarded as-is alongside ``tee_signature``. """ data_settlement_transaction_hash: Optional[str] = None @@ -450,7 +467,8 @@ class TextGenerationOutput: """Payment hash for the x402 transaction.""" tee_signature: Optional[str] = None - """RSA-PSS signature over the response produced by the TEE enclave.""" + """RSA-PSS signature over the response produced by the TEE enclave. + Forwarded as-is; verified at on-chain settlement, not at SDK return time.""" tee_timestamp: Optional[str] = None """ISO-8601 timestamp from the TEE at signing time."""