# Threat model

**Status:** living document. Last revised 2026-09-01.
Scope: the Nodus verification layer. The v1 coordinator's own security posture
(no consumer auth, no rate limiting) is documented in `../nodus/README.md`
§Security and is *out of scope here* except where it interacts with verification.

---

## 1. Parties and trust

| Party | v1 assumption | Target assumption |
|---|---|---|
| Consumer | trusted to pay | untrusted (may repudiate, may probe) |
| Coordinator | **fully trusted** — schedules, meters, verifies, settles | reduced: should not be able to silently forge settlement; eventually removable |
| Provider node | trusted to self-report | **fully untrusted, rationally malicious** |
| Hardware vendor | n/a | trusted only where a mechanism explicitly says so (L4) |

The provider is the adversary this repository is about. We model it as
**rational and economically motivated**, not arbitrarily Byzantine: it cheats
when expected profit is positive, and it is capable of engineering effort
proportional to the payoff. A rational adversary model is *stronger* guidance for
design than a worst-case one, because it makes the defence a question of
expected value rather than of impossibility.

Secondary adversary: **colluding providers**, since a network with replication
or n-of-m voting is only as good as the independence of its replicas.

---

## 2. Attack catalogue

Each attack lists the claim it violates, whether Nodus v1 stops it, and the
cheapest known defence. `E00x` references the experiment that measures the
defence.

### 2.1 Output attacks (violate Claim A)

| # | Attack | v1? | Cheapest known defence |
|---|---|---|---|
| T1 | Fabricate a plausible output, sign it | **not stopped** | replication / audit (E009), ZK (E001-006), TEE (E010) |
| T2 | Return a cached output for a repeated prompt | not stopped | freshness nonce bound into the committed statement |
| T3 | Return output from a *smaller/cheaper* model | not stopped | model-weight commitment + A-proof; or audit (E009) |
| T4 | Return output from a *different* model entirely | not stopped (`model_version` is self-attested) | as T3 |
| T5 | Truncate generation early, claim full length | partly (`completion_tokens <= max_tokens`) | coordinator-side re-tokenisation of returned text |

### 2.1a A threat that is also the default behaviour

E009-pre measured something that does not fit the usual attacker framing: on
this machine, MLX's `matmul` called with float32 inputs on the **Metal GPU**
returns results ~1,500× less accurate than the identical call on the CPU stream,
with a constant relative error of 7.7 × 10^-4 — roughly 11 bits of effective
mantissa. Threat T7 is therefore not only something an attacker *does*; it is
what a mainstream accelerator path does by default, to providers with no intent
to cheat.

Two consequences for this threat model:

1. **"Honest" is not well defined without a numeric contract.** A job
   specification that names a model but not its numerical semantics does not
   determine a correct answer, so no verifier can be right about T7.
2. **The tolerance a network must grant its least precise honest hardware is
   the budget available to every attacker.** This makes the loose-tolerance
   provider class strategically attractive to cheat from — a Sybil-adjacent
   incentive worth pricing in the CP-008 economic analysis.

### 2.2 Execution attacks (violate Claim B / E)

| # | Attack | v1? | Cheapest known defence |
|---|---|---|---|
| T6 | Skip layers / early-exit, output "close enough" | not stopped | segment commitments + random segment challenge (E005, E009) |
| T7 | Quantise more aggressively than specified | not stopped | **MEASURED (E009-pre): tolerance-bounded audit does NOT work across devices** — honest CPU/GPU divergence is within 3.7× of this attack's signature. Only exact/quantised-integer semantics, or numeric-class partitioning, defends |
| T8 | Fabricate intermediate states to satisfy a checkpoint scheme | n/a | **this is the central design trap** — see §3 |
| T9 | Sub-contract the job to a cheaper third party and relay | not stopped | not clearly an attack; becomes one if locality/latency is priced |
| T10 | Precompute the answer before the job is assigned | not stopped | unpredictable input / late-binding challenge |

### 2.3 Metering attacks (violate Claim C / D)

| # | Attack | v1? | Cheapest known defence |
|---|---|---|---|
| T11 | Inflate `prompt_tokens` in the attestation | **not stopped — settlement uses the attested value** (`coordinator/jobs.py:419-421`) | recompute from the prompt the coordinator itself sent (near-zero cost) |
| T12 | Inflate `completion_tokens` | not stopped, capped only by escrow | recompute by tokenising the returned text |
| T13 | Overstate `duration_ms` / throughput to win scheduling | not stopped | coordinator-side timing; reputation |
| T14 | Game the CU-v0 benchmark (run it on better hardware than serves jobs) | not stopped | random re-benchmarking bound to job execution |
| T15 | Claim the same computation for two jobs (duplicate billing) | partly (per-job replay check) | cross-job dedup on (input, model) commitments |

### 2.4 Protocol and identity attacks (violate Claim O)

| # | Attack | v1? | Cheapest known defence |
|---|---|---|---|
| T16 | Replay a previous result | stopped (per-job) | — |
| T17 | Edit a result in transit | stopped (HMAC) | — |
| T18 | Steal a node token and impersonate | out of scope; token is a bearer credential | asymmetric keys instead of HMAC |
| T19 | Sybil: one machine, many node identities | not stopped | proof of distinct hardware — open problem, relates to Claim D |
| T20 | Collude to pass n-of-m replication | not stopped | independent replica selection, stake, uncorrelated audit |

### 2.5 Attacks specific to probabilistic verification

| # | Attack | Defence |
|---|---|---|
| T21 | Cheat only on jobs unlikely to be audited (strategic cheating) | audit selection must be unpredictable to the provider *at execution time*; commit-then-reveal audit seed |
| T22 | Cheat on a small fraction of jobs so expected loss < expected gain | set penalty × detection probability > maximum gain — requires **stake**, so security becomes an economic parameter, not a cryptographic one |
| T23 | Exit scam: accumulate reputation, then defect at scale | bonded stake with delayed release; cap exposure per epoch |

T22 is important enough to state as a design principle:

> Under probabilistic verification, security is bounded by `p_detect × penalty`,
> not by soundness error alone. A verification layer without a slashable stake
> has no defence against a provider who cheats a little.

**Quantified (E016, DERIVED).** For layer-skipping under a one-random-layer
audit: a provider skipping `m` of `L` layers saves `m/L` of the job's compute
value `V` and is caught with probability `m/L`, so cheating is
negative-expected-value when `penalty·(m/L) > V·(m/L)` — the `m` and `L` cancel:

> **A stake equal to the job's compute value suffices, at any depth and any
> level of greed.**

Assumes a risk-neutral provider, a collectable penalty, a cheat that is
detectable once audited (true under exact arithmetic), and an audit target
chosen unpredictably *after* the provider commits — which requires a commitment
to all layer outputs, e.g. a Merkle root. Nodus v1 lacks that commitment today.

---

## 3. The checkpoint trap

A tempting and **false** intuition, explicitly called out so nobody in this
project rediscovers it:

> "If checkpoint A and checkpoint B are both correct, the computation between
> them must have been performed correctly."

This is false. Commitment to two endpoint states constrains nothing about the
path unless the transition itself is constrained. A prover who can obtain the
correct state at B by *any* means (cache, a different algorithm, a smaller
model that happens to agree, or simply having seen this input before) satisfies
both checkpoints having skipped the work.

Checkpoints become meaningful only when combined with **a constraint on the
transition**, i.e. one of:

- a proof that `state_B = F_segment(state_A)` (a real segment proof — L5),
- an interactive challenge over the segment that a cheating prover cannot answer
  without having the intermediate values (L3),
- a binding that makes state B unpredictable without doing the work (freshness).

Checkpoints alone are an *indexing* structure, not a verification mechanism.
Their value is that they make the *challenge* cheap: they let a verifier audit
1/N of the computation instead of all of it, once a transition constraint exists.
Experiment 005 and 009 test exactly this distinction.

---

## 4. Mandatory question

Every mechanism proposed in this repository must answer, in its own
`analysis.md`:

> **What can a malicious provider still get away with?**

An answer of "nothing" is a red flag and will be treated as an unfinished
analysis.
