# v0.5 – Contract-Backed ACL for Dedicated Private Sessions (Session-Based Only)

> Status: Design aligned with current mock implementation\
> Scope: **Session-based privacy and key access only** (no task-level ACL)

***

### 1. Purpose and Scope

v0.5 introduces an **on-chain ACL layer** for **private, dedicated sessions**, replacing most router env-based ACL logic for these flows.

Key points:

* **Scope = session only**
  * Keys are derived and authorized **per `sessionId`**, not per task.
* **One-way privacy**
  * A session becomes *encrypted/private* the first time a miner is added.
  * It **cannot** be downgraded back to non-encrypted.
* **Router integration change**
  * Router must stop treating `ENCRYPTION_ALLOWED_LIST` as the primary ACL source for dedicated private sessions.
  * Router must read from the **session-level allowlist** in this module, **cache it**, and only fall back to env in explicit legacy/emergency modes.

This doc describes the **actual mock implementation**:

* State:
  * `sessionAllowedMiners`
  * `sessionAllowedMinerList`
  * `sessionAllowedMinerIndex`
  * `sessionEncryptionEnabled`
* Functions:
  * `addSessionAllowedMiner`
  * `removeSessionAllowedMiner`
  * `getAllowedMinersCount`
  * `getAllowedMiners`

Session ownership is still resolved via `SessionData` (not in this module).

***

### 2. On-Chain State (Current Mock Implementation)

#### 2.1 Allowlist Core

State layout:

* `mapping(uint256 => mapping(address => bool)) public sessionAllowedMiners;`\
  Membership mapping (already existed).
* `mapping(uint256 => address[]) private sessionAllowedMinerList;`\
  Backing array to enable enumeration/pagination (new, private).
* `mapping(uint256 => mapping(address => uint256)) private sessionAllowedMinerIndex;`\
  1-based index for O(1) removal via swap-and-pop (new, private).
* `mapping(uint256 => bool) public sessionEncryptionEnabled;`\
  Session-level privacy flag.

Semantics:

* `sessionAllowedMiners[sessionId][miner]`\
  → `true` if `miner` is currently allowlisted for that session.
* `sessionAllowedMinerList[sessionId]`\
  → backing array to enumerate allowlisted miners.
* `sessionAllowedMinerIndex[sessionId][miner]`\
  → 1-based index into `sessionAllowedMinerList[sessionId]`; `0` means “not present”.\
  Used for **O(1)** removal via swap-and-pop.
* `sessionEncryptionEnabled[sessionId]`\
  → privacy flag for that session:
  * `false` (default): session is not private/encrypted.
  * `true`: session is **private/encrypted**, cannot be turned off.

***

### 3. Functions and Behavior

#### 3.1 `addSessionAllowedMiner(sessionId, miner)`

Purpose:

* Add a miner to the allowlist for a session.
* Auto-enable session encryption on first use.

Key behavior:

* **Ownership guard**:
  * Only the **session owner** (from `SessionData`) can call this.
* **Idempotent**:
  * If `sessionAllowedMiners[sessionId][miner]` is already `true`, function is a no-op (beyond the ownership check).
* **When miner is not present**:
  1. Set `sessionAllowedMiners[sessionId][miner] = true`.
  2. Append `miner` to `sessionAllowedMinerList[sessionId]`.
  3. Set `sessionAllowedMinerIndex[sessionId][miner] = newIndex` (1-based).
* **Auto-encryption**:
  * If this is the *first* miner added for the session:
    * Set `sessionEncryptionEnabled[sessionId] = true`.
    * This is **one-way**: there is no function to set it back to `false`.

Complexity:

* Time: **O(1)** for membership check + append.
* Storage: grows with number of allowlisted miners per session.

***

#### 3.2 `removeSessionAllowedMiner(sessionId, miner)`

Purpose:

* Remove a miner from the allowlist in **O(1)** time.

Key behavior:

* **Ownership guard**:
  * Only the **session owner** (from `SessionData`) can call this.
* Removal pattern (swap-and-pop):
  * Look up `idx = sessionAllowedMinerIndex[sessionId][miner]`.
  * If `idx == 0`, miner not present → no-op or revert (implementation choice).
  * Else:
    * Compute `lastIdx = sessionAllowedMinerList[sessionId].length`.
    * If `idx != lastIdx`, swap:
      * `last = sessionAllowedMinerList[sessionId][lastIdx - 1]`.
      * `sessionAllowedMinerList[sessionId][idx - 1] = last`.
      * `sessionAllowedMinerIndex[sessionId][last] = idx`.
    * Pop the last element from `sessionAllowedMinerList[sessionId]`.
    * Set:
      * `sessionAllowedMinerIndex[sessionId][miner] = 0`.
      * `sessionAllowedMiners[sessionId][miner] = false`.

Notes:

* **Does not** change `sessionEncryptionEnabled[sessionId]`:
  * Once encryption is enabled, the session stays private/encrypted even if list becomes empty.

Complexity:

* Time: **O(1)**.

***

#### 3.3 `getAllowedMinersCount(sessionId) → uint256`

Purpose:

* Return the size of the allowlist for a session.

Implementation:

* `return sessionAllowedMinerList[sessionId].length;`

Use:

* Call this first before pagination to ensure `offset < count`.

***

#### 3.4 `getAllowedMiners(sessionId, offset, limit) → address[]`

Purpose:

* Paged enumeration of allowlisted miners.

Constraints:

* `offset < totalCount` must hold, otherwise the call will revert.
* For an empty list (`totalCount == 0`), **any** call will revert:
  * Callers must always check `getAllowedMinersCount(sessionId)` first.

Usage:

1. Get total:
   * `uint256 total = getAllowedMinersCount(sessionId);`
2. Page through:
   * Ensure `offset < total`.
   * Fetch a page of size `limit` starting at `offset`.

Example:

* `offset = 0`, `limit = 50` to fetch first page.
* `offset = 50`, `limit = 50` for second page, etc.

Complexity:

* Time: **O(k)** where `k` = size of returned page (`limit`).

***

### 4. How Router Uses This ACL (v0.5, Session-Only)

#### 4.1 Privacy Flag

When a router handles a request for a given `sessionId`:

* Check:
  * `isPrivate = sessionEncryptionEnabled(sessionId);`
* If `isPrivate == true`:
  * Treat the session as **private/encrypted**.
  * All `/api/v2/completion` traffic for that session must:
    * Use **offchain payload v2** with encryption.
    * Disable streaming.
  * Key issuance must enforce ACL via `sessionAllowedMiners`.
* If `isPrivate == false`:
  * Session is non-private/plain.
  * Router can treat it as a normal (unencrypted) session unless explicitly configured otherwise.

***

#### 4.2 Key Issuance (`/api/v1/auth/payload_enc_key/session`)

For **miners**:

1. Caller sends:
   * `session_id`
   * `address` (or implied from signature)
   * `signature` over canonical scope string.
2. Router constructs scope string, for example:
   * `"session:" + session_id`
3. Router verifies signature (e.g., `verify_addr_str_message(scope_str)`).
4. Router verifies ACL:
   * If `sessionEncryptionEnabled(sessionId) == true`:
     * Check `sessionAllowedMiners(sessionId, minerAddr)` via the **public getter**.
   * Else:
     * For non-private sessions, router may still support legacy env-based logic if needed.
5. If `allowed == true`:
   * Derive scoped key with current `key_version`.
   * Return key + version to miner.

**Important:**\
Router implementation must be updated so that for **private dedicated sessions**:

* **Primary ACL is the contract**, not `ENCRYPTION_ALLOWED_LIST`.
* Env-based ACL is **optional fallback** and should be guarded by an explicit feature flag.

***

#### 4.3 Router Caching Strategy

Even though `sessionAllowedMiners` and `sessionEncryptionEnabled` are cheap to read:

* Routers should **cache** for hot sessions:
  * `sessionEncryptionEnabled(sessionId)`
  * Membership checks for active `(sessionId, minerAddr)` pairs.
* Cache invalidation/update should be driven by:
  * Events (if implemented), or
  * Periodic polling with TTL.

This is especially important when many miners are requesting keys for the same active sessions.

***

### 5. Interaction With v0 Private / Encrypted Inference

v0 private inference is **router-centric** and uses:

* Session-scoped encryption keys derived from an `ENCRYPTION_SEED`/keyring.
* Auth endpoints:
  * `/api/v1/auth/payload_enc_key/session`
* Offchain v2 encrypted payloads with metadata such as:
  * `alg`, `scope_type`, `session_id`, `key_version`, `nonce`, `tag`, `ciphertext`, `created_at`.

v0.5 changes **who is allowed** to get keys:

* Instead of `.env`-only ACL (`ENCRYPTION_ALLOWED_LIST`), dedicated private sessions use:
  * `sessionEncryptionEnabled[sessionId]`
  * `sessionAllowedMiners[sessionId][miner]`

for authorization.

Env-based ACL can still exist for:

* Non-private sessions.
* Emergency overrides.
* Legacy testing environments.

But **for dedicated private sessions**, the on-chain ACL is canonical.

***

### 6. Dashboard / UX Flow

#### 6.1 Enabling a Dedicated Private Session

Typical flow:

1. User creates a session (`SessionData` + router).
2. Dashboard allows user to **configure a dedicated node address**.
3. When user chooses to **enable private mode**:
   * Dashboard calls `addSessionAllowedMiner(sessionId, dedicatedNodeAddr)` on-chain.
4. Effects:
   * `sessionAllowedMiners[sessionId][dedicatedNodeAddr] = true`.
   * `sessionEncryptionEnabled[sessionId] = true` (if first miner).
   * Dedicated node address appears in `sessionAllowedMinerList[sessionId]`.

Dashboard UX:

* Show a **one-way warning**:
  * Enabling privacy is **permanent** for this session.
  * Session will use encrypted offchain v2 payloads and **no streaming**.
  * Only allowlisted miners can obtain keys to decrypt payloads.

#### 6.2 Managing Allowlisted Miners

* Add a miner:
  * `addSessionAllowedMiner(sessionId, minerAddr)` (owner only).
* Remove a miner:
  * `removeSessionAllowedMiner(sessionId, minerAddr)` (owner only).
* Query size:
  * `count = getAllowedMinersCount(sessionId)`.
* Paginate:
  * Use `getAllowedMiners(sessionId, offset, limit)` with `offset < count`.

Notes:

* Removing all miners **does not** revert `sessionEncryptionEnabled[sessionId]` to `false`.
* Dashboard should surface this:
  * “Once privacy is enabled, this session stays private; you can change which miners are allowed, but not turn privacy off.”

***

### 7. Ownership & Guards

* **Only the session owner** (as defined in `SessionData`) can:
  * Add miners via `addSessionAllowedMiner`.
  * Remove miners via `removeSessionAllowedMiner`.

This keeps the ownership model simple:

* One source of truth for *who controls a session* (SessionData owner).
* Session-level ACL tracks *which miners are allowed* to serve its private workloads.

There is **no separate router-owner** in this module; router identity/ownership is enforced via `SessionData`.

***

### 8. Optional Next Improvements

The current mock implementation already covers:

* O(1) add/remove membership.
* Paged enumeration.
* One-way privacy enable flag.

Potential next steps:

1. **Events (recommended)**

   * `SessionEncryptionEnabled(sessionId, by, timestamp)`
   * `SessionMinerAllowedAdded(sessionId, miner, by, timestamp)`
   * `SessionMinerAllowedRemoved(sessionId, miner, by, timestamp)`

   These allow router processes to subscribe and immediately refresh caches instead of polling.
2. **Batch operations**

   * `addSessionAllowedMiners(sessionId, address[] miners)`
   * `removeSessionAllowedMiners(sessionId, address[] miners)`

   Most helpful when sessions need to add/remove many miners at once.
3. **Combined status view**

   * `getSessionPrivacyStatus(sessionId) returns (bool enabled, uint256 allowedCount)`

   Useful for:

   * Dashboards.
   * Router introspection APIs.
   * Scripts that need a quick privacy snapshot.

***

### 9. Summary

* v0.5 defines a **contract-backed, session-based ACL** for private dedicated sessions.
* Core state:
  * `sessionAllowedMiners[sessionId][miner]`
  * `sessionAllowedMinerList[sessionId]`
  * `sessionAllowedMinerIndex[sessionId][miner]`
  * `sessionEncryptionEnabled[sessionId]`
* Core behaviors:
  * `addSessionAllowedMiner` (idempotent, O(1), auto-enables encryption on first use).
  * `removeSessionAllowedMiner` (O(1) swap-and-pop).
  * `getAllowedMinersCount` and `getAllowedMiners` (paged enumeration).
* Router changes:
  * For **private dedicated sessions**, key issuance must use **this contract ACL**.
  * Env-based ACL becomes **legacy/emergency** only for these flows.
  * Router must be updated to **read from the contract, cache results**, and only fall back to env when explicitly configured.
* UX:
  * Privacy is **one-way per session**.
  * Dashboard aligns with contract behavior and enforces that once `sessionEncryptionEnabled` is `true`, it stays `true`.

This keeps v0.5 privacy **simple, session-scoped, and effective**, while still leaving room for future task-level scoping or more advanced privacy policies in later versions.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.cortensor.network/technical-architecture/data-management/private-encrypted-inference/v0.5-contract-backed-acl-for-dedicated-private-sessions-session-based-only.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
