# `Vodozemac`
[🔗](https://git.sr.ht/~sbr/vodozemac)

Elixir bindings to Matrix's Olm and Megolm cryptographic primitives,
built on top of Element's [vodozemac][1] (Rust) via Rustler.

This library wraps the protocol primitives only — talking to a
homeserver, persisting serialized session state, and routing
decrypted events are caller concerns.

## Surface

  * **Account** — long-term Curve25519 + Ed25519 identity for a
    device. Generates one-time keys and is the input to outbound
    Olm sessions. (`account_*` functions.)
  * **Olm session** — pairwise channel between two devices, used
    to share Megolm room keys. (`olm_*` functions.)
  * **Megolm group session** — symmetric ratchet for one sender
    fanning out to many receivers. (`*_group_session` functions.)
  * **Cross-signing** — raw Ed25519/Curve25519 helpers used by
    Matrix's cross-signing identities.
  * **SAS** — short-authentication-string verification primitives.

## Persistence

All session state serializes to an opaque pickle binary. The
pickle is currently wrapped with vodozemac's default zero-key
(no confidentiality at rest); callers must apply their own
encryption layer until a `pickle_key` parameter lands in a later
release.

## Stability

Pre-1.0. Function signatures may move before the API freezes; see
`CHANGELOG.md` and the README's stability table.

[1]: https://github.com/matrix-org/vodozemac

# `account_create`

```elixir
@spec account_create() :: binary()
```

Create a fresh Olm account; returns its libolm-compatible pickle.

# `account_generate_one_time_keys`

```elixir
@spec account_generate_one_time_keys(binary(), non_neg_integer()) ::
  {binary(), [{binary(), binary()}]}
```

Generate up to `count` new one-time keys. Returns the updated
account pickle and a list of `{key_id_b64, public_key_b64}` tuples.

# `account_identity_keys`

```elixir
@spec account_identity_keys(binary()) :: {binary(), binary()}
```

Return the device's identity keys as `{curve25519_b64, ed25519_b64}`.

# `account_mark_published`

```elixir
@spec account_mark_published(binary()) :: binary()
```

Mark all unpublished one-time keys as published after `/keys/upload`.

# `account_max_one_time_keys`

```elixir
@spec account_max_one_time_keys(binary()) :: non_neg_integer()
```

Maximum number of one-time keys the account holds (vodozemac uses 50).

# `account_sign`

```elixir
@spec account_sign(binary(), iodata()) :: binary()
```

Sign `message` with the account's Ed25519 key.

# `curve25519_ecdh`

```elixir
@spec curve25519_ecdh(binary(), binary()) :: binary()
```

Perform an X25519 Diffie-Hellman between our base64-encoded secret
key and a peer's base64-encoded public key. Returns the 32-byte
shared secret (raw bytes), ready to feed into HKDF.

# `curve25519_keypair_new`

```elixir
@spec curve25519_keypair_new() :: {secret_b64 :: binary(), public_b64 :: binary()}
```

Generate a fresh Curve25519 keypair. Returns `{secret_b64, public_b64}`.
Used by the Megolm key backup (phase 5c) where the public key
encrypts every stored session under an ephemeral-static ECDH wrap.

# `ed25519_keypair_new`

```elixir
@spec ed25519_keypair_new() :: {secret_b64 :: binary(), public_b64 :: binary()}
```

Generate a fresh Ed25519 keypair. Returns
`{secret_b64, public_b64}` — base64 strings ready to embed in
Matrix cross-signing payloads. The caller is responsible for
persisting (and ideally encrypting) the secret.

# `ed25519_public_key`

```elixir
@spec ed25519_public_key(binary()) :: binary()
```

Recover the base64 public key from a base64 Ed25519 secret key.

# `ed25519_sign`

```elixir
@spec ed25519_sign(binary(), iodata()) :: binary()
```

Sign `message` with the given base64 Ed25519 secret key.

# `inbound_group_session_create`

```elixir
@spec inbound_group_session_create(binary()) ::
  {session_id :: binary(), pickle :: binary()}
```

Build an `InboundGroupSession` from the base64 `session_key` field
of an `m.room_key` to-device event. Returns the session id (which
matches the timeline event's `session_id`) and the pickle to
persist.

# `inbound_group_session_decrypt`

```elixir
@spec inbound_group_session_decrypt(binary(), binary()) ::
  {plaintext :: binary(), message_index :: non_neg_integer(),
   new_pickle :: binary()}
```

Decrypt a Megolm timeline ciphertext. Returns plaintext, the
message index for replay defence, and the updated pickle (the
session ratchets internal state on decrypt).

# `inbound_group_session_id`

```elixir
@spec inbound_group_session_id(binary()) :: binary()
```

Return the session id of an inbound Megolm group session. Same id
that the original sender embeds in `m.room.encrypted` events; use
it to look up the correct inbound session per incoming event.

# `olm_session_create_inbound`

```elixir
@spec olm_session_create_inbound(binary(), binary(), binary()) ::
  {plaintext :: binary(), session_id :: binary(), session_pickle :: binary(),
   new_account_pickle :: binary()}
```

Decrypt an inbound *pre-key* (type 0) Olm message, establishing the
inbound session in the process. Returns plaintext, the session id,
the session pickle to persist, and the updated account pickle.

# `olm_session_create_outbound`

```elixir
@spec olm_session_create_outbound(binary(), binary(), binary()) ::
  {session_id :: binary(), session_pickle :: binary(),
   new_account_pickle :: binary()}
```

Establish an outbound Olm session against a peer device. Returns
the session id, the session pickle, and a new account pickle (the
account's internal state advances).

# `olm_session_decrypt`

```elixir
@spec olm_session_decrypt(binary(), 0 | 1, binary()) ::
  {plaintext :: binary(), new_pickle :: binary()}
```

Decrypt with an existing Olm session.

# `olm_session_encrypt`

```elixir
@spec olm_session_encrypt(binary(), iodata()) ::
  {message_type :: 0 | 1, ciphertext_b64 :: binary(), new_pickle :: binary()}
```

Encrypt with an existing Olm session. Returns `{type, ciphertext, new_pickle}`.

# `olm_session_id`

```elixir
@spec olm_session_id(binary()) :: binary()
```

Return the session id of an Olm pairwise session. Both ends of a
successfully-established session derive the same id, so callers can
use it as a dedupe key when multiple pre-key messages race.

# `outbound_group_session_create`

```elixir
@spec outbound_group_session_create() ::
  {session_id :: binary(), session_key :: binary(), pickle :: binary()}
```

Create a fresh outbound group session. Returns the session id, the
base64 `session_key` (to be wrapped in `m.room_key` events and
shared with peers), and the pickle.

# `outbound_group_session_encrypt`

```elixir
@spec outbound_group_session_encrypt(binary(), iodata()) ::
  {ciphertext_b64 :: binary(), new_pickle :: binary()}
```

Encrypt `plaintext` with the outbound group session.

# `outbound_group_session_id`

```elixir
@spec outbound_group_session_id(binary()) :: binary()
```

Return the session id of an outbound Megolm group session. This is
the value to publish in the `session_id` field of every
`m.room.encrypted` event you send under this session so that
recipients can match it to the corresponding inbound session.

# `outbound_group_session_key`

```elixir
@spec outbound_group_session_key(binary()) :: binary()
```

Extract the current Megolm session key from an outbound session
pickle. Use the result as the `session_key` field of an
`m.room_key` to-device payload.

# `outbound_group_session_message_index`

```elixir
@spec outbound_group_session_message_index(binary()) :: non_neg_integer()
```

Index of the next message the outbound session would encrypt
(i.e. how many ratchet steps have been taken). Used to record
the starting index when uploading a session to server-side key
backup; receivers can only decrypt messages at or after the
index they were keyed at.

# `sas_bytes`

```elixir
@spec sas_bytes(reference(), binary()) ::
  {[non_neg_integer()],
   {non_neg_integer(), non_neg_integer(), non_neg_integer()}}
```

Derive the SAS bytes for the given info string. Returns the seven
emoji indices (0–63 each) and a 3-tuple of decimals to show
alongside.

# `sas_calculate_mac`

```elixir
@spec sas_calculate_mac(reference(), binary(), binary()) :: binary()
```

Compute a base64 MAC over `input` with the given `info` string.

# `sas_diffie_hellman`

```elixir
@spec sas_diffie_hellman(reference(), binary()) :: reference()
```

Perform ECDH with the peer's curve25519 public key. Consumes the
pre-DH handle (any subsequent use returns `:sas_consumed`) and
produces an `established_sas` handle.

# `sas_new`

```elixir
@spec sas_new() :: {reference(), binary()}
```

Create a fresh SAS instance with an ephemeral curve25519 keypair.
Returns `{handle, public_key_b64}` — the handle is an opaque
Rust-side reference; persist nothing about it, just thread it
through subsequent SAS calls in the same BEAM process.

# `sas_verify_mac`

```elixir
@spec sas_verify_mac(reference(), binary(), binary(), binary()) ::
  :ok | {:error, :bad_message}
```

Verify a peer-computed MAC. Returns `:ok` or `{:error, :bad_message}`.

# `verify_ed25519`

```elixir
@spec verify_ed25519(binary(), iodata(), binary()) :: :ok | {:error, :bad_message}
```

Verify an Ed25519 signature.

---

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