Signal-Android reproducible build differences

Signal-Android reproducible build differences

BarbossHack

2026/07/01


Starting from v8.10.2 and continuing through the latest version v8.16.1, Signal-Android is no longer reproducible. I reported the issue in #14809 and provided a fix for the resources.arsc comparison in #14828 . You can view the logs of the latest reproducible check here: https://github.com/BarbossHack/reproducible/actions/runs/28214319158


However, the remaining differences are harmless, and the Signal app can still be trusted.



➡️ AndroidManifest.xml

The AndroidManifest.xml packaged in the APK from the Play Store contains additional meta-data entries: com.android.stamp.source, com.android.stamp.type, com.android.vending.derived.apk.id

AndroidManifest.xml

These are Google Play distribution stamps.

When Signal uploads an Android App Bundle (not an APK, an AAB) to the Play Store, Google automatically:

  • Generates optimized APKs (splits) for different devices.
  • Adds these "stamp" tags to mark the APK as officially distributed by Google Play.

It is not a security concern for the following reasons:

  • These tags are purely informational, they contain no executable code.
  • They are added by Google after the app is built, during the distribution phase.

In fact, I will probably open a new MR to ignore these tags in the Signal apkdiff script. Some of them (the "split" ones) are already ignored.


➡️ classes3.dex

A DEX file contains the compiled Java/Kotlin code of the app, converted into Dalvik Executable (DEX).

Here, classes3.dex may currently contain tiny differences. We can see that the Play Store APK is missing some Kotlin annotations in the "Lottie" library (an Airbnb framework for playing Adobe After Effect animations on mobile).

classes3.dex

Why do these generic-signature annotations exist in the first place? Generics get erased at the bytecode level (Function1<? super LottieFrameInfo<Object>, Object> becomes just Function1 in the real method signature), so the compiler stores the original generic type string here purely so reflection tools, IDEs, or kotlin-reflect can recover it later. ART never reads it while executing code. These differences may come from an R8 non-determinism issue or a transitive library version drifting between build environments.

It is not a security concern for the following reasons:

  • Only non-executed annotations differ, so nothing about runtime behavior changed. At worst, code that calls kotlin-reflect on this internal synthetic class (nobody does) would see less type info.
  • I have run the reproducible script many times on the same Signal versions, and these annotations are not always added. Sometimes there are no differences at all in classes3.dex, while other times the annotations appear, even for the exact same Signal version.


➡️ assets/dexopt/baseline.prof

The baseline.prof file contains a compact, binary list of references of the most important methods and classes in the app. It tells ART which methods and classes are important to precompile (AOT) when installing the app, in order to speed up cold start time.

It is not a security concern for the following reasons:

  • The file only contains method/class references. It has no executable code.
  • It cannot change the app’s logic or behavior, it only affects how fast some code runs.
  • This is a consequences of the differences in classes3.dex: slight variations in .dex files can cause the profile compiler (profgen) to produce a slightly different binary baseline.prof. When there are no differences in classes3.dex, baseline.prof is reproducible.



Reproducible builds are important: don't trust, verify for yourself.



Report Page