thirty spkesDOCS
HomeDashboardGet an API key

Anti-Cheat

The miner owns the machine, samples its own tasks, runs its own benchmarks, and reports its own score. Every honest-looking number in the system is produced by someone with a direct financial interest in inflating it. This page is the complete account of why that is nonetheless safe — and where it still isn't.

The design principle: because the artifact is public and cryptographically bound to what ran, cheating is publicly detectable rather than something we must prevent by hiding data.

The attack surface, and what stops each attack

Attack Backstop Reason emitted
Run code B, claim artifact A's score The runtime hashes the bytes it loads and runs; the validator re-hashes the bundle it downloads artifact_binding_mismatch
Edit the proof after attestation report_data commits to every field; the quote breaks report_data_mismatch
Forge the enclave Full DCAP to a pinned Intel root bad_platform_quote
Run a modified runtime MRTD + RTMR1/2/3 gated against the owner's on-chain set unapproved_runtime
Run on a vulnerable platform TCB status policy tcb_out_of_date, tcb_revoked
Under-report cost Metered in-enclave, agent has no network and no key — (impossible)
Answer without the pool Trace inspected for ≥ 1 metered call per task no_pool_call
Publish a trace that doesn't match Trace hashed into call_log_hash, bound by the quote trace_mismatch
Reuse an old good proof Proof binds (epoch, nonce) epoch_nonce_mismatch
Run 20 times, submit the best One nonce per epoch, seeded by an unpredictable beacon epoch_nonce_mismatch
Grind within the epoch / swap the revealed proof (opt-in) Intra-epoch commit window (F7): commit report_data on-chain in-window, reveal exactly it no_proof_commit · commit_out_of_window · commit_mismatch
Front-run a rival's commit Commit-reveal delay
Copy a rival's commit string Commit is salted with your hotkey bad_commit
Resubmit a rival's proof Proof binds the hotkey hotkey_mismatch
Copy a rival's artifact Behavioural dedup on the attested answer vectors; earliest commit wins copy_of:<uid>
Hardcode answers Source is public and scanned hardcoded_answers
Memorise the benchmark (table in the opaque weights) Grounding check (default): every answer must derive from a logged pool call. Opt-in: secret held-out probe + collapse test ungrounded (default) · memorization (probe)
Call a stronger off-pool model PinnedBackend allow-list UnpinnedModelError
Point the validator at nothing Every audit fails closed audit_error:*, no_proof

The binding

This is the load-bearing mechanism, and the one an earlier adversarial review found vacuous before it was fixed.

The naive design has the miner supply source_hash and weights_hash alongside its results. That proves nothing — a miner runs code B and stamps artifact A's hashes. The fix has two halves, and both are required:

  1. The runtime derives the identity. The measured enclave load_agents the committed bytes and computes source_hash/weights_hash from exactly those bytes. The miner never supplies them. So "what ran" ≡ "what's attested."
  2. The validator binds to the commit. It downloads the public bundle itself, recomputes both hashes, and checks them against the on-chain commit via verify_commit — never taking the miner's word.

One hardware signature over {measurement, report_data} then asserts both "this approved enclave ran" and "it produced this exact payload."

Commit-reveal and the hotkey salt

koth1|<hf_repo>|<revision>|sha256(source_hash|weights_hash|hotkey)
  • The reveal delay stops front-running: nobody sees your artifact hashes before yours is committed.
  • The hotkey salt stops commit-copying: the hash only verifies against your hotkey, so republishing a rival's commit string binds nothing.
  • Earliest commit wins every tie, so being the copier never pays even when the copy is perfect.

The epoch nonce

The task slice is seeded from a per-epoch nonce derived from the block hash at epoch start:

nonce = sha256("koth-epoch|{epoch}|{beacon}")[:16]
seed  = sha256("{nonce}|{epoch}|{benchmark}")[:8]

Nobody — including the owner — knows the slice until that block is minted. This is what simultaneously kills best-of-N (you get one slice per epoch, and it binds into the proof) and pre-computation (you cannot memorise a slice you cannot predict).

Behavioural dedup

A weight hash is trivially defeated: perturb a float and the hash changes. So copies are detected by behaviour, not bytes.

By default the fingerprint is the attested scored-answer vector already in each proof — no re-execution needed. Miners whose answer vectors agree at ≥ 95 % are clustered; the earliest-committed member is kept and the rest are disqualified as copy_of:<uid>.

This is robust to weight-space perturbation, prompt reformatting, and cosmetic source edits — anything that leaves behaviour intact.

Memorisation: the default is grounding

The threat is a router that hides an answer table in its opaque weights — invisible to the public-source scan. Two backstops address it, and the important one is now the default.

The grounding check (default). Every scored answer must match a pool response logged in the hash-attested trace. An agent that ignores the pool and returns a memorised answer from its own weights matches no logged response and is disqualified ungrounded once the ungrounded fraction exceeds a small slack (default 15 %, for synthesis orchestration + parser noise). This is pure proof-inspection: the validator reads only the proof and the trace, runs no miner code, and needs no secret data — deterministic and $0. It closes the total memoriser (the memorise-everything strategy) directly.

Its residual is honest and specific: prompt-injection laundering — the agent tells the pool model the memorised answer inside a legitimate prompt and the model echoes it back, so the answer is "grounded" in a pool response. That passes. The counterfactual upgrade is a null-pool re-execution, which is exactly what the opt-in probe adds.

The opt-in re-execution probe. Enabled with --probe-bank (audit_mode="probe"), the validator re-runs the downloaded bound artifact on a secret held-out slice and disqualifies memorization on a statistically significant collapse. Getting this right required a real correction, because a secret probe is not the same difficulty as the public slice:

The naive test — a one-sided two-proportion z-test (z_crit = 2.33) on claimed vs fresh — is unsafe once the probe is secret. The probe is drawn from an owner-curated bank the miner cannot derive, so it need not match the difficulty of the scored slice. An honest, perfectly-generalising router then shows claimed > fresh purely because the probe is harder; at n = 64, a 15-point difficulty gap false-DQs an honest miner — and the test's power grows with more probe data, so collecting more evidence makes it worse.

The fix is difficulty normalisation against the cohort:

allowance = median over this epoch's miners of (claimed_acc - fresh_acc)     # capped at max_drop
flag if   (claimed - fresh) exceeds allowance by more than sampling noise
  • The median, not the mean, so a minority of genuine memorisers cannot inflate the allowance and hide behind it.
  • Capped at max_probe_drop (default 0.25) — the owner's declaration of "no honest probe is harder than this" — so a fully colluding cohort of memorisers cannot raise the allowance enough to escape.
  • With fewer than min_cohort (default 3) miners there is nothing to calibrate against, so it falls back to that same owner-declared cap rather than to zero. An allowance of zero would apply the strict absolute test and false-DQ honest miners whenever the probe is harder than the public set.

The separation this buys: "the probe is hard" makes everyone drop; "this miner memorised" makes only them drop.

The probe bank is committed on-chain as probe_commit (a canonical, order-independent hash of the bank). In probe mode a validator without the matching bank refuses to score at allprobe_bank_missing, probe_bank_mismatch, probe_bank_unverified — rather than running a toothless audit.

The intra-epoch commit window (opt-in)

The epoch nonce kills cross-epoch best-of-N, but within a single epoch a miner could still grind many runs against the same slice and reveal the luckiest. Two landed, off-by-default refinements close the wall-clock gap:

  • Commit window (F7). Each epoch the miner commits its proof's report_data() on-chain inside [epoch·blocks, +W], then reveals the proof. The validator scores it only if the revealed proof matches the on-chain commit and the commit landed in-window. That binds one attested run — a post-commit best-of-N swap is commit_mismatch, a late commit commit_out_of_window, no commit no_proof_commit — and caps pre-commit grinding to W's wall-clock. The window W is the actual lever; calibrate it to one suite run.
  • Grace window (F2). The validator scores a settled epoch (the latest whose grace deadline (current_block − G) // blocks has passed), never the live one — so every miner had the full commit-and-upload window and two validators polling at different blocks compute the same result.

Both are off by default (the offline sim and single-validator testnet don't need them); enable them together on mainnet once W and G are measured.

Public source, and the fraud-proof path

Because the source is public, the cheapest audit is to read it. The validator pattern-scans the artifact it downloaded itself for literal answer tables → hardcoded_answers.

Anyone can also raise a challenge against a miner. The adjudicator binds first:

if hash_source(challenge.source_text) != committed_source_hash:
    return (False, "unbound_source")     # a griefer cannot fabricate source against an honest miner
return scan_source(challenge.source_text)

Not yet implemented: challenger stake and slash. Without it the fraud-proof path is free to spam, so it is advisory today.

Metering and no-egress

Cost cannot be under-reported because the agent never touches the network or the API key:

parent (trusted runtime)                 child (untrusted agent)
  holds the pool key                       unshare --net  →  zero egress
  meters every call         ← call_model ←  agent(prompt, call_model)
  performs the real request → response  →
  records the authoritative trace          tmpfs over secret dirs, rlimits

Metering is parent-side and therefore un-bypassable. The published trace is hashed into call_log_hash, which the quote binds — so the trace cannot be edited either.

Fail-closed by default

A validator that cannot verify something disqualifies; it does not skip. An unset gate is a configuration error, not a permissive default:

mock_quote_rejected · mrtd_gate_unset · rtmr_gate_unset · tcb_policy_unset · probe_bank_missing · probe_bank_mismatch · audit_error:*

A governance_ready() preflight refuses to start the daemon at all until the owner has published the approved-measurement set on-chain.

What is still open

Stated plainly:

  • Grounding laundering. The default grounding check passes an answer the agent fed to the pool model and got echoed back (prompt-injection). The opt-in null-pool re-execution probe is the counterfactual upgrade for when real emissions attract a competent adversary.
  • Prompt-based exfiltration. The agent can embed the nonce or task text in a legitimate prompt to the pinned model, reaching a confederate through the sanctioned channel. A network namespace cannot close this. Bounded by running the probe audit against a trusted pool.
  • The measured image is not built. Until it is, unapproved_runtime cannot distinguish your runtime from any other TDX guest on the same cloud, because a stock CVM's MRTD is shared. This is the hard prerequisite for secure mainnet mining. See Attestation.
  • Probe-data curation. The opt-in secret bank must be sourced from tasks genuinely outside the pool models' training data. That is curation work, not code.
  • Challenger stake/slash is unimplemented.
  • The opt-in fresh-probe is statistical, not a proof. It bounds memorisation; it does not eliminate it.
  • Side channels under a miner-owned host are out of scope, as is a full hardware break.

See also