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