OID4VP Presentation (holder/wallet) flow (draft-ietf-oauth-openid4vp).
Given a verified AttestoClient.Wallet.PresentationRequest and the
wallet's held credentials (as returned by
AttestoClient.Wallet.request_credential/3), present/4 selects which
held credential satisfies each DCQL credential query, builds the
vp_token, and POSTs it to the verifier's response_uri (direct_post).
A presentation this module builds is the mirror image of Attesto.VpToken.verify/2
- it verifies there against the same nonce/
client_id/response_uri.
Two credential formats are supported, matching AttestoClient.Wallet:
- SD-JWT VC (
vc+sd-jwt/dc+sd-jwt) - the issuer-signed JWT plus the selected Disclosures, plus a holder Key Binding JWT (typ: kb+jwt) signed overnonce/aud/sd_hash(Attesto.SdJwt.verify_key_binding/3is the verifier-side mirror). mso_mdoc- a full ISO 18013-5DeviceResponse, delegated toAttestoClient.Wallet.Presentation.Mdoc.
Only response_mode: "direct_post" is built; "direct_post.jwt"
(encrypted responses) is a follow-up and fails closed with
:unsupported_response_mode.
Selection
Selection is deliberately simple - format, meta.vct_values /
meta.doctype_value, and top-level requested-claim presence - and does
not consider credential_sets (OR/AND grouping of alternative credential
queries). It is also fully host-overridable: pass an explicit :selection
(a %{query_id => held_credential} map) to present/4/build_vp_token/3
to skip DCQL matching outright, e.g. when the host runs its own consent UI
and lets the user pick.
Claim minimisation (SD-JWT VC only)
When a DCQL credential query's claims are all simple top-level paths
(["given_name"], not ["address", "street"] or an array wildcard), only
the matching Disclosures are included in the presentation; the issuer JWT
itself (and any claim visible outside _sd) is unaffected. A query with no
claims, or with any non-simple path, discloses every Disclosure the held
credential carries. mso_mdoc presentations always include every
IssuerSigned item (see AttestoClient.Wallet.Presentation.Mdoc).
All HTTP goes through AttestoClient.OAuthHTTP, so it is mockable the same
way as the rest of this library (req_options: [plug: ...]).
Summary
Functions
Build the vp_token map (%{query_id => presentation}) for an explicit
selection, without submitting it.
Build the vp_token and POST it to request.response_uri.
Select which held credential satisfies each DCQL credential query.
POST an already-built vp_token to request.response_uri as
application/x-www-form-urlencoded direct_post (vp_token JSON-encoded,
plus state when the request carried one). Unauthenticated, per OID4VP -
see AttestoClient.OAuthHTTP.post_form_open/3.
Types
Functions
@spec build_vp_token( %{required(String.t()) => map()}, AttestoClient.Wallet.PresentationRequest.t(), [ opt() ] ) :: {:ok, %{required(String.t()) => String.t()}} | {:error, term()}
Build the vp_token map (%{query_id => presentation}) for an explicit
selection, without submitting it.
Exposed separately so a host can build and inspect (or let the user
confirm) a presentation before it is sent, and so tests can assert on the
built vp_token directly.
@spec present(AttestoClient.Wallet.PresentationRequest.t(), [map()], [opt()]) :: {:ok, map()} | {:error, term()}
Build the vp_token and POST it to request.response_uri.
Requires :holder_keys (a %{query_id => holder_key} map covering every
selected query id - see the moduledoc). Selects via select/2 unless
:selection is supplied.
Select which held credential satisfies each DCQL credential query.
dcql_query is request.dcql_query; held_credentials is the wallet's
list of held credentials (see the moduledoc for the matching rule).
Returns {:error, {:no_match, query_id}} for the first query no held
credential satisfies.
@spec submit(AttestoClient.Wallet.PresentationRequest.t(), map(), [opt()]) :: {:ok, map()} | {:error, term()}
POST an already-built vp_token to request.response_uri as
application/x-www-form-urlencoded direct_post (vp_token JSON-encoded,
plus state when the request carried one). Unauthenticated, per OID4VP -
see AttestoClient.OAuthHTTP.post_form_open/3.