Skip to main content
Version: v0.13

SD-JWT Presentation

The IDK provides tools for holders to create presentations with selective disclosure and for verifiers to validate those presentations.

Creating Presentations

Holders use SdJwtService.presentSdJwt() to create presentations from issued credentials, selecting which claims to disclose.

Basic Presentation

import com.sphereon.sdjwt.PresentSdJwtArgs
import com.sphereon.sdjwt.SdMap
import com.sphereon.sdjwt.SdField

val sdJwtService = session.component.sdJwtService

// Create presentation with all disclosures included
val result = sdJwtService.presentSdJwt(
PresentSdJwtArgs(
sdJwt = issuedSdJwt,
disclosureSelection = null // null = include all disclosures
)
)

if (result.isOk) {
val presentation = result.value
println("Presentation: ${presentation.presentation}")
println("Disclosed claims: ${presentation.disclosedClaims}")
}

Selective Disclosure

Choose which claims to reveal by providing an SdMap:

// Select specific claims to disclose
val disclosureSelection = SdMap(
fields = mapOf(
"email" to SdField(sd = true), // Disclose email
"given_name" to SdField(sd = true) // Disclose given_name
// family_name, age, etc. are NOT disclosed
)
)

val result = sdJwtService.presentSdJwt(
PresentSdJwtArgs(
sdJwt = issuedSdJwt,
disclosureSelection = disclosureSelection
)
)

if (result.isOk) {
// Presentation contains only email and given_name disclosures
println("Disclosed: ${result.value.disclosedClaims}")
}

Key Binding JWT

For credentials with holder key binding, include a Key Binding JWT (KB-JWT) to prove possession of the holder key:

val result = sdJwtService.presentSdJwt(
PresentSdJwtArgs(
sdJwt = issuedSdJwt,
disclosureSelection = SdMap(
fields = mapOf(
"given_name" to SdField(sd = true),
"family_name" to SdField(sd = true)
)
),
// Key binding parameters
audience = "https://verifier.example.com",
nonce = verifierProvidedNonce,
holderKey = holderManagedKeyInfo
)
)

if (result.isOk) {
// Presentation format: JWT~disclosure1~disclosure2~KeyBindingJWT
val presentation = result.value.presentation
}

The Key Binding JWT contains:

ClaimDescription
audVerifier's identifier (audience)
nonceFresh nonce from verifier
iatIssued-at timestamp
sd_hashHash of the presentation (binds KB-JWT to specific disclosures)

Verifying Presentations

Verifiers use SdJwtService.verifySdJwt() to validate presentations.

Basic Verification

import com.sphereon.sdjwt.VerifySdJwtArgs

val sdJwtService = session.component.sdJwtService

val result = sdJwtService.verifySdJwt(
VerifySdJwtArgs(
sdJwt = presentation,
identifier = issuerIdentifier
)
)

if (result.isOk) {
val verification = result.value

if (verification.isValid) {
println("Signature valid: ${verification.signatureValid}")
println("Disclosures valid: ${verification.disclosuresValid}")

// Access disclosed claims
val fullPayload = verification.sdJwt.payload.fullPayload
val email = fullPayload["email"]?.toString()?.trim('"')
val givenName = fullPayload["given_name"]?.toString()?.trim('"')
} else {
println("Verification failed: ${verification.errorMessages}")
}
}

Verifying Key Binding

When validating presentations with holder binding:

val result = sdJwtService.verifySdJwt(
VerifySdJwtArgs(
sdJwt = presentation,
identifier = issuerIdentifier,
expectedAudience = "https://verifier.example.com",
expectedNonce = providedNonce
)
)

if (result.isOk) {
val verification = result.value

if (verification.isValid) {
// All checks passed: signature, disclosures, and key binding
println("Key binding valid: ${verification.keyBindingValid}")

// Access KB-JWT details
verification.sdJwt.keyBindingJwt?.let { kbJwt ->
println("KB-JWT audience: ${kbJwt.audience}")
println("KB-JWT nonce: ${kbJwt.nonce}")
println("KB-JWT sd_hash: ${kbJwt.sdHash}")
}
}
}

Accessing Verified Data

The verification result provides access to both the undisclosed and full payloads:

if (result.isOk && result.value.isValid) {
val sdJwt = result.value.sdJwt

// JWT header
val algorithm = sdJwt.algorithm // e.g., "ES256"
val keyId = sdJwt.keyId // "kid" from header

// Payload views
val undisclosed = sdJwt.payload.undisclosedPayload // Contains _sd array
val full = sdJwt.payload.fullPayload // All disclosed claims resolved

// Disclosures
sdJwt.disclosures.forEach { disclosure ->
println("${disclosure.key} = ${disclosure.value}")
}

// Digest to disclosure mapping
sdJwt.payload.digestedDisclosures.forEach { (digest, disclosure) ->
println("$digest -> ${disclosure.key}")
}
}

Complete Example

import com.sphereon.sdjwt.*

class PresentationService(
private val sdJwtService: SdJwtService
) {
// Holder: Create presentation for verifier
suspend fun createPresentation(
credential: String,
verifierAudience: String,
verifierNonce: String,
claimsToDisclose: Set<String>,
holderKey: ManagedIdentifierOptsOrResult
): String {
val disclosureSelection = SdMap(
fields = claimsToDisclose.associateWith { SdField(sd = true) }
)

val result = sdJwtService.presentSdJwt(
PresentSdJwtArgs(
sdJwt = credential,
disclosureSelection = disclosureSelection,
audience = verifierAudience,
nonce = verifierNonce,
holderKey = holderKey
)
)

return if (result.isOk) {
result.value.presentation
} else {
throw IllegalStateException("Presentation failed: ${result.error}")
}
}

// Verifier: Validate presentation
suspend fun verifyPresentation(
presentation: String,
issuerIdentifier: IdentifierOptsOrResult,
expectedAudience: String,
expectedNonce: String,
requiredClaims: Set<String>
): VerifiedClaims {
val result = sdJwtService.verifySdJwt(
VerifySdJwtArgs(
sdJwt = presentation,
identifier = issuerIdentifier,
expectedAudience = expectedAudience,
expectedNonce = expectedNonce
)
)

if (!result.isOk) {
throw IllegalStateException("Verification error: ${result.error}")
}

val verification = result.value

if (!verification.isValid) {
throw SecurityException(
"Verification failed: ${verification.errorMessages.joinToString()}"
)
}

// Check required claims are present
val disclosedClaims = verification.sdJwt.payload.fullPayload
val missingClaims = requiredClaims.filter { !disclosedClaims.containsKey(it) }

if (missingClaims.isNotEmpty()) {
throw IllegalArgumentException("Missing required claims: $missingClaims")
}

return VerifiedClaims(
issuer = disclosedClaims["iss"]?.toString()?.trim('"') ?: "",
subject = disclosedClaims["sub"]?.toString()?.trim('"'),
claims = disclosedClaims,
keyBindingVerified = verification.keyBindingValid
)
}
}

data class VerifiedClaims(
val issuer: String,
val subject: String?,
val claims: Map<String, Any?>,
val keyBindingVerified: Boolean
)

Verification Result

The SdJwtVerificationResult contains:

PropertyTypeDescription
sdJwtSdJwtCompactThe parsed SD-JWT structure
signatureValidBooleanJWT signature verification result
disclosuresValidBooleanAll disclosure digests match
keyBindingValidBooleanKB-JWT validation result (if present)
errorMessagesList<String>Validation error details
isValidBooleanCombined validation result

Presentation Arguments

The PresentSdJwtArgs accepts:

ParameterTypeDescription
sdJwtStringFull SD-JWT from issuer
disclosureSelectionSdMap?Claims to disclose (null = all)
audienceString?Verifier identifier for KB-JWT
nonceString?Verifier nonce for KB-JWT
holderKeyManagedIdentifierOptsOrResult?Key for signing KB-JWT
kbJwtOptsCreateJwsOptsAdditional JWS options