> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Architecture Overview

> End-to-end data flow for BlindCast: the upload path (encrypt-then-store), the playback path (fetch-then-decrypt), and the key hierarchy that connects them.

BlindCast has two data flows — **upload** and **playback** — connected by a shared key hierarchy. The server never handles plaintext video.

## Data flow

```mermaid theme={null}
sequenceDiagram
  participant Creator as Creator's device
  participant S3 as S3 / CDN
  participant KeyServer as Key Server
  participant Player as Viewer's browser

  Note over Creator,S3: Upload path (CLI or Uploader)
  Creator->>Creator: Encrypt each .ts segment (AES-128-CBC)
  Creator->>S3: PUT encrypted segments
  Creator->>Creator: Rewrite manifest — inject EXT-X-KEY tags
  Creator->>S3: PUT rewritten .m3u8

  Note over KeyServer,Player: Playback path (Player)
  Player->>S3: GET manifest
  Player->>KeyServer: GET /keys/:contentId (with Bearer token)
  KeyServer->>KeyServer: Authenticate viewer
  KeyServer->>KeyServer: HKDF derive content key from master key
  KeyServer-->>Player: content key (16 raw bytes)
  Player->>S3: GET encrypted .ts segment
  Player->>Player: Decrypt segment in browser
  Player-->>Player: Decoded video frames → render
```

## Key hierarchy

All encryption keys derive from a single **master key** using HKDF-SHA-256. You store and protect one secret — everything else is derived deterministically.

```mermaid theme={null}
flowchart TD
  M["Master Key<br/>(random bytes, 128–256 bit)"]
  Salt["Salt<br/>(random bytes, 32 bytes)"]
  HKDF1["HKDF-SHA-256<br/>info = contentId"]
  CK["Content Key<br/>AES-128-CBC"]
  HKDF2["HKDF-SHA-256<br/>info = epoch number"]
  SK["Segment Key<br/>AES-128-CBC<br/>(per rotation epoch)"]

  M --> HKDF1
  Salt --> HKDF1
  HKDF1 --> CK
  M --> HKDF2
  Salt --> HKDF2
  HKDF2 --> SK
```

* **Master key**: Generated once with `blindcast keygen`. Stored in a secret manager. Never sent to browsers.
* **Content key**: Derived per `contentId`. The key server issues this to authenticated viewers.
* **Segment key** *(optional)*: Derived per epoch for key rotation. The manifest includes a new `EXT-X-KEY` tag every N segments.

## Content IDs

A **content ID** is an arbitrary string that uniquely identifies a piece of content. It serves two critical purposes:

1. **Key derivation context** — the key server uses it as the HKDF `info` parameter: different content ID = different encryption key. Two pieces of content encrypted with the same master key but different content IDs produce completely different ciphertext.
2. **Storage path prefix** — by convention, encrypted segments are stored under `content/<contentId>/` in S3/R2. The manifest `EXT-X-KEY` URI also embeds the content ID.

Content IDs must be **consistent** across the entire pipeline — the same string must be used when encrypting (CLI or uploader), when rewriting the manifest, and when the player fetches the key. A mismatch means the player gets a different key and decryption fails silently.

**Recommended format:** lowercase alphanumeric with hyphens (e.g., `my-video-001`, `lecture-2026-03`). Avoid special characters — the content ID appears in URLs and S3 key paths.

## Control plane vs. data plane

The BlindCast Server separates **control plane** (content management, API keys) from **data plane** (key derivation, encrypted byte storage):

```mermaid theme={null}
graph LR
  subgraph "Control Plane"
    Admin["Admin Dashboard"]
    API["REST API<br/>/api/v1/*"]
    PG["Postgres"]
    Admin --> API --> PG
  end

  subgraph "Data Plane"
    Keys["Key Server<br/>/keys/:contentId"]
    Presign["Presign<br/>/presign"]
    S3["S3 / CDN"]
  end

  API -->|"validates content exists"| Keys
  Presign --> S3
```

* **Control plane** handles content registration, API key management, and admin operations. All state lives in Postgres.
* **Data plane** handles key derivation and presigned URLs. The key server checks the content registry before deriving keys — unregistered or disabled content returns 404.

For standalone deployments (no content registry), you can use the [Key Server](/key-server/overview) directly.

## What each tool does

| Tool                               | Responsibility                                                                      | Runs on                               |
| ---------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------- |
| [CLI](/cli/overview)               | Generate keys, encrypt segments, upload to S3, run dev server                       | Your machine or CI                    |
| [Uploader](/uploader/overview)     | Encrypt segments in-browser, upload via presigned URLs                              | Creator's browser                     |
| [Server](/server/overview)         | Full backend — content registry, API keys, key derivation, presign, admin dashboard | Docker container                      |
| [Key Server](/key-server/overview) | Standalone key derivation and viewer auth                                           | Docker container or Cloudflare Worker |
| [Player](/player/overview)         | Fetch manifest, get key, decrypt segments, render video                             | Viewer's browser                      |

<Expandable title="Internal architecture">
  Under the hood, BlindCast is built on four npm packages:

  * `@blindcast/crypto` — Pure Web Crypto API wrapper (AES-CBC, AES-GCM, AES-KW). Zero dependencies.
  * `@blindcast/keys` — HKDF key derivation, key hierarchies, Express + Worker key servers.
  * `@blindcast/server` — Drop-in Docker server: content registry, API keys, admin dashboard, composed from keys + storage.
  * `@blindcast/player` — hls.js custom loaders for encrypted HLS playback.
  * `@blindcast/storage` — Encrypt-then-upload pipeline with browser and Node.js transports.

  The CLI, Uploader, and Key Server Docker image are built on top of these packages. You don't need to interact with them directly.
</Expandable>

## Next steps

* [Quick Start](/getting-started/quick-start) — see encrypted playback in \~10 minutes
* [Zero-Knowledge Explained](/introduction/zero-knowledge-explained) — understand the trust model
