# Vodozemac
Elixir bindings to Matrix's Olm and Megolm cryptographic primitives,
backed by Element's [vodozemac][1] (Rust) — the modern successor to
the deprecated [libolm][3].
```elixir
def deps do
[{:vodozemac, "~> 0.1"}]
end
```
> ⚠️ Pre-1.0. The surface is documented and tested, but signatures
> may shift before 1.0 — see the stability table below for the rows
> that are explicitly unstable.
## Scope
Olm and Megolm are the pairwise and group ratchets defined by the
[Matrix end-to-end encryption spec][2]. This library wraps **only**
those primitives. It does **not** speak HTTP, parse Matrix events,
or talk to Synapse. Callers are responsible for:
- talking to the homeserver (`/keys/upload`, `/keys/query`,
`/keys/claim`, `/sendToDevice`, `/sync`)
- routing `m.room_key` / `m.room.encrypted` events
- persisting the serialized session state this library returns
- key backup transport
That boundary is deliberate so the same library can power a thin
client SDK, a server-side chat backend, a CLI testing tool, or an
appservice — none of which agree on the rest of the stack.
## Quick start
```elixir
# Long-term identity for a device.
account = Vodozemac.account_create()
# Identity keys to publish to the server.
{curve25519, ed25519} = Vodozemac.account_identity_keys(account)
# Generate ten unpublished one-time keys for the pre-key bundle.
{account, otks} = Vodozemac.account_generate_one_time_keys(account, 10)
# After `/keys/upload` succeeds, mark them published so future
# generate calls return only NEW ones.
account = Vodozemac.account_mark_published(account)
# Establish an outbound Megolm session for a room.
{session_id, session_key, outbound} = Vodozemac.outbound_group_session_create()
{ciphertext, outbound} = Vodozemac.outbound_group_session_encrypt(outbound, "hello")
# The corresponding inbound session is built from the session_key the
# sender shares (typically wrapped in an Olm m.room_key to-device event).
{^session_id, inbound} = Vodozemac.inbound_group_session_create(session_key)
{:ok, "hello", _index, _inbound} =
Vodozemac.inbound_group_session_decrypt(inbound, ciphertext)
```
Every function that mutates state returns a fresh pickle. Callers
**must** persist the latest pickle — once you advance the ratchet,
the previous pickle can no longer decrypt the next message.
## Status of the surface
| Area | API | Stability |
| ----------------------------- | ------------------------------ | ------------- |
| Account (identity + OTKs) | `account_*` | stable |
| Olm pairwise sessions | `olm_*` | stable |
| Inbound Megolm group session | `inbound_group_session_*` | stable |
| Outbound Megolm group session | `outbound_group_session_*` | stable |
| Raw Ed25519 / Curve25519 | `ed25519_*` / `curve25519_*` | stable |
| SAS (emoji verification) | `sas_*` | stable |
| Pickle encryption key | (not exposed) | **unstable** |
The last row is the headline pre-1.0 gap: pickle bytes are wrapped
with vodozemac's default zero-key (no confidentiality at rest).
Callers must apply their own at-rest encryption layer until a
`pickle_key` parameter lands in a later release.
## Precompiled binaries
**0.1.0 is source-only.** Consumers need a Rust toolchain
(`rustup install stable`) on `mix deps.get`; the NIF compiles from
source via Rustler. Precompiled tarballs for the targets below
are scheduled for **0.1.1**:
| Target | Status |
| ---------------------------- | ------------------- |
| `aarch64-apple-darwin` | planned for 0.1.1 |
| `x86_64-apple-darwin` | planned for 0.1.1 |
| `aarch64-unknown-linux-gnu` | planned for 0.1.1 |
| `x86_64-unknown-linux-gnu` | planned for 0.1.1 |
| FreeBSD | planned |
Once 0.1.1 ships, the precompiled artifact for your target is
fetched automatically from the [sr.ht][src] release ref. Force a
local cargo build at any time with
`RUSTLER_PRECOMPILATION_FORCE_BUILD=1` before `mix deps.get`.
## Hacking
The repo lives at [https://git.sr.ht/~sbr/vodozemac][src]. Patches
welcome — send via `git send-email` to the [~sbr/public-inbox][list]
list, or open a ticket on the [tracker][trk].
```sh
git clone https://git.sr.ht/~sbr/vodozemac
cd vodozemac
mix deps.get
RUSTLER_PRECOMPILATION_FORCE_BUILD=1 mix compile
mix test
```
## License
The Elixir + Rust glue in this repository is licensed under
**BSD-3-Clause** — see [`LICENSE`](LICENSE).
The precompiled NIF binaries (the `.tar.gz` shipped to the sr.ht
release ref for each tagged version) statically link Element's
[vodozemac][1] (Apache-2.0) and a tree of transitive Rust crates.
Each tarball includes:
- `LICENSE` — this project's BSD-3-Clause text
- `LICENSE-APACHE` — full Apache-2.0 text covering vodozemac
- `THIRD-PARTY-NOTICES` — auto-generated inventory of every linked
crate with its license text (built via
[`cargo-about`](https://embarkstudios.github.io/cargo-about/))
The source-only Hex package does **not** bundle vodozemac — Cargo
resolves it from crates.io at build time on the consumer's machine —
so the Apache-2.0 redistribution obligation only applies to the
precompiled artifacts.
[1]: https://github.com/matrix-org/vodozemac
[2]: https://spec.matrix.org/v1.11/client-server-api/#end-to-end-encryption
[3]: https://matrix.org/blog/2024/08/libolm-deprecated/
[src]: https://git.sr.ht/~sbr/vodozemac
[list]: https://lists.sr.ht/~sbr/public-inbox
[trk]: https://todo.sr.ht/~sbr/vodozemac