diff --git a/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp index 18c7bc0f86af..610d672f8e3c 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/goblin_bench/eccvm.bench.cpp @@ -2,7 +2,9 @@ #include "barretenberg/eccvm/eccvm_circuit_builder.hpp" #include "barretenberg/eccvm/eccvm_prover.hpp" +#include "barretenberg/eccvm/eccvm_test_utils.hpp" #include "barretenberg/eccvm/eccvm_verifier.hpp" +#include "barretenberg/srs/global_crs.hpp" using namespace benchmark; using namespace bb; @@ -40,13 +42,16 @@ Builder generate_trace(size_t target_num_gates) op_queue->merge(); } + // Add hiding op (required before creating the builder) + eccvm_test_utils::add_hiding_op_for_test(op_queue); + Builder builder{ op_queue }; return builder; } void eccvm_generate_prover(State& state) noexcept { - + srs::init_file_crs_factory(bb::srs::bb_crs_path()); size_t target_num_gates = 1 << static_cast(state.range(0)); for (auto _ : state) { Builder builder = generate_trace(target_num_gates); @@ -57,7 +62,7 @@ void eccvm_generate_prover(State& state) noexcept void eccvm_prove(State& state) noexcept { - + srs::init_file_crs_factory(bb::srs::bb_crs_path()); size_t target_num_gates = 1 << static_cast(state.range(0)); Builder builder = generate_trace(target_num_gates); std::shared_ptr prover_transcript = std::make_shared(); diff --git a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/mega_honk.bench.cpp b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/mega_honk.bench.cpp index 6c79987705f3..0a4370cbd353 100644 --- a/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/mega_honk.bench.cpp +++ b/barretenberg/cpp/src/barretenberg/benchmark/ultra_bench/mega_honk.bench.cpp @@ -1,13 +1,15 @@ #include #include "barretenberg/benchmark/ultra_bench/mock_circuits.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" +#include "barretenberg/ultra_honk/ultra_prover.hpp" using namespace benchmark; using namespace bb; /** - * @brief Benchmark: Construction of a Ultra Honk proof for a circuit determined by the provided circuit function + * @brief Benchmark: Construction of a Mega Honk proof for a circuit determined by the provided circuit function */ static void construct_proof_megahonk(State& state, void (*test_circuit_function)(MegaCircuitBuilder&, size_t)) noexcept { @@ -16,9 +18,6 @@ static void construct_proof_megahonk(State& state, void (*test_circuit_function) state, test_circuit_function, num_iterations); } -/** - * @brief Benchmark: Construction of a Ultra Honk proof with 2**n gates - */ static void construct_proof_megahonk_power_of_2(State& state) noexcept { auto log2_of_gates = static_cast(state.range(0)); @@ -26,6 +25,13 @@ static void construct_proof_megahonk_power_of_2(State& state) noexcept state, &bb::mock_circuits::generate_basic_arithmetic_circuit, log2_of_gates); } +static void construct_proof_multi_megahonk_power_of_2(State& state) noexcept +{ + auto log2_of_gates = static_cast(state.range(0)); + bb::mock_circuits::construct_proof_with_specified_num_iterations( + state, &bb::mock_circuits::generate_basic_arithmetic_circuit, log2_of_gates); +} + static void get_row_power_of_2(State& state) noexcept { auto log2_of_gates = static_cast(state.range(0)); @@ -41,9 +47,6 @@ static void get_row_power_of_2(State& state) noexcept } } -// Define benchmarks - -// This exists due to an issue where get_row was blowing up in time BENCHMARK_CAPTURE(construct_proof_megahonk, sha256, &generate_sha256_test_circuit) ->Unit(kMillisecond); BENCHMARK_CAPTURE(construct_proof_megahonk, @@ -51,14 +54,8 @@ BENCHMARK_CAPTURE(construct_proof_megahonk, &stdlib::generate_ecdsa_verification_test_circuit) ->Unit(kMillisecond); -BENCHMARK(get_row_power_of_2) - // 2**15 gates to 2**20 gates - ->DenseRange(15, 20) - ->Unit(kMillisecond); - -BENCHMARK(construct_proof_megahonk_power_of_2) - // 2**15 gates to 2**20 gates - ->DenseRange(15, 20) - ->Unit(kMillisecond); +BENCHMARK(get_row_power_of_2)->DenseRange(15, 20)->Unit(kMillisecond); +BENCHMARK(construct_proof_megahonk_power_of_2)->DenseRange(15, 20)->Unit(kMillisecond); +BENCHMARK(construct_proof_multi_megahonk_power_of_2)->DenseRange(16, 19)->Unit(kMillisecond); BENCHMARK_MAIN(); diff --git a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_prover.cpp b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_prover.cpp index 95b86f056781..c94f6168a949 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_prover.cpp @@ -321,16 +321,19 @@ void BatchedHonkTranslatorProver::execute_joint_pcs() auto mega_zk_shifted = mega_zk_inst->polynomials.get_to_be_shifted(); auto trans_shifted = translator_key->proving_key->polynomials.get_pcs_to_be_shifted(); auto joint_shifted = concatenate(mega_zk_shifted, trans_shifted); - polynomial_batcher.set_to_be_shifted_by_one(joint_shifted); + polynomial_batcher.set_to_be_shifted(joint_shifted); // Register MegaZK masking tails with the joint batcher if (mega_zk_inst->masking_tail_data.is_active()) { mega_zk_inst->masking_tail_data.add_tails_to_batcher(mega_zk_inst->polynomials, polynomial_batcher); } + const auto rho = transcript->template get_challenge("rho"); + const OpeningClaim prover_opening_claim = ShpleminiProver_::prove(joint_circuit_size, polynomial_batcher, + rho, joint_challenge, ck, transcript, diff --git a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.cpp b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.cpp index 51ed69e37e63..61a4a4521da3 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.cpp @@ -53,8 +53,8 @@ typename BatchedHonkTranslatorVerifier_::OinkResult BatchedHonkTranslator return OinkResult{ .public_inputs = mega_zk_verifier_instance->public_inputs, - .calldata_commitment = mega_zk_verifier_instance->witness_commitments.calldata, - .ecc_op_wires = mega_zk_verifier_instance->witness_commitments.get_ecc_op_wires().get_copy(), + .calldata_commitment = mega_zk_verifier_instance->received_commitments.calldata, + .ecc_op_wires = mega_zk_verifier_instance->received_commitments.get_ecc_op_wires().get_copy(), }; } @@ -287,14 +287,20 @@ typename BatchedHonkTranslatorVerifier_::ReductionResult BatchedHonkTrans } }(); - // Translator claim components (translator first: its masking poly must be at position 0 for Shplemini offset=2). + // Translator claim components. auto concat_shift_evals = TranslatorFlavor::reconstruct_concatenated_evaluations( trans_evals.get_groups_to_be_concatenated_shifted(), std::span(joint_challenge)); - RefVector joint_unshifted_comms = trans_commitments.get_pcs_unshifted(); - RefVector joint_unshifted_evals = trans_evals.get_pcs_unshifted(); + auto trans_unshifted_comms = trans_commitments.get_pcs_unshifted(); + auto trans_unshifted_evals = trans_evals.get_pcs_unshifted(); + auto trans_shifted_comms = trans_commitments.get_pcs_to_be_shifted(); + auto trans_pcs_shifted_evals = trans_evals.get_pcs_shifted(); + + // Build joint claim batchers: translator-first in unshifted, MegaZK-first in shifted (matching prover). + RefVector joint_unshifted_comms = trans_unshifted_comms; + RefVector joint_unshifted_evals = trans_unshifted_evals; - // Append MegaZK unshifted (no masking poly — translator provides the joint masking poly). + // Extend unshifted with MegaZK entries. for (auto& comm : mega_zk_commitments.get_unshifted()) { joint_unshifted_comms.push_back(comm); } @@ -302,12 +308,10 @@ typename BatchedHonkTranslatorVerifier_::ReductionResult BatchedHonkTrans joint_unshifted_evals.push_back(eval); } - // Shifted: MegaZK first, then translator. + // Shifted: MegaZK first, then translator (matching prover ordering). RefVector joint_shifted_comms = mega_zk_commitments.get_to_be_shifted(); RefVector joint_shifted_evals = mega_zk_evals.get_shifted(); - auto trans_shifted_comms = trans_commitments.get_pcs_to_be_shifted(); - auto trans_pcs_shifted_evals = trans_evals.get_pcs_shifted(); for (auto& comm : trans_shifted_comms) { joint_shifted_comms.push_back(comm); } @@ -351,7 +355,8 @@ typename BatchedHonkTranslatorVerifier_::ReductionResult BatchedHonkTrans { // Reconstruct MegaZK commitments from the stored verifier instance. MegaZKVerifierCommitments mega_zk_commitments{ mega_zk_verifier_instance->get_vk(), - mega_zk_verifier_instance->witness_commitments }; + mega_zk_verifier_instance->received_commitments }; + auto trans_commitments = verify_translator_oink( joint_proof, evaluation_input_x, batching_challenge_v, accumulated_result, op_queue_wire_commitments); bool sumcheck_verified = verify_joint_sumcheck(); diff --git a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.hpp b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.hpp index 51dde12dd1ab..c7d043d93953 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/batched_honk_translator/batched_honk_translator_verifier.hpp @@ -57,32 +57,48 @@ template class BatchedHonkTranslatorVerifier_ { using TransBF = typename TransFlavor::BF; // Joint RepeatedCommitmentsData for Shplemini's remove_repeated_commitments optimization. - // Joint unshifted = [Trans_unshifted(TU), MZK_unshifted(P+W)]. The translator's gemini_masking_poly - // is at position 0 of unshifted and is consumed by Shplemini's offset=2 (Q + masking). - // After Shplemini's offset=2, the virtual layout is: - // Unshifted: [Trans_rest(TU-1) | MZK_precomputed(P) | MZK_witness(W)] - // Shifted: [MZK_shifted(S) | Trans_shifted(TS)] + // After Shplemini's offset=2 (Q_commitment + translator_masking_poly), the virtual layout is: + // Unshifted: [Trans_unshifted_no_masking(TU-1) | MegaZK_precomputed(P) | MegaZK_witness(W)] + // Shifted: [MegaZK_shifted(S) | Trans_shifted(TS)] // - // Range 1 (Translator merged): ordered(5)+z_perm(1)+concat(5) in unshifted ↔ same in shifted - // Range 2 (MegaZK): witness[0..S-1] ↔ mega_zk_shifted[0..S-1] + // Range 1 (Translator): ordered(5)+z_perm(1) in unshifted ↔ same in shifted + // Range 2 (Translator): concat(5) in unshifted ↔ same in shifted + // Combined into two DuplicateRanges. static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = [] { - constexpr size_t TU = TranslatorFlavor::NUM_PCS_UNSHIFTED; // includes masking(1) constexpr size_t P = MegaZKFlavorT::NUM_PRECOMPUTED_ENTITIES; - constexpr size_t W = MegaZKFlavorT::NUM_WITNESS_ENTITIES; + constexpr size_t W = MegaZKFlavorT::REPEATED_COMMITMENTS.first.duplicate_start - + MegaZKFlavorT::REPEATED_COMMITMENTS.first.original_start; constexpr size_t S = MegaZKFlavorT::NUM_SHIFTED_ENTITIES; - // Translator repeated: ordered(5)+z_perm(1)+concat(5) in Trans_rest ↔ Trans_shifted - // Trans_rest starts at virtual 0; repeated starts at ordered_extra(1)+op(1)=2 - constexpr size_t TRANS_REPEAT_START = TranslatorFlavor::REPEATED_COMMITMENTS.first.original_start; - constexpr size_t TRANS_REPEAT_COUNT = - TranslatorFlavor::REPEATED_COMMITMENTS.first.count + TranslatorFlavor::REPEATED_COMMITMENTS.second.count; - // In shifted section: op_queue entries precede the repeated entries - constexpr size_t TRANS_SHIFTED_SKIP = TranslatorFlavor::NUM_PCS_TO_BE_SHIFTED - TRANS_REPEAT_COUNT; - return RepeatedCommitmentsData(TRANS_REPEAT_START, // Translator original in unshifted - (TU - 1) + P + W + S + TRANS_SHIFTED_SKIP, // Translator duplicate in shifted - TRANS_REPEAT_COUNT, // Translator count - (TU - 1) + P, // MegaZK original: witness start in unshifted - (TU - 1) + P + W, // MegaZK duplicate: shifted start - S); // MegaZK count + constexpr size_t TU = TranslatorFlavor::NUM_PCS_UNSHIFTED; + constexpr size_t TS = TranslatorFlavor::NUM_PCS_TO_BE_SHIFTED; + // In the joint prover poly array (after offset=2 consumes Q + trans masking): + // Unshifted: [ordered_extra(1), op(1), ordered(5), z_perm(1), concat(5), mega_precomputed(P), mega_witness(W)] + // Shifted: [mega_shifted(S), op_queue(3), ordered(5), z_perm(1), concat(5)] + // + // Translator repeated: + // Range 1: ordered(5)+z_perm(1) at unshifted[2..7] ↔ shifted[(TU-1)+P+W+S+3..(TU-1)+P+W+S+8] + // Range 2: concat(5) at unshifted[8..12] ↔ shifted[(TU-1)+P+W+S+9..(TU-1)+P+W+S+13] + // MegaZK repeated: + // witness ↔ mega_shifted: unshifted[(TU-1)+P-1..] ↔ shifted[(TU-1)+P+W-1..] + // (using MegaZK standalone offsets P-1, P+W-1 shifted by TU-1) + // + // We use two DuplicateRanges. Range 1 = translator ordered+z_perm+concat, Range 2 = MegaZK. + constexpr size_t TRANS_ORIG_START = + TranslatorFlavor::REPEATED_COMMITMENTS.first.original_start; // 2 (ordered[0]) + constexpr size_t TRANS_TOTAL_COUNT = TranslatorFlavor::REPEATED_COMMITMENTS.first.count + + TranslatorFlavor::REPEATED_COMMITMENTS.second.count; // 6+5=11 + // op_queue skip in shifted = 3 + constexpr size_t TRANS_SHIFTED_SKIP = TS - TRANS_TOTAL_COUNT; // 14-11 = 3 (op_queue wires are not repeated) + constexpr size_t MEGA_ZK_ORIG = (TU - 1) + MegaZKFlavorT::REPEATED_COMMITMENTS.first.original_start; + constexpr size_t MEGA_ZK_DUP = (TU - 1) + MegaZKFlavorT::REPEATED_COMMITMENTS.first.duplicate_start; + return RepeatedCommitmentsData(TRANS_ORIG_START, // Trans original: ordered[0] in unshifted + (TU - 1) + P + W + S + + TRANS_SHIFTED_SKIP, // Trans duplicate: ordered[0] in shifted + TRANS_TOTAL_COUNT, // Trans count: 11 + MEGA_ZK_ORIG, // MegaZK original: witness start in unshifted + MEGA_ZK_DUP, // MegaZK duplicate: mega_shifted start in shifted + S, // MegaZK count + 2 /* shplemini_offset: Q + translator_masking_poly */); }(); /** diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp index 23f5249ad317..b8d34dcce94d 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk.cpp @@ -158,7 +158,7 @@ Chonk::perform_recursive_verification_and_databus_consistency_checks( } // Extract the witness commitments and public inputs from the incoming verifier instance - WitnessCommitments witness_commitments = std::move(verifier_instance->witness_commitments); + WitnessCommitments witness_commitments = std::move(verifier_instance->received_commitments); std::vector public_inputs = std::move(verifier_instance->public_inputs); if (verifier_inputs.is_kernel) { diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/claim_batcher.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/claim_batcher.hpp index 4f7e3370c2da..8420938f8d15 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/claim_batcher.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/claim_batcher.hpp @@ -35,7 +35,8 @@ template struct ClaimBatcher_ { }; std::optional unshifted; // commitments and evaluations of unshifted polynomials - std::optional shifted; // commitments of to-be-shifted-by-1 polys, evals of their shifts + std::optional shifted; // commitments of to-be-shifted polys, evals of their shifts + size_t shift_exponent = 1; // shift depth: 1 for standard (G/X), k for interleaved (G/X^k) Batch get_unshifted() { return (unshifted) ? *unshifted : Batch{}; } Batch get_shifted() { return (shifted) ? *shifted : Batch{}; } @@ -47,9 +48,10 @@ template struct ClaimBatcher_ { * @details Computes scalars s_0, s_1 given by * \f[ * - s_0 = \left(\frac{1}{z-r} + \nu \times \frac{1}{z+r}\right) \f], - * - s_1 = \frac{1}{r} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) + * - s_1 = \frac{1}{r^k} \times \left(\frac{1}{z-r} - \nu \times \frac{1}{z+r}\right) * \f] - * where the scalars used to batch the claims are given by + * where k is the shift_exponent member (1 for standard shifts, BS for interleaved polynomials), + * and the scalars used to batch the claims are given by * \f[ * \left( * - s_0, @@ -77,9 +79,25 @@ template struct ClaimBatcher_ { unshifted->scalar = inverse_vanishing_eval_pos + nu_challenge * inverse_vanishing_eval_neg; } if (shifted) { - // r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) - shifted->scalar = - r_challenge.invert() * (inverse_vanishing_eval_pos - nu_challenge * inverse_vanishing_eval_neg); + // r⁻ᵏ ⋅ (1/(z−r) + (-1)^k ⋅ ν/(z+r)) where k is the shift_exponent + // This comes from A₀₋(X) = F(X) + (-1)^k · G(X)/r^k, needed because (-r)^k = (-1)^k · r^k + // For standard shifts k=1 (odd): r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) + // For interleaved shifts k=4 (even): r⁻⁴ ⋅ (1/(z−r) + ν/(z+r)) + if (shift_exponent == 1) { + // Fast path: avoid extra multiplication by neg_sign (important for recursive verifiers) + shifted->scalar = + r_challenge.invert() * (inverse_vanishing_eval_pos - nu_challenge * inverse_vanishing_eval_neg); + } else { + Fr r_power = r_challenge; + for (size_t i = 1; i < shift_exponent; ++i) { + r_power *= r_challenge; + } + const Fr r_inv_shift = r_power.invert(); + // (-1)^k: even k gives +1, odd k gives -1 (but k=1 handled above) + const Fr neg_sign = (shift_exponent % 2 == 0) ? Fr(1) : Fr(-1); + shifted->scalar = + r_inv_shift * (inverse_vanishing_eval_pos + neg_sign * nu_challenge * inverse_vanishing_eval_neg); + } } } /** diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp index a504ed460989..6b255eefd1fa 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/commitment_key.hpp @@ -165,6 +165,42 @@ template class CommitmentKey { }; CommitBatch start_batch() { return CommitBatch{ this, {}, {} }; } + + /** + * @brief Commit to an interleaved group of polynomials without materializing the full buffer. + * @details Computes [F] where F(X) = Σⱼ fⱼ(X^{batch_size}) · X^j for j=0..batch_size-1. + * If fewer than BATCH_SIZE chunks are provided, missing slots are zero. + * @param chunks Span of polynomial spans representing the group members + */ + template Commitment commit_interleaved(std::span> chunks) const + { + // BS=1: degenerate case — just commit the single polynomial directly + if constexpr (BATCH_SIZE == 1) { + BB_ASSERT(chunks.size() == 1, "commit_interleaved<1> expects exactly 1 chunk"); + return commit(chunks[0]); + } else { + if (chunks.size() > BATCH_SIZE) { + throw_or_abort("commit_interleaved: chunks.size() must be <= BATCH_SIZE"); + } + std::span point_table = get_monomial_points(); + + size_t n = 0; + for (const auto& chunk : chunks) { + n = std::max(n, chunk.end_index()); + } + const size_t total_size = n * BATCH_SIZE; + + if (total_size > get_monomial_size()) { + throw_or_abort(format("Attempting to commit to interleaved polynomial that needs ", + total_size, + " points with an SRS of size ", + get_monomial_size())); + } + + return scalar_multiplication::pippenger_interleaved( + chunks, std::span{ point_table.data(), total_size }, BATCH_SIZE); + } + } }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp index 7756469a1a96..5f580daaf80c 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini.hpp @@ -113,11 +113,11 @@ template class GeminiProver_ { * protocol * @details Opening multivariate polynomials using Gemini requires the computation of batched polynomials. * The first, here denoted A₀, is a linear combination of all polynomials to be opened. If we denote the linear - * combinations (based on challenge rho) of the unshifted and to-be-shifted-by-1 polynomials by F and G - * respectively, then A₀ = F + G/X. This polynomial is "folded" in Gemini to produce d-1 univariate polynomials + * combinations (based on challenge rho) of the unshifted and to-be-shifted polynomials by F and G + * respectively, then A₀ = F + G/X^k. This polynomial is "folded" in Gemini to produce d-1 univariate polynomials * Fold_i, i = 1, ..., d-1. The second and third are the partially evaluated batched polynomials - * A₀₊ = F + G/r, and A₀₋ = F - G/r. These are required in order to prove the opening of shifted polynomials - * G_i/X from the commitments to their unshifted counterparts G_i. + * A₀₊ = F + G/r^k, and A₀₋ = F + (-1)^k · G/r^k. These are required in order to prove the opening of shifted + * polynomials G_i/X^k from the commitments to their unshifted counterparts G_i. * @note TODO(https://github.com/AztecProtocol/barretenberg/issues/1223): There are certain operations herein * that could be made more efficient by e.g. reusing already initialized polynomials, possibly at the expense of * clarity. @@ -126,9 +126,10 @@ template class GeminiProver_ { size_t full_batched_size = 0; // size of the full batched polynomial (generally the circuit size) size_t actual_data_size_ = 0; // max end_index across all polynomials (actual data extent) + size_t shift_exponent = 1; // shift depth: 1 for standard (G/X), k for multi-linear (G/X^k) - Polynomial batched_unshifted; // linear combination of unshifted polynomials - Polynomial batched_to_be_shifted_by_one; // linear combination of to-be-shifted polynomials + Polynomial batched_unshifted; // linear combination of unshifted polynomials + Polynomial batched_to_be_shifted; // linear combination of to-be-shifted polynomials // Batched tails: small polynomials covering only the tail region (e.g. last NUM_MASKED_ROWS positions). // Populated during compute_batched if tails are registered. @@ -136,27 +137,30 @@ template class GeminiProver_ { Polynomial batched_shifted_tail_; public: - RefVector unshifted; // set of unshifted polynomials - RefVector to_be_shifted_by_one; // set of polynomials to be left shifted by 1 + RefVector unshifted; // set of unshifted polynomials + RefVector to_be_shifted; // set of polynomials to be left shifted by shift_exponent // Tails: small polynomials (e.g. masking values) to be batched with the same rho scalar // as their corresponding base polynomial. Pairs of (index in unshifted/shifted list, tail poly). std::vector> unshifted_tails_; std::vector> shifted_tails_; - PolynomialBatcher(const size_t full_batched_size, const size_t actual_data_size = 0) + PolynomialBatcher(const size_t full_batched_size, const size_t actual_data_size = 0, size_t shift_exponent = 1) : full_batched_size(full_batched_size) , actual_data_size_(actual_data_size == 0 ? full_batched_size : actual_data_size) + , shift_exponent(shift_exponent) , batched_unshifted(actual_data_size_, full_batched_size) - , batched_to_be_shifted_by_one(Polynomial::shiftable(actual_data_size_, full_batched_size)) + , batched_to_be_shifted(Polynomial::shiftable(actual_data_size_, full_batched_size, shift_exponent)) {} bool has_unshifted() const { return unshifted.size() > 0; } - bool has_to_be_shifted_by_one() const { return to_be_shifted_by_one.size() > 0; } + bool has_to_be_shifted() const { return to_be_shifted.size() > 0; } + + size_t get_shift_exponent() const { return shift_exponent; } // Set references to the polynomials to be batched void set_unshifted(RefVector polynomials) { unshifted = polynomials; } - void set_to_be_shifted_by_one(RefVector polynomials) { to_be_shifted_by_one = polynomials; } + void set_to_be_shifted(RefVector polynomials) { to_be_shifted = polynomials; } void add_unshifted_tail(size_t batcher_index, Polynomial&& tail) { @@ -168,9 +172,9 @@ template class GeminiProver_ { } /** - * @brief Compute batched polynomial A₀ = F + G/X as the linear combination of all polynomials to be opened, - * where F is the linear combination of the unshifted polynomials and G is the linear combination of the - * to-be-shifted-by-1 polynomials. + * @brief Compute batched polynomial A₀ = F + G/X^k as the linear combination of all polynomials to be opened, + * where F is the linear combination of the unshifted polynomials, G is the linear combination of the + * to-be-shifted polynomials, and k is the shift_exponent. * * @param challenge batching challenge * @return Polynomial A₀ @@ -178,6 +182,7 @@ template class GeminiProver_ { Polynomial compute_batched(const Fr& challenge) { BB_BENCH_NAME("compute_batched"); + Fr running_scalar(1); // Batch base polynomials; updates running_scalar in place @@ -215,20 +220,48 @@ template class GeminiProver_ { } Fr shifted_base = running_scalar; - if (has_to_be_shifted_by_one()) { - batch(batched_to_be_shifted_by_one, to_be_shifted_by_one); - full_batched += batched_to_be_shifted_by_one.shifted(); + if (has_to_be_shifted()) { + batch(batched_to_be_shifted, to_be_shifted); + full_batched += batched_to_be_shifted.shifted(shift_exponent); } batch_tails(batched_shifted_tail_, shifted_tails_, shifted_base); if (!batched_shifted_tail_.is_empty()) { - full_batched += batched_shifted_tail_.shifted(); + full_batched += batched_shifted_tail_.shifted(shift_exponent); } return full_batched; } /** - * @brief Compute partially evaluated batched polynomials A₀(X, r) = A₀₊ = F + G/r, A₀(X, -r) = A₀₋ = F - G/r + * @brief Compute rho-batched F and G (merging tails), return ownership. + * @details After calling compute_batched(rho), this extracts the accumulated F and G + * (including tails), allowing delegation to the pre-batched Gemini::prove overload. + * @return {F, G, shift_exponent} + */ + std::tuple extract_batched_pair() + { + // Merge tails into the main batched polynomials + if (!batched_unshifted_tail_.is_empty()) { + Polynomial extended(batched_unshifted, full_batched_size - batched_unshifted.start_index()); + extended += batched_unshifted_tail_; + batched_unshifted = std::move(extended); + } + if (!batched_shifted_tail_.is_empty()) { + batched_to_be_shifted += batched_shifted_tail_; + } + return { std::move(batched_unshifted), std::move(batched_to_be_shifted), shift_exponent }; + } + + /** + * @brief Compute partially evaluated batched polynomials A₀₊ and A₀₋ + * @details We need A₀₊(r) = A₀(r) and A₀₋(-r) = A₀(-r), where A₀(X) = F(X) + G(X)/X^k. + * Since (-r)^k = (-1)^k · r^k, the correct formulas are: + * A₀₊(X) = F(X) + G(X)/r^k + * A₀₋(X) = F(X) + (-1)^k · G(X)/r^k + * + * @note When k is even (e.g. k=4 for interleaved shifts), A₀₊ = A₀₋. This means the prover + * computes the same polynomial twice. A future optimization could detect this and avoid the + * redundant computation and commitment opening. * * @param r_challenge partial evaluation challenge * @return std::pair {A₀₊, A₀₋} @@ -248,16 +281,30 @@ template class GeminiProver_ { Polynomial A_0_neg = A_0_pos; - Fr r_inv = r_challenge.invert(); - if (has_to_be_shifted_by_one()) { - batched_to_be_shifted_by_one *= r_inv; - A_0_pos += batched_to_be_shifted_by_one; - A_0_neg -= batched_to_be_shifted_by_one; + const Fr r_inv_shift = (has_to_be_shifted() || !batched_shifted_tail_.is_empty()) + ? r_challenge.pow(shift_exponent).invert() + : Fr(0); // unused, but avoids uninitialized read + + if (has_to_be_shifted()) { + batched_to_be_shifted *= r_inv_shift; // G = G/r^k + + A_0_pos += batched_to_be_shifted; // A₀₊ += G/r^k + // A₀₋(X) = F(X) + (-1)^k · G(X)/r^k so that A₀₋(-r) = A₀(-r) + // since (-r)^k = (-1)^k · r^k. For odd k: subtract. For even k: add. + if (shift_exponent % 2 == 0) { + A_0_neg += batched_to_be_shifted; // A₀₋ += G/r^k (even shift) + } else { + A_0_neg -= batched_to_be_shifted; // A₀₋ -= G/r^k (odd shift) + } } if (!batched_shifted_tail_.is_empty()) { - batched_shifted_tail_ *= r_inv; + batched_shifted_tail_ *= r_inv_shift; A_0_pos += batched_shifted_tail_; - A_0_neg -= batched_shifted_tail_; + if (shift_exponent % 2 == 0) { + A_0_neg += batched_shifted_tail_; + } else { + A_0_neg -= batched_shifted_tail_; + } } return { A_0_pos, A_0_neg }; @@ -278,6 +325,23 @@ template class GeminiProver_ { template static std::vector prove(size_t circuit_size, PolynomialBatcher& polynomial_batcher, + const Fr& rho, + std::span multilinear_challenge, + const CommitmentKey& commitment_key, + const std::shared_ptr& transcript, + bool has_zk = false); + + /** + * @brief Gemini prove from pre-batched polynomials F and G (bypasses PolynomialBatcher). + * @details Used when the caller has already rho-batched groups into F (unshifted) and G (to-be-shifted). + * Computes A₀ = F + G/X^k, folds, commits, partial-evaluates, and constructs opening claims. + * @param shift_exponent The shift depth k (= BATCH_SIZE for interleaved flavors). + */ + template + static std::vector prove(size_t circuit_size, + Polynomial&& batched_unshifted, + Polynomial&& batched_to_be_shifted, + size_t shift_exponent, std::span multilinear_challenge, const CommitmentKey& commitment_key, const std::shared_ptr& transcript, diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp index 9408f4aad72b..defafeb2b615 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/gemini/gemini_impl.hpp @@ -51,52 +51,17 @@ template std::vector::Claim> GeminiProver_::prove( size_t circuit_size, PolynomialBatcher& polynomial_batcher, + const Fr& rho, std::span multilinear_challenge, const CommitmentKey& commitment_key, const std::shared_ptr& transcript, bool has_zk) { - // To achieve fixed proof size in Ultra and Mega, the multilinear opening challenge is be padded to a fixed size. - const size_t virtual_log_n = multilinear_challenge.size(); - const size_t log_n = numeric::get_msb(circuit_size); - - // Get the batching challenge - const Fr rho = transcript->template get_challenge("rho"); - - Polynomial A_0 = polynomial_batcher.compute_batched(rho); - - // Construct the d-1 Gemini foldings of A₀(X) - std::vector fold_polynomials = compute_fold_polynomials(log_n, multilinear_challenge, A_0, has_zk); - - // If virtual_log_n >= log_n, pad the fold commitments with dummy group elements [1]_1. - for (size_t l = 0; l < virtual_log_n - 1; l++) { - std::string label = "Gemini:FOLD_" + std::to_string(l + 1); - // When has_zk is true, we are sending commitments to 0. Seems to work, but maybe brittle. - transcript->send_to_verifier(label, commitment_key.commit(fold_polynomials[l])); - } - const Fr r_challenge = transcript->template get_challenge("Gemini:r"); - - const bool gemini_challenge_in_small_subgroup = (has_zk) && (r_challenge.pow(Curve::SUBGROUP_SIZE) == Fr(1)); - - // If Gemini evaluation challenge lands in the multiplicative subgroup used by SmallSubgroupIPA protocol, the - // evaluations of prover polynomials at this challenge would leak witness data. - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS - if (gemini_challenge_in_small_subgroup) { - throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); - } - - // Compute polynomials A₀₊(X) = F(X) + G(X)/r and A₀₋(X) = F(X) - G(X)/r - auto [A_0_pos, A_0_neg] = polynomial_batcher.compute_partially_evaluated_batch_polynomials(r_challenge); - // Construct claims for the d + 1 univariate evaluations A₀₊(r), A₀₋(-r), and Foldₗ(−r^{2ˡ}), l = 1, ..., d-1 - std::vector claims = construct_univariate_opening_claims( - virtual_log_n, std::move(A_0_pos), std::move(A_0_neg), std::move(fold_polynomials), r_challenge); - - for (size_t l = 1; l <= virtual_log_n; l++) { - std::string label = "Gemini:a_" + std::to_string(l); - transcript->send_to_verifier(label, claims[l].opening_pair.evaluation); - } - - return claims; + // Batch all polynomials with rho, then extract F and G and delegate to the pre-batched overload + polynomial_batcher.compute_batched(rho); + auto [F, G, shift_exp] = polynomial_batcher.extract_batched_pair(); + return prove( + circuit_size, std::move(F), std::move(G), shift_exp, multilinear_challenge, commitment_key, transcript, has_zk); }; /** @@ -234,4 +199,65 @@ std::vector::Claim> GeminiProver_::construc return claims; }; +/** + * @brief Gemini prove from pre-batched polynomials F and G. + * @details Computes A₀ = F + G/X^k, folds, commits, constructs opening claims. Used by the interleaved + * prover path (BS>1) which rho-batches groups externally and frees entity memory before PCS. + */ +template +template +std::vector::Claim> GeminiProver_::prove( + size_t circuit_size, + Polynomial&& batched_unshifted, + Polynomial&& batched_to_be_shifted, + size_t shift_exponent, + std::span multilinear_challenge, + const CommitmentKey& commitment_key, + const std::shared_ptr& transcript, + bool has_zk) +{ + const size_t virtual_log_n = multilinear_challenge.size(); + const size_t log_n = numeric::get_msb(circuit_size); + + // A₀ = F + G/X^k + Polynomial A_0(circuit_size); + A_0 += batched_unshifted; + A_0 += batched_to_be_shifted.shifted(shift_exponent); + + auto fold_polynomials = compute_fold_polynomials(log_n, multilinear_challenge, A_0, has_zk); + A_0 = {}; // free before committing folds + + for (size_t l = 0; l < virtual_log_n - 1; l++) { + transcript->send_to_verifier("Gemini:FOLD_" + std::to_string(l + 1), + commitment_key.commit(fold_polynomials[l])); + } + const Fr r_challenge = transcript->template get_challenge("Gemini:r"); + + const bool gemini_challenge_in_small_subgroup = has_zk && (r_challenge.pow(Curve::SUBGROUP_SIZE) == Fr(1)); + if (gemini_challenge_in_small_subgroup) { + throw_or_abort("Gemini evaluation challenge is in the SmallSubgroup."); + } + + // Partial evaluation: A₀₊ = F + G/r^k, A₀₋ = F ± G/r^k + Fr r_inv_k = r_challenge.pow(shift_exponent).invert(); + batched_to_be_shifted *= r_inv_k; + Polynomial A_0_pos = batched_unshifted; + A_0_pos += batched_to_be_shifted; + // For even k: (-r)^k = r^k, so A₀₋ = A₀₊. For odd k: A₀₋ = F - G/r^k. + Polynomial A_0_neg = (shift_exponent % 2 == 0) ? Polynomial(A_0_pos) : [&]() { + Polynomial neg = std::move(batched_unshifted); + neg -= batched_to_be_shifted; + return neg; + }(); + + auto claims = construct_univariate_opening_claims( + virtual_log_n, std::move(A_0_pos), std::move(A_0_neg), std::move(fold_polynomials), r_challenge); + + for (size_t l = 1; l <= virtual_log_n; l++) { + transcript->send_to_verifier("Gemini:a_" + std::to_string(l), claims[l].opening_pair.evaluation); + } + + return claims; +}; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp index 72febbb8fd9b..d0497497ea6c 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/ipa/ipa.test.cpp @@ -257,8 +257,12 @@ TEST_F(IPATest, ShpleminiIPAWithoutShift) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript); PCS::compute_opening_proof(ck, opening_claim, prover_transcript); @@ -297,8 +301,12 @@ TEST_F(IPATest, ShpleminiIPAWithShift) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript); PCS::compute_opening_proof(ck, opening_claim, prover_transcript); @@ -338,8 +346,12 @@ TEST_F(IPATest, ShpleminiIPAShiftsRemoval) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); const auto opening_claim = ShplonkProver::prove(ck, prover_opening_claims, prover_transcript); PCS::compute_opening_proof(ck, opening_claim, prover_transcript); diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp index 4cdbbea5ec65..d0784cc3fac5 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/kzg/kzg.test.cpp @@ -190,8 +190,12 @@ TEST_F(KZGTest, ShpleminiKzgWithShift) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); // Shplonk prover output: // - opening pair: (z_challenge, 0) @@ -243,8 +247,12 @@ TEST_F(KZGTest, ShpleminiKzgWithShiftAndInterleaving) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); // Shplonk prover output: // - opening pair: (z_challenge, 0) @@ -299,8 +307,12 @@ TEST_F(KZGTest, ShpleminiKzgShiftsRemoval) // Compute: // - (d+1) opening pairs: {r, \hat{a}_0}, {-r^{2^i}, a_i}, i = 0, ..., d-1 // - (d+1) Fold polynomials Fold_{r}^(0), Fold_{-r}^(0), and Fold^(i), i = 0, ..., d-1 - auto prover_opening_claims = - GeminiProver::prove(n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript); + auto prover_opening_claims = GeminiProver::prove(n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + mle_opening_point, + ck, + prover_transcript); // Shplonk prover output: // - opening pair: (z_challenge, 0) diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp index 7cc449d2f335..62d0cc571a24 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.hpp @@ -35,6 +35,7 @@ template class ShpleminiProver_ { template static OpeningClaim prove(size_t circuit_size, PolynomialBatcher& polynomial_batcher, + const FF& rho, std::span multilinear_challenge, const CommitmentKey& commitment_key, const std::shared_ptr& transcript, @@ -49,7 +50,7 @@ template class ShpleminiProver_ { const size_t virtual_log_n = multilinear_challenge.size(); std::vector opening_claims = GeminiProver::prove( - circuit_size, polynomial_batcher, multilinear_challenge, commitment_key, transcript, has_zk); + circuit_size, polynomial_batcher, rho, multilinear_challenge, commitment_key, transcript, has_zk); // Create opening claims for Libra masking univariates and Sumcheck Round Univariates std::vector libra_opening_claims; @@ -311,7 +312,7 @@ template class ShpleminiVerifier_ { // Compute the additional factors to be multiplied with unshifted and shifted commitments when lazily // reconstructing the commitment of Q_z // For unshifted values, the scalar is computed as (1/(z−r) + ν/(z+r)) - // For shifted values, the scalar is computed as r⁻¹ ⋅ (1/(z−r) − ν/(z+r)) + // For shifted values, the scalar is computed as r⁻ᵏ ⋅ (1/(z−r) − ν/(z+r)) where k is shift_exponent claim_batcher.compute_scalars_for_each_batch( inverse_vanishing_evals, shplonk_batching_challenge, gemini_evaluation_challenge); @@ -349,7 +350,7 @@ template class ShpleminiVerifier_ { constant_term_accumulator += gemini_fold_neg_evaluations[0] * shplonk_batching_challenge * inverse_vanishing_evals[1]; - remove_repeated_commitments(commitments, scalars, repeated_commitments, HasZK); + remove_repeated_commitments(commitments, scalars, repeated_commitments); // An optional boolean flag for SmallSubgroupIPAVerifier to check the consistency of the Libra evaluations bool consistency_checked = true; // For ZK flavors, the sumcheck output contains the evaluations of Libra univariates that submitted to the @@ -365,8 +366,17 @@ template class ShpleminiVerifier_ { shplonk_batching_challenge_powers, shplonk_evaluation_challenge); + // For interleaved flavors, the multivariate_challenge has extra interleaving coordinates + // prepended (e.g., [u0, u1, sumcheck_challenges...]). The Libra consistency check only uses + // sumcheck challenges, so strip the first log2(shift_exponent) entries. + const size_t interleaving_log_k = numeric::get_msb(claim_batcher.shift_exponent); + const auto& libra_challenge = + interleaving_log_k > 0 + ? std::vector(multivariate_challenge.begin() + static_cast(interleaving_log_k), + multivariate_challenge.end()) + : multivariate_challenge; consistency_checked = SmallSubgroupIPAVerifier::check_libra_evaluations_consistency( - libra_evaluations, gemini_evaluation_challenge, multivariate_challenge, libra_univariate_evaluation); + libra_evaluations, gemini_evaluation_challenge, libra_challenge, libra_univariate_evaluation); } // Currently, only used in ECCVM @@ -488,12 +498,11 @@ template class ShpleminiVerifier_ { */ static void remove_repeated_commitments(std::vector& commitments, std::vector& scalars, - const RepeatedCommitmentsData& repeated_commitments, - bool has_zk) + const RepeatedCommitmentsData& repeated_commitments) { - // The commitments/scalars vectors start with Shplonk:Q (and Gemini:masking_poly_comm if ZK) + // The commitments/scalars vectors start with Shplonk:Q (and optionally Gemini:masking_poly_comm) // before the prover polynomial commitments, so offset the AllEntities indices accordingly. - const size_t offset = has_zk ? 2 : 1; + const size_t offset = repeated_commitments.shplemini_offset; const auto& r1 = repeated_commitments.first; const auto& r2 = repeated_commitments.second; diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp index 99a0132cec4d..a858ffa8e21d 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini.test.cpp @@ -303,8 +303,10 @@ TYPED_TEST(ShpleminiTest, ShpleminiZKNoSumcheckOpenings) small_subgroup_ipa_prover.prove(); // Reduce to KZG or IPA based on the curve used in the test Flavor + const auto rho = prover_transcript->template get_challenge("rho"); const auto opening_claim = ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, + rho, mle_opening_point, ck, prover_transcript, @@ -409,8 +411,10 @@ TYPED_TEST(ShpleminiTest, ShpleminiZKWithSumcheckOpenings) small_subgroup_ipa_prover.prove(); // Reduce proving to a single claimed fed to KZG or IPA + const auto rho = prover_transcript->template get_challenge("rho"); const auto opening_claim = ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, + rho, challenge, ck, prover_transcript, @@ -515,8 +519,12 @@ TYPED_TEST(ShpleminiTest, HighDegreeAttackAccept) auto prover_transcript = NativeTranscript::test_prover_init_empty(); // Run Shplemini prover - const auto opening_claim = - ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, u, ck, prover_transcript); + const auto opening_claim = ShpleminiProver::prove(this->n, + mock_claims.polynomial_batcher, + prover_transcript->template get_challenge("rho"), + u, + ck, + prover_transcript); // Run KZG/IPA prover if constexpr (std::is_same_v) { @@ -582,7 +590,9 @@ TYPED_TEST(ShpleminiTest, HighDegreeAttackReject) auto prover_transcript = NativeTranscript::test_prover_init_empty(); // Run Shplemini prover - const auto opening_claim = ShpleminiProver::prove(big_n, mock_claims.polynomial_batcher, u, ck, prover_transcript); + const auto rho = prover_transcript->template get_challenge("rho"); + const auto opening_claim = + ShpleminiProver::prove(big_n, mock_claims.polynomial_batcher, rho, u, ck, prover_transcript); // Run KZG/IPA prover if constexpr (std::is_same_v) { @@ -665,8 +675,10 @@ TYPED_TEST(ShpleminiTest, LibraConsistencyCheckFailsOnCorruptedEvaluation) small_subgroup_ipa_prover.prove(); // Reduce to KZG or IPA based on the curve used in the test Flavor + const auto rho = prover_transcript->template get_challenge("rho"); const auto opening_claim = ShpleminiProver::prove(this->n, mock_claims.polynomial_batcher, + rho, mle_opening_point, ck, prover_transcript, @@ -762,8 +774,9 @@ void run_libra_tampering_test(ShpleminiTest* test, witness_polynomials[static_cast(tamper_polynomial)].at(0) += Fr::random_element(); } + const auto rho = prover_transcript->template get_challenge("rho"); const auto opening_claim = ShpleminiProver::prove( - test->n, mock_claims.polynomial_batcher, mle_opening_point, ck, prover_transcript, witness_polynomials); + test->n, mock_claims.polynomial_batcher, rho, mle_opening_point, ck, prover_transcript, witness_polynomials); if constexpr (std::is_same_v) { ShpleminiTest::IPA::compute_opening_proof(test->ck(), opening_claim, prover_transcript); @@ -888,4 +901,259 @@ TYPED_TEST(ShpleminiTest, LibraConcatenatedCommitmentTamperingCausesVerification this, TamperedPolynomial::None, TamperedCommitment::Concatenated, /*expected_consistency_checked=*/true); } +/** + * @brief Test Shplemini with interleaved polynomial commitments (shift_exponent=4). + * @details Opens 2 interleaved polynomials: one unshifted-only, one with both unshifted and shifted openings. + * Mimics the MultiHonk prover/verifier flow where polynomials are committed via interleaved Pippenger + * and evaluations are batched using Lagrange basis over interleaving challenges. + */ +TEST_F(ShpleminiKZGTest, InterleavedOpenings) +{ + using Fr = curve::BN254::ScalarField; + using Commitment = curve::BN254::AffineElement; + using Polynomial = bb::Polynomial; + using CK = CommitmentKey; + using PolynomialBatcher = GeminiProver_::PolynomialBatcher; + using OpeningClaim = ProverOpeningClaim; + using ClaimBatcher = ClaimBatcher_; + using ClaimBatch = ClaimBatcher::Batch; + + constexpr size_t BATCH_SIZE = 4; + const size_t interleaved_size = n * BATCH_SIZE; + + // Create component polynomials: 4 unshiftable + 4 shiftable + auto make_polys = [&](bool shiftable) { + std::array polys; + for (auto& p : polys) { + p = Polynomial(n); + p.at(0) = shiftable ? Fr::zero() : Fr::random_element(); + for (size_t i = 1; i < n; i++) { + p.at(i) = Fr::random_element(); + } + } + return polys; + }; + auto unshiftable_polys = make_polys(false); + auto shiftable_polys = make_polys(true); + + // Interleave: F[4i+j] = f_j[i] + auto interleave = [&](const std::array& polys) { + Polynomial result(interleaved_size); + for (size_t i = 0; i < n; i++) + for (size_t j = 0; j < BATCH_SIZE; j++) + result.at(BATCH_SIZE * i + j) = polys[j][i]; + return result; + }; + Polynomial P_unshiftable = interleave(unshiftable_polys); + Polynomial P_shiftable = interleave(shiftable_polys); + + // Commit + CK ck(interleaved_size); + Commitment C_unshiftable = ck.commit(P_unshiftable); + Commitment C_shiftable = ck.commit(P_shiftable); + + // Sumcheck challenge + interleaving challenges + std::vector sumcheck_challenge(log_n); + for (auto& c : sumcheck_challenge) + c = Fr::random_element(); + Fr u0 = Fr::random_element(); + Fr u1 = Fr::random_element(); + + // Lagrange basis: L₀=(1-u₀)(1-u₁), L₁=u₀(1-u₁), L₂=(1-u₀)u₁, L₃=u₀u₁ + Fr mu0 = Fr::one() - u0, mu1 = Fr::one() - u1; + std::array L = { mu0 * mu1, u0 * mu1, mu0 * u1, u0 * u1 }; + + // Compute batched evaluations via Lagrange basis + auto batch_eval = [&](const std::array& polys, const std::vector& challenge) { + Fr result = Fr::zero(); + for (size_t j = 0; j < BATCH_SIZE; j++) + result += polys[j].evaluate_mle(challenge) * L[j]; + return result; + }; + + Fr eval_unshiftable = batch_eval(unshiftable_polys, sumcheck_challenge); + Fr eval_shiftable = batch_eval(shiftable_polys, sumcheck_challenge); + + // Shifted evals: f_shift[i] = f[i+1] + std::array shifted_polys; + for (size_t j = 0; j < BATCH_SIZE; j++) { + shifted_polys[j] = Polynomial(n); + for (size_t i = 0; i + 1 < n; i++) + shifted_polys[j].at(i) = shiftable_polys[j][i + 1]; + } + Fr eval_shifted = batch_eval(shifted_polys, sumcheck_challenge); + + // Full challenge: [u₀, u₁] ++ sumcheck_challenge + std::vector full_challenge = { u0, u1 }; + full_challenge.insert(full_challenge.end(), sumcheck_challenge.begin(), sumcheck_challenge.end()); + + // --- Prover --- + auto prover_transcript = NativeTranscript::test_prover_init_empty(); + + Polynomial shiftable_for_batcher = Polynomial::shiftable(interleaved_size, interleaved_size, BATCH_SIZE); + for (size_t i = 1; i < n; i++) + for (size_t j = 0; j < BATCH_SIZE; j++) + shiftable_for_batcher.at(BATCH_SIZE * i + j) = shiftable_polys[j][i]; + + PolynomialBatcher batcher(interleaved_size, /*actual_data_size=*/0, /*shift_exponent=*/BATCH_SIZE); + batcher.set_unshifted(RefVector{ P_unshiftable, P_shiftable }); + batcher.set_to_be_shifted(RefVector{ shiftable_for_batcher }); + + auto rho = prover_transcript->template get_challenge("rho"); + OpeningClaim claim = + ShpleminiProver_::prove(interleaved_size, batcher, rho, full_challenge, ck, prover_transcript); + KZG::compute_opening_proof(ck, claim, prover_transcript); + + // --- Verifier --- + auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript); + + std::array unshifted_comms = { C_unshiftable, C_shiftable }; + std::array unshifted_evals = { eval_unshiftable, eval_shiftable }; + std::array shifted_comms = { C_shiftable }; + std::array shifted_evals = { eval_shifted }; + + ClaimBatcher claim_batcher{ + .unshifted = ClaimBatch{ RefArray(unshifted_comms), RefArray(unshifted_evals) }, + .shifted = ClaimBatch{ RefArray(shifted_comms), RefArray(shifted_evals) }, + .shift_exponent = BATCH_SIZE + }; + + std::vector padding(full_challenge.size(), Fr{ 1 }); + auto output = ShpleminiVerifier_::compute_batch_opening_claim( + padding, claim_batcher, full_challenge, Commitment::one(), verifier_transcript); + + auto pairing_points = KZG::reduce_verify_batch_opening_claim(std::move(output.batch_opening_claim), + verifier_transcript); + EXPECT_TRUE(pairing_points.check()); +} + +/** + * @brief Same as InterleavedOpenings but uses interleaved group buffers (strided entity views) + * instead of manually interleaving polynomials. + */ +TEST_F(ShpleminiKZGTest, InterleavedOpeningsWithGroupBuffers) +{ + using Fr = curve::BN254::ScalarField; + using Commitment = curve::BN254::AffineElement; + using Polynomial = bb::Polynomial; + using CK = CommitmentKey; + using PolynomialBatcher = GeminiProver_::PolynomialBatcher; + using OpeningClaim = ProverOpeningClaim; + using ClaimBatcher = ClaimBatcher_; + using ClaimBatch = ClaimBatcher::Batch; + + constexpr size_t BATCH_SIZE = 4; + const size_t interleaved_size = n * BATCH_SIZE; + + // Allocate group buffers and create strided entity views + // Unshiftable group buffer: entities have DIFFERENT sizes + // Entity 0: full size n, Entity 1: size n/2, Entity 2: size n/4, Entity 3: zero (nullptr equivalent) + Polynomial unshiftable_buf(interleaved_size); + std::array entity_sizes = { n, n / 2, n / 4, 0 }; + std::array unshiftable_entities; + for (size_t j = 0; j < BATCH_SIZE; j++) { + if (entity_sizes[j] > 0) { + unshiftable_entities[j] = + Polynomial::strided_view(unshiftable_buf.backing_memory(), BATCH_SIZE, j, 0, entity_sizes[j], n); + } + // else: leave as empty polynomial (zero contribution) + } + + // Shiftable group buffer: entity 0 full, entity 1 half, rest zero + Polynomial shiftable_buf = Polynomial::shiftable(interleaved_size, interleaved_size, BATCH_SIZE); + std::array shiftable_sizes = { n - 1, n / 2, 0, 0 }; + std::array shiftable_entities; + for (size_t j = 0; j < BATCH_SIZE; j++) { + if (shiftable_sizes[j] > 0) { + shiftable_entities[j] = + Polynomial::strided_view(shiftable_buf.backing_memory(), BATCH_SIZE, j, 1, shiftable_sizes[j], n); + } + } + + // Fill entity data through strided views + for (size_t j = 0; j < BATCH_SIZE; j++) { + for (size_t i = 0; i < entity_sizes[j]; i++) { + unshiftable_entities[j].at(i) = Fr::random_element(); + } + for (size_t i = 1; i <= shiftable_sizes[j]; i++) { + shiftable_entities[j].at(i) = Fr::random_element(); + } + } + + // Commit group buffers directly + CK ck(interleaved_size); + Commitment C_unshiftable = ck.commit(unshiftable_buf); + Commitment C_shiftable = ck.commit(shiftable_buf); + + // Sumcheck challenge + interleaving challenges + std::vector sumcheck_challenge(log_n); + for (auto& c : sumcheck_challenge) + c = Fr::random_element(); + Fr u0 = Fr::random_element(); + Fr u1 = Fr::random_element(); + + // Lagrange basis + Fr mu0 = Fr::one() - u0, mu1 = Fr::one() - u1; + std::array L = { mu0 * mu1, u0 * mu1, mu0 * u1, u0 * u1 }; + + // Compute batched evaluations via Lagrange basis from entity MLE evaluations + auto eval_entity = [&](const Polynomial& entity, const std::vector& challenge) { + return entity.is_empty() ? Fr::zero() : entity.evaluate_mle(challenge); + }; + + Fr eval_unshiftable = Fr::zero(); + Fr eval_shiftable = Fr::zero(); + for (size_t j = 0; j < BATCH_SIZE; j++) { + eval_unshiftable += eval_entity(unshiftable_entities[j], sumcheck_challenge) * L[j]; + eval_shiftable += eval_entity(shiftable_entities[j], sumcheck_challenge) * L[j]; + } + + // Shifted evals + Fr eval_shifted = Fr::zero(); + for (size_t j = 0; j < BATCH_SIZE; j++) { + if (!shiftable_entities[j].is_empty()) { + auto shifted = shiftable_entities[j].shifted(); + eval_shifted += shifted.evaluate_mle(sumcheck_challenge) * L[j]; + } + } + + // Full challenge: [u₀, u₁] ++ sumcheck_challenge + std::vector full_challenge = { u0, u1 }; + full_challenge.insert(full_challenge.end(), sumcheck_challenge.begin(), sumcheck_challenge.end()); + + // --- Prover: use group buffers directly --- + auto prover_transcript = NativeTranscript::test_prover_init_empty(); + + PolynomialBatcher batcher(interleaved_size, /*actual_data_size=*/0, /*shift_exponent=*/BATCH_SIZE); + batcher.set_unshifted(RefVector{ unshiftable_buf, shiftable_buf }); + batcher.set_to_be_shifted(RefVector{ shiftable_buf }); + + auto rho = prover_transcript->template get_challenge("rho"); + OpeningClaim claim = + ShpleminiProver_::prove(interleaved_size, batcher, rho, full_challenge, ck, prover_transcript); + KZG::compute_opening_proof(ck, claim, prover_transcript); + + // --- Verifier --- + auto verifier_transcript = NativeTranscript::test_verifier_init_empty(prover_transcript); + + std::array unshifted_comms = { C_unshiftable, C_shiftable }; + std::array unshifted_evals = { eval_unshiftable, eval_shiftable }; + std::array shifted_comms = { C_shiftable }; + std::array shifted_evals = { eval_shifted }; + + ClaimBatcher claim_batcher{ + .unshifted = ClaimBatch{ RefArray(unshifted_comms), RefArray(unshifted_evals) }, + .shifted = ClaimBatch{ RefArray(shifted_comms), RefArray(shifted_evals) }, + .shift_exponent = BATCH_SIZE + }; + + std::vector padding(full_challenge.size(), Fr{ 1 }); + auto output = ShpleminiVerifier_::compute_batch_opening_claim( + padding, claim_batcher, full_challenge, Commitment::one(), verifier_transcript); + + auto pairing_points = KZG::reduce_verify_batch_opening_claim(std::move(output.batch_opening_claim), + verifier_transcript); + EXPECT_TRUE(pairing_points.check()); +} + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_concatenated.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_concatenated.test.cpp index 919aef5efce8..2f08991504a6 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_concatenated.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplemini_concatenated.test.cpp @@ -137,10 +137,11 @@ class ShpleminiConcatenatedTest : public CommitmentTest { std::vector polys_vec(concat_polys.begin(), concat_polys.end()); polynomial_batcher.set_unshifted(RefVector(polys_vec)); - polynomial_batcher.set_to_be_shifted_by_one(RefVector(polys_vec)); + polynomial_batcher.set_to_be_shifted(RefVector(polys_vec)); + auto rho = prover_transcript->template get_challenge("rho"); auto prover_opening_claim = - ShpleminiProver_::prove(n, polynomial_batcher, challenge, ck, prover_transcript); + ShpleminiProver_::prove(n, polynomial_batcher, rho, challenge, ck, prover_transcript); KZG::compute_opening_proof(ck, prover_opening_claim, prover_transcript); // --- Verifier --- diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.cpp index 3619928aa28b..d2e882738de2 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.cpp @@ -445,6 +445,9 @@ Polynomial SmallSubgroupIPAProver:: template class SmallSubgroupIPAProver; template class SmallSubgroupIPAProver; template class SmallSubgroupIPAProver; +template class SmallSubgroupIPAProver; +template class SmallSubgroupIPAProver; +template class SmallSubgroupIPAProver; template class SmallSubgroupIPAProver; template class SmallSubgroupIPAProver; #ifdef STARKNET_GARAGA_FLAVORS diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/mock_witness_generator.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/mock_witness_generator.hpp index 37101b69e0ce..b7a8224a189f 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/mock_witness_generator.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/utils/mock_witness_generator.hpp @@ -112,7 +112,7 @@ template struct MockClaimGenerator { } polynomial_batcher.set_unshifted(RefVector(unshifted.polys)); - polynomial_batcher.set_to_be_shifted_by_one(RefVector(to_be_shifted.polys)); + polynomial_batcher.set_to_be_shifted(RefVector(to_be_shifted.polys)); claim_batcher = ClaimBatcher{ .unshifted = ClaimBatch{ RefVector(unshifted.commitments), RefVector(unshifted.evals) }, diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp index 01bac8d94ede..5c08314881ee 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes_recursion/shplemini.test.cpp @@ -146,16 +146,16 @@ template class ShpleminiRecursionTest : public CommitmentTesttemplate get_challenge("rho"); + auto prover_opening_claims = ShpleminiProver::prove( + N, mock_claims.polynomial_batcher, rho, u_challenge, commitment_key, prover_transcript); KZG::compute_opening_proof(commitment_key, prover_opening_claims, prover_transcript); diff --git a/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.cpp b/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.cpp index b320789e32e8..3e7fc466b0c4 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.cpp +++ b/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.cpp @@ -552,6 +552,43 @@ template curve::BN254::Element pippenger(PolynomialSpan(PolynomialSpan scalars, std::span points); +template +typename Curve::Element pippenger_interleaved(std::span> chunks, + std::span points, + size_t batch_size) noexcept +{ + using Fr = typename Curve::ScalarField; + + // Determine logical size n: max end_index across all chunks. + size_t n = 0; + for (const auto& chunk : chunks) { + n = std::max(n, chunk.end_index()); + } + const size_t total_size = n * batch_size; + + // Build interleaved scalar array: for logical index i, place chunk_j at position batch_size*i + j. + std::vector interleaved_scalars(total_size, Fr::zero()); + for (size_t j = 0; j < chunks.size(); j++) { + const auto& chunk = chunks[j]; + for (size_t i = chunk.start_index; i < chunk.end_index(); i++) { + interleaved_scalars[batch_size * i + j] = chunk[i]; + } + } + + auto scalars_span = PolynomialSpan(0, interleaved_scalars); + return MSM::msm(points.subspan(0, total_size), scalars_span, false); +} + +template curve::Grumpkin::Element pippenger_interleaved( + std::span> chunks, + std::span points, + size_t batch_size) noexcept; + +template curve::BN254::Element pippenger_interleaved( + std::span> chunks, + std::span points, + size_t batch_size) noexcept; + } // namespace bb::scalar_multiplication template class bb::scalar_multiplication::MSM; diff --git a/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.hpp b/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.hpp index 0f410bfe1a63..b8177182ea35 100644 --- a/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.hpp +++ b/barretenberg/cpp/src/barretenberg/ecc/scalar_multiplication/scalar_multiplication.hpp @@ -364,6 +364,19 @@ template typename Curve::Element pippenger_unsafe(PolynomialSpan scalars, std::span points) noexcept; +/** + * @brief MSM for interleaved polynomial groups without materializing the interleaved buffer. + * @details Computes [F] where F(X) = Σⱼ fⱼ(X^{batch_size}) · X^j. Builds the interleaved scalar + * array from individual chunk spans and delegates to standard MSM. + * @param chunks Individual polynomial spans (one per group member, can be < batch_size) + * @param points SRS points (size must be >= max_end_index * batch_size) + * @param batch_size Interleaving width + */ +template +typename Curve::Element pippenger_interleaved(std::span> chunks, + std::span points, + size_t batch_size) noexcept; + extern template class MSM; extern template class MSM; diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp index d518e24aa6a1..88e785b02843 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_flavor.hpp @@ -88,11 +88,13 @@ class ECCVMFlavor { static constexpr size_t NUM_DERIVED_WITNESS_ENTITIES_NON_SHIFTED = 1; // A container to be fed to ShpleminiVerifier to avoid redundant scalar muls, the first number is the index of the // first witness to be shifted. + static constexpr size_t SHPLEMINI_OFFSET = 2; // Shplonk:Q + Gemini:masking_poly_comm (ECCVM is always ZK) static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = RepeatedCommitmentsData(NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES - NUM_DERIVED_WITNESS_ENTITIES_NON_SHIFTED - NUM_SHIFTED_ENTITIES, NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES, - NUM_SHIFTED_ENTITIES); + NUM_SHIFTED_ENTITIES, + SHPLEMINI_OFFSET); using GrandProductRelations = std::tuple>; // define the tuple of Relations that comprise the Sumcheck relation diff --git a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp index 2b0d59010701..55c68e3b7990 100644 --- a/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/eccvm/eccvm_prover.cpp @@ -188,16 +188,19 @@ void ECCVMProver::execute_pcs_rounds() // evaluations produced by Sumcheck PolynomialBatcher polynomial_batcher(key->circuit_size); polynomial_batcher.set_unshifted(key->polynomials.get_unshifted()); - polynomial_batcher.set_to_be_shifted_by_one(key->polynomials.get_to_be_shifted()); + polynomial_batcher.set_to_be_shifted(key->polynomials.get_to_be_shifted()); // Add small tail polynomials for masked witness polys (avoids extending all polys to full dyadic size) if (key->masking_tail_data.is_active()) { key->masking_tail_data.add_tails_to_batcher(key->polynomials, polynomial_batcher); } + const auto rho = transcript->template get_challenge("rho"); + OpeningClaim multivariate_to_univariate_opening_claim = Shplemini::prove(key->circuit_size, polynomial_batcher, + rho, sumcheck_output.challenge, key->commitment_key, transcript, diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp index c8fa282fe79f..224d1b834764 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor.hpp @@ -37,8 +37,10 @@ // ===== Flavor forward declarations ===== namespace bb { -class UltraFlavor; -class UltraZKFlavor; +template class UltraFlavor_; +using UltraFlavor = UltraFlavor_<1>; +template class UltraZKFlavor_; +using UltraZKFlavor = UltraZKFlavor_<1>; class ECCVMFlavor; class UltraKeccakFlavor; #ifdef STARKNET_GARAGA_FLAVORS @@ -46,8 +48,12 @@ class UltraStarknetFlavor; class UltraStarknetZKFlavor; #endif class UltraKeccakZKFlavor; -class MegaFlavor; -class MegaZKFlavor; +template class MegaFlavor_; +using MegaFlavor = MegaFlavor_<1>; +using MultiMegaFlavor = MegaFlavor_<4>; +template class MegaZKFlavor_; +using MegaZKFlavor = MegaZKFlavor_<1>; +using MultiMegaZKFlavor = MegaZKFlavor_<4>; class MegaAvmFlavor; class TranslatorFlavor; class ECCVMRecursiveFlavor; @@ -84,6 +90,8 @@ struct MetaData { template struct PrecomputedData_ { RefArray polynomials; // polys whose commitments comprise the VK MetaData metadata; // execution trace metadata + // For BS>1: precomputed entity pointers grouped by interleaved group (for commit_interleaved) + std::vector> precomputed_groups; }; // ===== Fixed verification keys (ECCVM, Translator, AVM) ===== @@ -131,7 +139,11 @@ class FixedVKAndHash_ : public PrecomputedCommitments { * @tparam Codec The codec used for serialization (e.g., FrCodec, U256Codec) * @tparam HashFunction The hash function used for VK hashing (e.g., Poseidon2, Keccak) */ -template +template class NativeVerificationKey_ : public PrecomputedCommitments { public: using Commitment = typename PrecomputedCommitments::DataType; @@ -178,7 +190,9 @@ class NativeVerificationKey_ : public PrecomputedCommitments { /** * @brief Construct VK from precomputed data by committing to polynomials - * @details Only available when CommitmentKeyType is specified (not void) + * @details Only available when CommitmentKeyType is specified (not void). + * When InterleavingBatchSize > 1, groups polynomials into sequential chunks + * and commits each chunk using interleaved MSM. */ template requires(!std::is_void_v) @@ -187,9 +201,24 @@ class NativeVerificationKey_ : public PrecomputedCommitments { , num_public_inputs(precomputed.metadata.num_public_inputs) , pub_inputs_offset(precomputed.metadata.pub_inputs_offset) { - CommitmentKey commitment_key{ precomputed.metadata.dyadic_size }; - for (auto [polynomial, commitment] : zip_view(precomputed.polynomials, this->get_all())) { - commitment = commitment_key.commit(polynomial); + if constexpr (InterleavingBatchSize == 1) { + CommitmentKey commitment_key{ precomputed.metadata.dyadic_size }; + for (auto [polynomial, commitment] : zip_view(precomputed.polynomials, this->get_all())) { + commitment = commitment_key.commit(polynomial); + } + } else { + // Commit interleaved precomputed groups using commit_interleaved (no full buffer needed) + using Fr = typename Codec::DataType; + CommitmentKey commitment_key{ precomputed.metadata.dyadic_size * InterleavingBatchSize }; + for (auto [group, commitment] : zip_view(precomputed.precomputed_groups, this->get_all())) { + std::vector> spans; + for (const auto* ptr : group) { + if (ptr != nullptr) { + spans.push_back(*ptr); + } + } + commitment = commitment_key.template commit_interleaved(spans); + } } } diff --git a/barretenberg/cpp/src/barretenberg/flavor/flavor_concepts.hpp b/barretenberg/cpp/src/barretenberg/flavor/flavor_concepts.hpp index 7a36c6dd27b3..836c5932a9e7 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/flavor_concepts.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/flavor_concepts.hpp @@ -4,26 +4,40 @@ #include "barretenberg/common/type_traits.hpp" #include "barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp" #include + namespace bb { // clang-format off #ifdef STARKNET_GARAGA_FLAVORS template -concept IsUltraHonk = IsAnyOf; +concept IsUltraHonk = IsAnyOf; #else template -concept IsUltraHonk = IsAnyOf; +concept IsUltraHonk = IsAnyOf; #endif template -concept IsUltraOrMegaHonk = IsUltraHonk || IsAnyOf; +concept IsUltraOrMegaHonk = IsUltraHonk || IsAnyOf; + +template +concept IsMultiMegaFlavor = requires { T::INTERLEAVING_BATCH_SIZE; } && (T::INTERLEAVING_BATCH_SIZE > 1); template concept IsMegaFlavor = IsAnyOf, MegaRecursiveFlavor_, MegaAvmRecursiveFlavor_, MegaZKRecursiveFlavor_, - MegaZKRecursiveFlavor_>; + MegaZKRecursiveFlavor_, + DualMegaRecursiveFlavor_, + DualMegaRecursiveFlavor_, + DualMegaZKRecursiveFlavor_, + DualMegaZKRecursiveFlavor_, + MultiMegaRecursiveFlavor_, + MultiMegaRecursiveFlavor_, + MultiMegaZKRecursiveFlavor_, + MultiMegaZKRecursiveFlavor_>; template concept HasDataBus = IsMegaFlavor; @@ -42,11 +56,23 @@ concept IsRecursiveFlavor = IsAnyOf, UltraZKRecursiveFlavor_, UltraZKRecursiveFlavor_, + DualUltraRecursiveFlavor_, + DualUltraRecursiveFlavor_, + DualUltraZKRecursiveFlavor_, + DualUltraZKRecursiveFlavor_, MegaRecursiveFlavor_, MegaRecursiveFlavor_, MegaZKRecursiveFlavor_, MegaZKRecursiveFlavor_, MegaAvmRecursiveFlavor_, + DualMegaRecursiveFlavor_, + DualMegaRecursiveFlavor_, + DualMegaZKRecursiveFlavor_, + DualMegaZKRecursiveFlavor_, + MultiMegaRecursiveFlavor_, + MultiMegaRecursiveFlavor_, + MultiMegaZKRecursiveFlavor_, + MultiMegaZKRecursiveFlavor_, TranslatorRecursiveFlavor, ECCVMRecursiveFlavor, MultilinearBatchingRecursiveFlavor, diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp index a9c128802bfa..5e4ca193a2d9 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_flavor.hpp @@ -8,6 +8,7 @@ #include "barretenberg/commitment_schemes/kzg/kzg.hpp" #include "barretenberg/flavor/flavor.hpp" #include "barretenberg/flavor/flavor_macros.hpp" +#include "barretenberg/flavor/mega_interleaving_entities.hpp" #include "barretenberg/flavor/partially_evaluated_multivariates.hpp" #include "barretenberg/flavor/prover_polynomials.hpp" #include "barretenberg/flavor/relation_definitions.hpp" @@ -30,7 +31,20 @@ namespace bb { -class MegaFlavor { +// ============================================================ +// MegaFlavor_ template class +// ============================================================ + +/** + * @brief The Mega proving system flavor, parameterized on interleaving batch size. + * + * @details MegaFlavor_<1> (aliased as MegaFlavor) commits polynomials individually. + * MegaFlavor_<4> (aliased as MultiMegaFlavor) batches 4 polynomials per interleaved + * commitment, reducing witness commitments from 24 to 11. + * + * @tparam BATCH_SIZE_ The number of polynomials interleaved per commitment (1 or 4). + */ +template class MegaFlavor_ { public: using CircuitBuilder = MegaCircuitBuilder; using Curve = curve::BN254; @@ -54,6 +68,15 @@ class MegaFlavor { // To achieve fixed proof size and that the recursive verifier circuit is constant, we are using padding in Sumcheck // and Shplemini static constexpr bool USE_PADDING = true; + + // Interleaving parameters + static constexpr size_t INTERLEAVING_BATCH_SIZE = BATCH_SIZE_; + // log2(BATCH_SIZE): number of extra Gemini rounds for interleaving + static constexpr size_t INTERLEAVING_LOG_K = (BATCH_SIZE_ <= 1) ? 0 + : (BATCH_SIZE_ <= 2) ? 1 + : (BATCH_SIZE_ <= 4) ? 2 + : 3; + static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; // define the tuple of Relations that comprise the Sumcheck relation @@ -86,6 +109,10 @@ class MegaFlavor { static constexpr size_t NUM_SUBRELATIONS = compute_number_of_subrelations(); using SubrelationSeparator = FF; + // ================================================================ + // Entity classes (same for all BATCH_SIZE values) + // ================================================================ + /** * @brief A base class labelling precomputed entities and (ordered) subsets of interest. * @details Used to build the proving key and verification key. @@ -197,24 +224,7 @@ class MegaFlavor { return_data_read_tags, // column 22 return_data_inverses); // column 23 auto get_to_be_shifted() { return RefArray{ z_perm }; }; - }; - - /** - * @brief ZK-specific entities (only used when HasZK = true) - * @details Contains the Gemini masking polynomial used for zero-knowledge - */ - template class MaskingEntities { - public: - // When ZK is disabled, this class is empty - auto get_all() { return RefArray{}; } - auto get_all() const { return RefArray{}; } - static auto get_labels() { return std::vector{}; } - }; - - // Specialization for when ZK is enabled - template class MaskingEntities { - public: - DEFINE_FLAVOR_MEMBERS(DataType, gemini_masking_poly) + auto get_to_be_shifted() const { return RefArray{ z_perm }; }; }; /** @@ -253,6 +263,12 @@ class MegaFlavor { { return concatenate(WireEntities::get_all(), DerivedEntities::get_to_be_shifted()); } + auto get_to_be_shifted() const + { + return concatenate(WireEntities::get_all(), DerivedEntities::get_to_be_shifted()); + } + auto get_shiftable() { return get_to_be_shifted(); } + auto get_shiftable() const { return get_to_be_shifted(); } // Entities masked in ZK mode: all witness except ECC op wires (masked via random ops) // and calldata (left unmasked). @@ -318,6 +334,17 @@ class MegaFlavor { z_perm_shift) // column 4 }; + // ================================================================ + // Masking entities (BATCH_SIZE-dependent via external specialization) + // ================================================================ + + template + using MaskingEntities = MegaMaskingEntities_; + + // ================================================================ + // AllEntities_ (uniform structure, uses BATCH_SIZE-aware masking) + // ================================================================ + /** * @brief A base class labelling all entities (for instance, all of the polynomials used by the prover during * sumcheck) in this Honk variant along with particular subsets of interest @@ -343,6 +370,12 @@ class MegaFlavor { PrecomputedEntities::get_all(), WitnessEntities_::get_all()); }; + auto get_unshifted() const + { + return concatenate(MaskingEntities::get_all(), + PrecomputedEntities::get_all(), + WitnessEntities_::get_all()); + }; auto get_precomputed() { return PrecomputedEntities::get_all(); } auto get_witness() { return WitnessEntities_::get_all(); }; auto get_witness() const { return WitnessEntities_::get_all(); }; @@ -353,6 +386,10 @@ class MegaFlavor { // Default AllEntities alias (no ZK) template using AllEntities = AllEntities_; + // ================================================================ + // Entity counts + // ================================================================ + // Derive entity counts from the actual struct definitions static constexpr size_t NUM_PRECOMPUTED_ENTITIES = PrecomputedEntities::_members_size; static constexpr size_t NUM_WITNESS_ENTITIES = WireEntities::_members_size + DerivedEntities::_members_size; @@ -360,16 +397,15 @@ class MegaFlavor { static constexpr size_t NUM_UNSHIFTED_ENTITIES = NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES; static constexpr size_t NUM_ALL_ENTITIES = NUM_UNSHIFTED_ENTITIES + NUM_SHIFTED_ENTITIES; - static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = RepeatedCommitmentsData( - NUM_PRECOMPUTED_ENTITIES, NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES, NUM_SHIFTED_ENTITIES); + // ================================================================ + // BATCH_SIZE-dependent constants + // ================================================================ - // Size of the final PCS MSM after KZG adds quotient commitment: - // 1 (Shplonk Q) + NUM_UNSHIFTED + (log_n - 1) Gemini folds + 1 (G1 identity) + 1 (KZG W) - // (shifted commitments are removed as duplicates) - static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) - { - return NUM_UNSHIFTED_ENTITIES + log_n + 2; - } + static constexpr size_t SHPLEMINI_OFFSET = 1; // Shplonk:Q + + // ================================================================ + // AllValues, ProverPolynomials + // ================================================================ /** * @brief A field element for each entity of the flavor. These entities represent the prover polynomials evaluated @@ -383,9 +419,6 @@ class MegaFlavor { using AllValues = AllValues_; - /** - * @brief A container for the prover polynomials handles. - */ template using ProverPolynomials_ = ProverPolynomialsBase, AllValues_, Polynomial>; @@ -393,11 +426,14 @@ class MegaFlavor { using PrecomputedData = PrecomputedData_; - /** - * @brief The verification key stores commitments to the precomputed (non-witness) polynomials used by the - * verifier. - */ - using VerificationKey = NativeVerificationKey_, Codec, HashFunction, CommitmentKey>; + // ================================================================ + // Verification Key + // ================================================================ + + using VKPrecomputedType = + typename VKPrecomputedType_>::type; + + using VerificationKey = NativeVerificationKey_; using VKAndHash = VKAndHash_; @@ -426,106 +462,185 @@ class MegaFlavor { */ using WitnessCommitments = WitnessEntities; + // ================================================================ + // CommitmentLabels (individual polynomial labels, same for all BS) + // ================================================================ + /** * @brief A container for commitment labels. * @note It's debatable whether this should inherit from AllEntities. since most entries are not strictly needed. It * has, however, been useful during debugging to have these labels available. - * */ class CommitmentLabels : public AllEntities { public: CommitmentLabels() { - w_l = "W_L"; - w_r = "W_R"; - w_o = "W_O"; - w_4 = "W_4"; - z_perm = "Z_PERM"; - lookup_inverses = "LOOKUP_INVERSES"; - lookup_read_counts = "LOOKUP_READ_COUNTS"; - lookup_read_tags = "LOOKUP_READ_TAGS"; - ecc_op_wire_1 = "ECC_OP_WIRE_1"; - ecc_op_wire_2 = "ECC_OP_WIRE_2"; - ecc_op_wire_3 = "ECC_OP_WIRE_3"; - ecc_op_wire_4 = "ECC_OP_WIRE_4"; - calldata = "CALLDATA"; - calldata_read_counts = "CALLDATA_READ_COUNTS"; - calldata_read_tags = "CALLDATA_READ_TAGS"; - calldata_inverses = "CALLDATA_INVERSES"; - secondary_calldata = "SECONDARY_CALLDATA"; - secondary_calldata_read_counts = "SECONDARY_CALLDATA_READ_COUNTS"; - secondary_calldata_read_tags = "SECONDARY_CALLDATA_READ_TAGS"; - secondary_calldata_inverses = "SECONDARY_CALLDATA_INVERSES"; - return_data = "RETURN_DATA"; - return_data_read_counts = "RETURN_DATA_READ_COUNTS"; - return_data_read_tags = "RETURN_DATA_READ_TAGS"; - return_data_inverses = "RETURN_DATA_INVERSES"; - - q_c = "Q_C"; - q_l = "Q_L"; - q_r = "Q_R"; - q_o = "Q_O"; - q_4 = "Q_4"; - q_m = "Q_M"; - q_busread = "Q_BUSREAD"; - q_lookup = "Q_LOOKUP"; - q_arith = "Q_ARITH"; - q_delta_range = "Q_SORT"; - q_elliptic = "Q_ELLIPTIC"; - q_memory = "Q_MEMORY"; - q_nnf = "Q_NNF"; - q_poseidon2_external = "Q_POSEIDON2_EXTERNAL"; - q_poseidon2_internal = "Q_POSEIDON2_INTERNAL"; - sigma_1 = "SIGMA_1"; - sigma_2 = "SIGMA_2"; - sigma_3 = "SIGMA_3"; - sigma_4 = "SIGMA_4"; - id_1 = "ID_1"; - id_2 = "ID_2"; - id_3 = "ID_3"; - id_4 = "ID_4"; - table_1 = "TABLE_1"; - table_2 = "TABLE_2"; - table_3 = "TABLE_3"; - table_4 = "TABLE_4"; - lagrange_first = "LAGRANGE_FIRST"; - lagrange_last = "LAGRANGE_LAST"; - lagrange_ecc_op = "Q_ECC_OP_QUEUE"; + this->w_l = "W_L"; + this->w_r = "W_R"; + this->w_o = "W_O"; + this->w_4 = "W_4"; + this->z_perm = "Z_PERM"; + this->lookup_inverses = "LOOKUP_INVERSES"; + this->lookup_read_counts = "LOOKUP_READ_COUNTS"; + this->lookup_read_tags = "LOOKUP_READ_TAGS"; + this->ecc_op_wire_1 = "ECC_OP_WIRE_1"; + this->ecc_op_wire_2 = "ECC_OP_WIRE_2"; + this->ecc_op_wire_3 = "ECC_OP_WIRE_3"; + this->ecc_op_wire_4 = "ECC_OP_WIRE_4"; + this->calldata = "CALLDATA"; + this->calldata_read_counts = "CALLDATA_READ_COUNTS"; + this->calldata_read_tags = "CALLDATA_READ_TAGS"; + this->calldata_inverses = "CALLDATA_INVERSES"; + this->secondary_calldata = "SECONDARY_CALLDATA"; + this->secondary_calldata_read_counts = "SECONDARY_CALLDATA_READ_COUNTS"; + this->secondary_calldata_read_tags = "SECONDARY_CALLDATA_READ_TAGS"; + this->secondary_calldata_inverses = "SECONDARY_CALLDATA_INVERSES"; + this->return_data = "RETURN_DATA"; + this->return_data_read_counts = "RETURN_DATA_READ_COUNTS"; + this->return_data_read_tags = "RETURN_DATA_READ_TAGS"; + this->return_data_inverses = "RETURN_DATA_INVERSES"; + + this->q_c = "Q_C"; + this->q_l = "Q_L"; + this->q_r = "Q_R"; + this->q_o = "Q_O"; + this->q_4 = "Q_4"; + this->q_m = "Q_M"; + this->q_busread = "Q_BUSREAD"; + this->q_lookup = "Q_LOOKUP"; + this->q_arith = "Q_ARITH"; + this->q_delta_range = "Q_SORT"; + this->q_elliptic = "Q_ELLIPTIC"; + this->q_memory = "Q_MEMORY"; + this->q_nnf = "Q_NNF"; + this->q_poseidon2_external = "Q_POSEIDON2_EXTERNAL"; + this->q_poseidon2_internal = "Q_POSEIDON2_INTERNAL"; + this->sigma_1 = "SIGMA_1"; + this->sigma_2 = "SIGMA_2"; + this->sigma_3 = "SIGMA_3"; + this->sigma_4 = "SIGMA_4"; + this->id_1 = "ID_1"; + this->id_2 = "ID_2"; + this->id_3 = "ID_3"; + this->id_4 = "ID_4"; + this->table_1 = "TABLE_1"; + this->table_2 = "TABLE_2"; + this->table_3 = "TABLE_3"; + this->table_4 = "TABLE_4"; + this->lagrange_first = "LAGRANGE_FIRST"; + this->lagrange_last = "LAGRANGE_LAST"; + this->lagrange_ecc_op = "Q_ECC_OP_QUEUE"; }; }; + // ================================================================ + // VerifierCommitments_ + // ================================================================ + /** * Note: Made generic for use in MegaRecursive. **/ - template - class VerifierCommitments_ : public AllEntities_ { + template + class VerifierCommitments_ : public AllEntities_ { public: - VerifierCommitments_(const std::shared_ptr& verification_key, - const std::optional>& witness_commitments = std::nullopt) + VerifierCommitments_() = default; + + VerifierCommitments_(const std::shared_ptr& verification_key, + const std::optional>& witness_commitments = std::nullopt) { - // Copy the precomputed polynomial commitments into this - for (auto [precomputed, precomputed_in] : zip_view(this->get_precomputed(), verification_key->get_all())) { - precomputed = precomputed_in; - } - - // If provided, copy the witness polynomial commitments into this - if (witness_commitments.has_value()) { - for (auto [witness, witness_in] : - zip_view(this->get_witness(), witness_commitments.value().get_all())) { - witness = witness_in; - } - - // Set shifted commitments - this->w_l_shift = witness_commitments->w_l; - this->w_r_shift = witness_commitments->w_r; - this->w_o_shift = witness_commitments->w_o; - this->w_4_shift = witness_commitments->w_4; - this->z_perm_shift = witness_commitments->z_perm; - } + VerifierCommitmentsInit_::init(*this, verification_key, witness_commitments); } }; // Specialize for Mega (general case used in MegaRecursive). using VerifierCommitments = VerifierCommitments_; + + // ================================================================ + // Interleaved entity type aliases (from external specializations) + // ================================================================ + + template + using InterleavedWitnessCommitments_ = MegaInterleavedWitnessCommitments_; + + template using InterleavedWitnessCommitments = InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + + template + using InterleavedPrecomputedCommitments = MegaInterleavedPrecomputedCommitments_; + using InterleavedPrecomputed = InterleavedPrecomputedCommitments; + + // ================================================================ + // Interleaved commitment labels (from mega_interleaving_entities.hpp) + // ================================================================ + + template + using InterleavedCommitmentLabels_ = MegaInterleavedCommitmentLabels_; + using InterleavedCommitmentLabels = InterleavedCommitmentLabels_; + + using InterleavedPrecomputedLabels = MegaInterleavedPrecomputedLabels_; + + // ================================================================ + // Interleaved constants (from InterleavingConstants_) + // ================================================================ + + using IC = InterleavingConstants_; + + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = IC::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = IC::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = IC::NUM_ALL_INTERLEAVED_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = IC::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + // Oink round group descriptors (BS-dependent) + using OinkRounds = OinkWitnessRounds_; + + // ================================================================ + // Group accessors (delegate to GroupAccessors_ in mega_interleaving_entities.hpp) + // ================================================================ + + template static auto compute_lagrange_basis(std::span interleaving_challenges) + { + return compute_lagrange_basis_impl(interleaving_challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return GroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_unshifted_groups_mut(Entities& e) + { + return GroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + return GroupAccessors_::get_to_be_shifted_groups(e); + } + + template static auto get_shifted_groups(Entities& e) + { + return GroupAccessors_::get_shifted_groups(e); + } + + // ================================================================ + // REPEATED_COMMITMENTS and FINAL_PCS_MSM_SIZE + // (defined here because they depend on interleaved constants above) + // ================================================================ + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = + IC::make_repeated_commitments(NUM_PRECOMPUTED_ENTITIES, NUM_UNSHIFTED_ENTITIES, NUM_SHIFTED_ENTITIES); + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return IC::final_pcs_msm_size(NUM_UNSHIFTED_ENTITIES, log_n); + } }; +// ============================================================ +// Type aliases +// ============================================================ + +using MegaFlavor = MegaFlavor_<1>; +using DualMegaFlavor = MegaFlavor_<2>; +using MultiMegaFlavor = MegaFlavor_<4>; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_interleaving_entities.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_interleaving_entities.hpp new file mode 100644 index 000000000000..49b4f67b6d4c --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_interleaving_entities.hpp @@ -0,0 +1,1063 @@ +// === AUDIT STATUS === +// internal: { status: not started, auditors: [], commit: } +// external_1: { status: not started, auditors: [], commit: } +// external_2: { status: not started, auditors: [], commit: } +// ===================== + +#pragma once +#include "barretenberg/common/zip_view.hpp" +#include "barretenberg/flavor/flavor_macros.hpp" +#include "barretenberg/flavor/repeated_commitments_data.hpp" +#include +#include +#include +#include + +namespace bb { + +// ============================================================ +// External entity specializations parameterized on BATCH_SIZE +// ============================================================ +// These structs define interleaving-dependent data for MegaFlavor_. +// Each has explicit specializations for BS=1 (individual, the base case) +// and BS=4 (interleaved). To add a new batch size (e.g. BS=2), add +// specializations here. +// +// MegaFlavor_ delegates ALL batch-size-dependent logic here so that +// the flavor class itself is fully agnostic to BS. + +/** + * @brief ZK-specific masking entities, specialized per (DataType, BATCH_SIZE, HasZK). + * + * - (any BS, HasZK=false): empty + * - (BS=1, HasZK=true): single gemini_masking_poly + * - (BS=4, HasZK=true): 4 masking chunks (committed as one interleaved group) + */ +template struct MegaMaskingEntities_; + +// Non-ZK: always empty regardless of batch size +template struct MegaMaskingEntities_ { + auto get_all() { return RefArray{}; } + auto get_all() const { return RefArray{}; } + static auto get_labels() { return std::vector{}; } +}; + +// BS=1, ZK: single Gemini masking polynomial +template struct MegaMaskingEntities_ { + DEFINE_FLAVOR_MEMBERS(DataType, gemini_masking_poly) +}; + +// BS=2, ZK: 2 masking chunk polynomials +template struct MegaMaskingEntities_ { + DEFINE_FLAVOR_MEMBERS(DataType, masking_chunk_0, masking_chunk_1) +}; + +// BS=4, ZK: 4 masking chunk polynomials +template struct MegaMaskingEntities_ { + DEFINE_FLAVOR_MEMBERS(DataType, masking_chunk_0, masking_chunk_1, masking_chunk_2, masking_chunk_3) +}; + +/** + * @brief Interleaved witness commitments, specialized per (DataType, BATCH_SIZE, HasZK). + * + * For BS=1: empty (individual commitments are stored in WitnessCommitments). + * For BS=4: 11 (non-ZK) or 12 (ZK) interleaved witness commitments. + * + * Ordering: unshiftable groups first, then shiftable groups at the end. + * This enables the REPEATED_COMMITMENTS optimization for shift deduplication. + */ +template class MegaInterleavedWitnessCommitments_; + +// BS=1: empty (any HasZK) +template class MegaInterleavedWitnessCommitments_ { + public: + auto get_all() { return RefArray{}; } + auto get_all() const { return RefArray{}; } + static auto get_labels() { return std::vector{}; } + auto get_shiftable() { return RefArray{}; } + auto get_shiftable() const { return RefArray{}; } +}; + +// BS=2, non-ZK: 15 interleaved witness commitments +template class MegaInterleavedWitnessCommitments_ { + public: + DEFINE_FLAVOR_MEMBERS( + DataType, + interleaved_ecc_op_wires_1, // [ecc_op_wire_1, ecc_op_wire_2] - unshiftable + interleaved_ecc_op_wires_2, // [ecc_op_wire_3, ecc_op_wire_4] - unshiftable + interleaved_calldata, // [calldata, 0] - unshiftable + interleaved_secondary_calldata, // [secondary_calldata, 0] - unshiftable + interleaved_calldata_tags, // [calldata_read_counts, calldata_read_tags] - unshiftable + interleaved_scd_tags, // [scd_read_counts, scd_read_tags] - unshiftable + interleaved_return_data_tags, // [return_data_read_tags, return_data_read_counts] - unshiftable + interleaved_return_data, // [return_data, 0] - unshiftable + interleaved_lookup, // [lookup_read_counts, lookup_read_tags] - unshiftable + interleaved_inverses_1, // [lookup_inverses, calldata_inverses] - unshiftable + interleaved_inverses_2, // [scd_inverses, return_data_inverses] - unshiftable + interleaved_wires, // [w_l, w_r] - shiftable + interleaved_w_o, // [w_o, 0] - shiftable + interleaved_w_4, // [w_4, 0] - shiftable + interleaved_z_perm) // [z_perm, 0] - shiftable + + auto get_shiftable() { return RefArray{ interleaved_wires, interleaved_w_o, interleaved_w_4, interleaved_z_perm }; } + auto get_shiftable() const + { + return RefArray{ interleaved_wires, interleaved_w_o, interleaved_w_4, interleaved_z_perm }; + } + auto get_ecc_op_wires() { return RefArray{ interleaved_ecc_op_wires_1, interleaved_ecc_op_wires_2 }; } +}; + +// BS=2, ZK: 16 interleaved witness commitments (15 base + masking) +template class MegaInterleavedWitnessCommitments_ { + public: + DEFINE_FLAVOR_MEMBERS( + DataType, + interleaved_ecc_op_wires_1, // [ecc_op_wire_1, ecc_op_wire_2] - unshiftable + interleaved_ecc_op_wires_2, // [ecc_op_wire_3, ecc_op_wire_4] - unshiftable + interleaved_calldata, // [calldata, 0] - unshiftable + interleaved_secondary_calldata, // [secondary_calldata, 0] - unshiftable + interleaved_calldata_tags, // [calldata_read_counts, calldata_read_tags] - unshiftable + interleaved_scd_tags, // [scd_read_counts, scd_read_tags] - unshiftable + interleaved_return_data_tags, // [return_data_read_tags, return_data_read_counts] - unshiftable + interleaved_return_data, // [return_data, 0] - unshiftable + interleaved_lookup, // [lookup_read_counts, lookup_read_tags] - unshiftable + interleaved_inverses_1, // [lookup_inverses, calldata_inverses] - unshiftable + interleaved_inverses_2, // [scd_inverses, return_data_inverses] - unshiftable + masking_commitment, // masking chunks - unshiftable + interleaved_wires, // [w_l, w_r] - shiftable + interleaved_w_o, // [w_o, 0] - shiftable + interleaved_w_4, // [w_4, 0] - shiftable + interleaved_z_perm) // [z_perm, 0] - shiftable + + auto get_shiftable() { return RefArray{ interleaved_wires, interleaved_w_o, interleaved_w_4, interleaved_z_perm }; } + auto get_shiftable() const + { + return RefArray{ interleaved_wires, interleaved_w_o, interleaved_w_4, interleaved_z_perm }; + } + auto get_ecc_op_wires() { return RefArray{ interleaved_ecc_op_wires_1, interleaved_ecc_op_wires_2 }; } +}; + +// BS=4, non-ZK: 11 interleaved witness commitments +template class MegaInterleavedWitnessCommitments_ { + public: + DEFINE_FLAVOR_MEMBERS( + DataType, + interleaved_ecc_op_wires, // W₂: [ecc_op_wire_1..4] - unshiftable + interleaved_calldata, // W₃: [calldata, 0, 0, 0] - unshiftable + interleaved_secondary_calldata, // W₄: [secondary_calldata, 0, 0, 0] - unshiftable + interleaved_databus_tags, // W₅: [cd_read_counts, cd_read_tags, scd_read_counts, scd_read_tags] + interleaved_return_data_tags, // W₆: [rd_read_tags, rd_read_counts, 0, 0] - unshiftable + interleaved_return_data, // W₇: [return_data, 0, 0, 0] - unshiftable + interleaved_lookup, // W₉: [lookup_read_counts, lookup_read_tags, 0, 0] + interleaved_inverses, // W₁₀: all inverses - unshiftable + interleaved_wires, // W₁: [w_l, w_r, w_o, 0] - shiftable + interleaved_w_4, // W₈: [w_4, 0, 0, 0] - shiftable + interleaved_z_perm) // W₁₁: [z_perm, 0, 0, 0] - shiftable + + auto get_shiftable() { return RefArray{ interleaved_wires, interleaved_w_4, interleaved_z_perm }; } + auto get_shiftable() const { return RefArray{ interleaved_wires, interleaved_w_4, interleaved_z_perm }; } + auto get_ecc_op_wires() { return RefArray{ interleaved_ecc_op_wires }; } +}; + +// BS=4, ZK: 12 interleaved witness commitments (11 base + masking) +template class MegaInterleavedWitnessCommitments_ { + public: + DEFINE_FLAVOR_MEMBERS( + DataType, + interleaved_ecc_op_wires, // W₂: [ecc_op_wire_1..4] - unshiftable + interleaved_calldata, // W₃: [calldata, 0, 0, 0] - unshiftable + interleaved_secondary_calldata, // W₄: [secondary_calldata, 0, 0, 0] - unshiftable + interleaved_databus_tags, // W₅: [cd_read_counts, cd_read_tags, scd_read_counts, scd_read_tags] + interleaved_return_data_tags, // W₆: [rd_read_tags, rd_read_counts, 0, 0] - unshiftable + interleaved_return_data, // W₇: [return_data, 0, 0, 0] - unshiftable + interleaved_lookup, // W₉: [lookup_read_counts, lookup_read_tags, 0, 0] + interleaved_inverses, // W₁₀: all inverses - unshiftable + masking_commitment, // W₁₂: masking chunks - unshiftable + interleaved_wires, // W₁: [w_l, w_r, w_o, 0] - shiftable + interleaved_w_4, // W₈: [w_4, 0, 0, 0] - shiftable + interleaved_z_perm) // W₁₁: [z_perm, 0, 0, 0] - shiftable + + auto get_shiftable() { return RefArray{ interleaved_wires, interleaved_w_4, interleaved_z_perm }; } + auto get_shiftable() const { return RefArray{ interleaved_wires, interleaved_w_4, interleaved_z_perm }; } + auto get_ecc_op_wires() { return RefArray{ interleaved_ecc_op_wires }; } +}; + +/** + * @brief Interleaved precomputed commitments (8 total for BS=4, empty for BS=1). + * + * Groups are formed by sequential chunking of PrecomputedEntities (batch_size=4). + * With 31 entities, the last group has only 3 polynomials (zero-padded). + */ +template class MegaInterleavedPrecomputedCommitments_; + +// BS=1: empty +template class MegaInterleavedPrecomputedCommitments_ { + public: + using DataType = DataType_; + auto get_all() { return RefArray{}; } + auto get_all() const { return RefArray{}; } + static auto get_labels() { return std::vector{}; } + bool operator==(const MegaInterleavedPrecomputedCommitments_&) const = default; +}; + +// BS=2: 16 interleaved precomputed commitments +template class MegaInterleavedPrecomputedCommitments_ { + public: + using DataType = DataType_; + DEFINE_FLAVOR_MEMBERS(DataType, + interleaved_precomputed_0, // P₁: [q_m, q_c] + interleaved_precomputed_1, // P₂: [q_l, q_r] + interleaved_precomputed_2, // P₃: [q_o, q_4] + interleaved_precomputed_3, // P₄: [q_busread, q_lookup] + interleaved_precomputed_4, // P₅: [q_arith, q_delta_range] + interleaved_precomputed_5, // P₆: [q_elliptic, q_memory] + interleaved_precomputed_6, // P₇: [q_nnf, q_poseidon2_external] + interleaved_precomputed_7, // P₈: [q_poseidon2_internal, sigma_1] + interleaved_precomputed_8, // P₉: [sigma_2, sigma_3] + interleaved_precomputed_9, // P₁₀: [sigma_4, id_1] + interleaved_precomputed_10, // P₁₁: [id_2, id_3] + interleaved_precomputed_11, // P₁₂: [id_4, table_1] + interleaved_precomputed_12, // P₁₃: [table_2, table_3] + interleaved_precomputed_13, // P₁₄: [table_4, lagrange_first] + interleaved_precomputed_14, // P₁₅: [lagrange_last, lagrange_ecc_op] + interleaved_precomputed_15) // P₁₆: [databus_id, 0] (1 poly) + bool operator==(const MegaInterleavedPrecomputedCommitments_&) const = default; +}; + +// BS=4: 8 interleaved precomputed commitments +template class MegaInterleavedPrecomputedCommitments_ { + public: + using DataType = DataType_; + DEFINE_FLAVOR_MEMBERS(DataType, + interleaved_precomputed_0, // P₁: [q_m, q_c, q_l, q_r] + interleaved_precomputed_1, // P₂: [q_o, q_4, q_busread, q_lookup] + interleaved_precomputed_2, // P₃: [q_arith, q_delta_range, q_elliptic, q_memory] + interleaved_precomputed_3, // P₄: [q_nnf, q_poseidon2_external, q_poseidon2_internal, sigma_1] + interleaved_precomputed_4, // P₅: [sigma_2, sigma_3, sigma_4, id_1] + interleaved_precomputed_5, // P₆: [id_2, id_3, id_4, table_1] + interleaved_precomputed_6, // P₇: [table_2, table_3, table_4, lagrange_first] + interleaved_precomputed_7) // P₈: [lagrange_last, lagrange_ecc_op, databus_id] (3 polys) + bool operator==(const MegaInterleavedPrecomputedCommitments_&) const = default; +}; + +// ============================================================ +// VK precomputed type selector (BS-dependent) +// ============================================================ + +/** + * @brief Selects the VK precomputed commitment type based on batch size. + * BS=1: individual precomputed commitments (PrecomputedEntities). + * BS>1: interleaved precomputed commitments. + */ +template struct VKPrecomputedType_ { + using type = PrecomputedEntitiesCommitment; // BS=1 default +}; +template +struct VKPrecomputedType_<2, Commitment, PrecomputedEntitiesCommitment> { + using type = MegaInterleavedPrecomputedCommitments_; +}; +template +struct VKPrecomputedType_<4, Commitment, PrecomputedEntitiesCommitment> { + using type = MegaInterleavedPrecomputedCommitments_; +}; + +// ============================================================ +// VerifierCommitments initialization (BS-dependent) +// ============================================================ + +/** + * @brief Populates VerifierCommitments from VK and witness commitments. + * BS=1: copies individual precomputed + witness commitments into AllEntities slots. + * BS>1: no-op (verifier uses interleaved commitments directly for PCS). + */ +template struct VerifierCommitmentsInit_; + +template <> struct VerifierCommitmentsInit_<1> { + template + static void init(Self& self, const std::shared_ptr& verification_key, const std::optional& witness_comms) + { + for (auto [dest, src] : zip_view(self.get_precomputed(), verification_key->get_all())) { + dest = src; + } + if (witness_comms.has_value()) { + for (auto [dest, src] : zip_view(self.get_witness(), witness_comms->get_all())) { + dest = src; + } + for (auto [dest, src] : zip_view(self.get_shifted(), witness_comms->get_to_be_shifted())) { + dest = src; + } + } + } +}; + +template <> struct VerifierCommitmentsInit_<2> { + template + static void init(Self&, const std::shared_ptr&, const std::optional&) + { + // For BS > 1: individual precomputed/witness slots are not populated from the VK + // because the VK stores interleaved commitments. The verifier uses interleaved + // commitments directly for PCS verification. + } +}; + +template <> struct VerifierCommitmentsInit_<4> { + template + static void init(Self&, const std::shared_ptr&, const std::optional&) + { + // For BS > 1: individual precomputed/witness slots are not populated from the VK + // because the VK stores interleaved commitments. The verifier uses interleaved + // commitments directly for PCS verification. + } +}; + +// ============================================================ +// Interleaving constants (BS-dependent, fully specialized) +// ============================================================ + +template struct InterleavingConstants_; + +template <> struct InterleavingConstants_<1> { + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = 0; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = 0; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = 0; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = 0; + + // For BS=1, PCS uses individual commitments directly. + // original_start = NUM_PRECOMPUTED (shiftable polys start at beginning of witness block) + // duplicate_start = NUM_PRECOMPUTED + NUM_WITNESS (shifted entities follow unshifted) + static constexpr RepeatedCommitmentsData make_repeated_commitments(size_t num_precomputed, + size_t num_unshifted, + size_t num_shifted) + { + return RepeatedCommitmentsData(num_precomputed, num_unshifted, num_shifted); + } + + static constexpr size_t final_pcs_msm_size(size_t num_unshifted, size_t log_n) { return num_unshifted + log_n + 2; } +}; + +template <> struct InterleavingConstants_<2> { + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = 16; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = 15; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = + NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS + NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = 4; + + // For BS=2, PCS uses interleaved commitments. Shiftable groups are at the end. + static constexpr RepeatedCommitmentsData make_repeated_commitments(size_t /*num_precomputed*/, + size_t /*num_unshifted*/, + size_t /*num_shifted*/) + { + return RepeatedCommitmentsData(NUM_ALL_INTERLEAVED_COMMITMENTS - NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS, + NUM_ALL_INTERLEAVED_COMMITMENTS, + NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS); + } + + static constexpr size_t final_pcs_msm_size(size_t /*num_unshifted*/, size_t log_n) + { + constexpr size_t LOG_K = 1; // log2(2) + return NUM_ALL_INTERLEAVED_COMMITMENTS + log_n + LOG_K + 2; + } +}; + +template <> struct InterleavingConstants_<4> { + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = 8; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = 11; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = + NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS + NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = 3; + + // For BS=4, PCS uses interleaved commitments. Shiftable groups are at the end. + static constexpr RepeatedCommitmentsData make_repeated_commitments(size_t /*num_precomputed*/, + size_t /*num_unshifted*/, + size_t /*num_shifted*/) + { + return RepeatedCommitmentsData(NUM_ALL_INTERLEAVED_COMMITMENTS - NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS, + NUM_ALL_INTERLEAVED_COMMITMENTS, + NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS); + } + + static constexpr size_t final_pcs_msm_size(size_t /*num_unshifted*/, size_t log_n) + { + constexpr size_t LOG_K = 2; // log2(4) + return NUM_ALL_INTERLEAVED_COMMITMENTS + log_n + LOG_K + 2; + } +}; + +// ============================================================ +// Interleaved commitment labels (BS-dependent) +// ============================================================ + +/** + * @brief Labels for interleaved witness commitments, specialized per (BS, HasZK). + * For BS=1: empty. For BS=4: populates string fields for transcript labeling. + */ +template +class MegaInterleavedCommitmentLabels_ : public MegaInterleavedWitnessCommitments_ { + public: + MegaInterleavedCommitmentLabels_() = default; +}; + +// BS=2, non-ZK +template <> +class MegaInterleavedCommitmentLabels_<2, false> : public MegaInterleavedWitnessCommitments_ { + public: + MegaInterleavedCommitmentLabels_() + { + interleaved_wires = "INTERLEAVED_WIRES"; + interleaved_ecc_op_wires_1 = "INTERLEAVED_ECC_OP_WIRES_1"; + interleaved_ecc_op_wires_2 = "INTERLEAVED_ECC_OP_WIRES_2"; + interleaved_calldata = "INTERLEAVED_CALLDATA"; + interleaved_secondary_calldata = "INTERLEAVED_SECONDARY_CALLDATA"; + interleaved_calldata_tags = "INTERLEAVED_CALLDATA_TAGS"; + interleaved_scd_tags = "INTERLEAVED_SCD_TAGS"; + interleaved_return_data_tags = "INTERLEAVED_RETURN_DATA_TAGS"; + interleaved_return_data = "INTERLEAVED_RETURN_DATA"; + interleaved_w_o = "INTERLEAVED_W_O"; + interleaved_w_4 = "INTERLEAVED_W_4"; + interleaved_lookup = "INTERLEAVED_LOOKUP"; + interleaved_inverses_1 = "INTERLEAVED_INVERSES_1"; + interleaved_inverses_2 = "INTERLEAVED_INVERSES_2"; + interleaved_z_perm = "INTERLEAVED_Z_PERM"; + } +}; + +// BS=2, ZK (adds masking_commitment) +template <> +class MegaInterleavedCommitmentLabels_<2, true> : public MegaInterleavedWitnessCommitments_ { + public: + MegaInterleavedCommitmentLabels_() + { + interleaved_wires = "INTERLEAVED_WIRES"; + interleaved_ecc_op_wires_1 = "INTERLEAVED_ECC_OP_WIRES_1"; + interleaved_ecc_op_wires_2 = "INTERLEAVED_ECC_OP_WIRES_2"; + interleaved_calldata = "INTERLEAVED_CALLDATA"; + interleaved_secondary_calldata = "INTERLEAVED_SECONDARY_CALLDATA"; + interleaved_calldata_tags = "INTERLEAVED_CALLDATA_TAGS"; + interleaved_scd_tags = "INTERLEAVED_SCD_TAGS"; + interleaved_return_data_tags = "INTERLEAVED_RETURN_DATA_TAGS"; + interleaved_return_data = "INTERLEAVED_RETURN_DATA"; + interleaved_w_o = "INTERLEAVED_W_O"; + interleaved_w_4 = "INTERLEAVED_W_4"; + interleaved_lookup = "INTERLEAVED_LOOKUP"; + interleaved_inverses_1 = "INTERLEAVED_INVERSES_1"; + interleaved_inverses_2 = "INTERLEAVED_INVERSES_2"; + interleaved_z_perm = "INTERLEAVED_Z_PERM"; + masking_commitment = "Gemini:masking_poly_comm"; + } +}; + +// BS=4, non-ZK +template <> +class MegaInterleavedCommitmentLabels_<4, false> : public MegaInterleavedWitnessCommitments_ { + public: + MegaInterleavedCommitmentLabels_() + { + interleaved_wires = "INTERLEAVED_WIRES"; + interleaved_ecc_op_wires = "INTERLEAVED_ECC_OP_WIRES"; + interleaved_calldata = "INTERLEAVED_CALLDATA"; + interleaved_secondary_calldata = "INTERLEAVED_SECONDARY_CALLDATA"; + interleaved_databus_tags = "INTERLEAVED_DATABUS_TAGS"; + interleaved_return_data_tags = "INTERLEAVED_RETURN_DATA_TAGS"; + interleaved_return_data = "INTERLEAVED_RETURN_DATA"; + interleaved_w_4 = "INTERLEAVED_W_4"; + interleaved_lookup = "INTERLEAVED_LOOKUP"; + interleaved_inverses = "INTERLEAVED_INVERSES"; + interleaved_z_perm = "INTERLEAVED_Z_PERM"; + } +}; + +// BS=4, ZK (adds masking_commitment) +template <> +class MegaInterleavedCommitmentLabels_<4, true> : public MegaInterleavedWitnessCommitments_ { + public: + MegaInterleavedCommitmentLabels_() + { + interleaved_wires = "INTERLEAVED_WIRES"; + interleaved_ecc_op_wires = "INTERLEAVED_ECC_OP_WIRES"; + interleaved_calldata = "INTERLEAVED_CALLDATA"; + interleaved_secondary_calldata = "INTERLEAVED_SECONDARY_CALLDATA"; + interleaved_databus_tags = "INTERLEAVED_DATABUS_TAGS"; + interleaved_return_data_tags = "INTERLEAVED_RETURN_DATA_TAGS"; + interleaved_return_data = "INTERLEAVED_RETURN_DATA"; + interleaved_w_4 = "INTERLEAVED_W_4"; + interleaved_lookup = "INTERLEAVED_LOOKUP"; + interleaved_inverses = "INTERLEAVED_INVERSES"; + interleaved_z_perm = "INTERLEAVED_Z_PERM"; + masking_commitment = "Gemini:masking_poly_comm"; + } +}; + +/** + * @brief Labels for interleaved precomputed commitments, specialized per BS. + * For BS=1: empty. For BS=4: populates 8 label fields. + */ +template +class MegaInterleavedPrecomputedLabels_ : public MegaInterleavedPrecomputedCommitments_ { + public: + MegaInterleavedPrecomputedLabels_() = default; +}; + +// BS=2 +template <> class MegaInterleavedPrecomputedLabels_<2> : public MegaInterleavedPrecomputedCommitments_ { + public: + MegaInterleavedPrecomputedLabels_() + { + interleaved_precomputed_0 = "INTERLEAVED_PRECOMPUTED_0"; + interleaved_precomputed_1 = "INTERLEAVED_PRECOMPUTED_1"; + interleaved_precomputed_2 = "INTERLEAVED_PRECOMPUTED_2"; + interleaved_precomputed_3 = "INTERLEAVED_PRECOMPUTED_3"; + interleaved_precomputed_4 = "INTERLEAVED_PRECOMPUTED_4"; + interleaved_precomputed_5 = "INTERLEAVED_PRECOMPUTED_5"; + interleaved_precomputed_6 = "INTERLEAVED_PRECOMPUTED_6"; + interleaved_precomputed_7 = "INTERLEAVED_PRECOMPUTED_7"; + interleaved_precomputed_8 = "INTERLEAVED_PRECOMPUTED_8"; + interleaved_precomputed_9 = "INTERLEAVED_PRECOMPUTED_9"; + interleaved_precomputed_10 = "INTERLEAVED_PRECOMPUTED_10"; + interleaved_precomputed_11 = "INTERLEAVED_PRECOMPUTED_11"; + interleaved_precomputed_12 = "INTERLEAVED_PRECOMPUTED_12"; + interleaved_precomputed_13 = "INTERLEAVED_PRECOMPUTED_13"; + interleaved_precomputed_14 = "INTERLEAVED_PRECOMPUTED_14"; + interleaved_precomputed_15 = "INTERLEAVED_PRECOMPUTED_15"; + } +}; + +// BS=4 +template <> class MegaInterleavedPrecomputedLabels_<4> : public MegaInterleavedPrecomputedCommitments_ { + public: + MegaInterleavedPrecomputedLabels_() + { + interleaved_precomputed_0 = "INTERLEAVED_PRECOMPUTED_0"; + interleaved_precomputed_1 = "INTERLEAVED_PRECOMPUTED_1"; + interleaved_precomputed_2 = "INTERLEAVED_PRECOMPUTED_2"; + interleaved_precomputed_3 = "INTERLEAVED_PRECOMPUTED_3"; + interleaved_precomputed_4 = "INTERLEAVED_PRECOMPUTED_4"; + interleaved_precomputed_5 = "INTERLEAVED_PRECOMPUTED_5"; + interleaved_precomputed_6 = "INTERLEAVED_PRECOMPUTED_6"; + interleaved_precomputed_7 = "INTERLEAVED_PRECOMPUTED_7"; + } +}; + +// ============================================================ +// Lagrange basis computation (unified for all BS) +// ============================================================ + +/** + * @brief Compute Lagrange basis evaluations for interleaving. + * @details BS=1: trivially {1} (no interleaving challenges needed). + * BS=4: L₀(u₀,u₁) = (1-u₀)(1-u₁), L₁ = u₀(1-u₁), L₂ = (1-u₀)u₁, L₃ = u₀·u₁ + */ +template +static std::array compute_lagrange_basis_impl([[maybe_unused]] std::span interleaving_challenges) +{ + if constexpr (BS == 1) { + return { FF(1) }; + } else if constexpr (BS == 2) { + const auto& u = interleaving_challenges[0]; + return { FF(1) - u, u }; + } else { + static_assert(BS == 4, "Only BS=1, BS=2, and BS=4 are currently supported"); + const auto& u0 = interleaving_challenges[0]; + const auto& u1 = interleaving_challenges[1]; + auto one_minus_u0 = FF(1) - u0; + auto one_minus_u1 = FF(1) - u1; + return { one_minus_u0 * one_minus_u1, u0 * one_minus_u1, one_minus_u0 * u1, u0 * u1 }; + } +} + +// ============================================================ +// ============================================================ +// Group accessors (BS-dependent, fully specialized) +// ============================================================ + +/** + * @brief BS-specialized group accessors for PCS batching. + * BS=1: each polynomial forms its own group of size 1 (identity interleaving). + * BS=4: explicit interleaved groups of 4, with shiftable groups at the end. + */ +template struct GroupAccessors_; + +// BS=1: groups of size 1, built from entity accessors +template <> struct GroupAccessors_<1> { + template static auto get_unshifted_groups(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t; + using Group = std::vector; + + auto unshifted = e.get_unshifted(); + std::vector groups; + groups.reserve(unshifted.size()); + for (size_t i = 0; i < unshifted.size(); ++i) { + groups.push_back(Group{ static_cast(&unshifted[i]) }); + } + return groups; + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + + auto to_be_shifted = e.get_to_be_shifted(); + std::vector groups; + groups.reserve(to_be_shifted.size()); + for (size_t i = 0; i < to_be_shifted.size(); ++i) { + groups.push_back(Group{ &to_be_shifted[i] }); + } + return groups; + } + + template static auto get_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + + auto shifted = e.get_shifted(); + std::vector groups; + groups.reserve(shifted.size()); + for (size_t i = 0; i < shifted.size(); ++i) { + groups.push_back(Group{ &shifted[i] }); + } + return groups; + } +}; + +// BS=2: explicit interleaved groups of 2 +template <> struct GroupAccessors_<2> { + /** + * @brief Return interleaved groups of pointers into entities for PCS batching. + * @details Order: 16 precomputed groups (P₁-P₁₆) + 15 witness groups. + * Shiftable groups (wires, w_o, w_4, z_perm) are placed at the end for REPEATED_COMMITMENTS. + */ + template static auto get_unshifted_groups(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t; + using Group = std::vector; + return std::vector{ + // P₁-P₁₆: precomputed (sequential pairs of PrecomputedEntities) + { &e.q_m, &e.q_c }, + { &e.q_l, &e.q_r }, + { &e.q_o, &e.q_4 }, + { &e.q_busread, &e.q_lookup }, + { &e.q_arith, &e.q_delta_range }, + { &e.q_elliptic, &e.q_memory }, + { &e.q_nnf, &e.q_poseidon2_external }, + { &e.q_poseidon2_internal, &e.sigma_1 }, + { &e.sigma_2, &e.sigma_3 }, + { &e.sigma_4, &e.id_1 }, + { &e.id_2, &e.id_3 }, + { &e.id_4, &e.table_1 }, + { &e.table_2, &e.table_3 }, + { &e.table_4, &e.lagrange_first }, + { &e.lagrange_last, &e.lagrange_ecc_op }, + { &e.databus_id, nullptr }, + // Unshiftable witness groups + { &e.ecc_op_wire_1, &e.ecc_op_wire_2 }, + { &e.ecc_op_wire_3, &e.ecc_op_wire_4 }, + { &e.calldata, nullptr }, + { &e.secondary_calldata, nullptr }, + { &e.calldata_read_counts, &e.calldata_read_tags }, + { &e.secondary_calldata_read_counts, &e.secondary_calldata_read_tags }, + { &e.return_data_read_tags, &e.return_data_read_counts }, + { &e.return_data, nullptr }, + { &e.lookup_read_counts, &e.lookup_read_tags }, + { &e.lookup_inverses, &e.calldata_inverses }, + { &e.secondary_calldata_inverses, &e.return_data_inverses }, + // Shiftable witness groups at end + { &e.w_l, &e.w_r }, + { &e.w_o, nullptr }, + { &e.w_4, nullptr }, + { &e.z_perm, nullptr }, + }; + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l, &e.w_r }, + { &e.w_o, nullptr }, + { &e.w_4, nullptr }, + { &e.z_perm, nullptr }, + }; + } + + template static auto get_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l_shift, &e.w_r_shift }, + { &e.w_o_shift, nullptr }, + { &e.w_4_shift, nullptr }, + { &e.z_perm_shift, nullptr }, + }; + } +}; + +// BS=4: explicit interleaved groups +template <> struct GroupAccessors_<4> { + /** + * @brief Return interleaved groups of pointers into entities for PCS batching. + * @details Order: 8 precomputed groups (P₁-P₈) + 11 witness groups (W₁-W₁₁). + * Shiftable groups (W₁, W₈, W₁₁) are placed at the end for REPEATED_COMMITMENTS. + */ + template static auto get_unshifted_groups(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t; + using Group = std::vector; + return std::vector{ + // P₁-P₈: precomputed (sequential chunks of PrecomputedEntities) + { &e.q_m, &e.q_c, &e.q_l, &e.q_r }, + { &e.q_o, &e.q_4, &e.q_busread, &e.q_lookup }, + { &e.q_arith, &e.q_delta_range, &e.q_elliptic, &e.q_memory }, + { &e.q_nnf, &e.q_poseidon2_external, &e.q_poseidon2_internal, &e.sigma_1 }, + { &e.sigma_2, &e.sigma_3, &e.sigma_4, &e.id_1 }, + { &e.id_2, &e.id_3, &e.id_4, &e.table_1 }, + { &e.table_2, &e.table_3, &e.table_4, &e.lagrange_first }, + { &e.lagrange_last, &e.lagrange_ecc_op, &e.databus_id, nullptr }, + // W₂-W₁₀: unshiftable witness groups + { &e.ecc_op_wire_1, &e.ecc_op_wire_2, &e.ecc_op_wire_3, &e.ecc_op_wire_4 }, + { &e.calldata, nullptr, nullptr, nullptr }, + { &e.secondary_calldata, nullptr, nullptr, nullptr }, + { &e.calldata_read_counts, + &e.calldata_read_tags, + &e.secondary_calldata_read_counts, + &e.secondary_calldata_read_tags }, + { &e.return_data_read_tags, &e.return_data_read_counts, nullptr, nullptr }, + { &e.return_data, nullptr, nullptr, nullptr }, + { &e.lookup_read_counts, &e.lookup_read_tags, nullptr, nullptr }, + { &e.lookup_inverses, &e.calldata_inverses, &e.secondary_calldata_inverses, &e.return_data_inverses }, + // W₁, W₈, W₁₁: shiftable witness groups at end + { &e.w_l, &e.w_r, &e.w_o, nullptr }, + { &e.w_4, nullptr, nullptr, nullptr }, + { &e.z_perm, nullptr, nullptr, nullptr }, + }; + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l, &e.w_r, &e.w_o, nullptr }, + { &e.w_4, nullptr, nullptr, nullptr }, + { &e.z_perm, nullptr, nullptr, nullptr }, + }; + } + + template static auto get_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l_shift, &e.w_r_shift, &e.w_o_shift, nullptr }, + { &e.w_4_shift, nullptr, nullptr, nullptr }, + { &e.z_perm_shift, nullptr, nullptr, nullptr }, + }; + } +}; + +// ============================================================ +// Oink round group descriptors (BS-dependent) +// ============================================================ + +/** + * @brief Describes a single group to commit in an Oink round. + * @details Contains pointers to the entity polynomials (group members) and a transcript label. + * For BS=1, each group has one entity. For BS>1, groups have up to BS entities (with nullptr padding). + */ +template struct OinkGroupDescriptor { + std::vector entities; + std::string label; +}; + +/** + * @brief Per-round witness group descriptors for Oink, specialized per BS. + * @details Returns vectors of OinkGroupDescriptor for each oink round: + * - wires: w_l/w_r/w_o + ecc_op wires + databus entities (before eta) + * - lookup_and_w4: lookup counts/tags + w_4 (after eta) + * - inverses: lookup_inverses + databus inverses (after beta/gamma) + * - z_perm: z_perm (after grand product) + * + * Tail groups for ZK are obtained by calling the same methods on masking_tail_data.tails. + */ +template struct OinkWitnessRounds_; + +template <> struct OinkWitnessRounds_<1> { + template static auto wires(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + std::vector groups = { + { { &e.w_l }, "W_L" }, + { { &e.w_r }, "W_R" }, + { { &e.w_o }, "W_O" }, + { { &e.ecc_op_wire_1 }, "ECC_OP_WIRE_1" }, + { { &e.ecc_op_wire_2 }, "ECC_OP_WIRE_2" }, + { { &e.ecc_op_wire_3 }, "ECC_OP_WIRE_3" }, + { { &e.ecc_op_wire_4 }, "ECC_OP_WIRE_4" }, + { { &e.calldata }, "CALLDATA" }, + { { &e.calldata_read_counts }, "CALLDATA_READ_COUNTS" }, + { { &e.calldata_read_tags }, "CALLDATA_READ_TAGS" }, + { { &e.secondary_calldata }, "SECONDARY_CALLDATA" }, + { { &e.secondary_calldata_read_counts }, "SECONDARY_CALLDATA_READ_COUNTS" }, + { { &e.secondary_calldata_read_tags }, "SECONDARY_CALLDATA_READ_TAGS" }, + { { &e.return_data }, "RETURN_DATA" }, + { { &e.return_data_read_counts }, "RETURN_DATA_READ_COUNTS" }, + { { &e.return_data_read_tags }, "RETURN_DATA_READ_TAGS" }, + }; + return groups; + } + + template static auto lookup_and_w4(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.lookup_read_counts }, "LOOKUP_READ_COUNTS" }, + { { &e.lookup_read_tags }, "LOOKUP_READ_TAGS" }, + { { &e.w_4 }, "W_4" }, + }; + } + + template static auto inverses(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.lookup_inverses }, "LOOKUP_INVERSES" }, + { { &e.calldata_inverses }, "CALLDATA_INVERSES" }, + { { &e.secondary_calldata_inverses }, "SECONDARY_CALLDATA_INVERSES" }, + { { &e.return_data_inverses }, "RETURN_DATA_INVERSES" }, + }; + } + + template static auto z_perm(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.z_perm }, "Z_PERM" }, + }; + } +}; + +template <> struct OinkWitnessRounds_<2> { + private: + template + using Ptr = std::conditional_t, + std::decay_t().get_all()[0])> const*, + std::decay_t().get_all()[0])>*>; + template using D = OinkGroupDescriptor>; + + public: + // Overloads for entity-level types (ProverPolynomials, MaskingTailData::tails) + template + requires requires(E& e) { e.w_l; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.w_l, &e.w_r }, "INTERLEAVED_WIRES" }, + { { &e.ecc_op_wire_1, &e.ecc_op_wire_2 }, "INTERLEAVED_ECC_OP_WIRES_1" }, + { { &e.ecc_op_wire_3, &e.ecc_op_wire_4 }, "INTERLEAVED_ECC_OP_WIRES_2" }, + { { &e.calldata, nullptr }, "INTERLEAVED_CALLDATA" }, + { { &e.secondary_calldata, nullptr }, "INTERLEAVED_SECONDARY_CALLDATA" }, + { { &e.calldata_read_counts, &e.calldata_read_tags }, "INTERLEAVED_CALLDATA_TAGS" }, + { { &e.secondary_calldata_read_counts, &e.secondary_calldata_read_tags }, "INTERLEAVED_SCD_TAGS" }, + { { &e.return_data_read_tags, &e.return_data_read_counts }, "INTERLEAVED_RETURN_DATA_TAGS" }, + { { &e.return_data, nullptr }, "INTERLEAVED_RETURN_DATA" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.lookup_read_counts, &e.lookup_read_tags }, "INTERLEAVED_LOOKUP" }, + { { &e.w_o, nullptr }, "INTERLEAVED_W_O" }, + { { &e.w_4, nullptr }, "INTERLEAVED_W_4" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.lookup_inverses, &e.calldata_inverses }, "INTERLEAVED_INVERSES_1" }, + { { &e.secondary_calldata_inverses, &e.return_data_inverses }, "INTERLEAVED_INVERSES_2" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.z_perm, nullptr }, "INTERLEAVED_Z_PERM" }, + }; + } + + // Overloads for commitment-level types (InterleavedWitnessCommitments) + template + requires requires(E& e) { e.interleaved_wires; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.interleaved_wires }, "INTERLEAVED_WIRES" }, + { { &e.interleaved_ecc_op_wires_1 }, "INTERLEAVED_ECC_OP_WIRES_1" }, + { { &e.interleaved_ecc_op_wires_2 }, "INTERLEAVED_ECC_OP_WIRES_2" }, + { { &e.interleaved_calldata }, "INTERLEAVED_CALLDATA" }, + { { &e.interleaved_secondary_calldata }, "INTERLEAVED_SECONDARY_CALLDATA" }, + { { &e.interleaved_calldata_tags }, "INTERLEAVED_CALLDATA_TAGS" }, + { { &e.interleaved_scd_tags }, "INTERLEAVED_SCD_TAGS" }, + { { &e.interleaved_return_data_tags }, "INTERLEAVED_RETURN_DATA_TAGS" }, + { { &e.interleaved_return_data }, "INTERLEAVED_RETURN_DATA" }, + }; + } + + template + requires requires(E& e) { e.interleaved_w_4; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.interleaved_lookup }, "INTERLEAVED_LOOKUP" }, + { { &e.interleaved_w_o }, "INTERLEAVED_W_O" }, + { { &e.interleaved_w_4 }, "INTERLEAVED_W_4" }, + }; + } + + template + requires requires(E& e) { e.interleaved_inverses_1; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.interleaved_inverses_1 }, "INTERLEAVED_INVERSES_1" }, + { { &e.interleaved_inverses_2 }, "INTERLEAVED_INVERSES_2" }, + }; + } + + template + requires requires(E& e) { e.interleaved_z_perm; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.interleaved_z_perm }, "INTERLEAVED_Z_PERM" }, + }; + } +}; + +template <> struct OinkWitnessRounds_<4> { + private: + template + using Ptr = std::conditional_t, + std::decay_t().get_all()[0])> const*, + std::decay_t().get_all()[0])>*>; + template using D = OinkGroupDescriptor>; + + public: + // Overloads for entity-level types (ProverPolynomials, MaskingTailData::tails) + template + requires requires(E& e) { e.w_l; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.w_l, &e.w_r, &e.w_o, nullptr }, "INTERLEAVED_WIRES" }, + { { &e.ecc_op_wire_1, &e.ecc_op_wire_2, &e.ecc_op_wire_3, &e.ecc_op_wire_4 }, "INTERLEAVED_ECC_OP_WIRES" }, + { { &e.calldata, nullptr, nullptr, nullptr }, "INTERLEAVED_CALLDATA" }, + { { &e.secondary_calldata, nullptr, nullptr, nullptr }, "INTERLEAVED_SECONDARY_CALLDATA" }, + { { &e.calldata_read_counts, + &e.calldata_read_tags, + &e.secondary_calldata_read_counts, + &e.secondary_calldata_read_tags }, + "INTERLEAVED_DATABUS_TAGS" }, + { { &e.return_data_read_tags, &e.return_data_read_counts, nullptr, nullptr }, + "INTERLEAVED_RETURN_DATA_TAGS" }, + { { &e.return_data, nullptr, nullptr, nullptr }, "INTERLEAVED_RETURN_DATA" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.w_4, nullptr, nullptr, nullptr }, "INTERLEAVED_W_4" }, + { { &e.lookup_read_counts, &e.lookup_read_tags, nullptr, nullptr }, "INTERLEAVED_LOOKUP" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.lookup_inverses, &e.calldata_inverses, &e.secondary_calldata_inverses, &e.return_data_inverses }, + "INTERLEAVED_INVERSES" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.z_perm, nullptr, nullptr, nullptr }, "INTERLEAVED_Z_PERM" }, + }; + } + + // Overloads for commitment-level types (InterleavedWitnessCommitments) + template + requires requires(E& e) { e.interleaved_wires; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.interleaved_wires }, "INTERLEAVED_WIRES" }, + { { &e.interleaved_ecc_op_wires }, "INTERLEAVED_ECC_OP_WIRES" }, + { { &e.interleaved_calldata }, "INTERLEAVED_CALLDATA" }, + { { &e.interleaved_secondary_calldata }, "INTERLEAVED_SECONDARY_CALLDATA" }, + { { &e.interleaved_databus_tags }, "INTERLEAVED_DATABUS_TAGS" }, + { { &e.interleaved_return_data_tags }, "INTERLEAVED_RETURN_DATA_TAGS" }, + { { &e.interleaved_return_data }, "INTERLEAVED_RETURN_DATA" }, + }; + } + + template + requires requires(E& e) { e.interleaved_w_4; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.interleaved_w_4 }, "INTERLEAVED_W_4" }, + { { &e.interleaved_lookup }, "INTERLEAVED_LOOKUP" }, + }; + } + + template + requires requires(E& e) { e.interleaved_inverses; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.interleaved_inverses }, "INTERLEAVED_INVERSES" }, + }; + } + + template + requires requires(E& e) { e.interleaved_z_perm; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.interleaved_z_perm }, "INTERLEAVED_Z_PERM" }, + }; + } +}; + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_recursive_flavor.hpp index afa333cb2105..684ae7aeb336 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_recursive_flavor.hpp @@ -46,6 +46,8 @@ template class MegaRecursiveFlavor_ { // To achieve fixed proof size and that the recursive verifier circuit is constant, we are using padding in Sumcheck // and Shplemini static constexpr bool USE_PADDING = MegaFlavor::USE_PADDING; + static constexpr size_t INTERLEAVING_BATCH_SIZE = MegaFlavor::INTERLEAVING_BATCH_SIZE; + static constexpr size_t INTERLEAVING_LOG_K = MegaFlavor::INTERLEAVING_LOG_K; static constexpr size_t NUM_WIRES = MegaFlavor::NUM_WIRES; static constexpr size_t NUM_ALL_ENTITIES = MegaFlavor::NUM_ALL_ENTITIES; static constexpr size_t NUM_PRECOMPUTED_ENTITIES = MegaFlavor::NUM_PRECOMPUTED_ENTITIES; @@ -66,6 +68,33 @@ template class MegaRecursiveFlavor_ { return MegaFlavor::FINAL_PCS_MSM_SIZE(log_n); }; static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = MegaFlavor::REPEATED_COMMITMENTS; + using OinkRounds = MegaFlavor::OinkRounds; + + // Group accessors and Lagrange basis (delegate to generic BS=1 helpers) + template static auto compute_lagrange_basis(std::span challenges) + { + return compute_lagrange_basis_impl(challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return GroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_unshifted_groups_mut(Entities& e) + { + return GroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + return GroupAccessors_::get_to_be_shifted_groups(e); + } + + template static auto get_shifted_groups(Entities& e) + { + return GroupAccessors_::get_shifted_groups(e); + } static constexpr size_t NUM_RELATIONS = std::tuple_size_v; @@ -99,4 +128,184 @@ template class MegaRecursiveFlavor_ { using VKAndHash = VKAndHash_; }; +/** + * @brief Recursive counterpart to MultiMegaFlavor with interleaved commitments. + * @details Handles interleaved witness/precomputed commitments and Lagrange basis evaluation batching. + */ +template class MultiMegaRecursiveFlavor_ : public MegaRecursiveFlavor_ { + public: + using CircuitBuilder = BuilderType; + using Curve = stdlib::bn254; + using PCS = KZG; + using GroupElement = typename Curve::Element; + using FF = typename Curve::ScalarField; + using Commitment = typename Curve::Element; + using NativeFlavor = MultiMegaFlavor; + using Codec = stdlib::StdlibCodec; + using Transcript = StdlibTranscript; + + // Inherit interleaving parameters from native flavor + static constexpr size_t INTERLEAVING_BATCH_SIZE = NativeFlavor::INTERLEAVING_BATCH_SIZE; + static constexpr size_t INTERLEAVING_LOG_K = NativeFlavor::INTERLEAVING_LOG_K; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = NativeFlavor::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = + NativeFlavor::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_ALL_INTERLEAVED_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + static constexpr size_t NUM_WITNESS_ENTITIES = NativeFlavor::NUM_WITNESS_ENTITIES; + static constexpr size_t NUM_ALL_ENTITIES = NativeFlavor::NUM_ALL_ENTITIES; + static constexpr size_t NUM_UNSHIFTED_ENTITIES = NativeFlavor::NUM_UNSHIFTED_ENTITIES; + + static constexpr bool HasZK = false; + + // Labels are string-based and can be inherited directly from the native flavor + using InterleavedCommitmentLabels = typename NativeFlavor::InterleavedCommitmentLabels; + using CommitmentLabels = typename NativeFlavor::CommitmentLabels; + static constexpr bool USE_PADDING = NativeFlavor::USE_PADDING; + + // BATCHED_RELATION_PARTIAL_LENGTH must match native flavor + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = BATCHED_RELATION_PARTIAL_LENGTH - 1; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } + + // Reuse native flavor's InterleavedWitnessCommitments template + template + using InterleavedWitnessCommitments = NativeFlavor::InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + + template + using InterleavedPrecomputedCommitments = NativeFlavor::InterleavedPrecomputedCommitments; + using InterleavedPrecomputed = InterleavedPrecomputedCommitments; + + class AllValues : public MegaFlavor::AllEntities_ { + public: + using Base = MegaFlavor::AllEntities_; + using Base::Base; + }; + + using VerificationKey = StdlibVerificationKey_, + NativeFlavor::VerificationKey>; + + using VerifierCommitments = MegaFlavor::VerifierCommitments_; + + using VKAndHash = VKAndHash_; + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + using OinkRounds = NativeFlavor::OinkRounds; + + // Forward compute_lagrange_basis to native flavor + template static auto compute_lagrange_basis(std::span interleaving_challenges) + { + return NativeFlavor::compute_lagrange_basis(interleaving_challenges); + } + + // Forward static group methods to the native flavor + template static auto get_unshifted_groups(Entities& e) + { + return NativeFlavor::get_unshifted_groups(e); + } + template static auto get_to_be_shifted_groups(Entities& e) + { + return NativeFlavor::get_to_be_shifted_groups(e); + } + template static auto get_shifted_groups(Entities& e) + { + return NativeFlavor::get_shifted_groups(e); + } +}; + +/** + * @brief Recursive counterpart to DualMegaFlavor (BS=2 interleaved commitments). + */ +template class DualMegaRecursiveFlavor_ : public MegaRecursiveFlavor_ { + public: + using CircuitBuilder = BuilderType; + using Curve = stdlib::bn254; + using PCS = KZG; + using GroupElement = typename Curve::Element; + using FF = typename Curve::ScalarField; + using Commitment = typename Curve::Element; + using NativeFlavor = DualMegaFlavor; + using Codec = stdlib::StdlibCodec; + using Transcript = StdlibTranscript; + + // Inherit interleaving parameters from native flavor + static constexpr size_t INTERLEAVING_BATCH_SIZE = NativeFlavor::INTERLEAVING_BATCH_SIZE; + static constexpr size_t INTERLEAVING_LOG_K = NativeFlavor::INTERLEAVING_LOG_K; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = NativeFlavor::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = + NativeFlavor::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_ALL_INTERLEAVED_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + static constexpr size_t NUM_WITNESS_ENTITIES = NativeFlavor::NUM_WITNESS_ENTITIES; + static constexpr size_t NUM_ALL_ENTITIES = NativeFlavor::NUM_ALL_ENTITIES; + static constexpr size_t NUM_UNSHIFTED_ENTITIES = NativeFlavor::NUM_UNSHIFTED_ENTITIES; + + static constexpr bool HasZK = false; + + using InterleavedCommitmentLabels = typename NativeFlavor::InterleavedCommitmentLabels; + using CommitmentLabels = typename NativeFlavor::CommitmentLabels; + static constexpr bool USE_PADDING = NativeFlavor::USE_PADDING; + + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = BATCHED_RELATION_PARTIAL_LENGTH - 1; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } + + template + using InterleavedWitnessCommitments = NativeFlavor::InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + + template + using InterleavedPrecomputedCommitments = NativeFlavor::InterleavedPrecomputedCommitments; + using InterleavedPrecomputed = InterleavedPrecomputedCommitments; + + class AllValues : public MegaFlavor::AllEntities_ { + public: + using Base = MegaFlavor::AllEntities_; + using Base::Base; + }; + + using VerificationKey = StdlibVerificationKey_, + NativeFlavor::VerificationKey>; + + using VerifierCommitments = MegaFlavor::VerifierCommitments_; + + using VKAndHash = VKAndHash_; + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + using OinkRounds = NativeFlavor::OinkRounds; + + template static auto compute_lagrange_basis(std::span interleaving_challenges) + { + return NativeFlavor::compute_lagrange_basis(interleaving_challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return NativeFlavor::get_unshifted_groups(e); + } + template static auto get_to_be_shifted_groups(Entities& e) + { + return NativeFlavor::get_to_be_shifted_groups(e); + } + template static auto get_shifted_groups(Entities& e) + { + return NativeFlavor::get_shifted_groups(e); + } +}; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_zk_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_zk_flavor.hpp index 18e919102d3f..f817e6e7cdb1 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_zk_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_zk_flavor.hpp @@ -12,44 +12,89 @@ namespace bb { /** - * @brief Child class of MegaFlavor that runs with ZK Sumcheck. - * - * @details MegaZKFlavor enables ZK sumcheck (Libra masking, row-disabling, extended relation degree) - * but does NOT include a Gemini masking polynomial in its entities. In the batched Chonk context, - * the translator's masking polynomial (sized at 2^17 = joint circuit size) serves as the single - * Gemini masking polynomial for the joint PCS. + * @brief ZK child of MegaFlavor_ — adds ZK sumcheck, masking entities, and Libra commitments. + * @details MegaZKFlavor_<1> is the individual-polynomial ZK Mega flavor (gemini_masking_poly). + * MegaZKFlavor_<4> is the interleaved ZK flavor (masking_chunk_0..3) for the hiding kernel. */ -class MegaZKFlavor : public bb::MegaFlavor { +template class MegaZKFlavor_ : public MegaFlavor_ { public: - // MegaZK is only used in production to prove the Hiding Kernel - static constexpr size_t VIRTUAL_LOG_N = HIDING_KERNEL_LOG_N; + using Base = MegaFlavor_; + using typename Base::Commitment; + using typename Base::Curve; + using typename Base::FF; + using typename Base::VerificationKey; - // Indicates that this flavor runs with ZK Sumcheck. + static constexpr size_t VIRTUAL_LOG_N = HIDING_KERNEL_LOG_N; static constexpr bool HasZK = true; - - // MegaZK does not include a Gemini masking polynomial in its entities; the translator provides one - // at the correct joint circuit size in the batched Chonk flow. + // MegaZK never includes a standalone Gemini masking poly — the translator provides it in the batched flow. static constexpr bool HasGeminiMasking = false; - // The degree has to be increased because the relation is multiplied by the Row Disabling Polynomial - static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = MegaFlavor::BATCHED_RELATION_PARTIAL_LENGTH + 1; + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Base::BATCHED_RELATION_PARTIAL_LENGTH + 1; static_assert(BATCHED_RELATION_PARTIAL_LENGTH == Curve::LIBRA_UNIVARIATES_LENGTH, - "LIBRA_UNIVARIATES_LENGTH must be equal to MegaZKFlavor::BATCHED_RELATION_PARTIAL_LENGTH"); + "LIBRA_UNIVARIATES_LENGTH must be equal to MegaZKFlavor_::BATCHED_RELATION_PARTIAL_LENGTH"); + + // MegaZK has no masking entities in its layout (translator provides masking in the batched flow) + static constexpr size_t NUM_MASKING_ENTITIES = 0; + + static constexpr size_t NUM_ALL_ENTITIES = Base::NUM_ALL_ENTITIES + NUM_MASKING_ENTITIES; + static constexpr size_t NUM_UNSHIFTED_ENTITIES = Base::NUM_UNSHIFTED_ENTITIES + NUM_MASKING_ENTITIES; + // For BS=1: no extra witness entity (masking is handled outside entity layout) + // For BS>1: masking chunks flow through interleaved groups, not the individual witness list + static constexpr size_t NUM_WITNESS_ENTITIES = Base::NUM_WITNESS_ENTITIES; + + // Override AllEntities with no masking entities (translator provides masking in the batched flow) + template using AllEntities = typename Base::template AllEntities_; + + using AllValues = typename Base::template AllValues_; + using ProverPolynomials = typename Base::template ProverPolynomials_; + using PartiallyEvaluatedMultivariates = typename Base::template PartiallyEvaluatedMultivariates_; + using VerifierCommitments = typename Base::template VerifierCommitments_; - // Shplemini's remove_repeated_commitments uses offset = HasZK ? 2 : 1. Since MegaZK has HasZK=true - // but no masking poly in its entities, the offset is 1 larger than the actual entity layout. - // Compensate by shifting indices by -1 relative to MegaFlavor's REPEATED_COMMITMENTS. - static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = RepeatedCommitmentsData( - NUM_PRECOMPUTED_ENTITIES - 1, NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES - 1, NUM_SHIFTED_ENTITIES); + template using ProverUnivariates = AllEntities>; + using ExtendedEdges = ProverUnivariates; + + // Interleaved types: for BS=1 these are empty types; for BS>1 they carry the ZK masking members + template + using InterleavedWitnessCommitments = typename Base::template InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + using InterleavedCommitmentLabels = typename Base::template InterleavedCommitmentLabels_; + + // No extra interleaved groups — masking is handled by the translator in the batched flow. + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = Base::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = + Base::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS + NUM_INTERLEAVED_WITNESS_COMMITMENTS; + + using Transcript = NativeTranscript; + using VKAndHash = typename Base::VKAndHash; + + // BS=1: no gemini_masking_poly in entity layout, default shplemini_offset=1. BS>1: offset=1 (masking is + // interleaved). + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = + (BATCH_SIZE_ == 1) + ? RepeatedCommitmentsData(Base::NUM_PRECOMPUTED_ENTITIES, + Base::NUM_PRECOMPUTED_ENTITIES + Base::NUM_WITNESS_ENTITIES, + Base::NUM_SHIFTED_ENTITIES) + : RepeatedCommitmentsData(NUM_ALL_INTERLEAVED_COMMITMENTS - Base::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS, + NUM_ALL_INTERLEAVED_COMMITMENTS, + Base::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS); - // Size of the final PCS MSM for ZK = non-ZK size + NUM_LIBRA_COMMITMENTS (3) static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) { - return NUM_UNSHIFTED_ENTITIES + log_n + 2 + NUM_LIBRA_COMMITMENTS; + if constexpr (BATCH_SIZE_ == 1) { + // BS=1 ZK: use individual entity count (includes gemini_masking_poly) + return NUM_UNSHIFTED_ENTITIES + log_n + 2 + NUM_LIBRA_COMMITMENTS; + } else { + // BS>1 ZK: use interleaved commitment count (includes masking group) + const size_t pcs_log_n = log_n + Base::INTERLEAVING_LOG_K; + return NUM_ALL_INTERLEAVED_COMMITMENTS + pcs_log_n + 2 + NUM_LIBRA_COMMITMENTS; + } } - using Transcript = NativeTranscript; - using VKAndHash = MegaFlavor::VKAndHash; + // No group accessor overrides — MegaZK has no masking entities. Base class accessors are used directly. }; +using MegaZKFlavor = MegaZKFlavor_<1>; +using DualMegaZKFlavor = MegaZKFlavor_<2>; +using MultiMegaZKFlavor = MegaZKFlavor_<4>; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/mega_zk_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/mega_zk_recursive_flavor.hpp index 66c2f7943451..f239168ae4e2 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/mega_zk_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/mega_zk_recursive_flavor.hpp @@ -28,13 +28,83 @@ template class MegaZKRecursiveFlavor_ : public MegaRecurs static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } +}; + +/** + * @brief Recursive counterpart to MultiMegaZKFlavor with interleaved commitments and ZK. + * @details Used to instantiate a recursive verifier for ZK proofs created using MultiMegaZKFlavor + * (the hiding kernel in Chonk IVC). + */ +template class MultiMegaZKRecursiveFlavor_ : public MultiMegaRecursiveFlavor_ { + public: + using NativeFlavor = MultiMegaZKFlavor; + using Commitment = typename MultiMegaRecursiveFlavor_::Commitment; + using VerificationKey = typename MultiMegaRecursiveFlavor_::VerificationKey; + using FF = typename MultiMegaRecursiveFlavor_::FF; + + static constexpr bool HasZK = true; + static constexpr bool HasGeminiMasking = false; + + // VIRTUAL_LOG_N differs from parent (HIDING_KERNEL_LOG_N vs CONST_FOLDING_LOG_N) + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) { return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); } + + class AllValues : public NativeFlavor::template AllEntities { + public: + using Base = NativeFlavor::template AllEntities; + using Base::Base; + }; + + // Use false for masking parameter — MegaZK has no masking entities (translator provides masking) + using VerifierCommitments = MultiMegaFlavor::VerifierCommitments_; + using InterleavedPrecomputed = typename MultiMegaRecursiveFlavor_::InterleavedPrecomputed; +}; + +/** + * @brief Recursive counterpart to DualMegaZKFlavor (BS=2 interleaved + ZK). + */ +template class DualMegaZKRecursiveFlavor_ : public DualMegaRecursiveFlavor_ { + public: + using NativeFlavor = DualMegaZKFlavor; + using Commitment = typename DualMegaRecursiveFlavor_::Commitment; + using VerificationKey = typename DualMegaRecursiveFlavor_::VerificationKey; + using FF = typename DualMegaRecursiveFlavor_::FF; + + static constexpr bool HasZK = true; + static constexpr bool HasGeminiMasking = false; + + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } + + class AllValues : public NativeFlavor::template AllEntities { + public: + using Base = NativeFlavor::template AllEntities; + using Base::Base; + }; + + // Use false for masking parameter — MegaZK has no masking entities (translator provides masking) + using VerifierCommitments = DualMegaFlavor::VerifierCommitments_; + using InterleavedPrecomputed = typename DualMegaRecursiveFlavor_::InterleavedPrecomputed; }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp b/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp index a7290f15d807..b7c87d26da77 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp @@ -24,13 +24,13 @@ namespace bb { template class ProverPolynomialsBase : public AllEntitiesBase { public: - // Define all operations as default, except copy construction/assignment ProverPolynomialsBase() = default; ProverPolynomialsBase& operator=(const ProverPolynomialsBase&) = delete; ProverPolynomialsBase(const ProverPolynomialsBase& o) = delete; ProverPolynomialsBase(ProverPolynomialsBase&& o) noexcept = default; ProverPolynomialsBase& operator=(ProverPolynomialsBase&& o) noexcept = default; ~ProverPolynomialsBase() = default; + [[nodiscard]] size_t get_polynomial_size() const { return this->q_c.virtual_size(); } [[nodiscard]] AllValuesType get_row(size_t row_idx) const { @@ -74,6 +74,93 @@ class ProverPolynomialsBase : public AllEntitiesBase { return result; } + /** + * @brief Build a temporary interleaved polynomial from a group of entity pointers. + * @details Interleaves entity data: result[i*BS + j] = entity_j[i]. Null slots are zero. + * The result is a fresh polynomial (not a view), suitable for commitment or PCS batching. + * @param group Vector of entity pointers (some may be nullptr for padding). + * @param virtual_size The dyadic circuit size (entity-level). + * @param batch_size The interleaving width (BS). + * @param shiftable If true, the buffer starts at index BS (shiftable by BS). + */ + template + static Polynomial build_interleaved_polynomial(const Group& group, + size_t virtual_size, + size_t batch_size, + bool shiftable = false) + { + size_t max_end = 0; + for (size_t j = 0; j < group.size(); j++) { + if (group[j] != nullptr) { + max_end = std::max(max_end, group[j]->end_index()); + } + } + if (max_end == 0) { + return {}; + } + + const size_t buffer_size = max_end * batch_size; + const size_t buffer_virtual_size = virtual_size * batch_size; + + Polynomial buf = shiftable ? Polynomial::shiftable(buffer_size, buffer_virtual_size, batch_size) + : Polynomial(buffer_size, buffer_virtual_size); + + for (size_t j = 0; j < group.size(); j++) { + if (group[j] != nullptr) { + const auto& entity = *group[j]; + for (size_t i = entity.start_index(); i < entity.end_index(); i++) { + buf.at(i * batch_size + j) = entity[i]; + } + } + } + return buf; + } + + /** + * @brief Pre-batch interleaved groups into F and G using rho powers, freeing entity memory. + * @details Shifted groups are processed first (they share polys with last unshifted groups). + * Unshifted groups are freed greedily after consumption. + * @param Flavor The flavor providing GroupAccessors and NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS. + * @return {F, G} — rho-weighted batched polynomials of size n * BS. + */ + template + static std::pair batch_interleaved_groups_with_rho( + ProverPolynomialsBase&& polynomials, const FF& rho, size_t n, size_t batch_size, size_t pcs_size) + { + constexpr size_t NUM_SHIFTABLE = Flavor::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + auto unshifted_groups = Flavor::get_unshifted_groups_mut(polynomials); + auto shifted_groups = Flavor::get_to_be_shifted_groups(polynomials); + const size_t num_groups = unshifted_groups.size(); + const size_t shiftable_start = num_groups - NUM_SHIFTABLE; + + Polynomial batched_unshifted(pcs_size); + Polynomial batched_to_be_shifted = Polynomial::shiftable(pcs_size, pcs_size, batch_size); + + // Shifted groups first (they share polys with last unshifted groups — must read before freeing) + FF rho_shifted = rho.pow(num_groups); + for (size_t g = 0; g < shifted_groups.size(); g++) { + auto buf = build_interleaved_polynomial(shifted_groups[g], n, batch_size, true); + batched_to_be_shifted.add_scaled(buf, rho_shifted); + rho_shifted *= rho; + } + + // Unshifted groups with greedy freeing + FF rho_power(1); + for (size_t g = 0; g < num_groups; g++) { + auto buf = build_interleaved_polynomial(unshifted_groups[g], n, batch_size, g >= shiftable_start); + batched_unshifted.add_scaled(buf, rho_power); + rho_power *= rho; + for (auto* ptr : unshifted_groups[g]) { + if (ptr != nullptr) { + *ptr = Polynomial(); + } + } + } + + return { std::move(batched_unshifted), std::move(batched_to_be_shifted) }; + } + void increase_polynomials_virtual_size(const size_t size_in) { for (auto& polynomial : this->get_all()) { diff --git a/barretenberg/cpp/src/barretenberg/flavor/repeated_commitments_data.hpp b/barretenberg/cpp/src/barretenberg/flavor/repeated_commitments_data.hpp index 61f762e95141..8a55b1182cd7 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/repeated_commitments_data.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/repeated_commitments_data.hpp @@ -25,15 +25,19 @@ struct DuplicateRange { size_t duplicate_start = 0; // start index of the duplicate (shifted) entries in AllEntities size_t count = 0; // number of polynomials in this range }; - struct RepeatedCommitmentsData { DuplicateRange first; DuplicateRange second; + size_t shplemini_offset = 1; // number of entries before prover polys in shplemini vectors (Shplonk:Q, masking poly) RepeatedCommitmentsData() = default; - constexpr RepeatedCommitmentsData(size_t first_original_start, size_t first_duplicate_start, size_t first_count) + constexpr RepeatedCommitmentsData(size_t first_original_start, + size_t first_duplicate_start, + size_t first_count, + size_t shplemini_offset = 1) : first{ first_original_start, first_duplicate_start, first_count } + , shplemini_offset(shplemini_offset) {} constexpr RepeatedCommitmentsData(size_t first_original_start, @@ -41,9 +45,11 @@ struct RepeatedCommitmentsData { size_t first_count, size_t second_original_start, size_t second_duplicate_start, - size_t second_count) + size_t second_count, + size_t shplemini_offset = 1) : first{ first_original_start, first_duplicate_start, first_count } , second{ second_original_start, second_duplicate_start, second_count } + , shplemini_offset(shplemini_offset) {} }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp b/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp index f720abc1ea1a..1a581b4784a8 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp @@ -592,7 +592,162 @@ template struct MegaZKStructuredProofBase : MegaStructuredProo }; // ============================================================================ -// Translator proof structure (always ZK, with interleaved claims) +// MultiMega proof structure base +// ============================================================================ +template struct MultiMegaStructuredProofBase : StructuredProofHelper { + using Base = StructuredProofHelper; + using Base::BATCHED_RELATION_PARTIAL_LENGTH; + using Base::NUM_ALL_ENTITIES; + using typename Base::Commitment; + using typename Base::FF; + using typename Base::ProofData; + + static constexpr size_t INTERLEAVING_LOG_K = Flavor::INTERLEAVING_LOG_K; + + // Public inputs + std::vector public_inputs; + + // 11 interleaved witness commitments + 4 individual ecc_op_wire commits + Commitment interleaved_wires_comm; + Commitment interleaved_ecc_op_wires_comm; + Commitment ecc_op_wire_1_comm; + Commitment ecc_op_wire_2_comm; + Commitment ecc_op_wire_3_comm; + Commitment ecc_op_wire_4_comm; + Commitment interleaved_calldata_comm; + Commitment interleaved_secondary_calldata_comm; + Commitment interleaved_databus_tags_comm; + Commitment interleaved_return_data_tags_comm; + Commitment interleaved_return_data_comm; + Commitment interleaved_w_4_comm; + Commitment interleaved_lookup_comm; + Commitment interleaved_inverses_comm; + Commitment interleaved_z_perm_comm; + + // Sumcheck (operates on original polynomial size = log_n) + std::vector> sumcheck_univariates; + std::array sumcheck_evaluations; + + // PCS (operates on interleaved polynomial size = log_n + INTERLEAVING_LOG_K) + std::vector gemini_fold_comms; + std::vector gemini_fold_evals; + Commitment shplonk_q_comm; + Commitment kzg_w_comm; + + private: + void clear_vectors() + { + public_inputs.clear(); + sumcheck_univariates.clear(); + gemini_fold_comms.clear(); + gemini_fold_evals.clear(); + } + + public: + void deserialize(ProofData& proof_data, size_t num_public_inputs, size_t log_n) + { + const size_t pcs_log_n = log_n + INTERLEAVING_LOG_K; + size_t offset = 0; + clear_vectors(); + + // Public inputs + for (size_t i = 0; i < num_public_inputs; ++i) { + public_inputs.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + + // Round 1: 7 interleaved witness commitments + 4 individual ecc_op_wire commits + interleaved_wires_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_ecc_op_wires_comm = this->template deserialize_from_buffer(proof_data, offset); + ecc_op_wire_1_comm = this->template deserialize_from_buffer(proof_data, offset); + ecc_op_wire_2_comm = this->template deserialize_from_buffer(proof_data, offset); + ecc_op_wire_3_comm = this->template deserialize_from_buffer(proof_data, offset); + ecc_op_wire_4_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_calldata_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_secondary_calldata_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_databus_tags_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_return_data_tags_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_return_data_comm = this->template deserialize_from_buffer(proof_data, offset); + // Round 2 + interleaved_w_4_comm = this->template deserialize_from_buffer(proof_data, offset); + interleaved_lookup_comm = this->template deserialize_from_buffer(proof_data, offset); + // Round 3 + interleaved_inverses_comm = this->template deserialize_from_buffer(proof_data, offset); + // Round 4 + interleaved_z_perm_comm = this->template deserialize_from_buffer(proof_data, offset); + + // Sumcheck (log_n rounds, original polynomial size) + for (size_t i = 0; i < log_n; ++i) { + sumcheck_univariates.push_back( + this->template deserialize_from_buffer>(proof_data, + offset)); + } + sumcheck_evaluations = + this->template deserialize_from_buffer>(proof_data, offset); + + // PCS (pcs_log_n rounds, interleaved polynomial size) + for (size_t i = 0; i < pcs_log_n - 1; ++i) { + gemini_fold_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + for (size_t i = 0; i < pcs_log_n; ++i) { + gemini_fold_evals.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + shplonk_q_comm = this->template deserialize_from_buffer(proof_data, offset); + kzg_w_comm = this->template deserialize_from_buffer(proof_data, offset); + } + + void serialize(ProofData& proof_data, size_t log_n) const + { + const size_t pcs_log_n = log_n + INTERLEAVING_LOG_K; + size_t old_size = proof_data.size(); + proof_data.clear(); + + // Public inputs + for (const auto& pi : public_inputs) { + Base::serialize_to_buffer(pi, proof_data); + } + + // Round 1: 7 interleaved witness commitments + 4 individual ecc_op_wire commits + Base::serialize_to_buffer(interleaved_wires_comm, proof_data); + Base::serialize_to_buffer(interleaved_ecc_op_wires_comm, proof_data); + Base::serialize_to_buffer(ecc_op_wire_1_comm, proof_data); + Base::serialize_to_buffer(ecc_op_wire_2_comm, proof_data); + Base::serialize_to_buffer(ecc_op_wire_3_comm, proof_data); + Base::serialize_to_buffer(ecc_op_wire_4_comm, proof_data); + Base::serialize_to_buffer(interleaved_calldata_comm, proof_data); + Base::serialize_to_buffer(interleaved_secondary_calldata_comm, proof_data); + Base::serialize_to_buffer(interleaved_databus_tags_comm, proof_data); + Base::serialize_to_buffer(interleaved_return_data_tags_comm, proof_data); + Base::serialize_to_buffer(interleaved_return_data_comm, proof_data); + // Round 2 + Base::serialize_to_buffer(interleaved_w_4_comm, proof_data); + Base::serialize_to_buffer(interleaved_lookup_comm, proof_data); + // Round 3 + Base::serialize_to_buffer(interleaved_inverses_comm, proof_data); + // Round 4 + Base::serialize_to_buffer(interleaved_z_perm_comm, proof_data); + + // Sumcheck (log_n rounds) + for (size_t i = 0; i < log_n; ++i) { + Base::serialize_to_buffer(sumcheck_univariates[i], proof_data); + } + Base::serialize_to_buffer(sumcheck_evaluations, proof_data); + + // PCS (pcs_log_n rounds) + for (size_t i = 0; i < pcs_log_n - 1; ++i) { + Base::serialize_to_buffer(gemini_fold_comms[i], proof_data); + } + for (size_t i = 0; i < pcs_log_n; ++i) { + Base::serialize_to_buffer(gemini_fold_evals[i], proof_data); + } + Base::serialize_to_buffer(shplonk_q_comm, proof_data); + Base::serialize_to_buffer(kzg_w_comm, proof_data); + + BB_ASSERT_EQ(proof_data.size(), old_size); + } +}; + +// ============================================================================ +// Translator proof structure (always ZK) // ============================================================================ template struct TranslatorStructuredProofBase : StructuredProofHelper { using Base = StructuredProofHelper; @@ -987,6 +1142,9 @@ template <> struct StructuredProof : UltraZKStructuredProof template <> struct StructuredProof : MegaStructuredProofBase {}; template <> struct StructuredProof : MegaZKStructuredProofBase {}; +// MultiMega flavor +template <> struct StructuredProof : MultiMegaStructuredProofBase {}; + // Translator flavor template <> struct StructuredProof : TranslatorStructuredProofBase {}; diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp index 5f84f92f010d..79c8c27d7222 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_flavor.hpp @@ -8,9 +8,11 @@ #include "barretenberg/commitment_schemes/kzg/kzg.hpp" #include "barretenberg/flavor/flavor.hpp" #include "barretenberg/flavor/flavor_macros.hpp" +#include "barretenberg/flavor/mega_interleaving_entities.hpp" #include "barretenberg/flavor/partially_evaluated_multivariates.hpp" #include "barretenberg/flavor/prover_polynomials.hpp" #include "barretenberg/flavor/repeated_commitments_data.hpp" +#include "barretenberg/flavor/ultra_interleaving_entities.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/polynomials/univariate.hpp" #include "barretenberg/relations/delta_range_constraint_relation.hpp" @@ -29,7 +31,16 @@ namespace bb { -class UltraFlavor { +/** + * @brief The Ultra proving system flavor, parameterized on interleaving batch size. + * + * @details UltraFlavor_<1> (aliased as UltraFlavor) commits polynomials individually. + * UltraFlavor_<2> (aliased as DualUltraFlavor) batches 2 polynomials per interleaved + * commitment, reducing witness commitments from 8 to 5. + * + * @tparam BATCH_SIZE_ The number of polynomials interleaved per commitment (1 or 2). + */ +template class UltraFlavor_ { public: using CircuitBuilder = UltraCircuitBuilder; using Curve = curve::BN254; @@ -52,6 +63,11 @@ class UltraFlavor { // To achieve fixed proof size and that the recursive verifier circuit is constant, we are using padding in Sumcheck // and Shplemini static constexpr bool USE_PADDING = true; + + // Interleaving parameters + static constexpr size_t INTERLEAVING_BATCH_SIZE = BATCH_SIZE_; + static constexpr size_t INTERLEAVING_LOG_K = (BATCH_SIZE_ <= 1) ? 0 : (BATCH_SIZE_ <= 2) ? 1 : 2; + static constexpr size_t NUM_WIRES = CircuitBuilder::NUM_WIRES; // define the tuple of Relations that comprise the Sumcheck relation @@ -88,6 +104,8 @@ class UltraFlavor { static constexpr size_t num_frs_comm = FrCodec::calc_num_fields(); static constexpr size_t num_frs_fr = FrCodec::calc_num_fields(); + static constexpr size_t SHPLEMINI_OFFSET = 1; // Shplonk:Q + /** * @brief A base class labelling precomputed entities and (ordered) subsets of interest. * @details Used to build the proving key and verification key. @@ -174,6 +192,9 @@ class UltraFlavor { auto get_wires() { return RefArray{ w_l, w_r, w_o, w_4 }; }; auto get_to_be_shifted() { return RefArray{ w_l, w_r, w_o, w_4, z_perm }; }; + auto get_to_be_shifted() const { return RefArray{ w_l, w_r, w_o, w_4, z_perm }; }; + auto get_shiftable() { return get_to_be_shifted(); } + auto get_shiftable() const { return get_to_be_shifted(); } // All witness entities are masked in ZK mode auto get_masked() { return get_all(); } auto get_masked() const { return get_all(); } @@ -220,9 +241,19 @@ class UltraFlavor { PrecomputedEntities::get_all(), WitnessEntities::get_all()); }; + auto get_unshifted() const + { + return concatenate(MaskingEntities::get_all(), + PrecomputedEntities::get_all(), + WitnessEntities::get_all()); + }; auto get_precomputed() { return PrecomputedEntities::get_all(); } auto get_witness() { return WitnessEntities::get_all(); }; auto get_witness() const { return WitnessEntities::get_all(); }; + auto get_shifted() { return ShiftedEntities::get_all(); }; + auto get_shifted() const { return ShiftedEntities::get_all(); }; + auto get_to_be_shifted() { return WitnessEntities::get_to_be_shifted(); } + auto get_to_be_shifted() const { return WitnessEntities::get_to_be_shifted(); } }; // Default AllEntities alias (no ZK) @@ -235,18 +266,77 @@ class UltraFlavor { static constexpr size_t NUM_UNSHIFTED_ENTITIES = NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES; static constexpr size_t NUM_ALL_ENTITIES = NUM_UNSHIFTED_ENTITIES + NUM_SHIFTED_ENTITIES; - // A container to be fed to ShpleminiVerifier to avoid redundant scalar muls - static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = RepeatedCommitmentsData( - NUM_PRECOMPUTED_ENTITIES, NUM_PRECOMPUTED_ENTITIES + NUM_WITNESS_ENTITIES, NUM_SHIFTED_ENTITIES); + // ================================================================ + // Interleaving group accessors (Ultra-specific, BS-dependent) + // ================================================================ + + template static auto compute_lagrange_basis(std::span interleaving_challenges) + { + return compute_lagrange_basis_impl(interleaving_challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return UltraGroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_unshifted_groups_mut(Entities& e) + { + return UltraGroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + return UltraGroupAccessors_::get_to_be_shifted_groups(e); + } + + template static auto get_shifted_groups(Entities& e) + { + return UltraGroupAccessors_::get_shifted_groups(e); + } + + // Oink round group descriptors (Ultra-specific, BS-dependent) + using OinkRounds = UltraOinkWitnessRounds_; + + // ================================================================ + // BATCH_SIZE-dependent constants (via UltraInterleavingConstants_) + // ================================================================ + + using IC = UltraInterleavingConstants_; + + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = IC::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = IC::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = IC::NUM_ALL_INTERLEAVED_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = IC::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = + IC::make_repeated_commitments(NUM_PRECOMPUTED_ENTITIES, NUM_UNSHIFTED_ENTITIES, NUM_SHIFTED_ENTITIES); - // Size of the final PCS MSM after KZG adds quotient commitment: - // 1 (Shplonk Q) + NUM_UNSHIFTED + (log_n - 1) Gemini folds + 1 (G1 identity) + 1 (KZG W) - // (shifted commitments are removed as duplicates) static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) { - return NUM_UNSHIFTED_ENTITIES + log_n + 2; + return IC::final_pcs_msm_size(NUM_UNSHIFTED_ENTITIES, log_n); } + // ================================================================ + // Interleaved entity type aliases (from ultra_interleaving_entities.hpp) + // ================================================================ + + template + using InterleavedWitnessCommitments_ = UltraInterleavedWitnessCommitments_; + template using InterleavedWitnessCommitments = InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + + template + using InterleavedPrecomputedCommitments = UltraInterleavedPrecomputedCommitments_; + using InterleavedPrecomputed = InterleavedPrecomputedCommitments; + + using InterleavedCommitmentLabels = UltraInterleavedCommitmentLabels_; + using InterleavedPrecomputedLabels = UltraInterleavedPrecomputedLabels_; + + // ================================================================ + // AllValues, ProverPolynomials + // ================================================================ + /** * @brief A field element for each entity of the flavor. These entities represent the prover polynomials * evaluated at one point. @@ -269,11 +359,14 @@ class UltraFlavor { using PrecomputedData = PrecomputedData_; - /** - * @brief The verification key stores commitments to the precomputed (non-witness) polynomials used by the - * verifier. - */ - using VerificationKey = NativeVerificationKey_, Codec, HashFunction, CommitmentKey>; + // ================================================================ + // Verification Key + // ================================================================ + + using VKPrecomputedType = + typename UltraVKPrecomputedType_>::type; + + using VerificationKey = NativeVerificationKey_; using VKAndHash = VKAndHash_; @@ -312,80 +405,68 @@ class UltraFlavor { public: CommitmentLabels() { - w_l = "W_L"; - w_r = "W_R"; - w_o = "W_O"; - w_4 = "W_4"; - z_perm = "Z_PERM"; - lookup_inverses = "LOOKUP_INVERSES"; - lookup_read_counts = "LOOKUP_READ_COUNTS"; - lookup_read_tags = "LOOKUP_READ_TAGS"; - - q_c = "Q_C"; - q_l = "Q_L"; - q_r = "Q_R"; - q_o = "Q_O"; - q_4 = "Q_4"; - q_m = "Q_M"; - q_lookup = "Q_LOOKUP"; - q_arith = "Q_ARITH"; - q_delta_range = "Q_SORT"; - q_elliptic = "Q_ELLIPTIC"; - q_memory = "Q_MEMORY"; - q_nnf = "Q_NNF"; - q_poseidon2_external = "Q_POSEIDON2_EXTERNAL"; - q_poseidon2_internal = "Q_POSEIDON2_INTERNAL"; - sigma_1 = "SIGMA_1"; - sigma_2 = "SIGMA_2"; - sigma_3 = "SIGMA_3"; - sigma_4 = "SIGMA_4"; - id_1 = "ID_1"; - id_2 = "ID_2"; - id_3 = "ID_3"; - id_4 = "ID_4"; - table_1 = "TABLE_1"; - table_2 = "TABLE_2"; - table_3 = "TABLE_3"; - table_4 = "TABLE_4"; - lagrange_first = "LAGRANGE_FIRST"; - lagrange_last = "LAGRANGE_LAST"; + this->w_l = "W_L"; + this->w_r = "W_R"; + this->w_o = "W_O"; + this->w_4 = "W_4"; + this->z_perm = "Z_PERM"; + this->lookup_inverses = "LOOKUP_INVERSES"; + this->lookup_read_counts = "LOOKUP_READ_COUNTS"; + this->lookup_read_tags = "LOOKUP_READ_TAGS"; + + this->q_c = "Q_C"; + this->q_l = "Q_L"; + this->q_r = "Q_R"; + this->q_o = "Q_O"; + this->q_4 = "Q_4"; + this->q_m = "Q_M"; + this->q_lookup = "Q_LOOKUP"; + this->q_arith = "Q_ARITH"; + this->q_delta_range = "Q_SORT"; + this->q_elliptic = "Q_ELLIPTIC"; + this->q_memory = "Q_MEMORY"; + this->q_nnf = "Q_NNF"; + this->q_poseidon2_external = "Q_POSEIDON2_EXTERNAL"; + this->q_poseidon2_internal = "Q_POSEIDON2_INTERNAL"; + this->sigma_1 = "SIGMA_1"; + this->sigma_2 = "SIGMA_2"; + this->sigma_3 = "SIGMA_3"; + this->sigma_4 = "SIGMA_4"; + this->id_1 = "ID_1"; + this->id_2 = "ID_2"; + this->id_3 = "ID_3"; + this->id_4 = "ID_4"; + this->table_1 = "TABLE_1"; + this->table_2 = "TABLE_2"; + this->table_3 = "TABLE_3"; + this->table_4 = "TABLE_4"; + this->lagrange_first = "LAGRANGE_FIRST"; + this->lagrange_last = "LAGRANGE_LAST"; }; }; - /** - * @brief A container encapsulating all the commitments that the verifier receives (to precomputed polynomials and - * witness polynomials). - * - */ - template - class VerifierCommitments_ : public AllEntities_ { + // ================================================================ + // VerifierCommitments_ + // ================================================================ + + template + class VerifierCommitments_ : public AllEntities_ { public: - VerifierCommitments_(const std::shared_ptr& verification_key, - const std::optional>& witness_commitments = std::nullopt) + VerifierCommitments_(const std::shared_ptr& verification_key, + const std::optional>& witness_commitments = std::nullopt) { - // Copy the precomputed polynomial commitments into this - for (auto [precomputed, precomputed_in] : zip_view(this->get_precomputed(), verification_key->get_all())) { - precomputed = precomputed_in; - } - - // If provided, copy the witness polynomial commitments into this - if (witness_commitments.has_value()) { - for (auto [witness, witness_in] : - zip_view(this->get_witness(), witness_commitments.value().get_all())) { - witness = witness_in; - } - - // Set shifted commitments - this->w_l_shift = witness_commitments->w_l; - this->w_r_shift = witness_commitments->w_r; - this->w_o_shift = witness_commitments->w_o; - this->w_4_shift = witness_commitments->w_4; - this->z_perm_shift = witness_commitments->z_perm; - } + UltraVerifierCommitmentsInit_::init(*this, verification_key, witness_commitments); } }; // Specialize for Ultra (general case used in UltraRecursive). using VerifierCommitments = VerifierCommitments_; }; +// ============================================================ +// Type aliases +// ============================================================ + +using UltraFlavor = UltraFlavor_<1>; +using DualUltraFlavor = UltraFlavor_<2>; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_interleaving_entities.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_interleaving_entities.hpp new file mode 100644 index 000000000000..a04c496dd420 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_interleaving_entities.hpp @@ -0,0 +1,450 @@ +// === AUDIT STATUS === +// internal: { status: not started, auditors: [], commit: } +// external_1: { status: not started, auditors: [], commit: } +// external_2: { status: not started, auditors: [], commit: } +// ===================== + +#pragma once +#include "barretenberg/flavor/flavor_macros.hpp" +#include "barretenberg/flavor/mega_interleaving_entities.hpp" +#include "barretenberg/flavor/repeated_commitments_data.hpp" +#include +#include +#include + +namespace bb { + +// ============================================================ +// Ultra-specific interleaving entity specializations (BS=2) +// ============================================================ +// These structs define interleaving-dependent data for UltraFlavor_. +// BS=1 reuses the generic GroupAccessors_<1>, InterleavingConstants_<1>, +// and OinkGroupDescriptor from mega_interleaving_entities.hpp. +// BS=2 specializations provide Ultra-specific groupings (different entities from Mega). + +// ============================================================ +// Interleaved witness commitments (Ultra-specific) +// ============================================================ + +template class UltraInterleavedWitnessCommitments_; + +// BS=1: empty +template class UltraInterleavedWitnessCommitments_ { + public: + auto get_all() { return RefArray{}; } + auto get_all() const { return RefArray{}; } + static auto get_labels() { return std::vector{}; } + auto get_shiftable() { return RefArray{}; } + auto get_shiftable() const { return RefArray{}; } +}; + +// BS=2: 5 interleaved witness commitments (2 unshiftable + 3 shiftable) +template class UltraInterleavedWitnessCommitments_ { + public: + DEFINE_FLAVOR_MEMBERS(DataType, + interleaved_lookup, // [lookup_read_counts, lookup_read_tags] - unshiftable + interleaved_lookup_inverses, // [lookup_inverses, 0] - unshiftable + interleaved_wires, // [w_l, w_r] - shiftable + interleaved_w_o_w_4, // [w_o, w_4] - shiftable + interleaved_z_perm) // [z_perm, 0] - shiftable + + auto get_shiftable() { return RefArray{ interleaved_wires, interleaved_w_o_w_4, interleaved_z_perm }; } + auto get_shiftable() const { return RefArray{ interleaved_wires, interleaved_w_o_w_4, interleaved_z_perm }; } +}; + +// ============================================================ +// Interleaved precomputed commitments (Ultra-specific) +// ============================================================ + +template class UltraInterleavedPrecomputedCommitments_; + +// BS=1: empty +template class UltraInterleavedPrecomputedCommitments_ { + public: + using DataType = DataType_; + auto get_all() { return RefArray{}; } + auto get_all() const { return RefArray{}; } + static auto get_labels() { return std::vector{}; } + bool operator==(const UltraInterleavedPrecomputedCommitments_&) const = default; +}; + +// BS=2: 14 interleaved precomputed commitments (28 entities / 2) +template class UltraInterleavedPrecomputedCommitments_ { + public: + using DataType = DataType_; + DEFINE_FLAVOR_MEMBERS(DataType, + interleaved_precomputed_0, // P₁: [q_m, q_c] + interleaved_precomputed_1, // P₂: [q_l, q_r] + interleaved_precomputed_2, // P₃: [q_o, q_4] + interleaved_precomputed_3, // P₄: [q_lookup, q_arith] + interleaved_precomputed_4, // P₅: [q_delta_range, q_elliptic] + interleaved_precomputed_5, // P₆: [q_memory, q_nnf] + interleaved_precomputed_6, // P₇: [q_poseidon2_external, q_poseidon2_internal] + interleaved_precomputed_7, // P₈: [sigma_1, sigma_2] + interleaved_precomputed_8, // P₉: [sigma_3, sigma_4] + interleaved_precomputed_9, // P₁₀: [id_1, id_2] + interleaved_precomputed_10, // P₁₁: [id_3, id_4] + interleaved_precomputed_11, // P₁₂: [table_1, table_2] + interleaved_precomputed_12, // P₁₃: [table_3, table_4] + interleaved_precomputed_13) // P₁₄: [lagrange_first, lagrange_last] + bool operator==(const UltraInterleavedPrecomputedCommitments_&) const = default; +}; + +// ============================================================ +// VK precomputed type selector (Ultra-specific) +// ============================================================ + +template struct UltraVKPrecomputedType_ { + using type = PrecomputedEntitiesCommitment; // BS=1 default +}; +template +struct UltraVKPrecomputedType_<2, Commitment, PrecomputedEntitiesCommitment> { + using type = UltraInterleavedPrecomputedCommitments_; +}; + +// ============================================================ +// VerifierCommitments initialization (Ultra-specific, BS-dependent) +// ============================================================ + +template struct UltraVerifierCommitmentsInit_; + +template <> struct UltraVerifierCommitmentsInit_<1> { + template + static void init(Self& self, const std::shared_ptr& verification_key, const std::optional& witness_comms) + { + for (auto [dest, src] : zip_view(self.get_precomputed(), verification_key->get_all())) { + dest = src; + } + if (witness_comms.has_value()) { + for (auto [dest, src] : zip_view(self.get_witness(), witness_comms->get_all())) { + dest = src; + } + // Set shifted commitments + self.w_l_shift = witness_comms->w_l; + self.w_r_shift = witness_comms->w_r; + self.w_o_shift = witness_comms->w_o; + self.w_4_shift = witness_comms->w_4; + self.z_perm_shift = witness_comms->z_perm; + } + } +}; + +template <> struct UltraVerifierCommitmentsInit_<2> { + template + static void init(Self&, const std::shared_ptr&, const std::optional&) + { + // For BS > 1: verifier uses interleaved commitments directly for PCS verification. + } +}; + +// ============================================================ +// Interleaving constants (Ultra-specific) +// ============================================================ + +template struct UltraInterleavingConstants_; + +template <> struct UltraInterleavingConstants_<1> { + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = 0; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = 0; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = 0; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = 0; + + static constexpr RepeatedCommitmentsData make_repeated_commitments(size_t num_precomputed, + size_t num_unshifted, + size_t num_shifted) + { + return RepeatedCommitmentsData(num_precomputed, num_unshifted, num_shifted); + } + + static constexpr size_t final_pcs_msm_size(size_t num_unshifted, size_t log_n) { return num_unshifted + log_n + 2; } +}; + +template <> struct UltraInterleavingConstants_<2> { + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = 14; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = 5; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = + NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS + NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = 3; + + static constexpr RepeatedCommitmentsData make_repeated_commitments(size_t /*num_precomputed*/, + size_t /*num_unshifted*/, + size_t /*num_shifted*/) + { + return RepeatedCommitmentsData(NUM_ALL_INTERLEAVED_COMMITMENTS - NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS, + NUM_ALL_INTERLEAVED_COMMITMENTS, + NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS); + } + + static constexpr size_t final_pcs_msm_size(size_t /*num_unshifted*/, size_t log_n) + { + constexpr size_t LOG_K = 1; // log2(2) + return NUM_ALL_INTERLEAVED_COMMITMENTS + log_n + LOG_K + 2; + } +}; + +// ============================================================ +// Interleaved commitment labels (Ultra-specific) +// ============================================================ + +template +class UltraInterleavedCommitmentLabels_ : public UltraInterleavedWitnessCommitments_ { + public: + UltraInterleavedCommitmentLabels_() = default; +}; + +template <> class UltraInterleavedCommitmentLabels_<2> : public UltraInterleavedWitnessCommitments_ { + public: + UltraInterleavedCommitmentLabels_() + { + interleaved_wires = "INTERLEAVED_WIRES"; + interleaved_lookup = "INTERLEAVED_LOOKUP"; + interleaved_lookup_inverses = "INTERLEAVED_LOOKUP_INVERSES"; + interleaved_w_o_w_4 = "INTERLEAVED_W_O_W_4"; + interleaved_z_perm = "INTERLEAVED_Z_PERM"; + } +}; + +template +class UltraInterleavedPrecomputedLabels_ : public UltraInterleavedPrecomputedCommitments_ { + public: + UltraInterleavedPrecomputedLabels_() = default; +}; + +template <> +class UltraInterleavedPrecomputedLabels_<2> : public UltraInterleavedPrecomputedCommitments_ { + public: + UltraInterleavedPrecomputedLabels_() + { + interleaved_precomputed_0 = "INTERLEAVED_PRECOMPUTED_0"; + interleaved_precomputed_1 = "INTERLEAVED_PRECOMPUTED_1"; + interleaved_precomputed_2 = "INTERLEAVED_PRECOMPUTED_2"; + interleaved_precomputed_3 = "INTERLEAVED_PRECOMPUTED_3"; + interleaved_precomputed_4 = "INTERLEAVED_PRECOMPUTED_4"; + interleaved_precomputed_5 = "INTERLEAVED_PRECOMPUTED_5"; + interleaved_precomputed_6 = "INTERLEAVED_PRECOMPUTED_6"; + interleaved_precomputed_7 = "INTERLEAVED_PRECOMPUTED_7"; + interleaved_precomputed_8 = "INTERLEAVED_PRECOMPUTED_8"; + interleaved_precomputed_9 = "INTERLEAVED_PRECOMPUTED_9"; + interleaved_precomputed_10 = "INTERLEAVED_PRECOMPUTED_10"; + interleaved_precomputed_11 = "INTERLEAVED_PRECOMPUTED_11"; + interleaved_precomputed_12 = "INTERLEAVED_PRECOMPUTED_12"; + interleaved_precomputed_13 = "INTERLEAVED_PRECOMPUTED_13"; + } +}; + +// ============================================================ +// Group accessors (Ultra-specific, BS=2) +// ============================================================ + +template struct UltraGroupAccessors_; + +// BS=1: delegate to generic GroupAccessors_<1> +template <> struct UltraGroupAccessors_<1> { + template static auto get_unshifted_groups(Entities& e) + { + return GroupAccessors_<1>::template get_unshifted_groups(e); + } + template static auto get_to_be_shifted_groups(Entities& e) + { + return GroupAccessors_<1>::get_to_be_shifted_groups(e); + } + template static auto get_shifted_groups(Entities& e) + { + return GroupAccessors_<1>::get_shifted_groups(e); + } +}; + +// BS=2: explicit interleaved groups for Ultra entities +template <> struct UltraGroupAccessors_<2> { + template static auto get_unshifted_groups(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t; + using Group = std::vector; + return std::vector{ + // P₁-P₁₄: precomputed (sequential pairs) + { &e.q_m, &e.q_c }, + { &e.q_l, &e.q_r }, + { &e.q_o, &e.q_4 }, + { &e.q_lookup, &e.q_arith }, + { &e.q_delta_range, &e.q_elliptic }, + { &e.q_memory, &e.q_nnf }, + { &e.q_poseidon2_external, &e.q_poseidon2_internal }, + { &e.sigma_1, &e.sigma_2 }, + { &e.sigma_3, &e.sigma_4 }, + { &e.id_1, &e.id_2 }, + { &e.id_3, &e.id_4 }, + { &e.table_1, &e.table_2 }, + { &e.table_3, &e.table_4 }, + { &e.lagrange_first, &e.lagrange_last }, + // Unshiftable witness groups + { &e.lookup_read_counts, &e.lookup_read_tags }, + { &e.lookup_inverses, nullptr }, + // Shiftable witness groups at end + { &e.w_l, &e.w_r }, + { &e.w_o, &e.w_4 }, + { &e.z_perm, nullptr }, + }; + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l, &e.w_r }, + { &e.w_o, &e.w_4 }, + { &e.z_perm, nullptr }, + }; + } + + template static auto get_shifted_groups(Entities& e) + { + using T = std::decay_t; + using Group = std::vector; + return std::vector{ + { &e.w_l_shift, &e.w_r_shift }, + { &e.w_o_shift, &e.w_4_shift }, + { &e.z_perm_shift, nullptr }, + }; + } +}; + +// ============================================================ +// Oink round group descriptors (Ultra-specific, BS-dependent) +// ============================================================ + +template struct UltraOinkWitnessRounds_; + +// BS=1: individual polynomial descriptors +template <> struct UltraOinkWitnessRounds_<1> { + template static auto wires(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.w_l }, "W_L" }, + { { &e.w_r }, "W_R" }, + { { &e.w_o }, "W_O" }, + }; + } + template static auto lookup_and_w4(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.lookup_read_counts }, "LOOKUP_READ_COUNTS" }, + { { &e.lookup_read_tags }, "LOOKUP_READ_TAGS" }, + { { &e.w_4 }, "W_4" }, + }; + } + template static auto inverses(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.lookup_inverses }, "LOOKUP_INVERSES" }, + }; + } + template static auto z_perm(Entities& e) + { + using T = std::decay_t; + using Ptr = std::conditional_t, T const*, T*>; + using D = OinkGroupDescriptor; + return std::vector{ + { { &e.z_perm }, "Z_PERM" }, + }; + } +}; + +// BS=2: interleaved group descriptors +template <> struct UltraOinkWitnessRounds_<2> { + private: + template + using Ptr = std::conditional_t, + std::decay_t().get_all()[0])> const*, + std::decay_t().get_all()[0])>*>; + template using D = OinkGroupDescriptor>; + + public: + // Entity-level overloads + template + requires requires(E& e) { e.w_l; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.w_l, &e.w_r }, "INTERLEAVED_WIRES" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.lookup_read_counts, &e.lookup_read_tags }, "INTERLEAVED_LOOKUP" }, + { { &e.w_o, &e.w_4 }, "INTERLEAVED_W_O_W_4" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.lookup_inverses, nullptr }, "INTERLEAVED_LOOKUP_INVERSES" }, + }; + } + + template + requires requires(E& e) { e.w_l; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.z_perm, nullptr }, "INTERLEAVED_Z_PERM" }, + }; + } + + // Commitment-level overloads + template + requires requires(E& e) { e.interleaved_wires; } + static auto wires(E& e) + { + return std::vector>{ + { { &e.interleaved_wires }, "INTERLEAVED_WIRES" }, + }; + } + + template + requires requires(E& e) { e.interleaved_w_o_w_4; } + static auto lookup_and_w4(E& e) + { + return std::vector>{ + { { &e.interleaved_lookup }, "INTERLEAVED_LOOKUP" }, + { { &e.interleaved_w_o_w_4 }, "INTERLEAVED_W_O_W_4" }, + }; + } + + template + requires requires(E& e) { e.interleaved_lookup_inverses; } + static auto inverses(E& e) + { + return std::vector>{ + { { &e.interleaved_lookup_inverses }, "INTERLEAVED_LOOKUP_INVERSES" }, + }; + } + + template + requires requires(E& e) { e.interleaved_z_perm; } + static auto z_perm(E& e) + { + return std::vector>{ + { { &e.interleaved_z_perm }, "INTERLEAVED_Z_PERM" }, + }; + } +}; + +} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_keccak_zk_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_keccak_zk_flavor.hpp index bec69c43024c..23c3aa8c132b 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_keccak_zk_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_keccak_zk_flavor.hpp @@ -8,6 +8,7 @@ #include "barretenberg/constants.hpp" #include "barretenberg/flavor/ultra_keccak_flavor.hpp" +#include "barretenberg/flavor/ultra_zk_flavor.hpp" namespace bb { @@ -36,6 +37,8 @@ class UltraKeccakZKFlavor : public UltraKeccakFlavor { static constexpr size_t NUM_UNSHIFTED_ENTITIES = UltraKeccakFlavor::NUM_UNSHIFTED_ENTITIES + NUM_MASKING_POLYNOMIALS; + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = UltraZKFlavor::REPEATED_COMMITMENTS; + // Size of the final PCS MSM for ZK = non-ZK size + NUM_LIBRA_COMMITMENTS (3) static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) { diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_recursive_flavor.hpp index 7b4d0d616bb3..d87b17851ee0 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_recursive_flavor.hpp @@ -12,22 +12,11 @@ namespace bb { /** - * @brief The recursive counterpart to the "native" Ultra flavor. - * @details This flavor can be used to instantiate a recursive Ultra Honk verifier for a proof created using the - * conventional Ultra flavor. It is similar in structure to its native counterpart with two main differences: 1) the - * curve types are stdlib types (e.g. field_t instead of field) and 2) it does not specify any Prover related types - * (e.g. Polynomial, ProverUnivariates, etc.) since we do not emulate prover computation in circuits, i.e. it only makes - * sense to instantiate a Verifier with this flavor. - * - * @note Unlike conventional flavors, "recursive" flavors are templated by a builder (much like native vs stdlib types). - * This is because the flavor itself determines the details of the underlying verifier algorithm (i.e. the set of - * relations), while the Builder determines the arithmetization of that algorithm into a circuit. - * - * @tparam BuilderType Determines the arithmetization of the verifier circuit defined based on this flavor. + * @brief The recursive counterpart to the "native" Ultra flavor (BS=1). */ template class UltraRecursiveFlavor_ { public: - using CircuitBuilder = BuilderType; // Determines arithmetization of circuit instantiated with this flavor + using CircuitBuilder = BuilderType; using Curve = stdlib::bn254; using PCS = KZG; using GroupElement = typename Curve::Element; @@ -39,14 +28,11 @@ template class UltraRecursiveFlavor_ { using Transcript = StdlibTranscript; static constexpr size_t VIRTUAL_LOG_N = UltraFlavor::VIRTUAL_LOG_N; - // indicates when evaluating sumcheck, edges can be left as degree-1 monomials static constexpr bool USE_SHORT_MONOMIALS = UltraFlavor::USE_SHORT_MONOMIALS; - - // Indicates that this flavor runs with non-ZK Sumcheck. static constexpr bool HasZK = false; - // To achieve fixed proof size and that the recursive verifier circuit is constant, we are using padding in Sumcheck - // and Shplemini static constexpr bool USE_PADDING = UltraFlavor::USE_PADDING; + static constexpr size_t INTERLEAVING_BATCH_SIZE = UltraFlavor::INTERLEAVING_BATCH_SIZE; + static constexpr size_t INTERLEAVING_LOG_K = UltraFlavor::INTERLEAVING_LOG_K; static constexpr size_t NUM_WIRES = UltraFlavor::NUM_WIRES; static constexpr size_t NUM_ALL_ENTITIES = UltraFlavor::NUM_ALL_ENTITIES; static constexpr size_t NUM_PRECOMPUTED_ENTITIES = UltraFlavor::NUM_PRECOMPUTED_ENTITIES; @@ -57,15 +43,40 @@ template class UltraRecursiveFlavor_ { return UltraFlavor::FINAL_PCS_MSM_SIZE(log_n); }; static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = UltraFlavor::REPEATED_COMMITMENTS; + using OinkRounds = UltraFlavor::OinkRounds; + + // Group accessors delegate to UltraGroupAccessors_ (BS=1) + template static auto compute_lagrange_basis(std::span challenges) + { + return compute_lagrange_basis_impl(challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return UltraGroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_unshifted_groups_mut(Entities& e) + { + return UltraGroupAccessors_::template get_unshifted_groups(e); + } + + template static auto get_to_be_shifted_groups(Entities& e) + { + return UltraGroupAccessors_::get_to_be_shifted_groups(e); + } + + template static auto get_shifted_groups(Entities& e) + { + return UltraGroupAccessors_::get_shifted_groups(e); + } - // define the tuple of Relations that comprise the Sumcheck relation using Relations = UltraFlavor::Relations_; static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = compute_max_partial_relation_length(); static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = MAX_PARTIAL_RELATION_LENGTH + 1; static constexpr size_t NUM_RELATIONS = std::tuple_size::value; - // A challenge whose powers are used to batch subrelation contributions during Sumcheck static constexpr size_t NUM_SUBRELATIONS = NativeFlavor::NUM_SUBRELATIONS; using SubrelationSeparator = FF; @@ -73,10 +84,6 @@ template class UltraRecursiveFlavor_ { NativeFlavor::PrecomputedEntities, typename NativeFlavor::VerificationKey>; - /** - * @brief A field element for each entity of the flavor. These entities represent the prover polynomials - * evaluated at one point. - */ class AllValues : public UltraFlavor::AllEntities { public: using Base = UltraFlavor::AllEntities; @@ -84,13 +91,95 @@ template class UltraRecursiveFlavor_ { }; using CommitmentLabels = UltraFlavor::CommitmentLabels; - using WitnessCommitments = UltraFlavor::WitnessEntities; - - // Reuse the VerifierCommitments from Ultra using VerifierCommitments = UltraFlavor::VerifierCommitments_; + using VKAndHash = VKAndHash_; +}; + +/** + * @brief Recursive counterpart to DualUltraFlavor (BS=2 interleaved). + */ +template class DualUltraRecursiveFlavor_ : public UltraRecursiveFlavor_ { + public: + using CircuitBuilder = BuilderType; + using Curve = stdlib::bn254; + using PCS = KZG; + using GroupElement = typename Curve::Element; + using FF = typename Curve::ScalarField; + using Commitment = typename Curve::Element; + using NativeFlavor = DualUltraFlavor; + using Codec = stdlib::StdlibCodec; + using Transcript = StdlibTranscript; + + static constexpr size_t INTERLEAVING_BATCH_SIZE = NativeFlavor::INTERLEAVING_BATCH_SIZE; + static constexpr size_t INTERLEAVING_LOG_K = NativeFlavor::INTERLEAVING_LOG_K; + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = NativeFlavor::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS = + NativeFlavor::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_ALL_INTERLEAVED_COMMITMENTS; + static constexpr size_t NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS = NativeFlavor::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS; + + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + static constexpr size_t NUM_WITNESS_ENTITIES = NativeFlavor::NUM_WITNESS_ENTITIES; + static constexpr size_t NUM_ALL_ENTITIES = NativeFlavor::NUM_ALL_ENTITIES; + static constexpr size_t NUM_UNSHIFTED_ENTITIES = NativeFlavor::NUM_UNSHIFTED_ENTITIES; + + static constexpr bool HasZK = false; + + using InterleavedCommitmentLabels = typename NativeFlavor::InterleavedCommitmentLabels; + using CommitmentLabels = typename NativeFlavor::CommitmentLabels; + static constexpr bool USE_PADDING = NativeFlavor::USE_PADDING; + + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr size_t MAX_PARTIAL_RELATION_LENGTH = BATCHED_RELATION_PARTIAL_LENGTH - 1; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } + + template + using InterleavedWitnessCommitments = NativeFlavor::InterleavedWitnessCommitments_; + using InterleavedCommitments = InterleavedWitnessCommitments; + + template + using InterleavedPrecomputedCommitments = NativeFlavor::InterleavedPrecomputedCommitments; + using InterleavedPrecomputed = InterleavedPrecomputedCommitments; + + class AllValues : public UltraFlavor::AllEntities_ { + public: + using Base = UltraFlavor::AllEntities_; + using Base::Base; + }; + + using VerificationKey = StdlibVerificationKey_, + NativeFlavor::VerificationKey>; + + using VerifierCommitments = UltraFlavor::VerifierCommitments_; using VKAndHash = VKAndHash_; + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + using OinkRounds = NativeFlavor::OinkRounds; + + template static auto compute_lagrange_basis(std::span interleaving_challenges) + { + return NativeFlavor::compute_lagrange_basis(interleaving_challenges); + } + + template static auto get_unshifted_groups(Entities& e) + { + return NativeFlavor::get_unshifted_groups(e); + } + template static auto get_to_be_shifted_groups(Entities& e) + { + return NativeFlavor::get_to_be_shifted_groups(e); + } + template static auto get_shifted_groups(Entities& e) + { + return NativeFlavor::get_shifted_groups(e); + } }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_flavor.hpp index 7960fc175503..7771d0d3c0ba 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_flavor.hpp @@ -11,52 +11,78 @@ namespace bb { -/*! -\brief Child class of UltraFlavor that runs with ZK Sumcheck. -\details -Most of the properties of UltraFlavor are inherited without any changes. However, the BATCHED_RELATION_PARTIAL_LENGTH is -incremented by 1, as we are using the sumcheck with disabled rows, where the main Honk relation is multiplied by a sum -of multilinear Lagranges. Additionally, the transcript contains extra elements, such as commitments and evaluations of -Libra polynomials used in Sumcheck to make it ZK, as well as a commitment and an evaluation of a hiding polynomials that -turns the PCS stage ZK. -*/ -class UltraZKFlavor : public UltraFlavor { +/** + * @brief ZK child of UltraFlavor_ — adds ZK sumcheck, masking entities, and Libra commitments. + * @details UltraZKFlavor_<1> is the individual-polynomial ZK Ultra flavor (gemini_masking_poly). + * UltraZKFlavor_<2> is the interleaved ZK flavor (no masking entities; masking provided + * externally in the batched flow, like MegaZKFlavor). + */ +template class UltraZKFlavor_ : public UltraFlavor_ { public: - // This flavor runs with ZK Sumcheck + using Base = UltraFlavor_; + using typename Base::Commitment; + using typename Base::Curve; + using typename Base::FF; + using typename Base::VerificationKey; + + static constexpr size_t VIRTUAL_LOG_N = CONST_PROOF_SIZE_LOG_N; static constexpr bool HasZK = true; + // BS=1: standalone UltraZK includes Gemini masking poly. BS>1: masking provided externally. + static constexpr bool HasGeminiMasking = (BATCH_SIZE_ == 1); + // The degree has to be increased because the relation is multiplied by the Row Disabling Polynomial + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = Base::BATCHED_RELATION_PARTIAL_LENGTH + 1; + static_assert(BATCHED_RELATION_PARTIAL_LENGTH == Curve::LIBRA_UNIVARIATES_LENGTH, + "LIBRA_UNIVARIATES_LENGTH must be equal to UltraZKFlavor_::BATCHED_RELATION_PARTIAL_LENGTH"); - // The number of entities added for ZK (gemini_masking_poly) - static constexpr size_t NUM_MASKING_POLYNOMIALS = 1; + // BS=1: 1 masking entity (gemini_masking_poly). BS>1: 0 (masking handled externally). + static constexpr size_t NUM_MASKING_ENTITIES = HasGeminiMasking ? 1 : 0; - // Determine the number of evaluations of Prover and Libra Polynomials that the Prover sends to the Verifier in - // the rounds of ZK Sumcheck. - static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = UltraFlavor::BATCHED_RELATION_PARTIAL_LENGTH + 1; - static_assert(BATCHED_RELATION_PARTIAL_LENGTH == Curve::LIBRA_UNIVARIATES_LENGTH, - "LIBRA_UNIVARIATES_LENGTH must be equal to UltraZKFlavor::BATCHED_RELATION_PARTIAL_LENGTH"); + static constexpr size_t NUM_ALL_ENTITIES = Base::NUM_ALL_ENTITIES + NUM_MASKING_ENTITIES; + static constexpr size_t NUM_UNSHIFTED_ENTITIES = Base::NUM_UNSHIFTED_ENTITIES + NUM_MASKING_ENTITIES; + static constexpr size_t NUM_WITNESS_ENTITIES = Base::NUM_WITNESS_ENTITIES + NUM_MASKING_ENTITIES; - // Override AllEntities to use ZK version (includes gemini_masking_poly via MaskingEntities) - template using AllEntities = UltraFlavor::AllEntities_; + // Override AllEntities: BS=1 uses HasZK=true (includes masking poly), BS>1 uses false (no masking entities) + template using AllEntities = typename Base::template AllEntities_; + + using AllValues = typename Base::template AllValues_; + using ProverPolynomials = typename Base::template ProverPolynomials_; + using PartiallyEvaluatedMultivariates = typename Base::template PartiallyEvaluatedMultivariates_; + using VerifierCommitments = + typename Base::template VerifierCommitments_; + + template using ProverUnivariates = AllEntities>; + using ExtendedEdges = ProverUnivariates; - // NUM_WITNESS_ENTITIES includes gemini_masking_poly - static constexpr size_t NUM_WITNESS_ENTITIES = UltraFlavor::NUM_WITNESS_ENTITIES + NUM_MASKING_POLYNOMIALS; - // NUM_ALL_ENTITIES includes gemini_masking_poly - static constexpr size_t NUM_ALL_ENTITIES = UltraFlavor::NUM_ALL_ENTITIES + NUM_MASKING_POLYNOMIALS; - // NUM_UNSHIFTED_ENTITIES includes gemini_masking_poly - static constexpr size_t NUM_UNSHIFTED_ENTITIES = UltraFlavor::NUM_UNSHIFTED_ENTITIES + NUM_MASKING_POLYNOMIALS; + // Interleaved types: for BS=1 these are empty; for BS>1 they carry the same structure as non-ZK + static constexpr size_t NUM_INTERLEAVED_WITNESS_COMMITMENTS = Base::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + static constexpr size_t NUM_ALL_INTERLEAVED_COMMITMENTS = + Base::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS + NUM_INTERLEAVED_WITNESS_COMMITMENTS; - // Size of the final PCS MSM for ZK = non-ZK size + NUM_LIBRA_COMMITMENTS (3) - static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = CONST_PROOF_SIZE_LOG_N) + using Transcript = NativeTranscript; + using VKAndHash = typename Base::VKAndHash; + + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = + (BATCH_SIZE_ == 1) + ? RepeatedCommitmentsData(Base::NUM_PRECOMPUTED_ENTITIES, + Base::NUM_PRECOMPUTED_ENTITIES + Base::NUM_WITNESS_ENTITIES, + Base::NUM_SHIFTED_ENTITIES, + /*shplemini_offset=*/2) // Shplonk:Q + Gemini:masking_poly_comm + : RepeatedCommitmentsData(NUM_ALL_INTERLEAVED_COMMITMENTS - Base::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS, + NUM_ALL_INTERLEAVED_COMMITMENTS, + Base::NUM_SHIFTABLE_INTERLEAVED_COMMITMENTS); + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) { - return NUM_UNSHIFTED_ENTITIES + log_n + 2 + NUM_LIBRA_COMMITMENTS; + if constexpr (BATCH_SIZE_ == 1) { + return NUM_UNSHIFTED_ENTITIES + log_n + 2 + NUM_LIBRA_COMMITMENTS; + } else { + const size_t pcs_log_n = log_n + Base::INTERLEAVING_LOG_K; + return NUM_ALL_INTERLEAVED_COMMITMENTS + pcs_log_n + 2 + NUM_LIBRA_COMMITMENTS; + } } +}; - using AllValues = UltraFlavor::AllValues_; - using ProverPolynomials = UltraFlavor::ProverPolynomials_; - using PartiallyEvaluatedMultivariates = UltraFlavor::PartiallyEvaluatedMultivariates_; - using VerifierCommitments = UltraFlavor::VerifierCommitments_; +using UltraZKFlavor = UltraZKFlavor_<1>; +using DualUltraZKFlavor = UltraZKFlavor_<2>; - // Override ProverUnivariates and ExtendedEdges to include gemini_masking_poly - template using ProverUnivariates = AllEntities>; - using ExtendedEdges = ProverUnivariates; -}; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_recursive_flavor.hpp index 2969f9251c1c..497c7799697b 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/ultra_zk_recursive_flavor.hpp @@ -11,9 +11,7 @@ namespace bb { /** - * @brief The recursive counterpart to UltraZKFlavor. - * @details Adds ZK overrides (HasZK, BATCHED_RELATION_PARTIAL_LENGTH, AllValues with masking entities) - * on top of UltraRecursiveFlavor_. + * @brief The recursive counterpart to UltraZKFlavor (BS=1). */ template class UltraZKRecursiveFlavor_ : public UltraRecursiveFlavor_ { public: @@ -29,6 +27,7 @@ template class UltraZKRecursiveFlavor_ : public UltraRecu static constexpr size_t NUM_ALL_ENTITIES = NativeFlavor::NUM_ALL_ENTITIES; static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = UltraRecursiveFlavor_::VIRTUAL_LOG_N) { @@ -45,4 +44,37 @@ template class UltraZKRecursiveFlavor_ : public UltraRecu using VerifierCommitments = UltraFlavor::VerifierCommitments_; }; +/** + * @brief Recursive counterpart to DualUltraZKFlavor (BS=2 interleaved + ZK). + */ +template class DualUltraZKRecursiveFlavor_ : public DualUltraRecursiveFlavor_ { + public: + using NativeFlavor = DualUltraZKFlavor; + using Commitment = typename DualUltraRecursiveFlavor_::Commitment; + using VerificationKey = typename DualUltraRecursiveFlavor_::VerificationKey; + using FF = typename DualUltraRecursiveFlavor_::FF; + + static constexpr bool HasZK = true; + static constexpr bool HasGeminiMasking = false; + + static constexpr size_t VIRTUAL_LOG_N = NativeFlavor::VIRTUAL_LOG_N; + + static constexpr size_t BATCHED_RELATION_PARTIAL_LENGTH = NativeFlavor::BATCHED_RELATION_PARTIAL_LENGTH; + static constexpr RepeatedCommitmentsData REPEATED_COMMITMENTS = NativeFlavor::REPEATED_COMMITMENTS; + + static constexpr size_t FINAL_PCS_MSM_SIZE(size_t log_n = VIRTUAL_LOG_N) + { + return NativeFlavor::FINAL_PCS_MSM_SIZE(log_n); + } + + class AllValues : public NativeFlavor::template AllEntities { + public: + using Base = NativeFlavor::template AllEntities; + using Base::Base; + }; + + using VerifierCommitments = DualUltraFlavor::VerifierCommitments_; + using InterleavedPrecomputed = typename DualUltraRecursiveFlavor_::InterleavedPrecomputed; +}; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/honk/proof_length.hpp b/barretenberg/cpp/src/barretenberg/honk/proof_length.hpp index 5916214ff3ca..3b6f0f177105 100644 --- a/barretenberg/cpp/src/barretenberg/honk/proof_length.hpp +++ b/barretenberg/cpp/src/barretenberg/honk/proof_length.hpp @@ -9,6 +9,8 @@ #include "barretenberg/constants.hpp" #include "barretenberg/ecc/fields/field_conversion.hpp" #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include namespace bb::ProofLength { @@ -31,13 +33,21 @@ template struct CodecConstants { /** * @brief Computes Oink proof length from flavor traits. - * @details Oink sends witness commitments (W_L, W_R, W_O, etc.). - * For ZK flavors, NUM_WITNESS_ENTITIES includes the Gemini masking polynomial commitment. + * @details For interleaved flavors, uses NUM_INTERLEAVED_WITNESS_COMMITMENTS instead of NUM_WITNESS_ENTITIES. + * Recursive flavors delegate to their NativeFlavor since proof sizes are native concepts. */ template struct Oink : CodecConstants { using CodecConstants::num_frs_in_comm; - static constexpr size_t LENGTH_WITHOUT_PUB_INPUTS = Flavor::NUM_WITNESS_ENTITIES * num_frs_in_comm; + static constexpr size_t num_witness_commitments = []() { + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE > 1) { + return Flavor::NUM_INTERLEAVED_WITNESS_COMMITMENTS; + } else { + return Flavor::NUM_WITNESS_ENTITIES; + } + }(); + + static constexpr size_t LENGTH_WITHOUT_PUB_INPUTS = num_witness_commitments * num_frs_in_comm; }; /** @@ -97,28 +107,26 @@ template struct Shplemini : CodecConstants { * @details Honk proof = Oink + Sumcheck + Shplemini. * Note: IPA proof is handled separately for rollup flavors (appended by prover, split by verifier). */ +/** + * @brief Full Honk proof layout (used by UltraVerifier). + * @details Honk proof = Oink + Sumcheck + Shplemini. + * For interleaved flavors (INTERLEAVING_LOG_K > 0), sumcheck operates at log_n while + * PCS operates at log_n + INTERLEAVING_LOG_K. + * Note: IPA proof is handled separately for rollup flavors (appended by prover, split by verifier). + */ template struct Honk { static constexpr size_t LENGTH_WITHOUT_PUB_INPUTS(size_t log_n) { + const size_t pcs_log_n = log_n + Flavor::INTERLEAVING_LOG_K; return Oink::LENGTH_WITHOUT_PUB_INPUTS + Sumcheck::LENGTH(log_n) + - Shplemini::LENGTH(log_n); + Shplemini::LENGTH(pcs_log_n); } - /** - * @brief Derive num_public_inputs from proof size. - * @param proof_size Total proof size in field elements - * @param log_n Log of circuit size (VIRTUAL_LOG_N for padded, vk->log_circuit_size for non-padded) - */ static constexpr size_t derive_num_public_inputs(size_t proof_size, size_t log_n) { return proof_size - LENGTH_WITHOUT_PUB_INPUTS(log_n); } - /** - * @brief Expected proof size for API-level validation (excludes user public inputs). - * @details Computes: IO::PUBLIC_INPUTS_SIZE + Honk proof + IPA proof (if IO::HasIPA) - * @tparam IO The IO type (DefaultIO, RollupIO, etc.) - */ template static constexpr size_t expected_proof_size(size_t log_n) { size_t size = IO::PUBLIC_INPUTS_SIZE + LENGTH_WITHOUT_PUB_INPUTS(log_n); diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp index b3df369f23f0..044945022405 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_prover.cpp @@ -19,11 +19,13 @@ HonkProof HypernovaDeciderProver::construct_proof(Accumulator& accumulator) // Open the commitments with Shplemini PolynomialBatcher polynomial_batcher(dyadic_size, actual_data_size); polynomial_batcher.set_unshifted(RefVector(accumulator.non_shifted_polynomial)); - polynomial_batcher.set_to_be_shifted_by_one(RefVector(accumulator.shifted_polynomial)); + polynomial_batcher.set_to_be_shifted(RefVector(accumulator.shifted_polynomial)); + + const auto rho = transcript->template get_challenge("rho"); OpeningClaim prover_opening_claim; prover_opening_claim = - ShpleminiProver::prove(dyadic_size, polynomial_batcher, accumulator.challenge, ck, transcript); + ShpleminiProver::prove(dyadic_size, polynomial_batcher, rho, accumulator.challenge, ck, transcript); vinfo("HypernovaFoldingDecider: executed multivariate-to-univariate reduction"); diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_verifier.test.cpp index e5ea7faa9a60..b869f5285c1a 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_decider_verifier.test.cpp @@ -159,9 +159,9 @@ class HypernovaDeciderVerifierTests : public ::testing::Test { recursive_instance->alpha = FF::from_witness(builder, native_instance->alpha); // Convert witness commitments - auto native_comms = native_instance->witness_commitments.get_all(); + auto native_comms = native_instance->received_commitments.get_all(); for (auto [native_comm, recursive_comm] : - zip_view(native_comms, recursive_instance->witness_commitments.get_all())) { + zip_view(native_comms, recursive_instance->received_commitments.get_all())) { recursive_comm = Commitment::from_witness(builder, native_comm); } @@ -186,11 +186,7 @@ class HypernovaDeciderVerifierTests : public ::testing::Test { recursive_instance->relation_parameters.public_input_delta = FF::from_witness(builder, native_instance->relation_parameters.public_input_delta); - // For ZK flavors: convert gemini_masking_commitment - if constexpr (NativeFlavor::HasZK) { - recursive_instance->gemini_masking_commitment = - Commitment::from_witness(builder, native_instance->gemini_masking_commitment); - } + // Note: hypernova only uses MegaFlavor (non-ZK), so no masking commitment conversion needed. return recursive_instance; } diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.cpp index c2893178f4e5..b6fc528fd2e9 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.cpp @@ -45,7 +45,7 @@ HypernovaFoldingVerifier::Accumulator HypernovaFoldingVerifier:: } // Batch commitments - VerifierCommitments verifier_commitments(instance->get_vk(), instance->witness_commitments); + VerifierCommitments verifier_commitments(instance->get_vk(), instance->received_commitments); Commitment batched_unshifted_commitment = batch_mul(verifier_commitments.get_unshifted(), unshifted_challenges); Commitment batched_shifted_commitment = batch_mul(verifier_commitments.get_to_be_shifted(), shifted_challenges); @@ -131,7 +131,7 @@ std::tuple::Accumulator> H const auto [unshifted_challenges, shifted_challenges] = get_hypernova_batching_challenges(transcript, NUM_UNSHIFTED_ENTITIES, NUM_SHIFTED_ENTITIES); - VerifierCommitments verifier_commitments(instance->get_vk(), instance->witness_commitments); + VerifierCommitments verifier_commitments(instance->get_vk(), instance->received_commitments); MultilinearBatchingVerifier batching_verifier(transcript); auto [sumcheck_batching_result, new_accumulator] = diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp index 79a42f9ea188..979ff24316d4 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_verifier.test.cpp @@ -105,9 +105,9 @@ class HypernovaFoldingVerifierTests : public ::testing::Test { recursive_instance->alpha = FF::from_witness(builder, native_instance->alpha); // Convert witness commitments - auto native_comms = native_instance->witness_commitments.get_all(); + auto native_comms = native_instance->received_commitments.get_all(); for (auto [native_comm, recursive_comm] : - zip_view(native_comms, recursive_instance->witness_commitments.get_all())) { + zip_view(native_comms, recursive_instance->received_commitments.get_all())) { recursive_comm = Commitment::from_witness(builder, native_comm); } @@ -132,12 +132,6 @@ class HypernovaFoldingVerifierTests : public ::testing::Test { recursive_instance->relation_parameters.public_input_delta = FF::from_witness(builder, native_instance->relation_parameters.public_input_delta); - // For ZK flavors: convert gemini_masking_commitment - if constexpr (NativeFlavor::HasZK) { - recursive_instance->gemini_masking_commitment = - Commitment::from_witness(builder, native_instance->gemini_masking_commitment); - } - return recursive_instance; } diff --git a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp index 29a3396862cf..907e90d8829d 100644 --- a/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/multilinear_batching/multilinear_batching_prover.cpp @@ -18,14 +18,14 @@ MultilinearBatchingProver::MultilinearBatchingProver(MultilinearBatchingProverCl void MultilinearBatchingProver::execute_commitments_round() { - BB_BENCH(); + BB_BENCH_NAME("MultilinearBatching::execute_commitments_round"); transcript->send_to_verifier("non_shifted_accumulator_commitment", key.non_shifted_accumulator_commitment); transcript->send_to_verifier("shifted_accumulator_commitment", key.shifted_accumulator_commitment); } void MultilinearBatchingProver::execute_challenges_and_evaluations_round() { - BB_BENCH(); + BB_BENCH_NAME("MultilinearBatching::execute_challenges_and_evaluations_round"); for (size_t i = 0; i < Flavor::VIRTUAL_LOG_N; i++) { transcript->send_to_verifier("accumulator_challenge_" + std::to_string(i), key.accumulator_challenge[i]); } @@ -36,7 +36,7 @@ void MultilinearBatchingProver::execute_challenges_and_evaluations_round() void MultilinearBatchingProver::execute_relation_check_rounds() { - BB_BENCH(); + BB_BENCH_NAME("MultilinearBatching::execute_relation_check_rounds"); using Sumcheck = SumcheckProver; // Each linearly independent subrelation contribution is multiplied by `alpha^i`, where @@ -58,7 +58,7 @@ void MultilinearBatchingProver::execute_relation_check_rounds() MultilinearBatchingProverClaim MultilinearBatchingProver::compute_new_claim() { - BB_BENCH(); + BB_BENCH_NAME("MultilinearBatching::compute_new_claim"); // Batching challenge: the new claim is computed as instance + challenge * accumulator auto claim_batching_challenge = transcript->get_challenge("claim_batching_challenge"); diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp index 15761d5f1d91..c9fb4094431e 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp @@ -32,6 +32,7 @@ SharedShiftedVirtualZeroesArray _clone(const SharedShiftedVirtualZeroesArray size_t right_expansion = 0, size_t left_expansion = 0) { + BB_ASSERT(!array.is_strided()); // deep copy requires contiguous memory size_t expanded_size = array.size() + right_expansion + left_expansion; BackingMemory backing_clone = BackingMemory::allocate(expanded_size); // zero any left extensions to the array @@ -221,6 +222,7 @@ template Polynomial& Polynomial::operator*=(const Fr& scal template void Polynomial::multiply_chunk(const ThreadChunk& chunk, const Fr& scaling_factor) { + BB_ASSERT(!is_strided()); // in-place multiply requires contiguous memory for (size_t i : chunk.range(size())) { data()[i] *= scaling_factor; } @@ -267,13 +269,13 @@ void Polynomial::add_scaled_chunk(const ThreadChunk& chunk, } } -template Polynomial Polynomial::shifted() const +template Polynomial Polynomial::shifted(size_t k) const { - BB_ASSERT_GTE(coefficients_.start_, static_cast(1)); + BB_ASSERT_GTE(coefficients_.start_, k); Polynomial result; result.coefficients_ = coefficients_; - result.coefficients_.start_ -= 1; - result.coefficients_.end_ -= 1; + result.coefficients_.start_ -= k; + result.coefficients_.end_ -= k; return result; } diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp index 235dba268daa..b6c8214edfab 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -102,20 +102,62 @@ template class Polynomial { {} /** - * @brief Utility to create a shiftable polynomial of given virtual size. + * @brief Utility to create a shiftable polynomial of given virtual size (actual size = virtual_size). + * @details Uses standard shift depth (NUM_ZERO_ROWS=1). For custom shift depth, use the 3-arg overload. */ static Polynomial shiftable(size_t virtual_size) { - return Polynomial( - /*actual size*/ virtual_size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS); + return Polynomial(virtual_size - NUM_ZERO_ROWS, virtual_size, NUM_ZERO_ROWS); } /** * @brief Utility to create a shiftable polynomial of given size and virtual size. + * @details Uses standard shift depth (NUM_ZERO_ROWS=1). For custom shift depth, use the 3-arg overload. */ static Polynomial shiftable(size_t size, size_t virtual_size) { - return Polynomial(/*actual size*/ size - NUM_ZERO_ROWS, virtual_size, /*shiftable offset*/ NUM_ZERO_ROWS); + return Polynomial(size - NUM_ZERO_ROWS, virtual_size, NUM_ZERO_ROWS); } + /** + * @brief Utility to create a shiftable polynomial with explicit shift depth (num_zero_rows). + * @details For interleaved polynomials where shift is by k>1 (e.g., k=4 for batch_size=4). + */ + static Polynomial shiftable(size_t size, size_t virtual_size, size_t num_zero_rows) + { + return Polynomial(size - num_zero_rows, virtual_size, num_zero_rows); + } + /** + * @brief Create a strided view into a shared backing buffer (e.g., an interleaved group buffer). + * + * @details For entity j in an interleaved group, logical index i maps to + * physical position buffer[stride * i + j]. Since data() already accounts for the + * group buffer's start_index (pointing past zero rows for shiftable groups), + * the internal offset_ is simply entity_index. The accessor formula becomes + * data()[(i - start_) * stride_ + entity_index]. + * + * @param backing Shared backing memory (refcounted, keeps group buffer alive). + * @param stride Step between consecutive logical elements in backing memory (= batch size). + * @param entity_index Position of this entity within the interleaved group (0..stride-1). + * @param start_index First logical index with data (e.g. 1 for shiftable polynomials). + * @param logical_size Number of logical elements backed by memory (end_ - start_). + * @param virtual_size Total logical size including virtual zeros. + */ + static Polynomial strided_view(BackingMemory backing, + size_t stride, + size_t entity_index, + size_t start_index, + size_t logical_size, + size_t virtual_size) + { + Polynomial p; + p.coefficients_.start_ = start_index; + p.coefficients_.end_ = start_index + logical_size; + p.coefficients_.virtual_size_ = virtual_size; + p.coefficients_.backing_memory_ = std::move(backing); + p.coefficients_.stride_ = stride; + p.coefficients_.offset_ = entity_index; + return p; + } + // Allow polynomials to be entirely reset/dormant Polynomial() = default; @@ -147,6 +189,7 @@ template class Polynomial { if (is_empty()) { throw_or_abort("Checking is_zero on an empty Polynomial!"); } + BB_ASSERT(!is_strided()); // is_zero requires contiguous memory for (size_t i = 0; i < size(); i++) { if (coefficients_.data()[i] != 0) { return false; @@ -169,12 +212,14 @@ template class Polynomial { bool is_empty() const { return coefficients_.size() == 0; } /** - * @brief Returns a Polynomial the left-shift of self. + * @brief Returns a Polynomial that is the left-shift-by-k of self. * - * @details If the n coefficients of self are (0, a₁, …, aₙ₋₁), - * we returns the view of the n-1 coefficients (a₁, …, aₙ₋₁). + * @details If k=1 and the n coefficients of self are (0, a₁, …, aₙ₋₁), + * returns the view of the n-1 coefficients (a₁, …, aₙ₋₁). + * For general k, requires the first k coefficients to be zero. + * @param k The shift magnitude (default 1). */ - Polynomial shifted() const; + Polynomial shifted(size_t k = 1) const; /** * @brief Returns the polynomial equal to the reverse of self @@ -252,8 +297,16 @@ template class Polynomial { std::size_t virtual_size() const { return coefficients_.virtual_size(); } void increase_virtual_size(const size_t size_in) { coefficients_.increase_virtual_size(size_in); }; - Fr* data() { return coefficients_.data(); } - const Fr* data() const { return coefficients_.data(); } + Fr* data() + { + BB_ASSERT_DEBUG(!coefficients_.is_strided()); + return coefficients_.data(); + } + const Fr* data() const + { + BB_ASSERT_DEBUG(!coefficients_.is_strided()); + return coefficients_.data(); + } /** * @brief Our mutable accessor, unlike operator[]. @@ -312,7 +365,13 @@ template class Polynomial { // The extents of the actual memory-backed polynomial region size_t start_index() const { return coefficients_.start_; } size_t end_index() const { return coefficients_.end_; } - bool is_shiftable() const { return start_index() == NUM_ZERO_ROWS; } + bool is_shiftable(size_t k = NUM_ZERO_ROWS) const { return start_index() >= k; } + bool is_strided() const { return coefficients_.is_strided(); } + size_t stride() const { return coefficients_.stride_; } + size_t offset() const { return coefficients_.offset_; } + + // Access the underlying backing memory (for creating strided views that share the same buffer) + BackingMemory backing_memory() const { return coefficients_.backing_memory_; } /** * @brief Strictly iterates the defined region of the polynomial. @@ -322,8 +381,16 @@ template class Polynomial { * * @return std::span a span covering start_index() to end_index() */ - std::span coeffs(size_t offset = 0) { return { data() + offset, data() + size() }; } - std::span coeffs(size_t offset = 0) const { return { data() + offset, data() + size() }; } + std::span coeffs(size_t off = 0) + { + BB_ASSERT_DEBUG(!is_strided()); // coeffs() requires contiguous memory + return { data() + off, data() + size() }; + } + std::span coeffs(size_t off = 0) const + { + BB_ASSERT_DEBUG(!is_strided()); // coeffs() requires contiguous memory + return { data() + off, data() + size() }; + } /** * @brief Convert to an std::span bundled with our start index. * @return PolynomialSpan A span covering the entire polynomial. @@ -336,6 +403,28 @@ template class Polynomial { */ operator PolynomialSpan() const { return { start_index(), coeffs() }; } + /** + * @brief Batch-invert all elements in [start_index, end_index) in place. + * @details For contiguous polynomials, delegates to FF::batch_invert on the backing span. + * For strided views, gathers values into a temp buffer, batch-inverts, then scatters back. + */ + void batch_invert_in_place() + { + if (!is_strided()) { + Fr::batch_invert(coeffs()); + } else { + const size_t n = size(); + std::vector temp(n); + for (size_t i = 0; i < n; i++) { + temp[i] = at(start_index() + i); + } + Fr::batch_invert(temp); + for (size_t i = 0; i < n; i++) { + at(start_index() + i) = temp[i]; + } + } + } + auto indices() const { return std::ranges::iota_view(start_index(), end_index()); } auto indexed_values() { return zip_view(indices(), coeffs()); } auto indexed_values() const { return zip_view(indices(), coeffs()); } diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp index 6fcd1a407b9f..14566a6a7482 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.test.cpp @@ -29,6 +29,106 @@ TEST(Polynomial, Shifted) } } +// Test strided view: entity views into interleaved group buffer +TEST(Polynomial, StridedView) +{ + using FF = bb::fr; + using Polynomial = bb::Polynomial; + constexpr size_t BS = 4; + constexpr size_t N = 8; // logical size per entity + + // Allocate the group buffer (non-shiftable: all n*BS elements) + Polynomial group_buffer(N * BS); + + // Fill it manually: buffer[BS*i + j] = (i+1) * 100 + j + for (size_t i = 0; i < N; i++) { + for (size_t j = 0; j < BS; j++) { + group_buffer.at(BS * i + j) = FF(static_cast((i + 1) * 100 + j)); + } + } + + // Create strided views for each entity + std::array entities; + for (size_t j = 0; j < BS; j++) { + entities[j] = Polynomial::strided_view( + group_buffer.backing_memory(), BS, j, /*start_index=*/0, /*logical_size=*/N, /*virtual_size=*/N); + } + + // Verify reads through strided views + for (size_t j = 0; j < BS; j++) { + EXPECT_TRUE(entities[j].is_strided()); + EXPECT_EQ(entities[j].size(), N); + EXPECT_EQ(entities[j].virtual_size(), N); + for (size_t i = 0; i < N; i++) { + FF expected = FF(static_cast((i + 1) * 100 + j)); + EXPECT_EQ(entities[j].get(i), expected) << "entity " << j << " at index " << i; + } + } + + // Verify writes through strided views go into the group buffer + entities[2].at(3) = FF(999); + EXPECT_EQ(group_buffer.at(BS * 3 + 2), FF(999)); + + // Verify reads past logical end return zero (virtual zeros) + // (entities have virtual_size = N, so reading at N should return zero) + // Can't test this without increasing virtual_size > N; skip for now. +} + +// Test strided view for shiftable entities +TEST(Polynomial, StridedViewShiftable) +{ + using FF = bb::fr; + using Polynomial = bb::Polynomial; + constexpr size_t BS = 4; + constexpr size_t N = 8; // logical circuit size + + // Allocate shiftable group buffer: first BS positions are zero + Polynomial group_buffer = Polynomial::shiftable(N * BS, N * BS, BS); + + // Fill non-zero rows: buffer[BS*i + j] for i >= 1 + for (size_t i = 1; i < N; i++) { + for (size_t j = 0; j < BS; j++) { + group_buffer.at(BS * i + j) = FF(static_cast(i * 10 + j)); + } + } + + // Create shiftable strided views (start_index=1, logical_size=N-1) + std::array entities; + for (size_t j = 0; j < BS; j++) { + entities[j] = Polynomial::strided_view( + group_buffer.backing_memory(), BS, j, /*start_index=*/1, /*logical_size=*/N - 1, /*virtual_size=*/N); + } + + // Verify entity reads: entity_j[i] = buffer[BS*i + j] + for (size_t j = 0; j < BS; j++) { + EXPECT_TRUE(entities[j].is_shiftable()); + // get(0) should return zero (before start_index) + EXPECT_EQ(entities[j].get(0), FF(0)); + for (size_t i = 1; i < N; i++) { + FF expected = FF(static_cast(i * 10 + j)); + EXPECT_EQ(entities[j].get(i), expected) << "entity " << j << " at index " << i; + } + } + + // Create shifted views + std::array shifted; + for (size_t j = 0; j < BS; j++) { + shifted[j] = entities[j].shifted(); + } + + // Verify shifted reads: shifted_j[i] = entity_j[i+1] + for (size_t j = 0; j < BS; j++) { + for (size_t i = 0; i < N - 2; i++) { + EXPECT_EQ(shifted[j].get(i), entities[j].get(i + 1)) << "shifted entity " << j << " at index " << i; + } + } + + // Verify writes through entity view update the buffer and shifted view sees it + entities[1].at(3) = FF(42); + EXPECT_EQ(group_buffer.at(BS * 3 + 1), FF(42)); + EXPECT_EQ(shifted[1].get(2), FF(42)); // shifted[2] = entity[3] +} + // Simple test/demonstration of reverse functionality TEST(Polynomial, Reversed) { diff --git a/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp b/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp index d050a4ecec6a..038171f5b57e 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/shared_shifted_virtual_zeroes_array.hpp @@ -39,7 +39,7 @@ template struct SharedShiftedVirtualZeroesArray { { BB_ASSERT_DEBUG(index >= start_); BB_ASSERT_DEBUG(index < end_); - data()[index - start_] = value; + data()[((index - start_) * stride_) + offset_] = value; } /** @@ -58,7 +58,7 @@ template struct SharedShiftedVirtualZeroesArray { static const T zero{}; BB_ASSERT_DEBUG(index < virtual_size_ + virtual_padding); if (index >= start_ && index < end_) { - return data()[index - start_]; + return data()[((index - start_) * stride_) + offset_]; } return zero; // Return default element when index is out of the actual filled size } @@ -87,14 +87,14 @@ template struct SharedShiftedVirtualZeroesArray { { BB_ASSERT_DEBUG(index >= start_); BB_ASSERT_DEBUG(index < end_); - return data()[index - start_]; + return data()[((index - start_) * stride_) + offset_]; } // get() is more useful, but for completeness with the non-const operator[] const T& operator[](size_t index) const { BB_ASSERT_DEBUG(index >= start_); BB_ASSERT_DEBUG(index < end_); - return data()[index - start_]; + return data()[((index - start_) * stride_) + offset_]; } // MEMBERS: @@ -133,4 +133,23 @@ template struct SharedShiftedVirtualZeroesArray { * allow for efficient memory use when arrays are shifted or otherwise manipulated. */ BackingMemory backing_memory_; + + /** + * @brief Stride between consecutive logical elements in the backing memory. + * + * For standard polynomials, stride_ == 1 (contiguous storage). + * For interleaved polynomial group views, stride_ == batch_size (e.g. 4 for BS=4), + * meaning logical element i maps to physical position (i - start_) * stride_ + offset_. + */ + size_t stride_ = 1; + + /** + * @brief Offset within the interleaved group (0..stride_-1). + * + * For standard polynomials, offset_ == 0. + * For entity j in an interleaved group, offset_ == j. + */ + size_t offset_ = 0; + + bool is_strided() const { return stride_ != 1; } }; diff --git a/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation.hpp b/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation.hpp index c866d05b5228..53fee3d93bed 100644 --- a/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/databus_lookup_relation.hpp @@ -312,7 +312,7 @@ template class DatabusLookupRelationImpl { // Compute inverse polynomial I in place by inverting the product at each row // Note: zeroes are ignored as they are not used anyway - FF::batch_invert(inverse_polynomial.coeffs()); + inverse_polynomial.batch_invert_in_place(); }; /** diff --git a/barretenberg/cpp/src/barretenberg/relations/logderiv_lookup_relation.hpp b/barretenberg/cpp/src/barretenberg/relations/logderiv_lookup_relation.hpp index 5810e458cab5..192fe8ab4e42 100644 --- a/barretenberg/cpp/src/barretenberg/relations/logderiv_lookup_relation.hpp +++ b/barretenberg/cpp/src/barretenberg/relations/logderiv_lookup_relation.hpp @@ -291,7 +291,7 @@ template class LogDerivLookupRelationImpl { }); // Compute inverse polynomial I in place by inverting the product at each row - FF::batch_invert(inverse_polynomial.coeffs()); + inverse_polynomial.batch_invert_in_place(); }; /** diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp index 43c7beb71d22..1b110dbc2acc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp @@ -3,6 +3,8 @@ #include "barretenberg/common/test.hpp" #include "barretenberg/dsl/acir_format/gate_count_constants.hpp" #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/flavor/mega_recursive_flavor.hpp" +#include "barretenberg/flavor/mega_zk_recursive_flavor.hpp" #include "barretenberg/flavor/test_utils/proof_structures.hpp" #include "barretenberg/honk/proof_length.hpp" #include "barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp" @@ -30,7 +32,19 @@ using TestConfigs = testing::Types< RecursiveVerifierTestParams, DefaultIO>, RecursiveVerifierTestParams, DefaultIO>, RecursiveVerifierTestParams, DefaultIO>, - RecursiveVerifierTestParams, DefaultIO>>; + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>, + RecursiveVerifierTestParams, DefaultIO>>; /** * @brief Test suite for recursive verification of Honk proofs for both Ultra and Mega arithmetisation. @@ -456,7 +470,8 @@ template class RecursiveVerifierTest : public testing::Test { // We expect exactly one connected component (all variables properly connected) EXPECT_EQ(cc.size(), 1); - // Expected variables in one gate: + // TODO: MultiMega oink verifier receives 4 individual ecc_op_wire commitments for merge protocol + // compatibility that are unused by the recursive Multi verifier (it uses interleaved commitments). size_t expected_unconstrained = 0; EXPECT_EQ(variables_in_one_gate.size(), expected_unconstrained); } @@ -494,7 +509,12 @@ HEAVY_TYPED_TEST(RecursiveVerifierTest, IndependentVKHash) HEAVY_TYPED_TEST(RecursiveVerifierTest, SingleRecursiveVerificationFailure) { - TestFixture::test_recursive_verification_fails(); + using InnerFlavor = typename TypeParam::RecursiveFlavor::NativeFlavor; + if constexpr (IsMultiMegaFlavor) { + GTEST_SKIP() << "StructuredProof not available for MultiMega flavors"; + } else { + TestFixture::test_recursive_verification_fails(); + } }; /** diff --git a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp index e0f32cb9f6eb..06c0d450f06f 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/primitives/circuit_builders/circuit_builders_fwd.hpp @@ -8,6 +8,7 @@ construction in stdlib and contains macros for explicit instantiation. */ #pragma once #include +#include namespace bb { class Bn254FrParams; @@ -20,10 +21,20 @@ template class MegaCircuitBuilder_; using MegaCircuitBuilder = MegaCircuitBuilder_>; class StandardFlavor; -class UltraFlavor; -class UltraZKFlavor; -class MegaFlavor; -class MegaZKFlavor; +template class UltraFlavor_; +using UltraFlavor = UltraFlavor_<1>; +using DualUltraFlavor = UltraFlavor_<2>; +template class UltraZKFlavor_; +using UltraZKFlavor = UltraZKFlavor_<1>; +using DualUltraZKFlavor = UltraZKFlavor_<2>; +template class MegaFlavor_; +using MegaFlavor = MegaFlavor_<1>; +using DualMegaFlavor = MegaFlavor_<2>; +using MultiMegaFlavor = MegaFlavor_<4>; +template class MegaZKFlavor_; +using MegaZKFlavor = MegaZKFlavor_<1>; +using DualMegaZKFlavor = MegaZKFlavor_<2>; +using MultiMegaZKFlavor = MegaZKFlavor_<4>; class MegaAvmFlavor; class UltraKeccakFlavor; class UltraKeccakZKFlavor; @@ -42,11 +53,17 @@ template class Sumche using SumcheckTestFlavorGrumpkinZK = SumcheckTestFlavor_; template class UltraRecursiveFlavor_; +template class DualUltraRecursiveFlavor_; template class UltraZKRecursiveFlavor_; +template class DualUltraZKRecursiveFlavor_; template class UltraKeccakRecursiveFlavor_; template class MegaRecursiveFlavor_; template class MegaZKRecursiveFlavor_; template class MegaAvmRecursiveFlavor_; +template class DualMegaRecursiveFlavor_; +template class DualMegaZKRecursiveFlavor_; +template class MultiMegaRecursiveFlavor_; +template class MultiMegaZKRecursiveFlavor_; namespace avm2 { class AvmRecursiveFlavor; diff --git a/barretenberg/cpp/src/barretenberg/sumcheck/masking_tail_data.hpp b/barretenberg/cpp/src/barretenberg/sumcheck/masking_tail_data.hpp index b7e94be05499..f06e5db99de8 100644 --- a/barretenberg/cpp/src/barretenberg/sumcheck/masking_tail_data.hpp +++ b/barretenberg/cpp/src/barretenberg/sumcheck/masking_tail_data.hpp @@ -51,6 +51,32 @@ template struct MaskingTailData { bool is_active() const { return active; } size_t get_num_folded_values() const { return num_folded_values; } + /** + * @brief Build the interleaved group tail for a given group of entity tail pointers. + * @details Interleaves entity tails into a single polynomial: result[BS*i + j] = entity_tail_j[i]. + * For BS=1, this is just a copy of the single entity tail. Tails are tiny so this is cheap. + * @param tail_group Vector of pointers to entity tail polynomials (from Flavor::get_unshifted_groups(tails)). + * @param virtual_size Virtual size for the output polynomial (typically dyadic_size * BS). + */ + template + Polynomial interleave_tail_group(const TailGroup& tail_group, size_t virtual_size) const + { + constexpr size_t BS = Flavor::INTERLEAVING_BATCH_SIZE; + Polynomial group_tail; + for (size_t j = 0; j < tail_group.size(); j++) { + if (tail_group[j] != nullptr && !tail_group[j]->is_empty()) { + const auto& t = *tail_group[j]; + if (group_tail.is_empty()) { + group_tail = Polynomial(t.size() * BS, virtual_size, t.start_index() * BS); + } + for (size_t i = t.start_index(); i < t.end_index(); i++) { + group_tail.at(i * BS + j) = t.at(i); + } + } + } + return group_tail; + } + /** * @brief Register all masked polynomials and their shifted counterparts at once. * @details Uses get_masked() on the parallel AllEntities structs (is_masked, tails) to directly @@ -182,12 +208,75 @@ template struct MaskingTailData { m2 * (FF::one() - u0) * u1); } } + /** + * @brief Register tail polynomials with the PCS batcher (group-based interleaving path). + * @details Builds interleaved group tails and registers by group index. Used by ultra/mega provers + * where the batcher holds interleaved group polynomials. + */ + template void add_tails_to_batcher(PolynomialBatcher& batcher, size_t pcs_size) const + { + if (!active) { + return; + } + auto register_groups = [&](const auto& groups, auto add_tail_fn) { + for (size_t g = 0; g < groups.size(); g++) { + auto gt = interleave_tail_group(groups[g], pcs_size); + if (!gt.is_empty()) { + add_tail_fn(batcher, g, std::move(gt)); + } + } + }; + register_groups(Flavor::get_unshifted_groups(tails), + [](auto& b, size_t g, Polynomial&& t) { b.add_unshifted_tail(g, std::move(t)); }); + register_groups(Flavor::get_to_be_shifted_groups(tails), + [](auto& b, size_t g, Polynomial&& t) { b.add_shifted_tail(g, std::move(t)); }); + } + + /** + * @brief Accumulate interleaved tails into pre-batched polynomials F and G using rho powers. + * @details For each group, builds the interleaved tail, then adds it to batched_unshifted or + * batched_to_be_shifted at the correct rho power. Used by the BS>1 manual PCS path + * where PolynomialBatcher is bypassed. + * @param batched_unshifted The rho-weighted sum of unshifted interleaved groups (F). + * @param batched_to_be_shifted The rho-weighted sum of shifted interleaved groups (G). + * @param rho The batching challenge. + * @param num_unshifted_groups Total number of unshifted groups (shifted rho powers start after). + * @param pcs_size The interleaved polynomial size (n * BS). + */ + void accumulate_interleaved_tails(Polynomial& batched_unshifted, + Polynomial& batched_to_be_shifted, + const FF& rho, + size_t num_unshifted_groups, + size_t pcs_size) const + { + if (!active) { + return; + } + auto unshifted_tail_groups = Flavor::get_unshifted_groups(tails); + FF rho_power(1); + for (size_t g = 0; g < unshifted_tail_groups.size(); g++) { + auto gt = interleave_tail_group(unshifted_tail_groups[g], pcs_size); + if (!gt.is_empty()) { + batched_unshifted.add_scaled(gt, rho_power); + } + rho_power *= rho; + } + + auto shifted_tail_groups = Flavor::get_to_be_shifted_groups(tails); + FF rho_shifted = rho.pow(num_unshifted_groups); + for (size_t g = 0; g < shifted_tail_groups.size(); g++) { + auto gt = interleave_tail_group(shifted_tail_groups[g], pcs_size); + if (!gt.is_empty()) { + batched_to_be_shifted.add_scaled(gt, rho_shifted); + } + rho_shifted *= rho; + } + } /** - * @brief Register tail polynomials with the PCS batcher. - * @details Iterates only masked (unshifted) entities. For each, registers the tail with both - * batcher.unshifted and batcher.to_be_shifted_by_one if the source poly appears there. - * The batcher's shift mechanism handles producing the shifted version. + * @brief Register tail polynomials with the PCS batcher (pointer-matching path). + * @details Finds batcher indices by comparing polynomial data pointers. Used by ECCVM and + * batched translator where the batcher holds flat concatenated polynomial references. */ template void add_tails_to_batcher(const ProverPolynomials& prover_polynomials, PolynomialBatcher& batcher) const @@ -195,9 +284,6 @@ template struct MaskingTailData { if (!active) { return; } - - // Pointer-matching against batcher lists is needed here since the batcher is an external - // structure without flavor-aware getters. for (auto [poly, tail] : zip_view(prover_polynomials.get_masked(), tails.get_masked())) { for (size_t u = 0; u < batcher.unshifted.size(); u++) { if (batcher.unshifted[u].data() == poly.data()) { @@ -205,8 +291,8 @@ template struct MaskingTailData { break; } } - for (size_t s = 0; s < batcher.to_be_shifted_by_one.size(); s++) { - if (batcher.to_be_shifted_by_one[s].data() == poly.data()) { + for (size_t s = 0; s < batcher.to_be_shifted.size(); s++) { + if (batcher.to_be_shifted[s].data() == poly.data()) { batcher.add_shifted_tail(s, Polynomial(tail)); break; } diff --git a/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp b/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp index f781aa22f8df..01b31cb562e0 100644 --- a/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp +++ b/barretenberg/cpp/src/barretenberg/trace_to_polynomials/trace_to_polynomials.cpp @@ -10,6 +10,7 @@ #include "barretenberg/ext/starknet/flavor/ultra_starknet_zk_flavor.hpp" #include "barretenberg/flavor/mega_avm_flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" #include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/ultra_keccak_flavor.hpp" #include "barretenberg/flavor/ultra_keccak_zk_flavor.hpp" @@ -116,8 +117,14 @@ template class TraceToPolynomials; template class TraceToPolynomials; #endif template class TraceToPolynomials; +template class TraceToPolynomials; +template class TraceToPolynomials; template class TraceToPolynomials; template class TraceToPolynomials; template class TraceToPolynomials; +template class TraceToPolynomials; +template class TraceToPolynomials; +template class TraceToPolynomials; +template class TraceToPolynomials; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_flavor.hpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_flavor.hpp index a43adc682b7b..80797c5f6d43 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_flavor.hpp @@ -750,7 +750,8 @@ class TranslatorFlavor { NUM_ORDERED_RANGE + 1, 2 + NUM_ORDERED_RANGE + 1, 2 + NUM_PCS_TO_BE_SHIFTED + NUM_ORDERED_RANGE + 1, - NUM_CONCATENATED_POLYS); + NUM_CONCATENATED_POLYS, + 2 /* Q_commitment + gemini_masking_poly */); static constexpr size_t PROOF_LENGTH = /* 1. Gemini masking poly commitment */ (num_frs_comm) + diff --git a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp index ecd6a43f6ef6..733f9bdd2950 100644 --- a/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/translator_vm/translator_prover.cpp @@ -189,11 +189,14 @@ void TranslatorProver::execute_pcs_rounds() // Unshifted for PCS (excludes computable precomputed — verifier computes them locally) polynomial_batcher.set_unshifted(key->proving_key->polynomials.get_pcs_unshifted()); // Shifted for PCS (base to-be-shifted + concatenated) - polynomial_batcher.set_to_be_shifted_by_one(key->proving_key->polynomials.get_pcs_to_be_shifted()); + polynomial_batcher.set_to_be_shifted(key->proving_key->polynomials.get_pcs_to_be_shifted()); + + const auto rho = transcript->template get_challenge("rho"); const OpeningClaim prover_opening_claim = ShpleminiProver_::prove(key->proving_key->circuit_size, polynomial_batcher, + rho, sumcheck_output.challenge, ck, transcript, diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp index 21a1eaf3b89f..f30c0a77298a 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp @@ -2,6 +2,8 @@ #include "barretenberg/ecc/curves/bn254/g1.hpp" #include "barretenberg/flavor/flavor.hpp" #include "barretenberg/flavor/flavor_concepts.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/test_utils/proof_structures.hpp" #include "barretenberg/flavor/ultra_flavor.hpp" #include "barretenberg/honk/proof_length.hpp" @@ -26,10 +28,22 @@ using FlavorTypes = ::testing::Types; + MegaZKFlavor, + DualMegaFlavor, + DualMegaZKFlavor, + MultiMegaFlavor, + MultiMegaZKFlavor>; #else -using FlavorTypes = - ::testing::Types; +using FlavorTypes = ::testing::Types; #endif template class HonkTranscriptTests : public ::testing::Test { @@ -41,8 +55,8 @@ template class HonkTranscriptTests : public ::testing::Test { using Commitment = Flavor::Commitment; using ProverInstance = ProverInstance_; using Builder = Flavor::CircuitBuilder; - using Prover = UltraProver_; using IO = DefaultIO; + using Prover = UltraProver_; using Verifier = UltraVerifier_; using Proof = typename Flavor::Transcript::Proof; @@ -92,7 +106,7 @@ template class HonkTranscriptTests : public ::testing::Test { manifest_expected.add_entry(round, "public_input_" + std::to_string(1 + i), data_types_per_Frs); } - // For flavors with Gemini masking: masking polynomial commitment is sent at end of oink + // For ZK flavors with Gemini masking: masking polynomial commitment is sent at end of oink if constexpr (flavor_has_gemini_masking()) { manifest_expected.add_entry(round, "Gemini:masking_poly_comm", data_types_per_G); } @@ -194,6 +208,141 @@ template class HonkTranscriptTests : public ::testing::Test { return manifest_expected; } + /** + * @brief Construct a manifest for a MultiHonk proof (interleaved commitments) + */ + TranscriptManifest construct_multi_honk_manifest() + requires IsMultiMegaFlavor + { + TranscriptManifest manifest_expected; + + const size_t virtual_log_n = Flavor::VIRTUAL_LOG_N; + const size_t pcs_log_n = virtual_log_n + Flavor::INTERLEAVING_LOG_K; + + size_t NUM_PUBLIC_INPUTS = IO::PUBLIC_INPUTS_SIZE; + size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; + + size_t frs_per_Fr = FrCodec::calc_num_fields(); + size_t frs_per_G = FrCodec::calc_num_fields(); + size_t frs_per_uni = MAX_PARTIAL_RELATION_LENGTH * frs_per_Fr; + size_t frs_per_evals = (Flavor::NUM_ALL_ENTITIES)*frs_per_Fr; + + size_t round = 0; + manifest_expected.add_entry(round, "vk_hash", frs_per_Fr); + manifest_expected.add_entry(round, "public_input_0", frs_per_Fr); + for (size_t i = 0; i < NUM_PUBLIC_INPUTS; i++) { + manifest_expected.add_entry(round, "public_input_" + std::to_string(1 + i), frs_per_Fr); + } + // MegaZK flavors do not send masking in oink (translator provides it in the batched flow) + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE == 2) { + manifest_expected.add_entry(round, "INTERLEAVED_WIRES", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_ECC_OP_WIRES_1", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_ECC_OP_WIRES_2", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_CALLDATA", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_SECONDARY_CALLDATA", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_CALLDATA_TAGS", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_SCD_TAGS", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_RETURN_DATA_TAGS", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_RETURN_DATA", frs_per_G); + } else { + manifest_expected.add_entry(round, "INTERLEAVED_WIRES", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_ECC_OP_WIRES", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_CALLDATA", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_SECONDARY_CALLDATA", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_DATABUS_TAGS", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_RETURN_DATA_TAGS", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_RETURN_DATA", frs_per_G); + } + manifest_expected.add_challenge(round, "eta"); + + round++; + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE == 2) { + manifest_expected.add_entry(round, "INTERLEAVED_LOOKUP", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_W_O", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_W_4", frs_per_G); + } else { + manifest_expected.add_entry(round, "INTERLEAVED_W_4", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_LOOKUP", frs_per_G); + } + manifest_expected.add_challenge(round, std::array{ "beta", "gamma" }); + + round++; + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE == 2) { + manifest_expected.add_entry(round, "INTERLEAVED_INVERSES_1", frs_per_G); + manifest_expected.add_entry(round, "INTERLEAVED_INVERSES_2", frs_per_G); + } else { + manifest_expected.add_entry(round, "INTERLEAVED_INVERSES", frs_per_G); + } + manifest_expected.add_entry(round, "INTERLEAVED_Z_PERM", frs_per_G); + manifest_expected.add_challenge(round, "alpha"); + manifest_expected.add_challenge(round, "Sumcheck:gate_challenge"); + + round++; + + if constexpr (Flavor::HasZK) { + manifest_expected.add_entry(round, "Libra:concatenation_commitment", frs_per_G); + manifest_expected.add_entry(round, "Libra:Sum", frs_per_Fr); + manifest_expected.add_challenge(round, "Libra:Challenge"); + round++; + } + + for (size_t i = 0; i < virtual_log_n; ++i) { + manifest_expected.add_entry(round, "Sumcheck:univariate_" + std::to_string(i), frs_per_uni); + manifest_expected.add_challenge(round, "Sumcheck:u_" + std::to_string(i)); + round++; + } + + manifest_expected.add_entry(round, "Sumcheck:evaluations", frs_per_evals); + + if constexpr (Flavor::HasZK) { + manifest_expected.add_entry(round, "Libra:claimed_evaluation", frs_per_Fr); + } + + // Interleaving challenges are in the evaluations round + for (size_t i = 0; i < Flavor::INTERLEAVING_LOG_K; ++i) { + manifest_expected.add_challenge(round, "Shplemini:interleaving_challenge_" + std::to_string(i)); + } + + if constexpr (Flavor::HasZK) { + // SmallSubgroupIPA sends commitments after interleaving challenges → new round + round++; + manifest_expected.add_entry(round, "Libra:grand_sum_commitment", frs_per_G); + manifest_expected.add_entry(round, "Libra:quotient_commitment", frs_per_G); + } + + // Single ρ challenge for batching (no separate short challenges) + manifest_expected.add_challenge(round, "rho"); + + round++; + for (size_t i = 1; i < pcs_log_n; ++i) { + manifest_expected.add_entry(round, "Gemini:FOLD_" + std::to_string(i), frs_per_G); + } + manifest_expected.add_challenge(round, "Gemini:r"); + + round++; + for (size_t i = 1; i <= pcs_log_n; ++i) { + manifest_expected.add_entry(round, "Gemini:a_" + std::to_string(i), frs_per_Fr); + } + + if constexpr (Flavor::HasZK) { + manifest_expected.add_entry(round, "Libra:concatenation_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:shifted_grand_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:grand_sum_eval", frs_per_Fr); + manifest_expected.add_entry(round, "Libra:quotient_eval", frs_per_Fr); + } + + manifest_expected.add_challenge(round, "Shplonk:nu"); + + round++; + manifest_expected.add_entry(round, "Shplonk:Q", frs_per_G); + manifest_expected.add_challenge(round, "Shplonk:z"); + + round++; + manifest_expected.add_entry(round, "KZG:W", frs_per_G); + + return manifest_expected; + } + void generate_test_circuit(Builder& builder) { FF a = 1; @@ -232,11 +381,13 @@ TYPED_TEST(HonkTranscriptTests, ProverManifestConsistency) auto proof = prover.construct_proof(); // Check that the prover generated manifest agrees with the manifest hard coded in this suite - auto manifest_expected = TestFixture::construct_honk_manifest(prover.log_dyadic_size()); + TranscriptManifest manifest_expected; + if constexpr (IsMultiMegaFlavor) { + manifest_expected = TestFixture::construct_multi_honk_manifest(); + } else { + manifest_expected = TestFixture::construct_honk_manifest(prover.log_dyadic_size()); + } auto prover_manifest = prover.get_transcript()->get_manifest(); - // Note: a manifest can be printed using manifest.print() - manifest_expected.print(); - prover_manifest.print(); ASSERT_GT(manifest_expected.size(), 0); for (size_t round = 0; round < manifest_expected.size(); ++round) { if (prover_manifest[round] != manifest_expected[round]) { @@ -316,51 +467,56 @@ TYPED_TEST(HonkTranscriptTests, ChallengeGenerationTest) TYPED_TEST(HonkTranscriptTests, StructureTest) { using Flavor = TypeParam; - using FF = Flavor::FF; - using Commitment = Flavor::Commitment; - // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) - auto builder = typename TestFixture::Builder(); - TestFixture::generate_test_circuit(builder); - - // Automatically generate a transcript manifest by constructing a proof - auto prover_instance = std::make_shared(builder); - auto verification_key = std::make_shared(prover_instance->get_precomputed()); - auto vk_and_hash = std::make_shared(verification_key); - typename TestFixture::Prover prover(prover_instance, verification_key); - auto proof = prover.construct_proof(); - typename TestFixture::Verifier verifier(vk_and_hash); - EXPECT_TRUE(verifier.verify_proof(proof).result); - - const size_t virtual_log_n = Flavor::USE_PADDING ? Flavor::VIRTUAL_LOG_N : prover_instance->log_dyadic_size(); - - // Use StructuredProof test utility to deserialize/serialize proof data - StructuredProof proof_structure; - - // try deserializing and serializing with no changes and check proof is still valid - proof_structure.deserialize( - prover.get_transcript()->test_get_proof_data(), verification_key->num_public_inputs, virtual_log_n); - proof_structure.serialize(prover.get_transcript()->test_get_proof_data(), virtual_log_n); - - proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); - // we have changed nothing so proof is still valid - typename TestFixture::Verifier verifier2(vk_and_hash); - EXPECT_TRUE(verifier2.verify_proof(proof).result); - - Commitment one_group_val = Commitment::one(); - FF rand_val = FF::random_element(); - proof_structure.z_perm_comm = one_group_val * rand_val; // choose random object to modify - proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); - // we have not serialized it back to the proof so it should still be fine - typename TestFixture::Verifier verifier3(vk_and_hash); - EXPECT_TRUE(verifier3.verify_proof(proof).result); - - proof_structure.serialize(prover.get_transcript()->test_get_proof_data(), virtual_log_n); - proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); - // the proof is now wrong after serializing it - typename TestFixture::Verifier verifier4(vk_and_hash); - EXPECT_FALSE(verifier4.verify_proof(proof).result); - - proof_structure.deserialize( - prover.get_transcript()->test_get_proof_data(), verification_key->num_public_inputs, virtual_log_n); - EXPECT_EQ(static_cast(proof_structure.z_perm_comm), one_group_val * rand_val); + if constexpr (IsMultiMegaFlavor) { + GTEST_SKIP() << "StructureTest not applicable for MultiMega flavors (different proof structure)"; + } else { + using FF = Flavor::FF; + using Commitment = Flavor::Commitment; + // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) + auto builder = typename TestFixture::Builder(); + TestFixture::generate_test_circuit(builder); + + // Automatically generate a transcript manifest by constructing a proof + auto prover_instance = std::make_shared(builder); + auto verification_key = + std::make_shared(prover_instance->get_precomputed()); + auto vk_and_hash = std::make_shared(verification_key); + typename TestFixture::Prover prover(prover_instance, verification_key); + auto proof = prover.construct_proof(); + typename TestFixture::Verifier verifier(vk_and_hash); + EXPECT_TRUE(verifier.verify_proof(proof).result); + + const size_t virtual_log_n = Flavor::USE_PADDING ? Flavor::VIRTUAL_LOG_N : prover_instance->log_dyadic_size(); + + // Use StructuredProof test utility to deserialize/serialize proof data + StructuredProof proof_structure; + + // try deserializing and serializing with no changes and check proof is still valid + proof_structure.deserialize( + prover.get_transcript()->test_get_proof_data(), verification_key->num_public_inputs, virtual_log_n); + proof_structure.serialize(prover.get_transcript()->test_get_proof_data(), virtual_log_n); + + proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); + // we have changed nothing so proof is still valid + typename TestFixture::Verifier verifier2(vk_and_hash); + EXPECT_TRUE(verifier2.verify_proof(proof).result); + + Commitment one_group_val = Commitment::one(); + FF rand_val = FF::random_element(); + proof_structure.z_perm_comm = one_group_val * rand_val; // choose random object to modify + proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); + // we have not serialized it back to the proof so it should still be fine + typename TestFixture::Verifier verifier3(vk_and_hash); + EXPECT_TRUE(verifier3.verify_proof(proof).result); + + proof_structure.serialize(prover.get_transcript()->test_get_proof_data(), virtual_log_n); + proof = TestFixture::export_serialized_proof(prover, prover_instance->num_public_inputs(), virtual_log_n); + // the proof is now wrong after serializing it + typename TestFixture::Verifier verifier4(vk_and_hash); + EXPECT_FALSE(verifier4.verify_proof(proof).result); + + proof_structure.deserialize( + prover.get_transcript()->test_get_proof_data(), verification_key->num_public_inputs, virtual_log_n); + EXPECT_EQ(static_cast(proof_structure.z_perm_comm), one_group_val * rand_val); + } } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/lookup.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/lookup.test.cpp index b7473e4615e9..da1269e7d5dd 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/lookup.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/lookup.test.cpp @@ -1,9 +1,11 @@ +#include "barretenberg/flavor/ultra_zk_flavor.hpp" #include "ultra_honk.test.hpp" #include using namespace bb; -using FlavorTypes = testing::Types; +using FlavorTypes = testing:: + Types; TYPED_TEST_SUITE(UltraHonkTests, FlavorTypes); /** diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp index 3c9b29be4d25..7bf90f06c8d6 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp @@ -4,12 +4,16 @@ #include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/common/log.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" +#include "barretenberg/flavor/ultra_flavor.hpp" #include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/honk/proof_length.hpp" #include "barretenberg/honk/relation_checker.hpp" #include "barretenberg/stdlib_circuit_builders/mega_circuit_builder.hpp" #include "barretenberg/stdlib_circuit_builders/ultra_circuit_builder.hpp" #include "barretenberg/ultra_honk/oink_prover.hpp" +#include "barretenberg/ultra_honk/oink_verifier.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" @@ -17,7 +21,8 @@ using namespace bb; auto& engine = numeric::get_debug_randomness(); -using FlavorTypes = ::testing::Types; +using FlavorTypes = + ::testing::Types; template class MegaHonkTests : public ::testing::Test { public: @@ -74,7 +79,7 @@ TYPED_TEST(MegaHonkTests, ProofLengthCheck) // Construct a mega proof and ensure its size matches expectation; if not, the constant may need to be updated auto prover_instance = std::make_shared>(builder); auto verification_key = std::make_shared(prover_instance->get_precomputed()); - UltraProver_ prover(prover_instance, verification_key); + typename TestFixture::Prover prover(prover_instance, verification_key); HonkProof mega_proof = prover.construct_proof(); EXPECT_EQ(mega_proof.size(), ProofLength::Honk::LENGTH_WITHOUT_PUB_INPUTS(Flavor::VIRTUAL_LOG_N) + @@ -98,6 +103,107 @@ TYPED_TEST(MegaHonkTests, Basic) EXPECT_TRUE(honk_verified); } +/** + * @brief Validate that interleaved polynomial storage produces correct relation evaluations. + * @details Constructs a ProverInstance (which uses interleaved allocation for BS>1), + * then checks that the arithmetic relation is satisfied at every row. + * This validates the strided view read path without depending on Oink or PCS. + */ +TYPED_TEST(MegaHonkTests, InterleavedStorageRelationCheck) +{ + using Flavor = TypeParam; + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE == 1) { + GTEST_SKIP() << "Only relevant for interleaved (BS>1) flavors"; + } else { + typename Flavor::CircuitBuilder builder; + GoblinMockCircuits::construct_simple_circuit(builder); + + auto prover_instance = std::make_shared>(builder); + + // Check arithmetic relation (needs wires + selectors, no Oink-computed witnesses) + using FF = typename Flavor::FF; + auto failures = RelationChecker::check>(prover_instance->polynomials, + RelationParameters{}); + for (auto& [subrel_idx, row_idx] : failures) { + info("ArithmeticRelation subrelation ", subrel_idx, " failed at row ", row_idx); + } + EXPECT_TRUE(failures.empty()) << "ArithmeticRelation failed with interleaved storage"; + + // Also check ECC op queue relation (needs ecc_op wires + selectors) + auto ecc_failures = RelationChecker::check>(prover_instance->polynomials, + RelationParameters{}); + for (auto& [subrel_idx, row_idx] : ecc_failures) { + info("EccOpQueueRelation subrelation ", subrel_idx, " failed at row ", row_idx); + } + EXPECT_TRUE(ecc_failures.empty()) << "EccOpQueueRelation failed with interleaved storage"; + + // Check elliptic relation + auto elliptic_failures = + RelationChecker::check>(prover_instance->polynomials, RelationParameters{}); + for (auto& [subrel_idx, row_idx] : elliptic_failures) { + info("EllipticRelation subrelation ", subrel_idx, " failed at row ", row_idx); + } + EXPECT_TRUE(elliptic_failures.empty()) << "EllipticRelation failed with interleaved storage"; + } +} + +/** + * @brief Verify that strided entity views correctly map into their group buffers. + * @details For each entity in a group, check that entity[i] == group_buffer[BS*i + j]. + */ +TYPED_TEST(MegaHonkTests, InterleavedStorageEntityBufferConsistency) +{ + using Flavor = TypeParam; + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE == 1) { + GTEST_SKIP() << "Only relevant for interleaved (BS>1) flavors"; + } else { + constexpr size_t BS = Flavor::INTERLEAVING_BATCH_SIZE; + using ProverPolynomials = typename Flavor::ProverPolynomials; + using Poly = typename Flavor::Polynomial; + + typename Flavor::CircuitBuilder builder; + GoblinMockCircuits::construct_simple_circuit(builder); + + auto prover_instance = std::make_shared>(builder); + auto& polys = prover_instance->polynomials; + const size_t n = prover_instance->dyadic_size(); + + // Check that build_interleaved_polynomial correctly interleaves a shiftable wire group + std::vector w1_group; + size_t w1_nonzero; + if constexpr (BS == 2) { + w1_group = { &polys.w_l, &polys.w_r }; + w1_nonzero = 2; + } else { + w1_group = { &polys.w_l, &polys.w_r, &polys.w_o, nullptr }; + w1_nonzero = 3; + } + auto w1_buf = ProverPolynomials::build_interleaved_polynomial(w1_group, n, BS, /*shiftable=*/true); + for (size_t j = 0; j < w1_nonzero; j++) { + for (size_t i = 0; i < n; i++) { + ASSERT_EQ(w1_group[j]->get(i), w1_buf.get(BS * i + j)) << "W1 mismatch at entity=" << j << " row=" << i; + } + } + + // Check an ecc_op_wire group + std::vector w2_group; + size_t w2_nonzero; + if constexpr (BS == 2) { + w2_group = { &polys.ecc_op_wire_1, &polys.ecc_op_wire_2 }; + w2_nonzero = 2; + } else { + w2_group = { &polys.ecc_op_wire_1, &polys.ecc_op_wire_2, &polys.ecc_op_wire_3, &polys.ecc_op_wire_4 }; + w2_nonzero = 4; + } + auto w2_buf = ProverPolynomials::build_interleaved_polynomial(w2_group, n, BS); + for (size_t j = 0; j < w2_nonzero; j++) { + for (size_t i = 0; i < n; i++) { + ASSERT_EQ(w2_group[j]->get(i), w2_buf.get(BS * i + j)) << "W2 mismatch at entity=" << j << " row=" << i; + } + } + } +} + /** * @brief Test that increasing the virtual size of a valid set of prover polynomials still results in a valid Megahonk * proof @@ -107,14 +213,14 @@ TYPED_TEST(MegaHonkTests, DynamicVirtualSizeIncrease) { using Flavor = TypeParam; - // In MegaZKFlavor, we mask witness polynomials by placing random values at the indices `dyadic_circuit_size`-i for + // ZK flavors mask witness polynomials by placing random values at the indices `dyadic_circuit_size`-i for // i=1,2,3. This mechanism does not work with structured polynomials yet. - if constexpr (std::is_same_v) { - GTEST_SKIP() << "Skipping 'DynamicVirtualSizeIncrease' test for MegaZKFlavor."; + if constexpr (Flavor::HasZK) { + GTEST_SKIP() << "Skipping 'DynamicVirtualSizeIncrease' test for ZK flavors."; } typename Flavor::CircuitBuilder builder; - using Prover = UltraProver_; - using Verifier = UltraVerifier_; + using Prover = typename TestFixture::Prover; + using Verifier = typename TestFixture::Verifier; GoblinMockCircuits::construct_simple_circuit(builder); @@ -127,8 +233,6 @@ TYPED_TEST(MegaHonkTests, DynamicVirtualSizeIncrease) auto doubled_circuit_size = 2 * circuit_size; prover_instance_copy->polynomials.increase_polynomials_virtual_size(doubled_circuit_size); - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1158) - // prover_instance_copy->dyadic_circuit_size = doubled_circuit_size; auto verification_key = std::make_shared(prover_instance->get_precomputed()); Prover prover(prover_instance, verification_key); @@ -154,9 +258,9 @@ TYPED_TEST(MegaHonkTests, DynamicVirtualSizeIncrease) Verifier verifier_copy(vk_and_hash_copy); auto proof_copy = prover_copy.construct_proof(); - auto relation_failures_copy = - RelationChecker::check_all(prover_instance->polynomials, prover_instance->relation_parameters); - EXPECT_TRUE(relation_failures.empty()); + auto relation_failures_copy = RelationChecker::check_all(prover_instance_copy->polynomials, + prover_instance_copy->relation_parameters); + EXPECT_TRUE(relation_failures_copy.empty()); bool result_copy = verifier_copy.verify_proof(proof_copy).result; EXPECT_TRUE(result_copy); } @@ -171,10 +275,10 @@ TYPED_TEST(MegaHonkTests, DynamicVirtualSizeIncrease) TYPED_TEST(MegaHonkTests, PolySwap) { using Flavor = TypeParam; - // In MegaZKFlavor, we mask witness polynomials by placing random values at the indices `dyadic_circuit_size`-i, for + // ZK flavors mask witness polynomials by placing random values at the indices `dyadic_circuit_size`-i, for // i=1,2,3. This mechanism does not work with structured polynomials yet. - if constexpr (std::is_same_v) { - GTEST_SKIP() << "Skipping 'PolySwap' test for MegaZKFlavor."; + if constexpr (Flavor::HasZK) { + GTEST_SKIP() << "Skipping 'PolySwap' test for ZK flavors."; } using Builder = Flavor::CircuitBuilder; @@ -296,14 +400,19 @@ TYPED_TEST(MegaHonkTests, DyadicSizeJumpsToProtectMaskingArea) * @brief Verify that masked witness commitments differ from naive poly commits, and unmasked are equal. * @details For ZK flavors, MaskingTailData adds random tail values that shift masked commitments away * from commit(short_poly). Unmasked witness poly commitments should match exactly. + * Uses the oink prover/verifier round-trip to check commitments against the verifier's view. */ TYPED_TEST(MegaHonkTests, MaskingTailCommitments) { using Flavor = TypeParam; if constexpr (!Flavor::HasZK) { GTEST_SKIP() << "Masking only applies to ZK flavors"; + } else if constexpr (Flavor::INTERLEAVING_BATCH_SIZE > 1) { + // For BS>1, entity-level commitments are not populated (only group-level interleaved ones are). + GTEST_SKIP() << "Entity-level masking commitment check not applicable for interleaved flavors (BS>1)"; } else { using Builder = typename Flavor::CircuitBuilder; + using Transcript = typename Flavor::Transcript; using CommitmentKey = typename Flavor::CommitmentKey; Builder builder; @@ -311,28 +420,46 @@ TYPED_TEST(MegaHonkTests, MaskingTailCommitments) auto prover_instance = std::make_shared>(builder); auto verification_key = std::make_shared(prover_instance->get_precomputed()); - // Run oink to populate commitments - auto transcript = std::make_shared(); - OinkProver oink(prover_instance, verification_key, transcript); - oink.prove(); + // Run oink prover, then oink verifier on the same proof to get verifier-side commitments + OinkProver oink_prover(prover_instance, verification_key, std::make_shared()); + oink_prover.prove(); + HonkProof proof = oink_prover.export_proof(); + + auto vk_and_hash = std::make_shared(verification_key); + auto verifier_instance = std::make_shared>(vk_and_hash); + auto verifier_transcript = std::make_shared(); + verifier_transcript->load_proof(proof); + OinkVerifier oink_verifier{ verifier_instance, + verifier_transcript, + verification_key->num_public_inputs }; + oink_verifier.verify(); + + // Build unified VerifierCommitments (precomputed from VK + witness from received) + typename Flavor::VerifierCommitments verifier_commitments(verifier_instance->get_vk(), + verifier_instance->received_commitments); CommitmentKey ck(prover_instance->dyadic_size()); - // Masked polys: commit(poly) should differ from stored commitment + // Masked polys: naive commit(poly) should differ from the verifier commitment (tails shift it) auto masked_polys = prover_instance->polynomials.get_masked(); - auto masked_commitments = prover_instance->commitments.get_masked(); - for (auto [poly, commitment] : zip_view(masked_polys, masked_commitments)) { + auto masked_verifier_commitments = verifier_commitments.get_masked(); + for (auto [poly, commitment] : zip_view(masked_polys, masked_verifier_commitments)) { EXPECT_NE(ck.commit(poly), commitment) << "Masked commitment should differ from naive commit"; } - // Unmasked witness polys: commit(poly) should equal stored commitment + // Unmasked witness polys: naive commit(poly) should equal the verifier commitment auto witness_polys = prover_instance->polynomials.get_witness(); - auto witness_commitments = prover_instance->commitments.get_all(); + auto witness_verifier_commitments = verifier_commitments.get_witness(); auto witness_flags = prover_instance->masking_tail_data.is_masked.get_witness(); - for (auto [poly, commitment, is_masked] : zip_view(witness_polys, witness_commitments, witness_flags)) { + for (auto [poly, commitment, is_masked] : + zip_view(witness_polys, witness_verifier_commitments, witness_flags)) { if (!is_masked && !commitment.is_point_at_infinity()) { EXPECT_EQ(ck.commit(poly), commitment) << "Unmasked witness commitment should equal naive commit"; } } } } + +// ============================================================ +// DualUltra (BS=2) standalone tests +// ============================================================ diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp index 7f23f91af5e4..0ad1cd86c112 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.cpp @@ -6,7 +6,10 @@ #include "barretenberg/ultra_honk/oink_prover.hpp" #include "barretenberg/common/bb_bench.hpp" +#include "barretenberg/flavor/flavor_concepts.hpp" #include "barretenberg/flavor/mega_avm_flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/honk/library/grand_product_delta.hpp" #include "barretenberg/honk/library/grand_product_library.hpp" #include "barretenberg/honk/prover_instance_inspector.hpp" @@ -22,12 +25,14 @@ namespace bb { template void OinkProver::prove(bool emit_alpha) { BB_BENCH_NAME("OinkProver::prove"); - // For ZK, we need SRS points up to dyadic_size for tail masking commitments - const size_t ck_size = - Flavor::HasZK ? prover_instance->dyadic_size() : prover_instance->polynomials.max_end_index(); - commitment_key = CommitmentKey(ck_size); + if (!commitment_key.initialized()) { + // For ZK, we need SRS points up to dyadic_size for tail masking commitments + const size_t ck_size = + Flavor::HasZK ? prover_instance->dyadic_size() : prover_instance->polynomials.max_end_index(); + commitment_key = CommitmentKey(ck_size * BATCH_SIZE); + } - // Register all masked polys upfront (generates random tail values) + // Register all masked polys (generates random tail values and builds group-level tails) if constexpr (Flavor::HasZK) { prover_instance->masking_tail_data.register_all_masked_polys(); } @@ -46,7 +51,6 @@ template void OinkProver::prove(bool emit_alpha) /** * @brief Export the Oink proof */ - template typename OinkProver::Proof OinkProver::export_proof() { return transcript->export_proof(); @@ -71,143 +75,116 @@ template void OinkProver::send_vk_hash_and_public_inpu /** * @brief Commit to the wire polynomials (part of the witness), with the exception of the fourth wire, which is * only committed to after adding memory records. For Mega, we also commit to the ECC op wires and DataBus columns. + * + * For interleaved flavors (BATCH_SIZE > 1), commits directly to the interleaved group buffers. */ template void OinkProver::commit_to_wires() { BB_BENCH_NAME("OinkProver::commit_to_wires"); - auto batch = commitment_key.start_batch(); - auto& tails = prover_instance->masking_tail_data.tails; - - // Commit to the first three wire polynomials; w_4 is deferred until after memory records are added - batch.add_to_batch(prover_instance->polynomials.w_l, commitment_labels.w_l, &tails.w_l); - batch.add_to_batch(prover_instance->polynomials.w_r, commitment_labels.w_r, &tails.w_r); - batch.add_to_batch(prover_instance->polynomials.w_o, commitment_labels.w_o, &tails.w_o); - - if constexpr (IsMegaFlavor) { - for (auto [polynomial, tail, label] : zip_view(prover_instance->polynomials.get_ecc_op_wires(), - tails.get_ecc_op_wires(), - commitment_labels.get_ecc_op_wires())) { - batch.add_to_batch(polynomial, label, &tail); - } - for (auto [polynomial, tail, label] : zip_view(prover_instance->polynomials.get_databus_entities(), - tails.get_databus_entities(), - commitment_labels.get_databus_entities())) { - batch.add_to_batch(polynomial, label, &tail); - } - } - - auto computed_commitments = batch.commit_and_send_to_verifier(transcript); - prover_instance->commitments.w_l = computed_commitments[0]; - prover_instance->commitments.w_r = computed_commitments[1]; - prover_instance->commitments.w_o = computed_commitments[2]; - - if constexpr (IsMegaFlavor) { - size_t commitment_idx = 3; - for (auto& commitment : prover_instance->commitments.get_ecc_op_wires()) { - commitment = computed_commitments[commitment_idx++]; - } - for (auto& commitment : prover_instance->commitments.get_databus_entities()) { - commitment = computed_commitments[commitment_idx++]; - } - } + auto& p = prover_instance->polynomials; + auto& t = prover_instance->masking_tail_data.tails; + auto& c = prover_instance->commitments; + commit_round_groups(Flavor::OinkRounds::wires(p), Flavor::OinkRounds::wires(t), Flavor::OinkRounds::wires(c)); } -/** - * @brief Compute sorted witness-table accumulator and commit to the resulting polynomials. - * - */ template void OinkProver::commit_to_lookup_counts_and_w4() { BB_BENCH_NAME("OinkProver::commit_to_lookup_counts_and_w4"); - // Get eta challenge and compute powers (eta, eta², eta³) prover_instance->relation_parameters.compute_eta_powers(transcript->template get_challenge("eta")); - add_ram_rom_memory_records_to_wire_4(*prover_instance); - - // Commit to lookup argument polynomials and the finalized (i.e. with memory records) fourth wire polynomial - auto batch = commitment_key.start_batch(); - auto& tails = prover_instance->masking_tail_data.tails; - batch.add_to_batch(prover_instance->polynomials.lookup_read_counts, - commitment_labels.lookup_read_counts, - &tails.lookup_read_counts); - batch.add_to_batch( - prover_instance->polynomials.lookup_read_tags, commitment_labels.lookup_read_tags, &tails.lookup_read_tags); - batch.add_to_batch(prover_instance->polynomials.w_4, commitment_labels.w_4, &tails.w_4); - auto computed_commitments = batch.commit_and_send_to_verifier(transcript); - - prover_instance->commitments.lookup_read_counts = computed_commitments[0]; - prover_instance->commitments.lookup_read_tags = computed_commitments[1]; - prover_instance->commitments.w_4 = computed_commitments[2]; + auto& p = prover_instance->polynomials; + auto& t = prover_instance->masking_tail_data.tails; + auto& c = prover_instance->commitments; + commit_round_groups(Flavor::OinkRounds::lookup_and_w4(p), + Flavor::OinkRounds::lookup_and_w4(t), + Flavor::OinkRounds::lookup_and_w4(c)); } -/** - * @brief Compute log derivative inverse polynomial and its commitment, if required - * - */ template void OinkProver::commit_to_logderiv_inverses() { BB_BENCH_NAME("OinkProver::commit_to_logderiv_inverses"); auto [beta, gamma] = transcript->template get_challenges(std::array{ "beta", "gamma" }); prover_instance->relation_parameters.compute_beta_powers(beta); prover_instance->relation_parameters.gamma = gamma; - - // Compute the inverses used in log-derivative lookup relations compute_logderivative_inverses(*prover_instance); - - auto batch = commitment_key.start_batch(); - auto& tails = prover_instance->masking_tail_data.tails; - batch.add_to_batch( - prover_instance->polynomials.lookup_inverses, commitment_labels.lookup_inverses, &tails.lookup_inverses); - - // If Mega, commit to the databus inverse polynomials and send - if constexpr (IsMegaFlavor) { - for (auto [polynomial, tail, label] : zip_view(prover_instance->polynomials.get_databus_inverses(), - tails.get_databus_inverses(), - commitment_labels.get_databus_inverses())) { - batch.add_to_batch(polynomial, label, &tail); - }; - } - auto computed_commitments = batch.commit_and_send_to_verifier(transcript); - - prover_instance->commitments.lookup_inverses = computed_commitments[0]; - if constexpr (IsMegaFlavor) { - size_t commitment_idx = 1; - for (auto& commitment : prover_instance->commitments.get_databus_inverses()) { - commitment = computed_commitments[commitment_idx]; - commitment_idx++; - }; - } + auto& p = prover_instance->polynomials; + auto& t = prover_instance->masking_tail_data.tails; + auto& c = prover_instance->commitments; + commit_round_groups( + Flavor::OinkRounds::inverses(p), Flavor::OinkRounds::inverses(t), Flavor::OinkRounds::inverses(c)); } -/** - * @brief Compute the permutation grand product polynomial and commit to it. - */ template void OinkProver::commit_to_z_perm() { BB_BENCH_NAME("OinkProver::commit_to_z_perm"); - compute_grand_product_polynomial(*prover_instance); - - auto& z_perm = prover_instance->polynomials.z_perm; - auto batch = commitment_key.start_batch(); - batch.add_to_batch(z_perm, commitment_labels.z_perm, &prover_instance->masking_tail_data.tails.z_perm); - auto commitments = batch.commit_and_send_to_verifier(transcript); - prover_instance->commitments.z_perm = commitments[0]; + auto& p = prover_instance->polynomials; + auto& t = prover_instance->masking_tail_data.tails; + auto& c = prover_instance->commitments; + commit_round_groups(Flavor::OinkRounds::z_perm(p), Flavor::OinkRounds::z_perm(t), Flavor::OinkRounds::z_perm(c)); } template void OinkProver::commit_to_masking_poly() { if constexpr (flavor_has_gemini_masking()) { - // Create a random masking polynomial for Gemini const size_t polynomial_size = prover_instance->dyadic_size(); - prover_instance->polynomials.gemini_masking_poly = Polynomial::random(polynomial_size); + prover_instance->polynomials.gemini_masking_poly = Polynomial::random(polynomial_size); - // Commit to the masking polynomial and send to transcript auto masking_commitment = commitment_key.commit(prover_instance->polynomials.gemini_masking_poly); transcript->send_to_verifier("Gemini:masking_poly_comm", masking_commitment); } }; +/** + * @brief Commit a list of witness groups for one oink round and send to verifier. + * @details For each group: builds PolynomialSpans from entity pointers, calls commit_interleaved, + * adds ZK tail if applicable, sends to transcript. + * Uniform for all BS (commit_interleaved<1> degenerates to commit). + */ +template +template +void OinkProver::commit_round_groups(const PolyDescs& poly_groups, + const PolyDescs& tail_groups, + const CommDescs& comm_groups) +{ + using FF_ = typename Flavor::FF; + const size_t pcs_vsize = prover_instance->dyadic_size() * BATCH_SIZE; + + for (size_t i = 0; i < poly_groups.size(); i++) { + const auto& group = poly_groups[i]; + + // Build spans from entity pointers (skip nullptrs) + std::vector> spans; + for (const auto* ptr : group.entities) { + if (ptr != nullptr) { + spans.push_back(*ptr); + } + } + Commitment commitment = commitment_key.template commit_interleaved(spans); + + // ZK: commit the interleaved tail for this group and add to commitment + if constexpr (Flavor::HasZK) { + if (prover_instance->masking_tail_data.is_active()) { + auto tail = + prover_instance->masking_tail_data.interleave_tail_group(tail_groups[i].entities, pcs_vsize); + if (!tail.is_empty()) { + commitment = commitment + commitment_key.commit(tail); + } + } + } + + transcript->send_to_verifier(group.label, commitment); + + // Store in prover commitments (first non-null pointer in the commitment group) + for (auto* ptr : comm_groups[i].entities) { + if (ptr != nullptr) { + *ptr = commitment; + break; + } + } + } +} + /** * @brief Add RAM/ROM memory records to the fourth wire polynomial * @@ -299,8 +276,14 @@ template class OinkProver; template class OinkProver; #endif template class OinkProver; +template class OinkProver; +template class OinkProver; template class OinkProver; template class OinkProver; template class OinkProver; +template class OinkProver; +template class OinkProver; +template class OinkProver; +template class OinkProver; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp index 3cbcda45d3e9..2033b8300d6f 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.hpp @@ -26,6 +26,7 @@ #include "barretenberg/ultra_honk/prover_instance.hpp" namespace bb { + /** * @brief Executes the "Oink" phase of the Honk proving protocol: the initial rounds that commit to * witness data, lookup/logderivative inverses, and the permutation grand product, producing the @@ -42,6 +43,9 @@ namespace bb { * 6. commit_to_z_perm – compute and commit to the permutation grand product polynomial * 7. get alpha challenge * + * For interleaved flavors (INTERLEAVING_BATCH_SIZE > 1), polynomials are committed in groups + * using interleaved MSM, reducing the number of witness commitments. + * * After prove() completes, the prover instance holds all committed polynomials and relation * parameters needed by the subsequent Sumcheck and PCS phases in UltraProver. * @@ -55,12 +59,21 @@ template class OinkProver { using ProverInstance = ProverInstance_; using Transcript = typename Flavor::Transcript; using FF = typename Flavor::FF; + using Commitment = typename Flavor::Commitment; using Proof = typename Transcript::Proof; + using Polynomial = typename Flavor::Polynomial; + + static constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; public: + std::shared_ptr prover_instance; + std::shared_ptr honk_vk; + std::shared_ptr transcript; + CommitmentKey commitment_key; + OinkProver(std::shared_ptr prover_instance, std::shared_ptr honk_vk, - const std::shared_ptr& transcript) + const std::shared_ptr& transcript) : prover_instance(prover_instance) , honk_vk(honk_vk) , transcript(transcript) @@ -77,17 +90,21 @@ template class OinkProver { static void compute_grand_product_polynomial(ProverInstance& instance); private: - std::shared_ptr prover_instance; - std::shared_ptr honk_vk; - std::shared_ptr transcript; - CommitmentKey commitment_key; - typename Flavor::CommitmentLabels commitment_labels; void send_vk_hash_and_public_inputs(); void commit_to_wires(); void commit_to_lookup_counts_and_w4(); void commit_to_logderiv_inverses(); void commit_to_z_perm(); void commit_to_masking_poly(); + + /** + * @brief Commit a list of witness groups and send to verifier. + * @details For each group: builds PolynomialSpans, calls commit_interleaved, + * adds ZK tail if applicable, sends to transcript, stores in commitment_descs. + * Uniform for all BS (commit_interleaved<1> degenerates to commit). + */ + template + void commit_round_groups(const PolyDescs& poly_groups, const PolyDescs& tail_groups, const CommDescs& comm_groups); }; using MegaOinkProver = OinkProver; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.test.cpp index 5d46169a2dc5..b8c1743dd34e 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_prover.test.cpp @@ -81,22 +81,28 @@ TEST_F(OinkTests, OinkProverCommitments) auto vk_and_hash = std::make_shared(verification_key); auto verifier_instance = std::make_shared(vk_and_hash); - OinkProver prover(prover_instance, verification_key, std::make_shared()); + // Run oink prover to produce proof, then oink verifier to receive commitments + auto prover_transcript = std::make_shared(); + OinkProver prover(prover_instance, verification_key, prover_transcript); prover.prove(); HonkProof proof = prover.export_proof(); - Flavor::VerifierCommitments prover_commitments(verification_key, prover_instance->commitments); - - auto transcript = std::make_shared(); - transcript->load_proof(proof); - OinkVerifier verifier(verifier_instance, transcript, verification_key->num_public_inputs); + auto verifier_transcript = std::make_shared(); + verifier_transcript->load_proof(proof); + OinkVerifier verifier(verifier_instance, verifier_transcript, verification_key->num_public_inputs); verifier.verify(); + // Verify commitments by re-committing prover polynomials and comparing with verifier received + typename Flavor::CommitmentKey ck(prover_instance->dyadic_size()); Flavor::VerifierCommitments verifier_commitments(verifier_instance->get_vk(), - verifier_instance->witness_commitments); - - for (auto [prover_comm, verifier_comm, label] : zip_view( - prover_commitments.get_all(), verifier_commitments.get_all(), Flavor::VerifierCommitments::get_labels())) { - EXPECT_EQ(prover_comm, verifier_comm) << "Mismatch in commitments " << label; + verifier_instance->received_commitments); + + // Check that each non-zero witness commitment matches the direct polynomial commit + auto witness_polys = prover_instance->polynomials.get_witness(); + auto witness_comms = verifier_commitments.get_witness(); + for (auto [poly, comm] : zip_view(witness_polys, witness_comms)) { + if (!poly.is_empty() && !comm.is_point_at_infinity()) { + EXPECT_EQ(ck.commit(poly), comm); + } } } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.cpp index c4db36ce37ac..813444fee104 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.cpp @@ -9,6 +9,8 @@ #include "barretenberg/ext/starknet/flavor/ultra_starknet_flavor.hpp" #include "barretenberg/ext/starknet/flavor/ultra_starknet_zk_flavor.hpp" #include "barretenberg/flavor/mega_avm_recursive_flavor.hpp" +#include "barretenberg/flavor/mega_recursive_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/mega_zk_recursive_flavor.hpp" #include "barretenberg/flavor/ultra_keccak_zk_flavor.hpp" #include "barretenberg/flavor/ultra_zk_recursive_flavor.hpp" @@ -23,10 +25,12 @@ namespace bb { template void OinkVerifier::verify(bool emit_alpha) { receive_vk_hash_and_public_inputs(); + if constexpr (flavor_has_gemini_masking()) { - verifier_instance->gemini_masking_commitment = + verifier_instance->received_commitments.masking_commitment = transcript->template receive_from_prover("Gemini:masking_poly_comm"); } + receive_wire_commitments(); receive_lookup_counts_and_w4_commitments(); receive_logderiv_commitments(); @@ -77,82 +81,58 @@ template void OinkVerifier::receive_vk_hash_and_public /** * @brief Receive wire commitments (w_l, w_r, w_o). For Mega, also receive ECC op wire and DataBus commitments. - * The fourth wire (w_4) is received later, after memory records are incorporated. + * For interleaved flavors, receive interleaved commitments instead. */ template void OinkVerifier::receive_wire_commitments() { - // Get commitments to first three wire polynomials - verifier_instance->witness_commitments.w_l = transcript->template receive_from_prover(comm_labels.w_l); - verifier_instance->witness_commitments.w_r = transcript->template receive_from_prover(comm_labels.w_r); - verifier_instance->witness_commitments.w_o = transcript->template receive_from_prover(comm_labels.w_o); - - if constexpr (IsMegaFlavor) { - // Receive ECC op wire commitments - for (auto [commitment, label] : - zip_view(verifier_instance->witness_commitments.get_ecc_op_wires(), comm_labels.get_ecc_op_wires())) { - commitment = transcript->template receive_from_prover(label); - } - - // Receive DataBus related polynomial commitments - for (auto [commitment, label] : zip_view(verifier_instance->witness_commitments.get_databus_entities(), - comm_labels.get_databus_entities())) { - commitment = transcript->template receive_from_prover(label); - } - } + receive_round_groups(Flavor::OinkRounds::wires(verifier_instance->received_commitments)); } -/** - * @brief Get sorted witness-table accumulator and fourth wire commitments - * - */ template void OinkVerifier::receive_lookup_counts_and_w4_commitments() { - // Get eta challenge and compute powers (eta, eta², eta³) verifier_instance->relation_parameters.compute_eta_powers(transcript->template get_challenge("eta")); - - // Get commitments to lookup argument polynomials and fourth wire - verifier_instance->witness_commitments.lookup_read_counts = - transcript->template receive_from_prover(comm_labels.lookup_read_counts); - verifier_instance->witness_commitments.lookup_read_tags = - transcript->template receive_from_prover(comm_labels.lookup_read_tags); - verifier_instance->witness_commitments.w_4 = transcript->template receive_from_prover(comm_labels.w_4); + receive_round_groups(Flavor::OinkRounds::lookup_and_w4(verifier_instance->received_commitments)); } -/** - * @brief Receive beta/gamma challenges and log-derivative inverse commitments (plus databus inverses for Mega). - */ template void OinkVerifier::receive_logderiv_commitments() { auto [beta, gamma] = transcript->template get_challenges(std::array{ "beta", "gamma" }); verifier_instance->relation_parameters.compute_beta_powers(beta); verifier_instance->relation_parameters.gamma = gamma; - - verifier_instance->witness_commitments.lookup_inverses = - transcript->template receive_from_prover(comm_labels.lookup_inverses); - - if constexpr (IsMegaFlavor) { - for (auto [commitment, label] : zip_view(verifier_instance->witness_commitments.get_databus_inverses(), - comm_labels.get_databus_inverses())) { - commitment = transcript->template receive_from_prover(label); - } - } + receive_round_groups(Flavor::OinkRounds::inverses(verifier_instance->received_commitments)); } -/** - * @brief Compute public_input_delta for the permutation argument and receive z_perm commitment. - */ template void OinkVerifier::complete_grand_product_round() { auto vk = verifier_instance->get_vk(); - verifier_instance->relation_parameters.public_input_delta = compute_public_input_delta(verifier_instance->public_inputs, verifier_instance->relation_parameters.beta, verifier_instance->relation_parameters.gamma, vk->pub_inputs_offset); + receive_round_groups(Flavor::OinkRounds::z_perm(verifier_instance->received_commitments)); +} - verifier_instance->witness_commitments.z_perm = - transcript->template receive_from_prover(comm_labels.z_perm); +/** + * @brief Receive commitments for a round's groups from transcript and store. + * @details OinkRounds called on received_commitments produces descriptors whose entity + * pointers point into the commitment storage. The first entity in each group is + * the field where the commitment should be stored. + */ +template +template +void OinkVerifier::receive_round_groups(const GroupDescs& groups) +{ + for (const auto& group : groups) { + auto received = transcript->template receive_from_prover(group.label); + // Write through the first non-null entity pointer (which points into received_commitments) + for (auto* ptr : group.entities) { + if (ptr != nullptr) { + *ptr = received; + break; + } + } + } } // Native flavor instantiations @@ -164,8 +144,14 @@ template class OinkVerifier; template class OinkVerifier; #endif template class OinkVerifier; +template class OinkVerifier; +template class OinkVerifier; template class OinkVerifier; template class OinkVerifier; +template class OinkVerifier; +template class OinkVerifier; +template class OinkVerifier; +template class OinkVerifier; // Recursive flavor instantiations template class OinkVerifier>; @@ -177,5 +163,17 @@ template class OinkVerifier>; template class OinkVerifier>; template class OinkVerifier>; template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; +template class OinkVerifier>; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.hpp index 0769ac5f74d5..8049a69b7bc4 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/oink_verifier.hpp @@ -24,6 +24,9 @@ namespace bb { * 6. complete_grand_product_round – compute public_input_delta, receive z_perm * 7. get alpha challenge * + * For interleaved flavors (INTERLEAVING_BATCH_SIZE > 1), receives interleaved commitments instead + * of individual ones, reducing the number of witness commitments. + * * Works with both native and recursive flavors. When instantiated with a recursive flavor * (IsRecursiveFlavor), automatically handles the differences in VK access and VK hash assertion. */ @@ -33,7 +36,13 @@ template class OinkVerifier { using Commitment = typename Flavor::Commitment; using Instance = bb::VerifierInstance_; + static constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; + public: + std::shared_ptr transcript; + std::shared_ptr verifier_instance; + size_t num_public_inputs; + OinkVerifier(const std::shared_ptr& verifier_instance, const std::shared_ptr& transcript, size_t num_public_inputs) @@ -48,14 +57,19 @@ template class OinkVerifier { void verify(bool emit_alpha = true); private: - std::shared_ptr transcript; - std::shared_ptr verifier_instance; - typename Flavor::CommitmentLabels comm_labels; - size_t num_public_inputs; void receive_vk_hash_and_public_inputs(); void receive_wire_commitments(); void receive_lookup_counts_and_w4_commitments(); void receive_logderiv_commitments(); void complete_grand_product_round(); + + /** + * @brief Receive commitments for a round's groups from transcript. + * @details Group descriptors are obtained by calling OinkRounds on received_commitments, + * so entity pointers point into the commitment storage. The received commitment + * is written through the first non-null pointer in each group (for BS=1, that's + * the single entity; for BS>1, the first field of the interleaved commitment). + */ + template void receive_round_groups(const GroupDescs& groups); }; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp index 2e272a54de6e..42fc7d1aa67b 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp @@ -10,6 +10,9 @@ #include "barretenberg/common/log.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "barretenberg/flavor/mega_avm_flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_interleaving_entities.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/honk/composer/composer_lib.hpp" #include "barretenberg/honk/composer/permutation_lib.hpp" #include "barretenberg/honk/proof_system/logderivative_library.hpp" @@ -54,6 +57,10 @@ template ProverInstance_::ProverInstance_(Circuit& cir vinfo("allocating polynomials object in prover instance..."); populate_memory_records(circuit); + + // Allocate individual entity polynomials sized to their actual data extents. + // For interleaved flavors (BS>1), interleaved group buffers are built on-the-fly + // at commitment/PCS time — no persistent group buffers are stored. allocate_wires(); allocate_permutation_argument_polynomials(); allocate_selectors(circuit); @@ -67,7 +74,6 @@ template ProverInstance_::ProverInstance_(Circuit& cir allocate_databus_polynomials(circuit); } - // Set the shifted polynomials now that all of the to_be_shifted polynomials are defined. polynomials.set_shifted(); } @@ -352,8 +358,14 @@ template class ProverInstance_; template class ProverInstance_; #endif template class ProverInstance_; +template class ProverInstance_; +template class ProverInstance_; template class ProverInstance_; template class ProverInstance_; template class ProverInstance_; +template class ProverInstance_; +template class ProverInstance_; +template class ProverInstance_; +template class ProverInstance_; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.hpp index d7e258e8af31..23da74087f46 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.hpp @@ -70,7 +70,17 @@ template class ProverInstance_ { Flavor::PrecomputedData get_precomputed() { - return typename Flavor::PrecomputedData{ polynomials.get_precomputed(), metadata }; + std::vector> precomputed_groups; + if constexpr (Flavor::INTERLEAVING_BATCH_SIZE > 1) { + constexpr size_t num_precomputed = Flavor::NUM_INTERLEAVED_PRECOMPUTED_COMMITMENTS; + auto groups = Flavor::get_unshifted_groups(polynomials); + precomputed_groups.reserve(num_precomputed); + for (size_t g = 0; g < num_precomputed; g++) { + precomputed_groups.push_back(groups[g]); + } + } + return + typename Flavor::PrecomputedData{ polynomials.get_precomputed(), metadata, std::move(precomputed_groups) }; } ProverInstance_(Circuit& circuit); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/rom_ram.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/rom_ram.test.cpp index cb7b99620447..4a2b614d1aea 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/rom_ram.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/rom_ram.test.cpp @@ -1,5 +1,6 @@ #include "barretenberg/circuit_checker/circuit_checker.hpp" +#include "barretenberg/flavor/ultra_zk_flavor.hpp" #include "failure_test_utils.hpp" #include "ultra_honk.test.hpp" @@ -13,7 +14,8 @@ using FlavorTypes = testing::Types; #else -using FlavorTypes = testing::Types; +using FlavorTypes = testing:: + Types; #endif template class MemoryTests_ : public UltraHonkTests { diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp index 9841f13f488a..07d3b1ff5cfc 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_honk.test.cpp @@ -1,4 +1,5 @@ #include "ultra_honk.test.hpp" +#include "barretenberg/flavor/ultra_zk_flavor.hpp" #include "barretenberg/honk/proof_length.hpp" #include "barretenberg/honk/relation_checker.hpp" #include "barretenberg/ultra_honk/oink_prover.hpp" @@ -17,7 +18,8 @@ using FlavorTypes = testing::Types; #else -using FlavorTypes = testing::Types; +using FlavorTypes = testing:: + Types; #endif TYPED_TEST_SUITE(UltraHonkTests, FlavorTypes); /** @@ -58,6 +60,9 @@ TYPED_TEST(UltraHonkTests, ProofLengthCheck) */ TYPED_TEST(UltraHonkTests, ANonZeroPolynomialIsAGoodPolynomial) { + if constexpr (TypeParam::INTERLEAVING_BATCH_SIZE > 1) { + GTEST_SKIP() << "Strided entity views don't support coeffs() iteration"; + } auto circuit_builder = UltraCircuitBuilder(); TestFixture::set_default_pairing_points_and_ipa_claim_and_proof(circuit_builder); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp index 2e22ef8f93bb..8a4620d18c82 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.cpp @@ -8,6 +8,8 @@ #include "barretenberg/commitment_schemes/gemini/gemini.hpp" #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" #include "barretenberg/flavor/mega_avm_flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/sumcheck/sumcheck.hpp" #include "barretenberg/ultra_honk/oink_prover.hpp" namespace bb { @@ -23,18 +25,6 @@ UltraProver_::UltraProver_(std::shared_ptr prover_instan /** * @brief Export the complete proof, including IPA proof for rollup circuits - * @details Two-level proof structure for rollup circuits: - * - * **Prover Level (this function):** - * [public_inputs | honk_proof | ipa_proof] - * - Appends IPA proof if prover_instance->ipa_proof is non-empty - * - SYMMETRIC with UltraVerifier_::split_rollup_proof() which extracts the IPA portion - * - * **API Level (bbapi):** - * - _prove() further splits into: public_inputs (ACIR only) vs proof (rest including IPA) - * - concatenate_proof() reassembles for verification - * - * @note IPA_PROOF_LENGTH is defined in ipa.hpp as 4*CONST_ECCVM_LOG_N + 4 = 64 elements */ template typename UltraProver_::Proof UltraProver_::export_proof() { @@ -60,12 +50,14 @@ template void UltraProver_::generate_gate_challenges() template typename UltraProver_::Proof UltraProver_::construct_proof() { + constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; + // The CRS only needs to accommodate the actual data extent (max_end_index) rather than the // full dyadic_size. All committed polynomials fit within this bound: witness/selector polys // have backing ≤ max_end_index, Gemini fold polys have size ≤ dyadic_size/2 < max_end_index, // Shplonk quotient Q is sized at max(claim sizes), and KZG opening proof is sized at Q.size(). // For ZK, the gemini_masking_poly (at dyadic_size) is already reflected in max_end_index. - size_t key_size = prover_instance->polynomials.max_end_index(); + size_t key_size = prover_instance->polynomials.max_end_index() * BATCH_SIZE; if constexpr (Flavor::HasZK) { // Masking tails extend A_0 to dyadic_size in Gemini, so the commitment key must // accommodate the full dyadic circuit size (Shplonk quotient may be dyadic-sized). @@ -76,6 +68,7 @@ template typename UltraProver_::Proof UltraProver_ oink_prover(prover_instance, honk_vk, transcript); + oink_prover.commitment_key = commitment_key; oink_prover.prove(); vinfo("created oink proof"); @@ -84,6 +77,7 @@ template typename UltraProver_::Proof UltraProver_ void UltraProver_::execute_sumcheck_iop() /** * @brief Reduce the sumcheck multivariate evaluations to a single univariate opening claim via Shplemini, * then produce an opening proof with the PCS (KZG or IPA). + * + * For interleaved flavors (BATCH_SIZE > 1), prepends interleaving challenges and uses group buffers directly. */ template void UltraProver_::execute_pcs() { using OpeningClaim = ProverOpeningClaim; - using PolynomialBatcher = GeminiProver_::PolynomialBatcher; + using Polynomial = typename Flavor::Polynomial; + + constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; + constexpr size_t LOG_K = Flavor::INTERLEAVING_LOG_K; auto& ck = commitment_key; - PolynomialBatcher polynomial_batcher(prover_instance->dyadic_size(), prover_instance->polynomials.max_end_index()); - polynomial_batcher.set_unshifted(prover_instance->polynomials.get_unshifted()); - polynomial_batcher.set_to_be_shifted_by_one(prover_instance->polynomials.get_to_be_shifted()); + const size_t n = prover_instance->dyadic_size(); + const size_t pcs_size = n * BATCH_SIZE; - // For ZK: register masking tail polynomials with the batcher so PCS includes them - if constexpr (Flavor::HasZK) { - if (prover_instance->masking_tail_data.is_active()) { - prover_instance->masking_tail_data.add_tails_to_batcher(prover_instance->polynomials, polynomial_batcher); - } + // Build full challenge vector: interleaving challenges (if any) + sumcheck challenges + std::vector full_challenge; + full_challenge.reserve(LOG_K + sumcheck_output.challenge.size()); + for (size_t i = 0; i < LOG_K; i++) { + full_challenge.push_back( + transcript->template get_challenge("Shplemini:interleaving_challenge_" + std::to_string(i))); } + full_challenge.insert(full_challenge.end(), sumcheck_output.challenge.begin(), sumcheck_output.challenge.end()); - OpeningClaim prover_opening_claim; - if constexpr (!Flavor::HasZK) { - prover_opening_claim = ShpleminiProver_::prove( - prover_instance->dyadic_size(), polynomial_batcher, sumcheck_output.challenge, ck, transcript); - } else { - + // ZK: SmallSubgroupIPA + std::array libra_witness_polys{}; + if constexpr (Flavor::HasZK) { SmallSubgroupIPA small_subgroup_ipa_prover( zk_sumcheck_data, sumcheck_output.challenge, sumcheck_output.claimed_libra_evaluation, transcript, ck); small_subgroup_ipa_prover.prove(); + libra_witness_polys = small_subgroup_ipa_prover.get_witness_polynomials(); + } + + // Get the batching challenge ρ + const FF rho = transcript->template get_challenge("rho"); + + using GeminiProver = GeminiProver_; + using ProverPolynomials = typename Flavor::ProverPolynomials; + + OpeningClaim prover_opening_claim; + if constexpr (BATCH_SIZE > 1) { + // Pre-batch interleaved groups into F and G using rho, freeing entity memory + auto [F, G] = ProverPolynomials::template batch_interleaved_groups_with_rho( + std::move(prover_instance->polynomials), rho, n, BATCH_SIZE, pcs_size); + + // ZK: accumulate interleaved masking tails into the batched polynomials + if constexpr (Flavor::HasZK) { + if (prover_instance->masking_tail_data.is_active()) { + auto groups = Flavor::get_unshifted_groups(prover_instance->masking_tail_data.tails); + prover_instance->masking_tail_data.accumulate_interleaved_tails(F, G, rho, groups.size(), pcs_size); + } + } + vinfo("pre-batched interleaved groups"); - prover_opening_claim = ShpleminiProver_::prove(prover_instance->dyadic_size(), - polynomial_batcher, - sumcheck_output.challenge, - ck, - transcript, - small_subgroup_ipa_prover.get_witness_polynomials()); + // Gemini from pre-batched F and G (bypasses PolynomialBatcher) + auto opening_claims = GeminiProver::prove( + pcs_size, std::move(F), std::move(G), BATCH_SIZE, full_challenge, ck, transcript, Flavor::HasZK); + + // ZK: Libra opening claims + std::vector libra_opening_claims; + if constexpr (Flavor::HasZK) { + libra_opening_claims = ShpleminiProver_::compute_libra_opening_claims( + opening_claims[0].opening_pair.challenge, libra_witness_polys, transcript); + } + + prover_opening_claim = ShplonkProver_::prove( + ck, opening_claims, transcript, libra_opening_claims, {}, full_challenge.size()); + } else { + // BS=1: standard flow through PolynomialBatcher + Shplemini + typename GeminiProver::PolynomialBatcher batcher(pcs_size); + batcher.set_unshifted(prover_instance->polynomials.get_unshifted()); + batcher.set_to_be_shifted(prover_instance->polynomials.get_to_be_shifted()); + + if constexpr (Flavor::HasZK) { + if (prover_instance->masking_tail_data.is_active()) { + prover_instance->masking_tail_data.add_tails_to_batcher(prover_instance->polynomials, batcher); + } + prover_opening_claim = ShpleminiProver_::prove( + pcs_size, batcher, rho, full_challenge, ck, transcript, libra_witness_polys); + } else { + prover_opening_claim = + ShpleminiProver_::prove(pcs_size, batcher, rho, full_challenge, ck, transcript); + } } + vinfo("executed multivariate-to-univariate reduction"); PCS::compute_opening_proof(ck, prover_opening_claim, transcript); vinfo("computed opening proof"); @@ -169,8 +213,14 @@ template class UltraProver_; template class UltraProver_; #endif template class UltraProver_; +template class UltraProver_; +template class UltraProver_; template class UltraProver_; template class UltraProver_; template class UltraProver_; +template class UltraProver_; +template class UltraProver_; +template class UltraProver_; +template class UltraProver_; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp index b3c09a877f33..c4b38b0f3669 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_prover.hpp @@ -40,7 +40,7 @@ template class UltraProver_ { size_t log_dyadic_size() const { return prover_instance->log_dyadic_size(); } const std::shared_ptr& get_transcript() const { return transcript; } - private: + protected: std::shared_ptr prover_instance; std::shared_ptr transcript; std::shared_ptr honk_vk; @@ -65,5 +65,7 @@ using UltraStarknetZKProver = UltraProver_; using UltraKeccakZKProver = UltraProver_; using MegaProver = UltraProver_; using MegaZKProver = UltraProver_; +using MultiHonkProver = UltraProver_; +using MultiHonkZKProver = UltraProver_; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp index ed9f7a549153..a249c0e537bd 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.cpp @@ -10,6 +10,9 @@ #include "barretenberg/commitment_schemes/shplonk/shplemini.hpp" #include "barretenberg/common/bb_bench.hpp" #include "barretenberg/flavor/mega_avm_recursive_flavor.hpp" +#include "barretenberg/flavor/mega_flavor.hpp" +#include "barretenberg/flavor/mega_recursive_flavor.hpp" +#include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/mega_zk_recursive_flavor.hpp" #include "barretenberg/flavor/ultra_zk_recursive_flavor.hpp" #include "barretenberg/honk/proof_length.hpp" @@ -19,6 +22,82 @@ namespace bb { +/** + * @brief Assemble PCS commitments from the verifier instance. + * @details Uniform for all batch sizes: unshifted = [ZK masking] ++ VK precomputed ++ received witness. + * to_be_shifted = shiftable subset of received commitments. + */ +template static auto build_pcs_commitments(Instance& instance) +{ + using Commitment = typename Flavor::Commitment; + + struct Result { + std::vector unshifted; + std::vector to_be_shifted; + }; + + auto vk = instance.get_vk(); + auto& received = instance.received_commitments; + + Result result; + + // For flavors with Gemini masking (e.g. UltraZK): prepend masking commitment + if constexpr (flavor_has_gemini_masking()) { + result.unshifted.push_back(instance.received_commitments.masking_commitment); + } + + // VK precomputed + received witness commitments + for (auto& c : concatenate(vk->get_all(), received.get_all())) { + result.unshifted.push_back(c); + } + + // Shiftable subset + for (auto& c : received.get_shiftable()) { + result.to_be_shifted.push_back(c); + } + + return result; +} + +/** + * @brief Compute PCS evaluations from sumcheck claimed evaluations. + * @details Groups individual evaluations and combines via Lagrange basis. + * For BS=1: Lagrange basis is {1} and groups are singletons, so this is identity. + * For BS>1: groups evaluations and combines via multivariate Lagrange evaluations. + */ +template +static auto build_pcs_evaluations(typename Flavor::AllValues& claimed_evaluations, + std::span interleaving_challenges) +{ + using FF = typename Flavor::FF; + constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; + + struct Result { + std::vector unshifted; + std::vector shifted; + }; + + auto lagrange_basis = Flavor::compute_lagrange_basis(interleaving_challenges); + + auto compute_group_evals = [&](const auto& eval_groups) { + std::vector group_evals(eval_groups.size()); + for (size_t i = 0; i < eval_groups.size(); i++) { + FF eval(0); + for (size_t j = 0; j < BATCH_SIZE; j++) { + // nullptr slots are padding (e.g. group [w_l, w_r, w_o, nullptr] for BS=4) + if (j < eval_groups[i].size() && eval_groups[i][j] != nullptr) { + eval += *eval_groups[i][j] * lagrange_basis[j]; + } + } + group_evals[i] = eval; + } + return group_evals; + }; + + return Result{ compute_group_evals(Flavor::get_unshifted_groups(claimed_evaluations)), + compute_group_evals(Flavor::get_shifted_groups(claimed_evaluations)) }; +} + /** * @brief Compute log_n based on flavor. * @details Returns VIRTUAL_LOG_N for padded flavors, or VK's log_circuit_size otherwise. @@ -38,25 +117,20 @@ template size_t UltraVerifier_::compute_ * @brief Compute padding indicator array based on flavor configuration. * @details Must be called AFTER OinkVerifier::verify() so that VK fields are properly * tagged through the transcript (for recursive ZK flavors). - * @param log_n The log circuit size (from compute_log_n) - * @return std::vector padding indicator array + * - Non-ZK flavors: all 1s (no masking needed) + * - ZK without padding: all 1s (log_n == log_circuit_size) + * - ZK with padding: 1s for real rounds, 0s for padding rounds */ template std::vector UltraVerifier_::compute_padding_indicator_array(size_t log_n) const { - // - Non-ZK flavors: all 1s (no masking needed) - // - ZK without padding: all 1s (log_n == log_circuit_size, no padded region) - // - ZK with padding: computed to mask padded rounds (1s for real, 0s for padding) std::vector padding_indicator_array(log_n, FF{ 1 }); if constexpr (Flavor::HasZK && Flavor::USE_PADDING) { auto vk_ptr = verifier_instance->get_vk(); if constexpr (IsRecursive) { - // Recursive: use in-circuit computation via Lagrange polynomials - // Note: Must be called after OinkVerifier so log_circuit_size is properly tagged padding_indicator_array = stdlib::compute_padding_indicator_array(vk_ptr->log_circuit_size); } else { - // Native: simple loop comparison const size_t log_circuit_size = static_cast(vk_ptr->log_circuit_size); for (size_t idx = 0; idx < log_n; idx++) { padding_indicator_array[idx] = (idx < log_circuit_size) ? FF{ 1 } : FF{ 0 }; @@ -68,20 +142,9 @@ std::vector UltraVerifier_::compute_padding_ind } /** - * @brief Split a combined rollup proof into honk and IPA components - * @details Two-level proof structure for rollup circuits: - * - * **Prover Level (UltraProver_::export_proof()):** - * Creates: [public_inputs | honk_proof | ipa_proof] - * - IPA proof appended if prover_instance->ipa_proof is non-empty - * - * **Verifier Level (this function):** - * Splits: [honk_proof | ipa_proof] -> (honk_proof, ipa_proof) - * - SYMMETRIC with UltraProver_::export_proof() - * - IPA proof is exactly IPA_PROOF_LENGTH (64) elements at the end - * - * @note IPA_PROOF_LENGTH is defined in ipa.hpp as 4*CONST_ECCVM_LOG_N + 4 - * @see UltraProver_::export_proof() for the proof construction side + * @brief Split a combined rollup proof [honk_proof | ipa_proof] into its two components. + * @details Symmetric with UltraProver_::export_proof() which appends the IPA proof. + * IPA proof is exactly IPA_PROOF_LENGTH (= 4*CONST_ECCVM_LOG_N + 4) elements at the end. */ template std::pair::Proof, typename UltraVerifier_::Proof> UltraVerifier_< @@ -89,13 +152,11 @@ std::pair::Proof, typename UltraVerifier_::split_rollup_proof(const Proof& combined_proof) const requires(IO::HasIPA) { - // Validate combined proof is large enough to contain IPA proof BB_ASSERT_GTE(combined_proof.size(), IPA_PROOF_LENGTH, "Combined rollup proof is too small to contain IPA proof. Expected at least " + std::to_string(IPA_PROOF_LENGTH) + " elements, got " + std::to_string(combined_proof.size())); - // IPA proof is appended at the end (must match UltraProver_::export_proof()) const auto honk_proof_length = static_cast(combined_proof.size() - IPA_PROOF_LENGTH); Proof honk_proof(combined_proof.begin(), combined_proof.begin() + honk_proof_length); @@ -105,7 +166,7 @@ std::pair::Proof, typename UltraVerifier_ bool UltraVerifier_::verify_ipa(const Proof& ipa_proof, const IPAClaim& ipa_claim) @@ -124,9 +185,11 @@ bool UltraVerifier_::verify_ipa(const Proof& ipa_proof, const IPACla } /** - * @brief Reduce ultra proof to verification claims (works for both native and recursive) - * @details Contains all shared verification logic: Oink, Sumcheck, Shplemini - * @return ReductionResult with pairing points and intermediate consistency checks + * @brief Reduce ultra proof to verification claims (works for both native and recursive). + * @details Contains all shared verification logic: Oink, Sumcheck, Shplemini. + * For interleaved flavors (BATCH_SIZE > 1), evaluations are Lagrange-combined per group + * before being passed to the PCS. + * @return ReductionResult with pairing points and intermediate consistency checks. */ template typename UltraVerifier_::ReductionResult UltraVerifier_::reduce_to_pairing_check( @@ -136,57 +199,66 @@ typename UltraVerifier_::ReductionResult UltraVerifier_: using ClaimBatcher = ClaimBatcher_; using ClaimBatch = ClaimBatcher::Batch; + constexpr size_t BATCH_SIZE = Flavor::INTERLEAVING_BATCH_SIZE; + transcript->load_proof(proof); - // Compute log_n first (needed for proof layout calculation) const size_t log_n = compute_log_n(); - // Guard against proof size underflow before deriving num_public_inputs const size_t min_proof_size = ProofLength::Honk::LENGTH_WITHOUT_PUB_INPUTS(log_n); BB_ASSERT_GTE(proof.size(), min_proof_size, "Proof size too small. Got " + std::to_string(proof.size()) + " field elements, but need at least " + std::to_string(min_proof_size) + " (excluding public inputs) for log_n=" + std::to_string(log_n)); - // Derive num_public_inputs from proof size using centralized proof layout const size_t num_public_inputs = ProofLength::Honk::derive_num_public_inputs(proof.size(), log_n); OinkVerifier oink_verifier{ verifier_instance, transcript, num_public_inputs }; oink_verifier.verify(); - // Compute padding indicator array AFTER OinkVerifier so VK fields are properly tagged - auto padding_indicator_array = compute_padding_indicator_array(log_n); + auto sumcheck_padding_indicator_array = compute_padding_indicator_array(log_n); verifier_instance->gate_challenges = transcript->template get_dyadic_powers_of_challenge("Sumcheck:gate_challenge", log_n); - // Get the witness commitments that the verifier needs to verify - VerifierCommitments commitments{ verifier_instance->get_vk(), verifier_instance->witness_commitments }; - // For ZK flavors with Gemini masking: set gemini_masking_poly commitment from accumulator - if constexpr (flavor_has_gemini_masking()) { - commitments.gemini_masking_poly = verifier_instance->gemini_masking_commitment; - } - // Construct the sumcheck verifier SumcheckVerifier sumcheck(transcript, verifier_instance->alpha, log_n); - // Receive commitments to Libra masking polynomials for ZKFlavors std::array libra_commitments = {}; if constexpr (Flavor::HasZK) { libra_commitments[0] = transcript->template receive_from_prover("Libra:concatenation_commitment"); } - // Run the sumcheck verifier + SumcheckOutput sumcheck_output = sumcheck.verify( - verifier_instance->relation_parameters, verifier_instance->gate_challenges, padding_indicator_array); - // Get the claimed evaluation of the Libra polynomials for ZKFlavors + verifier_instance->relation_parameters, verifier_instance->gate_challenges, sumcheck_padding_indicator_array); + + vinfo("UltraVerifier: sumcheck verified = ", sumcheck_output.verified); + + constexpr size_t LOG_K = Flavor::INTERLEAVING_LOG_K; + + // Build full challenge vector: interleaving challenges (if any) + sumcheck challenges + std::vector full_challenge; + full_challenge.reserve(LOG_K + sumcheck_output.challenge.size()); + for (size_t i = 0; i < LOG_K; i++) { + full_challenge.push_back( + transcript->template get_challenge("Shplemini:interleaving_challenge_" + std::to_string(i))); + } + full_challenge.insert(full_challenge.end(), sumcheck_output.challenge.begin(), sumcheck_output.challenge.end()); + + // Receive remaining Libra commitments (after interleaving challenges, before Shplemini) if constexpr (Flavor::HasZK) { libra_commitments[1] = transcript->template receive_from_prover("Libra:grand_sum_commitment"); libra_commitments[2] = transcript->template receive_from_prover("Libra:quotient_commitment"); } - ClaimBatcher claim_batcher{ - .unshifted = ClaimBatch{ commitments.get_unshifted(), sumcheck_output.claimed_evaluations.get_unshifted() }, - .shifted = ClaimBatch{ commitments.get_to_be_shifted(), sumcheck_output.claimed_evaluations.get_shifted() } - }; + // PCS padding indicator: [1]*LOG_K prefix + sumcheck padding + std::vector pcs_padding_indicator_array; + pcs_padding_indicator_array.reserve(full_challenge.size()); + for (size_t i = 0; i < LOG_K; i++) { + pcs_padding_indicator_array.push_back(FF{ 1 }); + } + pcs_padding_indicator_array.insert(pcs_padding_indicator_array.end(), + sumcheck_padding_indicator_array.begin(), + sumcheck_padding_indicator_array.end()); const Commitment one_commitment = [&]() { if constexpr (IsRecursive) { @@ -196,37 +268,46 @@ typename UltraVerifier_::ReductionResult UltraVerifier_: } }(); - auto shplemini_output = Shplemini::compute_batch_opening_claim(padding_indicator_array, + // Build PCS commitment and evaluation data + auto pcs_comms = build_pcs_commitments(*verifier_instance); + auto pcs_evals = build_pcs_evaluations(sumcheck_output.claimed_evaluations, + std::span(full_challenge).first(LOG_K)); + + ClaimBatcher claim_batcher{ + .unshifted = ClaimBatch{ RefVector(pcs_comms.unshifted), RefVector(pcs_evals.unshifted) }, + .shifted = ClaimBatch{ RefVector(pcs_comms.to_be_shifted), RefVector(pcs_evals.shifted) }, + .shift_exponent = BATCH_SIZE + }; + + auto shplemini_output = Shplemini::compute_batch_opening_claim(pcs_padding_indicator_array, claim_batcher, - sumcheck_output.challenge, + full_challenge, one_commitment, transcript, Flavor::REPEATED_COMMITMENTS, libra_commitments, sumcheck_output.claimed_libra_evaluation); - // Build reduction result - ReductionResult result; - result.pairing_points = PCS::reduce_verify_batch_opening_claim( + ReductionResult reduction_result; + reduction_result.pairing_points = PCS::reduce_verify_batch_opening_claim( std::move(shplemini_output.batch_opening_claim), transcript, Flavor::FINAL_PCS_MSM_SIZE(log_n)); bool consistency_checked = true; if constexpr (Flavor::HasZK) { consistency_checked = shplemini_output.consistency_checked; - vinfo("Ultra Verifier (with ZK): Libra evals consistency checked ", consistency_checked ? "true" : "false"); + vinfo("UltraVerifier: consistency_checked=", consistency_checked ? "true" : "false"); } - vinfo("Ultra Verifier sumcheck_verified: ", sumcheck_output.verified ? "true" : "false"); - result.reduction_succeeded = sumcheck_output.verified && consistency_checked; + vinfo("UltraVerifier: sumcheck_verified=", sumcheck_output.verified ? "true" : "false"); + reduction_result.reduction_succeeded = sumcheck_output.verified && consistency_checked; - return result; + return reduction_result; } /** - * @brief Perform ultra verification - * @details - * For Rollup flavors, splits the combined proof internally. - * - Native: Performs immediate pairing verification (+ IPA for Rollup) - * - Recursive: Returns pairing points (+ IPA proof for Rollup) for deferred verification + * @brief Verify an Ultra Honk proof. + * @details For Rollup flavors, splits the combined proof into honk + IPA components. + * Native: performs immediate pairing verification (+ IPA for Rollup). + * Recursive: returns pairing points (+ IPA proof for Rollup) for deferred verification. */ template typename UltraVerifier_::Output UltraVerifier_::verify_proof( @@ -299,10 +380,18 @@ template class UltraVerifier_; template class UltraVerifier_; template class UltraVerifier_; template class UltraVerifier_; +template class UltraVerifier_; +template class UltraVerifier_; template class UltraVerifier_; // Rollup uses UltraFlavor + RollupIO template class UltraVerifier_; template class UltraVerifier_; template class UltraVerifier_; // Chonk +template class UltraVerifier_; +template class UltraVerifier_; +template class UltraVerifier_; +template class UltraVerifier_; +template class UltraVerifier_; +template class UltraVerifier_; #ifdef STARKNET_GARAGA_FLAVORS template class UltraVerifier_; @@ -346,4 +435,38 @@ template class UltraVerifier_, template class UltraVerifier_, stdlib::recursion::honk::GoblinAvmIO>; +// DualUltra recursive flavors +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; + +// DualMega recursive flavors +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::HidingKernelIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; + +// MultiMega recursive flavors +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; +template class UltraVerifier_, + stdlib::recursion::honk::HidingKernelIO>; +template class UltraVerifier_, + stdlib::recursion::honk::DefaultIO>; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp index 71ae85f8eb8d..f14058cb1454 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp @@ -201,30 +201,28 @@ template class UltraVerifier_ { /** * @brief Get witness commitments from the verifier instance */ - const typename Flavor::WitnessCommitments& get_witness_commitments() const - { - return verifier_instance->witness_commitments; - } + const auto& get_received_commitments() const { return verifier_instance->received_commitments; } /** - * @brief Get calldata commitment (MegaFlavor only) + * @brief Get calldata commitment (BS=1 only — individual calldata commitment) */ const Commitment& get_calldata_commitment() const - requires IsMegaFlavor + requires(IsMegaFlavor && !IsMultiMegaFlavor) { - return verifier_instance->witness_commitments.calldata; + return verifier_instance->received_commitments.calldata; } /** - * @brief Get ECC op wire commitments as an array (MegaFlavor only) + * @brief Get ECC op wire commitments from received_commitments. + * BS=1: returns 4 individual commitments. BS>1: returns 1 interleaved commitment. */ - auto get_ecc_op_wires() const + auto get_ecc_op_wires() requires IsMegaFlavor { - return verifier_instance->witness_commitments.get_ecc_op_wires().get_copy(); + return verifier_instance->received_commitments.get_ecc_op_wires().get_copy(); } - private: + protected: std::shared_ptr vk_and_hash; std::shared_ptr verifier_instance; std::shared_ptr transcript; @@ -248,5 +246,7 @@ using MegaVerifier = UltraVerifier_; using MegaZKVerifier = UltraVerifier_; using MegaZKRecursiveVerifier = UltraVerifier_, stdlib::recursion::honk::HidingKernelIO>; +using MultiHonkVerifier = UltraVerifier_; +using MultiHonkZKVerifier = UltraVerifier_; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/verifier_instance.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/verifier_instance.hpp index ad87e12c928f..c75927ebd3ba 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/verifier_instance.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/verifier_instance.hpp @@ -5,14 +5,45 @@ // ===================== #pragma once + +#include "barretenberg/ecc/fields/field_conversion.hpp" #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/flavor/flavor_concepts.hpp" #include "barretenberg/relations/relation_parameters.hpp" namespace bb { + /** - * @brief The VerifierInstance encapsulates all the necessary information for a Honk Verifier to verify a - * proof (sumcheck + Shplemini). In the context of folding, this is provided to the Hypernova verifier as an incoming - * instance. + * @brief Wraps WitnessCommitments with an additional masking commitment for ZK BS=1 flavors. + * @details For ZK, the masking commitment is prepended to unshifted PCS commitments. + * The masking_commitment field and label are provided uniformly so that + * oink_verifier and build_pcs_commitments don't need to branch on BS. + */ +template +struct WitnessCommitmentsWithMasking : public WitnessCommitments_ { + Commitment_ masking_commitment; +}; + +// Resolve the type for commitments received during Oink verification. +// BS=1 without Gemini masking: WitnessCommitments (e.g., UltraFlavor, MegaFlavor, MegaZKFlavor) +// BS=1 with Gemini masking: WitnessCommitmentsWithMasking (e.g., UltraZKFlavor) +// BS>1: InterleavedCommitments (includes masking for ZK via masking_commitment member) +template , + bool HasMasking = flavor_has_gemini_masking()> +struct ReceivedCommitmentsOf { + using type = typename Flavor::WitnessCommitments; // BS=1, no Gemini masking +}; +template struct ReceivedCommitmentsOf { + using type = WitnessCommitmentsWithMasking; // BS=1 + masking +}; +template struct ReceivedCommitmentsOf { + using type = typename Flavor::InterleavedCommitments; // BS>1 (ZK or not) +}; + +/** + * @brief Encapsulates all information needed by a Honk verifier: VK, witness commitments, challenges. * @details Works with both native and recursive flavors. */ template class VerifierInstance_ { @@ -28,13 +59,16 @@ template class VerifierInstance_ { std::vector public_inputs; - FF alpha; // challenge whose powers batch subrelation contributions during Sumcheck + FF alpha; RelationParameters relation_parameters; std::vector gate_challenges; - WitnessCommitments witness_commitments; - - Commitment gemini_masking_commitment; // ZK only: Gemini masking polynomial commitment + // Commitments received during Oink verification. + // BS=1 non-ZK: individual witness commitments. + // BS=1 ZK: witness commitments + masking commitment. + // BS>1: interleaved witness commitments (includes masking for ZK). + // All provide get_all() and get_shiftable(). + typename ReceivedCommitmentsOf::type received_commitments; explicit VerifierInstance_(std::shared_ptr vk_and_hash) : vk_and_hash(vk_and_hash) diff --git a/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp b/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp index dbf9c75acede..b35f263c0386 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/constraining/prover.cpp @@ -250,10 +250,12 @@ void AvmProver::execute_pcs_rounds() PolynomialBatcher polynomial_batcher(circuit_dyadic_size); polynomial_batcher.set_unshifted(RefVector{ batched_unshifted }); - polynomial_batcher.set_to_be_shifted_by_one(RefVector{ batched_shifted }); + polynomial_batcher.set_to_be_shifted(RefVector{ batched_shifted }); + + const auto rho = transcript->template get_challenge("rho"); const OpeningClaim prover_opening_claim = ShpleminiProver_::prove( - circuit_dyadic_size, polynomial_batcher, sumcheck_output.challenge, commitment_key, transcript); + circuit_dyadic_size, polynomial_batcher, rho, sumcheck_output.challenge, commitment_key, transcript); PCS::compute_opening_proof(commitment_key, prover_opening_claim, transcript); }