Packages
High-performance Elixir wrapper for Signal Protocol cryptographic primitives with libsodium. Provides idiomatic APIs for key generation, digital signatures, encryption, and session management.
Current section
Files
Jump to
Current section
Files
libsignal_protocol
README.md
README.md
# libsignal_protocol
[](https://hex.pm/packages/libsignal_protocol)
[](https://hex.pm/packages/libsignal_protocol)
[](LICENSE)
Elixir wrapper over `:libsignal_protocol_nif` -- the Erlang NIF in the [parent repo](https://github.com/Hydepwns/libsignal-protocol-nif). The wrapper is a thin facade: it forwards calls to the NIF and returns whatever the NIF returns (`{:ok, term} | {:error, atom}`).
The NIF is not built by this package. Either depend on the parent project's build artifacts or run `make build` in the repo root first. A missing NIF raises `UndefinedFunctionError` at the call site.
## Install
```elixir
def deps do
[{:libsignal_protocol, "~> 0.2"}]
end
```
## Modules
- `LibsignalProtocol` -- `init/0`, `generate_identity_key_pair/0`, `create_session/2`
- `SignalProtocol` -- keygen, X3DH, Double Ratchet, PreKeySignalMessage
- `SignalProtocol.PreKeyBundle` -- bundle serialize / parse / verify
## Quick start
```elixir
:ok = LibsignalProtocol.init()
# Each party generates an identity key pair (Ed25519)
{:ok, {alice_pub, alice_priv}} = SignalProtocol.generate_identity_key_pair()
{:ok, {bob_pub, bob_priv}} = SignalProtocol.generate_identity_key_pair()
# Pre-keys (Bob publishes these)
{:ok, {opk_id, opk_pub}} = SignalProtocol.generate_pre_key(1)
{:ok, {spk_id, spk_pub, spk_sig}} = SignalProtocol.generate_signed_pre_key(bob_priv, 2)
```
X3DH against a remote bundle. The bundle binary is `remote_identity_pub(32) ++ signed_prekey_pub(32) ++ signature(64)` with an optional trailing 32-byte one-time prekey. The signature is Ed25519 over `signed_prekey_pub` under the remote identity key.
```elixir
bundle = <<bob_pub::binary, spk_pub::binary, spk_sig::binary, opk_pub::binary>>
{:ok, {shared_secret, alice_eph_pub}} =
SignalProtocol.process_pre_key_bundle(alice_priv, bundle)
```
Double Ratchet:
```elixir
# Alice is the initiator -> is_alice = 1
{:ok, alice_dr} =
SignalProtocol.init_double_ratchet(shared_secret, alice_pub, bob_pub, <<>>, 1)
# Alice's first message is wrapped in a PreKeySignalMessage envelope
pre_key_info = {_reg_id = 1, opk_id, spk_id, alice_eph_pub}
{:ok, {wire, alice_dr}} =
SignalProtocol.dr_encrypt_prekey(alice_dr, "hello", pre_key_info)
# Subsequent messages
{:ok, {ct, alice_dr}} = SignalProtocol.dr_encrypt_message(alice_dr, "second")
```
Bob recovers the same shared secret from the envelope:
```elixir
{:ok, {_reg, _base, _id, opk_id, spk_id, inner}} = SignalProtocol.pksm_decode(wire)
{:ok, bob_shared} =
SignalProtocol.process_pre_key_bundle_bob(
bob_priv, spk_priv, opk_priv, alice_pub, alice_eph_pub
)
# is_alice = 0 for Bob
{:ok, bob_dr} =
SignalProtocol.init_double_ratchet(bob_shared, bob_pub, alice_pub, bob_priv, 0)
{:ok, {"hello", bob_dr}} = SignalProtocol.dr_decrypt_message(bob_dr, inner)
```
Full inline docs live in `lib/signal_protocol.ex`. End-to-end test flows are in the parent repo under `test/erl/unit/protocol/`.
## Pre-key bundles
`SignalProtocol.PreKeyBundle` handles the higher-level bundle format used between parties (versioned, includes key IDs):
```elixir
{:ok, bundle} =
SignalProtocol.PreKeyBundle.create(
registration_id, identity_key, {opk_id, opk_pub},
{spk_id, spk_pub, spk_sig}, base_key
)
{:ok, parsed} = SignalProtocol.PreKeyBundle.parse(bundle)
:ok = SignalProtocol.PreKeyBundle.verify_signature(bundle)
```
## Errors
NIF errors are returned verbatim. The wrapper does not translate, rename, or wrap them. If the NIF returns `{:error, :invalid_signature}`, that is what you get.
## License
Apache-2.0.