Reusable Components Across Protocols

Overview

Many components implemented for DIDComm are reusable across other protocols (OIDC4VCI, CHAPI, etc.). This document outlines what’s reusable and how to use it.

✅ Fully Reusable Components

1. Encryption & Key Management

Location: credentials/credential-core/src/main/kotlin/com/trustweave/credential/

KeyEncryption (crypto/secret/encryption/KeyEncryption.kt)

  • Reusable: ✅ Yes - Generic AES-256-GCM encryption
  • Usage: Any protocol that needs to encrypt keys locally
  • Example:
    1
    2
    
    val keyEncryption = KeyEncryption(masterKey)
    val encrypted = keyEncryption.encrypt(keyData)
    

LocalKeyStore (crypto/secret/LocalKeyStore.kt)

  • Reusable: ✅ Yes - Generic key storage interface
  • Usage: OIDC4VCI, CHAPI, or any protocol using keys
  • Example:
    1
    2
    
    val keyStore: LocalKeyStore = EncryptedFileLocalKeyStore(...)
    keyStore.store("key-id", secret)
    

MessageEncryption (storage/encryption/MessageEncryption.kt)

  • Reusable: ✅ Yes - Encrypts any data at rest
  • Usage: OIDC4VCI offers, CHAPI messages, any protocol data
  • Example:
    1
    2
    
    val encryption = AesMessageEncryption(encryptionKey, keyVersion = 1)
    val encrypted = encryption.encrypt(messageBytes)
    

EncryptionKeyManager (storage/encryption/EncryptionKeyManager.kt)

  • Reusable: ✅ Yes - Key versioning and rotation
  • Usage: Any protocol needing encryption key management
  • Example:
    1
    2
    
    val keyManager = InMemoryEncryptionKeyManager()
    val newVersion = keyManager.rotateKey()
    

KeyRotationPolicy (crypto/rotation/KeyRotationPolicy.kt)

  • Reusable: ✅ Yes - Any protocol using cryptographic keys
  • Usage: OIDC4VCI, CHAPI, or any key-based protocol
  • Example:
    1
    2
    
    val policy = TimeBasedRotationPolicy(maxAgeDays = 90)
    val shouldRotate = policy.shouldRotate(keyId, metadata)
    

2. Storage Infrastructure

ProtocolMessage Interface (storage/ProtocolMessage.kt)

  • Reusable: ✅ Yes - Generic message interface
  • Usage: All protocols should implement this
  • Example:
    1
    2
    3
    4
    5
    
    data class DidCommMessage(...) : ProtocolMessage {
        override val messageId: String get() = id
        override val messageType: String get() = type
        // ... implement other properties
    }
    

ProtocolMessageStorage Interface (storage/ProtocolMessageStorage.kt)

  • Reusable: ✅ Yes - Generic storage interface
  • Usage: All protocols can use this
  • Example:
    1
    2
    
    val storage: ProtocolMessageStorage<DidCommMessage> =
        PostgresMessageStorage(serializer, dataSource)
    

PostgresMessageStorage (storage/database/PostgresMessageStorage.kt)

  • Reusable: ✅ Yes - Works with any ProtocolMessage
  • Usage: DIDComm, OIDC4VCI, CHAPI, or any protocol
  • Example:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    // DIDComm
    val didCommStorage = PostgresMessageStorage(
        serializer = DidCommMessage.serializer(),
        dataSource = dataSource,
        tableName = "didcomm_messages"
    )
    
    // OIDC4VCI
    val oidcStorage = PostgresMessageStorage(
        serializer = Oidc4VciOffer.serializer(),
        dataSource = dataSource,
        tableName = "oidc4vci_offers"
    )
    

3. Advanced Features

Message Archiving (storage/archive/)

  • Reusable: ✅ Yes - Can archive any data
  • Usage: OIDC4VCI offers, CHAPI requests, any protocol data
  • Example:
    1
    2
    
    val archiver = S3MessageArchiver(storage, s3Client, bucketName)
    val result = archiver.archiveMessages(policy)
    

Message Replication (storage/replication/ReplicationManager.kt)

  • Reusable: ✅ Yes - Works with any ProtocolMessageStorage
  • Usage: High availability for any protocol
  • Example:
    1
    2
    3
    4
    5
    
    val replicationManager = ReplicationManager(
        primary = storage1,
        replicas = listOf(storage2, storage3),
        replicationMode = ReplicationMode.ASYNC
    )
    

Advanced Search (storage/search/)

  • Reusable: ✅ Partially - PostgreSQL full-text search is generic
  • Usage: Search any stored protocol messages
  • Example:
    1
    2
    
    val search = PostgresFullTextSearch(dataSource)
    val results = search.fullTextSearch("credential offer")
    

Message Analytics (storage/analytics/)

  • Reusable: ✅ Partially - Analytics logic is generic
  • Usage: Analyze traffic for any protocol
  • Example:
    1
    2
    
    val analytics = PostgresMessageAnalytics(dataSource)
    val stats = analytics.getStatistics(startTime, endTime)
    

🔄 Needs Abstraction (Currently DIDComm-Specific)

Storage Implementations

  • Current: PostgresDidCommMessageStorage, MongoDidCommMessageStorage
  • Solution: Use generic PostgresMessageStorage<T>, MongoMessageStorage<T>
  • Status: ✅ Generic implementations created

Storage Interfaces

  • Current: DidCommMessageStorage uses DidCommMessage
  • Solution: Use generic ProtocolMessageStorage<T>
  • Status: ✅ Generic interface created

❌ Not Reusable (Protocol-Specific)

DIDComm-Specific Components

  1. DidCommMessage Model - DIDComm-specific structure
  2. DidCommPacker - DIDComm packing/unpacking
  3. DidCommCrypto - DIDComm-specific encryption adapters

Usage Examples

OIDC4VCI with Generic Storage

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
// 1. Make OIDC4VCI offer implement ProtocolMessage
data class Oidc4VciOffer(
    val id: String,
    val type: String,
    val issuerDid: String,
    // ... other fields
) : ProtocolMessage {
    override val messageId: String get() = id
    override val messageType: String get() = type
    override val from: String? get() = issuerDid
    override val to: List<String> get() = listOf(holderDid)
    // ... implement other properties
}

// 2. Use generic storage
val oidcStorage = PostgresMessageStorage(
    serializer = Oidc4VciOffer.serializer(),
    dataSource = dataSource,
    tableName = "oidc4vci_offers",
    encryption = AesMessageEncryption(encryptionKey)
)

// 3. Use encryption
val encryption = AesMessageEncryption(
    encryptionKey = EncryptionKeyManager.generateRandomKey(),
    keyVersion = 1
)

// 4. Use key rotation
val rotationPolicy = TimeBasedRotationPolicy(maxAgeDays = 90)
val rotationManager = KeyRotationManager(keyStore, kms, rotationPolicy)

CHAPI with Generic Storage

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
// 1. Make CHAPI offer implement ProtocolMessage
data class ChapiOffer(
    val id: String,
    val type: String,
    val issuerDid: String,
    // ... other fields
) : ProtocolMessage {
    override val messageId: String get() = id
    override val messageType: String get() = type
    override val from: String? get() = issuerDid
    override val to: List<String> get() = emptyList()
    // ... implement other properties
}

// 2. Use generic storage
val chapiStorage = PostgresMessageStorage(
    serializer = ChapiOffer.serializer(),
    dataSource = dataSource,
    tableName = "chapi_offers"
)

// 3. Use archiving
val archiver = S3MessageArchiver(chapiStorage, s3Client, bucketName)
val policy = AgeBasedArchivePolicy(maxAgeDays = 30)
archiver.archiveMessages(policy)

Migration Guide

For DIDComm

  1. Update DidCommMessage:
    1
    2
    3
    4
    
    data class DidCommMessage(...) : ProtocolMessage {
        override val messageId: String get() = id
        // ... implement ProtocolMessage properties
    }
    
  2. Use Generic Storage:
    1
    2
    3
    4
    5
    6
    7
    
    val genericStorage = PostgresMessageStorage(
        serializer = DidCommMessage.serializer(),
        dataSource = dataSource,
        tableName = "didcomm_messages"
    )
    
    val didCommStorage = DidCommMessageStorageAdapter(genericStorage)
    

For OIDC4VCI

  1. Create ProtocolMessage Implementation:
    1
    
    data class Oidc4VciOffer(...) : ProtocolMessage { ... }
    
  2. Use Generic Storage:
    1
    2
    3
    4
    5
    
    val storage = PostgresMessageStorage(
        serializer = Oidc4VciOffer.serializer(),
        dataSource = dataSource,
        tableName = "oidc4vci_offers"
    )
    
  3. Replace In-Memory Maps:
    1
    2
    3
    4
    5
    
    // Before
    private val offers = mutableMapOf<String, Oidc4VciOffer>()
    
    // After
    private val storage: ProtocolMessageStorage<Oidc4VciOffer> = ...
    

For CHAPI

  1. Create ProtocolMessage Implementation:
    1
    
    data class ChapiOffer(...) : ProtocolMessage { ... }
    
  2. Use Generic Storage:
    1
    2
    3
    4
    5
    
    val storage = PostgresMessageStorage(
        serializer = ChapiOffer.serializer(),
        dataSource = dataSource,
        tableName = "chapi_offers"
    )
    

Benefits

  1. Code Reuse: Share storage, encryption, and key management across protocols
  2. Consistency: Same storage patterns across all protocols
  3. Maintainability: Fix bugs once, benefit everywhere
  4. Features: Get archiving, replication, search, analytics for free
  5. Flexibility: Easy to add new protocols

Summary

6 Fully Reusable Components: Encryption, key management, rotation, archiving, replication, generic storage

4 Reusable with Abstraction: Storage implementations, search, analytics (now abstracted)

3 Protocol-Specific: DIDComm message models, packing, crypto adapters

All reusable components are now in credentials/credential-core and can be used by any protocol!