EUDIW / ARF reference tutorial¶
Configure EUDIW / ARF reference helpers using the SD-JWT .NET ecosystem.
Duration: 25 minutes
Difficulty: Advanced
Prerequisites:
- Completed HAIP Profile Validation
- Understanding of EU eIDAS 2.0 regulation
Overview¶
EUDIW-style ecosystems under eIDAS 2.0 / ARF concepts use:
- Architecture Reference Framework (ARF) algorithms
- EU Trust List (LOTL) integration
- Person Identification Data (PID) handling
- HAIP Final flow/profile validation for OpenID4VC flows
Simple explanation¶
The EU Digital Identity Wallet ecosystem under eIDAS 2.0 requires specific credential formats, trust infrastructure, and protocol flows. This tutorial shows how SdJwt.Net.Eudiw provides ARF-aligned reference models for .NET implementations.
Packages used¶
| Package | Purpose |
|---|---|
SdJwt.Net.Eudiw |
EUDIW / ARF reference models |
SdJwt.Net.HAIP |
Profile validation |
SdJwt.Net.Oid4Vci |
Issuance protocol |
SdJwt.Net.Oid4Vp |
Presentation protocol |
Where this fits¶
flowchart LR
A["HAIP\nProfile"] --> B["EUDIW / ARF\nReference"]
B --> C["PID Issuance"]
B --> D["Cross-border\nPresentation"]
style B fill:#2a6478,color:#fff
This tutorial builds an EUDIW-style reference wallet configuration step by step.
Step 1: Install packages¶
dotnet add package SdJwt.Net.Eudiw
dotnet add package SdJwt.Net.Wallet
dotnet add package SdJwt.Net.HAIP
Step 2: Create the EudiWallet¶
using SdJwt.Net.Eudiw;
using SdJwt.Net.Wallet.Storage;
// Set up storage and key management
var store = new InMemoryCredentialStore();
var keyManager = new SoftwareKeyManager();
// Configure EUDIW options
var options = new EudiWalletOptions
{
WalletId = "demo-eudi-wallet",
DisplayName = "My EU Wallet",
EnforceArfCompliance = true,
MinimumHaipLevel = 2, // Legacy local policy setting
ValidateIssuerTrust = true,
TrustListCacheHours = 6,
SupportedCredentialTypes = new[]
{
EudiwConstants.Pid.DocType, // PID
EudiwConstants.Mdl.DocType // mDL
}
};
var wallet = new EudiWallet(store, keyManager, eudiOptions: options);
Console.WriteLine($"Wallet ID: {wallet.Options.WalletId}");
Console.WriteLine($"ARF Enforced: {wallet.IsArfEnforced}");
Console.WriteLine($"Legacy HAIP policy level: {wallet.MinimumHaipLevel}");
Deprecation note:
MinimumHaipLevelis a legacy compatibility API. Use HAIP Final flow/profile validation viaSdJwt.Net.HAIPfor new implementations.
Expected Output:
Wallet ID: demo-eudi-wallet
ARF Enforced: True
Legacy HAIP policy level: 2
Step 3: Validate ARF algorithms¶
EUDIW only allows ECDSA algorithms per the Architecture Reference Framework:
// Test algorithm compliance
var algorithms = new[]
{
("ES256", true), // Allowed
("ES384", true), // Allowed
("ES512", true), // Allowed
("RS256", false), // Not in ARF
("PS256", false), // Not in ARF
("HS256", false) // Symmetric not allowed
};
foreach (var (alg, expected) in algorithms)
{
var valid = wallet.ValidateAlgorithm(alg);
var status = valid == expected ? "OK" : "FAIL";
Console.WriteLine($"[{status}] {alg}: {valid}");
}
Expected Output:
[OK] ES256: True
[OK] ES384: True
[OK] ES512: True
[OK] RS256: False
[OK] PS256: False
[OK] HS256: False
Step 4: Validate EU member states¶
Only credentials from EU member states are accepted:
// EU member states
var euCountries = new[] { "DE", "FR", "ES", "IT", "PL", "NL", "BE", "AT" };
foreach (var country in euCountries)
{
var valid = wallet.ValidateMemberState(country);
Console.WriteLine($"{country}: {valid}");
}
Console.WriteLine();
// Non-EU countries (rejected)
var nonEuCountries = new[] { "US", "GB", "CH", "NO", "AU" };
foreach (var country in nonEuCountries)
{
var valid = wallet.ValidateMemberState(country);
Console.WriteLine($"{country}: {valid} (non-EU)");
}
// Get all supported member states
var allStates = wallet.GetSupportedMemberStates();
Console.WriteLine($"\nTotal EU member states: {allStates.Count}");
Expected Output:
DE: True
FR: True
ES: True
IT: True
PL: True
NL: True
BE: True
AT: True
US: False (non-EU)
GB: False (non-EU)
CH: False (non-EU)
NO: False (non-EU)
AU: False (non-EU)
Total EU member states: 27
Step 5: Validate credential types¶
Only PID, mDL, and qualified attestation types are allowed:
// Test credential types
var credTypes = new[]
{
EudiwConstants.Pid.DocType, // PID
EudiwConstants.Mdl.DocType, // mDL
"eu.europa.ec.eudi.health.1", // Qualified attestation
"custom.unknown.credential" // Unknown
};
foreach (var docType in credTypes)
{
var result = wallet.ValidateCredentialType(docType);
Console.WriteLine($"{docType}:");
Console.WriteLine($" Valid: {result.IsValid}");
Console.WriteLine($" Type: {result.CredentialType}");
}
Expected Output:
eu.europa.ec.eudi.pid.1:
Valid: True
Type: Pid
org.iso.18013.5.1.mDL:
Valid: True
Type: Mdl
eu.europa.ec.eudi.health.1:
Valid: True
Type: Qeaa
custom.unknown.credential:
Valid: False
Type: Unknown
Step 6: Validate PID claims¶
Person Identification Data must contain mandatory claims:
// Valid PID with all mandatory claims
var validPidClaims = new Dictionary<string, object>
{
["family_name"] = "Mueller",
["given_name"] = "Anna",
["birth_date"] = "1985-03-20",
["issuance_date"] = "2025-01-01",
["expiry_date"] = "2030-01-01",
["issuing_authority"] = "Bundesdruckerei",
["issuing_country"] = "DE"
};
var validResult = wallet.ValidatePidClaims(validPidClaims);
Console.WriteLine($"Valid PID: {validResult.IsValid}");
Console.WriteLine($"Missing claims: {validResult.MissingClaims.Count}");
// Invalid PID (missing mandatory claims)
var invalidPidClaims = new Dictionary<string, object>
{
["family_name"] = "Mueller",
["given_name"] = "Anna"
// Missing: birth_date, issuance_date, expiry_date, etc.
};
var invalidResult = wallet.ValidatePidClaims(invalidPidClaims);
Console.WriteLine($"\nIncomplete PID: {invalidResult.IsValid}");
Console.WriteLine($"Missing: {string.Join(", ", invalidResult.MissingClaims)}");
Expected Output:
Valid PID: True
Missing claims: 0
Incomplete PID: False
Missing: birth_date, issuance_date, expiry_date, issuing_authority, issuing_country
Step 7: Extract PID credential¶
Convert claims to a typed PID credential:
if (validResult.IsValid)
{
var pid = wallet.ExtractPidCredential(validPidClaims);
Console.WriteLine("Extracted PID:");
Console.WriteLine($" Name: {pid.GivenName} {pid.FamilyName}");
Console.WriteLine($" Birth Date: {pid.BirthDate}");
Console.WriteLine($" Issuer: {pid.IssuingAuthority}");
Console.WriteLine($" Country: {pid.IssuingCountry}");
Console.WriteLine($" Valid Until: {pid.ExpiryDate}");
}
Expected Output:
Extracted PID:
Name: Anna Mueller
Birth Date: 1985-03-20
Issuer: Bundesdruckerei
Country: DE
Valid Until: 2030-01-01
Step 8: Validate issuer trust¶
Validate that credential issuers are in EU Trust Lists:
// German PID provider (in EU Trust Lists)
var trustedResult = await wallet.ValidateIssuerTrustAsync(
"https://pid-provider.bundesdruckerei.de");
Console.WriteLine("Trusted Issuer:");
Console.WriteLine($" Trusted: {trustedResult.IsTrusted}");
Console.WriteLine($" Member State: {trustedResult.MemberState}");
Console.WriteLine($" Service Type: {trustedResult.ServiceType}");
// Non-EU issuer (not trusted)
var untrustedResult = await wallet.ValidateIssuerTrustAsync(
"https://issuer.example.com");
Console.WriteLine("\nUnknown Issuer:");
Console.WriteLine($" Trusted: {untrustedResult.IsTrusted}");
if (!untrustedResult.IsTrusted)
{
Console.WriteLine($" Errors: {string.Join(", ", untrustedResult.Errors)}");
}
Expected Output:
Trusted Issuer:
Trusted: True
Member State: DE
Service Type: QualifiedAttestation
Unknown Issuer:
Trusted: False
Errors: Issuer not found in EU Trust Lists
Step 9: Store with ARF enforcement¶
Credentials are validated against ARF requirements during storage:
try
{
// Store credential (validation happens automatically)
var stored = await wallet.StoreCredentialAsync(parsedCredential);
Console.WriteLine($"Stored successfully: {stored.Id}");
}
catch (ArfComplianceException ex)
{
Console.WriteLine("ARF Compliance Failure:");
foreach (var violation in ex.Violations)
{
Console.WriteLine($" - {violation}");
}
}
catch (EudiTrustException ex)
{
Console.WriteLine($"Trust Failure: {ex.Message}");
}
Step 10: Create presentations¶
Create presentations with ARF-enforced credentials:
try
{
var presentation = await wallet.CreatePresentationAsync(
credentialId: storedCredential.Id,
disclosurePaths: new[] { "family_name", "birth_date", "age_over_18" },
audience: "https://verifier.example.eu",
nonce: "unique-nonce-123"
);
Console.WriteLine($"Presentation created: {presentation.Length} bytes");
}
catch (ArfComplianceException ex)
{
Console.WriteLine($"Cannot present: {string.Join(", ", ex.Violations)}");
}
Complete example¶
using SdJwt.Net.Eudiw;
using SdJwt.Net.Wallet.Storage;
public class EudiwComplianceDemo
{
public static async Task RunAsync()
{
// 1. Create wallet
var store = new InMemoryCredentialStore();
var keyManager = new SoftwareKeyManager();
var options = new EudiWalletOptions
{
WalletId = "demo-wallet",
EnforceArfCompliance = true,
MinimumHaipLevel = 2, // Legacy local policy setting
ValidateIssuerTrust = true
};
> **Deprecation note:** `MinimumHaipLevel` is a legacy compatibility API. Use HAIP Final flow/profile validation via `SdJwt.Net.HAIP` for new implementations.
var wallet = new EudiWallet(store, keyManager, eudiOptions: options);
// 2. Validate algorithm
var isEs256Valid = wallet.ValidateAlgorithm("ES256");
Console.WriteLine($"ES256 valid: {isEs256Valid}");
// 3. Validate member state
var isDeValid = wallet.ValidateMemberState("DE");
Console.WriteLine($"Germany valid: {isDeValid}");
// 4. Validate PID claims
var pidClaims = new Dictionary<string, object>
{
["family_name"] = "Mueller",
["given_name"] = "Anna",
["birth_date"] = "1985-03-20",
["issuance_date"] = "2025-01-01",
["expiry_date"] = "2030-01-01",
["issuing_authority"] = "Bundesdruckerei",
["issuing_country"] = "DE"
};
var pidResult = wallet.ValidatePidClaims(pidClaims);
Console.WriteLine($"PID valid: {pidResult.IsValid}");
// 5. Extract typed credential
if (pidResult.IsValid)
{
var pid = wallet.ExtractPidCredential(pidClaims);
Console.WriteLine($"Citizen: {pid.GivenName} {pid.FamilyName}");
}
// 6. Validate issuer trust
var trustResult = await wallet.ValidateIssuerTrustAsync(
"https://pid-provider.bundesdruckerei.de");
Console.WriteLine($"Issuer trusted: {trustResult.IsTrusted}");
Console.WriteLine("\nEUDIW / ARF reference demo complete!");
}
}
Expected output¶
EUDIW configuration loaded
PID credential format: dc+sd-jwt
Trust list resolver configured
ARF validation: profile requirements met
Cross-border presentation: success
Demo vs production¶
This package provides reference models for experimentation. Production EUDIW deployments require certified wallets, accredited issuers, and member state trust infrastructure that is outside the scope of this library.
Common mistakes¶
- Treating reference models as certified EUDIW components (they are implementation building blocks, not certified products)
- Using legacy MinimumHaipLevel validation (use HAIP Final flow/profile validation instead)
Key takeaways¶
- The reference ARF-oriented policy allows ES256/ES384/ES512
- Only 27 EU member states are accepted
- PID requires seven mandatory claims
- Issuers must be in EU Trust Lists
- ARF validation runs at storage time
- ARF-enforced presentations protect holders
Next steps¶
- Multi-Credential Flow - Combine PID with other attestations
- EUDIW Cross-Border Verification - Real-world verification scenarios
- EUDIW - Complete architecture reference