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.

Last updated