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.
TODO (audit 2026-05): Locations below are aspirational. As of this audit:
KeyEncryption,LocalKeyStore,MessageEncryption,EncryptionKeyManager,KeyRotationPolicy/KeyRotationManager,ReplicationManager,*MessageStorage,EncryptedFileLocalKeyStore,HybridKmsSecretResolver,KmsSecretResolverall live undercredentials/plugins/didcomm/src/main/kotlin/org/trustweave/credential/didcomm/...— not incredential-core.- The
ProtocolMessage/ genericProtocolMessageStorage<T>/ genericPostgresMessageStorage<T>/S3MessageArchiver/PostgresFullTextSearch/PostgresMessageAnalyticstypes referenced below do not currently exist in the codebase. The DIDComm-specific equivalents (DidCommMessage,DidCommMessageStorage,PostgresDidCommMessageStorage,MongoDidCommMessageStorage) do exist.- Treat the rest of this document as a design proposal for cross-protocol abstraction, not a description of the current API.
✅ Fully Reusable Components
1. Encryption & Key Management
Location: credentials/credential-core/src/main/kotlin/org.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:
DidCommMessageStorageusesDidCommMessage - Solution: Use generic
ProtocolMessageStorage<T> - Status: ✅ Generic interface created
❌ Not Reusable (Protocol-Specific)
DIDComm-Specific Components
- DidCommMessage Model - DIDComm-specific structure
- DidCommPacker - DIDComm packing/unpacking
- 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
- Update DidCommMessage:
1 2 3 4
data class DidCommMessage(...) : ProtocolMessage { override val messageId: String get() = id // ... implement ProtocolMessage properties }
- 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
- Create ProtocolMessage Implementation:
1
data class Oidc4VciOffer(...) : ProtocolMessage { ... }
- Use Generic Storage:
1 2 3 4 5
val storage = PostgresMessageStorage( serializer = Oidc4VciOffer.serializer(), dataSource = dataSource, tableName = "oidc4vci_offers" )
- Replace In-Memory Maps:
1 2 3 4 5
// Before private val offers = mutableMapOf<String, Oidc4VciOffer>() // After private val storage: ProtocolMessageStorage<Oidc4VciOffer> = ...
For CHAPI
- Create ProtocolMessage Implementation:
1
data class ChapiOffer(...) : ProtocolMessage { ... }
- Use Generic Storage:
1 2 3 4 5
val storage = PostgresMessageStorage( serializer = ChapiOffer.serializer(), dataSource = dataSource, tableName = "chapi_offers" )
Benefits
- Code Reuse: Share storage, encryption, and key management across protocols
- Consistency: Same storage patterns across all protocols
- Maintainability: Fix bugs once, benefit everywhere
- Features: Get archiving, replication, search, analytics for free
- 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!