The problem: all or nothing
When we show an ID card, we show name, address, national ID number and photo all at once — even when the only thing needed to buy a drink is "is this person an adult". Digital was no different: tear part of a signed document away and the signature breaks.
SD-JWT (Selective Disclosure JWT) is the structure that removes that constraint.
How it works
The core idea: the issuer does not sign each value directly — it signs a hash of the value.
1. For each claim the issuer attaches a random salt and builds a structure like `[salt, "birthdate", "1994-03-21"]`.
2. It hashes that structure, places the hash in the credential body, and signs the whole body.
3. The structure holding the original value is kept in the wallet as a separate piece (a disclosure).
At presentation time the wallet sends the signed body together with only the disclosures for the claims being revealed. The verifier hashes each disclosure it receives and matches it against the hashes in the body. A match means the value is the one the issuer signed. For claims whose disclosure was not sent, only the hash remains in the body, and the original value cannot be recovered from it. The salt blocks dictionary attacks.
The signature stays valid. Sending only part of the credential does not break it.
"Is an adult" takes one more step
To hide the birthdate itself, the issuer has to issue a derived claim in advance, such as `age_over_18: true`. That claim is its own disclosure, so it can be sent without the birthdate disclosure. The verifier neither has to compute the age nor learn it.
There is an important design decision here. Which derived claims exist is decided at issuance time. If the issuer only created `age_over_18`, then when "19 or older" is needed later, the credential has to be reissued. That is why schema design matters.
Whether the presenter is the real holder is a separate question
Selective disclosure answers "is the value genuine" but not "does this person own the credential". Disclosures can be copied wholesale and used by someone else.
That is why a Key Binding JWT is attached. At issuance the holder's public key is placed in the credential body; at presentation the wallet signs the nonce and the verifier identifier with that key. The verifier checks this signature too. Copied disclosures alone do not pass.
What the verifier actually checks comes down to four things.
1. Is the issuer signature valid
2. Do the hashes of the received disclosures match the body
3. Is the Key Binding JWT signed with the public key in the body, with the right nonce and audience
4. Has the credential been revoked (status list lookup)
If any one of the four fails, reject. The failure paths the conformance suite tests so relentlessly are exactly these four.
Where it bites in practice
- If you store the disclosures as-is in your logs, selective disclosure loses its meaning. The verification receipt should record "what was checked" and not the values themselves.
- Claims left as hashes still reveal that they exist. "This credential has an address claim" is not hidden.
- More disclosures means a larger presentation. That becomes a real constraint when transferring over QR.