# `RequestSeal.Discovery.KeySet`
[🔗](https://github.com/baselabs/request_seal/blob/v0.5.2/lib/request_seal/discovery/key_set.ex#L1)

Bounded source-bound public keys, freshness and directory proof status.

`revision` is SHA-256 over the fetched JSON bytes, not an origin-assigned sequence.
Default IDs are computed RFC thumbprints; directory-assigned IDs never replace
cryptographic identity or thumbprint revocation. Key restrictions remain
enforced by `RequestSeal.PublicKey`. The cache's `:clock` supports
caller-controlled time for resolution and fetching. `etag` is the source's
ETag validator, if it sent one; the cache revalidates conditionally with it.

# `t`

```elixir
@type t() :: %RequestSeal.Discovery.KeySet{
  etag: binary() | nil,
  expires_at: integer(),
  fetched_at: integer(),
  keys: %{required(binary()) =&gt; RequestSeal.Discovery.Resolution.t()},
  origin: binary(),
  proof: :signed | :unsigned | :not_applicable,
  revision: binary(),
  source: RequestSeal.Discovery.Source.t()
}
```

# `lookup`

```elixir
@spec lookup(t(), binary(), [RequestSeal.Crypto.algorithm()]) ::
  {:ok, RequestSeal.Discovery.Resolution.t()} | :error
```

Find a fresh, nonrevoked key by its source-selected ID under an algorithm allowlist.

Lookup uses system time. It revalidates the set's source, recomputes the key's
thumbprint, and rejects stale, revoked, incompatible or malformed entries.

## Parameters

  * `set` - a `RequestSeal.Discovery.KeySet`, normally from
    `RequestSeal.Discovery.fetch/2`.
  * `keyid` - an ID under the source's `key_id` mode: an RFC 7638 thumbprint
    (43 base64url characters) by default, or a directory-assigned ID of 1–256
    printable ASCII bytes with `key_id: :directory`.
  * `algorithms` - 1 to 64 algorithm identifiers accepted by a policy (the names
    in `RequestSeal.Crypto.algorithms/0` or `{:jws, name}` tuples). The first
    entry the key can use, and that matches any algorithm the source asserted,
    is selected.

## Returns

  * `{:ok, %RequestSeal.Discovery.Resolution{}}` - the entry with `algorithm`
    set to the selected algorithm and `revision` set to the set's revision.
  * `:error` - the ID is malformed for the mode or absent; the set or entry has
    expired; the thumbprint is revoked by the source; no allowed algorithm fits
    the key; the source or allowlist is invalid; or `set` is not a key set.

## Examples

A key set normally comes from `RequestSeal.Discovery.fetch/2`; this example
builds one from a locally generated Ed25519 key so it runs offline.

    iex> {public, _private} = :crypto.generate_key(:eddsa, :ed25519)
    iex> {:ok, key} =
    ...>   RequestSeal.PublicKey.import(
    ...>     %{"kty" => "OKP", "crv" => "Ed25519", "x" => Base.url_encode64(public, padding: false)},
    ...>     :jwk
    ...>   )
    iex> {:ok, thumbprint} = RequestSeal.PublicKey.thumbprint(key)
    iex> {:ok, source} =
    ...>   RequestSeal.Discovery.Source.new(%{type: :jwks_uri, location: "https://keys.example/jwks.json"})
    iex> entry = %RequestSeal.Discovery.Resolution{
    ...>   key: key,
    ...>   key_id: thumbprint,
    ...>   thumbprint: thumbprint,
    ...>   algorithm: "ed25519",
    ...>   origin: "https://keys.example",
    ...>   source_type: :jwks_uri,
    ...>   location: source.location,
    ...>   fetched_at: 0,
    ...>   expires_at: 253_402_300_799,
    ...>   proof: :not_applicable
    ...> }
    iex> set = %RequestSeal.Discovery.KeySet{
    ...>   source: source,
    ...>   origin: "https://keys.example",
    ...>   keys: %{thumbprint => entry},
    ...>   fetched_at: 0,
    ...>   expires_at: 253_402_300_799,
    ...>   revision: "example-revision",
    ...>   proof: :not_applicable
    ...> }
    iex> {:ok, resolution} =
    ...>   RequestSeal.Discovery.KeySet.lookup(set, thumbprint, ["ecdsa-p256-sha256", "ed25519"])
    iex> {resolution.algorithm, resolution.revision, resolution.thumbprint == thumbprint}
    {"ed25519", "example-revision", true}
    iex> RequestSeal.Discovery.KeySet.lookup(set, thumbprint, ["ecdsa-p256-sha256"])
    :error
    iex> RequestSeal.Discovery.KeySet.lookup(set, "unknown", ["ed25519"])
    :error

---

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