-
Notifications
You must be signed in to change notification settings - Fork 0
SD JWT VC
SD-JWT VC lets
an issuer mark claims as selectively disclosable: the holder later presents
only the claims it chooses, and proves possession of the key the credential
was bound to (cnf) with a Key Binding JWT over the verifier's aud +
nonce.
from openvc.keys import Ed25519SigningKey
from openvc.proof.sd_jwt import SdJwtVcProofSuite
issuer = Ed25519SigningKey.generate(kid="https://issuer.example#key-1")
holder = Ed25519SigningKey.generate(kid="holder-key-1")
suite = SdJwtVcProofSuite()
# Issue: given_name and age are selectively disclosable; cnf binds the holder key.
sd_jwt = suite.issue(
{"iss": "https://issuer.example", "given_name": "Ada",
"family_name": "Lovelace", "age": 36},
signing_key=issuer, disclosable=["given_name", "age"],
holder_jwk=holder.public_jwk(), vct="https://credentials.example/identity")
# Present: the holder signs a KB-JWT over this verifier's aud + nonce.
presentation = suite.create_presentation(
sd_jwt, holder_key=holder, audience="https://verifier.example", nonce="n-123")
# Verify: issuer signature, disclosure digests, KB-JWT, aud/nonce, vct.
result = suite.verify(
presentation, public_key_jwk=issuer.public_jwk(),
audience="https://verifier.example", nonce="n-123", require_key_binding=True,
expected_vct="https://credentials.example/identity")
print(result.vct, result.key_bound, result.claims["given_name"])The pipeline also accepts SD-JWT presentations directly —
verify_credential(presentation, ...) detects the format and resolves the
issuer key from iss (see
Resolving issuer keys).
An issuer anchored on a trusted list (eIDAS / EUDI) can carry its certificate chain in the
SD-JWT VC header — suite.issue(..., x5c=[<base64 DER>, …]), leaf first — so a verifier
chains it to its anchors and binds the leaf to iss in one call:
sd_jwt = suite.issue(
{"iss": "https://issuer.example", ...}, signing_key=issuer, vct=VCT, x5c=x5c_chain)
result = verify_credential(sd_jwt, x5c_trust_anchors=[root]) # chains x5c -> root, binds issThe leaf certificate's key must be the issuer's signing key and iss must appear in the
leaf's Subject Alternative Name, or verification fails closed (see Trust anchors).
An issuer can pin the credential type's published
Type Metadata
document with vct#integrity; the verifier resolves the document, checks the
hash, and validates the disclosed claims against the type's claims metadata
(paths + mandatory):
import base64
import hashlib
import json
from openvc.keys import Ed25519SigningKey
from openvc.proof.sd_jwt import SdJwtVcProofSuite
from openvc.type_metadata import validate_type_metadata
VCT = "https://credentials.example.com/education_credential/v1"
type_metadata = {
"vct": VCT,
"name": "Example Education Credential",
"claims": [
{"path": ["name"], "sd": "always", "mandatory": True},
{"path": ["degree"], "sd": "always"},
],
}
metadata_bytes = json.dumps(type_metadata).encode()
integrity = "sha256-" + base64.b64encode(hashlib.sha256(metadata_bytes).digest()).decode()
issuer = Ed25519SigningKey.generate(kid="https://issuer.example#key-1")
holder = Ed25519SigningKey.generate(kid="holder-key-1")
suite = SdJwtVcProofSuite()
issued = suite.issue(
{"iss": "https://issuer.example", "vct#integrity": integrity,
"name": "Ada Lovelace", "degree": "Mathematics"},
signing_key=issuer, vct=VCT, disclosable=["name", "degree"],
holder_jwk=holder.public_jwk())
verified = suite.verify(issued, public_key_jwk=issuer.public_jwk())
result = validate_type_metadata(
verified.claims, vct=verified.vct,
vct_integrity=verified.claims.get("vct#integrity"),
resolve=lambda url: {VCT: metadata_bytes}[url]) # in-memory; real: fetch by URL
print(result.documents[0]["name"], [c["path"] for c in result.claims])In production, resolve Type Metadata over the network with the SSRF-guarded
default — openvc.resolvers.default_type_metadata_resolver — instead of the
in-memory lambda above.
If a credential's credentialStatus (or credentialSchema) must be
enforceable, do not list it in disclosable: a holder could simply omit
the disclosure and the verifier would never see the pointer. Fail-closed
status only works for claims the holder cannot withhold — the same applies to
ecdsa-sd-2023's mandatory pointers (see the
Security model).
📖 Published automatically from the wiki/ directory of the main repository — edits made directly on the wiki are overwritten by the next sync. To change a page, open a PR against wiki/. Every python block on these pages is executed by CI. · API reference · LGPL-3.0-or-later
Start
Proof formats
Verifying in practice
- Presentations & OpenID4VP
- Resolving issuer keys
- Relying-party certificates
- Credential schemas
- Status lists
- Trust anchors: EU TL & EBSI
- Async verification
Walkthroughs
Operating
Reference