-
Notifications
You must be signed in to change notification settings - Fork 592
fix: minor fixes pt. 4 #22282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix: minor fixes pt. 4 #22282
Changes from all commits
843a614
d4ef447
9bb6e1a
64c64a5
f203bfc
0a3f1ad
6869984
703bb46
f3781b5
cf2ceb8
98fd9aa
2cca8c5
052059b
874ce28
5e3113e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -474,8 +474,9 @@ template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA | |
| G_zero = | ||
| scalar_multiplication::pippenger_unsafe<Curve>(data.s_vec, { &srs_elements[0], /*size*/ poly_length }); | ||
| } | ||
| BB_ASSERT_EQ( | ||
| G_zero, data.G_zero_from_prover, "G_0 should be equal to G_0 sent in transcript. IPA verification fails."); | ||
| if (G_zero != data.G_zero_from_prover) { | ||
| return false; | ||
| } | ||
|
|
||
| // Step 10. | ||
| // Compute C_right = a_0 * G_s + a_0 * b_0 * U | ||
|
|
@@ -809,8 +810,8 @@ template <typename Curve_, size_t log_poly_length = CONST_ECCVM_LOG_N> class IPA | |
| srs_elements.resize(poly_length); | ||
| Commitment computed_G_zero = Commitment::batch_mul(srs_elements, s_vec); | ||
| // check the computed G_zero and the claimed G_zero are the same. | ||
| claimed_G_zero.assert_equal(computed_G_zero); | ||
| BB_ASSERT_EQ(computed_G_zero.get_value(), claimed_G_zero.get_value(), "G_zero doesn't match received G_zero."); | ||
| // The circuit constraint enforces correctness; mismatched witnesses will produce an unsatisfiable circuit. | ||
| claimed_G_zero.assert_equal(computed_G_zero, "G_zero doesn't match received G_zero."); | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as here - i think this assert was disruptive for circuit construction |
||
|
|
||
| bool running_truth_value = verifier_accumulator.running_truth_value; | ||
| return running_truth_value; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,11 +30,11 @@ MultilinearBatchingFlavor::ProvingKey::ProvingKey(ProverClaim&& accumulator_clai | |
| polynomials.batched_shifted_accumulator = preshifted_accumulator.shifted(); | ||
| polynomials.batched_shifted_instance = preshifted_instance.shifted(); | ||
|
|
||
| // Construct `eq` polynomials from challenges | ||
| polynomials.eq_accumulator = | ||
| ProverEqPolynomial<FF>::construct(accumulator_claim.challenge, bb::numeric::get_msb(circuit_size)); | ||
| polynomials.eq_instance = | ||
| ProverEqPolynomial<FF>::construct(instance_claim.challenge, bb::numeric::get_msb(circuit_size)); | ||
| // Construct eq polynomials with 2^m coefficients (m = log2(circuit_size)), not 2^VIRTUAL_LOG_N. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's efficient and correct, but the implementation is somewhat convoluted |
||
| // Virtual sumcheck rounds handle the remaining challenges without needing the full table. | ||
| const size_t log_circuit_size = bb::numeric::get_msb(circuit_size); | ||
| polynomials.eq_accumulator = ProverEqPolynomial<FF>::construct(accumulator_claim.challenge, log_circuit_size); | ||
| polynomials.eq_instance = ProverEqPolynomial<FF>::construct(instance_claim.challenge, log_circuit_size); | ||
| polynomials.increase_polynomials_virtual_size(virtual_circuit_size); | ||
|
|
||
| // Move incoming challenges and copy commitments with corresponding evaluations | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,5 @@ | ||
| #include "barretenberg/circuit_checker/circuit_checker.hpp" | ||
| #include "barretenberg/commitment_schemes/kzg/kzg.hpp" | ||
| #include "barretenberg/common/test.hpp" | ||
| #include "barretenberg/ecc/fields/field_conversion.hpp" | ||
| #include "barretenberg/goblin/merge_prover.hpp" | ||
|
|
@@ -347,6 +348,152 @@ template <typename Curve> class MergeTests : public testing::Test { | |
|
|
||
| prove_and_verify_merge(op_queue, settings, TamperProofMode::LEval, false); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Test that an honestly constructed merge proof with shift_size = 0 (empty left table) verifies | ||
| * @details Manually constructs a merge proof where the left table is empty, M = R, and all PCS | ||
| * openings are computed honestly. This bypasses the op queue (which structurally prevents empty | ||
| * subtables) to test the protocol math directly. | ||
| */ | ||
| static void test_honest_empty_left_table() | ||
| { | ||
| if constexpr (IsRecursive) { | ||
| GTEST_SKIP() << "Native-only test"; | ||
| return; | ||
| } | ||
| if constexpr (!IsRecursive) { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. checking that an honest prover can prove a merge with shift size 0 - i got puzzled why we have an assert on the verifier side |
||
|
|
||
| using InnerFlavor = MegaFlavor; | ||
| using InnerBuilder = typename InnerFlavor::CircuitBuilder; | ||
| using Polynomial = bb::Polynomial<bb::fr>; | ||
| using CommitmentKey = bb::CommitmentKey<curve::BN254>; | ||
|
|
||
| // Build an op queue with some ops to serve as the right (previous) table | ||
| auto op_queue = std::make_shared<ECCOpQueue>(); | ||
| InnerBuilder circuit{ op_queue }; | ||
| GoblinMockCircuits::construct_simple_circuit(circuit); | ||
| op_queue->merge(); | ||
|
|
||
| // Right table = the merged previous table; Left table = empty | ||
| std::array<Polynomial, NUM_WIRES> right_table = op_queue->construct_ultra_ops_table_columns(); | ||
| std::array<Polynomial, NUM_WIRES> left_table; | ||
| std::array<Polynomial, NUM_WIRES> merged_table; | ||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| left_table[idx] = Polynomial(0); // empty | ||
| merged_table[idx] = Polynomial(right_table[idx]); // M = R | ||
| } | ||
|
|
||
| CommitmentKey ck(right_table[0].size()); | ||
| auto prover_transcript = std::make_shared<NativeTranscript>(); | ||
|
|
||
| // === Replicate MergeProver::construct_proof with shift_size = 0 === | ||
| prover_transcript->send_to_verifier("shift_size", static_cast<uint32_t>(0)); | ||
|
|
||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| prover_transcript->send_to_verifier("MERGED_TABLE_" + std::to_string(idx), | ||
| ck.commit(merged_table[idx])); | ||
| } | ||
|
|
||
| std::array<std::string, 4> degree_labels = { "LEFT_TABLE_DEGREE_CHECK_0", | ||
| "LEFT_TABLE_DEGREE_CHECK_1", | ||
| "LEFT_TABLE_DEGREE_CHECK_2", | ||
| "LEFT_TABLE_DEGREE_CHECK_3" }; | ||
| // Consume challenges to advance transcript state (values unused since L is empty) | ||
| prover_transcript->template get_challenges<bb::fr>(degree_labels); | ||
| Polynomial reversed_batched_left_tables(0); // empty | ||
| prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES", | ||
| ck.commit(reversed_batched_left_tables)); | ||
|
|
||
| std::array<std::string, 13> shplonk_labels = { | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_0", "SHPLONK_MERGE_BATCHING_CHALLENGE_1", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_2", "SHPLONK_MERGE_BATCHING_CHALLENGE_3", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_4", "SHPLONK_MERGE_BATCHING_CHALLENGE_5", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_6", "SHPLONK_MERGE_BATCHING_CHALLENGE_7", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_8", "SHPLONK_MERGE_BATCHING_CHALLENGE_9", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_10", "SHPLONK_MERGE_BATCHING_CHALLENGE_11", | ||
| "SHPLONK_MERGE_BATCHING_CHALLENGE_12" | ||
| }; | ||
| auto shplonk_batching_challenges = prover_transcript->template get_challenges<bb::fr>(shplonk_labels); | ||
|
|
||
| bb::fr kappa = prover_transcript->template get_challenge<bb::fr>("kappa"); | ||
| bb::fr kappa_inv = kappa.invert(); | ||
|
|
||
| // Evaluations at kappa | ||
| std::vector<bb::fr> evals; | ||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| evals.emplace_back(left_table[idx].evaluate(kappa)); // L_j(κ) = 0 | ||
| prover_transcript->send_to_verifier("LEFT_TABLE_EVAL_" + std::to_string(idx), evals.back()); | ||
| } | ||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| evals.emplace_back(right_table[idx].evaluate(kappa)); | ||
| prover_transcript->send_to_verifier("RIGHT_TABLE_EVAL_" + std::to_string(idx), evals.back()); | ||
| } | ||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| evals.emplace_back(merged_table[idx].evaluate(kappa)); | ||
| prover_transcript->send_to_verifier("MERGED_TABLE_EVAL_" + std::to_string(idx), evals.back()); | ||
| } | ||
| evals.emplace_back(reversed_batched_left_tables.evaluate(kappa_inv)); // G(κ⁻¹) = 0 | ||
| prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES_EVAL", evals.back()); | ||
|
|
||
| // Shplonk batched quotient: Q such that Q·(X-κ)·(X-κ⁻¹) = ... | ||
| // With L=0 and G=0, only R and M (=R) terms contribute at kappa. | ||
| size_t poly_size = merged_table[0].size(); | ||
| Polynomial shplonk_batched_quotient(poly_size); | ||
|
|
||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| // L terms: zero (left_table is empty) | ||
| // R terms | ||
| bb::fr beta_r = shplonk_batching_challenges[NUM_WIRES + idx]; | ||
| shplonk_batched_quotient.add_scaled(right_table[idx], beta_r); | ||
| shplonk_batched_quotient.at(0) -= beta_r * evals[NUM_WIRES + idx]; | ||
| // M terms | ||
| bb::fr beta_m = shplonk_batching_challenges[2 * NUM_WIRES + idx]; | ||
| shplonk_batched_quotient.add_scaled(merged_table[idx], beta_m); | ||
| shplonk_batched_quotient.at(0) -= beta_m * evals[2 * NUM_WIRES + idx]; | ||
| } | ||
| shplonk_batched_quotient.factor_roots(kappa); | ||
| // G term at kappa_inv: G=0 so nothing to add | ||
|
|
||
| prover_transcript->send_to_verifier("SHPLONK_BATCHED_QUOTIENT", ck.commit(shplonk_batched_quotient)); | ||
|
|
||
| // Shplonk opening claim | ||
| bb::fr z = prover_transcript->template get_challenge<bb::fr>("shplonk_opening_challenge"); | ||
| Polynomial Q_prime(std::move(shplonk_batched_quotient)); | ||
| Q_prime *= -(z - kappa); | ||
|
|
||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| bb::fr beta_r = shplonk_batching_challenges[NUM_WIRES + idx]; | ||
| Q_prime.add_scaled(right_table[idx], beta_r); | ||
| Q_prime.at(0) -= beta_r * evals[NUM_WIRES + idx]; | ||
| bb::fr beta_m = shplonk_batching_challenges[2 * NUM_WIRES + idx]; | ||
| Q_prime.add_scaled(merged_table[idx], beta_m); | ||
| Q_prime.at(0) -= beta_m * evals[2 * NUM_WIRES + idx]; | ||
| } | ||
| // G term: G=0, so (G-g)·factor = 0, nothing to add | ||
|
|
||
| ProverOpeningClaim<curve::BN254> opening_claim = { .polynomial = std::move(Q_prime), | ||
| .opening_pair = { z, bb::fr(0) } }; | ||
| KZG<curve::BN254>::compute_opening_proof(ck, opening_claim, prover_transcript); | ||
|
|
||
| auto native_proof = prover_transcript->export_proof(); | ||
|
|
||
| // === Verify with unmodified verifier (which has BB_ASSERT_GT for shift_size > 0) === | ||
| // Build input commitments: t = empty (zero commits), T_prev = right table | ||
| InputCommitments input_commitments; | ||
| for (size_t idx = 0; idx < NUM_WIRES; idx++) { | ||
| input_commitments.t_commitments[idx] = ck.commit(left_table[idx]); | ||
| input_commitments.T_prev_commitments[idx] = ck.commit(right_table[idx]); | ||
| } | ||
|
|
||
| auto verifier_transcript = std::make_shared<NativeTranscript>(); | ||
| MergeVerifierType verifier{ MergeSettings::PREPEND, verifier_transcript }; | ||
| auto result = verifier.reduce_to_pairing_check(native_proof, input_commitments); | ||
|
|
||
| bool pairing_verified = result.pairing_points.check(); | ||
| bool verified = pairing_verified && result.reduction_succeeded; | ||
| EXPECT_TRUE(verified); | ||
| } // if constexpr (!IsRecursive) | ||
| } | ||
| }; | ||
|
|
||
| // Define test types: native and recursive contexts | ||
|
|
@@ -406,6 +553,11 @@ TYPED_TEST(MergeTests, EvalFailureAppend) | |
| TestFixture::test_eval_failure(MergeSettings::APPEND); | ||
| } | ||
|
|
||
| TYPED_TEST(MergeTests, HonestEmptyLeftTable) | ||
| { | ||
| TestFixture::test_honest_empty_left_table(); | ||
| } | ||
|
|
||
| /** | ||
| * @brief Test that mixing values from different transcript instances causes instant failure | ||
| * @details Simulates a realistic scenario where a circuit contains two merge verifiers, each with | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -122,13 +122,6 @@ typename MergeVerifier_<Curve>::ReductionResult MergeVerifier_<Curve>::reduce_to | |
| // For native: shift_size is uint32_t | ||
| // For stdlib: shift_size is FF (we'll get the value later) | ||
| const FF shift_size = transcript->template receive_from_prover<FF>("shift_size"); | ||
| ; | ||
| if constexpr (IsRecursive) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm guessing that these checks were intended to flag unexpected behavior rather than invalid behavior. It used to be the case that we always added some ops to everything - I thought we got rid of that but then I would think this would've triggered. Maybe @federicobarbacovi can weigh in here?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If shift size is zero, proving used to fail because Shplonk tries to access the zero coefficient of the polynomials it's proving, see here. We are not using Shplonk directly in the merge anymore, so I don't know whether proving would still fail with shift size equal to zero. Re whether we always add something: yes, we do add ecc ops to each builder during finalisation, see here. At the same time, the first merge in Chonk happens against an empty table. The reason why the first merge doesn't trigger this assertion is that the shift size is computed for the left table. As the first merge is in prepend mode, the left table is the table coming from the first app, which is never zero because of the ops we add. |
||
| BB_ASSERT_GT(uint32_t(shift_size.get_value()), 0U, "Shift size should always be bigger than 0"); | ||
| } else { | ||
|
|
||
| BB_ASSERT_GT(shift_size, 0U, "Shift size should always be bigger than 0"); | ||
| } | ||
|
|
||
| // Store T_commitments of the verifier | ||
| TableCommitments merged_table_commitments; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't see any reason to panic here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm yeah this does seem funny