@@ -252,6 +252,7 @@ where
252252
253253 let mut small_value_sumcheck =
254254 SmallValueSumCheck :: < E :: Scalar , SPARTAN_T_DEGREE > :: from_accumulators ( accumulators) ;
255+ let mut transition_round = l0;
255256
256257 // ===== Small-Value Rounds (0 to ℓ₀-1) =====
257258 // During these rounds, we use the precomputed accumulators. Polynomials are NOT bound
@@ -268,9 +269,13 @@ where
268269 // 2. Get eq factor values ℓ_i(0), ℓ_i(1), ℓ_i(∞)
269270 let li = small_value_sumcheck. eq_round_values ( taus[ round] ) ;
270271
271- // 3. Derive t(1) from sumcheck constraint: s(0) + s(1) = claim
272- let t1 = derive_t1 ( li. at_zero ( ) , li. at_one ( ) , claim_per_round, t0)
273- . ok_or ( SpartanError :: InvalidSumcheckProof ) ?;
272+ // 3. Derive t(1) from the sumcheck constraint. If ℓ_i(1)=0, the optimized
273+ // path cannot recover t_i(1), so we fall back to the standard prover from
274+ // this round onward.
275+ let Some ( t1) = derive_t1 ( li. at_zero ( ) , li. at_one ( ) , claim_per_round, t0) else {
276+ transition_round = round;
277+ break ;
278+ } ;
274279
275280 // 4. Build round polynomial s_i(X) = ℓ_i(X) · t_i(X)
276281 let poly = build_univariate_round_polynomial ( & li, t0, t1, t_inf) ;
@@ -296,35 +301,47 @@ where
296301
297302 // ===== Transition Phase =====
298303 // Create bound field-element polynomials via batched eq-weighted small-value accumulation.
299- // Binds all l0 variables in a single pass using field×int unreduced accumulation.
304+ // Binds all completed small-value rounds in a single pass using field×int
305+ // unreduced accumulation.
300306 let ( _bind_span, bind_t) = start_span ! ( "bind_poly_vars_transition" ) ;
301307 let ( mut poly_A, mut poly_B, mut poly_C) =
302- bind_three_polys_batched_small_value ( poly_A_small, poly_B_small, poly_C_small, & r[ ..l0] ) ;
308+ bind_three_polys_batched_small_value (
309+ poly_A_small,
310+ poly_B_small,
311+ poly_C_small,
312+ & r[ ..transition_round] ,
313+ ) ;
303314 info ! (
304315 elapsed_ms = %bind_t. elapsed( ) . as_millis( ) ,
305316 "bind_poly_vars_transition"
306317 ) ;
307318
308319 // ===== Remaining Rounds (ℓ₀ to ℓ-1) =====
309- // Pop the top layer from e_in_pyramid. The top layer was used by the accumulator;
310- // EqSumCheckInstance needs the remaining layers (without the first suffix tau τ[l₀]).
311- // This matches the Nova optimization where τ[l₀] is tracked in eval_eq_left.
312- e_in_pyramid. pop ( ) ;
313-
314- // Create EqSumCheckInstance from precomputed pyramids, avoiding redundant eq computation.
315- // The accumulated eq factor from small-value rounds is passed directly.
316- let mut eq_instance = eq_sumcheck:: EqSumCheckInstance :: < E > :: from_pyramids (
317- e_in_pyramid,
318- e_xout_pyramid,
319- & taus[ l0..] ,
320- small_value_sumcheck. eq_alpha ( ) ,
321- ) ;
320+ let mut eq_instance = if transition_round == l0 {
321+ // Pop the top layer from e_in_pyramid. The top layer was used by the accumulator;
322+ // EqSumCheckInstance needs the remaining layers (without the first suffix tau τ[l₀]).
323+ // This matches the Nova optimization where τ[l₀] is tracked in eval_eq_left.
324+ e_in_pyramid. pop ( ) ;
325+
326+ // Reuse the precomputed eq pyramids when all planned small-value rounds succeeded.
327+ eq_sumcheck:: EqSumCheckInstance :: < E > :: from_pyramids (
328+ e_in_pyramid,
329+ e_xout_pyramid,
330+ & taus[ l0..] ,
331+ small_value_sumcheck. eq_alpha ( ) ,
332+ )
333+ } else {
334+ eq_sumcheck:: EqSumCheckInstance :: < E > :: new_with_eval_eq_left (
335+ & taus[ transition_round..] ,
336+ small_value_sumcheck. eq_alpha ( ) ,
337+ )
338+ } ;
322339
323340 // Continue with the remaining rounds using the eq instance built from pyramids.
324341 // The eq_instance was created with taus[l0..], so its internal round counter tracks
325342 // local rounds. We pass global round index for the round_idx parameter since
326343 // eval_one_case_cubic_three_inputs uses it for a round-0 optimization.
327- for round in l0 ..num_rounds {
344+ for round in transition_round ..num_rounds {
328345 let ( _round_span, round_t) = start_span ! ( "sumcheck_round" , round = round) ;
329346
330347 let poly = {
@@ -533,14 +550,26 @@ mod tests {
533550 . map ( |( & a, & b) | a * b)
534551 . collect ( ) ;
535552
536- // Convert to field elements
553+ // Random taus
554+ let taus: Vec < F > = ( 0 ..num_vars) . map ( |_| F :: random ( & mut rng) ) . collect ( ) ;
555+
556+ run_equivalence_test_with_taus :: < SV > ( taus, az_small, bz_small, cz_small) ;
557+ }
558+
559+ fn run_equivalence_test_with_taus < SV > (
560+ taus : Vec < F > ,
561+ az_small : Vec < SV > ,
562+ bz_small : Vec < SV > ,
563+ cz_small : Vec < SV > ,
564+ ) where
565+ SV : SmallValue + Mul < Output = SV > ,
566+ F : SmallValueEngine < SV > ,
567+ {
568+ let num_vars = taus. len ( ) ;
537569 let az_vals: Vec < F > = az_small. iter ( ) . map ( |& v| F :: small_to_field ( v) ) . collect ( ) ;
538570 let bz_vals: Vec < F > = bz_small. iter ( ) . map ( |& v| F :: small_to_field ( v) ) . collect ( ) ;
539571 let cz_vals: Vec < F > = cz_small. iter ( ) . map ( |& v| F :: small_to_field ( v) ) . collect ( ) ;
540572
541- // Random taus
542- let taus: Vec < F > = ( 0 ..num_vars) . map ( |_| F :: random ( & mut rng) ) . collect ( ) ;
543-
544573 // Claim = 0 for satisfying witness (Az·Bz = Cz on {0,1}^n)
545574 let claim: F = F :: ZERO ;
546575
@@ -626,6 +655,27 @@ mod tests {
626655 run_equivalence_test :: < i64 > ( num_vars) ;
627656 }
628657 }
658+
659+ #[ test]
660+ fn test_sumcheck_equivalence_when_first_tau_is_zero ( ) {
661+ const NUM_VARS : usize = 6 ;
662+ const SEED : u64 = 0xDEADBEEF ;
663+ let mut rng = StdRng :: seed_from_u64 ( SEED ) ;
664+ let n = 1usize << NUM_VARS ;
665+
666+ let az_small: Vec < i32 > = ( 0 ..n) . map ( |_| rng. gen_range ( -100i32 ..=100i32 ) ) . collect ( ) ;
667+ let bz_small: Vec < i32 > = ( 0 ..n) . map ( |_| rng. gen_range ( -100i32 ..=100i32 ) ) . collect ( ) ;
668+ let cz_small: Vec < i32 > = az_small
669+ . iter ( )
670+ . zip ( & bz_small)
671+ . map ( |( & a, & b) | a * b)
672+ . collect ( ) ;
673+
674+ let mut taus: Vec < F > = ( 0 ..NUM_VARS ) . map ( |_| F :: random ( & mut rng) ) . collect ( ) ;
675+ taus[ 0 ] = F :: ZERO ;
676+
677+ run_equivalence_test_with_taus :: < i32 > ( taus, az_small, bz_small, cz_small) ;
678+ }
629679}
630680
631681#[ cfg( test) ]
0 commit comments