diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/README.md b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/README.md index 19c52343c19f..5fac2861850d 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/README.md +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/README.md @@ -4,10 +4,10 @@ A curve-agnostic zero-knowledge protocol for proving inner products of small vec ## Overview -SmallSubgroupIPA enables proving statements of the form $ \langle F, G \rangle = s$ where: -- $ G$ is a witness polynomial (prover's secret) -- $ F$ is a challenge polynomial (derived from public challenges) -- $ s$ is the claimed inner product +SmallSubgroupIPA enables proving statements of the form $\langle F, G \rangle = s$ where: +- $G$ is a witness polynomial (prover's secret) +- $F$ is a challenge polynomial (derived from public challenges) +- $s$ is the claimed inner product This protocol is used in two contexts: 1. **ZK-Sumcheck**: Proving correct evaluation of Libra masking polynomials diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp index 61f05cb88c84..8a30480d4899 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/small_subgroup_ipa/small_subgroup_ipa.hpp @@ -341,9 +341,6 @@ template class SmallSubgroupIPAVerifier { */ static void handle_edge_cases(const FF& vanishing_poly_eval) { - - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1194). Handle edge cases in PCS - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1186). Insecure pattern. bool evaluation_challenge_in_small_subgroup = false; if constexpr (Curve::is_stdlib_type) { evaluation_challenge_in_small_subgroup = (vanishing_poly_eval.get_value() == FF(0).get_value()); 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 e910e78f56da..de2eabc8c557 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/test_utils/proof_structures.hpp @@ -6,6 +6,7 @@ #pragma once +#include "barretenberg/eccvm/eccvm_flavor.hpp" #include "barretenberg/flavor/mega_flavor.hpp" #include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/ultra_flavor.hpp" @@ -13,6 +14,7 @@ #include "barretenberg/flavor/ultra_keccak_zk_flavor.hpp" #include "barretenberg/flavor/ultra_zk_flavor.hpp" #include "barretenberg/transcript/transcript.hpp" +#include "barretenberg/translator_vm/translator_flavor.hpp" namespace bb { @@ -584,6 +586,375 @@ template struct MegaZKStructuredProofBase : MegaStructuredProo } }; +// ============================================================================ +// Translator proof structure (always ZK, with interleaved claims) +// ============================================================================ +template struct TranslatorStructuredProofBase : 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; + + // Batch size = total committed witness entities - gemini_masking_poly - z_perm + // Total committed = NUM_WITNESS_ENTITIES - 3 - NUM_OP_QUEUE_WIRES (from PROOF_LENGTH formula) + static constexpr size_t NUM_BATCH_WITNESS_COMMS = Flavor::NUM_WITNESS_ENTITIES - 3 - Flavor::NUM_OP_QUEUE_WIRES - 2; + + // Witness commitments + Commitment gemini_masking_poly_comm; + std::vector witness_comms; // non-opqueue wires + ordered range constraints + Commitment z_perm_comm; + + // Libra (ZK - Translator is always ZK) + Commitment libra_concatenation_commitment; + FF libra_sum; + + // Sumcheck + std::vector> sumcheck_univariates; + FF libra_claimed_evaluation; + std::array sumcheck_evaluations; + + // Post-sumcheck Libra commitments + Commitment libra_grand_sum_commitment; + Commitment libra_quotient_commitment; + + // Gemini/Shplemini + std::vector gemini_fold_comms; + std::vector gemini_fold_evals; + + // Translator-specific: Gemini evaluations for interleaved claims + FF gemini_p_pos_eval; + FF gemini_p_neg_eval; + + // Libra evaluations + FF libra_concatenation_eval; + FF libra_shifted_grand_sum_eval; + FF libra_grand_sum_eval; + FF libra_quotient_eval; + + // Final PCS + Commitment shplonk_q_comm; + Commitment kzg_w_comm; + + void deserialize(ProofData& proof_data, size_t /*num_public_inputs*/, size_t log_n) + { + size_t offset = 0; + witness_comms.clear(); + sumcheck_univariates.clear(); + gemini_fold_comms.clear(); + gemini_fold_evals.clear(); + + // Witness commitments + gemini_masking_poly_comm = this->template deserialize_from_buffer(proof_data, offset); + for (size_t i = 0; i < NUM_BATCH_WITNESS_COMMS; ++i) { + witness_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + z_perm_comm = this->template deserialize_from_buffer(proof_data, offset); + + // Libra pre-sumcheck + libra_concatenation_commitment = this->template deserialize_from_buffer(proof_data, offset); + libra_sum = this->template deserialize_from_buffer(proof_data, offset); + + // Sumcheck univariates + 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); + libra_claimed_evaluation = this->template deserialize_from_buffer(proof_data, offset); + + // Libra post-sumcheck commitments + libra_grand_sum_commitment = this->template deserialize_from_buffer(proof_data, offset); + libra_quotient_commitment = this->template deserialize_from_buffer(proof_data, offset); + + // Gemini fold commitments and evaluations + for (size_t i = 0; i < log_n - 1; ++i) { + gemini_fold_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + for (size_t i = 0; i < log_n; ++i) { + gemini_fold_evals.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + + // Translator-specific: Gemini P_pos and P_neg evaluations (for interleaved claims) + gemini_p_pos_eval = this->template deserialize_from_buffer(proof_data, offset); + gemini_p_neg_eval = this->template deserialize_from_buffer(proof_data, offset); + + // Libra evaluations + libra_concatenation_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_shifted_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_quotient_eval = this->template deserialize_from_buffer(proof_data, offset); + + // Final PCS + 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 + { + size_t old_size = proof_data.size(); + proof_data.clear(); + + // Witness commitments + Base::serialize_to_buffer(gemini_masking_poly_comm, proof_data); + for (const auto& comm : witness_comms) { + Base::serialize_to_buffer(comm, proof_data); + } + Base::serialize_to_buffer(z_perm_comm, proof_data); + + // Libra pre-sumcheck + Base::serialize_to_buffer(libra_concatenation_commitment, proof_data); + Base::serialize_to_buffer(libra_sum, proof_data); + + // Sumcheck univariates + 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); + Base::serialize_to_buffer(libra_claimed_evaluation, proof_data); + + // Libra post-sumcheck commitments + Base::serialize_to_buffer(libra_grand_sum_commitment, proof_data); + Base::serialize_to_buffer(libra_quotient_commitment, proof_data); + + // Gemini fold commitments and evaluations + for (size_t i = 0; i < log_n - 1; ++i) { + Base::serialize_to_buffer(gemini_fold_comms[i], proof_data); + } + for (size_t i = 0; i < log_n; ++i) { + Base::serialize_to_buffer(gemini_fold_evals[i], proof_data); + } + + // Translator-specific: Gemini P_pos and P_neg evaluations + Base::serialize_to_buffer(gemini_p_pos_eval, proof_data); + Base::serialize_to_buffer(gemini_p_neg_eval, proof_data); + + // Libra evaluations + Base::serialize_to_buffer(libra_concatenation_eval, proof_data); + Base::serialize_to_buffer(libra_shifted_grand_sum_eval, proof_data); + Base::serialize_to_buffer(libra_grand_sum_eval, proof_data); + Base::serialize_to_buffer(libra_quotient_eval, proof_data); + + // Final PCS + 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); + } +}; + +// ============================================================================ +// ECCVM proof structure (always ZK, committed sumcheck, translation sub-protocol) +// ============================================================================ +template struct ECCVMStructuredProofBase : StructuredProofHelper { + using Base = StructuredProofHelper; + using Base::NUM_ALL_ENTITIES; + using typename Base::Commitment; + using typename Base::FF; + using typename Base::ProofData; + + // Witness commitments (masking_poly + NUM_WIRES wires + lookup_inverses + z_perm) + Commitment gemini_masking_poly_comm; + std::vector wire_comms; + Commitment lookup_inverses_comm; + Commitment z_perm_comm; + + // Libra pre-sumcheck + Commitment libra_concatenation_commitment; + FF libra_sum; + + // Committed sumcheck rounds (each round: commitment + eval_0 + eval_1, interleaved in proof) + std::vector sumcheck_round_comms; + std::vector sumcheck_round_eval_0s; + std::vector sumcheck_round_eval_1s; + + // Sumcheck evaluations + std::array sumcheck_evaluations; + + // Libra post-sumcheck + FF libra_claimed_evaluation; + Commitment libra_grand_sum_commitment; + Commitment libra_quotient_commitment; + + // Gemini/Shplemini + std::vector gemini_fold_comms; + std::vector gemini_fold_evals; + + // Libra SmallSubgroupIPA evaluations + FF libra_concatenation_eval; + FF libra_shifted_grand_sum_eval; + FF libra_grand_sum_eval; + FF libra_quotient_eval; + + // First Shplonk Q (from Shplemini) + Commitment shplonk_q_comm; + + // Translation data + Commitment translation_masking_comm; + FF translation_op_eval; + FF translation_Px_eval; + FF translation_Py_eval; + FF translation_z1_eval; + FF translation_z2_eval; + FF translation_masking_eval; + Commitment translation_grand_sum_commitment; + Commitment translation_quotient_commitment; + FF translation_concatenation_eval; + FF translation_shifted_grand_sum_eval; + FF translation_grand_sum_eval; + FF translation_quotient_eval; + + // Final Shplonk Q + Commitment final_shplonk_q_comm; + + void deserialize(ProofData& proof_data, size_t /*num_public_inputs*/, size_t log_n) + { + size_t offset = 0; + wire_comms.clear(); + sumcheck_round_comms.clear(); + sumcheck_round_eval_0s.clear(); + sumcheck_round_eval_1s.clear(); + gemini_fold_comms.clear(); + gemini_fold_evals.clear(); + + // Witness commitments + gemini_masking_poly_comm = this->template deserialize_from_buffer(proof_data, offset); + for (size_t i = 0; i < Flavor::NUM_WIRES; ++i) { + wire_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + lookup_inverses_comm = this->template deserialize_from_buffer(proof_data, offset); + z_perm_comm = this->template deserialize_from_buffer(proof_data, offset); + + // Libra pre-sumcheck + libra_concatenation_commitment = this->template deserialize_from_buffer(proof_data, offset); + libra_sum = this->template deserialize_from_buffer(proof_data, offset); + + // Committed sumcheck rounds (interleaved: comm, eval_0, eval_1 per round) + for (size_t i = 0; i < log_n; ++i) { + sumcheck_round_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + sumcheck_round_eval_0s.push_back(this->template deserialize_from_buffer(proof_data, offset)); + sumcheck_round_eval_1s.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + + // Sumcheck evaluations + sumcheck_evaluations = + this->template deserialize_from_buffer>(proof_data, offset); + + // Libra post-sumcheck + libra_claimed_evaluation = this->template deserialize_from_buffer(proof_data, offset); + libra_grand_sum_commitment = this->template deserialize_from_buffer(proof_data, offset); + libra_quotient_commitment = this->template deserialize_from_buffer(proof_data, offset); + + // Gemini fold commitments and evaluations + for (size_t i = 0; i < log_n - 1; ++i) { + gemini_fold_comms.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + for (size_t i = 0; i < log_n; ++i) { + gemini_fold_evals.push_back(this->template deserialize_from_buffer(proof_data, offset)); + } + + // Libra SmallSubgroupIPA evaluations + libra_concatenation_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_shifted_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + libra_quotient_eval = this->template deserialize_from_buffer(proof_data, offset); + + // First Shplonk Q + shplonk_q_comm = this->template deserialize_from_buffer(proof_data, offset); + + // Translation data + translation_masking_comm = this->template deserialize_from_buffer(proof_data, offset); + translation_op_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_Px_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_Py_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_z1_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_z2_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_masking_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_grand_sum_commitment = this->template deserialize_from_buffer(proof_data, offset); + translation_quotient_commitment = this->template deserialize_from_buffer(proof_data, offset); + translation_concatenation_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_shifted_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_grand_sum_eval = this->template deserialize_from_buffer(proof_data, offset); + translation_quotient_eval = this->template deserialize_from_buffer(proof_data, offset); + + // Final Shplonk Q + final_shplonk_q_comm = this->template deserialize_from_buffer(proof_data, offset); + } + + void serialize(ProofData& proof_data, size_t log_n) const + { + size_t old_size = proof_data.size(); + proof_data.clear(); + + // Witness commitments + Base::serialize_to_buffer(gemini_masking_poly_comm, proof_data); + for (const auto& comm : wire_comms) { + Base::serialize_to_buffer(comm, proof_data); + } + Base::serialize_to_buffer(lookup_inverses_comm, proof_data); + Base::serialize_to_buffer(z_perm_comm, proof_data); + + // Libra pre-sumcheck + Base::serialize_to_buffer(libra_concatenation_commitment, proof_data); + Base::serialize_to_buffer(libra_sum, proof_data); + + // Committed sumcheck rounds + for (size_t i = 0; i < log_n; ++i) { + Base::serialize_to_buffer(sumcheck_round_comms[i], proof_data); + Base::serialize_to_buffer(sumcheck_round_eval_0s[i], proof_data); + Base::serialize_to_buffer(sumcheck_round_eval_1s[i], proof_data); + } + + // Sumcheck evaluations + Base::serialize_to_buffer(sumcheck_evaluations, proof_data); + + // Libra post-sumcheck + Base::serialize_to_buffer(libra_claimed_evaluation, proof_data); + Base::serialize_to_buffer(libra_grand_sum_commitment, proof_data); + Base::serialize_to_buffer(libra_quotient_commitment, proof_data); + + // Gemini fold commitments and evaluations + for (size_t i = 0; i < log_n - 1; ++i) { + Base::serialize_to_buffer(gemini_fold_comms[i], proof_data); + } + for (size_t i = 0; i < log_n; ++i) { + Base::serialize_to_buffer(gemini_fold_evals[i], proof_data); + } + + // Libra SmallSubgroupIPA evaluations + Base::serialize_to_buffer(libra_concatenation_eval, proof_data); + Base::serialize_to_buffer(libra_shifted_grand_sum_eval, proof_data); + Base::serialize_to_buffer(libra_grand_sum_eval, proof_data); + Base::serialize_to_buffer(libra_quotient_eval, proof_data); + + // First Shplonk Q + Base::serialize_to_buffer(shplonk_q_comm, proof_data); + + // Translation data + Base::serialize_to_buffer(translation_masking_comm, proof_data); + Base::serialize_to_buffer(translation_op_eval, proof_data); + Base::serialize_to_buffer(translation_Px_eval, proof_data); + Base::serialize_to_buffer(translation_Py_eval, proof_data); + Base::serialize_to_buffer(translation_z1_eval, proof_data); + Base::serialize_to_buffer(translation_z2_eval, proof_data); + Base::serialize_to_buffer(translation_masking_eval, proof_data); + Base::serialize_to_buffer(translation_grand_sum_commitment, proof_data); + Base::serialize_to_buffer(translation_quotient_commitment, proof_data); + Base::serialize_to_buffer(translation_concatenation_eval, proof_data); + Base::serialize_to_buffer(translation_shifted_grand_sum_eval, proof_data); + Base::serialize_to_buffer(translation_grand_sum_eval, proof_data); + Base::serialize_to_buffer(translation_quotient_eval, proof_data); + + // Final Shplonk Q + Base::serialize_to_buffer(final_shplonk_q_comm, proof_data); + + BB_ASSERT_EQ(proof_data.size(), old_size); + } +}; + // ============================================================================ // Flavor Specializations // ============================================================================ @@ -600,4 +971,10 @@ template <> struct StructuredProof : UltraZKStructuredProof template <> struct StructuredProof : MegaStructuredProofBase {}; template <> struct StructuredProof : MegaZKStructuredProofBase {}; +// Translator flavor +template <> struct StructuredProof : TranslatorStructuredProofBase {}; + +// ECCVM flavor +template <> struct StructuredProof : ECCVMStructuredProofBase {}; + } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.test.cpp index 66a117a5a9a2..904d4fa6f2a3 100644 --- a/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/goblin/goblin_verifier.test.cpp @@ -1,6 +1,5 @@ #include "barretenberg/goblin/goblin_verifier.hpp" #include "barretenberg/circuit_checker/circuit_checker.hpp" -#include "barretenberg/common/assert.hpp" #include "barretenberg/common/test.hpp" #include "barretenberg/goblin/goblin.hpp" #include "barretenberg/goblin/mock_circuits.hpp" @@ -27,41 +26,19 @@ class GoblinRecursiveVerifierTests : public testing::Test { using RecursiveMergeCommitments = bb::GoblinRecursiveVerifier::MergeVerifier::InputCommitments; using Transcript = UltraStdlibTranscript; using FF = TranslatorFlavor::FF; - using BF = TranslatorFlavor::BF; static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } - // Compute the size of a Translator commitment (in bb::fr's) - static constexpr size_t comm_frs = FrCodec::calc_num_fields(); // 4 - static constexpr size_t eval_frs = FrCodec::calc_num_fields(); // 1 - struct ProverOutput { GoblinProof proof; MergeCommitments merge_commitments; RecursiveMergeCommitments recursive_merge_commitments; }; - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1298): - // Better recursion testing - create more flexible proof tampering tests. - // Tamper with the `op` commitment in the merge commitments (op commitments are no longer in translator proof) static void tamper_with_op_commitment(MergeCommitments& merge_commitments) { // The first commitment in merged table is the `op` wire commitment merge_commitments.t_commitments[0] = merge_commitments.t_commitments[0] * FF(2); }; - // Translator proof ends with [..., Libra:quotient_eval, Shplonk:Q, KZG:W]. We invalidate the proof by multiplying - // the eval by 2 (it leads to a Libra consistency check failure). - static void tamper_with_libra_eval(HonkProof& translator_proof) - { - // Proof tail size - static constexpr size_t tail_size = 2 * comm_frs + eval_frs; // 2*4 + 1 = 9 - - // Index of the target field (one fr) from the beginning - const size_t idx = translator_proof.size() - tail_size; - - // Tamper: multiply by 2 (or tweak however you like) - translator_proof[idx] = translator_proof[idx] + translator_proof[idx]; - }; - // ECCVM pre-IPA proof ends with evaluations including `op`. We tamper with the `op` evaluation. // The structure is: [..., op_eval, x_lo_y_hi_eval, x_hi_z_1_eval, y_lo_z_2_eval, IPA_proof...] // So op_eval is 3 fields before the IPA proof starts. @@ -238,22 +215,27 @@ TEST_F(GoblinRecursiveVerifierTests, IndependentVKHash) } /** - * @brief Ensure failure of the goblin recursive verification circuit for a bad ECCVM proof - * + * @brief Tampered merge commitments cause the Translator pairing check to fail + * @details Tests the Merge-Translator cross-component connection: merge_commitments flow into the Translator verifier + * as op queue wire commitments. A mismatch causes the KZG pairing check to fail (not a circuit failure). */ -TEST_F(GoblinRecursiveVerifierTests, ECCVMFailure) +TEST_F(GoblinRecursiveVerifierTests, MergeToTranslatorBindingFailure) { - BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group etc - Builder builder; + auto [proof, merge_commitments, _] = create_goblin_prover_output(); - auto [proof, merge_commitments, recursive_merge_commitments] = create_goblin_prover_output(&builder); + // Tamper with the op commitment in merge commitments (used by Translator verifier) + MergeCommitments tampered_merge_commitments = merge_commitments; + tamper_with_op_commitment(tampered_merge_commitments); + Builder builder; - // Tamper with the ECCVM proof - for (auto& val : proof.eccvm_proof) { - if (val > 0) { // tamper by finding the first non-zero value and incrementing it by 1 - val += 1; - break; - } + RecursiveMergeCommitments recursive_merge_commitments; + for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) { + recursive_merge_commitments.t_commitments[idx] = + RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.t_commitments[idx]); + recursive_merge_commitments.T_prev_commitments[idx] = + RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.T_prev_commitments[idx]); + recursive_merge_commitments.t_commitments[idx].fix_witness(); + recursive_merge_commitments.T_prev_commitments[idx].fix_witness(); } auto transcript = std::make_shared(); @@ -262,101 +244,34 @@ TEST_F(GoblinRecursiveVerifierTests, ECCVMFailure) transcript, stdlib_proof, recursive_merge_commitments, MergeSettings::APPEND }; auto goblin_rec_verifier_output = verifier.reduce_to_pairing_check_and_ipa_opening(); - EXPECT_FALSE(CircuitChecker::check(builder)); - - srs::init_file_crs_factory(bb::srs::bb_crs_path()); - auto crs_factory = srs::get_grumpkin_crs_factory(); - VerifierCommitmentKey grumpkin_verifier_commitment_key(1 << CONST_ECCVM_LOG_N, crs_factory); - OpeningClaim native_claim = goblin_rec_verifier_output.ipa_claim.get_native_opening_claim(); - auto native_ipa_transcript = std::make_shared(goblin_rec_verifier_output.ipa_proof.get_value()); - - bool native_result = - IPA::reduce_verify(grumpkin_verifier_commitment_key, native_claim, native_ipa_transcript); - EXPECT_FALSE(native_result); -} - -/** - * @brief Ensure failure of the goblin recursive verification circuit for a bad Translator proof - * - */ -TEST_F(GoblinRecursiveVerifierTests, TranslatorFailure) -{ - auto [proof, merge_commitments, _] = create_goblin_prover_output(); - - // Tamper with the op commitment in merge commitments (used by Translator verifier) - { - MergeCommitments tampered_merge_commitments = merge_commitments; - tamper_with_op_commitment(tampered_merge_commitments); - Builder builder; - - RecursiveMergeCommitments recursive_merge_commitments; - for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) { - recursive_merge_commitments.t_commitments[idx] = - RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.t_commitments[idx]); - recursive_merge_commitments.T_prev_commitments[idx] = - RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.T_prev_commitments[idx]); - recursive_merge_commitments.t_commitments[idx].fix_witness(); - recursive_merge_commitments.T_prev_commitments[idx].fix_witness(); - } - - auto transcript = std::make_shared(); - GoblinStdlibProof stdlib_proof(builder, proof); - bb::GoblinRecursiveVerifier verifier{ - transcript, stdlib_proof, recursive_merge_commitments, MergeSettings::APPEND - }; - auto goblin_rec_verifier_output = verifier.reduce_to_pairing_check_and_ipa_opening(); - - // Aggregate merge + translator pairing points - goblin_rec_verifier_output.translator_pairing_points.aggregate(goblin_rec_verifier_output.merge_pairing_points); - - // Circuit is correct but pairing check should fail - EXPECT_TRUE(CircuitChecker::check(builder)); - - // Check that the pairing fails natively - bb::PairingPoints native_pairing_points( - goblin_rec_verifier_output.translator_pairing_points.P0().get_value(), - goblin_rec_verifier_output.translator_pairing_points.P1().get_value()); - bool pairing_result = native_pairing_points.check(); - EXPECT_FALSE(pairing_result); - } - // Tamper with the Translator proof non - preamble values - { - auto tampered_proof = proof; - tamper_with_libra_eval(tampered_proof.translator_proof); - Builder builder; + // Aggregate merge + translator pairing points + goblin_rec_verifier_output.translator_pairing_points.aggregate(goblin_rec_verifier_output.merge_pairing_points); - RecursiveMergeCommitments recursive_merge_commitments; - for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) { - recursive_merge_commitments.t_commitments[idx] = - RecursiveCommitment::from_witness(&builder, merge_commitments.t_commitments[idx]); - recursive_merge_commitments.T_prev_commitments[idx] = - RecursiveCommitment::from_witness(&builder, merge_commitments.T_prev_commitments[idx]); - recursive_merge_commitments.t_commitments[idx].fix_witness(); - recursive_merge_commitments.T_prev_commitments[idx].fix_witness(); - } + // Circuit is correct but pairing check should fail + EXPECT_TRUE(CircuitChecker::check(builder)); - auto transcript = std::make_shared(); - GoblinStdlibProof stdlib_proof(builder, tampered_proof); - bb::GoblinRecursiveVerifier verifier{ - transcript, stdlib_proof, recursive_merge_commitments, MergeSettings::APPEND - }; - [[maybe_unused]] auto goblin_rec_verifier_output = verifier.reduce_to_pairing_check_and_ipa_opening(); - EXPECT_FALSE(CircuitChecker::check(builder)); - } + // Check that the pairing fails natively + bb::PairingPoints native_pairing_points( + goblin_rec_verifier_output.translator_pairing_points.P0().get_value(), + goblin_rec_verifier_output.translator_pairing_points.P1().get_value()); + bool pairing_result = native_pairing_points.check(); + EXPECT_FALSE(pairing_result); } /** - * @brief Ensure failure of the goblin recursive verification circuit for bad translation evaluations - * + * @brief Tampered ECCVM translation evaluations cause the Translator circuit to fail + * @details Tests the ECCVM-Translator cross-component connection: translation evaluations (op, Px, Py, z1, z2) from + * the ECCVM proof become `accumulated_result` in the Translator verifier. Tampering with these causes the Translator's + * relation constraints to fail in-circuit. */ -TEST_F(GoblinRecursiveVerifierTests, TranslationEvaluationsFailure) +TEST_F(GoblinRecursiveVerifierTests, ECCVMToTranslatorBindingFailure) { Builder builder; auto [proof, merge_commitments, recursive_merge_commitments] = create_goblin_prover_output(&builder); - // Tamper with the `op` evaluation in the ECCVM proof using the helper function + // Tamper with the `op` evaluation in the ECCVM proof tamper_with_eccvm_op_eval(proof.eccvm_proof); auto transcript = std::make_shared(); @@ -368,67 +283,4 @@ TEST_F(GoblinRecursiveVerifierTests, TranslationEvaluationsFailure) EXPECT_FALSE(CircuitChecker::check(builder)); } - -/** - * @brief Ensure failure of the goblin recursive verification circuit for bad translation evaluations - * - */ -TEST_F(GoblinRecursiveVerifierTests, TranslatorMergeConsistencyFailure) -{ - - { - - Builder builder; - - auto [proof, merge_commitments, recursive_merge_commitments] = create_goblin_prover_output(&builder); - - // Check natively that the proof is correct. - auto native_transcript = std::make_shared(); - bb::GoblinVerifier native_verifier(native_transcript, proof, merge_commitments, MergeSettings::APPEND); - auto native_result = native_verifier.reduce_to_pairing_check_and_ipa_opening(); - // Aggregate merge + translator pairing points before checking - native_result.translator_pairing_points.aggregate(native_result.merge_pairing_points); - bool pairing_verified = native_result.translator_pairing_points.check(); - auto ipa_transcript = std::make_shared(native_result.ipa_proof); - auto ipa_vk = VerifierCommitmentKey{ ECCVMFlavor::ECCVM_FIXED_SIZE }; - bool ipa_verified = IPA::reduce_verify(ipa_vk, native_result.ipa_claim, ipa_transcript); - EXPECT_TRUE(pairing_verified && ipa_verified); - - // Tamper with the op commitment in merge commitments (used by Translator verifier) - MergeCommitments tampered_merge_commitments = merge_commitments; - tamper_with_op_commitment(tampered_merge_commitments); - - // Construct and check the Goblin Recursive Verifier circuit - - RecursiveMergeCommitments tampered_recursive_merge_commitments; - for (size_t idx = 0; idx < MegaFlavor::NUM_WIRES; idx++) { - tampered_recursive_merge_commitments.t_commitments[idx] = - RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.t_commitments[idx]); - tampered_recursive_merge_commitments.T_prev_commitments[idx] = - RecursiveCommitment::from_witness(&builder, tampered_merge_commitments.T_prev_commitments[idx]); - tampered_recursive_merge_commitments.t_commitments[idx].fix_witness(); - tampered_recursive_merge_commitments.T_prev_commitments[idx].fix_witness(); - } - - auto transcript = std::make_shared(); - GoblinStdlibProof stdlib_proof(builder, proof); - bb::GoblinRecursiveVerifier verifier{ - transcript, stdlib_proof, tampered_recursive_merge_commitments, MergeSettings::APPEND - }; - auto goblin_rec_verifier_output = verifier.reduce_to_pairing_check_and_ipa_opening(); - - // Aggregate merge + translator pairing points - goblin_rec_verifier_output.translator_pairing_points.aggregate(goblin_rec_verifier_output.merge_pairing_points); - - // Circuit is correct but pairing check should fail - EXPECT_TRUE(CircuitChecker::check(builder)); - - // Check that the pairing fails natively - bb::PairingPoints native_pairing_points( - goblin_rec_verifier_output.translator_pairing_points.P0().get_value(), - goblin_rec_verifier_output.translator_pairing_points.P1().get_value()); - bool pairing_result = native_pairing_points.check(); - EXPECT_FALSE(pairing_result); - } -} } // namespace bb::stdlib::recursion::honk diff --git a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.test.cpp index abfbde91accb..a0d14d1e227d 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/eccvm_verifier/eccvm_recursive_verifier.test.cpp @@ -3,8 +3,8 @@ #include "barretenberg/dsl/acir_format/gate_count_constants.hpp" #include "barretenberg/eccvm/eccvm_prover.hpp" #include "barretenberg/eccvm/eccvm_verifier.hpp" +#include "barretenberg/flavor/test_utils/proof_structures.hpp" #include "barretenberg/stdlib/honk_verifier/ultra_verification_keys_comparator.hpp" -#include "barretenberg/stdlib/test_utils/tamper_proof.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" @@ -189,21 +189,95 @@ class ECCVMRecursiveTests : public ::testing::Test { EXPECT_FALSE(CircuitChecker::check(outer_circuit)); } - static void test_recursive_verification_failure_tampered_proof() + /** + * @brief Verify that StructuredProof can round-trip serialize/deserialize a proof. + * @details Validates the field layout matches the actual ECCVM proof structure. This is the foundation + * for targeted proof tampering in TargetedProofTampering. + */ + static void test_structured_proof_round_trip() { - for (size_t idx = 0; idx < 2; idx++) { + InnerBuilder builder = generate_circuit(&engine); + std::shared_ptr prover_transcript = std::make_shared(); + InnerProver prover(builder, prover_transcript); + auto [proof, opening_claim] = prover.construct_proof(); + + ASSERT_EQ(proof.size(), InnerFlavor::PROOF_LENGTH); + + StructuredProof structured_proof; + auto proof_data = prover.transcript->test_get_proof_data(); + structured_proof.deserialize(proof_data, /*num_public_inputs=*/0, CONST_ECCVM_LOG_N); + structured_proof.serialize(proof_data, CONST_ECCVM_LOG_N); + + auto original_data = prover.transcript->test_get_proof_data(); + ASSERT_EQ(proof_data.size(), original_data.size()); + EXPECT_EQ(proof_data, original_data); + } + + enum class TamperType { + MODIFY_SUMCHECK_UNIVARIATE, // Tests committed sumcheck first-round sum constraint (circuit FAIL) + MODIFY_SUMCHECK_EVAL, // Tests Gemini consistency constraint (circuit FAIL) + MODIFY_IPA_CLAIM, // Tests IPA opening (circuit PASS, IPA FAIL) + MODIFY_TRANSLATION_EVAL, // Tests translation masking consistency constraint (circuit FAIL) + MODIFY_LIBRA_EVAL, // Tests Libra SmallSubgroupIPA consistency constraint (circuit FAIL) + END + }; + + static void tamper_eccvm_proof(InnerProver& prover, + typename InnerFlavor::Transcript::Proof& proof, + TamperType tamper_type) + { + using FF = InnerFF; + static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES; + + StructuredProof structured_proof; + structured_proof.deserialize( + prover.transcript->test_get_proof_data(), /*num_public_inputs=*/0, CONST_ECCVM_LOG_N); + + switch (tamper_type) { + case TamperType::MODIFY_SUMCHECK_UNIVARIATE: + // Committed sumcheck: break the first-round sum by modifying eval_0 without compensating eval_1. + // Preserving the sum would only break IPA opening (external), not any in-circuit constraint. + structured_proof.sumcheck_round_eval_0s[0] += FF::random_element(); + break; + case TamperType::MODIFY_SUMCHECK_EVAL: + structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element(); + break; + case TamperType::MODIFY_IPA_CLAIM: + // Modify the final Shplonk Q commitment — bypasses circuit constraints but corrupts IPA opening claim. + structured_proof.final_shplonk_q_comm = structured_proof.final_shplonk_q_comm * FF(2); + break; + case TamperType::MODIFY_TRANSLATION_EVAL: + structured_proof.translation_op_eval = FF::random_element(); + break; + case TamperType::MODIFY_LIBRA_EVAL: + structured_proof.libra_quotient_eval = FF::random_element(); + break; + case TamperType::END: + break; + } + + structured_proof.serialize(prover.transcript->test_get_proof_data(), CONST_ECCVM_LOG_N); + prover.transcript->test_set_proof_parsing_state(0, InnerFlavor::PROOF_LENGTH); + proof = prover.export_proof(); + } + + static void test_recursive_verification_fails() + { + for (size_t idx = 0; idx < static_cast(TamperType::END); idx++) { + TamperType tamper_type = static_cast(idx); + InnerBuilder builder = generate_circuit(&engine); std::shared_ptr prover_transcript = std::make_shared(); InnerProver prover(builder, prover_transcript); auto [proof, opening_claim] = prover.construct_proof(); - // Compute IPA proof - auto ipa_transcript_prover = std::make_shared(); - PCS::compute_opening_proof(prover.key->commitment_key, opening_claim, ipa_transcript_prover); - HonkProof ipa_proof_native = ipa_transcript_prover->export_proof(); + // Compute IPA proof from the genuine opening claim (needed for MODIFY_IPA_CLAIM case) + auto ipa_transcript = std::make_shared(); + PCS::compute_opening_proof(prover.key->commitment_key, opening_claim, ipa_transcript); + HonkProof ipa_proof = ipa_transcript->export_proof(); - // Tamper with the proof to be verified - tamper_with_proof(proof, static_cast(idx)); + // Tamper with the proof + tamper_eccvm_proof(prover, proof, tamper_type); OuterBuilder outer_circuit; auto stdlib_proof = stdlib::Proof(outer_circuit, proof); @@ -212,24 +286,21 @@ class ECCVMRecursiveTests : public ::testing::Test { auto recursive_result = verifier.reduce_to_ipa_opening(); stdlib::recursion::honk::DefaultIO::add_default(outer_circuit); - if (idx == 0) { - // In this case, we changed the first non-zero value in the proof. It leads to a circuit check failure. - EXPECT_FALSE(CircuitChecker::check(outer_circuit)); - } else { - // Changing the last commitment in the `proof_data` would not result in a circuit check failure at - // this stage. + if (tamper_type == TamperType::MODIFY_IPA_CLAIM) { + // Modifying the final Shplonk Q bypasses circuit constraints but causes IPA failure EXPECT_TRUE(CircuitChecker::check(outer_circuit)); - // However, IPA recursive verifier must fail, as one of the commitments is incorrect. + // Verify IPA fails with the tampered opening claim VerifierCommitmentKey native_pcs_vk(1UL << CONST_ECCVM_LOG_N); VerifierCommitmentKey> stdlib_pcs_vkey( &outer_circuit, 1UL << CONST_ECCVM_LOG_N, native_pcs_vk); - - // Construct ipa_transcript from proof - auto stdlib_ipa_proof = stdlib::Proof(outer_circuit, ipa_proof_native); - std::shared_ptr ipa_transcript = std::make_shared(stdlib_ipa_proof); + auto stdlib_ipa_proof = stdlib::Proof(outer_circuit, ipa_proof); + auto ipa_verify_transcript = std::make_shared(stdlib_ipa_proof); EXPECT_FALSE(IPA::full_verify_recursive( - stdlib_pcs_vkey, recursive_result.ipa_claim, ipa_transcript)); + stdlib_pcs_vkey, recursive_result.ipa_claim, ipa_verify_transcript)); + } else { + // All other tamper types should cause a circuit constraint violation + EXPECT_FALSE(CircuitChecker::check(outer_circuit)) << "Expected circuit failure for TamperType " << idx; } } } @@ -286,10 +357,14 @@ TEST_F(ECCVMRecursiveTests, SingleRecursiveVerificationFailure) ECCVMRecursiveTests::test_recursive_verification_failure(); }; -TEST_F(ECCVMRecursiveTests, SingleRecursiveVerificationFailureTamperedProof) +TEST_F(ECCVMRecursiveTests, StructureTest) +{ + ECCVMRecursiveTests::test_structured_proof_round_trip(); +}; + +TEST_F(ECCVMRecursiveTests, TargetedProofTampering) { - BB_DISABLE_ASSERTS(); // Avoid on_curve assertion failure in cycle_group constructor - ECCVMRecursiveTests::test_recursive_verification_failure_tampered_proof(); + ECCVMRecursiveTests::test_recursive_verification_fails(); }; TEST_F(ECCVMRecursiveTests, IndependentVKHash) diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp similarity index 87% rename from barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp rename to barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp index 6215d8b444bf..4217018f8394 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/ultra_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_verifier/honk_recursive_verifier.test.cpp @@ -3,8 +3,9 @@ #include "barretenberg/common/test.hpp" #include "barretenberg/dsl/acir_format/gate_count_constants.hpp" #include "barretenberg/flavor/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" -#include "barretenberg/stdlib/test_utils/tamper_proof.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" #include "ultra_verification_keys_comparator.hpp" @@ -296,75 +297,77 @@ template class RecursiveVerifierTest : public testing::Test { } } - /** - * @brief Construct verifier circuits for proofs whose data have been tampered with. Expect failure - * - */ - static void test_recursive_verification_fails() - requires(!IsAnyOf) + enum class TamperType { + MODIFY_SUMCHECK_UNIVARIATE, // Tests sumcheck round consistency constraint (circuit FAIL) + MODIFY_SUMCHECK_EVAL, // Tests final relation check constraint (circuit FAIL) + MODIFY_KZG_WITNESS, // Tests pairing check (circuit PASS, pairing FAIL) + MODIFY_LIBRA_EVAL, // Tests Libra consistency constraint (circuit FAIL, ZK only) + END + }; + + static void tamper_honk_proof(InnerProver& inner_prover, + typename InnerFlavor::Transcript::Proof& inner_proof, + TamperType type) { - for (size_t idx = 0; idx < static_cast(TamperType::END); idx++) { - // Create an arbitrary inner circuit - auto inner_circuit = create_inner_circuit(); + using FF = InnerFF; + static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES; + + StructuredProof structured_proof; + const auto num_public_inputs = inner_prover.num_public_inputs(); + const size_t log_n = InnerFlavor::USE_PADDING ? InnerFlavor::VIRTUAL_LOG_N : inner_prover.log_dyadic_size(); + structured_proof.deserialize(inner_prover.get_transcript()->test_get_proof_data(), num_public_inputs, log_n); + + switch (type) { + case TamperType::MODIFY_SUMCHECK_UNIVARIATE: { + FF delta = FF::random_element(); + structured_proof.sumcheck_univariates[0].value_at(0) += delta; + structured_proof.sumcheck_univariates[0].value_at(1) -= delta; + break; + } + case TamperType::MODIFY_SUMCHECK_EVAL: + structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element(); + break; + case TamperType::MODIFY_KZG_WITNESS: + structured_proof.kzg_w_comm = structured_proof.kzg_w_comm * FF::random_element(); + break; + case TamperType::MODIFY_LIBRA_EVAL: + if constexpr (InnerFlavor::HasZK) { + structured_proof.libra_quotient_eval = FF::random_element(); + } + break; + case TamperType::END: + break; + } - // Generate a proof over the inner circuit - auto prover_instance = std::make_shared(inner_circuit); - // Generate the corresponding inner verification key - auto inner_verification_key = - std::make_shared(prover_instance->get_precomputed()); - InnerProver inner_prover(prover_instance, inner_verification_key); - auto inner_proof = inner_prover.construct_proof(); + structured_proof.serialize(inner_prover.get_transcript()->test_get_proof_data(), log_n); + inner_prover.get_transcript()->test_set_proof_parsing_state( + 0, ProofLength::Honk::LENGTH_WITHOUT_PUB_INPUTS(log_n) + num_public_inputs); + inner_proof = inner_prover.export_proof(); + } - // Tamper with the proof to be verified + static void test_recursive_verification_fails() + { + for (size_t idx = 0; idx < static_cast(TamperType::END); idx++) { TamperType tamper_type = static_cast(idx); - tamper_with_proof(inner_prover, inner_proof, tamper_type); - // Create a recursive verification circuit for the proof of the inner circuit - OuterBuilder outer_circuit; - auto stdlib_vk_and_hash = - std::make_shared(outer_circuit, inner_verification_key); - RecursiveVerifier verifier{ stdlib_vk_and_hash }; - OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof); - VerifierOutput output = verifier.verify_proof(stdlib_inner_proof); - - // Wrong Gemini witnesses lead to the pairing check failure in non-ZK case but don't break any - // constraints. In ZK-cases, tampering with Gemini witnesses leads to SmallSubgroupIPA consistency check - // failure. - if ((tamper_type != TamperType::MODIFY_GEMINI_WITNESS) || (InnerFlavor::HasZK)) { - // We expect the circuit check to fail due to the bad proof. - EXPECT_FALSE(CircuitChecker::check(outer_circuit)); - } else { - EXPECT_TRUE(CircuitChecker::check(outer_circuit)); - EXPECT_FALSE(output.points_accumulator.check()); + if (tamper_type == TamperType::MODIFY_LIBRA_EVAL && !InnerFlavor::HasZK) { + continue; } - } - } - /** - * @brief Tamper with a MegaZK proof in two ways. First, we modify the first non-zero value in the proof, which has - * to lead to a CircuitChecker failure. Then we also modify the last commitment ("KZG:W") in the proof, in this - * case, CircuitChecker succeeds, but the pairing check must fail. - * - */ - static void test_recursive_verification_fails() - requires(IsAnyOf) - { - for (size_t idx = 0; idx < 2; idx++) { // Create an arbitrary inner circuit auto inner_circuit = create_inner_circuit(); // Generate a proof over the inner circuit auto prover_instance = std::make_shared(inner_circuit); - // Generate the corresponding inner verification key auto inner_verification_key = std::make_shared(prover_instance->get_precomputed()); InnerProver inner_prover(prover_instance, inner_verification_key); auto inner_proof = inner_prover.construct_proof(); // Tamper with the proof to be verified - tamper_with_proof(inner_proof, /*end_of_proof*/ static_cast(idx)); + tamper_honk_proof(inner_prover, inner_proof, tamper_type); - // Create a recursive verification circuit for the proof of the inner circuit + // Create a recursive verification circuit for the tampered proof OuterBuilder outer_circuit; auto stdlib_vk_and_hash = std::make_shared(outer_circuit, inner_verification_key); @@ -372,15 +375,13 @@ template class RecursiveVerifierTest : public testing::Test { OuterStdlibProof stdlib_inner_proof(outer_circuit, inner_proof); VerifierOutput output = verifier.verify_proof(stdlib_inner_proof); - if (idx == 0) { - // We expect the circuit check to fail due to the bad proof. - EXPECT_FALSE(CircuitChecker::check(outer_circuit)); - } else { - // Wrong witnesses lead to the pairing check failure in non-ZK case but don't break any - // constraints. In ZK-cases, tampering with Gemini witnesses leads to SmallSubgroupIPA consistency check - // failure. + if (tamper_type == TamperType::MODIFY_KZG_WITNESS) { + // Expected to result in pairing failure but no circuit constraint violations EXPECT_TRUE(CircuitChecker::check(outer_circuit)); EXPECT_FALSE(output.points_accumulator.check()); + } else { + // All other tamper types should cause a circuit constraint violation + EXPECT_FALSE(CircuitChecker::check(outer_circuit)); } } } diff --git a/barretenberg/cpp/src/barretenberg/stdlib/test_utils/tamper_proof.hpp b/barretenberg/cpp/src/barretenberg/stdlib/test_utils/tamper_proof.hpp deleted file mode 100644 index 7339d2ed68ad..000000000000 --- a/barretenberg/cpp/src/barretenberg/stdlib/test_utils/tamper_proof.hpp +++ /dev/null @@ -1,114 +0,0 @@ -#pragma once - -#include "barretenberg/commitment_schemes/ipa/ipa.hpp" -#include "barretenberg/commitment_schemes/pairing_points.hpp" -#include "barretenberg/flavor/flavor_concepts.hpp" -#include "barretenberg/flavor/test_utils/proof_structures.hpp" -#include "barretenberg/honk/proof_length.hpp" - -namespace bb { - -enum class TamperType { - MODIFY_SUMCHECK_UNIVARIATE, // Tamper with coefficients of a Sumcheck Round Univariate - MODIFY_SUMCHECK_EVAL, // Tamper with a multilinear evaluation of an entity - MODIFY_Z_PERM_COMMITMENT, // Tamper with the commitment to z_perm - MODIFY_GEMINI_WITNESS, // Tamper with a fold polynomial - END -}; - -/** - * @brief Compute the proof length for re-exporting after tampering - * @details ProofLength::Honk excludes IPA (handled separately by prover/verifier for rollup flavors) - * @param num_public_inputs Number of public inputs in the proof - * @param log_n Log of circuit size (use VIRTUAL_LOG_N for padded flavors, actual log_dyadic_size for non-padded) - */ -template size_t compute_proof_length_for_export(size_t num_public_inputs, size_t log_n) -{ - return ProofLength::Honk::LENGTH_WITHOUT_PUB_INPUTS(log_n) + num_public_inputs; -} - -/** - * @brief Test method that provides several ways to tamper with a proof. - * TODO(https://github.com/AztecProtocol/barretenberg/issues/1298): Currently, several tests are failing due to - * challenges not being re-computed after tampering. We need to extend this tool to allow for more elaborate tampering. - */ -template -void tamper_with_proof(InnerProver& inner_prover, ProofType& inner_proof, TamperType type) -{ - using FF = typename InnerFlavor::FF; - static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES; - - // Deserialize proof into structured form - StructuredProof structured_proof; - const auto num_public_inputs = inner_prover.num_public_inputs(); - const size_t log_n = InnerFlavor::USE_PADDING ? CONST_PROOF_SIZE_LOG_N : inner_prover.log_dyadic_size(); - structured_proof.deserialize(inner_prover.get_transcript()->test_get_proof_data(), num_public_inputs, log_n); - - // Apply tampering based on type - switch (type) { - case TamperType::MODIFY_SUMCHECK_UNIVARIATE: { - FF delta = FF::random_element(); - // Preserve S_0(0) + S_0(1) = target_total_sum, but S_0(u_0) = S_1(0) + S_1(1) will fail - structured_proof.sumcheck_univariates[0].value_at(0) += delta; - structured_proof.sumcheck_univariates[0].value_at(1) -= delta; - break; - } - case TamperType::MODIFY_SUMCHECK_EVAL: - structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element(); - break; - case TamperType::MODIFY_Z_PERM_COMMITMENT: - structured_proof.z_perm_comm = structured_proof.z_perm_comm * FF::random_element(); - break; - case TamperType::MODIFY_GEMINI_WITNESS: - structured_proof.gemini_fold_comms[0] = structured_proof.gemini_fold_comms[0] * FF::random_element(); - structured_proof.gemini_fold_evals[0] = FF::zero(); - break; - case TamperType::END: - break; - } - - // Serialize back and re-export the tampered proof - structured_proof.serialize(inner_prover.get_transcript()->test_get_proof_data(), log_n); - inner_prover.get_transcript()->test_set_proof_parsing_state( - 0, compute_proof_length_for_export(num_public_inputs, log_n)); - inner_proof = inner_prover.export_proof(); -} - -/** - * @brief Tamper with a proof by modifying curve points directly in the proof vector. - * @param inner_proof The proof vector to tamper with - * @param end_of_proof If true, tamper with the last commitment; if false, tamper with the first pairing point - */ -template -void tamper_with_proof(ProofType& inner_proof, bool end_of_proof) -{ - using Commitment = typename InnerFlavor::Curve::AffineElement; - using FF = typename InnerFlavor::FF; - using Codec = typename InnerFlavor::Transcript::Codec; - - static constexpr size_t NUM_FRS_PER_COMMITMENT = Codec::template calc_num_fields(); - - if (end_of_proof) { - // Tamper with the last commitment in the proof - size_t offset = inner_proof.size() - NUM_FRS_PER_COMMITMENT; - auto element_span = std::span{ inner_proof }.subspan(offset, NUM_FRS_PER_COMMITMENT); - auto commitment = Codec::template deserialize_from_fields(element_span); - commitment = commitment * FF(2); - auto serialized = Codec::serialize_to_fields(commitment); - std::copy(serialized.begin(), serialized.end(), inner_proof.begin() + static_cast(offset)); - } else { - // Tamper with the first pairing point (P0) by adding the generator - using PP = bb::PairingPoints; - static constexpr size_t NUM_FRS = Codec::template calc_num_fields(); - - if (inner_proof.size() >= NUM_FRS) { - auto pp_span = std::span{ inner_proof }.subspan(0, NUM_FRS); - PP pairing_points = Codec::template deserialize_from_fields(pp_span); - pairing_points.P0() = pairing_points.P0() + Commitment::one(); - auto serialized = Codec::serialize_to_fields(pairing_points); - std::copy(serialized.begin(), serialized.end(), inner_proof.begin()); - } - } -} - -} // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.test.cpp index 85b1c494bcf1..c4c0dbe0cee3 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/translator_vm_verifier/translator_recursive_verifier.test.cpp @@ -1,5 +1,7 @@ +#include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/circuit_checker/translator_circuit_checker.hpp" #include "barretenberg/common/log.hpp" +#include "barretenberg/flavor/test_utils/proof_structures.hpp" #include "barretenberg/stdlib/honk_verifier/ultra_verification_keys_comparator.hpp" #include "barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp" #include "barretenberg/transcript/origin_tag.hpp" @@ -236,6 +238,127 @@ class TranslatorRecursiveTests : public ::testing::Test { ASSERT_TRUE(verified); } + /** + * @brief Verify that StructuredProof can round-trip serialize/deserialize a proof. + * @details Validates the field layout matches the actual Translator proof structure. This is the foundation + * for targeted proof tampering in SingleRecursiveVerificationFailure. + */ + static void test_structured_proof_round_trip() + { + InnerBF batching_challenge_v = InnerBF::random_element(); + InnerBF evaluation_challenge_x = InnerBF::random_element(); + + InnerBuilder circuit_builder = generate_test_circuit(batching_challenge_v, evaluation_challenge_x); + auto prover_transcript = std::make_shared(); + auto proving_key = std::make_shared(circuit_builder); + InnerProver prover{ proving_key, prover_transcript }; + auto proof = prover.construct_proof(); + + ASSERT_EQ(proof.size(), InnerFlavor::PROOF_LENGTH); + + StructuredProof structured_proof; + auto proof_data = prover.transcript->test_get_proof_data(); + structured_proof.deserialize(proof_data, /*num_public_inputs=*/0, InnerFlavor::CONST_TRANSLATOR_LOG_N); + structured_proof.serialize(proof_data, InnerFlavor::CONST_TRANSLATOR_LOG_N); + + auto original_data = prover.transcript->test_get_proof_data(); + ASSERT_EQ(proof_data.size(), original_data.size()); + EXPECT_EQ(proof_data, original_data); + } + + enum class TamperType { + MODIFY_SUMCHECK_UNIVARIATE, // Tests sumcheck round consistency constraint (circuit FAIL) + MODIFY_SUMCHECK_EVAL, // Tests final relation check constraint (circuit FAIL) + MODIFY_KZG_WITNESS, // Tests pairing check (circuit PASS, pairing FAIL) + MODIFY_LIBRA_EVAL, // Tests Libra consistency constraint (circuit FAIL) + END + }; + + static void tamper_translator_proof(InnerProver& prover, + typename InnerFlavor::Transcript::Proof& proof, + TamperType tamper_type) + { + using FF = InnerFF; + static constexpr size_t FIRST_WITNESS_INDEX = InnerFlavor::NUM_PRECOMPUTED_ENTITIES; + static constexpr size_t LOG_N = InnerFlavor::CONST_TRANSLATOR_LOG_N; + + StructuredProof structured_proof; + structured_proof.deserialize(prover.transcript->test_get_proof_data(), /*num_public_inputs=*/0, LOG_N); + + switch (tamper_type) { + case TamperType::MODIFY_SUMCHECK_UNIVARIATE: { + FF delta = FF::random_element(); + structured_proof.sumcheck_univariates[0].value_at(0) += delta; + structured_proof.sumcheck_univariates[0].value_at(1) -= delta; + break; + } + case TamperType::MODIFY_SUMCHECK_EVAL: + structured_proof.sumcheck_evaluations[FIRST_WITNESS_INDEX] = FF::random_element(); + break; + case TamperType::MODIFY_KZG_WITNESS: + structured_proof.kzg_w_comm = structured_proof.kzg_w_comm * FF::random_element(); + break; + case TamperType::MODIFY_LIBRA_EVAL: + structured_proof.libra_quotient_eval = FF::random_element(); + break; + case TamperType::END: + break; + } + + structured_proof.serialize(prover.transcript->test_get_proof_data(), LOG_N); + prover.transcript->test_set_proof_parsing_state(0, InnerFlavor::PROOF_LENGTH); + proof = prover.export_proof(); + } + + static void test_recursive_verification_fails() + { + for (size_t idx = 0; idx < static_cast(TamperType::END); idx++) { + TamperType tamper_type = static_cast(idx); + + // Generate challenges + InnerBF batching_challenge_v = InnerBF::random_element(); + InnerBF evaluation_challenge_x = InnerBF::random_element(); + + // Create inner translator circuit and generate proof + InnerBuilder circuit_builder = generate_test_circuit(batching_challenge_v, evaluation_challenge_x); + auto proving_key = std::make_shared(circuit_builder); + auto prover_transcript = std::make_shared(); + InnerProver prover{ proving_key, prover_transcript }; + auto proof = prover.construct_proof(); + + // Tamper with the proof + tamper_translator_proof(prover, proof, tamper_type); + + // Set up outer recursive circuit + OuterBuilder outer_circuit; + + // Create recursive verifier inputs (unaffected by proof tampering) + auto recursive_inputs = + create_recursive_verifier_inputs(&outer_circuit, prover, evaluation_challenge_x, batching_challenge_v); + + // Create recursive verifier and verify tampered proof + stdlib::Proof stdlib_proof(outer_circuit, proof); + auto transcript = std::make_shared(stdlib_proof); + stdlib::Proof stdlib_proof_for_verifier(outer_circuit, proof); + RecursiveVerifier verifier{ transcript, + stdlib_proof_for_verifier, + recursive_inputs.evaluation_challenge_x, + recursive_inputs.batching_challenge_v, + recursive_inputs.accumulated_result, + recursive_inputs.op_queue_commitments }; + auto recursive_result = verifier.reduce_to_pairing_check(); + + if (tamper_type == TamperType::MODIFY_KZG_WITNESS) { + // KZG witness tampering bypasses circuit constraints but causes pairing failure + EXPECT_TRUE(CircuitChecker::check(outer_circuit)); + EXPECT_FALSE(recursive_result.pairing_points.check()); + } else { + // All other tamper types should cause a circuit constraint violation + EXPECT_FALSE(CircuitChecker::check(outer_circuit)); + } + } + } + static void test_independent_vk_hash() { auto [outer_circuit_256, verification_key_256] = create_recursive_verifier_circuit(256); @@ -251,6 +374,16 @@ TEST_F(TranslatorRecursiveTests, SingleRecursiveVerification) TranslatorRecursiveTests::test_recursive_verification(); }; +TEST_F(TranslatorRecursiveTests, StructureTest) +{ + TranslatorRecursiveTests::test_structured_proof_round_trip(); +}; + +TEST_F(TranslatorRecursiveTests, SingleRecursiveVerificationFailure) +{ + TranslatorRecursiveTests::test_recursive_verification_fails(); +}; + TEST_F(TranslatorRecursiveTests, IndependentVKHash) { TranslatorRecursiveTests::test_independent_vk_hash(); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp similarity index 81% rename from barretenberg/cpp/src/barretenberg/ultra_honk/ultra_transcript.test.cpp rename to barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp index 1bad1a2f583f..f3945864a5d7 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_transcript.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/honk_transcript.test.cpp @@ -1,18 +1,19 @@ #include "barretenberg/commitment_schemes/ipa/ipa.hpp" #include "barretenberg/ecc/curves/bn254/g1.hpp" #include "barretenberg/flavor/flavor.hpp" +#include "barretenberg/flavor/test_utils/proof_structures.hpp" #include "barretenberg/flavor/ultra_flavor.hpp" +#include "barretenberg/honk/proof_length.hpp" #include "barretenberg/numeric/bitop/get_msb.hpp" #include "barretenberg/polynomials/univariate.hpp" #include "barretenberg/stdlib/primitives/pairing_points.hpp" #include "barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp" -#include "barretenberg/stdlib/test_utils/tamper_proof.hpp" #include "barretenberg/transcript/transcript.hpp" #include "barretenberg/ultra_honk/prover_instance.hpp" #include "barretenberg/ultra_honk/ultra_prover.hpp" #include "barretenberg/ultra_honk/ultra_verifier.hpp" -#include "gtest/gtest.h" +#include using namespace bb; @@ -22,11 +23,15 @@ using FlavorTypes = ::testing::Types; + UltraKeccakZKFlavor, + MegaFlavor, + MegaZKFlavor>; #else -using FlavorTypes = ::testing::Types; +using FlavorTypes = + ::testing::Types; #endif -template class UltraTranscriptTests : public ::testing::Test { + +template class HonkTranscriptTests : public ::testing::Test { public: static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } @@ -36,26 +41,26 @@ template class UltraTranscriptTests : public ::testing::Test { using ProverInstance = ProverInstance_; using Builder = Flavor::CircuitBuilder; using Prover = UltraProver_; - using IO = DefaultIO; // Native IO for native flavors + using IO = DefaultIO; using Verifier = UltraVerifier_; using Proof = typename Flavor::Transcript::Proof; /** - * @brief Construct a manifest for a Ultra Honk proof + * @brief Construct a manifest for a Honk proof (Ultra or Mega) * - * @details This is where we define the "Manifest" for a Ultra Honk proof. The tests in this suite are - * intented to warn the developer if the Prover/Verifier has deviated from this manifest, however, the - * Transcript class is not otherwise contrained to follow the manifest. + * @details This is where we define the "Manifest" for a Honk proof. The tests in this suite are + * intended to warn the developer if the Prover/Verifier has deviated from this manifest, however, the + * Transcript class is not otherwise constrained to follow the manifest. * * @note Entries in the manifest consist of a name string and a size (bytes), NOT actual data. * * @return TranscriptManifest */ - TranscriptManifest construct_ultra_honk_manifest(const size_t& log_n) + TranscriptManifest construct_honk_manifest(const size_t& log_n) { TranscriptManifest manifest_expected; - const size_t virtual_log_n = Flavor::USE_PADDING ? CONST_PROOF_SIZE_LOG_N : log_n; + const size_t virtual_log_n = Flavor::USE_PADDING ? Flavor::VIRTUAL_LOG_N : log_n; size_t MAX_PARTIAL_RELATION_LENGTH = Flavor::BATCHED_RELATION_PARTIAL_LENGTH; // Size of types is number of bb::frs needed to represent the types @@ -93,6 +98,24 @@ template class UltraTranscriptTests : public ::testing::Test { manifest_expected.add_entry(round, "W_L", data_types_per_G); manifest_expected.add_entry(round, "W_R", data_types_per_G); manifest_expected.add_entry(round, "W_O", data_types_per_G); + + // Mega-specific witness commitments: ECC op wires and databus polynomials + if constexpr (IsMegaFlavor) { + manifest_expected.add_entry(round, "ECC_OP_WIRE_1", data_types_per_G); + manifest_expected.add_entry(round, "ECC_OP_WIRE_2", data_types_per_G); + manifest_expected.add_entry(round, "ECC_OP_WIRE_3", data_types_per_G); + manifest_expected.add_entry(round, "ECC_OP_WIRE_4", data_types_per_G); + manifest_expected.add_entry(round, "CALLDATA", data_types_per_G); + manifest_expected.add_entry(round, "CALLDATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "CALLDATA_READ_TAGS", data_types_per_G); + manifest_expected.add_entry(round, "SECONDARY_CALLDATA", data_types_per_G); + manifest_expected.add_entry(round, "SECONDARY_CALLDATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "SECONDARY_CALLDATA_READ_TAGS", data_types_per_G); + manifest_expected.add_entry(round, "RETURN_DATA", data_types_per_G); + manifest_expected.add_entry(round, "RETURN_DATA_READ_COUNTS", data_types_per_G); + manifest_expected.add_entry(round, "RETURN_DATA_READ_TAGS", data_types_per_G); + } + manifest_expected.add_challenge(round, "eta"); round++; @@ -103,6 +126,12 @@ template class UltraTranscriptTests : public ::testing::Test { round++; manifest_expected.add_entry(round, "LOOKUP_INVERSES", data_types_per_G); + // Mega-specific databus inverse commitments + if constexpr (IsMegaFlavor) { + manifest_expected.add_entry(round, "CALLDATA_INVERSES", data_types_per_G); + manifest_expected.add_entry(round, "SECONDARY_CALLDATA_INVERSES", data_types_per_G); + manifest_expected.add_entry(round, "RETURN_DATA_INVERSES", data_types_per_G); + } manifest_expected.add_entry(round, "Z_PERM", data_types_per_G); manifest_expected.add_challenge(round, "alpha"); @@ -173,39 +202,23 @@ template class UltraTranscriptTests : public ::testing::Test { IO::add_default(builder); } - void generate_random_test_circuit(Builder& builder) - { - auto a = FF::random_element(); - auto b = FF::random_element(); - builder.add_variable(a); - builder.add_public_variable(a); - builder.add_public_variable(b); - - if constexpr (IO::HasIPA) { - auto [stdlib_opening_claim, ipa_proof] = - IPA>::create_random_valid_ipa_claim_and_proof(builder); - stdlib_opening_claim.set_public(); - builder.ipa_proof = ipa_proof; - } - } - Proof export_serialized_proof(Prover& prover, const size_t num_public_inputs, const size_t log_n) { // reset internal variables needed for exporting the proof - // Note: compute_proof_length_for_export excludes IPA proof length since export_proof appends it separately - size_t proof_length = compute_proof_length_for_export(num_public_inputs, log_n); + // Note: this excludes IPA proof length since export_proof appends it separately + size_t proof_length = ProofLength::Honk::LENGTH_WITHOUT_PUB_INPUTS(log_n) + num_public_inputs; prover.get_transcript()->test_set_proof_parsing_state(0, proof_length); return prover.export_proof(); } }; -TYPED_TEST_SUITE(UltraTranscriptTests, FlavorTypes); +TYPED_TEST_SUITE(HonkTranscriptTests, FlavorTypes); /** * @brief Ensure consistency between the manifest hard coded in this testing suite and the one generated by the * standard honk prover over the course of proof construction. */ -TYPED_TEST(UltraTranscriptTests, ProverManifestConsistency) +TYPED_TEST(HonkTranscriptTests, ProverManifestConsistency) { // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) auto builder = typename TestFixture::Builder(); @@ -219,7 +232,7 @@ TYPED_TEST(UltraTranscriptTests, 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_ultra_honk_manifest(prover.log_dyadic_size()); + auto 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(); @@ -238,11 +251,10 @@ TYPED_TEST(UltraTranscriptTests, ProverManifestConsistency) } /** - * @brief Ensure consistency between the manifest generated by the ultra honk prover over the course of proof + * @brief Ensure consistency between the manifest generated by the honk prover over the course of proof * construction and the one generated by the verifier over the course of proof verification. - * */ -TYPED_TEST(UltraTranscriptTests, VerifierManifestConsistency) +TYPED_TEST(HonkTranscriptTests, VerifierManifestConsistency) { // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) auto builder = typename TestFixture::Builder(); @@ -277,9 +289,8 @@ TYPED_TEST(UltraTranscriptTests, VerifierManifestConsistency) /** * @brief Check that multiple challenges can be generated and sanity check * @details We generate 6 challenges that are each 128 bits, and check that they are not 0. - * */ -TYPED_TEST(UltraTranscriptTests, ChallengeGenerationTest) +TYPED_TEST(HonkTranscriptTests, ChallengeGenerationTest) { using Flavor = TypeParam; using FF = Flavor::FF; @@ -302,7 +313,7 @@ TYPED_TEST(UltraTranscriptTests, ChallengeGenerationTest) ASSERT_NE(challenges[2], 0) << "Challenge c is 0"; } -TYPED_TEST(UltraTranscriptTests, StructureTest) +TYPED_TEST(HonkTranscriptTests, StructureTest) { using Flavor = TypeParam; using FF = Flavor::FF; @@ -320,7 +331,7 @@ TYPED_TEST(UltraTranscriptTests, StructureTest) typename TestFixture::Verifier verifier(vk_and_hash); EXPECT_TRUE(verifier.verify_proof(proof).result); - const size_t virtual_log_n = Flavor::USE_PADDING ? CONST_PROOF_SIZE_LOG_N : prover_instance->log_dyadic_size(); + 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; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp deleted file mode 100644 index 9164fcf625ee..000000000000 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_transcript.test.cpp +++ /dev/null @@ -1,355 +0,0 @@ -#include "barretenberg/ecc/curves/bn254/g1.hpp" -#include "barretenberg/flavor/flavor.hpp" -#include "barretenberg/numeric/bitop/get_msb.hpp" -#include "barretenberg/polynomials/univariate.hpp" -#include "barretenberg/stdlib/special_public_inputs/special_public_inputs.hpp" -#include "barretenberg/stdlib/test_utils/tamper_proof.hpp" -#include "barretenberg/transcript/transcript.hpp" -#include "barretenberg/ultra_honk/prover_instance.hpp" -#include "barretenberg/ultra_honk/ultra_prover.hpp" -#include "barretenberg/ultra_honk/ultra_verifier.hpp" - -#include - -using namespace bb; - -using FlavorTypes = ::testing::Types; - -template class MegaTranscriptTests : public ::testing::Test { - public: - static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } - - using ProverInstance = ProverInstance_; - using Prover = UltraProver_; - using Proof = typename Flavor::Transcript::Proof; - using FF = Flavor::FF; - - static Proof export_serialized_proof(Prover& prover, const size_t num_public_inputs, const size_t log_n) - { - // reset internal variables needed for exporting the proof - // Note: compute_proof_length_for_export excludes IPA proof length since export_proof appends it separately - size_t proof_length = compute_proof_length_for_export(num_public_inputs, log_n); - prover.get_transcript()->test_set_proof_parsing_state(0, proof_length); - return prover.export_proof(); - } - /** - * @brief Construct a manifest for a Mega Honk proof - * - * @details This is where we define the "Manifest" for a Mega Honk proof. The tests in this suite are - * intented to warn the developer if the Prover/Verifier has deviated from this manifest, however, the - * Transcript class is not otherwise contrained to follow the manifest. - * - * @note Entries in the manifest consist of a name string and a size (bytes), NOT actual data. - * - * @return TranscriptManifest - */ - static TranscriptManifest construct_mega_honk_manifest() - { - using Commitment = typename Flavor::Commitment; - TranscriptManifest manifest_expected; - - const size_t virtual_log_n = Flavor::VIRTUAL_LOG_N; - - size_t NUM_PUBLIC_INPUTS = - stdlib::recursion::honk::DefaultIO::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); - } - if constexpr (Flavor::HasZK) { - manifest_expected.add_entry(round, "Gemini:masking_poly_comm", frs_per_G); - } - manifest_expected.add_entry(round, "W_L", frs_per_G); - manifest_expected.add_entry(round, "W_R", frs_per_G); - manifest_expected.add_entry(round, "W_O", frs_per_G); - manifest_expected.add_entry(round, "ECC_OP_WIRE_1", frs_per_G); - manifest_expected.add_entry(round, "ECC_OP_WIRE_2", frs_per_G); - manifest_expected.add_entry(round, "ECC_OP_WIRE_3", frs_per_G); - manifest_expected.add_entry(round, "ECC_OP_WIRE_4", frs_per_G); - manifest_expected.add_entry(round, "CALLDATA", frs_per_G); - manifest_expected.add_entry(round, "CALLDATA_READ_COUNTS", frs_per_G); - manifest_expected.add_entry(round, "CALLDATA_READ_TAGS", frs_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA", frs_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA_READ_COUNTS", frs_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA_READ_TAGS", frs_per_G); - manifest_expected.add_entry(round, "RETURN_DATA", frs_per_G); - manifest_expected.add_entry(round, "RETURN_DATA_READ_COUNTS", frs_per_G); - manifest_expected.add_entry(round, "RETURN_DATA_READ_TAGS", frs_per_G); - manifest_expected.add_challenge(round, "eta"); - - round++; - manifest_expected.add_entry(round, "LOOKUP_READ_COUNTS", frs_per_G); - manifest_expected.add_entry(round, "LOOKUP_READ_TAGS", frs_per_G); - manifest_expected.add_entry(round, "W_4", frs_per_G); - manifest_expected.add_challenge(round, std::array{ "beta", "gamma" }); - - round++; - manifest_expected.add_entry(round, "LOOKUP_INVERSES", frs_per_G); - manifest_expected.add_entry(round, "CALLDATA_INVERSES", frs_per_G); - manifest_expected.add_entry(round, "SECONDARY_CALLDATA_INVERSES", frs_per_G); - manifest_expected.add_entry(round, "RETURN_DATA_INVERSES", frs_per_G); - manifest_expected.add_entry(round, "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) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Sumcheck:univariate_" + idx, frs_per_uni); - std::string label = "Sumcheck:u_" + idx; - manifest_expected.add_challenge(round, label); - 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); - manifest_expected.add_entry(round, "Libra:grand_sum_commitment", frs_per_G); - manifest_expected.add_entry(round, "Libra:quotient_commitment", frs_per_G); - } - - manifest_expected.add_challenge(round, "rho"); - - round++; - for (size_t i = 1; i < virtual_log_n; ++i) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Gemini:FOLD_" + idx, frs_per_G); - } - manifest_expected.add_challenge(round, "Gemini:r"); - round++; - for (size_t i = 1; i <= virtual_log_n; ++i) { - std::string idx = std::to_string(i); - manifest_expected.add_entry(round, "Gemini:a_" + idx, 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); - manifest_expected.add_challenge(round, "KZG:masking_challenge"); - - return manifest_expected; - } - - void generate_test_circuit(auto& builder) - { - // Add some ecc op gates - for (size_t i = 0; i < 3; ++i) { - auto point = Flavor::Curve::AffineElement::one() * FF::random_element(); - auto scalar = FF::random_element(); - builder.queue_ecc_mul_accum(point, scalar); - } - builder.queue_ecc_eq(); - - // Add one conventional gates that utilize public inputs - FF a = FF::random_element(); - FF b = FF::random_element(); - FF c = FF::random_element(); - FF d = a + b + c; - uint32_t a_idx = builder.add_public_variable(a); - uint32_t b_idx = builder.add_variable(b); - uint32_t c_idx = builder.add_variable(c); - uint32_t d_idx = builder.add_variable(d); - - builder.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, FF(1), FF(1), FF(1), FF(-1), FF(0) }); - stdlib::recursion::honk::DefaultIO::add_default(builder); - } -}; -TYPED_TEST_SUITE(MegaTranscriptTests, FlavorTypes); -/** - * @brief Ensure consistency between the manifest hard coded in this testing suite and the one generated by the - * standard honk prover over the course of proof construction. - */ -TYPED_TEST(MegaTranscriptTests, ProverManifestConsistency) -{ - using Flavor = TypeParam; - using ProverInstance = ProverInstance_; - - using Prover = UltraProver_; - // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) - auto builder = typename Flavor::CircuitBuilder(); - 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()); - Prover prover(prover_instance, verification_key); - prover.get_transcript()->enable_manifest(); - 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_mega_honk_manifest(); - auto prover_manifest = prover.get_transcript()->get_manifest(); - // Note: a manifest can be printed using 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]) { - info("Prover manifest discrepency in round ", round); - info("Prover manifest:"); - prover_manifest[round].print(); - info("Expected manifest:"); - manifest_expected[round].print(); - FAIL(); - } - } -} - -/** - * @brief Ensure consistency between the manifest generated by the mega honk prover over the course of proof - * construction and the one generated by the verifier over the course of proof verification. - * - */ -TYPED_TEST(MegaTranscriptTests, VerifierManifestConsistency) -{ - using Flavor = TypeParam; - using ProverInstance = ProverInstance_; - using VerificationKey = Flavor::VerificationKey; - using Prover = UltraProver_; - using Verifier = UltraVerifier_; - - // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) - auto builder = typename Flavor::CircuitBuilder(); - TestFixture::generate_test_circuit(builder); - - // Automatically generate a transcript manifest in the prover 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); - Prover prover(prover_instance, verification_key); - prover.get_transcript()->enable_manifest(); - auto proof = prover.construct_proof(); - - // Automatically generate a transcript manifest in the verifier by verifying a proof - auto verifier_transcript = std::make_shared(); - verifier_transcript->enable_manifest(); - Verifier verifier(vk_and_hash, verifier_transcript); - [[maybe_unused]] auto verifier_output = verifier.verify_proof(proof); - - // Check consistency between the manifests generated by the prover and verifier - auto prover_manifest = prover.get_transcript()->get_manifest(); - - auto verifier_manifest = verifier.get_transcript()->get_manifest(); - - // Note: a manifest can be printed using manifest.print() - ASSERT_GT(prover_manifest.size(), 0); - for (size_t round = 0; round < prover_manifest.size(); ++round) { - if (prover_manifest[round] != verifier_manifest[round]) { - info("Prover/Verifier manifest discrepency in round ", round); - prover_manifest[round].print(); - verifier_manifest[round].print(); - FAIL(); - } - } -} - -/** - * @brief Check that multiple challenges can be generated and sanity check - * @details We generate 6 challenges that are each 128 bits, and check that they are not 0. - * - */ -TYPED_TEST(MegaTranscriptTests, ChallengeGenerationTest) -{ - using Flavor = TypeParam; - using FF = Flavor::FF; - // initialized with random value sent to verifier - auto transcript = Flavor::Transcript::test_prover_init_empty(); - // test a bunch of challenges - std::vector challenge_labels{ "a", "b", "c", "d", "e", "f" }; - auto challenges = transcript->template get_challenges(challenge_labels); - // check they are not 0 - for (size_t i = 0; i < challenges.size(); ++i) { - ASSERT_NE(challenges[i], 0) << "Challenge " << i << " is 0"; - } - constexpr uint32_t random_val{ 17 }; // arbitrary - transcript->send_to_verifier("random val", random_val); - // test more challenges - challenge_labels = { "a", "b", "c" }; - challenges = transcript->template get_challenges(challenge_labels); - ASSERT_NE(challenges[0], 0) << "Challenge a is 0"; - ASSERT_NE(challenges[1], 0) << "Challenge b is 0"; - ASSERT_NE(challenges[2], 0) << "Challenge c is 0"; -} - -TYPED_TEST(MegaTranscriptTests, StructureTest) -{ - using Flavor = TypeParam; - using ProverInstance = ProverInstance_; - using VerificationKey = Flavor::VerificationKey; - using FF = Flavor::FF; - using Commitment = typename Flavor::Commitment; - using Prover = UltraProver_; - using Verifier = UltraVerifier_; - - // Construct a simple circuit of size n = 8 (i.e. the minimum circuit size) - typename Flavor::CircuitBuilder builder; - this->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); - Prover prover(prover_instance, verification_key); - auto proof = prover.construct_proof(); - Verifier verifier(vk_and_hash); - EXPECT_TRUE(verifier.verify_proof(proof).result); - - const size_t virtual_log_n = Flavor::VIRTUAL_LOG_N; - - // 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 - 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 - 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 - 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); -}