Skip to main content
Version: v0.13

DID Resolution

The IDK provides a complete DID resolution system through the DidResolverRegistry, which manages method-specific resolvers and provides a unified API for resolution and dereferencing.

DidResolverRegistry

The DidResolverRegistry is the main entry point for DID resolution. It aggregates all registered method-specific resolvers and routes requests appropriately.

val resolverRegistry: DidResolverRegistry = session.component.didResolverRegistry

// Check supported methods
val methods = resolverRegistry.getSupportedMethods()
println("Supported methods: $methods") // [key, jwk, web]

// Check if a method is supported
if (resolverRegistry.hasResolver("web")) {
println("did:web is supported")
}

// Get method capabilities
val capabilities = resolverRegistry.getCapabilities("web")
println("Supports update: ${capabilities?.lifecycle?.supportsUpdate}")

Basic Resolution

Resolve a DID to retrieve its DID Document:

val resolverRegistry: DidResolverRegistry = session.component.didResolverRegistry

val result = resolverRegistry.resolve("did:web:example.com")

result.fold(
success = { resolutionResult ->
val didDocument = resolutionResult.didDocument
println("DID: ${didDocument?.id}")

// Access verification methods
didDocument?.verificationMethod?.forEach { vm ->
println("Key ID: ${vm.id}")
println("Type: ${vm.type}")
println("Controller: ${vm.controller}")
}

// Access services
didDocument?.service?.forEach { svc ->
println("Service: ${svc.id} -> ${svc.serviceEndpoint}")
}
},
failure = { error ->
println("Resolution failed: ${error.message}")
}
)

Resolution Options

val result = resolverRegistry.resolve(
did = "did:web:example.com",
options = DidResolutionOptions(
accept = "application/did+ld+json", // Response format
noCache = false // Use cached result if available
)
)

Resolution Result

The DidResolutionResult contains:

data class DidResolutionResult(
val didDocument: DidDocument?,
val didResolutionMetadata: DidResolutionMetadata,
val didDocumentMetadata: DidDocumentMetadata
)

// Metadata includes
data class DidDocumentMetadata(
val created: String?, // ISO 8601 timestamp
val updated: String?, // ISO 8601 timestamp
val deactivated: Boolean?, // Whether DID is deactivated
val versionId: String? // Version identifier
)

Dereference DID URLs

Dereference specific parts of a DID document using DID URLs:

Get a Verification Method by Fragment

val resolverRegistry: DidResolverRegistry = session.component.didResolverRegistry

// Dereference did:web:example.com#key-1
val result = resolverRegistry.dereference("did:web:example.com#key-1")

result.fold(
success = { dereferenceResult ->
val verificationMethod = dereferenceResult.contentStream
println("Key: ${verificationMethod?.id}")
println("Public Key: ${verificationMethod?.publicKeyJwk}")
},
failure = { error ->
println("Dereference failed: ${error.message}")
}
)

Get a Service by Query Parameter

// Dereference did:web:example.com?service=hub
val result = resolverRegistry.dereference("did:web:example.com?service=hub")

DID URL Syntax

did:web:example.com/path?query=value#fragment
│ │ │
│ │ └── Fragment (key ID)
│ └── Query parameters
└── Path

Method-Specific Resolvers

Access individual resolvers for advanced use cases:

// Get the did:web resolver directly
val webResolver: DidResolver? = resolverRegistry.getResolver("web")

webResolver?.let { resolver ->
// Check capabilities
println("Supports deactivation: ${resolver.capabilities.lifecycle.supportsDeactivate}")

// Resolve directly through the method resolver
val result = resolver.resolve("did:web:example.com")
}

Available Resolvers

ResolverMethodModule
KeyDidResolverImpldid:keylib-did-methods-key
JwkDidResolverImpldid:jwklib-did-methods-jwk
WebDidResolverImpldid:weblib-did-methods-web

Resolvers are automatically registered via DI multibinding when their modules are included.

Querying Verification Methods

Find Keys by Purpose

val resolverRegistry: DidResolverRegistry = session.component.didResolverRegistry

// Resolve the DID first
val result = resolverRegistry.resolve("did:web:example.com")

result.fold(
success = { resolutionResult ->
val doc = resolutionResult.didDocument ?: return@fold

// Get authentication keys
val authKeyIds = doc.authentication?.mapNotNull { it.reference ?: it.embedded?.id }
println("Authentication keys: $authKeyIds")

// Get assertion method keys
val assertionKeyIds = doc.assertionMethod?.mapNotNull { it.reference ?: it.embedded?.id }
println("Assertion keys: $assertionKeyIds")

// Get key agreement keys (for encryption)
val keyAgreementIds = doc.keyAgreement?.mapNotNull { it.reference ?: it.embedded?.id }
println("Key agreement keys: $keyAgreementIds")

// Resolve a specific verification method
val keyId = "#key-1"
val verificationMethod = doc.verificationMethod?.find {
it.id == keyId || it.id.endsWith(keyId)
}
println("Found key: ${verificationMethod?.publicKeyJwk}")
},
failure = { error -> println("Failed: ${error.message}") }
)

Helper Extension Functions

// Extension to get verification methods for a purpose
fun DidDocument.getVerificationMethodsForPurpose(
purpose: VerificationPurpose
): List<VerificationMethod> {
val refs = when (purpose) {
VerificationPurpose.AUTHENTICATION -> authentication
VerificationPurpose.ASSERTION_METHOD -> assertionMethod
VerificationPurpose.KEY_AGREEMENT -> keyAgreement
VerificationPurpose.CAPABILITY_INVOCATION -> capabilityInvocation
VerificationPurpose.CAPABILITY_DELEGATION -> capabilityDelegation
} ?: return emptyList()

return refs.mapNotNull { ref ->
ref.embedded ?: verificationMethod?.find {
it.id == ref.reference || it.id.endsWith("#${ref.reference}")
}
}
}

// Usage
val authKeys = didDocument.getVerificationMethodsForPurpose(VerificationPurpose.AUTHENTICATION)

Integration with Identifier Resolution

DIDs integrate with the unified Identifier Resolution system for cryptographic operations.

Resolve DID to Key Material

val identifierService: IIdentifierService = session.component.identifierService

// Resolve a DID to get its public key
val result = identifierService.resolve(
ExternalIdentifierOpts(
method = IdentifierMethodDefaults.DID,
identifier = "did:web:example.com",
kid = "#key-1" // Optional: select specific key
)
)

result.fold(
success = { resolved: ExternalIdentifierResult.Did ->
// Access the JWK
val jwk = resolved.keyInfo.key
println("Key type: ${jwk.kty}")

// Access all JWKs from the document
resolved.jwks.forEach { keyInfo ->
println("Available key: ${keyInfo.kid}")
}

// Access JWKs by purpose
val didJwks = resolved.didJwks
val authKeys = didJwks?.get("authentication")
val assertionKeys = didJwks?.get("assertionMethod")
},
failure = { error ->
println("Resolution failed: ${error.message}")
}
)

Parsed DID Information

val result: ExternalIdentifierResult.Did = identifierService.resolve(opts).getOrThrow()

// Access parsed DID components
val parsed = result.didParsed
println("Method: ${parsed.method}") // "web"
println("ID: ${parsed.id}") // "example.com"
println("Path: ${parsed.path}") // "/users/alice"
println("Fragment: ${parsed.fragment}") // "key-1"

// Access full DID document
val didDocument = result.didDocument

Using DIDs for Signature Verification

val verifyJwsCommand: VerifyJwsCommand = session.component.verifyJwsCommand

// Verify a JWS signed by a DID
val result = verifyJwsCommand.execute(
VerifyJwsArgs(
jws = signedJwt,
identifierOpts = ExternalIdentifierOpts(
method = IdentifierMethodDefaults.DID,
identifier = "did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK"
)
),
sessionContext
)

when {
result.isSuccess -> println("Signature valid!")
else -> println("Verification failed: ${result.error}")
}

Using DIDs for SD-JWT Verification

val verifySdJwtCommand: VerifySdJwtCommand = session.component.verifySdJwtCommand

val result = verifySdJwtCommand.execute(
VerifySdJwtArgs(
sdJwt = sdJwtPresentation,
issuerIdentifier = ExternalIdentifierOpts(
method = IdentifierMethodDefaults.DID,
identifier = issuerDid
)
),
sessionContext
)

Querying Stored DIDs

Query DIDs stored in the local repository using DidFilter:

val didManager: IDidManager = session.component.didManager

// Filter by method
val webDids = didManager.list(DidFilter(method = "web"))

// Filter by alias
val namedDids = didManager.list(DidFilter(alias = "production-signing"))

// Filter by role
val managedDids = didManager.list(DidFilter(role = DidRole.MANAGED))
val externalDids = didManager.list(DidFilter(role = DidRole.EXTERNAL))

// Combined filters
val activeWebDids = didManager.list(
DidFilter(
method = "web",
role = DidRole.MANAGED,
includeDeactivated = false
)
)

// Include deactivated DIDs
val allDids = didManager.list(
DidFilter(includeDeactivated = true)
)

DidFilter Properties

PropertyTypeDescription
methodString?Filter by DID method (e.g., "web", "key")
aliasString?Filter by exact alias match
roleDidRole?Filter by MANAGED or EXTERNAL
includeDeactivatedBooleanInclude deactivated DIDs (default: false)

Find by DID or Alias

// Find by DID string
val did = didManager.findByDid("did:web:example.com")

// Find by alias
val did = didManager.findByAlias("my-signing-did")

Method Capabilities

Query what a DID method supports:

val resolverRegistry: DidResolverRegistry = session.component.didResolverRegistry

val capabilities = resolverRegistry.getCapabilities("web")
capabilities?.let { caps ->
// Resolution capabilities
println("Supports resolution: ${caps.resolution.supportsResolution}")

// Lifecycle capabilities
println("Supports create: ${caps.lifecycle.supportsCreate}")
println("Supports update: ${caps.lifecycle.supportsUpdate}")
println("Supports deactivate: ${caps.lifecycle.supportsDeactivate}")

// Key management
println("Supports key rotation: ${caps.keyManagement.supportsKeyRotation}")
println("Supported key types: ${caps.keyManagement.supportedKeyTypes}")

// Metadata
println("Method name: ${caps.metadata.methodName}")
println("Specification: ${caps.metadata.specificationUrl}")
}

Caching

The resolver registry supports caching for improved performance:

// Resolution with cache control
val result = resolverRegistry.resolve(
did = "did:web:example.com",
options = DidResolutionOptions(noCache = true) // Force fresh resolution
)

Cache behavior by method:

  • did:key - Always cached (immutable, derived from identifier)
  • did:jwk - Always cached (immutable, derived from identifier)
  • did:web - Cached with TTL based on HTTP cache headers

REST API (Universal Resolver)

Expose DID resolution as a REST API:

dependencies {
implementation("com.sphereon.idk:lib-did-rest-resolver-server:0.13.0")
}

Endpoint

GET /1.0/identifiers/{did}

Example Request

GET /1.0/identifiers/did:web:example.com HTTP/1.1
Accept: application/did+ld+json

Example Response

{
"didDocument": {
"@context": ["https://www.w3.org/ns/did/v1"],
"id": "did:web:example.com",
"verificationMethod": [...]
},
"didResolutionMetadata": {
"contentType": "application/did+ld+json"
},
"didDocumentMetadata": {
"created": "2024-01-15T10:30:00Z",
"updated": "2024-06-20T14:45:00Z"
}
}

Error Handling

Common resolution errors:

ErrorDescription
UNSUPPORTED_OPERATIONDID method not registered
ILLEGAL_ARGUMENTMalformed DID syntax
NETWORK_ERRORFailed to fetch did:web document
NOT_FOUNDDID document not found
result.fold(
success = { /* handle success */ },
failure = { error ->
when {
error.message?.contains("No resolver registered") == true ->
println("Unsupported DID method")
error.message?.contains("Invalid DID") == true ->
println("Malformed DID")
else -> println("Error: ${error.message}")
}
}
)

Next Steps