kavachOS

Verifiable credentials

Issue and verify W3C Verifiable Credentials for agent identity, permissions, and delegation.

Why verifiable credentials matter for agents

When agent A calls agent B, how does B know what A is allowed to do? Today, most systems answer this with a network call back to a central auth server. That works inside one organization, but falls apart when agents cross trust boundaries.

W3C Verifiable Credentials solve this. A credential is a signed JSON document that says "agent X has permissions Y, issued by authority Z." The agent carries the credential and presents it directly. The verifier checks the cryptographic signature and reads the permissions. No network call needed.

This matters for three reasons:

  1. Offline verification -- an agent can prove its identity and permissions without the issuing server being reachable
  2. Cross-organization trust -- if you trust the issuer's DID, you trust the credential, regardless of where the agent lives
  3. Delegation chains -- a credential can encode a chain of delegations, so a sub-agent can prove it was authorized by its parent

VCs build on top of KavachOS DID support. If you have not set up DIDs yet, see the DID identity page first.

Issuing credentials

Create an issuer bound to a DID keypair. The issuer can produce credentials in JWT or JSON-LD format.

import { generateDidKey } from 'kavachos/did';
import { createVCIssuer } from 'kavachos/vc';

const keyPair = await generateDidKey();

const issuer = createVCIssuer({
  issuerDid: keyPair.did,
  privateKeyJwk: keyPair.privateKeyJwk,
  publicKeyJwk: keyPair.publicKeyJwk,
  defaultTtl: 86400, // 24 hours
});

Agent identity credential

Encodes who the agent is, what it can do, and how much you trust it.

const result = await issuer.issueAgentCredential({
  agentId: 'agent-data-processor',
  name: 'Data Processor',
  agentType: 'autonomous',
  permissions: ['read:datasets', 'write:reports'],
  trustLevel: 0.85,
  format: 'jwt',
});

if (result.success) {
  // Send the JWT to the agent -- it carries this as a bearer credential
  const jwt = result.data.jwt;
}
const result = await issuer.issueAgentCredential({
  agentId: 'agent-data-processor',
  name: 'Data Processor',
  agentType: 'autonomous',
  permissions: ['read:datasets', 'write:reports'],
  trustLevel: 0.85,
  format: 'json-ld',
});

if (result.success) {
  // The credential includes an embedded proof
  const vc = result.data.credential;
  console.log(vc.proof.type); // "JsonWebSignature2020"
}

Permission credential

Grants specific permissions to an agent without encoding the full identity.

const result = await issuer.issuePermissionCredential({
  agentId: 'agent-helper',
  permissions: ['read:files', 'execute:tools'],
  ttl: 3600, // 1 hour
});

Delegation credential

Encodes a chain of delegations so a sub-agent can prove it was authorized by its parent, which was authorized by its parent, and so on.

const result = await issuer.issueDelegationCredential({
  agentId: 'sub-agent',
  chain: [
    {
      delegator: 'did:key:z6MkRoot...',
      delegatee: 'did:key:z6MkMiddle...',
      permissions: ['read:data', 'write:data'],
      createdAt: new Date().toISOString(),
    },
    {
      delegator: 'did:key:z6MkMiddle...',
      delegatee: 'sub-agent',
      permissions: ['read:data'],
      createdAt: new Date().toISOString(),
    },
  ],
  delegationScope: ['read:data'],
});

Verifying credentials

Create a verifier to check credentials from any source. The verifier validates the signature, checks expiry, and optionally checks revocation status.

import { createVCVerifier } from 'kavachos/vc';

const verifier = createVCVerifier({
  // Optional: resolve DIDs to public keys automatically
  resolveDidKey: async (did) => {
    // Look up the DID document and return the public key
    const doc = await myDidResolver.resolve(did);
    return doc?.verificationMethod[0]?.publicKeyJwk ?? null;
  },
  // Optional: check revocation
  checkRevocationStatus: async (status) => {
    const list = await fetch(status.statusListCredential);
    // Check if the credential at statusListIndex is revoked
    return isRevoked(await list.json(), status.statusListIndex);
  },
});

Verify a JWT credential

const result = await verifier.verifyCredential(jwtString);

if (result.success) {
  console.log(result.data.issuer);    // "did:key:z6Mk..."
  console.log(result.data.format);    // "jwt"
  console.log(result.data.expiresAt); // Date or null
}

Verify a JSON-LD credential

const result = await verifier.verifyCredential(credentialObject);

if (result.success) {
  console.log(result.data.format); // "json-ld"
}

Verify a presentation (multiple credentials)

An agent might present several credentials at once to prove both identity and permissions.

const presentation = {
  '@context': ['https://www.w3.org/ns/credentials/v2'],
  type: ['VerifiablePresentation'],
  holder: 'did:key:z6MkAgent...',
  verifiableCredential: [agentCredential, permissionCredential],
};

const result = await verifier.verifyPresentation(presentation);

if (result.success) {
  for (const verified of result.data.credentials) {
    console.log(verified.issuer, verified.credential.type);
  }
}

Extract permissions

After verification, pull out the KavachOS-specific permissions.

const extracted = verifier.extractPermissions(verifiedCredential);

console.log(extracted.agentId);        // "agent-data-processor"
console.log(extracted.permissions);    // ["read:datasets", "write:reports"]
console.log(extracted.trustLevel);     // 0.85
console.log(extracted.delegationScope); // []

Portable identity

The key advantage of VCs is portability. An agent issued a credential by KavachOS instance A can present it to service B without B ever talking to A.

Agent                     Service B
  |                          |
  |-- present VC ----------->|
  |                          |-- check signature (local)
  |                          |-- check expiry (local)
  |                          |-- extract permissions
  |                          |
  |<-- authorized -----------|

The only requirement is that service B trusts the issuer's DID. This can be configured as a simple allowlist of trusted DIDs, or through a more formal trust framework.

Integration with DID support

VCs work with both did:key and did:web identifiers. The issuer DID is embedded in the credential's issuer field. When verifying, the verifier resolves the DID to get the public key.

For did:key, resolution is local (the key is embedded in the identifier). For did:web, the verifier fetches the DID document from the well-known URL.

import { generateDidKey } from 'kavachos';
import { createVCIssuer, createVCVerifier } from 'kavachos/vc';

// Issuer creates credentials with their did:key
const issuerKeys = await generateDidKey();
const issuer = createVCIssuer({
  issuerDid: issuerKeys.did,
  privateKeyJwk: issuerKeys.privateKeyJwk,
  publicKeyJwk: issuerKeys.publicKeyJwk,
});

// Verifier trusts this issuer's DID
const trustedKeys = new Map([[issuerKeys.did, issuerKeys.publicKeyJwk]]);

const verifier = createVCVerifier({
  resolveDidKey: async (did) => trustedKeys.get(did) ?? null,
});

On this page