diff --git a/barretenberg/cpp/scripts/audit/audit_scopes/numeric_audit_scope.md b/barretenberg/cpp/scripts/audit/audit_scopes/numeric_audit_scope.md index 808e8cf90836..7a1f1bba84b8 100644 --- a/barretenberg/cpp/scripts/audit/audit_scopes/numeric_audit_scope.md +++ b/barretenberg/cpp/scripts/audit/audit_scopes/numeric_audit_scope.md @@ -12,22 +12,23 @@ Note: Paths relative to `aztec-packages/barretenberg/cpp/src/barretenberg` 3. `numeric/bitop/keep_n_lsb.hpp` 4. `numeric/bitop/pow.hpp` 5. `numeric/bitop/rotate.hpp` +6. `numeric/bitop/sparse_form.hpp` ### Random Number Generation -6. `numeric/random/engine.cpp` -7. `numeric/random/engine.hpp` +7. `numeric/random/engine.cpp` +8. `numeric/random/engine.hpp` ### Unsigned Integer Types -8. `numeric/uint128/uint128.hpp` -9. `numeric/uint128/uint128_impl.hpp` -10. `numeric/uint256/uint256.hpp` -11. `numeric/uint256/uint256_impl.hpp` -12. `numeric/uintx/uintx.cpp` -13. `numeric/uintx/uintx.hpp` -14. `numeric/uintx/uintx_impl.hpp` +9. `numeric/uint128/uint128.hpp` +10. `numeric/uint128/uint128_impl.hpp` +11. `numeric/uint256/uint256.hpp` +12. `numeric/uint256/uint256_impl.hpp` +13. `numeric/uintx/uintx.cpp` +14. `numeric/uintx/uintx.hpp` +15. `numeric/uintx/uintx_impl.hpp` ### General Utilities -15. `numeric/general/general.hpp` +16. `numeric/general/general.hpp` ## Summary of Module diff --git a/barretenberg/cpp/scripts/ci_benchmark_ivc_flows.sh b/barretenberg/cpp/scripts/ci_benchmark_ivc_flows.sh index de1f01b6cd58..75ac4f06c66d 100755 --- a/barretenberg/cpp/scripts/ci_benchmark_ivc_flows.sh +++ b/barretenberg/cpp/scripts/ci_benchmark_ivc_flows.sh @@ -120,6 +120,12 @@ function chonk_flow { } ] EOF + + # Extract component timings from hierarchical breakdown if available + if [[ -f "$output/benchmark_breakdown.json" ]]; then + echo "Extracting component timings from hierarchical breakdown..." + python3 scripts/extract_component_benchmarks.py "$output" "$name_path" + fi } export -f verify_ivc_flow run_bb_cli_bench diff --git a/barretenberg/cpp/scripts/ci_benchmark_ultrahonk_circuits.sh b/barretenberg/cpp/scripts/ci_benchmark_ultrahonk_circuits.sh index 1d06bfc181d9..a11289b9332b 100755 --- a/barretenberg/cpp/scripts/ci_benchmark_ultrahonk_circuits.sh +++ b/barretenberg/cpp/scripts/ci_benchmark_ultrahonk_circuits.sh @@ -129,52 +129,7 @@ EOF # Extract component timings from hierarchical breakdown if available if [[ -f "$output/benchmark_breakdown.json" ]]; then echo "Extracting component timings from hierarchical breakdown..." - - # Use Python to extract key component timings - # The breakdown JSON format is: { "operation_name": [{"parent": "...", "time": nanoseconds, ...}], ... } - python3 << PYTHON_SCRIPT -import json -import sys - -try: - with open("$output/benchmark_breakdown.json", "r") as f: - data = json.load(f) - - benchmarks = [] - - # Key components to track (case-insensitive matching) - key_components = ["sumcheck", "pcs", "pippenger", "commitment", "circuit", "oink", "compute"] - - for op_name, entries in data.items(): - # Check if this is a key component we want to track - if any(comp.lower() in op_name.lower() for comp in key_components): - # Sum up all timings for this operation (there may be multiple entries with different parents) - total_time_ns = sum(entry.get("time", 0) for entry in entries) - time_ms = total_time_ns / 1_000_000 - - # Create a safe benchmark name (replace special chars) - safe_name = op_name.replace("::", "_").replace(" ", "_") - - benchmarks.append({ - "name": f"$name_path/{safe_name}_ms", - "unit": "ms", - "value": round(time_ms, 2), - "extra": f"stacked:$name_path/components" - }) - - # Append to existing benchmarks file - with open("$output/benchmarks.bench.json", "r") as f: - existing = json.load(f) - - existing.extend(benchmarks) - - with open("$output/benchmarks.bench.json", "w") as f: - json.dump(existing, f, indent=2) - - print(f"Extracted {len(benchmarks)} component timings") -except Exception as e: - print(f"Warning: Could not extract component timings: {e}", file=sys.stderr) -PYTHON_SCRIPT + python3 scripts/extract_component_benchmarks.py "$output" "$name_path" fi echo "Benchmark complete. Results in $output/" diff --git a/barretenberg/cpp/scripts/extract_component_benchmarks.py b/barretenberg/cpp/scripts/extract_component_benchmarks.py new file mode 100644 index 000000000000..8565380d5437 --- /dev/null +++ b/barretenberg/cpp/scripts/extract_component_benchmarks.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Extracts component timings from a hierarchical benchmark breakdown and appends +them to the benchmark JSON file as stacked chart entries. + +Usage: extract_component_benchmarks.py + +The output_dir must contain: + - benchmark_breakdown.json (hierarchical timing data from bb --bench_out_hierarchical) + - benchmarks.bench.json (existing benchmark results to append to) + +The breakdown JSON format is: + { "operation_name": [{"parent": "...", "time": nanoseconds, ...}], ... } + +Component entries are added with extra: "stacked:/components" so the +benchmark dashboard renders them as a single multi-line chart. +""" +import json +import sys + +if len(sys.argv) != 3: + print(f"Usage: {sys.argv[0]} ", file=sys.stderr) + sys.exit(1) + +output_dir = sys.argv[1] +name_path = sys.argv[2] + +try: + with open(f"{output_dir}/benchmark_breakdown.json", "r") as f: + data = json.load(f) + + benchmarks = [] + + # Key components to track (case-insensitive matching) + key_components = ["sumcheck", "pcs", "pippenger", "commitment", "circuit", "oink", "compute"] + + for op_name, entries in data.items(): + # Check if this is a key component we want to track + if any(comp.lower() in op_name.lower() for comp in key_components): + # Sum up all timings for this operation (there may be multiple entries with different parents) + total_time_ns = sum(entry.get("time", 0) for entry in entries) + time_ms = total_time_ns / 1_000_000 + + # Create a safe benchmark name (replace special chars) + safe_name = op_name.replace("::", "_").replace(" ", "_") + + benchmarks.append({ + "name": f"{name_path}/{safe_name}_ms", + "unit": "ms", + "value": round(time_ms, 2), + "extra": f"stacked:{name_path}/components" + }) + + # Append to existing benchmarks file + with open(f"{output_dir}/benchmarks.bench.json", "r") as f: + existing = json.load(f) + + existing.extend(benchmarks) + + with open(f"{output_dir}/benchmarks.bench.json", "w") as f: + json.dump(existing, f, indent=2) + + print(f"Extracted {len(benchmarks)} component timings") +except Exception as e: + print(f"Warning: Could not extract component timings: {e}", file=sys.stderr) 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/prover_polynomials.hpp b/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp index 959b0e99469e..6af015c231b3 100644 --- a/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp +++ b/barretenberg/cpp/src/barretenberg/flavor/prover_polynomials.hpp @@ -31,7 +31,7 @@ class ProverPolynomialsBase : public AllEntitiesBase { ProverPolynomialsBase(ProverPolynomialsBase&& o) noexcept = default; ProverPolynomialsBase& operator=(ProverPolynomialsBase&& o) noexcept = default; ~ProverPolynomialsBase() = default; - [[nodiscard]] size_t get_polynomial_size() const { return this->q_c.size(); } + [[nodiscard]] size_t get_polynomial_size() const { return this->q_c.virtual_size(); } [[nodiscard]] AllValuesType get_row(size_t row_idx) const { AllValuesType result; 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/hypernova/hypernova_prover.cpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp index 89d8552e9605..a0ab9bd02309 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.cpp @@ -81,6 +81,24 @@ Polynomial HypernovaFoldingProver::batch_polynomials BB_ASSERT_EQ( challenges.size(), N, "The number of challenges provided does not match the number of polynomials to batch."); + // Ensure the first polynomial has enough backing to accumulate all others (they may have different backing sizes + // and/or start indices). add_scaled requires destination start_index <= source start_index. + size_t min_start = polynomials_to_batch[0].start_index(); + size_t max_end = polynomials_to_batch[0].end_index(); + for (size_t idx = 1; idx < N; idx++) { + min_start = std::min(min_start, polynomials_to_batch[idx].start_index()); + max_end = std::max(max_end, polynomials_to_batch[idx].end_index()); + } + + if (min_start < polynomials_to_batch[0].start_index() || max_end > polynomials_to_batch[0].end_index()) { + Polynomial result(max_end - min_start, full_batched_size, min_start); + result += polynomials_to_batch[0]; + for (size_t idx = 1; idx < N; idx++) { + result.add_scaled(polynomials_to_batch[idx], challenges[idx]); + } + return result; + } + for (size_t idx = 1; idx < N; idx++) { polynomials_to_batch[0].add_scaled(polynomials_to_batch[idx], challenges[idx]); } diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/count_leading_zeros.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/count_leading_zeros.hpp index a371c0692f83..236dc1ca19e3 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/count_leading_zeros.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/count_leading_zeros.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -35,7 +35,10 @@ template <> constexpr inline size_t count_leading_zeros(uint128_t con return static_cast(__builtin_clzll(hi)); } auto lo = static_cast(u); - return static_cast(__builtin_clzll(lo)) + 64; + if (lo != 0U) { + return static_cast(__builtin_clzll(lo)) + 64; + } + return 128; } template <> constexpr inline size_t count_leading_zeros(uint256_t const& u) diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.hpp index a18e43975645..6815dc2a5e67 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.hpp @@ -1,13 +1,15 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== #pragma once #include +#include #include #include +#include namespace bb::numeric { // from http://supertech.csail.mit.edu/papers/debruijn.pdf @@ -72,8 +74,17 @@ template constexpr inline T get_lsb(const T in) template constexpr inline T round_up_power_2(const T in) { + if (in == 0) { + return 0; + } auto lower_bound = T(1) << get_msb(in); - return (lower_bound == in || lower_bound == 1) ? in : lower_bound * 2; + if (lower_bound == in) { + return in; + } + // Overflow check: lower_bound is the highest power of 2 <= in, + // so lower_bound * 2 would overflow if lower_bound is already the top bit. + assert(lower_bound <= (std::numeric_limits::max() >> 1)); + return lower_bound * 2; } } // namespace bb::numeric \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.test.cpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.test.cpp index 3abdd73b155e..3b8c420c4f7d 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.test.cpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/get_msb.test.cpp @@ -33,3 +33,97 @@ TEST(bitop, GetMsbSizeT7) auto r = numeric::get_msb(a); EXPECT_EQ(r, 7U); } + +// Verify De Bruijn lookup tables by testing every bit position with multiple input patterns +TEST(bitop, GetMsbUint32AllPositions) +{ + for (uint32_t i = 0; i < 32; i++) { + // Power of 2: exactly one bit set + EXPECT_EQ(numeric::get_msb(uint32_t(1U << i)), i); + // All bits set up to position i (exercises the post-smearing pattern) + uint32_t all_ones = (i == 31) ? 0xFFFFFFFF : ((1U << (i + 1)) - 1); + EXPECT_EQ(numeric::get_msb(all_ones), i); + // MSB set plus random low bit + if (i > 0) { + EXPECT_EQ(numeric::get_msb(uint32_t((1U << i) | 1U)), i); + } + } +} + +TEST(bitop, GetMsbUint64AllPositions) +{ + for (uint64_t i = 0; i < 64; i++) { + // Power of 2 + EXPECT_EQ(numeric::get_msb(uint64_t(1ULL << i)), i); + // All bits set up to position i + uint64_t all_ones = (i == 63) ? 0xFFFFFFFFFFFFFFFFULL : ((1ULL << (i + 1)) - 1); + EXPECT_EQ(numeric::get_msb(all_ones), i); + // MSB set plus low bit + if (i > 0) { + EXPECT_EQ(numeric::get_msb(uint64_t((1ULL << i) | 1ULL)), i); + } + } +} + +// get_lsb tests +TEST(bitop, GetLsbZero) +{ + EXPECT_EQ(numeric::get_lsb(uint32_t(0)), 0U); + EXPECT_EQ(numeric::get_lsb(uint64_t(0)), 0U); +} + +TEST(bitop, GetLsbOne) +{ + EXPECT_EQ(numeric::get_lsb(uint32_t(1)), 0U); + EXPECT_EQ(numeric::get_lsb(uint64_t(1)), 0U); +} + +TEST(bitop, GetLsbPowersOfTwo) +{ + for (uint32_t i = 0; i < 32; i++) { + EXPECT_EQ(numeric::get_lsb(uint32_t(1U << i)), i); + } + for (uint64_t i = 0; i < 64; i++) { + EXPECT_EQ(numeric::get_lsb(uint64_t(1ULL << i)), i); + } +} + +TEST(bitop, GetLsbComposite) +{ + // LSB of 0b1100 is bit 2 + EXPECT_EQ(numeric::get_lsb(uint32_t(0b1100)), 2U); + // LSB of 0xFF00 is bit 8 + EXPECT_EQ(numeric::get_lsb(uint64_t(0xFF00)), 8U); +} + +// round_up_power_2 tests +TEST(bitop, RoundUpPower2Zero) +{ + EXPECT_EQ(numeric::round_up_power_2(uint32_t(0)), 0U); + EXPECT_EQ(numeric::round_up_power_2(uint64_t(0)), 0ULL); +} + +TEST(bitop, RoundUpPower2PowersOfTwo) +{ + // Powers of two should be returned unchanged + for (uint32_t i = 0; i < 31; i++) { + uint32_t val = 1U << i; + EXPECT_EQ(numeric::round_up_power_2(val), val); + } +} + +TEST(bitop, RoundUpPower2NonPowers) +{ + EXPECT_EQ(numeric::round_up_power_2(uint32_t(3)), 4U); + EXPECT_EQ(numeric::round_up_power_2(uint32_t(5)), 8U); + EXPECT_EQ(numeric::round_up_power_2(uint32_t(7)), 8U); + EXPECT_EQ(numeric::round_up_power_2(uint32_t(9)), 16U); + EXPECT_EQ(numeric::round_up_power_2(uint32_t(100)), 128U); + EXPECT_EQ(numeric::round_up_power_2(uint64_t(1000)), 1024ULL); +} + +TEST(bitop, RoundUpPower2LargestValid) +{ + // Largest non-power-of-2 that doesn't overflow: 2^30 + 1 -> 2^31 + EXPECT_EQ(numeric::round_up_power_2(uint32_t((1U << 30) + 1)), 1U << 31); +} diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/keep_n_lsb.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/keep_n_lsb.hpp index e3fe0434fa3e..9bfa6689ac00 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/keep_n_lsb.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/keep_n_lsb.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/pow.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/pow.hpp index 38f06712b7c2..ea6816fd196e 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/pow.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/pow.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/rotate.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/rotate.hpp index dea0d0e0ca82..cc7075d21b8b 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/rotate.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/rotate.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/bitop/sparse_form.hpp b/barretenberg/cpp/src/barretenberg/numeric/bitop/sparse_form.hpp index 1bb271fe510a..8ae55f3bd6b6 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/bitop/sparse_form.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/bitop/sparse_form.hpp @@ -5,6 +5,7 @@ // ===================== #pragma once +#include "barretenberg/common/assert.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include #include @@ -15,8 +16,13 @@ namespace bb::numeric { +/** + * @brief Decompose a uint256_t into digits in the given base (least-significant digit first). + * If num_slices > 0, returns exactly that many digits. If num_slices == 0, returns as many as needed. + */ inline std::vector slice_input(const uint256_t& input, const uint64_t base, const size_t num_slices) { + BB_ASSERT(base > 0); uint256_t target = input; std::vector slices; if (num_slices > 0) { @@ -33,12 +39,17 @@ inline std::vector slice_input(const uint256_t& input, const uint64_t return slices; } +/** + * @brief Decompose a uint256_t using a different base for each digit position (least-significant first). + * Throws if the input is too large to be fully represented by the given bases. + */ inline std::vector slice_input_using_variable_bases(const uint256_t& input, const std::vector& bases) { uint256_t target = input; std::vector slices; for (size_t i = 0; i < bases.size(); ++i) { + BB_ASSERT(bases[i] > 0); if (target >= bases[i] && i == bases.size() - 1) { throw_or_abort(format("Last key slice greater than ", bases[i])); } @@ -48,6 +59,9 @@ inline std::vector slice_input_using_variable_bases(const uint256_t& i return slices; } +/** + * @brief Compute [1, base, base^2, ..., base^(num_slices-1)] as uint256_t values. + */ template constexpr std::array get_base_powers() { std::array output{}; @@ -58,6 +72,11 @@ template constexpr std::array constexpr uint256_t map_into_sparse_form(const uint64_t input) { uint256_t out = 0UL; @@ -73,6 +92,11 @@ template constexpr uint256_t map_into_sparse_form(const uint64_t return out; } +/** + * @brief Decode a sparse-form uint256_t back to a 32-bit value. + * Extracts the base-adic digits from most-significant to least-significant, and recovers the original + * binary value by reading the low bit of each digit. + */ template constexpr uint64_t map_from_sparse_form(const uint256_t& input) { uint256_t target = input; @@ -102,6 +126,12 @@ template constexpr uint64_t map_from_sparse_form(const uint256_t return output; } +/** + * @brief Integer type that stores each bit as a separate digit in the given base. + * Supports addition with single-pass carry propagation. Used to build plookup tables + * for bitwise operations (XOR, AND) where two sparse_ints are added and the resulting + * per-digit values encode the operation's truth table. + */ template class sparse_int { public: sparse_int(const uint64_t input = 0) @@ -118,6 +148,8 @@ template class sparse_int { sparse_int& operator=(sparse_int&& other) noexcept = default; ~sparse_int() noexcept = default; + // Single-pass carry propagation: correct when all input limbs are < base, which is guaranteed + // by the constructor (limbs are 0 or 1) and maintained by this operator (carry produces values < base). sparse_int operator+(const sparse_int& other) const { sparse_int result(*this); @@ -126,6 +158,9 @@ template class sparse_int { if (result.limbs[i] >= base) { result.limbs[i] -= base; ++result.limbs[i + 1]; + // After carry: result.limbs[i] < base (since both inputs were < base, sum < 2*base, + // so subtracting base gives a value < base). The carry of 1 into limbs[i+1] cannot + // cascade because limbs[i+1] hasn't been added to other.limbs[i+1] yet. } } result.limbs[num_bits - 1] += other.limbs[num_bits - 1]; diff --git a/barretenberg/cpp/src/barretenberg/numeric/general/general.hpp b/barretenberg/cpp/src/barretenberg/numeric/general/general.hpp index b147ce6f1182..df44e75deb48 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/general/general.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/general/general.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/random/engine.cpp b/barretenberg/cpp/src/barretenberg/numeric/random/engine.cpp index 6d30a12a75e6..8a86f9fc75a7 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/random/engine.cpp +++ b/barretenberg/cpp/src/barretenberg/numeric/random/engine.cpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/random/engine.hpp b/barretenberg/cpp/src/barretenberg/numeric/random/engine.hpp index d2e3e8b0b821..2ef64d5bceb9 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/random/engine.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/random/engine.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128.hpp b/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128.hpp index 17c79af7eb1d..ad2762d16f87 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -37,12 +37,15 @@ class alignas(32) uint128_t { return { static_cast(a), static_cast(a >> 32), 0, 0 }; } - constexpr explicit operator uint64_t() { return (static_cast(data[1]) << 32) + data[0]; } + constexpr explicit operator uint64_t() const { return (static_cast(data[1]) << 32) + data[0]; } constexpr uint128_t& operator=(const uint128_t& other) = default; constexpr uint128_t& operator=(uint128_t&& other) = default; constexpr ~uint128_t() = default; - explicit constexpr operator bool() const { return static_cast(data[0]); }; + explicit constexpr operator bool() const + { + return (data[0] != 0) || (data[1] != 0) || (data[2] != 0) || (data[3] != 0); + }; template explicit constexpr operator T() const { return static_cast(data[0]); }; diff --git a/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128_impl.hpp b/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128_impl.hpp index 590c67ef6a5d..2d5214b63f8d 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uint128/uint128_impl.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.hpp b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.hpp index 156407981dfa..f679dfd1c4b5 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -109,9 +109,12 @@ class alignas(32) uint256_t { constexpr uint256_t& operator=(uint256_t&& other) noexcept = default; constexpr ~uint256_t() noexcept = default; - explicit constexpr operator bool() const { return static_cast(data[0]); }; + explicit constexpr operator bool() const + { + return (data[0] != 0) || (data[1] != 0) || (data[2] != 0) || (data[3] != 0); + }; - constexpr explicit operator uint128_t() { return (static_cast(data[1]) << 64) + data[0]; } + constexpr explicit operator uint128_t() const { return (static_cast(data[1]) << 64) + data[0]; } template explicit constexpr operator T() const { return static_cast(data[0]); }; [[nodiscard]] constexpr bool get_bit(uint64_t bit_index) const; diff --git a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.test.cpp b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.test.cpp index ae72524449cd..710d3e0d83f6 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.test.cpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256.test.cpp @@ -336,3 +336,148 @@ TEST(uint256, ToFromBuffer) auto b = from_buffer(buf); EXPECT_EQ(a, b); } + +// operator bool: verify all limbs are checked (not just data[0]) +TEST(uint256, BoolConversion) +{ + EXPECT_FALSE(static_cast(uint256_t(0))); + EXPECT_TRUE(static_cast(uint256_t(1))); + // These cases caught the old bug where only data[0] was checked + EXPECT_TRUE(static_cast(uint256_t{ 0, 1, 0, 0 })); + EXPECT_TRUE(static_cast(uint256_t{ 0, 0, 1, 0 })); + EXPECT_TRUE(static_cast(uint256_t{ 0, 0, 0, 1 })); +} + +// operator uint128_t: verify both lower limbs are preserved +TEST(uint256, Uint128Conversion) +{ + constexpr uint256_t a{ 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc, 0xdddddddddddddddd }; + auto lo128 = static_cast(a); + EXPECT_EQ(static_cast(lo128), 0xaaaaaaaaaaaaaaaa); + EXPECT_EQ(static_cast(lo128 >> 64), 0xbbbbbbbbbbbbbbbb); + + // Verify const objects use the correct overload (not the integral template) + const uint256_t b{ 0x1111111111111111, 0x2222222222222222, 0, 0 }; + auto b128 = static_cast(b); + EXPECT_EQ(static_cast(b128 >> 64), 0x2222222222222222); +} + +// Addition with carry propagation across all limbs +TEST(uint256, AddCarryPropagation) +{ + // Carry from limb 0 to limb 1 + uint256_t a{ UINT64_MAX, 0, 0, 0 }; + uint256_t b{ 1, 0, 0, 0 }; + uint256_t c = a + b; + EXPECT_EQ(c, (uint256_t{ 0, 1, 0, 0 })); + + // Carry propagates through all limbs + uint256_t d{ UINT64_MAX, UINT64_MAX, UINT64_MAX, 0 }; + uint256_t e = d + uint256_t(1); + EXPECT_EQ(e, (uint256_t{ 0, 0, 0, 1 })); + + // Full overflow wraps to zero + uint256_t f{ UINT64_MAX, UINT64_MAX, UINT64_MAX, UINT64_MAX }; + uint256_t g = f + uint256_t(1); + EXPECT_EQ(g, uint256_t(0)); +} + +// mul_extended: verify full 512-bit product is correct +TEST(uint256, MulExtended) +{ + // Simple case: known product + uint256_t a{ 0, 0, 0, 1 }; // 2^192 + uint256_t b{ 0, 0, 0, 1 }; // 2^192 + auto [lo, hi] = a.mul_extended(b); + // 2^192 * 2^192 = 2^384, which is hi.data[2] bit 0 + EXPECT_EQ(lo, uint256_t(0)); + EXPECT_EQ(hi, (uint256_t{ 0, 0, 1, 0 })); + + // Verify with random values + a = engine.get_random_uint256(); + b = engine.get_random_uint256(); + auto [ab_lo, ab_hi] = a.mul_extended(b); + + // Truncated product should match low half + EXPECT_EQ(a * b, ab_lo); + + // Verify commutativity + auto [ba_lo, ba_hi] = b.mul_extended(a); + EXPECT_EQ(ab_lo, ba_lo); + EXPECT_EQ(ab_hi, ba_hi); + + // Verify hi is zero when inputs are small + uint256_t small_a{ 0xFFFFFFFF, 0, 0, 0 }; + uint256_t small_b{ 0xFFFFFFFF, 0, 0, 0 }; + auto [sm_lo, sm_hi] = small_a.mul_extended(small_b); + EXPECT_EQ(sm_hi, uint256_t(0)); + EXPECT_EQ(sm_lo, small_a * small_b); +} + +// Single-limb divmod +TEST(uint256, DivModSingleLimb) +{ + for (size_t i = 0; i < 64; ++i) { + uint256_t a = engine.get_random_uint256(); + uint64_t b = engine.get_random_uint256().data[0]; + if (b == 0) { + b = 1; + } + auto [q, r] = a.divmod(b); + // Verify roundtrip: q * b + r == a + uint256_t reconstructed = q * uint256_t(b) + uint256_t(r); + EXPECT_EQ(reconstructed, a); + // Remainder must be less than divisor + EXPECT_LT(r, b); + } +} + +// slice +TEST(uint256, Slice) +{ + constexpr uint256_t a{ 0xaaaaaaaaaaaaaaaa, 0xbbbbbbbbbbbbbbbb, 0xcccccccccccccccc, 0xdddddddddddddddd }; + + // Slice bottom 64 bits + uint256_t bottom = a.slice(0, 64); + EXPECT_EQ(bottom, uint256_t(0xaaaaaaaaaaaaaaaa)); + + // Slice bits [64, 128) + uint256_t mid = a.slice(64, 128); + EXPECT_EQ(mid, uint256_t(0xbbbbbbbbbbbbbbbb)); + + // Slice across limb boundary [32, 96) + uint256_t cross = a.slice(32, 96); + uint256_t expected = (a >> 32) & ((uint256_t(1) << 64) - 1); + EXPECT_EQ(cross, expected); + + // Full slice + uint256_t full = a.slice(0, 256); + EXPECT_EQ(full, a); +} + +// pow +TEST(uint256, Pow) +{ + // x^0 = 1 + uint256_t a{ 12345, 0, 0, 0 }; + EXPECT_EQ(a.pow(uint256_t(0)), uint256_t(1)); + + // x^1 = x + EXPECT_EQ(a.pow(uint256_t(1)), a); + + // 0^n = 0 for n > 0 + EXPECT_EQ(uint256_t(0).pow(uint256_t(5)), uint256_t(0)); + + // 2^10 = 1024 + EXPECT_EQ(uint256_t(2).pow(uint256_t(10)), uint256_t(1024)); + + // 3^20 = 3486784401 + EXPECT_EQ(uint256_t(3).pow(uint256_t(20)), uint256_t(3486784401ULL)); + + // Verify a^2 == a * a for random value + uint256_t b = engine.get_random_uint256(); + EXPECT_EQ(b.pow(uint256_t(2)), b * b); + + // Verify a^3 == a * a * a + EXPECT_EQ(b.pow(uint256_t(3)), b * b * b); +} diff --git a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256_impl.hpp b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256_impl.hpp index 83942d37bc38..21b706759fe4 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uint256/uint256_impl.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== diff --git a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.cpp b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.cpp index 9026c2237dc3..aa6081b6fd14 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.cpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.cpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -12,12 +12,4 @@ namespace bb::numeric { template class uintx; template class uintx; -// NOTE: this instantiation is only used to maintain a 1024 barrett reduction test. -// The simpler route would have been to delete that test as this modulus is otherwise not used, but this was more -// conservative. -constexpr uint512_t TEST_MODULUS(uint256_t{ "0x04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b1" }, - uint256_t{ "0x0925c4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d" }); - -template std::pair uintx::barrett_reduction() const; - } // namespace bb::numeric diff --git a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.hpp b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.hpp index 5c9068c8e0c9..5a42fb866fb8 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.hpp @@ -1,19 +1,18 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== /** - * uintx - * Copyright Aztec 2020 + * uintx: a wide unsigned integer formed by pairing two `base_uint` halves (lo, hi). * - * An unsigned 512 bit integer type. + * Instantiated as: + * uintx = uint512_t (two 256-bit halves) + * uintx = uint1024_t (two 512-bit halves) * - * Constructor and all methods are constexpr. Ideally, uintx should be able to be treated like any other literal - *type. - * - * Not optimized for performance, this code doesn"t touch any of our hot paths when constructing PLONK proofs + * Constructor and all methods are constexpr. + * Not optimized for performance; does not touch hot paths when constructing proofs. **/ #pragma once @@ -57,7 +56,7 @@ template class uintx { uintx& operator=(uintx&& other) noexcept = default; ~uintx() = default; - constexpr explicit operator bool() const { return static_cast(lo); }; + constexpr explicit operator bool() const { return static_cast(lo) || static_cast(hi); }; constexpr explicit operator uint8_t() const { return static_cast(lo); }; constexpr explicit operator uint16_t() const { return static_cast(lo); }; constexpr explicit operator uint32_t() const { return static_cast(lo); }; @@ -82,7 +81,7 @@ template class uintx { constexpr uintx slice(const uint64_t start, const uint64_t end) const { const uint64_t range = end - start; - const uintx mask = range == base_uint::length() ? -uintx(1) : (uintx(1) << range) - 1; + const uintx mask = (uintx(1) << range) - 1; return ((*this) >> start) & mask; } diff --git a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.test.cpp b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.test.cpp index ee88020b2e76..bbbb85696b8d 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.test.cpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx.test.cpp @@ -1,10 +1,18 @@ #include "./uintx.hpp" #include "../random/engine.hpp" +#include "./uintx_impl.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include using namespace bb; +// Explicit instantiation of barrett_reduction for the test-only 1024-bit modulus. +namespace bb::numeric { +constexpr uint512_t TEST_MODULUS(uint256_t{ "0x04689e957a1242c84a50189c6d96cadca602072d09eac1013b5458a2275d69b1" }, + uint256_t{ "0x0925c4b8763cbf9c599a6f7c0348d21cb00b85511637560626edfa5c34c6b38d" }); +template std::pair uintx::barrett_reduction() const; +} // namespace bb::numeric + namespace { auto& engine = numeric::get_debug_randomness(); } // namespace @@ -294,6 +302,37 @@ TEST(uintx, DISABLEDRInv) */ } +TEST(uintx, Slice) +{ + // Construct a uint512_t with known lo and hi halves + uint256_t lo_val(1, 2, 3, 4); + uint256_t hi_val(5, 6, 7, 8); + uint512_t val(lo_val, hi_val); + + // Slice the lower 256 bits + uint512_t lower = val.slice(0, 256); + EXPECT_EQ(lower.lo, lo_val); + EXPECT_EQ(lower.hi, uint256_t(0)); + + // Slice the upper 256 bits + uint512_t upper = val.slice(256, 512); + EXPECT_EQ(upper.lo, hi_val); + EXPECT_EQ(upper.hi, uint256_t(0)); + + // Slice a sub-limb range within lo + uint512_t small_slice = val.slice(0, 64); + EXPECT_EQ(static_cast(small_slice), uint64_t(1)); + + // Slice crossing the lo/hi boundary + uint512_t cross_boundary = val.slice(128, 384); + // Should get bits [128..256) from lo (limbs 2,3) and bits [256..384) from hi (limbs 0,1) + uint512_t expected_cross(uint256_t(3, 4, 5, 6)); + EXPECT_EQ(cross_boundary, expected_cross); + + // Full-width slice should return the original value + EXPECT_EQ(val.slice(0, 512), val); +} + TEST(uintx, BarrettReductionRegression) { // Test specific modulus and self values that may cause issues diff --git a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx_impl.hpp b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx_impl.hpp index 0af4869a56a3..77327e096f07 100644 --- a/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx_impl.hpp +++ b/barretenberg/cpp/src/barretenberg/numeric/uintx/uintx_impl.hpp @@ -1,5 +1,5 @@ // === AUDIT STATUS === -// internal: { status: Planned, auditors: [], commit: } +// internal: { status: Complete, auditors: [Luke], commit: } // external_1: { status: not started, auditors: [], commit: } // external_2: { status: not started, auditors: [], commit: } // ===================== @@ -303,9 +303,9 @@ std::pair, uintx> uintx::barrett_reductio } uintx remainder = x - qm_lo; - // because redc_parameter is an imperfect representation of 2^{2k} / n (might be too small), - // the computed quotient may be off by up to 4 (classic algorithm should be up to 1, - // TODO(https://github.com/AztecProtocol/barretenberg/issues/1051): investigate, why) + // The quotient estimate can be off by up to 4. Classic Barrett guarantees at most 1 correction + // when k = ceil(log2(modulus)) and x < modulus^2. Here k = base_uint::length() - 1 (a fixed, + // conservative choice), so x / 2^{2k} can be up to 3, giving an error bound of 4. size_t i = 0; while (remainder >= uintx(modulus)) { BB_ASSERT_LT(i, 4U); 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); -} diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp index 24e922ad6807..bdbefac3fac6 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/prover_instance.cpp @@ -176,9 +176,9 @@ template void ProverInstance_::allocate_selectors(cons selector = Polynomial(block.size(), dyadic_size(), block.trace_offset()); } - // Set the other non-gate selector polynomials (e.g. q_l, q_r, q_m etc.) to full size + // Set the other non-gate selector polynomials (e.g. q_l, q_r, q_m etc.) to active trace size for (auto& selector : polynomials.get_non_gate_selectors()) { - selector = Polynomial(dyadic_size()); + selector = Polynomial(trace_active_range_size(), dyadic_size()); } } diff --git a/barretenberg/rust/barretenberg-rs/Cargo.toml b/barretenberg/rust/barretenberg-rs/Cargo.toml index 04f90e4408cb..f637a2a82a56 100644 --- a/barretenberg/rust/barretenberg-rs/Cargo.toml +++ b/barretenberg/rust/barretenberg-rs/Cargo.toml @@ -10,6 +10,9 @@ readme = "README.md" # crates.io allows max 5 keywords keywords = ["zero-knowledge", "snark", "plonk", "noir", "aztec"] categories = ["cryptography", "api-bindings"] +# Include generated files in the published crate even though they're gitignored. +# They're generated by `yarn generate` in ts/ and must exist on disk before `cargo publish`. +include = ["src/**/*.rs", "build.rs", "README.md"] [dependencies] # Serialization diff --git a/barretenberg/rust/bootstrap.sh b/barretenberg/rust/bootstrap.sh index 7c5c885eb2e3..5b9352241071 100755 --- a/barretenberg/rust/bootstrap.sh +++ b/barretenberg/rust/bootstrap.sh @@ -46,6 +46,30 @@ function test { BB_LIB_DIR="$(cd ../cpp/build/lib && pwd)" RUSTFLAGS="-C link-arg=-Wl,--allow-multiple-definition" denoise "cargo test --release --features ffi" } +function release { + echo_header "barretenberg-rs release" + + local version=${REF_NAME#v} + + # Set the workspace version to match the release tag + sed -i "s/^version = \".*\"/version = \"$version\"/" Cargo.toml + + # Generated files must exist (created during build step) + if [ ! -f barretenberg-rs/src/api.rs ] || [ ! -f barretenberg-rs/src/generated_types.rs ]; then + echo "ERROR: generated files not found. Run 'cd ../ts && yarn generate' first." + exit 1 + fi + + # Publish to crates.io (--allow-dirty because version was just set and generated files are gitignored) + local extra_flags="" + if ! gh release view "v$version" --repo AztecProtocol/aztec-packages &>/dev/null; then + # No matching GitHub release yet — skip verification build (which would try to download libbb-external.a) + echo "No GitHub release found for v$version, adding --no-verify (pass REF_NAME matching a release for full verification)" + extra_flags="--no-verify" + fi + retry "denoise 'cargo publish --allow-dirty $extra_flags -p barretenberg-rs'" +} + function test_download { echo_header "barretenberg-rs download test" @@ -121,6 +145,5 @@ case "$cmd" in $cmd ;; *) - echo "Unknown command: $cmd" - exit 1 + default_cmd_handler "$@" esac diff --git a/barretenberg/rust/tests/Cargo.toml b/barretenberg/rust/tests/Cargo.toml index 676f665a1b25..c52ddf88621e 100644 --- a/barretenberg/rust/tests/Cargo.toml +++ b/barretenberg/rust/tests/Cargo.toml @@ -3,6 +3,7 @@ name = "barretenberg-tests" version.workspace = true edition.workspace = true rust-version.workspace = true +publish = false [features] ffi = ["barretenberg-rs/ffi"] diff --git a/bootstrap.sh b/bootstrap.sh index 161c5ec6fe9c..170301486b18 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -504,6 +504,7 @@ function release { projects=( barretenberg/cpp barretenberg/ts + barretenberg/rust noir l1-contracts noir-projects/aztec-nr @@ -758,6 +759,7 @@ case "$cmd" in pull_submodules noir/bootstrap.sh build_native # Build nargo for acir_tests barretenberg/bootstrap.sh ci + barretenberg/cpp/bootstrap.sh build_bench ;; #######################