This folder contains the tooling to rebuild tagged releases inside Docker and compare them to published APKs.
- Build from
/androidusing the Android-localDockerfileandreproducible/Dockerfile, while cloning the monorepo and building from/root/wallet/androidinside the container. - Base image publishing is done by the
Publish Base Imageworkflow to build/pushghcr.io/gemwalletcom/gem-android-base:<base-tag>(where<base-tag>lives inreproducible/base_image_tag.txt), forlinux/amd64andlinux/arm64. - Verification defaults to
ghcr.io/gemwalletcom/gem-android-base:<base-tag>and runs onlinux/amd64(override withVERIFY_DOCKER_PLATFORMif you must). - Require
local.propertiesfor GitHub packages; use the same base image used for releases. - Strip signing artifacts, patch map-id when present, then copy the official signing block onto the rebuilt APK with apksigcopier to confirm payload identity without exposing keys.
- Keep outputs under
artifacts/reproducible/<tag>/and only run diffoscope if hashes still differ after signature copy.
- Docker
- unzip, curl
- uv for tool installs, plus
apksigcopieranddiffoscope:uv tool install apksigcopier diffoscope - Android SDK build-tools
dexdumpfordiff_dexdump.py(e.g.,${ANDROID_HOME}/build-tools/<ver>/dexdump) - Tooling snapshot: Gradle wrapper 9.2.1, AGP 9.0.0, Kotlin/KSP from
gradle/libs.versions.toml; R8 is the AGP-bundled version (AGP still does not expose a public map-id seed/template flag).
- Auth + credentials:
- Ensure
local.propertieshas GitHub package creds (e.g.,gpr.username,gpr.token) and obtain the official APK path (or URL for CI). - Use the same token to log in to GHCR so the base image pull succeeds (otherwise it will rebuild locally). Example using
gpr.tokenfromlocal.properties:Or, with an exported token:grep gpr.token ../local.properties | cut -d'=' -f2- | docker login ghcr.io -u "$(grep gpr.username ../local.properties | cut -d'=' -f2-)" --password-stdin
echo <github-token> | docker login ghcr.io -u <github-username> --password-stdin(token needsread:packages).
- Ensure
- Run:
./verify_apk.py <git-tag-or-branch> <path-to-official-apk> [--stage all|build|diff]. Outputs:official.apk,rebuilt.apk,r8_patched.apk(when needed),rebuilt_signed.apk,diffoscope.htmlunderartifacts/reproducible/<tag>/. - CI: trigger the
Verify APKworkflow dispatch (.github/workflows/android-verify-apk.yml) withtag,official_apk_url, optionalstage, and optionalbase_image_tag; artifacts upload mirrors local outputs. Ifbase_image_tagis empty, the workflow readsreproducible/base_image_tag.txt. verify_apk.pywilldocker pullthe base image by default (setVERIFY_PULL_BASE=falseto skip). It then reuses a local image or builds if the pull fails.- Optional manual map-id patch:
./fix_pg_map_id.py <apk-in> <apk-out> <pg-map-id>. - Optional dexdump diff:
./diff_dexdump.py <official-apk> <rebuilt-apk> [--out-dir DIR] [--dexdump PATH] [--tag TAG]to write per-dex dumps/diffs (defaults toartifacts/reproducible/<tag>/dexdumpwhen--tagis provided).
- AGP 9.0.0 (bundled R8) still does not provide a public DSL/property to set deterministic map-id values for release builds; we patch via
fix_pg_map_id.pyand confirm payload identity by copying the official signing block (apksigcopier). R8_MAP_ID_SEEDis kept as reproducible tooling input, but AGP 9.0.0 does not currently wire it to a supported map-id flag.
- Track AGP/R8 support for deterministic map-id configuration (seed/template/fixed id) through public APIs.
- Wire deterministic map-id into release builds once AGP exposes it.
- Re-run
./verify_apk.py <tag> <apk>and confirm hashes match without signature copying.