|
| 1 | +# Phala vLLM Proxy E2EE Protocol Specification |
| 2 | + |
| 3 | +This document defines the End-to-End Encryption (E2EE) protocol used by the Phala vLLM Proxy to ensure data privacy between clients and the Model running in a Trusted Execution Environment (TEE). |
| 4 | + |
| 5 | +## 1. Overview |
| 6 | + |
| 7 | +The protocol supports two major versions: |
| 8 | +- **v1**: Legacy compatibility mode (kept for backward compatibility and Near-compatible integrations). |
| 9 | +- **v2**: Enhanced E2EE with AAD (Additional Authenticated Data) and Replay Protection (**recommended** for security-sensitive deployments). |
| 10 | + |
| 11 | +Supported Algorithms: |
| 12 | +- **ECDSA**: Using `secp256k1` curve for signing and ECDH key exchange. |
| 13 | +- **Ed25519**: Using Ed25519 for signing and X25519 for key exchange (via birational equivalence). |
| 14 | + |
| 15 | +## 2. Handshake and Key Derivation |
| 16 | + |
| 17 | +### 2.1 Server Public Key Discovery |
| 18 | +Clients can obtain the server's public key (either ECDSA or Ed25519 depending on the environment) via the `/v1/attestation/report` endpoint. |
| 19 | + |
| 20 | +The response contains: |
| 21 | +- `signing_address`: The EVM address (for ECDSA) or 32-byte raw public key hex (64 chars) (for Ed25519). |
| 22 | +- `signing_public_key`: The raw hex-encoded public key (no `0x` prefix). Available at both the top-level and within each item in `all_attestations`. |
| 23 | + - **Ed25519**: 32 bytes (64 hex characters). |
| 24 | + - **ECDSA**: 64 bytes (128 hex characters), representing uncompressed point `(x, y)` without the `04` prefix. |
| 25 | + |
| 26 | +### 2.2 Client Ephemeral Key Generation |
| 27 | +For each encryption operation, the client generates an ephemeral key pair of the same type as the server's key. |
| 28 | + |
| 29 | +### 2.3 Shared Secret and AES Key |
| 30 | +1. Perform ECDH (or X25519 exchange) between the client's ephemeral private key and the server's public key. |
| 31 | +2. Derivation via HKDF-SHA256: |
| 32 | + - **ECDSA Info**: `b"ecdsa_encryption"` |
| 33 | + - **Ed25519 Info**: `b"ed25519_encryption"` |
| 34 | + - **Length**: 32 bytes |
| 35 | + - **Salt**: `None` |
| 36 | + |
| 37 | +## 3. Request Encryption (v2) |
| 38 | + |
| 39 | +### 3.1 Headers and Version Selection |
| 40 | + |
| 41 | +The following headers are used for E2EE: |
| 42 | +- `X-Signing-Algo`: `ecdsa` or `ed25519` |
| 43 | +- `X-Client-Pub-Key`: Client's public key (Hex) |
| 44 | +- `X-Model-Pub-Key`: Server's public key (Hex) |
| 45 | + |
| 46 | +Version selection rules: |
| 47 | + |
| 48 | +1. **Strict v2 mode** is used when: |
| 49 | +- `X-E2EE-Version: 2` is provided, or |
| 50 | +- both `X-E2EE-Nonce` and `X-E2EE-Timestamp` are provided. |
| 51 | + |
| 52 | +2. **Legacy v1 mode** is used when: |
| 53 | +- E2EE key headers are provided, but v2 conditions are not met |
| 54 | +- (e.g. no nonce/timestamp and no explicit `X-E2EE-Version: 2`). |
| 55 | + |
| 56 | +3. **Plain (non-E2EE) mode** is used when: |
| 57 | +- E2EE key headers are not provided. |
| 58 | + |
| 59 | +In strict v2 mode: |
| 60 | +- `X-E2EE-Nonce`: Minimum 16-character unique string per request. |
| 61 | +- `X-E2EE-Timestamp`: Unix timestamp (seconds). |
| 62 | +- `X-E2EE-Nonce` and `X-E2EE-Timestamp` must be provided together. |
| 63 | + |
| 64 | +### 3.2 AAD Construction (v2 Request) |
| 65 | +Format: `v2|req|algo={algo}|model={model}|m={message_index}|c={content_index}|n={nonce}|ts={timestamp}` |
| 66 | +- `algo`: lowercase signing algo name. |
| 67 | +- `model`: Model name from request payload. |
| 68 | +- `message_index`: 0-indexed position in `messages` array. |
| 69 | +- `content_index`: `-` for top-level `content` string, or 0-indexed if content is a list of items. |
| 70 | + |
| 71 | +### 3.3 Payload Format |
| 72 | +Encapsulated fields (like `messages[i].content`) are replaced with: |
| 73 | +`ephemeral_public_key (bytes) + nonce (12 bytes) + ciphertext (bytes)` |
| 74 | +The result is Hex-encoded. |
| 75 | + |
| 76 | +## 4. Response Encryption (v2) |
| 77 | + |
| 78 | +### 4.1 Response Headers |
| 79 | + |
| 80 | +When E2EE is applied, the server returns: |
| 81 | +- `X-E2EE-Applied`: `true` |
| 82 | +- `X-E2EE-Version`: negotiated version (`1` or `2`) |
| 83 | +- `X-E2EE-Alg`: same as requested (`ecdsa` or `ed25519`) |
| 84 | + |
| 85 | +When E2EE is not applied, the server returns: |
| 86 | +- `X-E2EE-Applied`: `false` |
| 87 | + |
| 88 | +### 4.2 AAD Construction (v2 Response) |
| 89 | +Format: `v2|resp|algo={algo}|model={model}|id={obj_id}|choice={choice_index}|field={field_name}|n={nonce}|ts={timestamp}` |
| 90 | +- `id`: Chat completion ID (`chatcmpl-...`). |
| 91 | +- `choice_index`: 0-indexed position in `choices` array. |
| 92 | +- `field_name`: `content` or `reasoning_content`. |
| 93 | +- `nonce/timestamp`: Reused from the original request. |
| 94 | + |
| 95 | +## 5. Error Codes |
| 96 | + |
| 97 | +| Error Type | Meaning | |
| 98 | +|---|---| |
| 99 | +| `e2ee_header_missing` | Required E2EE headers are missing. | |
| 100 | +| `e2ee_invalid_signing_algo` | Unsupported algorithm specified. | |
| 101 | +| `e2ee_invalid_public_key` | Public key format or length is invalid. | |
| 102 | +| `e2ee_model_key_mismatch` | `X-Model-Pub-Key` does not match the server instance. | |
| 103 | +| `e2ee_invalid_version` | Unsupported `X-E2EE-Version`. | |
| 104 | +| `e2ee_invalid_nonce` | Nonce length or format is invalid (e.g., < 16 chars). | |
| 105 | +| `e2ee_replay_detected` | Nonce + Timestamp has already been consumed. | |
| 106 | +| `e2ee_invalid_timestamp` | Timestamp is malformed or outside the allowed window. | |
| 107 | +| `e2ee_decryption_failed` | MAC tag mismatch or invalid ciphertext format. | |
| 108 | + |
| 109 | + |
| 110 | +## 6. Compatibility Note |
| 111 | + |
| 112 | +This implementation supports a legacy E2EE behavior for Near-compatible clients and a strict v2 behavior for enhanced security. |
| 113 | + |
| 114 | +- Use **v2** (`X-E2EE-Version: 2` or nonce+timestamp) for AAD binding and replay protection. |
| 115 | +- Use **legacy/v1** only when interoperability with existing clients requires it. |
0 commit comments