Tutorial: Selective disclosure¶
Learn to hide and reveal claims when presenting credentials.
Time: 10 minutes
Level: Beginner
Sample: samples/SdJwt.Net.Samples/01-Beginner/02-SelectiveDisclosure.cs
What you will learn¶
- How the holder controls which claims to reveal
- How to create presentations with selected disclosures
- Privacy-preserving credential sharing
Simple explanation¶
This tutorial shows how a holder can reveal only some facts from a credential. Think of sealed envelopes: the issuer puts each claim in its own envelope. The holder chooses which envelopes to open.
Key insight: Which claims can be selectively disclosed is decided at issuance time, not presentation time. The issuer must plan which claims are disclosable when creating the SD-JWT.
Packages used¶
| Package | Purpose |
|---|---|
SdJwt.Net |
Core SD-JWT selective disclosure |
Where this fits¶
flowchart LR
A["Issue SD-JWT"] --> B["Choose Disclosures"]
B --> C["Build Presentation"]
C --> D["Verify"]
style B fill:#2a6478,color:#fff
style C fill:#2a6478,color:#fff
The three actors¶
- Issuer - Creates and signs the SD-JWT
- Holder - Receives the credential and controls disclosure
- Verifier - Requests and validates specific claims
Step 1: Issue a credential (issuer)¶
var claims = new JwtPayload
{
["iss"] = "https://employer.example",
["sub"] = "employee-123",
["given_name"] = "Bob",
["family_name"] = "Johnson",
["employee_id"] = "EMP-456",
["department"] = "Engineering",
["salary"] = 95000,
["start_date"] = "2020-03-15"
};
var options = new SdIssuanceOptions
{
DisclosureStructure = new
{
given_name = true,
family_name = true,
employee_id = true,
department = true,
salary = true, // Sensitive!
start_date = true
}
};
var result = issuer.Issue(claims, options);
Step 2: Store as holder¶
The holder receives the full issuance string containing all disclosures:
using SdJwt.Net.Holder;
var holder = new SdJwtHolder(result.Issuance);
Step 3: Create selective presentation¶
When presenting to a verifier, the holder chooses what to reveal:
// Scenario: Proving employment without revealing salary
var presentation = holder.CreatePresentation(
disclosure =>
disclosure.ClaimName == "given_name" ||
disclosure.ClaimName == "family_name" ||
disclosure.ClaimName == "department"
// salary is NOT included
);
What happens¶
| Claim | In Presentation |
|---|---|
| given_name | Revealed |
| family_name | Revealed |
| department | Revealed |
| employee_id | Hidden |
| salary | Hidden |
| start_date | Hidden |
The verifier can prove Bob works in Engineering, but cannot see his salary.
Step 4: Verify selectively disclosed claims¶
using SdJwt.Net.Verifier;
var verifier = new SdVerifier(_ => Task.FromResult<SecurityKey>(issuerPublicKey));
var result = await verifier.VerifyAsync(presentation, validationParams);
// Only disclosed claims are visible
foreach (var claim in result.ClaimsPrincipal.Claims)
{
Console.WriteLine($"{claim.Type}: {claim.Value}");
}
// Output:
// given_name: Bob
// family_name: Johnson
// department: Engineering
// (salary is NOT visible)
Privacy patterns¶
Minimum disclosure¶
Only reveal what is strictly necessary:
// Age verification: prove over 21 without showing birthdate
var presentation = holder.CreatePresentation(
d => d.ClaimName == "age_over_21"
);
Graduated disclosure¶
Different contexts require different levels:
// For job application: name and department
var jobPresentation = holder.CreatePresentation(
d => d.ClaimName is "given_name" or "family_name" or "department"
);
// For background check: more details
var backgroundPresentation = holder.CreatePresentation(
d => d.ClaimName is "given_name" or "family_name" or "employee_id" or "start_date"
);
Complete example¶
// Issuer creates credential
var issuer = new SdIssuer(issuerKey, SecurityAlgorithms.EcdsaSha256);
var result = issuer.Issue(claims, options);
// Holder stores and selectively presents
var holder = new SdJwtHolder(result.Issuance);
var presentation = holder.CreatePresentation(
d => d.ClaimName == "department" // Only reveal department
);
// Verifier validates
var verifier = new SdVerifier(_ => Task.FromResult<SecurityKey>(issuerKey));
var verified = await verifier.VerifyAsync(presentation, validationParams);
// Department is visible, salary is not
Run the sample¶
cd samples/SdJwt.Net.Samples
dotnet run -- 1.2
Expected output¶
Full presentation: 3 disclosures included
Partial presentation: 1 disclosure included (given_name only)
Hidden claims are not visible to the verifier
Demo vs production¶
Disclosure selection happens at presentation time. In production, your wallet UI should let the user review which claims will be shared before sending.
Common mistakes¶
- Expecting to add new disclosures at presentation time (disclosures are fixed at issuance)
- Confusing claim suppression with encryption (hidden claims are omitted, not encrypted)
Next steps¶
- Holder Binding - Prove you own the credential
- Verification Flow - Complete end-to-end flow
Key takeaways¶
- Holders control what claims to reveal
- Verifiers only see disclosed claims
- Hidden claims remain cryptographically protected
- The same credential supports multiple disclosure scenarios