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
- Android/Kotlin
- iOS/Swift
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}")
}
import SphereonSdJwt
let sdJwtService = session.component.sdJwtService
// Create presentation with all disclosures included
let result = try await sdJwtService.presentSdJwt(
args: PresentSdJwtArgs(
sdJwt: issuedSdJwt,
disclosureSelection: nil // nil = include all disclosures
)
)
if result.isOk {
let presentation = result.value
print("Presentation: \(presentation.presentation)")
print("Disclosed claims: \(presentation.disclosedClaims)")
}
Selective Disclosure
Choose which claims to reveal by providing an SdMap:
- Android/Kotlin
- iOS/Swift
// 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}")
}
// Select specific claims to disclose
let disclosureSelection = SdMap(
fields: [
"email": SdField(sd: true), // Disclose email
"given_name": SdField(sd: true) // Disclose given_name
// family_name, age, etc. are NOT disclosed
]
)
let result = try await sdJwtService.presentSdJwt(
args: PresentSdJwtArgs(
sdJwt: issuedSdJwt,
disclosureSelection: disclosureSelection
)
)
if result.isOk {
// Presentation contains only email and given_name disclosures
print("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:
- Android/Kotlin
- iOS/Swift
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
}
let result = try await sdJwtService.presentSdJwt(
args: PresentSdJwtArgs(
sdJwt: issuedSdJwt,
disclosureSelection: SdMap(
fields: [
"given_name": SdField(sd: true),
"family_name": SdField(sd: true)
]
),
// Key binding parameters
audience: "https://verifier.example.com",
nonce: verifierProvidedNonce,
holderKey: holderManagedKeyInfo
)
)
if result.isOk {
// Presentation format: JWT~disclosure1~disclosure2~KeyBindingJWT
let presentation = result.value.presentation
}
The Key Binding JWT contains:
| Claim | Description |
|---|---|
aud | Verifier's identifier (audience) |
nonce | Fresh nonce from verifier |
iat | Issued-at timestamp |
sd_hash | Hash of the presentation (binds KB-JWT to specific disclosures) |
Verifying Presentations
Verifiers use SdJwtService.verifySdJwt() to validate presentations.
Basic Verification
- Android/Kotlin
- iOS/Swift
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}")
}
}
import SphereonSdJwt
let sdJwtService = session.component.sdJwtService
let result = try await sdJwtService.verifySdJwt(
args: VerifySdJwtArgs(
sdJwt: presentation,
identifier: issuerIdentifier
)
)
if result.isOk {
let verification = result.value
if verification.isValid {
print("Signature valid: \(verification.signatureValid)")
print("Disclosures valid: \(verification.disclosuresValid)")
// Access disclosed claims
let fullPayload = verification.sdJwt.payload.fullPayload
let email = fullPayload["email"]?.description
let givenName = fullPayload["given_name"]?.description
} else {
print("Verification failed: \(verification.errorMessages)")
}
}
Verifying Key Binding
When validating presentations with holder binding:
- Android/Kotlin
- iOS/Swift
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}")
}
}
}
let result = try await sdJwtService.verifySdJwt(
args: VerifySdJwtArgs(
sdJwt: presentation,
identifier: issuerIdentifier,
expectedAudience: "https://verifier.example.com",
expectedNonce: providedNonce
)
)
if result.isOk {
let verification = result.value
if verification.isValid {
// All checks passed: signature, disclosures, and key binding
print("Key binding valid: \(verification.keyBindingValid)")
// Access KB-JWT details
if let kbJwt = verification.sdJwt.keyBindingJwt {
print("KB-JWT audience: \(kbJwt.audience ?? "")")
print("KB-JWT nonce: \(kbJwt.nonce ?? "")")
print("KB-JWT sd_hash: \(kbJwt.sdHash ?? "")")
}
}
}
Accessing Verified Data
The verification result provides access to both the undisclosed and full payloads:
- Android/Kotlin
- iOS/Swift
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}")
}
}
if result.isOk && result.value.isValid {
let sdJwt = result.value.sdJwt
// JWT header
let algorithm = sdJwt.algorithm // e.g., "ES256"
let keyId = sdJwt.keyId // "kid" from header
// Payload views
let undisclosed = sdJwt.payload.undisclosedPayload // Contains _sd array
let full = sdJwt.payload.fullPayload // All disclosed claims resolved
// Disclosures
for disclosure in sdJwt.disclosures {
print("\(disclosure.key) = \(disclosure.value)")
}
}
Complete Example
- Android/Kotlin
- iOS/Swift
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
)
import SphereonSdJwt
class PresentationService {
private let sdJwtService: SdJwtService
init(sdJwtService: SdJwtService) {
self.sdJwtService = sdJwtService
}
// Holder: Create presentation for verifier
func createPresentation(
credential: String,
verifierAudience: String,
verifierNonce: String,
claimsToDisclose: Set<String>,
holderKey: ManagedIdentifierOptsOrResult
) async throws -> String {
var fields: [String: SdField] = [:]
for claim in claimsToDisclose {
fields[claim] = SdField(sd: true)
}
let disclosureSelection = SdMap(fields: fields)
let result = try await sdJwtService.presentSdJwt(
args: PresentSdJwtArgs(
sdJwt: credential,
disclosureSelection: disclosureSelection,
audience: verifierAudience,
nonce: verifierNonce,
holderKey: holderKey
)
)
guard result.isOk else {
throw NSError(domain: "Presentation", code: -1)
}
return result.value.presentation
}
// Verifier: Validate presentation
func verifyPresentation(
presentation: String,
issuerIdentifier: IdentifierOptsOrResult,
expectedAudience: String,
expectedNonce: String,
requiredClaims: Set<String>
) async throws -> VerifiedClaims {
let result = try await sdJwtService.verifySdJwt(
args: VerifySdJwtArgs(
sdJwt: presentation,
identifier: issuerIdentifier,
expectedAudience: expectedAudience,
expectedNonce: expectedNonce
)
)
guard result.isOk else {
throw NSError(domain: "Verification", code: -1)
}
let verification = result.value
guard verification.isValid else {
throw NSError(domain: "Verification", code: -2,
userInfo: [NSLocalizedDescriptionKey: verification.errorMessages.joined(separator: ", ")])
}
return VerifiedClaims(
issuer: verification.sdJwt.payload.fullPayload["iss"]?.description ?? "",
subject: verification.sdJwt.payload.fullPayload["sub"]?.description,
claims: verification.sdJwt.payload.fullPayload,
keyBindingVerified: verification.keyBindingValid
)
}
}
struct VerifiedClaims {
let issuer: String
let subject: String?
let claims: [String: Any]
let keyBindingVerified: Bool
}
Verification Result
The SdJwtVerificationResult contains:
| Property | Type | Description |
|---|---|---|
sdJwt | SdJwtCompact | The parsed SD-JWT structure |
signatureValid | Boolean | JWT signature verification result |
disclosuresValid | Boolean | All disclosure digests match |
keyBindingValid | Boolean | KB-JWT validation result (if present) |
errorMessages | List<String> | Validation error details |
isValid | Boolean | Combined validation result |
Presentation Arguments
The PresentSdJwtArgs accepts:
| Parameter | Type | Description |
|---|---|---|
sdJwt | String | Full SD-JWT from issuer |
disclosureSelection | SdMap? | Claims to disclose (null = all) |
audience | String? | Verifier identifier for KB-JWT |
nonce | String? | Verifier nonce for KB-JWT |
holderKey | ManagedIdentifierOptsOrResult? | Key for signing KB-JWT |
kbJwtOpts | CreateJwsOpts | Additional JWS options |