Land recursion_circuit: F256 Ligerito, recursion tower, Option-F encoders - #26
Conversation
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>
…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>
…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>
|
@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
left a comment
There was a problem hiding this comment.
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:1715 — prove_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:761 — RecordingChallenger 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:6566 — RealTape::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:247 — alloc_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:4817 — recursive_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:32 — gather_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:333 — TEST_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:9975 — build_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:2305 — each_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:436 — BitSpreadTable'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:569 — embedded_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:1571 — fold_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
…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>
|
All review findings are now fixed on this branch, in three commits:
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 🤖 Generated with Claude Code |
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>
Syncs
recursion_circuitintomain. The branch containsmain'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
field = "f256"enforced), 100/120-bit profile families (Fast/Slim/Secure + 100/128 variants).src/tower.rswithTowerConfig::{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).flock_chainCLI, and padded-commit batch paths are deleted.SkipPointthreads through the branch grinding with the φ₈ arm keeping the fused PoW+squeeze (byte pins held).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).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 thegpu_roundtrip_m*tests pass.🤖 Generated with Claude Code