Run private inference on Venice with Trusted Execution Environment (TEE) models and end-to-end encryption that keep prompts unreadable to the host.
Venice offers privacy-enhanced models that run in Trusted Execution Environments (TEE) and support End-to-End Encryption (E2EE). These models provide cryptographic guarantees that your data remains private—even from Venice.
TEE models run inside hardware-secured enclaves (Intel TDX, NVIDIA Confidential Computing). The model weights and your data are protected from the host system—including Venice’s infrastructure.
Whether the attestation passed server-side verification
nonce
Your nonce, confirming freshness
model
The attested model ID
tee_provider
TEE provider identifier
intel_quote
Raw Intel TDX quote (base64) for client-side verification
nvidia_payload
NVIDIA GPU attestation data (if applicable)
signing_key
Public key for verifying response signatures (typically required for E2EE flows; may be omitted for some plain TEE models)
signing_address
Ethereum address derived from signing key
For production use, verify the attestation client-side by parsing the Intel TDX quote and checking the NVIDIA attestation.
For plain TEE model verification, signing_address and server-side verification fields are sufficient for baseline attestation checks. A signing_key is required when you need client-side E2EE key agreement and strict key-binding checks.
TEE models can sign their responses, proving the output came from the attested enclave:
# After getting a completion, verify the signaturecurl "https://api.venice.ai/api/v1/tee/signature?model=tee-qwen3-5-122b-a10b&request_id=chatcmpl-abc123" \ -H "Authorization: Bearer $API_KEY_VENICE"
response = requests.get( f"https://api.venice.ai/api/v1/tee/signature", params={"model": "tee-qwen3-5-122b-a10b", "request_id": completion_id}, headers={"Authorization": f"Bearer {api_key}"})signature = response.json()# Verify signature matches the signing_address from attestation
E2EE models add client-side encryption on top of TEE protection. Your prompts are encrypted before leaving your device, and only the TEE can decrypt them.Venice E2EE uses:
ECDH (Elliptic Curve Diffie-Hellman) on secp256k1 for key exchange
HKDF-SHA256 for key derivation
AES-256-GCM for symmetric encryption
TEE attestation to verify the model runs in a secure enclave
E2EE requires client-side implementation. The examples below show the complete protocol.
import requestsdef get_e2ee_models(api_key: str) -> list: """Get list of models that support E2EE.""" response = requests.get( 'https://api.venice.ai/api/v1/models', headers={'Authorization': f'Bearer {api_key}'} ) models = response.json()['data'] return [ model for model in models if model.get('model_spec', {}).get('capabilities', {}).get('supportsE2EE') ]# Example usagemodels = get_e2ee_models('your-api-key')print('E2EE Models:', [m['id'] for m in models])
Use these helper functions to validate keys and encrypted content before sending requests.
function validateClientPubkey(pubkeyHex) { if (pubkeyHex.length !== 130 || !pubkeyHex.startsWith('04')) { throw new Error(`Client pubkey must be 130 hex chars starting with '04' (got ${pubkeyHex.length})`) }}function isValidEncrypted(s) { // Minimum: ephemeral_pub (65) + nonce (12) + tag (16) = 93 bytes = 186 hex chars return s.length >= 186 && /^[0-9a-fA-F]+$/.test(s)}
def validate_client_pubkey(pubkey_hex: str) -> None: """Validate client public key format.""" if len(pubkey_hex) != 130 or not pubkey_hex.startswith('04'): raise ValueError(f"Client pubkey must be 130 hex chars starting with '04' (got {len(pubkey_hex)})")def is_valid_encrypted(s: str) -> bool: """Check if string is valid hex-encrypted content.""" # Minimum: ephemeral_pub (65) + nonce (12) + tag (16) = 93 bytes = 186 hex chars return len(s) >= 186 and all(c in '0123456789abcdefABCDEF' for c in s)
The attestation proves the model is running in a genuine TEE. Always verify the attestation before trusting the model’s public key.
Important: Nonce Length - The client nonce must be 32 bytes (64 hex characters). Some TEE providers require exactly 32 bytes and will reject shorter nonces.
import crypto from 'crypto'async function fetchAndVerifyAttestation(modelId, apiKey) { // Generate client nonce for replay protection (32 bytes = 64 hex chars) const clientNonce = crypto.randomBytes(32).toString('hex') const response = await fetch( `https://api.venice.ai/api/v1/tee/attestation?model=${encodeURIComponent(modelId)}&nonce=${clientNonce}`, { headers: { Authorization: `Bearer ${apiKey}` } } ) const attestation = await response.json() // Verify attestation if (attestation.verified !== true) { throw new Error('TEE attestation verification failed on server') } if (attestation.nonce !== clientNonce) { throw new Error('Attestation nonce mismatch - possible replay attack') } // Get model's public key for encryption const modelPublicKey = attestation.signing_key || attestation.signing_public_key if (!modelPublicKey) { throw new Error('No signing key in attestation response') } return { modelPublicKey, signingAddress: attestation.signing_address, attestation, }}
import secretsimport requestsdef fetch_and_verify_attestation(model_id: str, api_key: str) -> dict: """Fetch and verify TEE attestation for a model.""" # Generate client nonce for replay protection (32 bytes = 64 hex chars) client_nonce = secrets.token_hex(32) response = requests.get( f'https://api.venice.ai/api/v1/tee/attestation', params={'model': model_id, 'nonce': client_nonce}, headers={'Authorization': f'Bearer {api_key}'} ) attestation = response.json() # Verify attestation if attestation.get('verified') != True: raise ValueError('TEE attestation verification failed on server') if attestation.get('nonce') != client_nonce: raise ValueError('Attestation nonce mismatch - possible replay attack') # Get model's public key for encryption model_public_key = attestation.get('signing_key') or attestation.get('signing_public_key') if not model_public_key: raise ValueError('No signing key in attestation response') return { 'model_public_key': model_public_key, 'signing_address': attestation.get('signing_address'), 'attestation': attestation }
Encrypt user and system messages before sending. Only user and system role messages need encryption.
When E2EE headers are present, alluser and system role messages must be encrypted. Sending any plaintext content in these roles will result in an “Encrypted field is not valid hex” error.
import { gcm } from '@noble/ciphers/aes.js'import { hkdf } from '@noble/hashes/hkdf.js'import { sha256 } from '@noble/hashes/sha2.js'import { ec as EC } from 'elliptic'import crypto from 'crypto'const HKDF_INFO = new TextEncoder().encode('ecdsa_encryption')function encryptMessage(plaintext, modelPublicKeyHex) { const ec = new EC('secp256k1') // Normalize public key (add 04 prefix if needed) let normalizedKey = modelPublicKeyHex if (!normalizedKey.startsWith('04') && normalizedKey.length === 128) { normalizedKey = '04' + normalizedKey } const modelPublicKey = ec.keyFromPublic(normalizedKey, 'hex') // Generate ephemeral key pair for this message const ephemeralKeyPair = ec.genKeyPair() // ECDH shared secret const sharedSecret = ephemeralKeyPair.derive(modelPublicKey.getPublic()) const sharedSecretBytes = new Uint8Array(sharedSecret.toArray('be', 32)) // Derive AES key using HKDF const aesKey = hkdf(sha256, sharedSecretBytes, undefined, HKDF_INFO, 32) // Generate random nonce const nonce = crypto.randomBytes(12) // Encrypt with AES-GCM const cipher = gcm(aesKey, nonce) const encrypted = cipher.encrypt(new TextEncoder().encode(plaintext)) // Get ephemeral public key (uncompressed) const ephemeralPublic = new Uint8Array(ephemeralKeyPair.getPublic(false, 'array')) // Combine: ephemeral_public (65 bytes) + nonce (12 bytes) + ciphertext const result = new Uint8Array(65 + 12 + encrypted.length) result.set(ephemeralPublic, 0) result.set(nonce, 65) result.set(encrypted, 65 + 12) return Buffer.from(result).toString('hex')}function encryptMessagesForE2EE(messages, modelPublicKey) { return messages.map(msg => { if (msg.role === 'user' || msg.role === 'system') { return { ...msg, content: encryptMessage(msg.content, modelPublicKey), } } return msg })}
from cryptography.hazmat.primitives.ciphers.aead import AESGCMfrom cryptography.hazmat.primitives.kdf.hkdf import HKDFfrom cryptography.hazmat.primitives import hashesfrom ecdsa import SECP256k1, VerifyingKey, SigningKeyimport osHKDF_INFO = b'ecdsa_encryption'def encrypt_message(plaintext: str, model_public_key_hex: str) -> str: """Encrypt a message using ECDH + HKDF + AES-GCM.""" # Normalize public key key_hex = model_public_key_hex if not key_hex.startswith('04') and len(key_hex) == 128: key_hex = '04' + key_hex model_public_key_bytes = bytes.fromhex(key_hex) # Parse model's public key (skip 04 prefix) model_verifying_key = VerifyingKey.from_string( model_public_key_bytes[1:], curve=SECP256k1 ) # Generate ephemeral key pair for this message ephemeral_private = SigningKey.generate(curve=SECP256k1) ephemeral_public = ephemeral_private.get_verifying_key() # ECDH: compute shared secret shared_point = model_verifying_key.pubkey.point * ephemeral_private.privkey.secret_multiplier shared_secret = shared_point.x().to_bytes(32, 'big') # Derive AES key using HKDF hkdf = HKDF( algorithm=hashes.SHA256(), length=32, salt=None, info=HKDF_INFO, ) aes_key = hkdf.derive(shared_secret) # Generate random nonce nonce = os.urandom(12) # Encrypt with AES-GCM aesgcm = AESGCM(aes_key) ciphertext = aesgcm.encrypt(nonce, plaintext.encode('utf-8'), None) # Get ephemeral public key (uncompressed: 04 || x || y) ephemeral_public_bytes = b'\x04' + ephemeral_public.to_string() # Combine: ephemeral_public (65 bytes) + nonce (12 bytes) + ciphertext result = ephemeral_public_bytes + nonce + ciphertext return result.hex()def encrypt_messages_for_e2ee(messages: list, model_public_key: str) -> list: """Encrypt user and system messages.""" encrypted_messages = [] for msg in messages: if msg['role'] in ('user', 'system'): encrypted_messages.append({ **msg, 'content': encrypt_message(msg['content'], model_public_key) }) else: encrypted_messages.append(msg) return encrypted_messages
Don’t just trust the verified: true response. Parse the Intel TDX quote client-side and verify the measurements match expected values. For NVIDIA GPUs, check the attestation via NVIDIA’s verification service.
Use fresh nonces
Always generate a new random nonce for each attestation request. This prevents replay attacks where an attacker could serve a stale attestation.
Verify key binding
The signing key should be bound to the TDX REPORTDATA field. This proves the key was generated inside the enclave.
Check for debug mode
Verify the TDX attestation doesn’t have debug flags set. A debug enclave can be inspected and should not be trusted for production.
Use our SDKs for E2EE
E2EE requires careful cryptographic implementation. Use our official SDKs rather than implementing the protocol yourself.
models = client.models.list()for model in models.data: caps = getattr(model, 'model_spec', {}).get('capabilities', {}) if caps.get('supportsTeeAttestation') or caps.get('supportsE2EE'): print(f"{model.id}: TEE={caps.get('supportsTeeAttestation')}, E2EE={caps.get('supportsE2EE')}")