mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-10-05 19:28:25 +00:00
We were shipping a QR decoder we could not verify. io.github.zxing-cpp:android ships four .so files built by a third party on toolchains we cannot see, and nothing in this tree could check them -- while tools/arti-build holds libarti_android.so to a pinned-NDK, canonical-path, byte-for-byte reproducible standard. A QR scanner is a thing you point at a stranger's phone; there was no principled reason for the binary that parses the result to be the exempt one. tools/zxing-cpp-build mirrors tools/arti-build: the NDK revision is pinned (and is deliberately the same revision arti pins, so one install serves both), the upstream tag is pinned and cloned at that tag only, absolute paths are remapped, SOURCE_DATE_EPOCH comes from the tag's commit rather than from build time, and everyone builds at the same canonical path. Verified, not asserted: two clean builds of arm64-v8a produced identical bytes (dec4397c3e2f1e482b1905119dd4ea9e285f3420ffe4e66f38b2d22f54639dbf) and verify-reproducible.sh confirms they match what is committed. NDK discovery reads each candidate's source.properties and refuses anything but the pinned revision -- no wildcards, borrowing arti's r25b-vs-r27 lesson rather than re-learning it. After each build the script decodes the library's own .note.android.ident and fails unless the min SDK and NDK build number are what was asked for: the gate checks the input toolchain, the stamp checks the output, and only the second catches a stale CMake cache slipping a different compiler past the first. All four ABIs, unlike arti's two. Tor is optional and can be absent; a scanner that fails to load is a broken core feature, and what this replaced was pure Java that worked everywhere. Dropping the AAR means carrying the two things it supplied besides the binary: - Its Kotlin half, vendored verbatim at src/main/java/zxingcpp. The package and class name are load-bearing -- the library exports Java_zxingcpp_BarcodeReader_readYBuffer -- so it keeps both, its upstream Apache-2.0 header, and an exclusion from spotless so our MIT header is never stamped onto someone else's file. - Its consumer ProGuard rule. Without -keep class zxingcpp.**, R8 renames the class and the scanner fails to start in release builds only, with no build error and no warning. Not a size change: the published AAR's libraries are already stripped, and ours come out only marginally smaller. This buys verifiability. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0134jvyriixNTHST4WRbbqbX
53 lines
2.9 KiB
Bash
53 lines
2.9 KiB
Bash
# Deterministic build environment for libzxingcpp_android.so.
|
|
#
|
|
# Sourced by build-zxingcpp.sh so every build path stays in lockstep. Makes the
|
|
# native library byte-for-byte reproducible, which is what lets F-Droid,
|
|
# Zapstore or any third party rebuild the shipped .so from this tag and confirm
|
|
# it matches — the same bar tools/arti-build holds libarti_android.so to.
|
|
#
|
|
# The caller must already have set:
|
|
# SCRIPT_DIR — tools/zxing-cpp-build
|
|
# SOURCE_DIR — the zxing-cpp clone (.zxing-cpp-source)
|
|
# BUILD_ROOT — the canonical build root
|
|
#
|
|
# What makes a C++ shared library non-reproducible, and the fix for each:
|
|
# 1. Compiler + linker version -> the pinned NDK (ANDROID_NDK_VERSION). clang
|
|
# and lld stamp their versions into .comment exactly as rustc does.
|
|
# 2. Upstream source -> pinned git tag (ZXING_CPP_VERSION), cloned
|
|
# at that tag and nothing else.
|
|
# 3. Absolute paths baked into __FILE__, assertions and debug records
|
|
# -> -ffile-prefix-map rewrites them to stable
|
|
# virtual paths, so two machines with different checkout dirs agree.
|
|
# 4. Timestamps -> SOURCE_DATE_EPOCH, derived from the pinned
|
|
# tag's commit rather than from when the build happens.
|
|
# 5. Archive metadata -> ar writes mtimes/uids into static archives;
|
|
# the D (deterministic) flag zeroes them. The NDK's llvm-ar defaults to D,
|
|
# but it is set explicitly so a host ar cannot change the answer.
|
|
|
|
# Rewrite every host-specific absolute prefix the compiler would otherwise bake
|
|
# into the binary. Both flags are needed: -ffile-prefix-map covers __FILE__ and
|
|
# debug info, -fdebug-prefix-map is kept for older clangs that ignore the first.
|
|
REPRO_CFLAGS="-ffile-prefix-map=${SOURCE_DIR}=/zxing-cpp"
|
|
REPRO_CFLAGS="${REPRO_CFLAGS} -ffile-prefix-map=${BUILD_ROOT}=/build"
|
|
REPRO_CFLAGS="${REPRO_CFLAGS} -fdebug-prefix-map=${SOURCE_DIR}=/zxing-cpp"
|
|
REPRO_CFLAGS="${REPRO_CFLAGS} -fdebug-prefix-map=${BUILD_ROOT}=/build"
|
|
|
|
# No __DATE__/__TIME__ anywhere in the output, whatever upstream does with them.
|
|
REPRO_CFLAGS="${REPRO_CFLAGS} -Wno-builtin-macro-redefined -D__DATE__=\"redacted\" -D__TIME__=\"redacted\""
|
|
|
|
export ZXING_REPRO_CFLAGS="${REPRO_CFLAGS}"
|
|
|
|
# lld's default build-id is a hash of the content, so it is already a function
|
|
# of the input bytes — but pin it rather than inherit whatever the NDK's
|
|
# default becomes. A content hash also means a matching rebuild keeps the same
|
|
# BuildID, which is what an auditor compares.
|
|
export ZXING_REPRO_LDFLAGS="-Wl,--build-id=sha1"
|
|
|
|
# Pin SOURCE_DATE_EPOCH to the commit the tag points at: deterministic for a
|
|
# given ZXING_CPP_VERSION, and independent of when the build actually runs.
|
|
if [ -d "${SOURCE_DIR}/.git" ]; then
|
|
_epoch="$(git -C "${SOURCE_DIR}" log -1 --format=%ct 2>/dev/null || true)"
|
|
[ -n "${_epoch}" ] && export SOURCE_DATE_EPOCH="${_epoch}"
|
|
unset _epoch
|
|
fi
|