AttestoClient.Wallet.Presentation (AttestoClient v2.3.1)

Copy Markdown View Source

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:

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

opt()

@type opt() ::
  {:selection, %{optional(String.t()) => map()}}
  | {:holder_keys, %{optional(String.t()) => JOSE.JWK.t() | map() | String.t()}}
  | {:alg, String.t()}
  | {:kid, String.t()}
  | {:now, integer()}
  | {:req_options, keyword()}
  | {:timeout, pos_integer()}

Functions

build_vp_token(selection, request, opts \\ [])

@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.

present(request, held_credentials, opts \\ [])

@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(arg1, held_credentials)

@spec select(map(), [map()]) ::
  {:ok, %{required(String.t()) => map()}} | {:error, term()}

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.

submit(request, vp_token, opts \\ [])

@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.