Verifiable Credentials (VCs)

TrustWeave expansions in this guide are authored by Geoknoesis LLC. They reflect Geoknoesis’ recommended patterns for W3C Verifiable Credentials on the JVM.

What is a Verifiable Credential?

A Verifiable Credential is a tamper-evident attestation following the W3C VC Data Model. It combines:

1
2
3
dependencies {
    implementation("org.trustweave:common:0.6.0")
}

Result: Grants access to the credential builders and verification helpers referenced throughout this guide.

  1. Metadata – issuer, issuance/expiration dates, schema references.
  2. Credential subject – the claims being asserted (name, degree, license, etc.).
  3. Proof – cryptographic signature binding the issuer to the credential content.

Why VCs matter in TrustWeave

  • They are the unit of trust flowing between issuers and verifiers.
  • Wallets store VCs, anchor clients notarise them, and verification routines replay the proofs.
  • Typed builders and canonicalisation keep the credential lifecycle consistent across DID methods and signature suites.

Credential Lifecycle

A verifiable credential goes through a complete lifecycle from creation to revocation:

flowchart LR
    A[Issuance<br/>Issuer creates VC<br/>Signs with private key] --> B[Storage<br/>Holder stores in wallet<br/>Organizes with metadata]
    B --> C[Presentation<br/>Holder creates VP<br/>Selectively discloses claims]
    C --> D[Verification<br/>Verifier checks proof<br/>Validates issuer & claims]
    D --> E{Status Check}
    E -->|Valid| F[Accept Credential<br/>Use in application]
    E -->|Invalid/Expired| G[Reject Credential<br/>Handle error]
    E -->|Revoked| H[Reject Credential<br/>Credential revoked]
    F --> I[Use in Application]
    A -.->|Optional| J[Revocation<br/>Issuer revokes VC<br/>Adds to status list]
    J --> H
    
    style A fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style B fill:#2196f3,stroke:#1565c0,stroke-width:2px,color:#fff
    style C fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
    style D fill:#9c27b0,stroke:#6a1b9a,stroke-width:2px,color:#fff
    style E fill:#ffc107,stroke:#f57c00,stroke-width:2px,color:#000
    style F fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style G fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style H fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style I fill:#00bcd4,stroke:#00838f,stroke-width:2px,color:#fff
    style J fill:#795548,stroke:#5d4037,stroke-width:2px,color:#fff

Key Stages:

  1. Issuance: Issuer creates and signs the credential with their private key
  2. Storage: Holder stores the credential in their wallet for future use
  3. Presentation: Holder creates a verifiable presentation (optionally with selective disclosure)
  4. Verification: Verifier checks the proof, validates issuer DID, and checks status
  5. Revocation (optional): Issuer can revoke credentials, which are checked during verification

Trust Flow

The credential flow involves multiple actors working together to establish and verify trust:

flowchart LR
    subgraph Issuer["Issuer"]
        I1[Create DID<br/>Generate Keys]
        I2[Issue Credential<br/>Sign with Key]
        I3[Publish DID Document<br/>to Resolver]
    end
    
    subgraph Holder["Holder"]
        H1[Receive Credential<br/>Store in Wallet]
        H2[Create Presentation<br/>Selective Disclosure]
    end
    
    subgraph Verifier["Verifier"]
        V1[Receive Presentation<br/>Request Verification]
        V2[Verify Proof<br/>Check Issuer]
        V3[Check Revocation<br/>Check Expiration]
    end
    
    subgraph Resolver["DID Resolver"]
        R1[Resolve Issuer DID<br/>Fetch DID Document]
        R2[Provide Public Keys<br/>for Verification]
    end
    
    I1 --> I2
    I2 -->|Issue VC| H1
    I1 -->|Publish| I3
    I3 -->|Available| R1
    
    H1 --> H2
    H2 -->|Present VP| V1
    
    V1 --> V2
    V2 -->|Resolve DID| R1
    R1 -->|Return Keys| R2
    R2 -->|Verify Signature| V2
    V2 -->|Check Status| V3
    V3 -->|Valid| V4[Accept Credential]
    V3 -->|Invalid| V5[Reject Credential]
    
    style Issuer fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style Holder fill:#2196f3,stroke:#1565c0,stroke-width:2px,color:#fff
    style Verifier fill:#ff9800,stroke:#e65100,stroke-width:2px,color:#fff
    style Resolver fill:#9c27b0,stroke:#6a1b9a,stroke-width:2px,color:#fff
    style V4 fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style V5 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff

Key Interactions:

  1. Issuer → Resolver: Issuer publishes their DID document (with public keys) to a DID resolver
  2. Issuer → Holder: Issuer creates and signs a credential, delivering it to the holder
  3. Holder → Verifier: Holder creates a presentation and presents it to the verifier
  4. Verifier → Resolver: Verifier resolves the issuer’s DID to get public keys for verification
  5. Verifier → Status Registry (not shown): Verifier checks revocation status if configured

How TrustWeave issues and verifies VCs

Component Purpose
CredentialService / CredentialServices / credentialService(...) Core issuance, verification, and presentation APIs (IssuanceRequest, VerificationOptions).
trustWeave.issue { } High-level DSL performing canonicalisation, signing, and proof attachment.
trustWeave.verify { } Rebuilds canonical form, resolves DIDs, validates proofs, and returns VerificationResult.
IssuanceRequest / ProofOptions Lower-level request objects when calling CredentialService.issue directly.

Detailed API signatures live in the Credential Service API reference.

Example: issuing a credential

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import org.trustweave.trust.TrustWeave
import org.trustweave.did.identifiers.Did
import kotlinx.datetime.Clock

suspend fun issueEmployeeBadge(trustWeave: TrustWeave, issuerDid: Did, issuerKeyId: String) =
    trustWeave.issue {
        credential {
            type("EmploymentCredential")
            issuer(issuerDid)
            subject {
                id("did:key:holder-123")
                "role" to "Site Reliability Engineer"
                "level" to "L5"
            }
            issued(Clock.System.now())
        }
        signedBy(issuerDid = issuerDid, keyId = issuerKeyId)
    }

**Outcome:** Issues a signed credential, returning **`IssuanceResult`** (use `when` or `getOrThrow()` from `org.trustweave.credential.results`).

TrustWeave automatically:

- Canonicalises the JSON payload using JSON Canonicalization Scheme (JCS).
- Signs the digest through the configured `KeyManagementService`.
- Embeds the resulting proof (`Ed25519Signature2020` by default) into the VC.
- Returns a `VerifiableCredential` data class that you can store or present.

### Example: verifying a credential

```kotlin
import org.trustweave.trust.TrustWeave
import org.trustweave.credential.model.vc.VerifiableCredential
import org.trustweave.credential.results.VerificationResult

suspend fun verifyBadge(trustWeave: TrustWeave, credential: VerifiableCredential) {
    val result = trustWeave.verify {
        credential(credential)
    }
    when (result) {
        is VerificationResult.Valid -> println("Credential verified successfully")
        is VerificationResult.Invalid -> println("Verification failed: ${result.allErrors.joinToString()}")
    }
}

**Outcome:** Surfaces verification success or failure via the **`VerificationResult`** hierarchy (`Valid` vs typed `Invalid.*`).

### Verification Pipeline

The verification process involves multiple steps that TrustWeave executes automatically:

```mermaid
flowchart TD
    A[Start Verification<br/>trustWeave.verify { }] --> B[Parse Credential<br/>Validate JSON Structure]
    B --> C{Structure Valid?}
    C -->|No| Z[Invalid: Malformed Structure]
    C -->|Yes| D[Resolve Issuer DID<br/>Fetch DID Document]
    D --> E{DID Resolution<br/>Success?}
    E -->|No| Z1[Invalid: DID Not Found]
    E -->|Yes| F[Extract Public Key<br/>From Verification Method]
    F --> G[Canonicalize Credential<br/>Apply JCS]
    G --> H[Compute Digest<br/>SHA-256 Hash]
    H --> I[Verify Signature<br/>Check Proof]
    I --> J{Signature Valid?}
    J -->|No| Z2[Invalid: Invalid Signature]
    J -->|Yes| K[Check Expiration<br/>Validate issued/expiration Dates]
    K --> L{Not Expired?}
    L -->|No| Z3[Invalid: Credential Expired]
    L -->|Yes| M[Check Revocation<br/>Query Status List]
    M --> N{Not Revoked?}
    N -->|No| Z4[Invalid: Credential Revoked]
    N -->|Yes| O[Validate Schema<br/>If Schema Specified]
    O --> P{Schema Valid?}
    P -->|No| Z5[Invalid: Schema Validation Failed]
    P -->|Yes| Q[Check Trust Policy<br/>If Trust Required]
    Q --> R{Trusted Issuer?}
    R -->|No| Z6[Invalid: Issuer Not Trusted]
    R -->|Yes| S[Valid: Verification Success]
    
    style A fill:#1976d2,stroke:#0d47a1,stroke-width:2px,color:#fff
    style S fill:#4caf50,stroke:#2e7d32,stroke-width:2px,color:#fff
    style Z fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z1 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z2 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z3 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z4 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z5 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff
    style Z6 fill:#f44336,stroke:#c62828,stroke-width:2px,color:#fff

Verification Steps:

  1. Parse & Validate: Ensures credential has correct structure
  2. Resolve Issuer DID: Fetches DID document from resolver
  3. Extract Public Key: Gets verification method from DID document
  4. Canonicalize: Applies JCS to get deterministic JSON
  5. Verify Signature: Checks cryptographic proof
  6. Check Expiration: Validates credential hasn’t expired
  7. Check Revocation: Queries status list if present
  8. Validate Schema: Verifies claims against schema (if specified)
  9. Check Trust: Validates issuer against trust registry (if enabled)

All steps must pass for verification to succeed. Any failure returns detailed error information in the VerificationResult.

Practical usage tips

  • Service-level control – call CredentialService.issue(IssuanceRequest(...)) when you need explicit ProofSuiteId, ProofOptions, schema, or validity windows outside the DSL.
  • Anchoring – store the credential digest with a BlockchainAnchorClient to prove freshness (see Blockchain Anchoring).
  • Revocation – integrate status endpoints by adding credentialStatus claims; custom verification policies can enforce them.
  • Error handling – credential operations throw exceptions (e.g., IllegalStateException) on failure. Use try-catch blocks for error handling. See Error Handling.
  • Input validation – TrustWeave automatically validates credential structure, issuer DID format, and method registration before issuance.

VC Structure

A Verifiable Credential contains:

A Verifiable Credential contains:

  1. Metadata – issuer, issuance/expiration dates, schema references
  2. Credential Subject – the claims being asserted (name, degree, license, etc.)
  3. Proof – cryptographic signature binding the issuer to the credential content
  4. Schema – optional schema for validation
  5. Status – optional revocation status

Example VC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
  "@context": [
    "https://www.w3.org/2018/credentials/v1",
    "https://www.w3.org/2018/credentials/examples/v1"
  ],
  "id": "https://example.com/credentials/3732",
  "type": ["VerifiableCredential", "UniversityDegreeCredential"],
  "issuer": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
  "issuanceDate": "2023-01-01T00:00:00Z",
  "expirationDate": "2028-01-01T00:00:00Z",
  "credentialSubject": {
    "id": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
    "degree": {
      "type": "BachelorDegree",
      "name": "Bachelor of Science in Computer Science",
      "university": "Example University"
    }
  },
  "proof": {
    "type": "Ed25519Signature2020",
    "created": "2023-01-01T00:00:00Z",
    "verificationMethod": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK#keys-1",
    "proofPurpose": "assertionMethod",
    "proofValue": "z5J1pJ2..."
  }
}

VC Lifecycle

1. Issuance

A credential is issued by an issuer to a subject:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import org.trustweave.credential.model.vc.VerifiableCredential
import org.trustweave.credential.model.CredentialType
import org.trustweave.credential.model.ProofType
import org.trustweave.testkit.services.*
import org.trustweave.trust.types.getOrThrowDid
import org.trustweave.trust.types.getOrThrow
import org.trustweave.credential.results.getOrThrow
import org.trustweave.trust.TrustWeave
import org.trustweave.did.resolver.DidResolutionResult
import org.trustweave.did.resolver.errorMessage
import org.trustweave.did.identifiers.Did
import org.trustweave.did.identifiers.extractKeyId
import kotlinx.datetime.Clock

// Issue credential using TrustWeave DSL API

val trustWeave = TrustWeave.build {
    keys { provider(IN_MEMORY); algorithm(ED25519) }
    did { method(KEY) { algorithm(ED25519) } }
    credentials { defaultProofType(ProofType.Ed25519Signature2020) }
}


val issuerDid = trustWeave.createDid {
    method(KEY)
    algorithm(ED25519)
}.getOrThrowDid()

val issuerDoc = when (val res = trustWeave.resolveDid(issuerDid)) {
    is DidResolutionResult.Success -> res.document
    else -> throw IllegalStateException(res.errorMessage ?: "Failed to resolve DID")
}
val issuerKeyId = issuerDoc.verificationMethod.firstOrNull()?.extractKeyId()
    ?: throw IllegalStateException("No verification method found")

val subjectDid = Did("did:key:subject")

val issuedCredential = trustWeave.issue {
    credential {
        type(CredentialType.Person)
        issuer(issuerDid)
        subject {
            id(subjectDid)
            "name" to "Alice"
            "email" to "alice@example.com"
        }
        issued(Clock.System.now())
    }
    signedBy(issuerDid)
}.getOrThrow()

Outcome: Produces a signed credential ready for distribution, anchored to the specific proof type and key you configured.

2. Storage

Store credentials in a wallet:

1
2
3
4
import org.trustweave.testkit.credential.BasicWallet

val wallet = BasicWallet()
val credentialId = wallet.store(issuedCredential)

Outcome: Persists the credential in a wallet so it can be queried, organized, and presented later.

3. Presentation

Create a Verifiable Presentation via the service or TrustWeave DSL (holder proof + challenge/domain live in PresentationRequest.proofOptions):

1
2
3
4
5
6
7
8
9
import org.trustweave.credential.requests.PresentationRequest
import org.trustweave.credential.proof.ProofOptions

val request = PresentationRequest(
    disclosedClaims = setOf("name", "email"),
    proofOptions = ProofOptions(challenge = "verifier-nonce", domain = "verifier.example.com")
)
// val presentation = credentialService.createPresentation(listOf(issuedCredential), request)
// or: trustWeave.presentationResult { ... }

Outcome: Wraps one or more credentials in a holder-signed presentation, enabling selective disclosure downstream.

4. Verification

Verify a credential or presentation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.trustweave.trust.TrustWeave
import org.trustweave.credential.results.VerificationResult
import org.trustweave.testkit.services.*

val trustWeave = TrustWeave.build {
    keys { provider(IN_MEMORY); algorithm(ED25519) }
    did { method(KEY) { algorithm(ED25519) } }
}

val result = trustWeave.verify {
    credential(issuedCredential)
    checkExpiration()
    // checkRevocation() requires status list integration
}

when (result) {
    is VerificationResult.Valid -> println("Credential verified")
    is VerificationResult.Invalid -> println("Verification errors: ${result.allErrors.joinToString()}")
}

Outcome: VerificationResult tells you whether the credential satisfied cryptographic checks, expiration, revocation (when configured), and optional schema/trust rules.

Important: The built-in verifier performs structural checks today (proof fields, expiration, DID resolution). Integrate a dedicated cryptographic proof validator and revocation resolver for production deployments.

5. Revocation

Revoke a credential if needed:

1
2
// Credential status is checked during verification
// Revocation is handled via credentialStatus field

Types of Claims

Identity Claims

Claims about who you are:

  • Name
  • Date of birth
  • Nationality
  • Email address

Achievement Claims

Claims about what you’ve accomplished:

  • Educational degrees
  • Professional certifications
  • Awards
  • Skills

Authorization Claims

Claims about what you’re allowed to do:

  • Access permissions
  • Membership status
  • Role assignments

Proof Types

TrustWeave supports multiple proof types:

  • Ed25519Signature2020: Ed25519 signatures (recommended)
  • JsonWebSignature2020: JWT-based proofs
  • BbsBlsSignature2020: BBS+ signatures for selective disclosure

Schema Validation

Credentials can reference schemas for validation:

1
2
3
4
5
6
7
8
val credential = VerifiableCredential(
    // ...
    credentialSchema = CredentialSchema(
        id = "https://example.com/schemas/person.json",
        type = "JsonSchemaValidator2018",
        schemaFormat = SchemaFormat.JSON_SCHEMA
    )
)

Privacy Features

Selective Disclosure

Reveal only specific fields from a credential:

1
2
3
4
5
6
// Prefer TrustWeave / wallet APIs that accept PresentationRequest + disclosedClaims.
// Illustrative:
// wallet.present(
//     credentialIds = listOf(credentialId),
//     request = PresentationRequest(disclosedClaims = setOf("name", "email"), proofOptions = ProofOptions(...))
// )

Zero-Knowledge Proofs

Some proof types (like BBS+) support zero-knowledge proofs, allowing you to prove claims without revealing the actual values.

Common Use Cases

  • Education: Diplomas, certificates, transcripts
  • Employment: Work history, skills, references
  • Healthcare: Medical records, prescriptions, test results
  • Identity: Government IDs, passports, driver’s licenses
  • Membership: Club memberships, subscriptions, loyalty programs

Best Practices

  1. Always verify credentials before trusting them
  2. Check expiration dates to ensure credentials are still valid
  3. Verify revocation status to ensure credentials haven’t been revoked
  4. Use selective disclosure to minimize data exposure
  5. Store credentials securely in a wallet

See also

  • Credential Service API](../api-reference/credential-service-api.md) for parameter details and SPI guidance
  • Quick Start – Step 4 & 5](../getting-started/quick-start.md#step-4-issue-a-credential-and-store-it) for a runnable walkthrough
  • Wallets](wallets.md) for storage and presentation
  • Architecture Overview](../introduction/architecture-overview.md) for the credential flow diagram

Next Steps

Ready to use credentials?

  • Issue Credentials](../how-to/issue-credentials.md) - Step-by-step guide
  • Verify Credentials](../how-to/verify-credentials.md) - Step-by-step guide
  • Quick Start – Step 4 & 5](../getting-started/quick-start.md#step-4-issue-a-credential-and-store-it) - Create your first credential

Want to learn more?

  • Wallets](wallets.md) - Managing credentials
  • Wallet API Tutorial](../tutorials/wallet-api-tutorial.md) - Hands-on wallet examples
  • Credential Service API Reference](../api-reference/credential-service-api.md) - Complete API documentation
  • Core API Reference](../api-reference/core-api.md) - TrustWeave API

This site uses Just the Docs, a documentation theme for Jekyll.