Skip to main content

SD-JWT VC debugger

Paste a compact SD-JWT VC below to decode it: the issuer-signed JWT header and payload, every disclosure with the digest it matches, the decoy digests, the reconstructed claims and the key binding JWT. It is free, and it runs entirely in your browser.

An SD-JWT VC is deliberately hard to read. The credential arrives as one long string of tilde-separated base64url segments, the claims you care about are not in the payload at all — only their SHA-256 digests are — and some of those digests stand for nothing, so that a verifier cannot tell how many claims the holder withheld.

This tool takes the whole thing apart: the issuer-signed JWT, every disclosure, which digest each disclosure satisfies, which digests nothing satisfies, the payload as it looks once the disclosures are applied, and the key binding JWT at the end.

Your credential never leaves this page

Everything here runs in your browser. The credential you paste is not uploaded, not stored, not logged and not put in the URL — there is no server side to this page at all. Even so, a real credential out of a wallet is personal data about its holder, so prefer a test credential when you can, and clear the box when you are done.

Paste a credential​

0 of 100,000 charactersDecoded in your browser. Nothing is uploaded, stored or logged.

What this tool checks​

  • Structure. The compact serialisation is <issuer-signed JWT>~<disclosure>~…~<disclosure>~ with an optional key binding JWT after the last tilde. A segment that is not base64url, not JSON, or not a JWS comes back as a named error instead of a blank screen.
  • Digests. Each disclosure is hashed with SHA-256 over the bytes of its encoded form — not over the decoded array — and the resulting base64url digest is looked for in every _sd array in the payload, and in every {"...": "<digest>"} array element.
  • Decoys. A digest in _sd that no disclosure hashes to is listed as decoy or withheld. That is the normal, intended state of a selective-disclosure credential: you cannot tell from the credential alone whether the issuer padded the list or the holder chose not to disclose.
  • Unmatched disclosures. A disclosure whose digest appears nowhere in the payload is listed as unmatched. It is not substituted into the reconstructed payload, which is exactly what a verifier must do: a disclosure that the issuer never signed a digest for proves nothing.
  • Recursive disclosures. A disclosed value may itself be an _sd container, so disclosing address only reveals the parts of the address that were also disclosed. The reconstruction resolves those nested digests in the same pass.
  • The digest algorithm. _sd_alg defaults to sha-256. Any other value stops the tool with an explicit message rather than silently checking the wrong hash.
  • Key binding. If the credential ends in a kb+jwt, its sd_hash is recomputed over the presented prefix — the issuer-signed JWT, every disclosure, and the trailing tilde — and compared. A mismatch means the key binding JWT belongs to a different set of disclosures.

What this tool cannot tell you​

It does not verify any signature, and it is not a trust decision. A credential can decode perfectly and still be forged, revoked or expired.

  • The issuer's signature is not checked. Checking it would mean fetching a key named by the credential itself — an iss URL, a JWKS endpoint, or an x5c chain — and a tool that fetches whatever untrusted input points at is a server-side request forgery primitive. So this page never makes a network request, and the x5c chain, if present, is shown verbatim and nothing more.
  • The holder's key binding signature is not checked either. The sd_hash comparison tells you the key binding JWT was made for this set of disclosures; it says nothing about who made it.
  • The status list is not fetched. The status claim is shown as text so you can see which list and which index it points at, but whether that bit is set is a question for your verifier.
  • nbf and exp are shown with their UTC rendering. Deciding whether the credential is inside its validity window is left to you.

For the real checks, use the Verify a Credential API in the Business Wallet, which validates the signature, the trust chain and the status list.

How selective disclosure works​

The issuer signs a payload that contains digests instead of claims:

{
"iss": "https://wallet.acc.credenco.com/oid4vci/tulip-trust-bank",
"vct": "https://credentials.credenco.com/employee_badge",
"employer": "Tulip Trust Bank",
"_sd_alg": "sha-256",
"_sd": [
"WWvtLNMO3yx2V92RUz9ayofl3aFQymKAEZRBxfn2XEw",
"XgGXwy_mmTppKjeMrI-e5goRyAnEPF7_TnztVa6ryUM"
]
}

employer is always visible. Everything in _sd is not: each entry is the SHA-256 digest of a small JSON array the issuer also handed to the holder, base64url-encoded:

["2GLC42sKQveCfGfryNRN9w", "given_name", "Joris"]

The holder presents the signed JWT plus only the disclosures they want to reveal. The verifier hashes each one, finds the digest in _sd, and so learns given_name is Joris — under the issuer's original signature, without the issuer having to re-sign anything, and without the verifier learning anything about the digests left over.

Array elements work the same way, except the placeholder sits inside the array as {"...": "<digest>"}, and an element whose disclosure is absent is simply not there.

For the wider picture, see OpenID4VP presentation explained and DCQL explained.

Frequently asked questions​

Does anything I paste get sent to Credenco?​

No. The page makes no network request while decoding, there is no backend for it, and nothing is written to local storage, cookies or the URL. Reloading the page loses whatever you pasted, by design.

Why does the tool say "decoy or withheld" instead of telling me which?​

Because the credential cannot tell them apart, and neither can a verifier — that is the point of the design. An issuer may add digests for claims it never issued, so that the number of entries in _sd does not reveal how many claims the holder is hiding.

Why will it not verify the signature?​

Verifying means resolving the issuer's public key from a location the credential itself names. A browser tool that fetches arbitrary URLs out of untrusted input is a security hole, so this page decodes only. Use the Business Wallet's verification API, or your own verifier library, for the cryptographic checks.

It says a disclosure is unmatched. What does that mean?​

The disclosure decoded fine, but its digest appears in no _sd array and no array placeholder in the payload. Usually the credential was edited after it was issued, disclosures were pasted in from another credential, or a byte went missing in a copy/paste. A verifier must ignore such a disclosure, and so does the reconstruction here.

Can it read mdoc or ISO mDL credentials?​

No — but the mdoc debugger can. This tool only reads SD-JWT VC in its compact, tilde-separated serialisation; mdoc credentials are CBOR, which is a different format entirely.