tba-profile-credential-sd-jwt-vc 0.4.0.dev2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (16) hide show
  1. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/PKG-INFO +16 -0
  2. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/pyproject.toml +10 -0
  3. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/__init__.py +91 -0
  4. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/artifact.py +29 -0
  5. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/bindings.py +37 -0
  6. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/claims.py +66 -0
  7. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/compatibility.py +37 -0
  8. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/errors.py +20 -0
  9. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/features.py +24 -0
  10. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/metadata.py +35 -0
  11. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/migrations.py +16 -0
  12. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/py.typed +0 -0
  13. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/schemas.py +31 -0
  14. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/status.py +28 -0
  15. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/validation.py +37 -0
  16. tba_profile_credential_sd_jwt_vc-0.4.0.dev2/src/tba_profile_credential_sd_jwt_vc/versions.py +26 -0
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: tba-profile-credential-sd-jwt-vc
3
+ Version: 0.4.0.dev2
4
+ Summary:
5
+ Requires-Python: >=3.10,<3.15
6
+ Classifier: Programming Language :: Python :: 3
7
+ Classifier: Programming Language :: Python :: 3.10
8
+ Classifier: Programming Language :: Python :: 3.11
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: tba-concrete-claim-credential-status (==0.4.0.dev2)
13
+ Requires-Dist: tba-concrete-claim-credential-type (==0.4.0.dev2)
14
+ Requires-Dist: tba-concrete-sd-jwt (==0.4.0.dev2)
15
+ Requires-Dist: tba-concrete-sd-jwt-vc-credential (==0.4.0.dev2)
16
+ Requires-Dist: tba-contracts-identity (==0.4.0.dev2)
@@ -0,0 +1,10 @@
1
+ [project]
2
+ name="tba-profile-credential-sd-jwt-vc"
3
+ version="0.4.0.dev2"
4
+ requires-python=">=3.10,<3.15"
5
+ dependencies=["tba-contracts-identity==0.4.0.dev2","tba-concrete-claim-credential-type==0.4.0.dev2","tba-concrete-claim-credential-status==0.4.0.dev2","tba-concrete-sd-jwt==0.4.0.dev2","tba-concrete-sd-jwt-vc-credential==0.4.0.dev2"]
6
+ [build-system]
7
+ requires=["poetry-core>=1.0.0"]
8
+ build-backend="poetry.core.masonry.api"
9
+ [tool.poetry]
10
+ packages=[{include="tba_profile_credential_sd_jwt_vc",from="src"}]
@@ -0,0 +1,91 @@
1
+ """Versioned SD-JWT VC draft-profile ownership."""
2
+
3
+ from tba_contracts_identity.protocol_processing import (
4
+ build_protocol_capability_report,
5
+ )
6
+
7
+ from .bindings import CAPABILITY_REQUIREMENTS
8
+ from .artifact import parse_sd_jwt_vc
9
+ from .claims import (
10
+ SD_JWT_VC_CLAIM_CLASSES,
11
+ SdJwtVcClaimSetPayload,
12
+ compose_sd_jwt_vc_claim_set,
13
+ parse_sd_jwt_vc_claims,
14
+ )
15
+ from .compatibility import (
16
+ COMPATIBILITY_PATHS,
17
+ SdJwtVcCompatibility,
18
+ compatibility,
19
+ )
20
+ from .errors import (
21
+ SdJwtVcKeyBindingError,
22
+ SdJwtVcProfileError,
23
+ UnsupportedSdJwtVcMediaTypeError,
24
+ )
25
+ from .features import FEATURES_BY_VERSION, supports
26
+ from .migrations import migrate_claims
27
+ from .metadata import SdJwtVcTypeMetadata, parse_type_metadata
28
+ from .schemas import (
29
+ LEGACY_SD_JWT_VC_TYP,
30
+ SD_JWT_VC_CARRIER,
31
+ SdJwtVcCarrier,
32
+ select_carrier,
33
+ )
34
+ from .versions import CURRENT_VERSION, VERSION_HISTORY, SdJwtVcVersion, select_version
35
+ from .status import SdJwtVcStatusReference, parse_status_reference
36
+ from .validation import validate_sd_jwt_vc
37
+ from tba_concrete_sd_jwt_vc_credential import SdJwtVcCredential
38
+
39
+ DRAFT_REVISION = "draft-ietf-oauth-sd-jwt-vc-17"
40
+ MEDIA_TYPE = SD_JWT_VC_CARRIER.media_type
41
+ TYP = SD_JWT_VC_CARRIER.typ
42
+ SdJwtVc = SdJwtVcCredential
43
+
44
+
45
+ def capability_report() -> dict[str, object]:
46
+ return build_protocol_capability_report(
47
+ protocol="sd-jwt-vc",
48
+ revision=CURRENT_VERSION.identifier,
49
+ features=tuple(FEATURES_BY_VERSION[CURRENT_VERSION.identifier]),
50
+ evidence_links=("tests/unit/test_versioned_eat_sd_jwt_vc_profiles.py",),
51
+ extra_requirements=CAPABILITY_REQUIREMENTS,
52
+ include_default_artifact_requirements=False,
53
+ )
54
+
55
+
56
+ __all__ = [
57
+ "CAPABILITY_REQUIREMENTS",
58
+ "COMPATIBILITY_PATHS",
59
+ "CURRENT_VERSION",
60
+ "DRAFT_REVISION",
61
+ "FEATURES_BY_VERSION",
62
+ "LEGACY_SD_JWT_VC_TYP",
63
+ "MEDIA_TYPE",
64
+ "SD_JWT_VC_CARRIER",
65
+ "SD_JWT_VC_CLAIM_CLASSES",
66
+ "SdJwtVcClaimSetPayload",
67
+ "SdJwtVcCredential",
68
+ "SdJwtVcStatusReference",
69
+ "SdJwtVcTypeMetadata",
70
+ "VERSION_HISTORY",
71
+ "SdJwtVcCarrier",
72
+ "SdJwtVc",
73
+ "SdJwtVcCompatibility",
74
+ "SdJwtVcKeyBindingError",
75
+ "SdJwtVcProfileError",
76
+ "SdJwtVcVersion",
77
+ "TYP",
78
+ "UnsupportedSdJwtVcMediaTypeError",
79
+ "capability_report",
80
+ "compatibility",
81
+ "compose_sd_jwt_vc_claim_set",
82
+ "migrate_claims",
83
+ "parse_sd_jwt_vc",
84
+ "parse_sd_jwt_vc_claims",
85
+ "parse_status_reference",
86
+ "parse_type_metadata",
87
+ "select_carrier",
88
+ "select_version",
89
+ "supports",
90
+ "validate_sd_jwt_vc",
91
+ ]
@@ -0,0 +1,29 @@
1
+ import base64
2
+ import json
3
+ from typing import Mapping
4
+
5
+ from tba_concrete_sd_jwt import parse_sd_jwt_serialization
6
+ from tba_concrete_sd_jwt_vc_credential import SdJwtVcCredential
7
+
8
+
9
+ def _decode_object(segment: str) -> Mapping[str, object]:
10
+ try:
11
+ value = json.loads(
12
+ base64.urlsafe_b64decode(segment + "=" * (-len(segment) % 4))
13
+ )
14
+ except (ValueError, UnicodeDecodeError) as exc:
15
+ raise ValueError("invalid SD-JWT VC JSON segment") from exc
16
+ if not isinstance(value, dict):
17
+ raise ValueError("SD-JWT VC segment must be an object")
18
+ return value
19
+
20
+
21
+ def parse_sd_jwt_vc(value: str) -> SdJwtVcCredential:
22
+ serialization = parse_sd_jwt_serialization(value)
23
+ header_segment, claims_segment, _ = serialization.issuer_jwt.split(".")
24
+ return SdJwtVcCredential(
25
+ serialization, _decode_object(header_segment), _decode_object(claims_segment)
26
+ )
27
+
28
+
29
+ __all__ = ["parse_sd_jwt_vc"]
@@ -0,0 +1,37 @@
1
+ """SD-JWT VC wire operations mapped to reportable capabilities."""
2
+
3
+ from tba_contracts_identity.capabilities import ProtocolCapabilityRequirement
4
+
5
+ from .versions import CURRENT_VERSION
6
+
7
+ CAPABILITY_REQUIREMENTS = (
8
+ ProtocolCapabilityRequirement(
9
+ "sd-jwt-vc",
10
+ CURRENT_VERSION.identifier,
11
+ "sd-jwt-vc-decode",
12
+ "application/dc+sd-jwt",
13
+ "artifact.processing",
14
+ "decode",
15
+ "sd-jwt-vc-disclosures",
16
+ ),
17
+ ProtocolCapabilityRequirement(
18
+ "sd-jwt-vc",
19
+ CURRENT_VERSION.identifier,
20
+ "sd-jwt-vc-validation",
21
+ "issuer-signed JWT and disclosures",
22
+ "artifact.processing",
23
+ "validate",
24
+ "verified-sd-jwt-vc",
25
+ ),
26
+ ProtocolCapabilityRequirement(
27
+ "sd-jwt-vc",
28
+ CURRENT_VERSION.identifier,
29
+ "sd-jwt-vc-encode",
30
+ "application/dc+sd-jwt",
31
+ "artifact.processing",
32
+ "encode",
33
+ "sd-jwt-vc",
34
+ ),
35
+ )
36
+
37
+ __all__ = ["CAPABILITY_REQUIREMENTS"]
@@ -0,0 +1,66 @@
1
+ """SD-JWT VC claim-set composition."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Mapping
5
+
6
+ from tba_concrete_claim_credential_status import CredentialStatusClaim
7
+ from tba_concrete_claim_credential_type import CredentialTypeClaim
8
+ from tba_contracts_identity.claims import ClaimSet
9
+
10
+ from .versions import CURRENT_VERSION
11
+
12
+ SD_JWT_VC_CLAIM_CLASSES = (CredentialTypeClaim, CredentialStatusClaim)
13
+
14
+
15
+ @dataclass(frozen=True, slots=True)
16
+ class SdJwtVcClaimSetPayload:
17
+ credential_type: str
18
+ issuer: str | None = None
19
+ issued_at: int | None = None
20
+ not_before: int | None = None
21
+ expiration: int | None = None
22
+ confirmation: Mapping[str, object] | None = None
23
+ status: Mapping[str, object] | None = None
24
+ type_integrity: str | None = None
25
+
26
+
27
+ def parse_sd_jwt_vc_claims(claims: Mapping[str, object]) -> SdJwtVcClaimSetPayload:
28
+ credential_type = claims.get("vct")
29
+ if not isinstance(credential_type, str) or not credential_type:
30
+ raise ValueError("SD-JWT VC requires a non-empty vct claim")
31
+ for name in ("iat", "nbf", "exp"):
32
+ value = claims.get(name)
33
+ if value is not None and (
34
+ not isinstance(value, int) or isinstance(value, bool)
35
+ ):
36
+ raise ValueError(f"{name} must be a NumericDate")
37
+ for name in ("cnf", "status"):
38
+ if name in claims and not isinstance(claims[name], Mapping):
39
+ raise ValueError(f"{name} must be an object")
40
+ integrity = claims.get("vct#integrity")
41
+ if integrity is not None and (not isinstance(integrity, str) or not integrity):
42
+ raise ValueError("vct#integrity must be an integrity metadata string")
43
+ return SdJwtVcClaimSetPayload(
44
+ credential_type,
45
+ claims.get("iss") if isinstance(claims.get("iss"), str) else None,
46
+ claims.get("iat"),
47
+ claims.get("nbf"),
48
+ claims.get("exp"),
49
+ claims.get("cnf"),
50
+ claims.get("status"),
51
+ integrity,
52
+ )
53
+
54
+
55
+ def compose_sd_jwt_vc_claim_set(*claims: object) -> ClaimSet:
56
+ if not any(isinstance(claim, CredentialTypeClaim) for claim in claims):
57
+ raise ValueError("SD-JWT VC requires vct")
58
+ return ClaimSet(tuple(claims), "sd-jwt-vc", CURRENT_VERSION.identifier)
59
+
60
+
61
+ __all__ = [
62
+ "SD_JWT_VC_CLAIM_CLASSES",
63
+ "SdJwtVcClaimSetPayload",
64
+ "compose_sd_jwt_vc_claim_set",
65
+ "parse_sd_jwt_vc_claims",
66
+ ]
@@ -0,0 +1,37 @@
1
+ """SD-JWT VC draft compatibility paths."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from .versions import CURRENT_VERSION, VERSION_HISTORY
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class SdJwtVcCompatibility:
10
+ source: str
11
+ target: str
12
+ compatible: bool
13
+ migration_required: bool
14
+
15
+
16
+ COMPATIBILITY_PATHS = tuple(
17
+ SdJwtVcCompatibility(
18
+ version.identifier,
19
+ CURRENT_VERSION.identifier,
20
+ True,
21
+ version.identifier != CURRENT_VERSION.identifier,
22
+ )
23
+ for version in VERSION_HISTORY
24
+ )
25
+
26
+
27
+ def compatibility(
28
+ source: str,
29
+ target: str = CURRENT_VERSION.identifier,
30
+ ) -> SdJwtVcCompatibility:
31
+ for path in COMPATIBILITY_PATHS:
32
+ if path.source == source and path.target == target:
33
+ return path
34
+ return SdJwtVcCompatibility(source, target, False, False)
35
+
36
+
37
+ __all__ = ["COMPATIBILITY_PATHS", "SdJwtVcCompatibility", "compatibility"]
@@ -0,0 +1,20 @@
1
+ """SD-JWT VC profile mapping errors."""
2
+
3
+
4
+ class SdJwtVcProfileError(ValueError):
5
+ """Base error for a selected SD-JWT VC draft mapping."""
6
+
7
+
8
+ class UnsupportedSdJwtVcMediaTypeError(SdJwtVcProfileError):
9
+ """Raised when the current `dc+sd-jwt` carrier is not selected."""
10
+
11
+
12
+ class SdJwtVcKeyBindingError(SdJwtVcProfileError):
13
+ """Raised when required KB-JWT holder binding is absent or invalid."""
14
+
15
+
16
+ __all__ = [
17
+ "SdJwtVcKeyBindingError",
18
+ "SdJwtVcProfileError",
19
+ "UnsupportedSdJwtVcMediaTypeError",
20
+ ]
@@ -0,0 +1,24 @@
1
+ FEATURES_BY_VERSION = {
2
+ "draft-10": frozenset({"vct", "status", "key-binding"}),
3
+ "draft-13": frozenset({"vct", "status", "key-binding", "type-metadata"}),
4
+ "draft-17": frozenset(
5
+ {
6
+ "vct",
7
+ "status",
8
+ "key-binding",
9
+ "type-metadata",
10
+ "vct-integrity",
11
+ "dc-media-type",
12
+ }
13
+ ),
14
+ }
15
+
16
+
17
+ def supports(feature: str, version: str = "draft-17") -> bool:
18
+ try:
19
+ return feature in FEATURES_BY_VERSION[version]
20
+ except KeyError as exc:
21
+ raise ValueError(f"unsupported SD-JWT VC version: {version}") from exc
22
+
23
+
24
+ __all__ = ["FEATURES_BY_VERSION", "supports"]
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass
2
+ from typing import Mapping, Sequence
3
+
4
+
5
+ @dataclass(frozen=True, slots=True)
6
+ class SdJwtVcTypeMetadata:
7
+ credential_type: str
8
+ name: str | None = None
9
+ description: str | None = None
10
+ extends: str | None = None
11
+ claim_metadata: Sequence[Mapping[str, object]] = ()
12
+
13
+
14
+ def parse_type_metadata(value: Mapping[str, object]) -> SdJwtVcTypeMetadata:
15
+ credential_type = value.get("vct")
16
+ if not isinstance(credential_type, str) or not credential_type:
17
+ raise ValueError("type metadata requires vct")
18
+ claims = value.get("claims", ())
19
+ if not isinstance(claims, list) or not all(
20
+ isinstance(item, Mapping) for item in claims
21
+ ):
22
+ raise ValueError("type metadata claims must be an array of objects")
23
+ extends = value.get("extends")
24
+ if extends is not None and not isinstance(extends, str):
25
+ raise ValueError("extends must be a string")
26
+ return SdJwtVcTypeMetadata(
27
+ credential_type,
28
+ value.get("name") if isinstance(value.get("name"), str) else None,
29
+ value.get("description") if isinstance(value.get("description"), str) else None,
30
+ extends,
31
+ tuple(dict(item) for item in claims),
32
+ )
33
+
34
+
35
+ __all__ = ["SdJwtVcTypeMetadata", "parse_type_metadata"]
@@ -0,0 +1,16 @@
1
+ from typing import Any, Mapping
2
+
3
+
4
+ def migrate_claims(
5
+ value: Mapping[str, Any], *, source: str, target: str = "draft-17"
6
+ ) -> dict[str, Any]:
7
+ if source == target:
8
+ return dict(value)
9
+ if source not in {"draft-10", "draft-13"} or target != "draft-17":
10
+ raise ValueError(f"unsupported SD-JWT VC migration: {source} -> {target}")
11
+ if not isinstance(value.get("vct"), str):
12
+ raise ValueError("SD-JWT VC migration requires vct")
13
+ return dict(value)
14
+
15
+
16
+ __all__ = ["migrate_claims"]
@@ -0,0 +1,31 @@
1
+ """Selected SD-JWT VC carrier descriptors."""
2
+
3
+ from dataclasses import dataclass
4
+
5
+ from .errors import UnsupportedSdJwtVcMediaTypeError
6
+
7
+
8
+ @dataclass(frozen=True, slots=True)
9
+ class SdJwtVcCarrier:
10
+ media_type: str
11
+ typ: str
12
+
13
+
14
+ SD_JWT_VC_CARRIER = SdJwtVcCarrier("application/dc+sd-jwt", "dc+sd-jwt")
15
+ LEGACY_SD_JWT_VC_TYP = "vc+sd-jwt"
16
+
17
+
18
+ def select_carrier(media_type: str) -> SdJwtVcCarrier:
19
+ if media_type != SD_JWT_VC_CARRIER.media_type:
20
+ raise UnsupportedSdJwtVcMediaTypeError(
21
+ f"unsupported SD-JWT VC media type: {media_type}"
22
+ )
23
+ return SD_JWT_VC_CARRIER
24
+
25
+
26
+ __all__ = [
27
+ "LEGACY_SD_JWT_VC_TYP",
28
+ "SD_JWT_VC_CARRIER",
29
+ "SdJwtVcCarrier",
30
+ "select_carrier",
31
+ ]
@@ -0,0 +1,28 @@
1
+ from dataclasses import dataclass
2
+ from typing import Mapping
3
+
4
+
5
+ @dataclass(frozen=True, slots=True)
6
+ class SdJwtVcStatusReference:
7
+ mechanism: str
8
+ index: int | None = None
9
+ uri: str | None = None
10
+
11
+
12
+ def parse_status_reference(value: Mapping[str, object]) -> SdJwtVcStatusReference:
13
+ if len(value) != 1:
14
+ raise ValueError("status must select exactly one status mechanism")
15
+ mechanism, details = next(iter(value.items()))
16
+ if not isinstance(details, Mapping):
17
+ raise ValueError("status mechanism details must be an object")
18
+ index, uri = details.get("idx"), details.get("uri")
19
+ if index is not None and (
20
+ not isinstance(index, int) or isinstance(index, bool) or index < 0
21
+ ):
22
+ raise ValueError("status index must be a non-negative integer")
23
+ if uri is not None and (not isinstance(uri, str) or not uri):
24
+ raise ValueError("status URI must be a non-empty string")
25
+ return SdJwtVcStatusReference(mechanism, index, uri)
26
+
27
+
28
+ __all__ = ["SdJwtVcStatusReference", "parse_status_reference"]
@@ -0,0 +1,37 @@
1
+ from tba_concrete_sd_jwt import decode_disclosure, disclosure_digest
2
+ from tba_concrete_sd_jwt_vc_credential import SdJwtVcCredential
3
+
4
+ from .claims import parse_sd_jwt_vc_claims
5
+ from .schemas import LEGACY_SD_JWT_VC_TYP, SD_JWT_VC_CARRIER
6
+ from .status import parse_status_reference
7
+
8
+
9
+ def validate_sd_jwt_vc(
10
+ credential: SdJwtVcCredential, *, accept_legacy_typ: bool = False
11
+ ) -> None:
12
+ accepted_types = (
13
+ {SD_JWT_VC_CARRIER.typ, LEGACY_SD_JWT_VC_TYP}
14
+ if accept_legacy_typ
15
+ else {SD_JWT_VC_CARRIER.typ}
16
+ )
17
+ if credential.header.get("typ") not in accepted_types:
18
+ raise ValueError("invalid SD-JWT VC typ")
19
+ parsed = parse_sd_jwt_vc_claims(credential.claims)
20
+ if parsed.status is not None:
21
+ parse_status_reference(parsed.status)
22
+ algorithm = str(credential.claims.get("_sd_alg", "sha-256")).replace("-", "")
23
+ referenced = credential.claims.get("_sd", [])
24
+ if not isinstance(referenced, list) or not all(
25
+ isinstance(item, str) for item in referenced
26
+ ):
27
+ raise ValueError("_sd must be an array")
28
+ seen = set()
29
+ for encoded in credential.disclosures:
30
+ decode_disclosure(encoded)
31
+ digest = disclosure_digest(encoded, algorithm)
32
+ if digest not in referenced or digest in seen:
33
+ raise ValueError("unreferenced or duplicate SD-JWT VC disclosure")
34
+ seen.add(digest)
35
+
36
+
37
+ __all__ = ["validate_sd_jwt_vc"]
@@ -0,0 +1,26 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass(frozen=True, slots=True)
5
+ class SdJwtVcVersion:
6
+ identifier: str
7
+ status: str
8
+ published: str
9
+
10
+
11
+ VERSION_HISTORY = (
12
+ SdJwtVcVersion("draft-10", "superseded-draft", "2024-09"),
13
+ SdJwtVcVersion("draft-13", "superseded-draft", "2025-02"),
14
+ SdJwtVcVersion("draft-17", "active-draft", "2025-07"),
15
+ )
16
+ CURRENT_VERSION = VERSION_HISTORY[-1]
17
+
18
+
19
+ def select_version(identifier: str = "draft-17") -> SdJwtVcVersion:
20
+ for version in VERSION_HISTORY:
21
+ if version.identifier == identifier:
22
+ return version
23
+ raise ValueError(f"unsupported SD-JWT VC version: {identifier}")
24
+
25
+
26
+ __all__ = ["CURRENT_VERSION", "VERSION_HISTORY", "SdJwtVcVersion", "select_version"]