Verify Counted
End-to-end encryption is only worth something if the code running in your browser is the code we say we serve. This page exists so you do not have to take our word for it.
What is published
Every version of Counted is built once, in a frozen environment, and the result is fingerprinted: the server, the WebAssembly module, its JavaScript and every file of the app, as SHA-256. The list is served by the very version that is running:
https://counted.fr/SHA256SUMS.txt
Each line is a file's fingerprint and its path on counted.fr: assets/web_bg-….wasm, assets/web-….js, the fonts and stylesheets. The same list is in every release's notes, away from the server that serves it.
Check what you receive
From a terminal, download the list and every file it names, then compare:
curl -sO https://counted.fr/SHA256SUMS.txt
for f in $(awk '$2 != "server" {print $2}' SHA256SUMS.txt); do
curl -s --create-dirs -o "$f" "https://counted.fr/$f"
done
sha256sum -c --ignore-missing SHA256SUMS.txt
Every line must end in OK. server is the server executable; it is not served, its fingerprint is there for comparison with a build.
Build it yourself
The build is reproducible: base images and compiler pinned by digest, same paths, same flags. The client's source code is public at github.com/counted-labs/counted, one tag per version, under the AGPL-3.0 licence. From it, three commands rebuild what this site serves and compare:
git clone --branch vX.Y.Z https://github.com/counted-labs/counted.git && cd counted
docker buildx build --platform linux/amd64 -f Dockerfile.client --target sums --output type=local,dest=out .
curl -s https://counted.fr/SHA256SUMS.txt | grep -v ' server$' | diff - out/SHA256SUMS.txt
An empty diff means every file your browser receives is byte for byte what those sources produce. The server line is the native binary: its code is not published, it is skipped from the comparison, and it never sees your data in clear - everything is encrypted on your device before it is sent. Each GitHub release also carries the SHA256SUMS.txt of its version, a copy independent of this server. We run the same rebuild ourselves, without cache, for every release.
Check the signature
Every SHA256SUMS.txt is signed by our build pipeline with Sigstore, and the signature is written to a public append-only log: a release's list cannot be swapped afterwards without it showing. With cosign:
base=https://github.com/counted-labs/counted/releases/download/vX.Y.Z
curl -sLO $base/SHA256SUMS.txt -sLO $base/SHA256SUMS.txt.sigstore.json
cosign verify-blob --bundle SHA256SUMS.txt.sigstore.json \
--certificate-identity "https://gitlab.com/jbosi/counted//.gitlab-ci.yml@refs/heads/main" \
--certificate-oidc-issuer https://gitlab.com SHA256SUMS.txt
curl -s https://counted.fr/SHA256SUMS.txt | cmp - SHA256SUMS.txt
Verified OK, then no output from cmp: the list this site serves is exactly the one the build of that release signed. The signature proves where the list comes from, not that the code is safe - that is what the two checks above are for.
Checked without us
Two checks run on GitHub's machines, which share nothing with ours, and their results are public:
- For every release, the client is rebuilt from its tag and compared with that release's signed
SHA256SUMS.txt. - Every 6 hours, the list counted.fr serves is compared with the signed list of a published release; every listed file must be served unchanged, and the app's pages must load only files on the list.
The mobile apps
The Android and iOS apps are built from the same sources. What can be checked depends on the store:
- F-Droid rebuilds every version from the public repository and distributes our APK only if its own build gives the same bytes, signature aside.
- Google Play signs the app with its own key, whose certificate is below: it proves a copy came from Play, but that copy cannot be compared with a build yet.
- The App Store encrypts each copy for the device that installs it: nobody can compare it with a build, us included. Identifiers: team
UK936HFQ24, appfr.counted.app.
Signing certificate of the F-Droid APK and of the one attached to GitHub releases, as SHA-256:
D8:D4:02:6F:49:6F:3C:21:54:F1:38:E8:17:10:33:2E:A5:28:8A:AA:B8:56:7A:2F:8D:0E:71:3D:E1:D5:93:E8
To check the app installed on your phone, the AppVerifier app compares its certificate with this one. From a computer:
adb pull "$(adb shell pm path fr.counted.app | head -1 | cut -d: -f2)" counted.apk
apksigner verify --print-certs counted.apk | grep SHA-256
Google Play signing certificate, as SHA-256:
4C:1E:7D:F5:2C:FB:D6:03:14:C2:0B:E4:AA:7E:9E:7D:30:19:1C:50:24:3B:F5:09:1A:21:F4:B1:30:98:A6:DC
What this proves
- The code your browser runs is the code whose fingerprint is published.
- Two people checking at the same time receive the same code.
- A version rebuilt from its sources yields the same files - so nothing was added between the code and what is served.
What this does not prove
- That the code is correct: a fingerprint attests a file's identity, not its quality.
- That a different file served to one person would be caught: the automated checks see what everyone receives, and your browser checks nothing by itself. If that worries you, use the mobile app, whose code is fixed at install.
- What the server does: its code is not public. It only receives encrypted data, but how it handles metadata cannot be checked from outside. Our security policy says, for each claim, what backs it.
A fingerprint that does not match? Write to us with the line and the date.