Vodozemac (vodozemac v0.1.4)

Copy Markdown View Source

Elixir bindings to Matrix's Olm and Megolm cryptographic primitives, built on top of Element's vodozemac (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.

Summary

Functions

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

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

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

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

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

Sign message with the account's Ed25519 key.

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.

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.

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.

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

Sign message with the given base64 Ed25519 secret key.

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.

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

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.

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.

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).

Decrypt with an existing Olm session.

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

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.

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.

Encrypt plaintext with the outbound group session.

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.

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.

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.

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.

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

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.

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.

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

Functions

account_create()

@spec account_create() :: binary()

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

account_generate_one_time_keys(pickle, count)

@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(pickle)

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

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

account_mark_published(pickle)

@spec account_mark_published(binary()) :: binary()

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

account_max_one_time_keys(pickle)

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

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

account_sign(pickle, message)

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

Sign message with the account's Ed25519 key.

curve25519_ecdh(our_secret_b64, their_public_b64)

@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()

@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()

@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(secret_b64)

@spec ed25519_public_key(binary()) :: binary()

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

ed25519_sign(secret_b64, message)

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

Sign message with the given base64 Ed25519 secret key.

inbound_group_session_create(session_key_b64)

@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(pickle, ciphertext_b64)

@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(pickle)

@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(account_pickle, their_curve25519, ciphertext_b64)

@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(account_pickle, their_curve25519, their_one_time_key)

@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(pickle, message_type, ciphertext_b64)

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

Decrypt with an existing Olm session.

olm_session_encrypt(pickle, plaintext)

@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(pickle)

@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()

@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(pickle, plaintext)

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

Encrypt plaintext with the outbound group session.

outbound_group_session_id(pickle)

@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(pickle)

@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(pickle)

@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(established, info)

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(established, input, info)

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

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

sas_diffie_hellman(sas, their_public_b64)

@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()

@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(established, input, info, tag_b64)

@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(signer_ed25519_b64, message, signature_b64)

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

Verify an Ed25519 signature.