diff --git a/.github/workflows/verify-reproducible.yml b/.github/workflows/verify-reproducible.yml new file mode 100644 index 0000000..d5457a7 --- /dev/null +++ b/.github/workflows/verify-reproducible.yml @@ -0,0 +1,49 @@ +name: Verify reproducible build + +# Rebuilds the app on a clean runner and publishes the unsigned APK's SHA-256, so +# anyone can check a published release against a build nobody could tamper with. +# Needs no secrets: it never signs anything. + +on: + push: + branches: [main, master] + tags: ['v*'] + pull_request: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Pinned exactly as README-REPRODUCIBLE.md §2 requires. A different JDK build + # can produce a different APK, which is the whole thing we are proving. + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17.0.20+8' + + - name: Install the pinned SDK packages + run: | + yes | sdkmanager --licenses >/dev/null 2>&1 || true + sdkmanager "platforms;android-35" "build-tools;35.0.0" >/dev/null + + - name: Build (unsigned — no keystore on CI by design) + run: ./gradlew --no-daemon clean assembleRelease + + - name: Report the hash + run: | + APK=app/build/outputs/apk/release/app-release-unsigned.apk + SUM=$(sha256sum "$APK" | cut -d' ' -f1) + echo "### Unsigned APK" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "sha256 $SUM" >> $GITHUB_STEP_SUMMARY + echo "bytes $(stat -c%s "$APK")" >> $GITHUB_STEP_SUMMARY + echo "commit $GITHUB_SHA" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + + - uses: actions/upload-artifact@v4 + with: + name: app-release-unsigned + path: app/build/outputs/apk/release/app-release-unsigned.apk diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..bf74b1b --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,60 @@ +# Cutting a release + +The reproducible build works and `docs/reproducible-build.html` is current. What is +still missing is the path between this repository and the binary: + +- the APK is only reachable from `b.pyblock.xyz:8443/app.php`, so someone who finds + the source here cannot get the binary here; +- there are no tags and no releases, so someone holding an APK cannot tell which + commit produced it without reading commit messages. There are now three + `Release build N` commits — 21, 22 and 23 — and not one tag. + +A tagged release closes both. Nothing below needs anything this repo does not +already have; the build is already reproducible. + +## Per release + +```bash +# 1. Tag the commit that produced the build +git tag -a v0.2.3-23 -m "PyBLØCK ᛒ 0.2.3 (build 23)" +git push origin v0.2.3-23 + +# 2. Build and sign as usual (keystore.properties present) +./gradlew clean assembleRelease + +# 3. Publish, attaching the signed APK +gh release create v0.2.3-23 app/build/outputs/apk/release/app-release.apk \ + --title "PyBLØCK ᛒ 0.2.3 (build 23)" +``` + +Release notes worth carrying, all of which you already compute for the docs page: + +``` +APK SHA-256 6c46683cbf3ad661285faa2ac8399756cf20ac6a7c5ff657762e224c9663237a +Cert SHA-256 e86002aa3ac72325099f92065ec8ab3b7adc70db9e74514ebd53c78acdba3fb5 +Unsigned 186f6c9f380298ebd9b0c7d5c63b158b6678b8ec7201e9ca0bdc94b0552a6b27 +Reproducible: README-REPRODUCIBLE.md +``` + +The workflow added alongside this file rebuilds every commit on a clean runner and +prints that unsigned hash in the job summary, so the number can be checked against a +build nobody controlled. + +## Zapstore + +Zapstore reads GitHub releases, so the tag has to exist first. Publishing links your +APK signing certificate to your nostr identity via NIP-C1 on the first publish — +which is why only the keystore holder can do it. + +```bash +go install github.com/zapstore/zsp@latest +zsp publish --wizard # fills zapstore.yaml with your npub — commit it +``` + +The relay fetches `zapstore.yaml` from this repo, checks the pubkey matches, and +whitelists you automatically. Later releases can use a NIP-46 bunker instead of an +nsec in the environment: + +```bash +SIGN_WITH="bunker://..." zsp publish -r github.com/AstrolexisAI/pyblock-blake2b +``` diff --git a/zapstore.yaml b/zapstore.yaml new file mode 100644 index 0000000..b94cf17 --- /dev/null +++ b/zapstore.yaml @@ -0,0 +1,24 @@ +# Zapstore publishing manifest. +# +# Replace with the nostr identity that will publish. The relay fetches +# this file from the repository and checks that the pubkey matches the signer, which +# is how a publisher gets whitelisted automatically. +# +# Generated/validated by: zsp publish --wizard + +repository: https://github.com/AstrolexisAI/pyblock-blake2b +pubkey: + +apps: + - name: PyBLØCK ᛒ + identifier: com.astrolexis.pyblockblake2b + summary: Self-custody Bitcoin wallet for the BLAKE2b proof-of-work fork + description: | + Native Android wallet for Bitcoin-BLAKE2b. On-device keys, coin control, + Ricochet, PayNym, live pool and CHIRP stats, and community Nostr chat. + + No Google Play Services: push runs over UnifiedPush, so a distributor such + as ntfy is needed for chat and DM notifications. Reproducible build and + Apache-2.0 — see README-REPRODUCIBLE.md. + license: Apache-2.0 + tags: [bitcoin, wallet, blake2b, self-custody, nostr]