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
- `SignalProtocol` -- keygen, X3DH, Double Ratchet, PreKeySignalMessage
- `SignalProtocol.PreKeyBundle` -- bundle encode / decode / signature check
## Quick start
```elixir
# 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 the public halves)
{:ok, {opk_id, opk_pub, opk_priv}} = SignalProtocol.generate_pre_key(1)
{:ok, {spk_id, spk_pub, spk_priv, spk_sig}} = SignalProtocol.generate_signed_pre_key(bob_priv, 2)
# Keep opk_priv and spk_priv: process_pre_key_bundle_bob/5 needs them.
```
X3DH against a remote bundle. `PreKeyBundle.encode/1` produces the exact binary the NIF consumes -- `identity_pub(32) || signed_pre_key_pub(32) || signature(64)`, plus an optional trailing 32-byte one-time pre-key. The signature is Ed25519 over `signed_pre_key_pub` under the identity key.
```elixir
alias SignalProtocol.PreKeyBundle
{:ok, bundle} =
PreKeyBundle.encode(%PreKeyBundle{
identity_key: bob_pub,
signed_pre_key: spk_pub,
signature: spk_sig,
one_time_pre_key: opk_pub
})
{: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.