Barretenberg
The ZK-SNARK library at the core of Aztec
Loading...
Searching...
No Matches
merge.test.cpp
Go to the documentation of this file.
13
14namespace bb {
15
16// Helper traits to extract Builder type from Curve
17template <typename Curve, typename = void> struct BuilderTypeHelper {
18 struct DummyBuilder {};
20};
21
22template <typename Curve> struct BuilderTypeHelper<Curve, std::enable_if_t<Curve::is_stdlib_type>> {
23 using type = typename Curve::Builder;
24};
25
31template <typename Curve> class MergeTests : public testing::Test {
32 public:
34
35 using FF = typename Curve::ScalarField;
37 using GroupElement = typename Curve::Element;
44
45 static constexpr bool IsRecursive = Curve::is_stdlib_type;
47
48 // Builder type is only available in recursive context
50
51 enum class TamperProofMode : uint8_t { None, MCommitment, LEval };
52
53 static std::shared_ptr<ECCOpQueue> construct_final_merge_op_queue(const size_t num_subtables_up_to_tail = 1)
54 {
55 using InnerFlavor = MegaFlavor;
56 using InnerBuilder = typename InnerFlavor::CircuitBuilder;
57
58 auto op_queue = std::make_shared<ECCOpQueue>();
59 for (size_t idx = 0; idx < num_subtables_up_to_tail; ++idx) {
60 InnerBuilder circuit{ op_queue };
62 op_queue->merge();
63 }
64
65 op_queue->construct_zk_columns();
66
67 InnerBuilder hiding_circuit{ op_queue };
69
70 // The merge protocol is only used for the hiding kernel, whose subtable has a fixed size. The prover and
71 // verifier both rely on this (the verifier hard-codes the shift size from it), so pad the final subtable to
72 // match.
73 BB_ASSERT_LTE(op_queue->get_current_subtable_size(), bb::HIDING_KERNEL_ULTRA_OPS);
74 while (op_queue->get_current_subtable_size() < bb::HIDING_KERNEL_ULTRA_OPS) {
75 op_queue->no_op_ultra_only();
76 }
77 return op_queue;
78 }
79
84 template <typename T> static auto to_native(const T& val)
85 {
86 if constexpr (IsRecursive) {
87 return val.get_value();
88 } else {
89 return val;
90 }
91 }
92
98 {
99 if constexpr (IsRecursive) {
100 auto commitment = Commitment::from_witness(&builder, native_commitment);
101 commitment.unset_free_witness_tag();
102 return commitment;
103 } else {
104 (void)builder; // Unused in native context
105 return native_commitment;
106 }
107 }
108
114 static Proof create_proof(BuilderType& builder, const std::vector<bb::fr>& native_proof)
115 {
116 if constexpr (IsRecursive) {
117 // Create stdlib::Proof, which is std::vector<stdlib::field_t<Builder>>
118 stdlib::Proof<BuilderType> stdlib_proof(builder, native_proof);
119 // It's already the right type (std::vector<FF>), just return it
120 return stdlib_proof;
121 } else {
122 (void)builder; // Unused in native context
123 return native_proof;
124 }
125 }
126
131 {
132 if constexpr (IsRecursive) {
134 } else {
135 (void)builder; // Unused in native context
136 return true;
137 }
138 }
139
143 static void tamper_with_proof(std::vector<bb::fr>& merge_proof, const TamperProofMode tampering_mode)
144 {
145 const size_t m_commitment_idx = 0; // Index of first commitment to merged table in merge proof
146 const size_t l_eval_idx = 21; // Index of first evaluation of l(1/kappa) in merge proof
147
148 switch (tampering_mode) {
150 // Tamper with the commitment in the proof
151 auto m_commitment =
152 FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(std::span{ merge_proof }.subspan(
153 m_commitment_idx, FrCodec::calc_num_fields<curve::BN254::AffineElement>()));
154 m_commitment = m_commitment + curve::BN254::AffineElement::one();
155 auto m_commitment_frs = FrCodec::serialize_to_fields<curve::BN254::AffineElement>(m_commitment);
156 for (size_t idx = 0; idx < 4; ++idx) {
157 merge_proof[m_commitment_idx + idx] = m_commitment_frs[idx];
158 }
159 break;
160 }
162 // Tamper with the evaluation in the proof
163 merge_proof[l_eval_idx] -= bb::fr(1);
164 break;
165 default:
166 // Nothing to do
167 break;
168 }
169 }
170
176 const TamperProofMode tampering_mode = TamperProofMode::None,
177 const bool expected = true)
178 {
179 // Create native merge proof
180 auto prover_transcript = std::make_shared<NativeTranscript>();
181 MergeProver merge_prover{ op_queue, prover_transcript };
182 auto native_proof = merge_prover.construct_proof();
183 tamper_with_proof(native_proof, tampering_mode);
184
185 // Construct shifted column polynomials matching the circuit's ecc_op_wire layout
186 auto t_current = op_queue->construct_current_ultra_ops_subtable_columns();
187 auto T_prev = op_queue->construct_table_columns_up_to_tail();
188
191 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
192 native_t_commitments[idx] = merge_prover.pcs_commitment_key.commit(t_current[idx]);
193 native_T_prev_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_prev[idx]);
194 }
195
196 auto T_merged = op_queue->construct_ultra_ops_table_columns();
197 std::array<curve::BN254::AffineElement, NUM_WIRES> expected_merged_commitments;
198 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
199 expected_merged_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_merged[idx]);
200 }
201
202 // Create builder (only used in recursive context)
204
205 // Create commitments and proof in the appropriate context
206 InputCommitments input_commitments;
207 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
208 input_commitments.t_commitments[idx] = create_commitment(builder, native_t_commitments[idx]);
209 input_commitments.T_prev_commitments[idx] = create_commitment(builder, native_T_prev_commitments[idx]);
210 }
211 Proof proof = create_proof(builder, native_proof);
212
213 // Verify the proof
214 auto transcript = std::make_shared<Transcript>();
215 MergeVerifierType verifier{ transcript };
216 auto result = verifier.reduce_to_pairing_check(proof, input_commitments);
217
218 // Perform pairing check and verify
219 bool pairing_verified = result.pairing_points.check();
220 bool verified = pairing_verified && result.reduction_succeeded;
221 EXPECT_EQ(verified, expected);
222
223 // If verification is expected to succeed, also check that the merged table commitments match
224 if (expected) {
225 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
226 EXPECT_EQ(to_native(result.merged_commitments[idx]), expected_merged_commitments[idx])
227 << "Merged table commitment mismatch at index " << idx;
228 }
229 }
230
231 // Check circuit validity (only relevant in recursive context)
232 if constexpr (IsRecursive) {
233 bool circuit_valid = check_circuit(builder);
234 EXPECT_EQ(circuit_valid, expected);
235 }
236 }
237
243 {
244 auto op_queue = construct_final_merge_op_queue();
245
246 // Construct a merge proof and ensure its size matches expectation
247 auto transcript = std::make_shared<NativeTranscript>();
248 MergeProver merge_prover{ op_queue, transcript };
249 auto merge_proof = merge_prover.construct_proof();
250
251 EXPECT_EQ(merge_proof.size(), MERGE_PROOF_SIZE);
252 }
253
257 static void test_single_merge()
258 {
259 auto op_queue = construct_final_merge_op_queue();
260
261 prove_and_verify_merge(op_queue);
262 }
263
268 {
269 auto op_queue = construct_final_merge_op_queue(/*num_subtables_up_to_tail=*/3);
270 prove_and_verify_merge(op_queue);
271 }
272
276 static void test_merge_failure()
277 {
278 auto op_queue = construct_final_merge_op_queue();
279
281 }
282
286 static void test_eval_failure()
287 {
288 auto op_queue = construct_final_merge_op_queue();
289
291 }
292
303 {
304 if constexpr (IsRecursive) {
305 GTEST_SKIP() << "Native-only test";
306 return;
307 }
308 if constexpr (!IsRecursive) {
311
312 // The shift size the verifier hard-codes (see MergeVerifier::reduce_to_pairing_check).
313 const size_t shift_size =
315
316 // Left table deliberately exceeds the hard-coded shift size; right table is small.
317 const size_t left_size = shift_size + 2;
318 const size_t right_size = 4;
319 const size_t merged_size = shift_size + right_size;
320
324 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
325 left_table[idx] = Polynomial::random(left_size);
326 right_table[idx] = Polynomial::random(right_size);
327 // M = L + X^shift·R, so the concatenation identity holds and only the degree check fails.
328 merged_table[idx] = Polynomial(merged_size);
329 for (size_t i = 0; i < left_size; i++) {
330 merged_table[idx].at(i) += left_table[idx].at(i);
331 }
332 for (size_t i = 0; i < right_size; i++) {
333 merged_table[idx].at(shift_size + i) += right_table[idx].at(i);
334 }
335 }
336
337 CommitmentKey ck(merged_size);
338 auto prover_transcript = std::make_shared<NativeTranscript>();
339
340 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
341 prover_transcript->send_to_verifier("MERGED_TABLE_" + std::to_string(idx),
342 ck.commit(merged_table[idx]));
343 }
344
345 std::array<std::string, 4> degree_labels = { "LEFT_TABLE_DEGREE_CHECK_0",
346 "LEFT_TABLE_DEGREE_CHECK_1",
347 "LEFT_TABLE_DEGREE_CHECK_2",
348 "LEFT_TABLE_DEGREE_CHECK_3" };
349 auto degree_check_challenges = prover_transcript->template get_challenges<bb::fr>(degree_labels);
350
351 // Batched left table, then keep only the first `shift_size` coefficients when reversing into G. The
352 // high-degree coefficients at indices ≥ shift_size are silently dropped — exactly what makes the degree
353 // identity fail at the verifier.
354 Polynomial batched_left_tables(left_size);
355 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
356 batched_left_tables.add_scaled(left_table[idx], degree_check_challenges[idx]);
357 }
358 Polynomial reversed_batched_left_tables(shift_size);
359 for (size_t j = 0; j < shift_size; j++) {
360 reversed_batched_left_tables.at(j) = batched_left_tables.at(shift_size - 1 - j);
361 }
362 prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES",
363 ck.commit(reversed_batched_left_tables));
364
365 bb::fr kappa = prover_transcript->template get_challenge<bb::fr>("kappa");
366 bb::fr kappa_inv = kappa.invert();
367
368 std::vector<bb::fr> evals;
369 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
370 evals.emplace_back(left_table[idx].evaluate(kappa));
371 prover_transcript->send_to_verifier("LEFT_TABLE_EVAL_" + std::to_string(idx), evals.back());
372 }
373 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
374 evals.emplace_back(right_table[idx].evaluate(kappa));
375 prover_transcript->send_to_verifier("RIGHT_TABLE_EVAL_" + std::to_string(idx), evals.back());
376 }
377 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
378 evals.emplace_back(merged_table[idx].evaluate(kappa));
379 prover_transcript->send_to_verifier("MERGED_TABLE_EVAL_" + std::to_string(idx), evals.back());
380 }
381 evals.emplace_back(reversed_batched_left_tables.evaluate(kappa_inv));
382 prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES_EVAL", evals.back());
383
384 std::array<std::string, 13> shplonk_labels = {
385 "SHPLONK_MERGE_BATCHING_CHALLENGE_0", "SHPLONK_MERGE_BATCHING_CHALLENGE_1",
386 "SHPLONK_MERGE_BATCHING_CHALLENGE_2", "SHPLONK_MERGE_BATCHING_CHALLENGE_3",
387 "SHPLONK_MERGE_BATCHING_CHALLENGE_4", "SHPLONK_MERGE_BATCHING_CHALLENGE_5",
388 "SHPLONK_MERGE_BATCHING_CHALLENGE_6", "SHPLONK_MERGE_BATCHING_CHALLENGE_7",
389 "SHPLONK_MERGE_BATCHING_CHALLENGE_8", "SHPLONK_MERGE_BATCHING_CHALLENGE_9",
390 "SHPLONK_MERGE_BATCHING_CHALLENGE_10", "SHPLONK_MERGE_BATCHING_CHALLENGE_11",
391 "SHPLONK_MERGE_BATCHING_CHALLENGE_12"
392 };
393 auto shplonk_batching_challenges = prover_transcript->template get_short_challenges<bb::fr>(shplonk_labels);
394
395 // Replicate MergeProver's Shplonk batched quotient so the PCS opening verifies honestly.
396 std::array<std::array<Polynomial, NUM_WIRES>*, 3> tables = { &left_table, &right_table, &merged_table };
397 Polynomial shplonk_batched_quotient(merged_size);
398 for (size_t table_idx = 0; table_idx < 3; table_idx++) {
399 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
400 bb::fr challenge = shplonk_batching_challenges[(table_idx * NUM_WIRES) + idx];
401 shplonk_batched_quotient.add_scaled((*tables[table_idx])[idx], challenge);
402 shplonk_batched_quotient.at(0) -= challenge * evals[(table_idx * NUM_WIRES) + idx];
403 }
404 }
405 shplonk_batched_quotient.factor_roots(kappa);
406 {
407 Polynomial reversed_copy(reversed_batched_left_tables);
408 reversed_copy.at(0) -= evals.back();
409 reversed_copy.factor_roots(kappa_inv);
410 shplonk_batched_quotient.add_scaled(reversed_copy, shplonk_batching_challenges.back());
411 }
412 prover_transcript->send_to_verifier("SHPLONK_BATCHED_QUOTIENT", ck.commit(shplonk_batched_quotient));
413
414 bb::fr z = prover_transcript->template get_challenge<bb::fr>("shplonk_opening_challenge");
415 Polynomial Q_prime(std::move(shplonk_batched_quotient));
416 Q_prime *= -(z - kappa);
417 for (size_t table_idx = 0; table_idx < 3; table_idx++) {
418 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
419 bb::fr challenge = shplonk_batching_challenges[(table_idx * NUM_WIRES) + idx];
420 Q_prime.add_scaled((*tables[table_idx])[idx], challenge);
421 Q_prime.at(0) -= challenge * evals[(table_idx * NUM_WIRES) + idx];
422 }
423 }
424 {
425 Polynomial reversed_copy(reversed_batched_left_tables);
426 reversed_copy.at(0) -= evals.back();
427 Q_prime.add_scaled(reversed_copy,
428 shplonk_batching_challenges.back() * (z - kappa) * (z - kappa_inv).invert());
429 }
430
431 ProverOpeningClaim<curve::BN254> opening_claim = { .polynomial = std::move(Q_prime),
432 .opening_pair = { z, bb::fr(0) } };
433 KZG<curve::BN254>::compute_opening_proof(ck, opening_claim, prover_transcript);
434
435 auto native_proof = prover_transcript->export_proof();
436
437 InputCommitments input_commitments;
438 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
439 input_commitments.t_commitments[idx] = ck.commit(right_table[idx]);
440 input_commitments.T_prev_commitments[idx] = ck.commit(left_table[idx]);
441 }
442
443 auto verifier_transcript = std::make_shared<NativeTranscript>();
444 MergeVerifierType verifier{ verifier_transcript };
445 auto result = verifier.reduce_to_pairing_check(native_proof, input_commitments);
446
447 // The PCS opening (pairing) is honest and passes; the degree check is what rejects the proof.
448 EXPECT_TRUE(result.pairing_points.check());
449 EXPECT_FALSE(result.reduction_succeeded);
450 } // if constexpr (!IsRecursive)
451 }
452};
453
454// Define test types: native and recursive contexts
455using CurveTypes = ::testing::Types<curve::BN254, // Native
456 stdlib::bn254<MegaCircuitBuilder>, // Recursive (Mega)
457 stdlib::bn254<UltraCircuitBuilder>>; // Recursive (Ultra)
458
460
461TYPED_TEST(MergeTests, MergeProofSizeCheck)
462{
463 TestFixture::test_merge_proof_size();
464}
465
467{
468 TestFixture::test_single_merge();
469}
470
471TYPED_TEST(MergeTests, MultipleMerges)
472{
473 TestFixture::test_multiple_merges();
474}
475
476TYPED_TEST(MergeTests, MergeFailure)
477{
478 TestFixture::test_merge_failure();
479}
480
482{
483 TestFixture::test_eval_failure();
484}
485
486TYPED_TEST(MergeTests, DegreeCheckFailure)
487{
488 TestFixture::test_degree_check_failure();
489}
490
504TYPED_TEST(MergeTests, DifferentTranscriptOriginTagFailure)
505{
506 if constexpr (!TestFixture::IsRecursive) {
507 GTEST_SKIP() << "OriginTag tests only apply to recursive context";
508 }
509
510 using BuilderType = typename TestFixture::BuilderType;
511 using MergeVerifierType = typename TestFixture::MergeVerifierType;
512 using Transcript = typename TestFixture::Transcript;
513 constexpr size_t NUM_WIRES = TestFixture::NUM_WIRES;
514
515 // Create single builder for both verifiers (realistic - both in same circuit)
516 BuilderType builder;
517
518 // === Generate two separate merge proofs (simulating two independent merge operations) ===
519 auto op_queue_1 = TestFixture::construct_final_merge_op_queue();
520 auto prover_transcript_1 = std::make_shared<NativeTranscript>();
521 MergeProver prover_1{ op_queue_1, prover_transcript_1 };
522 auto proof_1 = prover_1.construct_proof();
523
524 auto op_queue_2 = TestFixture::construct_final_merge_op_queue();
525 auto prover_transcript_2 = std::make_shared<NativeTranscript>();
526 MergeProver prover_2{ op_queue_2, prover_transcript_2 };
527 auto proof_2 = prover_2.construct_proof();
528
529 // Get native commitments for proof 1 (shifted to match circuit ecc_op_wire layout)
530 auto t_1 = op_queue_1->construct_current_ultra_ops_subtable_columns();
531 auto T_prev_1 = op_queue_1->construct_table_columns_up_to_tail();
533 std::array<curve::BN254::AffineElement, NUM_WIRES> native_T_prev_commitments_1;
534 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
535 native_t_commitments_1[idx] = prover_1.pcs_commitment_key.commit(t_1[idx]);
536 native_T_prev_commitments_1[idx] = prover_1.pcs_commitment_key.commit(T_prev_1[idx]);
537 }
538
539 // === Create first verifier with its own transcript instance ===
540 auto transcript_1 = std::make_shared<Transcript>();
541 [[maybe_unused]] MergeVerifierType verifier_1{ transcript_1 };
542
543 [[maybe_unused]] auto proof_1_recursive = TestFixture::create_proof(builder, proof_1);
544
545 // Create commitments for verifier 1 - these will be "owned" by transcript_1
546 // When we read from the proof using transcript_1, those values get tagged with transcript_1's parent_tag
547 typename MergeVerifierType::InputCommitments input_commitments_1;
548 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
549 input_commitments_1.t_commitments[idx] = TestFixture::create_commitment(builder, native_t_commitments_1[idx]);
550 input_commitments_1.T_prev_commitments[idx] =
551 TestFixture::create_commitment(builder, native_T_prev_commitments_1[idx]);
552 }
553
554 // === Create second verifier with a DIFFERENT transcript instance ===
555 // This simulates having two independent merge verifiers in the same circuit
556 auto transcript_2 = std::make_shared<Transcript>();
557 MergeVerifierType verifier_2{ transcript_2 };
558
559 auto proof_2_recursive = TestFixture::create_proof(builder, proof_2);
560
561 // Get the parent tags to show they're different
562 OriginTag tag_1 = extract_transcript_tag(*transcript_1);
563 OriginTag tag_2 = extract_transcript_tag(*transcript_2);
564
565 info("Verifier 1 transcript_index: ", tag_1.transcript_index);
566 info("Verifier 2 transcript_index: ", tag_2.transcript_index);
567 ASSERT_NE(tag_1.transcript_index, tag_2.transcript_index) << "Transcripts should have different parent tags";
568
569 // === SECURITY VIOLATION: Try to use commitments from proof 1 with verifier 2 ===
570
571 // To make this more realistic, we need to actually receive values from transcript_1 into the commitments
572 // In a real scenario, the verifier would receive_from_prover which tags values with the transcript's parent_tag
573 // For this test, we'll manually tag the commitments as if they came from transcript_1
574 OriginTag transcript_1_tag(tag_1.transcript_index, 0, /*is_submitted=*/true);
575 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
576 // Tag these commitments as if they were read from transcript_1
577 if constexpr (TestFixture::IsRecursive) {
578 input_commitments_1.t_commitments[idx].set_origin_tag(transcript_1_tag);
579 input_commitments_1.T_prev_commitments[idx].set_origin_tag(transcript_1_tag);
580 }
581 }
582
583 // Now try to verify proof_2 using verifier_2 (with transcript_2) but with commitments tagged for transcript_1
584 // When verifier_2 reads from proof_2_recursive using transcript_2, those values will have tag_2.parent_tag
585 // When it tries to mix them with input_commitments_1 (which have tag_1.parent_tag), the check should trigger
586 info("Attempting to mix transcript_1 commitments with transcript_2 proof verification...");
587
588 // Catch the exception and verify it's the expected cross-transcript error
589#ifndef NDEBUG
590 EXPECT_THROW_WITH_MESSAGE([[maybe_unused]] auto result =
591 verifier_2.reduce_to_pairing_check(proof_2_recursive, input_commitments_1),
592 "Tags from different transcripts were involved in the same computation");
593#endif
594}
595
600class MergeTranscriptTests : public ::testing::Test {
601 public:
603
611 {
612 TranscriptManifest manifest_expected;
613
614 // Size calculations
615 size_t frs_per_Fr = 1; // Native field element
616 size_t frs_per_G = FrCodec::calc_num_fields<curve::BN254::AffineElement>(); // Commitment = 4 frs
617
618 size_t round = 0;
619
620 // Round 0: Prover sends merged table commitments, gets degree check challenges
621 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
622 manifest_expected.add_entry(round, "MERGED_TABLE_" + std::to_string(idx), frs_per_G);
623 }
624 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_0");
625 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_1");
626 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_2");
627 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_3");
628
629 // Round 1: degre check polynomial, kappa
630 round++;
631 manifest_expected.add_entry(round, "REVERSED_BATCHED_LEFT_TABLES", frs_per_G);
632 manifest_expected.add_challenge(round, "kappa");
633
634 // Round 2: evaluations of all tables at kappa, 1/kappa, shplonk challenges
635 round++;
636 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
637 manifest_expected.add_entry(round, "LEFT_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
638 }
639 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
640 manifest_expected.add_entry(round, "RIGHT_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
641 }
642 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
643 manifest_expected.add_entry(round, "MERGED_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
644 }
645 manifest_expected.add_entry(round, "REVERSED_BATCHED_LEFT_TABLES_EVAL", frs_per_Fr);
646
647 for (size_t idx = 0; idx < (3 * NUM_WIRES) + 1; ++idx) {
648 manifest_expected.add_challenge(round, "SHPLONK_MERGE_BATCHING_CHALLENGE_" + std::to_string(idx));
649 }
650
651 // Round 3: Shplonk quotient, shplonk opening challenge
652 round++;
653 manifest_expected.add_entry(round, "SHPLONK_BATCHED_QUOTIENT", frs_per_G);
654 manifest_expected.add_challenge(round, "shplonk_opening_challenge");
655
656 // Round 4: KZG:W
657 round++;
658 manifest_expected.add_entry(round, "KZG:W", frs_per_G);
659
660 return manifest_expected;
661 }
662};
663
667TEST_F(MergeTranscriptTests, ProverManifestConsistency)
668{
670
671 // Construct merge proof with manifest enabled
672 auto transcript = std::make_shared<NativeTranscript>();
673 transcript->enable_manifest();
674 MergeProver merge_prover{ op_queue, transcript };
675 auto merge_proof = merge_prover.construct_proof();
676
677 // Check prover manifest matches expected manifest
678 auto manifest_expected = construct_merge_manifest();
679 auto prover_manifest = transcript->get_manifest();
680
681 ASSERT_GT(manifest_expected.size(), 0);
682 ASSERT_EQ(prover_manifest.size(), manifest_expected.size())
683 << "Prover manifest has " << prover_manifest.size() << " rounds, expected " << manifest_expected.size();
684
685 for (size_t round = 0; round < manifest_expected.size(); ++round) {
686 ASSERT_EQ(prover_manifest[round], manifest_expected[round]) << "Prover manifest discrepancy in round " << round;
687 }
688}
689
693TEST_F(MergeTranscriptTests, VerifierManifestConsistency)
694{
696
697 // Generate merge proof with prover manifest enabled
698 auto prover_transcript = std::make_shared<NativeTranscript>();
699 prover_transcript->enable_manifest();
700 MergeProver merge_prover{ op_queue, prover_transcript };
701 auto merge_proof = merge_prover.construct_proof();
702
703 // Construct commitments for verifier (shifted to match circuit ecc_op_wire layout)
704 MergeVerifier::InputCommitments merge_commitments;
705 auto t_current = op_queue->construct_current_ultra_ops_subtable_columns();
706 auto T_prev = op_queue->construct_table_columns_up_to_tail();
707 for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) {
708 merge_commitments.t_commitments[idx] = merge_prover.pcs_commitment_key.commit(t_current[idx]);
709 merge_commitments.T_prev_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_prev[idx]);
710 }
711
712 // Verify proof with verifier manifest enabled
713 auto verifier_transcript = std::make_shared<NativeTranscript>();
714 verifier_transcript->enable_manifest();
715 MergeVerifier merge_verifier{ verifier_transcript };
716 auto result = merge_verifier.reduce_to_pairing_check(merge_proof, merge_commitments);
717
718 // Verification should succeed
719 ASSERT_TRUE(result.pairing_points.check() && result.reduction_succeeded);
720
721 // Check prover and verifier manifests match
722 auto prover_manifest = prover_transcript->get_manifest();
723 auto verifier_manifest = verifier_transcript->get_manifest();
724
725 ASSERT_GT(prover_manifest.size(), 0);
726 ASSERT_EQ(prover_manifest.size(), verifier_manifest.size())
727 << "Prover has " << prover_manifest.size() << " rounds, verifier has " << verifier_manifest.size();
728
729 for (size_t round = 0; round < prover_manifest.size(); ++round) {
730 ASSERT_EQ(prover_manifest[round], verifier_manifest[round])
731 << "Prover/Verifier manifest discrepancy in round " << round;
732 }
733}
734
735} // namespace bb
#define BB_ASSERT_LTE(left, right,...)
Definition assert.hpp:158
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:224
Common transcript class for both parties. Stores the data for the current round, as well as the manif...
CommitmentKey object over a pairing group 𝔾₁.
static size_t get_append_offset_for_verifier()
static constexpr size_t compute_fixed_append_offset(size_t append_offset, bool include_zk_prefix=true)
static void construct_simple_circuit(MegaBuilder &builder)
Generate a simple test circuit with some ECC op gates and conventional arithmetic gates.
static void compute_opening_proof(const CK &ck, const ProverOpeningClaim< Curve > &opening_claim, const std::shared_ptr< Transcript > &prover_trancript)
Computes the KZG commitment to an opening proof polynomial at a single evaluation point.
Definition kzg.hpp:44
static constexpr size_t NUM_WIRES
static constexpr size_t NUM_WIRES
Prover for the single-step Goblin ECC op queue merge protocol.
BB_PROFILE MergeProof construct_proof()
Prove proper construction of the aggregate Goblin ECC op queue polynomials T_j.
Unified test fixture for native and recursive merge verification.
static bool check_circuit(BuilderType &builder)
Check circuit validity (only relevant in recursive context)
typename Curve::ScalarField FF
typename Curve::Element GroupElement
static void prove_and_verify_merge(const std::shared_ptr< ECCOpQueue > &op_queue, const TamperProofMode tampering_mode=TamperProofMode::None, const bool expected=true)
Prove and verify a merge proof in both native and recursive contexts.
static Commitment create_commitment(BuilderType &builder, const curve::BN254::AffineElement &native_commitment)
Create a commitment from a native commitment value.
static std::shared_ptr< ECCOpQueue > construct_final_merge_op_queue(const size_t num_subtables_up_to_tail=1)
typename Curve::AffineElement Commitment
typename MergeVerifierType::Proof Proof
static constexpr bool IsRecursive
static void test_eval_failure()
Test failure when g_j(kappa) ≠ kappa^{k-1} * l_j(1/kappa)
static void test_merge_proof_size()
Test that merge proof size matches the expected constant.
static auto to_native(const T &val)
Convert a stdlib type to its native value.
static void tamper_with_proof(std::vector< bb::fr > &merge_proof, const TamperProofMode tampering_mode)
Tamper with the merge proof for failure testing.
static Proof create_proof(BuilderType &builder, const std::vector< bb::fr > &native_proof)
Create a proof object from a vector of field elements.
static void test_multiple_merges()
Test a final merge proof with multiple historical subtables up to the tail.
typename MergeVerifierType::InputCommitments InputCommitments
typename MergeVerifierType::PairingPoints PairingPoints
static void test_degree_check_failure()
Test failure when deg(l) ≥ shift_size.
static constexpr size_t NUM_WIRES
typename MergeVerifierType::Transcript Transcript
typename MergeVerifierType::TableCommitments TableCommitments
typename BuilderTypeHelper< Curve >::type BuilderType
static void SetUpTestSuite()
static void test_single_merge()
Test basic merge proof construction and verification.
static void test_merge_failure()
Test failure when m ≠ l + X^k r.
Test class for merge protocol transcript pinning tests.
static void SetUpTestSuite()
static TranscriptManifest construct_merge_manifest()
Construct the expected manifest for a Merge protocol proof.
Verifier for the single-step Goblin ECC op queue merge protocol.
std::vector< FF > Proof
TranscriptFor_t< Curve > Transcript
std::conditional_t< Curve::is_stdlib_type, stdlib::recursion::PairingPoints< Curve >, bb::PairingPoints< Curve > > PairingPoints
ReductionResult reduce_to_pairing_check(const Proof &proof, const InputCommitments &input_commitments)
Reduce the merge proof to a pairing check.
std::array< Commitment, NUM_WIRES > TableCommitments
static Polynomial random(size_t size, size_t start_index=0)
void add_scaled(PolynomialSpan< const Fr > other, const Fr &scaling_factor)
adds the polynomial q(X) 'other', multiplied by a scaling factor.
Fr evaluate(const Fr &z) const
Fr & at(size_t index)
Our mutable accessor, unlike operator[]. We abuse precedent a bit to differentiate at() and operator[...
void factor_roots(const Fr &root)
Divides p(X) by (X-r) in-place. Assumes that p(rⱼ)=0 for all j.
Polynomial p and an opening pair (r,v) such that p(r) = v.
Definition claim.hpp:36
Polynomial polynomial
Definition claim.hpp:41
void add_entry(size_t round, const std::string &element_label, size_t element_size)
void add_challenge(size_t round, const std::string &label)
Add a single challenge label to the manifest for the given round.
static bool check(const Builder &circuit)
Check the witness satisifies the circuit.
typename Group::affine_element AffineElement
Definition bn254.hpp:22
typename Group::element Element
Definition grumpkin.hpp:63
static constexpr bool is_stdlib_type
Definition grumpkin.hpp:67
typename Group::affine_element AffineElement
Definition grumpkin.hpp:64
A simple wrapper around a vector of stdlib field elements representing a proof.
Definition proof.hpp:20
#define info(...)
Definition log.hpp:93
AluTraceBuilder builder
Definition alu.test.cpp:124
testing::Types< stdlib::secp256k1< UltraCircuitBuilder >, stdlib::secp256r1< UltraCircuitBuilder >, stdlib::secp256k1< MegaCircuitBuilder >, stdlib::secp256r1< MegaCircuitBuilder > > CurveTypes
std::filesystem::path bb_crs_path()
void init_file_crs_factory(const std::filesystem::path &path)
Entry point for Barretenberg command-line interface.
Definition api.hpp:5
TEST_F(IPATest, ChallengesAreZero)
Definition ipa.test.cpp:160
TYPED_TEST_SUITE(CommitmentKeyTest, Curves)
field< Bn254FrParams > fr
Definition fr.hpp:155
::testing::Types< curve::BN254, curve::Grumpkin > CurveTypes
OriginTag extract_transcript_tag(const TranscriptType &transcript)
Extract origin tag context from a transcript.
TYPED_TEST(CommitmentKeyTest, CommitToZeroPoly)
CommitmentKey< Curve > ck
STL namespace.
constexpr decltype(auto) get(::tuplet::tuple< T... > &&t) noexcept
Definition tuple.hpp:13
std::string to_string(bb::avm2::ValueTag tag)
This file contains part of the logic for the Origin Tag mechanism that tracks the use of in-circuit p...
size_t transcript_index
constexpr field invert() const noexcept
VectorField result