Skip to content

ADR-0030: Verifiable Credentials v2 — Trust-Anchored Knowledge

  • Status: Accepted (2026-04-19)
  • Date: 2026-04-17

Context

Trails already has the foundational plumbing for verifiable trust:

  • DIDs (ADR-0011): every principal is a did:key or did:web, resolved via pluggable IdentityResolver. DID documents carry verification keys.
  • PROV-O (ADR-0009): every capability invocation produces a provenance trace — who did what, when, derived from what.
  • Cedar policy (ADR-0006): authorization decisions gate capability invocations with explainable, auditable verdicts.

What is missing is the ability to make verifiable, portable claims about principals, data, and operations that can be validated outside the originating Trails instance. Concrete gaps:

  1. An agent proves it ran a compliance check, but the evidence is a PROV-O trace locked inside one Oxigraph store. A regulator cannot verify it without direct store access.
  2. A federated peer (ADR-0023) wants to trust incoming data, but has no cryptographic proof that the source instance validated its schema.
  3. An AI model card must be machine-verifiable, not just a markdown file. No KG framework ships this today.
  4. GDPR data-provenance obligations require attestations that travel with the data, not just internal logs.

W3C Verifiable Credentials Data Model v2.0 became a Recommendation in May 2025. It standardizes JSON-LD credentials with cryptographic proofs, selective disclosure, and a holder-controlled presentation model. The combination of DID + PROV-O + VC in a single framework is genuinely unique — no other KG framework ships VC issuance and verification as a core primitive.

Use cases:

  • GDPR data provenance: attest that data was ingested from a verified, consented source; attestation travels with the data.
  • Supply chain attestation: each transformation step issues a VC; downstream consumers verify the chain.
  • AI model cards: machine-verifiable credentials for model lineage, training data provenance, evaluation results.
  • Regulated industries: audit-grade proof that a KG conforms to a declared schema or policy baseline.
  • Agent trust credentials: an agent carries VCs proving its capabilities, authorization scope, and issuer identity.

Decision

1. trails.credentials module

A new top-level module providing four core operations. All operations are pure cryptography + JSON-LD — no AI/LLM dependency.

from trails.credentials import (
    issue_credential,
    verify_credential,
    present_credentials,
    verify_presentation,
)

# Issue
vc = issue_credential(
    issuer_did="did:key:z6MkExample...",
    subject_did="did:key:z6MkSubject...",
    claims={"type": "DataProvenanceCredential", "source": "https://example.com/dataset"},
    proof_type="Ed25519Signature2020",
)

# Verify
result = verify_credential(vc)
assert result.valid
assert result.issuer == "did:key:z6MkExample..."

# Present (with selective disclosure)
vp = present_credentials(
    holder_did="did:key:z6MkSubject...",
    credentials=[vc],
    selective_disclosure=True,
)

# Verify presentation
vp_result = verify_presentation(vp)

Function signatures:

  • issue_credential(issuer_did, subject_did, claims, *, proof_type="Ed25519Signature2020") -> VerifiableCredential
  • verify_credential(vc) -> VerificationResult(valid, issuer, checks)
  • present_credentials(holder_did, credentials, *, selective_disclosure=True) -> VerifiablePresentation
  • verify_presentation(vp) -> VerificationResult

Supported proof types (v1 allowlist): Ed25519Signature2020, JsonWebSignature2020 (ES256, ES384). Algorithm allowlist mirrors ADR-0011's VC verification constraints. alg=none and symmetric algorithms are rejected.

2. Credential types for KG operations

Five built-in credential types, plus a decorator for custom types:

Type Attests
DataProvenanceCredential Data was ingested from a verified source with stated provenance
SchemaComplianceCredential A KG (or subset) conforms to a declared SHACL shape or baseline
AgentCapabilityCredential An agent is authorized to invoke specific capabilities
AuditTrailCredential Packages a PROV-O activity trace as a verifiable attestation
ReviewCredential A human or automated reviewer approved a dataset, model, or output

Custom credential types:

from trails.credentials import credential_type

@credential_type("https://trails.dev/credentials/ModelCard")
class ModelCardCredential:
    model_name: str
    training_data_hash: str
    evaluation_score: float
    evaluator_did: str

3. Integration with existing Trails primitives

PROV-O (ADR-0009): - prov:wasAttributedTo links to the issuer DID on every credential issuance activity. - The credential IRI is stored as a prov:Entity in the provenance graph, linked via prov:wasGeneratedBy to the issuance activity. - AuditTrailCredential wraps an existing prov:Activity chain as a portable, verifiable attestation.

Cedar policy (ADR-0006): - principal.credentials attribute in Cedar context exposes the set of VCs presented by the requesting principal. - Policies can require specific credential types:

permit(
  principal,
  action == Action::"invoke",
  resource == Capability::"ingest_sensitive_data"
) when {
  principal.credentials.has(CredentialType::"DataProvenanceCredential")
};
- Credential-gated policies are evaluated before handler execution, same as all Cedar decisions.

Federation (ADR-0023): - Federated peers can require VCs for cross-instance queries. Trust levels map to credential requirements: - Level 0 (open): no credentials required. - Level 1 (authenticated): valid DID + any VC from a trusted issuer. - Level 2 (attested): specific credential types required (e.g., SchemaComplianceCredential for schema-sensitive queries). - Baseline negotiation (ADR-0027) can declare [baseline.policy] require_credentials = true.

Baselines (ADR-0027): - [credentials] section in baseline TOML declares required credential types for the instance, default proof types, and trusted issuer DIDs.

4. Storage

Credentials stored as JSON-LD in a dedicated named graph (trails:credentials), queryable via SPARQL:

SELECT ?vc ?type ?issuer ?subject ?issuanceDate
WHERE {
  GRAPH <trails:credentials> {
    ?vc a ?type ;
        cred:issuer ?issuer ;
        cred:credentialSubject ?subject ;
        cred:issuanceDate ?issuanceDate .
  }
}

The trails:credentials graph follows the same kernel-write-exclusive pattern as the prov: graph (ADR-0009 §c): only the trails-cred writer may insert into this graph. Principal-initiated writes are denied with CredentialWriteDenied.

Credential documents are stored in their full JSON-LD expanded form. Compact form is used for API responses and CLI output.

5. CLI

trails cred issue   --type DataProvenance --subject did:key:z6Mk... --claims '{...}'
trails cred verify  <vc.json>
trails cred list    [--type TYPE] [--issuer DID] [--subject DID] [--expired]
trails cred present --credentials vc1.json,vc2.json [--selective-disclosure]
trails cred revoke  <credential-id>        # v2 only, see non-goals
trails cred inspect <vc.json>              # pretty-print + validation summary

trails cred issue resolves the issuer DID, loads the signing key from the local keystore (~/.trails/keys/ or TRAILS_KEYSTORE), signs the credential, stores it in trails:credentials, and emits the JSON-LD to stdout.

trails cred verify performs: (a) JSON-LD structure validation, (b) proof verification against the issuer's DID document, © expiry check, (d) revocation check (v2). Returns exit code 0 on success, 1 on failure, with structured JSON diagnostics on stderr.

6. Status dashboard

The trails-admin UI (ADR-0019, M10) gains a Credentials panel:

  • Issued credentials: list with type, subject, issuance date, expiry, verification status.
  • Received credentials: credentials presented by remote peers or agents, with verification result.
  • Verification log: timestamped log of all verification attempts (success/failure/reason).
  • Credential statistics: counts by type, issuer, expiry distribution, verification failure rate.

Non-goals

  • Not a full identity provider. Trails issues and verifies VCs; it does not manage user accounts, passwords, or OIDC flows.
  • No DIDComm messaging. Agent-to-agent secure messaging is a separate concern (future ADR).
  • No blockchain anchoring. If an app needs blockchain-anchored proofs, it can use an external anchoring service and reference the anchor in the credential. The framework does not embed a blockchain client.
  • No credential revocation registry in v1. Revocation requires a distributed status mechanism (StatusList2021 or similar). Planned for v2 of this feature. V1 credentials have an expirationDate but no real-time revocation.
  • No wallet management UI. Credential storage is KG-native (named graph); end-user wallet UX is out of scope.

Dependencies

ADR Relationship
ADR-0011 (DIDs) Issuer/subject/holder identity; DID resolution for proof verification
ADR-0009 (PROV-O) Provenance integration; AuditTrailCredential wraps PROV-O traces
ADR-0006 (Cedar) Credential-gated policies; principal.credentials in Cedar context
ADR-0023 (Federation) Trust levels; credential requirements for cross-instance queries
ADR-0027 (Baselines) Credential requirements declared in baseline config

Consequences

Positive

  • Unique positioning. No other KG framework ships VC issuance/verification as a core primitive. The DID + PROV-O + VC combination is a genuine differentiator.
  • Regulatory readiness. GDPR data provenance, EU AI Act model cards, and supply chain attestations become first-class features rather than bolt-on integrations.
  • Federation trust. Cross-instance queries gain cryptographic trust anchors beyond TLS. Peers can require verifiable attestations, not just authentication.
  • Agent trust. Agents carry machine-verifiable credentials proving their capabilities and authorization — critical for multi-agent systems where trust is earned, not assumed.
  • Zero LLM dependency. The entire credential stack is pure cryptography + JSON-LD. No cost, no latency, no model risk.
  • Standards-aligned. VC 2.0 is a W3C Recommendation; the framework avoids inventing a proprietary trust format.

Negative

  • Complexity budget. VCs add a new concept that developers must understand. Mitigated by: sensible defaults (credentials optional unless policy requires them), CLI tooling for issuance/verification, admin UI for visibility.
  • Key management burden. Issuing VCs requires signing keys. Mitigated by: did:key works with any Ed25519 keypair (same keys used for biscuit tokens per ADR-0010); trails new can scaffold a keystore.
  • JSON-LD processing overhead. VC 2.0 is JSON-LD native; expansion and compaction have non-trivial cost. Mitigated by: caching context documents; using pyld with a preloaded context cache; benchmarking against NFR-Perf1 thresholds.
  • Selective disclosure complexity. SD-JWT or BBS+ for selective disclosure is cryptographically complex. Resolved: Phase 4 ships Ed25519SelectiveDisclosure2020 — salted SHA-256 field hashes with an optional Ed25519 signature over the full hash set. No BBS+ dependency; full ecosystem maturity not required. Verifiers check disclosed fields against their hashes; redacted fields are provably included without being revealed.

Revisit conditions

  • If VC 2.0 adoption stalls and a competing format (e.g., SD-JWT without VC wrapper) gains dominant traction, consider supporting both or switching.
  • If a future W3C VC standard mandates BBS+ or SD-JWT, add it as an additional proof type alongside Ed25519SelectiveDisclosure2020; no breaking change required since proof types are negotiated per-credential.
  • If the did:keydid:jwk migration in the DID ecosystem completes, update default proof types accordingly.
  • If revocation needs arise before v2, fast-track StatusList2021 integration.