Skip to content

Land recursion_circuit: F256 Ligerito, recursion tower, Option-F encoders - #26

Merged
rothblum merged 602 commits into
mainfrom
recursion_circuit
Aug 25, 2026
Merged

Land recursion_circuit: F256 Ligerito, recursion tower, Option-F encoders#26
rothblum merged 602 commits into
mainfrom
recursion_circuit

Conversation

@rothblum

@rothblum rothblum commented Aug 16, 2026

Copy link
Copy Markdown
Collaborator

Syncs recursion_circuit into main. The branch contains main's tip (last re-merged at 2dcd945: genus-95 curve code #27, AG-skip zerocheck #28/#31, all-core P+E pool #29, Ligerito lookahead + NTT deep-pass #30 — 25 conflict hunks, notes in the merge commit). Please land with a merge commit (docs and memory reference branch SHAs).

What lands

  • F256 Ligerito ladder (97cc1d2): 256-bit-field recursion levels with the F256→F128 code switch, two-point OOD binding, per-m embedded security configs (field = "f256" enforced), 100/120-bit profile families (Fast/Slim/Secure + 100/128 variants).
  • Recursion tower productionized: src/tower.rs with TowerConfig::{Chain100, Chain128} — fast leaves, slim outers at the m*=29/nu*=14 envelope (always on). Measured m=32 chain128 headline: ~702 ms/leaf, ~373k compressions/s (warm medians, tower_online_bench).
  • zk.golf-derived encoders: BLAKE3 fused-adder R1CS (10,298 AND rows) and SHA-256 Option-F (25,470 useful bits / 199 cols); blake3 batch prove at m=32 measured ~686k c/s.
  • Union-only batch proving: single-slot union with dense stack + integer-lane commit, proof-IO v21; the legacy shift-argument chain product, flock_chain CLI, and padded-commit batch paths are deleted.
  • Fused FS/PoW grinding transcript (fast128 16-bit query PoW) per the 128-bit grinding audit; SkipPoint threads through the branch grinding with the φ₈ arm keeping the fused PoW+squeeze (byte pins held).
  • GPU prover ported to the current protocol (abd9e7f→4150ef0): transcript-v2 word-aligned framing, Option-F BLAKE3 witness layout, and the F256 fold ladder — capped Merkle (the commitment IS the cap), stratified query sampling, the full 128-bit grinding schedule, GF(2^256) device/host field ops. Validated in layers: host-only ladder replay matches the Rust driver byte-for-byte at m22/23/26/30; GPU vector test matches at m22; GPU prove → Rust verify roundtrips green at m22 (incl. tamper rejection), m32, m34 (steady prove 4.6 ms / 47.8 ms / 172.7 ms).
  • Cleanup pass: workspace compiles with zero warnings; dead code, superseded API twins, and completed probe benches removed; docs/scripts updated; workspace-wide clippy clean on current stable.

Verification

cargo check --workspace --all-targets: 0 errors, 0 warnings. cargo test --release --workspace: 674 tests pass, 0 failures. Blake3/SHA-256 m=6 fixture anchors byte-stable; m=32 union benchmarks re-measured. CUDA CI (Blackwell self-hosted): full vector suite green (make -k run-tests, incl. the new F256 field / challenger / ladder oracles) and the gpu_roundtrip_m* tests pass.

🤖 Generated with Claude Code

rothblum and others added 30 commits August 3, 2026 20:22
Under a mask, the sigma fingerprint table is never materialized: the
rhs leaves read tag(sigma(x)) inline on live cells, and the deferred
"live (.) s_sigma" evaluation is the SPARSE sum over live cells only —
Sum_live eq_lo(row)·eq_hi(slot)·tag(sigma(x)), O(live + 2^nu + 2^c) —
on both the prover and the sigma-known verifier arm. Exact (XOR
addition is associative), transcript-identical to the dense form.

The verifier effect is decisive: the plain (root-style) wiring
verify's O(2^mu) masked-table build + dense MLE becomes
live-proportional — swap outer plain verify 25-47 -> 5 ms, now within
~2 ms of the deferred path. The "root pays O(2^mu)" concern for sigma
v1 largely dissolves. Prover: the 53 ms s_sig build + most of the
68 ms evals pass drop from prove_batched.

Remaining O(2^mu) prover terms for phase 2b: the leaf write traffic,
build-layers, and the layer-sumcheck folds/messages — the grouped
(per-slot live-prefix, all-ones tail) layer representation; constant-1
regions are fold-invariant, so the structure persists through rounds.

All gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… is live-proportional

The masked batched prove now runs on grouped layers: per-group live
prefixes over implicit all-ones tails (GVec; rows == 1 degenerates to
per-entry ones-vs-real). Constant-1 regions are FOLD-INVARIANT, so the
structure persists through every pass:

- gv_build_prev: group merging with max-length prefixes (the shorter
  side multiplies implicit 1s = a copy), then row-pair splitting below
  the single-group layer.
- gv_fold: prefix halving; the odd boundary pair mixes in a 1; once
  rows == 1, group-pair folds skip both-ones pairs without a write.
- gv_message: branch-minimal guarded reads over the live pair extent,
  plus the closed-form all-ones tail — (1 + lambda)·eq-mass into G(1)
  only (1 + 1 = 0 kills G(inf) in char 2), with the eq-mass of any
  range in O(1) from prefix-sum arrays over the split eq tables —
  SP1's padding_adjustment, adapted to per-group tails.
- Slice-based kernels, rayon over groups past par_threshold; unfused
  fold/message (fusion existed for 2^mu memory traffic; prefixes are
  live-sized).

prove_batched dispatches grouped whenever masked. The DENSE masked
pipeline stays reachable (prove_batched_dense_masked_for_tests) and
grouped_matches_dense_masked pins proof + claim BYTE EQUALITY on
random (f, g, sigma, mask) across three (nu, c) shapes — a permanent
differential oracle, per the retired-oracle lesson.

COLD numbers (m30 sanity probe at ~117 ms first), swap outer, mu 24:
grouped wiring = leaves 3.2 + build 4.1 + sumchecks 19.1 + evals 0.3
about 27 ms (dense was ~46 at mu 23, ~2x that at mu 24); whole prove
177 ms, verify 5 ms plain / 2 ms deferred. The session's full arc for
the swap outer: prove 2031 -> 177 (11.5x), plain verify 554 -> 5
(110x). The sumcheck residual is largely GENUINE live data (b3's
196k live cells + full public slots). All gates green; two throttle
artifacts en route (941 ms reading, a red m30 probe) were re-measured
per the box discipline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…written up

A focused design note (docs/padded-permutation-check.tex) for the live
mask: the setting and the hypercube cost problem, the SP1 LogUp
inspiration (0/1 padding + padding_adjustment), the masked fingerprint
and its soundness lemma (the multiset argument restricted to the live
set; sigma fixes dead cells; the mask is statement-derived), the
no-degree-change analysis — including why the endpoint stays closed
form (w dead-zero + the selector multiplying only structural terms)
with the E_n / D_n prefix eq-sum derivations — the masked-and-sparse
sigma side (route B unchanged), the grouped live-proportional prover
(fold invariance, max-merge, the (1+lambda)-mass tail closed form),
the transcript-identity oracle, measured numbers, and the code map.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pass retires

The w materialization before the grand product copied all 2^mu cells
(gate blocks, publics, zero-filled pads). Under the live mask, neither
the grouped GKR's leaves nor the live gather ever reads a dead cell, so
the pooled buffer's dead regions now stay untouched — semantically zero
(the committed data's dead rows remain honest zeros; the buffer's are
simply never read). Per-slot live-prefix copies, rayon over slots.

The product_gkr mask contract relaxes accordingly: dead cells must be
sigma-fixed (still debug-asserted), but their f/g entries are never
read under a mask, so the zero-value check is gone — a caller may hand
a dirty pooled buffer.

Measured (swap outer, mu 24): build w ~10 -> 0.4-0.5 ms (0.07 at the
inner's mu 21); prove floor 161 ms. The wiring block is now fully
live-proportional: leaves + layers + sumchecks + gather + build w.
All gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…wer committed words

The census (PF-DEBUG) attributed the swap outer's prefix-slot bloat:
13.8k rows = ghat 384 + eqc 3.5k (588 runs x 6 rows) + the general run
weights 9.4k (2 RS statements + the element pair, 2 rows per column
over 1172 non-comp columns at k_cols = 11). The run-weight machinery
scaled with the INNER'S COLUMN SPACE (2048 jagged columns), not with
the verification content — which is why the open grew since mvp9
(whose single-slot inner had 4 columns) while the trace barely changed.

THE RESTRUCTURE, two identities, values unchanged (the native replica
and the anchor delta are untouched):
- Sum_r run_w[r]*eqc[r] = Sum_y eq(z_col, y)*eqc[run(y)] — one dot of
  the statement's eq table against the statement-INDEPENDENT
  per-column eqc expansion (free wire reuse). The eq table builds by
  DOUBLING (2 MAC rows per parent: e*c and e + e*c) and the dot is
  2^k MACs — 8-word MAC rows replace 64-word prefix rows. The
  complement trick retires: the full dot covers the comp run by
  partition of unity.
- Bilinearity folds group members directly: the group weight equals
  Sum_m gamma_m * (m's own dot); one-hot gathers still bind through
  one prefix row and pick their run's eqc wire.

Measured: prove 161 -> 147 ms, proof 360.9 -> 343.8 KiB, mu/dense_m
unchanged. Full suite green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olumns

Four column cuts to the residual-region gates, all trading cheap
live-prefix rows for envelope columns:

- gate_kappa floor 6 -> 3, so kappa tracks c_need down (FinalDot
  25 cols -> kappa 5 from this alone);
- PrefixGate factor fusion: one mult_lin cell per factor,
  pr' = pr*(1 + a + b) with the B side a linear combination
  (c_need 2+4pl -> n_in+pl; 34 -> 26 at pl 8, kappa 6 -> 5);
- ResidualGate W-elision: the normalized suffix factors get no
  cells — every use fuses into a product as a mult_lin side
  (s_col, inv) — and the t cell disappears at pl = 0
  (c_need = 4pl + 4yr + [pl>0]);
- PartialCombine sub-chunks to 4-wide (17 cols, kappa 5): combine
  rows are pure accumulate with no cross-position structure.
  (mult_acc patterns can never fuse to one cell — a row's RHS is
  exactly one column — which is why this one sub-chunks.)

Census before/after (leaf-outer registry, nu 13): element extent
640 -> 512 columns exactly (was 8x64 + 2x32 + 4x16; now
4x64 + 6x32 + 4x16), so M_elem drops and the union is 1024
columns / k_cols 10 (was 2048 / 11). The swap outer's expect
machinery walks half the column space: mu 24 -> 23, proof
343.8 -> 336.1 KiB, prove ~140 ms against an m30 probe reading
~137 (cold-equivalent ~117-120, was 147).

The eqc prefix-sharing trie is skipped deliberately: consecutive
runs stride ~2^11, so shared prefixes diverge at level ~12 and
the win is only ~30% — the envelope was the real target.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…omised

The module doc always said a merge of two recursive proofs folds 4->1
(two inherited, two fresh), but the API took Option<&Accumulator> and
only the leaf and 3->1 shapes were exercised. A real merge node's
children EACH carry an accumulator, so every entry now takes
priors: &[&Accumulator]; gather/gather_element/fold_sigma push all
priors' claims first, in order, then the fresh assertions.

Transcript compatibility is free: the bind byte becomes the prior
COUNT, and 0/1 coincide with the old is_some flag, so pre-existing
transcripts are unchanged (existing aggregate tests pass untouched
except for the call-site spelling). check_priors centralises the
registry-digest and per-class claim-count guards that were previously
scattered and partially missing on the prove side.

New coverage, ahead of arithmetising this in the merge circuit:
- union_lincheck: the true merge shape — two leaf accumulators plus
  two fresh assertions fold 4->1 per matrix and discharge; a tampered
  INHERITED claim is rejected or poisons the accumulator.
- circuit_wiring merge-node test: each child folded alone (sigma
  included), then a two-prior merge with NO fresh assertions — the
  inherited claims fold 2->1 per group, the sigma claims ride the
  shared circuit digest, and one root discharge covers both children.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The merge node's first region, boundary-first: two mvp10-style circuit
children (one digest, distinct witnesses) verified deferred, their sigma
claims folded 2->1 exactly as aggregate's sigma group runs it, and the
verify_fold transcript recorded and pinned op-for-op. The claims' weights
and values are absorbed field-for-field (an eq weight's low [1] included,
so the wire is bound, not assumed), the col/bridge/row surfaces sit at
computed stream ordinals held against the FoldProof, and BOTH endpoint
identities close from located words + squeezes alone — the exact dataflow
the in-circuit replay consumes next. All squeezes are scalars, so the
challenge ordinal IS the finalization ordinal.

Scaffolding domain for now: the real merge node spans bind + every
per-type fold under one challenger, and the claim words will CONNECT to
child-tape wires — later steps, per the mvp8 fixed-start precedent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The merge node's first region, transcribed: the fold transcript runs
through one b3 slot, the lambda/mu/rho squeezes are chain wires, the
claim coordinates and round messages are absorbed stream words, the
twelve rounds ride the existing MergedRoundGate slot, and the weight
evals at rho are PrefixGate eq products seeded by the absorbed low
words (bound, not assumed). Both endpoint identities publish as
zero-deltas; the accumulator (rho_col, rho_row, value) publishes as
the statement, is rebuilt from the PUBLIC SEGMENT ALONE, equals the
native verify_fold output, and discharges against the children's own
sigma table. Zero new gate types.

Two shape facts found by the assembly:
- The transcript TAIL past the last squeeze (the output value's
  observe) is absorbed but never compressed — no chain wire exists for
  it, and the real merge tape ends the same way. The value enters as
  its own input; the row endpoint delta is what binds it.
- The registered Ligerito configs floor at m=22, and this outer is the
  first small enough to land under it (dense_m 21 at nu2 6) — the row
  capacity floors at 2^7, live-prefix rows making the slack ~free.

Outer: chain b3 rows 55, nu 7, dense_m 22, mu 13, proof 122.7 KiB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
verify_aggregate_classes for the two mixed children, recorded and pinned
under ONE challenger: bind (label + registry digest + prior count 0),
the blake3 A/B folds (14 col + 14 row rounds each, 2 claims), the MacGate
A/B folds (3+3, pure eq), and the sigma fold — 333 ops, 821 stream
values, 100 squeezes, every op/value/squeeze located and held against the
AggregateProof and the assertions' own claim constructors. All TEN
endpoint identities close from located words alone, with the weights
REBUILT from the stream and evaluated through Weight::eval — the boolean
claims' length-64 lows (lagrange row weights, z_partial column weights)
included, the one weight shape step 1 never met. The five located outputs
reassemble the verifier's Accumulator surface-for-surface.

Also the FIRST end-to-end exercise of the aggregate's element group
anywhere: gather_element, el_folds, and discharge_element had no test
(the native merge-node test's children are boolean-only; per_element
appears in union_lincheck only as Vec::new()). The mixed children fold
and discharge all three groups.

Every finalizing op is still a scalar squeeze, so challenge ordinal ==
finalization ordinal across the whole merge tape — the property the
in-circuit chain wiring (next) relies on.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bind + all five folds of the merge node under one chain: the b3 slot
replays the merge tape (640 rows), every λ/μ/ρ is a chain squeeze wire,
the 80 rounds ride the shared MergedRoundGate slot, and the endpoint
weight evals split by low width — eq weights seed PrefixGate rows with
the absorbed low word (step 1's shape), while the boolean claims'
LENGTH-64 LOWS (lagrange row weights, z_partial column weights) fold
through 8 chained LeafEvalGate(8) rows each, their group-expansion
factors eq(ρ[3..6], h) entering as boundary publics checker-validated
against the PUBLISHED ρ coordinates (the alpha-expansion trust class,
mvp7's query-phase precedent). Zero new gate types again.

Ten endpoint zero-deltas publish; the five accumulator claims
(ρ_col, ρ_row, value) publish as the merge node's statement — the
checker reassembles the verifier's Accumulator from the PUBLIC SEGMENT
ALONE, asserts it equals the native one, and discharges all three
groups (boolean, element, sigma) against the real matrices. The last
fold's output value takes the transcript-tail input path (bound by its
row endpoint delta), as step 1 recorded.

Outer: nu 10 / dense_m 24 / mu 16 / proof 239.2 KiB.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ilder

The mvp11 merge node needs the two child-tape regions, which are exactly
mvp10_circuit_inner_tape's assembly twice — so that assembly is extracted
(the build_leaf_outer precedent: convert mvp10 first, keep it green):

- build_mixed_inner(n_blocks, mac_take, seed) -> MixedInner: the minimal
  mixed inner (mvp10 at 256/32, the mvp11 children at 128/16), proven +
  verified deferred; the deferred assertions ride along as the independent
  reference. mvp11_child is now a one-line wrapper.
- ChildTape::new: the RECORDING verify + every tape pin, region locate and
  native replica from mvp10's step 1 — re-asserted on every instantiation.
- ChildSlots + emit_child_region: the whole in-circuit emission (chain,
  query phase, wiring GKR + sigma emission, element PIOP, multipoint
  intake + anchor expect, W-rounds, spine, residual), slots created once
  and shared so a second region adds rows, not columns.
- check_child_region: the public-segment walk, now base-relative
  (ShapeBuilder::public_len is new) so a multi-region outer can walk
  region after region.
- emit_residual_region's slots are now cache-keyed: a second same-shape
  region reuses them instead of duplicating columns.

mvp10 green at its old shape (dense_m 28 / mu 21, 61-word leaves, pd 17,
P 2); whole circuit_merkle ignored suite 14/14. Proof size moved 311.8 ->
301.2 KiB (slot creation order shifted the registry; nothing pinned).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…erge outer

mvp11_merge_fold_region's outer circuit now carries THREE regions: the two
children's complete deferred verifiers (mvp10's assembly via the extracted
emit_child_region, instantiated twice over SHARED slots — rows, not
columns) and the fold region, all on one b3 slot. Each child's tape is
re-pinned by ChildTape::new (a recording verify per child) and its public
block walked by check_child_region against its own native replicas; the
fold region rides the children's slots (macs/mrs, the residual prefix
slot, the 8-lane leaf-eval).

One latent indexing bug surfaced: the fold's boundary-expanded eq publics
recorded vals.len()-1 as their PUBLIC index, which only coincided in the
single-region layout — they now record sb.public_len() at declaration.

Still scaffolding: the fold's claim stream words remain natively pinned
against the children's assertions, NOT yet connected to child-region wires
— that is step 3b (the connects).

Outer: total b3 rows 10,830 / nu 14 / dense_m 28 / mu 22 / 376.9 KiB.
Whole circuit_merkle ignored suite 14/14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d regions

The scaffolding gap closes: the fold region's absorbed claim surfaces are
now copy-constrained (sb.connect) to the child regions' own
assertion-emission wires, so the merge outer folds the claims its child
regions verifiably emitted — not free inputs natively pinned.

Per child, per claim family:
- sigma: FULLY wire-to-wire — value = the child's deferred s_sigma stream
  word (the same wire its rhs input check consumes), points = its GKR
  squeeze wires, eq lows = the constant 1.
- element A/B: row point = r_con's head (the child's element zc round
  wires past the row vars), col point = r_col's head (its element lc
  rounds REVERSED).
- boolean A/B: row point = x_inner_rest via the BATCH-MAJOR mlv mapping
  (round 0 = the dim-6 var, rounds 1..1+nu = x_outer, the rest =
  x_inner_rest[1..] — two wrong layout guesses died on the new
  ChildTape mapping asserts before this one held, the method-note
  discipline); col point = rr (lc rounds reversed); col lows = the
  child's absorbed z_partial words, word for word.
- What has no child wire stays the boundary pattern, now LEGIBLE: the
  matrix-eval values and the boolean lagrange row lows publish (the
  fold's own words), and the checker holds them against the children's
  assertions and REBUILDS the lows from each child's newly PUBLISHED
  z_skip (lagrange_weights_naive at K_SKIP). The in-circuit
  SkipNodeGate/phi8 derivation of the lows is the recorded upgrade.

ChildTape gains the z_partial ordinal + the surface->ordinal mapping
asserts; ChildRegion exposes the element/boolean round wires and
z_partial words; the child tail publishes z_skip (checker-validated).

Outer unchanged in shape: b3 rows 10,830 (5095+5095+640) / nu 14 /
dense_m 28 / mu 22 / 376.9 KiB. circuit_merkle ignored suite 14/14.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mvp11_merge_fold_region now runs the aggregate at the real merge arity:
two MORE children's leaf folds become PRIOR accumulators (each child of a
merge arrives with its subtree's accumulator), so every fold group folds
[inherited, inherited, fresh, fresh] — 4->1, as the module promised and
dd2ce8c implemented natively. The in-circuit fold loops were already
claim-count-generic, so the region machinery is UNTOUCHED; what the shape
adds:

- bind's transcript byte is the prior COUNT (2), pinned.
- A boolean fold group now MIXES low widths: inherited claims are
  accumulator outputs — pure eq, low [1] — beside the fresh claims'
  64-wide lagrange/z_partial lows. emit_weight's per-claim branch and the
  op-shape builder were already width-driven; a new pin asserts every
  claim in a group spans the same variable counts.
- The inherited surfaces: eq lows bind to the constant 1 in-circuit
  (sb.connect to ow); points and values publish (the fold's own absorbed
  words) and the checker holds them against the priors' own accumulators.
  The wire-to-wire connection arrives when merge outers STACK — a prior's
  surface is exactly what a previous merge outer publishes as its
  accumulator claim (the step-2 publics).
- The fresh claims' connects shift to index n_priors + k; everything else
  (child regions, checker walks, discharge of the 4->1 outputs) holds
  unchanged.

Fold tape 333 -> 413 ops / 821 -> 1021 values / 100 -> 120 squeezes;
outer shape unchanged: b3 rows 11,000 / nu 14 / dense_m 28 / mu 22 /
380.3 KiB. circuit_merkle ignored suite 14/14 (test-only change; full
gate green at 33e8dc7).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…5 folds)

The merge node's fold machinery becomes five width-driven, file-scope
helpers — fold_region_ops (the op tape for any claim-list set),
locate_and_pin_folds, replay_fold_endpoints, emit_fold_region,
check_fold_publics — with mvp11_merge_fold_region converted first and
kept green (the faithful-extraction order, again).

mvp11_swap_children_fold_scale then runs the merge fold over the REAL
recursion node: two deferred verifies of the leaf outer (build_leaf_outer
— deterministic, so one build serves both children; the 4->1 test covers
claim distinctness, the registry scale is this test's content). The
census lands at exactly 35 folds — 3 boolean types (A/B, 64-wide
lagrange/z_partial lows) + 14 element types (A/B, pure eq) + sigma —
437 sumcheck rounds, tape 1944 ops / 3431 values / 577 squeezes. Every
endpoint closes from located words AND as a published zero-delta, and
the 35-group Accumulator reassembles from the public segment alone and
discharges against the real node's own matrices and sigma table.

The headline: the fold region is CHEAP at real scale — the outer is
chain b3 rows 3887 / nu 12 / dense_m 26 / mu 18 / 345.9 KiB, smaller
than the 4->1 mixed-children outer. The merge node's eventual size is
its children's tape regions (the composition phase), not its folds.

circuit_merkle ignored suite 15/15 (test-only change).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same circuit (the foldability key), claims at unrelated FS points — the
2->1 merge needs two DISTINCT real children so its connects cannot pass
on index bugs. build_leaf_outer() keeps the old seed; mvp9 green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tter

The 2->1 merge node needs child-tape regions at leaf-outer scale, which
are exactly mvp10_leaf_outer_inner_tape's assembly per child — so that
assembly is extracted (the same faithful-extraction order as the minimal
child: convert the swap test first, keep it green):

- RealTape::new: the RECORDING verify of a leaf outer + every swap-test
  tape pin, region locate and native replica (GKR walk, rs x2 + the
  two-halves target, spine replay, general strip, anchor-expect replica,
  PoW locate) — re-asserted on every instantiation. NEW surfaces for the
  coming connects: z_skip/z_partial located and the surface->ordinal
  mapping asserts ported from the minimal child (batch-major mlv layout,
  r_con/r_col orders).
- emit_real_child_region over the SHARED ChildSlots (the same slot set —
  a second real child adds rows, not columns): chain, PoW, query phase,
  W-rounds, spine, residual, wiring GKR + sigma emission, multi-slot
  element PIOP, multipoint intake, anchor expect (one-hot gathers +
  eq-table dots), and ALL THREE assertion emissions. NEW: the z_skip
  wire publishes at the tail (+1), mirroring the minimal child.
- check_real_child_region: the checker walk, base-relative via pub_base.
- RealRegion exposes the assertion-emission wires the 2->1 connects
  will consume — including the matrix/element eval ADVICE pairs, which
  the real child has as bound publics (richer than the minimal child).

Swap test green at its exact recorded shape: prove 147 ms / verify 5
(deferred 2) / nu 15 / dense_m 29 / mu 23 / 336.1 KiB. Suite 15/15.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
mvp11_two_to_one_recursion_node: two DISTINCT real recursion nodes
(seeded leaf outers — one circuit digest, unrelated FS points; the outer
circuit's shape is seed-independent, which the digest assert pins) are
verified, folded, and attested by ONE outer proof:

- TWO REAL CHILD-TAPE REGIONS: each child's complete deferred verifier
  (the swap assembly via emit_real_child_region) over SHARED slots —
  the second child adds rows, not columns; the cell-slot budget holds
  at the single-swap-outer census.
- THE FOLD REGION at the real registry: 35 folds (3 boolean + 14
  element types x A/B + sigma) via the width-driven helpers, unchanged.
- THE CONNECTS, per child and family: every claim's points to the
  child's chain squeeze wires (batch-major mlv map for boolean rows,
  reversed lc rounds for cols, r_con/r_col heads for element), the
  z_partial lows word-for-word, sigma FULLY (value = the child's own
  deferred s_sigma stream word), and — richer than the minimal
  children — the matrix/element EVAL VALUES to the children's bound
  advice publics. One lagrange-low surface per child (lagrange(z_skip)
  is type-independent, so all six boolean folds' lows connect to one
  set) publishes and is checker-rebuilt from that child's PUBLISHED
  z_skip.

The 35-group Accumulator reassembles from the public segment alone,
equals the native verifier's, and discharges all three groups against
the node circuit's own matrices and sigma table. The node is not yet
SELF-similar (normalization is deliberately out of scope) — but this
proof attests both children's verification AND the fold that combined
their claims: 2->1 recursion.

Node: total b3 rows 39,025 (17,569 x2 children + 3,887 fold) / nu 16 /
dense_m 30 / mu 24 / proof 366.3 KiB / prove 258 ms / verify 6 ms
(DEFERRED 3 ms). circuit_merkle ignored suite 16/16.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…asures

build_node_outer(&lo0, &lo1) -> (LeafOuter, Accumulator): the 2->1 node
repackaged as a builder whose output IS a valid child input — recursable
(BLAKE3/BLAKE3, the gotchas' third occurrence) and returned as a
LeafOuter, so the builder composes with ITSELF. The 2->1 test becomes a
thin wrapper, green at its exact shape.

mvp12_recursion_tower: 4 leaves -> 2 nodes -> 1 LEVEL-2 NODE, built by
calling build_node_outer on its own outputs. Pinned: the node circuit is
seed-independent (two nodes over different leaf pairs share one digest);
a node proof PARSES AS A CHILD (RealTape re-asserts the whole swap map on
it); the level-2 node verifies two level-1 nodes in-circuit with all
connects; the root discharges one accumulator per level.

THE CENSUS SAYS THE TOWER DIVERGES, and why:
  level-1: b3 rows 39,025 / nu 16 / dense_m 30 / mu 24 / publics
           130,864 / proof 366.3 KiB
  level-2: b3 rows 103,902 / nu 17 / dense_m 31 / mu 26 / publics
           431,287 / proof 424.7 KiB (~3.3x publics per level)
The driver is NOT the proof (366->425 KiB, mild): it is the PUBLIC
SEGMENT. The statement binding absorbs the child's publics wholesale
(16 bytes/word), and the outer's publics are dominated by its own chain
words — every absorbed transcript word is currently a public_input. So
each child costs the parent ~pub/4 chain rows, and publics compound
geometrically. Recorded in the handoff as THE normalization wall: split
the chain words to WITNESS inputs (the proof body is existentially
quantified; only the child-statement binding and the checker-tier
publics need exposure) — a statement-design decision, flagged for Ron
alongside the two accumulator-chain walls (registry nu-pinning; sigma
digest-keying).

Bounded-depth trees work today (this test IS a depth-2 tree with a
4-leaf frontier). circuit_merkle ignored suite 17/17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The recursion-composition fix, per the approved public-interface design —
a node's public segment is its STATEMENT, not its transcript:

- emit_fs_chain: stream words are now classified. The child PROOF BODY
  (every Value word — rounds, caps' siblings, opened rows, multipoint
  values) enters as WITNESS input, existentially quantified and bound
  in-circuit by the chain compressions + the region gates that consume
  it; nothing native reads it. What stays public: the byte payloads
  (bytes_payload_mask — the STATEMENT surfaces: registry digest, counts,
  caps (checker-read until the in-circuit cap select), a child's circuit
  digest + public words) and domain constants. PoW nonces are witness
  (their wires publish separately where the grinding checker reads them).
- Shared structural constants (cw): params and zero-pad words collapse
  from one public per ROW to one per VALUE (~39k -> a handful at the
  node), shared through copy constraints — also the auditable surface a
  fixed-shape checker contract should pin (recorded).
- emit_opening/emit_query_phase take the cache (Option for the legacy
  hand-built-vals tests, which pass None and keep byte-identical
  behavior).

Tower census, before -> after:
  level-1 node: publics 130,864 -> 60,217 | b3 rows 39,025 -> 35,565 |
                proof 366.3 -> 356.0 KiB
  level-2 node: publics 431,287 -> 169,371 | rows 103,902 -> 68,232 |
                proof 424.7 -> 370.2 KiB
The geometric term is tamed (x3.3 -> x2.8 publics, x2.7 -> x1.9 rows)
but not gone: the residue is (a) the child's publics re-absorbed as the
statement binding — shrinks as tier 2 shrinks — and (b) the still-public
in-circuit-bound advice (sqrt-chain ys, g0s, residual qf/awp, hi-group
alphas), the next demotion. circuit_merkle ignored suite 17/17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Second demotion pass, same rule as the stream-word split: advice whose
binding is IN-CIRCUIT (published deltas / native recomputation of the
OUTPUTS) and whose value no checker indexes becomes sb.input():
- the sqrt-chain ys (pinned exactly by the published squaring deltas —
  char-2 squaring is a bijection), both child emitters;
- the GKR and element-zerocheck g0s (pinned by the round deltas);
- the residual qf/awp and the query phase's hi-group leaf-eval factors
  (the alpha-expansion tier: the checker recomputes natively and
  validates the published ACCS — the advice values were never read);
- the seven baked ghash weights join the shared-constant cache.

Tower census, the session arc (before the split -> now):
  level-1: publics 130,864 -> 60,217 -> 38,007 | rows 39,025 -> 33,173
           | proof 366.3 -> 345.8 KiB
  level-2: publics 431,287 -> 104,529 | rows 103,902 -> 55,612
           | proof 424.7 -> 422.1 KiB — and the ENVELOPE nearly closes:
           level-2 now sits at nu 16 / dense_m 30 (was 17/31), one mu
           step above level-1 (25 vs 24).
The residual publics are (a) the children's own public segments
re-absorbed as the statement binding — the leaf builder's inline advice
has not had this pass yet, and tier-2 arithmetization shrinks it further
— and (b) the checker-tier contract. circuit_merkle ignored suite 17/17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Same rule, mvp9's inline sites: the zc g0s and the anchor expect's
sqrt-chain ys become witness (bound by their published deltas); the seven
ghash eq weights and the 64 phi8 lambda node constants join the shared
cache. The leaf's public segment is the node's child-binding term, so
this propagates up the tower:

  level-1 node: publics 38,007 -> 33,995 | rows 33,001
  level-2 node: publics 104,529 -> 96,137 | rows 52,778 | 415.3 KiB
  (envelope holding: level-2 at nu 16 / dense_m 30 / mu 25)

Remaining publics are now dominated by (a) the published ZERO-DELTAS
(~3k of literal zeros per region — the connect-to-zw conversion is the
next cut, touching every emitter's publish list + checker walk, left
for a fresh pass) and (b) the checker-tier contract + query publics.
circuit_merkle ignored suite 17/17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The last publics cut of the interface redesign: every published-zero
delta across the recursion assemblies is now a connect — the proof
itself fails on a broken identity, and neither a public word nor a
checker item exists for it.

CORE (circuit.rs): the single-producer-per-class rule is RELAXED.
Multiple gate-output cells in one wire class ARE the circuit's
assert_eq between computed values — the permutation argument forces
every cell of a class equal, witgen already asserts a second producer
agrees (CircuitShape::run), and the acyclicity check now draws edges
from EVERY producer to every consumer. The MultipleProducers error is
gone; its pin test flips to expecting acceptance.

TESTS: output-vs-output identities connect directly (grand-product
tops, layer closes, input checks, fold endpoints, T_m == anchor.v,
claim == expect) — dropping their compare rows too; gate-output deltas
(round identities, sqrt-chain squarings) connect to a dedicated
ASSERT-ZERO ANCHOR public per region — one zero public NO gate
consumes, so the class adds no dataflow edges (connecting to the
ubiquitous zw creates cycles; found by mvp9 going Cyclic). Applied to
the fold region, both child emitters, and the leaf builder; every
checker walk and n_tail renumbered.

Tower census, the full interface-redesign arc (start -> now):
  level-1: publics 130,864 -> 24,295 (5.4x) | rows 39,025 -> 31,197 |
           nu 16 -> 15 | mu 24 -> 23 | proof 366.3 -> 341.6 KiB
  level-2: publics 431,287 -> 69,130 (6.2x) | rows 103,902 -> 46,663 |
           nu 17 -> 16 | mu 26 -> 25 | proof 424.7 -> 394.9 KiB
Full gate: workspace + all ignored suites green (m30 probe passes
alone at ~121-126 ms, box warm — the recorded discipline).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nnect

The first production-checker audit fix: the statement closure between the
residual side's inner and the spine's t_r — previously two publics an
external checker had to compare — is now a copy constraint in all three
assemblies (both child emitters and the leaf builder), using the
multi-producer relaxation. Both wires stay published as test
cross-checks; the production contract item is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two data-incomplete items from the audit ledger, fixed per spec:

THE FAMILY-H PAIR: the merged target and V split into their halves —
the packed-direct half of the target and V's group-value sum are now
MAC CHAINS IN-CIRCUIT (over absorbed value words × gamma squeeze
wires); only the RS halves (the transpose dots / linearized-coefficient
combination) remain advice, and  CLOSES IN-CIRCUIT
as a copy constraint — q_eval needs no exposure. The words the two
advice checks reference are RE-EXPOSED as tail publics (s_hat_v 2x128,
the r_dprime squeeze wires, the two rs gammas, the 256+P multipoint
values, then rs_half and V_rs themselves at known positions) — all
validated by the checker, all removable when the family-H
arithmetization lands.

THE BOOLEAN MatrixAssertion EQUATION: mat_pub now carries every datum
check_reported needs — x_inner_rest (batch-major mlv wires), x_outer,
the const-pin beta wires with their count-derived eps advice, the
z_partial words — and the ~20-row BOOLEAN LINCHECK REPLAY lands: entry
= alpha·v_a + v_b + sum beta·eps from absorbed finals and squeeze
wires, rounds through the shared MergedRoundGate, so the published
chain end IS the equation's bound target. A native pre-assert (the
method-note discipline) replays entry -> target against the deferred
assertion before any gate exists — it also confirmed the batch-major
x_outer mapping.

RealTape grows the prerequisites (rs-region fins, zc finals ordinal,
beta locates, outer_len, lc-round message ordinals as 3-tuples, the
split natives); the 2->1 z_skip read moves before the new family-H
tail block. circuit_merkle ignored suite 17/17.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per level the query phase folds the ABSORBED cap wires to one root
(2^c-1 PARENT rows, fixed positional order, no swaps) and every opening
runs FULL depth and CONNECTS to it — cap-internal siblings recomputed
natively from the cap layer, appended after the proof's path hints. The
per-query (challenge, terminal) select publics (3/query, ~1.3k/child)
and their checker tier are deleted at all four sites; to_publish is
alphas-only. cap_wires locates each cap payload by CONTENT (whole-
payload equality, forward search); the bit-spread width is max DEPTH
(field renamed spread_w). Census: level-1 node publics -> 20,633,
level-2 -> 60,712; child dense_m 27->28 and node mu 24->25 (boundary
crossings); prove 365->334 ms L1, 367.7 KiB. mvp6 keeps the inline
boundary-select as the legacy pin.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Entry 0 of cap_payloads — the L0 cap, the commitment — stays a statement
public; entries 1.. are PROOF BODY, bound by the chain and the in-circuit
cap trees with nothing checker-read, so their payloads enter as witness
(cap_wires split into cap_payloads + cap_wires; the tapes carry cap_pays
and demote in ::new; mvp7/mvp9 demote locally). Level-1 node publics
20,633 -> 18,293, level-2 -> 54,264, node rows 39,844 -> 39,482.

The node census now prints the cell-slot split: 289 gate + 1 public —
the mu-24 boundary is 256, so reclaiming mu is a >= 34-schema-word
consolidation (Round 3), not a free trim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bind_statement_circuit is v2 (flock-circuit-stmt-v2): it absorbs
union::publics_digest(public) — the words' 16-B-LE byte string in 1 KiB
chunks, each a BLAKE3 chunk leaf, LEFT-FOLDED through hash_pair. A chain,
not a tree: nothing is opened, and both pieces are exactly the pinned
circuit compressions. Empty segment -> the zero digest (length is bound
through the circuit digest's public layout).

The parent's replay takes the child's public words as WITNESS:
emit_publics_hash re-derives the digest from those wires (chunk chains +
PARENT folds, cw-cached params) and CONNECTS it to the absorbed payload —
output-output, no consumers, no checker item. Both child emitters carry
the region; the b3_rows estimates count it.

Census: level-1 node publics 18,293 -> 13,861; LEVEL-2 54,264 -> 15,968 —
the geometric publics term is dead, and the residual level-1/level-2 gap
is statement shape (the envelope's job). Rows 38,884 / 50,876; proofs
364.3 / 412.0 KiB. Timings not quoted (degraded-box session; probe
first).

Found en route, recorded in the handoff: the wiring RECOMBINATION
(f_eval == gathers + publics-MLE) is enforced only by the scaffolding
native verify in the tape constructors — never in-circuit, missing from
the Round-0 ledger; the recorded fix rides this H region (publics-MLE
folds + an eq-weighted gather chain) in the round-4 batch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One extra capacity bit was supposed to re-engage the pay-per-live arms
(witgen fill, element a/b copies, boolean run-list skipping) at the
level-2 node. Measured: boolean committed area is CAPACITY-shaped, so
M_bool 31->32 doubled the boolean stack (open 84->190 ms, level-1 prove
260->390), and the level-2 element arms STILL ran dense (copies 206->182
ms — the dominant slots sit above the 50% worth_skipping gate even at
half utilization). Reverted; the finding stays as a comment at the
sizing line. The level-2 dense-arm cost is Round-3 work: audit
dead_rows_unread's composition and shrink element live rows — not a
capacity knob.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
rothblum and others added 6 commits August 14, 2026 22:50
…v21)

Blake3Setup, Sha256HybridSetup and KeccakSetup prove/verify through
prove_fast_ligerito_union now: each setup carries its single-slot
Registry, pcs_params are the DENSE params (dense_m + integer lanes), and
counts below capacity commit zero dummy rows (blake3/sha2 via the partial
batch-major generators; keccak keeps full-capacity counts + keccak_f(0)
padding for its walker-carried const pin, copied onto the registry type).
The padded-commit prove_fast bodies, the generic prove_ligerito twins,
prove_fast_timed, and the row/batch-major witness dispatch are deleted;
PCS_TRACE=1 is the phase breakdown now.

proof-IO v21: the R1cs bundle flavor's payload is the merged union proof.
The m6 fixtures re-pin (wire version moves all four mixed pins; integer
lanes move both anchors — which now pin setup.prove_fast itself, the
hand-rolled anchor assembly being exactly what the setups do).

Remaining padded-commit users, flagged in-code as the next
consolidate-or-retire candidates: keccak3 (no batch-major producer yet)
and the SHA-256 Merkle-path product (padded-layout region openings).

Full suite green incl. all ignored hash-table roundtrips, const-pin
soundness on the union path, and the rewritten two_blake3_tables direct
arm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Used to measure the fast-vs-secure prover gap at m=32: the whole
difference is OOD sampling (L0 full-witness MLE eval 9.0ms + 2/level
~7ms), absent under UDR; secure pays +155 KiB of query openings instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- flock-core: drop the virtual-fold experiment cluster (VirtualEqTerm/
  VirtualEqBasis dead methods, fold_f_blocked, fold_and_msg_blocked_virtual,
  SumcheckProver::fold_blocked_*_virtual, generate_f_and_claim_blocked) and
  the superseded pre-extension recursive_prover_with_basis_impl (~590 lines);
  derive_ladder_shape wrapper folded into derive_ladder_shape_tuned's doc;
  evaluate_dense_at_residual gated cfg(test).
- pcs: delete the non-grinding twins (open_batch_mixed_..._s_hat_v,
  verify_opening_batch_ligerito_mixed) — pure OpeningGrinding::disabled()
  delegations; callers pass disabled() explicitly (transcript-identical).
- tower.rs: cfg(test)-gate the test-harness residue from the circuit_merkle
  move (Online reporting, census, Rng/Tree/MerklePathGate, chain circuit
  helpers); delete the fields dead even in tests (RealTape.n_p,
  ChildRegion.el_*_w).
- prover: prove_fast_core_with_codeword is private (no external callers).
- hash setups: drop the new_batch_major aliases (the layout split died with
  the padded commit); hash_throughput bench and its driver script measure
  one layout instead of the same one twice.

cargo check --workspace --all-targets: 0 warnings. 633 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
13 *_probe benches, padded_vs_dense (measures a distinction the union
consolidation removed), and the keccak_mid_density / mle_eval_bench
examples — all completed experiments with no consumers; recoverable from
git history.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- README: drop the deleted flock_chain CLI section, collapse the benchmark
  table's dead row-major/batch-major split, note the encoder shrink since
  measurement.
- Fix broken intra-doc links (chain_common, prove_fast_from_witness,
  crate::chain, circuit_merkle) and the unsound-challenger feature comment.
- Historical docs get a note that --test circuit_merkle invocations predate
  the tower productionization (tests now: --lib tower::<name>); current-doc
  commands stay accurate.
- Competitor benchmark scripts point at living benches (keccak3_proof,
  sha2_proof) instead of the retired keccak_proof/sha2_chain_proof.
- schedule.rs module doc no longer calls the production registry 'Phase 0
  scaffolding'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Textual: .gitignore union; the ligerito.rs visibility bumps auto-merged.

Semantic fixups so the CUDA oracle harness compiles against the evolved
protocol (each dump bin + .cu test is a CLOSED oracle pair — the bins keep
their original vector formats):
- dump_commit_vectors: PcsParams gained num_lanes (None = the pow2 path the
  kernels implement); the single root is now the depth-0 cap layer.
- dump_merkle_open_vectors / dump_ligerito_l0_vectors: merkle_multi_proof
  left the live protocol (cap layers replaced it in 3251bc5) — a frozen copy
  lives in src/bin/dump_common/merkle_octopus.rs; LigeroWitness::root()
  likewise inlined as tree-top.
- dump_zerocheck_round1_vectors: the old two-field PaddingSpec is
  PaddingSpec::uniform(k_log, useful_bits, 2^(m-k_log)) in the run-list API.

Workspace: 0 warnings, 633 tests pass. NOTE: the .cu kernels still target
the pre-F256 F128 ladder; regenerating vectors for changed-shape stages and
porting kernels to the current protocol is follow-up work on a CUDA box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rothblum
rothblum marked this pull request as draft August 16, 2026 19:45
rothblum and others added 7 commits August 17, 2026 20:09
…recursion_circuit

Brings in main's four post-CUDA PRs: the genus-95 curve code (#27), the
AG-skip zerocheck wired end to end (#28/#31), the all-core (P+E) pool with
topology-gated hops (#29), and the Ligerito initial-sumcheck lookahead +
NTT deep-pass package (#30).

Resolution notes, beyond the mechanical hunks:

- NTT deep pass: main's fused 2-layer restructure adopted with this
  branch's live-lane threading; fused_2layer_row_op now iterates the row
  slice's own length (the textual auto-merge would have indexed dead lanes
  out of bounds when live < num_ntts).
- Ligerito lookahead: machinery + tests kept; the F128 driver main
  scheduled it in was already replaced here by the F256-ladder driver
  (extension.rs), which accepts and ignores the coefficients — transcript-
  neutral by exact polynomial identity. Porting the two-rounds-per-pass
  schedule onto the ladder's fused folds is the recorded follow-up.
- SkipPoint: threaded through the lincheck grinding (the fused PoW+squeeze
  transcript is unchanged on the phi8 arm); phi8-only protocol surfaces
  (merged transport, union lincheck, union claim-point builders) convert
  at the boundary via .phi8().
- The retired chain product stays deleted; main's small edits to those
  files (SkipPoint adaptations) die with them. Blake3Setup regains the
  generate_witness_ab dispatch and the prove_fast_timed wrapper that the
  AG methods and the blake3_rs_vs_ag bench consume.
- AG open calls route through the grinding-aware open with
  pcs_params.opening_grinding() (disabled on default profiles, so main's
  AG transcripts are unchanged).
- Workspace-wide clippy cleanup so `-D warnings` stays green on current
  stable.

Verified: cargo fmt --check and clippy --release -D warnings clean;
cargo test --release --workspace green (674 passed). Byte pins: the m6
merged-union fixtures pass; the four ignored failures (transcript_shape's
element pin, union_element's three mixed tests) fail identically on
pre-merge HEAD with identical computed hashes — pre-existing fixture rot,
not moved bytes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…script-v2)

The Rust FsChallenger moved to 16-byte op headers ([op, kind, 0*6, len u64
LE]) with byte payloads zero-padded to a multiple of 16, and squeezes that
absorb only their header (no re-absorption of the squeezed output) — the
framing that lets a recursion circuit replay the transcript with pure copy
constraints. The harness's host challenger (challenger.hpp) and the
on-device zerocheck-tail replica (zc_challenger_device.cuh) still spoke the
old 1-2 byte tag + 8-byte length framing, so every FS-replaying CUDA test
diverged at its first challenge (the Blackwell leg's ALPHA FAIL in
test_lincheck).

Ported both to the new framing. Unchanged: the SHA-256 squeeze stream
(SHA256(state || ctr)), the PoW state digest (plain clone-finalize) and
preimage (digest || nonce LE), and the nonce absorb (observe_bytes, which
now frames itself).

Verified host-side against the real challenger: dump_challenger_vectors +
test_challenger (clang++, no GPU) — all 12 ops (observes, labels, bytes,
scalar/vec squeezes, 8- and 12-bit grinds, query loop) match byte-for-bit.
The device replica is the same mechanical change; the Blackwell leg is its
test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One Blackwell run now reports every failing target instead of stopping at
the first — the step still fails if any target failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The GPU witness kernel still generated the pre-Option-E/F trace: uniform
250-bit G blocks (six 2-op adds + two lin-id rows), z-const at bit 512,
out_hi at 15,153. The R1CS moved twice since (Option E dissolved the lin-id
slots into the cascade; Option F fused each G's two message adds into
3-operand carry-save adders and trimmed round 1's constant-c ADD_C1 rows),
so the blake3_witness target and the GPU prove roundtrips diverged from the
Rust generator at block 0.

Ported to the current layout (blake3.rs):
- fused_add3_parts: 61 rows per fused ADD (31 majority + 30 ripple,
  the ripple group stored >>1);
- variable G blocks: 183/183/182/182 for round 1's column G's (c = IV[g]
  constant trims ADD_C1 to 30/30/29/29 rows), 184 generically, with the
  G_BASE prefix sum in branch arithmetic (no local array);
- the I/O-aligned regions (cv 0, out_lo 256, m 512, params 1024,
  out_hi 1152, G blocks 1408..11706) and the constant pin at the END
  (bit 11,706), set in all three slices;
- per-G records shrink to BitRecord<3>; the message schedule uses the
  precomposed B3_PERM_R in the serial builder too (same composition as
  blake3.rs::per_round_msg_idx).

The G math lives in one b3_g_update shared by the serial and the 12-lane
warp builders; the kernel scaffolding (warp-per-block, shared-buffer
staging, stripe transpose, launcher API) is unchanged, so no caller moves.

The header is now host-compilable (kernels guarded behind __CUDACC__), and
the serial builder + stripe transpose were validated on the host against
dump_blake3_witness_vectors (24 blocks, n_total 32): every block x slice
u64 and every z_lincheck byte matches the Rust generator bit-for-bit. The
warp builder shares the same b3_g_update; the Blackwell leg is its test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_blake3_witness_host runs blake3_witness.cuh's serial builder + stripe
transpose on the host against the same B3WT oracle the GPU target uses —
plain C++, builds with $(CXX). This is how the Option-F port was validated
before its first Blackwell run; keep using it to catch witness-layout drift
locally instead of through CI. Targets: test_blake3_witness_host (build),
blake3_witness_host (build + dump + run).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ped-path helpers

Groundwork for the F256 fold ladder port (the transcript the branch
verifier requires), all host-validated locally against the real Rust
implementations before costing a Blackwell run:

- f256.cuh: F256Ext = the quadratic extension of GHASH F128
  (u^2 + u + x^-1), Karatsuba mul (3 base products + x^-1 shift-and-fold),
  2-product base mul, u*B for split_basis. The {c0,c1} layout reinterprets
  in place as split_coordinates' word list. Host + device paths share the
  composition; test_f256_host + dump_f256_vectors pin the host side
  bit-for-bit against flock_core::field::F256.
- challenger.hpp: sample_f256 (one two-word vec squeeze), observe_f256
  (length-2 slice observe), fused grind_pow_and_sample_f128{,_vec} (SHA
  discipline: grind then sample; 0-bit still absorbs the 0 nonce), and the
  stratified query schedule (LevelSchedule::decompose + queries_from_words
  + the fused query-phase grind). dump/test_challenger extended with all
  new op shapes incl. a full 279-query stratified phase — byte-identical.
- merkle_open{.hpp,_device.cuh}: capped per-query path indices + device
  gather (the live protocol; the octopus multi-proof stays as the frozen
  oracle), cap-layer extraction (the commitment IS the cap; no root).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…atch-up)

The GPU prover now speaks the branch's CURRENT protocol end to end — the
roundtrip gap's four fronts in one arc:

- ligerito_f256.cuh: the complete F256 ladder as run_ligerito_f256
  (flock-ligerito-basis-f256-split-v0), porting extension.rs::
  recursive_prover_with_basis_impl — L0 OOD loop beta-batched into the
  round-0 message/target/basis with claim-PoW, initial_k F256-challenge
  folds with the CODE-SWITCH replacement message, recursive commits of
  the split base-field table (NTT/Merkle kernels reused unchanged —
  the protocol never commits F256 words), level OODs, stratified query
  phases with query/consistency PoW, capped per-query paths, transpose-
  NTT induce (duplicate queries combined host-side in char 2), presplit
  introduce/glue. F256 state rides {c0,c1} arrays, so each code switch
  is a pointer cast; the four len*16-byte ping-pong regions keep the old
  ladder's peak. Fold/message kernels mirror extension.rs' formulas at
  fold distance 1 (adjacent pairing).
- prove_ffi.cu: the full 128-bit grinding schedule lands in the PIOPs +
  transport (zerocheck initial/skip/round PoW, lincheck alpha/beta/round/
  skip, ring-switch r'' per claim, ONE fused two-word gamma squeeze —
  the old two scalar squeezes were mis-framed); bind_statement absorbs
  the CAP (no root); the zerocheck tail runs host-driven to the end
  (the device finisher predates per-round PoW); the old F128 recursion
  section and the octopus open are gone.
- gpu_roundtrip.rs rebuilt for the current structs (Commitment{cap},
  sumcheck_transcript_f256, all five nonce families, matrix_evals) and
  passes the grinding schedule from the PcsParams accessors.
- Vector tests: test_ligerito_f256 (GPU) and test_ligerito_f256_host
  (pure host, no nvcc) replay the ladder against dump_ligerito_f256_
  vectors' oracle from the REAL Rust driver at registered configs. The
  host replay is green byte-for-byte on every proof field at m22/23/26/
  30 (r=2..4) locally — transcript order, code-switch algebra, caps,
  stratified queries + capped paths, induce, and all three PoW nonce
  families validated before any Blackwell run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rothblum
rothblum marked this pull request as ready for review August 18, 2026 11:41
@rothblum
rothblum requested a review from erabinov August 18, 2026 11:41
@rothblum

Copy link
Copy Markdown
Collaborator Author

@erabinov ready for your review — this is now the complete branch: the last two commits (01bb0d4, 4150ef0) close the GPU roundtrip gap by porting the prover to the F256 ladder (capped Merkle, stratified queries, full 128-bit grinding schedule). The CUDA leg is green end to end on the Blackwell runner: vector suite incl. the new F256/ladder oracles, and gpu_roundtrip at m22/32/34 (m34 steady prove 172.7 ms). The ladder has a host-only replay test (make ligerito_f256_host, no nvcc) that pins the whole orchestration against the Rust driver byte-for-byte — useful as the first stop if anything ever diverges. Plan is to land with a merge commit (docs/memory reference branch SHAs).

🤖 Generated with Claude Code

@rothblum rothblum left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two-axis review (Standards + Spec per subsystem; every finding verified against the head, the decisive ones by execution).

Summary of Findings

Blocking findings: one soundness-margin defect in the F256 ladder's consistency-batch grinding (off-by-one level indexing, mirrored prover/verifier — the CUDA port mirrors the Rust schedule, so the fix must land on both sides), four encoder entry points that panic unconditionally (reproduced on this head; already fixed in stacked PR #32), a transcript-recording defect (already fixed on ag-union), a latent tape-walker divergence, an unsound safe API, plus dead-API, CI-coverage, and doc-rot sweeps. The core protocol verified clean: config enforcement (field="f256", all 98 TOMLs), prover/verifier transcript symmetry across zerocheck/lincheck/union/opening, union bounds and tamper coverage, encoder census constants, CUDA transcript constants, and the PR's own claims (cargo check --all-targets 0 warnings; exactly 674/674 tests green — both re-run on the head).

Comments

crates/flock-core/src/pcs/ligerito/extension.rs:1315 — The consistency α for level ℓ is ground with consistency_bits(ℓ+1) (same at :1128; the verifier mirrors the shift), so bits[0] is never applied and each level delivers the next level's bits — m29_secure L3 gets 3 where validate() (ligerito.rs:1549) certifies 4. Grind each α with its own level's bits on both sides.

crates/flock-prover/src/r1cs_hashes/blake3.rs:1715prove_fast_ag, prove_fast_timed, prove_fast_ag_timed, and verify_ag pass the union-shaped pcs_params into the standard-pack pipelines, so pcs::commit's length assert panics on every call — reproduced via the ignored AG roundtrip on this head. Land the stacked fix (PR #32, f8ba9a7 + 22150da: route them through direct_pcs_params()) with this PR.

crates/flock-core/src/transcript_record.rs:761RecordingChallenger does not override hash_kind, so the trait default reports Sha256 for a BLAKE3 inner transcript; AG paths seed their DRBG from it (ag_skip.rs:765, lincheck.rs:1235), so a recorded run derives different randomness than the bare run. Forward to self.inner.hash_kind() — the fix exists on ag-union (a3f1098).

crates/flock-prover/src/tower.rs:6566RealTape::new's PoW locator advances the payload ordinal only on ObserveBytes | Pow, but the stream builder (transcript_record.rs:617) and the sibling walker at tower.rs:5534 also count LegacyPow, so a tape containing one mislocates every later nonce word. Count LegacyPow here and in labeled_bytes_payloads (tower.rs:14960).

crates/flock-core/src/lib.rs:247alloc_zeroed_vec<T: Copy> is a safe pub fn whose own SAFETY note requires the all-zero bit pattern to be valid for T, unenforced by the signature — safe code can call it with T = &u8 and obtain null references (UB). Bound T by an unsafe Zeroable marker trait implemented for the POD types the crate uses.

crates/flock-core/src/pcs/ligerito.rs:4817recursive_verifier_with_basis_succinct and recursive_verifier_with_basis (:5549) observe label flock-ligerito-basis-v0 while every basis prover emits flock-ligerito-basis-f256-split-v0, so they reject every produced proof, have zero callers, and their docs claim production use falsely. Delete both and their orphaned helpers.

cuda-ghash/merkle_open_device.cuh:32gather_tree_nodes_device and merkle_cap_layer_device discard every cudaMalloc/cudaMemcpy/kernel error via (void); at the ladder's final level this is the last device work before flock_cuda_prove_blake3 returns, so a failure yields exit code 0 with a zeroed opening path. Return cudaError_t and check it at each call site like the file's other CUDA calls.

cuda-ghash/Makefile:333TEST_BINS and run-tests omit the new test_blake3_witness_host, so neither CUDA CI leg builds or runs the host witness test this PR adds. Add test_blake3_witness_host to TEST_BINS and blake3_witness_host to run-tests.

crates/flock-prover/src/tower.rs:9975build_fl_node_k is documented (:9711) as the k-ary FL lever for any k ≥ 2, but ChildSlots::new_env always sets b3_alt: Some, so child i ≥ 2 hits panic!("split-BLAKE recursion supports exactly two children"). Narrow the doc and assert to k == 2.

crates/flock-prover/tests/union_element.rs:147 — All 12 element-in-union end-to-end tests are #[ignore]d and CI runs without --ignored, so the element union path this PR adds is never executed in CI. Un-ignore element_only_union_roundtrip — it passes in 0.03 s on this head.

crates/flock-prover/tests/union_m6_fixtures.rs:187 — Both m=6 byte-anchor tests are #[ignore]d and no CI leg runs them, so the PR's "fixture anchors byte-stable" claim is enforced by nothing automated. Un-ignore both — they pass in 2.2 s on this head.

crates/flock-core/src/challenger.rs:973 — Under hash-count, the Blake3Chain fused PoW+squeeze paths (through :1029, and apply_pow_squeeze at :500) increment neither SQUEEZES/SQUEEZED_BYTES nor verify-side POW_SHA256, so the counters' documented meanings are false and verifier_hash_count undercounts chained transcripts. Add the increments to the fused/chain paths.

crates/flock-prover/src/prover.rs:1043 — The GKR_PAR_DIAG block prices a fork/join "before anyone pays the transcript change", but the one-sided fork at :990 has shipped and no "honest sequential transcript below" exists; ProvePhaseTimings::witness_place_s/witness_compact_s (:1814) are likewise never written or read. Delete the probe block and both fields.

crates/flock-prover/src/proof_io.rs:3 — The module doc says three bundle types, "currently 20", and flavor 3 valid — VERSION is 21 and peek_flavor rejects byte 3 — while BundleFlavor::Chain (:189) and HashKind (:147) are unconstructable/unconsumed and the UnknownFlavor/FlavorMismatch/counts/test docs (:224/:232/:302/:707) repeat the falsehoods. Delete the dead API and rewrite these docs to the two live flavors at v21.

crates/flock-core/src/pcs/ligerito/extension.rs:2305each_split_level_removes_k_minus_one_original_variables asserts (dimension+1) − k == dimension − (k−1), a pure integer identity touching no production code, so it passes even if the code-switch logic is deleted. Delete the vacuous test.

crates/flock-core/src/transcript_record.rs:93 — Three comments (:93, :156, :366) claim squeezed output is re-absorbed, contradicting this file's own :429 ("Squeezed output never appears") and the challenger; :112 says a Pow absorbs a 32-byte nonce (it is one 16-byte word), and :1112 says "Four finalizing ops" above assert_eq!(…, 5). Correct all five to the header-absorption/duplex facts.

crates/flock-core/src/circuit.rs:296 — The deleted MultipleProducers doc is fused mid-word onto Cyclic's, and the struct doc (:307) still claims validation enforces "at most one producer per class" — Circuit::new deliberately allows multiple (:417), and schedule.rs:54 still cites the removed variant. Fix the mangled line and document the multi-producer assert-eq semantics in all three places.

crates/flock-core/src/circuit/builder.rs:51 — The Wire doc justifies forward references with "a squeezed challenge is re-absorbed into the transcript that produced it" — false on this design (transcript_record.rs:429) — and tests/circuit_builder.rs:290/:377 repeat it while the code wires no squeezed word from a producing row. Replace the rationale with a true forward-reference example and fix both test docs.

crates/flock-prover/src/r1cs_hashes/merkle_glue.rs:436BitSpreadTable's layout doc places the constant-one column at 128·(depth+3), but the code puts position_mask_pos there and the constant at 128·(depth+7), omitting the four position words; wiring by this doc lands on the wrong columns. Rewrite the block to the mask/check/position_*/const order the accessors define.

crates/flock-prover/src/r1cs_hashes/blake3.rs:2719 — This doc and sha2.rs:2318 claim padding blocks are "filled with a valid all-zero-input compression, constant = 1", but prove_fast now uses generate_witness_batch_major_partial, which leaves dummy rows identically zero. Reword both to the zero-dummy mechanism and delete keccak.rs:649's garbled leftover fragment.

crates/flock-prover/src/r1cs_hashes/merkle_path_common.rs:1 — Docs still present the deleted chain code as extant: chain_common references here (:1, :188, :249), schedule.rs:76, lincheck.rs:1864, and flock-prover lib.rs:6's [chain] intra-doc link. Replace these with self-contained wording for the surviving modules.

crates/flock-core/src/pcs/ligerito.rs:569embedded_initial_k's doc says "6 everywhere except m29 Fast", but the shipped TOMLs carry 4 for the m28 fast/slim family and 5 for m29 (echoed at :7081/:7089); l0_cap_depth (:238) likewise promises a legacy fallback its body never takes, ignoring its _d parameter. State the real per-m values and drop the dead "else" clause and parameter.

crates/flock-core/src/pcs/jagged.rs:3819 — Dangling or false PCS references left by this PR's renames: [ProductPair] ×3 here (densify returns a VirtualPair), jagged::JaggedWeight (ligerito.rs:3880, jagged.rs:373 — also "called twice" with one call site), the Pair paragraph parked on LAZY_RS_MAX_SIDES (:4095), "the removed jagged open" (:4746), [commit_lane_grid] (commit.rs:439/:1072), and [derive_ladder_shape] (ligerito.rs:4422, stratified.rs:53). Repoint each to the live name.

crates/flock-core/src/zerocheck/multilinear.rs:1571fold_and_round_pair_sparse_into's doc says the returned layout is "exactly shrink_intervals of live_in", but the code returns the pair-aligned cover, a strict superset; the :1569 bullet directs callers to zero_dead_regions (:1773), a pub fn with zero callers whose live counterpart is expand_to_dense, and univariate_skip_optimized.rs:649 claims the run-list branch has "no production callers" (prover.rs:903 reaches it). Fix the doc, delete the dead fn, drop the parenthetical.

crates/flock-core/src/verifier.rs:232 — The Mixed binding doc says it absorbs "the commitment root", but bind_statement absorbs the whole cap layer and pcs.rs:320 states there is no root; the same stale word recurs at union.rs:1645 and union_mixed.rs:4. Change "root" to "cap" in all three.

crates/flock-prover/src/tower.rs:300 — tower.rs doc/dead-code drift in ten places: :300 (TOWER_ENV_M no longer exists), :246/:642/:17992 (env_free_counts hard-coded true; historical-cap arms dead; ENV_PAD unread), :324/:370 (stale slot-key map), :406, :960, :2541 (18 weights, not 17), :7723, :15378 (node-builder doc sits on record_child_verify), :16819, :17184. Sweep them against the code.

docs/128-bit-grinding-audit.md:6 — The redirect note names mvp11_two_to_one_recursion_node, mvp12_recursion_tower, envelope_registry_diff (and recursion-100-128-variants.md:7 names mvp7_real_query_phase) as runnable --lib tower:: tests, but none exist on head and --exact passes vacuously; :588 links the deleted tests/circuit_merkle.rs and :638 says v20 (head is v21). Rewrite to the surviving tower tests and v21.

docs/merkle-r1cs-notes.md:43 — The status line says the multipoint-twisted prototype "is not wired into verify_batch_merged — integration is the unfinished part", but on head it is the production merged path (pcs.rs:1655/:2131). Add a dated note that the integration shipped. Also annotate multi-table-design.tex:1584's chain_common.rs row "(retired 2026-08-14)" like its sibling row.

Validation Problems

  • CUDA reviewed statically only (no toolchain on the review box); the GPU vector/roundtrip legs rest on the Blackwell CI.
  • The decisive checks ran on the head (the four-entry-point panic reproduced, both ignored-test runtimes measured, 674-test suite and 0-warning check re-run); the PCS doc-rot findings rest on line-anchored static verification with spot-checks — all spot-checks held.

🤖 Generated with Claude Code

rothblum and others added 3 commits August 19, 2026 10:24
…nd, close walker/counter gaps

PR #26 review, functional findings:

- blake3.rs: prove_fast_ag, prove_fast_timed, prove_fast_ag_timed, and
  verify_ag passed the union-shaped pcs_params into the standard-pack
  pipelines and panicked on the commit length assert; all four now route
  through direct_pcs_params() (same fix as f256-lookahead f8ba9a7/22150da).
  prove_fast_ligerito_ag_roundtrip un-ignored as the default-run guard.
- transcript_record.rs: RecordingChallenger forwards hash_kind() to the
  inner challenger (the trait default reported Sha256 for BLAKE3 chains,
  diverging recorded AG runs) — same fix as ag-union a3f1098.
- tower.rs: the RealTape PoW locator and labeled_bytes_payloads now count
  LegacyPow like the stream builder and the ChildTape walker; a tape
  holding one no longer mislocates later nonce words. build_fl_node_k
  narrowed to k == 2 (ChildSlots has no slots for a third child) with the
  doc corrected.
- lib.rs: alloc_zeroed_vec now requires an unsafe Zeroable marker trait —
  the safe fn could hand null references to safe code (T = &u8).
- ligerito.rs: deleted the dead F128 basis verifiers (label mismatch made
  them reject every produced proof; zero callers) + the orphaned
  fold_round_grinding_bits; the two induce_sumcheck helpers their deletion
  orphaned are now cfg(test) (their remaining callers are algebra guards).
- challenger.rs: the fused PoW+squeeze transition counts into the
  hash-count ledgers (POW/SQUEEZES/SQUEEZED_BYTES were silent on chains).
- prover.rs: GKR_PAR_DIAG probe deleted (the one-sided fork shipped);
  dead witness_place_s/witness_compact_s fields removed.
- proof_io.rs: dead BundleFlavor::Chain and HashKind removed; header and
  error docs rewritten to the two live flavors at v21.
- extension.rs: deleted the vacuous integer-identity test.
- union_element/union_m6_fixtures: the m22 element roundtrip and both m6
  byte anchors are default-run (0.03 s / 2.2 s) so CI enforces them.
- cuda-ghash: merkle_open_device.cuh fails fast on every CUDA error via a
  header-local MOD_CK (errors were (void)-discarded — a failed final-level
  gather returned success with a zeroed path); Makefile TEST_BINS and
  run-tests gain test_blake3_witness_host / blake3_witness_host.

Validation: cargo check --workspace --all-targets 0 warnings; m6 anchors,
element roundtrip, and the AG roundtrip green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PR #26 review, doc-rot and dead-code findings. All statements verified
against the head; no behavior changes (m6 byte anchors green).

- transcript_record.rs: squeeze output is never re-absorbed — five
  comments corrected to the header-absorption/duplex facts (incl. the
  16-byte Pow nonce word and the five-finalization test comment).
- circuit.rs/schedule.rs/circuit/builder.rs + tests/circuit_builder.rs:
  multi-producer classes are allowed (assert-eq semantics) — the mangled
  MultipleProducers fragment, the "at most one producer" claim, and the
  re-absorbed-challenge rationale replaced with what the code does.
- merkle_glue.rs: BitSpreadTable layout block lists the four position
  columns; the constant-one column is at 128*(depth+7).
- blake3/sha2/keccak: const-pin docs reworded to the zero-dummy padding;
  keccak's garbled fragment deleted.
- chain remnants: chain_common/[chain] references replaced with
  self-contained wording (merkle_path_common, schedule, lincheck,
  flock-prover lib.rs).
- PCS docs: embedded_initial_k states the real m28=4/m29=5 families;
  l0_cap_depth drops its ignored `_d` parameter (9 call sites); dangling
  names repointed (commit_lane_major, fill_weight_range, VirtualPair,
  derive_ladder_shape_tuned, merged-open sumcheck); the Pair paragraph
  moved onto enum Pair; zero_dead_regions (zero callers) deleted with the
  sparse-fold doc corrected to the pair-aligned cover; univariate_skip's
  "no production callers" parenthetical dropped (the union path reaches it).
- verifier/union/union_mixed: "commitment root" -> cap (there is no root).
- tower.rs: envelope_shape/TOWER_ENV_M, slot-key map, SkipNode/SkipClose,
  Blake3Gate rationale, W_18, PoW wire pair, FoldPub publishes, and the
  publics* comment corrected; env_free_counts() inlined (always true) with
  the dead historical-cap arms, counts_bool census field, and the
  ENV_PAD-oracle comments removed; the 2->1 node doc re-homed from
  record_child_verify onto build_node_outer_app.
- docs/: grinding-audit + variants redirect notes name the surviving tower
  tests (the quoted mvp*/envelope names were retired); circuit_merkle.rs
  link retargeted to src/tower.rs; v20 claim date-scoped (head is v21);
  merkle-r1cs-notes gets a dated shipped-integration note; multi-table
  code map marks chain_common.rs retired.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y-one)

The query-consistency batching PoW was indexed one level high: level ℓ's
α (bad-event degree ceil_log2(queries[ℓ])) was ground under
consistency_batch_grinding_bits[ℓ+1] at L0 and every intermediate level
(the final level already used its own), so bits[0] was never applied and
the delivered schedule was not the one validate() certifies per level.
With the shipped increasing arrays this over-ground fast/slim mid-levels
and under-delivered exactly where an array decreases — m29_secure L3 got
3 bits where the validator certifies 4, leaving the batching error at
2^-128 instead of strictly below. DELIBERATE TRANSCRIPT CHANGE.

- extension.rs: prover L0 + intermediate levels and both verifier mirrors
  now use consistency_bits(level); the final-level sites were already
  correct. Claim-batch indexing is untouched — it is destination-keyed by
  design (each transition grinds the destination level's bits, index 0
  included).
- cuda-ghash/ligerito_f256.cuh: query_phase grinds
  consistency_batch_grinding_bits[open_level]; the now-meaningless
  next_level parameter is gone from it and its three call sites.
- union_m6_fixtures: both m22 anchors and the nu10 full-utilization
  fixture re-pinned (history entry added); the three small nu10 shapes
  hold — their adjacent levels' bits are equal. Header v20 -> v21.

Validation: full workspace suite green (m6 anchors stable across two
print runs), clippy clean, x86_64-apple-darwin 0 errors (66 pre-existing
aarch64-gated dead-kernel warnings, identical on the base). GPU
roundtrips need the Blackwell CI re-run — Rust and CUDA moved in
lockstep.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@rothblum

Copy link
Copy Markdown
Collaborator Author

All review findings are now fixed on this branch, in three commits:

  • 1247591 — functional: the four stranded AG/timed entry points route through direct-shape params (the AG roundtrip is default-run), RecordingChallenger forwards hash_kind, the tape walkers count LegacyPow, alloc_zeroed_vec requires an unsafe Zeroable marker, the dead F128 basis verifiers and GKR_PAR_DIAG probe are gone, proof_io's dead Chain/HashKind API removed, the m22 element roundtrip + both m6 anchors are default-run, and cuda-ghash fails fast on CUDA errors with test_blake3_witness_host wired into CI.
  • f780a6f — every doc-rot finding swept (transcript_record re-absorption claims, multi-producer semantics, BitSpreadTable layout, chain remnants, PCS dangling names, tower drift, docs/ redirects); env_free_counts inlined with its dead arms removed.
  • 700cace — the consistency-batch grinding off-by-one: each level now grinds its OWN bits entry (prover+verifier in lockstep, CUDA mirrored, m6 anchors re-pinned with a history entry). DELIBERATE TRANSCRIPT CHANGE — the Blackwell CUDA CI needs one re-run to revalidate the GPU roundtrips.

Full workspace suite green after each commit; clippy clean; x86_64-apple-darwin 0 errors (66 pre-existing warnings, identical on the base). The fixes are merged up the stack (f256-lookahead cc1412a, ag-union 7603139), both green.

🤖 Generated with Claude Code

rothblum and others added 2 commits August 19, 2026 12:48
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
test_ligerito_f256_host carries its own copy of the query-phase transcript
walk; 700cace fixed ligerito_f256.cuh (the GPU test went green on CI) but
missed this twin, which still ground level l's consistency alpha under
xbits[l+1] — the Blackwell run-tests leg failed at "open[2] row word 32
differs". Same fix: each level grinds its OWN bits; the next_level
parameter is gone. Claim bits stay destination-keyed by design.

Validated locally: the host replay matches the real F256 driver on every
proof field at m=22 and m=23.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants