From 7e8ea632302510a1f75d5bb5a12fd0de6b959d92 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:19:39 +0200 Subject: [PATCH 1/2] Prepare reproducible Wear 0.1.0 release --- .github/workflows/release.yml | 2 +- docs/maintainer-release-guide.md | 79 ++++++++++++------- docs/reproducible-builds.md | 31 ++++---- tools/reproducible-builds/build-release.sh | 25 +++++- tools/reproducible-builds/compare-release.sh | 2 + .../prepare-github-release.sh | 6 ++ tools/reproducible-builds/sign-play-bundle.sh | 52 +++++++----- tools/reproducible-builds/sign-release.sh | 2 + .../verify-github-release.sh | 2 + wear/build.gradle.kts | 19 +++-- 10 files changed, 149 insertions(+), 71 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ccf20b33..75c7ef31 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -102,7 +102,7 @@ jobs: "$RUNNER_TEMP/attestation-subjects/BITCHAT_SHA256SUMS.unsigned" cp "$RUNNER_TEMP"/release-a/bitchat-android-*-unsigned.apk \ "$RUNNER_TEMP/attestation-subjects/" - cp "$RUNNER_TEMP/release-a/bitchat-android-release-unsigned.aab" \ + cp "$RUNNER_TEMP"/release-a/bitchat-android-*-unsigned.aab \ "$RUNNER_TEMP/attestation-subjects/" - name: Attest verified unsigned release diff --git a/docs/maintainer-release-guide.md b/docs/maintainer-release-guide.md index 991cc520..5bcb4742 100644 --- a/docs/maintainer-release-guide.md +++ b/docs/maintainer-release-guide.md @@ -11,11 +11,10 @@ The central rule is: No keystore or password is stored in GitHub, GitHub Actions, the repository, release notes, or workflow artifacts. -This runbook currently releases only the phone app, `com.bitchat.droid`. -The `:wear` module is tested and dependency-locked in CI but is not included in -the canonical release artifact set. Do not publish its development-signed -outputs. Wear distribution requires a separate signing identity, reproducible -artifact contract, Play listing, and maintainer runbook. +This runbook releases both the phone and Wear OS apps under the shared Play +application ID `com.bitchat.droid`. Wear releases use the independent version +code range beginning at `1000000001`; every phone and Wear artifact uploaded to +one Play listing must have a unique version code. For the technical trust model and third-party verification instructions, see [Reproducible builds](reproducible-builds.md). @@ -28,7 +27,7 @@ For the technical trust model and third-party verification instructions, see | Create tag | Maintainer machine | Signed `vX.Y.Z` tag | | Build twice | GitHub Actions | Two identical unsigned APK/AAB builds | | Promote build | GitHub Actions | Attested `verified-unsigned-release` artifact | -| Sign | Maintainer machine | Three installable APKs and one Play upload AAB | +| Sign | Maintainer machine | Four installable APKs and two Play upload AABs | | Test Play build | Play Console internal track | Play-generated APKs tested before public rollout | | Prepare release | Maintainer machine and GitHub draft | Checksummed public release assets | | Publish | GitHub Releases and Play Console | Public GitHub release and promoted Play rollout | @@ -42,8 +41,8 @@ These are four separate signing identities: 2. **GitHub APK signature**: lets Android install and update the APKs published on GitHub. Use the existing GitHub release key. Its certificate SHA-256 must match `BITCHAT_GITHUB_RELEASE_CERT_SHA256` in `gradle.properties`. -3. **Play upload signature**: the maintainer signs the AAB with the Play upload - key so Play will accept it. +3. **Play upload signature**: the maintainer signs both AABs with the Play + upload key so Play will accept them. 4. **Play app signature**: Google generates device APKs and signs them with the separate Play app-signing key. @@ -119,9 +118,10 @@ a command line, in a shell profile, or in a release-notes file. ## 1. Prepare and approve the release commit -1. Update `versionCode` and `versionName` in `app/build.gradle.kts`. - `versionCode` must be greater than every build previously uploaded to Play. - `versionName` must match the release tag without the leading `v`. +1. Update `versionCode` and `versionName` in `app/build.gradle.kts`. For a Wear + release, also update them in `wear/build.gradle.kts` using the reserved Wear + version-code range. Every code must be unique across both form factors. + The phone `versionName` must match the release tag without the leading `v`. 2. Merge all intended release changes into `main`. 3. Confirm required CI checks are green. 4. Complete the @@ -147,6 +147,7 @@ Set shell variables for the rest of the release: export REPOSITORY=permissionlesstech/bitchat-android export TAG=vX.Y.Z export VERSION_CODE=NN +export WEAR_VERSION_CODE=1000000001 export RELEASE_DIR="release-${TAG#v}" ``` @@ -156,6 +157,7 @@ Confirm the source version: grep -nE 'versionCode|versionName' app/build.gradle.kts test "$(sed -n 's/.*versionName = "\([^"]*\)".*/\1/p' app/build.gradle.kts)" = "${TAG#v}" test "$(sed -n 's/.*versionCode = \([0-9][0-9]*\).*/\1/p' app/build.gradle.kts)" = "$VERSION_CODE" +test "$(sed -n 's/.*versionCode = \([0-9_][0-9_]*\).*/\1/p' wear/build.gradle.kts | tr -d _)" = "$WEAR_VERSION_CODE" ``` Stop if either `test` command fails. @@ -251,6 +253,8 @@ The directory must initially contain exactly these canonical files: - `bitchat-android-x86-unsigned.apk` - `bitchat-android-x86_64-unsigned.apk` - `bitchat-android-release-unsigned.aab` +- `bitchat-android-wear-unsigned.apk` +- `bitchat-android-wear-release-unsigned.aab` Verify the checksum manifest: @@ -328,6 +332,7 @@ It creates: - `bitchat-android-arm64.apk` - `bitchat-android-universal.apk` +- `bitchat-android-wear.apk` - `bitchat-android-x86_64.apk` The unsigned armv7 and x86 APKs remain available for reproducibility, but are @@ -337,7 +342,7 @@ If the helper stops after creating any signed file, do not continue or overwrite files manually. Start again in a new directory downloaded from the same successful workflow run. -## 6. Sign the Google Play AAB locally +## 6. Sign the Google Play AABs locally Set the pinned JDK and Play upload-key locations, then load the passwords: @@ -361,18 +366,20 @@ Run: tools/reproducible-builds/sign-play-bundle.sh "$RELEASE_DIR" ``` -The helper creates `bitchat-android-play-upload.aab`, verifies its JAR -signature, proves that every non-signature payload entry matches the canonical +The helper creates `bitchat-android-play-upload.aab` and +`bitchat-android-wear-play-upload.aab`, verifies their JAR signatures, proves +that every non-signature payload entry matches the corresponding canonical unsigned AAB, and updates `SHA256SUMS`. -This is the only file to upload to Play Console: +These are the only files to upload to Play Console: ```text release-X.Y.Z/bitchat-android-play-upload.aab +release-X.Y.Z/bitchat-android-wear-play-upload.aab ``` -Do not upload an APK or `bitchat-android-release-unsigned.aab` to Play. Do not -open Android Studio and rebuild the bundle. +Do not upload an APK or either `*-release-unsigned.aab` to Play. Do not open +Android Studio and rebuild the bundles. Remove passwords from the environment after both signing steps: @@ -394,16 +401,20 @@ tools/reproducible-builds/prepare-github-release.sh "$RELEASE_DIR" The helper verifies all checksums and renames the public build information and checksum manifests. It refuses missing or pre-existing release files. -The final GitHub Release must contain all 13 files below: +The final GitHub Release must contain all 17 files below: | Asset | Signed? | Why it is published | |---|---:|---| | `bitchat-android-arm64.apk` | APK release key | Primary direct-install APK | | `bitchat-android-universal.apk` | APK release key | Fallback direct-install APK | | `bitchat-android-x86_64.apk` | APK release key | x86_64 install APK | -| Five `bitchat-android-*-unsigned.apk` files | No | Reproducibility inputs for every ABI target | +| `bitchat-android-wear.apk` | APK release key | Wear OS direct-install APK | +| Five phone `bitchat-android-*-unsigned.apk` files | No | Reproducibility inputs for every phone ABI target | | `bitchat-android-release-unsigned.aab` | No | Canonical reproducible Play input | | `bitchat-android-play-upload.aab` | Play upload key | Exact bundle uploaded to Play | +| `bitchat-android-wear-unsigned.apk` | No | Canonical reproducible Wear install input | +| `bitchat-android-wear-release-unsigned.aab` | No | Canonical reproducible Wear Play input | +| `bitchat-android-wear-play-upload.aab` | Play upload key | Exact bundle uploaded to the Wear OS track | | `BITCHAT_BUILDINFO.json` | GitHub attestation | Source commit and pinned toolchain | | `BITCHAT_SHA256SUMS.unsigned` | GitHub attestation | Original canonical CI manifest | | `BITCHAT_SHA256SUMS` | No detached signature | SHA-256 for every published asset | @@ -432,12 +443,13 @@ Create a local release-notes file. At minimum it must contain: ## Bitchat Android vX.Y.Z - Version code: NN +- Wear version: 0.1.0 (code 1000000001) - Source tag: vX.Y.Z - GitHub APK signing certificate SHA-256: FINGERPRINT - Play upload certificate SHA-256: FINGERPRINT - Play app-signing certificate SHA-256: FINGERPRINT -Checksums, canonical unsigned APK/AAB inputs, the exact Play upload AAB, and +Checksums, canonical unsigned APK/AAB inputs, the exact Play upload AABs, and build information are attached. See `docs/reproducible-builds.md` for public verification instructions. @@ -474,7 +486,7 @@ gh release view "$TAG" --repo "$REPOSITORY" --web Keep it as a draft until the Play internal-track checks below pass. -## 9. Upload and test the AAB in Google Play +## 9. Upload and test the AABs in Google Play 1. Open Play Console and select `com.bitchat.droid`. 2. Open **Test and release > Testing > Internal testing**. @@ -495,16 +507,23 @@ Keep it as a draft until the Play internal-track checks below pass. 10. In **Setup > App integrity**, confirm the Play app-signing certificate SHA-256 is the value recorded in the GitHub release notes. -Promote this same tested Play release from internal testing to production. -Do not rebuild or upload a second AAB for production. Use a staged production -rollout when appropriate. +For the initial Wear release, open **Test and release > Advanced settings > +Form factors**, add Wear OS, upload the required Wear screenshot, and use the +dedicated **Wear OS only** test track. Upload exactly +`bitchat-android-wear-play-upload.aab`, confirm version code `1000000001`, and +complete the Wear OS opt-in and review flow. Promote the already-tested Wear +artifact on its dedicated track; do not add it to the mobile track. + +Promote these same tested Play releases from their test tracks to their +respective production tracks. Do not rebuild or upload replacement AABs for +production. Use staged production rollouts when appropriate. Google signs the device APKs with the Play app-signing key, so Play-delivered APKs will not be byte-identical to the GitHub APKs. That is expected. ## 10. Publish GitHub and promote Play -After the internal Play build passes and the GitHub draft has all 13 assets: +After both Play test builds pass and the GitHub draft has all 17 assets: ```bash gh release edit "$TAG" \ @@ -539,7 +558,7 @@ tools/reproducible-builds/verify-github-release.sh "$TAG" Also verify: - the GitHub release is marked **Latest**; -- the three signed APKs install and show the expected version; +- the four signed APKs install and show the expected version; - the Play listing shows the intended production version and rollout state; - a Play-installed build is signed by the app-signing certificate recorded in the release notes; and @@ -572,10 +591,10 @@ Also verify: - [ ] `verified-unsigned-release` downloaded by run ID - [ ] Checksums, source commit, and attestations verified - [ ] GitHub APKs signed locally with the pinned certificate -- [ ] Play AAB signed locally with the registered upload key -- [ ] `BITCHAT_SHA256SUMS` verifies all 13 release assets +- [ ] Phone and Wear Play AABs signed locally with the registered upload key +- [ ] `BITCHAT_SHA256SUMS` verifies all 17 release assets - [ ] GitHub draft created with certificate fingerprints and all assets -- [ ] Exact signed AAB uploaded to and tested on Play internal track +- [ ] Exact signed phone and Wear AABs uploaded to and tested on their tracks - [ ] GitHub Release published - [ ] Same tested Play release promoted to production - [ ] Public GitHub and Play verification completed @@ -586,3 +605,5 @@ Also verify: - [GitHub CLI: create a release](https://cli.github.com/manual/gh_release_create) - [Google Play App Signing](https://support.google.com/googleplay/android-developer/answer/9842756) - [Play Console App bundle explorer](https://support.google.com/googleplay/android-developer/answer/9859152) +- [Package and distribute Wear OS apps](https://developer.android.com/training/wearables/packaging) +- [Manage form-factor releases on dedicated tracks](https://support.google.com/googleplay/android-developer/answer/13295490) diff --git a/docs/reproducible-builds.md b/docs/reproducible-builds.md index 3d94e8f1..3b7cd5f8 100644 --- a/docs/reproducible-builds.md +++ b/docs/reproducible-builds.md @@ -1,16 +1,14 @@ # Reproducible builds -Bitchat's canonical phone-app release build produces byte-for-byte reproducible -unsigned APKs and an unsigned Android App Bundle (AAB). CI builds the release +Bitchat's canonical release build produces byte-for-byte reproducible unsigned +phone and Wear OS APKs and Android App Bundles (AABs). CI builds the release twice in independent jobs and exposes a verified release artifact only when -every canonical byte matches. The `:wear` module is tested and -dependency-locked but is outside this phone artifact contract until a separate -Wear release and signing process is defined. +every canonical byte matches. Signing remains local: no keystore or signing password is stored in or exposed to GitHub Actions. Anyone can reproduce the unsigned artifacts; maintainers download the verified CI output, sign the selected GitHub APKs and Play upload -AAB locally, then manually publish those exact files. +AABs locally, then manually publish those exact files. Maintainers should use the complete [Android maintainer release guide](maintainer-release-guide.md) for the @@ -71,6 +69,8 @@ The output contains: - unsigned APKs for arm64, armv7, x86, x86_64, and universal installs - `bitchat-android-release-unsigned.aab` +- `bitchat-android-wear-unsigned.apk` +- `bitchat-android-wear-release-unsigned.aab` - `BUILDINFO.json` - `SHA256SUMS.unsigned` @@ -104,13 +104,13 @@ tools/reproducible-builds/verify-github-release.sh vX.Y.Z That command: -1. downloads all release APKs, the AAB, build information, and checksum files; +1. downloads all release APKs, AABs, build information, and checksum files; 2. verifies the canonical unsigned build's GitHub artifact-attestation subjects against this repository; 3. verifies `BITCHAT_SHA256SUMS`; 4. checks that the local source commit is the release commit; 5. rebuilds in the pinned container; and -6. byte-compares every unsigned APK, the unsigned AAB, build information, and +6. byte-compares every unsigned APK, both unsigned AABs, build information, and the unsigned checksum manifest. To verify the published checksums and attestations without rebuilding: @@ -154,8 +154,8 @@ gh attestation verify bitchat-android-universal-unsigned.apk \ Google Play App Signing changes the verification boundary: -- maintainers upload a signed AAB using the upload key; -- Google Play generates optimized, device-specific APK splits from that AAB; +- maintainers upload signed phone and Wear AABs using the upload key; +- Google Play generates optimized, device-specific APK splits from those AABs; - Google signs the delivered APKs with the app-signing key. Consequently, a Play-delivered APK is not expected to be byte-identical to the @@ -163,8 +163,8 @@ GitHub universal APK or to a locally built APK. Use this procedure instead: 1. In Play Console, open **Test and release > App bundle explorer**, select the release/version code, and download the original app bundle if that option is - available to your account. Compare its unsigned payload with the reproduced - `bitchat-android-release-unsigned.aab`: + available to your account. Compare its unsigned payload with the matching + reproduced phone or Wear AAB: ```bash tools/reproducible-builds/compare-archive-payloads.sh \ @@ -204,9 +204,10 @@ maintainers to retain the uploaded AAB, publish its digest and provenance, and record the Play version code and app-signing certificate fingerprint alongside the release. -The GitHub workflow builds and attests the canonical unsigned AAB. A maintainer -locally creates `bitchat-android-play-upload.aab` from that exact file and -uploads it manually to Google Play. +The GitHub workflow builds and attests both canonical unsigned AABs. A +maintainer locally creates `bitchat-android-play-upload.aab` and +`bitchat-android-wear-play-upload.aab` from those exact files and uploads them +manually to Google Play. ## Maintainer release process diff --git a/tools/reproducible-builds/build-release.sh b/tools/reproducible-builds/build-release.sh index 197dffc9..e474dc32 100755 --- a/tools/reproducible-builds/build-release.sh +++ b/tools/reproducible-builds/build-release.sh @@ -52,17 +52,28 @@ gradle_args=( ) cd "$PROJECT_ROOT" -./gradlew "${gradle_args[@]}" :app:clean :app:bundleRelease +./gradlew "${gradle_args[@]}" \ + :app:clean :wear:clean \ + :app:bundleRelease :wear:bundleRelease aab_source="$PROJECT_ROOT/app/build/outputs/bundle/release/app-release.aab" if [ ! -f "$aab_source" ]; then - echo "error: expected release AAB not found" >&2 + echo "error: expected phone release AAB not found" >&2 exit 1 fi cp "$aab_source" "$OUTPUT_DIR/bitchat-android-release-unsigned.aab" +wear_aab_source="$PROJECT_ROOT/wear/build/outputs/bundle/release/wear-release.aab" +if [ ! -f "$wear_aab_source" ]; then + echo "error: expected Wear release AAB not found" >&2 + exit 1 +fi +cp "$wear_aab_source" "$OUTPUT_DIR/bitchat-android-wear-release-unsigned.aab" + # AGP cannot build split APKs and an app bundle from the same intermediates. -./gradlew "${gradle_args[@]}" :app:clean :app:assembleRelease +./gradlew "${gradle_args[@]}" \ + :app:clean :wear:clean \ + :app:assembleRelease :wear:assembleRelease declare -A apk_names=( ["app-arm64-v8a-release-unsigned.apk"]="bitchat-android-arm64-unsigned.apk" @@ -81,6 +92,13 @@ for source_name in "${!apk_names[@]}"; do cp "$source_path" "$OUTPUT_DIR/${apk_names[$source_name]}" done +wear_apk_source="$PROJECT_ROOT/wear/build/outputs/apk/release/wear-release-unsigned.apk" +if [ ! -f "$wear_apk_source" ]; then + echo "error: expected Wear release APK not found" >&2 + exit 1 +fi +cp "$wear_apk_source" "$OUTPUT_DIR/bitchat-android-wear-unsigned.apk" + source_commit="${BITCHAT_SOURCE_COMMIT:-$(git -C "$PROJECT_ROOT" rev-parse HEAD)}" if ! [[ "$source_commit" =~ ^([0-9a-f]{40}|[0-9a-f]{64})$ ]]; then echo "error: source commit must be a full Git object ID" >&2 @@ -109,6 +127,7 @@ EOF sha256sum BUILDINFO.json sha256sum bitchat-android-*-unsigned.apk sha256sum bitchat-android-release-unsigned.aab + sha256sum bitchat-android-wear-release-unsigned.aab } | sort -k2 > SHA256SUMS.unsigned ) diff --git a/tools/reproducible-builds/compare-release.sh b/tools/reproducible-builds/compare-release.sh index 611c2d0e..39608304 100755 --- a/tools/reproducible-builds/compare-release.sh +++ b/tools/reproducible-builds/compare-release.sh @@ -12,6 +12,8 @@ artifacts=( bitchat-android-armv7-unsigned.apk bitchat-android-release-unsigned.aab bitchat-android-universal-unsigned.apk + bitchat-android-wear-release-unsigned.aab + bitchat-android-wear-unsigned.apk bitchat-android-x86-unsigned.apk bitchat-android-x86_64-unsigned.apk ) diff --git a/tools/reproducible-builds/prepare-github-release.sh b/tools/reproducible-builds/prepare-github-release.sh index 73ea9f32..b87e7843 100755 --- a/tools/reproducible-builds/prepare-github-release.sh +++ b/tools/reproducible-builds/prepare-github-release.sh @@ -30,6 +30,10 @@ required=( bitchat-android-release-unsigned.aab bitchat-android-universal-unsigned.apk bitchat-android-universal.apk + bitchat-android-wear-play-upload.aab + bitchat-android-wear-release-unsigned.aab + bitchat-android-wear-unsigned.apk + bitchat-android-wear.apk bitchat-android-x86-unsigned.apk bitchat-android-x86_64-unsigned.apk bitchat-android-x86_64.apk @@ -53,6 +57,8 @@ for artifact_path in "$RELEASE_DIR"/*; do bitchat-android-armv7-unsigned.apk|\ bitchat-android-play-upload.aab|bitchat-android-release-unsigned.aab|\ bitchat-android-universal-unsigned.apk|bitchat-android-universal.apk|\ + bitchat-android-wear-play-upload.aab|bitchat-android-wear-release-unsigned.aab|\ + bitchat-android-wear-unsigned.apk|bitchat-android-wear.apk|\ bitchat-android-x86-unsigned.apk|\ bitchat-android-x86_64-unsigned.apk|bitchat-android-x86_64.apk) ;; diff --git a/tools/reproducible-builds/sign-play-bundle.sh b/tools/reproducible-builds/sign-play-bundle.sh index fabb8409..abc51a97 100755 --- a/tools/reproducible-builds/sign-play-bundle.sh +++ b/tools/reproducible-builds/sign-play-bundle.sh @@ -48,13 +48,6 @@ fi "${SHA256[@]}" -c SHA256SUMS.unsigned ) -UNSIGNED_AAB="$RELEASE_DIR/bitchat-android-release-unsigned.aab" -SIGNED_AAB="$RELEASE_DIR/bitchat-android-play-upload.aab" -if [ -e "$SIGNED_AAB" ]; then - echo "error: signed Play upload AAB already exists" >&2 - exit 1 -fi - TEMP_DIR="$(mktemp -d)" trap 'rm -rf "$TEMP_DIR"' EXIT STOREPASS_FILE="$TEMP_DIR/storepass" @@ -63,17 +56,40 @@ printf '%s\n' "$BITCHAT_PLAY_KEYSTORE_PASSWORD" > "$STOREPASS_FILE" printf '%s\n' "$BITCHAT_PLAY_KEY_PASSWORD" > "$KEYPASS_FILE" chmod 600 "$STOREPASS_FILE" "$KEYPASS_FILE" -"$JARSIGNER" \ - -keystore "$KEYSTORE" \ - -storepass:file "$STOREPASS_FILE" \ - -keypass:file "$KEYPASS_FILE" \ - -digestalg SHA-256 \ - -signedjar "$SIGNED_AAB" \ - "$UNSIGNED_AAB" \ - "$BITCHAT_PLAY_UPLOAD_KEY_ALIAS" +unsigned_names=( + "bitchat-android-release-unsigned.aab" + "bitchat-android-wear-release-unsigned.aab" +) +signed_names=( + "bitchat-android-play-upload.aab" + "bitchat-android-wear-play-upload.aab" +) -"$JARSIGNER" -verify "$SIGNED_AAB" >/dev/null -"$SCRIPT_DIR/compare-archive-payloads.sh" "$UNSIGNED_AAB" "$SIGNED_AAB" +for ((index = 0; index < ${#unsigned_names[@]}; index++)); do + unsigned_aab="$RELEASE_DIR/${unsigned_names[$index]}" + signed_aab="$RELEASE_DIR/${signed_names[$index]}" + + if [ ! -f "$unsigned_aab" ]; then + echo "error: unsigned Play AAB not found: ${unsigned_names[$index]}" >&2 + exit 1 + fi + if [ -e "$signed_aab" ]; then + echo "error: signed Play upload AAB already exists: ${signed_names[$index]}" >&2 + exit 1 + fi + + "$JARSIGNER" \ + -keystore "$KEYSTORE" \ + -storepass:file "$STOREPASS_FILE" \ + -keypass:file "$KEYPASS_FILE" \ + -digestalg SHA-256 \ + -signedjar "$signed_aab" \ + "$unsigned_aab" \ + "$BITCHAT_PLAY_UPLOAD_KEY_ALIAS" + + "$JARSIGNER" -verify "$signed_aab" >/dev/null + "$SCRIPT_DIR/compare-archive-payloads.sh" "$unsigned_aab" "$signed_aab" +done ( cd "$RELEASE_DIR" @@ -86,4 +102,4 @@ chmod 600 "$STOREPASS_FILE" "$KEYPASS_FILE" } | sort -k2 > SHA256SUMS ) -echo "Verified unsigned AAB was signed locally with the Play upload key and checksummed." +echo "Verified unsigned phone and Wear AABs were signed locally with the Play upload key and checksummed." diff --git a/tools/reproducible-builds/sign-release.sh b/tools/reproducible-builds/sign-release.sh index b36d2da9..6808b60e 100755 --- a/tools/reproducible-builds/sign-release.sh +++ b/tools/reproducible-builds/sign-release.sh @@ -68,11 +68,13 @@ export BITCHAT_GITHUB_KEY_PASSWORD unsigned_names=( "bitchat-android-arm64-unsigned.apk" "bitchat-android-universal-unsigned.apk" + "bitchat-android-wear-unsigned.apk" "bitchat-android-x86_64-unsigned.apk" ) signed_names=( "bitchat-android-arm64.apk" "bitchat-android-universal.apk" + "bitchat-android-wear.apk" "bitchat-android-x86_64.apk" ) diff --git a/tools/reproducible-builds/verify-github-release.sh b/tools/reproducible-builds/verify-github-release.sh index 64cb7574..c2776686 100755 --- a/tools/reproducible-builds/verify-github-release.sh +++ b/tools/reproducible-builds/verify-github-release.sh @@ -50,6 +50,8 @@ attested_artifacts=( bitchat-android-armv7-unsigned.apk bitchat-android-release-unsigned.aab bitchat-android-universal-unsigned.apk + bitchat-android-wear-release-unsigned.aab + bitchat-android-wear-unsigned.apk bitchat-android-x86-unsigned.apk bitchat-android-x86_64-unsigned.apk ) diff --git a/wear/build.gradle.kts b/wear/build.gradle.kts index 819fc5cd..7fea2abe 100644 --- a/wear/build.gradle.kts +++ b/wear/build.gradle.kts @@ -9,14 +9,17 @@ plugins { android { namespace = "com.bitchat.watch" compileSdk = libs.versions.compileSdk.get().toInt() + buildToolsVersion = libs.versions.buildTools.get() defaultConfig { - applicationId = "com.bitchat.watch" + applicationId = "com.bitchat.droid" minSdk = 33 // Wear OS 4 (Pixel Watch 1+): the S+ Bluetooth permissions the app // declares only exist from API 31, and API 30 would additionally require location // for BLE scan results, which the app deliberately refuses. targetSdk = libs.versions.targetSdk.get().toInt() - versionCode = 1 + // Wear releases use a separate high range because Play requires every artifact in + // one application ID to have a unique version code across all form factors. + versionCode = 1_000_000_001 versionName = "0.1.0" vectorDrawables { @@ -24,6 +27,11 @@ android { } } + dependenciesInfo { + includeInApk = false + includeInBundle = false + } + buildTypes { release { isMinifyEnabled = true @@ -32,9 +40,9 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) - // Sign with the debug key so release builds can be installed over the - // debug app during development (same signature = seamless upgrade). - signingConfig = signingConfigs.getByName("debug") + vcsInfo { + include = false + } } } @@ -158,6 +166,7 @@ tasks.matching { it.name.contains("UnitTest", ignoreCase = true) }.configureEach } kotlin { + jvmToolchain(21) compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } From c9a4e6815743bdbb0fdfa8f19909e68b07977b06 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Aug 2026 15:38:15 +0200 Subject: [PATCH 2/2] Serialize reproducible release modules --- tools/reproducible-builds/build-release.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tools/reproducible-builds/build-release.sh b/tools/reproducible-builds/build-release.sh index e474dc32..bd3af191 100755 --- a/tools/reproducible-builds/build-release.sh +++ b/tools/reproducible-builds/build-release.sh @@ -52,9 +52,7 @@ gradle_args=( ) cd "$PROJECT_ROOT" -./gradlew "${gradle_args[@]}" \ - :app:clean :wear:clean \ - :app:bundleRelease :wear:bundleRelease +./gradlew "${gradle_args[@]}" :app:clean :app:bundleRelease aab_source="$PROJECT_ROOT/app/build/outputs/bundle/release/app-release.aab" if [ ! -f "$aab_source" ]; then @@ -63,6 +61,8 @@ if [ ! -f "$aab_source" ]; then fi cp "$aab_source" "$OUTPUT_DIR/bitchat-android-release-unsigned.aab" +./gradlew "${gradle_args[@]}" :wear:clean :wear:bundleRelease + wear_aab_source="$PROJECT_ROOT/wear/build/outputs/bundle/release/wear-release.aab" if [ ! -f "$wear_aab_source" ]; then echo "error: expected Wear release AAB not found" >&2 @@ -71,9 +71,7 @@ fi cp "$wear_aab_source" "$OUTPUT_DIR/bitchat-android-wear-release-unsigned.aab" # AGP cannot build split APKs and an app bundle from the same intermediates. -./gradlew "${gradle_args[@]}" \ - :app:clean :wear:clean \ - :app:assembleRelease :wear:assembleRelease +./gradlew "${gradle_args[@]}" :app:clean :app:assembleRelease declare -A apk_names=( ["app-arm64-v8a-release-unsigned.apk"]="bitchat-android-arm64-unsigned.apk" @@ -92,6 +90,8 @@ for source_name in "${!apk_names[@]}"; do cp "$source_path" "$OUTPUT_DIR/${apk_names[$source_name]}" done +./gradlew "${gradle_args[@]}" :wear:clean :wear:assembleRelease + wear_apk_source="$PROJECT_ROOT/wear/build/outputs/apk/release/wear-release-unsigned.apk" if [ ! -f "$wear_apk_source" ]; then echo "error: expected Wear release APK not found" >&2