# `Attesto.Mdoc`
[🔗](https://github.com/XukuLLC/attesto/blob/v2.0.1/lib/attesto/mdoc.ex#L2)

Issue and verify ISO 18013-5 mdoc credentials and OID4VP mdoc
presentations.

Issuance builds the MobileSecurityObject (MSO), binds the holder's device
key, and signs issuer authentication with ES256 `COSE_Sign1`. `verify/3`
checks issuer signatures, item digests, and validity for a bare
`IssuerSigned` structure. The document type is bound ONLY when
`:expected_doc_type` is supplied — without it, any doc type from a trusted
issuer is accepted, so a caller that needs a specific doc type (e.g. a DCQL
`mso_mdoc` query) MUST pass it. `Attesto.VpToken` threads this via its
`:query_constraints` for the OID4VP path.

`verify_device_response/4` additionally verifies an OID4VP presentation: a
full ISO 18013-5 `DeviceResponse` whose `DeviceSigned.deviceAuth` is an
ES256 `COSE_Sign1` (detached payload) over `DeviceAuthentication =
["DeviceAuthentication", SessionTranscript, DocType,
DeviceNameSpacesBytes]`, where `SessionTranscript`'s `Handover` is OID4VP's
`OpenID4VPHandover` (redirect-flow form, per the OID4VP 1.0 "Handover and
SessionTranscript Definitions"). `COSE_Mac0` device authentication is not
supported.

`peek_doc_type/1` reads an unverified `docType` out of a `DeviceResponse`
for issuer-key resolution ahead of verification; see `Attesto.VpToken`.

# `validity`

```elixir
@type validity() :: %{
  signed: integer(),
  valid_from: integer(),
  valid_until: integer()
}
```

# `verified`

```elixir
@type verified() :: %{
  doc_type: String.t(),
  namespaces: %{required(String.t()) =&gt; %{required(String.t()) =&gt; term()}},
  device_key: map(),
  validity: validity()
}
```

# `verified_presentation`

```elixir
@type verified_presentation() :: %{
  doc_type: String.t(),
  namespaces: %{required(String.t()) =&gt; %{required(String.t()) =&gt; term()}},
  device_namespaces: %{
    required(String.t()) =&gt; %{required(String.t()) =&gt; term()}
  },
  validity: validity()
}
```

# `verify_error`

```elixir
@type verify_error() ::
  :digest_mismatch
  | :expired
  | :invalid_cose
  | :invalid_key
  | :invalid_mdoc
  | :invalid_signature
  | :not_yet_valid
  | :unexpected_doc_type
  | :unsupported_algorithm
```

# `issue`

```elixir
@spec issue(keyword()) :: {:ok, String.t()} | {:error, :invalid_options}
```

Issue a base64url-encoded ISO 18013-5 IssuerSigned structure.

Required options are `:doc_type`, `:namespaces`, `:device_key`,
`:issuer_pem`, and `:validity`. `:x5chain` optionally carries a list of
issuer-certificate DER binaries in the COSE unprotected header.

# `peek_doc_type`

```elixir
@spec peek_doc_type(binary()) :: {:ok, String.t()} | {:error, :invalid_mdoc}
```

Peek an OID4VP `DeviceResponse`'s first document `docType`, supplied as
base64url or raw CBOR bytes, without verifying any signature.

Mirrors `Attesto.JWS.peek_json` peeking an SD-JWT's unverified `iss`:
the result is UNVERIFIED input and exists only to select candidate issuer
keys ahead of verification (e.g. for `Attesto.VpToken`'s `:resolve_issuer`
callback). It MUST NOT be used to make a network request — the presenter
controls this value.

# `verify`

```elixir
@spec verify(binary(), JOSE.JWK.t() | map() | String.t(), keyword()) ::
  {:ok, verified()} | {:error, verify_error()}
```

Verify an IssuerSigned mdoc supplied as base64url or raw CBOR bytes.

`trusted` is the issuer's public JWK or PEM. Set `:expected_doc_type` to
bind verification to a requested credential type. A `:now` option may be
supplied as Unix seconds or a `DateTime` for deterministic clock checks.

# `verify_device_response`

```elixir
@spec verify_device_response(
  binary(),
  keyword(),
  JOSE.JWK.t() | map() | String.t(),
  keyword()
) ::
  {:ok, [verified_presentation()]} | {:error, verify_error()}
```

Verify an OID4VP mdoc presentation: a full ISO 18013-5 `DeviceResponse`
supplied as base64url or raw CBOR bytes.

`context` supplies the OID4VP request values needed to reconstruct the
`OpenID4VPHandover` and `SessionTranscript`: `:client_id`, `:nonce`, and
`:response_uri` are required. `:response_encryption_jwk` is the
Verifier's public response-encryption key (present only for the
`direct_post.jwt` Response Mode) and is folded in as the handover's JWK
thumbprint; omit it for unencrypted `direct_post`.

`trusted` and `opts` verify each document's `IssuerSigned` structure
exactly as in `verify/3` (`:expected_doc_type` and `:now` included).
Each document's `DeviceSigned.deviceAuth` is additionally verified as an
ES256 `COSE_Sign1` with a detached payload, signed by the device key
bound in that document's MSO.

Returns one verified result per document in the `DeviceResponse`.

---

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