The Blind Machine

Cohort Histogram

1.0.0 Ed25519 signed

Application digest — content address of the signed/ payload

9f030d1c07b87761e9aed5ca7214a5cac8ed3f18cde2c71d8032bcbfb1a3a953
cohort_histogram / signed / server.py

104 lines · 3.89 KB · sha256:3bab405bd9f1…2f60f42f

Raw
#!/usr/bin/env python3"""server.py — the BLIND computation for `cohort_histogram`.This is the ONLY code that runs server-side. The kit shim`30_compute_encrypted.py` is its sole caller: it maps the argparse CLI(`--context/--inputs/--out`) onto the reserved ``compute`` function below. Theauthor writes only this pure, bytes-in/bytes-out function; all the argv/fileplumbing lives in the (kit-owned) shim.Trust boundary, made structural by the signature:    compute(inputs: list[bytes], public_context: bytes) -> bytesThere is NO secret-context parameter — this function is incapable of receiving asecret key. It runs in the network-isolated sandbox (`--network none`, read-onlyfs, non-root, resource-limited) with the PUBLIC context and ciphertexts only, sothe server never sees a single plaintext bucket membership.The fold is written ONCE against an abstract evaluator ``E`` (the cleartext oraclein docs/simulation_mode.md swaps a plaintext evaluator into the same shape so itcannot drift from this encrypted path):    aggregate(inputs, E):          # E provides zero / add        acc = <first input>        for ct in rest(inputs):    # cohort_histogram: pure coordinate-wise add            acc = E.add(acc, ct)        return accAdditive-only: the sentinel slot rides along, so ``sum_i (h_i || 1)`` yields theper-bucket count vector in the first B slots and the exact contributor count N inthe trailing sentinel slot. Because each ``h_i`` is one-hot, the first B slotsthemselves also sum to N — a redundancy stage 50 asserts.Determinism / verify-by-re-execution: BFV addition is deterministic, so the sameordered set of input ciphertexts always yields the same result ciphertext bytes(encryption is randomized, the *compute* is not). Re-running this stage on thesame inputs reproduces a bit-identical result digest."""from __future__ import annotationsfrom typing import Iterable, Protocolclass Evaluator(Protocol):    """The abstract op interface both engines implement (see simulation_mode)."""    def zero(self, length: int): ...    def add(self, a, b): ...class BFVEvaluator:    """The real (encrypted) evaluator: ops on TenSEAL BFV ciphertexts."""    def __init__(self, context) -> None:        self.context = context    def zero(self, length: int):        import tenseal as ts        return ts.bfv_vector(self.context, [0] * length)    def add(self, a, b):        return a + b    def load(self, blob: bytes):        import tenseal as ts        return ts.bfv_vector_from(self.context, blob)def aggregate(inputs: Iterable, evaluator: Evaluator, length: int | None = None):    """Fold ``inputs`` under ``evaluator.add`` into a single aggregate.    Folds from the first input (no length needed). If ``inputs`` is empty a    zero vector of ``length`` is returned (requires ``length``).    """    iterator = iter(inputs)    try:        accumulator = next(iterator)    except StopIteration:        if length is None:            raise ValueError("aggregate() needs a length when there are no inputs")        return evaluator.zero(length)    for item in iterator:        accumulator = evaluator.add(accumulator, item)    return accumulatordef compute(inputs: list[bytes], public_context: bytes) -> bytes:    """RESERVED blind entrypoint — homomorphically sum the contributor ciphertexts.    Deserialize the PUBLIC context and each ciphertext, fold them into one    aggregate ciphertext, and return the serialized result. No secret key is    present; defensively refuse a context that carries one.    """    import tenseal as ts    context = ts.context_from(public_context)    if context.is_private():        # The server must never receive a secret key.        raise ValueError("compute stage received a context holding a secret key")    evaluator = BFVEvaluator(context)    vectors = [evaluator.load(blob) for blob in inputs]    return aggregate(vectors, evaluator).serialize()

Inside signed payload digest 9f030d1c07b8…b1a3a953. Change one byte here and the application becomes a different application.

Keyboard shortcuts

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

Send feedback

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