diff --git a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh index 184a0149e1d6..3ecdb6146f8c 100755 --- a/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh +++ b/barretenberg/cpp/scripts/test_civc_standalone_vks_havent_changed.sh @@ -11,7 +11,7 @@ cd .. # - Generate a hash for versioning: sha256sum bb-civc-inputs.tar.gz # - Upload the compressed results: aws s3 cp bb-civc-inputs.tar.gz s3://aztec-ci-artifacts/protocol/bb-civc-inputs-[hash(0:8)].tar.gz # Note: In case of the "Test suite failed to run ... Unexpected token 'with' " error, need to run: docker pull aztecprotocol/build:3.0 -pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-c8814328.tar.gz" +pinned_civc_inputs_url="https://aztec-ci-artifacts.s3.us-east-2.amazonaws.com/protocol/bb-civc-inputs-79e2c5e0.tar.gz" # For easily rerunning the inputs generation if [[ "${1:-}" == "--update_inputs" ]]; then diff --git a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp index 78f4d31ff8fe..bd126810043c 100644 --- a/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp +++ b/barretenberg/cpp/src/barretenberg/commitment_schemes/shplonk/shplonk.hpp @@ -381,7 +381,9 @@ template class ShplonkVerifier_ { commitments.insert(commitments.end(), polynomial_commitments.begin(), polynomial_commitments.end()); scalars.insert(scalars.end(), commitments.size() - 1, Fr(0)); // Initialised as circuit constants - for (size_t idx = 0; idx < num_claims; idx++) { + // The first two powers of nu have already been initialized, we need another `num_claims - 2` powers to batch + // all the claims + for (size_t idx = 0; idx < num_claims - 2; idx++) { pows_of_nu.emplace_back(pows_of_nu.back() * pows_of_nu[1]); } @@ -481,6 +483,7 @@ template class ShplonkVerifier_ { * @param g1_identity * @return BatchOpeningClaim */ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1475): Compute g1_identity inside the function body BatchOpeningClaim export_batch_opening_claim(const Commitment& g1_identity) { commitments.emplace_back(g1_identity); diff --git a/barretenberg/cpp/src/barretenberg/constants.hpp b/barretenberg/cpp/src/barretenberg/constants.hpp index 229ba0de2a90..389e2d2e30fd 100644 --- a/barretenberg/cpp/src/barretenberg/constants.hpp +++ b/barretenberg/cpp/src/barretenberg/constants.hpp @@ -37,7 +37,7 @@ static constexpr uint32_t NUM_LIBRA_COMMITMENTS = 3; // extra evaluations static constexpr uint32_t NUM_SMALL_IPA_EVALUATIONS = 4; -static constexpr uint32_t MERGE_PROOF_SIZE = 49; // used to ensure mock proofs are generated correctly +static constexpr uint32_t MERGE_PROOF_SIZE = 65; // used to ensure mock proofs are generated correctly // There are 5 distinguished wires in ECCVM that have to be opened as univariates to establish the connection between // ECCVM and Translator diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/mock_verifier_inputs.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/mock_verifier_inputs.cpp index cd424849349d..dca385c80d7f 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/mock_verifier_inputs.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/mock_verifier_inputs.cpp @@ -177,11 +177,10 @@ Goblin::MergeProof create_mock_merge_proof() // Populate mock subtable size proof.emplace_back(mock_val); - // There are 8 entities in the merge protocol (4 columns x 2 components; aggregate transcript, previous aggregate - // transcript) and 12 evalations (4 columns x 3 components; aggregate transcript, previous aggregate - // transcript, current transcript) - const size_t NUM_TRANSCRIPT_ENTITIES = 8; - const size_t NUM_TRANSCRIPT_EVALUATIONS = 12; + // There are 12 entities in the merge protocol (4 columns x 3 components: T_{prev,j}, T_j, g_j(X) = X^{l-1} t_j(X)) + // and 8 evaluations (4 columns x 2 components: g_j(kappa), t_j(1/kappa)) + const size_t NUM_TRANSCRIPT_ENTITIES = 12; + const size_t NUM_TRANSCRIPT_EVALUATIONS = 8; // Transcript poly commitments for (size_t i = 0; i < NUM_TRANSCRIPT_ENTITIES; ++i) { @@ -193,7 +192,13 @@ Goblin::MergeProof create_mock_merge_proof() for (size_t i = 0; i < NUM_TRANSCRIPT_EVALUATIONS; ++i) { proof.emplace_back(mock_val); } - // Batched KZG quotient commitment + + // Shplonk proof: commitment to the quotient + for (const FF& val : mock_commitment_frs) { + proof.emplace_back(val); + } + + // KZG proof: commitment to W for (const FF& val : mock_commitment_frs) { proof.emplace_back(val); } diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp index f357c66eb941..12ab345ea45b 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.cpp @@ -368,6 +368,18 @@ template Polynomial Polynomial::right_shifted(const size_t return result; } +template Polynomial Polynomial::reverse() const +{ + const size_t end_index = this->end_index(); + const size_t start_index = this->start_index(); + const size_t poly_size = this->size(); + Polynomial reversed(/*size=*/poly_size, /*virtual_size=*/end_index); + for (size_t idx = end_index; idx > start_index; --idx) { + reversed.at(end_index - idx) = this->at(idx - 1); + } + return reversed; +} + template class Polynomial; template class Polynomial; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp index 5fa813534f78..2a2382f83508 100644 --- a/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp +++ b/barretenberg/cpp/src/barretenberg/polynomials/polynomial.hpp @@ -179,6 +179,15 @@ template class Polynomial { */ Polynomial right_shifted(const size_t magnitude) const; + /** + * @brief Returns the polynomial equal to the reverse of self + * + * @details If the coefficients of self are \f$(a_0, \dots, a_n)\f$, we return the polynomial with coefficients + * \f$(a_n, \dots, a_0)\f$ + * @note Resulting polynomial uses new backing memory; n = self->size() + */ + Polynomial reverse() const; + /** * @brief evaluate multi-linear extension p(X_0,…,X_{n-1}) = \sum_i a_i*L_i(X_0,…,X_{n-1}) at u = * (u_0,…,u_{n-1}) If the polynomial is embedded into a lower dimension k; + const size_t SIZE = 10; + const size_t VIRTUAL_SIZE = 20; + const size_t START_IDX = 2; + const size_t END_IDX = SIZE + START_IDX; + auto poly = Polynomial::random(SIZE, VIRTUAL_SIZE, START_IDX); + + // Instantiate the shift via the right_shifted method + auto poly_reversed = poly.reverse(); + + EXPECT_EQ(poly_reversed.size(), poly.size()); + EXPECT_EQ(poly_reversed.virtual_size(), poly.end_index()); + + // The reversed is indeed the reversed + for (size_t i = 0; i < END_IDX; ++i) { + EXPECT_EQ(poly_reversed.get(END_IDX - 1 - i), poly.get(i)); + } + + // If I change the original polynomial, the reversed polynomial is not updated + FF initial_value = poly.at(3); + poly.at(3) = 25; + EXPECT_EQ(poly_reversed.at(END_IDX - 4), initial_value); +} + // Simple test/demonstration of share functionality TEST(Polynomial, Share) { diff --git a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.cpp index ec8958a957bd..ed313b4d1dfb 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.cpp @@ -5,6 +5,7 @@ // ===================== #include "barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" namespace bb::stdlib::recursion::goblin { @@ -18,98 +19,139 @@ MergeRecursiveVerifier_::MergeRecursiveVerifier_(CircuitBuilder* {} /** - * @brief Computes inputs to a pairing check that, if verified, establishes proper construction of the aggregate Goblin - * ECC op queue polynomials T_j, j = 1,2,3,4. - * @details Let T_j be the jth column of the aggregate ecc op table after prepending the subtable columns t_j containing - * the contribution from a single circuit. T_{j,prev} corresponds to the columns of the aggregate table at the - * previous stage. For each column we have the relationship T_j = t_j + right_shift(T_{j,prev}, k), where k is the - * length of the subtable columns t_j. This protocol demonstrates, assuming the length of t is at most k, that the - * aggregate ecc op table has been constructed correctly via the simple Schwartz-Zippel check: + * @brief Computes inputs to a pairing check that, if verified, establishes proper construction of the aggregate + * Goblin ECC op queue polynomials T_j, j = 1,2,3,4. + * @details Let \f$l_j\f$, \f$r_j\f$, \f$m_j\f$ be three vectors. The Merge wants to convince the verifier that the + * polynomials l_j, r_j, m_j for which they have sent commitments [l_j], [r_j], [m_j] satisfy + * - m_j(X) = l_j(X) + X^l r_j(X) (1) + * - deg(l_j(X)) < k (2) + * where k = shift_size. * - * T_j(\kappa) = t_j(\kappa) + \kappa^k * (T_{j,prev}(\kappa)). + * To check condition (1), the verifier samples a challenge kappa and request from the prover a proof that + * the polynomial + * p_j(X) = l_j(kappa) + kappa^k r_j(kappa) - m_j(kappa) + * opens to 0 at kappa. + * + * To check condition (2), the verifier requests from the prover the commitment to a polynomial g_j, and + * then requests proofs that + * l_j(1/kappa) = c g_j(kappa) = d + * Then, they verify c * kappa^{k-1} = d, which implies, up to negligible probability, that + * g_j(X) = X^{l-1} l_j(1/X), which means that deg(l_j(X)) < l. + * + * The verifier must therefore check 12 opening claims: p_j(kappa) = 0, l_j(1/kappa), g_j(kappa) + * We use Shplonk to verify the claims with a single MSM (instead of computing [p_j] from [l_j], [r_j], [m_j] + * and then open it). We initialize the Shplonk verifier with the following commitments: + * [l_1], [r_1], [m_1], [g_1], ..., [l_4], [r_4], [m_4], [g_4] + * Then, we verify the various claims: + * - p_j(kappa) = 0: The commitment to p_j is constructed from the commitments to l_j, r_j, m_j, so + * the claim passed to the Shplonk verifier specifies the indices of these commitments in + * the above vector: {4 * (j-1), 4 * (j-1) + 1, 4 * (j-1) + 2}, the coefficients + * reconstructing p_j from l_j, r_j, m_j: {1, kappa^k, -1}, and the claimed + * evaluation: 0. + * - l_j(1/kappa) = v_j: The index in this case is {4 * (j-1)}, the coefficient is { 1 }, and the evaluation is + * v_j. + * - g_j(kappa) = w_j: The index is {3 + 4 * (j-1)}, the coefficient is { 1 }, and the evaluation is w_j. + * The claims are passed in the following order: + * {kappa, 0}, {kappa, 0}, {kappa, 0}, {kappa, 0}, {1/kappa, v_1}, {kappa, w_1}, .., {1/kappa, v_4}, {kappa, w_4} + * + * In the Goblin scenario, we have: + * - \f$l_j = t_j, r_j = T_{prev,j}, m_j = T_j\f$ if we are prepending the subtable + * - \f$l_j = T_{prev,j}, r_j = t_j, m_j = T_j\f$ if we are appending the subtable * * @tparam CircuitBuilder * @param proof - * @param t_commitments The commitments to t_j read from the transcript by the PG recursive verifier with which the - * Merge recursive verifier shares a transcript - * @return std::array Inputs to final pairing + * @param t_commitments The commitments to t_j read from the transcript by the PG recursive verifier with which + * the Merge recursive verifier shares a transcript + * @return PairingPoints Inputs to final pairing */ template MergeRecursiveVerifier_::PairingPoints MergeRecursiveVerifier_::verify_proof( const stdlib::Proof& proof, const RefArray t_commitments) { - // Transform proof into a stdlib object + using Claims = typename ShplonkVerifier_::LinearCombinationOfClaims; + transcript->load_proof(proof); - FF subtable_size = transcript->template receive_from_prover("subtable_size"); + FF shift_size = transcript->template receive_from_prover("shift_size"); + BB_ASSERT_GT(shift_size.get_value(), 0U, "Shift size should always be bigger than 0"); - // Receive table column polynomial commitments [T_{j,prev}], and [T_j], j = 1,2,3,4 - std::array T_prev_commitments; + // Vector of commitments to be passed to the Shplonk verifier + // The vector is composed of: [l_1], [r_1], [m_1], [g_1], ..., [l_4], [r_4], [m_4], [g_4] + std::vector table_commitments; for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - std::string suffix = std::to_string(idx); - T_prev_commitments[idx] = transcript->template receive_from_prover("T_PREV_" + suffix); - T_commitments[idx] = transcript->template receive_from_prover("T_CURRENT_" + suffix); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1473): remove receiving commitment to T_prev + auto T_prev_commitment = transcript->template receive_from_prover("T_PREV_" + std::to_string(idx)); + auto left_table = settings == MergeSettings::PREPEND ? t_commitments[idx] : T_prev_commitment; + auto right_table = settings == MergeSettings::PREPEND ? T_prev_commitment : t_commitments[idx]; + + table_commitments.emplace_back(left_table); + table_commitments.emplace_back(right_table); + table_commitments.emplace_back( + transcript->template receive_from_prover("MERGED_TABLE_" + std::to_string(idx))); + table_commitments.emplace_back( + transcript->template receive_from_prover("LEFT_TABLE_REVERSED_" + std::to_string(idx))); } - FF kappa = transcript->template get_challenge("kappa"); - - // Receive evaluations t_j(\kappa), T_{j,prev}(\kappa), T_j(\kappa), j = 1,2,3,4 - std::array t_evals; - std::array T_prev_evals; - std::array T_evals; - std::vector opening_claims; - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - t_evals[idx] = transcript->template receive_from_prover("t_eval_" + std::to_string(idx + 1)); - opening_claims.emplace_back(OpeningClaim{ { kappa, t_evals[idx] }, t_commitments[idx] }); - } - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - T_prev_evals[idx] = transcript->template receive_from_prover("T_prev_eval_" + std::to_string(idx + 1)); - opening_claims.emplace_back(OpeningClaim{ { kappa, T_prev_evals[idx] }, T_prev_commitments[idx] }); - } - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - T_evals[idx] = transcript->template receive_from_prover("T_eval_" + std::to_string(idx + 1)); - opening_claims.emplace_back(OpeningClaim{ { kappa, T_evals[idx] }, T_commitments[idx] }); + // Store T_commitments of the verifier + size_t commitment_idx = 2; // Index of [m_j = T_j] in the vector of commitments + for (auto& commitment : T_commitments) { + commitment = table_commitments[commitment_idx]; + commitment_idx += NUM_WIRES; } - // Check the appropriate identity based on the given merge settings + // Evaluation challenge + const FF kappa = transcript->template get_challenge("kappa"); + const FF kappa_inv = kappa.invert(); + const FF pow_kappa = kappa.pow(shift_size); + const FF pow_kappa_minus_one = pow_kappa * kappa_inv; + + // Opening claims to be passed to the Shplonk verifier + std::vector opening_claims; + + // Add opening claim for p_j(X) = l_j(X) + X^k r_j(X) - m_j(X) + commitment_idx = 0; for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - if (settings == MergeSettings::PREPEND) { - // T_j(\kappa) = t_j(\kappa) + \kappa^m * T_{j,prev}(\kappa) - FF T_prev_shifted_eval_reconstructed = T_prev_evals[idx] * kappa.pow(subtable_size); - T_evals[idx].assert_equal(t_evals[idx] + T_prev_shifted_eval_reconstructed); - } else { - // T_j(\kappa) = T_{j,prev}(\kappa) + \kappa^m * t_j(\kappa) - FF t_shifted_eval_reconstructed = t_evals[idx] * kappa.pow(subtable_size); - T_evals[idx].assert_equal(T_prev_evals[idx] + t_shifted_eval_reconstructed); - } + opening_claims.emplace_back(Claims{ { /*index of [l_j]*/ commitment_idx, + /*index of [r_j]*/ commitment_idx + 1, + /*index of [m_j]*/ commitment_idx + 2 }, + { FF(1), pow_kappa, FF(-1) }, + { kappa, FF(0) } }); + + // Move commitment_idx to the index of [l_{j+1}] + commitment_idx += NUM_WIRES; } - FF alpha = transcript->template get_challenge("alpha"); - - // Constuct inputs to batched commitment and batched evaluation from constituents using batching challenge \alpha - std::vector scalars; - std::vector commitments; - scalars.emplace_back(FF(builder, 1)); - commitments.emplace_back(opening_claims[0].commitment); - auto batched_eval = opening_claims[0].opening_pair.evaluation; - auto alpha_pow = alpha; - for (size_t idx = 1; idx < opening_claims.size(); ++idx) { - auto& claim = opening_claims[idx]; - scalars.emplace_back(alpha_pow); - commitments.emplace_back(claim.commitment); - batched_eval += alpha_pow * claim.opening_pair.evaluation; - if (idx < opening_claims.size() - 1) { - alpha_pow *= alpha; - } + // Add opening claim for l_j(1/kappa), g_j(kappa) and check g_j(kappa) = l_j(1/kappa) * kappa^{k-1} + commitment_idx = 0; + for (size_t idx = 0; idx < NUM_WIRES; ++idx) { + // Opening claim for l_j(1/kappa) + FF left_table_eval_kappa_inv = + transcript->template receive_from_prover("left_table_eval_kappa_inv_" + std::to_string(idx)); + opening_claims.emplace_back( + Claims{ { /*index of [l_j]*/ commitment_idx }, { FF(1) }, { kappa_inv, left_table_eval_kappa_inv } }); + + // Opening claim for g_j(kappa) + FF left_table_reversed_eval = + transcript->template receive_from_prover("left_table_reversed_eval_" + std::to_string(idx)); + opening_claims.emplace_back( + Claims{ { /*index of [g_j]*/ commitment_idx + 3 }, { FF(1) }, { kappa, left_table_reversed_eval } }); + + // Move commitment_idx to index of left_table_{j+1} + commitment_idx += NUM_WIRES; + + // Degree identity + left_table_reversed_eval.assert_equal(left_table_eval_kappa_inv * pow_kappa_minus_one); } - auto batched_commitment = Commitment::batch_mul(commitments, scalars, /*max_num_bits=*/0, /*with_edgecases=*/true); - - OpeningClaim batched_claim = { { kappa, batched_eval }, batched_commitment }; + // Initialize Shplonk verifier + ShplonkVerifier_ verifier(table_commitments, transcript, opening_claims.size()); + verifier.reduce_verification_vector_claims_no_finalize(opening_claims); - auto pairing_points = KZG::reduce_verify(batched_claim, transcript); + // Export batched claim + auto batch_opening_claim = verifier.export_batch_opening_claim(Commitment::one(kappa.get_context())); - return { pairing_points[0], pairing_points[1] }; + // KZG verifier + return KZG::reduce_verify_batch_opening_claim(batch_opening_claim, transcript); } template class MergeRecursiveVerifier_; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp index 237f56607007..f94b87c9ad7f 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp @@ -20,7 +20,6 @@ template class MergeRecursiveVerifier_ { using Commitment = typename Curve::Element; using GroupElement = typename Curve::Element; using KZG = ::bb::KZG; - using OpeningClaim = ::bb::OpeningClaim; using Transcript = bb::BaseTranscript>; using PairingPoints = stdlib::recursion::PairingPoints; @@ -28,7 +27,10 @@ template class MergeRecursiveVerifier_ { std::shared_ptr transcript; MergeSettings settings; + // Number of columns that jointly constitute the op_queue, should be the same as the number of wires in the + // MegaCircuitBuilder static constexpr size_t NUM_WIRES = MegaExecutionTraceBlocks::NUM_WIRES; + std::array T_commitments; explicit MergeRecursiveVerifier_(CircuitBuilder* builder, diff --git a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_verifier.test.cpp b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_verifier.test.cpp index 0d0eca7c9691..80be3db381fb 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_verifier.test.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/merge_verifier/merge_verifier.test.cpp @@ -1,6 +1,7 @@ #include "barretenberg/ultra_honk/merge_verifier.hpp" #include "barretenberg/circuit_checker/circuit_checker.hpp" #include "barretenberg/common/test.hpp" +#include "barretenberg/ecc/fields/field_conversion.hpp" #include "barretenberg/goblin/mock_circuits.hpp" #include "barretenberg/stdlib/merge_verifier/merge_recursive_verifier.hpp" #include "barretenberg/stdlib/primitives/curves/bn254.hpp" @@ -31,11 +32,48 @@ template class RecursiveMergeVerifierTest : public test using Commitment = InnerFlavor::Commitment; using FF = InnerFlavor::FF; using VerifierCommitmentKey = bb::VerifierCommitmentKey; + using MergeProof = MergeProver::MergeProof; + + enum class TamperProofMode { None, Shift, MCommitment, LEval }; public: static void SetUpTestSuite() { bb::srs::init_file_crs_factory(bb::srs::bb_crs_path()); } - static void prove_and_verify_merge(const std::shared_ptr& op_queue) + static void tamper_with_proof(MergeProof& merge_proof, const TamperProofMode tampering_mode) + { + const size_t shift_idx = 0; // Index of shift_size in the merge proof + const size_t m_commitment_idx = 5; // Index of first commitment to merged table in merge proof + const size_t l_eval_idx = 50; // Index of first evaluation of l(1/kappa) in merge proof + + switch (tampering_mode) { + case TamperProofMode::Shift: + // Tamper with the shift size in the proof + merge_proof[shift_idx] += 1; + break; + case TamperProofMode::MCommitment: { + // Tamper with the commitment in the proof + Commitment m_commitment = bb::field_conversion::convert_from_bn254_frs( + std::span{ merge_proof }.subspan(m_commitment_idx, 4)); + m_commitment = m_commitment + Commitment::one(); + auto m_commitment_frs = bb::field_conversion::convert_to_bn254_frs(m_commitment); + for (size_t idx = 0; idx < 4; ++idx) { + merge_proof[m_commitment_idx + idx] = m_commitment_frs[idx]; + } + break; + } + case TamperProofMode::LEval: + // Tamper with the evaluation in the proof + merge_proof[l_eval_idx] -= FF(1); + break; + default: + // Nothing to do + break; + } + } + + static void prove_and_verify_merge(const std::shared_ptr& op_queue, + const TamperProofMode tampering_mode = TamperProofMode::None, + const bool expected = true) { RecursiveBuilder outer_circuit; @@ -57,26 +95,29 @@ template class RecursiveMergeVerifierTest : public test // Construct Merge proof auto merge_proof = merge_prover.construct_proof(); + tamper_with_proof(merge_proof, tampering_mode); // Create a recursive merge verification circuit for the merge proof RecursiveMergeVerifier verifier{ &outer_circuit }; + verifier.transcript->enable_manifest(); verifier.settings = op_queue->get_current_settings(); const stdlib::Proof stdlib_merge_proof(outer_circuit, merge_proof); auto pairing_points = verifier.verify_proof(stdlib_merge_proof, t_commitments_rec); // Check for a failure flag in the recursive verifier circuit - EXPECT_EQ(outer_circuit.failed(), false) << outer_circuit.err(); + EXPECT_EQ(outer_circuit.failed(), !expected) << outer_circuit.err(); // Check 1: Perform native merge verification then perform the pairing on the outputs of the recursive merge // verifier and check that the result agrees. MergeVerifier native_verifier; + native_verifier.transcript->enable_manifest(); native_verifier.settings = op_queue->get_current_settings(); bool verified_native = native_verifier.verify_proof(merge_proof, t_commitments); VerifierCommitmentKey pcs_verification_key; - auto verified_recursive = + bool verified_recursive = pcs_verification_key.pairing_check(pairing_points.P0.get_value(), pairing_points.P1.get_value()); EXPECT_EQ(verified_native, verified_recursive); - EXPECT_TRUE(verified_recursive); + EXPECT_EQ(verified_recursive, expected); // Check 2: Ensure that the underlying native and recursive merge verification algorithms agree by ensuring // the manifests produced by each agree. @@ -85,9 +126,42 @@ template class RecursiveMergeVerifierTest : public test for (size_t i = 0; i < recursive_manifest.size(); ++i) { EXPECT_EQ(recursive_manifest[i], native_manifest[i]); } + } - // Check the recursive merge verifier circuit - CircuitChecker::check(outer_circuit); + /** + * @brief Test failure when degree(l) > shift_size (as read from the proof) + */ + static void test_degree_check_failure() + { + auto op_queue = std::make_shared(); + + InnerBuilder circuit{ op_queue }; + GoblinMockCircuits::construct_simple_circuit(circuit); + prove_and_verify_merge(op_queue, TamperProofMode::Shift, false); + } + + /** + * @brief Test failure when m \neq l + X^k r + */ + static void test_merge_failure() + { + auto op_queue = std::make_shared(); + + InnerBuilder circuit{ op_queue }; + GoblinMockCircuits::construct_simple_circuit(circuit); + prove_and_verify_merge(op_queue, TamperProofMode::MCommitment, false); + } + + /** + * @brief Test failure g_j(kappa) = kappa^{k-1} * l_j(1/kappa) + */ + static void test_eval_failure() + { + auto op_queue = std::make_shared(); + + InnerBuilder circuit{ op_queue }; + GoblinMockCircuits::construct_simple_circuit(circuit); + prove_and_verify_merge(op_queue, TamperProofMode::LEval, false); } /** @@ -124,4 +198,19 @@ TYPED_TEST(RecursiveMergeVerifierTest, SingleRecursiveVerification) TestFixture::test_recursive_merge_verification(); }; +TYPED_TEST(RecursiveMergeVerifierTest, DegreeCheckFailure) +{ + TestFixture::test_degree_check_failure(); +}; + +TYPED_TEST(RecursiveMergeVerifierTest, MergeFailure) +{ + TestFixture::test_merge_failure(); +}; + +TYPED_TEST(RecursiveMergeVerifierTest, EvalFailure) +{ + TestFixture::test_eval_failure(); +}; + } // namespace bb::stdlib::recursion::goblin 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 4094f1ccda40..966c4b4cb7fe 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/mega_honk.test.cpp @@ -310,24 +310,6 @@ TYPED_TEST(MegaHonkTests, MultipleCircuitsMergeOnly) } } -TYPED_TEST(MegaHonkTests, MultipleCircuitsMergeOnlyAppend) -{ - using Flavor = TypeParam; - // Instantiate EccOpQueue. This will be shared across all circuits in the series - auto op_queue = std::make_shared(); - // Construct multiple test circuits that share an ECC op queue. Generate and verify a proof for each. - size_t NUM_CIRCUITS = 3; - for (size_t i = 0; i < NUM_CIRCUITS; ++i) { - auto builder = typename Flavor::CircuitBuilder{ op_queue, MergeSettings::APPEND }; - - GoblinMockCircuits::construct_simple_circuit(builder); - - // Construct and verify Goblin ECC op queue Merge proof - auto merge_verified = this->construct_and_verify_merge_proof(op_queue); - EXPECT_TRUE(merge_verified); - } -} - TYPED_TEST(MegaHonkTests, MultipleCircuitsMergeOnlyPrependThenAppend) { using Flavor = TypeParam; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_prover.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_prover.cpp index 0ac5d7662447..b5e28c963da3 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_prover.cpp @@ -5,6 +5,7 @@ // ===================== #include "merge_prover.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" #include "barretenberg/flavor/mega_zk_flavor.hpp" namespace bb { @@ -18,94 +19,125 @@ MergeProver::MergeProver(const std::shared_ptr& op_queue, const CommitmentKey& commitment_key, const std::shared_ptr& transcript) : op_queue(op_queue) - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1420): pass commitment keys by value , pcs_commitment_key(commitment_key.initialized() ? commitment_key : CommitmentKey(op_queue->get_ultra_ops_table_num_rows())) , transcript(transcript){}; /** * @brief Prove proper construction of the aggregate Goblin ECC op queue polynomials T_j, j = 1,2,3,4. - * @details Let T_j be the jth column of the aggregate ecc op table after prepending the subtable columns t_j containing - * the contribution from the present circuit. T_{j,prev} corresponds to the columns of the aggregate table at the - * previous stage. For each column we have the relationship T_j = t_j + right_shift(T_{j,prev}, k), where k is the - * length of the subtable columns t_j. This protocol demonstrates, assuming the length of t is at most k, that the - * aggregate ecc op table has been constructed correctly via the simple Schwartz-Zippel check: + * @details Let \f$l_j\f$, \f$r_j\f$, \f$m_j\f$ be three vectors. The Merge prover wants to convince the verifier that, + * for j = 1, 2, 3, 4: + * - m_j(X) = l_j(X) + X^l r_j(X) (1) + * - deg(l_j(X)) < k (2) + * where k = shift_size. * - * T_j(\kappa) = t_j(\kappa) + \kappa^k * (T_{j,prev}(\kappa)). + * Condition (1) is equivalent, up to negligible probability, to: + * l_j(kappa) + kappa^k r_j(kappa) - m_j(kappa) = 0 + * so the prover constructs the polynomial + * p_j(X) := l_j(X) + kappa^{k-1} r_j(X) - m_j(X) + * and proves that it opens to 0 at kappa. * - * @note: the prover doesn't commit to t_j because it shares a transcript with the PG instance that folds the present + * To convince the verifier of (2), the prover commits to g_j(X) (allegedly equal to X^{k-1} l_j(1/X)) and provides + * openings: + * c = l_j(1/kappa) d = g_j(kappa) + * The verifier then checks that: c * kappa^{k-1} = d. This check is equivalent, up to negligible probability, to + * \f$g_j(X) = X^{k-1} l_j(1/X)\f$, which implies \f$deg(l_j) < k$. + * + * In the Goblin scenario, we have: + * - \f$l_j = t_j, r_j = T_{prev,j}, m_j = T_j\f$ if we are prepending the subtable + * - \f$l_j = T_{prev,j}, r_j = t_j, m_j = T_j\f$ if we are appending the subtable + * + * @note The prover doesn't commit to t_j because it shares a transcript with the PG instance that folds the present * circuit, and therefore t_j has already been added to the transcript by PG. * - * @return honk::proof + * @return MergeProver::MergeProof */ MergeProver::MergeProof MergeProver::construct_proof() { - // Extract columns of the full table T_j, the previous table T_{j,prev}, and the current subtable t_j - std::array T_current = op_queue->construct_ultra_ops_table_columns(); - std::array T_prev = op_queue->construct_previous_ultra_ops_table_columns(); - std::array t_current = op_queue->construct_current_ultra_ops_subtable_columns(); + std::array left_table; + std::array right_table; + std::array merged_table = op_queue->construct_ultra_ops_table_columns(); // T + std::array left_table_reversed; - const size_t current_table_size = T_current[0].size(); + if (op_queue->get_current_settings() == MergeSettings::PREPEND) { + left_table = op_queue->construct_current_ultra_ops_subtable_columns(); // t + right_table = op_queue->construct_previous_ultra_ops_table_columns(); // T_prev + } else { + left_table = op_queue->construct_previous_ultra_ops_table_columns(); // T_prev + right_table = op_queue->construct_current_ultra_ops_subtable_columns(); // t + } + // Compute g_j(X) + for (size_t idx = 0; idx < NUM_WIRES; ++idx) { + left_table_reversed[idx] = left_table[idx].reverse(); + } + + const size_t merged_table_size = merged_table[0].size(); // TODO(https://github.com/AztecProtocol/barretenberg/issues/1341): Once the op queue is fixed, we won't have to // send the shift size in the append mode. This is desirable to ensure we don't reveal the number of ecc ops in a // subtable when sending a merge proof to the rollup. - const size_t shift_size = - op_queue->get_current_settings() == MergeSettings::PREPEND ? t_current[0].size() : T_prev[0].size(); + const size_t shift_size = left_table[0].size(); transcript->send_to_verifier("shift_size", static_cast(shift_size)); - // Compute/get commitments [t^{shift}], [T_prev], and [T] and add to transcript + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1473): remove generation of commitment to T_prev + // Compute commitments [T_prev], [m_j], [g_j], and send to the verifier for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - // Compute commitments - Commitment T_prev_commitment = pcs_commitment_key.commit(T_prev[idx]); - Commitment T_commitment = pcs_commitment_key.commit(T_current[idx]); - - std::string suffix = std::to_string(idx); - transcript->send_to_verifier("T_PREV_" + suffix, T_prev_commitment); - transcript->send_to_verifier("T_CURRENT_" + suffix, T_commitment); + // Note: This is hacky at the moment because the prover still needs to commit to T_prev. Once we connect two + // steps of the Merge, T_prev will not be sent by the Merge prover, so the following lines will be removed + auto previous_table = + op_queue->get_current_settings() == MergeSettings::PREPEND ? right_table[idx] : left_table[idx]; + transcript->send_to_verifier("T_PREV" + std::to_string(idx), pcs_commitment_key.commit(previous_table)); + transcript->send_to_verifier("MERGED_TABLE_" + std::to_string(idx), + pcs_commitment_key.commit(merged_table[idx])); + transcript->send_to_verifier("LEFT_TABLE_REVERSED_" + std::to_string(idx), + pcs_commitment_key.commit(left_table_reversed[idx])); } - // Compute evaluations T_j(\kappa), T_{j,prev}(\kappa), t_j(\kappa), add to transcript. For each polynomial we add a - // univariate opening claim {p(X), (\kappa, p(\kappa))} to the set of claims to be checked via batched KZG. + // Compute evaluation challenge const FF kappa = transcript->template get_challenge("kappa"); + const FF pow_kappa = kappa.pow(shift_size); + const FF kappa_inv = kappa.invert(); - // Add univariate opening claims for each polynomial. + // Opening claims for each polynomial p_j, l_j, g_j + // + // The opening claims are sent in the following order: + // {kappa, 0}, {kappa, 0}, {kappa, 0}, {kappa, 0}, + // {1/kappa, l_1(1/kappa)}, {kappa, g_1(kappa)}, + // {1/kappa, l_2(1/kappa)}, {kappa, g_2(kappa)}, + // {1/kappa, l_3(1/kappa)}, {kappa, g_3(kappa)}, + // {1/kappa, l_4(1/kappa)}, {kappa, g_4(kappa)} std::vector opening_claims; - // Compute evaluation t(\kappa) - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - FF evaluation = t_current[idx].evaluate(kappa); - transcript->send_to_verifier("t_eval_" + std::to_string(idx), evaluation); - opening_claims.emplace_back(OpeningClaim{ std::move(t_current[idx]), { kappa, evaluation } }); - } - // Compute evaluation T_prev(\kappa) + + // Set opening claims p_j(\kappa) = l_j(X) + kappa^l r_j(X) - m_j(X) for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - FF evaluation = T_prev[idx].evaluate(kappa); - transcript->send_to_verifier("T_prev_eval_" + std::to_string(idx), evaluation); - opening_claims.emplace_back(OpeningClaim{ T_prev[idx], { kappa, evaluation } }); + Polynomial partially_evaluated_difference(merged_table_size); + partially_evaluated_difference += left_table[idx]; + partially_evaluated_difference.add_scaled(right_table[idx], pow_kappa); + partially_evaluated_difference -= merged_table[idx]; + + opening_claims.emplace_back(OpeningClaim{ partially_evaluated_difference, { kappa, FF(0) } }); } - // Compute evaluation T(\kappa) + // Compute evaluation l_j(1/kappa), g_j(\kappa), send to verifier, and set opening claims for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - FF evaluation = T_current[idx].evaluate(kappa); - transcript->send_to_verifier("T_eval_" + std::to_string(idx), evaluation); - opening_claims.emplace_back(OpeningClaim{ std::move(T_current[idx]), { kappa, evaluation } }); - } + FF evaluation; - FF alpha = transcript->template get_challenge("alpha"); + // Evaluate l_j(1/kappa) + evaluation = left_table[idx].evaluate(kappa_inv); + transcript->send_to_verifier("left_table_eval_kappa_inv_" + std::to_string(idx), evaluation); + opening_claims.emplace_back(OpeningClaim{ left_table[idx], { kappa_inv, evaluation } }); - // Construct batched polynomial to be opened via KZG - Polynomial batched_polynomial(current_table_size); - FF batched_eval(0); - FF alpha_pow(1); - for (auto& claim : opening_claims) { - batched_polynomial.add_scaled(claim.polynomial, alpha_pow); - batched_eval += alpha_pow * claim.opening_pair.evaluation; - alpha_pow *= alpha; + // Evaluate g_j(\kappa) + evaluation = left_table_reversed[idx].evaluate(kappa); + transcript->send_to_verifier("left_table_reversed_eval" + std::to_string(idx), evaluation); + opening_claims.emplace_back(OpeningClaim{ left_table_reversed[idx], { kappa, evaluation } }); } - // Construct and commit to KZG quotient polynomial q = (f - v) / (X - kappa) - OpeningClaim batched_claim = { std::move(batched_polynomial), { kappa, batched_eval } }; - PCS::compute_opening_proof(pcs_commitment_key, batched_claim, transcript); + // Shplonk prover + OpeningClaim shplonk_opening_claim = ShplonkProver_::prove(pcs_commitment_key, opening_claims, transcript); + + // KZG prover + PCS::compute_opening_proof(pcs_commitment_key, shplonk_opening_claim, transcript); return transcript->export_proof(); } diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp index 688906006895..158b616f7a7b 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.cpp @@ -5,6 +5,7 @@ // ===================== #include "merge_verifier.hpp" +#include "barretenberg/commitment_schemes/shplonk/shplonk.hpp" #include "barretenberg/flavor/mega_zk_flavor.hpp" #include "barretenberg/flavor/ultra_flavor.hpp" @@ -16,94 +17,145 @@ MergeVerifier::MergeVerifier(const std::shared_ptr& transcript, Merg /** * @brief Verify proper construction of the aggregate Goblin ECC op queue polynomials T_j, j = 1,2,3,4. - * @details Let T_j be the jth column of the aggregate ecc op table after prepending the subtable columns t_j containing - * the contribution from a single circuit. T_{j,prev} corresponds to the columns of the aggregate table at the - * previous stage. For each column we have the relationship T_j = t_j + right_shift(T_{j,prev}, k), where k is the - * length of the subtable columns t_j. This protocol demonstrates, assuming the length of t is at most k, that the - * aggregate ecc op table has been constructed correctly via the simple Schwartz-Zippel check: + * @details Let \f$l_j\f$, \f$r_j\f$, \f$m_j\f$ be three vectors. The Merge wants to convince the verifier that the + * polynomials l_j, r_j, m_j for which they have sent commitments [l_j], [r_j], [m_j] satisfy + * - m_j(X) = l_j(X) + X^l r_j(X) (1) + * - deg(l_j(X)) < k (2) + * where k = shift_size. * - * T_j(\kappa) = t_j(\kappa) + \kappa^k * (T_{j,prev}(\kappa)). + * To check condition (1), the verifier samples a challenge kappa and request from the prover a proof that + * the polynomial + * p_j(X) = l_j(kappa) + kappa^k r_j(kappa) - m_j(kappa) + * opens to 0 at kappa. * - * @tparam Flavor - * @param t_commitments The commitments to t_j read from the transcript by the PG verifier with which the Merge verifier - * shares a transcript + * To check condition (2), the verifier requests from the prover the commitment to a polynomial g_j, and + * then requests proofs that + * l_j(1/kappa) = c g_j(kappa) = d + * Then, they verify c * kappa^{k-1} = d, which implies, up to negligible probability, that + * g_j(X) = X^{l-1} l_j(1/X), which means that deg(l_j(X)) < l. + * + * The verifier must therefore check 12 opening claims: p_j(kappa) = 0, l_j(1/kappa), g_j(kappa) + * We use Shplonk to verify the claims with a single MSM (instead of computing [p_j] from [l_j], [r_j], [m_j] + * and then open it). We initialize the Shplonk verifier with the following commitments: + * [l_1], [r_1], [m_1], [g_1], ..., [l_4], [r_4], [m_4], [g_4] + * Then, we verify the various claims: + * - p_j(kappa) = 0: The commitment to p_j is constructed from the commitments to l_j, r_j, m_j, so + * the claim passed to the Shplonk verifier specifies the indices of these commitments in + * the above vector: {4 * (j-1), 4 * (j-1) + 1, 4 * (j-1) + 2}, the coefficients + * reconstructing p_j from l_j, r_j, m_j: {1, kappa^k, -1}, and the claimed + * evaluation: 0. + * - l_j(1/kappa) = v_j: The index in this case is {4 * (j-1)}, the coefficient is { 1 }, and the evaluation is + * v_j. + * - g_j(kappa) = w_j: The index is {3 + 4 * (j-1)}, the coefficient is { 1 }, and the evaluation is w_j. + * The claims are passed in the following order: + * {kappa, 0}, {kappa, 0}, {kappa, 0}, {kappa, 0}, {1/kappa, v_1}, {kappa, w_1}, .., {1/kappa, v_4}, {kappa, w_4} + * + * In the Goblin scenario, we have: + * - \f$l_j = t_j, r_j = T_{prev,j}, m_j = T_j\f$ if we are prepending the subtable + * - \f$l_j = T_{prev,j}, r_j = t_j, m_j = T_j\f$ if we are appending the subtable + * + * @param proof + * @param t_commitments The commitments to t_j read from the transcript by the PG verifier with which the Merge + * verifier shares a transcript * @return bool Verification result */ bool MergeVerifier::verify_proof(const HonkProof& proof, const RefArray& t_commitments) { + using Claims = typename ShplonkVerifier_::LinearCombinationOfClaims; + transcript->load_proof(proof); const uint32_t shift_size = transcript->template receive_from_prover("shift_size"); + BB_ASSERT_GT(shift_size, 0U, "Shift size should always be bigger than 0"); - // Receive table column polynomial commitments [T_{j,prev}], and [T_j], j = 1,2,3,4 - std::array T_prev_commitments; + // Vector of commitments to be passed to the Shplonk verifier + // The vector is composed of: [l_1], [r_1], [m_1], [g_1], ..., [l_4], [r_4], [m_4], [g_4] + std::vector table_commitments; for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - std::string suffix = std::to_string(idx); - T_prev_commitments[idx] = transcript->template receive_from_prover("T_PREV_" + suffix); - T_commitments[idx] = transcript->template receive_from_prover("T_CURRENT_" + suffix); - } - - FF kappa = transcript->template get_challenge("kappa"); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/1473): remove receiving commitment to T_prev + auto T_prev_commitment = transcript->template receive_from_prover("T_PREV_" + std::to_string(idx)); + auto left_table = settings == MergeSettings::PREPEND ? t_commitments[idx] : T_prev_commitment; + auto right_table = settings == MergeSettings::PREPEND ? T_prev_commitment : t_commitments[idx]; - // Receive evaluations t_j(\kappa), T_{j,prev}(\kappa), T_j(\kappa), j = 1,2,3,4 - std::array t_evals; - std::array T_prev_evals; - std::array T_evals; - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - t_evals[idx] = transcript->template receive_from_prover("t_eval_" + std::to_string(idx)); - } - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - T_prev_evals[idx] = transcript->template receive_from_prover("T_prev_eval_" + std::to_string(idx)); - } - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - T_evals[idx] = transcript->template receive_from_prover("T_eval_" + std::to_string(idx)); + table_commitments.emplace_back(left_table); + table_commitments.emplace_back(right_table); + table_commitments.emplace_back( + transcript->template receive_from_prover("MERGED_TABLE_" + std::to_string(idx))); + table_commitments.emplace_back( + transcript->template receive_from_prover("LEFT_TABLE_REVERSED_" + std::to_string(idx))); } - // Check the identity according to whether the current subtable is prepended or appended. If it fails, return false - bool identity_checked = true; - for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - bool current_check = false; - if (settings == MergeSettings::PREPEND) { - // In prepend mode we shift the evaluation of the previous table and check the identity - // T_j(\kappa) = t_j(\kappa) + \kappa^m * T_{j,prev}(\kappa) - FF T_prev_shifted_eval = T_prev_evals[idx] * kappa.pow(shift_size); - current_check = T_evals[idx] == t_evals[idx] + T_prev_shifted_eval; - } else { - // In append mode we shift the evaluation of the new subtable and check the identity - // T_j(\kappa) = T_{j,prev}(\kappa) + \kappa^m * t_j(\kappa) - FF t_shifted_eval = t_evals[idx] * kappa.pow(shift_size); - current_check = T_evals[idx] == T_prev_evals[idx] + t_shifted_eval; - } - identity_checked = identity_checked && current_check; + // Store T_commitments of the verifier + size_t commitment_idx = 2; // Index of [m_j = T_j] in the vector of commitments + for (auto& commitment : T_commitments) { + commitment = table_commitments[commitment_idx]; + commitment_idx += NUM_WIRES; } - FF alpha = transcript->template get_challenge("alpha"); + // Evaluation challenge + const FF kappa = transcript->template get_challenge("kappa"); + const FF kappa_inv = kappa.invert(); + const FF pow_kappa = kappa.pow(shift_size); - // Construct batched commitment and evaluation from constituents - Commitment batched_commitment = t_commitments[0]; - FF batched_eval = t_evals[0]; - auto alpha_pow = alpha; - for (size_t idx = 1; idx < NUM_WIRES; ++idx) { - batched_commitment = batched_commitment + (t_commitments[idx] * alpha_pow); - batched_eval += alpha_pow * t_evals[idx]; - alpha_pow *= alpha; - } + // Opening claims to be passed to the Shplonk verifier + std::vector opening_claims; + + // Add opening claim for p_j(X) = l_j(X) + X^k r_j(X) - m_j(X) + commitment_idx = 0; for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - batched_commitment = batched_commitment + (T_prev_commitments[idx] * alpha_pow); - batched_eval += alpha_pow * T_prev_evals[idx]; - alpha_pow *= alpha; + Claims claim{ { /*index of [l_j]*/ commitment_idx, + /*index of [r_j]*/ commitment_idx + 1, + /*index of [m_j]*/ commitment_idx + 2 }, + { FF::one(), pow_kappa, FF::neg_one() }, + { kappa, FF::zero() } }; + opening_claims.emplace_back(claim); + + // Move commitment_idx to the index of [l_{j+1}] + commitment_idx += NUM_WIRES; } + + // Boolean keeping track of the degree identities + bool degree_check_verified = true; + + // Add opening claim for l_j(1/kappa), g_j(kappa) and check g_j(kappa) = l_j(1/kappa) * kappa^{k-1} + commitment_idx = 0; for (size_t idx = 0; idx < NUM_WIRES; ++idx) { - batched_commitment = batched_commitment + (T_commitments[idx] * alpha_pow); - batched_eval += alpha_pow * T_evals[idx]; - alpha_pow *= alpha; + Claims claim; + + // Opening claim for l_j(1/kappa) + FF left_table_eval_kappa_inv = + transcript->template receive_from_prover("left_table_eval_kappa_inv_" + std::to_string(idx)); + claim = { { commitment_idx }, { FF::one() }, { kappa_inv, left_table_eval_kappa_inv } }; + opening_claims.emplace_back(claim); + + // Move commitment_idx to index of g_j + commitment_idx += 3; + + // Opening claim for g_j(kappa) + FF left_table_reversed_eval = + transcript->template receive_from_prover("left_table_reversed_eval_" + std::to_string(idx)); + claim = { { commitment_idx }, { FF::one() }, { kappa, left_table_reversed_eval } }; + opening_claims.emplace_back(claim); + + // Move commitment_idx to index of left_table_{j+1} + commitment_idx += 1; + + // Degree identity + degree_check_verified &= (left_table_eval_kappa_inv * kappa.pow(shift_size - 1) == left_table_reversed_eval); } - OpeningClaim batched_claim = { { kappa, batched_eval }, batched_commitment }; + // Initialize Shplonk verifier + ShplonkVerifier_ verifier(table_commitments, transcript, opening_claims.size()); + verifier.reduce_verification_vector_claims_no_finalize(opening_claims); + + // Export batched claim + auto batch_opening_claim = verifier.export_batch_opening_claim(Commitment::one()); - auto pairing_points = PCS::reduce_verify(batched_claim, transcript); + // KZG verifier + auto pairing_points = PCS::reduce_verify_batch_opening_claim(batch_opening_claim, transcript); VerifierCommitmentKey pcs_vkey{}; - auto verified = pcs_vkey.pairing_check(pairing_points[0], pairing_points[1]); - return identity_checked && verified; + bool claims_verified = pcs_vkey.pairing_check(pairing_points[0], pairing_points[1]); + + return degree_check_verified && claims_verified; } } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.hpp index f8ab5b5e804d..c521259964f6 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/merge_verifier.hpp @@ -23,7 +23,6 @@ class MergeVerifier { using Curve = curve::BN254; using FF = typename Curve::ScalarField; using PCS = bb::KZG; - using OpeningClaim = bb::OpeningClaim; using VerifierCommitmentKey = bb::VerifierCommitmentKey; using Transcript = NativeTranscript; diff --git a/barretenberg/sol/lib/forge-std b/barretenberg/sol/lib/forge-std deleted file mode 160000 index 74cfb77e308d..000000000000 --- a/barretenberg/sol/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 74cfb77e308dd188d2f58864aaf44963ae6b88b1 diff --git a/barretenberg/sol/lib/openzeppelin-contracts b/barretenberg/sol/lib/openzeppelin-contracts deleted file mode 160000 index e50c24f5839d..000000000000 --- a/barretenberg/sol/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e50c24f5839db17f46991478384bfda14acfb830 diff --git a/barretenberg/sol/lib/solidity-stringutils b/barretenberg/sol/lib/solidity-stringutils deleted file mode 160000 index 46983c6d9462..000000000000 --- a/barretenberg/sol/lib/solidity-stringutils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 46983c6d9462a80229cf0d5bab8ea3b3ee31066c diff --git a/bb-pilcom/powdr b/bb-pilcom/powdr deleted file mode 160000 index c3006c11819d..000000000000 --- a/bb-pilcom/powdr +++ /dev/null @@ -1 +0,0 @@ -Subproject commit c3006c11819d9b53fb183c9c12a10b83481bb631 diff --git a/l1-contracts/lib/circuits b/l1-contracts/lib/circuits deleted file mode 160000 index 82aac79b466e..000000000000 --- a/l1-contracts/lib/circuits +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 82aac79b466e3430f4946f99919e0866a5ad82f0 diff --git a/l1-contracts/lib/forge-std b/l1-contracts/lib/forge-std deleted file mode 160000 index 0e7097750918..000000000000 --- a/l1-contracts/lib/forge-std +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0e7097750918380d84dd3cfdef595bee74dabb70 diff --git a/l1-contracts/lib/openzeppelin-contracts b/l1-contracts/lib/openzeppelin-contracts deleted file mode 160000 index 448efeea6640..000000000000 --- a/l1-contracts/lib/openzeppelin-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 448efeea6640bbbc09373f03fbc9c88e280147ba