libsignal Documentation

Home to the Signal Protocol as well as other cryptographic primitives which make Signal possible.

5,939 stars Rust Rust 1 file ยท ~7,341 tokens
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy libsignal on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name libsignal --restart always signalapp/libsignal:latest

File: README.md

Overview

libsignal contains platform-agnostic APIs used by the official Signal clients and servers, exposed
as a Java, Swift, or TypeScript library. The underlying implementations are written in Rust:

  • libsignal-protocol: Implements the Signal protocol, including the Double Ratchet algorithm. A
    replacement for libsignal-protocol-java and libsignal-metadata-java.
  • signal-crypto: Cryptographic primitives such as AES-GCM. We use RustCrypto's where we can
    but sometimes have differing needs.
  • device-transfer: Support logic for Signal's device-to-device transfer feature.
  • attest: Functionality for remote attestation of SGX enclaves and server-side HSMs.
  • zkgroup: Functionality for zero-knowledge groups and related features available in Signal.
  • zkcredential: An abstraction for the sort of zero-knowledge credentials used by zkgroup, based on the paper "The Signal Private Group System" by Chase, Perrin, and Zaverucha.
  • poksho: Utilities for implementing zero-knowledge proofs (such as those used by zkgroup); stands for "proof-of-knowledge, stateful-hash-object".
  • account-keys: Functionality for consistently using PINs as passwords in Signal's Secure Value Recovery system, as well as other account-wide key operations.
  • usernames: Functionality for username generation, hashing, and proofs.
  • media: Utilities for manipulating media.

This repository is used by the Signal client apps (Android, iOS, and Desktop) as well as
server-side. Use outside of Signal is unsupported. In particular, the products of this repository
are the Java, Swift, and TypeScript libraries that wrap the underlying Rust implementations. All
APIs and implementations are subject to change without notice, as are the JNI, C, and Node add-on
"bridge" layers. However, backwards-incompatible changes to the Java, Swift, TypeScript, and
non-bridge Rust APIs will be reflected in the version number on a best-effort basis, including
increases to the minimum supported tools versions.

Building

Toolchain Installation

To build anything in this repository you must have Rust installed, as well
as recent versions of Clang, libclang, CMake, Make, protoc, Python (3.9+), and git.

Linux/Debian

On a Debian-like system, you can get these extra dependencies through apt:

shell
$ apt-get install clang libclang-dev cmake make protobuf-compiler libprotobuf-dev python3 git

macOS

On macOS, we have a best-effort maintained script to set up the Rust toolchain you can run by:

shell
$ bin/mac_setup.sh

Rust

First Build and Test

The build currently uses a specific version of the Rust nightly compiler, which
will be downloaded automatically by cargo. To build and test the basic protocol
libraries:

shell
$ cargo build
...
$ cargo test
...

Additional Rust Tools

The basic tools above should get you set up for most libsignal Rust development.

Eventually, you may find that you need some additional Rust tools like taplo for code formatting.

You should always install any Rust tools you need that may affect the build from cargo rather than from your system
package manager (e.g. apt or brew). Package managers sometimes contain outdated versions of these tools that can break
the build with incompatibility issues (especially cbindgen).

To install the main Rust extra dependencies matching the versions we use, you can run the following commands:

shell
$ cargo +stable install --version "$(cat acknowledgments/cargo-about-version)" --locked cargo-about
$ cargo +stable install --version "$(cat .taplo-cli-version)" --locked taplo-cli
$ cargo +stable install cargo-fuzz

Java/Android

Toolchain Setup / Configuration

To build for Android you must install several additional packages including a JDK,
the Android NDK/SDK, and add the Android targets to the Rust compiler, using

rustup target add armv7-linux-androideabi aarch64-linux-android i686-linux-android x86_64-linux-android

Our officially supported JDK version for Android builds is JDK 21, so be sure to install e.g. OpenJDK 21, and then point JAVA_HOME to it.

You can easily do this on macOS via:

shell
export JAVA_HOME=$(/usr/libexec/java_home -v 21)

On Linux, the way you do this varies by distribution. For Debian based distributions like Ubuntu, you can use:

shell
sudo update-alternatives --config java

We also check-in a .tools_version file for use with runtime version managers.

Building and Testing

To build the Java/Android jar and aar, and run the tests:

shell
$ cd java
$ ./gradlew test
$ ./gradlew build # if you need AAR outputs

You can pass -P debugLevelLogs to Gradle to build without filtering out debug- and verbose-level
logs from Rust, and -P jniTypeTagging to enable additional checks in the Rust JNI bridging code.

Alternately, a build system using Docker is available:

shell
$ cd java
$ make

When exposing new APIs to Java, you will need to run rust/bridge/jni/bin/gen_java_decl.py in
addition to rebuilding. This requires installing the cbindgen Rust tool, as detailed above.

Use as a library

Signal publishes Java packages for its own use, under the names org.signal:libsignal-server,
org.signal:libsignal-client, and org.signal:libsignal-android. libsignal-client and libsignal-server
contain native libraries for Debian-flavored x86_64 Linux as well as Windows (x86_64) and macOS
(x86_64 and arm64). libsignal-android contains native libraries for armeabi-v7a, arm64-v8a, x86, and
x86_64 Android. These are located in a Maven repository at
https://build-artifacts.signal.org/libraries/maven/; for use from Gradle, add the following to your
repositories block:

text
maven {
  name = "SignalBuildArtifacts"
  // The "uri()" part is only necessary for Kotlin Gradle; Groovy Gradle accepts a bare string here.
  url = uri("https://build-artifacts.signal.org/libraries/maven/")
}

Older builds were published to Maven Central instead.

When building for Android you need both libsignal-android and libsignal-client, but the Windows
and macOS libraries in libsignal-client won't automatically be excluded from your final app. You can
explicitly exclude them using packaging:

text
android {
  // ...
  packaging {
    resources {
      excludes += setOf("libsignal_jni*.dylib", "signal_jni*.dll")
    }
  }
  // ...
}

You can additionally exclude libsignal_jni_testing.so if you do not plan to use any of the APIs
intended for client testing.

Testing a local build with Signal-Android

The Signal-Android gradle.properties file has a commented-out line to include libsignal as part of the build. Uncomment that and adjust the path; optionally, you can restrict the architectures you want to build for by adding androidArchs=aarch64 to libsignal's gradle.properties. (The set of recognized architectures is in java/build_jni.sh.) If you're using an IDE, you'll need to re-import the Gradle structure at this point. When you're done, revert the changes to the Android app's gradle.properties and re-import once more.

Note that this does not import the Rust parts of the project into the IDE. Doing that in a multi-language IDE like IDEA is possible, but finicky; as of 2025 the most reliable way to do it is to open the Android project first, add the libsignal repo root directory as a Rust project second (only including the top-level directory), and only then make the changes to gradle.properties.

Swift

To learn about the Swift build process see swift/README.md

Node

You'll need Node installed to build. If you have nvm, you can run nvm use to select an
appropriate version automatically.

We use npm as our package manager, and a Python script to control building the Rust library, accessible as npm run build.

shell
$ cd node
$ nvm use
$ npm install
$ npm run build
$ npm run tsc
$ npm run test

When testing changes locally, you can use npm run build to do an incremental rebuild of the Rust library. Alternately, npm run build-with-debug-level-logs will rebuild without filtering out debug- and verbose-level logs.

When exposing new APIs to Node, you will need to run just generate-node in
addition to rebuilding.

NPM

Signal publishes the NPM package @signalapp/libsignal-client for its own use, including native
libraries for Windows, macOS, and Debian-flavored Linux. Both x64 and arm64 builds are included for
all three platforms, but the arm64 builds for Windows and Linux are considered experimental, since
there are no official builds of Signal for those architectures.

Testing a local build with Signal-Desktop

After running all the build commands above, adjust the @signalapp/libsignal-client dependency in the Desktop app's package.json to "link:path/to/libsignal/node" and run pnpm install. When you're done, revert the changes to package.json and run pnpm install again.

Contributions

Signal does accept external contributions to this project. However unless the change is
simple and easily understood, for example fixing a bug or portability issue, adding a new
test, or improving performance, first open an issue to discuss your intended change as not
all changes can be accepted.

Contributions that will not be used directly by one of Signal's official client apps may still be
considered, but only if they do not pose an undue maintenance burden or conflict with the goals of
the project.

Signing a CLA (Contributor License Agreement) is required for all contributions.

Code Formatting and Acknowledgments

You can run the styler on the entire project by running:

shell
just format-all

You can run more extensive tests as well as linters and clippy by running:

shell
just check-pre-commit

When making a PR that adjusts dependencies, you'll need to regenerate our acknowledgments files. See acknowledgments/README.md.

Cryptography Notice

This distribution includes cryptographic software. The country in which you currently reside may have restrictions on
the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption
software, please check your country's laws, regulations and policies concerning the import, possession, or use, and
re-export of encryption software, to see if this is permitted. See http://www.wassenaar.org/ for more information.

The U.S. Government Department of Commerce, Bureau of Industry and Security (BIS), has classified this software as
Export Commodity Control Number (ECCN) 5D002.C.1, which includes information security software using or performing
cryptographic functions with asymmetric algorithms. The form and manner of this distribution makes it eligible for
export under the License Exception ENC Technology Software Unrestricted (TSU) exception (see the BIS Export
Administration Regulations, Section 740.13) for both object code and source code.

License

Copyright 2020-2026 Signal Messenger, LLC

Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html


File: acknowledgments/README.md

This directory contains pre-generated acknowledgments for the Rust dependencies of libsignal. CI enforces that they are kept up to date.

Updating

If you update libsignal's dependencies, you'll need to update this listing. Install cargo-about if you haven't already:

shell
cargo +stable install --locked cargo-about --version $(cat acknowledgments/cargo-about-version)

Then:

  1. Run bin/regenerate_acknowledgments.sh.
  2. Check the HTML output for new "synthesized" entries. This can indicate that the license for a particular dependency was not properly detected.
  3. If there are any unaccounted-for "synthesized" entries, add new "clarify" entries to about.toml.

Apart from the projects in this very repo, there are a few other crates that unavoidably have "synthesized" licenses based on their Cargo manifests:

  • cesu8: Very old crate whose repository contains a license file for the Rust project itself, rather than the crate.
  • curve25519-dalek 4.1.3: used only as a dev-dependency; our real version has a proper license via about.toml
  • hpke-rs-*: Uploaded without a license file, though a license is listed in the Cargo.toml for each crate.

File: doc/src/backups/README.md

Backups

libsignal has a handful of APIs related to backups:

Account keys

Going forward, a number of account keys, including backup keys, will be derived from an account entropy pool, a 64-character alphanumeric ASCII string. While the AEP is represented as a plain String in libsignal APIs, methods to work with it can be found on AccountEntropyPool, including generation, validation, and derivation of other keys.

Derived from the account entropy pool is the BackupKey, a strongly-typed 32-byte blob that is used for all aspects of backups. There are many keys and identifiers that are derivable from a BackupKey.

Finally, the key specifically used to encrypt backup files is the MessageBackupKey, another strongly-typed object that consists of an HMAC key and an AES key for signing and encrypting the backup, respectively.

Backup validation

There are a few different APIs that work with backup files:

MessageBackup: Bulk validation of an encrypted backup file stream

Takes in an encrypted input stream and produces validation results. Hard errors are thrown as exceptions, soft errors (unknown fields) are returned for manual checking or logging.

Validation makes two passes over the stream to verify its contained MAC both before and after parsing, so the relevant APIs take a callback to produce streams rather than a single stream object. There is no guarantee that the first stream has been fully consumed before the second is produced, so do not reuse the same stream object.

Provided by:

  • MessageBackup in Java
  • validateMessageBackup in Swift
  • validate in the MessageBackup module in TypeScript

OnlineBackupValidator: Validation of a backup as it's being made

Feed frames into the validator to check them one by one, but don't forget to finalize the backup (close in Java, finalize in Swift and TypeScript) to run the end-of-file checks!

This is usually going to be faster than the bulk validation because it skips the decryption and decompression steps, but of course this also means the encryption and compression of the backup being created aren't tested.

Only logs soft errors rather than returning them in a manipulatable form.

ComparableBackup: "Canonicalization" of a backup for testing purposes

Takes an unencrypted backup as input and produces a JSON string as output, which can be formatted and diffed as JSON or just line-by-line. Don't forget to check for any unknown fields that get reported as well; they won't be included in the JSON and could invalidate any comparison.

Note that this format should not be considered stable (i.e. don't persist it or try to parse it). It's only intended for comparing two backups to each other; the usual way this is used is for an app to import Backup A and then immediately export its data as Backup B, then verify that the results are identical.

BackupAuthCredential

The BackupAuthCredential types follow the usual zkgroup construction from the client's perspective, similar to the other AuthCredential variants:

pseudocode
let requestContext = BackupAuthCredentialRequestContext.create(backupKey, aci)
let httpResponse = goRequestBackupAuthCredentialsFromServer(requestContext.getRequest())
for each response in httpResponse {
    let expectedRedemptionTime = start of each day
    let credential = requestContext.receive(response, expectedRedemptionTime, serverParams)
    log(credential.backupLevel, credential.type)
    goSaveCredential(credential, expectedRedemptionTime)
}
pseudocode
let presentation = credential.present(serverParams)
goDoSomeBackupOperation(presentation)

More information on these credentials in the client/server docs.


File: doc/src/README.md

Introduction

libsignal contains platform-agnostic APIs used by the official Signal clients and servers, exposed as a Java, Swift, or TypeScript library. The underlying implementations are written in Rust.

This documentation is meant primarily for developers working at Signal who will be calling the libsignal APIs, and secondarily for those who maintain libsignal itself. It's meant to be a high-level guide to what's available, and generally won't contain implementation details or detailed API-by-API descriptions. It's not meant to be any sort of promise or commitment across versions of the library.

That is, if you're outside of Signal, please don't read too much into these.

Viewing the book

First, install mdBook. Then, from the doc directory:

console
% mdbook serve
2025-01-21 18:05:27 [INFO] (mdbook::book): Book building has started
2025-01-21 18:05:27 [INFO] (mdbook::book): Running the html backend
2025-01-21 18:05:27 [INFO] (mdbook::cmd::serve): Serving on: http://localhost:3000
2025-01-21 18:05:27 [INFO] (mdbook::cmd::watch::poller): Watching for changes...
2025-01-21 18:05:27 [INFO] (warp::server): Server::run; addr=[::1]:3000
2025-01-21 18:05:27 [INFO] (warp::server): listening on http://[::1]:3000

Now you can open the URL listed (probably http://localhost:3000) and view the rendered book. This is also a "watch" mode, which is convenient when editing the book---just save and watch the page reload.


File: doc/src/SUMMARY.md

Summary

Introduction


File: rust/protocol/fuzz/README.md

This directory contains fuzz targets used with cargo fuzz.

text
// In the parent directory (rust/protocol)
cargo install cargo-fuzz
cargo fuzz list
cargo fuzz run <fuzz-target>

// If you find a crash
RUST_BACKTRACE=1 cargo fuzz run -D <fuzz-target> <crash-artifact>

For more information, including how to check the coverage of the explored corpus, see https://rust-fuzz.github.io.


File: rust/message-backup/README.md

This crate contains a validator for Signal's backup file format. Structurally, these files are an
encrypted series of length-delimited protos, following the rules described in backup.proto.

Updating backup.proto

  1. Install Rust and the other prerequisites as described in the top-level README.

  2. Copy in the new version of backup.proto.

  3. Run cargo test -p libsignal-message-backup to find out what needs updating.

  4. Fix all errors by copying from the logic for similar fields.

    • This will be easier if you have a Rust-capable IDE, like VS Code with the rust-analyzer extension, or RustRover / IDEA with the Rust plugin.

    • It's okay to not do any validation of new fields right away; just make sure they're populated in the corresponding Rust type.

    • Usually you'll change two files: the file that parses and validates the protobuf, and the scrambler file that walks the protobuf message tree.

    When the above command runs cleanly, you're done.

  5. Mention the change in the upcoming release notes. This can be pretty simple. If there are other backup updates, group them together.

  6. Open a PR and let a libsignal maintainer know what validation should be added later, if any.

Implementation Details

The crate is used in some specific ways that have led to unusual design decisions, particularly concerning the model types in the backup submodule:

  • We use the protobuf-rs crate instead of the more popular, better-supported, and used-elsewhere-in-libsignal prost because prost doesn't support doing anything with unknown fields. (We just want to report them, we don't need to store them in every message. But even that doesn't seem interesting to the mainline prost folks. See https://github.com/tokio-rs/prost/issues/2.)

  • The model types roughly parallel the protobuf message types, but they use stronger types for their fields, representing the stronger constraints of a valid backup. For example, a contact's ACI will be represented using libsignal_core::Aci rather than a plain protobuf bytes (Vec in Rust).

  • Since we want to be sure we don't ignore validity constraints, we don't use .. when matching the protobuf struct types; we always want to explicitly enumerate all the fields. Similarly, since we want the model types to always represent validated data, even elsewhere in the crate, we often include a non-public _limit_construction_to_module member, forcing construction to go through the conversion from a protobuf message or reuse deliberately-constructed test data (even if they modify the value after).

  • The client apps do a lightweight "validation" whenever they generate backups, but they also want to do a full deserialization in their tests (mostly using the "comparable" form described below). Therefore, many of the types nearer the root of the model are parameterized with a Method; the two methods are Store and ValidateOnly, where the latter minimizes memory use by throwing out most of what it reads once it's been validated.

  • The model types implement serde::Serialize, but are deliberately not trying to be a fully value-preserving, round-trip mechanism. Rather, the Serialized form represents a "comparable" form, one that has gone through some canonicalization, so that disparate backup implementations can still be compared. A restore implementation should aim to satisfy serialize(backup) == serialize(export(restoreFrom(backup))).

    This is exposed to the client apps using pretty-printed JSON, since getting good output from a structural diff algorithm is hard and the goal should be "no differences" anyway.

    (The fully value-preserving, round-trip mechanism for serializing a backup is to keep it in the pre-validated protobuf form.)

Updating the test data

Sometimes, when introducing new required fields, test data will need to be updated. Most of the ".jsonproto" files can be updated automatically by setting an OVERWRITE_EXPECTED_DATA=1 environment variable prior to running the tests.

This, however, is not the case for the tests in test-cases/ folder. ".jsonproto" files there will need to be updated manually.

If you find yourself needing to update a ".binproto" file, use the combination of "json_to_binproto" and "binproto_to_json" tools located in the examples/ folder.

In the worst case, when you need to update the ".binproto.encrypted" files, use the "encrypt_backup" tool providing it "--hmac-key" and "--aes-key" from the test output as well as the "--iv 49494949494949494949494949494949" (which corresponds to 16 characters 'I' for IV). BE AWARE that encrypt_backup tool will take ANY input you provide. Tests expect a serialized protobuf bytes (a.k.a "binproto") to be fed into encrypt_data in order to produce ".binproto.encrypted" files.


File: rust/bridge/README.md

Overview

In order for libsignal to be used from Java, Swift, and TypeScript, the Rust
code must be exposed, or "bridged" to those languages with compatible entry
points. That's the purpose of the crates in this directory.

Bridged function definitions

libsignal-bridge defines functions that are callable from Java,
Swift, and TypeScript. These definitions make use of the #[bridge_fn] and
#[bridge_io] macros to provide the language-specific glue code that makes the
functions visible from other languages.

libsignal-bridge-macros is a proc-macro crate that
defines the #[bridge_fn] and #[bridge_io] macros. Attaching one of these
macros to a function definition causes the macro to emit #[cfg]-guarded entry
point for each supported language. These entry points, before calling the actual
annotated Rust function, perform functionality like language-specific parsing
and conversion of input arguments, panic trapping, and
spawning of tasks for async functions. They also handle return value
conversion and language-specific error handling.

The macros attach any needed code to the bridged functions to enable
language-specific mechanisms (more below) to
identify and collect the entry points and make them callable.

Bridged types

The libsignal-bridge-types crate defines traits and
impls that reduce the amount of boilerplate code required to write bridged
functions. These generally fall into three categories:

  1. type conversions
  2. runtime support
  3. language utilities

Type conversions

Input arguments are converted from their language-native form into Rust types
using the ArgTypeInfo traits defined for each language in the per-language
submodule (libsignal_bridge_types::ffi, libsignal_bridge_types::jni, and
libsignal_bridge_types::node). To make a type usable as an argument to a
function annotated with #[bridge_fn] or #[bridge_io], the three
ArgTypeInfo traits need to be implemented for that type, either directly or
indirectly. The type also needs to be added to the ffi_arg_type,
jni_arg_type, and node_arg_type macros.

Returned types are treated similarly, except that the language-specific traits
are named ResultTypeInfo, and the macros are ffi_result_type and similar.

Runtime support

Some Rust features aren't exposed directly to other languages, but instead
require some runtime support. As an example, when #[bridge_fn] is attached to
an async function, the exposed entry point takes as an additional argument the
async runtime to be used. libsignal_bridge_types defines traits that support
this.

Language utilities

Some languages require additional code to support bridged functions. The jni
module, for example, defines a function to be invoked when the library is loaded
that caches a class loader for use during later return type conversions. The
node module defines a function that registers a sequence of annotated
functions with a JavaScript runtime.

Bridged function collection

To make the function entry points generated by the #[bridge_fn] and
#[bridge_io] macros visible to the target languages, the functions must be
collected and exposed. The mechanism for this is language-specific, but relies
on annotations attached to the entry points.

For Swift, and Java, cbindgen is used to process the
libsignal-ffi and libsignal-jni crates to produce a list
of function prototypes. For Java, these prototypes are munged by the
gen_java_decl.py and interpolated into the
Native.java.in file to produce a self-contained
class definition. For Swift, the cbindgen output is saved directly to a
C-style .h file that the Swift toolchain can consume.

For TypeScript, the libsignal-node crate is expanded and processed by
libsignal-node-native_ts and the output is interpolated into
Native.ts.in. The output, however, only
declares the function signatures; to make them accessible to the JavaScript
runtime, additional machinery is used. This takes the form of #[linkme]
annotations on to the generated entry points; the linkme crate is used to
collect all these entry points at link time for explicit registration at
runtime.