Skip to content

Commit aa1ae33

Browse files
author
mondaylord
authored
Merge pull request #5 from Dstack-TEE/feat/e2ee
feat: complete E2EE support for chat/completions (ECDSA + Ed25519) with replay protection, attestation public key, and verification tooling
2 parents 66659aa + 3f27559 commit aa1ae33

13 files changed

Lines changed: 2108 additions & 14 deletions

docs/e2ee_protocol.md

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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

Comments
 (0)