baseid-delegation
Delegate scoped credential access to agents, employees, or representatives. Each delegation is claim-restricted, time-limited, and chain-validated. Supports multi-hop re-delegation with automatic scope narrowing and encrypted credential exchange via DIDComm.
Key Features
Section titled “Key Features”- Delegation Scope — claim restrictions, depth limits, time bounds, purpose tagging
- Delegation Tokens — signed grants of scoped access with Ed25519/P-256 signatures
- Chain Validation — multi-hop delegation with scope narrowing (each re-delegation can only reduce permissions)
- Delegation Manager —
delegate(),present_as_delegate(),verify_delegated()high-level API - Encrypted Exchange — DIDComm-signed credential packaging for intermediary-blind relay
Quick Start
Section titled “Quick Start”use baseid_delegation::*;use baseid_crypto::KeyPair;use baseid_core::types::KeyType;
// Generate a signing keylet key = KeyPair::generate(KeyType::Ed25519)?;
// Define what the delegate can dolet scope = ScopeBuilder::new("age verification") .allow_claims(&["given_name", "age"]) .max_depth(0) // no re-delegation .valid_for_seconds(3600) // 1 hour .build()?;
// Create a delegation tokenlet token = delegate( "cred-123", // credential reference "did:key:alice", // delegator "did:key:bob", // delegate scope, 0, // depth &key, // signer)?;
// Build a chain and presentlet chain = DelegationChain::new(token);let presentation = present_as_delegate( b"credential-jwt-data", chain, vec!["given_name".to_string()],)?;
// Verify the delegated presentationlet result = verify_delegated(&presentation)?;assert!(result.valid);assert!(result.chain_valid);assert!(result.scope_compliant);Delegation Scope
Section titled “Delegation Scope”A DelegationScope defines the boundaries of what a delegate may do:
| Field | Description |
|---|---|
allowed_claims | Which claims the delegate may present (empty = all) |
max_depth | Maximum re-delegation depth (0 = cannot re-delegate) |
valid_until | Expiration timestamp |
valid_from | Optional start time |
purpose | Human-readable purpose description |
Use ScopeBuilder for ergonomic construction:
let scope = ScopeBuilder::new("employment verification") .allow_claims(&["employer", "title", "start_date"]) .max_depth(1) // allow one re-delegation .valid_for_seconds(86400) // 24 hours .build()?;
// Check claim accessassert!(scope.is_claim_allowed("employer"));assert!(!scope.is_claim_allowed("salary")); // not in scopeassert!(!scope.is_expired());Scope Narrowing
Section titled “Scope Narrowing”When re-delegating, the new scope must be a subset of the parent scope. The narrow() method enforces this:
- Claims must be a subset (or equal)
- Depth must be less or equal
- Expiry must be same or earlier
- Attempting to widen scope returns
DelegationError::ScopeWidening
let parent = ScopeBuilder::new("parent") .allow_claims(&["name", "age", "address"]) .max_depth(2) .build()?;
// This succeeds (subset)let child = ScopeBuilder::new("child") .allow_claims(&["name", "age"]) .max_depth(1) .build()?;let narrowed = parent.narrow(&child)?;
// This fails (ssn not in parent)let invalid = ScopeBuilder::new("bad") .allow_claims(&["name", "ssn"]) .build()?;assert!(parent.narrow(&invalid).is_err());Delegation Tokens
Section titled “Delegation Tokens”A DelegationToken is a signed grant of scoped access:
let token = DelegationToken::create( "did:key:alice", // delegator "did:key:bob", // delegate scope, "cred-ref-123", // credential reference 0, // depth &signing_key, // Ed25519/P-256 signer)?;
// Verify signatureassert!(token.verify_signature(&signing_key.public));
// Check propertiesassert!(!token.is_expired());assert!(!token.can_redelegate()); // max_depth=0Tokens are fully serializable via serde (JSON round-trip supported).
Delegation Chains
Section titled “Delegation Chains”A DelegationChain tracks multi-hop delegation from original holder to current delegate:
// Alice delegates to Boblet t1 = delegate("cred", "did:key:alice", "did:key:bob", scope1, 0, &key_a)?;
// Bob re-delegates to Carol (narrower scope)let t2 = delegate("cred", "did:key:bob", "did:key:carol", scope2, 1, &key_b)?;
let mut chain = DelegationChain::new(t1);chain.extend(t2)?; // validates chaining rules
assert!(chain.verify().is_ok());assert_eq!(chain.original_delegator(), Some("did:key:alice"));assert_eq!(chain.current_delegate(), Some("did:key:carol"));assert_eq!(chain.depth(), 1);assert_eq!(chain.len(), 2);Chain validation enforces:
- Delegate continuity — previous delegate must equal next delegator
- Depth increment — each hop increments depth by 1
- Re-delegation allowed — previous token’s
max_depth > current depth - Scope narrowing — each hop can only reduce permissions
- Credential match — all tokens reference the same credential
- Expiry — no expired tokens in the chain
Manager Operations
Section titled “Manager Operations”The manager module provides three high-level functions:
delegate() — Create a delegation token
Section titled “delegate() — Create a delegation token”let token = delegate( "credential-ref", "did:key:delegator", "did:key:delegate", scope, 0, // depth &signer, // &dyn Signer)?;present_as_delegate() — Present credentials as delegate
Section titled “present_as_delegate() — Present credentials as delegate”Validates the chain and checks that disclosed claims are within scope:
let presentation = present_as_delegate( b"credential-data", chain, vec!["name".to_string(), "age".to_string()],)?;Returns DelegationError::ScopeViolation if any disclosed claim is outside scope.
verify_delegated() — Verify a delegated presentation
Section titled “verify_delegated() — Verify a delegated presentation”let result = verify_delegated(&presentation)?;// DelegatedVerificationResult:// chain_valid: bool — chain structure valid// scope_compliant: bool — disclosed claims within scope// valid: bool — overall validity (chain + scope + not expired)// delegator: String — original delegator DID// delegate: String — current delegate DID// depth: u32 — chain depthEncrypted Exchange
Section titled “Encrypted Exchange”Share credentials through intermediaries without revealing content, using DIDComm v2 signed messages.
Direct exchange
Section titled “Direct exchange”use baseid_delegation::{encrypt_for_did, decrypt_credential};
// Sender packages credentiallet encrypted = encrypt_for_did( b"credential-jwt-here", "did:key:recipient", &sender_key, // &dyn Signer "did:key:sender", "did:key:sender#key-1",)?;
// Recipient decryptslet decrypted = decrypt_credential(&encrypted, &sender_key.public)?;assert_eq!(decrypted, b"credential-jwt-here");Relay package
Section titled “Relay package”Package a credential with its delegation token for intermediary relay. The relay can verify the delegation without seeing the credential content:
use baseid_delegation::{package_for_relay, decrypt_credential};
let package = package_for_relay( b"credential-payload", &delegation_token, &sender_key, "did:key:sender", "did:key:sender#key-1",)?;
// Relay can inspect delegation_token but not credentialassert_eq!(package.delegation_token.delegate, "did:key:bob");
// Recipient decryptslet data = decrypt_credential(&package.envelope, &sender_key.public)?;Server API
Section titled “Server API”The BaseID server exposes three delegation endpoints:
POST /api/delegation/delegate
Section titled “POST /api/delegation/delegate”Create a delegation token.
{ "credential_ref": "cred-123", "delegator_did": "did:key:alice", "delegate_did": "did:key:bob", "allowed_claims": ["name", "age"], "max_depth": 0, "valid_seconds": 3600, "purpose": "age verification"}Returns 201 Created with the signed DelegationToken JSON.
POST /api/delegation/verify-chain
Section titled “POST /api/delegation/verify-chain”Verify a delegation chain.
{ "chain": { "tokens": [{ "...token..." }] }}Returns chain validity, delegator, delegate, depth, and link count.
POST /api/delegation/present
Section titled “POST /api/delegation/present”Present a credential as a delegate and verify the presentation.
{ "credential_data": "credential-jwt-string", "chain": { "tokens": [{ "...token..." }] }, "disclosed_claims": ["name"]}Returns 200 OK with DelegatedVerificationResult or 400 Bad Request on scope violation.
Agentic AI Use Case
Section titled “Agentic AI Use Case”Credential delegation is designed for scenarios where an AI agent acts on behalf of a human:
- User creates delegation — scoped to specific claims, time-limited, purpose-tagged
- Agent receives token — cannot exceed delegated scope
- Agent presents credentials — verifier sees the full delegation chain
- Verifier validates — chain integrity, scope compliance, expiry, and original delegator identity
This enables use cases like:
- AI assistant presenting age verification on behalf of the user
- Employee agent submitting compliance credentials within defined boundaries
- Healthcare proxy presenting patient consent with claim restrictions
Error Types
Section titled “Error Types”All errors implement BilingualError (EN/FR):
| Error | Description |
|---|---|
ScopeViolation | Disclosed claim not in delegation scope |
ChainBroken | Chain continuity violated (delegate mismatch, depth gap) |
Expired | Delegation token has expired |
DepthExceeded | Re-delegation depth exceeds maximum |
InvalidToken | Token creation or validation failed |
ExchangeFailed | DIDComm packaging/unpackaging failed |
ScopeWidening | Attempted to widen scope during re-delegation |
Dependencies
Section titled “Dependencies”| Crate | Purpose |
|---|---|
baseid-core | Shared types, bilingual errors |
baseid-crypto | Signing, verification (Ed25519, P-256) |
baseid-didcomm | DIDComm v2 message signing for encrypted exchange |