WalletScrutiny is an independent security research project that verifies whether Bitcoin and cryptocurrency wallets published on the Google Play Store can be reproduced from their public source code. Our goal is to give users confidence that the app they install matches what the developers published — and to surface cases where it doesn't. We publish our findings openly at walletscrutiny.com/android/com.cypherstack.stackwallet.
Summary
We attempted a reproducible build verification of Stack Wallet v2.4.4 (versionCode 301, Play Store distribution). The good news: 3 of 4 split APKs are reproducible from the public source after accounting for expected Play Store distribution artifacts. The split_config.arm64_v8a.apk is not reproducible due to differences in 10 native libraries, primarily for reasons we believe are fixable.
We're opening this issue to share our findings and ask a few questions that would help us (and anyone else) reproduce this build fully.
Results
| Split |
Result |
base.apk |
✅ Reproducible |
split_config.en.apk |
✅ Reproducible |
split_config.xxhdpi.apk |
✅ Reproducible |
split_config.arm64_v8a.apk |
❌ Not reproducible (10 native .so files differ) |
Play Store distribution artifacts (signing stamps, stamp-cert-sha256, resources.arsc packaging metadata, locale code normalization) were excluded as acceptable diffs.
Findings and Questions
1. Production API keys compiled into libapp.so
strings analysis of the official libapp.so shows production API keys compiled into the binary. When we build from the public source, scripts/prebuild.sh generates an empty lib/external_api_keys.dart stub, so the resulting libapp.so differs from the official binary. The binary alone does not tell us how or where the production keys were supplied at build time.
Request: Would it be possible to document the expected format of external_api_keys.dart, or provide a way for third-party verifiers to confirm the app logic is identical even without the production keys? One approach used by other projects is to publish a checksum of the app binary built without keys alongside a signed statement that the production build differs only in key values.
2. Source compatibility issue: flutter_riverpod 1.0.4 + Flutter 3.38.1 AOT
We were unable to build the app without a source patch. Under Flutter 3.38.1 strict AOT compilation, ConsumerState (from flutter_riverpod 1.0.4) does not provide a concrete activate() method required by SingleTickerProviderStateMixin. This causes a compile-time error in:
lib/pages_desktop_specific/desktop_menu_item.dart
lib/widgets/desktop/desktop_tor_status_button.dart
lib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dart
The error is:
Error: The class doesn't have a concrete implementation of the super-invoked member 'activate'.
We applied a workaround (an intermediate ConsumerStateWithMixinCompat base class), but this means our compiled libapp.so contains code not in the official binary. Do you use a different Flutter version for releases, or is there a different fix in your build pipeline? Any upstream fix here would help future verifications.
3. Release tag vs build commit
When inspecting the compiled libapp.so, the embedded commit hash does not match the build_301 tag (243015b450c941969a76b548c1a81da524534ffc, 2026-02-09). The embedded hash corresponds to an earlier commit from 2026-02-03 — approximately 6 days before the tag commit date (2026-02-09).
Question: Was the build_301 tag applied after the APK was built, rather than at the exact commit used for the release build? If so, is there a way to identify the exact commit that was used? For reproducibility purposes, it helps to be able to check out the exact state the binary was compiled from.
4. Build environment for releases
The official libapp.so contains an embedded source path that suggests the APK may have been produced from a local developer workstation rather than a CI environment. A reproducible build process ideally produces release APKs from a CI environment with a pinned, auditable toolchain — this makes it straightforward for third parties to reproduce the exact binary. Is there a documented or planned CI pipeline for release builds?
Build Environment We Used
For reference, here is the environment under which we achieved 3/4 reproducible splits:
| Component |
Version |
| Flutter |
3.38.1 (Dart SDK 3.10.0) |
| JDK |
OpenJDK 21 |
| Android NDK |
28.2.13676358 |
| Rust |
1.89.0 (default), 1.85.1 (flutter_libmwc) |
| Go |
1.24.13 |
| AGP |
8.11.1 |
| Gradle |
8.14 |
All crypto native libraries (frostdart, libepic_cash_wallet, libmwc_wallet, libxelis_flutter, libtor_ffi_plugin, libflutter_libsparkmobile, libmwebd, libsecp256k1) were compiled from source using the submodules pinned at build_301.
Our full verification report and script (stackwallet_build.sh) are available at WalletScrutiny.
Thanks for building an open-source wallet. We hope these findings are useful, and we're happy to answer any questions about our methodology.
— WalletScrutiny team
WalletScrutiny is an independent security research project that verifies whether Bitcoin and cryptocurrency wallets published on the Google Play Store can be reproduced from their public source code. Our goal is to give users confidence that the app they install matches what the developers published — and to surface cases where it doesn't. We publish our findings openly at walletscrutiny.com/android/com.cypherstack.stackwallet.
Summary
We attempted a reproducible build verification of Stack Wallet v2.4.4 (versionCode 301, Play Store distribution). The good news: 3 of 4 split APKs are reproducible from the public source after accounting for expected Play Store distribution artifacts. The
split_config.arm64_v8a.apkis not reproducible due to differences in 10 native libraries, primarily for reasons we believe are fixable.We're opening this issue to share our findings and ask a few questions that would help us (and anyone else) reproduce this build fully.
Results
base.apksplit_config.en.apksplit_config.xxhdpi.apksplit_config.arm64_v8a.apk.sofiles differ)Play Store distribution artifacts (signing stamps,
stamp-cert-sha256,resources.arscpackaging metadata, locale code normalization) were excluded as acceptable diffs.Findings and Questions
1. Production API keys compiled into
libapp.sostringsanalysis of the officiallibapp.soshows production API keys compiled into the binary. When we build from the public source,scripts/prebuild.shgenerates an emptylib/external_api_keys.dartstub, so the resultinglibapp.sodiffers from the official binary. The binary alone does not tell us how or where the production keys were supplied at build time.Request: Would it be possible to document the expected format of
external_api_keys.dart, or provide a way for third-party verifiers to confirm the app logic is identical even without the production keys? One approach used by other projects is to publish a checksum of the app binary built without keys alongside a signed statement that the production build differs only in key values.2. Source compatibility issue:
flutter_riverpod 1.0.4+ Flutter 3.38.1 AOTWe were unable to build the app without a source patch. Under Flutter 3.38.1 strict AOT compilation,
ConsumerState(fromflutter_riverpod 1.0.4) does not provide a concreteactivate()method required bySingleTickerProviderStateMixin. This causes a compile-time error in:lib/pages_desktop_specific/desktop_menu_item.dartlib/widgets/desktop/desktop_tor_status_button.dartlib/pages/settings_views/global_settings_view/tor_settings/tor_settings_view.dartThe error is:
We applied a workaround (an intermediate
ConsumerStateWithMixinCompatbase class), but this means our compiledlibapp.socontains code not in the official binary. Do you use a different Flutter version for releases, or is there a different fix in your build pipeline? Any upstream fix here would help future verifications.3. Release tag vs build commit
When inspecting the compiled
libapp.so, the embedded commit hash does not match thebuild_301tag (243015b450c941969a76b548c1a81da524534ffc, 2026-02-09). The embedded hash corresponds to an earlier commit from 2026-02-03 — approximately 6 days before the tag commit date (2026-02-09).Question: Was the
build_301tag applied after the APK was built, rather than at the exact commit used for the release build? If so, is there a way to identify the exact commit that was used? For reproducibility purposes, it helps to be able to check out the exact state the binary was compiled from.4. Build environment for releases
The official
libapp.socontains an embedded source path that suggests the APK may have been produced from a local developer workstation rather than a CI environment. A reproducible build process ideally produces release APKs from a CI environment with a pinned, auditable toolchain — this makes it straightforward for third parties to reproduce the exact binary. Is there a documented or planned CI pipeline for release builds?Build Environment We Used
For reference, here is the environment under which we achieved 3/4 reproducible splits:
All crypto native libraries (
frostdart,libepic_cash_wallet,libmwc_wallet,libxelis_flutter,libtor_ffi_plugin,libflutter_libsparkmobile,libmwebd,libsecp256k1) were compiled from source using the submodules pinned atbuild_301.Our full verification report and script (
stackwallet_build.sh) are available at WalletScrutiny.Thanks for building an open-source wallet. We hope these findings are useful, and we're happy to answer any questions about our methodology.
— WalletScrutiny team