Skip to content

Latest commit

 

History

History
127 lines (97 loc) · 5.33 KB

File metadata and controls

127 lines (97 loc) · 5.33 KB

Reproducible Builds

Monolith's release APK is built reproducibly: anyone can rebuild the apk from the public source code inside a pinned build environment and confirm, bit-for-bit via hash comparison, that the published APK contains exactly the code provided on Github.

Two different SHA-256 values are involved:

Hash Of what Answers
Download hash the signed APK on the website "Do I have the same APK the developer published?"
Reproducible hash the unsigned APK you rebuild here "Prove that it was built from the Github source code"

Trust roots (pinned for this release)

Item Value
Source this repository
Build image aronbasalt/monolith-build@sha256:2c3cb23203e54f5f83cc30dfb479a49e679a8bc27c431f176d392ea3704e8dee
Expected unsigned APK SHA-256 245dcfd1d02b0c94ccbd0aebac72327119cdc007cbc17b464a274b6e2909111c

The build image is identified by digest, not by tag: a tag can be re-pointed, a digest is the content's fingerprint and cannot be swapped. It freezes the whole toolchain (JDK 17, Android build-tools 35.0.0, NDK 27.0.12077973, CMake 3.22.1, Gradle 8.13).

Prerequisites

  • Docker (the image targets linux/amd64; on Apple Silicon it runs via emulation)
  • Internet connection

Step 1: Get the source code

Download and unpack the source code from Github

Step 2: Pull the pinned build image by digest

docker pull aronbasalt/monolith-build@sha256:2c3cb23203e54f5f83cc30dfb479a49e679a8bc27c431f176d392ea3704e8dee

Step 3: Build the APK

Run from the repository root (the folder that contains gradlew). Mount the source at exactly /src.

Linux / macOS:

docker run --rm --platform=linux/amd64 -v "$(pwd):/src" \
  aronbasalt/monolith-build@sha256:2c3cb23203e54f5f83cc30dfb479a49e679a8bc27c431f176d392ea3704e8dee \
  bash -lc "./gradlew --no-daemon clean assembleRelease && sha256sum app/build/outputs/apk/release/app-release-unsigned.apk"

Windows (PowerShell):

docker run --rm --platform=linux/amd64 -v "${PWD}:/src" `
  aronbasalt/monolith-build@sha256:2c3cb23203e54f5f83cc30dfb479a49e679a8bc27c431f176d392ea3704e8dee `
  bash -lc "./gradlew --no-daemon clean assembleRelease && sha256sum app/build/outputs/apk/release/app-release-unsigned.apk"

Step 4: Check the reproducible hash

The printed SHA-256 must equal:

245dcfd1d02b0c94ccbd0aebac72327119cdc007cbc17b464a274b6e2909111c

A match means your independent build is bit-for-bit identical to the reference unsigned APK.

Step 5: Compare against the published (signed) APK

The public APK differs from your rebuild only by the signature. Confirm this by comparing with the signature excluded:

  • the APK Signing Block (v2/v3)
  • META-INF/*.SF, META-INF/*.RSA, META-INF/*.EC, META-INF/*.MF (v1)

Recommended — apksigcopier (clear pass/fail). apksigcopier is a Python tool and internally calls apksigner; neither ships in the build image, so install them inside a throwaway container — this does not change the published image (it lives only in that one container and is discarded with --rm). Put the downloaded signed APK at the repo root as published.apk, then run:

docker run --rm -v "$(pwd):/work" \
  aronbasalt/monolith-build@sha256:2c3cb23203e54f5f83cc30dfb479a49e679a8bc27c431f176d392ea3704e8dee \
  bash -lc '
    apt-get update -qq && apt-get install -y -qq python3-pip >/dev/null
    pip3 install --quiet apksigcopier        # if pip refuses: add --break-system-packages
    ln -sf /opt/android-sdk/build-tools/35.0.0/apksigner /usr/local/bin/apksigner
    apksigcopier compare /work/published.apk --unsigned /work/app/build/outputs/apk/release/app-release-unsigned.apk
    echo "exit: $?   (0 = identical apart from the signature = verified)"
  '

To inspect any remaining difference — diffoscope:

diffoscope <PUBLISHED_APK> app/build/outputs/apk/release/app-release-unsigned.apk

Nothing should differ other than the signature.

Known exception — libsodium.so (prebuilt)

app/src/main/cpp/libs/arm64-v8a/libsodium.so is a prebuilt binary (from the Kalium JNI bindings 2.0.2), committed to the repository with a documented SHA-256 (see app/src/main/cpp/libs/PROVENANCE.md). It is not compiled from source here, so it is not independently reproducible — but being fixed in Git, it contributes identically to every build. Trust is anchored via its SHA-256 and the Kalium release.

Scope & honest limitations

  • The comparison covers the unsigned APK content; the signature is intentionally excluded (only the developer holds the private signing key).
  • The build image is the trust root and is pinned by digest. It is published as a binary artifact and is not bit-for-bit rebuildable from the Dockerfile alone, because apt and sdkmanager fetch packages at image-build time. Verify the image by its digest to not rely on rebuilding it.
  • Verification is performed on linux/amd64 (the image enforces this platform).

Reproducibility setup behind this: pinned Gradle/AGP/NDK/CMake versions, Gradle dependency verification (gradle/verification-metadata.xml), distributionSha256Sum for the Gradle distribution, no VCS stamp (vcsInfo.include = false), neutral native build flags (-ffile-prefix-map, --build-id=none), and a digest-pinned container image.