Architecture
Cifra is a Bittensor subnet where miners compete to build the fastest fully-homomorphic circuit for a model the owner publishes. This is the engineering rationale; the whitepaper is the narrative version.
The three roles
| Role | Runs in | Does |
|---|---|---|
| Owner | — | Publishes a plaintext reference model + calibration + rules (accuracy floor, complexity cap, p_error floor, toolchain pin) to a HuggingFace repo. Sets no weights. |
| Miner | .venv (+ shells .venv-fhe) |
Compiles its own optimized circuit for the model, uploads the recipe to HuggingFace, commits {repo, rev} on-chain. |
| Validator | .venv (+ shells .venv-fhe) |
Downloads each recipe, compiles + grades it in a sandbox, ranks passing miners by measured FHE latency, sets geometric weights. |
owner ──publish──► reference model + calibration + rules
miner ──compile──► recipe.json ──HF upload──► set_commitment({repo, rev})
validator ─get_commitment─► download ─► SANDBOX(compile 128-bit → accuracy gate → latency + complexity)
─► latency rank (3% tie-band, copy-priority) ─► geometric weights ─► set_weights
Why these choices
Rank by measured latency; gate deterministically. The competitive metric is the
measured fhe="execute" latency of the circuit (mean ms per encrypted inference —
keygen excluded), because latency is what the owner ultimately buys. Latency is a
local measurement, so the design keeps consensus tight in three ways:
- Only the ordering matters. Weights are geometric down the leaderboard (1, 1/3, 1/9, …), so validators need only agree on ranks, not raw milliseconds.
- A 3% tie-band absorbs jitter. Same-machine repeat timings vary ~1–2%, while real circuit improvements shift latency by tens of percent. Latencies within 3% of the band leader count as equal, and within a band the earlier on-chain committer wins (the commit block is read from the chain, identical on every validator). A challenger must be genuinely >3% faster to displace an incumbent — which also makes copy-and-tweak leapfrogging pointless.
- The gates stay fully deterministic. Eligibility (accuracy floor, forced 128-bit,
complexity band) uses
circuit.complexityand a seeded gate set — identical everywhere under the pinned toolchain.
complexity is still measured and logged next to latency (it is the deterministic,
hardware-neutral proxy), and the caveat is stated plainly: validators on very different
hardware can disagree on adjacent ranks; keeping validator hardware roughly uniform
keeps the ranking stable. See core/scoring.py.
The validator compiles, not the miner. A recipe is a description — Concrete-ML model
JSON loaded via loaders.loads, never pickle/joblib — so it carries no executable code.
The validator builds the compile Configuration itself, which buys two guarantees:
- 128-bit security is structural. The validator forces
security_level = SECURITY_128_BITS, and Concrete's enum has no weaker rung — so "weak-crypto-for-speed" cannot be requested. The verdict re-checks the achieved level. Seefhe/compile.py. - No RCE. No miner binary ever runs on the validator. Compilation happens in an isolated
subprocess anyway (defense in depth): CPU + wall-clock bounded, credentials stripped. See
sandbox/jail.pyandfhe/evaluate.py.
The accuracy gate is deterministic. Gate inputs are seeded from the task_id
(core/sampling.py), so a miner's self-check and every
validator draw the identical set — the pass/fail boundary is reproducible.
Copy-priority. Recipes are fingerprinted (SHA-256 of the canonical envelope); the
earliest on-chain committer of a fingerprint keeps the reward, later copies score zero.
See apply_copy_priority in core/scoring.py.
Toolchain pin. Deterministic complexity holds only within one concrete-ml /
concrete-python build. The task pins both versions; a validator whose versions differ
excludes itself from scoring rather than emit divergent numbers.
The two-venv split
bittensor needs numpy 2.x; concrete-ml pins numpy 1.26.4 — they cannot share a venv. The
split is also the security boundary: the chain process (which holds the wallet) never imports
concrete-ml and never runs miner-influenced code; it only shells into the isolated
.venv-fhe evaluator and reads back a small JSON verdict. The one rule: no module imports
both bittensor and concrete-ml.
cifra/
core/ pure numpy — recipe envelope, scoring, sampling (safe in both venvs)
chain/ the only bittensor import — metagraph, get/set_commitment, set_weights
hub/ HuggingFace upload/download (rev-pinned, byte-capped)
fhe/ concrete-ml — compile (128-bit), sandbox evaluator, miner optimizer
sandbox/ isolate the evaluator subprocess (CPU + wall-clock bounded)
roles/ miner / validator orchestration + task loading (from a HF repo id)
cli.py `cifra miner|validator ...`
See also
- configuration.md — every setting and how to supply it
- running-a-miner.md · running-a-validator.md