MetaMask / MetaMask/metamask-mobile

Reproducible-build check of v8.6.0: builds cleanly from source, artifacts differ

Open
#34,806 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

external-contributor ta-triaged
Dominant language
TypeScript
Stars
3k
Forks
1.7k
Avg merge
1d 14h
Merged PRs (30d)
669

Description

What is this about?

Who we are

Hi MetaMask team — we're from WalletScrutiny, a project that
independently checks whether cryptocurrency wallets can be rebuilt from their published source
and compared against the binaries users actually install. We do this across Android, iOS,
desktop and hardware wallets, and we publish results either way.

This is informational. We're not reporting a bug and we're not asking for any work — we just
think you should see our results directly rather than only reading them on our site.

What we did

We built MetaMask Android v8.6.0 from tag v8.6.0
(commit 4f0d82c75de7acca97cfd68dd34e2c0ed9c386c7) in a clean container and compared the
resulting split APKs against the three official Google Play splits.

The build succeeded: ./gradlew bundleProdRelease completed in 37m 2s across 3390 Gradle
tasks and produced an AAB, from which we extracted splits with bundletool.

That's worth stating plainly. The last version we attempted, v7.76.0 back in May, would not
build from source for us at all. For 8.6.0 the only obstacles were on our side: matching your
declared toolchain, and supplying a signing keystore, since signingConfigs.mainProd expects
one that isn't in the public repo. Once both were handled the build went through cleanly.

We used the documented setup mechanics: .js.env and .android.env created from their committed
.example files, and android/app/google-services.json generated via the
GOOGLE_SERVICES_B64_ANDROID path described in the README's Firebase Messaging Setup section.

Two prerequisites we did not fulfil, and should be upfront about: the README tells external
contributors to obtain an Infura API key and set MM_INFURA_PROJECT_ID, which we left at the
example file's null; and it says to supply your own Firebase project configuration, whereas we
passed a fixed configuration of our own through the documented base64 mechanism. So this is not a
complete external-contributor setup as written.

Environment used:

Component Version Source
React Native 0.83.6 package.json
Node 24.16.0 package.json engines / .nvmrc
Yarn 4.14.1 packageManager
compileSdk / targetSdk 36 android/build.gradle
build-tools 36.0.0 android/build.gradle
NDK 27.1.12297006 android/build.gradle
Gradle 8.14.3 wrapper
JDK 17 our container

What we found

The built splits do not match the official ones. Counts below are differing lines from a
recursive diff of the unzipped APKs, excluding root-level META-INF/:

Split Differences
base.apk 424
split_config.arm64_v8a.apk 24
split_config.xxhdpi.apk 4

Where they sit:

  • base.apk — 363 compiled resource files under res/, resources.arsc,
    AndroidManifest.xml, all four DEX files, assets/index.android.bundle, the baseline
    profiles, assets/app.config, assets/app.manifest, the Sentry asset files, and several
    META-INF/services entries.
  • split_config.arm64_v8a.apk — all 22 bundled native libraries differ, plus
    AndroidManifest.xml.
  • split_config.xxhdpi.apkAndroidManifest.xml, resources.arsc, and one drawable.

Some files appear in the official APKs but not in ours. Ten are Google Wallet / tap-and-pay
resources (add_to_googlewallet_button.xml, tokenization_fragment.xml,
tp_issuer_progress.xml and similar), plus play-services-tapandpay.properties, a Google Pay
drawable in the xxhdpi split, META-INF/services/rg.H and META-INF/services/wg.s, and
stamp-cert-sha256 in all three splits.

We have not determined why any of these differ. We're reporting observations, not a
diagnosis. One thing we can state precisely rather than guess at: the split APKs we compared are
unsigned, because we extract them with bundletool without passing a keystore, so signature
material was excluded from the counts above. Everything else we simply haven't traced, and we'd
rather say so than speculate in your issue tracker.

Why we're posting this

Mainly so it isn't a surprise if you come across our listing.

If any of it is useful to you, we're glad. If some of these differences have obvious
explanations from your side, we'd genuinely like to know — it would improve our reporting, and
we'll correct anything we've got wrong.

We're not asking you to make the build byte-for-byte reproducible. That's real engineering
effort and whether it's worth it is your call. We'd only note that reproducibility is a
meaningful security property for a self-custody wallet: it lets anyone confirm the app in the
Play Store was built from the source you published, without having to take anyone's word for it.
If it's ever something you want to pursue, we're happy to help however we can.

For reference

Our verification is scripted and containerized: the build itself runs entirely inside a
container, so beyond Docker or Podman it needs only a shell, network access, and a copy of the
official APKs to compare against. Happy to share the script or walk through the comparison
method if that's useful.

Thanks for reading, and thanks for keeping the source open.

Related issues

https://github.com/MetaMask/metamask-mobile/issues/30428

Scenario

No response

Design

No response

Technical Details

No response

Threat Modeling Framework

No response

Acceptance Criteria

No response

Stakeholder review needed before the work gets merged
  • Engineering (needed in most cases)
  • Design
  • Product
  • QA (automation tests are required to pass before merging PRs but not all changes are covered by automation tests - please review if QA is needed beyond automation tests)
  • Security
  • Legal
  • Marketing
  • Management (please specify)
  • Other (please specify)
References

No response

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

This is an informational report, not a requested change: it names ./gradlew bundleProdRelease, tag v8.6.0, and the APK comparison workflow. Start by reviewing the documented setup and the cited files .js.env, .android.env, package.json, .nvmrc, and android/build.gradle; no acceptance criteria or definition of done is provided.

Written by the indexing model from the issue text.

Assessment

Tech stack
android, react-native, typescript
Domain
build-system, mobile, security
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.