Chained Credential

How multi-hop delegation chains reference their parents, attenuate authority, and verify — one credential per hop

Chained Delegation

Delegation chains support scenarios where authority flows through multiple entities: a Responsible Party delegates to a principal or agent, which re-delegates a narrower slice of that authority onward.

There is no separate "chained credential" type. Every hop in a chain is an ordinary DelegationCredential, and hops reference each other by identifier — a child credential never embeds its parent. A verifier resolves each referenced hop independently and recomputes the whole chain, fail-closed.

Referencing the parent

How a hop points at its parent depends on the schema era (one model, two eras):

  • Legacy VC 1.0 era — the delegation payload carries parentId, the identifier of the parent delegation (SPEC.md §6.1, §6.4)
  • Card era (VC 2.0 + ZCAP-LD) — the capability carries parentCapability, referencing the parent capability's id; the root hop's parentCapability equals its invocationTarget (SPEC.md §6.10)

A card-era hop looks like this:

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://w3id.org/security/zcap/v1",
    "https://kya-os.org/ns/delegation/v1"
  ],
  "type": ["VerifiableCredential", "DelegationCredential"],
  "issuer": "<delegator DID>",
  "validUntil": "2027-01-01T00:00:00Z",
  "credentialSubject": {
    "id": "urn:zcap:del_123",
    "invoker": "<delegate DID>",
    "parentCapability": "<parent capability id | root invocationTarget>",
    "invocationTarget": "<resource DID>",
    "allowedAction": ["payments.transfer"],
    "caveats": [
      { "type": "ValidUntil", "date": "2026-12-01T00:00:00Z" },
      { "type": "MaxAmount", "limit": "500.00", "currency": "USD" }
    ]
  },
  "credentialStatus": {
    "type": "BitstringStatusListEntry",
    "statusPurpose": "revocation",
    "statusListIndex": "42",
    "statusListCredential": "https://example.com/status/delegations"
  },
  "proof": { "type": "DataIntegrityProof", "cryptosuite": "eddsa-jcs-2022" }
}

The chain as a graph

Delegations form a directed acyclic graph, and every chain terminates at a Responsible Party — the root issuer, ultimately accountable for actions taken under any descendant delegation:

     [Root: Responsible Party → Principal]
     [Principal → Agent A]
     ┌──────┴──────┐
     ▼             ▼
[A → B]        [A → C]

Attenuation invariants

A chain is valid only if every hop attenuates its parent. Any broadening hop invalidates the whole chain (SPEC.md §6.10):

  1. Action subset — child allowedAction ⊆ parent's
  2. Monotone caveats — no parent caveat silently dropped; for shared caveat types the child must narrow (MaxAmount ≤ parent, same currency; ValidUntil ≤ parent); unknown caveat types are replicated verbatim
  3. Validity narrowing — child validUntil ≤ parent's
  4. Continuity — the parent's delegate (invoker) must equal the child's issuer: you may only re-delegate what was delegated to your key. In the legacy era this is the rule that a child's issuerDid must equal its parent's subjectDid
  5. Constant targetinvocationTarget is identical along the chain
  6. Bounded depth — at most 10 hops (MAX_DELEGATION_DEPTH)
  7. Root rule — the root parentCapability equals its invocationTarget; when a resource owner is asserted, the root issuer must equal that owner

Verifying a chain

When a verifier receives the leaf credential:

  1. Resolve every hop of the chain by reference, root to leaf
  2. Verify each hop's signature against its issuer's DID document
  3. Check the attenuation invariants at every link
  4. Check revocation status for every hop, on every verification — a revoked ancestor invalidates the entire subtree (cascading revocation, SPEC.md §6.5)
  5. Confirm the leaf delegate is the key presenting the request

In @kya-os/mcp, validateDelegationChain and evaluateDelegationChain (subpath @kya-os/mcp/card) implement this recompute for card-era chains, and the delegation verifier in @kya-os/mcp/delegation covers the legacy era.