Documentation
Releases and verifying them
Status: the key is generated, the releases are signed.
The release public key:
RWRA0GxkCXP1xdvCABffa3Lu8XBFplqC9P7JSefgpCk/NSkZ6q9k4i4YIt is in
scripts/verify-release.shtoo. The private half never leaves the machine that signs, and is not in CI — a key in a CI secret is a key anyone with push access can use.
The cheapest attack on a new chain is not breaking the algorithm. It is handing out a modified binary: it is enough to slip someone a node that sends the key elsewhere when a transaction is signed, or a miner that mines to somebody else’s address every hundredth block. That is why a release has two properties, and each solves something different:
| what it proves | what it does not prove | |
|---|---|---|
| the signature | the binary was released by the key holder | that the source does what it claims |
| reproducibility | the binary came from this source | that the source is free of bugs |
One without the other is half the story: a signature without reproducibility means “trust me”, and reproducibility without a signature means “trust whoever sent you that file”.
How a release is made
A release has four things people download — the all-in-one miner for Linux and for Windows, and
the GUI wallet for Linux and for Windows — and three of them cannot be built on our own runners.
Tauri links the system webview, so the Windows installer needs a Windows machine; nvcc drives MSVC
there, so the GPU plugin for Windows needs one too. The release is therefore assembled by GitHub
Actions and signed afterwards, away from CI:
git tag -s v0.1.0 && git push origin v0.1.0
# → the `release` workflow builds everything and leaves a DRAFT prerelease
scripts/sign-release.sh v0.1.0
# → signs SHA256SUMS and attaches the signature
gh release edit v0.1.0 --repo powmachina/powmachina --draft=false
The workflow calls scripts/release.sh … --no-sign for the binaries, so what CI produces comes out
of the same script a release made by hand would use — two build paths would drift, and the
difference would only show up in the checksums. The GUI installers and the Windows plugin come from
their own jobs (wallet-gui, cuda-windows), which the release workflow calls rather than
copies: the -gencode list of supported cards has to stay identical everywhere.
sign-release.sh checks that the files on the release match SHA256SUMS before signing, in both
directions — a file added to the release after CI built it would otherwise be covered by a signature
that never saw it.
Everything local, without GitHub (no GUI installers this way):
git tag -s v0.1.0 && scripts/release.sh v0.1.0
The script builds in a container pinned by digest, not by tag — golang:1.26 changes with every
patch release and the binary’s bytes change with it. The build runs with -trimpath,
-buildvcs=false and -ldflags="-s -w -buildid="; the modules are fetched in a separate step, so
the build itself needs no network.
The source has to be clean (git status). A release from a work-in-progress tree cannot be
reproduced from git, so it would not be a release but a file.
Signing is done with minisign (ed25519). GPG would mean explaining keyrings and the web of trust to people who only want to run a node; minisign has a one-line public key and a one-command verification. The private key is not in the repo or in CI — signing happens on the machine where it lives, not in a pipeline that every commit can start. That is the reason the workflow leaves a draft: a key in a CI secret is a key anyone with push access can use, and then the signature stops meaning what it is supposed to mean.
How whoever downloaded a binary verifies it
scripts/verify-release.sh ~/Downloads/powmachina-v0.1.0
Or by hand, in this order:
minisign -Vm SHA256SUMS -P <the public key> # 1. the hash list is from us
sha256sum -c SHA256SUMS # 2. the files match the list
The other order is pointless: an unsigned hash list only proves that a file agrees with itself.
And whoever does not want to trust us at all builds it themselves and compares:
git checkout $(grep ^commit VERSION | cut -d' ' -f2)
scripts/release.sh v0.1.0 --check # builds twice and compares the hashes
What is not reproducible and why
| status | |
|---|---|
powmachinad, the wallet, the miner, the signer (linux-amd64) |
✅ bit-exact in the pinned container |
powmachinad.exe, the wallet, the miner (windows-amd64) |
✅ cross-built through mingw in the same image |
libpowm-cuda.so |
❌ no — nvcc is not deterministic and the plugin is built on a machine with a card (rule 3: the kernel has to pass the test vectors, otherwise it is not released). The hash in SHA256SUMS says which file came out; it does not say that you will build it the same way from the same source. |
| macOS | ❌ not there yet. A cross build with cgo needs the Apple SDK, and that cannot legally just be packed into a container. |
powm-cuda.dll (GPU mining on Windows) |
❌ not reproducible, for the same reason as the .so. It is built by the cuda-windows workflow (nvcc driving MSVC) and verified against the test vectors on a machine with a card before a release takes it. The files carry no Authenticode signature — Windows says “unknown publisher” and Smart App Control blocks them where it is on. |
| the quorum signer on Windows | ❌ deliberately. A quorum key belongs on a machine that runs around the clock. |
| the images in the registry | ❌ they are not a release. They are images for our own machines, and :latest moves. |
What is not here is written down here deliberately. what to verify yourself is the list of admitted weak spots and this is one of them.