Skip to content

Open source

The client and the verifier are open source under Apache-2.0, published at github.com/tomdyson/blindproof-client. The backend and the desktop apps are closed. This page explains the boundary, the reasoning, and the publishing mechanics.

What's open, and why

BlindProof's central claim is that the manuscript's content never leaves the author's machine. That claim is only worth something if it can be checked, and the two pieces of code that embody it are exactly the two that are public:

  • client/blindproof.py — everything that touches plaintext: key derivation (argon2id → HKDF-SHA256), encryption (AES-256-GCM), per-snapshot HMAC commitments, and the sync code that shows precisely what is and isn't uploaded.
  • client/verify.py — the verifier that ships inside every proof bundle. A publisher can audit the copy in their bundle, or read it here; the bundle's PDF records the shipped copy's SHA-256 so the two can be compared. Publishing its canonical source backs the durability promise: verification works with no BlindProof infrastructure, indefinitely.

The licence is Apache-2.0 — permissive, with a patent grant, and no copyleft obligations imposed on the closed Mac app, which loads client/blindproof.py at runtime as its single crypto implementation. SPDX headers live in both files; the full text ships as the public repo's LICENSE (kept in the monorepo at client/public/LICENSE).

The mirror model

The monorepo stays the source of truth; the public repo is a read-only downstream mirror. Rationale in commercial-considerations.md §Repository topology: the apps hard-depend on the client, so extracting it as a separate upstream would add cross-repo friction for no visibility gain.

Mechanics:

  • scripts/publish_client.py assembles the public tree into a staging directory: client/blindproof.pyblindproof.py, client/verify.pyverify.py, client/tests/tests/, plus the static overlay in client/public/ (README, LICENSE, the public repo's own CI workflow). An allowlist in the script refuses to stage any unexpected file — the guard against accidentally publishing something private is code, not convention.
  • .github/workflows/publish-client.yml runs on every push to main that touches the mirrored paths (plus workflow_dispatch for seeding and on-demand republish). It runs the client suite against a disposable copy of the staging tree (pytest writes __pycache__/.pytest_cache into whatever it touches — testing the tree that gets pushed is how caches leaked into the first snapshot), re-verifies the pristine tree against the allowlist (publish_client.py --verify) immediately before publishing, then clones the public repo with the CLIENT_MIRROR_TOKEN fine-grained PAT, replaces the tree wholesale, and pushes one squashed commitMirror blindproof@<short-sha> — when anything changed.
  • Monorepo history is never pushed. Each publish is a fresh snapshot commit on the mirror's own linear history, so nothing from the private repo's past (paths, messages, mistakes) can leak. The short SHA in the commit message is the traceability link back.

Consequences worth knowing:

  • Editing the public repo directly is pointless — the next publish overwrites it. Changes land in client/ here and flow outward.
  • The wire contract is pinned from both sides. Published clients can't be updated retroactively, so backend/api/tests/test_client_contract.py and client/tests/test_wire_contract.py (which itself mirrors publicly) fail if a schema change would strand them.
  • client/verify.py changes trigger a backend redeploy (deploy.yml path filter): the verifier ships from the backend image into every bundle, so the mirror and production must move together.

One-time setup

The mirror needs two things that live outside this repo: the public repository tomdyson/blindproof-client (created empty), and a fine-grained PAT scoped to only that repository with two permissions — Contents: Read and write and Workflows: Read and write — stored as the CLIENT_MIRROR_TOKEN Actions secret in the monorepo. Workflows write is needed because the published tree includes the public repo's own CI workflow (.github/workflows/tests.yml); without it, GitHub rejects the push with "refusing to allow a Personal Access Token to create or update workflow ... without workflow scope". Seed or re-seed the mirror by dispatching publish-client from the Actions tab. Repo-level settings (issues off, description) are manual, one-off.

What stays closed

The Django backend, the Mac app, the Windows app when it exists, and the docs/spec/strategy material. The survival promise for authors does not depend on any of it staying available: proof bundles verify independently forever, and the shutdown plan (see Threat model) commits to a source release if the service ever winds down.