SD-JWT & Selective Disclosure
SD-JWT (Selective Disclosure JWT) is the credential format iDen2 issues. It lets the holder reveal individual fields of a credential — just dateOfBirth, say — without exposing everything else, while the verifier can still cryptographically verify what was revealed.
The problem it solves
A classic JWT credential is all-or-nothing: to prove your date of birth you'd hand over the entire signed payload, including your name, address, and everything else in it. SD-JWT fixes this by removing selected claim values from the signed payload and replacing them with salted hashes. The actual values travel separately as disclosures that the holder can include or withhold per presentation.
How an SD-JWT is structured
An SD-JWT is the issuer-signed JWT followed by zero or more disclosures, joined by the ~ separator, and optionally terminated by a Key Binding JWT:
<issuer-signed JWT>~<disclosure 1>~<disclosure 2>~...~<KB-JWT>
- Each disclosure is a Base64-encoded
[salt, claimName, claimValue]triple. Its hash appears in the signed JWT, so a verifier can confirm any disclosed value is authentic — and the random salt prevents guessing withheld values from their hashes. - The KB-JWT (Key Binding JWT) is signed with the holder's private key — the same key the credential was bound to at issuance (
holderBinding). It proves the presenter isn't replaying someone else's credential and binds the presentation to the verifier'sclient_idaudience.
Disclosable vs. always-disclosed claims
You decide disclosability at issuance time with the selectiveDisclosure.fields object on POST /api/v1/iaas/credential-offers:
- Mark fields with
{ "sd": true }to make them individually disclosable. - Credential Factory wraps those fields as disclosures in the issued SD-JWT.
- At presentation time, the holder's wallet includes only the fields the verifier requested (and the holder consented to).
Fields absent from selectiveDisclosure.fields are always disclosed — they stay in the signed payload in plaintext and appear in every presentation.
{
"credentialData": {
"givenName": "Alice",
"familyName": "Smith",
"dateOfBirth": "1990-01-15"
},
"selectiveDisclosure": {
"fields": {
"dateOfBirth": { "sd": true }
}
}
}
Here dateOfBirth is holder-controlled, while givenName and familyName are revealed in every presentation.
Every key in selectiveDisclosure.fields must exactly match a key in credentialData. A mismatch (typo, case difference, extra key) returns a 400 error.
Rule of thumb
Mark everything personal as sd: true unless you have a concrete reason a field must always be visible. Disclosability costs nothing at issuance, but an always-disclosed field can never be hidden later — the privacy decision is permanent for the life of the credential.
Where to go next
- Issuance Integration Guide
- OpenID4VP — how disclosed fields reach the verifier as
credentialData