Packages · Digital identity & credentials
k2gl/sd-jwt-vc
SD-JWT Verifiable Credentials (dc+sd-jwt): issue and verify.
The credential layer over SD-JWT — vct rules and issuer key discovery for EUDI-style relying parties.
Install
composer require k2gl/sd-jwt-vc Reach for it when
- You verify dc+sd-jwt credentials as a relying party (issuer metadata, x5c, or pinned keys).
- You issue SD-JWT VCs and want the vct/protected-claims rules enforced.
Look elsewhere when
- You need the raw SD-JWT format without the credential rules — use sd-jwt directly.
k2gl/sd-jwt-vc
SD-JWT-based Verifiable Credentials
(draft-ietf-oauth-sd-jwt-vc)
in pure PHP: issue and verify dc+sd-jwt credentials — the format HAIP and the EU Digital
Identity Wallet build on. RFC 9901 processing is provided by
k2gl/sd-jwt; this package adds the credential layer:
the typ/vct rules, the protected-claims rules, and Issuer key discovery.
Tracks draft -17 (at the IESG for publication). The test suite verifies the draft's own
worked examples byte for byte. The most churn-prone surface across draft revisions has been
the media type; the verifier accepts both dc+sd-jwt and the pre-2024 vc+sd-jwt
(switchable off).
Install
composer require k2gl/sd-jwt-vc
Requires PHP 8.1+. The JWT VC Issuer Metadata resolver speaks PSR-18/PSR-17, so bring any HTTP client (or none, if you pin keys or use x5c).
Usage
Verify a presentation (Verifier / relying party)
use K2gl\SdJwt\KeyBinding;
use K2gl\SdJwtVc\JwtVcIssuerMetadata;
use K2gl\SdJwtVc\SdJwtVcVerifier;
$verifier = new SdJwtVcVerifier;
$credential = $verifier->verifyPresentation(
$compact,
new JwtVcIssuerMetadata($psr18Client, $psr17RequestFactory), // resolves /.well-known/jwt-vc-issuer
KeyBinding::required(audience: 'https://verifier.example.org', nonce: $nonce),
);
$credential->vct(); // "https://credentials.example.com/identity_credential"
$credential->claims(); // the Processed SD-JWT Payload
$credential->status(); // the status claim, if any (e.g. Token Status List reference)
Key discovery mechanisms (Section 2.5), all pluggable via IssuerKeyResolver:
JwtVcIssuerMetadata— web-based resolution from/.well-known/jwt-vc-issuer,jwksorjwks_uri, kid-aware,issuer⇔issidentity enforced.X5cIssuerKeys— thex5cheader chain, validated against your trust anchors (signatures, validity windows, anchor membership; revocation is out of scope).StaticIssuerKeys— keys pinned in configuration.- Any k2gl/dsse
Verifierdirectly, when there is exactly one trusted key.
Issue a credential
use K2gl\SdJwt\Jws\JwsSigner;
use K2gl\SdJwt\Sd;
use K2gl\SdJwtVc\SdJwtVcIssuer;
$issuer = new SdJwtVcIssuer(JwsSigner::es256FromPem($pem));
$credential = $issuer->issue([
'vct' => 'https://credentials.example.com/identity_credential',
'iss' => 'https://issuer.example.com',
'iat' => time(),
'cnf' => ['jwk' => $holderJwk],
'given_name' => Sd::hide('John'),
'birthdate' => Sd::hide('1983-07-29'),
]);
$compact = $credential->toCompact();
The typ header is always dc+sd-jwt; vct is required; the claims that the draft
forbids disclosing selectively (iss, nbf, exp, cnf, vct, vct#integrity,
status) are rejected if wrapped in Sd::hide() — and rejected again at verification
time if a foreign issuer tried it.
Presentation building (selecting disclosures, Key Binding) is the Holder side and lives in
k2gl/sd-jwt's Presentation.
Scope
dc+sd-jwtissuance and verification per draft -17, compact serialization.- Key discovery: JWT VC Issuer Metadata (PSR-18), x5c chains, pinned keys.
statusclaim surfaced for a status-mechanism check by the application; a Token Status List implementation is a separate concern.- Type Metadata (Section 4: display, claim metadata,
vct#integrity) is not implemented — it is wallet-display machinery, not needed to issue or verify.
License
MIT © Nick Harin
API
Public classes and methods, generated from the source.
K2gl\SdJwtVc\Exception\InvalidSdJwtVcException class
K2gl\SdJwtVc\Exception\IssuerKeyResolutionFailed class
K2gl\SdJwtVc\Exception\SdJwtVcException class
K2gl\SdJwtVc\IssuerKeyResolver interface
resolve(?string $issuer, stdClass $header): Verifier
K2gl\SdJwtVc\JwtVcIssuerMetadata class
__construct( private readonly ClientInterface $httpClient, private readonly RequestFactoryInterface $requestFactory, )resolve(?string $issuer, stdClass $header): VerifierwellKnownUrl(string $issuer): string
K2gl\SdJwtVc\SdJwtVcIssuer class
__construct( JwsSigner $signer, string $hashAlgorithm = HashAlgorithm::DEFAULT, ?Closure $saltGenerator = null, )issue(array|stdClass $claims, array $header = []): SdJwt
K2gl\SdJwtVc\SdJwtVcVerifier class
__construct( array $allowedAlgorithms = SdJwtVerifier::DEFAULT_ALGORITHMS, array $allowedHashAlgorithms = SdJwtVerifier::DEFAULT_HASH_ALGORITHMS, ?int $clock = null, int $clockLeewaySeconds = 0, private readonly bool $acceptLegacyType = true, )verify(SdJwt|string $sdJwtVc, IssuerKeyResolver|Verifier $issuerKeys): VerifiedSdJwtVcverifyPresentation( SdJwt|string $presentation, IssuerKeyResolver|Verifier $issuerKeys, KeyBinding $keyBinding, ): VerifiedSdJwtVc
K2gl\SdJwtVc\StaticIssuerKeys class
add(string $issuer, Verifier $verifier): selfaddJwks(string $issuer, array $jwks): selfresolve(?string $issuer, stdClass $header): Verifier
K2gl\SdJwtVc\VerifiedSdJwtVc class
__construct(private readonly VerifiedSdJwt $inner)vct(): stringissuer(): ?stringstatus(): ?stdClasspayload(): stdClassclaims(): arraykeyBindingPayload(): ?stdClass
K2gl\SdJwtVc\X5cIssuerKeys class
__construct(array $trustedCertificates, private readonly ?int $clock = null)resolve(?string $issuer, stdClass $header): Verifier