Skip to content
This repository was archived by the owner on Sep 12, 2026. It is now read-only.

Commit c53a22e

Browse files
TomWambsgansclaude
andcommitted
Send ring-switch s_hat_v on the stream, drop RingSwitchProof
The ring-switch message was the last plain scalar riding a struct: `RingSwitchProof` wrapped 64 E values that `observe_ext_slice` bound and the verifier read back out of `BatchOpeningProof`. `add_scalars` and `next_scalars` do both jobs in one call, so the struct is deleted and `BatchOpeningProof` is now just the WHIR Merkle data. `omit_replayed_ab` goes with it. It existed because the prover put AB's s_hat_v in the struct and then removed it after checking it matched lincheck's z_partial. On the stream a prebound claim simply sends nothing, so there is nothing to take back. The verifier's shape validation collapses too: reading exactly 64 words is the width check, and a short stream is the new `VerifyError:: Truncated` instead of a length test on attacker-supplied data. The guest reads the 64 words with the same `fs_next` walk it uses for z_partial, so read and bind stay one act and the separate obs loop and the `rs_shatv` hint buffer both disappear. The sponge sequence is unchanged: the same 64 values are bound at the same point, so every challenge is identical. Only the stream grew by 64 words and the struct shrank by the same, and `flock_stream_end` still marks where flock's reduction ends. testall, clippyall, docall and ruff clean. recursion_2to1{,_mixed}, recursion_generic_many and recursion_soundness_binds pass with the edited guest; the python verifier's pinning test passes. Guest cycles 727,750 and proof size 223.8 KiB, both unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent 86a75ba commit c53a22e

9 files changed

Lines changed: 153 additions & 259 deletions

File tree

crates/flock/tests/blake3_batch.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -95,9 +95,7 @@ fn verifier_ring(ab: &ZClaim, c: &ZClaim, ab_s_hat_v: &[F192], qflock_vars: usiz
9595
RingSwitchVerify {
9696
offset: 0,
9797
qflock_vars,
98-
reconstructed: vec![pcs::ring_switch::RingSwitchProof {
99-
s_hat_v: ab_s_hat_v.to_vec(),
100-
}],
98+
reconstructed: vec![ab_s_hat_v.to_vec()],
10199
claims: vec![ring_claim(ab, None, qflock_vars), ring_claim(c, None, qflock_vars)],
102100
}
103101
}
@@ -160,9 +158,7 @@ fn blake3_batch_prove_verify() {
160158
let reduced = setup.prove_reduction_precomputed(&z_packed, &a_packed, &b_packed, &z_lincheck, &mut ps);
161159
drop((z_packed, a_packed, b_packed, z_lincheck));
162160
let ring = prover_ring(&reduced, mu);
163-
let mut opening = open_batch_mixed_whir_stacked(&mut ps, &q_flock, &prover_data, &prover_config, &[], &ring);
164-
assert_eq!(opening.ring_switches[0].s_hat_v, reduced.ab.s_hat_v.as_deref().unwrap());
165-
opening.ring_switches.remove(0);
161+
let opening = open_batch_mixed_whir_stacked(&mut ps, &q_flock, &prover_data, &prover_config, &[], &ring);
166162
let open_s = t.elapsed().as_secs_f64();
167163
let prove_s = t_prove.elapsed().as_secs_f64();
168164

crates/lean_vm/src/blake3_flock.rs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -400,29 +400,11 @@ pub fn ring_switch_verify(
400400
crate::pcs::RingSwitchVerify {
401401
offset,
402402
qflock_vars,
403-
reconstructed: vec![::pcs::ring_switch::RingSwitchProof {
404-
s_hat_v: ab_s_hat_v.to_vec(),
405-
}],
403+
reconstructed: vec![ab_s_hat_v.to_vec()],
406404
claims: vec![ring_claim(&ab, None, qflock_vars), ring_claim(&c, None, qflock_vars)],
407405
}
408406
}
409407

410-
/// Drop the AB ring-switch message from the opening after checking that it is
411-
/// the same `z_partial` already sent by lincheck. Verifiers reconstruct and
412-
/// use it as an already-bound ring-switch message.
413-
pub fn omit_replayed_ab(opening: &mut crate::pcs::BatchOpeningProof, ab_s_hat_v: &[F192]) {
414-
assert_eq!(
415-
opening.ring_switches.len(),
416-
2,
417-
"Flock opening must carry AB and C ring-switch messages"
418-
);
419-
assert_eq!(
420-
opening.ring_switches[0].s_hat_v, ab_s_hat_v,
421-
"lincheck and AB ring-switch values drifted"
422-
);
423-
opening.ring_switches.remove(0);
424-
}
425-
426408
#[cfg(test)]
427409
mod tests {
428410
use super::*;
@@ -585,20 +567,7 @@ mod tests {
585567
let committed = crate::pcs::commit(&mut ps, &stacked.q, crate::pcs::LOG_INV_RATE);
586568
let (_z, reduced) = prove_reduction(&blocks, &mut ps);
587569
let ring = ring_switch_open(blocks.len(), offset, &reduced);
588-
let mut open = crate::pcs::open(&mut ps, &committed, &stacked.q, &points, &ring);
589-
omit_replayed_ab(
590-
&mut open,
591-
reduced
592-
.ab
593-
.s_hat_v
594-
.as_deref()
595-
.expect("Flock AB ring-switch values are captured"),
596-
);
597-
assert_eq!(
598-
open.ring_switches.len(),
599-
1,
600-
"only C's ring-switch message travels in the opening"
601-
);
570+
let open = crate::pcs::open(&mut ps, &committed, &stacked.q, &points, &ring);
602571
ps.hint_opening(open);
603572
let bundle = ps.into_proof();
604573

crates/lean_vm/src/cpu/mod.rs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -617,15 +617,7 @@ pub fn prove(program: &Program, public_input: [F192; 2], log_inv_rate: usize) ->
617617
drop(flock_reduction);
618618
let offset = w.layout.placements[QFLOCK].offset;
619619
let ring = crate::blake3_flock::ring_switch_open(n_blocks, offset, &reduced);
620-
let mut mixed_open = crate::stage!("PCS open", || { pcs::open(&mut ps, &committed, &w.q, &slots, &ring) });
621-
crate::blake3_flock::omit_replayed_ab(
622-
&mut mixed_open,
623-
reduced
624-
.ab
625-
.s_hat_v
626-
.as_deref()
627-
.expect("Flock AB ring-switch values are captured"),
628-
);
620+
let mixed_open = crate::stage!("PCS open", || { pcs::open(&mut ps, &committed, &w.q, &slots, &ring) });
629621
// flock's scalar sub-proof already rode the shared stream (add_scalar at its
630622
// protocol points); only the Merkle-bearing stacked opening needs the hint
631623
// channel.

crates/lean_vm/src/leaf.rs

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -462,25 +462,15 @@ fn decompose_prove(
462462

463463
// Pass 2: replay in the original order; duplicates reuse the recorded claim.
464464
let mut fresh_iter = jobs.iter().zip(vals.iter());
465-
decompose_formula(
466-
blocks,
467-
lay,
468-
zeta,
469-
w,
470-
gamma,
471-
owners,
472-
forms,
473-
claims,
474-
|col, zeta_lo| {
475-
let (&(jc, jk), &v) = fresh_iter
476-
.next()
477-
.expect("job enumeration matches decompose_formula's col_val order");
478-
debug_assert_eq!((jc, jk), (col, zeta_lo.len()), "job/coord order drift");
479-
debug_assert_eq!(v, mle_eval(cols[col], zeta_lo), "job/coord order drift");
480-
ps.add_scalar(v);
481-
Ok(v)
482-
},
483-
)
465+
decompose_formula(blocks, lay, zeta, w, gamma, owners, forms, claims, |col, zeta_lo| {
466+
let (&(jc, jk), &v) = fresh_iter
467+
.next()
468+
.expect("job enumeration matches decompose_formula's col_val order");
469+
debug_assert_eq!((jc, jk), (col, zeta_lo.len()), "job/coord order drift");
470+
debug_assert_eq!(v, mle_eval(cols[col], zeta_lo), "job/coord order drift");
471+
ps.add_scalar(v);
472+
Ok(v)
473+
})
484474
.expect("prover decomposition is infallible")
485475
}
486476

@@ -687,7 +677,13 @@ pub fn prove_balance(
687677
// Framework blocks keep their per-column claims (deduped: push/pull share ζ);
688678
// every table block becomes a form for the zerocheck instead.
689679
let mut claims: Vec<ColumnClaim> = Vec::new();
690-
let sides = sides([push, pull, count], [&push_lay, &pull_lay, &count_lay], &w, &count_w, gamma);
680+
let sides = sides(
681+
[push, pull, count],
682+
[&push_lay, &pull_lay, &count_lay],
683+
&w,
684+
&count_w,
685+
gamma,
686+
);
691687
// Each table's columns at ζ[..τ], computed once and shared by the three sides
692688
// (a form's linear part factors through them). Nothing here travels, neither the
693689
// evaluations nor any total: the verifier derives each side's table share as `Ṽ₀(ζ)` less the
@@ -840,7 +836,13 @@ pub fn verify_balance(
840836
// here, the batch's target being what pins it, so no table column is opened at ζ.
841837
let mut claims: Vec<ColumnClaim> = Vec::new();
842838
let mut forms = std::array::from_fn(|_| tables.iter().map(|&(_, n)| BusForm::new(n)).collect::<Vec<_>>());
843-
let sides = sides([push, pull, count], [&push_lay, &pull_lay, &count_lay], &w, &count_w, gamma);
839+
let sides = sides(
840+
[push, pull, count],
841+
[&push_lay, &pull_lay, &count_lay],
842+
&w,
843+
&count_w,
844+
gamma,
845+
);
844846
let mut totals = [F192::ZERO; 3];
845847
for (s, &(blocks, lay, a, g)) in sides.iter().enumerate() {
846848
let framework = decompose_verify(

0 commit comments

Comments
 (0)