NFT-Style Capability Token System
iris advanced 6 min read
ELI5
A capability token is like a VIP backstage pass at a concert. It says exactly which areas you can access (capabilities), which concert it’s for (council scope), when it expires (TTL), and it’s signed by the venue so security knows it’s real. If you lose it or misbehave, the venue can cancel it immediately (revocation).
Technical Deep Dive
CapabilityToken Data Model
classDiagram class CapabilityToken { +UUID token_id +UUID iris_sprite_id +UUID meridian_council_id +string[] capabilities +datetime issued_at +datetime expires_at +dict metadata +boolean is_active +to_dict() dict +compute_signature() string +to_string() string }Token Generation Flow
flowchart TD A["TokenGenerator.generate_token()"] --> B["Validate TTL ≤ max_token_ttl_seconds"] B --> C["Fetch sprite from iris-service"] C --> D{"Requested capabilities ⊆ sprite capabilities?"} D -->|No| E["ValueError: Invalid capabilities"] D -->|Yes| F["Create CapabilityToken"] F --> G["Serialize to canonical dict"] G --> H["Sign with HMAC-SHA256"] H --> I["Format: {token_id}.{sig_32chars}"] I --> J["Return TokenResponse"]Token Properties
| Property | Value | Description |
|---|---|---|
| Token ID | UUID v4 | Unique identifier for this token instance |
| Sprite ID | UUID | The IRIS sprite this token authorises |
| Council ID | UUID | The MERIDIAN council this token is scoped to |
| Capabilities | string[] | Subset of the sprite’s declared capabilities |
| Issued At | ISO 8601 | Token creation timestamp |
| Expires At | ISO 8601 | Calculated as issued_at + ttl_seconds |
| Max TTL | 86,400s (24h) | Configurable via AdapterConfig.max_token_ttl_seconds |
| Default TTL | 3,600s (1h) | Used when no TTL specified |
Token Serialization (Deterministic)
def to_dict(self): return { "token_id": str(self.token_id), "iris_sprite_id": str(self.iris_sprite_id), "meridian_council_id": str(self.meridian_council_id), "capabilities": sorted(self.capabilities), # Sorted for determinism "issued_at": self.issued_at.isoformat(), "expires_at": self.expires_at.isoformat(), "metadata": self.metadata, }Signing Algorithm
import hmacimport hashlibimport json
def compute_signature(self): payload = json.dumps(self.to_dict(), separators=(",", ":"), sort_keys=True) # ⚠️ Production: replace with secure key management key = b"hardcoded-signing-key-replace-in-production" return hmac.new(key, payload.encode(), hashlib.sha256).hexdigest()Token string format: {token_id}.{first_32_chars_of_signature}
Example: 550e8400-e29b-41d4-a716-446655440000.a3f5c7d9e1b2f4a6c8d0e2f4a6b8c0d2
Token Verification
sequenceDiagram participant Client participant Adapter as iris-meridian-adapter participant Store as MappingStore
Client->>Adapter: verify_token(token_string, token_id?) Adapter->>Adapter: Parse "{id}.{sig}" format Adapter->>Store: Lookup token by token_id alt Token not found Adapter-->>Client: Invalid else Token found Adapter->>Adapter: Recompute signature from stored payload Adapter->>Adapter: Compare with provided signature alt Signatures match AND not expired AND active Adapter-->>Client: Valid else Adapter-->>Client: Invalid end endRevocation
Tokens can be revoked by:
- Setting
active=Falsein the mapping store - The
is_activeproperty checks bothactiveflag andexpires_attime
@propertydef is_active(self): return self.active and datetime.now(timezone.utc) < self.expires_atToken Fingerprint (Integrity)
The TokenResponse protobuf includes a token_fingerprint field — a Blake3 hash of the token’s canonical JSON payload:
token_fingerprint = blake3.blake3(payload_json.encode()).hexdigest()This provides an additional integrity check independent of the HMAC signature.
Key Terms
- Capability token → A time-limited, signed, council-scoped authorization credential
- Capability-based security → Access control based on what actions are permitted (capabilities), not who you are (identity)
- HMAC-SHA256 → Hash-based Message Authentication Code using SHA-256; provides integrity and authenticity
- Token revocation → Invalidating a token before its natural expiration by setting
active=False - Deterministic serialization → Sorting keys and capabilities so the same logical token always produces the same bytes for signing
- Token fingerprint → Blake3 hash of token payload for additional integrity verification
Q&A
Q: Why HMAC-SHA256 instead of Blake3 for signing?
A: HMAC is a standard construction for message authentication. While Blake3 has a built-in keyed mode (blake3(key=...)), HMAC-SHA256 is more widely supported and audited. The token’s integrity is additionally protected by a Blake3 fingerprint in the response.
Q: Can a token grant capabilities the sprite doesn’t have?
A: No. TokenGenerator.validate_capabilities() ensures requested capabilities are a subset of the sprite’s declared capabilities. This prevents privilege escalation.
Q: What happens if the signing key is compromised? A: All tokens signed with that key could be forged. Production deployments must use a secure key management system with key rotation. The current hardcoded key is a placeholder.
Q: How long should tokens live? A: The default is 1 hour, max is 24 hours. Short-lived tokens reduce the window of compromise. For long-running operations, clients should request fresh tokens or implement refresh logic.
Q: Can tokens be renewed?
A: Not directly. The current implementation issues new tokens rather than renewing existing ones. A refresh mechanism could be added by extending TokenGenerator.
Examples
Capability tokens are like ski resort lift tickets:
- Token ID = The unique barcode on your ticket
- Sprite = You (the skier)
- Council = The specific ski resort (not valid at other resorts)
- Capabilities = What you’re allowed to do: “green runs”, “blue runs”, “ski rental” (but not “helicopter skiing” unless you paid for that)
- Expiration = End of day (ticket stops working at 5 PM)
- Signature = The holographic stamp that proves the ticket is real, not photocopied
- Revocation = If you violate safety rules, the resort cancels your ticket at the gate
- Fingerprint = A microscopic security thread woven into the ticket paper
neighbors on the map
- Cross-System Fingerprint Verification verifying sprite identity across IRIS and MERIDIAN
- Sprite Data Model & Schema creating or validating a new sprite