@educreds/sd-jwt-adapter
v0.1.0
Published
EduCreds SD-JWT Adapter — Selective Disclosure JWT (SD-JWT-VC) issuance and verification bridge
Maintainers
Readme
@educreds/sd-jwt-adapter
SD-JWT (Selective Disclosure JWT) as an EXTERNAL interoperability adapter — NOT the internal EduCreds credential format.
This service derives an SD-JWT presentation for third-party verifiers that require that format (government digital identity wallets, EU Digital Identity Wallet, enterprise verifiers with SD-JWT-only pipelines). EduCreds itself issues W3C VCs and EVF verifies them — the adapter only projects a verified EVF result into SD-JWT when an external party asks.
External Verifier
│ POST /sd-jwt/issue { certificateId, disclose:[...] }
▼
SD-JWT Adapter ← this service
│ calls EVF over HMAC / API key
▼
EduCreds Verification Framework (EVF) ← authoritative verifier
│
▼
Identity Platform · Credential Platform · Blockchain · PoIC · ERSThe adapter only mints SD-JWT for VERIFIED credentials. If EVF returns
INVALID / UNVERIFIABLE, the adapter refuses (HTTP 422) — it never soft-
discloses unverified data, and it never fabricates claims (every disclosed value
comes straight from the EVF result).
Why this is external-only
EduCreds' internal verification already minimizes exposure: the employer asks EVF "is this valid?" and receives a verdict + selected fields — the raw credential never leaves the trust platform. SD-JWT's extra value (cryptographic per-claim hiding) matters mainly in wallet-first ecosystems where the credential physically leaves the holder. For EduCreds, SD-JWT is an interoperability enhancement, exactly like the OpenID4VC adapter — not a foundational capability.
EduCreds owns its internal protocol. External standards are implemented through adapters — not by changing the platform's core architecture.
What it does
GET /.well-known/sd-jwt-configuration— public discovery (algorithms, allow-listed claims, issuer base URL).POST /sd-jwt/issue— API-key gated control-plane call. Input:{ certificateId, disclose:[claim,...], holderDid?, holderJwk? }.- Calls EVF
POST /api/v1/evf/verify. ForVALIDresults, mints an SD-JWT whose_sdarray holds the SHA-256 digests of exactly the requested (and present) claim disclosures. Everything else stays hidden. - Returns the SD-JWT presentation string (
issuerJWT~disclosure~...~kbjwt), the disclosure list, and anevidenceRefpointer back to the EVF verification.
SD-JWT mechanics (hand-rolled, no new deps)
- Each disclosed claim → disclosure =
base64url([salt, claim, value]). - Issuer JWT embeds
sha256(disclosure)in_sd; a verifier confirms each disclosed claim was authorized by the issuer without seeing the rest. - KB-JWT (key-binding JWT) is the final
~segment, binding the presentation to the holder key — preventing replay by a different party. - Signing uses Node's built-in
crypto(ES256). No external crypto dependency is added.
What it does NOT do
- It does not store credentials as SD-JWT; W3C VC remains canonical.
- It does not modify issuance, wallets, or EVF.
- It does not disclose claims outside the configured
sdJwt.allowedClaimsallow-list (safety: even a verifier can only request pre-approved fields). - It does not persist student PII — the audit log records certificate id + disclosed-claim names only.
HTTP contract
| Method | Path | Auth | Purpose |
|---|---|---|---|
| GET | /.well-known/sd-jwt-configuration | public | SD-JWT discovery |
| GET | /health | public | Liveness |
| GET | /readyz | public | Readiness |
| POST | /sd-jwt/issue | API key (x-api-key) | Mint SD-JWT for a verified credential |
Issue response
{
"sdJwt": "eyJhbGc...~WyJzYWx0IiwiZGVncmVlIiwiQSJd~...~eyJhbGc...",
"issuerJwt": "eyJhbGc...",
"disclosures": [{ "claim": "degree", "value": "BSc Computer Science" }],
"disclosedClaims": ["degree", "institution"],
"evidenceRef": { "verificationId": "v-1", "evfStatus": "VALID" }
}Configuration
See config/sd-jwt-adapter.example.yaml. Key points:
evf— points at the EduCreds EVF gateway (sharedSecretHMAC preferred, orapiKey). HMAC canonical is byte-compatible with EVF's access guard.security.apiKeys— required to call/sd-jwt/issue.sdJwt.allowedClaims— the disclosure allow-list (safety boundary).issuerPrivateKey— supply a stable ES256 key viaSDJWT_ISSUER_KEY_PEMin production. The dev fallback generates an ephemeral key — never use it in production.
SDJWT_CONFIG_PATH=/etc/educreds/sd-jwt-adapter.yaml node dist/index.js
# or: npm startRelationship to other adapters
| Component | Role |
|---|---|
| connector-agent | Edge SIS connector (Tier 1 issuance source) |
| verifier-agent | Employer-side EVF verification companion |
| oid4vc-adapter | External OpenID4VP ↔ EVF translation boundary |
| sd-jwt-adapter | External SD-JWT (selective disclosure) ↔ EVF projection |
Like the others, this is a standalone, deployable adapter — not a core platform dependency. If SD-JWT evolves or is replaced, only this adapter changes; the Identity Platform, Credential Platform, Trust Platform, and EVF remain unaffected. It should be implemented/deployed only when a concrete partner requires SD-JWT (per CTO decision: P3 / external-interoperability-optional).
Tests
npm run build && npm testissuer.spec.ts— disclosure hiding, KB-JWT binding, tamper rejection (digest mismatch → invalid), unverified refused.routes.spec.ts— discovery, issue (valid / unverified 422 / missing 400 / disallowed 400 / missing API key 401).
