# Triangles Release Process > Canonical release pipeline for `SamiAhmed7777/triangles_v5`. This document > is the source of truth for *how* a release is cut. The implementation lives > in `scripts/verify-reproducible-build.sh` and `scripts/sign-release.sh`. ## Goals 1. **Reproducible** — any two builders with the same source tree, same toolchain, and same flags produce byte-identical binaries. 2. **Signed** — every release artifact has a detached PGP signature that verifiers can check against a known public key. 3. **Verifiable end-to-end** — a third party can confirm a release is legitimate using only `gpg` and `sha256sum`, both installed by default on every Linux distribution. ## Pipeline overview ``` source tag (e.g. v6.1.4) │ ▼ ┌─────────────────────┐ │ CI builds all 4 │ .github/workflows/build-all.yml │ targets on each │ (ubuntu / windows / macos) │ platform │ └──────────┬───────────┘ │ produces: daemon.tar.gz, qt.tar.gz, .deb, .dmg, .exe, ... ▼ ┌─────────────────────┐ │ Local maintainer │ scripts/sign-release.sh │ signs artifacts │ (uses release signing key in local keyring) └──────────┬───────────┘ │ produces: SHA256SUMS, *.asc detached signatures ▼ ┌─────────────────────┐ │ Push to GitHub │ .github/workflows/distribute.yml │ release + Docker │ (uploads artifacts, builds Docker image, │ + Homebrew tap + │ updates Homebrew formula, submits │ WinGet + Snap │ WinGet + Snap PRs) └──────────┬───────────┘ │ ▼ ┌─────────────────────┐ │ Verifier │ scripts/sign-release.sh --verify │ independently │ + gpg --import │ confirms │ └─────────────────────┘ ``` ## Reproducibility — how it works today The Triangles build is already reproducible for Release builds with the following properties: | Property | Implementation | |---|---| | `BUILD_DESC` | Git describe output, written to `build.h` at build time | | `BUILD_DATE` | **Commit timestamp** (NOT wall-clock), from `git log -n 1 --format=%ci` | | `__DATE__`/`__TIME__` fallback | Dead code in practice — `build.h` always defines `BUILD_DATE` | | Build paths in binaries | Mapped with `-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.` so absolute source paths do not leak into debug info | ### Verifying reproducibility Run on a clean checkout: ```bash scripts/verify-reproducible-build.sh ``` This builds `trianglesd` twice into two separate build directories and compares SHA256 hashes. Exits 0 on success. Options: - `BUILD_TYPE=Debug scripts/verify-reproducible-build.sh` - `TARGET=triangles-qt scripts/verify-reproducible-build.sh` - `BUILD_DIR_A=/tmp/A BUILD_DIR_B=/tmp/B scripts/verify-reproducible-build.sh` ## Signing — how it works ### Generate (or import) a release signing key **One-time setup** (the maintainer's machine): ```bash # The release-signing key currently in use is: # # uid: Krystie Triangles Release # fp: 523A 8183 3EB7 2015 73E1 EFE1 DCF2 5799 6810 7984 # sub: 6913 E136 10F6 9818 3429 CE20 C2DC 6061 8C85 A159 # algo: RSA-4096, created 2026-04-29, expires 2028-04-28 # # This is an unattended signing key used by the release CI to sign # release artifacts (daemon.tar.gz, qt.tar.gz, .deb, .dmg, .exe, .AppImage) # without a human in the loop. It is stored as a GitHub Actions secret. # # Git tags are signed by the maintainer's personal key # (uid `Sami `, fp `53AA 858E F0DD D528 EC2C 2ABD # 0BF7 F887 2FE0 E859`) so the tag and the artifacts can be verified # independently. # To print the public key for the current release-signing key: gpg --armor --export 0xDCF2579968107984 > release-pubkey.asc # To export the maintainer's tag-signing secret key (for backup): gpg --export-secret-keys 0x0BF7F8872FE0E859 > release-seckey-BACKUP.asc chmod 600 release-seckey-BACKUP.asc ``` **Import an existing key** (e.g. on a new maintainer machine): ```bash gpg --import release-seckey-BACKUP.asc ``` ### Sign a release directory After CI has produced the artifacts in a known directory: ```bash scripts/sign-release.sh /path/to/release-dir ``` This will: 1. Generate `SHA256SUMS` for every release artifact (.tar.gz, .deb, .dmg, .exe, .zip, .AppImage) 2. Write a detached PGP signature (`.asc`) for each artifact 3. Write a detached PGP signature over `SHA256SUMS` itself 4. Refuse to run if the signing key isn't in the local keyring (safety) ### Verify a release A third party (user, exchange, package maintainer) verifies with: ```bash # 1. Import the public key (one-time). gpg --import release-pubkey.asc # 2. Verify everything in the release directory. scripts/sign-release.sh --verify /path/to/release-dir ``` This checks: - `SHA256SUMS.asc` against `SHA256SUMS` (the master signature) - Each `.asc` against its `` (belt-and-suspenders) - Each artifact's SHA256 against `SHA256SUMS` (integrity) ## Why both per-artifact signatures AND a SHA256SUMS signature? - **SHA256SUMS + signature**: small, fast to verify, single point of trust. If the SHA256SUMS.asc checks out and a file's SHA256 matches an entry, you're done — you trust that entry. - **Per-artifact signatures**: defense against a hypothetical attack where someone modifies `SHA256SUMS` but not the artifacts (or vice versa). Two independent signature chains. For most verifiers, checking `SHA256SUMS.asc` + `sha256sum -c SHA256SUMS` is sufficient. The per-artifact .asc files are insurance. ## CI integration `.github/workflows/build-all.yml` already produces the artifacts. The remaining work (separate PR) is to add a "sign" job that runs `scripts/sign-release.sh` against the assembled release directory using a key stored as a GitHub Actions secret. **Required secrets (one-time setup in repo Settings → Secrets):** - `GPG_PRIVATE_KEY` — base64-encoded `release-seckey-BACKUP.asc` (see [GitHub docs on encrypted secrets](https://docs.github.com/en/actions/security-guides/encrypted-secrets)) - `GPG_PASSPHRASE` — passphrase for the signing key (if any) - `GITHUB_TOKEN` — already provided by Actions **Suggested job sketch** (in `.github/workflows/build-all.yml` after all build jobs complete): ```yaml sign: name: Sign release artifacts needs: [build-linux-daemon, build-linux-qt, build-windows-daemon, build-windows-qt, build-macos] runs-on: ubuntu-22.04 if: startsWith(github.ref, 'refs/tags/v') steps: - uses: actions/checkout@v4 - name: Import signing key run: | echo "${{ secrets.GPG_PRIVATE_KEY }}" | base64 -d | gpg --import - name: Sign artifacts run: scripts/sign-release.sh release-artifacts/ env: GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} ``` ## Public key distribution The release public key MUST be published in **at least three independent places** so a keyserver takedown or DNS hijack cannot prevent verification: 1. **This repository** — `release-pubkey.asc` at the repo root, committed on every release tag. 2. **The website** — `https://cryptographic-triangles.org/release-pubkey.asc` 3. **Public keyservers** — submit to `keys.openpgp.org`, `keyserver.ubuntu.com`, `pgp.mit.edu`. Each is independently operated. Distribution list refreshed with every key rotation (rare; treat as multi-year commitment). ## Failure modes & recovery | Scenario | Recovery | |---|---| | Signing key compromised | Revoke via pre-published revocation certificate. Re-cut release. Document incident. | | Signing key lost (no backup) | Cannot sign new releases. Existing artifacts still verify against the published public key. Treat as catastrophic; re-mint a new key and treat the chain as fork-vulnerable until community updates. | | Public key not yet distributed | User gets `gpg: Can't check signature: No public key`. Provide clear "first verify the key fingerprint out-of-band" instructions on the website. | | CI secret leaked | Rotate the signing key immediately; treat all artifacts signed with the old key as suspect. | | `SHA256SUMS` signed but artifacts don't match | `sha256sum -c` fails. Either an artifact was corrupted in transit, or someone tampered. Re-download from GitHub and re-verify. | ## Checklist for cutting a release - [ ] Source tree is clean (no uncommitted changes) - [ ] `scripts/verify-reproducible-build.sh` passes (builds are reproducible) - [ ] All CI jobs on the release tag are green - [ ] Release artifacts are in a single directory (`release-artifacts/`) - [ ] `scripts/sign-release.sh release-artifacts/` runs without error - [ ] `scripts/sign-release.sh --verify release-artifacts/` passes - [ ] `release-pubkey.asc` is current and committed to the repo - [ ] GitHub release created with all artifacts + SHA256SUMS + SHA256SUMS.asc - [ ] `distribute.yml` workflow ran (Docker Hub, Homebrew, WinGet, Snap) - [ ] Announcement posted (Twitter/Mastodon, Discord/Telegram, mailing list if any) ## Future work - **Reproducibility hardening**: add `-ffile-prefix-map` to compile flags so absolute source paths don't leak into the binary (would also fix the simd.c:265 UBSan build-id drift). - **Gitian-style deterministic builds**: containerized build environment pinned to a specific GCC/binutils version, so multiple independent verifiers can rebuild from source and get identical hashes. - **Transparency log**: publish each release artifact hash to a Sigstore / sigsum / Certificate Transparency-style log so any tampering is publicly auditable. - **Key rotation policy**: document how/when the signing key gets rotated (probably never, but state the policy).