Protocol Implementation Guide

Complete guide for implementing credential exchange protocols in TrustWeave.

Overview

This guide covers how to implement new credential exchange protocols using the protocol abstraction layer. All protocols implement the CredentialExchangeProtocol interface, allowing them to be used interchangeably.

Architecture

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
┌─────────────────────────────────────────────┐
│  ExchangeProtocolRegistry                   │
│  (Manages all protocol implementations)     │
└─────────────────────────────────────────────┘
                     │
         ┌───────────┼───────────┐
         │           │           │
         ▼           ▼           ▼
┌─────────────┐ ┌──────────┐ ┌──────────┐
│  DIDComm    │ │ OIDC4VCI  │ │  CHAPI   │
│  Protocol   │ │ Protocol  │ │ Protocol │
└─────────────┘ └──────────┘ └──────────┘
                     │
         ┌───────────┴───────────┐
         │                       │
         ▼                       ▼
┌──────────────────┐  ┌──────────────────┐
│  ExchangeService  │  │  CredentialService│
│  (Unified API)    │  │  (Issuance/Verify)│
└──────────────────┘  └──────────────────┘

Implemented Protocols

1. DIDComm V2

Status: ✅ Fully Implemented

  • Protocol Name: "didcomm"
  • Library: Custom implementation with didcomm-java integration
  • Supported Operations: All (offer, request, issue, proof request, proof presentation)
  • Documentation: credentials/plugins/didcomm/README.md

2. OIDC4VCI

Status: ✅ Implemented (Basic)

  • Protocol Name: "oidc4vci"
  • Library: walt.id waltid-openid4vc (optional)
  • Supported Operations: Issuance only (offer, request, issue)
  • Documentation: credentials/plugins/oidc4vci/README.md

3. CHAPI

Status: ✅ Implemented (Basic)

  • Protocol Name: "chapi"
  • Library: Custom implementation (browser API wrapper)
  • Supported Operations: Offer, issue, proof request, proof presentation
  • Documentation: credentials/plugins/chapi/README.md

Implementing a New Protocol

Step 1: Create Plugin Structure

1
2
3
4
5
6
7
8
9
10
11
credentials/plugins/your-protocol/
├── build.gradle.kts
├── README.md
└── src/main/kotlin/org.trustweave/credential/yourprotocol/
    ├── YourProtocolService.kt
    ├── models/
    │   └── YourProtocolModels.kt
    └── exchange/
        ├── YourProtocolExchangeProtocol.kt
        └── spi/
            └── YourProtocolExchangeProtocolProvider.kt

Step 2: Implement CredentialExchangeProtocol

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
package org.trustweave.credential.yourprotocol.exchange

import org.trustweave.credential.exchange.*

class YourProtocolExchangeProtocol(
    private val service: YourProtocolService
) : CredentialExchangeProtocol {

    override val protocolName = "yourprotocol"

    override val supportedOperations = setOf(
        ExchangeOperation.OFFER_CREDENTIAL,
        ExchangeOperation.REQUEST_CREDENTIAL,
        ExchangeOperation.ISSUE_CREDENTIAL
    )

    override suspend fun offerCredential(
        request: CredentialOfferRequest
    ): CredentialOfferResponse {
        val offer = service.createOffer(
            issuerDid = request.issuerDid,
            holderDid = request.holderDid,
            preview = request.credentialPreview
        )

        return CredentialOfferResponse(
            offerId = offer.id,
            offerData = offer,
            protocolName = protocolName
        )
    }

    // Implement other operations...
}

Step 3: Create Service Implementation

1
2
3
4
5
6
7
8
9
10
11
package org.trustweave.credential.yourprotocol

class YourProtocolService {
    suspend fun createOffer(
        issuerDid: String,
        holderDid: String,
        preview: CredentialPreview
    ): YourProtocolOffer {
        // Protocol-specific implementation
    }
}

Step 4: Create SPI Provider

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.trustweave.credential.yourprotocol.exchange.spi

import org.trustweave.credential.exchange.CredentialExchangeProtocol
import org.trustweave.credential.exchange.spi.CredentialExchangeProtocolProvider

class YourProtocolExchangeProtocolProvider : CredentialExchangeProtocolProvider {
    override val name = "yourprotocol"
    override val supportedProtocols = listOf("yourprotocol")

    override fun create(
        protocolName: String,
        options: Map<String, Any?>
    ): CredentialExchangeProtocol? {
        if (protocolName != "yourprotocol") return null

        val service = YourProtocolService(
            // Initialize from options
        )

        return YourProtocolExchangeProtocol(service)
    }
}

Step 5: Register SPI Provider

Create src/main/resources/META-INF/services/org.trustweave.credential.exchange.spi.CredentialExchangeProtocolProvider:

1
org.trustweave.credential.yourprotocol.exchange.spi.YourProtocolExchangeProtocolProvider

Step 6: Add to Build File

1
2
3
4
5
6
7
8
9
dependencies {
    implementation(project(":credentials:credential-core"))
    implementation(project(":did:did-core"))
    implementation(project(":common"))

    // Protocol-specific dependencies
    implementation(libs.kotlinx.serialization.json)
    implementation(libs.kotlinx.coroutines.core)
}

Protocol-Specific Options

Each protocol may require specific options in the options map:

DIDComm

1
2
3
4
5
6
7
8
import org.trustweave.credential.exchange.options.ExchangeOptions

val options = ExchangeOptions.builder()
    .addMetadata("fromKeyId", "did:key:issuer#key-1")
    .addMetadata("toKeyId", "did:key:holder#key-1")
    .addMetadata("encrypt", true)
    .threadId("thread-id")
    .build()

OIDC4VCI

1
2
3
4
5
6
7
8
9
import org.trustweave.credential.exchange.options.ExchangeOptions
import kotlinx.serialization.json.JsonPrimitive

val options = ExchangeOptions.builder()
    .addMetadata("credentialIssuer", "https://issuer.example.com")
    .addMetadata("credentialTypes", JsonPrimitive("VerifiableCredential,PersonCredential"))
    .addMetadata("grants", JsonPrimitive("authorization_code"))
    .addMetadata("redirectUri", "https://holder.example.com/callback")
    .build()

CHAPI

1
2
3
4
5
import org.trustweave.credential.exchange.options.ExchangeOptions

val options = ExchangeOptions.Empty
// CHAPI typically doesn't require additional options
// Messages are generated for browser use

Testing

Unit Tests

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Test
fun `test offer credential`() = runTest {
    val service = YourProtocolService()
    val protocol = YourProtocolExchangeProtocol(service)

    val offer = protocol.offerCredential(
        CredentialOfferRequest(
            issuerDid = "did:key:issuer",
            holderDid = "did:key:holder",
            credentialPreview = CredentialPreview(
                attributes = listOf(CredentialAttribute("name", "Alice"))
            )
        )
    )

    assertEquals("yourprotocol", offer.protocolName)
    assertNotNull(offer.offerId)
}

Integration Tests

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
import org.trustweave.credential.exchange.*
import org.trustweave.credential.exchange.registry.ExchangeProtocolRegistries
import org.trustweave.credential.exchange.request.ExchangeRequest
import org.trustweave.credential.exchange.result.ExchangeResult
import org.trustweave.credential.identifiers.*

@Test
fun `test complete exchange flow`() = runTest {
    val registry = ExchangeProtocolRegistries.default()
    registry.register(YourProtocolExchangeProtocol(service))
    
    val exchangeService = ExchangeServices.createExchangeService(
        protocolRegistry = registry,
        credentialService = credentialService,
        didResolver = didResolver
    )

    // Test full flow
    val offerResult = exchangeService.offer(
        ExchangeRequest.Offer(
            protocolName = "yourprotocol".requireExchangeProtocolName(),
            issuerDid = issuerDid,
            holderDid = holderDid,
            credentialPreview = preview,
            options = ExchangeOptions.builder().build()
        )
    )
    val offer = when (offerResult) {
        is ExchangeResult.Success -> offerResult.value
        else -> throw IllegalStateException("Offer failed: $offerResult")
    }
    
    val requestResult = exchangeService.request(
        ExchangeRequest.Request(
            protocolName = "yourprotocol".requireExchangeProtocolName(),
            holderDid = holderDid,
            issuerDid = issuerDid,
            offerId = offer.offerId,
            options = ExchangeOptions.builder().build()
        )
    )
    val request = when (requestResult) {
        is ExchangeResult.Success -> requestResult.value
        else -> throw IllegalStateException("Request failed: $requestResult")
    }
    
    val issueResult = exchangeService.issue(
        ExchangeRequest.Issue(
            protocolName = "yourprotocol".requireExchangeProtocolName(),
            issuerDid = issuerDid,
            holderDid = holderDid,
            credential = credential,
            requestId = request.requestId,
            options = ExchangeOptions.builder().build()
        )
    )
    val issue = when (issueResult) {
        is ExchangeResult.Success -> issueResult.value
        else -> throw IllegalStateException("Issue failed: $issueResult")
    }

    assertNotNull(issue.credential)
}

Best Practices

  1. Error Handling: Always validate inputs and provide clear error messages
  2. Type Safety: Use strongly-typed models for protocol-specific data
  3. Documentation: Document protocol-specific options and limitations
  4. Testing: Write comprehensive tests for all operations
  5. SPI Registration: Always register SPI providers for auto-discovery
  6. Protocol Metadata: Implement supportedOperations correctly

Common Patterns

Converting Common Models to Protocol-Specific

1
2
3
4
5
6
7
8
9
10
// Convert common preview to protocol-specific
val protocolPreview = ProtocolPreview(
    attributes = request.credentialPreview.attributes.map { attr ->
        ProtocolAttribute(
            name = attr.name,
            value = attr.value,
            mimeType = attr.mimeType
        )
    }
)

Handling Optional Operations

1
2
3
4
5
6
7
8
9
10
override suspend fun requestProof(
    request: ProofRequestRequest
): ProofRequestResponse {
    if (!supportedOperations.contains(ExchangeOperation.REQUEST_PROOF)) {
        throw UnsupportedOperationException(
            "Protocol '$protocolName' does not support proof requests"
        )
    }
    // Implementation...
}

Protocol-Specific Data in Responses

1
2
3
4
5
return CredentialOfferResponse(
    offerId = offer.id,
    offerData = offer, // Protocol-specific format
    protocolName = protocolName
)

Future Protocols

Potential protocols to implement:

  • OIDC4VP: OpenID Connect for Verifiable Presentations
  • SIOPv2: Self-Issued OpenID Provider v2
  • WACI: Wallet and Credential Interactions
  • ISO/IEC 18013-5: Mobile Driving License (mDL)
  • ISO/IEC 23220: Verifiable Credentials standard

References


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