Repository navigation
Decide whether this node issues passports as SD-JWT VCs, now that core reads array elements #449
Description
#359 left this as "a capability decision for later": whether this node ever issues a passport as an SD-JWT VC and lets a holder present part of it without us being reachable. Nothing here has changed that, and this records what core now offers, what it still lacks, and what has to be decided. It is not actionable until a dpp-core release carries the changes below.
Where this repository stands
It has no SD-JWT surface at all: sd-jwt, sd_jwt and selective disclos appear in no file. The only credential path is X-DPP-Credential, a compact JWS of a DppAccessCredential (VC-JWT), in crates/dpp-vault/src/middleware/credential.rs. #70 is the other credential door (UNTP, application/vc+jwt).
What core has done since #359
- RFC 9901 conformance is claimed in core's register, on the RFC's own examples, for object-property and array-element Disclosures with SHA-256 and no Key Binding (docs(crypto): claim RFC 9901, 8785 and 8032 dpp-core#417).
- Array elements are read and written (feat(crypto)!: read and write array Disclosures dpp-core#418, closing Run RFC 9901's own SD-JWT examples, and settle array-element disclosures, before claiming conformance dpp-core#395): a verifier now takes a placeholder back or removes it, as the RFC says. Before, it handed the caller an object where a string was expected.
Neither is a reason to build the door. They make the primitive trustworthy.
What core says is still missing for a credential worth issuing
From dpp_vc::sd_jwt_vc's own documentation, restated here so the decision does not need it open:
- No
statusclaim and noexp. An issued credential cannot be withdrawn except by revoking the issuing key, andverifydoes not check astatusclaim if one is present. - No key binding (RFC 9901 clause 4.3), so a presented credential can be replayed by anyone who holds it.
- EdDSA only. The RFC's own examples are signed with ES256, and whether to add a second curve is open (Open question: add P-256/ES256 as a second signing curve? dpp-core#78).
- The profile is an Internet-Draft (
draft-ietf-oauth-sd-jwt-vc-19), so no conformance is claimed for it. - No Type Metadata, and no way to say that one element of an array is non-public (Decide whether a disclosure class can apply to one element of an array dpp-core#421).
Decisions
- Does the node issue them at all? Core's own framing: it does not replace the audience-scoped read, it is the other shape, for holders who want to prove things without the node. Is there a holder in the product who does?
- Where does it live? A route and a media type, and how it sits beside UNTP DPP credential as a second door #70 and the negotiation seam.
- When is it issued? A passport's signed views freeze at publish; core's design conceals every non-public claim at issuance, so that a later reclassification costs nothing. Issue once at publish, or on request?
- How is the key found? Core publishes issuer metadata keyed by the thumbprint URI that is also the
kid, and the node already publishes adid:webdocument. Two discovery paths to one key needs one answer. - Withdrawal. Given the first gap above, is issuing acceptable before a status mechanism exists? My recommendation is no, because the credential cannot be recalled.
- Who presents? The node holds no holder keys, so presentation is somebody else's act. See Decide how part of an SD-JWT VC is presented: who chooses the Disclosures, and where #450.
Merge gate for any implementation
Written now so it is not discovered late:
- A token the node issues verifies with an off-the-shelf SD-JWT verifier, not only with core's. Verifying our signature with our own code proves less than this needs, for the reason UNTP DPP credential as a second door #70 gives. Finding one that accepts
EdDSAis part of Check the SD-JWT issuer's payload against an independent implementation dpp-core#420. - A field classified non-public appears in no presentation unless its Disclosure was chosen.
- Every array-valued field is either issued whole or its handling is the recorded decision from Decide whether a disclosure class can apply to one element of an array dpp-core#421.
- No claim of conformance with the credential profile while it is a draft.
Done when
A decision on each point above is recorded here. If the answer to 1 is yes, the implementation is split into issues of its own.
Activity
LKSNDRTMLKV commentedon Oct 12, 2026
What dpp-core 0.22.0 changes for this decision
Lands here only after core 0.22.0 is released. The repin itself needs no SD-JWT change, because this repository still has no SD-JWT surface.
Key binding exists in core now (odal-node/dpp-core#428, in PR odal-node/dpp-core#429, stacked on odal-node/dpp-core#426). Three things, one per role of RFC 9901:
cnf(dpp_crypto::sd_jwt::holder_key_confirmation).SdJwt::present_with_key_bindingsigns a Key Binding JWT withiat,aud,nonceand thesd_hashof what is presented.SdJwt::disclosed_payload_with_key_bindingand aKeyBindingPolicy: the nonce it issued, its audience, and an instant it supplies.Both independent implementations in core's
sdjwt-oracle.ymljudge it, and core validates the Key Binding JWTs they issue. That removes the "no key binding" line from the gaps listed above. Nothing about who holds the keys has changed: the node still holds no holder keys.What core still does not do for a credential (
dpp_vc::sd_jwt_vc):KeyBindingPolicy, and it would ship in a core release after 0.22.0. It was left out on purpose, so as not to build an API before this decision.statusclaim and noexp, no Type Metadata, and EdDSA only, as before.The merge gate above, updated. "Finding one that accepts
EdDSA" is done:@sd-jwt/core0.22.0 andeudi-lib-jvm-sdjwt-kt0.20.2 both verify core's EdDSA tokens and Key Binding JWTs, so an implementation here can be judged by the same two.When this is decided: whatever implementation follows starts from core 0.22.0 or later and lands after it.