Skip to content

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.

  • 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 Managerdelegate(), present_as_delegate(), verify_delegated() high-level API
  • Encrypted Exchange — DIDComm-signed credential packaging for intermediary-blind relay
use baseid_delegation::*;
use baseid_crypto::KeyPair;
use baseid_core::types::KeyType;
// Generate a signing key
let key = KeyPair::generate(KeyType::Ed25519)?;
// Define what the delegate can do
let 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 token
let token = delegate(
"cred-123", // credential reference
"did:key:alice", // delegator
"did:key:bob", // delegate
scope,
0, // depth
&key, // signer
)?;
// Build a chain and present
let chain = DelegationChain::new(token);
let presentation = present_as_delegate(
b"credential-jwt-data",
chain,
vec!["given_name".to_string()],
)?;
// Verify the delegated presentation
let result = verify_delegated(&presentation)?;
assert!(result.valid);
assert!(result.chain_valid);
assert!(result.scope_compliant);

A DelegationScope defines the boundaries of what a delegate may do:

FieldDescription
allowed_claimsWhich claims the delegate may present (empty = all)
max_depthMaximum re-delegation depth (0 = cannot re-delegate)
valid_untilExpiration timestamp
valid_fromOptional start time
purposeHuman-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 access
assert!(scope.is_claim_allowed("employer"));
assert!(!scope.is_claim_allowed("salary")); // not in scope
assert!(!scope.is_expired());

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());

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 signature
assert!(token.verify_signature(&signing_key.public));
// Check properties
assert!(!token.is_expired());
assert!(!token.can_redelegate()); // max_depth=0

Tokens are fully serializable via serde (JSON round-trip supported).

A DelegationChain tracks multi-hop delegation from original holder to current delegate:

// Alice delegates to Bob
let 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

The manager module provides three high-level functions:

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 depth

Share credentials through intermediaries without revealing content, using DIDComm v2 signed messages.

use baseid_delegation::{encrypt_for_did, decrypt_credential};
// Sender packages credential
let encrypted = encrypt_for_did(
b"credential-jwt-here",
"did:key:recipient",
&sender_key, // &dyn Signer
"did:key:sender",
"did:key:sender#key-1",
)?;
// Recipient decrypts
let decrypted = decrypt_credential(&encrypted, &sender_key.public)?;
assert_eq!(decrypted, b"credential-jwt-here");

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 credential
assert_eq!(package.delegation_token.delegate, "did:key:bob");
// Recipient decrypts
let data = decrypt_credential(&package.envelope, &sender_key.public)?;

The BaseID server exposes three delegation endpoints:

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.

Verify a delegation chain.

{
"chain": {
"tokens": [{ "...token..." }]
}
}

Returns chain validity, delegator, delegate, depth, and link count.

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.

Credential delegation is designed for scenarios where an AI agent acts on behalf of a human:

  1. User creates delegation — scoped to specific claims, time-limited, purpose-tagged
  2. Agent receives token — cannot exceed delegated scope
  3. Agent presents credentials — verifier sees the full delegation chain
  4. 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

All errors implement BilingualError (EN/FR):

ErrorDescription
ScopeViolationDisclosed claim not in delegation scope
ChainBrokenChain continuity violated (delegate mismatch, depth gap)
ExpiredDelegation token has expired
DepthExceededRe-delegation depth exceeds maximum
InvalidTokenToken creation or validation failed
ExchangeFailedDIDComm packaging/unpackaging failed
ScopeWideningAttempted to widen scope during re-delegation
CratePurpose
baseid-coreShared types, bilingual errors
baseid-cryptoSigning, verification (Ed25519, P-256)
baseid-didcommDIDComm v2 message signing for encrypted exchange