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_LISTas 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:
sessionAllowedMinerssessionAllowedMinerListsessionAllowedMinerIndexsessionEncryptionEnabled
Functions:
addSessionAllowedMinerremoveSessionAllowedMinergetAllowedMinersCountgetAllowedMiners
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]→trueifmineris currently allowlisted for that session.sessionAllowedMinerList[sessionId]→ backing array to enumerate allowlisted miners.sessionAllowedMinerIndex[sessionId][miner]→ 1-based index intosessionAllowedMinerList[sessionId];0means “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)
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 alreadytrue, function is a no-op (beyond the ownership check).
When miner is not present:
Set
sessionAllowedMiners[sessionId][miner] = true.Append
minertosessionAllowedMinerList[sessionId].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)
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
getAllowedMinersCount(sessionId) → uint256Purpose:
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[]
getAllowedMiners(sessionId, offset, limit) → address[]Purpose:
Paged enumeration of allowlisted miners.
Constraints:
offset < totalCountmust hold, otherwise the call will revert.For an empty list (
totalCount == 0), any call will revert:Callers must always check
getAllowedMinersCount(sessionId)first.
Usage:
Get total:
uint256 total = getAllowedMinersCount(sessionId);
Page through:
Ensure
offset < total.Fetch a page of size
limitstarting atoffset.
Example:
offset = 0,limit = 50to fetch first page.offset = 50,limit = 50for 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/completiontraffic 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)
/api/v1/auth/payload_enc_key/session)For miners:
Caller sends:
session_idaddress(or implied from signature)signatureover canonical scope string.
Router constructs scope string, for example:
"session:" + session_id
Router verifies signature (e.g.,
verify_addr_str_message(scope_str)).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.
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:
User creates a session (
SessionData+ router).Dashboard allows user to configure a dedicated node address.
When user chooses to enable private mode:
Dashboard calls
addSessionAllowedMiner(sessionId, dedicatedNodeAddr)on-chain.
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)withoffset < count.
Notes:
Removing all miners does not revert
sessionEncryptionEnabled[sessionId]tofalse.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:
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.
Batch operations
addSessionAllowedMiners(sessionId, address[] miners)removeSessionAllowedMiners(sessionId, address[] miners)
Most helpful when sessions need to add/remove many miners at once.
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).getAllowedMinersCountandgetAllowedMiners(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
sessionEncryptionEnabledistrue, it staystrue.
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