The Blind Machine

Allele Frequency Count

1.0.0 Ed25519 signed

Application digest — content address of the signed/ payload

b94bd9320ea0f15b2ec265ecd0cf855f273548ffb920f395212256f4d4664eed
allele_frequency_count / SECURITY.md

60 lines · 3.11 KB · sha256:a5aa78a3b18c…e476adf8

Security notes — allele_frequency_count

Scoped to this bundle. The platform-wide threat model lives in
docs/manifesto.md, docs/requirements.md, and docs/simulation_mode.md §5.
Kerckhoffs applied to a product: no guarantee rests on the secrecy — or the
honesty — of the server.
Don’t trust, verify.

Trust classes (what may cross the wire)

class example artifact may leave the owner’s machine?
RAW raw.json genotypes no
ENCODED encoded.json dosage vector no
PRIVATE secret_context.tenseal (secret key), plain.json no, ever
ENCRYPTED cipher.bin, result.bin yes
PUBLIC public_context.tenseal yes

Only ENCRYPTED and PUBLIC are ever uploaded. 00_keygen.py writes the secret key
to secret_context.tenseal, which is used only by 40_decrypt.py on the
researcher’s machine. There is no /api/v1 endpoint that accepts a secret key.

Server holds no secret key

30_compute_encrypted.py — the only server-side stage, a kit shim that runs
server.py’s compute — loads the public context plus ciphertexts and
homomorphically adds. It defensively refuses a context that carries a secret key
(context.is_private() → error). The server
therefore never sees a single plaintext genotype; it operates on ciphertext and
returns ciphertext. Decryption happens only where the secret key lives: locally.

The append-1 sentinel is NOT a MAC

The trailing sentinel slot decrypts to the exact contributor count N, and
dropping one upload yields N−1 (test: test_sentinel_tracks_dropped_upload). It
catches mechanical corruption / miscounting — it gives no guarantee that
contributions are distinct, genuine, or non-Sybil. Call it what it is: an
integrity check, not authenticity.

What FHE here does and does not hide

  • Hides: individual genotype vectors from the server (inputs are ciphertext).
  • Does not hide: the released aggregate itself, and metadata (researcher
    identity, participant count/timing, ciphertext sizes, protocol choice).
  • Differencing (K vs K+1): the statistic leaks an individual if you can
    compute A_{K+1} − A_K. aggregate_only + min_contributors ≥ 20 +
    allowed_runs_per_project: 1 (cohort freeze + min-N + run cap) mitigate
    this; they are not a complete defense. Overlapping/Sybil differencing across
    separately frozen cohorts needs DP + cross-job query budgets (v2). Documented,
    not hand-waved — see docs/simulation_mode.md §5.
  • Verify-by-re-execution is determinism, not zero-knowledge. Re-running
    30_compute_encrypted.py on the same ciphertexts reproduces a bit-identical
    result digest; it proves the computation, it is not a ZK proof.

Exactness / parameter safety

BFV is exact in Z_t. The plaintext modulus must satisfy t > max coordinate sum = 2·N. The default t = 1032193 (a 20-bit batching prime) stays exact for N
up to ~500k; a real run at larger N must raise t (or the simulation feasibility
sweep will report infeasible-at-these-params on overflow). The sentinel sum is
N, always ≪ t.

Packaged support file for application digest b94bd9320ea0…d4664eed. It ships in the archive for review, but is outside the signed payload digest.

Keyboard shortcuts

  • Cmd/Ctrl+K
    Focus global search
  • ?
    Open keyboard shortcuts

Send feedback

We'll only use this to respond to your feedback.