Two arborist-side artifacts close the v7 § 16.1 hand-wave without
violating the language constraint (arborist stays pure-Python; ZK
toolchain lives in sibling repo arborist-zk-bench).
docs/zk-frontier-bench.md — bench plan + parked verdict
========================================================
Bench plan covers Plonky3 (or equivalent) circuit at three sizes
(256/1024/4096 affine), measurement targets (prover ms, proof
bytes, verify ms, peak memory), platform pair (Apple M3 + Linux
x86), acceptance thresholds (≤30s prover at 4096; ≤100KB proof;
≤100ms verify; ≤16GB peak).
Preliminary projection from published Plonky3/Halo2 numbers:
size 256: 0.5–2 s prove / 30–80 KB / 5–30 ms verify
size 1024: 5–30 s prove / 50–120 KB / 10–50 ms verify
size 4096: 60–600 s prove / 100–300 KB / 30–200 ms verify
↑ likely UNAFFORDABLE at frontier scale
Most-likely outcome: VIABLE at small scales (256/1024 — useful for
distillation models); UNAFFORDABLE at LLaMA-class hidden width
(4096).
Decision tree from real measurement:
VIABLE → commission ZK as v8 capability;
follow-up tickets for arborist [zk] extra,
governance_policy.frontier_proof_mode field,
hermes-side activation export.
UNAFFORDABLE → park ZK; v7-Local stays v7's terminal
contribution; answer→model binding stays
out of scope.
docs/zk-wire-protocol.md — consumer-side schema
================================================
Specifies the wire format arborist consumes WHEN/IF ZK proofs
become available. The sibling repo emits proofs; arborist
validates schema + signature + delegates ZK verification to a
sibling-repo verifier binary. arborist itself NEVER runs the
prover or verifier — that's the language-constraint contract.
- Artifact: arborist-zk-proof-v1 JSON with schema_version,
proof_system, circuit_id, frontier_node, public_inputs,
public_outputs, proof_bytes_b64, verifier_setup_id, issued_at,
issuer_pubkey_ed25519, issuer_signature_ed25519.
- Trigger: governance_policy.frontier_proof_mode ∈
{"reveal" (default), "zk"}.
- Binding: model_weights_zk_root + frontier_proof_circuit_id
columns added to providence_cache (schema migration deferred
to integration ticket).
- Validation pipeline: schema check → recompute commitment
sanity → Ed25519 signature check → delegate to sibling
verifier → bind to audit chain.
- Threat model: compromised prover, wire-format injection,
proof replay, issuer-key compromise — each with its own
mitigation.
Status closure
==============
Ticket flipped from "open · awaiting go/no-go" to "parked ·
bench-plan + wire-protocol landed 2026-05-09 (sibling-repo
measurement pending)." This is honest: arborist's side is done.
Sibling repo `arborist-zk-bench` (not yet built) produces the
real numbers; doc updates roll back here when measurements
arrive.
Result: the v7 § 16.1 hand-wave is replaced with explicit
thresholds + preliminary projection + ready consumer schema.
The hand-wave is closed even though the bench-question is
parked.
8.7 KiB
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):
biasB, subset-maskS, 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):
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.rslives in the sibling repo. - No bench harness in arborist.
bench/zk_frontier_bench.pylives in the sibling repo. arborist'sbench/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_rootfield 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:
- Update §3 with measured numbers, replacing the projection.
- Flip the verdict line at top of this doc: PARKED → VIABLE or UNAFFORDABLE.
- Open follow-up tickets per §4's decision tree.
- 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.