# `MobDev.Plugin.Crypto`
[🔗](https://github.com/genericjam/mob_dev/blob/master/lib/mob_dev/plugin/crypto.ex#L1)

Ed25519 sign/verify primitives + canonical-term encoding for plugin signing.

The signing scheme (see `MOB_PLUGIN_SECURITY.md`, Phase 2):

- Plugin authors generate a per-plugin Ed25519 keypair; the public key
  ships in `priv/mob_plugin.pub` and the manifest+sources are signed
  with the private key into `priv/mob_plugin.sig`.
- Hosts verify the signature against the public key at activation time
  and refuse to build untrusted/unsigned plugins.

This module is the single place the Ed25519 + canonical-encoding
decisions are encoded. Keep it crypto-only — workflow (sign/verify
orchestration, fingerprint storage) lives in `Sign`, `Verify`, and
`TrustStore`.

# `fingerprint`

```elixir
@type fingerprint() :: String.t()
```

Host-visible trust identifier; base64-encoded SHA-256 of the public key.

# `priv_key`

```elixir
@type priv_key() :: &lt;&lt;_::256&gt;&gt;
```

Raw 32-byte Ed25519 private key.

# `pub_key`

```elixir
@type pub_key() :: &lt;&lt;_::256&gt;&gt;
```

Raw 32-byte Ed25519 public key.

# `signature`

```elixir
@type signature() :: &lt;&lt;_::512&gt;&gt;
```

Raw 64-byte Ed25519 signature.

# `canonical_encode`

```elixir
@spec canonical_encode(term()) :: binary()
```

Canonical encoding of an arbitrary Erlang term to a binary.

Uses `:erlang.term_to_binary/2` with `:deterministic` so the same logical
value produces the same bytes regardless of map iteration order.
Centralised here so the determinism flag is in one place: `sign/2`,
`verify/3`, and any future hash-of-payload helper all agree.

# `fingerprint`

```elixir
@spec fingerprint(pub_key()) :: fingerprint()
```

Computes the host-visible trust identifier for a public key.

Format: `"ed25519:<base64>"` where `<base64>` is the standard base64
encoding (with `=` padding) of the SHA-256 digest of the raw 32-byte
public key. Suitable for storing in `mob.exs` under
`:trusted_plugins` and for comparing two keys for equality without
printing the key itself.

# `generate_keypair`

```elixir
@spec generate_keypair() :: {priv_key(), pub_key()}
```

Generates a fresh Ed25519 keypair.

Returns `{priv_bin, pub_bin}` as raw 32-byte binaries. The format
matches what `:crypto.sign/4` and `:crypto.verify/5` accept directly
with the `:eddsa`/`:ed25519` options.

# `sign`

```elixir
@spec sign(term(), priv_key()) :: signature()
```

Signs `payload_term` with `priv_bin`.

The term is canonically encoded via `canonical_encode/1` before signing,
so the signature is over the deterministic binary form — the same map
with the same contents produces the same signature regardless of map
insertion order.

# `verify`

```elixir
@spec verify(term(), signature(), pub_key()) :: :ok | {:error, :invalid_signature}
```

Verifies `signature_bin` against `payload_term` and `pub_bin`.

Returns `:ok` on valid signature, `{:error, :invalid_signature}`
otherwise. Mirrors `sign/2` — the same canonical encoding is applied
before verifying.

A wrong-*size* signature or public key (not a 64-byte sig / 32-byte
Ed25519 key) is treated as an invalid signature rather than crashing:
`:crypto.verify/5` raises `:badarg` from OpenSSL when handed an
ill-sized key, and these bytes originate from attacker-controlled
plugin files (`priv/mob_plugin.sig` / `priv/mob_plugin.pub`). The
documented contract (`:ok | {:error, :invalid_signature}`) must hold
for every binary input, so the raise is caught here.

---

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