libsignal-protocol-nif
Signal Protocol crypto for the BEAM. Erlang NIF, libsodium underneath, idiomatic wrappers for Elixir and Gleam. Three Hex packages ship from this repo:
libsignal_protocol_nif-- Erlang NIF +.erlstubslibsignal_protocol-- Elixir wrapperlibsignal_protocol_gleam-- Gleam wrapper
What's implemented
- Curve25519 ECDH, Ed25519 sign/verify
- AES-256-GCM, ChaCha20-Poly1305 (AEAD)
- SHA-256, SHA-512, HMAC-SHA256, HKDF-SHA-256
- X3DH key agreement (Alice + Bob sides)
- Double Ratchet with header encryption (DR-HE)
- PreKeySignalMessage envelope encode/decode
sodium_memzeroon sensitive scratch buffers
Linux and macOS (Apple Silicon and Intel) are the regularly tested targets. Windows builds are not in CI.
Build
git clone https://github.com/Hydepwns/libsignal-protocol-nif.git
cd libsignal-protocol-nif
nix-shell --run "make build"
nix-shell --run "make test-unit"
Without Nix you need libsodium and CMake on the path:
- macOS:
brew install libsodium cmake - Debian/Ubuntu:
sudo apt-get install libsodium-dev cmake build-essential
Toolchain: Erlang/OTP 26, Elixir 1.16, rebar 3.22. Exact versions pinned in .tool-versions.
make build writes two shared libraries to priv/: signal_nif.{so,dylib} (lower-level crypto) and libsignal_protocol_nif.{so,dylib} (sessions, X3DH, Double Ratchet).
Install
Erlang (rebar.config):
{deps, [{libsignal_protocol_nif, "0.2.0"}]}.
Elixir (mix.exs):
{:libsignal_protocol, "~> 0.2"}
Gleam (gleam.toml):
[dependencies]
libsignal_protocol_gleam = "~> 0.2"
The Hex package ships sources only. At consumer rebar3 compile time, c_src/build_nif.sh fetches a pre-built NIF tarball from the matching GitHub Release for the consumer's platform. Pre-built triplets:
aarch64-apple-darwin(macOS Apple Silicon)aarch64-unknown-linux-gnu(Linux ARM64)x86_64-unknown-linux-gnu(Linux x86_64)
For any other platform (including Intel Mac), or when the download fails, the script falls back to a cmake source build. That requires libsodium + OpenSSL development headers + cmake on the system. Set LIBSIGNAL_NIF_BUILD_FROM_SOURCE=1 to skip the download attempt and go straight to the source path.
Erlang example
{ok, {Pub, Priv}} = signal_nif:generate_ed25519_keypair(),
Msg = <<"hello">>,
{ok, Sig} = signal_nif:sign_data(Priv, Msg),
ok = signal_nif:verify_signature(Pub, Msg, Sig),
Key = crypto:strong_rand_bytes(32),
IV = crypto:strong_rand_bytes(12),
{ok, Ct, Tag} = signal_nif:aes_gcm_encrypt(Key, IV, Msg, <<>>, 16),
{ok, Msg} = signal_nif:aes_gcm_decrypt(Key, IV, Ct, <<>>, Tag, byte_size(Msg)).
For full X3DH + Double Ratchet flows see test/erl/unit/protocol/double_ratchet_SUITE.erl. Elixir and Gleam examples live in each wrapper's README.
Troubleshooting
{error, {load_failed, ...}}: run make build first and confirm priv/*.{so,dylib} exists. If only the default profile loads, check scripts/copy_nifs.sh -- the rebar3 post-compile hook fans NIFs out into _build/{test,unit+test}/lib/nif/priv/.
fatal error: sodium.h: No such file: install libsodium development headers.
macOS link issues: the Makefile sets DYLD_LIBRARY_PATH for the openssl@3 keg. Inspect with otool -L priv/signal_nif.so.
Docs
Contributor guide: CONTRIBUTING.md. Build and CI details: CLAUDE.md.
License
Apache-2.0. See LICENSE.