# Ticket #000016 — ZK Phase-2 frontier proof (concretize the hand-wave)

**Status:** parked · landed bench-plan + wire-protocol artifacts 2026-05-09 (`docs/zk-frontier-bench.md`, `docs/zk-wire-protocol.md`); pending sibling-repo measurement
**Opened:** 2026-05-07
**Scope:** Build one zero-knowledge proof for one v7-Local affine
preactivation frontier on a small model, measure prover time + proof
size, and use that data to decide whether ZK is viable for arborist's
QA layer or stays out of scope. Replaces v7's hand-waved "swap
SHA-256 → Poseidon" with a measured artifact.
**Audience:** fox + future ZK adopters.
**Hard constraint:** ZK is opt-in. The v7-Local sound path with full
local revelation stays the default. If ZK lands, it lives behind a
policy flag `governance_policy.frontier_proof_mode ∈ {reveal, zk}`.
Cache invariants stay at 8 dims.
**Language constraint:** arborist itself stays pure-Python (per
arborist `CLAUDE.md` operational rules). Any non-Python ZK
toolchain (Plonky3, Halo2, etc.) lives in a sibling repo
(`arborist-zk-bench`); arborist communicates with it over a wire
protocol arborist defines. arborist's `pyproject.toml` does not
gain a Rust dependency.

---

## 1. Problem statement

Merkle-AGI v7 § 16.1:

> Phase-2 ZK Track: replace local integer checks at affine frontiers
> with a ZK circuit that proves ‖z_full − z_S‖_p ≤ ε‖z_full‖_p
> without revealing a_u(x) or w. Swap SHA-256 for Poseidon/Rescue in
> proof-only paths; keep SHA-256 in public commitments.

That is **not a swap.** Specifically:

- Poseidon is a different trust model than SHA-256 — newer, less
  vetted, fewer cryptanalysis-years, higher prover cost per byte
  hashed than SHA-256 but lower per-constraint inside a SNARK.
- Building the actual frontier circuit is non-trivial. An affine
  preactivation = matrix-vector multiply + bias + norm computation.
  Inside a ZK circuit, every multiplication and addition costs
  constraints; circuit size scales with operator size.
- Prover time on a frontier-scale layer (e.g., 4096 × 4096 GEMM) on
  consumer hardware is unmeasured. Could be seconds, could be hours.
- Proof size and verification cost downstream are unmeasured.

Without numbers, "Phase-2 ZK" is a roadmap stub, not a viable path.
This ticket commissions the measurement.

### 1.1 Why this matters for arborist specifically

arborist's QA layer doesn't currently run model inference — it
queries `hermes.ai.unturf.com`. So v7-Local proofs (full local
revelation) require that endpoint to expose its activations, which
is privacy-hostile. ZK is the *only* path to an answer→model-weights
binding in arborist's deployment shape.

Concretely: if a user asks a question via `arborist ask`, today the
audit chain binds the question, the retrieval plan, the context, and
the answer. It does *not* bind which model weights produced the
answer. v7-Local would require Hermes to publish its activations on
every query — leak channel of unbounded size. ZK is the only sound
way to bind without leaking.

If ZK at frontier scale is unaffordable (e.g., minutes-per-frontier
prover time), the answer→model binding stays out of scope and v7-Local
remains v7's terminal contribution. If ZK is affordable
(sub-second prover at typical frontier widths), arborist can fold
`model_weights_zk_root` into the providence record.

---

## 2. Design choices

### 2.1 Proof system

**A. Halo2 (PLONKish, KZG commitments).** Mature, used by Zcash. No
trusted setup beyond ceremony. Decent prover time on commodity
hardware.

**B. Plonky3 (FRI-based, recursive-friendly).** Newer. Quantum-
resistant (FRI). Faster prover on small circuits. Recursion supports
proof aggregation.

**C. Groth16.** Smallest proofs (~200 bytes), trusted setup per-
circuit. Battle-tested. Expensive to redo per circuit revision.

**D. STARK (FRI-based, no trusted setup).** Largest proofs but no
trusted setup, post-quantum. Plonky3 is in this family.

**Recommendation:** B (Plonky3). Fast prover on commodity hardware,
recursion-friendly for batching across multiple frontiers, no
trusted setup, post-quantum hedge. Halo2 is the second choice if
Plonky3 maturity proves blocking.

### 2.2 Hash function inside the circuit

**A. Poseidon.** SNARK-friendly. ~10-20× cheaper than SHA-256 inside
a circuit. Less vetted.

**B. Rescue / Rescue-Prime.** Similar to Poseidon, slightly newer.

**C. Native SHA-256 inside circuit.** Most expensive but maximum
trust transfer from existing cryptanalysis.

**Recommendation:** A (Poseidon) inside the circuit, SHA-256 for
all public commitments. Document the trust delta explicitly; if
Poseidon is later broken, only the proof's binding is affected, not
the model-root commitment itself.

### 2.3 Quantization choice

The v7-Local kernels run on int64 with 128-bit accumulators. Inside
a SNARK, native field arithmetic (~256 bits) gives more headroom
than int64. Choices:

**A. Match v7-Local int64 + bigint accumulators.** Maximum equivalence
with non-ZK path; circuit must encode bigint arithmetic.

**B. Native field arithmetic, with a wider quantization (Δ_zk).**
Smaller circuit, but the ZK-mode commitments differ from v7-Local
commitments at the same node. Two parallel commitments per node.

**C. Native field with int64 sub-encoding.** Compromise: int64 values
encoded as field elements, arithmetic done in the field, range
checks proved.

**Recommendation:** C. Standard zkML practice. Keeps Δ uniform with
v7-Local non-ZK path, manageable circuit size, range-check-friendly.

### 2.4 Measurement scope (this ticket)

To produce a usable verdict we need:

- One affine layer at three sizes: 256×256, 1024×1024, 4096×4096.
- Wall-clock prover time on commodity hardware (e.g., Apple M3 Max
  + a generic Linux x86 box for cross-platform).
- Proof size in bytes.
- Verification time in ms.
- Memory peak during prover.

Three sizes give enough data to project to frontier-scale (e.g.,
LLaMA-class hidden width). Two platforms catch Apple-Silicon-only
optimizations.

---

## 3. Recommendation

Build one Plonky3 circuit for one affine preactivation node
(256×256 GEMM + bias + L1 norm + ε-check). Measure prover time +
proof size + verify time at sizes {256, 1024, 4096} on two
platforms. Report results in `docs/zk-frontier-bench.md`. Use those
numbers to either commission ZK as a v8 capability or definitively
park it as unaffordable.

---

## 4. Implementation sketch

### 4.1 Repo layout (sibling repo, NOT arborist proper)

```
arborist-zk-bench/                # sibling repo
├── circuits/
│   └── affine_preact.rs          # Plonky3 circuit (or other ZK lang)
├── prover/                       # Rust crate
├── verifier/                     # Rust crate
├── wire/                         # Wire protocol arborist consumes
│   └── proof.json.schema
└── README.md
```

arborist proper consumes proofs as canonical bytes via a Python
verifier-helper that reads the wire schema. The ZK toolchain lives
outside arborist so a fresh `pip install arborist` never pulls Rust.
If/when the Plonky3 measurements show viability, arborist gains an
optional `[zk]` extra in `pyproject.toml` that pulls a pure-Python
schema validator + a binary protocol parser — never the prover
itself.

### 4.2 Circuit shape

For a single affine frontier:

- Inputs: int64 weights W, int64 activations A, int64 bias B, int64
  subset-mask S, integer epsilon (numerator/denominator).
- Witness (private): full A, full W.
- Public outputs: SHA-256 commitments of W and A (re-bound to the
  v9.8 audit chain), commitment to (S, ε), boolean "ε-inequality
  holds."
- Constraints: range checks on int64 values; field-level GEMM;
  L1-norm constraint via sum of absolute values; final inequality
  check.

### 4.3 Bench harness

```python
# bench/zk_frontier_bench.py
sizes = [256, 1024, 4096]
platforms = [host_id()]  # caller runs on each platform
for size in sizes:
    A = random_int64(size)
    W = random_int64((size, size))
    B = random_int64(size)
    t0 = time.time()
    proof = prove_affine(W, A, B, S, eps)
    prove_ms = (time.time() - t0) * 1000
    t1 = time.time()
    ok = verify_affine(proof, public_inputs)
    verify_ms = (time.time() - t1) * 1000
    record({
        "size": size,
        "platform": platforms[-1],
        "prove_ms": prove_ms,
        "verify_ms": verify_ms,
        "proof_bytes": len(proof),
        "ok": ok,
    })
```

Results land in `docs/zk-frontier-bench.md` as a table.

### 4.4 Acceptance thresholds

For ZK to be commissioned as a v8 capability:

- Prover ≤ 30 seconds at size 4096 on commodity hardware.
- Proof size ≤ 100 KB.
- Verify ≤ 100 ms.

If any threshold fails, ZK stays parked.

---

## 5. Out of scope

- Productionization of the ZK path. This ticket measures viability;
  shipping ZK in `arborist ask` is its own ticket.
- Multi-frontier proof aggregation (recursive proofs). If the size-4096
  number is acceptable, aggregation is a follow-up.
- Cross-validator ZK proof exchange. That's part of v8 consensus
  (ticket #000012).
- Integration with Hermes-side activations. If ZK is commissioned,
  Hermes must expose a circuit-friendly activation export — separate
  ticket on the Hermes side.

---

## 6. Risks & open questions

- **Plonky3 maturity.** Library is young; API stability not
  guaranteed. Mitigation: pin commit, document.
- **Rust toolchain dependency** newly introduced to arborist.
  Mitigation: ZK lives in `arborist/zk/`, optional dep flagged in
  `pyproject.toml` extras, default install path stays pure-Python.
- **Bench reproducibility.** Wall-clock varies across machines.
  Mitigation: run twice on each platform; report median + range.
- **Negative result.** Most likely outcome at first attempt is
  "size 4096 takes minutes, ZK is parked." That's a useful result,
  not a failure — closes the hand-wave.

---

## 7. Status

**Open · awaiting go/no-go.** Implementation lives in sibling repo
`arborist-zk-bench`, NEVER inside arborist (per language
constraint). Recommend deferring until a v8 / v7-W ticket creates
real demand. arborist gains at most a wire-format consumer once
results justify integration.

Closure criterion: `docs/zk-frontier-bench.md` exists with measured
numbers at three sizes on at least one platform. Verdict line at
top: "viable" or "parked" with the threshold check shown.
