@@ -1085,10 +1085,18 @@ pub fn gkr_verify<E: IsField>(
10851085
10861086 if round_polys. is_empty ( ) {
10871087 // Trivial layer (0 variables in parent): no sumcheck rounds.
1088- // The prover just provides child_claims directly.
1089- // No gate check here --- soundness is enforced by later layers' sumchecks.
1090- // (The verifier's combined_claim may differ from the prover's by a
1091- // scaling factor at the first trivial layer, so we skip the check.)
1088+ // Gate check: verify that n_claim * (dl·dr) = nl·dr + nr·dl.
1089+ //
1090+ // The verifier works in normalized form: n_claim = root_n/root_d, d_claim = 1.
1091+ // The prover's gate equation root_n + λ·root_d = nl·dr + nr·dl + λ·dl·dr,
1092+ // divided by root_d (= dl·dr), becomes n_claim·(dl·dr) = nl·dr + nr·dl
1093+ // (the λ terms cancel). This binds claimed_sum to the actual tree structure.
1094+ let [ ref nl, ref nr, ref dl, ref dr] = layer_proof. child_claims ;
1095+ let lhs = & n_claim * & ( dl * dr) ;
1096+ let rhs = & ( nl * dr) + & ( nr * dl) ;
1097+ if lhs != rhs {
1098+ return Err ( GkrError :: GateCheckFailed { layer : layer_idx } ) ;
1099+ }
10921100 } else {
10931101 // Non-trivial layer: verify sumcheck inline.
10941102 let num_rounds = round_polys. len ( ) ;
@@ -1985,7 +1993,33 @@ pub fn gkr_verify_batch<E: IsField>(
19851993 }
19861994
19871995 if round_polys. is_empty ( ) {
1988- // Trivial layer: no sumcheck needed
1996+ // Trivial layer: no sumcheck rounds.
1997+ // Gate check: verify that the alpha-batched combined claims match the
1998+ // alpha-batched gate evaluations (gate_i = nl·dr + nr·dl + λ·dl·dr, scaled
1999+ // by 2^n_unused_i to match combined_claims).
2000+ {
2001+ let mut actual_sum = FieldElement :: < E > :: zero ( ) ;
2002+ let mut expected_sum = FieldElement :: < E > :: zero ( ) ;
2003+ let mut alpha_pow = FieldElement :: < E > :: one ( ) ;
2004+ for ( idx, & i) in active_instances. iter ( ) . enumerate ( ) {
2005+ let [ ref nl, ref nr, ref dl, ref dr] =
2006+ layer_proof. child_claims_by_instance [ idx] ;
2007+ let gate = & ( & ( nl * dr) + & ( nr * dl) ) + & ( & lambda * & ( dl * dr) ) ;
2008+ let n_unused = max_layers - n_layers_by_instance[ i] ;
2009+ let gate_scaled = if n_unused > 0 {
2010+ & gate * & FieldElement :: < E > :: from ( 1u64 << n_unused)
2011+ } else {
2012+ gate
2013+ } ;
2014+ actual_sum = & actual_sum + & ( & alpha_pow * & combined_claims[ idx] ) ;
2015+ expected_sum = & expected_sum + & ( & alpha_pow * & gate_scaled) ;
2016+ alpha_pow = & alpha_pow * & sumcheck_alpha;
2017+ }
2018+ if actual_sum != expected_sum {
2019+ return Err ( GkrError :: GateCheckFailed { layer : layer_idx } ) ;
2020+ }
2021+ }
2022+
19892023 // Append child claims and sample eta (same as prover)
19902024 for claims in & layer_proof. child_claims_by_instance {
19912025 for claim in claims {
0 commit comments