
Introduction
You don’t need to reveal your last name to prove you’re over 18. You don’t even need to reveal your date of birth.
The traditional JSON Web Token (JWT) always reveals everything in plain sight. After it is signed, there is no way to choose which part of the token is shared and which is not. You either share the whole token or none of it.
Imagine the following scenario. Let’s say there is an official institution in your country that issues Personal Identification Documents in digital form. They could do it in the form of a JWT, where the payload would contain your personal data, e.g., first and last name, date of birth, address, and so on. You can share that data in digital form with anyone you’d like. Given that the institution’s public key is well known, anyone could verify that your credential is indeed issued by that very same institution.
Now, let’s say you want to go to a local nightclub on a Saturday night. The security guard at the entrance asks you for your ID to check that you are older than 18. Your options here are very limited. You can either show them your full ID, thus revealing all your most private information, or turn around and go home, while your privacy-unaware friends are having the night of their lives inside.
Can we do better than that? Is there a way not to reveal everything just to prove some small part? Turns out we can, and there is. And part of the answer is the Selective Disclosure for JWTs (SD-JWT).
SD-JWT is not just a theoretical improvement or a niche curiosity — it is rapidly becoming a foundational building block in real-world digital identity systems. In particular, it is one of the key technologies adopted in the European Digital Identity (EUDI) ecosystem, where citizens will use EUDI Wallets to securely store and present digital credentials. Selective disclosure plays a central role there, allowing users to share only the information needed for a given interaction, and nothing more.
In other words, the problem is not that digital credentials don’t work — it’s that they reveal far more than necessary. Selective disclosure lets us keep the authority and verifiability of signed digital credentials while restoring privacy and control to the person presenting them.
A Short Primer
First, let’s remind ourselves of the big picture, within which we will be discussing selective disclosure in verifiable credentials. Three main entities are differentiated — Issuer, Holder, and Verifier.
The Issuer is the entity that issues credentials. For example, it can be the government for issuing personal identification documents or driving licenses, a local gym for issuing gym cards, or anything in between.
The Holder is an entity to whom credentials are issued. It can be you, me, or anyone else who has some kind of a credential.
The Verifier is an entity that the credentials are presented to. Verifier can be, for example, a police officer asking for a driving license, or a security guard asking for proof of age at the nightclub entrance. Among other things, the Verifier will check that the presented credential is issued by a specific Issuer it trusts.
Simple Selective Disclosure
Before diving into the actual SD-JWT format, let’s first look at how selective disclosure works as a simple cryptographic primitive. We’ll then build on that to construct an SD-JWT.
Suppose the Issuer wants to issue a credential with claims to the Holder, i.e., . In the most basic form, they can do so by composing the message (where denotes concatenation), computing the signature over the claims, and transferring and to the Holder.
Now suppose the Verifier asks the holder to present a credential with certain claims. The goal is that during the presentation, the Holder should be able to choose which specific claims from a credential they want to disclose to the Verifier, while still being able to prove who the Issuer was.
The naive way would be to just send a list of only the claims the Verifier requested, taken from the credential. However, for the Verifier to be able to actually verify the validity of the credential, it must also somehow convince itself of the claims’ integrity. Normally, this would be done by verifying the signature , and this signature would also have to be disclosed to the Verifier by the Holder; however, the signature the Issuer produced was over all of the claims (), not only those that we would disclose 1.
As such, the key issue solved by the approach we will describe is:
Find a way to verify a signature made over all claims, without having to disclose all of the claims themselves.
Now, here is where the magic happens. In order to achieve selective disclosure, the Issuer will hide all the claims using a one-way function, e.g., a hash function. They will compute the digest for each claim . The message will now be computed as , meaning that the signature will be computed over all of the digests, instead of claims. In this case, the Holder will receive:
- the original claims ();
- the message ;
- the signature () from the Issuer.
During presentation, the Holder sends to the Verifier the message (the concatenation of all digests), the Issuer’s signature, and the subset of claims they want to disclose. The Verifier can now verify the Issuer’s signature, using as the payload for signature verification. They will also recompute the digests for the disclosed claims, and check that those digests exist in signed by the Issuer. By knowing the digests of undisclosed claims, Verifier does not know anything about the actual claim values. And that’s the essence of selective disclosure; the Issuer signs hashes of all claims, while the Holder reveals only the ones they choose. Simple at its core, right?
There is, however, one more subtle but important detail. If the Issuer simply hashes each claim value as-is, an attacker could still guess undisclosed claims by hashing all likely values and comparing them to the revealed digests. This is especially problematic for claims with small or predictable value spaces, such as gender, country codes, or age ranges. To prevent such dictionary attacks, each digest should be computed not just over the raw claim, but over the claim combined with a randomly generated value. This random value is commonly called a salt. The Issuer will generate a salt value for each claim , and compute each digest as . By adding a unique, high-entropy salt to every claim before hashing it, the Issuer ensures that even identical claims produce completely different digests, making it computationally infeasible for a Verifier to guess any undisclosed information. This enhancement preserves the simplicity of the scheme while significantly strengthening its privacy guarantees. Each claim has its own unique salt value. When the Holder discloses a claim, they send both the claim value and its salt so the Verifier can recompute the digest.
We now have a basic selective disclosure credential: one where the Issuer signs hidden values, the Holder reveals only what they choose, and the Verifier can still check authenticity. The actual SD-JWT specification builds on this general idea, but packages it into a structured, interoperable format that works with the existing JWT ecosystem. Now that we understand the cryptographic building blocks, we can look at how they come together in the SD-JWT format.
SD-JWT Format, SD-JWT VC
Now that we have built a simple selective disclosure credential from first principles, we can finally look at how the Selective Disclosure for JSON Web Tokens (SD-JWT) standard turns this idea into a practical, interoperable format. SD-JWT builds on top of the existing JWT ecosystem, while adding a structured way to reveal claims selectively, prove their integrity, and prevent replay attacks.
SD-JWT VC is a draft standard building on top of the SD-JWT format, which further elaborates it for use as a Verifiable Credential format.
At its core, SD-JWT takes the same approach we described earlier and applies it to a JSON document, resulting in:
- a signed JWT issued by the Issuer, containing a mix of digests and raw claims (depending on selective disclosability of each).
- Disclosures — Holder-controlled items containing
(salt, name, value)triplets for individual selectively disclosable JSON nodes, e.g.["N7gF93", "name", "John"]. As JSON is a tree-like format, these can even be recursive.
Additionally, to address certain Holder authenticity issues during presentation, the Holder can produce a (optional) Holder-signed Key Binding JWT, proving possession of the corresponding private key, and integrity protecting the disclosed set of claims; more on that later.
During presentation, the Holder sends the SD-JWT plus only the disclosures they decide to reveal. The Verifier re-hashes each disclosed claim, checks that its digest appears inside the SD-JWT, verifies the Issuer’s signature, and optionally checks Holder Binding. If all these checks pass, the Verifier can trust the disclosed claims — while learning absolutely nothing about the undisclosed ones.
In the following examples, we will also show how to issue, hold, and verify SD-JWT VCs using our Rust crate bh-sd-jwt. The code snippets are intentionally short and focused, serving as practical illustrations of how the concepts from this section translate into real-world usage.
Let’s look at how SD-JWT structures these components. The following credential will be used to build a proper SD-JWT.
{
"name": "John",
"address": {
"street": "Baker Street",
"number": 1
},
"nationalities": ["DE", "EN"]
}The Issuer can choose which claims will be selectively disclosable, and which will always be visible. To illustrate each possible case for hiding claims, they will hide the following:
nameclaim,numberclaim fromaddress(address.number),- the whole
addressclaim, - and the first element of
nationalitiesclaim (nationalities[0]).
For each of those claims, the Issuer must:
- generate random salt value,
- compute a digest, i.e., , in the simplified form,
- replace the actual claim with the corresponding digest.
Disclosures
There are two types of disclosures — the ones created from claims within a JSON object, and the ones created from claims within JSON array. Let’s first start with the ones from a JSON object.
JSON Object Properties
The disclosures for claims from a JSON object are always composed as a base64url-encoded JSON array with exactly three elements in the following order:
- A salt value — string value with sufficient entropy that must be unique for each claim, e.g.,
base64url-encoded 128 bits of cryptographically secure random data. - The claim name — the name under which the claim exists in the original JSON object. It is always a string value.
- The claim value — the exact value from the original JSON object. It can be of any JSON-supported type.
For example, the disclosure for the name claim of the above credential then becomes the following (where "VhSUjGbX" is the salt value):
["VhSUjGbX", "name", "John"]The base64url-encoded value then becomes "WyJWaFNVakdiWCIsICJuYW1lIiwgIkpvaG4iXQ".
JSON Array Properties
The disclosures for claims from a JSON array are always composed as a base64url-encoded JSON array with exactly two elements in the following order:
- A salt value — string value with sufficient entropy that must be unique for each claim, e.g.,
base64url-encoded 128 bits of cryptographically secure random data. - The array element value — the exact value from the original JSON array. It can be of any JSON-supported type.
For example, the disclosure for the "DE" element of "nationalities" claim in the above credential then becomes the following (where "GsgRTt4g" is the salt value):
["GsgRTt4g", "DE"]The base64url-encoded value then becomes "WyJHc2dSVHQ0ZyIsICJERSJd".
Hide Claims With Digests
Let’s now hide all the aforementioned claims by replacing them with the base64url-encoded digests of their respective disclosures. The digests of the JSON object disclosures must be hidden under the "_sd" claim within the parent JSON object. The "_sd_alg" claim will contain the digest algorithm used to hide the claims, and if present, it must be at the top level. If it is not present, the default value of "sha-256" must be used.
For the name claim, we already computed the base64url-encoded disclosure, and, using the SHA-256 algorithm, its base64url-encoded digest becomes "LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ". The credential will now look as follows:
{
"_sd_alg": "sha-256",
"_sd": [
"LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ"
],
"address": {
"street": "Baker Street",
"number": 1
},
"nationalities": ["DE", "EN"]
}The address.number claim will have the following disclosure:
["mNirk7sN", "number", 1]After encoding it becomes "WyJtTmlyazdzTiIsICJudW1iZXIiLCAxXQ", and the digest is "WGNBC57Lcm91MNDNJeKHpI5-2r03VOp1lXoSW9buCLY". The credential now looks like the following:
{
"_sd_alg": "sha-256",
"_sd": [
"LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ"
],
"address": {
"_sd": [
"WGNBC57Lcm91MNDNJeKHpI5-2r03VOp1lXoSW9buCLY"
],
"street": "Baker Street"
},
"nationalities": ["DE", "EN"]
}The address claim will have the following disclosure:
[
"g6UrxQ51",
"address",
{
"_sd": [
"WGNBC57Lcm91MNDNJeKHpI5-2r03VOp1lXoSW9buCLY"
],
"street": "Baker Street"
}
]The value here is the whole address object. Note that its _sd claim is included as well. This also means that claims must be hidden such that the lower-level claims are hidden first. Also, the street claim is visible in plain text. Not all claims need to be hidden. Here, whenever the address is disclosed, the street will be disclosed as well. Furthermore, in order to disclose the number, address also needs to be disclosed. The encoding of the disclosure becomes "Ww0KICAi...AgfQ0KXQ" (shortened because it is long), and its digest is "dokKcznvqnNmZYN0gW79ugtZbppAfGxqkU3inalzl-c". The credential now looks like this:
{
"_sd_alg": "sha-256",
"_sd": [
"LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ",
"dokKcznvqnNmZYN0gW79ugtZbppAfGxqkU3inalzl-c"
],
"nationalities": ["DE", "EN"]
}To hide the element of a JSON array, it is replaced with the JSON object containing only the key "...", whose value is the digest of the respective claim. Arrays have no stable, semantic property names, so the digest cannot be stored under "_sd" like regular object properties. Instead, the "..." wrapper keeps the digest in place of the original element, while preserving the array structure and index positions. We already computed the base64url-encoded disclosure of the "DE" "nationalities" element, and its digest is "pmzRofJy40lmfjC5EIrUGRf4xSDyhh8RcffHCXhAp6k". The final credential now looks like:
{
"_sd_alg": "sha-256",
"_sd": [
"LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ",
"dokKcznvqnNmZYN0gW79ugtZbppAfGxqkU3inalzl-c"
],
"nationalities": [
{
"...": "pmzRofJy40lmfjC5EIrUGRf4xSDyhh8RcffHCXhAp6k"
},
"EN"
]
}Key Binding
Selective disclosure improves privacy, but does not prevent identity theft. If the Verifier receives an SD-JWT and a set of disclosures from the Holder, nothing (so far) prevents that Verifier from simply turning around and presenting those same values to someone else. The Verifier would be able to impersonate the Holder, because without an additional safeguard, the SD-JWT contains no proof that the presenter is the legitimate Holder.
To prevent this kind of replay attack, the notion of Key Binding (also called Holder Binding) is introduced. Key Binding ensures that:
Only the Holder, who is in possession of a specific private key can successfully present the SD-JWT.
Now the Holder also needs to have a key pair usable for signing. First, they will need to prove to the Issuer (using some out-of-band mechanism) that they possess the key, by producing, for example, a signature by the private key over the Issuer-provided challenge.
The Issuer then embeds a reference to the Holder’s public key inside the SD-JWT, in a dedicated JWT claim named "cnf" (confirmation), as defined in RFC 7800. A typical structure in case of an EC key looks like this:
{
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
}The Holder will later need to sign their presentation (which will also have to include a challenge received from the Verifier, to ensure freshness) using the corresponding private key. The Verifier will verify it using the public key in the "cnf" claim. Because only the Holder can produce such a signature, a Verifier cannot reuse the credential elsewhere, even if they learned all the disclosed claims and digests. Key Binding therefore ensures that credentials are non-transferable.
Key Binding JWT (KB-JWT)
During presentation, the Holder must also send a Key Binding JWT (KB-JWT). This is another small JWT that the Holder signs using their own private key. Its job is to prove two things:
- the presenter controls the private key bound to the credential, and
- this presentation is fresh, not replayed from someone else.
Without going into further details, the KB-JWT must contain the following elements:
- in the JWT header:
typ— a string that must be"kb+jwt",alg— a signature algorithm,
- in the JWT payload:
iat— the time of issuance,aud— the intended receiver of the KB-JWT,nonce— the Verifier-generated random string that ensures freshness,sd_hash— thebase64url-encoded hash value over the Issuer-signed JWT and the selected disclosures.
Any Verifier caring about authenticating the owner of the credential (which would be the vast majority of them) will want to also verify this KB-JWT.
Putting It All Together
At this point, we have assembled all the pieces of an SD-JWT: hidden claims encoded as digests, visible claims left intact, disclosures holding the actual values, and a Holder-bound public key. What remains is to understand how everything is serialized, what the Issuer hands to the Holder, and what the Holder later presents to a Verifier.
Issuance
During issuance, the Issuer sends to the Holder the JWT whose payload with disclosure digests we created earlier, and all of the disclosures. The JWT payload will optionally contain a cnf claim if Holder Binding is used. From our example, the payload will be:
{
"_sd_alg": "sha-256",
"_sd": [
"LirivX7-bOlclPXAabMyghQ_7qBdILcg0lm7YfZFpoQ",
"dokKcznvqnNmZYN0gW79ugtZbppAfGxqkU3inalzl-c"
],
"nationalities": [
{
"...": "pmzRofJy40lmfjC5EIrUGRf4xSDyhh8RcffHCXhAp6k"
},
"EN"
],
"cnf": {
"jwk": {
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
}
}After signing the JWT, the Issuer creates the following string, and sends it to the Holder:
<Issuer-signed JWT>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure N>~It contains the Issuer-signed JWT with the above payload followed by a single tilde (~), and the base64url-encoded disclosures separated by the tilde character, with a single tilde character at the end. In our example, it might look something like this (line breaks and ellipses added for readability):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...~
WyJWaFNVakdiWCIsICJuYW1lIiwgIkpvaG4iXQ~
WyJtTmlyazdzTiIsICJudW1iZXIiLCAxXQ~
Ww0KICAiZzZVcnhRNTEiLA0KICAiYWRkcmVz...~
WyJHc2dSVHQ0ZyIsICJERSJd~Here, some additional claims that might be present in the Issuer-signed JWT payload are intentionally left over, for example, some standard JWT claims such as iss, iat, etc., in order to keep it as simple as possible.
The SD-JWT serialization explained here is the compact serialized format. One can also optionally support a JSON Serialization format, that will not be further explained here.
In Rust, using the bh-sd-jwt crate, issuing such a credential roughly looks like this:
// the issued claims
let claims = json_object!({
"name": "John",
"address": {
"street": "Baker Street",
"number": 1
},
"nationalities": ["DE", "EN"]
});
// construct the full payload with additional fields, such as `cnf`
let jwt_payload = IssuerJwt::new(
"identity_card".to_owned(), // the type of the credential
UriBuf::new("https://example.com/issuer".into()).unwrap(), // `iss` claim
holder_pk, // Holder's public key
claims,
)
.unwrap();
// issue the given credential
let issued_sd_jwt = Issuer::new(Sha256)
.issue(
jwt_payload,
// the claims that are set to be selectively disclosable by the issuer
&[
&[Key("name")],
&[Key("address"), Key("number")],
&[Key("address")],
&[Key("nationalities"), Index(0)],
],
issuer_signer, // cryptographic implementation for creating Issuer's signature
&mut rand::thread_rng(),
)
.unwrap()
.into_string_compact();
// accept the issued credential on the Holder side
let holder = Holder::verify_issued(
&issued_sd_jwt,
issuer_pk, // Issuer's public key
|_| Some(Box::new(Sha256)), // implementation for creating hash values
|_| Some(&Es256Verifier), // signature verification implementation
current_time, // current time in seconds since EPOCH
)
.await
.unwrap();Verification
Verification is where everything finally comes together. Once the Holder presents an SD-JWT, the Verifier’s job is to check three things:
- the SD-JWT really came from the Issuer,
- the disclosed claims match the digests inside it, and
- the presenter is the legitimate Holder (if Holder Binding is used).
The presentation the Verifier receives from the Holder looks very similar to what the Holder receives from the Issuer, with a few differences:
- it contains only the disclosures the Holder wanted to disclose (e.g., just the one containing the
name), - it optionally includes the Holder-signed Key Binding JWT.
It looks like this:
<SD-JWT>~<Disclosure 1>~<Disclosure 2>~...~<Disclosure M>~<KB-JWT?>The Holder Binding (KB-JWT) here is not followed by a tilde. That is how the Verifier can tell if there is a Holder Binding or not; if the string ends with a tilde, there is no KB-JWT, otherwise, the last segment is the KB-JWT. Nevertheless, the Verifier must be the one that chooses whether they require the Holder Binding or not; they must not make that decision based on what the Holder sends them.
For example, if only the name is to be disclosed, and Holder Binding is not used, the presentation might look like this (line breaks and ellipses added for readability):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...~
WyJWaFNVakdiWCIsICJuYW1lIiwgIkpvaG4iXQ~At this point, the Verifier will:
- Verify the Issuer signature on the SD-JWT using the Issuer’s public key,
- Decode and validate the disclosures, recomputing their digests and checking that they appear in the appropriate
_sdarray or"..."wrapper, - Optionally validate the KB-JWT against the
cnfclaim,aud,nonce, andsd_hash.
If all of these checks succeed, the Verifier can trust that the disclosed claims are authentic, issued by the expected Issuer, and presented by the legitimate Holder — while learning nothing about the claims that were deliberately left undisclosed.
In Rust, using the bh-sd-jwt crate, verifying such a presentation could look like this:
// the SD-JWT Verifier
let verifier = Verifier::new("target_aud".to_owned(), &mut rand::thread_rng()).unwrap();
// present only `name` from the credential
let sd_jwt_kb = holder
.present(
&[&[Key("name")]],
verifier.key_binding_challenge().to_owned(), // `nonce` and `aud` challenge
current_time, // current time in seconds since EPOCH
holder_signer, // cryptographic implementation for creating Holder's signature
)
.unwrap();
// verify the presented credential
let verified_claims = verifier
.verify(
sd_jwt_kb,
issuer_pk, // Issuer's public key
current_time, // current time in seconds since EPOCH
|_| Some(Box::new(Sha256)), // implementation for creating hash values
|_| Some(&Es256Verifier), // signature verification implementation
)
.await
.unwrap();It Is Not All Ideal
At first glance, one might think this solves the privacy concerns we’ve described in the intro entirely. However, the SD-JWT VC format is unfortunately not without its limitations.
Linkability of Presentations
You might have noticed that, in order to present anything at all, we always need to send the JWT containing the root JSON object, as it comes with the Issuer’s signature and all the hash pointers needed to further reconstruct the payload. Unfortunately, this means that merely comparing the JWT portions of two different SD-JWT(+KB) presentations is sufficient to establish whether they describe the same credential!
This issue is called linkability; namely, colluding Verifiers can “compare notes” to correlate presentations they’ve received to determine that they come from the same person. Furthermore, they can then easily perform a union of seen claims. This means that the nightclub from the intro could compare the presentation you’ve given them with another Verifier’s and potentially learn your given and family name, if you’ve presented those claims to that other Verifier.
Unfortunately, this means that SD-JWT, as well as similar Verifiable Credential formats (such as mDoc)2, permit malicious Verifiers to track users and gradually build up a larger profile, even if none of the presentations made could be correlated by their disclosed contents alone. Solving this issue through cryptographic means requires a substantially different approach to format design. Even then, the issue itself is far broader than just the credential formats or cryptographic schemes used, and needs to be considered across all levels of the technology stack.
As a possible mitigation, the RFC mentions the possibility of batch issuance; instead of issuing just one copy of a credential, Issuers could create multiple inessentially-different copies (i.e. differing in randomness, such as the salt values used), while Holders would have to take care to never present the same version to multiple Verifiers. This comes with obvious downsides; you could “use up” a credential by presenting it too many times, and then you’d have to get “more” of it from an Issuer.
It is also worth mentioning that the issue of (un)linkability in the context of EUDI caused quite a stir within the cryptography expert community and that they’ve submitted feedback criticizing these shortcomings.
The EUDI Architecture and Reference Framework (ARF) does specify that Relying Parties (i.e. Verifiers) would require certification by Access Certificate Authorities, whose CA certificates would themselves need to be included on Trusted Lists. We can only hope that this certification process might somewhat reduce the risk described above by filtering out some bad actors in the initial stages, but of course it cannot eliminate it as there will be too many Verifiers to actively oversee.
Issuer Phone-Home
We haven’t yet discussed how the Issuer’s public key is obtained; the SD-JWT VC draft standard specifies some mechanisms. However, through these the Issuer might be able to glean some information into how the credentials it issued are being used, even without collusion with Verifiers.
The most obvious leaky faucet is the web-based key resolution scheme described in the draft standard, which explicitly contacts the Issuer when verifying the credential. Based on the IP address of the origin of the request, the Issuer obtains a growing list of all the Verifiers its credentials are being presented to; by personalizing the endpoint at which the public key is to be retrieved, it can trace a single credential’s and its owner’s path through the ecosystem.
However, based on the above, as well as insights from the ARF, one might intuit that the other described mechanism, which is based on long-established X.509 PKI, might become the more popular one. At first glance it seems superior to the previous approach in privacy regards, as the public key is already embedded in the certificate chain in the header of the JWT. And yet, Verifiers will still need to check for revocation of these certificates, which is an online protocol! If it happens to be the case that the Issuer is itself a CA on the certificate chain, it can leverage its hosting of CRLs/OCSP in the same way as in the previous approach. 3
Over-Disclosure
As described earlier, not all claims have to be selectively disclosable. This applies to claims both in the root object, and in nested objects/arrays.
Let’s take another look at the previously shown example payload:
{
"name": "John",
"address": {
"street": "Baker Street",
"number": 1
},
"nationalities": ["DE", "EN"]
}The Issuer has control over which (sub-)claims are selectively disclosable. For example, we made the whole address a hidden object, and address.number was hidden too, but not address.street. This means, in order to disclose anything from within address, we need include its corresponding disclosure in the presentation, which will necessarily disclose address.street because it is just a regular claim with no support for selective disclosure.
In this example this might not be a problem due to the meaning of the fields (after all, an address with a number but without a street doesn’t make much sense). But in general this means that the set of claims you might need to disclose to a Verifier could be a superset of what the Verifier is explicitly asking for! You can either accept or decline to present — because the Issuer did not give you wiggle room for some claims.
Generally, this is mitigated by Issuers making every claim (that the specification allows) selectively disclosable, but this is done at their discretion. You could still end up with credentials where you can sometimes be forced into over-disclosure because of the choices the Issuer made. Therefore, the question remains: will your EUDI wallet’s UX notify you of cases like these? If it were to naively filter a fully reconstructed payload by the Verifier’s requested claims to display it to you, it might miss edge cases like these.
Conclusion
SD-JWT VCs are a brand new format of Verifiable Credentials, which might be coming to your EUDI Wallet soon! Hopefully this post has demystified this new technology for you, together with its strengths and weaknesses.
All FortID products already support SD-JWT VC, powered by our open-source Rust library.
Footnotes
There exist alternative cryptographic approaches to selective disclosure where this is not the case, thus sidestepping the issue that results from this. Examples include multi-message/anonymous credential signature schemes such as BBS+ and Camenisch-Lysyanskaya signatures, as well as other, ZKP-based approaches. ↩
In fact, all credential formats based on the core idea from the simple selective disclosure example described earlier are affected by this. ↩
This will obviously be a limitation of all credential formats depending on X.509 PKI for authenticity. ↩

