@@ -389,12 +389,15 @@ fn evaluate_mle<E: IsField>(
389389pub fn gkr_prove < E : IsField > (
390390 tree : & [ SummationLayer < E > ] ,
391391 transcript : & mut impl IsTranscript < E > ,
392- ) -> (
393- GkrProof < E > ,
394- Vec < FieldElement < E > > ,
395- FieldElement < E > ,
396- FieldElement < E > ,
397- ) {
392+ ) -> Result <
393+ (
394+ GkrProof < E > ,
395+ Vec < FieldElement < E > > ,
396+ FieldElement < E > ,
397+ FieldElement < E > ,
398+ ) ,
399+ GkrError ,
400+ > {
398401 assert ! ( !tree. is_empty( ) , "tree must have at least one layer" ) ;
399402
400403 let num_layers = tree. len ( ) ; // layers 0..num_layers-1, root is at num_layers-1
@@ -408,24 +411,26 @@ pub fn gkr_prove<E: IsField>(
408411 let root_n = & root. numerators [ 0 ] ;
409412 let root_d = & root. denominators [ 0 ] ;
410413
411- // Compute claimed_sum = root_n / root_d
412- let root_d_inv = root_d. inv ( ) . expect ( "root denominator must be nonzero" ) ;
414+ // Compute claimed_sum = root_n / root_d. root_d = 0 means the LogUp challenge z
415+ // collided with a fingerprint denominator (probability ~1/p ≈ 2^{-64}); return
416+ // an error rather than panicking so the caller can retry with a fresh transcript.
417+ let root_d_inv = root_d. inv ( ) . map_err ( |_| GkrError :: ZeroDenominator ) ?;
413418 let claimed_sum = root_n * & root_d_inv;
414419
415420 // Append the claimed sum to the transcript
416421 transcript. append_field_element ( & claimed_sum) ;
417422
418423 // If the tree has only 1 layer (just leaves = root), no reductions needed
419424 if num_layers == 1 {
420- return (
425+ return Ok ( (
421426 GkrProof {
422427 claimed_sum,
423428 layer_proofs : vec ! [ ] ,
424429 } ,
425430 vec ! [ ] ,
426431 root_n. clone ( ) ,
427432 root_d. clone ( ) ,
428- ) ;
433+ ) ) ;
429434 }
430435
431436 let mut layer_proofs = Vec :: with_capacity ( num_layers - 1 ) ;
@@ -992,18 +997,18 @@ pub fn gkr_prove<E: IsField>(
992997 }
993998 }
994999
995- (
1000+ Ok ( (
9961001 GkrProof {
9971002 claimed_sum,
9981003 layer_proofs,
9991004 } ,
10001005 current_point,
10011006 n_claim,
10021007 d_claim,
1003- )
1008+ ) )
10041009}
10051010
1006- /// Errors that can occur during GKR verification.
1011+ /// Errors that can occur during GKR proving or verification.
10071012#[ derive( Debug , Clone ) ]
10081013pub enum GkrError {
10091014 /// The summation tree structure is invalid.
@@ -1015,6 +1020,9 @@ pub enum GkrError {
10151020 /// The claimed sum does not match (unused in the verifier itself,
10161021 /// but available for callers that compare the claimed sum to an external value).
10171022 ClaimedSumMismatch ,
1023+ /// The root denominator is zero (LogUp challenge z collided with a fingerprint
1024+ /// denominator). Probability ~1/p ≈ 2^{-64}; prover should sample a new transcript.
1025+ ZeroDenominator ,
10181026}
10191027
10201028impl fmt:: Display for GkrError {
@@ -1032,6 +1040,12 @@ impl fmt::Display for GkrError {
10321040 GkrError :: ClaimedSumMismatch => {
10331041 write ! ( f, "claimed sum mismatch" )
10341042 }
1043+ GkrError :: ZeroDenominator => {
1044+ write ! (
1045+ f,
1046+ "GKR root denominator is zero (LogUp challenge z collided with a fingerprint denominator; probability ~1/p ≈ 2^{{-64}})"
1047+ )
1048+ }
10351049 }
10361050 }
10371051}
@@ -1069,7 +1083,7 @@ pub fn gkr_verify<E: IsField>(
10691083 // Step 2: Initialize claims.
10701084 // The verifier sets n_claim = claimed_sum, d_claim = 1.
10711085 // This represents the same rational value as root_n/root_d.
1072- // Soundness of the first (trivial) layer is enforced by later sumcheck layers .
1086+ // Trivial layers (0 sumcheck rounds) are gate-checked directly in the loop below .
10731087 let mut n_claim = proof. claimed_sum . clone ( ) ;
10741088 let mut d_claim = FieldElement :: < E > :: one ( ) ;
10751089 let mut current_point: Vec < FieldElement < E > > = vec ! [ ] ;
@@ -2400,7 +2414,8 @@ mod tests {
24002414 let tree = build_summation_tree ( nums, dens) ;
24012415
24022416 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2403- let ( proof, final_point, final_n_claim, final_d_claim) = gkr_prove ( & tree, & mut transcript) ;
2417+ let ( proof, final_point, final_n_claim, final_d_claim) =
2418+ gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
24042419
24052420 // claimed_sum = 68/55
24062421 let expected_sum = & FE :: from ( 68u64 ) * & FE :: from ( 55u64 ) . inv ( ) . unwrap ( ) ;
@@ -2451,7 +2466,8 @@ mod tests {
24512466 let tree = build_summation_tree ( nums. clone ( ) , dens. clone ( ) ) ;
24522467
24532468 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2454- let ( proof, final_point, final_n_claim, final_d_claim) = gkr_prove ( & tree, & mut transcript) ;
2469+ let ( proof, final_point, final_n_claim, final_d_claim) =
2470+ gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
24552471
24562472 // claimed_sum = root_n / root_d = 1136 / 384
24572473 let expected_sum = & FE :: from ( 1136u64 ) * & FE :: from ( 384u64 ) . inv ( ) . unwrap ( ) ;
@@ -2505,7 +2521,7 @@ mod tests {
25052521 let expected_sum = root_n * & root_d. inv ( ) . unwrap ( ) ;
25062522
25072523 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2508- let ( proof, _, _, _) = gkr_prove ( & tree, & mut transcript) ;
2524+ let ( proof, _, _, _) = gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
25092525
25102526 assert_eq ! ( proof. claimed_sum, expected_sum) ;
25112527 }
@@ -2520,7 +2536,8 @@ mod tests {
25202536 let tree = build_summation_tree ( nums. clone ( ) , dens. clone ( ) ) ;
25212537
25222538 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2523- let ( proof, final_point, final_n_claim, final_d_claim) = gkr_prove ( & tree, & mut transcript) ;
2539+ let ( proof, final_point, final_n_claim, final_d_claim) =
2540+ gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
25242541
25252542 // Should have 3 layer proofs
25262543 assert_eq ! ( proof. layer_proofs. len( ) , 3 ) ;
@@ -2553,7 +2570,8 @@ mod tests {
25532570 let tree = build_summation_tree ( nums. clone ( ) , dens. clone ( ) ) ;
25542571
25552572 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAB ] ) ;
2556- let ( proof, final_point, final_n_claim, final_d_claim) = gkr_prove ( & tree, & mut transcript) ;
2573+ let ( proof, final_point, final_n_claim, final_d_claim) =
2574+ gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
25572575
25582576 // Should have 4 layer proofs
25592577 assert_eq ! ( proof. layer_proofs. len( ) , 4 ) ;
@@ -2586,10 +2604,10 @@ mod tests {
25862604 let tree = build_summation_tree ( nums, dens) ;
25872605
25882606 let mut t1 = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0x42 ] ) ;
2589- let ( proof1, point1, n1, d1) = gkr_prove ( & tree, & mut t1) ;
2607+ let ( proof1, point1, n1, d1) = gkr_prove ( & tree, & mut t1) . unwrap ( ) ;
25902608
25912609 let mut t2 = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0x42 ] ) ;
2592- let ( proof2, point2, n2, d2) = gkr_prove ( & tree, & mut t2) ;
2610+ let ( proof2, point2, n2, d2) = gkr_prove ( & tree, & mut t2) . unwrap ( ) ;
25932611
25942612 assert_eq ! ( proof1. claimed_sum, proof2. claimed_sum) ;
25952613 assert_eq ! ( point1, point2) ;
@@ -2635,7 +2653,7 @@ mod tests {
26352653 // Re-run the protocol manually to extract the combined_claim at each layer
26362654 // and verify the sumcheck round poly sum
26372655 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2638- let ( proof, _, _, _) = gkr_prove ( & tree, & mut transcript) ;
2656+ let ( proof, _, _, _) = gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
26392657
26402658 // Replay transcript to get the same challenges
26412659 let mut replay = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
@@ -2700,7 +2718,8 @@ mod tests {
27002718 let tree = build_summation_tree ( nums, dens) ;
27012719
27022720 let mut transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ ] ) ;
2703- let ( proof, final_point, final_n_claim, final_d_claim) = gkr_prove ( & tree, & mut transcript) ;
2721+ let ( proof, final_point, final_n_claim, final_d_claim) =
2722+ gkr_prove ( & tree, & mut transcript) . unwrap ( ) ;
27042723
27052724 assert_eq ! ( proof. claimed_sum, FE :: from( 6u64 ) ) ; // 42/7 = 6
27062725 assert_eq ! ( proof. layer_proofs. len( ) , 0 ) ;
@@ -2732,7 +2751,8 @@ mod tests {
27322751
27332752 // Prove
27342753 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
2735- let ( proof, prover_point, prover_n, prover_d) = gkr_prove ( & tree, & mut prover_transcript) ;
2754+ let ( proof, prover_point, prover_n, prover_d) =
2755+ gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
27362756
27372757 // Verify with a fresh transcript (same seed)
27382758 let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
@@ -2772,7 +2792,8 @@ mod tests {
27722792
27732793 // Prove
27742794 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
2775- let ( proof, prover_point, prover_n, prover_d) = gkr_prove ( & tree, & mut prover_transcript) ;
2795+ let ( proof, prover_point, prover_n, prover_d) =
2796+ gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
27762797
27772798 // Verify with a fresh transcript (same seed)
27782799 let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
@@ -2818,7 +2839,7 @@ mod tests {
28182839
28192840 // Prove with correct claimed_sum
28202841 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
2821- let ( mut proof, _, _, _) = gkr_prove ( & tree, & mut prover_transcript) ;
2842+ let ( mut proof, _, _, _) = gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
28222843
28232844 // Tamper with the claimed_sum
28242845 proof. claimed_sum = & proof. claimed_sum + & FE :: one ( ) ;
@@ -2836,6 +2857,52 @@ mod tests {
28362857 ) ;
28372858 }
28382859
2860+ #[ test]
2861+ fn test_gkr_verify_trivial_layer_gate_check_rejected ( ) {
2862+ // A 4-leaf tree produces:
2863+ // layer_proofs[0]: trivial (root→layer1, parent_num_vars=0, round_polys=[])
2864+ // layer_proofs[1]: non-trivial (layer1→leaves, parent_num_vars=1)
2865+ //
2866+ // Tamper with child_claims of the trivial layer_proofs[0] and assert
2867+ // that gkr_verify returns GkrError::GateCheckFailed.
2868+ let nums = vec ! [
2869+ FE :: from( 1u64 ) ,
2870+ FE :: from( 3u64 ) ,
2871+ FE :: from( 5u64 ) ,
2872+ FE :: from( 7u64 ) ,
2873+ ] ;
2874+ let dens = vec ! [
2875+ FE :: from( 2u64 ) ,
2876+ FE :: from( 4u64 ) ,
2877+ FE :: from( 6u64 ) ,
2878+ FE :: from( 8u64 ) ,
2879+ ] ;
2880+ let tree = build_summation_tree ( nums, dens) ;
2881+
2882+ let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xC0 ] ) ;
2883+ let ( mut proof, _, _, _) = gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
2884+
2885+ // layer_proofs[0] is the trivial layer: round_polys must be empty
2886+ assert ! (
2887+ proof. layer_proofs[ 0 ] . sumcheck_proof. round_polys. is_empty( ) ,
2888+ "layer_proofs[0] must be the trivial layer for a 4-leaf tree"
2889+ ) ;
2890+
2891+ // Corrupt nl (child_claims[0]) by adding 1 — this breaks the gate equation
2892+ // n_claim*(dl*dr) = nl*dr + nr*dl without altering the transcript ordering
2893+ proof. layer_proofs [ 0 ] . child_claims [ 0 ] =
2894+ proof. layer_proofs [ 0 ] . child_claims [ 0 ] . clone ( ) + FE :: one ( ) ;
2895+
2896+ let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xC0 ] ) ;
2897+ let result = gkr_verify ( & proof, & mut verifier_transcript) ;
2898+
2899+ assert ! (
2900+ matches!( result, Err ( GkrError :: GateCheckFailed { layer: 0 } ) ) ,
2901+ "Tampered trivial-layer child_claims must be rejected with GateCheckFailed {{ layer: 0 }}, got: {:?}" ,
2902+ result
2903+ ) ;
2904+ }
2905+
28392906 #[ test]
28402907 fn test_gkr_prove_verify_roundtrip_16 ( ) {
28412908 // 16 leaves with various fractions
@@ -2846,7 +2913,8 @@ mod tests {
28462913
28472914 // Prove
28482915 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
2849- let ( proof, prover_point, prover_n, prover_d) = gkr_prove ( & tree, & mut prover_transcript) ;
2916+ let ( proof, prover_point, prover_n, prover_d) =
2917+ gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
28502918
28512919 // Verify with a fresh transcript (same seed)
28522920 let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xAA ] ) ;
@@ -2885,7 +2953,8 @@ mod tests {
28852953
28862954 // Prove
28872955 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0x5F , 0x00 ] ) ;
2888- let ( proof, prover_point, prover_n, prover_d) = gkr_prove ( & tree, & mut prover_transcript) ;
2956+ let ( proof, prover_point, prover_n, prover_d) =
2957+ gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
28892958
28902959 // Verify with a fresh transcript (same seed)
28912960 let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0x5F , 0x00 ] ) ;
@@ -2922,7 +2991,8 @@ mod tests {
29222991 let tree = build_summation_tree ( nums. clone ( ) , dens. clone ( ) ) ;
29232992
29242993 let mut prover_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xBB ] ) ;
2925- let ( proof, prover_point, prover_n, prover_d) = gkr_prove ( & tree, & mut prover_transcript) ;
2994+ let ( proof, prover_point, prover_n, prover_d) =
2995+ gkr_prove ( & tree, & mut prover_transcript) . unwrap ( ) ;
29262996
29272997 let mut verifier_transcript = DefaultTranscript :: < GoldilocksField > :: new ( & [ 0xBB ] ) ;
29282998 let result = gkr_verify ( & proof, & mut verifier_transcript) ;
0 commit comments