# `Attesto.WalletAttestation`
[🔗](https://github.com/XukuLLC/attesto/blob/v2.1.0/lib/attesto/wallet_attestation.ex#L1)

OAuth 2.0 Attestation-Based Client Authentication
(`draft-ietf-oauth-attestation-based-client-auth-10`, 2026-07-06), the
"Wallet Attestation" client authentication method OID4VCI recommends for
native-app Wallets in place of `private_key_jwt`/mTLS.

A Wallet Provider (the Client Attester) issues its Wallet a **Client
Attestation JWT** (`typ` `oauth-client-attestation+jwt`) binding the
Wallet/Client Instance's public key into its `cnf` claim. To authenticate a
request, the Client Instance additionally presents a **Client Attestation
PoP JWT** (`typ` `oauth-client-attestation-pop+jwt`) signed by that
instance key, proving possession of it to this Authorization or Resource
Server.

`verify/3` verifies both JWTs together per the draft's §7
"Verification and Processing" rules and returns the proven Client Instance
key. The host owns the trusted Wallet Provider key material and any
Challenge issuance/tracking; this module only performs the checks the
draft prescribes. Conn-free and fail-closed.

## Client Attestation JWT (`typ=oauth-client-attestation+jwt`, draft §4)

  * `sub` - REQUIRED. The OAuth `client_id` of the Wallet instance.
  * `exp` - REQUIRED. Expiration time; rejected once passed.
  * `cnf` - REQUIRED. A `{"jwk" => <public JWK>}` (RFC 7800) confirmation
    key - the Client Instance Key used to sign the PoP JWT.
  * `iat` - OPTIONAL.

The draft version implemented here carries **no `aud`** on the Client
Attestation JWT itself (earlier drafts did; it was removed) - the
receiving server's identity is asserted by the PoP JWT's `aud` instead.
Trust in the signer (the Wallet Provider / Client Attester) is
out-of-band, per host-supplied trusted keys; the draft leaves the
discovery mechanism (PKI, `kid`+`jku`, pre-shared metadata) unspecified.

## Client Attestation PoP JWT (`typ=oauth-client-attestation-pop+jwt`, draft §5.1)

  * `aud` - REQUIRED. This Authorization Server's issuer identifier URL
    (RFC 8414) or, for a Resource Server, its resource identifier URL
    (RFC 9728). Single-valued; a Client Attestation PoP JWT targets one
    audience only.
  * `jti` - REQUIRED. A unique identifier the caller may use for its own
    replay tracking (see `:replay_check`).
  * `iat` - REQUIRED. Freshness is checked against `:max_age_seconds`.
  * `challenge` - OPTIONAL. Echoes a server-issued Challenge (draft §6);
    checked when the caller supplies `:expected_challenge`.

It MUST be signed by the private half of the Client Attestation's `cnf`
key - this module verifies exactly that.

# `instance_key`

```elixir
@type instance_key() :: %{jwk: map(), jkt: String.t()}
```

# `replay_check_fun`

```elixir
@type replay_check_fun() :: (String.t(), pos_integer() -&gt; :ok | {:error, :replay})
```

# `verified`

```elixir
@type verified() :: %{
  instance_key: instance_key(),
  attestation_claims: map(),
  pop_claims: map(),
  replay_key: String.t(),
  replay_ttl: pos_integer()
}
```

# `verify_error`

```elixir
@type verify_error() ::
  :invalid_attestation
  | :invalid_typ
  | :invalid_alg
  | :unsupported_critical_header
  | :invalid_signature
  | :expired
  | :invalid_client_id
  | :missing_cnf
  | :invalid_cnf
  | :invalid_pop
  | :invalid_pop_typ
  | :invalid_pop_alg
  | :unsupported_pop_critical_header
  | :invalid_pop_signature
  | :invalid_pop_audience
  | :invalid_pop_challenge
  | :missing_pop_jti
  | :invalid_pop_jti
  | :missing_pop_iat
  | :invalid_pop_iat
  | :pop_expired
  | :replay
```

# `verify_opts`

```elixir
@type verify_opts() :: [
  trusted_wallet_provider_jwks: map() | [map()],
  audience: String.t(),
  client_id: String.t(),
  expected_challenge: String.t(),
  now: DateTime.t() | non_neg_integer(),
  max_age_seconds: pos_integer(),
  accepted_algs: [Attesto.SigningAlg.alg()],
  enforce_fapi_alg_policy: boolean(),
  replay_check: replay_check_fun() | nil
]
```

# `verify`

```elixir
@spec verify(String.t(), String.t(), verify_opts()) ::
  {:ok, verified()} | {:error, verify_error()}
```

Verify a Client Attestation JWT together with its Client Attestation PoP
JWT and return the proven Client Instance key.

## Required opts

  * `:trusted_wallet_provider_jwks` - an RFC 7517 JWK Set, a single public
    JWK map, or a list of public JWK maps the Client Attestation JWT's
    signature is checked against. Establishing which Wallet Provider(s) to
    trust is the host's responsibility (draft §7.1 leaves this out of
    scope).
  * `:audience` - this Authorization/Resource Server's own identifier; the
    PoP JWT's `aud` MUST equal it exactly.

## Optional opts

  * `:client_id` - when set, the Client Attestation's `sub` MUST equal it.
  * `:expected_challenge` - a Challenge (draft §6) previously issued to
    the client; when set, the PoP's `challenge` claim MUST match it. The
    Challenge is a server-issued, non-secret freshness token (visible on
    the wire already), so this is a plain equality check, matching
    `Attesto.CredentialProof`'s `c_nonce` check.
  * `:now` - clock reference (DateTime or unix seconds).
  * `:max_age_seconds` - how far in the past the PoP's `iat` may be.
    Default 300.
  * `:accepted_algs` - JWS algorithms accepted for both JWTs' signatures.
    Defaults to `Attesto.SigningAlg.fapi_algs/0`.
  * `:enforce_fapi_alg_policy` - additionally enforce the FAPI RSA
    modulus / Edwards curve restrictions on the Client Attestation
    signer's key. Defaults to `true` when `:accepted_algs` is omitted,
    `false` otherwise (matches `Attesto.ClientAssertion.verify/5`).
  * `:replay_check` - a 2-arity function `(replay_key, ttl_seconds) -> :ok
    | {:error, :replay}`, called after every other PoP check passes.
    `replay_key` is a fixed-length digest namespacing the `jti` by the
    Client Instance Key's thumbprint - do not assume it is the raw `jti`.
    Omitted, no replay check is performed inline; the caller may instead
    record the returned `replay_key`/`replay_ttl` itself after any later
    binding step (see `Attesto.DPoP`'s "Replay protection" for why that
    order matters when there is one).

Returns `{:ok, %{instance_key: %{jwk:, jkt:}, attestation_claims:,
pop_claims:, replay_key:, replay_ttl:}}`.

---

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