diff --git a/barretenberg/cpp/Earthfile b/barretenberg/cpp/Earthfile index 7d97dbfd191..aa63cbaa4fa 100644 --- a/barretenberg/cpp/Earthfile +++ b/barretenberg/cpp/Earthfile @@ -189,6 +189,7 @@ test: COPY --dir +test-binaries/build build FROM +preset-release-assert-test COPY --dir ./srs_db/+build/. srs_db + COPY ../../noir/+build-acir-tests/ /usr/src/acir_tests/acir_tests/ # limit hardware concurrency, if provided IF [ "$HARDWARE_CONCURRENCY" != "" ] ENV HARDWARE_CONCURRENCY=$hardware_concurrency diff --git a/barretenberg/cpp/src/barretenberg/bb/main.cpp b/barretenberg/cpp/src/barretenberg/bb/main.cpp index 1424e3dd582..fd65f240005 100644 --- a/barretenberg/cpp/src/barretenberg/bb/main.cpp +++ b/barretenberg/cpp/src/barretenberg/bb/main.cpp @@ -206,18 +206,15 @@ template bool proveAndVerifyHonk(const std::string& bytec template bool proveAndVerifyHonkProgram(const std::string& bytecodePath, const std::string& witnessPath) { - auto constraint_systems = get_constraint_systems(bytecodePath); - auto witness_stack = get_witness_stack(witnessPath); + auto program_stack = acir_format::get_acir_program_stack(bytecodePath, witnessPath); - while (!witness_stack.empty()) { - auto witness_stack_item = witness_stack.back(); - auto witness = witness_stack_item.second; - auto constraint_system = constraint_systems[witness_stack_item.first]; + while (!program_stack.empty()) { + auto stack_item = program_stack.back(); - if (!proveAndVerifyHonkAcirFormat(constraint_system, witness)) { + if (!proveAndVerifyHonkAcirFormat(stack_item.constraints, stack_item.witness)) { return false; } - witness_stack.pop_back(); + program_stack.pop_back(); } return true; } diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 35d4d1fe6c1..d749fe001f2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -104,6 +104,36 @@ struct AcirFormat { using WitnessVector = std::vector>; using WitnessVectorStack = std::vector>; +struct AcirProgram { + AcirFormat constraints; + WitnessVector witness; +}; + +/** + * @brief Storage for constaint_systems/witnesses for a stack of acir programs + * @details In general the number of items in the witness stack will be equal or greater than the number of constraint + * systems because the program may consist of multiple calls to the same function. + * + */ +struct AcirProgramStack { + std::vector constraint_systems; + WitnessVectorStack witness_stack; + + size_t size() const { return witness_stack.size(); } + bool empty() const { return witness_stack.empty(); } + + AcirProgram back() + { + auto witness_stack_item = witness_stack.back(); + auto witness = witness_stack_item.second; + auto constraint_system = constraint_systems[witness_stack_item.first]; + + return { constraint_system, witness }; + } + + void pop_back() { witness_stack.pop_back(); } +}; + template Builder create_circuit(const AcirFormat& constraint_system, size_t size_hint = 0, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp new file mode 100644 index 00000000000..81c02043a28 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_integration.test.cpp @@ -0,0 +1,335 @@ +#ifndef __wasm__ +#include "barretenberg/bb/exec_pipe.hpp" +#include "barretenberg/common/streams.hpp" +#include "barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp" + +#include +#include + +class AcirIntegrationTest : public ::testing::Test { + public: + static std::vector get_bytecode(const std::string& bytecodePath) + { + std::filesystem::path filePath = bytecodePath; + if (filePath.extension() == ".json") { + // Try reading json files as if they are a Nargo build artifact + std::string command = "jq -r '.bytecode' \"" + bytecodePath + "\" | base64 -d | gunzip -c"; + return exec_pipe(command); + } + + // For other extensions, assume file is a raw ACIR program + std::string command = "gunzip -c \"" + bytecodePath + "\""; + return exec_pipe(command); + } + + // Function to check if a file exists + bool file_exists(const std::string& path) + { + std::ifstream file(path); + return file.good(); + } + + acir_format::AcirProgramStack get_program_stack_data_from_test_file(const std::string& test_program_name) + { + std::string base_path = "../../acir_tests/acir_tests/" + test_program_name + "/target"; + std::string bytecode_path = base_path + "/program.json"; + std::string witness_path = base_path + "/witness.gz"; + + return acir_format::get_acir_program_stack(bytecode_path, witness_path); + } + + acir_format::AcirProgram get_program_data_from_test_file(const std::string& test_program_name) + { + auto program_stack = get_program_stack_data_from_test_file(test_program_name); + ASSERT(program_stack.size() == 1); // Otherwise this method will not return full stack data + + return program_stack.back(); + } + + template bool prove_and_verify_honk(Flavor::CircuitBuilder& builder) + { + using Prover = UltraProver_; + using Verifier = UltraVerifier_; + using VerificationKey = Flavor::VerificationKey; + + Prover prover{ builder }; + // builder.blocks.summarize(); + // info("num gates = ", builder.get_num_gates()); + // info("total circuit size = ", builder.get_total_circuit_size()); + // info("circuit size = ", prover.instance->proving_key.circuit_size); + // info("log circuit size = ", prover.instance->proving_key.log_circuit_size); + auto proof = prover.construct_proof(); + + // Verify Honk proof + auto verification_key = std::make_shared(prover.instance->proving_key); + Verifier verifier{ verification_key }; + + return verifier.verify_proof(proof); + } +}; + +class AcirIntegrationSingleTest : public AcirIntegrationTest, public testing::WithParamInterface { + protected: + static void SetUpTestSuite() { srs::init_crs_factory("../srs_db/ignition"); } +}; + +class AcirIntegrationFoldingTest : public AcirIntegrationTest, public testing::WithParamInterface { + protected: + static void SetUpTestSuite() { srs::init_crs_factory("../srs_db/ignition"); } +}; + +TEST_P(AcirIntegrationSingleTest, ProveAndVerifyProgram) +{ + using Flavor = GoblinUltraFlavor; + using Builder = Flavor::CircuitBuilder; + + std::string test_name = GetParam(); + info("Test: ", test_name); + acir_format::AcirProgram acir_program = get_program_data_from_test_file(test_name); + + // Construct a bberg circuit from the acir representation + Builder builder = acir_format::create_circuit(acir_program.constraints, 0, acir_program.witness); + + // Construct and verify Honk proof + EXPECT_TRUE(prove_and_verify_honk(builder)); +} + +// TODO(https://github.com/AztecProtocol/barretenberg/issues/994): Run all tests +INSTANTIATE_TEST_SUITE_P(AcirTests, + AcirIntegrationSingleTest, + testing::Values("1327_concrete_in_generic", + "1_mul", + "2_div", + "3_add", + "4_sub", + "5_over", + "6", + "6_array", + "7", + "7_function", + "aes128_encrypt", + "arithmetic_binary_operations", + "array_dynamic", + "array_dynamic_blackbox_input", + "array_dynamic_main_output", + "array_dynamic_nested_blackbox_input", + "array_eq", + "array_if_cond_simple", + "array_len", + "array_neq", + "array_sort", + "array_to_slice", + "array_to_slice_constant_length", + "assert", + "assert_statement", + "assert_statement_recursive", + "assign_ex", + "bigint", + "bit_and", + "bit_not", + "bit_shifts_comptime", + "bit_shifts_runtime", + "blake3", + "bool_not", + "bool_or", + "break_and_continue", + "brillig_acir_as_brillig", + "brillig_array_eq", + "brillig_array_to_slice", + "brillig_arrays", + "brillig_assert", + "brillig_bit_shifts_runtime", + "brillig_blake2s", + "brillig_blake3", + "brillig_calls", + "brillig_calls_array", + "brillig_calls_conditionals", + "brillig_conditional", + "brillig_cow", + "brillig_cow_assign", + "brillig_cow_regression", + "brillig_ecdsa_secp256k1", + "brillig_ecdsa_secp256r1", + "brillig_embedded_curve", + "brillig_fns_as_values", + "brillig_hash_to_field", + "brillig_identity_function", + "brillig_keccak", + "brillig_loop", + "brillig_nested_arrays", + "brillig_not", + "brillig_oracle", + "brillig_pedersen", + "brillig_recursion", + "brillig_references", + // "brillig_scalar_mul", + "brillig_schnorr", + "brillig_sha256", + "brillig_signed_cmp", + "brillig_signed_div", + "brillig_slice_input", + "brillig_slices", + "brillig_to_be_bytes", + "brillig_to_bits", + "brillig_to_bytes_integration", + "brillig_to_le_bytes", + "brillig_top_level", + "brillig_unitialised_arrays", + "brillig_wrapping", + "cast_bool", + "closures_mut_ref", + "conditional_1", + "conditional_2", + "conditional_regression_421", + "conditional_regression_547", + "conditional_regression_661", + "conditional_regression_short_circuit", + "conditional_regression_underflow", + "custom_entry", + "databus", + "debug_logs", + "diamond_deps_0", + // "distinct_keyword", + "double_verify_nested_proof", + "double_verify_proof", + "double_verify_proof_recursive", + "ecdsa_secp256k1", + "ecdsa_secp256r1", + "eddsa", + "embedded_curve_ops", + "field_attribute", + "generics", + "global_consts", + "hash_to_field", + "hashmap", + "higher_order_functions", + "if_else_chain", + "import", + "inline_never_basic", + "integer_array_indexing", + "keccak256", + "main_bool_arg", + "main_return", + "merkle_insert", + "missing_closure_env", + "modules", + "modules_more", + "modulus", + "nested_array_dynamic", + "nested_array_dynamic_simple", + "nested_array_in_slice", + "nested_arrays_from_brillig", + "no_predicates_basic", + "no_predicates_brillig", + "no_predicates_numeric_generic_poseidon", + "operator_overloading", + "pedersen_check", + "pedersen_commitment", + "pedersen_hash", + "poseidon_bn254_hash", + "poseidonsponge_x5_254", + "pred_eq", + "prelude", + "references", + "regression", + "regression_2660", + "regression_3051", + "regression_3394", + "regression_3607", + "regression_3889", + "regression_4088", + "regression_4124", + "regression_4202", + "regression_4383", + "regression_4436", + "regression_4449", + "regression_4709", + "regression_capacity_tracker", + "regression_mem_op_predicate", + "regression_method_cannot_be_found", + // "regression_sha256_slice", + "regression_struct_array_conditional", + // "scalar_mul", + "schnorr", + "sha256", + "sha2_byte", + "side_effects_constrain_array", + "signed_arithmetic", + "signed_comparison", + "signed_division", + "simple_2d_array", + "simple_add_and_ret_arr", + "simple_array_param", + "simple_bitwise", + "simple_comparison", + "simple_mut", + "simple_not", + "simple_print", + "simple_program_addition", + "simple_radix", + "simple_shield", + "simple_shift_left_right", + "slice_coercion", + "slice_dynamic_index", + "slice_init_with_complex_type", + "slice_loop", + "slices", + "strings", + "struct", + "struct_array_inputs", + "struct_fields_ordering", + "struct_inputs", + "submodules", + "to_be_bytes", + "to_bytes_consistent", + "to_bytes_integration", + "to_le_bytes", + "trait_as_return_type", + "trait_impl_base_type", + "traits_in_crates_1", + "traits_in_crates_2", + "tuple_inputs", + "tuples", + "type_aliases", + "u128", + "u16_support", + "unconstrained_empty", + "unit_value", + "unsafe_range_constraint", + "witness_compression", + "xor")); + +TEST_P(AcirIntegrationFoldingTest, ProveAndVerifyProgramStack) +{ + using Flavor = GoblinUltraFlavor; + using Builder = Flavor::CircuitBuilder; + + std::string test_name = GetParam(); + info("Test: ", test_name); + + auto program_stack = get_program_stack_data_from_test_file(test_name); + + while (!program_stack.empty()) { + auto program = program_stack.back(); + + // Construct a bberg circuit from the acir representation + auto builder = acir_format::create_circuit(program.constraints, 0, program.witness); + + // Construct and verify Honk proof for the individidual circuit + EXPECT_TRUE(prove_and_verify_honk(builder)); + + program_stack.pop_back(); + } +} + +INSTANTIATE_TEST_SUITE_P(AcirTests, + AcirIntegrationFoldingTest, + testing::Values("fold_after_inlined_calls", + "fold_basic", + "fold_basic_nested_call", + "fold_call_witness_condition", + "fold_complex_outputs", + "fold_distinct_return", + "fold_fibonacci", + "fold_numeric_generic_poseidon")); +#endif \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp new file mode 100644 index 00000000000..51c554489e8 --- /dev/null +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.cpp @@ -0,0 +1,602 @@ +#include "acir_to_constraint_buf.hpp" +#ifndef __wasm__ +#include "barretenberg/bb/get_bytecode.hpp" +#endif +namespace acir_format { +/** + * @brief Construct a poly_tuple for a standard width-3 arithmetic gate from its acir representation + * + * @param arg acir representation of an 3-wire arithmetic operation + * @return poly_triple + * @note In principle Program::Expression can accommodate arbitrarily many quadratic and linear terms but in practice + * the ones processed here have a max of 1 and 3 respectively, in accordance with the standard width-3 arithmetic gate. + */ +poly_triple serialize_arithmetic_gate(Program::Expression const& arg) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): The initialization of the witness indices a,b,c + // to 0 is implicitly assuming that (builder.zero_idx == 0) which is no longer the case. Now, witness idx 0 in + // general will correspond to some non-zero value and some witnesses which are not explicitly set below will be + // erroneously populated with this value. This does not cause failures however because the corresponding selector + // will indeed be 0 so the gate will be satisfied. Still, its a bad idea to have erroneous wire values + // even if they dont break the relation. They'll still add cost in commitments, for example. + poly_triple pt{ + .a = 0, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = 0, + .q_r = 0, + .q_o = 0, + .q_c = 0, + }; + + // Flags indicating whether each witness index for the present poly_tuple has been set + bool a_set = false; + bool b_set = false; + bool c_set = false; + + // If necessary, set values for quadratic term (q_m * w_l * w_r) + ASSERT(arg.mul_terms.size() <= 1); // We can only accommodate 1 quadratic term + // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2} + if (!arg.mul_terms.empty()) { + const auto& mul_term = arg.mul_terms[0]; + pt.q_m = uint256_t(std::get<0>(mul_term)); + pt.a = std::get<1>(mul_term).value; + pt.b = std::get<2>(mul_term).value; + a_set = true; + b_set = true; + } + + // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o + ASSERT(arg.linear_combinations.size() <= 3); // We can only accommodate 3 linear terms + for (const auto& linear_term : arg.linear_combinations) { + bb::fr selector_value(uint256_t(std::get<0>(linear_term))); + uint32_t witness_idx = std::get<1>(linear_term).value; + + // If the witness index has not yet been set or if the corresponding linear term is active, set the witness + // index and the corresponding selector value. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): May need to adjust the pt.a == witness_idx + // check (and the others like it) since we initialize a,b,c with 0 but 0 is a valid witness index once the + // +1 offset is removed from noir. + if (!a_set || pt.a == witness_idx) { // q_l * w_l + pt.a = witness_idx; + pt.q_l = selector_value; + a_set = true; + } else if (!b_set || pt.b == witness_idx) { // q_r * w_r + pt.b = witness_idx; + pt.q_r = selector_value; + b_set = true; + } else if (!c_set || pt.c == witness_idx) { // q_o * w_o + pt.c = witness_idx; + pt.q_o = selector_value; + c_set = true; + } else { + return poly_triple{ + .a = 0, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = 0, + .q_r = 0, + .q_o = 0, + .q_c = 0, + }; + } + } + + // Set constant value q_c + pt.q_c = uint256_t(arg.q_c); + return pt; +} +mul_quad_ serialize_mul_quad_gate(Program::Expression const& arg) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): The initialization of the witness indices a,b,c + // to 0 is implicitly assuming that (builder.zero_idx == 0) which is no longer the case. Now, witness idx 0 in + // general will correspond to some non-zero value and some witnesses which are not explicitly set below will be + // erroneously populated with this value. This does not cause failures however because the corresponding selector + // will indeed be 0 so the gate will be satisfied. Still, its a bad idea to have erroneous wire values + // even if they dont break the relation. They'll still add cost in commitments, for example. + mul_quad_ quad{ .a = 0, + .b = 0, + .c = 0, + .d = 0, + .mul_scaling = 0, + .a_scaling = 0, + .b_scaling = 0, + .c_scaling = 0, + .d_scaling = 0, + .const_scaling = 0 }; + + // Flags indicating whether each witness index for the present mul_quad has been set + bool a_set = false; + bool b_set = false; + bool c_set = false; + bool d_set = false; + ASSERT(arg.mul_terms.size() <= 1); // We can only accommodate 1 quadratic term + // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2} + if (!arg.mul_terms.empty()) { + const auto& mul_term = arg.mul_terms[0]; + quad.mul_scaling = uint256_t(std::get<0>(mul_term)); + quad.a = std::get<1>(mul_term).value; + quad.b = std::get<2>(mul_term).value; + a_set = true; + b_set = true; + } + // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o + ASSERT(arg.linear_combinations.size() <= 4); // We can only accommodate 4 linear terms + for (const auto& linear_term : arg.linear_combinations) { + bb::fr selector_value(uint256_t(std::get<0>(linear_term))); + uint32_t witness_idx = std::get<1>(linear_term).value; + + // If the witness index has not yet been set or if the corresponding linear term is active, set the witness + // index and the corresponding selector value. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): May need to adjust the quad.a == witness_idx + // check (and the others like it) since we initialize a,b,c with 0 but 0 is a valid witness index once the + // +1 offset is removed from noir. + if (!a_set || quad.a == witness_idx) { + quad.a = witness_idx; + quad.a_scaling = selector_value; + a_set = true; + } else if (!b_set || quad.b == witness_idx) { + quad.b = witness_idx; + quad.b_scaling = selector_value; + b_set = true; + } else if (!c_set || quad.c == witness_idx) { + quad.c = witness_idx; + quad.c_scaling = selector_value; + c_set = true; + } else if (!d_set || quad.d == witness_idx) { + quad.d = witness_idx; + quad.d_scaling = selector_value; + d_set = true; + } else { + throw_or_abort("Cannot assign linear term to a constraint of width 4"); + } + } + + // Set constant value q_c + quad.const_scaling = uint256_t(arg.q_c); + return quad; +} + +void handle_arithmetic(Program::Opcode::AssertZero const& arg, AcirFormat& af) +{ + if (arg.value.linear_combinations.size() <= 3) { + poly_triple pt = serialize_arithmetic_gate(arg.value); + // Even if the number of linear terms is less than 3, we might not be able to fit it into a width-3 arithmetic + // gate. This is the case if the linear terms are all disctinct witness from the multiplication term. In that + // case, the serialize_arithmetic_gate() function will return a poly_triple with all 0's, and we use a width-4 + // gate instead. We could probably always use a width-4 gate in fact. + if (pt == poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) { + af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); + } else { + af.poly_triple_constraints.push_back(pt); + } + } else { + af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); + } +} + +void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, AcirFormat& af) +{ + std::visit( + [&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + af.logic_constraints.push_back(LogicConstraint{ + .a = arg.lhs.witness.value, + .b = arg.rhs.witness.value, + .result = arg.output.value, + .num_bits = arg.lhs.num_bits, + .is_xor_gate = false, + }); + } else if constexpr (std::is_same_v) { + af.logic_constraints.push_back(LogicConstraint{ + .a = arg.lhs.witness.value, + .b = arg.rhs.witness.value, + .result = arg.output.value, + .num_bits = arg.lhs.num_bits, + .is_xor_gate = true, + }); + } else if constexpr (std::is_same_v) { + af.range_constraints.push_back(RangeConstraint{ + .witness = arg.input.witness.value, + .num_bits = arg.input.num_bits, + }); + } else if constexpr (std::is_same_v) { + af.aes128_constraints.push_back(AES128Constraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return AES128Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .iv = map(arg.iv, + [](auto& e) { + return AES128Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .key = map(arg.key, + [](auto& e) { + return AES128Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .outputs = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.sha256_constraints.push_back(Sha256Constraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return Sha256Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.sha256_compression.push_back(Sha256Compression{ + .inputs = map(arg.inputs, + [](auto& e) { + return Sha256Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .hash_values = map(arg.hash_values, + [](auto& e) { + return Sha256Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.blake2s_constraints.push_back(Blake2sConstraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return Blake2sInput{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.blake3_constraints.push_back(Blake3Constraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return Blake3Input{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.schnorr_constraints.push_back(SchnorrConstraint{ + .message = map(arg.message, [](auto& e) { return e.witness.value; }), + .public_key_x = arg.public_key_x.witness.value, + .public_key_y = arg.public_key_y.witness.value, + .result = arg.output.value, + .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), + }); + } else if constexpr (std::is_same_v) { + af.pedersen_constraints.push_back(PedersenConstraint{ + .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .hash_index = arg.domain_separator, + .result_x = arg.outputs[0].value, + .result_y = arg.outputs[1].value, + }); + } else if constexpr (std::is_same_v) { + af.pedersen_hash_constraints.push_back(PedersenHashConstraint{ + .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .hash_index = arg.domain_separator, + .result = arg.output.value, + }); + } else if constexpr (std::is_same_v) { + af.ecdsa_k1_constraints.push_back(EcdsaSecp256k1Constraint{ + .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), + .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), + .pub_x_indices = map(arg.public_key_x, [](auto& e) { return e.witness.value; }), + .pub_y_indices = map(arg.public_key_y, [](auto& e) { return e.witness.value; }), + .result = arg.output.value, + }); + } else if constexpr (std::is_same_v) { + af.ecdsa_r1_constraints.push_back(EcdsaSecp256r1Constraint{ + .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), + .pub_x_indices = map(arg.public_key_x, [](auto& e) { return e.witness.value; }), + .pub_y_indices = map(arg.public_key_y, [](auto& e) { return e.witness.value; }), + .result = arg.output.value, + .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), + }); + } else if constexpr (std::is_same_v) { + af.multi_scalar_mul_constraints.push_back(MultiScalarMul{ + .points = map(arg.points, [](auto& e) { return e.witness.value; }), + .scalars = map(arg.scalars, [](auto& e) { return e.witness.value; }), + .out_point_x = arg.outputs[0].value, + .out_point_y = arg.outputs[1].value, + .out_point_is_infinite = arg.outputs[2].value, + }); + } else if constexpr (std::is_same_v) { + af.ec_add_constraints.push_back(EcAdd{ + .input1_x = arg.input1[0].witness.value, + .input1_y = arg.input1[1].witness.value, + .input1_infinite = arg.input1[2].witness.value, + .input2_x = arg.input2[0].witness.value, + .input2_y = arg.input2[1].witness.value, + .input2_infinite = arg.input2[2].witness.value, + .result_x = arg.outputs[0].value, + .result_y = arg.outputs[1].value, + .result_infinite = arg.outputs[2].value, + }); + } else if constexpr (std::is_same_v) { + af.keccak_constraints.push_back(KeccakConstraint{ + .inputs = map(arg.inputs, + [](auto& e) { + return HashInput{ + .witness = e.witness.value, + .num_bits = e.num_bits, + }; + }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + .var_message_size = arg.var_message_size.witness.value, + }); + } else if constexpr (std::is_same_v) { + af.keccak_permutations.push_back(Keccakf1600{ + .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + auto c = RecursionConstraint{ + .key = map(arg.verification_key, [](auto& e) { return e.witness.value; }), + .proof = map(arg.proof, [](auto& e) { return e.witness.value; }), + .public_inputs = map(arg.public_inputs, [](auto& e) { return e.witness.value; }), + .key_hash = arg.key_hash.witness.value, + }; + af.recursion_constraints.push_back(c); + } else if constexpr (std::is_same_v) { + af.bigint_from_le_bytes_constraints.push_back(BigIntFromLeBytes{ + .inputs = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .modulus = map(arg.modulus, [](auto& e) -> uint32_t { return e; }), + .result = arg.output, + }); + } else if constexpr (std::is_same_v) { + af.bigint_to_le_bytes_constraints.push_back(BigIntToLeBytes{ + .input = arg.input, + .result = map(arg.outputs, [](auto& e) { return e.value; }), + }); + } else if constexpr (std::is_same_v) { + af.bigint_operations.push_back(BigIntOperation{ + .lhs = arg.lhs, + .rhs = arg.rhs, + .result = arg.output, + .opcode = BigIntOperationType::Add, + }); + } else if constexpr (std::is_same_v) { + af.bigint_operations.push_back(BigIntOperation{ + .lhs = arg.lhs, + .rhs = arg.rhs, + .result = arg.output, + .opcode = BigIntOperationType::Sub, + }); + } else if constexpr (std::is_same_v) { + af.bigint_operations.push_back(BigIntOperation{ + .lhs = arg.lhs, + .rhs = arg.rhs, + .result = arg.output, + .opcode = BigIntOperationType::Mul, + }); + } else if constexpr (std::is_same_v) { + af.bigint_operations.push_back(BigIntOperation{ + .lhs = arg.lhs, + .rhs = arg.rhs, + .result = arg.output, + .opcode = BigIntOperationType::Div, + }); + } else if constexpr (std::is_same_v) { + af.poseidon2_constraints.push_back(Poseidon2Constraint{ + .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), + .result = map(arg.outputs, [](auto& e) { return e.value; }), + .len = arg.len, + }); + } + }, + arg.value.value); +} + +BlockConstraint handle_memory_init(Program::Opcode::MemoryInit const& mem_init) +{ + BlockConstraint block{ .init = {}, .trace = {}, .type = BlockType::ROM }; + std::vector init; + std::vector trace; + + auto len = mem_init.init.size(); + for (size_t i = 0; i < len; ++i) { + block.init.push_back(poly_triple{ + .a = mem_init.init[i].value, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = 0, + .q_o = 0, + .q_c = 0, + }); + } + + // Databus is only supported for Goblin, non Goblin builders will treat call_data and return_data as normal array. + if (IsGoblinUltraBuilder) { + if (std::holds_alternative(mem_init.block_type.value)) { + block.type = BlockType::CallData; + } else if (std::holds_alternative(mem_init.block_type.value)) { + block.type = BlockType::ReturnData; + } + } + + return block; +} + +bool is_rom(Program::MemOp const& mem_op) +{ + return mem_op.operation.mul_terms.size() == 0 && mem_op.operation.linear_combinations.size() == 0 && + uint256_t(mem_op.operation.q_c) == 0; +} + +void handle_memory_op(Program::Opcode::MemoryOp const& mem_op, BlockConstraint& block) +{ + uint8_t access_type = 1; + if (is_rom(mem_op.op)) { + access_type = 0; + } + if (access_type == 1) { + // We are not allowed to write on the databus + ASSERT((block.type != BlockType::CallData) && (block.type != BlockType::ReturnData)); + block.type = BlockType::RAM; + } + + MemOp acir_mem_op = MemOp{ .access_type = access_type, + .index = serialize_arithmetic_gate(mem_op.op.index), + .value = serialize_arithmetic_gate(mem_op.op.value) }; + block.trace.push_back(acir_mem_op); +} + +AcirFormat circuit_serde_to_acir_format(Program::Circuit const& circuit) +{ + AcirFormat af; + // `varnum` is the true number of variables, thus we add one to the index which starts at zero + af.varnum = circuit.current_witness_index + 1; + af.recursive = circuit.recursive; + af.num_acir_opcodes = static_cast(circuit.opcodes.size()); + af.public_inputs = join({ map(circuit.public_parameters.value, [](auto e) { return e.value; }), + map(circuit.return_values.value, [](auto e) { return e.value; }) }); + std::map block_id_to_block_constraint; + for (auto gate : circuit.opcodes) { + std::visit( + [&](auto&& arg) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + handle_arithmetic(arg, af); + } else if constexpr (std::is_same_v) { + handle_blackbox_func_call(arg, af); + } else if constexpr (std::is_same_v) { + auto block = handle_memory_init(arg); + uint32_t block_id = arg.block_id.value; + block_id_to_block_constraint[block_id] = block; + } else if constexpr (std::is_same_v) { + auto block = block_id_to_block_constraint.find(arg.block_id.value); + if (block == block_id_to_block_constraint.end()) { + throw_or_abort("unitialized MemoryOp"); + } + handle_memory_op(arg, block->second); + } + }, + gate.value); + } + for (const auto& [block_id, block] : block_id_to_block_constraint) { + if (!block.trace.empty()) { + af.block_constraints.push_back(block); + } + } + return af; +} + +AcirFormat circuit_buf_to_acir_format(std::vector const& buf) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `program_buf_to_acir_format` + // once Honk fully supports all ACIR test flows + // For now the backend still expects to work with a single ACIR function + auto circuit = Program::Program::bincodeDeserialize(buf).functions[0]; + + return circuit_serde_to_acir_format(circuit); +} + +/** + * @brief Converts from the ACIR-native `WitnessMap` format to Barretenberg's internal `WitnessVector` format. + * + * @param witness_map ACIR-native `WitnessMap` deserialized from a buffer + * @return A `WitnessVector` equivalent to the passed `WitnessMap`. + * @note This transformation results in all unassigned witnesses within the `WitnessMap` being assigned the value 0. + * Converting the `WitnessVector` back to a `WitnessMap` is unlikely to return the exact same `WitnessMap`. + */ +WitnessVector witness_map_to_witness_vector(WitnessStack::WitnessMap const& witness_map) +{ + WitnessVector wv; + size_t index = 0; + for (auto& e : witness_map.value) { + // ACIR uses a sparse format for WitnessMap where unused witness indices may be left unassigned. + // To ensure that witnesses sit at the correct indices in the `WitnessVector`, we fill any indices + // which do not exist within the `WitnessMap` with the dummy value of zero. + while (index < e.first.value) { + wv.push_back(bb::fr(0)); + index++; + } + wv.push_back(bb::fr(uint256_t(e.second))); + index++; + } + return wv; +} + +/** + * @brief Converts from the ACIR-native `WitnessMap` format to Barretenberg's internal `WitnessVector` format. + * + * @param buf Serialized representation of a `WitnessMap`. + * @return A `WitnessVector` equivalent to the passed `WitnessMap`. + * @note This transformation results in all unassigned witnesses within the `WitnessMap` being assigned the value 0. + * Converting the `WitnessVector` back to a `WitnessMap` is unlikely to return the exact same `WitnessMap`. + */ +WitnessVector witness_buf_to_witness_data(std::vector const& buf) +{ + // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `witness_buf_to_witness_stack` + // once Honk fully supports all ACIR test flows. + // For now the backend still expects to work with the stop of the `WitnessStack`. + auto witness_stack = WitnessStack::WitnessStack::bincodeDeserialize(buf); + auto w = witness_stack.stack[witness_stack.stack.size() - 1].witness; + + return witness_map_to_witness_vector(w); +} + +std::vector program_buf_to_acir_format(std::vector const& buf) +{ + auto program = Program::Program::bincodeDeserialize(buf); + + std::vector constraint_systems; + constraint_systems.reserve(program.functions.size()); + for (auto const& function : program.functions) { + constraint_systems.emplace_back(circuit_serde_to_acir_format(function)); + } + + return constraint_systems; +} + +WitnessVectorStack witness_buf_to_witness_stack(std::vector const& buf) +{ + auto witness_stack = WitnessStack::WitnessStack::bincodeDeserialize(buf); + WitnessVectorStack witness_vector_stack; + witness_vector_stack.reserve(witness_stack.stack.size()); + for (auto const& stack_item : witness_stack.stack) { + witness_vector_stack.emplace_back( + std::make_pair(stack_item.index, witness_map_to_witness_vector(stack_item.witness))); + } + return witness_vector_stack; +} + +#ifndef __wasm__ +AcirProgramStack get_acir_program_stack(std::string const& bytecode_path, std::string const& witness_path) +{ + auto bytecode = get_bytecode(bytecode_path); + auto constraint_systems = program_buf_to_acir_format(bytecode); + + auto witness_data = get_bytecode(witness_path); + auto witness_stack = witness_buf_to_witness_stack(witness_data); + + return { constraint_systems, witness_stack }; +} +#endif +} // namespace acir_format \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp index b24c18d5cca..493baa2f515 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_to_constraint_buf.hpp @@ -1,610 +1,26 @@ #pragma once #include "acir_format.hpp" -#include "barretenberg/common/container.hpp" -#include "barretenberg/common/throw_or_abort.hpp" -#include "barretenberg/dsl/acir_format/aes128_constraint.hpp" -#include "barretenberg/dsl/acir_format/bigint_constraint.hpp" -#include "barretenberg/dsl/acir_format/blake2s_constraint.hpp" -#include "barretenberg/dsl/acir_format/blake3_constraint.hpp" -#include "barretenberg/dsl/acir_format/block_constraint.hpp" -#include "barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp" -#include "barretenberg/dsl/acir_format/keccak_constraint.hpp" -#include "barretenberg/dsl/acir_format/logic_constraint.hpp" -#include "barretenberg/dsl/acir_format/pedersen.hpp" -#include "barretenberg/dsl/acir_format/poseidon2_constraint.hpp" -#include "barretenberg/dsl/acir_format/range_constraint.hpp" -#include "barretenberg/dsl/acir_format/recursion_constraint.hpp" -#include "barretenberg/dsl/acir_format/schnorr_verify.hpp" -#include "barretenberg/dsl/acir_format/sha256_constraint.hpp" -#include "barretenberg/plonk_honk_shared/arithmetization/gate_data.hpp" #include "serde/index.hpp" -#include -#include namespace acir_format { -using mul_quad = mul_quad_; -/** - * @brief Construct a poly_tuple for a standard width-3 arithmetic gate from its acir representation - * - * @param arg acir representation of an 3-wire arithmetic operation - * @return poly_triple - * @note In principle Program::Expression can accommodate arbitrarily many quadratic and linear terms but in practice - * the ones processed here have a max of 1 and 3 respectively, in accordance with the standard width-3 arithmetic gate. - */ -poly_triple serialize_arithmetic_gate(Program::Expression const& arg) -{ - // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): The initialization of the witness indices a,b,c - // to 0 is implicitly assuming that (builder.zero_idx == 0) which is no longer the case. Now, witness idx 0 in - // general will correspond to some non-zero value and some witnesses which are not explicitly set below will be - // erroneously populated with this value. This does not cause failures however because the corresponding selector - // will indeed be 0 so the gate will be satisfied. Still, its a bad idea to have erroneous wire values - // even if they dont break the relation. They'll still add cost in commitments, for example. - poly_triple pt{ - .a = 0, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = 0, - .q_r = 0, - .q_o = 0, - .q_c = 0, - }; - - // Flags indicating whether each witness index for the present poly_tuple has been set - bool a_set = false; - bool b_set = false; - bool c_set = false; - - // If necessary, set values for quadratic term (q_m * w_l * w_r) - ASSERT(arg.mul_terms.size() <= 1); // We can only accommodate 1 quadratic term - // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2} - if (!arg.mul_terms.empty()) { - const auto& mul_term = arg.mul_terms[0]; - pt.q_m = uint256_t(std::get<0>(mul_term)); - pt.a = std::get<1>(mul_term).value; - pt.b = std::get<2>(mul_term).value; - a_set = true; - b_set = true; - } - - // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o - ASSERT(arg.linear_combinations.size() <= 3); // We can only accommodate 3 linear terms - for (const auto& linear_term : arg.linear_combinations) { - bb::fr selector_value(uint256_t(std::get<0>(linear_term))); - uint32_t witness_idx = std::get<1>(linear_term).value; - - // If the witness index has not yet been set or if the corresponding linear term is active, set the witness - // index and the corresponding selector value. - // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): May need to adjust the pt.a == witness_idx - // check (and the others like it) since we initialize a,b,c with 0 but 0 is a valid witness index once the - // +1 offset is removed from noir. - if (!a_set || pt.a == witness_idx) { // q_l * w_l - pt.a = witness_idx; - pt.q_l = selector_value; - a_set = true; - } else if (!b_set || pt.b == witness_idx) { // q_r * w_r - pt.b = witness_idx; - pt.q_r = selector_value; - b_set = true; - } else if (!c_set || pt.c == witness_idx) { // q_o * w_o - pt.c = witness_idx; - pt.q_o = selector_value; - c_set = true; - } else { - return poly_triple{ - .a = 0, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = 0, - .q_r = 0, - .q_o = 0, - .q_c = 0, - }; - } - } - - // Set constant value q_c - pt.q_c = uint256_t(arg.q_c); - return pt; -} -mul_quad serialize_mul_quad_gate(Program::Expression const& arg) -{ - // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): The initialization of the witness indices a,b,c - // to 0 is implicitly assuming that (builder.zero_idx == 0) which is no longer the case. Now, witness idx 0 in - // general will correspond to some non-zero value and some witnesses which are not explicitly set below will be - // erroneously populated with this value. This does not cause failures however because the corresponding selector - // will indeed be 0 so the gate will be satisfied. Still, its a bad idea to have erroneous wire values - // even if they dont break the relation. They'll still add cost in commitments, for example. - mul_quad quad{ .a = 0, - .b = 0, - .c = 0, - .d = 0, - .mul_scaling = 0, - .a_scaling = 0, - .b_scaling = 0, - .c_scaling = 0, - .d_scaling = 0, - .const_scaling = 0 }; - - // Flags indicating whether each witness index for the present mul_quad has been set - bool a_set = false; - bool b_set = false; - bool c_set = false; - bool d_set = false; - ASSERT(arg.mul_terms.size() <= 1); // We can only accommodate 1 quadratic term - // Note: mul_terms are tuples of the form {selector_value, witness_idx_1, witness_idx_2} - if (!arg.mul_terms.empty()) { - const auto& mul_term = arg.mul_terms[0]; - quad.mul_scaling = uint256_t(std::get<0>(mul_term)); - quad.a = std::get<1>(mul_term).value; - quad.b = std::get<2>(mul_term).value; - a_set = true; - b_set = true; - } - // If necessary, set values for linears terms q_l * w_l, q_r * w_r and q_o * w_o - ASSERT(arg.linear_combinations.size() <= 4); // We can only accommodate 4 linear terms - for (const auto& linear_term : arg.linear_combinations) { - bb::fr selector_value(uint256_t(std::get<0>(linear_term))); - uint32_t witness_idx = std::get<1>(linear_term).value; - - // If the witness index has not yet been set or if the corresponding linear term is active, set the witness - // index and the corresponding selector value. - // TODO(https://github.com/AztecProtocol/barretenberg/issues/816): May need to adjust the quad.a == witness_idx - // check (and the others like it) since we initialize a,b,c with 0 but 0 is a valid witness index once the - // +1 offset is removed from noir. - if (!a_set || quad.a == witness_idx) { - quad.a = witness_idx; - quad.a_scaling = selector_value; - a_set = true; - } else if (!b_set || quad.b == witness_idx) { - quad.b = witness_idx; - quad.b_scaling = selector_value; - b_set = true; - } else if (!c_set || quad.c == witness_idx) { - quad.c = witness_idx; - quad.c_scaling = selector_value; - c_set = true; - } else if (!d_set || quad.d == witness_idx) { - quad.d = witness_idx; - quad.d_scaling = selector_value; - d_set = true; - } else { - throw_or_abort("Cannot assign linear term to a constraint of width 4"); - } - } - - // Set constant value q_c - quad.const_scaling = uint256_t(arg.q_c); - return quad; -} - -void handle_arithmetic(Program::Opcode::AssertZero const& arg, AcirFormat& af) -{ - if (arg.value.linear_combinations.size() <= 3) { - poly_triple pt = serialize_arithmetic_gate(arg.value); - // Even if the number of linear terms is less than 3, we might not be able to fit it into a width-3 arithmetic - // gate. This is the case if the linear terms are all disctinct witness from the multiplication term. In that - // case, the serialize_arithmetic_gate() function will return a poly_triple with all 0's, and we use a width-4 - // gate instead. We could probably always use a width-4 gate in fact. - if (pt == poly_triple{ 0, 0, 0, 0, 0, 0, 0, 0 }) { - af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); - } else { - af.poly_triple_constraints.push_back(pt); - } - } else { - af.quad_constraints.push_back(serialize_mul_quad_gate(arg.value)); - } -} - -void handle_blackbox_func_call(Program::Opcode::BlackBoxFuncCall const& arg, AcirFormat& af) -{ - std::visit( - [&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - af.logic_constraints.push_back(LogicConstraint{ - .a = arg.lhs.witness.value, - .b = arg.rhs.witness.value, - .result = arg.output.value, - .num_bits = arg.lhs.num_bits, - .is_xor_gate = false, - }); - } else if constexpr (std::is_same_v) { - af.logic_constraints.push_back(LogicConstraint{ - .a = arg.lhs.witness.value, - .b = arg.rhs.witness.value, - .result = arg.output.value, - .num_bits = arg.lhs.num_bits, - .is_xor_gate = true, - }); - } else if constexpr (std::is_same_v) { - af.range_constraints.push_back(RangeConstraint{ - .witness = arg.input.witness.value, - .num_bits = arg.input.num_bits, - }); - } else if constexpr (std::is_same_v) { - af.aes128_constraints.push_back(AES128Constraint{ - .inputs = map(arg.inputs, - [](auto& e) { - return AES128Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .iv = map(arg.iv, - [](auto& e) { - return AES128Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .key = map(arg.key, - [](auto& e) { - return AES128Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .outputs = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.sha256_constraints.push_back(Sha256Constraint{ - .inputs = map(arg.inputs, - [](auto& e) { - return Sha256Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.sha256_compression.push_back(Sha256Compression{ - .inputs = map(arg.inputs, - [](auto& e) { - return Sha256Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .hash_values = map(arg.hash_values, - [](auto& e) { - return Sha256Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.blake2s_constraints.push_back(Blake2sConstraint{ - .inputs = map(arg.inputs, - [](auto& e) { - return Blake2sInput{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.blake3_constraints.push_back(Blake3Constraint{ - .inputs = map(arg.inputs, - [](auto& e) { - return Blake3Input{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.schnorr_constraints.push_back(SchnorrConstraint{ - .message = map(arg.message, [](auto& e) { return e.witness.value; }), - .public_key_x = arg.public_key_x.witness.value, - .public_key_y = arg.public_key_y.witness.value, - .result = arg.output.value, - .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), - }); - } else if constexpr (std::is_same_v) { - af.pedersen_constraints.push_back(PedersenConstraint{ - .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), - .hash_index = arg.domain_separator, - .result_x = arg.outputs[0].value, - .result_y = arg.outputs[1].value, - }); - } else if constexpr (std::is_same_v) { - af.pedersen_hash_constraints.push_back(PedersenHashConstraint{ - .scalars = map(arg.inputs, [](auto& e) { return e.witness.value; }), - .hash_index = arg.domain_separator, - .result = arg.output.value, - }); - } else if constexpr (std::is_same_v) { - af.ecdsa_k1_constraints.push_back(EcdsaSecp256k1Constraint{ - .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), - .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), - .pub_x_indices = map(arg.public_key_x, [](auto& e) { return e.witness.value; }), - .pub_y_indices = map(arg.public_key_y, [](auto& e) { return e.witness.value; }), - .result = arg.output.value, - }); - } else if constexpr (std::is_same_v) { - af.ecdsa_r1_constraints.push_back(EcdsaSecp256r1Constraint{ - .hashed_message = map(arg.hashed_message, [](auto& e) { return e.witness.value; }), - .pub_x_indices = map(arg.public_key_x, [](auto& e) { return e.witness.value; }), - .pub_y_indices = map(arg.public_key_y, [](auto& e) { return e.witness.value; }), - .result = arg.output.value, - .signature = map(arg.signature, [](auto& e) { return e.witness.value; }), - }); - } else if constexpr (std::is_same_v) { - af.multi_scalar_mul_constraints.push_back(MultiScalarMul{ - .points = map(arg.points, [](auto& e) { return e.witness.value; }), - .scalars = map(arg.scalars, [](auto& e) { return e.witness.value; }), - .out_point_x = arg.outputs[0].value, - .out_point_y = arg.outputs[1].value, - .out_point_is_infinite = arg.outputs[2].value, - }); - } else if constexpr (std::is_same_v) { - af.ec_add_constraints.push_back(EcAdd{ - .input1_x = arg.input1[0].witness.value, - .input1_y = arg.input1[1].witness.value, - .input1_infinite = arg.input1[2].witness.value, - .input2_x = arg.input2[0].witness.value, - .input2_y = arg.input2[1].witness.value, - .input2_infinite = arg.input2[2].witness.value, - .result_x = arg.outputs[0].value, - .result_y = arg.outputs[1].value, - .result_infinite = arg.outputs[2].value, - }); - } else if constexpr (std::is_same_v) { - af.keccak_constraints.push_back(KeccakConstraint{ - .inputs = map(arg.inputs, - [](auto& e) { - return HashInput{ - .witness = e.witness.value, - .num_bits = e.num_bits, - }; - }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - .var_message_size = arg.var_message_size.witness.value, - }); - } else if constexpr (std::is_same_v) { - af.keccak_permutations.push_back(Keccakf1600{ - .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - auto c = RecursionConstraint{ - .key = map(arg.verification_key, [](auto& e) { return e.witness.value; }), - .proof = map(arg.proof, [](auto& e) { return e.witness.value; }), - .public_inputs = map(arg.public_inputs, [](auto& e) { return e.witness.value; }), - .key_hash = arg.key_hash.witness.value, - }; - af.recursion_constraints.push_back(c); - } else if constexpr (std::is_same_v) { - af.bigint_from_le_bytes_constraints.push_back(BigIntFromLeBytes{ - .inputs = map(arg.inputs, [](auto& e) { return e.witness.value; }), - .modulus = map(arg.modulus, [](auto& e) -> uint32_t { return e; }), - .result = arg.output, - }); - } else if constexpr (std::is_same_v) { - af.bigint_to_le_bytes_constraints.push_back(BigIntToLeBytes{ - .input = arg.input, - .result = map(arg.outputs, [](auto& e) { return e.value; }), - }); - } else if constexpr (std::is_same_v) { - af.bigint_operations.push_back(BigIntOperation{ - .lhs = arg.lhs, - .rhs = arg.rhs, - .result = arg.output, - .opcode = BigIntOperationType::Add, - }); - } else if constexpr (std::is_same_v) { - af.bigint_operations.push_back(BigIntOperation{ - .lhs = arg.lhs, - .rhs = arg.rhs, - .result = arg.output, - .opcode = BigIntOperationType::Sub, - }); - } else if constexpr (std::is_same_v) { - af.bigint_operations.push_back(BigIntOperation{ - .lhs = arg.lhs, - .rhs = arg.rhs, - .result = arg.output, - .opcode = BigIntOperationType::Mul, - }); - } else if constexpr (std::is_same_v) { - af.bigint_operations.push_back(BigIntOperation{ - .lhs = arg.lhs, - .rhs = arg.rhs, - .result = arg.output, - .opcode = BigIntOperationType::Div, - }); - } else if constexpr (std::is_same_v) { - af.poseidon2_constraints.push_back(Poseidon2Constraint{ - .state = map(arg.inputs, [](auto& e) { return e.witness.value; }), - .result = map(arg.outputs, [](auto& e) { return e.value; }), - .len = arg.len, - }); - } - }, - arg.value.value); -} - -BlockConstraint handle_memory_init(Program::Opcode::MemoryInit const& mem_init) -{ - BlockConstraint block{ .init = {}, .trace = {}, .type = BlockType::ROM }; - std::vector init; - std::vector trace; - - auto len = mem_init.init.size(); - for (size_t i = 0; i < len; ++i) { - block.init.push_back(poly_triple{ - .a = mem_init.init[i].value, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = 1, - .q_r = 0, - .q_o = 0, - .q_c = 0, - }); - } - - // Databus is only supported for Goblin, non Goblin builders will treat call_data and return_data as normal array. - if (IsGoblinUltraBuilder) { - if (std::holds_alternative(mem_init.block_type.value)) { - block.type = BlockType::CallData; - } else if (std::holds_alternative(mem_init.block_type.value)) { - block.type = BlockType::ReturnData; - } - } - return block; -} - -bool is_rom(Program::MemOp const& mem_op) -{ - return mem_op.operation.mul_terms.size() == 0 && mem_op.operation.linear_combinations.size() == 0 && - uint256_t(mem_op.operation.q_c) == 0; -} - -void handle_memory_op(Program::Opcode::MemoryOp const& mem_op, BlockConstraint& block) -{ - uint8_t access_type = 1; - if (is_rom(mem_op.op)) { - access_type = 0; - } - if (access_type == 1) { - // We are not allowed to write on the databus - ASSERT((block.type != BlockType::CallData) && (block.type != BlockType::ReturnData)); - block.type = BlockType::RAM; - } - - MemOp acir_mem_op = MemOp{ .access_type = access_type, - .index = serialize_arithmetic_gate(mem_op.op.index), - .value = serialize_arithmetic_gate(mem_op.op.value) }; - block.trace.push_back(acir_mem_op); -} - -AcirFormat circuit_serde_to_acir_format(Program::Circuit const& circuit) -{ - AcirFormat af; - // `varnum` is the true number of variables, thus we add one to the index which starts at zero - af.varnum = circuit.current_witness_index + 1; - af.recursive = circuit.recursive; - af.num_acir_opcodes = static_cast(circuit.opcodes.size()); - af.public_inputs = join({ map(circuit.public_parameters.value, [](auto e) { return e.value; }), - map(circuit.return_values.value, [](auto e) { return e.value; }) }); - std::map block_id_to_block_constraint; - for (auto gate : circuit.opcodes) { - std::visit( - [&](auto&& arg) { - using T = std::decay_t; - if constexpr (std::is_same_v) { - handle_arithmetic(arg, af); - } else if constexpr (std::is_same_v) { - handle_blackbox_func_call(arg, af); - } else if constexpr (std::is_same_v) { - auto block = handle_memory_init(arg); - uint32_t block_id = arg.block_id.value; - block_id_to_block_constraint[block_id] = block; - } else if constexpr (std::is_same_v) { - auto block = block_id_to_block_constraint.find(arg.block_id.value); - if (block == block_id_to_block_constraint.end()) { - throw_or_abort("unitialized MemoryOp"); - } - handle_memory_op(arg, block->second); - } - }, - gate.value); - } - for (const auto& [block_id, block] : block_id_to_block_constraint) { - if (!block.trace.empty()) { - af.block_constraints.push_back(block); - } - } - return af; -} - -AcirFormat circuit_buf_to_acir_format(std::vector const& buf) -{ - // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `program_buf_to_acir_format` - // once Honk fully supports all ACIR test flows - // For now the backend still expects to work with a single ACIR function - auto circuit = Program::Program::bincodeDeserialize(buf).functions[0]; - - return circuit_serde_to_acir_format(circuit); -} - -/** - * @brief Converts from the ACIR-native `WitnessMap` format to Barretenberg's internal `WitnessVector` format. - * - * @param witness_map ACIR-native `WitnessMap` deserialized from a buffer - * @return A `WitnessVector` equivalent to the passed `WitnessMap`. - * @note This transformation results in all unassigned witnesses within the `WitnessMap` being assigned the value 0. - * Converting the `WitnessVector` back to a `WitnessMap` is unlikely to return the exact same `WitnessMap`. - */ -WitnessVector witness_map_to_witness_vector(WitnessStack::WitnessMap const& witness_map) -{ - WitnessVector wv; - size_t index = 0; - for (auto& e : witness_map.value) { - // ACIR uses a sparse format for WitnessMap where unused witness indices may be left unassigned. - // To ensure that witnesses sit at the correct indices in the `WitnessVector`, we fill any indices - // which do not exist within the `WitnessMap` with the dummy value of zero. - while (index < e.first.value) { - wv.push_back(bb::fr(0)); - index++; - } - wv.push_back(bb::fr(uint256_t(e.second))); - index++; - } - return wv; -} +AcirFormat circuit_buf_to_acir_format(std::vector const& buf); /** * @brief Converts from the ACIR-native `WitnessMap` format to Barretenberg's internal `WitnessVector` format. * * @param buf Serialized representation of a `WitnessMap`. - * @return A `WitnessVector` equivalent to the passed `WitnessMap`. + * @return A `WitnessVector` equivalent to the passed `WitnessMap`.xo * @note This transformation results in all unassigned witnesses within the `WitnessMap` being assigned the value 0. * Converting the `WitnessVector` back to a `WitnessMap` is unlikely to return the exact same `WitnessMap`. */ -WitnessVector witness_buf_to_witness_data(std::vector const& buf) -{ - // TODO(https://github.com/AztecProtocol/barretenberg/issues/927): Move to using just `witness_buf_to_witness_stack` - // once Honk fully supports all ACIR test flows. - // For now the backend still expects to work with the stop of the `WitnessStack`. - auto witness_stack = WitnessStack::WitnessStack::bincodeDeserialize(buf); - auto w = witness_stack.stack[witness_stack.stack.size() - 1].witness; - - return witness_map_to_witness_vector(w); -} - -std::vector program_buf_to_acir_format(std::vector const& buf) -{ - auto program = Program::Program::bincodeDeserialize(buf); - - std::vector constraint_systems; - constraint_systems.reserve(program.functions.size()); - for (auto const& function : program.functions) { - constraint_systems.emplace_back(circuit_serde_to_acir_format(function)); - } +WitnessVector witness_buf_to_witness_data(std::vector const& buf); - return constraint_systems; -} +std::vector program_buf_to_acir_format(std::vector const& buf); -WitnessVectorStack witness_buf_to_witness_stack(std::vector const& buf) -{ - auto witness_stack = WitnessStack::WitnessStack::bincodeDeserialize(buf); - WitnessVectorStack witness_vector_stack; - witness_vector_stack.reserve(witness_stack.stack.size()); - for (auto const& stack_item : witness_stack.stack) { - witness_vector_stack.emplace_back( - std::make_pair(stack_item.index, witness_map_to_witness_vector(stack_item.witness))); - } - return witness_vector_stack; -} +WitnessVectorStack witness_buf_to_witness_stack(std::vector const& buf); -} // namespace acir_format +#ifndef __wasm__ +AcirProgramStack get_acir_program_stack(std::string const& bytecode_path, std::string const& witness_path); +#endif +} // namespace acir_format \ No newline at end of file diff --git a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp index 7477343ec1e..fa1808adf9c 100644 --- a/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp +++ b/barretenberg/cpp/src/barretenberg/plonk_honk_shared/arithmetization/arithmetization.hpp @@ -187,13 +187,12 @@ template class UltraArith { void summarize() const { info("Gate blocks summary:"); - info("pub inputs:\t", pub_inputs.size()); - info("arithmetic:\t", arithmetic.size()); + info("pub inputs :\t", pub_inputs.size()); + info("arithmetic :\t", arithmetic.size()); info("delta range:\t", delta_range.size()); - info("elliptic:\t", elliptic.size()); - info("auxiliary:\t", aux.size()); - info("lookups:\t", lookup.size()); - info(""); + info("elliptic :\t", elliptic.size()); + info("auxiliary :\t", aux.size()); + info("lookups :\t", lookup.size()); } size_t get_total_structured_size() @@ -349,16 +348,16 @@ template class UltraHonkArith { void summarize() const { info("Gate blocks summary: (actual gates / fixed capacity)"); - info("goblin ecc op:\t", ecc_op.size(), "/", ecc_op.get_fixed_size()); - info("pub inputs:\t", pub_inputs.size(), "/", pub_inputs.get_fixed_size()); - info("arithmetic:\t", arithmetic.size(), "/", arithmetic.get_fixed_size()); - info("delta range:\t", delta_range.size(), "/", delta_range.get_fixed_size()); - info("elliptic:\t", elliptic.size(), "/", elliptic.get_fixed_size()); - info("auxiliary:\t", aux.size(), "/", aux.get_fixed_size()); - info("lookups:\t", lookup.size(), "/", lookup.get_fixed_size()); - info("busread:\t", busread.size(), "/", busread.get_fixed_size()); - info("poseidon ext:\t", poseidon_external.size(), "/", poseidon_external.get_fixed_size()); - info("poseidon int:\t", poseidon_internal.size(), "/", poseidon_internal.get_fixed_size()); + info("goblin ecc op :\t", ecc_op.size(), "/", ecc_op.get_fixed_size()); + info("pub inputs :\t", pub_inputs.size(), "/", pub_inputs.get_fixed_size()); + info("arithmetic :\t", arithmetic.size(), "/", arithmetic.get_fixed_size()); + info("delta range :\t", delta_range.size(), "/", delta_range.get_fixed_size()); + info("elliptic :\t", elliptic.size(), "/", elliptic.get_fixed_size()); + info("auxiliary :\t", aux.size(), "/", aux.get_fixed_size()); + info("lookups :\t", lookup.size(), "/", lookup.get_fixed_size()); + info("busread :\t", busread.size(), "/", busread.get_fixed_size()); + info("poseidon ext :\t", poseidon_external.size(), "/", poseidon_external.get_fixed_size()); + info("poseidon int :\t", poseidon_internal.size(), "/", poseidon_internal.get_fixed_size()); info(""); }