SD-JWT VC
An SD-JWT VC is the credential format OpenCredentials issues. It is a compact, issuer-signed JWT that carries digests of claims, followed by ~-separated disclosures that reveal individual claims. A verifier hashes each disclosure and matches it to a signed digest, so only disclosed claims are visible and none can be forged. TinyCloud receives it inside a vc+sd-jwt envelope, which the Node verifies when it gates a delegation.
Role
SD-JWT lets a credential be issued once, with every claim digested and signed, while the holder reveals only what is needed. In TinyCloud's mailbox credentials it also carries the binding to the holder's key: the credential's subject is the holder's did:key. The Node therefore knows the presenter is the person who proved the mailbox. It is the wire format of the credentials → policy engine pipeline, in Layer 2.
Mechanics
- Structure.
<issuer-JWT>~<disclosure_1>~…~<disclosure_n>. The JWT is signedEdDSA(Ed25519) by the witness asdid:web:issuer.credentials.org, with_sd_alg: sha-256. Each disclosure is base64url of the JSON array[salt, claimName, value]. - Issuance. The witness issues
vc+sd-jwtcredentials of typeopencredentials.email/v1.subandholderDidare the acquiring DID, the status freshness is 300 s, andemailandemailDomainare disclosable. - Verification in the Node. The Node parses SD-JWT itself; no external verifier crate is involved. It:
- checks the envelope (
format: "vc+sd-jwt", matchingholderDid/subjectDid, profile, issuer,kid, descriptor digest); - checks the JWT header (
alg: EdDSA,typ/kidif present) and the issuer signature against the operator-pinned key; - hashes every disclosure, which must match a signed digest and may not repeat a claim name;
- checks
iss,sub,vct, profile and descriptor digest on the disclosed payload.
- checks the envelope (
- Presentation. The envelope travels in a signed
PolicyCredentialPresentation(v3 account path, or v4 accountless withholderDid == subjectDid). Older pages described aGrantPresentation { sdJwt }; that was the policy-core v0 shape and is not used. - Client helpers.
js/opencredentials-client/src/sd_jwt.ts(parse_sd_jwt/present_sd_jwt) andrust/opencredentials_sd_jwtprovide generic parse/present and issue/verify.
Shape
SD-JWT = <compact-JWT> "~" *( <disclosure> "~" )
disclosure = base64url( JSON [ salt, claimName, value ] )
envelope = { type: "OpenCredentialsIssuedCredential", version: 1,
protocol: "tinycloud.credentials/acquisition/v1", format: "vc+sd-jwt",
profile, credentialType, schema, issuerDid, issuerKid, subjectDid, holderDid,
claims, claimsDigest, descriptorDigest, credentialId,
issuedAt, notBefore, expiresAt, status, credential: "<SD-JWT>" }
Relationships
Issued by the witness for OpenCredentials; verified by the Node in credential-gated-delegation during policy-v3-admission; the overview is feeds-policy-engine; signed by a did:web issuer about a did:key holder.
Example
Bob's credential for bob@example.com under tinycloud.email-domain-proof/v1 has disclosures for email and emailDomain. The Node recomputes each disclosure's SHA-256 and finds it in the signed _sd list. It checks the signature against the pinned issuer key, sees sub equal to Bob's did:key, and reads emailDomain: example.com to match the policy.
Status & drift
shipped. In-tree SD-JWT verification for opencredentials.email/v1 is in production Node 1.17.3. Key-binding JWTs are not used; holder binding comes from sub/holderDid plus the signed presentation. The requirement's claims must match both the disclosed payload and the envelope's claims object, which is itself bound by claimsDigest. The witness issues email as well as emailDomain under both mailbox profiles. Other VCTs are not accepted by the Node.
Sources
tinycloud-node(05c6a93, Node 1.17.3):tinycloud-node-server/src/policy_v3.rs:5866-6060(envelope keys, SD-JWT parse, signature, disclosure digests, claim checks)OpenCredentials(60364a9):rust/opencredentials_witness/src/credentials/mod.rs:1790-2045(issuance, disclosed claims),js/opencredentials-client/src/sd_jwt.ts,rust/opencredentials_sd_jwt/src/lib.rsjs-sdk(d43e51ea):packages/sdk-core/src/policy/credential-admission.ts:360-376(v4 presentation)