
Local SD-JWT and OpenID4VP verification for EUDI Wallet
There are already decent SD-JWT decoders. Paste a token, get a JSON tree. That solves half the problem. When I started wiring EU Digital Identity presentations for EUDI × Solana, the questions I actua
There are already decent SD-JWT decoders. Paste a token, get a JSON tree. That solves half the problem.
When I started wiring EU Digital Identity presentations for EUDI × Solana, the questions I actually had were different:
Would this presentation verify? Decoding is the easy part. I cared about signatures, disclosure binding, validity window, audience, and nonce.
Is the request asking for more than it needs? Exact birth date when an age-over-18 assertion would do. A portrait in a remote flow. The whole credential instead of named claims.
I could not find a paste-in tool that answered both, locally. So I built one.
EUDI Inspector is open source (Apache-2.0), a static SPA. Nothing you paste leaves the browser.
What it does
Paste an SD-JWT VC (with or without a Key-Binding JWT) or an OpenID4VP presentation request. It:
Decodes the artifact and reconstructs the resolved claim set: every disclosure woven back into the issuer payload,
_sdmachinery stripped.Verifies over WebCrypto: issuer and holder signatures, the disclosure seal (
sd_hash), the validity window, audience/nonce binding, and the disclosure↔issuer binding (each disclosure's digest must appear in the issuer-signed_sd). That last check catches a claim injected after issuance. The issuer signature alone does not cover the disclosures.Flags over-asking in a request against an editable, advisory rule set. Each hit comes with a rationale. Never a verdict.
Checks the issuer against a small trust snapshot or anchors you paste. Labeled informational, not authoritative.
A check is pass, fail, or skip. Skip when there is no input to decide; never a silent fail. Leave the issuer key blank and the signature check skips. Paste it and it runs.
Why "local" is the point
Presentation artifacts are PII. Shipping them to a hosted debugger is an easy habit and a bad one.
The inspector is a static site with a strict CSP (connect-src 'self'). No backend, no telemetry, no network calls with your data. Key resolution is paste-only on purpose: if the tool cannot fetch metadata or follow a request_uri, it cannot leak your token either. Fetch the request yourself, paste the result.
That also keeps the threat model simple. You can self-host it. You can read the ADRs for the design trade-offs.
What is different from a decoder
Decoders are crowded. Paradym, sdjwt.co, a handful of SPAs. They stop at structure.
The EU reference verifier and full OIDF stacks do real verification, but they are Docker services and conformance harnesses, not a URL you open with a paste box.
What I wanted was all of this in one client-side tool:
| Capability | Typical decoder | Heavyweight verifier | EUDI Inspector |
|---|---|---|---|
| Decode SD-JWT | yes | yes | yes |
| Local sig + binding checks | no / partial | yes (server) | yes (browser) |
| OpenID4VP request inspect | rare | yes | yes |
| Over-asking flags | no | no | yes |
| Zero backend | often | no | yes |
Over-asking is subjective, so the rules live as editable JSON with a rationale per rule. You can disagree with a rule; you can change it. The tool does not pretend to be a compliance oracle.
Honest gaps
No mdoc/mDL yet. The EUDI PID also ships as ISO 18013-5 (CBOR/COSE). That is the biggest usefulness cap and the next feature, not a nice-to-have.
Trust is thin in v1: informational membership against pasted JWKS and one reference issuer. It will never issue a legal standing verdict. That is intentional.
IETF spec vectors are still on the backlog. The good/bad fixture matrix plus an independent ES256 vector already gate the checks.
Try it
Load good-presentation for a clean pass, then bad-forged-disclosure to see why the binding check exists. If you are building the relying-party side of an EUDI pilot, I want the feedback, especially on the over-asking rules and on real request shapes from the wild.