Skip to content

Latest commit

 

History

History
138 lines (112 loc) · 7.11 KB

File metadata and controls

138 lines (112 loc) · 7.11 KB

Verifying the flock Android APK is built from this source

flock's privacy rests on end-to-end encryption — but the one court order that bypasses it entirely is a compelled targeted, backdoored build shipped to one user (see docs/PRIVACY.md, "When a court comes knocking", and the plan in docs/plans/2026-07-06-verifiable-builds.md). We cannot make ourselves unable to comply with a lawful order. We can make compliance detectable: the release APK is reproducible, so anyone can rebuild it from a tagged commit and confirm the shipped bytes match this public source. A build that didn't match — a build carrying something this source does not — would stand out.

This page is the procedure. It needs no flock context and no trust in us beyond the source you are reading.

What is verified

The unsigned release APK is byte-for-byte reproducible from a clean commit. Measured 2026-07-06: four independent clean builds (two raw Gradle, the full build-apk.sh release pipeline, and npm run apk:verify) produced an identical app-release-unsigned.apk (sha256 d663c61f… for that dev tree). The build carries no wall-clock timestamp — both the build id and the "built at" date derive from the commit (vite.config.ts), so the same commit yields the same bytes whoever builds it, whenever, in any timezone.

The signed APK (flock-release.apk, what you install) is not something a third party can reproduce, because only we hold the signing key. That is expected: you verify the unsigned content matches this source, then separately trust our signature over it (Android enforces that every update is signed by the same key).

Toolchain (the versions this was verified with)

Reproducibility is only guaranteed against the same major toolchain. Pinned by the repo where possible (package-lock.json, the Gradle wrapper, patch-android.mjs):

Component Version Pinned by
JDK 21 (verified 21.0.11) build-apk.sh (Homebrew openjdk@21)
Node / npm 24 / 11 (verified 24.14.0 / 11.9.0) — (use the same major)
Capacitor 8.4.1 package-lock.json
Android Gradle Plugin 8.13.0 @capacitor/android template
Gradle 8.14.3 android/gradle/wrapper
Kotlin Gradle plugin 2.1.0 native/patch-android.mjs
Android SDK platform 36, build-tools 35.0.0 android/variables.gradle
minSdk / compileSdk / targetSdk 24 / 36 / 36 android/variables.gradle

How to verify

# 1. Get the exact source for the release you want to check.
git clone https://github.com/forgesworn/flock && cd flock
git checkout <release-tag-or-commit>     # MUST match the build you're verifying
git status --porcelain                   # must be empty — a dirty tree is marked +dev

# 2. Exact dependencies, and a JDK 21.
npm ci
export JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home  # or your JDK 21

# 2b. Regenerate the self-hosted basemap assets (gitignored, deterministic):
node scripts/fetch-basemap-assets.mjs   # glyphs + sprite
# The town PMTiles under app/public/basemap/ are build-host local state
# (gitignored, produced with go-pmtiles — see scripts/fetch-basemap-assets.mjs).
# A release built WITH them bundled (e.g. 7363e51) will not byte-match a
# rebuild without the same files; restore them before building.

# 3. Build the unsigned release APK and print its hash.
npm run apk:verify
#   (on a fresh clone this first runs `npx cap add android` to generate the
#    gitignored native project from the committed scripts, then builds.)

The command prints:

reproducibility anchor (sha256):
<64-hex>  app/build/outputs/apk/release/app-release-unsigned.apk

Compare against the published hash

Compare the printed hash to the released anchor, published in two places so a compromised or compelled host cannot swap both the APK and its hash unnoticed:

  1. On the download hosthttps://flock.forgesworn.dev/downloads/flock.apk.unsigned.sha256 (emitted by deploy/deploy.sh alongside the APK).

  2. Off-host — an SSH-signed git tag release/<build> whose message embeds the same hash, plus the append-only ledger docs/transparency/RELEASES.jsonl. This rides git (not our web host) to every clone and the forge, so a targeted build absent from this signed history is anomalous on its face. Verify it:

    git config gpg.ssh.allowedSignersFile docs/transparency/allowed_signers
    git verify-tag release/<build>    # → Good "git" signature for releases@flock.forgesworn.dev

    The tag message carries unsigned APK sha256: … — it must equal the hash you rebuilt above and the on-host anchor. (A project-key Nostr note is the intended third, fully-independent channel; see docs/transparency/README.md.)

Match across channels → the shipped release is built from this exact source. Then confirm the APK you actually downloaded is properly signed and by the expected key:

apksigner verify --print-certs flock.apk     # exit 0; certificate SHA-256 must equal
                                              # the canonical key in docs/SIGNING.md
                                              # (320ab5bc…25869877)

Known limits (stated honestly)

  • Reproducibility ≠ immunity. It makes a silent, targeted build detectable. It does not make a published, universal change impossible — that is what reading the source and the transparency log is for.
  • Toolchain drift breaks byte-identity. A different AGP/Gradle/JDK major version can change output. Verify with the versions above; CI should rebuild and diff on each release tag so a regression is caught, not discovered later. (Observed 2026-08-08: even same-major toolchains on different hosts emitted a byte-different AndroidManifest.xml — identical permission set, different element ordering from the manifest merger. Compare entry-by-entry CRCs, not just the whole-file hash, when localising a mismatch.)
  • Map assets are inputs too. A rebuild on 2026-08-08 of the deployed 7363e51 APK confirmed every dex/resource entry byte-identical; the only payload differences were the gitignored basemap inputs (glyphs/sprite + five town PMTiles) absent from the fresh clone. Verifiability of the map bundle therefore rests on pmtiles provenance (the go-pmtiles commands) or on committing those inputs — an open decision recorded in docs/ROADMAP.md.
  • Transitive dependency drift. Direct versions are pinned; a far-future rebuild could still resolve different transitive artefacts. Gradle dependency locking is a future hardening step (plan doc).
  • The signing key is trust, not proof. Reproducibility proves the content matches source; that the update is signed by our key is a separate trust anchor (native/release.keystore, backed up out-of-band, never in the repo).

Related

  • docs/PRIVACY.md — the threat this answers ("When a court comes knocking").
  • docs/plans/2026-07-06-verifiable-builds.md — the full plan (PWA side, transparency log).
  • native/build-apk.sh (verify mode), deploy/deploy.sh (publishes the anchor), vite.config.ts (commit-derived, timezone-independent build stamp).