DID-Based Trust
Decentralized Identifiers (DIDs) are self-sovereign identifiers that resolve to DID Documents containing public keys and service endpoints. While DIDs are self-issued by design, real-world applications still need to answer the question: "Should I trust this DID?"
The IDK provides a DID trust validation layer that applies configurable policies to determine whether a given DID should be trusted. This is separate from DID resolution itself; the trust layer decides policy, while the DID resolver handles resolution.
How It Works
The DidTrustValidationService implements the TrustValidationService interface and is automatically contributed to the session scope via @ContributesIntoSet(SessionScope::class). It handles the TrustContext.TYPE_DID trust type.
Validation follows a two-layer approach:
Layer 1: Method Allow-List Gate
Before any other checks, the service verifies that the DID method (the second segment of a DID, such as web in did:web:example.com) is in the configured list of allowed methods. If the method is not allowed, validation fails immediately. This provides a coarse-grained first filter. For example, you might allow web and key methods but reject peer DIDs.
Layer 2: Trust List Check
If the DID method passes the allow-list gate, the service checks whether the specific DID appears in the configured trusted DIDs list. This is a fine-grained check that lets you enumerate exactly which DIDs your application trusts. When no trusted DIDs list is configured, any DID with an allowed method passes this layer.
Layer 3: Controller Chain Validation
Finally, the service validates the DID controller chain. A DID Document may declare a controller field pointing to another DID. The service verifies that the controller chain is valid and terminates at a trusted DID or at the DID itself (self-controlled).
Usage
Access the trust validation service from the session graph and call validate() with a TrustContext of type TYPE_DID:
- Android/kotlin
- iOS/Swift
val trustValidationService = session.graph.trustValidationService
// Build a trust context for DID trust
val context = TrustContext(
type = TrustContext.TYPE_DID,
parameters = mapOf(
"did" to "did:web:issuer.example.com"
)
)
val result = trustValidationService.validate(context)
when (result.status) {
TrustStatus.TRUSTED -> {
println("DID is trusted")
println("Trust anchor: ${result.trustAnchor?.name}")
}
TrustStatus.UNTRUSTED -> {
println("DID is not trusted: ${result.details}")
}
TrustStatus.VALIDATION_ERROR -> {
println("Validation error: ${result.details}")
}
else -> {
println("Status: ${result.status}, details: ${result.details}")
}
}
let trustValidationService = session.graph.trustValidationService
// Build a trust context for DID trust
let context = TrustContext(
type: TrustContext.TYPE_DID,
parameters: [
"did": "did:web:issuer.example.com"
]
)
let result = try await trustValidationService.validate(context: context)
if result.trusted {
print("DID is trusted")
print("Trust anchor: \(result.trustAnchor?.name ?? "unknown")")
} else if result.status == .validationError {
print("Validation error: \(result.details ?? "unknown error")")
} else {
print("DID is not trusted: \(result.details ?? "no details")")
}
Overriding Allowed Methods and Trusted DIDs
You can override the configured allow-lists on a per-request basis:
- Android/kotlin
- iOS/Swift
val context = TrustContext(
type = TrustContext.TYPE_DID,
parameters = mapOf(
"did" to "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"allowedMethods" to "key,web",
"trustedDids" to "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK,did:web:trusted.example.com"
)
)
val result = trustValidationService.validate(context)
let context = TrustContext(
type: TrustContext.TYPE_DID,
parameters: [
"did": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK",
"allowedMethods": "key,web",
"trustedDids": "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK,did:web:trusted.example.com"
]
)
let result = try await trustValidationService.validate(context: context)
Context Parameters
| Parameter | Required | Description |
|---|---|---|
did | Yes | The DID to validate |
allowedMethods | No | Comma-separated list of allowed DID methods; overrides config |
trustedDids | No | Comma-separated list of explicitly trusted DIDs; overrides config |
identifierJson | No | Pre-resolved DID Document as a JSON string (avoids re-resolution) |
Using the Command Directly
You can also invoke the validation command directly via the command ID trust.did.validate:
- Android/kotlin
- iOS/Swift
val commandManager = session.graph.commandManager
val result = commandManager.execute(
ValidateDidTrustCommand(
did = "did:web:issuer.example.com",
allowedMethods = listOf("web", "key", "jwk"),
trustedDids = listOf(
"did:web:issuer.example.com",
"did:web:another-trusted.example.com"
)
)
)
let commandManager = session.graph.commandManager
let result = try await commandManager.execute(
command: ValidateDidTrustCommand(
did: "did:web:issuer.example.com",
allowedMethods: ["web", "key", "jwk"],
trustedDids: [
"did:web:issuer.example.com",
"did:web:another-trusted.example.com"
]
)
)
Configuration
Enable and configure DID trust validation via properties:
# Enable DID trust validation
trust.anchors.did.enabled=false
# Explicitly trusted DIDs (comma-separated)
trust.anchors.did.trusted-dids=did:web:example.com,did:key:z6Mk...
# Allowed DID methods (comma-separated, without the "did:" prefix)
trust.anchors.did.allowed-methods=web,key,jwk
When trust.anchors.did.enabled is set to true but no trusted-dids are configured, the service will trust any DID whose method appears in allowed-methods. When trusted-dids is configured, only DIDs that match both the method allow-list and the trusted DIDs list are considered trusted.
Module Dependencies
The DID trust module requires lib-trust-did and lib-did-resolver on the classpath. Add the following to your build.gradle.kts:
dependencies {
implementation("com.sphereon.idk:lib-trust-did:<version>")
implementation("com.sphereon.idk:lib-did-resolver:<version>")
}
The DidTrustValidationService is automatically discovered and registered when these modules are present on the classpath. No additional wiring is needed beyond enabling the feature in configuration.