From ace676e8a52f3f255dbd9a71c2101e22f8ef8952 Mon Sep 17 00:00:00 2001 From: AztecBot Date: Fri, 8 May 2026 06:18:27 +0000 Subject: [PATCH 1/2] fix(bb): clamp BatchMergeProver degree-check loop to fix nightly debug SIGABRT --- .../src/barretenberg/goblin/batch_merge_prover.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp b/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp index 7245b05b1990..4713b4f4bf9e 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp @@ -33,11 +33,17 @@ typename BatchMergeProver::Polynomial BatchMergeProver::compute_degree_check_pol reversed_columns.emplace_back(poly.reverse()); } + // The two inputs only mismatch when the prover is invoked in a misuse path that the + // BB_ASSERT_LTE(N, M, ...) check in construct_proof would normally catch — e.g. the + // TooManySubtablesFails test bypasses asserts to exercise verifier rejection. In that case + // flattened_columns is sized for N (= num_subtables) but degree_check_challenges is sized for + // M (= max_subtables) < N, and we must clamp to avoid an out-of-bounds read on the challenges. + const size_t num_terms = std::min(flattened_columns.size(), degree_check_challenges.size()); std::vector> reversed_column_spans; std::vector scalars; - reversed_column_spans.reserve(flattened_columns.size()); - scalars.reserve(flattened_columns.size()); - for (size_t idx = 0; idx < flattened_columns.size(); ++idx) { + reversed_column_spans.reserve(num_terms); + scalars.reserve(num_terms); + for (size_t idx = 0; idx < num_terms; ++idx) { reversed_column_spans.emplace_back(reversed_columns[idx]); scalars.push_back(degree_check_challenges[idx]); } From fcb061baea8431d93f46121828b1acb81c6a818a Mon Sep 17 00:00:00 2001 From: federicobarbacovi <171914500+federicobarbacovi@users.noreply.github.com> Date: Fri, 8 May 2026 10:05:00 +0000 Subject: [PATCH 2/2] Fix test --- .../barretenberg/goblin/batch_merge.test.cpp | 23 +++++++++++++++++-- .../goblin/batch_merge_prover.cpp | 12 +++------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/goblin/batch_merge.test.cpp b/barretenberg/cpp/src/barretenberg/goblin/batch_merge.test.cpp index ce0834b66282..3f0616e66441 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/batch_merge.test.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/batch_merge.test.cpp @@ -41,7 +41,8 @@ enum class FaultMode : uint8_t { PADDING_NOT_INFINITY, // padded slot sends non-zero shift size and non-zero commitment/eval SHIFT_SIZE_MINUS_ONE, // send k-1 as shift size for a subtable polynomial of size k ZK_TABLE_DEGREE_TOO_HIGH, // zk table has degree above verifier hard-coded ZK shift - ZERO_SUBTABLES_CLAIM // send 0 as number of subtables + ZERO_SUBTABLES_CLAIM, // send 0 as number of subtables, + TOO_MANY_SUBTABLES, // send a number of subtables above the max that the verifier is configured for }; void populate_subtable(const std::shared_ptr& op_queue, size_t num_ops) @@ -238,11 +239,29 @@ class TweakableBatchMergeProver : public BatchMergeProver { } // Step 6: degree-check poly + if (fault_mode == FaultMode::TOO_MANY_SUBTABLES) { + // This is the case in which we test that if the prover sends more columns than the max number of tables + // then the verifier rejects + size_t diff = flattened_cols.size() - num_degree_check_challenges; + for (size_t idx = 0; idx < diff * NUM_WIRES; ++idx) { + // Add challenges for the extra columns sent by the prover + degree_check_challenges.push_back(degree_check_challenges.back() * degree_check_challenge); + } + } + Polynomial degree_check_poly = compute_degree_check_polynomial(flattened_cols, degree_check_challenges, max_shift_size); + + if (fault_mode == FaultMode::TOO_MANY_SUBTABLES) { + // Remove the extra challenge added above to keep the degree check poly consistent with the rest of the + // proof + degree_check_challenges.pop_back(); + } + if (fault_mode == FaultMode::BAD_DEGREE_CHECK_POLY && !degree_check_poly.is_empty()) { degree_check_poly.at(0) += FF(1); } + transcript->send_to_verifier("DEGREE_CHECK_POLY", pcs_commitment_key.commit(degree_check_poly)); // Step 7 @@ -473,7 +492,7 @@ TYPED_TEST(BatchMergeTests, TooManySubtablesFails) } else { BB_DISABLE_ASSERTS(); auto op_queue = make_op_queue_with_n_subtables(TestFixture::NumSubtables + 1); - auto res = TestFixture::prove_and_verify(op_queue); + auto res = TestFixture::prove_and_verify(op_queue, FaultMode::TOO_MANY_SUBTABLES); EXPECT_FALSE(res.reduction_ok); // Caught by product check EXPECT_FALSE(res.pairing_ok); // Verifier uses fewer commitments than the one sent if constexpr (TestFixture::IsRecursive) { diff --git a/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp b/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp index 4713b4f4bf9e..7245b05b1990 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/batch_merge_prover.cpp @@ -33,17 +33,11 @@ typename BatchMergeProver::Polynomial BatchMergeProver::compute_degree_check_pol reversed_columns.emplace_back(poly.reverse()); } - // The two inputs only mismatch when the prover is invoked in a misuse path that the - // BB_ASSERT_LTE(N, M, ...) check in construct_proof would normally catch — e.g. the - // TooManySubtablesFails test bypasses asserts to exercise verifier rejection. In that case - // flattened_columns is sized for N (= num_subtables) but degree_check_challenges is sized for - // M (= max_subtables) < N, and we must clamp to avoid an out-of-bounds read on the challenges. - const size_t num_terms = std::min(flattened_columns.size(), degree_check_challenges.size()); std::vector> reversed_column_spans; std::vector scalars; - reversed_column_spans.reserve(num_terms); - scalars.reserve(num_terms); - for (size_t idx = 0; idx < num_terms; ++idx) { + reversed_column_spans.reserve(flattened_columns.size()); + scalars.reserve(flattened_columns.size()); + for (size_t idx = 0; idx < flattened_columns.size(); ++idx) { reversed_column_spans.emplace_back(reversed_columns[idx]); scalars.push_back(degree_check_challenges[idx]); }