# Private / Encrypted Inference v0 Spec

> **Status:** Design for Testnet Phase #3 (v0 only – dedicated sessions).\
> **Scope:** How private / encrypted inference works today on *dedicated-node* sessions, including env config, key issuance, offchain payload v2 format, router tool support, and end-to-end execution flow.

***

### 1. High-Level Summary

v0 private inference is **router-centric** and **session-scoped**, and is only supported on the **v2 completion surface**:

* Applies to:
  * `POST /api/v2/completion` (offchain payload–aware completion endpoint).
  * v1 completion endpoints remain non-encrypted / non-offchain for now.
* Only **dedicated sessions** are supported (a router is wired directly to a specific miner or miner group).
* The **router**:
  * Owns a secret keyring (`ENCRYPTION_SEED` + versions).
  * Issues scoped encryption keys via auth endpoints.
  * Encrypts all prompts/results for private sessions.
  * Stores encrypted blobs in an **offchain payload v2** store (e.g., S3).
  * Stores only **URN references** in the Session / SessionQueue records.
* **Miners**:
  * See offchain payload v2 objects with explicit encryption metadata.
  * Ask the router for the right key if they are allowed.
  * Decrypt, run inference, re-encrypt the result, and push it back offchain.
* **Apps** (router owner / dev):
  * Call `/api/v2/completion` against private sessions.
  * Receive decrypted final results from the router.
  * Do not see keys; encryption/decryption is handled inside the router.

Because entire payloads are encrypted, **streaming is not supported** for encrypted v0 workloads.

***

### 2. Components & Roles

* **Router Node**
  * Hosts:
    * Completion endpoint: `POST /api/v2/completion`.
    * Auth endpoints:
      * `POST /api/v1/auth/payload_enc_key/session`
      * `POST /api/v1/auth/payload_enc_key/task`
  * Owns:
    * Encryption keyring (`ENCRYPTION_SEED` + versioning).
    * `ENCRYPTION_ALLOWED_LIST` policy.
    * Offchain payload v2 integration (e.g., S3 bucket, URN scheme).
    * Tooling to:
      * Generate strong secrets for env.
      * Rotate keys and backfill encrypted payloads.
* **Dedicated Miner Node**
  * Assigned to one or more private sessions.
  * Holds an address (EOA) used to authenticate when requesting keys.
  * Has logic to:
    * Detect encrypted payloads in offchain v2.
    * Call router auth endpoints to get keys.
    * Decrypt, run models, re-encrypt outputs.
* **App / User**
  * Talks to router’s `/api/v2/completion`.
  * Selects which sessions are **private** (dedicated, encrypted).
  * Treats encrypted sessions as non-streaming.

***

### 3. v0 Env Configuration

#### 3.1 Encryption Seed (Keyring Root)

Basic v0 uses a single env seed as the root of a versioned keyring:

* `ENCRYPTION_SEED` – strong, random, secret seed used to derive scoped payload keys.

Example (conceptual):

```
ENCRYPTION_SEED="replace-with-strong-random-seed"
```

Keyring model (high-level):

* Internally, the router should behave as if it has:
  * `active_version = vN`
  * `legacy_versions = [vN-1, vN-2, ...]`
* For v0:
  * Encryption uses `active_version`.
  * Decryption checks `key_version` inside the encrypted payload metadata and selects the right seed/version.
  * This requires the router to **retain old seed material** (decrypt-only) until rotation and backfill are complete.

Key derivation (conceptual):

* Deterministic, scoped, versioned:

  K(scope, key\_version) = HKDF(seed\_for\_version, info = "cts:v0:" + scope)

Current SHA-256 derivation is acceptable for early v0, but HKDF-style derivation is the target.

#### 3.2 Allowed List – v0 Policy

`ENCRYPTION_ALLOWED_LIST` controls **who can request keys** and **for which scopes**.

Supported forms:

* Global address – can access keys for all sessions/tasks:

  ```
  ENCRYPTION_ALLOWED_LIST="0xAAA...AAA"
  ```
* Session-scoped – address allowed for a specific session:

  ```
  ENCRYPTION_ALLOWED_LIST="101:0xBBB...BBB"
  ```
* Task-scoped – address allowed for a specific session + task:

  ```
  ENCRYPTION_ALLOWED_LIST="101-9001:0xCCC...CCC"
  ```
* Combined example – commas inside a scope, semicolons between scopes:

  ```
  ENCRYPTION_ALLOWED_LIST="0xAAA...AAA;101:0xBBB...BBB;101-9001:0xCCC...CCC,0xDDD...DDD"
  ```

Semantics:

* `0xAAA...AAA`\
  → Address can request **any** scoped key (any session/task).
* `101:0xBBB...BBB`\
  → Address can request keys for **session 101** (session-level scope).
* `101-9001:0xCCC...CCC`\
  → Address can request keys for **session 101, task 9001** only.

Env format must have:

* No spaces.
* Addresses as `0x` + 40 hex characters.

***

### 4. v0 Auth Endpoints (Key Issuance)

These endpoints are **internal** to the Router’s private inference design and are called by trusted operators (router owner, miners). They are *not* general public APIs.

#### 4.1 Session-Level Key Issuance

* Endpoint: `POST /api/v1/auth/payload_enc_key/session`

Request body (conceptual):

* `address` – caller EOA.
* `signature` – signature over the canonical scope string.
* `session_id` – integer session ID.

Canonical signed message:

* For session scope:

  ```
  "session_id"
  ```

Example for `session_id = 101`:

* Signed string: `"101"`

Router behavior:

1. Verify `signature` matches `address` over `"101"`.
2. Check `ENCRYPTION_ALLOWED_LIST` to see if `address` is allowed for:
   * Global, or
   * `101`, or
   * Any compatible pattern (depending on policy rules).
3. If authorized, derive `payload_enc_key` from:
   * Current keyring seed and
   * Scope string (`"101"`),
   * Associated `key_version`.
4. Return:
   * `payload_enc_key`
   * `key_version` (e.g., `"v3"`).

#### 4.2 Task-Level Key Issuance

* Endpoint: `POST /api/v1/auth/payload_enc_key/task`

Request body (conceptual):

* `address`
* `signature`
* `session_id`
* `task_id`

Canonical signed message:

* For task scope:

  ```
  "session_id:task_id"
  ```

Example for `session_id = 101`, `task_id = 9001`:

* Signed string: `"101:9001"`

Router behavior is the same as session-level, but scope is the pair `(session_id, task_id)` and may use a different policy branch in `ENCRYPTION_ALLOWED_LIST`.

***

### 5. Offchain Payload v2 Format (Encrypted vs Plain)

#### 5.1 Plain Offchain Payload v2 (Baseline)

For **non-encrypted** workloads, offchain v2 is conceptually:

* A JSON document containing:
  * The prompt / input (in plaintext).
  * Optional helper metadata (timestamps, tags, etc.).

The Session / SessionQueue objects store only a **URN** pointing to this JSON blob in S3 (or another blob store). When a miner resolves the URN, it sees a normal JSON payload with plaintext fields.

#### 5.2 Encrypted Offchain v2 (v0 Private Inference)

For **encrypted** workloads, offchain v2 wraps payloads in an encryption envelope with explicit metadata.

Example envelope (conceptual):

```
{
  "version": "v2",
  "payload_type": "encrypted",
  "data": {
    "alg": "aes-256-gcm",
    "scope_type": "session",      // or "task"
    "session_id": 101,
    "task_id": 9001,
    "key_version": "v3",
    "nonce": "base64-encoded",
    "tag": "base64-encoded",
    "ciphertext": "base64-encoded",
    "created_at": "2026-03-01T12:34:56Z"
  }
}
```

Key points:

* `payload_type = "encrypted"` tells miners:
  * This is not plaintext; do not parse `ciphertext` directly.
* `data.alg` and related fields describe:
  * Encryption algorithm.
  * Scope type (session vs task).
  * Scope identifiers.
  * `key_version` used for derivation.
  * Nonce, tag, ciphertext, and timestamp.
* The `ciphertext` contains the entire original prompt JSON (offchain v2 payload body) encrypted under the scoped key.

For **plain** offchain v2 (non-encrypted), the payload would instead have:

* `payload_type = "plain"` (or omitted).
* A `data` object containing plaintext fields (e.g., `prompt`, `metadata`).

***

### 6. End-to-End Flow (v0 Dedicated Session)

This describes the full flow for **one private completion call** on a dedicated session using `/api/v2/completion`.

#### 6.1 One-Time Setup

1. **Router + Miner Pairing**
   * Operator configures a dedicated session (e.g., `session_id = 101`) and assigns it to a specific miner or miner group.
   * Session is marked as:
     * `private = true`
     * `offchain_v2 = true`
2. **Router Env**
   * Set `ENCRYPTION_SEED` to a strong random value.
   * Configure `ENCRYPTION_ALLOWED_LIST` so that:
     * The miner’s EOA is allowed globally or for session `101`.
     * The router owner/operator address is allowed as needed.
3. **Miner Config**
   * Miner has:
     * A configured EOA used as its identity.
     * Logic to:
       * Detect `payload_type = "encrypted"` in offchain v2.
       * Call `/api/v1/auth/payload_enc_key/*` with signed scope strings.
       * Perform AES-GCM decrypt/encrypt and model execution.

#### 6.2 App → Router: Private Completion Call

1. App calls:

   ```
   POST /api/v2/completion
   ```

   with:

   * `session_id = 101`
   * Prompt body in plaintext.
   * Implicitly or explicitly flagged as a **private session** (based on session config).
2. Router receives plaintext request:
   * Checks session `101` config:
     * Private + encrypted.
     * Offchain v2 enabled.
3. Router obtains a **session-level payload key**:
   * Derives from keyring (`ENCRYPTION_SEED` + active version) and scope `"101"`.
   * Internally equivalent to calling the session-level auth logic.
4. Router encrypts the prompt:
   * Uses `alg = aes-256-gcm`.
   * Generates `nonce`, computes `tag`, and produces `ciphertext`.
   * Wraps everything into an offchain v2 envelope with:
     * `payload_type = "encrypted"`.
     * `scope_type = "session"`.
     * `session_id = 101`.
     * `key_version` = current key version.
5. Router uploads encrypted payload to offchain storage (e.g., S3):
   * Stores object keyed by URN, such as:

     ```
     urn:cts:offchain:v2:payload:...
     ```
6. Router updates Session / SessionQueue:
   * Stores **only the URN** (not the plaintext prompt) in its internal records.
7. Router enqueues work for the dedicated miner:
   * Job contains:
     * URN.
     * Session ID / task metadata.
   * No plaintext leaves the router.

#### 6.3 Miner: Decrypt, Run, Re-Encrypt

1. Miner sees a new job for `session_id = 101` with a URN.
2. Miner resolves URN → fetches the offchain v2 blob:
   * Sees:
     * `version = "v2"`
     * `payload_type = "encrypted"`
     * Encryption metadata inside `data`.
3. Miner decides it needs a scoped key:
   * Scope for session-level:

     ```
     "101"
     ```
4. Miner signs the scope string `"101"` with its EOA to generate `signature`.
5. Miner calls:

   ```
   POST /api/v1/auth/payload_enc_key/session
   ```

   with:

   * `address` (miner EOA).
   * `session_id = 101`.
   * `signature` (over `"101"`).
6. Router verifies:
   * Signature is valid for `address`.
   * `address` is authorized by `ENCRYPTION_ALLOWED_LIST`.
7. Router derives `payload_enc_key` and returns it plus `key_version`.
8. Miner uses:
   * `payload_enc_key`
   * `nonce` and `tag` from offchain v2 envelope\
     to decrypt `ciphertext` and recover the original prompt JSON.
9. Miner runs inference locally:
   * Uses whatever model/config is attached to session `101`.
10. Miner prepares encrypted result envelope:
    * Encrypts the model output JSON using the same scoped key and `key_version`.
    * Produces a new offchain v2 object with:
      * `payload_type = "encrypted"`.
      * Updated `ciphertext`, `nonce`, `tag`, `created_at`.
11. Miner uploads encrypted result to offchain storage:
    * Gets a **result URN** for the encrypted output.
12. Miner reports completion back to the router:
    * Updates Session / job record with the result URN.

#### 6.4 Router → App: Decrypt and Return Result

1. Router sees that the job for session `101` is complete:
   * Reads result URN from Session / SessionQueue.
2. Router resolves URN → fetches encrypted result envelope.
3. Router derives or fetches the scoped key:
   * Uses `session_id = 101`.
   * Reads `key_version` from the envelope.
   * Uses keyring to get the correct seed/version.
4. Router decrypts the result:
   * Recovers the plaintext model output JSON.
5. Router returns plaintext result to the app:
   * `/api/v2/completion` response is a normal completion payload (no encryption envelope).

To the app, this looks like a normal completion; the privacy path between router and miner is invisible.

***

### 7. Streaming & Behavior Differences in v0

Because v0 encrypts **entire payloads**:

* `/api/v2/completion` for private sessions is **non-streaming**:
  * Router buffers full prompt → encrypts → offchain.
  * Router buffers full result → decrypts → returns to caller.
* v1 completion endpoints are unaffected and behave as before (no offchain v2 + encryption).
* Logs / telemetry for private sessions must:
  * Avoid logging plaintext prompts/results.
  * Prefer URNs and high-level metadata.

***

### 8. Quick Reference – v0 Checklist

To run v0 private dedicated sessions safely:

1. **Router**
   * Has `ENCRYPTION_SEED` configured and stored securely.
   * Has `ENCRYPTION_ALLOWED_LIST` configured:
     * Global dev/operator and/or specific miner addresses.
   * Supports:
     * `/api/v2/completion` with offchain v2.
     * `/api/v1/auth/payload_enc_key/session` and `/api/v1/auth/payload_enc_key/task`.
   * Integrates:
     * Offchain payload v2 store (e.g., S3).
     * URN generation and resolution.
2. **Sessions**
   * Are explicitly marked as:
     * Dedicated → bound to specific miner(s).
     * Private → require encryption.
     * Offchain v2 → store payloads in blob store, URNs in DB.
3. **Miners**
   * Have an EOA identity.
   * Implement:
     * URN resolution.
     * Encrypted envelope detection and AES-GCM decrypt/encrypt.
     * Auth calls to key issuance endpoints.
   * Never store plaintext payloads in logs.
4. **Apps**
   * Use `/api/v2/completion` against private sessions.
   * Treat them as non-streaming calls.
   * Handle standard plaintext completion responses.

***

### 9. v0 Tooling for Secrets & Key Rotation

To make v0 operational, the router needs **two main tooling paths**:

1. **Secret generation tooling** (one-time or periodic).
2. **Key rotation + backfill tooling** (ongoing operational safety).

All of this applies specifically to **`/api/v2/completion`** and offchain v2 encrypted workloads.

#### 9.1 Secret Generation Tool (Router Operator Utility)

Goal:

* Easily generate a strong `ENCRYPTION_SEED` and write it into router env/config.

Expected behavior:

* CLI (example shape):

  ```
  router-encryption init-seed \
    --config /etc/cortensor/router.env \
    --seed-bytes 32
  ```
* Responsibilities:
  * Generate cryptographically strong random bytes for `ENCRYPTION_SEED`.
  * Encode as safe ASCII (e.g., hex or base64).
  * Write or update env/config file (or secrets backend).
  * Optionally print:
    * Seed fingerprint (e.g., first 8 bytes of hash) for ops verification.
  * Never log the full seed to stdout in production mode.

Notes:

* Only run on **fresh** environments or during a controlled rotation flow.
* Should verify that:
  * The file permissions for config are appropriate.
  * The router will reload or be restarted after the change.

#### 9.2 Key Rotation & Backfill Tool (Router-Side)

Goal:

* Rotate `ENCRYPTION_SEED` and re-encrypt all **router-owned, dedicated private sessions** without breaking references (URNs stay valid).

Constraints:

* Offchain v2 objects must be re-encrypted **in place** or with new blobs under the **same URN** (or under a new URN plus a pointer update).
* Old keys must remain available in **decrypt-only** mode until migration is complete.

Conceptual flow:

1. **Preparation**
   * Generate a new seed (via the Secret Generation Tool).
   * Add it to the keyring as:
     * `key_version = vN+1` (active).
   * Mark old seeds (`vN`, `vN-1`, etc.) as decrypt-only.
2. **Session Enumeration**
   * Tool scans router storage to find:
     * All sessions configured as:
       * Private = true.
       * Offchain v2 = true.
       * Dedicated node sessions owned by this router.
   * For each such session, tool enumerates:
     * Prompt payload URNs.
     * Result payload URNs (if needed).
3. **Backfill Pass (Re-encrypt)**
   * For each URN:
     * Fetch current offchain v2 envelope.
     * Read `key_version` (old version).
     * Use keyring to get the right old key.
     * Decrypt `ciphertext` into plaintext JSON.
     * Re-encrypt plaintext JSON with **new active key version**.
     * Produce a new envelope with:
       * Same structure.
       * Updated `key_version` = active version.
       * New nonce and tag.
     * Write back:
       * Either:
         * Overwrite existing blob at the same URN, or
         * Write a new blob and update router’s internal references to point to the new URN.
   * Maintain audit logs:
     * URN processed.
     * Old `key_version`.
     * New `key_version`.
     * Success/failure status.
4. **Verification**
   * For a sample of URNs:
     * Run a test decrypt path using the active key version.
     * Confirm that:
       * Decryption succeeds.
       * The payload is structurally valid (basic JSON checks).
   * Optionally:
     * Run a dry-run mode that:
       * Decrypts and re-encrypts in memory only.
       * Does not write changes, just validates key readiness.
5. **Finalize**
   * Once all dedicated private session payloads are re-encrypted:
     * Mark old key versions as:
       * “decrypt-only allowed for grace period” or
       * Fully retired (if no old payloads remain).
   * Remove old seeds from keyring when safe.

CLI sketch (example):

```
router-encryption rotate-keys \
  --config /etc/cortensor/router.env \
  --from-version v3 \
  --to-version v4 \
  --scope dedicated-private \
  --dry-run=false
```

Behavior notes:

* **Scope**:
  * In v0, this tool focuses only on:
    * Dedicated node sessions.
    * Router-owned offchain v2 URNs.
  * Shared pools and ephemeral sessions come later (v1+).
* **Safety**:
  * Never delete old blobs or seeds until:
    * Backfill has completed,
    * Verification passed,
    * A grace window has elapsed.
* **Performance**:
  * Should be resumable:
    * Track progress per URN.
    * Allow stopping and continuing without re-processing successfully migrated payloads.

#### 9.3 Router Behavior During Rotation

While rotation/backfill is in progress:

* Decryption path:
  * Must look at `key_version` inside the encrypted envelope.
  * Select the appropriate seed from keyring.
  * Support:
    * Both old and new versions for the migration period.
* Encryption path (for new calls):
  * Must always use the **active** key version only.

This ensures that:

* New traffic is always protected under the newest key version.
* Legacy data is gradually migrated without downtime.

***

This v0 detail document, plus the separate privacy roadmap (v0 → v3/v4 + TEEs), together define how Cortensor introduces **practical, operational private inference** on dedicated sessions:

* Router and miners share scoped, deterministic keys via auth endpoints.
* Payloads move offchain in encrypted v2 envelopes.
* `/api/v2/completion` becomes the **primary encrypted workload entry**.
* Router tools manage:
  * Strong secret generation.
  * Safe key rotation and backfill over time, without breaking URNs or session history.
