Lion Cipher Lab
Verify a Verifiable Credential with SD-JWT (build issuer, holder, and verifier from scratch)
- Identity & Access Management
- Advanced
- ~90 min
- Free · no account required
Why it matters
Digital identity is moving into wallets. The EU Digital Identity Wallet, and the OpenID for Verifiable Presentations (OpenID4VP) flows built around it, need a credential format that lets a person prove ONE fact -- 'I am over 18', 'I am a licensed pharmacist', 'I live in this canton' -- without dumping their entire identity document at a bar, a website, or a border kiosk. A plain signed JWT cannot do that: it is all-or-nothing, so the moment you show it to prove your age you have also revealed your name, full birthdate, document number and everything else in it. SD-JWT (Selective Disclosure…
What you'll learn
- the selective-disclosure model behind modern wallet credentials (prove a claim without revealing the underlying data)
- implementing SD-JWT from scratch: salted Disclosures, SHA-256 digests, and the _sd array in a signed JWT
- building all three roles -- issuer, holder, and verifier -- and the ~-separated combined format that ties them together
- holder key binding (the KB-JWT) as the defense against bearer-token theft of a credential
- naming and proving four SD-JWT failure modes: missing key binding, skipped _sd digest check, weak/reused salt, and ignored aud/nonce
- the WHEN/WHY judgment for choosing SD-JWT VC vs an mdoc/mDL vs a plain signed JWT
Steps in this lab
- The one idea: prove a claim without revealing the data behind it
- Set up: a clean folder and two packages
- Build the machinery from scratch (sdjwt.py)
- The issuer: turn claims into salted Disclosures + a signed _sd digest list
- The holder: choose what to reveal, then bind it with a KB-JWT
- The verifier: check the signature, re-hash every Disclosure, verify the binding
- Run the happy path: prove 'over 18' without revealing the birthdate
- Failure mode A -- no key binding: the credential becomes a bearer token
- Failure mode B -- skipping the _sd digest check: forged claims accepted
- Failure mode C -- weak or reused salt: linkability and value-guessing
- Failure mode D -- ignoring aud/nonce: a presentation replays across verifiers
- Judgment: when SD-JWT VC, when an mdoc/mDL, when just a signed JWT
- Clean up