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
delegationpayload carriesparentId, 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'sid; the root hop'sparentCapabilityequals itsinvocationTarget(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):
- Action subset — child
allowedAction ⊆parent's - 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 - Validity narrowing — child
validUntil ≤parent's - Continuity — the parent's delegate (
invoker) must equal the child'sissuer: you may only re-delegate what was delegated to your key. In the legacy era this is the rule that a child'sissuerDidmust equal its parent'ssubjectDid - Constant target —
invocationTargetis identical along the chain - Bounded depth — at most 10 hops (
MAX_DELEGATION_DEPTH) - Root rule — the root
parentCapabilityequals itsinvocationTarget; when a resource owner is asserted, the rootissuermust equal that owner
Verifying a chain
When a verifier receives the leaf credential:
- Resolve every hop of the chain by reference, root to leaf
- Verify each hop's signature against its issuer's DID document
- Check the attenuation invariants at every link
- Check revocation status for every hop, on every verification — a revoked ancestor invalidates the entire subtree (cascading revocation, SPEC.md §6.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.
Reference, never embed
A hop references its parent by identifier; it never carries a copy of the parent credential. Embedding would let an issuer present a doctored parent — the verifier must resolve and check each hop for itself. All chain checks are fail-closed: an unresolvable hop, a broadening hop, or a revoked hop invalidates the chain.