# Delivery and Keys Schema

**Proto files:** `proto/qpc/v1/delivery.proto`, `proto/qpc/v1/keys.proto`
**Package:** `qpc.v1`
**Method IDs:** 200-205 (delivery), 300-304 (key packages and hybrid keys), 510-520 (key transparency)

This page documents the Protobuf message definitions for the delivery service (store-and-forward message relay) and the key management service (MLS KeyPackages, hybrid post-quantum keys, and key transparency).

---

## delivery.proto

The delivery service is a store-and-forward relay. It is intentionally MLS-unaware: all payloads are opaque byte strings routed by recipient key and channel ID. The server never inspects or decrypts message content.

### Full proto listing

```protobuf
syntax = "proto3";
package qpc.v1;

// Delivery service: enqueue, fetch, peek, ack, batch (6 methods).
// Method IDs: 200-205.

message Envelope {
  uint64 seq = 1;
  bytes data = 2;
}

message EnqueueRequest {
  bytes recipient_key = 1;
  bytes payload = 2;
  bytes channel_id = 3;
  uint32 ttl_secs = 4;
  // Client-generated idempotency key (16 bytes, UUID v7).
  // Server deduplicates enqueue requests with the same message_id within a TTL window.
  bytes message_id = 5;
}

message EnqueueResponse {
  uint64 seq = 1;
  bytes delivery_proof = 2;
  // True if this was a duplicate enqueue (message_id already seen).
  bool duplicate = 3;
}

message FetchRequest {
  bytes recipient_key = 1;
  bytes channel_id = 2;
  uint32 limit = 3;
  // Device ID for multi-device scoping.
  bytes device_id = 4;
}

message FetchResponse {
  repeated Envelope payloads = 1;
}

message FetchWaitRequest {
  bytes recipient_key = 1;
  bytes channel_id = 2;
  uint64 timeout_ms = 3;
  uint32 limit = 4;
  bytes device_id = 5;
}

message FetchWaitResponse {
  repeated Envelope payloads = 1;
}

message PeekRequest {
  bytes recipient_key = 1;
  bytes channel_id = 2;
  uint32 limit = 3;
  bytes device_id = 4;
}

message PeekResponse {
  repeated Envelope payloads = 1;
}

message AckRequest {
  bytes recipient_key = 1;
  bytes channel_id = 2;
  uint64 seq_up_to = 3;
  bytes device_id = 4;
}

message AckResponse {}

message BatchEnqueueRequest {
  repeated bytes recipient_keys = 1;
  bytes payload = 2;
  bytes channel_id = 3;
  uint32 ttl_secs = 4;
  bytes message_id = 5;
}

message BatchEnqueueResponse {
  repeated uint64 seqs = 1;
}
```

### Envelope

The `Envelope` wrapper is returned by fetch, peek, and fetch-wait operations.

| Field | Type | Description |
|-------|------|-------------|
| `seq` | `uint64` | Server-assigned monotonic sequence number for ordering and acknowledgment. |
| `data` | `bytes` | The original payload bytes submitted at enqueue time. |

### Enqueue (ID 200)

Appends an opaque payload to a recipient's queue. Returns immediately.

**Request:**

| Field | Type | Description |
|-------|------|-------------|
| `recipient_key` | `bytes` | Recipient's Ed25519 identity public key (32 bytes). Primary queue index. |
| `payload` | `bytes` | Opaque byte string. Typically a TLS-encoded MLS ciphertext blob. |
| `channel_id` | `bytes` | Channel identifier (16-byte UUID v7 recommended). Empty = default channel. |
| `ttl_secs` | `uint32` | Time-to-live in seconds. Server garbage-collects expired messages. 0 = server default. |
| `message_id` | `bytes` | Client-generated idempotency key (16 bytes, UUID v7). Server deduplicates within the TTL window. |

**Response:**

| Field | Type | Description |
|-------|------|-------------|
| `seq` | `uint64` | Server-assigned sequence number for this message. |
| `delivery_proof` | `bytes` | Cryptographic proof of delivery (reserved for future use). |
| `duplicate` | `bool` | `true` if this `message_id` was already seen within the TTL window; the payload was not stored again. |

### Fetch (ID 201)

Returns and retains queued messages up to `limit`. Does not remove messages from the queue; use `Ack` to advance the read cursor.

**Request:**

| Field | Type | Description |
|-------|------|-------------|
| `recipient_key` | `bytes` | Recipient's Ed25519 identity public key (32 bytes). |
| `channel_id` | `bytes` | Channel identifier. Must match the value used at enqueue time. |
| `limit` | `uint32` | Maximum number of envelopes to return. 0 = server default. |
| `device_id` | `bytes` | Optional device identifier for multi-device queue scoping. |

**Response:**

| Field | Type | Description |
|-------|------|-------------|
| `payloads` | `repeated Envelope` | Messages in FIFO order. Empty list if no messages are pending. |

### FetchWait (ID 202)

Long-poll variant of `Fetch`. Blocks on the server until messages arrive or `timeout_ms` elapses.

**Request:**

| Field | Type | Description |
|-------|------|-------------|
| `recipient_key` | `bytes` | Recipient's Ed25519 identity public key (32 bytes). |
| `channel_id` | `bytes` | Channel identifier. |
| `timeout_ms` | `uint64` | Maximum wait time in milliseconds. 0 = return immediately (equivalent to Fetch). |
| `limit` | `uint32` | Maximum number of envelopes to return. |
| `device_id` | `bytes` | Optional device identifier. |

**Response:** Same as `FetchResponse`.

FetchWait eliminates polling latency: the server holds the RPC open until a `Notify` is signalled by a concurrent `Enqueue` call, or until `timeout_ms` expires.

### Peek (ID 203)

Non-destructive read. Returns messages without removing them and without advancing the acknowledgment cursor.

**Request / Response:** Same field layout as `FetchRequest` / `FetchResponse`.

Peek is useful for inspecting pending messages without marking them as delivered.

### Ack (ID 204)

Advances the delivery cursor, removing all messages with `seq <= seq_up_to` from the queue.

**Request:**

| Field | Type | Description |
|-------|------|-------------|
| `recipient_key` | `bytes` | Recipient's Ed25519 identity public key (32 bytes). |
| `channel_id` | `bytes` | Channel identifier. |
| `seq_up_to` | `uint64` | All messages with sequence number <= this value are removed. |
| `device_id` | `bytes` | Optional device identifier. |

**Response:** Empty (`AckResponse {}`).

### BatchEnqueue (ID 205)

Fan-out: enqueues the same payload to multiple recipients in a single RPC call.

**Request:**

| Field | Type | Description |
|-------|------|-------------|
| `recipient_keys` | `repeated bytes` | List of recipient Ed25519 identity public keys. |
| `payload` | `bytes` | Opaque payload, delivered identically to all recipients. |
| `channel_id` | `bytes` | Channel identifier. |
| `ttl_secs` | `uint32` | Time-to-live in seconds. |
| `message_id` | `bytes` | Idempotency key (16 bytes). |

**Response:**

| Field | Type | Description |
|-------|------|-------------|
| `seqs` | `repeated uint64` | Server-assigned sequence numbers, one per `recipient_key`, in the same order. |

---

## keys.proto

Key management for MLS KeyPackages, hybrid post-quantum keys, and key transparency audit.

### Full proto listing

```protobuf
syntax = "proto3";
package qpc.v1;

// Key package + hybrid key CRUD (5 methods).
// Method IDs: 300-304.

message UploadKeyPackageRequest {
  bytes identity_key = 1;
  bytes package = 2;
}

message UploadKeyPackageResponse {
  bytes fingerprint = 1;
}

message FetchKeyPackageRequest {
  bytes identity_key = 1;
}

message FetchKeyPackageResponse {
  bytes package = 1;
}

message UploadHybridKeyRequest {
  bytes identity_key = 1;
  bytes hybrid_public_key = 2;
}

message UploadHybridKeyResponse {}

message FetchHybridKeyRequest {
  bytes identity_key = 1;
}

message FetchHybridKeyResponse {
  bytes hybrid_public_key = 1;
}

message FetchHybridKeysRequest {
  repeated bytes identity_keys = 1;
}

message FetchHybridKeysResponse {
  repeated bytes keys = 1;
}

// Key revocation (method ID 510).
message RevokeKeyRequest {
  bytes identity_key = 1;
  string reason = 2; // "compromised", "superseded", "user_revoked"
}

message RevokeKeyResponse {
  bool success = 1;
  uint64 leaf_index = 2;
}

// Check revocation status (method ID 511).
message CheckRevocationRequest {
  bytes identity_key = 1;
}

message CheckRevocationResponse {
  bool revoked = 1;
  string reason = 2;
  uint64 timestamp_ms = 3;
}

// KT audit log retrieval (method ID 520).
message AuditKeyTransparencyRequest {
  uint64 start = 1;
  uint64 end = 2;
}

message AuditKeyTransparencyResponse {
  repeated LogEntry entries = 1;
  uint64 tree_size = 2;
  bytes root = 3;
}

message LogEntry {
  uint64 index = 1;
  bytes leaf_hash = 2;
}
```

### UploadKeyPackage (ID 300)

Uploads a single-use MLS KeyPackage. KeyPackages are stored in a FIFO queue per identity; each is consumed once by `FetchKeyPackage`.

| Field | Type | Description |
|-------|------|-------------|
| `identity_key` | `bytes` | Uploader's Ed25519 identity public key (32 bytes). Index key for the queue. |
| `package` | `bytes` | openmls-serialised KeyPackage (bincode format, as required by `DiskKeyStore`). |

Response: `fingerprint` -- SHA-256 digest of the stored package (32 bytes). Callers should record this to detect tampering.

### FetchKeyPackage (ID 301)

Fetches and atomically removes one KeyPackage for the given identity. Returns empty bytes if no packages are stored. The removal is atomic; concurrent fetches will not receive the same package.

### UploadHybridKey (ID 302)

Uploads the client's hybrid (X25519 + ML-KEM-768) public key. Unlike KeyPackages, hybrid keys are not single-use -- each identity stores exactly one, overwriting the previous value.

| Field | Type | Description |
|-------|------|-------------|
| `identity_key` | `bytes` | Uploader's Ed25519 identity public key (32 bytes). |
| `hybrid_public_key` | `bytes` | Concatenated X25519 public key (32 bytes) + ML-KEM-768 encapsulation key. |

### FetchHybridKey (ID 303)

Fetches a single peer's hybrid public key. Non-destructive.

### FetchHybridKeys (ID 304)

Batch variant of `FetchHybridKey`. Returns one key per input identity key, in the same order. Missing keys are returned as empty bytes at the corresponding index.

### RevokeKey (ID 510)

Revokes an identity key by appending a revocation entry to the key transparency Merkle log.

| Field | Type | Description |
|-------|------|-------------|
| `identity_key` | `bytes` | Identity key to revoke (32 bytes). |
| `reason` | `string` | One of: `"compromised"`, `"superseded"`, `"user_revoked"`. |

Response: `leaf_index` is the index of the revocation entry in the KT Merkle log.

### CheckRevocation (ID 511)

Checks whether an identity key has been revoked.

Response fields: `revoked` (bool), `reason` (string), `timestamp_ms` (uint64 unix milliseconds of the revocation event).

### AuditKeyTransparency (ID 520)

Returns a range of entries from the key transparency append-only Merkle log.

| Field | Type | Description |
|-------|------|-------------|
| `start` | `uint64` | First leaf index (inclusive). |
| `end` | `uint64` | Last leaf index (exclusive). 0 = up to current tree size. |

Response: `entries` (list of `LogEntry`), `tree_size` (current log size), `root` (Merkle root hash).

---

## Further reading

- [Wire Format Overview](overview.md) -- frame format and transport parameters
- [Method ID Reference](envelope-schema.md) -- all 44 method IDs
- [Auth Schema](auth-schema.md) -- OPAQUE authentication proto definitions
- [RPC Reference](node-service-schema.md) -- all proto definitions for all 14 files
- [Storage Backend](../internals/storage-backend.md) -- how KeyPackages and hybrid keys are persisted
