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, Shift, 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 return op_queue;
70 }
71
76 template <typename T> static auto to_native(const T& val)
77 {
78 if constexpr (IsRecursive) {
79 return val.get_value();
80 } else {
81 return val;
82 }
83 }
84
90 {
91 if constexpr (IsRecursive) {
92 auto commitment = Commitment::from_witness(&builder, native_commitment);
93 commitment.unset_free_witness_tag();
94 return commitment;
95 } else {
96 (void)builder; // Unused in native context
97 return native_commitment;
98 }
99 }
100
106 static Proof create_proof(BuilderType& builder, const std::vector<bb::fr>& native_proof)
107 {
108 if constexpr (IsRecursive) {
109 // Create stdlib::Proof, which is std::vector<stdlib::field_t<Builder>>
110 stdlib::Proof<BuilderType> stdlib_proof(builder, native_proof);
111 // It's already the right type (std::vector<FF>), just return it
112 return stdlib_proof;
113 } else {
114 (void)builder; // Unused in native context
115 return native_proof;
116 }
117 }
118
123 {
124 if constexpr (IsRecursive) {
126 } else {
127 (void)builder; // Unused in native context
128 return true;
129 }
130 }
131
135 static void tamper_with_proof(std::vector<bb::fr>& merge_proof, const TamperProofMode tampering_mode)
136 {
137 const size_t shift_idx = 0; // Index of shift_size in the merge proof
138 const size_t m_commitment_idx = 1; // Index of first commitment to merged table in merge proof
139 const size_t l_eval_idx = 22; // Index of first evaluation of l(1/kappa) in merge proof
140
141 switch (tampering_mode) {
143 // Tamper with the shift size in the proof
144 merge_proof[shift_idx] += bb::fr(1);
145 break;
147 // Tamper with the commitment in the proof
148 auto m_commitment =
149 FrCodec::deserialize_from_fields<curve::BN254::AffineElement>(std::span{ merge_proof }.subspan(
150 m_commitment_idx, FrCodec::calc_num_fields<curve::BN254::AffineElement>()));
151 m_commitment = m_commitment + curve::BN254::AffineElement::one();
152 auto m_commitment_frs = FrCodec::serialize_to_fields<curve::BN254::AffineElement>(m_commitment);
153 for (size_t idx = 0; idx < 4; ++idx) {
154 merge_proof[m_commitment_idx + idx] = m_commitment_frs[idx];
155 }
156 break;
157 }
159 // Tamper with the evaluation in the proof
160 merge_proof[l_eval_idx] -= bb::fr(1);
161 break;
162 default:
163 // Nothing to do
164 break;
165 }
166 }
167
173 const TamperProofMode tampering_mode = TamperProofMode::None,
174 const bool expected = true)
175 {
176 // Create native merge proof
177 auto prover_transcript = std::make_shared<NativeTranscript>();
178 MergeProver merge_prover{ op_queue, prover_transcript };
179 auto native_proof = merge_prover.construct_proof();
180 tamper_with_proof(native_proof, tampering_mode);
181
182 // Construct shifted column polynomials matching the circuit's ecc_op_wire layout
183 auto t_current = op_queue->construct_current_ultra_ops_subtable_columns();
184 auto T_prev = op_queue->construct_table_columns_up_to_tail();
185
188 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
189 native_t_commitments[idx] = merge_prover.pcs_commitment_key.commit(t_current[idx]);
190 native_T_prev_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_prev[idx]);
191 }
192
193 auto T_merged = op_queue->construct_ultra_ops_table_columns();
194 std::array<curve::BN254::AffineElement, NUM_WIRES> expected_merged_commitments;
195 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
196 expected_merged_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_merged[idx]);
197 }
198
199 // Create builder (only used in recursive context)
201
202 // Create commitments and proof in the appropriate context
203 InputCommitments input_commitments;
204 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
205 input_commitments.t_commitments[idx] = create_commitment(builder, native_t_commitments[idx]);
206 input_commitments.T_prev_commitments[idx] = create_commitment(builder, native_T_prev_commitments[idx]);
207 }
208 Proof proof = create_proof(builder, native_proof);
209
210 // Verify the proof
211 auto transcript = std::make_shared<Transcript>();
212 MergeVerifierType verifier{ transcript };
213 auto result = verifier.reduce_to_pairing_check(proof, input_commitments);
214
215 // Perform pairing check and verify
216 bool pairing_verified = result.pairing_points.check();
217 bool verified = pairing_verified && result.reduction_succeeded;
218 EXPECT_EQ(verified, expected);
219
220 // If verification is expected to succeed, also check that the merged table commitments match
221 if (expected) {
222 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
223 EXPECT_EQ(to_native(result.merged_commitments[idx]), expected_merged_commitments[idx])
224 << "Merged table commitment mismatch at index " << idx;
225 }
226 }
227
228 // Check circuit validity (only relevant in recursive context)
229 if constexpr (IsRecursive) {
230 bool circuit_valid = check_circuit(builder);
231 EXPECT_EQ(circuit_valid, expected);
232 }
233 }
234
240 {
241 auto op_queue = construct_final_merge_op_queue();
242
243 // Construct a merge proof and ensure its size matches expectation
244 auto transcript = std::make_shared<NativeTranscript>();
245 MergeProver merge_prover{ op_queue, transcript };
246 auto merge_proof = merge_prover.construct_proof();
247
248 EXPECT_EQ(merge_proof.size(), MERGE_PROOF_SIZE);
249 }
250
254 static void test_single_merge()
255 {
256 auto op_queue = construct_final_merge_op_queue();
257
258 prove_and_verify_merge(op_queue);
259 }
260
265 {
266 auto op_queue = construct_final_merge_op_queue(/*num_subtables_up_to_tail=*/3);
267 prove_and_verify_merge(op_queue);
268 }
269
274 {
275 auto op_queue = construct_final_merge_op_queue();
276
278 }
279
283 static void test_merge_failure()
284 {
285 auto op_queue = construct_final_merge_op_queue();
286
288 }
289
293 static void test_eval_failure()
294 {
295 auto op_queue = construct_final_merge_op_queue();
296
298 }
299
307 {
308 if constexpr (IsRecursive) {
309 GTEST_SKIP() << "Native-only test";
310 return;
311 }
312 if constexpr (!IsRecursive) {
313
314 using InnerFlavor = MegaFlavor;
315 using InnerBuilder = typename InnerFlavor::CircuitBuilder;
318
319 // Build an op queue with some ops to serve as the right (previous) table
320 auto op_queue = std::make_shared<ECCOpQueue>();
321 InnerBuilder circuit{ op_queue };
323 op_queue->merge();
324
325 // Right table = the merged previous table; Left table = empty
327 op_queue->construct_ultra_ops_table_columns(/*include_zk_ops=*/false);
330 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
331 left_table[idx] = Polynomial(0); // empty
332 merged_table[idx] = Polynomial(right_table[idx]); // M = R
333 }
334
335 CommitmentKey ck(right_table[0].size());
336 auto prover_transcript = std::make_shared<NativeTranscript>();
337
338 // === Replicate MergeProver::construct_proof with shift_size = 0 ===
339 prover_transcript->send_to_verifier("shift_size", static_cast<uint32_t>(0));
340
341 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
342 prover_transcript->send_to_verifier("MERGED_TABLE_" + std::to_string(idx),
343 ck.commit(merged_table[idx]));
344 }
345
346 std::array<std::string, 4> degree_labels = { "LEFT_TABLE_DEGREE_CHECK_0",
347 "LEFT_TABLE_DEGREE_CHECK_1",
348 "LEFT_TABLE_DEGREE_CHECK_2",
349 "LEFT_TABLE_DEGREE_CHECK_3" };
350 // Consume challenges to advance transcript state (values unused since L is empty)
351 prover_transcript->template get_challenges<bb::fr>(degree_labels);
352 Polynomial reversed_batched_left_tables(0); // empty
353 prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES",
354 ck.commit(reversed_batched_left_tables));
355
356 std::array<std::string, 13> shplonk_labels = {
357 "SHPLONK_MERGE_BATCHING_CHALLENGE_0", "SHPLONK_MERGE_BATCHING_CHALLENGE_1",
358 "SHPLONK_MERGE_BATCHING_CHALLENGE_2", "SHPLONK_MERGE_BATCHING_CHALLENGE_3",
359 "SHPLONK_MERGE_BATCHING_CHALLENGE_4", "SHPLONK_MERGE_BATCHING_CHALLENGE_5",
360 "SHPLONK_MERGE_BATCHING_CHALLENGE_6", "SHPLONK_MERGE_BATCHING_CHALLENGE_7",
361 "SHPLONK_MERGE_BATCHING_CHALLENGE_8", "SHPLONK_MERGE_BATCHING_CHALLENGE_9",
362 "SHPLONK_MERGE_BATCHING_CHALLENGE_10", "SHPLONK_MERGE_BATCHING_CHALLENGE_11",
363 "SHPLONK_MERGE_BATCHING_CHALLENGE_12"
364 };
365 auto shplonk_batching_challenges = prover_transcript->template get_challenges<bb::fr>(shplonk_labels);
366
367 bb::fr kappa = prover_transcript->template get_challenge<bb::fr>("kappa");
368 bb::fr kappa_inv = kappa.invert();
369
370 // Evaluations at kappa
371 std::vector<bb::fr> evals;
372 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
373 evals.emplace_back(left_table[idx].evaluate(kappa)); // L_j(κ) = 0
374 prover_transcript->send_to_verifier("LEFT_TABLE_EVAL_" + std::to_string(idx), evals.back());
375 }
376 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
377 evals.emplace_back(right_table[idx].evaluate(kappa));
378 prover_transcript->send_to_verifier("RIGHT_TABLE_EVAL_" + std::to_string(idx), evals.back());
379 }
380 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
381 evals.emplace_back(merged_table[idx].evaluate(kappa));
382 prover_transcript->send_to_verifier("MERGED_TABLE_EVAL_" + std::to_string(idx), evals.back());
383 }
384 evals.emplace_back(reversed_batched_left_tables.evaluate(kappa_inv)); // G(κ⁻¹) = 0
385 prover_transcript->send_to_verifier("REVERSED_BATCHED_LEFT_TABLES_EVAL", evals.back());
386
387 // Shplonk batched quotient: Q such that Q·(X-κ)·(X-κ⁻¹) = ...
388 // With L=0 and G=0, only R and M (=R) terms contribute at kappa.
389 size_t poly_size = merged_table[0].size();
390 Polynomial shplonk_batched_quotient(poly_size);
391
392 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
393 // L terms: zero (left_table is empty)
394 // R terms
395 bb::fr beta_r = shplonk_batching_challenges[NUM_WIRES + idx];
396 shplonk_batched_quotient.add_scaled(right_table[idx], beta_r);
397 shplonk_batched_quotient.at(0) -= beta_r * evals[NUM_WIRES + idx];
398 // M terms
399 bb::fr beta_m = shplonk_batching_challenges[2 * NUM_WIRES + idx];
400 shplonk_batched_quotient.add_scaled(merged_table[idx], beta_m);
401 shplonk_batched_quotient.at(0) -= beta_m * evals[2 * NUM_WIRES + idx];
402 }
403 shplonk_batched_quotient.factor_roots(kappa);
404 // G term at kappa_inv: G=0 so nothing to add
405
406 prover_transcript->send_to_verifier("SHPLONK_BATCHED_QUOTIENT", ck.commit(shplonk_batched_quotient));
407
408 // Shplonk opening claim
409 bb::fr z = prover_transcript->template get_challenge<bb::fr>("shplonk_opening_challenge");
410 Polynomial Q_prime(std::move(shplonk_batched_quotient));
411 Q_prime *= -(z - kappa);
412
413 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
414 bb::fr beta_r = shplonk_batching_challenges[NUM_WIRES + idx];
415 Q_prime.add_scaled(right_table[idx], beta_r);
416 Q_prime.at(0) -= beta_r * evals[NUM_WIRES + idx];
417 bb::fr beta_m = shplonk_batching_challenges[2 * NUM_WIRES + idx];
418 Q_prime.add_scaled(merged_table[idx], beta_m);
419 Q_prime.at(0) -= beta_m * evals[2 * NUM_WIRES + idx];
420 }
421 // G term: G=0, so (G-g)·factor = 0, nothing to add
422
423 ProverOpeningClaim<curve::BN254> opening_claim = { .polynomial = std::move(Q_prime),
424 .opening_pair = { z, bb::fr(0) } };
425 KZG<curve::BN254>::compute_opening_proof(ck, opening_claim, prover_transcript);
426
427 auto native_proof = prover_transcript->export_proof();
428
429 // Build input commitments: T_prev = empty (zero commits), t = right table
430 InputCommitments input_commitments;
431 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
432 input_commitments.t_commitments[idx] = ck.commit(right_table[idx]);
433 input_commitments.T_prev_commitments[idx] = ck.commit(left_table[idx]);
434 }
435
436 auto verifier_transcript = std::make_shared<NativeTranscript>();
437 MergeVerifierType verifier{ verifier_transcript };
438 auto result = verifier.reduce_to_pairing_check(native_proof, input_commitments);
439
440 bool pairing_verified = result.pairing_points.check();
441 bool verified = pairing_verified && result.reduction_succeeded;
442 EXPECT_TRUE(verified);
443 } // if constexpr (!IsRecursive)
444 }
445};
446
447// Define test types: native and recursive contexts
448using CurveTypes = ::testing::Types<curve::BN254, // Native
449 stdlib::bn254<MegaCircuitBuilder>, // Recursive (Mega)
450 stdlib::bn254<UltraCircuitBuilder>>; // Recursive (Ultra)
451
453
454TYPED_TEST(MergeTests, MergeProofSizeCheck)
455{
456 TestFixture::test_merge_proof_size();
457}
458
460{
461 TestFixture::test_single_merge();
462}
463
464TYPED_TEST(MergeTests, MultipleMerges)
465{
466 TestFixture::test_multiple_merges();
467}
468
469TYPED_TEST(MergeTests, DegreeCheckFailure)
470{
471 TestFixture::test_degree_check_failure();
472}
473
474TYPED_TEST(MergeTests, MergeFailure)
475{
476 TestFixture::test_merge_failure();
477}
478
480{
481 TestFixture::test_eval_failure();
482}
483
484TYPED_TEST(MergeTests, HonestEmptyLeftTable)
485{
486 TestFixture::test_honest_empty_left_table();
487}
488
502TYPED_TEST(MergeTests, DifferentTranscriptOriginTagFailure)
503{
504 if constexpr (!TestFixture::IsRecursive) {
505 GTEST_SKIP() << "OriginTag tests only apply to recursive context";
506 }
507
508 using BuilderType = typename TestFixture::BuilderType;
509 using MergeVerifierType = typename TestFixture::MergeVerifierType;
510 using Transcript = typename TestFixture::Transcript;
511 constexpr size_t NUM_WIRES = TestFixture::NUM_WIRES;
512
513 // Create single builder for both verifiers (realistic - both in same circuit)
514 BuilderType builder;
515
516 // === Generate two separate merge proofs (simulating two independent merge operations) ===
517 auto op_queue_1 = TestFixture::construct_final_merge_op_queue();
518 auto prover_transcript_1 = std::make_shared<NativeTranscript>();
519 MergeProver prover_1{ op_queue_1, prover_transcript_1 };
520 auto proof_1 = prover_1.construct_proof();
521
522 auto op_queue_2 = TestFixture::construct_final_merge_op_queue();
523 auto prover_transcript_2 = std::make_shared<NativeTranscript>();
524 MergeProver prover_2{ op_queue_2, prover_transcript_2 };
525 auto proof_2 = prover_2.construct_proof();
526
527 // Get native commitments for proof 1 (shifted to match circuit ecc_op_wire layout)
528 auto t_1 = op_queue_1->construct_current_ultra_ops_subtable_columns();
529 auto T_prev_1 = op_queue_1->construct_table_columns_up_to_tail();
531 std::array<curve::BN254::AffineElement, NUM_WIRES> native_T_prev_commitments_1;
532 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
533 native_t_commitments_1[idx] = prover_1.pcs_commitment_key.commit(t_1[idx]);
534 native_T_prev_commitments_1[idx] = prover_1.pcs_commitment_key.commit(T_prev_1[idx]);
535 }
536
537 // === Create first verifier with its own transcript instance ===
538 auto transcript_1 = std::make_shared<Transcript>();
539 [[maybe_unused]] MergeVerifierType verifier_1{ transcript_1 };
540
541 [[maybe_unused]] auto proof_1_recursive = TestFixture::create_proof(builder, proof_1);
542
543 // Create commitments for verifier 1 - these will be "owned" by transcript_1
544 // When we read from the proof using transcript_1, those values get tagged with transcript_1's parent_tag
545 typename MergeVerifierType::InputCommitments input_commitments_1;
546 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
547 input_commitments_1.t_commitments[idx] = TestFixture::create_commitment(builder, native_t_commitments_1[idx]);
548 input_commitments_1.T_prev_commitments[idx] =
549 TestFixture::create_commitment(builder, native_T_prev_commitments_1[idx]);
550 }
551
552 // === Create second verifier with a DIFFERENT transcript instance ===
553 // This simulates having two independent merge verifiers in the same circuit
554 auto transcript_2 = std::make_shared<Transcript>();
555 MergeVerifierType verifier_2{ transcript_2 };
556
557 auto proof_2_recursive = TestFixture::create_proof(builder, proof_2);
558
559 // Get the parent tags to show they're different
560 OriginTag tag_1 = extract_transcript_tag(*transcript_1);
561 OriginTag tag_2 = extract_transcript_tag(*transcript_2);
562
563 info("Verifier 1 transcript_index: ", tag_1.transcript_index);
564 info("Verifier 2 transcript_index: ", tag_2.transcript_index);
565 ASSERT_NE(tag_1.transcript_index, tag_2.transcript_index) << "Transcripts should have different parent tags";
566
567 // === SECURITY VIOLATION: Try to use commitments from proof 1 with verifier 2 ===
568
569 // To make this more realistic, we need to actually receive values from transcript_1 into the commitments
570 // In a real scenario, the verifier would receive_from_prover which tags values with the transcript's parent_tag
571 // For this test, we'll manually tag the commitments as if they came from transcript_1
572 OriginTag transcript_1_tag(tag_1.transcript_index, 0, /*is_submitted=*/true);
573 for (size_t idx = 0; idx < NUM_WIRES; idx++) {
574 // Tag these commitments as if they were read from transcript_1
575 if constexpr (TestFixture::IsRecursive) {
576 input_commitments_1.t_commitments[idx].set_origin_tag(transcript_1_tag);
577 input_commitments_1.T_prev_commitments[idx].set_origin_tag(transcript_1_tag);
578 }
579 }
580
581 // Now try to verify proof_2 using verifier_2 (with transcript_2) but with commitments tagged for transcript_1
582 // When verifier_2 reads from proof_2_recursive using transcript_2, those values will have tag_2.parent_tag
583 // When it tries to mix them with input_commitments_1 (which have tag_1.parent_tag), the check should trigger
584 info("Attempting to mix transcript_1 commitments with transcript_2 proof verification...");
585
586 // Catch the exception and verify it's the expected cross-transcript error
587#ifndef NDEBUG
588 EXPECT_THROW_WITH_MESSAGE([[maybe_unused]] auto result =
589 verifier_2.reduce_to_pairing_check(proof_2_recursive, input_commitments_1),
590 "Tags from different transcripts were involved in the same computation");
591#endif
592}
593
598class MergeTranscriptTests : public ::testing::Test {
599 public:
601
609 {
610 TranscriptManifest manifest_expected;
611 constexpr size_t NUM_WIRES = 4;
612
613 // Size calculations
614 size_t frs_per_Fr = 1; // Native field element
615 size_t frs_per_G = FrCodec::calc_num_fields<curve::BN254::AffineElement>(); // Commitment = 4 frs
616 size_t frs_per_uint32 = 1; // shift_size
617
618 size_t round = 0;
619
620 // Round 0: Prover sends shift_size and merged table commitments, gets degree check challenges
621 manifest_expected.add_entry(round, "shift_size", frs_per_uint32);
622 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
623 manifest_expected.add_entry(round, "MERGED_TABLE_" + std::to_string(idx), frs_per_G);
624 }
625 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_0");
626 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_1");
627 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_2");
628 manifest_expected.add_challenge(round, "LEFT_TABLE_DEGREE_CHECK_3");
629
630 // Round 1: Batching challenges + kappa, then send batched polynomial commitment
631 round++;
632 for (size_t idx = 0; idx < 13; ++idx) {
633 manifest_expected.add_challenge(round, "SHPLONK_MERGE_BATCHING_CHALLENGE_" + std::to_string(idx));
634 }
635 manifest_expected.add_challenge(round, "kappa");
636 manifest_expected.add_entry(round, "REVERSED_BATCHED_LEFT_TABLES", frs_per_G);
637
638 // Round 2: Shplonk opening challenge, then send all evaluations and quotient
639 round++;
640 manifest_expected.add_challenge(round, "shplonk_opening_challenge");
641 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
642 manifest_expected.add_entry(round, "LEFT_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
643 }
644 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
645 manifest_expected.add_entry(round, "RIGHT_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
646 }
647 for (size_t idx = 0; idx < NUM_WIRES; ++idx) {
648 manifest_expected.add_entry(round, "MERGED_TABLE_EVAL_" + std::to_string(idx), frs_per_Fr);
649 }
650 manifest_expected.add_entry(round, "REVERSED_BATCHED_LEFT_TABLES_EVAL", frs_per_Fr);
651 manifest_expected.add_entry(round, "SHPLONK_BATCHED_QUOTIENT", frs_per_G);
652
653 // Round 3: KZG:W commitment
654 round++;
655 manifest_expected.add_entry(round, "KZG:W", frs_per_G);
656
657 return manifest_expected;
658 }
659};
660
664TEST_F(MergeTranscriptTests, ProverManifestConsistency)
665{
667
668 // Construct merge proof with manifest enabled
669 auto transcript = std::make_shared<NativeTranscript>();
670 transcript->enable_manifest();
671 MergeProver merge_prover{ op_queue, transcript };
672 auto merge_proof = merge_prover.construct_proof();
673
674 // Check prover manifest matches expected manifest
675 auto manifest_expected = construct_merge_manifest();
676 auto prover_manifest = transcript->get_manifest();
677
678 ASSERT_GT(manifest_expected.size(), 0);
679 ASSERT_EQ(prover_manifest.size(), manifest_expected.size())
680 << "Prover manifest has " << prover_manifest.size() << " rounds, expected " << manifest_expected.size();
681
682 for (size_t round = 0; round < manifest_expected.size(); ++round) {
683 ASSERT_EQ(prover_manifest[round], manifest_expected[round]) << "Prover manifest discrepancy in round " << round;
684 }
685}
686
690TEST_F(MergeTranscriptTests, VerifierManifestConsistency)
691{
693
694 // Generate merge proof with prover manifest enabled
695 auto prover_transcript = std::make_shared<NativeTranscript>();
696 prover_transcript->enable_manifest();
697 MergeProver merge_prover{ op_queue, prover_transcript };
698 auto merge_proof = merge_prover.construct_proof();
699
700 // Construct commitments for verifier (shifted to match circuit ecc_op_wire layout)
701 MergeVerifier::InputCommitments merge_commitments;
702 auto t_current = op_queue->construct_current_ultra_ops_subtable_columns();
703 auto T_prev = op_queue->construct_table_columns_up_to_tail();
704 for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) {
705 merge_commitments.t_commitments[idx] = merge_prover.pcs_commitment_key.commit(t_current[idx]);
706 merge_commitments.T_prev_commitments[idx] = merge_prover.pcs_commitment_key.commit(T_prev[idx]);
707 }
708
709 // Verify proof with verifier manifest enabled
710 auto verifier_transcript = std::make_shared<NativeTranscript>();
711 verifier_transcript->enable_manifest();
712 MergeVerifier merge_verifier{ verifier_transcript };
713 auto result = merge_verifier.reduce_to_pairing_check(merge_proof, merge_commitments);
714
715 // Verification should succeed
716 ASSERT_TRUE(result.pairing_points.check() && result.reduction_succeeded);
717
718 // Check prover and verifier manifests match
719 auto prover_manifest = prover_transcript->get_manifest();
720 auto verifier_manifest = verifier_transcript->get_manifest();
721
722 ASSERT_GT(prover_manifest.size(), 0);
723 ASSERT_EQ(prover_manifest.size(), verifier_manifest.size())
724 << "Prover has " << prover_manifest.size() << " rounds, verifier has " << verifier_manifest.size();
725
726 for (size_t round = 0; round < prover_manifest.size(); ++round) {
727 ASSERT_EQ(prover_manifest[round], verifier_manifest[round])
728 << "Prover/Verifier manifest discrepancy in round " << round;
729 }
730}
731
732} // namespace bb
#define EXPECT_THROW_WITH_MESSAGE(code, expectedMessageRegex)
Definition assert.hpp:193
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 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:43
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)
static void test_honest_empty_left_table()
Test that an honestly constructed merge proof with shift_size = 0 (empty left table) verifies.
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 degree(l) > shift_size (as read from the proof)
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
Structured polynomial class that represents the coefficients 'a' of a_0 + a_1 x .....
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:19
#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:155
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