diff --git a/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp b/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp index 48d47785ca7..29fc2b5ea21 100644 --- a/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/api/api_chonk.cpp @@ -4,11 +4,8 @@ #include "barretenberg/api/log.hpp" #include "barretenberg/bbapi/bbapi.hpp" #include "barretenberg/chonk/chonk.hpp" -#include "barretenberg/chonk/chonk_verifier.hpp" -#include "barretenberg/chonk/mock_circuit_producer.hpp" #include "barretenberg/chonk/private_execution_steps.hpp" #include "barretenberg/chonk/proof_compression.hpp" -#include "barretenberg/common/assert.hpp" #include "barretenberg/common/get_bytecode.hpp" #include "barretenberg/common/map.hpp" #include "barretenberg/common/throw_or_abort.hpp" @@ -39,36 +36,27 @@ CircuitKind parse_circuit_kind(const std::string& s) } /** - * @brief Write a Chonk VK (raw bytes, or JSON fields when `flags.output_format == "json"`) to `output_path`. + * @brief Compute and write a Chonk VK in the per-kind flavor selected by `flags.circuit_kind` + * (app → MegaApp, kernel → MegaKernel, hiding → MegaZK). Delegates to ChonkComputeVk; the dummy + * IVC inputs that make a kernel's recursion constraints satisfiable are handled in the ACIR layer. */ -void write_chonk_vk_data(const ChonkVkData& vk, const std::filesystem::path& output_path, const API::Flags& flags) +void write_chonk_vk(std::vector bytecode, const std::filesystem::path& output_path, const API::Flags& flags) { + const CircuitKind kind = parse_circuit_kind(flags.circuit_kind); + auto response = bbapi::ChonkComputeVk{ .circuit = { .bytecode = std::move(bytecode) }, .kind = kind }.execute(); + const bool is_stdout = output_path == "-"; if (is_stdout) { - write_bytes_to_stdout(vk.bytes); + write_bytes_to_stdout(response.bytes); } else if (flags.output_format == "json") { // Note: Chonk VK doesn't have a hash, so we pass an empty string - std::string json_content = VkJson::build(vk.fields, "", flags.scheme); + std::string json_content = VkJson::build(response.fields, "", flags.scheme); write_file(output_path / "vk.json", std::vector(json_content.begin(), json_content.end())); info("VK (JSON) saved to ", output_path / "vk.json"); } else { - write_file(output_path / "vk", vk.bytes); + write_file(output_path / "vk", response.bytes); } } - -/** - * @brief Compute and write a Chonk VK in the per-kind flavor selected by `flags.circuit_kind` - * (app → MegaApp, kernel → MegaKernel, hiding → MegaZK). Delegates to ChonkComputeVk; the dummy - * IVC inputs that make a kernel's recursion constraints satisfiable are handled in the ACIR layer. - */ -void write_chonk_vk(std::vector bytecode, const std::filesystem::path& output_path, const API::Flags& flags) -{ - const CircuitKind kind = parse_circuit_kind(flags.circuit_kind); - auto response = bbapi::ChonkComputeVk{ .circuit = { .bytecode = std::move(bytecode) }, .kind = kind }.execute(); - write_chonk_vk_data( - { .bytes = std::move(response.bytes), .fields = std::move(response.fields) }, output_path, flags); -} - } // anonymous namespace void ChonkAPI::prove(const Flags& flags, @@ -103,38 +91,23 @@ void ChonkAPI::prove(const Flags& flags, bbapi::ChonkAccumulate{ .witness = std::move(step.witness) }.execute(request); } - // The step bytecode has been moved out, so --write_vk writes the hiding kernel VK held by the IVC (the one - // ChonkProve verifies the proof against) instead of recomputing it. It is read before ChonkProve consumes the IVC. - const auto hiding_vk = flags.write_vk ? request.ivc_in_progress->get_hiding_kernel_vk_and_hash()->vk : nullptr; auto proof = bbapi::ChonkProve{}.execute(request).proof; const bool output_to_stdout = output_dir == "-"; - const auto write_proof = [&]() { - const auto proof_fields = proof.to_field_elements(); - if (output_to_stdout) { - vinfo("writing Chonk proof to stdout"); - write_bytes_to_stdout(to_buffer(proof_fields)); - } else if (flags.output_format == "json") { - vinfo("writing Chonk proof (JSON) in directory ", output_dir); - // Note: Chonk proof doesn't have a vk_hash, so we pass an empty string - std::string json_content = ProofJson::build(proof_fields, "", flags.scheme); - write_file(output_dir / "proof.json", std::vector(json_content.begin(), json_content.end())); - info("Proof (JSON) saved to ", output_dir / "proof.json"); - } else { - vinfo("writing Chonk proof in directory ", output_dir); - write_file(output_dir / "proof", to_buffer(proof_fields)); - } - }; - - write_proof(); - - if (flags.write_vk) { - vinfo("writing Chonk vk in directory ", output_dir); - BB_ASSERT(hiding_vk != nullptr, "ChonkAPI::prove - hiding kernel VK was not read before proving"); - // Write CHONK vk for the hiding kernel (last step) — proven as MegaZK. - write_chonk_vk_data( - { .bytes = to_buffer(*hiding_vk), .fields = hiding_vk->to_field_elements() }, output_dir, flags); + const auto proof_fields = proof.to_field_elements(); + if (output_to_stdout) { + vinfo("writing Chonk proof to stdout"); + write_bytes_to_stdout(to_buffer(proof_fields)); + } else if (flags.output_format == "json") { + vinfo("writing Chonk proof (JSON) in directory ", output_dir); + // Note: Chonk proof doesn't have a vk_hash, so we pass an empty string + std::string json_content = ProofJson::build(proof_fields, "", flags.scheme); + write_file(output_dir / "proof.json", std::vector(json_content.begin(), json_content.end())); + info("Proof (JSON) saved to ", output_dir / "proof.json"); + } else { + vinfo("writing Chonk proof in directory ", output_dir); + write_file(output_dir / "proof", to_buffer(proof_fields)); } } @@ -208,22 +181,6 @@ void ChonkAPI::proof_stats(const std::filesystem::path& proof_path, const std::f } } -// WORKTODO(bbapi) remove this -bool ChonkAPI::prove_and_verify(const std::filesystem::path& input_path) -{ - PrivateExecutionSteps steps; - steps.parse(PrivateExecutionStepRaw::load_and_decompress(input_path)); - - std::shared_ptr ivc = steps.accumulate(); - // Construct the hiding kernel as the final step of the IVC - - auto proof = ivc->prove(); - auto vk_and_hash = ivc->get_hiding_kernel_vk_and_hash(); - ChonkNativeVerifier verifier(vk_and_hash); - const bool verified = verifier.verify(proof); - return verified; -} - void ChonkAPI::gates(const Flags& flags, const std::filesystem::path& bytecode_path) { BB_BENCH_NAME("ChonkAPI::gates"); @@ -241,7 +198,6 @@ void ChonkAPI::write_solidity_verifier([[maybe_unused]] const Flags& flags, bool ChonkAPI::check_precomputed_vks(const Flags& flags, const std::filesystem::path& input_path) { BB_BENCH_NAME("ChonkAPI::check_precomputed_vks"); - bbapi::BBApiRequest request; std::vector raw_steps = PrivateExecutionStepRaw::load_and_decompress(input_path); bbapi::VkPolicy vk_policy = bbapi::parse_vk_policy(flags.vk_policy); diff --git a/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp b/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp index 4ed7081c5f7..bbfe9cf09cc 100644 --- a/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp +++ b/barretenberg/cpp/src/barretenberg/api/api_chonk.hpp @@ -18,7 +18,7 @@ namespace bb { * TypeScript (private function calls) * → msgpack encode to ivc-inputs.msgpack * → ChonkAPI::prove() loads and accumulates all steps - * → Chonk proof + VK written to output directory + * → Chonk proof written to output directory * ``` * * **Key entry points:** @@ -27,7 +27,7 @@ namespace bb { * - `write_vk()`: Computes verification key for standalone or IVC circuits * - `check_precomputed_vks()`: Validates that precomputed VKs match computed ones * - * @see PrivateExecutionSteps for the input format + * @see PrivateExecutionStepRaw for the input format * @see Chonk for the underlying IVC implementation */ class ChonkAPI : public API { @@ -37,11 +37,11 @@ class ChonkAPI : public API { * @brief Main production entry point: generate a Chonk proof from private execution steps. * * @details Loads execution steps from ivc-inputs.msgpack, accumulates each circuit via Chonk, - * and writes the final proof (and optionally VK) to the output directory. + * and writes the final proof to the output directory. * - * @param flags Runtime flags (vk_policy, write_vk, etc.) + * @param flags Runtime flags (vk_policy, output_format, etc.) * @param input_path Path to ivc-inputs.msgpack containing PrivateExecutionStepRaw entries - * @param output_dir Directory to write proof (and vk if flags.write_vk is set) + * @param output_dir Directory to write the proof */ void prove(const Flags& flags, const std::filesystem::path& input_path, const std::filesystem::path& output_dir); @@ -57,11 +57,6 @@ class ChonkAPI : public API { const std::filesystem::path& proof_path, const std::filesystem::path& vk_path) override; - /** - * @brief Test/debug function: prove and verify in one call (bypasses serialization). - */ - bool prove_and_verify(const std::filesystem::path& input_path); - /** * @brief Output gate count statistics for a circuit. */ diff --git a/barretenberg/cpp/src/barretenberg/api/file_io.hpp b/barretenberg/cpp/src/barretenberg/api/file_io.hpp index 3bf84b67ef4..b3e1dc8edba 100644 --- a/barretenberg/cpp/src/barretenberg/api/file_io.hpp +++ b/barretenberg/cpp/src/barretenberg/api/file_io.hpp @@ -156,8 +156,8 @@ inline std::vector read_vk_file(const std::filesystem::path& vk_path) return read_file(vk_path.string()); } catch (const std::runtime_error&) { THROW std::runtime_error("Unable to open file: " + vk_path.string() + - "\nGenerate a vk during proving by running `bb prove` with an additional `--write_vk` " - "flag, or run `bb write_vk` to generate a standalone vk." + "\nRun `bb write_vk` to generate a vk. For UltraHonk, you can also generate it during " + "proving with `bb prove --write_vk`." "\nIf you already have a vk file, specify its path with `--vk_path `."); } } diff --git a/barretenberg/cpp/src/barretenberg/bb/cli.cpp b/barretenberg/cpp/src/barretenberg/bb/cli.cpp index afe3529ad65..db4c71a7a58 100644 --- a/barretenberg/cpp/src/barretenberg/bb/cli.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/cli.cpp @@ -258,7 +258,8 @@ int parse_and_run_cli_command(int argc, char* argv[]) }; const auto add_write_vk_flag = [&](CLI::App* subcommand) { - return subcommand->add_flag("--write_vk", flags.write_vk, "Write the provided circuit's verification key"); + return subcommand->add_flag( + "--write_vk", flags.write_vk, "Write the provided circuit's verification key (UltraHonk only)"); }; const auto remove_zk_option = [&](CLI::App* subcommand) { @@ -1007,6 +1008,10 @@ int parse_and_run_cli_command(int argc, char* argv[]) } else if (flags.scheme == "chonk") { ChonkAPI api; if (prove->parsed()) { + if (flags.write_vk) { + throw_or_abort("prove --write_vk is not supported for Chonk. Use bb write_vk --scheme chonk " + "--circuit_kind hiding -b -o ."); + } if (!std::filesystem::exists(ivc_inputs_path)) { throw_or_abort("The prove command for Chonk expect a valid file passed with --ivc_inputs_path " " (default ./ivc-inputs.msgpack)"); diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp index a9f407469bb..d59c8119156 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk.cpp @@ -317,9 +317,8 @@ ChonkComputeVk::Response ChonkComputeVk::execute([[maybe_unused]] const BBApiReq static_cast(kind), ")"); - auto constraint_system = acir_format::circuit_buf_to_mega_acir_format(std::move(circuit.bytecode)); - - acir_format::AcirProgram program{ constraint_system, /*witness=*/{} }; + acir_format::AcirProgram program{ acir_format::circuit_buf_to_mega_acir_format(std::move(circuit.bytecode)), + /*witness=*/{} }; auto verification_key = compute_chonk_vk(program, kind); info("ChonkComputeVk - VK derived, size: ", verification_key.bytes.size(), " bytes"); return { .bytes = std::move(verification_key.bytes), .fields = std::move(verification_key.fields) }; diff --git a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk_pinned_inputs.test.cpp b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk_pinned_inputs.test.cpp index bbb485b586a..9fb113317a3 100644 --- a/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk_pinned_inputs.test.cpp +++ b/barretenberg/cpp/src/barretenberg/bbapi/bbapi_chonk_pinned_inputs.test.cpp @@ -82,8 +82,13 @@ class ChonkPinnedIvcInputsTest : public ::testing::Test { auto raw_steps = bb::PrivateExecutionStepRaw::load_and_decompress(inputs_path); ASSERT_FALSE(raw_steps.empty()) << "no execution steps in " << inputs_path; - const auto hiding_bytecode = raw_steps.back().bytecode; + ASSERT_EQ(raw_steps.back().kind, bb::CircuitKind::HidingKernel); + auto hiding_bytecode = raw_steps.back().bytecode; const auto hiding_vk = raw_steps.back().vk; + const auto derived_vk = bb::bbapi::ChonkComputeVk{ .circuit = { .bytecode = std::move(hiding_bytecode) }, + .kind = bb::CircuitKind::HidingKernel } + .execute(); + ASSERT_EQ(hiding_vk, derived_vk.bytes) << "embedded hiding VK differs from bytecode-derived VK"; bb::bbapi::BBApiRequest request; request.vk_policy = bb::bbapi::VkPolicy::DEFAULT; @@ -108,7 +113,7 @@ class ChonkPinnedIvcInputsTest : public ::testing::Test { auto prove_response = bb::bbapi::ChonkProve{}.execute(request); auto verify_response = - bb::bbapi::ChonkVerify{ .proof = std::move(prove_response.proof), .vk = hiding_vk }.execute(); + bb::bbapi::ChonkVerify{ .proof = std::move(prove_response.proof), .vk = derived_vk.bytes }.execute(); EXPECT_TRUE(verify_response.valid) << "ChonkVerify rejected " << flow_dir.filename(); } }; diff --git a/barretenberg/cpp/src/barretenberg/chonk/chonk_step_processor.hpp b/barretenberg/cpp/src/barretenberg/chonk/chonk_step_processor.hpp index 8d313edff3a..3c6e3d9cd3f 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/chonk_step_processor.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/chonk_step_processor.hpp @@ -32,7 +32,6 @@ class ChonkStepProcessor { public: explicit ChonkStepProcessor(std::vector circuit_kinds); - std::shared_ptr get_ivc() const { return ivc; } size_t get_num_circuits_accumulated() const { return ivc->get_num_circuits_accumulated(); } void process_step(ChonkExecutionStep&& step, ChonkPrecomputedVkPolicy policy = ChonkPrecomputedVkPolicy::DEFAULT); diff --git a/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.cpp b/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.cpp index a9a9fef8e38..962b1c4ee0c 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.cpp +++ b/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.cpp @@ -5,12 +5,15 @@ // ===================== #include "private_execution_steps.hpp" -#include "barretenberg/chonk/chonk.hpp" -#include "barretenberg/chonk/chonk_step_processor.hpp" +#include "barretenberg/common/bb_bench.hpp" #include "barretenberg/common/serialize.hpp" #include "barretenberg/common/thread.hpp" -#include "barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp" +#include "barretenberg/serialize/msgpack_impl.hpp" +#include #include +#include +#include +#include namespace bb { @@ -142,56 +145,6 @@ std::vector PrivateExecutionStepRaw::parse_uncompressed return raw_steps; } -void PrivateExecutionSteps::parse(std::vector&& steps) -{ - BB_BENCH(); - - // Preallocate space to write into diretly as push_back would not be thread safe - folding_stack.resize(steps.size()); - precomputed_vks.resize(steps.size()); - function_names.resize(steps.size()); - kinds.resize(steps.size()); - - // Parse each step's bytecode/witness in parallel (thread-safe with msgpack format) - parallel_for(steps.size(), [&](size_t i) { - PrivateExecutionStepRaw step = std::move(steps[i]); - step.self_decompress(); - - acir_format::AcirFormat constraints = acir_format::circuit_buf_to_mega_acir_format(std::move(step.bytecode)); - acir_format::WitnessVector witness = acir_format::witness_buf_to_witness_vector(std::move(step.witness)); - - folding_stack[i] = { std::move(constraints), std::move(witness) }; - if (step.vk.empty()) { - // For backwards compatibility, but it affects performance and correctness. - precomputed_vks[i] = {}; - } else { - precomputed_vks[i] = std::move(step.vk); - } - function_names[i] = std::move(step.function_name); - kinds[i] = step.kind; - }); -} - -std::shared_ptr PrivateExecutionSteps::accumulate() -{ - auto step_processor = ChonkStepProcessor(kinds); - - for (auto& vk : precomputed_vks) { - if (vk.empty()) { - info("DEPRECATED: Precomputed VKs expected for the given circuits."); - break; - } - } - for (size_t i = 0; i < folding_stack.size(); ++i) { - step_processor.process_step({ .name = std::move(function_names[i]), - .program = std::move(folding_stack[i]), - .precomputed_vk = std::move(precomputed_vks[i]), - .kind = kinds[i] }); - } - - return step_processor.get_ivc(); -} - void PrivateExecutionStepRaw::compress_and_save(std::vector&& steps, const std::filesystem::path& output_path) { diff --git a/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.hpp b/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.hpp index 8897fa5c49a..58922dc76e9 100644 --- a/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.hpp +++ b/barretenberg/cpp/src/barretenberg/chonk/private_execution_steps.hpp @@ -5,18 +5,14 @@ // ===================== #include "barretenberg/chonk/circuit_input.hpp" -#include "barretenberg/dsl/acir_format/acir_format.hpp" #include "barretenberg/serialize/msgpack.hpp" #include #include -#include #include #include namespace bb { -class Chonk; - /** * @brief This is the msgpack encoding of the objects returned by the following typescript: * const stepToStruct = (step: PrivateExecutionStep) => { @@ -57,43 +53,4 @@ struct PrivateExecutionStepRaw { const std::filesystem::path& output_path); }; -/** - * @brief Parsed private execution steps ready for Chonk accumulation. - * - * @details After deserializing from msgpack, this struct holds the decoded ACIR programs, - * their witnesses, precomputed VKs, and function names. The accumulate() method runs the - * complete IVC accumulation over all steps. - * - * Data flow: - * TypeScript (Aztec client) → msgpack encode → ivc-inputs.msgpack - * → PrivateExecutionStepRaw::load_and_decompress() - * → PrivateExecutionSteps::parse() - * → PrivateExecutionSteps::accumulate() - * → Chonk IVC proof - * - * - */ -struct PrivateExecutionSteps { - std::vector folding_stack; ///< ACIR programs with witnesses - std::vector function_names; ///< Function names for logging - std::vector> precomputed_vks; ///< Serialized precomputed VKs (performance) - std::vector kinds; ///< Per-step CircuitKind - - /** - * @brief Creates a Chonk instance and accumulates each circuit in the folding stack. - * Uses precomputed VKs when available for performance. The returned Chonk instance - * is ready to call prove() to generate the final IVC proof. - * - * @return Shared pointer to Chonk instance with accumulated state - */ - std::shared_ptr accumulate(); - - /** - * @brief Converts PrivateExecutionStepRaw entries (which contain raw bytecode/witness bytes) - * into structured AcirProgram objects ready for circuit construction. - * - * @param steps Raw execution steps (will be moved from) - */ - void parse(std::vector&& steps); -}; } // namespace bb diff --git a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp index c14b19752ab..d04b6d6bc10 100644 --- a/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp +++ b/barretenberg/cpp/src/barretenberg/hypernova/hypernova_prover.hpp @@ -66,6 +66,7 @@ class HypernovaFoldingProver { /** * @brief Turn an instance into an accumulator by running Sumcheck. + * @details Consumes the instance's polynomials; callers must not reuse them after this call. */ template Accumulator instance_to_accumulator( @@ -109,6 +110,7 @@ class HypernovaFoldingProver { /** * @brief Turn an instance into an accumulator and cache the resulting claim for the final batching. + * @details Consumes the instance's polynomials; callers must not reuse them after this call. * @return This instance's proof slice (Oink + Sumcheck), exported from the shared transcript. */ template @@ -163,7 +165,6 @@ class HypernovaFoldingProver { /** * @brief Convert the output of the sumcheck run on the incoming instance into an accumulator. - * @details Instance polynomials are destroyed. */ template Accumulator sumcheck_output_to_accumulator(SumcheckOutput& sumcheck_output, @@ -175,14 +176,17 @@ class HypernovaFoldingProver { auto [unshifted_challenges, shifted_challenges] = get_hypernova_batching_challenges( transcript, InstanceFlavor::NUM_UNSHIFTED_ENTITIES, InstanceFlavor::NUM_SHIFTED_ENTITIES); - // Note: batch_polynomials destroys the input polynomials, so the unshifted polynomials cannot be used after - // this. + auto unshifted = instance->polynomials.get_unshifted(); + auto to_be_shifted = instance->polynomials.get_to_be_shifted(); + // Unshifted batching may modify and move its first polynomial, so shifted batching must not depend on it. + for (const auto& polynomial : to_be_shifted) { + BB_ASSERT(&unshifted[0] != &polynomial, + "The first unshifted polynomial must not be among the to-be-shifted entities"); + } Polynomial batched_unshifted_polynomial = batch_polynomials( - instance->polynomials.get_unshifted(), instance->dyadic_size(), unshifted_challenges); - // Note: batch_polynomials destroys the input polynomials, so the shifted polynomials cannot be used after this. - // Also, this call is ok because shifted and unshifted entities are not shared. + unshifted, instance->dyadic_size(), unshifted_challenges); Polynomial batched_shifted_polynomial = batch_polynomials( - instance->polynomials.get_to_be_shifted(), instance->dyadic_size(), shifted_challenges); + to_be_shifted, instance->dyadic_size(), shifted_challenges); FF batched_unshifted_evaluation(0); FF batched_shifted_evaluation(0); @@ -213,9 +217,9 @@ class HypernovaFoldingProver { } /** - * @brief Batch prover polynomials. Original polynomials_to_batch are destroyed. - * @details When the first polynomial's range covers the others, the batch accumulates in it and it is moved into - * the result, so callers must not use it afterwards. Otherwise the batch is built in a new polynomial. + * @brief Compute a weighted sum of prover polynomials. + * @details If the first polynomial's range covers the others, accumulate into it and move it into the result. + * Otherwise, allocate the result without modifying the inputs. Only the first input may be modified or moved from. */ template static Polynomial batch_polynomials(RefArray, N> polynomials_to_batch, @@ -251,7 +255,6 @@ class HypernovaFoldingProver { return result; } add_scaled_batch(polynomials_to_batch[0], sources_span, tail_scalars); - // The batch is accumulated in place in the first polynomial; hand over its memory instead of copying it. return std::move(polynomials_to_batch[0]); }