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:
$ apt-get install clang libclang-dev cmake make protobuf-compiler libprotobuf-dev python3 gitmacOS
On macOS, we have a best-effort maintained script to set up the Rust toolchain you can run by:
$ bin/mac_setup.shRust
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:
$ 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:
$ 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-fuzzJava/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:
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:
sudo update-alternatives --config javaWe 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:
$ cd java
$ ./gradlew test
$ ./gradlew build # if you need AAR outputsYou 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:
$ cd java
$ makeWhen 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 yourrepositories block:
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:
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.
$ cd node
$ nvm use
$ npm install
$ npm run build
$ npm run tsc
$ npm run testWhen 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:
just format-allYou can run more extensive tests as well as linters and clippy by running:
just check-pre-commitWhen making a PR that adjusts dependencies, you'll need to regenerate our acknowledgments files. See acknowledgments/README.md.
Legal things
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:
cargo +stable install --locked cargo-about --version $(cat acknowledgments/cargo-about-version)Then:
- Run
bin/regenerate_acknowledgments.sh. - Check the HTML output for new "synthesized" entries. This can indicate that the license for a particular dependency was not properly detected.
- 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:
MessageBackupin JavavalidateMessageBackupin Swiftvalidatein theMessageBackupmodule 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:
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)
}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:
% 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]:3000Now 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
File: rust/protocol/fuzz/README.md
This directory contains fuzz targets used with cargo fuzz.
// 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
Install Rust and the other prerequisites as described in the top-level README.
Copy in the new version of backup.proto.
Run
cargo test -p libsignal-message-backupto find out what needs updating.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.
Mention the change in the upcoming release notes. This can be pretty simple. If there are other backup updates, group them together.
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-rscrate instead of the more popular, better-supported, and used-elsewhere-in-libsignalprostbecauseprostdoesn'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::Acirather than a plain protobufbytes(Vecin 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_modulemember, 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 areStoreandValidateOnly, 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 satisfyserialize(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:
- type conversions
- runtime support
- 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, andlibsignal_bridge_types::node). To make a type usable as an argument to a
function annotated with #[bridge_fn] or #[bridge_io], the threeArgTypeInfo 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. Thenode 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 thelibsignal-ffi and libsignal-jni crates to produce a list
of function prototypes. For Java, these prototypes are munged by thegen_java_decl.py and interpolated into theNative.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 bylibsignal-node-native_ts and the output is interpolated intoNative.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.