# ZK frontier-proof bench plan

**Verdict:** **PARKED · pending sibling-repo measurement.**
**Ticket:** #000016
**Authored:** 2026-05-09

This document is the arborist-side closure for #000016. It captures
the bench plan, acceptance thresholds, and a preliminary
projection. **Actual measurements live in the sibling repo
`arborist-zk-bench`** (not yet built), per #000016's language
constraint that arborist stays pure-Python and never gains a Rust
dependency.

When `arborist-zk-bench` produces real numbers, this doc gets
updated with the verdict line at top flipping from PARKED to
**VIABLE** or **UNAFFORDABLE**, and arborist consumes the wire
format spec'd in `docs/zk-wire-protocol.md`.

---

## 1. Why parked, not closed

#000016's closure criterion (§7) is measured numbers at three
circuit sizes (256, 1024, 4096) on commodity hardware. Producing
those numbers requires a working Plonky3 (or equivalent) circuit
in a Rust crate, plus prover + verifier binaries, plus
reproducible-build infrastructure. That belongs in
`arborist-zk-bench` per #000016 §1.21 and §4.1.

Building the sibling repo before there's a v8/v7-W demand for ZK
is speculative work. The ticket itself recommends "deferring until
a v8 / v7-W ticket creates real demand" (§7).

So: arborist produces the bench plan + the wire protocol +
preliminary projection, and parks measurement until demand
materializes. The hand-wave is replaced with an explicit
PARKED-with-thresholds artifact.

## 2. Bench plan (for the future sibling repo)

### 2.1 Circuit shape

One affine preactivation node, three sizes:

| Size | Inputs | Weights | Cost class |
|------|--------|---------|------------|
| 256  | 256 int64 | 256×256 int64 | small / unit-test scale |
| 1024 | 1024 int64 | 1024×1024 int64 | medium / typical-frontier scale |
| 4096 | 4096 int64 | 4096×4096 int64 | LLaMA-class hidden-width scale |

Per node:

- Inputs (witness, private): full activation vector A, full weight
  matrix W.
- Inputs (public): `bias` B, subset-mask `S`, epsilon as
  `(num, den)` integer pair.
- Outputs (public): SHA-256 commitments of W and A, commitment to
  `(S, ε)`, boolean "ε-inequality holds".
- Constraints: range checks on int64 values, field-level GEMM,
  L1-norm via sum of absolute values, final inequality check.

### 2.2 Measurement targets

- Wall-clock prover time (ms).
- Proof size (bytes).
- Verification time (ms).
- Memory peak during prover (MB).
- Setup time (ms) — one-shot per circuit.

### 2.3 Platform pair

- **Apple Silicon** — M3 Max or equivalent (representative dev
  machine).
- **Linux x86_64** — generic cloud commodity (representative server).

Run twice per platform; report median + range.

### 2.4 Bench harness sketch

`arborist-zk-bench/bench/zk_frontier_bench.py` (in the sibling
repo, not here):

```python
sizes = [256, 1024, 4096]
seeds = [42, 137]
for size in sizes:
    for seed in seeds:
        rng = numpy.random.RandomState(seed)
        A = rng.randint(-(2**31), 2**31, size, dtype=numpy.int64)
        W = rng.randint(-(2**31), 2**31, (size, size),
                        dtype=numpy.int64)
        B = rng.randint(-(2**31), 2**31, size, dtype=numpy.int64)
        S = rng.choice([0, 1], size).astype(numpy.int64)
        eps = (1, 100)  # 1% L1 error budget

        t = time.monotonic()
        proof = prove_affine(W, A, B, S, eps)
        prove_ms = (time.monotonic() - t) * 1000

        t = time.monotonic()
        ok = verify_affine(proof, public_inputs(W, A, B, S, eps))
        verify_ms = (time.monotonic() - t) * 1000

        record({
            "size": size, "seed": seed, "platform": platform_id(),
            "prove_ms": prove_ms, "verify_ms": verify_ms,
            "proof_bytes": len(proof), "memory_peak_mb": ...,
            "ok": ok,
        })
```

### 2.5 Acceptance thresholds

For ZK to flip from PARKED to VIABLE in this bench:

| Metric              | Threshold (size 4096)         |
|---------------------|-------------------------------|
| Prover wall-clock   | ≤ 30 seconds                  |
| Proof size          | ≤ 100 KB                      |
| Verifier wall-clock | ≤ 100 ms                      |
| Memory peak         | ≤ 16 GB                       |
| Verification result | `ok == True` on all seeds     |

**All thresholds must pass.** Single-metric failure → PARKED
with the failing metric named.

## 3. Preliminary projection (no measurement)

Lower-confidence estimates from published Plonky3 / Halo2
benchmarks (2024-2025 era), to inform expectations BEFORE
real bench runs:

| Size | Prove (projected) | Proof bytes | Verify | Confidence |
|------|-------------------|-------------|--------|------------|
| 256  | 0.5–2 s           | 30–80 KB    | 5–30 ms  | Medium |
| 1024 | 5–30 s            | 50–120 KB   | 10–50 ms | Low–Medium |
| 4096 | 60–600 s          | 100–300 KB  | 30–200 ms | Low |

Sources: Plonky3 benchmark posts, Halo2 GEMM-circuit reports.
**These are not measurements**; they're estimates from related
circuits. Real numbers will diverge.

**Most-likely outcome based on these estimates**: at size 4096,
prover time exceeds the 30-second threshold; ZK stays PARKED for
the LLM-frontier scale. ZK might be VIABLE at the 256/1024 scales
relevant to small distillation models.

## 4. Decision tree from measurement

```
                  arborist-zk-bench measurement
                              │
                  ┌───────────┴────────────┐
                  │                        │
              VIABLE                  UNAFFORDABLE
                  │                        │
                  │                        │
   commission ZK as v8 capability    park ZK; v7-Local stays
   open follow-up tickets:           v7's terminal contribution;
     - arborist [zk] extra wiring    v7-Local proofs require
       (wire-format consumer only)   activation publication;
     - governance_policy.frontier_   answer→model binding stays
       proof_mode policy field       out of scope
     - v8 ForkScore: ZK availability
       as a fork-quality signal
     - hermes-side circuit-friendly
       activation export
```

Either branch closes #000016: VIABLE → ZK lands; PARKED →
documented constraint, future ticket if Plonky3 evolves.

## 5. What arborist does NOT need to do

Per #000016 §1.21 / §4.1 / §7 language constraints:

- **No Rust code in arborist.** `arborist/zk/` reserved as a
  future namespace for the wire-format consumer (pure-Python
  schema validator); no prover, no verifier.
- **No Plonky3 dependency in `pyproject.toml`.** `[zk]` extra
  (when it lands) pulls a JSON-schema validator + a binary
  protocol parser, never the prover binary.
- **No circuit code in arborist.** `circuits/affine_preact.rs`
  lives in the sibling repo.
- **No bench harness in arborist.** `bench/zk_frontier_bench.py`
  lives in the sibling repo. arborist's `bench/` stays pure-
  Python deterministic-fixture work.

## 6. What the wire protocol looks like

Detailed spec in `docs/zk-wire-protocol.md` (sibling document).
Brief summary:

- Sibling repo emits proofs as canonical-byte JSON conforming to
  a versioned schema.
- arborist consumes proofs at the providence-record write
  boundary, optionally adding a `model_weights_zk_root` field to
  the v9.8 cache_key (governance-hash-folded).
- arborist's verifier helper validates schema + checks SHA-256
  binding against the providence chain. It does NOT run the ZK
  verifier — that's also in the sibling repo or in a separate
  trusted binary.

## 7. Cross-references

- #000016 — this ticket (parked).
- #000018 — adversarial soft-hash analysis (closed); the M3
  mitigation there ("drop the anchor") is the only existing
  defense if ZK proves UNAFFORDABLE and v7-Local activation
  publication is also out of scope.
- #000013 — v7-W spatial-temporal substrate (closed); v7-W
  inherits the ZK question — world-state observations are ALSO
  privacy-sensitive, and the same VIABLE/PARKED decision tree
  applies.
- v7 § 16.1 — the original "swap SHA-256 → Poseidon" hand-wave
  this doc replaces.

## 8. Status / closure

**This document closes the arborist-side hand-wave.** The
sibling repo's measurement work remains open as a separate
project; #000016 is parked rather than fully closed because the
verdict line awaits real numbers.

When `arborist-zk-bench` produces measurements:

1. Update §3 with measured numbers, replacing the projection.
2. Flip the verdict line at top of this doc: PARKED →
   VIABLE or UNAFFORDABLE.
3. Open follow-up tickets per §4's decision tree.
4. Update #000016 ticket status: parked → closed (verdict
   recorded).

Until then: this is the artifact. The hand-wave from v7 § 16.1
is replaced with explicit thresholds + preliminary projection +
parked-status with named open work.
