Skip to content

Commit 7f6b85e

Browse files
diegokingstonColoCarlettiMauroToscano
authored
refactor(stark): unify & clean up the commitment layer (#735)
* refactor(stark): extract Merkle commitment into a commitment module Unifies the two near-identical bit-reversed leaf hashers (per-row + per-row-pair) into one keccak_leaves_bit_reversed_grouped(columns, rows_per_leaf), and the two near-identical commit fns (commit_columns_bit_reversed + commit_composition_polynomial) into commit_bit_reversed(columns, rows_per_leaf), in a new crypto/stark/src/commitment.rs. Removes them from the IsStarkProver trait (they used no self). prover.rs re-exports the named leaf hashers for the math-cuda GPU parity test. Byte-identical (stark 128/128). * perf(stark): row-pair the trace commitment (one Merkle path per query) Every FRI query opens a value and its symmetric counterpart (LDE positions 2*iota, 2*iota+1). The composition-poly commitment already grouped that pair into one leaf; the trace (main/aux/precomputed) committed one row per leaf and opened TWO leaves (proof + proof_sym) per query. Commit the trace with rows_per_leaf=2 too, so each query opens ONE leaf/path; drop the now-redundant proof_sym from PolynomialOpenings (also shrinks the composition opening, which stored the path twice). verify_opening_pair now reconstructs the paired leaf and verifies once (mirrors verify_composition_poly_opening); the dead verify_opening is removed. Halves trace Merkle authentication-path data per query (smaller proofs + less verifier hashing). stark 128/128 (prove+verify). NOTE: proof FORMAT change (not byte-identical). cuda follow-up: the GPU trace leaf+tree builders (gpu_lde::try_expand_leaf_and_tree_batched_keep/_ext3_keep + math-cuda kernels) still build 1-row leaves and must switch to the row-pair pattern (the GPU composition builder already pairs) or cuda proofs will fail verification. * refactor(stark): prover cleanup — par helpers, ROWS_PER_LEAF, error propagation, doc/log fixes * docs(stark): fix commitment.rs leaf-layout docs after trace pairing The trace commitment now uses the row-pair leaf layout (ROWS_PER_LEAF=2), same as composition; rows_per_leaf=1 is only kept for the GPU parity test. Update the module/const/wrapper docs that still described the pre-pairing per-row trace. * refactor(prover): dedup commit pipeline (commit_plain + spill_tree) (B) Extract two helpers on IsStarkProver, collapsing the near-duplicate main-trace and aux-trace commit code: - spill_tree<C>: the identical-except-label disk-spill block, shared by the main / preprocessed-split / aux commit sites (4 call sites). - commit_plain<C>: commit_bit_reversed + spill_tree + TableCommit::plain, shared by the main-trace (non-preprocessed None arm) and aux-trace plain-commit paths. Proof output is byte-identical (stark 128/128, +disk-spill 133/133). The only behavioral delta is in the instruments profiling feature: the aux commit's timing bucket now includes its (tiny) disk spill, matching what the main path already measured. clippy clean on default / disk-spill / instruments; builds on the combined feature set. * fix(prover): row-pair the preprocessed-table commitments (CI fix) The trace Merkle commitment moved to a row-pair leaf layout (ROWS_PER_LEAF=2), but the 5 preprocessed-table commitment computers (bitwise/keccak_rc/page/decode/register) still built a 1-row-per-leaf tree manually, so the prover's row-pair precomputed root no longer matched the computed/hardcoded one -> PrecomputedCommitmentMismatch at prove time. - Route all 5 compute_*commitment fns through the shared stark::commitment::commit_bit_reversed(.., ROWS_PER_LEAF), dropping the manual bit-reverse + columns2rows + 1-row BatchedMerkleTree::build. - Regenerate the hardcoded bitwise/keccak_rc/zero_page static commitments for the row-pair layout (via compute_static_commitments). - cargo fmt (prover.rs + touched tables). Verified: static_commitments drift tests 5/5, stark 128/128, clippy + fmt clean, no PrecomputedCommitmentMismatch. Full ELF prove/verify runs in CI (guest artifacts absent locally). * test(prover): regenerate SUB_DECODE_COMMITMENT_BLOWUP_2 for row-pair layout The compile-time decode-commitment const for sub.elf shifted with the row-pair preprocessed commitment; regenerated via commitment_from_elf (the print_decode_commitment_for_sub regen path). * test(prover): TEMP print actual decode commitment to regenerate const from CI * test(prover): set SUB_DECODE_COMMITMENT_BLOWUP_2 to CI-computed row-pair value Regenerated from CI's sub.elf (local riscv toolchain unavailable); removed the temporary print instrumentation. * mplement changes in GPU * refactor * Fix CUDA LDE clippy lint * fix(cuda): align review cleanup with row-pair commits (#723) * fix(stark): silence dead_code on par_for_each_mut (debug-checks-only after merge) * style(stark): cargo fmt after merge resolution * test(cuda): focused GPU row-pair commitment prove+verify test * fix(cuda): drop stale R2 parts-LDE asserts (#700 fused path), silence GPU column-LDE dead_code The R2 parts-LDE / comp-poly-tree GPU dispatches no longer fire since #699/#700 route degree-3 tables through the 2-part fused coset_lde_full path (no AIR has number_of_parts > 2). try_expand_columns_batched* are debug-checks-only after the #650 row-major LDE became production. * review(stark): address PR #735 review — coverage, dead code, cleanups (#740) Follow-up to the row-pair commitment PR. Excludes the intentional proof-format break (proof_sym removal / leaf-count change), which is by design. Test coverage (in their own files under crypto/stark/src/tests/, per the crate's test structure): - tests/commitment_tests.rs: direct unit tests pinning the row-pair leaf layout (R=1 and R=2) against an independent reference, wrapper agreement, commit-root consistency, and empty-input short-circuit. Previously the leaf layout was only covered transitively via full prove->verify, and GPU parity tests compared against an inline reimpl rather than this module. - tests/row_pair_opening_tests.rs: two negative tests for the row-pair verify_opening_pair — a tampered symmetric trace evaluation and a corrupted Merkle authentication path must both be rejected. Removing proof_sym deleted the old "symmetric opening mismatch" rejection class; these restore it (an impl ignoring evaluations_sym / the auth path would otherwise pass every existing test). Reuses the now pub(crate) make_valid_simple_proof helper. - cuda_path_integration.rs: restore assert!(gpu_comp_poly_tree_calls() > 0). try_build_comp_poly_tree_gpu is dispatched unconditionally (round 2, after the parts-count branch), so it fires for the common number_of_parts == 2 (degree-3) case — it was NOT obsolete. Keep the genuinely-dead parts-LDE assertion dropped. Cleanups: - Delete dead fn commit_plain (zero callers; main/aux commit inline commit_rows_bit_reversed + spill_tree, which are row-major and incompatible with its column-major signature — the dedup never landed). - Delete orphaned pub fn columns2rows (all callers removed by #735). - Add ProvingError::Fft and map FFTError to it instead of WrongParameter (internal FFT failure is not a caller-supplied-parameter error). - Fix stale profiler label commit_composition_poly -> commit_bit_reversed. - Drop the rot-prone "// = 2" comment on the local ROWS_PER_LEAF alias. * review(stark): tidy test layout + remove AGENTS.md (follow-up to #740) (#744) Two items that landed too late for #740: - Move the shared make_valid_simple_proof helper out of small_trace_tests.rs into tests/trace_test_helpers.rs, where the crate keeps shared test helpers (matching how prover_tests sources get_trace_evaluations). row_pair_opening_tests.rs and small_trace_tests.rs now both import it from there instead of one test file reaching sideways into another. - Delete AGENTS.md (added by #735). --------- Co-authored-by: Joaquin Carletti <joaquin.carletti@lambdaclass.com> Co-authored-by: MauroFab <maurotoscano2@gmail.com> Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com>
1 parent 7974e45 commit 7f6b85e

30 files changed

Lines changed: 1082 additions & 616 deletions

crypto/math-cuda/kernels/keccak.cu

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -159,8 +159,8 @@ extern "C" __global__ void keccak256_leaves_base_batched(
159159
uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
160160
if (tid >= num_rows) return;
161161

162-
// Bit-reverse the row index so we read columns at `br` but write the
163-
// hashed leaf at `tid` — matching the CPU `commit_columns_bit_reversed`.
162+
// Bit-reverse the row index so we read columns at `br` but write the hashed
163+
// leaf at `tid` — matching the CPU per-row `commit_bit_reversed(.., 1)`.
164164
uint64_t br = __brevll(tid) >> (64 - log_num_rows);
165165

166166
uint64_t st[25];
@@ -181,6 +181,51 @@ extern "C" __global__ void keccak256_leaves_base_batched(
181181
finalize_keccak256(st, rate_pos, hashed_leaves_out + tid * 32);
182182
}
183183

184+
// ---------------------------------------------------------------------------
185+
// Goldilocks BASE-FIELD row-pair leaf hashing.
186+
//
187+
// Leaf `leaf_idx` hashes TWO consecutive bit-reversed rows
188+
// br_0 = reverse_index(2*leaf_idx), br_1 = reverse_index(2*leaf_idx + 1)
189+
// each written column-by-column in canonical BE (same per-row byte layout as
190+
// `keccak256_leaves_base_batched`), in (br_0 row: col 0..K-1) then (br_1 row:
191+
// col 0..K-1) order. `num_leaves = num_rows / 2`; writes 32 bytes to
192+
// `hashed_leaves_out[leaf_idx * 32 ..]`. Matches the CPU
193+
// `keccak_leaves_row_pair_bit_reversed` (rows_per_leaf = 2) — the base-field
194+
// analog of `keccak_comp_poly_leaves_ext3`.
195+
// ---------------------------------------------------------------------------
196+
extern "C" __global__ void keccak256_leaves_base_row_pair_batched(
197+
const uint64_t *columns_base_ptr,
198+
uint64_t col_stride,
199+
uint64_t num_cols,
200+
uint64_t num_rows,
201+
uint64_t log_num_rows,
202+
uint8_t *hashed_leaves_out) {
203+
uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
204+
uint64_t num_leaves = num_rows >> 1;
205+
if (tid >= num_leaves) return;
206+
207+
uint64_t br_0 = __brevll(2 * tid) >> (64 - log_num_rows);
208+
uint64_t br_1 = __brevll(2 * tid + 1) >> (64 - log_num_rows);
209+
210+
uint64_t st[25];
211+
#pragma unroll
212+
for (int i = 0; i < 25; ++i) st[i] = 0;
213+
214+
uint32_t rate_pos = 0;
215+
// First row (br_0): col 0..K-1.
216+
for (uint64_t c = 0; c < num_cols; ++c) {
217+
uint64_t v = columns_base_ptr[c * col_stride + br_0];
218+
absorb_lane(st, rate_pos, bswap64(goldilocks::canonical(v)));
219+
}
220+
// Second row (br_1): col 0..K-1.
221+
for (uint64_t c = 0; c < num_cols; ++c) {
222+
uint64_t v = columns_base_ptr[c * col_stride + br_1];
223+
absorb_lane(st, rate_pos, bswap64(goldilocks::canonical(v)));
224+
}
225+
226+
finalize_keccak256(st, rate_pos, hashed_leaves_out + tid * 32);
227+
}
228+
184229
// ---------------------------------------------------------------------------
185230
// Goldilocks EXT3 leaf hashing (3 base-field components per ext3 element).
186231
//
@@ -349,35 +394,49 @@ extern "C" __global__ void keccak_merkle_level(
349394
}
350395

351396
// ---------------------------------------------------------------------------
352-
// Row-major base leaf hashing.
397+
// Row-major ROW-PAIR leaf hashing.
398+
//
399+
// Row-major analog of `keccak256_leaves_base_row_pair_batched` (which reads a
400+
// column-major slab): each leaf hashes TWO consecutive bit-reversed rows.
401+
// Leaf `tid` hashes row `reverse_index(2*tid)` followed by row
402+
// `reverse_index(2*tid + 1)`, each as `m` canonical big-endian lanes read from
403+
// the contiguous row-major buffer (`data + br * m`). `num_leaves = num_rows/2`;
404+
// writes 32 bytes to `hashed_leaves_out[tid*32 ..]`.
353405
//
354-
// Input layout: data[row * m + col] for `num_rows` rows and `m` columns.
355-
// For leaf `tid`, reads the bit-reversed row `br(tid)` — a contiguous slice
356-
// of `m` elements starting at data[br * m]. Coalesced when multiple threads
357-
// in the same warp process consecutive `tid` values (they read non-overlapping
358-
// rows, each a contiguous block of m u64s in order).
406+
// `m` is the row stride in u64s: base trace = num columns; ext3 trace = 3 *
407+
// num columns (an ext3 element's components c0,c1,c2 are consecutive, matching
408+
// the CPU `write_bytes_be`). Byte layout therefore equals the CPU
409+
// `commit_bit_reversed(.., ROWS_PER_LEAF=2)` and the verifier's
410+
// `verify_opening_pair` (queried row ‖ its symmetric counterpart, one leaf).
359411
// ---------------------------------------------------------------------------
360-
extern "C" __global__ void keccak256_leaves_base_row_major(
412+
extern "C" __global__ void keccak256_leaves_base_row_major_row_pair(
361413
const uint64_t *data,
362414
uint64_t m,
363415
uint64_t num_rows,
364416
uint64_t log_num_rows,
365417
uint8_t *hashed_leaves_out)
366418
{
367419
uint64_t tid = (uint64_t)blockIdx.x * blockDim.x + threadIdx.x;
368-
if (tid >= num_rows) return;
369-
uint64_t br = __brevll(tid) >> (64 - log_num_rows);
370-
const uint64_t *row = data + br * m;
420+
uint64_t num_leaves = num_rows >> 1;
421+
if (tid >= num_leaves) return;
422+
423+
uint64_t br_0 = __brevll(2 * tid) >> (64 - log_num_rows);
424+
uint64_t br_1 = __brevll(2 * tid + 1) >> (64 - log_num_rows);
425+
const uint64_t *row_0 = data + br_0 * m;
426+
const uint64_t *row_1 = data + br_1 * m;
371427

372428
uint64_t st[25];
373429
#pragma unroll
374430
for (int i = 0; i < 25; ++i) st[i] = 0;
375431

376432
uint32_t rate_pos = 0;
433+
// First row (br_0): cols 0..m-1.
377434
for (uint64_t c = 0; c < m; ++c) {
378-
uint64_t canon = goldilocks::canonical(row[c]);
379-
uint64_t lane = bswap64(canon);
380-
absorb_lane(st, rate_pos, lane);
435+
absorb_lane(st, rate_pos, bswap64(goldilocks::canonical(row_0[c])));
436+
}
437+
// Second row (br_1): cols 0..m-1.
438+
for (uint64_t c = 0; c < m; ++c) {
439+
absorb_lane(st, rate_pos, bswap64(goldilocks::canonical(row_1[c])));
381440
}
382441
finalize_keccak256(st, rate_pos, hashed_leaves_out + tid * 32);
383442
}

crypto/math-cuda/src/device.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,8 +147,9 @@ pub struct Backend {
147147
pub matrix_transpose_strided: CudaFunction,
148148

149149
// keccak.ptx
150-
pub keccak256_leaves_base_row_major: CudaFunction,
150+
pub keccak256_leaves_base_row_major_row_pair: CudaFunction,
151151
pub keccak256_leaves_base_batched: CudaFunction,
152+
pub keccak256_leaves_base_row_pair_batched: CudaFunction,
152153
pub keccak256_leaves_ext3_batched: CudaFunction,
153154
pub keccak_comp_poly_leaves_ext3: CudaFunction,
154155
pub keccak_fri_leaves_ext3: CudaFunction,
@@ -247,9 +248,11 @@ impl Backend {
247248
ntt_dit_level_row_major: ntt.load_function("ntt_dit_level_row_major")?,
248249
pointwise_mul_row_major: ntt.load_function("pointwise_mul_row_major")?,
249250
matrix_transpose_strided: ntt.load_function("matrix_transpose_strided")?,
250-
keccak256_leaves_base_row_major: keccak
251-
.load_function("keccak256_leaves_base_row_major")?,
251+
keccak256_leaves_base_row_major_row_pair: keccak
252+
.load_function("keccak256_leaves_base_row_major_row_pair")?,
252253
keccak256_leaves_base_batched: keccak.load_function("keccak256_leaves_base_batched")?,
254+
keccak256_leaves_base_row_pair_batched: keccak
255+
.load_function("keccak256_leaves_base_row_pair_batched")?,
253256
keccak256_leaves_ext3_batched: keccak.load_function("keccak256_leaves_ext3_batched")?,
254257
keccak_comp_poly_leaves_ext3: keccak.load_function("keccak_comp_poly_leaves_ext3")?,
255258
keccak_fri_leaves_ext3: keccak.load_function("keccak_fri_leaves_ext3")?,

0 commit comments

Comments
 (0)