@learncard/sd-jwt-vc-plugin
v0.1.0
Published
SD-JWT-VC holder + verifier support for LearnCard. Implements RFC 9901 (SD-JWT) and draft-ietf-oauth-sd-jwt-vc.
Keywords
Readme
@learncard/sd-jwt-vc-plugin
SD-JWT-VC holder + verifier support for LearnCard. Implements:
- RFC 9901 — Selective Disclosure for JWTs (SD-JWT)
- draft-ietf-oauth-sd-jwt-vc-16 — SD-JWT-VC profile for Verifiable Credentials
Token Status List (revocation / suspension) is on the roadmap — see Slice 4 in the status table below.
Status
Holder read-path: feature-complete. The plugin parses, reconstructs, and verifies SD-JWT-VCs against any DID-resolvable issuer (did:key, did:web, did:jwk). Presentation (KB-JWT signing) lands in Slice 3; status list lands in Slice 4.
| Capability | Status |
|---|---|
| Parse compact serialization (Issuer JWT + Disclosures + optional KB-JWT) | ✅ Slice 1 |
| Reconstruct fully-disclosed claims | ✅ Slice 1 |
| Verify issuer signature (via DID resolution) + disclosure hashes | ✅ Slice 1 |
| dc+sd-jwt and legacy vc+sd-jwt format strings | ✅ Slice 1 |
| Per-claim selective disclosure preview | ✅ Slice 1 |
| Wallet display view-model (vct → category) | ⏳ Slice 2 |
| Holder presentation + KB-JWT signing | ⏳ Slice 3 |
| Token Status List checking (cached + network) | ⏳ Slice 4 |
Spec versions
- SD-JWT: RFC 9901 (Proposed Standard, Nov 2025)
- SD-JWT-VC: draft-ietf-oauth-sd-jwt-vc-16 (May 2026). Format string
dc+sd-jwtis canonical; legacyvc+sd-jwtis accepted on read for back-compat.
Architectural notes
- Format-plugin, not transport. The plugin is self-contained — it knows nothing about OID4VCI, OID4VP, or VC-API. The
openid4vcplugin delegates to this plugin vialearnCard.invoke.parseSdJwtVc(),verifySdJwtVc(), etc. - Library:
@sd-jwt/core+@sd-jwt/sd-jwt-vc(OpenWallet Foundation, Apache-2.0). Callback-based crypto so our existingjoseEd25519 signer plugs in. - DID resolution: delegated to
@learncard/didkit-plugin(resolveDid()). Supportsdid:key,did:web,did:jwk,did:ethr,did:pkh:*,did:tz. - Browser-first: no Node-only dependencies. Uses Web Crypto SHA-256,
crypto.getRandomValues, andjose.
Installation
pnpm add @learncard/sd-jwt-vc-pluginRequired peer plugin: @learncard/didkit-plugin (for issuer DID resolution).
Usage
import { initLearnCard } from '@learncard/init';
const learnCard = await initLearnCard({ seed: 'a'.repeat(64) });
// Parse only — no network, no verification
const parsed = await learnCard.invoke.parseSdJwtVc(compact);
console.log(parsed.vct); // 'https://credentials.example.com/...'
console.log(parsed.claims); // fully-reconstructed payload
// Verify (parses + verifies issuer signature + checks disclosure hashes)
const check = await learnCard.invoke.verifySdJwtVc(compact);
if (check.errors.length > 0) {
console.error('Verification failed:', check.errors);
}Errors
All errors thrown by this plugin are SdJwtVcError instances with a stable code field for UI mapping. See src/types.ts for the full code list.
Testing
pnpm --filter @learncard/sd-jwt-vc-plugin testThe unit suite round-trips real credentials end-to-end: each test generates a fresh Ed25519 keypair, issues an SD-JWT-VC via SDJwtVcInstance from the OpenWallet Foundation library, then exercises the plugin's parse / verify paths against the live output. Hand-crafted fixtures cover the negative-shape cases (missing iss, vct, alg).
Cross-implementation conformance against RFC 9901 Appendix A vectors and OpenWallet's published test suite lands as part of Slice 4 (see status table above).
