# `Attesto.VpToken`
[🔗](https://github.com/XukuLLC/attesto/blob/v2.0.1/lib/attesto/vp_token.ex#L1)

OID4VP `vp_token` verification for SD-JWT VC (`dc+sd-jwt`) and ISO mdoc
(`mso_mdoc`) presentations (OID4VP §7).

The response is a DCQL-shaped map from credential-query IDs to one or more
presentations. Verification is conn-free. SD-JWT VC entries delegate issuer
signature, disclosure, VC claim, and holder Key Binding JWT checks to
`Attesto.SdJwtVc` and `Attesto.SdJwt`; `mso_mdoc` entries delegate `Device`-
`Response` and device-signature checks to `Attesto.Mdoc.verify_device_response/4`.

Holder key binding is mandatory for SD-JWT VC: a credential without a
`cnf.jwk` or a valid Key Binding JWT cannot satisfy an OID4VP request. mdoc
presentations carry their own device-signature binding instead.

## Format dispatch

Each query ID's format is resolved via the optional `:formats` option — a
map of query ID to `"dc+sd-jwt"` or `"mso_mdoc"` — falling back to shape
detection when a query ID is absent from `:formats` (or the option itself is
omitted): a `~`-delimited string is SD-JWT VC, a plain base64url string is
`mso_mdoc`. Prefer `:formats` when the DCQL query is known ahead of time;
detection exists for callers that don't thread it through.

## mdoc context

An `mso_mdoc` entry additionally needs `:response_uri` — the OID4VP
`response_uri` the wallet's `DeviceResponse` was bound to via its
`OpenID4VPHandover` `SessionTranscript`. `:audience` doubles as the
handover's `client_id` and the shared `:nonce` as its nonce. Only
unencrypted `direct_post` is supported (the handover's JWK thumbprint is
always `nil`). SD-JWT-VC-only callers never need `:response_uri`; it is
required only when the `vp_token` actually contains an `mso_mdoc` entry.

# `mdoc_safe_result`

```elixir
@type mdoc_safe_result() :: %{
  doc_type: String.t(),
  namespaces: map(),
  device_namespaces: map(),
  validity: map()
}
```

# `safe_result`

```elixir
@type safe_result() :: %{
  vct: String.t(),
  iss: String.t(),
  claims: map(),
  cnf: map() | nil
}
```

# `verify_result`

```elixir
@type verify_result() :: {:ok, map()} | {:error, term()}
```

# `constraints_from_dcql`

```elixir
@spec constraints_from_dcql(map()) :: %{optional(String.t()) =&gt; map()}
```

Derive per-query-id verification constraints from a DCQL query, for the
`:query_constraints` option of `verify/2`.

For each credential query it extracts the requested `format`, the accepted
credential type (`vct_values` for `dc+sd-jwt`, `doctype_value`/`doctype_values`
for `mso_mdoc`), and the claim `path`/`values` entries. `verify/2` enforces
these after signature and holder binding so a validly-signed credential of the
wrong type — or one disclosing a value outside the requested set — cannot
satisfy the query. Query IDs absent from the returned map are unconstrained.

# `verify`

```elixir
@spec verify(
  term(),
  keyword()
) :: verify_result()
```

Verify an OID4VP `vp_token` carrying SD-JWT VC and/or `mso_mdoc` presentations.

Required options are `:nonce`, `:audience`, and exactly one issuer trust
source: `:issuer_jwks` for static issuer keys or `:resolve_issuer` for a
callback receiving the presentation's (unverified) issuer identity. The
optional `:now` value is passed to both the VC/mdoc and holder-binding
verifiers. See the moduledoc for `:formats` and `:response_uri`.

> #### `:resolve_issuer` receives UNVERIFIED issuer material {: .warning}
>
> For SD-JWT VC, the callback is handed the `iss` peeked from the still-
> unverified issuer JWT (verifying the signature requires the key, which
> requires `iss` — so the lookup is unavoidably ahead of verification). For
> `mso_mdoc`, it is handed the `docType` peeked from the still-unverified
> `DeviceResponse` in the same way. The signature is then checked against
> whatever keys the callback returns, so forged issuer material cannot forge
> a credential — it only misdirects the key lookup. But the callback MUST
> NOT make a network request derived from this value without an allow-list:
> the presenter controls it, so a naive fetch is an SSRF sink. Resolve from
> a trusted issuer registry, not by dereferencing the peeked value.

A string presentation produces one safe result for its query ID. A list of
presentations produces a list of safe results. Raw JWTs and raw
`DeviceResponse` bytes are never returned.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
