1. The signing certificate
Every official Titlan release is signed with one release certificate. This certificate does not rotate: a Titlan APK signed by any other certificate is not an official release. Its SHA-256 fingerprint, as the two common tools print it:
As keytool prints it:
EC:DD:E6:C1:76:29:D7:44:7C:62:17:13:7B:27:B0:AF:9F:91:5D:C6:C5:CA:CF:8C:38:FF:02:D0:B2:2C:8A:E0
As apksigner --print-certs prints it:
ecdde6c17629d7447c6217137b27b0af9f915dc6c5cacf8c38ff02d0b22c8ae0
Check the APK you downloaded (called titlan.apk below) with either tool. apksigner ships with the Android SDK build tools; keytool ships with the JDK.
apksigner verify --print-certs titlan.apk
keytool -printcert -jarfile titlan.apk
Compare the certificate SHA-256 digest that the command prints with the value above. If it differs, the file was not signed with the Titlan release certificate: do not install it.
2. Release artifacts
Titlan downloads are published only on GitHub Releases. Each release entry publishes the signed APK together with its SHA-256, beside the CycloneDX software bills of materials (protocol core, relay, and the app's dependency closure), the reproducibility report, and the build provenance attestation, so the download and its proof sit side by side.
Compute the hash of the file you downloaded and compare it with the SHA-256 on that release's entry:
sha256sum titlan.apk
This page does not repeat per-release hashes; the release entry is the authoritative place for them.
3. Reproducible builds
The repository's reproducible-build script is what CI runs on every push and in every tagged build. What it does:
- It builds the release artifacts twice, each time from a fresh copy of the source tree placed at a fixed canonical path: the relay's release binary (Cargo with locked dependencies and embedded path prefixes remapped) and the unsigned release APK (Gradle with locked dependencies).
- It pins the build timestamp to the commit's own time, so both passes see the same value.
- It compares the SHA-256 of each artifact across the two passes and fails if any differ. On an APK mismatch it lists the differing archive entries.
- It writes a report naming the toolchain versions and both hashes of each artifact.
Hashes are comparable across machines only when built from the same canonical path with the same pinned toolchains; the repository's build documentation lists the pins. CI builds are unsigned by design: signing happens offline, outside the repository and CI, and the signed APK's SHA-256 is published with the release.
To reproduce a release yourself, check out the tagged commit, install the pinned toolchains, and run the script. Its report can then be compared with the reproducibility report published for that release.
Further reading
- Wire protocol specification 1.0 (frozen; third-party implementable)
- Threat model 1.0 (what is mitigated, what is residual, what is accepted)
- SECURITY.md (how to report a vulnerability)