From 69eba2d0cd4df1e1cf9446be0c40b6f74900e74a Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Thu, 13 Apr 2023 17:29:41 +0200 Subject: [PATCH 01/64] Added recursion constraint into dsl + tests --- .../dsl/acir_format/acir_format.cpp | 23 +++ .../dsl/acir_format/acir_format.hpp | 3 + .../dsl/acir_format/recursion_constraint.cpp | 77 ++++++++ .../dsl/acir_format/recursion_constraint.hpp | 64 +++++++ .../acir_format/recursion_constraint.test.cpp | 172 ++++++++++++++++++ 5 files changed, 339 insertions(+) create mode 100644 cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp create mode 100644 cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp create mode 100644 cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index c702ec980a..2b7b9526b6 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -86,6 +86,11 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) for (const auto& constraint : constraint_system.hash_to_field_constraints) { create_hash_to_field_constraints(composer, constraint); } + + // Add recursion constraints + for (const auto& constraint : constraint_system.recursion_constraints) { + create_recursion_constraints(composer, constraint); + } } Composer create_circuit(const acir_format& constraint_system, @@ -165,6 +170,11 @@ Composer create_circuit(const acir_format& constraint_system, create_hash_to_field_constraints(composer, constraint); } + // Add recursion constraints + for (const auto& constraint : constraint_system.recursion_constraints) { + create_recursion_constraints(composer, constraint); + } + return composer; } @@ -249,6 +259,10 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, create_hash_to_field_constraints(composer, constraint); } + // Add recursion constraints + for (const auto& constraint : constraint_system.recursion_constraints) { + create_recursion_constraints(composer, constraint); + } return composer; } Composer create_circuit_with_witness(const acir_format& constraint_system, std::vector witness) @@ -330,6 +344,10 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: create_hash_to_field_constraints(composer, constraint); } + // Add recursion constraints + for (const auto& constraint : constraint_system.recursion_constraints) { + create_recursion_constraints(composer, constraint); + } return composer; } void create_circuit_with_witness(Composer& composer, const acir_format& constraint_system, std::vector witness) @@ -408,6 +426,11 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai for (const auto& constraint : constraint_system.hash_to_field_constraints) { create_hash_to_field_constraints(composer, constraint); } + + // Add recursion constraints + for (const auto& constraint : constraint_system.recursion_constraints) { + create_recursion_constraints(composer, constraint); + } } } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 55d5ee969e..12592c6a32 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -7,6 +7,7 @@ #include "schnorr_verify.hpp" #include "ecdsa_secp256k1.hpp" #include "merkle_membership_constraint.hpp" +#include "recursion_constraint.hpp" #include "pedersen.hpp" #include "hash_to_field.hpp" #include "barretenberg/stdlib/types/types.hpp" @@ -29,6 +30,8 @@ struct acir_format { std::vector hash_to_field_constraints; std::vector pedersen_constraints; std::vector merkle_membership_constraints; + std::vector recursion_constraints; + // A standard plonk arithmetic constraint, as defined in the poly_triple struct, consists of selector values // for q_M,q_L,q_R,q_O,q_C and indices of three variables taking the role of left, right and output wire std::vector constraints; diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp new file mode 100644 index 0000000000..b6abe2e5fc --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -0,0 +1,77 @@ +#include "recursion_constraint.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" +#include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" +#include "barretenberg/stdlib/recursion/verifier/verifier.hpp" + +using namespace proof_system::plonk::stdlib::types; + +using verification_key_ct = proof_system::plonk::stdlib::recursion::verification_key; +using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation_state; +using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; +namespace acir_format { + +/** + * @brief Add constraints required to recursively verify an UltraPlonk proof + * + * @param composer + * @param input + */ +void create_recursion_constraints(Composer& composer, const RecursionConstraint& input) +{ + // The env_crs should be ok, we use this when generating the constraint system in dsl + auto env_crs = std::make_unique(); + + // Construct an in-circuit representation of the verification key. + // For now, the v-key is a circuit constant and is fixed for the circuit. + // (We may need a separate recursion opcode for this to vary, or add more config witnesses to this opcode) + auto data_clone = input.verification_key_data; + std::shared_ptr key = + std::make_shared(std::move(data_clone), env_crs->get_verifier_crs()); + std::shared_ptr circuit_key = verification_key_ct::from_constants(&composer, key); + const auto& aggregation_input = input.input_aggregation_object; + aggregation_state_ct previous_aggregation; + + // If we have previously recursively verified proofs, `is_aggregation_object_nonzero = true` + // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! + if (input.is_aggregation_object_nonzero) { + std::array aggregation_elements; + for (size_t i = 0; i < 4; ++i) { + aggregation_elements[i] = + bn254::fq_ct(field_ct::from_witness_index(&composer, aggregation_input[4 * i]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 1]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 2]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included + // in stdlib::recursion::verify_proof + previous_aggregation.P0 = bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); + previous_aggregation.P1 = bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); + previous_aggregation.has_data = true; + } else { + previous_aggregation.has_data = false; + } + + transcript::Manifest manifest = Composer::create_unrolled_manifest(1); + + // recursively verify the proof + aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof( + &composer, circuit_key, manifest, input.proof, previous_aggregation); + + // Assign the output aggregation object to the proof public inputs (16 field elements representing two G1 points) + result.add_proof_outputs_as_public_inputs(); + + ASSERT(result.public_inputs.size() == 1); + + // Assign the `public_input` field to the public input of the inner proof + result.public_inputs[0].assert_equal(field_ct::from_witness_index(&composer, input.public_input)); + + // Assign the recursive proof outputs to `output_aggregation_object` + for (size_t i = 0; i < result.proof_witness_indices.size(); ++i) { + const auto lhs = field_ct::from_witness_index(&composer, result.proof_witness_indices[i]); + const auto rhs = field_ct::from_witness_index(&composer, input.output_aggregation_object[i]); + lhs.assert_equal(rhs); + } +} + +} // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp new file mode 100644 index 0000000000..c67dacb10f --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -0,0 +1,64 @@ +#pragma once +#include +#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/plonk/proof_system//verification_key/verification_key.hpp" + +namespace acir_format { + +/** + * @brief RecursionConstraint struct contains information required to recursively verify a proof! + * + * @details The recursive verifier algorithm produces an 'aggregation object' representing 2 G1 points, expressed as 16 + * witness values. The smart contract Verifier must be aware of this aggregation object in order to complete the full + * recursive verification. If the circuit verifies more than 1 proof, the recursion algorithm will update a pre-existing + * aggregation object (`input_aggregation_object`). + * + * @details We currently require that the inner circuit being verified only has a single public input. If more are + * required, the outer circuit can hash them down to 1 input. + * + * @param verification_key_data The inner circuit vkey. Is converted into circuit witness values (internal to the + * backend) + * @param proof The plonk proof. Is converted into circuit witness values (internal to the backend) + * @param is_aggregation_object_nonzero A flag to tell us whether the circuit has already recursively verified proofs + * (and therefore an aggregation object is present) + * @param public_input The index of the single public input + * @param input_aggregation_object Witness indices of pre-existing aggregation object (if it exists) + * @param output_aggregation_object Witness indecies of the aggregation object produced by recursive verification + */ +struct RecursionConstraint { + static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements + plonk::verification_key_data verification_key_data; + plonk::proof proof; + bool is_aggregation_object_nonzero; + uint32_t public_input; + std::array input_aggregation_object; + std::array output_aggregation_object; + + friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; +}; + +void create_recursion_constraints(plonk::stdlib::types::Composer& composer, const RecursionConstraint& input); + +template inline void read(B& buf, RecursionConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.verification_key_data); + read(buf, constraint.proof); + read(buf, constraint.is_aggregation_object_nonzero); + read(buf, constraint.public_input); + read(buf, constraint.input_aggregation_object); + read(buf, constraint.output_aggregation_object); +} + +template inline void write(B& buf, RecursionConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.verification_key_data); + write(buf, constraint.proof); + write(buf, constraint.is_aggregation_object_nonzero); + write(buf, constraint.public_input); + write(buf, constraint.input_aggregation_object); + write(buf, constraint.output_aggregation_object); +} + +} // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp new file mode 100644 index 0000000000..911e36c8eb --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -0,0 +1,172 @@ +#include "acir_format.hpp" +#include "recursion_constraint.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" + +#include +#include + +using namespace proof_system::plonk::stdlib::types; + +Composer create_inner_circuit() +{ + /** + * constraints produced by Noir program: + * fn main(x : u32, y : pub u32) { + * let z = x ^ y; + * + * constrain z != 10; + * } + **/ + acir_format::RangeConstraint range_a{ + .witness = 1, + .num_bits = 32, + }; + acir_format::RangeConstraint range_b{ + .witness = 2, + .num_bits = 32, + }; + + acir_format::LogicConstraint logic_constraint{ + .a = 1, + .b = 2, + .result = 3, + .num_bits = 32, + .is_xor_gate = 1, + }; + poly_triple expr_a{ + .a = 3, + .b = 4, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = 0, + .q_c = -10, + }; + poly_triple expr_b{ + .a = 4, + .b = 5, + .c = 6, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }; + poly_triple expr_c{ + .a = 4, + .b = 6, + .c = 4, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + + }; + poly_triple expr_d{ + .a = 6, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = -1, + .q_r = 0, + .q_o = 0, + .q_c = 1, + }; + // EXPR [ (1, _4, _5) (-1, _6) 0 ] + // EXPR [ (1, _4, _6) (-1, _4) 0 ] + // EXPR [ (-1, _6) 1 ] + + acir_format::acir_format constraint_system{ + .varnum = 7, + .public_inputs = { 2 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .constraints = { expr_a, expr_b, expr_c, expr_d }, + }; + + uint256_t inverse_of_five = fr(5).invert(); + auto composer = acir_format::create_circuit_with_witness(constraint_system, + { + 5, + 10, + 15, + 5, + inverse_of_five, + 1, + }); + + return composer; +} + +TEST(RecursionConstraint, TestRecursionConstraint) +{ + auto inner_composer = create_inner_circuit(); + + auto inner_prover = inner_composer.create_prover(); + auto inner_proof = inner_prover.construct_proof(); + auto inner_verifier = inner_composer.create_verifier(); + + std::vector keybuf; + write(keybuf, *(inner_verifier.key)); + + std::array output_vars; + for (size_t i = 0; i < 16; ++i) { + // variable idx 1 = public input + // variable idx 2-18 = output_vars + output_vars[i] = (static_cast(i + 2)); + } + verification_key_data keydata; + uint8_t const* vk_buf = &keybuf[0]; + read(vk_buf, keydata); + + acir_format::RecursionConstraint recursion_constraint{ + .verification_key_data = keydata, + .proof = inner_proof, + .is_aggregation_object_nonzero = false, + .public_input = 1, + .input_aggregation_object = {}, + .output_aggregation_object = output_vars, + }; + + std::vector witness; + for (size_t i = 0; i < 18; ++i) { + witness.emplace_back(0); + } + + acir_format::acir_format constraint_system{ + .varnum = 18, + .public_inputs = { 1 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .recursion_constraints = { recursion_constraint }, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); + auto prover = composer.create_prover(); + + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); + + EXPECT_EQ(composer.get_variable(1), 10); +} From 7a80056b0e7ae8e150c719295d83f0c1bea7d614 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Fri, 14 Apr 2023 00:15:35 +0200 Subject: [PATCH 02/64] fix dsl ecdsa constraint. Added ecdsa dsl tests --- .../dsl/acir_format/ecdsa_secp256k1.cpp | 23 ++- .../dsl/acir_format/ecdsa_secp256k1.test.cpp | 145 ++++++++++++++++++ .../stdlib/encryption/ecdsa/ecdsa.hpp | 5 + .../stdlib/encryption/ecdsa/ecdsa.test.cpp | 87 +++++++++++ .../stdlib/encryption/ecdsa/ecdsa_impl.hpp | 87 ++++++++++- 5 files changed, 338 insertions(+), 9 deletions(-) create mode 100644 cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp index d81a670cb0..291245d3d5 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp @@ -102,17 +102,24 @@ void create_ecdsa_verify_constraints(Composer& composer, const EcdsaSecp256k1Con pub_key_x_fq.assert_is_in_field(); pub_key_y_fq.assert_is_in_field(); - secp256k1_ct::g1_bigfr_ct public_key = secp256k1_ct::g1_bigfr_ct(pub_key_x_fq, pub_key_y_fq); + for (size_t i = 0; i < 32; ++i) { + sig.r[i].assert_equal(field_ct::from_witness_index(&composer, input.signature[i])); + sig.s[i].assert_equal(field_ct::from_witness_index(&composer, input.signature[i + 32])); + pub_key_x_byte_arr[i].assert_equal(field_ct::from_witness_index(&composer, input.pub_x_indices[i])); + pub_key_y_byte_arr[i].assert_equal(field_ct::from_witness_index(&composer, input.pub_y_indices[i])); + } + for (size_t i = 0; i < input.message.size(); ++i) { + message[i].assert_equal(field_ct::from_witness_index(&composer, input.message[i])); + } - bool_ct signature_result = stdlib::ecdsa::verify_signature(message, public_key, sig); - + bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert(message, public_key, sig); bool_ct signature_result_normalized = signature_result.normalize(); - composer.assert_equal(signature_result_normalized.witness_index, input.result); } diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp new file mode 100644 index 0000000000..57f9af0a1c --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -0,0 +1,145 @@ +#include "acir_format.hpp" +#include "ecdsa_secp256k1.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" +#include "barretenberg/crypto/ecdsa/ecdsa.hpp" + +#include +#include + +using namespace proof_system::plonk::stdlib::types; +using curve = stdlib::secp256k1; + +size_t generate_ecdsa_constraint(acir_format::EcdsaSecp256k1Constraint& ecdsa_constraint, + std::vector& witness_values) +{ + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + uint256_t pub_x_value = account.public_key.x; + uint256_t pub_y_value = account.public_key.y; + + std::vector message_in; + std::vector pub_x_indices_in; + std::vector pub_y_indices_in; + std::vector signature_in; + size_t offset = 1; + for (size_t i = 0; i < message_string.size(); ++i) { + message_in.emplace_back(i + offset); + const auto byte = static_cast(message_string[i]); + witness_values.emplace_back(byte); + } + offset += message_in.size(); + + for (size_t i = 0; i < 32; ++i) { + pub_x_indices_in.emplace_back(i + offset); + witness_values.emplace_back(pub_x_value.slice(248 - i * 8, 256 - i * 8)); + } + offset += pub_x_indices_in.size(); + for (size_t i = 0; i < 32; ++i) { + pub_y_indices_in.emplace_back(i + offset); + witness_values.emplace_back(pub_y_value.slice(248 - i * 8, 256 - i * 8)); + } + offset += pub_y_indices_in.size(); + for (size_t i = 0; i < 32; ++i) { + signature_in.emplace_back(i + offset); + witness_values.emplace_back(signature.r[i]); + } + offset += signature.r.size(); + for (size_t i = 0; i < 32; ++i) { + signature_in.emplace_back(i + offset); + witness_values.emplace_back(signature.s[i]); + } + offset += signature.s.size(); + + witness_values.emplace_back(1); + const auto result_in = static_cast(offset); + offset += 1; + witness_values.emplace_back(1); + + ecdsa_constraint = acir_format::EcdsaSecp256k1Constraint{ + .message = message_in, + .pub_x_indices = pub_x_indices_in, + .pub_y_indices = pub_y_indices_in, + .result = result_in, + .signature = signature_in, + }; + return offset; +} + +TEST(ECDSASecp256k1, TestECDSAConstraintSucceed) +{ + acir_format::EcdsaSecp256k1Constraint ecdsa_constraint; + std::vector witness_values; + size_t num_variables = generate_ecdsa_constraint(ecdsa_constraint, witness_values); + acir_format::acir_format constraint_system{ + .varnum = static_cast(num_variables), + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = { ecdsa_constraint }, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .recursion_constraints = {}, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness_values); + + EXPECT_EQ(composer.get_variable(ecdsa_constraint.result), 1); + auto prover = composer.create_prover(); + + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} + +TEST(ECDSASecp256k1, TestECDSAConstraintFail) +{ + acir_format::EcdsaSecp256k1Constraint ecdsa_constraint; + std::vector witness_values; + size_t num_variables = generate_ecdsa_constraint(ecdsa_constraint, witness_values); + + // set result value to be false + witness_values[witness_values.size() - 1] = 0; + + // tamper with signature + witness_values[witness_values.size() - 20] += 1; + + acir_format::acir_format constraint_system{ + .varnum = static_cast(num_variables), + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = { ecdsa_constraint }, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .recursion_constraints = {}, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness_values); + + EXPECT_EQ(composer.get_variable(ecdsa_constraint.result), 0); + auto prover = composer.create_prover(); + + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp index 1864557af4..e4fa1b217a 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp @@ -18,6 +18,11 @@ bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, const signature& sig); +template +bool_t verify_signature_noassert(const stdlib::byte_array& message, + const G1& public_key, + const signature& sig); + template static signature from_witness(Composer* ctx, const crypto::ecdsa::signature& input) { diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp index b1edc24d84..91101df01e 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp @@ -54,4 +54,91 @@ TEST(stdlib_ecdsa, verify_signature) bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, true); } + +TEST(stdlib_ecdsa, verify_signature_noassert_succeed) +{ + Composer composer = Composer(); + + // whaaablaghaaglerijgeriij + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + bool first_result = crypto::ecdsa::verify_signature( + message_string, account.public_key, signature); + EXPECT_EQ(first_result, true); + + curve::g1_bigfr_ct public_key = curve::g1_bigfr_ct::from_witness(&composer, account.public_key); + + std::vector rr(signature.r.begin(), signature.r.end()); + std::vector ss(signature.s.begin(), signature.s.end()); + + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; + + curve::byte_array_ct message(&composer, message_string); + + curve::bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert( + message, public_key, sig); + + EXPECT_EQ(signature_result.get_value(), true); + + std::cerr << "composer gates = " << composer.get_num_gates() << std::endl; + benchmark_info("UltraComposer", "ECDSA", "Signature Verification Test", "Gate Count", composer.get_num_gates()); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} + +TEST(stdlib_ecdsa, verify_signature_noassert_fail) +{ + Composer composer = Composer(); + + // whaaablaghaaglerijgeriij + std::string message_string = "Instructions unclear, ask again later."; + + crypto::ecdsa::key_pair account; + account.private_key = curve::fr::random_element(); + account.public_key = curve::g1::one * account.private_key; + + crypto::ecdsa::signature signature = + crypto::ecdsa::construct_signature(message_string, account); + + // tamper w. signature to make fail + signature.r[0] += 1; + + bool first_result = crypto::ecdsa::verify_signature( + message_string, account.public_key, signature); + EXPECT_EQ(first_result, false); + + curve::g1_bigfr_ct public_key = curve::g1_bigfr_ct::from_witness(&composer, account.public_key); + + std::vector rr(signature.r.begin(), signature.r.end()); + std::vector ss(signature.s.begin(), signature.s.end()); + + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; + + curve::byte_array_ct message(&composer, message_string); + + curve::bool_ct signature_result = + stdlib::ecdsa::verify_signature_noassert( + message, public_key, sig); + + EXPECT_EQ(signature_result.get_value(), false); + + std::cerr << "composer gates = " << composer.get_num_gates() << std::endl; + benchmark_info("UltraComposer", "ECDSA", "Signature Verification Test", "Gate Count", composer.get_num_gates()); + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool proof_result = verifier.verify_proof(proof); + EXPECT_EQ(proof_result, true); +} } // namespace test_stdlib_ecdsa diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp index 12355453d6..411afa2c1d 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa_impl.hpp @@ -6,6 +6,19 @@ namespace proof_system::plonk { namespace stdlib { namespace ecdsa { +/** + * @brief Verify ECDSA signature. Produces unsatisfiable constraints if signature fails + * + * @tparam Composer + * @tparam Curve + * @tparam Fq + * @tparam Fr + * @tparam G1 + * @param message + * @param public_key + * @param sig + * @return bool_t + */ template bool_t verify_signature(const stdlib::byte_array& message, const G1& public_key, @@ -62,10 +75,82 @@ bool_t verify_signature(const stdlib::byte_array& message, result_mod_r.binary_basis_limbs[2].element.assert_equal(r.binary_basis_limbs[2].element); result_mod_r.binary_basis_limbs[3].element.assert_equal(r.binary_basis_limbs[3].element); result_mod_r.prime_basis_limb.assert_equal(r.prime_basis_limb); - return bool_t(ctx, true); } +/** + * @brief Verify ECDSA signature. Returns 0 if signature fails (i.e. does not produce unsatisfiable constraints) + * + * @tparam Composer + * @tparam Curve + * @tparam Fq + * @tparam Fr + * @tparam G1 + * @param message + * @param public_key + * @param sig + * @return bool_t + */ +template +bool_t verify_signature_noassert(const stdlib::byte_array& message, + const G1& public_key, + const signature& sig) +{ + Composer* ctx = message.get_context() ? message.get_context() : public_key.x.context; + + stdlib::byte_array hashed_message = + static_cast>(stdlib::sha256(message)); + + Fr z(hashed_message); + z.assert_is_in_field(); + + Fr r(sig.r); + // force r to be < secp256k1 group modulus, so we can compare with `result_mod_r` below + r.assert_is_in_field(); + + Fr s(sig.s); + + // r and s should not be zero + r.assert_is_not_equal(Fr::zero()); + s.assert_is_not_equal(Fr::zero()); + + Fr u1 = z / s; + Fr u2 = r / s; + + G1 result; + if constexpr (Composer::type == ComposerType::PLOOKUP) { + ASSERT(Curve::type == proof_system::CurveType::SECP256K1); + public_key.validate_on_curve(); + result = G1::secp256k1_ecdsa_mul(public_key, u1, u2); + } else { + result = G1::batch_mul({ G1::one(ctx), public_key }, { u1, u2 }); + } + result.x.self_reduce(); + + // transfer Fq value x to an Fr element and reduce mod r + Fr result_mod_r(ctx, 0); + result_mod_r.binary_basis_limbs[0].element = result.x.binary_basis_limbs[0].element; + result_mod_r.binary_basis_limbs[1].element = result.x.binary_basis_limbs[1].element; + result_mod_r.binary_basis_limbs[2].element = result.x.binary_basis_limbs[2].element; + result_mod_r.binary_basis_limbs[3].element = result.x.binary_basis_limbs[3].element; + result_mod_r.binary_basis_limbs[0].maximum_value = result.x.binary_basis_limbs[0].maximum_value; + result_mod_r.binary_basis_limbs[1].maximum_value = result.x.binary_basis_limbs[1].maximum_value; + result_mod_r.binary_basis_limbs[2].maximum_value = result.x.binary_basis_limbs[2].maximum_value; + result_mod_r.binary_basis_limbs[3].maximum_value = result.x.binary_basis_limbs[3].maximum_value; + + result_mod_r.prime_basis_limb = result.x.prime_basis_limb; + + result_mod_r.assert_is_in_field(); + + bool_t output(ctx, true); + output &= result_mod_r.binary_basis_limbs[0].element == (r.binary_basis_limbs[0].element); + output &= result_mod_r.binary_basis_limbs[1].element == (r.binary_basis_limbs[1].element); + output &= result_mod_r.binary_basis_limbs[2].element == (r.binary_basis_limbs[2].element); + output &= result_mod_r.binary_basis_limbs[3].element == (r.binary_basis_limbs[3].element); + output &= result_mod_r.prime_basis_limb == (r.prime_basis_limb); + return output; +} + } // namespace ecdsa } // namespace stdlib } // namespace proof_system::plonk \ No newline at end of file From 2436dcf59516a1e49286ba602236dbe5016c8b67 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Sun, 16 Apr 2023 11:15:25 +0200 Subject: [PATCH 03/64] changed dsl recursion to pass proof/key as witnesses --- .../dsl/acir_format/recursion_constraint.cpp | 16 +++-- .../dsl/acir_format/recursion_constraint.hpp | 8 +-- .../acir_format/recursion_constraint.test.cpp | 42 ++++++++++--- .../dsl/acir_proofs/acir_proofs.cpp | 12 ++++ .../verification_key/verification_key.cpp | 60 +++++++++++++++++++ .../verification_key/verification_key.hpp | 5 ++ .../recursion/transcript/transcript.hpp | 48 +++++++++++++++ .../verification_key/verification_key.hpp | 57 ++++++++++++++++++ .../stdlib/recursion/verifier/verifier.hpp | 43 ++++++++++++- .../transcript/transcript_wrappers.hpp | 40 +++++++++++++ 10 files changed, 312 insertions(+), 19 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index b6abe2e5fc..373ee3c15a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -24,10 +24,6 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // Construct an in-circuit representation of the verification key. // For now, the v-key is a circuit constant and is fixed for the circuit. // (We may need a separate recursion opcode for this to vary, or add more config witnesses to this opcode) - auto data_clone = input.verification_key_data; - std::shared_ptr key = - std::make_shared(std::move(data_clone), env_crs->get_verifier_crs()); - std::shared_ptr circuit_key = verification_key_ct::from_constants(&composer, key); const auto& aggregation_input = input.input_aggregation_object; aggregation_state_ct previous_aggregation; @@ -53,10 +49,20 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } transcript::Manifest manifest = Composer::create_unrolled_manifest(1); + std::vector key_fields; + key_fields.reserve(input.key.size()); + for (const auto& idx : input.key) { + key_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + } + std::vector proof_fields; + proof_fields.reserve(input.proof.size()); + for (const auto& idx : input.proof) { + proof_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + } // recursively verify the proof aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof( - &composer, circuit_key, manifest, input.proof, previous_aggregation); + &composer, manifest, env_crs->get_verifier_crs(), key_fields, proof_fields, previous_aggregation); // Assign the output aggregation object to the proof public inputs (16 field elements representing two G1 points) result.add_proof_outputs_as_public_inputs(); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index c67dacb10f..0e794f3f73 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -27,8 +27,8 @@ namespace acir_format { */ struct RecursionConstraint { static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements - plonk::verification_key_data verification_key_data; - plonk::proof proof; + std::vector key; + std::vector proof; bool is_aggregation_object_nonzero; uint32_t public_input; std::array input_aggregation_object; @@ -42,7 +42,7 @@ void create_recursion_constraints(plonk::stdlib::types::Composer& composer, cons template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; - read(buf, constraint.verification_key_data); + read(buf, constraint.key); read(buf, constraint.proof); read(buf, constraint.is_aggregation_object_nonzero); read(buf, constraint.public_input); @@ -53,7 +53,7 @@ template inline void read(B& buf, RecursionConstraint& constraint) template inline void write(B& buf, RecursionConstraint const& constraint) { using serialize::write; - write(buf, constraint.verification_key_data); + write(buf, constraint.key); write(buf, constraint.proof); write(buf, constraint.is_aggregation_object_nonzero); write(buf, constraint.public_input); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 911e36c8eb..7154fdd23e 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -117,8 +117,8 @@ TEST(RecursionConstraint, TestRecursionConstraint) auto inner_proof = inner_prover.construct_proof(); auto inner_verifier = inner_composer.create_verifier(); - std::vector keybuf; - write(keybuf, *(inner_verifier.key)); + // std::vector keybuf; + // write(keybuf, *(inner_verifier.key)); std::array output_vars; for (size_t i = 0; i < 16; ++i) { @@ -126,13 +126,31 @@ TEST(RecursionConstraint, TestRecursionConstraint) // variable idx 2-18 = output_vars output_vars[i] = (static_cast(i + 2)); } - verification_key_data keydata; - uint8_t const* vk_buf = &keybuf[0]; - read(vk_buf, keydata); + // verification_key_data keydata; + // uint8_t const* vk_buf = &keybuf[0]; + // read(vk_buf, keydata); + transcript::StandardTranscript transcript( + inner_proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + + const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); + const std::vector key_witnesses = inner_verifier.key->export_transcript_in_recursion_format(); + + std::vector proof_indices; + const size_t proof_size = proof_witnesses.size(); + + for (size_t i = 0; i < proof_size; ++i) { + proof_indices.emplace_back(static_cast(i + 18)); + } + + std::vector key_indices; + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + 18 + proof_size)); + } acir_format::RecursionConstraint recursion_constraint{ - .verification_key_data = keydata, - .proof = inner_proof, + .key = key_indices, + .proof = proof_indices, .is_aggregation_object_nonzero = false, .public_input = 1, .input_aggregation_object = {}, @@ -140,12 +158,18 @@ TEST(RecursionConstraint, TestRecursionConstraint) }; std::vector witness; - for (size_t i = 0; i < 18; ++i) { + for (size_t i = 0; i < 17; ++i) { witness.emplace_back(0); } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } acir_format::acir_format constraint_system{ - .varnum = 18, + .varnum = static_cast(witness.size() + 1), .public_inputs = { 1 }, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = {}, diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 21585f6df4..9b899aa69f 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -93,6 +93,18 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* return buffer.size(); } +// void serialize_proof_into_field_elements(uint8_t** proof_data_buf) +// { +// } + +// void serialize_verification_key_into_field_elements(uint8_t** vk_buf) +// { +// plonk::verification_key key; +// read(vk_buf, key); + +// // do the serialize thing... +// } + size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index f6cf92bd73..56f4a52cf5 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -107,6 +107,31 @@ verification_key::verification_key(const size_t num_gates, , polynomial_manifest(composer_type) {} +verification_key::verification_key(const std::vector& key_as_fields, + std::shared_ptr const& crs, + uint32_t composer_type_) +{ + composer_type = composer_type_; + polynomial_manifest = PolynomialManifest(composer_type); + reference_string = crs; + // std::cout << "key as field = " << key_as_fields[3] << std::endl; + // for (const auto& f : key_as_fields) { + // std::cout << "f : " << f << std::endl; + // } + circuit_size = static_cast(uint256_t(key_as_fields[3])); + + std::cout << "circuit size = " << circuit_size << std::endl; + + log_circuit_size = numeric::get_msb(circuit_size); + num_public_inputs = static_cast(uint256_t(key_as_fields[4])); + contains_recursive_proof = static_cast(uint256_t(key_as_fields[5])); + domain = evaluation_domain(circuit_size); + + // ADD THE COMITMENTS DERP DERP DERP + // AS WELL AS RECURSIVE PROOF PUBLIC INPUT INDICES? + // +} + verification_key::verification_key(verification_key_data&& data, std::shared_ptr const& crs) : composer_type(data.composer_type) , circuit_size(data.circuit_size) @@ -178,4 +203,39 @@ sha256::hash verification_key::sha256_hash() return sha256::sha256(to_buffer(vk_data)); } +std::vector verification_key::export_transcript_in_recursion_format() +{ + std::vector output; + output.emplace_back(domain.root); + output.emplace_back(domain.domain); + output.emplace_back(domain.generator); + output.emplace_back(circuit_size); + output.emplace_back(num_public_inputs); + output.emplace_back(contains_recursive_proof); + + for (auto& commitment_entry : commitments) { + std::cout << "key = " << commitment_entry.first << std::endl; + std::cout << "value = " << commitment_entry.second << std::endl; + } + for (const auto& descriptor : polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + std::cout << "key = " << descriptor.commitment_label << std::endl; + const auto element = commitments.at(std::string(descriptor.commitment_label)); + std::cout << "read" << std::endl; + const uint256_t x = element.x; + const uint256_t y = element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + output.emplace_back(x_lo); + output.emplace_back(x_hi); + output.emplace_back(y_lo); + output.emplace_back(y_hi); + } + } + + return output; +} + } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp index 709eea6ee7..3165ed9923 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp @@ -55,6 +55,10 @@ struct verification_key { const size_t num_inputs, std::shared_ptr const& crs, uint32_t composer_type); + verification_key(const std::vector& key_as_fields, + std::shared_ptr const& crs, + uint32_t composer_type_); + verification_key(const verification_key& other); verification_key(verification_key&& other); verification_key& operator=(verification_key&& other); @@ -62,6 +66,7 @@ struct verification_key { ~verification_key() = default; sha256::hash sha256_hash(); + std::vector export_transcript_in_recursion_format(); uint32_t composer_type; size_t circuit_size; diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index 73055fb376..db3d95c51b 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -48,6 +48,54 @@ template class Transcript { // } } + /** + * @brief Construct a new Transcript object using a proof represented as a field_pt vector + * + * N.B. If proof is represented as a uint8_t vector, Transcript will convert into witnesses in-situ. + * Use this constructor method if the proof is *already present* as circuit witnesses! + * @param in_context + * @param input_manifest + * @param field_buffer + * @param num_public_inputs + */ + Transcript(Composer* in_context, + const transcript::Manifest input_manifest, + const std::vector& field_buffer, + const size_t num_public_inputs) + : context(in_context) + , transcript_base(input_manifest, transcript::HashType::PedersenBlake3s, 16) + , current_challenge(in_context) + { + size_t count = 0; + + const auto num_rounds = input_manifest.get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (auto manifest_element : input_manifest.get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + add_field_element(manifest_element.name, field_buffer[count++]); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto x_lo = field_buffer[count++]; + const auto x_hi = field_buffer[count++]; + const auto y_lo = field_buffer[count++]; + const auto y_hi = field_buffer[count++]; + fq_pt x(x_lo, x_hi); + fq_pt y(y_lo, y_hi); + group_pt element(x, y); + add_group_element(manifest_element.name, element); + } else { + ASSERT(manifest_element.name == "public_inputs"); + std::vector public_inputs; + for (size_t i = 0; i < num_public_inputs; ++i) { + public_inputs.emplace_back(field_buffer[count++]); + } + add_field_element_vector(manifest_element.name, public_inputs); + } + } + } + } + } + transcript::Manifest get_manifest() const { return transcript_base.get_manifest(); } int check_field_element_cache(const std::string& element_name) const diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 57b8a39207..2c982f6074 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -25,6 +25,19 @@ namespace stdlib { namespace recursion { template struct evaluation_domain { + static evaluation_domain from_field_pt_vector(const std::vector>& fields) + { + evaluation_domain domain; + domain.root = fields[0]; + domain.root_inverse = domain.root.invert(); + domain.domain = fields[1]; + domain.domain_inverse = domain.domain.invert(); + domain.generator = fields[2]; + domain.generator_inverse = domain.generator.invert(); + domain.size = domain.domain; + return domain; + } + static evaluation_domain from_witness(Composer* ctx, const barretenberg::evaluation_domain& input) { evaluation_domain domain; @@ -105,6 +118,50 @@ template struct evaluation_domain { */ template struct verification_key { using Composer = typename Curve::Composer; + + static std::shared_ptr from_field_pt_vector(Composer* ctx, + std::shared_ptr const& crs, + const std::vector>& fields) + { + std::vector fields_raw; + for (const auto& f : fields) { + fields_raw.emplace_back(f.get_value()); + std::cout << "f: " << f.get_value() << std::endl; + } + std::cout << "a" << std::endl; + std::shared_ptr key = std::make_shared(); + std::cout << "b" << std::endl; + key->context = ctx; + key->base_key = std::make_shared(fields_raw, crs, Composer::type); + std::cout << "c" << std::endl; + + key->reference_string = key->base_key->reference_string; + key->polynomial_manifest = PolynomialManifest(Composer::type); + key->domain = evaluation_domain::from_field_pt_vector({ fields[0], fields[1], fields[2] }); + + key->n = fields[3]; + key->num_public_inputs = fields[4]; + key->contains_recursive_proof = bool_t(fields[5]); + + size_t count = 6; + for (const auto& descriptor : key->polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + + const auto x_lo = fields[count++]; + const auto x_hi = fields[count++]; + const auto y_lo = fields[count++]; + const auto y_hi = fields[count++]; + const typename Curve::fq_ct x(x_lo, x_hi); + const typename Curve::fq_ct y(y_lo, y_hi); + const typename Curve::g1_ct element(x, y); + + key->commitments.insert({ std::string(descriptor.commitment_label), element }); + } + } + std::cout << "d" << std::endl; + return key; + } + static std::shared_ptr from_witness(Composer* ctx, const std::shared_ptr& input_key) { diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index aa185e34d7..5c59bf9580 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -182,6 +182,48 @@ aggregation_state verify_proof(typename Curve::Composer* context, const transcript::Manifest& manifest, const plonk::proof& proof, const aggregation_state previous_output = aggregation_state()) +{ + using Composer = typename Curve::Composer; + + key->program_width = program_settings::program_width; + + Transcript transcript = Transcript(context, proof.proof_data, manifest); + + return verify_proof_(context, key, transcript, previous_output); +} + +/** + * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, + * which includes detailed comments. + */ +template +aggregation_state verify_proof(typename Curve::Composer* context, + const transcript::Manifest& manifest, + std::shared_ptr const& crs, + const std::vector& key, + const std::vector& proof, + const aggregation_state previous_output = aggregation_state()) +{ + using Composer = typename Curve::Composer; + + std::shared_ptr> vkey = verification_key::from_field_pt_vector(context, crs, key); + vkey->program_width = program_settings::program_width; + + const size_t num_public_inputs = static_cast(uint256_t(vkey->num_public_inputs.get_value()).data[0]); + Transcript transcript = Transcript(context, manifest, proof, num_public_inputs); + + return verify_proof_(context, vkey, transcript, previous_output); +} + +/** + * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, + * which includes detailed comments. + */ +template +aggregation_state verify_proof_(typename Curve::Composer* context, + std::shared_ptr> key, + Transcript& transcript, + const aggregation_state previous_output = aggregation_state()) { using fr_ct = typename Curve::fr_ct; using fq_ct = typename Curve::fq_ct; @@ -190,7 +232,6 @@ aggregation_state verify_proof(typename Curve::Composer* context, key->program_width = program_settings::program_width; - Transcript transcript = Transcript(context, proof.proof_data, manifest); std::map kate_g1_elements; std::map kate_fr_elements_at_zeta; std::map kate_fr_elements_at_zeta_large; diff --git a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp index bd33eff800..ab0b02d4c6 100644 --- a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp +++ b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp @@ -56,6 +56,46 @@ class StandardTranscript : public Transcript { // TODO(luke): temporary function for debugging barretenberg::fr get_mock_challenge() { return barretenberg::fr::random_element(); }; + + /** + * @brief Returns transcript represented as a vector of barretenberg::fr. + * Used to represent recursive proofs (i.e. proof represented as circuit-native field elements) + * + * @return std::vector + */ + std::vector export_transcript_in_recursion_format() + { + std::vector fields; + const auto num_rounds = get_manifest().get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (auto manifest_element : get_manifest().get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(get_field_element(manifest_element.name)); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto group_element = get_group_element(manifest_element.name); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } else { + ASSERT(manifest_element.name == "public_inputs"); + const auto public_inputs_vector = get_field_element_vector(manifest_element.name); + for (const auto& ele : public_inputs_vector) { + fields.emplace_back(ele); + } + } + } + } + } + return fields; + } }; } // namespace transcript From 292f6265b862847af71df13e5c5eecaf73954ad6 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Sun, 16 Apr 2023 11:40:42 +0200 Subject: [PATCH 04/64] recursive verification key no longer encapsulates a native key recursive verification key no longer encapsulates a reference string (neither were fundamentally needed!) --- .../dsl/acir_format/recursion_constraint.cpp | 3 +- .../acir_format/recursion_constraint.test.cpp | 2 +- .../verification_key/verification_key.cpp | 47 ++++++----------- .../verification_key/verification_key.hpp | 5 +- .../verification_key/verification_key.hpp | 51 ++++++++----------- .../stdlib/recursion/verifier/verifier.hpp | 41 +++++++-------- 6 files changed, 57 insertions(+), 92 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 373ee3c15a..4c9d08a32a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -1,5 +1,4 @@ #include "recursion_constraint.hpp" -#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" #include "barretenberg/stdlib/recursion/verifier/verifier.hpp" @@ -62,7 +61,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // recursively verify the proof aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof( - &composer, manifest, env_crs->get_verifier_crs(), key_fields, proof_fields, previous_aggregation); + &composer, manifest, key_fields, proof_fields, previous_aggregation); // Assign the output aggregation object to the proof public inputs (16 field elements representing two G1 points) result.add_proof_outputs_as_public_inputs(); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 7154fdd23e..f36446e3db 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -134,7 +134,7 @@ TEST(RecursionConstraint, TestRecursionConstraint) inner_proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); - const std::vector key_witnesses = inner_verifier.key->export_transcript_in_recursion_format(); + const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); std::vector proof_indices; const size_t proof_size = proof_witnesses.size(); diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index 56f4a52cf5..7ba7af5175 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -107,31 +107,6 @@ verification_key::verification_key(const size_t num_gates, , polynomial_manifest(composer_type) {} -verification_key::verification_key(const std::vector& key_as_fields, - std::shared_ptr const& crs, - uint32_t composer_type_) -{ - composer_type = composer_type_; - polynomial_manifest = PolynomialManifest(composer_type); - reference_string = crs; - // std::cout << "key as field = " << key_as_fields[3] << std::endl; - // for (const auto& f : key_as_fields) { - // std::cout << "f : " << f << std::endl; - // } - circuit_size = static_cast(uint256_t(key_as_fields[3])); - - std::cout << "circuit size = " << circuit_size << std::endl; - - log_circuit_size = numeric::get_msb(circuit_size); - num_public_inputs = static_cast(uint256_t(key_as_fields[4])); - contains_recursive_proof = static_cast(uint256_t(key_as_fields[5])); - domain = evaluation_domain(circuit_size); - - // ADD THE COMITMENTS DERP DERP DERP - // AS WELL AS RECURSIVE PROOF PUBLIC INPUT INDICES? - // -} - verification_key::verification_key(verification_key_data&& data, std::shared_ptr const& crs) : composer_type(data.composer_type) , circuit_size(data.circuit_size) @@ -203,7 +178,14 @@ sha256::hash verification_key::sha256_hash() return sha256::sha256(to_buffer(vk_data)); } -std::vector verification_key::export_transcript_in_recursion_format() +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * NOTE: only used by the dsl at the moment. Might be cleaner to make this a dsl function? + * + * @return std::vector + */ +std::vector verification_key::export_key_in_recursion_format() { std::vector output; output.emplace_back(domain.root); @@ -212,16 +194,17 @@ std::vector verification_key::export_transcript_in_recursion_f output.emplace_back(circuit_size); output.emplace_back(num_public_inputs); output.emplace_back(contains_recursive_proof); - - for (auto& commitment_entry : commitments) { - std::cout << "key = " << commitment_entry.first << std::endl; - std::cout << "value = " << commitment_entry.second << std::endl; + for (size_t i = 0; i < 16; ++i) { + if (recursive_proof_public_input_indices.size() > i) { + output.emplace_back(recursive_proof_public_input_indices[i]); + } else { + output.emplace_back(0); + ASSERT(contains_recursive_proof == false); + } } for (const auto& descriptor : polynomial_manifest.get()) { if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { - std::cout << "key = " << descriptor.commitment_label << std::endl; const auto element = commitments.at(std::string(descriptor.commitment_label)); - std::cout << "read" << std::endl; const uint256_t x = element.x; const uint256_t y = element.y; const barretenberg::fr x_lo = x.slice(0, 136); diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp index 3165ed9923..aa240f52c9 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp @@ -55,9 +55,6 @@ struct verification_key { const size_t num_inputs, std::shared_ptr const& crs, uint32_t composer_type); - verification_key(const std::vector& key_as_fields, - std::shared_ptr const& crs, - uint32_t composer_type_); verification_key(const verification_key& other); verification_key(verification_key&& other); @@ -66,7 +63,7 @@ struct verification_key { ~verification_key() = default; sha256::hash sha256_hash(); - std::vector export_transcript_in_recursion_format(); + std::vector export_key_in_recursion_format(); uint32_t composer_type; size_t circuit_size; diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 2c982f6074..c297973dbb 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -120,30 +120,32 @@ template struct verification_key { using Composer = typename Curve::Composer; static std::shared_ptr from_field_pt_vector(Composer* ctx, - std::shared_ptr const& crs, const std::vector>& fields) { std::vector fields_raw; - for (const auto& f : fields) { - fields_raw.emplace_back(f.get_value()); - std::cout << "f: " << f.get_value() << std::endl; - } - std::cout << "a" << std::endl; std::shared_ptr key = std::make_shared(); - std::cout << "b" << std::endl; key->context = ctx; - key->base_key = std::make_shared(fields_raw, crs, Composer::type); - std::cout << "c" << std::endl; - key->reference_string = key->base_key->reference_string; key->polynomial_manifest = PolynomialManifest(Composer::type); key->domain = evaluation_domain::from_field_pt_vector({ fields[0], fields[1], fields[2] }); key->n = fields[3]; key->num_public_inputs = fields[4]; - key->contains_recursive_proof = bool_t(fields[5]); - size_t count = 6; + // NOTE: For now `contains_recursive_proof` and `recursive_proof_public_input_indices` need to be circuit + // constants! + key->contains_recursive_proof = static_cast(uint256_t(fields[5].get_value())); + for (size_t i = 0; i < 16; ++i) { + const uint32_t idx = static_cast(uint256_t(fields[6 + i].get_value())); + key->recursive_proof_public_input_indices.emplace_back(idx); + } + // Apply constraints to force the recursive proof information to be circuit constants + fields[5].assert_equal(key->contains_recursive_proof); + for (size_t i = 0; i < 16; ++i) { + fields[6 + i].assert_equal(key->recursive_proof_public_input_indices[i]); + } + + size_t count = 22; for (const auto& descriptor : key->polynomial_manifest.get()) { if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { @@ -158,7 +160,6 @@ template struct verification_key { key->commitments.insert({ std::string(descriptor.commitment_label), element }); } } - std::cout << "d" << std::endl; return key; } @@ -168,16 +169,14 @@ template struct verification_key { std::shared_ptr key = std::make_shared(); // Native data: key->context = ctx; - key->base_key = input_key; - key->reference_string = input_key->reference_string; key->polynomial_manifest = input_key->polynomial_manifest; // Circuit types: key->n = witness_t(ctx, barretenberg::fr(input_key->circuit_size)); key->num_public_inputs = witness_t(ctx, input_key->num_public_inputs); key->domain = evaluation_domain::from_witness(ctx, input_key->domain); - key->contains_recursive_proof = witness_t(ctx, input_key->contains_recursive_proof); - + key->contains_recursive_proof = input_key->contains_recursive_proof; + key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices; for (const auto& [tag, value] : input_key->commitments) { key->commitments.insert({ tag, Curve::g1_ct::from_witness(ctx, value) }); } @@ -190,15 +189,13 @@ template struct verification_key { { std::shared_ptr key = std::make_shared(); key->context = ctx; - key->base_key = input_key; key->n = field_t(ctx, input_key->circuit_size); key->num_public_inputs = field_t(ctx, input_key->num_public_inputs); - key->contains_recursive_proof = bool_t(ctx, input_key->contains_recursive_proof); + key->contains_recursive_proof = input_key->contains_recursive_proof; + key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices; key->domain = evaluation_domain::from_constants(ctx, input_key->domain); - key->reference_string = input_key->reference_string; - for (const auto& [tag, value] : input_key->commitments) { key->commitments.insert({ tag, typename Curve::g1_ct(value) }); } @@ -321,23 +318,17 @@ template struct verification_key { field_t num_public_inputs; field_t z_pow_n; - // NOTE: This does not strictly need to be a circuit type. It can be used to check in the circuit - // if a proof contains any aggregated state. - bool_t contains_recursive_proof; - evaluation_domain domain; std::map commitments; // Native data: - std::shared_ptr reference_string; - PolynomialManifest polynomial_manifest; - + // Used to check in the circuit if a proof contains any aggregated state. + bool contains_recursive_proof = false; + std::vector recursive_proof_public_input_indices; size_t program_width = 4; - - std::shared_ptr base_key; Composer* context; }; diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index 5c59bf9580..f3b4004218 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -199,14 +199,13 @@ aggregation_state verify_proof(typename Curve::Composer* context, template aggregation_state verify_proof(typename Curve::Composer* context, const transcript::Manifest& manifest, - std::shared_ptr const& crs, const std::vector& key, const std::vector& proof, const aggregation_state previous_output = aggregation_state()) { using Composer = typename Curve::Composer; - std::shared_ptr> vkey = verification_key::from_field_pt_vector(context, crs, key); + std::shared_ptr> vkey = verification_key::from_field_pt_vector(context, key); vkey->program_width = program_settings::program_width; const size_t num_public_inputs = static_cast(uint256_t(vkey->num_public_inputs.get_value()).data[0]); @@ -366,10 +365,6 @@ aggregation_state verify_proof_(typename Curve::Composer* context, rhs_scalars.push_back(random_separator); } - // Check if recursive proof information is correctly set. - key->contains_recursive_proof.assert_equal(key->base_key->contains_recursive_proof, - "contains_recursive_proof is incorrectly set"); - /** * N.B. if this key contains a recursive proof, then ALL potential verification keys being verified by the outer *circuit must ALSO contain a recursive proof (this is not a concern if the key is being generated from circuit @@ -377,7 +372,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, *code path should be used with extreme caution if the verification key is not being generated from circuit *constants **/ - if (key->base_key->contains_recursive_proof) { + if (key->contains_recursive_proof) { const auto public_inputs = transcript.get_field_element_vector("public_inputs"); const auto recover_fq_from_public_inputs = [&public_inputs](const size_t idx0, const size_t idx1, const size_t idx2, const size_t idx3) { @@ -390,22 +385,22 @@ aggregation_state verify_proof_(typename Curve::Composer* context, fr_ct recursion_separator_challenge = transcript.get_challenge_field_element("separator", 2); - const auto x0 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[0], - key->base_key->recursive_proof_public_input_indices[1], - key->base_key->recursive_proof_public_input_indices[2], - key->base_key->recursive_proof_public_input_indices[3]); - const auto y0 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[4], - key->base_key->recursive_proof_public_input_indices[5], - key->base_key->recursive_proof_public_input_indices[6], - key->base_key->recursive_proof_public_input_indices[7]); - const auto x1 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[8], - key->base_key->recursive_proof_public_input_indices[9], - key->base_key->recursive_proof_public_input_indices[10], - key->base_key->recursive_proof_public_input_indices[11]); - const auto y1 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[12], - key->base_key->recursive_proof_public_input_indices[13], - key->base_key->recursive_proof_public_input_indices[14], - key->base_key->recursive_proof_public_input_indices[15]); + const auto x0 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[0], + key->recursive_proof_public_input_indices[1], + key->recursive_proof_public_input_indices[2], + key->recursive_proof_public_input_indices[3]); + const auto y0 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[4], + key->recursive_proof_public_input_indices[5], + key->recursive_proof_public_input_indices[6], + key->recursive_proof_public_input_indices[7]); + const auto x1 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[8], + key->recursive_proof_public_input_indices[9], + key->recursive_proof_public_input_indices[10], + key->recursive_proof_public_input_indices[11]); + const auto y1 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[12], + key->recursive_proof_public_input_indices[13], + key->recursive_proof_public_input_indices[14], + key->recursive_proof_public_input_indices[15]); opening_elements.push_back(g1_ct(x0, y0)); opening_scalars.push_back(recursion_separator_challenge); From bc02eff9b63c92453575846d8b409ea5c6639e33 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Sun, 16 Apr 2023 16:53:40 +0200 Subject: [PATCH 05/64] added serialization test for acir_proofs --- .../dsl/acir_proofs/acir_proofs.test.cpp | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp new file mode 100644 index 0000000000..ce28fe62df --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -0,0 +1,140 @@ +#include "acir_proofs.hpp" +#include "../acir_format/acir_format.hpp" +#include "barretenberg/srs/io.hpp" +#include +#include + +using namespace proof_system::plonk::stdlib::types; + +void create_inner_circuit(acir_format::acir_format& constraint_system, std::vector& witness) +{ + /** + * constraints produced by Noir program: + * fn main(x : u32, y : pub u32) { + * let z = x ^ y; + * + * constrain z != 10; + * } + **/ + acir_format::RangeConstraint range_a{ + .witness = 1, + .num_bits = 32, + }; + acir_format::RangeConstraint range_b{ + .witness = 2, + .num_bits = 32, + }; + + acir_format::LogicConstraint logic_constraint{ + .a = 1, + .b = 2, + .result = 3, + .num_bits = 32, + .is_xor_gate = 1, + }; + poly_triple expr_a{ + .a = 3, + .b = 4, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = 0, + .q_c = -10, + }; + poly_triple expr_b{ + .a = 4, + .b = 5, + .c = 6, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }; + poly_triple expr_c{ + .a = 4, + .b = 6, + .c = 4, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + + }; + poly_triple expr_d{ + .a = 6, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = -1, + .q_r = 0, + .q_o = 0, + .q_c = 1, + }; + + constraint_system = acir_format::acir_format{ + .varnum = 7, + .public_inputs = { 2 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .constraints = { expr_a, expr_b, expr_c, expr_d }, + }; + + uint256_t inverse_of_five = fr(5).invert(); + + witness.emplace_back(5); + witness.emplace_back(10); + witness.emplace_back(15); + witness.emplace_back(5); + witness.emplace_back(inverse_of_five); + witness.emplace_back(1); +} + +static acir_format::acir_format constraint_system; +static std::vector witness; + +TEST(AcirProofs, TestSerialization) +{ + create_inner_circuit(constraint_system, witness); + + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + EXPECT_EQ(verified, true); +} From d9656e9c5fcfca6e3a8df7fbab352f531e0f4fec Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Tue, 18 Apr 2023 13:46:51 +0200 Subject: [PATCH 06/64] Added serialization methods into dsl for recursive proof composition Added passing serialization tests Fixed format of RecursionConstraint to be compatible with existing ACIR formatting --- .../dsl/acir_format/acir_format.cpp | 10 +- .../dsl/acir_format/acir_format.hpp | 2 + .../dsl/acir_format/recursion_constraint.cpp | 62 +++++- .../dsl/acir_format/recursion_constraint.hpp | 16 +- .../acir_format/recursion_constraint.test.cpp | 4 - .../dsl/acir_proofs/acir_proofs.cpp | 92 +++++++-- .../dsl/acir_proofs/acir_proofs.hpp | 6 + .../dsl/acir_proofs/acir_proofs.test.cpp | 192 +++++++++++++++++- .../types/polynomial_manifest.hpp | 2 +- .../verification_key/verification_key.cpp | 45 ++++ .../verification_key/verification_key.hpp | 2 + .../verification_key/verification_key.hpp | 13 +- .../stdlib/recursion/verifier/verifier.hpp | 5 +- .../transcript/transcript_wrappers.hpp | 43 +++- 14 files changed, 453 insertions(+), 41 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 2b7b9526b6..65313934a6 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -89,7 +89,7 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) // Add recursion constraints for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); } } @@ -172,7 +172,7 @@ Composer create_circuit(const acir_format& constraint_system, // Add recursion constraints for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); } return composer; @@ -261,7 +261,7 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, // Add recursion constraints for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); } return composer; } @@ -346,7 +346,7 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: // Add recursion constraints for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); } return composer; } @@ -429,7 +429,7 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai // Add recursion constraints for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); } } diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 12592c6a32..950de43460 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -73,6 +73,7 @@ template inline void read(B& buf, acir_format& data) read(buf, data.pedersen_constraints); read(buf, data.hash_to_field_constraints); read(buf, data.fixed_base_scalar_mul_constraints); + read(buf, data.recursion_constraints); read(buf, data.constraints); } @@ -91,6 +92,7 @@ template inline void write(B& buf, acir_format const& data) write(buf, data.pedersen_constraints); write(buf, data.hash_to_field_constraints); write(buf, data.fixed_base_scalar_mul_constraints); + write(buf, data.recursion_constraints); write(buf, data.constraints); } diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 4c9d08a32a..d6af1512f3 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -1,6 +1,8 @@ #include "recursion_constraint.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" #include "barretenberg/stdlib/recursion/verifier/verifier.hpp" +#include "barretenberg/transcript/transcript_wrappers.hpp" using namespace proof_system::plonk::stdlib::types; @@ -9,14 +11,54 @@ using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; namespace acir_format { +void generate_dummy_proof() {} /** * @brief Add constraints required to recursively verify an UltraPlonk proof * * @param composer * @param input + * @tparam has_valid_witness_assignment. Do we have witnesses or are we just generating keys? + * @tparam inner_proof_contains_recursive_proof. Do we expect the inner proof to also have performed recursive + * verification? We need to know this at circuit-compile time. + * + * @note We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. + * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, + * or we need non-witness data to be provided as metadata in the ACIR opcode */ +template void create_recursion_constraints(Composer& composer, const RecursionConstraint& input) { + + // If we do not have a witness, we must ensure that our dummy witness will not trigger + // on-curve errors and inverting-zero errors + { + // get a fake key/proof that satisfies on-curve + inversion-zero checks + const std::vector dummy_key = plonk::verification_key::export_dummy_key_in_recursion_format( + PolynomialManifest(Composer::type), inner_proof_contains_recursive_proof); + const auto manifest = Composer::create_unrolled_manifest(1); + const std::vector dummy_proof = + transcript::StandardTranscript::export_dummy_transcript_in_recursion_format(manifest); + for (size_t i = 0; i < input.proof.size(); ++i) { + const auto proof_field_idx = input.proof[i]; + // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), + // we add our dummy proof values as Composer variables. + // if we DO have a valid witness assignment, we use the real witness assignment + barretenberg::fr dummy_field = + has_valid_witness_assignment ? composer.get_variable(proof_field_idx) : dummy_proof[i]; + // Create a copy constraint between our dummy field and the witness index provided by RecursionConstraint. + // This will make the RecursionConstraint idx equal to `dummy_field`. + // In the case of a valid witness assignment, this does nothing (as dummy_field = real value) + // In the case of no valid witness assignment, this makes sure that the RecursionConstraint witness indices + // will not trigger basic errors (check inputs are on-curve, check we are not inverting 0) + composer.assert_equal(composer.add_variable(dummy_field), proof_field_idx); + } + for (size_t i = 0; i < input.key.size(); ++i) { + const auto key_field_idx = input.key[i]; + barretenberg::fr dummy_field = + has_valid_witness_assignment ? composer.get_variable(key_field_idx) : dummy_key[i]; + composer.assert_equal(composer.add_variable(dummy_field), key_field_idx); + } + } // The env_crs should be ok, we use this when generating the constraint system in dsl auto env_crs = std::make_unique(); @@ -28,7 +70,11 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // If we have previously recursively verified proofs, `is_aggregation_object_nonzero = true` // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! - if (input.is_aggregation_object_nonzero) { + bool inner_aggregation_indices_all_zero = true; + for (const auto& idx : aggregation_input) { + inner_aggregation_indices_all_zero &= (idx == 0); + } + if (!inner_aggregation_indices_all_zero) { std::array aggregation_elements; for (size_t i = 0; i < 4; ++i) { aggregation_elements[i] = @@ -60,10 +106,11 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } // recursively verify the proof - aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof( - &composer, manifest, key_fields, proof_fields, previous_aggregation); - - // Assign the output aggregation object to the proof public inputs (16 field elements representing two G1 points) + aggregation_state_ct result = proof_system::plonk::stdlib::recursion:: + verify_proof( + &composer, manifest, key_fields, proof_fields, previous_aggregation); + // Assign the output aggregation object to the proof public inputs (16 field elements representing two + // G1 points) result.add_proof_outputs_as_public_inputs(); ASSERT(result.public_inputs.size() == 1); @@ -79,4 +126,9 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } } +template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); +template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); +template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); +template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); + } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 0e794f3f73..f94ec453d4 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -24,12 +24,14 @@ namespace acir_format { * @param public_input The index of the single public input * @param input_aggregation_object Witness indices of pre-existing aggregation object (if it exists) * @param output_aggregation_object Witness indecies of the aggregation object produced by recursive verification + * + * @note If input_aggregation_object witness indices are all zero, we interpret this to mean that the inner proof does + * NOT contai */ struct RecursionConstraint { static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements std::vector key; std::vector proof; - bool is_aggregation_object_nonzero; uint32_t public_input; std::array input_aggregation_object; std::array output_aggregation_object; @@ -37,14 +39,23 @@ struct RecursionConstraint { friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; }; +template void create_recursion_constraints(plonk::stdlib::types::Composer& composer, const RecursionConstraint& input); +extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, + const RecursionConstraint&); +extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, + const RecursionConstraint&); +extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, + const RecursionConstraint&); +extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, + const RecursionConstraint&); + template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; read(buf, constraint.key); read(buf, constraint.proof); - read(buf, constraint.is_aggregation_object_nonzero); read(buf, constraint.public_input); read(buf, constraint.input_aggregation_object); read(buf, constraint.output_aggregation_object); @@ -55,7 +66,6 @@ template inline void write(B& buf, RecursionConstraint const& const using serialize::write; write(buf, constraint.key); write(buf, constraint.proof); - write(buf, constraint.is_aggregation_object_nonzero); write(buf, constraint.public_input); write(buf, constraint.input_aggregation_object); write(buf, constraint.output_aggregation_object); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index f36446e3db..b5d3a9fa69 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -126,9 +126,6 @@ TEST(RecursionConstraint, TestRecursionConstraint) // variable idx 2-18 = output_vars output_vars[i] = (static_cast(i + 2)); } - // verification_key_data keydata; - // uint8_t const* vk_buf = &keybuf[0]; - // read(vk_buf, keydata); transcript::StandardTranscript transcript( inner_proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); @@ -151,7 +148,6 @@ TEST(RecursionConstraint, TestRecursionConstraint) acir_format::RecursionConstraint recursion_constraint{ .key = key_indices, .proof = proof_indices, - .is_aggregation_object_nonzero = false, .public_input = 1, .input_aggregation_object = {}, .output_aggregation_object = output_vars, diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 9b899aa69f..733b652463 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -52,6 +52,8 @@ size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk { auto constraint_system = from_buffer(constraint_system_buf); + // constraint_system.recursion_constraints[0]. + // We know that we don't actually need any CRS to create a proving key, so just feed in a nothing. // Hacky, but, right now it needs *something*. auto crs_factory = std::make_unique(); @@ -93,17 +95,81 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* return buffer.size(); } -// void serialize_proof_into_field_elements(uint8_t** proof_data_buf) -// { -// } - -// void serialize_verification_key_into_field_elements(uint8_t** vk_buf) -// { -// plonk::verification_key key; -// read(vk_buf, key); +/** + * @brief Takes in a proof buffer and converts into a vector of field elements. + * The Recursion opcode requires the proof serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param proof_data_buf + * @param serialized_proof_data_buf + */ +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length) +{ + plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; + + transcript::StandardTranscript transcript( + proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + + std::vector output = transcript.export_transcript_in_recursion_format(); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + // NOTE: currently we dump the fr values into memory in Mongtomery form + // This is so we don't have to re-convert into Montgomery form when we read these back in. + // I think this makes it easier to handle the data in barretenberg-sys / aztec-backend. + // If this is not the case, the commented out serialize code will convert out of Montgomery form before writing to + // the buffer + // for (size_t i = 0; i < output.size(); ++i) { + // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + // } + // for (size_t i = 0; i < output.size(); ++i) { + // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + // } + memcpy(raw_buf, (void*)output.data(), output_size_bytes); + *serialized_proof_data_buf = raw_buf; + + return output_size_bytes; +} -// // do the serialize thing... -// } +/** + * @brief Takes in a verification key buffer and converts into a vector of field elements. + * The Recursion opcode requires the vk serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param vk_buf + * @param serialized_vk_buf + */ +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf) +{ + auto crs = std::make_shared(g2x); + plonk::verification_key_data vk_data; + read(vk_buf, vk_data); + auto vkey = std::make_shared(std::move(vk_data), crs); + + std::vector output = vkey->export_key_in_recursion_format(); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + // NOTE: currently we dump the fr values into memory in Mongtomery form + // This is so we don't have to re-convert into Montgomery form when we read these back in. + // I think this makes it easier to handle the data in barretenberg-sys / aztec-backend. + // If this is not the case, the commented out serialize code will convert out of Montgomery form before writing to + // the buffer + // for (size_t i = 0; i < output.size(); ++i) { + // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + // } + memcpy(raw_buf, (void*)output.data(), output_size_bytes); + + *serialized_vk_buf = raw_buf; + + return output_size_bytes; +} size_t new_proof(void* pippenger, uint8_t const* g2x, @@ -125,12 +191,12 @@ size_t new_proof(void* pippenger, reinterpret_cast(pippenger), g2x); proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); - Composer composer(proving_key, nullptr); + auto env_crs = std::make_unique(); - create_circuit_with_witness(composer, constraint_system, witness); + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); auto prover = composer.create_prover(); - + auto verifier = composer.create_verifier(); auto heapProver = new stdlib::types::Prover(std::move(prover)); auto& proof_data = heapProver->construct_proof().proof_data; *proof_data_buf = proof_data.data(); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index 4bd126aae9..180da3fec8 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -8,6 +8,12 @@ uint32_t get_exact_circuit_size(uint8_t const* constraint_system_buf); uint32_t get_total_circuit_size(uint8_t const* constraint_system_buf); size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf); size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf); +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length); size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index ce28fe62df..a31ad76d52 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -100,11 +100,10 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect witness.emplace_back(1); } -static acir_format::acir_format constraint_system; -static std::vector witness; - TEST(AcirProofs, TestSerialization) { + acir_format::acir_format constraint_system; + std::vector witness; create_inner_circuit(constraint_system, witness); std::vector witness_buf; // = to_buffer(witness); @@ -138,3 +137,190 @@ TEST(AcirProofs, TestSerialization) &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); EXPECT_EQ(verified, true); } + +TEST(AcirProofs, TestSerializationWithRecursion) +{ + uint8_t* proof_data_fields = nullptr; + uint8_t* vk_fields = nullptr; + size_t proof_fields_size = 0; + size_t vk_fields_size = 0; + + // inner circuit + { + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = + new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); + proof_fields_size = + acir_proofs::serialize_proof_into_field_elements(proof_data_buf, &proof_data_fields, proof_length); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + // free((void*)vk_buf); + // free((void*)pk_buf); + } + // outer circuit + { + std::vector proof_witnesses(proof_fields_size / 32); + std::vector key_witnesses(vk_fields_size / 32); + memcpy(proof_witnesses.data(), (void*)proof_data_fields, proof_fields_size); + memcpy(&key_witnesses[0], (void*)vk_fields, vk_fields_size); + + std::vector proof_indices; + + const size_t proof_size = proof_witnesses.size(); + + for (size_t i = 0; i < proof_size; ++i) { + proof_indices.emplace_back(static_cast(i + 18)); + } + + std::vector key_indices; + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + 18 + proof_size)); + } + + std::array output_vars; + for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { + // variable idx 1 = public input + // variable idx 2-18 = output_vars + output_vars[i] = (static_cast(i + 2)); + } + acir_format::RecursionConstraint recursion_constraint{ + .key = key_indices, + .proof = proof_indices, + .public_input = 1, + .input_aggregation_object = {}, + .output_aggregation_object = output_vars, + }; + + // { + // std::vector witness; + // for (size_t i = 0; i < 17; ++i) { + // witness.emplace_back(0); + // } + // for (const auto& wit : proof_witnesses) { + // witness.emplace_back(wit); + // } + // for (const auto& wit : key_witnesses) { + // witness.emplace_back(wit); + // } + + // acir_format::acir_format constraint_system{ + // .varnum = static_cast(witness.size() + 1), + // .public_inputs = { 1 }, + // .fixed_base_scalar_mul_constraints = {}, + // .logic_constraints = {}, + // .range_constraints = {}, + // .schnorr_constraints = {}, + // .ecdsa_constraints = {}, + // .sha256_constraints = {}, + // .blake2s_constraints = {}, + // .hash_to_field_constraints = {}, + // .pedersen_constraints = {}, + // .merkle_membership_constraints = {}, + // .recursion_constraints = { recursion_constraint }, + // .constraints = {}, + // }; + // auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); + // auto prover = composer.create_prover(); + + // auto proof = prover.construct_proof(); + // auto verifier = composer.create_verifier(); + // EXPECT_EQ(verifier.verify_proof(proof), true); + + // EXPECT_EQ(composer.get_variable(1), 10); + + // } + std::vector witness; + for (size_t i = 0; i < 17; ++i) { + witness.emplace_back(0); + } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } + + acir_format::acir_format constraint_system{ + .varnum = static_cast(witness.size() + 1), + .public_inputs = { 1 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .recursion_constraints = { recursion_constraint }, + .constraints = {}, + }; + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = + new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + EXPECT_EQ(verified, true); + } +} diff --git a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp index aefd348ff9..65911eba38 100644 --- a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp @@ -200,7 +200,7 @@ class PolynomialManifest { }; } - const std::vector& get() { return manifest; }; + const std::vector& get() const { return manifest; }; size_t size() const { return manifest.size(); } diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index 7ba7af5175..d355e6fc7e 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -221,4 +221,49 @@ std::vector verification_key::export_key_in_recursion_format() return output; } +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * A dummy key is used when building a circuit without a valid witness assignment. + * We want the transcript to contain valid G1 points to prevent on-curve errors being thrown. + * We want a non-zero circuit size as this element will be inverted by the circuit + * and we do not want an "inverting 0" error thrown + * + * @return std::vector + */ +std::vector verification_key::export_dummy_key_in_recursion_format( + const PolynomialManifest& polynomial_manifest, const bool contains_recursive_proof) +{ + std::vector output; + output.emplace_back(1); // domain.domain (will be inverted) + output.emplace_back(1); // domain.root (will be inverted) + output.emplace_back(1); // domain.generator (will be inverted) + + output.emplace_back(1); // circuit size + output.emplace_back(1); // num public inputs + + output.emplace_back(contains_recursive_proof); // contains_recursive_proof + for (size_t i = 0; i < 16; ++i) { + output.emplace_back(0); // recursive_proof_public_input_indices + } + + for (const auto& descriptor : polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + const auto element = barretenberg::g1::affine_one; + const uint256_t x = element.x; + const uint256_t y = element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + output.emplace_back(x_lo); + output.emplace_back(x_hi); + output.emplace_back(y_lo); + output.emplace_back(y_hi); + } + } + + return output; +} + } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp index aa240f52c9..974ada27b1 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp @@ -64,6 +64,8 @@ struct verification_key { sha256::hash sha256_hash(); std::vector export_key_in_recursion_format(); + static std::vector export_dummy_key_in_recursion_format( + const PolynomialManifest& polynomial_manifest, bool contains_recursive_proof = 0); uint32_t composer_type; size_t circuit_size; diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index c297973dbb..48ce7c81ae 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -29,6 +29,7 @@ template struct evaluation_domain { { evaluation_domain domain; domain.root = fields[0]; + domain.root_inverse = domain.root.invert(); domain.domain = fields[1]; domain.domain_inverse = domain.domain.invert(); @@ -119,8 +120,11 @@ template struct evaluation_domain { template struct verification_key { using Composer = typename Curve::Composer; - static std::shared_ptr from_field_pt_vector(Composer* ctx, - const std::vector>& fields) + template + static std::shared_ptr from_field_pt_vector( + Composer* ctx, + const std::vector>& fields, + std::array recursive_proof_public_input_indices = {}) { std::vector fields_raw; std::shared_ptr key = std::make_shared(); @@ -140,9 +144,9 @@ template struct verification_key { key->recursive_proof_public_input_indices.emplace_back(idx); } // Apply constraints to force the recursive proof information to be circuit constants - fields[5].assert_equal(key->contains_recursive_proof); + fields[5].assert_equal(inner_proof_contains_recursive_proof); for (size_t i = 0; i < 16; ++i) { - fields[6 + i].assert_equal(key->recursive_proof_public_input_indices[i]); + fields[6 + i].assert_equal(recursive_proof_public_input_indices[i]); } size_t count = 22; @@ -160,6 +164,7 @@ template struct verification_key { key->commitments.insert({ std::string(descriptor.commitment_label), element }); } } + return key; } diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index f3b4004218..b841491bfd 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -196,7 +196,7 @@ aggregation_state verify_proof(typename Curve::Composer* context, * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, * which includes detailed comments. */ -template +template aggregation_state verify_proof(typename Curve::Composer* context, const transcript::Manifest& manifest, const std::vector& key, @@ -205,7 +205,8 @@ aggregation_state verify_proof(typename Curve::Composer* context, { using Composer = typename Curve::Composer; - std::shared_ptr> vkey = verification_key::from_field_pt_vector(context, key); + std::shared_ptr> vkey = + verification_key::template from_field_pt_vector(context, key); vkey->program_width = program_settings::program_width; const size_t num_public_inputs = static_cast(uint256_t(vkey->num_public_inputs.get_value()).data[0]); diff --git a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp index ab0b02d4c6..ad849889cc 100644 --- a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp +++ b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp @@ -68,7 +68,7 @@ class StandardTranscript : public Transcript { std::vector fields; const auto num_rounds = get_manifest().get_num_rounds(); for (size_t i = 0; i < num_rounds; ++i) { - for (auto manifest_element : get_manifest().get_round_manifest(i).elements) { + for (const auto& manifest_element : get_manifest().get_round_manifest(i).elements) { if (!manifest_element.derived_by_verifier) { if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { fields.emplace_back(get_field_element(manifest_element.name)); @@ -96,6 +96,47 @@ class StandardTranscript : public Transcript { } return fields; } + + /** + * @brief Get a dummy fake proof for recursion. All elliptic curve group elements are still valid points to prevent + * errors being thrown. + * + * @param manifest + * @return std::vector + */ + static std::vector export_dummy_transcript_in_recursion_format(const Manifest& manifest) + { + std::vector fields; + const auto num_rounds = manifest.get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (const auto& manifest_element : manifest.get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(0); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto group_element = barretenberg::g1::affine_one; + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } else { + ASSERT(manifest_element.name == "public_inputs"); + const size_t num_public_inputs = manifest_element.num_bytes / 32; + for (size_t j = 0; j < num_public_inputs; ++j) { + fields.emplace_back(0); + } + } + } + } + } + return fields; + } }; } // namespace transcript From 209f81335bd7e3b2f33f5eb5ec4a3b351180b589 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Tue, 18 Apr 2023 14:54:08 +0200 Subject: [PATCH 07/64] added verificaiton key hash into RecursionConstraint Can be used by backend to force recursive verification key to be a specific value (it is represented via witnesses and therefore is not by-default constraint to represent a specific circuit) --- .../dsl/acir_format/recursion_constraint.cpp | 15 +++- .../dsl/acir_format/recursion_constraint.hpp | 3 + .../acir_format/recursion_constraint.test.cpp | 9 ++- .../dsl/acir_proofs/acir_proofs.cpp | 14 +++- .../dsl/acir_proofs/acir_proofs.hpp | 3 +- .../dsl/acir_proofs/acir_proofs.test.cpp | 81 ++++++++----------- .../verification_key/verification_key.cpp | 10 +++ .../stdlib/recursion/verifier/verifier.hpp | 23 ------ 8 files changed, 77 insertions(+), 81 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index d6af1512f3..895c27f99c 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -9,6 +9,8 @@ using namespace proof_system::plonk::stdlib::types; using verification_key_ct = proof_system::plonk::stdlib::recursion::verification_key; using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation_state; using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; +using Transcript_ct = proof_system::plonk::stdlib::recursion::Transcript; + namespace acir_format { void generate_dummy_proof() {} @@ -106,9 +108,16 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } // recursively verify the proof - aggregation_state_ct result = proof_system::plonk::stdlib::recursion:: - verify_proof( - &composer, manifest, key_fields, proof_fields, previous_aggregation); + std::shared_ptr vkey = + verification_key_ct::template from_field_pt_vector(&composer, key_fields); + vkey->program_width = noir_recursive_settings::program_width; + Transcript_ct transcript(&composer, manifest, proof_fields, 1); + aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( + &composer, vkey, transcript, previous_aggregation); + + // Assign correct witness value to the verification key hash + vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); + // Assign the output aggregation object to the proof public inputs (16 field elements representing two // G1 points) result.add_proof_outputs_as_public_inputs(); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index f94ec453d4..3a24727d01 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -33,6 +33,7 @@ struct RecursionConstraint { std::vector key; std::vector proof; uint32_t public_input; + uint32_t key_hash; std::array input_aggregation_object; std::array output_aggregation_object; @@ -57,6 +58,7 @@ template inline void read(B& buf, RecursionConstraint& constraint) read(buf, constraint.key); read(buf, constraint.proof); read(buf, constraint.public_input); + read(buf, constraint.key_hash); read(buf, constraint.input_aggregation_object); read(buf, constraint.output_aggregation_object); } @@ -67,6 +69,7 @@ template inline void write(B& buf, RecursionConstraint const& const write(buf, constraint.key); write(buf, constraint.proof); write(buf, constraint.public_input); + write(buf, constraint.key_hash); write(buf, constraint.input_aggregation_object); write(buf, constraint.output_aggregation_object); } diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index b5d3a9fa69..cfd564f7f6 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -124,7 +124,7 @@ TEST(RecursionConstraint, TestRecursionConstraint) for (size_t i = 0; i < 16; ++i) { // variable idx 1 = public input // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 2)); + output_vars[i] = (static_cast(i + 3)); } transcript::StandardTranscript transcript( @@ -137,24 +137,25 @@ TEST(RecursionConstraint, TestRecursionConstraint) const size_t proof_size = proof_witnesses.size(); for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 18)); + proof_indices.emplace_back(static_cast(i + 19)); } std::vector key_indices; const size_t key_size = key_witnesses.size(); for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 18 + proof_size)); + key_indices.emplace_back(static_cast(i + 19 + proof_size)); } acir_format::RecursionConstraint recursion_constraint{ .key = key_indices, .proof = proof_indices, .public_input = 1, + .key_hash = 2, .input_aggregation_object = {}, .output_aggregation_object = output_vars, }; std::vector witness; - for (size_t i = 0; i < 17; ++i) { + for (size_t i = 0; i < 18; ++i) { witness.emplace_back(0); } for (const auto& wit : proof_witnesses) { diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 733b652463..4d01512eca 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -144,7 +144,8 @@ size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, */ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, uint8_t const* vk_buf, - uint8_t** serialized_vk_buf) + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) { auto crs = std::make_shared(g2x); plonk::verification_key_data vk_data; @@ -154,7 +155,8 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, std::vector output = vkey->export_key_in_recursion_format(); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); + // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; auto raw_buf = (uint8_t*)malloc(output_size_bytes); // NOTE: currently we dump the fr values into memory in Mongtomery form // This is so we don't have to re-convert into Montgomery form when we read these back in. @@ -164,10 +166,16 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, // for (size_t i = 0; i < output.size(); ++i) { // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); // } - memcpy(raw_buf, (void*)output.data(), output_size_bytes); + // copy all but the vkey hash into raw_buf + memcpy(raw_buf, (void*)output.data(), output_size_bytes); *serialized_vk_buf = raw_buf; + // copy the vkey hash into vk_hash_raw_buf + auto vk_hash_raw_buf = (uint8_t*)malloc(32); + memcpy(vk_hash_raw_buf, (void*)&output[output.size() - 1], 32); + *serialized_vk_hash_buf = vk_hash_raw_buf; + return output_size_bytes; } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index 180da3fec8..ced41993c3 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -10,7 +10,8 @@ size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, uint8_t const* vk_buf, - uint8_t** serialized_vk_buf); + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, size_t proof_data_length); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index a31ad76d52..de011de21e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -142,6 +142,7 @@ TEST(AcirProofs, TestSerializationWithRecursion) { uint8_t* proof_data_fields = nullptr; uint8_t* vk_fields = nullptr; + uint8_t* vk_hash_buf = nullptr; size_t proof_fields_size = 0; size_t vk_fields_size = 0; @@ -180,92 +181,70 @@ TEST(AcirProofs, TestSerializationWithRecursion) pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); proof_fields_size = acir_proofs::serialize_proof_into_field_elements(proof_data_buf, &proof_data_fields, proof_length); - vk_fields_size = - acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields); + vk_fields_size = acir_proofs::serialize_verification_key_into_field_elements( + &g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); bool verified = acir_proofs::verify_proof( &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); EXPECT_EQ(verified, true); delete pippenger_ptr_base; - // free((void*)vk_buf); - // free((void*)pk_buf); + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); } // outer circuit { + fr vk_hash_value; std::vector proof_witnesses(proof_fields_size / 32); std::vector key_witnesses(vk_fields_size / 32); memcpy(proof_witnesses.data(), (void*)proof_data_fields, proof_fields_size); memcpy(&key_witnesses[0], (void*)vk_fields, vk_fields_size); + memcpy(&vk_hash_value, (void*)vk_hash_buf, 32); std::vector proof_indices; const size_t proof_size = proof_witnesses.size(); for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 18)); + proof_indices.emplace_back(static_cast(i + 19)); } std::vector key_indices; const size_t key_size = key_witnesses.size(); for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 18 + proof_size)); + key_indices.emplace_back(static_cast(i + 19 + proof_size)); } std::array output_vars; for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { // variable idx 1 = public input // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 2)); + output_vars[i] = (static_cast(i + 3)); } acir_format::RecursionConstraint recursion_constraint{ .key = key_indices, .proof = proof_indices, .public_input = 1, + .key_hash = 2, .input_aggregation_object = {}, .output_aggregation_object = output_vars, }; - // { - // std::vector witness; - // for (size_t i = 0; i < 17; ++i) { - // witness.emplace_back(0); - // } - // for (const auto& wit : proof_witnesses) { - // witness.emplace_back(wit); - // } - // for (const auto& wit : key_witnesses) { - // witness.emplace_back(wit); - // } - - // acir_format::acir_format constraint_system{ - // .varnum = static_cast(witness.size() + 1), - // .public_inputs = { 1 }, - // .fixed_base_scalar_mul_constraints = {}, - // .logic_constraints = {}, - // .range_constraints = {}, - // .schnorr_constraints = {}, - // .ecdsa_constraints = {}, - // .sha256_constraints = {}, - // .blake2s_constraints = {}, - // .hash_to_field_constraints = {}, - // .pedersen_constraints = {}, - // .merkle_membership_constraints = {}, - // .recursion_constraints = { recursion_constraint }, - // .constraints = {}, - // }; - // auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); - // auto prover = composer.create_prover(); - - // auto proof = prover.construct_proof(); - // auto verifier = composer.create_verifier(); - // EXPECT_EQ(verifier.verify_proof(proof), true); - - // EXPECT_EQ(composer.get_variable(1), 10); - - // } + // Add a constraint that fixes the vk hash to be the expected value! + poly_triple vk_equality_constraint{ + .a = recursion_constraint.key_hash, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = 0, + .q_o = 0, + .q_c = -vk_hash_value, + }; + std::vector witness; - for (size_t i = 0; i < 17; ++i) { + for (size_t i = 0; i < 18; ++i) { witness.emplace_back(0); } for (const auto& wit : proof_witnesses) { @@ -289,7 +268,7 @@ TEST(AcirProofs, TestSerializationWithRecursion) .pedersen_constraints = {}, .merkle_membership_constraints = {}, .recursion_constraints = { recursion_constraint }, - .constraints = {}, + .constraints = { vk_equality_constraint }, }; std::vector witness_buf; @@ -322,5 +301,13 @@ TEST(AcirProofs, TestSerializationWithRecursion) bool verified = acir_proofs::verify_proof( &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + free((void*)proof_data_fields); + free((void*)vk_fields); + free((void*)vk_hash_buf); } } diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index d355e6fc7e..743f563770 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -218,6 +218,15 @@ std::vector verification_key::export_key_in_recursion_format() } } + verification_key_data vkey_data{ + .composer_type = composer_type, + .circuit_size = static_cast(circuit_size), + .num_public_inputs = static_cast(num_public_inputs), + .commitments = commitments, + .contains_recursive_proof = contains_recursive_proof, + .recursive_proof_public_input_indices = recursive_proof_public_input_indices, + }; + output.emplace_back(vkey_data.compress_native(0)); // key_hash return output; } @@ -262,6 +271,7 @@ std::vector verification_key::export_dummy_key_in_recursion_fo output.emplace_back(y_hi); } } + output.emplace_back(0); // key_hash return output; } diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index b841491bfd..a99325cc86 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -192,29 +192,6 @@ aggregation_state verify_proof(typename Curve::Composer* context, return verify_proof_(context, key, transcript, previous_output); } -/** - * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, - * which includes detailed comments. - */ -template -aggregation_state verify_proof(typename Curve::Composer* context, - const transcript::Manifest& manifest, - const std::vector& key, - const std::vector& proof, - const aggregation_state previous_output = aggregation_state()) -{ - using Composer = typename Curve::Composer; - - std::shared_ptr> vkey = - verification_key::template from_field_pt_vector(context, key); - vkey->program_width = program_settings::program_width; - - const size_t num_public_inputs = static_cast(uint256_t(vkey->num_public_inputs.get_value()).data[0]); - Transcript transcript = Transcript(context, manifest, proof, num_public_inputs); - - return verify_proof_(context, vkey, transcript, previous_output); -} - /** * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, * which includes detailed comments. From 6496228698d9cfb1ac2eebc28518f495096bf3e3 Mon Sep 17 00:00:00 2001 From: zac-williamson Date: Tue, 18 Apr 2023 20:13:47 +0200 Subject: [PATCH 08/64] fixed compiler errors in stdlib_recursion_tests --- .../dsl/acir_format/recursion_constraint.hpp | 2 +- .../recursion/verifier/verifier.test.cpp | 22 +++++++++++++------ .../verifier/verifier_turbo.test.cpp | 20 ++++++++++++----- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 3a24727d01..45abd037c2 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -26,7 +26,7 @@ namespace acir_format { * @param output_aggregation_object Witness indecies of the aggregation object produced by recursive verification * * @note If input_aggregation_object witness indices are all zero, we interpret this to mean that the inner proof does - * NOT contai + * NOT contain */ struct RecursionConstraint { static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp index 82e8042b02..ad28907bfb 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp @@ -277,6 +277,7 @@ template class stdlib_verifier : public testing::Test { public: static void test_recursive_proof_composition() { + auto env_crs = std::make_unique(); InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); std::vector inner_inputs{ barretenberg::fr::random_element(), @@ -294,7 +295,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -326,6 +327,8 @@ template class stdlib_verifier : public testing::Test { static void test_recursive_proof_composition_ultra_no_tables() { + auto env_crs = std::make_unique(); + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -342,7 +345,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); @@ -375,6 +378,7 @@ template class stdlib_verifier : public testing::Test { return; // We only care about running this test for turbo and ultra outer circuits, since in practice the // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates // (16m) that it's a waste of time testing it. + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); @@ -396,7 +400,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -434,6 +438,7 @@ template class stdlib_verifier : public testing::Test { std::vector inner_inputs_b{ barretenberg::fr::random_element(), barretenberg::fr::random_element(), barretenberg::fr::random_element() }; + auto env_crs = std::make_unique(); create_inner_circuit(inner_composer_a, inner_inputs_a); create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); @@ -447,7 +452,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -487,6 +492,7 @@ template class stdlib_verifier : public testing::Test { auto circuit_output = create_outer_circuit_with_variable_inner_circuit(inner_composer_a, inner_composer_b, outer_composer, false); g1::affine_element P[2]; + auto env_crs = std::make_unique(); P[0].x = barretenberg::fq(circuit_output.aggregation_state.P0.x.get_value().lo); P[0].y = barretenberg::fq(circuit_output.aggregation_state.P0.y.get_value().lo); @@ -494,7 +500,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_b[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_b[1]); @@ -529,6 +535,7 @@ template class stdlib_verifier : public testing::Test { create_inner_circuit(inner_composer_a, inner_inputs_a); create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + auto env_crs = std::make_unique(); auto circuit_output = create_outer_circuit_with_variable_inner_circuit( inner_composer_a, inner_composer_b, outer_composer, true, true); @@ -539,7 +546,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -561,6 +568,7 @@ template class stdlib_verifier : public testing::Test { static void test_recursive_proof_composition_with_constant_verification_key() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -584,7 +592,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp index 1f4410148b..fee90bd030 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp @@ -165,6 +165,7 @@ template class stdlib_verifier_turbo : public testing:: public: static void test_recursive_proof_composition() { + auto env_crs = std::make_unique(); InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); std::vector inner_inputs{ barretenberg::fr::random_element(), @@ -181,7 +182,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -209,6 +210,8 @@ template class stdlib_verifier_turbo : public testing:: static void test_double_verification() { + auto env_crs = std::make_unique(); + if constexpr (std::is_same::value) return; // We only care about running this test for turbo and ultra outer circuits, since in practice the // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates @@ -234,7 +237,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -262,6 +265,8 @@ template class stdlib_verifier_turbo : public testing:: // variable circuits static void test_recursive_proof_composition_with_variable_verification_key_a() { + auto env_crs = std::make_unique(); + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -285,7 +290,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -309,6 +314,7 @@ template class stdlib_verifier_turbo : public testing:: // variable circuits static void test_recursive_proof_composition_with_variable_verification_key_b() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -332,7 +338,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_b[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_b[1]); @@ -354,6 +360,7 @@ template class stdlib_verifier_turbo : public testing:: static void test_recursive_proof_composition_with_variable_verification_key_failure_case() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -377,7 +384,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -399,6 +406,7 @@ template class stdlib_verifier_turbo : public testing:: static void test_recursive_proof_composition_with_constant_verification_key() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -422,7 +430,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); From 5f21e220586ada20a376684b6e52cd6d5d4be90b Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 25 Apr 2023 14:14:03 +0100 Subject: [PATCH 09/64] feat(dsl)!: Noir recursion updates (#379) * merge master in zw/noir-recursion * add dsl files to commit * namespace stuff and debugging * c bind functions * constraint test and comment removal * revert some changes to RecursionConstraint while debugging serialization issues * dispaly error when trying to use create_circuit_with_witness * is_recursive flag as part of new_proof and verify_proof, new RecursiveProver type in dsl, and serialization changes for recursion acir_proofs methods * remove debug output from TestSerializationWithRecursion --- cpp/CMakeLists.txt | 8 +- .../crypto/generators/generator_data.cpp | 10 +- .../dsl/acir_format/acir_format.cpp | 3 +- .../dsl/acir_format/acir_format.hpp | 23 +- .../dsl/acir_format/acir_format.test.cpp | 52 +- .../dsl/acir_format/blake2s_constraint.cpp | 2 - .../dsl/acir_format/blake2s_constraint.hpp | 4 +- .../dsl/acir_format/ecdsa_secp256k1.cpp | 4 +- .../dsl/acir_format/ecdsa_secp256k1.hpp | 4 +- .../dsl/acir_format/ecdsa_secp256k1.test.cpp | 4 +- .../dsl/acir_format/fixed_base_scalar_mul.cpp | 2 - .../dsl/acir_format/fixed_base_scalar_mul.hpp | 4 +- .../dsl/acir_format/hash_to_field.cpp | 6 +- .../dsl/acir_format/hash_to_field.hpp | 4 +- .../dsl/acir_format/logic_constraint.cpp | 4 +- .../dsl/acir_format/logic_constraint.hpp | 13 +- .../merkle_membership_constraint.cpp | 7 +- .../merkle_membership_constraint.hpp | 5 +- .../barretenberg/dsl/acir_format/pedersen.cpp | 9 +- .../barretenberg/dsl/acir_format/pedersen.hpp | 4 +- .../dsl/acir_format/recursion_constraint.cpp | 19 +- .../dsl/acir_format/recursion_constraint.hpp | 19 +- .../acir_format/recursion_constraint.test.cpp | 10 +- .../dsl/acir_format/schnorr_verify.cpp | 9 +- .../dsl/acir_format/schnorr_verify.hpp | 4 +- .../dsl/acir_format/sha256_constraint.cpp | 3 +- .../dsl/acir_format/sha256_constraint.hpp | 4 +- .../dsl/acir_proofs/acir_proofs.cpp | 106 +- .../dsl/acir_proofs/acir_proofs.hpp | 11 +- .../dsl/acir_proofs/acir_proofs.test.cpp | 40 +- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 32 +- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 18 +- cpp/src/barretenberg/dsl/types.hpp | 74 ++ cpp/src/barretenberg/ecc/fields/field.hpp | 4 + .../standard_honk_composer_helper.cpp | 1 + .../ultra_honk_composer_helper.cpp | 356 ++++++ .../ultra_honk_composer_helper.hpp | 72 ++ .../composer/standard_honk_composer.test.cpp | 103 -- .../honk/composer/ultra_honk_composer.hpp | 470 ++++++++ .../composer/ultra_honk_composer.test.cpp | 1036 +++++++++++++++++ cpp/src/barretenberg/honk/flavor/flavor.hpp | 56 +- .../honk/proof_system/prover_library.cpp | 10 +- .../honk/proof_system/prover_library.test.cpp | 34 +- .../honk/proof_system/ultra_prover.cpp | 56 + .../honk/proof_system/ultra_prover.hpp | 63 + .../honk/proof_system/verifier.cpp | 1 - .../honk/proof_system/verifier.test.cpp | 1 - .../grand_product_computation_relation.hpp | 87 ++ .../grand_product_initialization_relation.hpp | 44 + .../honk/sumcheck/relations/relation.hpp | 1 - ...test.cpp => relation_consistency.test.cpp} | 232 +++- .../relations/relation_correctness.test.cpp | 258 ++++ .../relations/ultra_arithmetic_relation.hpp | 87 ++ .../ultra_arithmetic_relation_secondary.hpp | 67 ++ .../proofs/join_split/c_bind.cpp | 32 +- .../join_split/compute_circuit_data.cpp | 3 +- .../proofs/join_split/join_split.cpp | 16 +- .../proofs/join_split/join_split.hpp | 5 +- .../proofs/join_split/join_split.test.cpp | 22 +- .../proofs/join_split/join_split_circuit.cpp | 17 +- .../proofs/join_split/join_split_circuit.hpp | 13 +- .../proofs/join_split/join_split_tx.hpp | 8 +- .../proofs/mock/mock_circuit.test.cpp | 4 +- .../notes/circuit/account/account_note.hpp | 5 +- .../proofs/notes/circuit/account/commit.hpp | 6 +- .../proofs/notes/circuit/asset_id.cpp | 4 +- .../proofs/notes/circuit/asset_id.hpp | 4 +- .../proofs/notes/circuit/bridge_call_data.hpp | 4 +- .../proofs/notes/circuit/claim/claim_note.hpp | 5 +- .../claim/complete_partial_commitment.hpp | 5 +- .../notes/circuit/claim/compute_nullifier.hpp | 5 +- .../claim/create_partial_commitment.hpp | 4 +- .../notes/circuit/claim/witness_data.hpp | 5 +- .../value/complete_partial_commitment.hpp | 6 +- .../notes/circuit/value/compute_nullifier.cpp | 4 +- .../notes/circuit/value/compute_nullifier.hpp | 5 +- .../circuit/value/compute_nullifier.test.cpp | 6 +- .../value/create_partial_commitment.hpp | 6 +- .../proofs/notes/circuit/value/value_note.hpp | 4 +- .../notes/circuit/value/value_note.test.cpp | 8 +- .../notes/circuit/value/witness_data.hpp | 5 +- .../barretenberg/join_split_example/types.hpp | 50 + .../plonk/composer/ultra_composer.cpp | 58 + .../plonk/composer/ultra_composer.hpp | 3 + .../plonk/composer/ultra_composer.test.cpp | 256 ++-- .../kate_commitment_scheme.cpp | 1 + .../kate_commitment_scheme.hpp | 1 + .../plonk/proof_system/constants.hpp | 12 +- .../plonk/proof_system/prover/c_bind.cpp | 5 +- .../proof_system/prover/c_bind_unrolled.cpp | 110 -- .../plonk/proof_system/prover/prover.cpp | 2 + .../plonk/proof_system/prover/prover.hpp | 1 + .../types/polynomial_manifest.hpp | 2 +- .../proof_system/types/program_settings.hpp | 16 + .../proof_system/types/prover_settings.hpp | 8 + .../verification_key.test.cpp | 27 +- .../plonk/proof_system/verifier/verifier.cpp | 1 + .../plonk/proof_system/verifier/verifier.hpp | 3 +- .../composer/permutation_helper.hpp | 54 +- .../encryption/schnorr/schnorr.test.cpp | 45 +- .../stdlib/hash/blake2s/blake2s.test.cpp | 32 +- .../stdlib/hash/sha256/sha256.bench.cpp | 18 +- .../stdlib/hash/sha256/sha256.test.cpp | 88 +- .../barretenberg/stdlib/merkle_tree/hash.hpp | 24 +- .../stdlib/merkle_tree/hash.test.cpp | 30 +- .../stdlib/merkle_tree/membership.test.cpp | 42 +- .../stdlib/merkle_tree/merkle_tree.test.cpp | 12 +- .../primitives/biggroup/biggroup.test.cpp | 1 - .../primitives/byte_array/byte_array.test.cpp | 3 +- .../stdlib/primitives/group/group.test.cpp | 7 +- .../packed_byte_array.test.cpp | 11 +- .../primitives/safe_uint/safe_uint.test.cpp | 4 +- .../verification_key.test.cpp | 12 +- cpp/src/barretenberg/stdlib/types/types.hpp | 100 -- 114 files changed, 3775 insertions(+), 1035 deletions(-) create mode 100644 cpp/src/barretenberg/dsl/types.hpp create mode 100644 cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp create mode 100644 cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp create mode 100644 cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp create mode 100644 cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp create mode 100644 cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp create mode 100644 cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp rename cpp/src/barretenberg/honk/sumcheck/relations/{relation.test.cpp => relation_consistency.test.cpp} (57%) create mode 100644 cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp create mode 100644 cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp create mode 100644 cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp create mode 100644 cpp/src/barretenberg/join_split_example/types.hpp delete mode 100644 cpp/src/barretenberg/plonk/proof_system/prover/c_bind_unrolled.cpp delete mode 100644 cpp/src/barretenberg/stdlib/types/types.hpp diff --git a/cpp/CMakeLists.txt b/cpp/CMakeLists.txt index 582d728537..4f719a0335 100644 --- a/cpp/CMakeLists.txt +++ b/cpp/CMakeLists.txt @@ -22,14 +22,8 @@ option(SERIALIZE_CANARY "Build with serialize canary" OFF) option(ENABLE_ASAN "Address sanitizer for debugging tricky memory corruption" OFF) option(ENABLE_HEAVY_TESTS "Enable heavy tests when collecting coverage" OFF) option(INSTALL_BARRETENBERG "Enable installation of barretenberg. (Projects embedding barretenberg may want to turn this OFF.)" ON) -option(USE_TURBO "Enable the use of TurboPlonk in barretenberg." OFF) -if(USE_TURBO) - message(STATUS "Building barretenberg for TurboPlonk Composer.") - add_definitions(-DUSE_TURBO) -else() - message(STATUS "Building barretenberg for UltraPlonk Composer.") -endif() +message(STATUS "Building barretenberg for UltraPlonk Composer.") if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64") message(STATUS "Compiling for ARM.") diff --git a/cpp/src/barretenberg/crypto/generators/generator_data.cpp b/cpp/src/barretenberg/crypto/generators/generator_data.cpp index 7970dfe659..1ad6127923 100644 --- a/cpp/src/barretenberg/crypto/generators/generator_data.cpp +++ b/cpp/src/barretenberg/crypto/generators/generator_data.cpp @@ -6,18 +6,14 @@ namespace { // The number of unique base points with default main index with precomputed ladders #ifdef __wasm__ -constexpr size_t num_default_generators = 64; -constexpr size_t num_generators_per_hash_index = 16; -constexpr size_t num_hash_indices = 32; -// TODO need to resolve memory out of bounds when these are too high +constexpr size_t num_default_generators = 32; #else constexpr size_t num_default_generators = 2048; -constexpr size_t num_hash_indices = 32; -constexpr size_t num_generators_per_hash_index = 128; #endif constexpr size_t hash_indices_generator_offset = 2048; - +constexpr size_t num_hash_indices = 16; +constexpr size_t num_generators_per_hash_index = 8; constexpr size_t num_indexed_generators = num_hash_indices * num_generators_per_hash_index; constexpr size_t size_of_generator_data_array = hash_indices_generator_offset + num_indexed_generators; constexpr size_t num_generator_types = 3; diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 65313934a6..e4f524869c 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -1,8 +1,6 @@ #include "acir_format.hpp" #include "barretenberg/common/log.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { void read_witness(Composer& composer, std::vector witness) @@ -114,6 +112,7 @@ Composer create_circuit(const acir_format& constraint_system, composer.add_variable(0); } } + // Add arithmetic gates for (const auto& constraint : constraint_system.constraints) { composer.create_poly_gate(constraint); diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 950de43460..39940e7f3a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -10,7 +10,7 @@ #include "recursion_constraint.hpp" #include "pedersen.hpp" #include "hash_to_field.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -39,23 +39,20 @@ struct acir_format { friend bool operator==(acir_format const& lhs, acir_format const& rhs) = default; }; -void read_witness(plonk::stdlib::types::Composer& composer, std::vector witness); +void read_witness(Composer& composer, std::vector witness); -void create_circuit(plonk::stdlib::types::Composer& composer, const acir_format& constraint_system); +void create_circuit(Composer& composer, const acir_format& constraint_system); -plonk::stdlib::types::Composer create_circuit(const acir_format& constraint_system, - std::unique_ptr&& crs_factory); +Composer create_circuit(const acir_format& constraint_system, + std::unique_ptr&& crs_factory); -plonk::stdlib::types::Composer create_circuit_with_witness(const acir_format& constraint_system, - std::vector witness, - std::unique_ptr&& crs_factory); +Composer create_circuit_with_witness(const acir_format& constraint_system, + std::vector witness, + std::unique_ptr&& crs_factory); -plonk::stdlib::types::Composer create_circuit_with_witness(const acir_format& constraint_system, - std::vector witness); +Composer create_circuit_with_witness(const acir_format& constraint_system, std::vector witness); -void create_circuit_with_witness(plonk::stdlib::types::Composer& composer, - const acir_format& constraint_system, - std::vector witness); +void create_circuit_with_witness(Composer& composer, const acir_format& constraint_system, std::vector witness); // Serialisation template inline void read(B& buf, acir_format& data) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index ff957c6b9b..5c17c353c1 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -4,6 +4,46 @@ #include #include "barretenberg/common/streams.hpp" +TEST(acir_format, test_a_single_constraint_no_pub_inputs) +{ + + poly_triple constraint{ + .a = 1, + .b = 2, + .c = 3, + .q_m = 0, + .q_l = 1, + .q_r = 1, + .q_o = -1, + .q_c = 0, + }; + + acir_format::acir_format constraint_system{ + .varnum = 4, + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .merkle_membership_constraints = {}, + .constraints = { constraint }, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, { 0, 0, 1 }); + + auto prover = composer.create_ultra_with_keccak_prover(); + auto proof = prover.construct_proof(); + + auto verifier = composer.create_ultra_with_keccak_verifier(); + + EXPECT_EQ(verifier.verify_proof(proof), false); +} + TEST(acir_format, test_logic_gate_from_noir_circuit) { /** @@ -105,10 +145,10 @@ TEST(acir_format, test_logic_gate_from_noir_circuit) std::cout << "made composer" << std::endl; - auto prover = composer.create_prover(); + auto prover = composer.create_ultra_with_keccak_prover(); auto proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_ultra_with_keccak_verifier(); EXPECT_EQ(verifier.verify_proof(proof), true); } @@ -174,10 +214,10 @@ TEST(acir_format, test_schnorr_verify_pass) 67, 16, 37, 128, 85, 76, 19, 253, 30, 77, 192, 53, 138, 205, 69, 33, 236, 163, 83, 194, 84, 137, 184, 221, 176, 121, 179, 27, 63, 70, 54, 16, 176, 250, 39, 239, 1, 0, 0, 0 }); - auto prover = composer.create_prover(); + auto prover = composer.create_ultra_with_keccak_prover(); auto proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_ultra_with_keccak_verifier(); EXPECT_EQ(verifier.verify_proof(proof), true); } @@ -243,10 +283,10 @@ TEST(acir_format, test_schnorr_verify_small_range) 67, 16, 37, 128, 85, 76, 19, 253, 30, 77, 192, 53, 138, 205, 69, 33, 236, 163, 83, 194, 84, 137, 184, 221, 176, 121, 179, 27, 63, 70, 54, 16, 176, 250, 39, 239, 1, 0, 0, 0 }); - auto prover = composer.create_prover(); + auto prover = composer.create_ultra_with_keccak_prover(); auto proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + auto verifier = composer.create_ultra_with_keccak_verifier(); EXPECT_EQ(verifier.verify_proof(proof), true); } \ No newline at end of file diff --git a/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp index 03deb176f6..8cd00269af 100644 --- a/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.cpp @@ -1,8 +1,6 @@ #include "blake2s_constraint.hpp" #include "round.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { void create_blake2s_constraints(Composer& composer, const Blake2sConstraint& constraint) diff --git a/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.hpp index 643eb1e39b..f68bf2019a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/blake2s_constraint.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -19,7 +19,7 @@ struct Blake2sConstraint { friend bool operator==(Blake2sConstraint const& lhs, Blake2sConstraint const& rhs) = default; }; -void create_blake2s_constraints(plonk::stdlib::types::Composer& composer, const Blake2sConstraint& constraint); +void create_blake2s_constraints(Composer& composer, const Blake2sConstraint& constraint); template inline void read(B& buf, Blake2sInput& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp index 291245d3d5..87a873dc16 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.cpp @@ -2,10 +2,10 @@ #include "barretenberg/crypto/ecdsa/ecdsa.hpp" #include "barretenberg/stdlib/encryption/ecdsa/ecdsa.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { +using namespace proof_system::plonk; + crypto::ecdsa::signature ecdsa_convert_signature(Composer& composer, std::vector signature) { diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp index 9fee1b035a..55d8f6c16d 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -28,7 +28,7 @@ struct EcdsaSecp256k1Constraint { friend bool operator==(EcdsaSecp256k1Constraint const& lhs, EcdsaSecp256k1Constraint const& rhs) = default; }; -void create_ecdsa_verify_constraints(plonk::stdlib::types::Composer& composer, const EcdsaSecp256k1Constraint& input); +void create_ecdsa_verify_constraints(Composer& composer, const EcdsaSecp256k1Constraint& input); template inline void read(B& buf, EcdsaSecp256k1Constraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index 57f9af0a1c..875fc4d589 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -7,8 +7,8 @@ #include #include -using namespace proof_system::plonk::stdlib::types; -using curve = stdlib::secp256k1; +using namespace proof_system::plonk; +using curve = stdlib::secp256k1; size_t generate_ecdsa_constraint(acir_format::EcdsaSecp256k1Constraint& ecdsa_constraint, std::vector& witness_values) diff --git a/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.cpp b/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.cpp index 81251944e6..453ab56f47 100644 --- a/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.cpp @@ -1,7 +1,5 @@ #include "fixed_base_scalar_mul.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { void create_fixed_base_constraint(Composer& composer, const FixedBaseScalarMul& input) diff --git a/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.hpp b/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.hpp index 2b61305a5c..80e52e03db 100644 --- a/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/fixed_base_scalar_mul.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -12,7 +12,7 @@ struct FixedBaseScalarMul { friend bool operator==(FixedBaseScalarMul const& lhs, FixedBaseScalarMul const& rhs) = default; }; -void create_fixed_base_constraint(plonk::stdlib::types::Composer& composer, const FixedBaseScalarMul& input); +void create_fixed_base_constraint(Composer& composer, const FixedBaseScalarMul& input); template inline void read(B& buf, FixedBaseScalarMul& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/hash_to_field.cpp b/cpp/src/barretenberg/dsl/acir_format/hash_to_field.cpp index 998b414cd4..afc3e2cf9f 100644 --- a/cpp/src/barretenberg/dsl/acir_format/hash_to_field.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/hash_to_field.cpp @@ -1,10 +1,10 @@ #include "hash_to_field.hpp" #include "round.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { +using namespace proof_system::plonk; + void create_hash_to_field_constraints(Composer& composer, const HashToFieldConstraint constraint) { @@ -30,7 +30,7 @@ void create_hash_to_field_constraints(Composer& composer, const HashToFieldConst // Hash To Field using blake2s. // Note: It does not need to be blake2s in the future - byte_array_ct out_bytes = proof_system::plonk::stdlib::blake2s(arr); + byte_array_ct out_bytes = stdlib::blake2s(arr); field_ct out(out_bytes); field_ct normalised_out = out.normalize(); diff --git a/cpp/src/barretenberg/dsl/acir_format/hash_to_field.hpp b/cpp/src/barretenberg/dsl/acir_format/hash_to_field.hpp index 275ce3fd0d..504493ce3e 100644 --- a/cpp/src/barretenberg/dsl/acir_format/hash_to_field.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/hash_to_field.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -19,7 +19,7 @@ struct HashToFieldConstraint { friend bool operator==(HashToFieldConstraint const& lhs, HashToFieldConstraint const& rhs) = default; }; -void create_hash_to_field_constraints(plonk::stdlib::types::Composer& composer, HashToFieldConstraint constraint); +void create_hash_to_field_constraints(Composer& composer, HashToFieldConstraint constraint); template inline void read(B& buf, HashToFieldInput& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/logic_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/logic_constraint.cpp index ca14b7cbc4..09990fe04b 100644 --- a/cpp/src/barretenberg/dsl/acir_format/logic_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/logic_constraint.cpp @@ -1,10 +1,10 @@ #include "logic_constraint.hpp" #include "barretenberg/stdlib/primitives/logic/logic.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { +using namespace proof_system::plonk; + void create_logic_gate(Composer& composer, const uint32_t a, const uint32_t b, diff --git a/cpp/src/barretenberg/dsl/acir_format/logic_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/logic_constraint.hpp index 0a194838ad..964f84f5af 100644 --- a/cpp/src/barretenberg/dsl/acir_format/logic_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/logic_constraint.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -14,16 +14,11 @@ struct LogicConstraint { friend bool operator==(LogicConstraint const& lhs, LogicConstraint const& rhs) = default; }; -void create_logic_gate(plonk::stdlib::types::Composer& composer, - uint32_t a, - uint32_t b, - uint32_t result, - size_t num_bits, - bool is_xor_gate); +void create_logic_gate(Composer& composer, uint32_t a, uint32_t b, uint32_t result, size_t num_bits, bool is_xor_gate); -void xor_gate(plonk::stdlib::types::Composer& composer, uint32_t a, uint32_t b, uint32_t result); +void xor_gate(Composer& composer, uint32_t a, uint32_t b, uint32_t result); -void and_gate(plonk::stdlib::types::Composer& composer, uint32_t a, uint32_t b, uint32_t result); +void and_gate(Composer& composer, uint32_t a, uint32_t b, uint32_t result); template inline void read(B& buf, LogicConstraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.cpp index 3f30a61fcb..780373f5be 100644 --- a/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.cpp @@ -1,9 +1,6 @@ #include "merkle_membership_constraint.hpp" #include "barretenberg/stdlib/merkle_tree/membership.hpp" -using namespace proof_system::plonk::stdlib::types; -using namespace proof_system::plonk::stdlib::merkle_tree; - namespace acir_format { void create_merkle_check_membership_constraint(Composer& composer, const MerkleMembershipConstraint& input) @@ -22,7 +19,7 @@ void create_merkle_check_membership_constraint(Composer& composer, const MerkleM // We are given the HashPath as a Vec // We want to first convert it into a Vec<(fr, fr)> then cast this to hash_path // struct which requires the method create_witness_hashpath - hash_path hash_path; + hash_path_ct hash_path; // In Noir we accept a hash path that only contains one hash per tree level // It is ok to reuse the leaf as it will be overridden in check_subtree_membership when computing the current root @@ -39,7 +36,7 @@ void create_merkle_check_membership_constraint(Composer& composer, const MerkleM } } - auto exists = check_subtree_membership(root, hash_path, leaf, index_bits, 0); + auto exists = plonk::stdlib::merkle_tree::check_subtree_membership(root, hash_path, leaf, index_bits, 0); composer.assert_equal_constant(exists.witness_index, fr::one()); } diff --git a/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.hpp index 1fa1481e88..25bfcd0404 100644 --- a/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/merkle_membership_constraint.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -14,8 +14,7 @@ struct MerkleMembershipConstraint { friend bool operator==(MerkleMembershipConstraint const& lhs, MerkleMembershipConstraint const& rhs) = default; }; -void create_merkle_check_membership_constraint(plonk::stdlib::types::Composer& composer, - const MerkleMembershipConstraint& input); +void create_merkle_check_membership_constraint(Composer& composer, const MerkleMembershipConstraint& input); template inline void read(B& buf, MerkleMembershipConstraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/pedersen.cpp b/cpp/src/barretenberg/dsl/acir_format/pedersen.cpp index 3ac7951ec9..d272a3a8ed 100644 --- a/cpp/src/barretenberg/dsl/acir_format/pedersen.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/pedersen.cpp @@ -1,9 +1,9 @@ #include "pedersen.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { +using namespace proof_system::plonk; + void create_pedersen_constraint(Composer& composer, const PedersenConstraint& input) { std::vector scalars; @@ -13,12 +13,9 @@ void create_pedersen_constraint(Composer& composer, const PedersenConstraint& in field_ct scalar_as_field = field_ct::from_witness_index(&composer, scalar); scalars.push_back(scalar_as_field); } -#ifdef USE_TURBO - auto point = pedersen_commitment::commit(scalars); -#else + // TODO: Does Noir need additive homomorphic Pedersen hash? If so, using plookup version won't help. auto point = stdlib::pedersen_plookup_commitment::commit(scalars); -#endif composer.assert_equal(point.x.witness_index, input.result_x); composer.assert_equal(point.y.witness_index, input.result_y); diff --git a/cpp/src/barretenberg/dsl/acir_format/pedersen.hpp b/cpp/src/barretenberg/dsl/acir_format/pedersen.hpp index b801d2a8f2..3144359b2f 100644 --- a/cpp/src/barretenberg/dsl/acir_format/pedersen.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/pedersen.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -13,7 +13,7 @@ struct PedersenConstraint { friend bool operator==(PedersenConstraint const& lhs, PedersenConstraint const& rhs) = default; }; -void create_pedersen_constraint(plonk::stdlib::types::Composer& composer, const PedersenConstraint& input); +void create_pedersen_constraint(Composer& composer, const PedersenConstraint& input); template inline void read(B& buf, PedersenConstraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 895c27f99c..441b47c2d8 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -4,15 +4,10 @@ #include "barretenberg/stdlib/recursion/verifier/verifier.hpp" #include "barretenberg/transcript/transcript_wrappers.hpp" -using namespace proof_system::plonk::stdlib::types; - -using verification_key_ct = proof_system::plonk::stdlib::recursion::verification_key; -using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation_state; -using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; -using Transcript_ct = proof_system::plonk::stdlib::recursion::Transcript; - namespace acir_format { +using namespace proof_system::plonk; + void generate_dummy_proof() {} /** * @brief Add constraints required to recursively verify an UltraPlonk proof @@ -61,8 +56,6 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& composer.assert_equal(composer.add_variable(dummy_field), key_field_idx); } } - // The env_crs should be ok, we use this when generating the constraint system in dsl - auto env_crs = std::make_unique(); // Construct an in-circuit representation of the verification key. // For now, the v-key is a circuit constant and is fixed for the circuit. @@ -135,9 +128,9 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } } -template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); -template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); -template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); -template void create_recursion_constraints(plonk::stdlib::types::Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 45abd037c2..cfc0116349 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -1,7 +1,8 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" -#include "barretenberg/plonk/proof_system//verification_key/verification_key.hpp" +// #include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" namespace acir_format { @@ -41,16 +42,12 @@ struct RecursionConstraint { }; template -void create_recursion_constraints(plonk::stdlib::types::Composer& composer, const RecursionConstraint& input); +void create_recursion_constraints(Composer& composer, const RecursionConstraint& input); -extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, - const RecursionConstraint&); -extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, - const RecursionConstraint&); -extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, - const RecursionConstraint&); -extern template void create_recursion_constraints(plonk::stdlib::types::Composer&, - const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); template inline void read(B& buf, RecursionConstraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index cfd564f7f6..2b23dcfa07 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -6,9 +6,9 @@ #include #include -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk; -Composer create_inner_circuit() +acir_format::Composer create_inner_circuit() { /** * constraints produced by Noir program: @@ -127,8 +127,10 @@ TEST(RecursionConstraint, TestRecursionConstraint) output_vars[i] = (static_cast(i + 3)); } - transcript::StandardTranscript transcript( - inner_proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + transcript::StandardTranscript transcript(inner_proof.proof_data, + acir_format::Composer::create_manifest(1), + transcript::HashType::PlookupPedersenBlake3s, + 16); const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); diff --git a/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.cpp b/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.cpp index f92176bd08..cb717ed99a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.cpp @@ -1,10 +1,11 @@ #include "schnorr_verify.hpp" +#include "barretenberg/stdlib/encryption/schnorr/schnorr.hpp" #include "barretenberg/crypto/schnorr/schnorr.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_format { +using namespace proof_system::plonk::stdlib; + crypto::schnorr::signature convert_signature(Composer& composer, std::vector signature) { @@ -82,9 +83,9 @@ void create_schnorr_verify_constraints(Composer& composer, const SchnorrConstrai point_ct pub_key{ witness_ct(&composer, pubkey_value_x), witness_ct(&composer, pubkey_value_y) }; - schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, new_sig); + schnorr_signature_bits_ct sig = schnorr::convert_signature(&composer, new_sig); - bool_ct signature_result = stdlib::schnorr::signature_verification_result(message, pub_key, sig); + bool_ct signature_result = schnorr::signature_verification_result(message, pub_key, sig); bool_ct signature_result_normalized = signature_result.normalize(); diff --git a/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.hpp b/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.hpp index f8e6b9c53f..7b6b984294 100644 --- a/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/schnorr_verify.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -25,7 +25,7 @@ struct SchnorrConstraint { friend bool operator==(SchnorrConstraint const& lhs, SchnorrConstraint const& rhs) = default; }; -void create_schnorr_verify_constraints(plonk::stdlib::types::Composer& composer, const SchnorrConstraint& input); +void create_schnorr_verify_constraints(Composer& composer, const SchnorrConstraint& input); template inline void read(B& buf, SchnorrConstraint& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.cpp index e54697d4e7..07c8166bd1 100644 --- a/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.cpp @@ -1,8 +1,7 @@ #include "sha256_constraint.hpp" #include "round.hpp" #include "barretenberg/stdlib/hash/sha256/sha256.hpp" - -using namespace proof_system::plonk::stdlib::types; +#include "barretenberg/dsl/types.hpp" namespace acir_format { diff --git a/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.hpp index dec3bca40a..eccb1521ab 100644 --- a/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/sha256_constraint.hpp @@ -1,7 +1,7 @@ #pragma once #include #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" namespace acir_format { @@ -21,7 +21,7 @@ struct Sha256Constraint { // This function does not work (properly) because the stdlib:sha256 function is not working correctly for 512 bits // pair -void create_sha256_constraints(plonk::stdlib::types::Composer& composer, const Sha256Constraint& constraint); +void create_sha256_constraints(Composer& composer, const Sha256Constraint& constraint); template inline void read(B& buf, Sha256Input& constraint) { diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 4d01512eca..4580792808 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -2,12 +2,10 @@ #include "acir_proofs.hpp" #include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" #include "barretenberg/dsl/acir_format/acir_format.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/dsl/types.hpp" #include "barretenberg/srs/reference_string/pippenger_reference_string.hpp" #include "barretenberg/plonk/proof_system/verification_key/sol_gen.hpp" -using namespace proof_system::plonk::stdlib::types; - namespace acir_proofs { size_t get_solidity_verifier(uint8_t const* g2x, uint8_t const* vk_buf, uint8_t** output_buf) @@ -79,9 +77,9 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* reinterpret_cast(pippenger), g2x); proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); - Composer composer(proving_key, nullptr); + acir_format::Composer composer(proving_key, nullptr); auto verification_key = - plonk::stdlib::types::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); + acir_format::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); // The composer_type has not yet been set. We need to set the composer_type for when we later read in and // construct the verification key so that we have the correct polynomial manifest @@ -110,25 +108,18 @@ size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; transcript::StandardTranscript transcript( - proof.proof_data, Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + proof.proof_data, acir_format::Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); std::vector output = transcript.export_transcript_in_recursion_format(); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); auto raw_buf = (uint8_t*)malloc(output_size_bytes); - // NOTE: currently we dump the fr values into memory in Mongtomery form - // This is so we don't have to re-convert into Montgomery form when we read these back in. - // I think this makes it easier to handle the data in barretenberg-sys / aztec-backend. - // If this is not the case, the commented out serialize code will convert out of Montgomery form before writing to - // the buffer - // for (size_t i = 0; i < output.size(); ++i) { - // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); - // } - // for (size_t i = 0; i < output.size(); ++i) { - // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); - // } - memcpy(raw_buf, (void*)output.data(), output_size_bytes); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size(); ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } *serialized_proof_data_buf = raw_buf; return output_size_bytes; @@ -158,23 +149,17 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; auto raw_buf = (uint8_t*)malloc(output_size_bytes); - // NOTE: currently we dump the fr values into memory in Mongtomery form - // This is so we don't have to re-convert into Montgomery form when we read these back in. - // I think this makes it easier to handle the data in barretenberg-sys / aztec-backend. - // If this is not the case, the commented out serialize code will convert out of Montgomery form before writing to - // the buffer - // for (size_t i = 0; i < output.size(); ++i) { - // barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); - // } - - // copy all but the vkey hash into raw_buf - memcpy(raw_buf, (void*)output.data(), output_size_bytes); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size(); ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } + + // copy the vkey into serialized_vk_buf *serialized_vk_buf = raw_buf; - // copy the vkey hash into vk_hash_raw_buf - auto vk_hash_raw_buf = (uint8_t*)malloc(32); - memcpy(vk_hash_raw_buf, (void*)&output[output.size() - 1], 32); - *serialized_vk_hash_buf = vk_hash_raw_buf; + // copy the vkey hash into serialized_vk_hash_buf + *serialized_vk_hash_buf = &raw_buf[(output.size() - 1) * 32]; return output_size_bytes; } @@ -184,7 +169,8 @@ size_t new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf) + uint8_t** proof_data_buf, + bool is_recursive) { auto constraint_system = from_buffer(constraint_system_buf); @@ -199,21 +185,33 @@ size_t new_proof(void* pippenger, reinterpret_cast(pippenger), g2x); proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); - auto env_crs = std::make_unique(); - - auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); - - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto heapProver = new stdlib::types::Prover(std::move(prover)); - auto& proof_data = heapProver->construct_proof().proof_data; - *proof_data_buf = proof_data.data(); - - return proof_data.size(); + // TODO: either need a context flag for recursive proofs or a new_recursive_proof method that uses regular + // UltraProver + acir_format::Composer composer(proving_key, nullptr); + + create_circuit_with_witness(composer, constraint_system, witness); + + if (is_recursive) { + auto prover = composer.create_prover(); + auto heapProver = new acir_format::RecursiveProver(std::move(prover)); + auto& proof_data = heapProver->construct_proof().proof_data; + *proof_data_buf = proof_data.data(); + return proof_data.size(); + } else { + auto prover = composer.create_ultra_with_keccak_prover(); + auto heapProver = new acir_format::Prover(std::move(prover)); + auto& proof_data = heapProver->construct_proof().proof_data; + *proof_data_buf = proof_data.data(); + return proof_data.size(); + } } -bool verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +bool verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive) { bool verified = false; @@ -226,13 +224,21 @@ bool verify_proof( read(vk_buf, vk_data); auto verification_key = std::make_shared(std::move(vk_data), crs); - Composer composer(nullptr, verification_key); + acir_format::Composer composer(nullptr, verification_key); create_circuit(composer, constraint_system); plonk::proof pp = { std::vector(proof, proof + length) }; - auto verifier = composer.create_verifier(); + // for inner circuit use new prover and verifier method for outer circuit use the normal prover and verifier + // TODO: either need a context flag for recursive verify or a new_recursive_verify_proof method that uses + // regular UltraVerifier + if (is_recursive) { + auto verifier = composer.create_verifier(); + verified = verifier.verify_proof(pp); + } else { + auto verifier = composer.create_ultra_with_keccak_verifier(); + verified = verifier.verify_proof(pp); + } - verified = verifier.verify_proof(pp); #ifndef __wasm__ } catch (const std::exception& e) { verified = false; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index ced41993c3..95556f11a7 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -20,8 +20,13 @@ size_t new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf); -bool verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); + uint8_t** proof_data_buf, + bool is_recursive); +bool verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive); } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index de011de21e..b344086ca3 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -4,7 +4,7 @@ #include #include -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk; void create_inner_circuit(acir_format::acir_format& constraint_system, std::vector& witness) { @@ -131,10 +131,10 @@ TEST(AcirProofs, TestSerialization) uint8_t* proof_data_buf = nullptr; size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); bool verified = acir_proofs::verify_proof( - &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); EXPECT_EQ(verified, true); } @@ -178,14 +178,18 @@ TEST(AcirProofs, TestSerializationWithRecursion) uint8_t* proof_data_buf = nullptr; size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); proof_fields_size = acir_proofs::serialize_proof_into_field_elements(proof_data_buf, &proof_data_fields, proof_length); vk_fields_size = acir_proofs::serialize_verification_key_into_field_elements( &g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); - bool verified = acir_proofs::verify_proof( - &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + bool verified = acir_proofs::verify_proof(&g2x_buffer[0], + vk_buf, + &constraint_system_buf[0], + proof_data_buf, + static_cast(proof_length), + true); EXPECT_EQ(verified, true); delete pippenger_ptr_base; @@ -198,14 +202,17 @@ TEST(AcirProofs, TestSerializationWithRecursion) fr vk_hash_value; std::vector proof_witnesses(proof_fields_size / 32); std::vector key_witnesses(vk_fields_size / 32); - memcpy(proof_witnesses.data(), (void*)proof_data_fields, proof_fields_size); - memcpy(&key_witnesses[0], (void*)vk_fields, vk_fields_size); - memcpy(&vk_hash_value, (void*)vk_hash_buf, 32); + for (size_t i = 0; i < proof_fields_size / 32; i++) { + proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); + } + for (size_t i = 0; i < vk_fields_size / 32; i++) { + key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); + } + vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); std::vector proof_indices; const size_t proof_size = proof_witnesses.size(); - for (size_t i = 0; i < proof_size; ++i) { proof_indices.emplace_back(static_cast(i + 19)); } @@ -296,10 +303,14 @@ TEST(AcirProofs, TestSerializationWithRecursion) uint8_t* proof_data_buf = nullptr; size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf); - - bool verified = acir_proofs::verify_proof( - &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length)); + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof(&g2x_buffer[0], + vk_buf, + &constraint_system_buf[0], + proof_data_buf, + static_cast(proof_length), + false); EXPECT_EQ(verified, true); delete pippenger_ptr_base; @@ -308,6 +319,5 @@ TEST(AcirProofs, TestSerializationWithRecursion) free((void*)proof_data_buf); free((void*)proof_data_fields); free((void*)vk_fields); - free((void*)vk_hash_buf); } } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 4051aada3a..ac26be3fd7 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -35,19 +35,41 @@ WASM_EXPORT size_t acir_proofs_init_verification_key(void* pippenger, return acir_proofs::init_verification_key(pippenger, g2x, pk_buf, vk_buf); } +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) +{ + return acir_proofs::serialize_verification_key_into_field_elements( + g2x, vk_buf, serialized_vk_buf, serialized_vk_hash_buf); +} +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length) +{ + return acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, serialized_proof_data_buf, proof_data_length); +} + WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf) + uint8_t** proof_data_buf, + bool is_recursive) { - return acir_proofs::new_proof(pippenger, g2x, pk_buf, constraint_system_buf, witness_buf, proof_data_buf); + return acir_proofs::new_proof( + pippenger, g2x, pk_buf, constraint_system_buf, witness_buf, proof_data_buf, is_recursive); } -WASM_EXPORT bool acir_proofs_verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive) { - return acir_proofs::verify_proof(g2x, vk_buf, constraint_system_buf, proof, length); + return acir_proofs::verify_proof(g2x, vk_buf, constraint_system_buf, proof, length, is_recursive); } } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index a628c928cb..39591b37d1 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -14,12 +14,24 @@ WASM_EXPORT size_t acir_proofs_init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length); WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf); -WASM_EXPORT bool acir_proofs_verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); + uint8_t** proof_data_buf, + bool is_recursive); +WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive); } diff --git a/cpp/src/barretenberg/dsl/types.hpp b/cpp/src/barretenberg/dsl/types.hpp new file mode 100644 index 0000000000..be741363f9 --- /dev/null +++ b/cpp/src/barretenberg/dsl/types.hpp @@ -0,0 +1,74 @@ +#pragma once +#include "barretenberg/plonk/composer/ultra_composer.hpp" + +#include "barretenberg/plonk/proof_system/prover/prover.hpp" +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" +#include "barretenberg/stdlib/primitives/bit_array/bit_array.hpp" +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.hpp" +#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" +#include "barretenberg/stdlib/primitives/uint/uint.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" +#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen_plookup.hpp" +#include "barretenberg/stdlib/merkle_tree/hash_path.hpp" +#include "barretenberg/stdlib/encryption/schnorr/schnorr.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" +#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" +#include "barretenberg/stdlib/primitives/memory/rom_table.hpp" +#include "barretenberg/stdlib/recursion/verifier/program_settings.hpp" +#include "barretenberg/stdlib/recursion/verification_key/verification_key.hpp" +#include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" + +namespace acir_format { + +using Composer = plonk::UltraComposer; + +using Prover = std::conditional_t< + std::same_as, + plonk::UltraWithKeccakProver, + std::conditional_t, plonk::TurboProver, plonk::Prover>>; + +using Verifier = std::conditional_t< + std::same_as, + plonk::UltraWithKeccakVerifier, + std::conditional_t, plonk::TurboVerifier, plonk::Verifier>>; + +using RecursiveProver = plonk::UltraProver; + +using witness_ct = proof_system::plonk::stdlib::witness_t; +using public_witness_ct = proof_system::plonk::stdlib::public_witness_t; +using bool_ct = proof_system::plonk::stdlib::bool_t; +using byte_array_ct = proof_system::plonk::stdlib::byte_array; +using packed_byte_array_ct = proof_system::plonk::stdlib::packed_byte_array; +using field_ct = proof_system::plonk::stdlib::field_t; +using suint_ct = proof_system::plonk::stdlib::safe_uint_t; +using uint8_ct = proof_system::plonk::stdlib::uint8; +using uint16_ct = proof_system::plonk::stdlib::uint16; +using uint32_ct = proof_system::plonk::stdlib::uint32; +using uint64_ct = proof_system::plonk::stdlib::uint64; +using bit_array_ct = proof_system::plonk::stdlib::bit_array; +using fq_ct = proof_system::plonk::stdlib::bigfield; +using biggroup_ct = proof_system::plonk::stdlib::element; +using point_ct = proof_system::plonk::stdlib::point; +using pedersen_commitment = proof_system::plonk::stdlib::pedersen_commitment; +using group_ct = proof_system::plonk::stdlib::group; +using bn254 = proof_system::plonk::stdlib::bn254; +using secp256k1_ct = proof_system::plonk::stdlib::secp256k1; + +using hash_path_ct = proof_system::plonk::stdlib::merkle_tree::hash_path; + +using schnorr_signature_bits_ct = proof_system::plonk::stdlib::schnorr::signature_bits; + +// Ultra-composer specific typesv +using rom_table_ct = proof_system::plonk::stdlib::rom_table; + +using verification_key_ct = proof_system::plonk::stdlib::recursion::verification_key; +using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation_state; +using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; +using Transcript_ct = proof_system::plonk::stdlib::recursion::Transcript; + +} // namespace acir_format diff --git a/cpp/src/barretenberg/ecc/fields/field.hpp b/cpp/src/barretenberg/ecc/fields/field.hpp index 66c59ea0d2..80a87f46af 100644 --- a/cpp/src/barretenberg/ecc/fields/field.hpp +++ b/cpp/src/barretenberg/ecc/fields/field.hpp @@ -25,6 +25,10 @@ namespace barretenberg { template struct alignas(32) field { public: // We don't initialize data by default since we'd lose a lot of time on pointless initializations. + // Other alternatives have been noted, such as casting to get around constructors where they matter, + // however it is felt that sanitizer tools (e.g. MSAN) can detect garbage well, whereas doing + // hacky casts where needed would require rework to critical algos like MSM, FFT, Sumcheck. + // Instead, the recommended solution is use an explicit = 0 where initialization is important. field() noexcept {} constexpr field(const uint256_t& input) noexcept diff --git a/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp b/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp index 8c61e02a5d..6867d9af37 100644 --- a/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp +++ b/cpp/src/barretenberg/honk/composer/composer_helper/standard_honk_composer_helper.cpp @@ -165,6 +165,7 @@ StandardProver StandardHonkComposerHelper::create_prover( compute_witness(circuit_constructor); size_t num_sumcheck_rounds(circuit_proving_key->log_circuit_size); + // TODO(luke): what is this manifest? Remove? auto manifest = Flavor::create_manifest(circuit_constructor.public_inputs.size(), num_sumcheck_rounds); StandardProver output_state(std::move(wire_polynomials), circuit_proving_key); diff --git a/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp new file mode 100644 index 0000000000..1ef6fdcba0 --- /dev/null +++ b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp @@ -0,0 +1,356 @@ +#include "ultra_honk_composer_helper.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/plonk/proof_system/types/program_settings.hpp" +#include "barretenberg/plonk/proof_system/types/prover_settings.hpp" +// #include "barretenberg/plonk/proof_system/verifier/verifier.hpp" +#include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" +#include "barretenberg/proof_system/composer/permutation_helper.hpp" +#include "barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp" + +#include +#include +#include +#include + +namespace proof_system::honk { + +/** + * @brief Compute witness polynomials + * + * TODO(luke): The wire polynomials are returned directly whereas the sorted list polys are added to the proving + * key. This should be made consistent once Cody's Flavor work is settled. + */ +template +void UltraHonkComposerHelper::compute_witness(CircuitConstructor& circuit_constructor) +{ + if (computed_witness) { + return; + } + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : circuit_constructor.lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + const size_t filled_gates = circuit_constructor.num_gates + circuit_constructor.public_inputs.size(); + const size_t total_num_gates = std::max(filled_gates, tables_size + lookups_size); + + const size_t subgroup_size = circuit_constructor.get_circuit_subgroup_size(total_num_gates + NUM_RESERVED_GATES); + + // Pad the wires (pointers to `witness_indices` of the `variables` vector). + // Note: the remaining NUM_RESERVED_GATES indices are padded with zeros within `compute_witness_base` (called + // next). + for (size_t i = filled_gates; i < total_num_gates; ++i) { + circuit_constructor.w_l.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_r.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_o.emplace_back(circuit_constructor.zero_idx); + circuit_constructor.w_4.emplace_back(circuit_constructor.zero_idx); + } + + // TODO(#340)(luke): within compute_witness_base, the 3rd argument is used in the calculation of the dyadic circuit + // size (subgroup_size). Here (and in other split composers) we're passing in NUM_RANDOMIZED_GATES, but elsewhere, + // e.g. directly above, we use NUM_RESERVED_GATES in a similar role. Therefore, these two constants must be equal + // for everything to be consistent. What we should do is compute the dyadic circuit size once and for all then pass + // that around rather than computing in multiple places. + wire_polynomials = compute_witness_base(circuit_constructor, total_num_gates, NUM_RANDOMIZED_GATES); + + polynomial s_1(subgroup_size); + polynomial s_2(subgroup_size); + polynomial s_3(subgroup_size); + polynomial s_4(subgroup_size); + polynomial z_lookup(subgroup_size + 1); // Only instantiated in this function; nothing assigned. + + // Save space for adding random scalars in the s polynomial later. The subtracted 1 allows us to insert a `1` at the + // end, to ensure the evaluations (and hence coefficients) aren't all 0. See ComposerBase::compute_proving_key_base + // for further explanation, as a similar trick is done there. + size_t count = subgroup_size - tables_size - lookups_size - s_randomness - 1; + for (size_t i = 0; i < count; ++i) { + s_1[i] = 0; + s_2[i] = 0; + s_3[i] = 0; + s_4[i] = 0; + } + + for (auto& table : circuit_constructor.lookup_tables) { + const fr table_index(table.table_index); + auto& lookup_gates = table.lookup_gates; + for (size_t i = 0; i < table.size; ++i) { + if (table.use_twin_keys) { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + table.column_2[i].from_montgomery_form().data[0], + }, + { + table.column_3[i], + 0, + }, + }); + } else { + lookup_gates.push_back({ + { + table.column_1[i].from_montgomery_form().data[0], + 0, + }, + { + table.column_2[i], + table.column_3[i], + }, + }); + } + } + +#ifdef NO_TBB + std::sort(lookup_gates.begin(), lookup_gates.end()); +#else + std::sort(std::execution::par_unseq, lookup_gates.begin(), lookup_gates.end()); +#endif + + for (const auto& entry : lookup_gates) { + const auto components = entry.to_sorted_list_components(table.use_twin_keys); + s_1[count] = components[0]; + s_2[count] = components[1]; + s_3[count] = components[2]; + s_4[count] = table_index; + ++count; + } + } + + // Initialise the `s_randomness` positions in the s polynomials with 0. + // These will be the positions where we will be adding random scalars to add zero knowledge + // to plookup (search for `Blinding` in plonk/proof_system/widgets/random_widgets/plookup_widget_impl.hpp + // ProverPlookupWidget::compute_sorted_list_polynomial()) + for (size_t i = 0; i < s_randomness; ++i) { + s_1[count] = 0; + s_2[count] = 0; + s_3[count] = 0; + s_4[count] = 0; + ++count; + } + + // TODO(luke): Adding these to the key for now but this is inconsistent since these are 'witness' polys. Need + // to see what becomes of the proving key before making a decision here. + circuit_proving_key->polynomial_store.put("s_1_lagrange", std::move(s_1)); + circuit_proving_key->polynomial_store.put("s_2_lagrange", std::move(s_2)); + circuit_proving_key->polynomial_store.put("s_3_lagrange", std::move(s_3)); + circuit_proving_key->polynomial_store.put("s_4_lagrange", std::move(s_4)); + + computed_witness = true; +} + +template +UltraProver UltraHonkComposerHelper::create_prover(CircuitConstructor& circuit_constructor) +{ + finalize_circuit(circuit_constructor); + + compute_proving_key(circuit_constructor); + compute_witness(circuit_constructor); + + UltraProver output_state(std::move(wire_polynomials), circuit_proving_key); + + return output_state; +} + +// /** +// * Create verifier: compute verification key, +// * initialize verifier with it and an initial manifest and initialize commitment_scheme. +// * +// * @return The verifier. +// * */ +// // TODO(Cody): This should go away altogether. +// template +// plonk::UltraVerifier UltraHonkComposerHelper::create_verifier( +// const CircuitConstructor& circuit_constructor) +// { +// auto verification_key = compute_verification_key(circuit_constructor); + +// plonk::UltraVerifier output_state(circuit_verification_key, +// create_manifest(circuit_constructor.public_inputs.size())); + +// std::unique_ptr> kate_commitment_scheme = +// std::make_unique>(); + +// output_state.commitment_scheme = std::move(kate_commitment_scheme); + +// return output_state; +// } + +template +std::shared_ptr UltraHonkComposerHelper::compute_proving_key( + const CircuitConstructor& circuit_constructor) +{ + if (circuit_proving_key) { + return circuit_proving_key; + } + + size_t tables_size = 0; + size_t lookups_size = 0; + for (const auto& table : circuit_constructor.lookup_tables) { + tables_size += table.size; + lookups_size += table.lookup_gates.size(); + } + + const size_t minimum_circuit_size = tables_size + lookups_size; + const size_t num_randomized_gates = NUM_RANDOMIZED_GATES; + // Initialize circuit_proving_key + // TODO(#229)(Kesha): replace composer types. + circuit_proving_key = initialize_proving_key( + circuit_constructor, crs_factory_.get(), minimum_circuit_size, num_randomized_gates, ComposerType::PLOOKUP); + + construct_lagrange_selector_forms(circuit_constructor, circuit_proving_key.get()); + + // TODO(#217)(luke): Naively enforcing non-zero selectors for Honk will result in some relations not being + // satisfied. + // enforce_nonzero_polynomial_selectors(circuit_constructor, circuit_proving_key.get()); + + compute_honk_generalized_sigma_permutations(circuit_constructor, + circuit_proving_key.get()); + + compute_first_and_last_lagrange_polynomials(circuit_proving_key.get()); + + const size_t subgroup_size = circuit_proving_key->circuit_size; + + polynomial poly_q_table_column_1(subgroup_size); + polynomial poly_q_table_column_2(subgroup_size); + polynomial poly_q_table_column_3(subgroup_size); + polynomial poly_q_table_column_4(subgroup_size); + + size_t offset = subgroup_size - tables_size - s_randomness - 1; + + // Create lookup selector polynomials which interpolate each table column. + // Our selector polys always need to interpolate the full subgroup size, so here we offset so as to + // put the table column's values at the end. (The first gates are for non-lookup constraints). + // [0, ..., 0, ...table, 0, 0, 0, x] + // ^^^^^^^^^ ^^^^^^^^ ^^^^^^^ ^nonzero to ensure uniqueness and to avoid infinity commitments + // | table randomness + // ignored, as used for regular constraints and padding to the next power of 2. + + for (size_t i = 0; i < offset; ++i) { + poly_q_table_column_1[i] = 0; + poly_q_table_column_2[i] = 0; + poly_q_table_column_3[i] = 0; + poly_q_table_column_4[i] = 0; + } + + for (const auto& table : circuit_constructor.lookup_tables) { + const fr table_index(table.table_index); + + for (size_t i = 0; i < table.size; ++i) { + poly_q_table_column_1[offset] = table.column_1[i]; + poly_q_table_column_2[offset] = table.column_2[i]; + poly_q_table_column_3[offset] = table.column_3[i]; + poly_q_table_column_4[offset] = table_index; + ++offset; + } + } + + // Initialise the last `s_randomness` positions in table polynomials with 0. We don't need to actually randomise + // the table polynomials. + for (size_t i = 0; i < s_randomness; ++i) { + poly_q_table_column_1[offset] = 0; + poly_q_table_column_2[offset] = 0; + poly_q_table_column_3[offset] = 0; + poly_q_table_column_4[offset] = 0; + ++offset; + } + + // // In the case of using UltraPlonkComposer for a circuit which does _not_ make use of any lookup tables, all + // four + // // table columns would be all zeros. This would result in these polys' commitments all being the point at + // infinity + // // (which is bad because our point arithmetic assumes we'll never operate on the point at infinity). To avoid + // this, + // // we set the last evaluation of each poly to be nonzero. The last `num_roots_cut_out_of_vanishing_poly = 4` + // // evaluations are ignored by constraint checks; we arbitrarily choose the very-last evaluation to be nonzero. + // See + // // ComposerBase::compute_proving_key_base for further explanation, as a similar trick is done there. We could + // // have chosen `1` for each such evaluation here, but that would have resulted in identical commitments for + // // all four columns. We don't want to have equal commitments, because biggroup operations assume no points are + // // equal, so if we tried to verify an ultra proof in a circuit, the biggroup operations would fail. To combat + // // this, we just choose distinct values: + size_t num_selectors = circuit_constructor.num_selectors; + ASSERT(offset == subgroup_size - 1); + auto unique_last_value = num_selectors + 1; // Note: in compute_proving_key_base, moments earlier, each selector + // vector was given a unique last value from 1..num_selectors. So we + // avoid those values and continue the count, to ensure uniqueness. + poly_q_table_column_1[subgroup_size - 1] = unique_last_value; + poly_q_table_column_2[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_3[subgroup_size - 1] = ++unique_last_value; + poly_q_table_column_4[subgroup_size - 1] = ++unique_last_value; + + circuit_proving_key->polynomial_store.put("table_value_1_lagrange", std::move(poly_q_table_column_1)); + circuit_proving_key->polynomial_store.put("table_value_2_lagrange", std::move(poly_q_table_column_2)); + circuit_proving_key->polynomial_store.put("table_value_3_lagrange", std::move(poly_q_table_column_3)); + circuit_proving_key->polynomial_store.put("table_value_4_lagrange", std::move(poly_q_table_column_4)); + + // Copy memory read/write record data into proving key. Prover needs to know which gates contain a read/write + // 'record' witness on the 4th wire. This wire value can only be fully computed once the first 3 wire polynomials + // have been committed to. The 4th wire on these gates will be a random linear combination of the first 3 wires, + // using the plookup challenge `eta` + std::copy(circuit_constructor.memory_read_records.begin(), + circuit_constructor.memory_read_records.end(), + std::back_inserter(circuit_proving_key->memory_read_records)); + std::copy(circuit_constructor.memory_write_records.begin(), + circuit_constructor.memory_write_records.end(), + std::back_inserter(circuit_proving_key->memory_write_records)); + + circuit_proving_key->recursive_proof_public_input_indices = + std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); + + circuit_proving_key->contains_recursive_proof = contains_recursive_proof; + + return circuit_proving_key; +} + +// /** +// * Compute verification key consisting of selector precommitments. +// * +// * @return Pointer to created circuit verification key. +// * */ +// template +// std::shared_ptr UltraHonkComposerHelper::compute_verification_key( +// const CircuitConstructor& circuit_constructor) +// { +// if (circuit_verification_key) { +// return circuit_verification_key; +// } + +// if (!circuit_proving_key) { +// compute_proving_key(circuit_constructor); +// } +// circuit_verification_key = compute_verification_key_common(circuit_proving_key, +// crs_factory_->get_verifier_crs()); + +// circuit_verification_key->composer_type = type; // Invariably plookup for this class. + +// // See `add_recusrive_proof()` for how this recursive data is assigned. +// circuit_verification_key->recursive_proof_public_input_indices = +// std::vector(recursive_proof_public_input_indices.begin(), +// recursive_proof_public_input_indices.end()); + +// circuit_verification_key->contains_recursive_proof = contains_recursive_proof; + +// return circuit_verification_key; +// } + +// template +// void UltraHonkComposerHelper::add_table_column_selector_poly_to_proving_key( +// polynomial& selector_poly_lagrange_form, const std::string& tag) +// { +// polynomial selector_poly_lagrange_form_copy(selector_poly_lagrange_form, circuit_proving_key->small_domain.size); + +// selector_poly_lagrange_form.ifft(circuit_proving_key->small_domain); +// auto& selector_poly_coeff_form = selector_poly_lagrange_form; + +// polynomial selector_poly_coset_form(selector_poly_coeff_form, circuit_proving_key->circuit_size * 4); +// selector_poly_coset_form.coset_fft(circuit_proving_key->large_domain); + +// circuit_proving_key->polynomial_store.put(tag, std::move(selector_poly_coeff_form)); +// circuit_proving_key->polynomial_store.put(tag + "_lagrange", std::move(selector_poly_lagrange_form_copy)); +// circuit_proving_key->polynomial_store.put(tag + "_fft", std::move(selector_poly_coset_form)); +// } + +template class UltraHonkComposerHelper; +} // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp new file mode 100644 index 0000000000..de309735e6 --- /dev/null +++ b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "barretenberg/proof_system/composer/composer_helper_lib.hpp" +#include "barretenberg/plonk/composer/splitting_tmp/composer_helper/composer_helper_lib.hpp" +#include "barretenberg/srs/reference_string/file_reference_string.hpp" +#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +// #include "barretenberg/plonk/proof_system/verifier/verifier.hpp" + +#include +#include +#include +#include + +namespace proof_system::honk { +// TODO(Kesha): change initializations to specify this parameter +// Cody: What does this mean? +template class UltraHonkComposerHelper { + public: + // TODO(#340)(luke): In the split composers, NUM_RANDOMIZED_GATES has replaced NUM_RESERVED_GATES (in some places) + // to determine the next-power-of-2 circuit size. (There are some places in this composer that still use + // NUM_RESERVED_GATES). Therefore for consistency within this composer itself, and consistency with the original + // Ultra Composer, this value must match that of NUM_RESERVED_GATES. This issue needs to be reconciled + // simultaneously here and in the other split composers. + static constexpr size_t NUM_RANDOMIZED_GATES = 4; // equal to the number of multilinear evaluations leaked + static constexpr size_t program_width = CircuitConstructor::program_width; + std::vector wire_polynomials; + std::shared_ptr circuit_proving_key; + std::shared_ptr circuit_verification_key; + // TODO(#218)(kesha): we need to put this into the commitment key, so that the composer doesn't have to handle srs + // at all + std::shared_ptr crs_factory_; + + std::vector recursive_proof_public_input_indices; + bool contains_recursive_proof = false; + bool computed_witness = false; + + // This variable controls the amount with which the lookup table and witness values need to be shifted + // above to make room for adding randomness into the permutation and witness polynomials in the plookup widget. + // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ + // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. + static constexpr size_t s_randomness = 3; + + explicit UltraHonkComposerHelper(std::shared_ptr crs_factory) + : crs_factory_(std::move(crs_factory)) + {} + + UltraHonkComposerHelper(std::shared_ptr p_key, std::shared_ptr v_key) + : circuit_proving_key(std::move(p_key)) + , circuit_verification_key(std::move(v_key)) + {} + + UltraHonkComposerHelper(UltraHonkComposerHelper&& other) noexcept = default; + UltraHonkComposerHelper(UltraHonkComposerHelper const& other) noexcept = default; + UltraHonkComposerHelper& operator=(UltraHonkComposerHelper&& other) noexcept = default; + UltraHonkComposerHelper& operator=(UltraHonkComposerHelper const& other) noexcept = default; + ~UltraHonkComposerHelper() = default; + + void finalize_circuit(CircuitConstructor& circuit_constructor) { circuit_constructor.finalize_circuit(); }; + + std::shared_ptr compute_proving_key(const CircuitConstructor& circuit_constructor); + // std::shared_ptr compute_verification_key(const CircuitConstructor& circuit_constructor); + + void compute_witness(CircuitConstructor& circuit_constructor); + + UltraProver create_prover(CircuitConstructor& circuit_constructor); + // UltraVerifier create_verifier(const CircuitConstructor& circuit_constructor); + + void add_table_column_selector_poly_to_proving_key(polynomial& small, const std::string& tag); +}; + +} // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp index 84717e21a4..8ab126ba01 100644 --- a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp +++ b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp @@ -304,109 +304,6 @@ TEST(StandardHonkComposer, VerificationKeyCreation) composer.circuit_constructor.selectors.size() + composer.num_wires * 2 + 2); } -/** - * @brief A test taking sumcheck relations and applying them to the witness and selector polynomials to ensure that the - * realtions are correct. - * - * TODO(Kesha): We'll have to update this function once we add zk, since the relation will be incorrect for he first few - * indices - * - */ -TEST(StandardHonkComposer, SumcheckRelationCorrectness) -{ - // Create a composer and a dummy circuit with a few gates - StandardHonkComposer composer = StandardHonkComposer(); - static const size_t num_wires = StandardHonkComposer::num_wires; - fr a = fr::one(); - // Using the public variable to check that public_input_delta is computed and added to the relation correctly - uint32_t a_idx = composer.add_public_variable(a); - fr b = fr::one(); - fr c = a + b; - fr d = a + c; - uint32_t b_idx = composer.add_variable(b); - uint32_t c_idx = composer.add_variable(c); - uint32_t d_idx = composer.add_variable(d); - for (size_t i = 0; i < 16; i++) { - composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() }); - composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() }); - } - // Create a prover (it will compute proving key and witness) - auto prover = composer.create_prover(); - - // Generate beta and gamma - fr beta = fr::random_element(); - fr gamma = fr::random_element(); - - // Compute public input delta - const auto public_inputs = composer.circuit_constructor.get_public_inputs(); - auto public_input_delta = - honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); - - sumcheck::RelationParameters params{ - .beta = beta, - .gamma = gamma, - .public_input_delta = public_input_delta, - }; - - constexpr size_t num_polynomials = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; - // Compute grand product polynomial - polynomial z_perm_poly = - prover_library::compute_permutation_grand_product(prover.key, prover.wire_polynomials, beta, gamma); - - // Create an array of spans to the underlying polynomials to more easily - // get the transposition. - // Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial - // in the list below - std::array, num_polynomials> evaluations_array; - - using POLYNOMIAL = proof_system::honk::StandardArithmetization::POLYNOMIAL; - evaluations_array[POLYNOMIAL::W_L] = prover.wire_polynomials[0]; - evaluations_array[POLYNOMIAL::W_R] = prover.wire_polynomials[1]; - evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2]; - evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly; - evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted(); - evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange"); - evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange"); - evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange"); - evaluations_array[POLYNOMIAL::Q_O] = prover.key->polynomial_store.get("q_3_lagrange"); - evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange"); - evaluations_array[POLYNOMIAL::SIGMA_1] = prover.key->polynomial_store.get("sigma_1_lagrange"); - evaluations_array[POLYNOMIAL::SIGMA_2] = prover.key->polynomial_store.get("sigma_2_lagrange"); - evaluations_array[POLYNOMIAL::SIGMA_3] = prover.key->polynomial_store.get("sigma_3_lagrange"); - evaluations_array[POLYNOMIAL::ID_1] = prover.key->polynomial_store.get("id_1_lagrange"); - evaluations_array[POLYNOMIAL::ID_2] = prover.key->polynomial_store.get("id_2_lagrange"); - evaluations_array[POLYNOMIAL::ID_3] = prover.key->polynomial_store.get("id_3_lagrange"); - evaluations_array[POLYNOMIAL::LAGRANGE_FIRST] = prover.key->polynomial_store.get("L_first_lagrange"); - evaluations_array[POLYNOMIAL::LAGRANGE_LAST] = prover.key->polynomial_store.get("L_last_lagrange"); - - // Construct the round for applying sumcheck relations and results for storing computed results - auto relations = std::tuple(honk::sumcheck::ArithmeticRelation(), - honk::sumcheck::GrandProductComputationRelation(), - honk::sumcheck::GrandProductInitializationRelation()); - - fr result = 0; - for (size_t i = 0; i < prover.key->circuit_size; i++) { - // Compute an array containing all the evaluations at a given row i - std::array evaluations_at_index_i; - for (size_t j = 0; j < num_polynomials; ++j) { - evaluations_at_index_i[j] = evaluations_array[j][i]; - } - - // For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the - // i-th row/vertex of the hypercube. - // We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at the first index at which the result is not - // 0, since result = 0 + C(transposed), which we expect will equal 0. - std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - - std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); - ASSERT_EQ(result, 0); - } -} - TEST(StandardHonkComposer, BaseCase) { auto composer = StandardHonkComposer(); diff --git a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp new file mode 100644 index 0000000000..5fa40b4626 --- /dev/null +++ b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp @@ -0,0 +1,470 @@ +#pragma once +#include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp" +#include "barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.hpp" +#include + +namespace proof_system::honk { + +class UltraHonkComposer { + + public: + // An instantiation of the circuit constructor that only depends on arithmetization, not on the proof system + UltraCircuitConstructor circuit_constructor; + // Composer helper contains all proof-related material that is separate from circuit creation such as: + // 1) Proving and verification keys + // 2) CRS + // 3) Converting variables to witness vectors/polynomials + UltraHonkComposerHelper composer_helper; + size_t& num_gates; + + UltraHonkComposer() + : UltraHonkComposer("../srs_db/ignition", 0){}; + + UltraHonkComposer(std::string const& crs_path, const size_t size_hint) + : UltraHonkComposer(std::unique_ptr(new FileReferenceStringFactory(crs_path)), + size_hint){}; + + UltraHonkComposer(std::shared_ptr const& crs_factory, const size_t size_hint) + : circuit_constructor(size_hint) + , composer_helper(crs_factory) + , num_gates(circuit_constructor.num_gates){}; + + UltraHonkComposer(std::shared_ptr const& p_key, + std::shared_ptr const& v_key, + size_t size_hint = 0); + UltraHonkComposer(UltraHonkComposer&& other) = default; + UltraHonkComposer& operator=(UltraHonkComposer&& other) = delete; + ~UltraHonkComposer() = default; + + uint32_t get_zero_idx() { return circuit_constructor.zero_idx; } + + uint32_t add_variable(const barretenberg::fr& in) { return circuit_constructor.add_variable(in); } + + barretenberg::fr get_variable(const uint32_t index) const { return circuit_constructor.get_variable(index); } + + void finalize_circuit() { circuit_constructor.finalize_circuit(); }; + + UltraProver create_prover() { return composer_helper.create_prover(circuit_constructor); }; + // UltraVerifier create_verifier() { return composer_helper.create_verifier(circuit_constructor); }; + + void create_add_gate(const add_triple& in) { circuit_constructor.create_add_gate(in); } + + void create_big_add_gate(const add_quad& in, const bool use_next_gate_w_4 = false) + { + circuit_constructor.create_big_add_gate(in, use_next_gate_w_4); + }; + + // void create_big_add_gate_with_bit_extraction(const add_quad& in); + // void create_big_mul_gate(const mul_quad& in); + // void create_balanced_add_gate(const add_quad& in); + + // void create_mul_gate(const mul_triple& in) override; + // void create_bool_gate(const uint32_t a) override; + // void create_poly_gate(const poly_triple& in) override; + void create_ecc_add_gate(const ecc_add_gate& in) { circuit_constructor.create_ecc_add_gate(in); }; + + // void fix_witness(const uint32_t witness_index, const barretenberg::fr& witness_value); + + // void add_recursive_proof(const std::vector& proof_output_witness_indices) + // { + // if (contains_recursive_proof) { + // failure("added recursive proof when one already exists"); + // } + // contains_recursive_proof = true; + + // for (const auto& idx : proof_output_witness_indices) { + // set_public_input(idx); + // recursive_proof_public_input_indices.push_back((uint32_t)(public_inputs.size() - 1)); + // } + // } + + void create_new_range_constraint(const uint32_t variable_index, + const uint64_t target_range, + std::string const msg = "create_new_range_constraint") + { + circuit_constructor.create_new_range_constraint(variable_index, target_range, msg); + }; + // void create_range_constraint(const uint32_t variable_index, const size_t num_bits, std::string const& msg) + // { + // if (num_bits <= DEFAULT_PLOOKUP_RANGE_BITNUM) { + // /** + // * N.B. if `variable_index` is not used in any arithmetic constraints, this will create an unsatisfiable + // * circuit! + // * this range constraint will increase the size of the 'sorted set' of range-constrained integers + // by 1. + // * The 'non-sorted set' of range-constrained integers is a subset of the wire indices of all + // arithmetic + // * gates. No arithemtic gate => size imbalance between sorted and non-sorted sets. Checking for this + // * and throwing an error would require a refactor of the Composer to catelog all 'orphan' variables + // not + // * assigned to gates. + // **/ + // create_new_range_constraint(variable_index, 1ULL << num_bits, msg); + // } else { + // decompose_into_default_range(variable_index, num_bits, DEFAULT_PLOOKUP_RANGE_BITNUM, msg); + // } + // } + + // accumulator_triple create_logic_constraint(const uint32_t a, + // const uint32_t b, + // const size_t num_bits, + // bool is_xor_gate); + // accumulator_triple create_and_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + // accumulator_triple create_xor_constraint(const uint32_t a, const uint32_t b, const size_t num_bits); + + // uint32_t put_constant_variable(const barretenberg::fr& variable); + + // size_t get_num_constant_gates() const override { return 0; } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * + // * @param count return arument, number of existing gates + // * @param rangecount return argument, extra gates due to range checks + // * @param romcount return argument, extra gates due to rom reads + // * @param ramcount return argument, extra gates due to ram read/writes + // */ + // void get_num_gates_split_into_components(size_t& count, + // size_t& rangecount, + // size_t& romcount, + // size_t& ramcount) const + // { + // count = num_gates; + // // each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set + // for (size_t i = 0; i < rom_arrays.size(); ++i) { + // for (size_t j = 0; j < rom_arrays[i].state.size(); ++j) { + // if (rom_arrays[i].state[j][0] == UNINITIALIZED_MEMORY_RECORD) { + // romcount += 2; + // } + // } + // romcount += (rom_arrays[i].records.size()); + // romcount += 1; // we add an addition gate after procesing a rom array + // } + + // constexpr size_t gate_width = ultra_settings::program_width; + // // each RAM gate adds +2 extra gates due to the ram reads being copied to a sorted list set, + // // as well as an extra gate to validate timestamps + // std::vector ram_timestamps; + // std::vector ram_range_sizes; + // std::vector ram_range_exists; + // for (size_t i = 0; i < ram_arrays.size(); ++i) { + // for (size_t j = 0; j < ram_arrays[i].state.size(); ++j) { + // if (ram_arrays[i].state[j] == UNINITIALIZED_MEMORY_RECORD) { + // ramcount += NUMBER_OF_GATES_PER_RAM_ACCESS; + // } + // } + // ramcount += (ram_arrays[i].records.size() * NUMBER_OF_GATES_PER_RAM_ACCESS); + // ramcount += NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY; // we add an addition gate after procesing a ram + // array + + // // there will be 'max_timestamp' number of range checks, need to calculate. + // const auto max_timestamp = ram_arrays[i].access_count - 1; + + // // if a range check of length `max_timestamp` already exists, we are double counting. + // // We record `ram_timestamps` to detect and correct for this error when we process range lists. + // ram_timestamps.push_back(max_timestamp); + // size_t padding = (gate_width - (max_timestamp % gate_width)) % gate_width; + // if (max_timestamp == gate_width) + // padding += gate_width; + // const size_t ram_range_check_list_size = max_timestamp + padding; + + // size_t ram_range_check_gate_count = (ram_range_check_list_size / gate_width); + // ram_range_check_gate_count += 1; // we need to add 1 extra addition gates for every distinct range list + + // ram_range_sizes.push_back(ram_range_check_gate_count); + // ram_range_exists.push_back(false); + // // rangecount += ram_range_check_gate_count; + // } + // for (const auto& list : range_lists) { + // auto list_size = list.second.variable_indices.size(); + // size_t padding = (gate_width - (list.second.variable_indices.size() % gate_width)) % gate_width; + // if (list.second.variable_indices.size() == gate_width) + // padding += gate_width; + // list_size += padding; + + // for (size_t i = 0; i < ram_timestamps.size(); ++i) { + // if (list.second.target_range == ram_timestamps[i]) { + // ram_range_exists[i] = true; + // } + // } + // rangecount += (list_size / gate_width); + // rangecount += 1; // we need to add 1 extra addition gates for every distinct range list + // } + // // update rangecount to include the ram range checks the composer will eventually be creating + // for (size_t i = 0; i < ram_range_sizes.size(); ++i) { + // if (!ram_range_exists[i]) { + // rangecount += ram_range_sizes[i]; + // } + // } + // } + + // /** + // * @brief Get the final number of gates in a circuit, which consists of the sum of: + // * 1) Current number number of actual gates + // * 2) Number of public inputs, as we'll need to add a gate for each of them + // * 3) Number of Rom array-associated gates + // * 4) NUmber of range-list associated gates + // * + // * @return size_t + // */ + // virtual size_t get_num_gates() const override + // { + // // if circuit finalised already added extra gates + // if (circuit_finalised) { + // return num_gates; + // } + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + // return count + romcount + ramcount + rangecount; + // } + + // virtual void print_num_gates() const override + // { + // size_t count = 0; + // size_t rangecount = 0; + // size_t romcount = 0; + // size_t ramcount = 0; + + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + + // size_t total = count + romcount + ramcount + rangecount; + // std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount + // << ", range " << rangecount << "), pubinp = " << public_inputs.size() << std::endl; + // } + + void assert_equal(const uint32_t a_variable_idx, + const uint32_t b_variable_idx, + std::string const& msg = "assert_equal") + { + circuit_constructor.assert_equal(a_variable_idx, b_variable_idx, msg); + } + + // void assert_equal_constant(const uint32_t a_idx, + // const barretenberg::fr& b, + // std::string const& msg = "assert equal constant") + // { + // if (variables[a_idx] != b && !failed()) { + // failure(msg); + // } + // auto b_idx = put_constant_variable(b); + // assert_equal(a_idx, b_idx, msg); + // } + + // /** + // * Plookup Methods + // **/ + // void add_table_column_selector_poly_to_proving_key(polynomial& small, const std::string& tag); + // void initialize_precomputed_table( + // const plookup::BasicTableId id, + // bool (*generator)(std::vector&, + // std::vector&, + // std::vector&), + // std::array (*get_values_from_key)(const std::array)); + + // plookup::BasicTable& get_table(const plookup::BasicTableId id); + // plookup::MultiTable& create_table(const plookup::MultiTableId id); + + plookup::ReadData create_gates_from_plookup_accumulators( + const plookup::MultiTableId& id, + const plookup::ReadData& read_values, + const uint32_t key_a_index, + std::optional key_b_index = std::nullopt) + { + return circuit_constructor.create_gates_from_plookup_accumulators(id, read_values, key_a_index, key_b_index); + }; + + // /** + // * Generalized Permutation Methods + // **/ + std::vector decompose_into_default_range( + const uint32_t variable_index, + const uint64_t num_bits, + const uint64_t target_range_bitnum = DEFAULT_PLOOKUP_RANGE_BITNUM, + std::string const& msg = "decompose_into_default_range") + { + return circuit_constructor.decompose_into_default_range(variable_index, num_bits, target_range_bitnum, msg); + }; + // std::vector decompose_into_default_range_better_for_oddlimbnum( + // const uint32_t variable_index, + // const size_t num_bits, + // std::string const& msg = "decompose_into_default_range_better_for_oddlimbnum"); + void create_dummy_constraints(const std::vector& variable_index) + { + circuit_constructor.create_dummy_constraints(variable_index); + }; + void create_sort_constraint(const std::vector& variable_index) + { + circuit_constructor.create_sort_constraint(variable_index); + }; + void create_sort_constraint_with_edges(const std::vector& variable_index, + const barretenberg::fr& start, + const barretenberg::fr& end) + { + circuit_constructor.create_sort_constraint_with_edges(variable_index, start, end); + }; + + void assign_tag(const uint32_t variable_index, const uint32_t tag) + { + circuit_constructor.assign_tag(variable_index, tag); + } + + // void assign_tag(const uint32_t variable_index, const uint32_t tag) + // { + // ASSERT(tag <= current_tag); + // ASSERT(real_variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); + // real_variable_tags[real_variable_index[variable_index]] = tag; + // } + + uint32_t create_tag(const uint32_t tag_index, const uint32_t tau_index) + { + return circuit_constructor.create_tag(tag_index, tau_index); + } + + // uint32_t get_new_tag() + // { + // current_tag++; + // return current_tag; + // } + + // RangeList create_range_list(const uint64_t target_range); + // void process_range_list(const RangeList& list); + // void process_range_lists(); + + // /** + // * Custom Gate Selectors + // **/ + // void apply_aux_selectors(const AUX_SELECTORS type); + + // /** + // * Non Native Field Arithmetic + // **/ + void range_constrain_two_limbs(const uint32_t lo_idx, + const uint32_t hi_idx, + const size_t lo_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS, + const size_t hi_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS) + { + circuit_constructor.range_constrain_two_limbs(lo_idx, hi_idx, lo_limb_bits, hi_limb_bits); + }; + // std::array decompose_non_native_field_double_width_limb( + // const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); + std::array evaluate_non_native_field_multiplication( + const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) + { + return circuit_constructor.evaluate_non_native_field_multiplication(input, + range_constrain_quotient_and_remainder); + }; + // std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& + // input); typedef std::pair scaled_witness; typedef std::tuple add_simple; std::array evaluate_non_native_field_subtraction( + // add_simple limb0, + // add_simple limb1, + // add_simple limb2, + // add_simple limb3, + // std::tuple limbp); + // std::array evaluate_non_native_field_addition(add_simple limb0, + // add_simple limb1, + // add_simple limb2, + // add_simple limb3, + // std::tuple + // limbp); + + // /** + // * Memory + // **/ + + size_t create_RAM_array(const size_t array_size) { return circuit_constructor.create_RAM_array(array_size); }; + size_t create_ROM_array(const size_t array_size) { return circuit_constructor.create_ROM_array(array_size); }; + + void set_ROM_element(const size_t rom_id, const size_t index_value, const uint32_t value_witness) + { + circuit_constructor.set_ROM_element(rom_id, index_value, value_witness); + }; + // void set_ROM_element_pair(const size_t rom_id, + // const size_t index_value, + // const std::array& value_witnesses); + uint32_t read_ROM_array(const size_t rom_id, const uint32_t index_witness) + { + return circuit_constructor.read_ROM_array(rom_id, index_witness); + }; + // std::array read_ROM_array_pair(const size_t rom_id, const uint32_t index_witness); + // void create_ROM_gate(RomRecord& record); + // void create_sorted_ROM_gate(RomRecord& record); + // void process_ROM_array(const size_t rom_id, const size_t gate_offset_from_public_inputs); + // void process_ROM_arrays(const size_t gate_offset_from_public_inputs); + + // void create_RAM_gate(RamRecord& record); + // void create_sorted_RAM_gate(RamRecord& record); + // void create_final_sorted_RAM_gate(RamRecord& record, const size_t ram_array_size); + + // size_t create_RAM_array(const size_t array_size); + void init_RAM_element(const size_t ram_id, const size_t index_value, const uint32_t value_witness) + { + circuit_constructor.init_RAM_element(ram_id, index_value, value_witness); + }; + uint32_t read_RAM_array(const size_t ram_id, const uint32_t index_witness) + { + return circuit_constructor.read_RAM_array(ram_id, index_witness); + }; + void write_RAM_array(const size_t ram_id, const uint32_t index_witness, const uint32_t value_witness) + { + circuit_constructor.write_RAM_array(ram_id, index_witness, value_witness); + }; + // void process_RAM_array(const size_t ram_id, const size_t gate_offset_from_public_inputs); + // void process_RAM_arrays(const size_t gate_offset_from_public_inputs); + + // /** + // * Member Variables + // **/ + + // uint32_t zero_idx = 0; + bool circuit_finalised = false; + + // // This variable controls the amount with which the lookup table and witness values need to be shifted + // // above to make room for adding randomness into the permutation and witness polynomials in the plookup widget. + // // This must be (num_roots_cut_out_of_the_vanishing_polynomial - 1), since the variable num_roots_cut_out_of_ + // // vanishing_polynomial cannot be trivially fetched here, I am directly setting this to 4 - 1 = 3. + // static constexpr size_t s_randomness = 3; + + // // these are variables that we have used a gate on, to enforce that they are equal to a defined value + // std::map constant_variable_indices; + + // std::vector lookup_tables; + // std::vector lookup_multi_tables; + // std::map range_lists; // DOCTODO: explain this. + + // /** + // * @brief Each entry in ram_arrays represents an independent RAM table. + // * RamTranscript tracks the current table state, + // * as well as the 'records' produced by each read and write operation. + // * Used in `compute_proving_key` to generate consistency check gates required to validate the RAM read/write + // history + // */ + // std::vector ram_arrays; + + // /** + // * @brief Each entry in ram_arrays represents an independent ROM table. + // * RomTranscript tracks the current table state, + // * as well as the 'records' produced by each read operation. + // * Used in `compute_proving_key` to generate consistency check gates required to validate the ROM read history + // */ + // std::vector rom_arrays; + + // // Stores gate index of ROM and RAM reads (required by proving key) + // std::vector memory_read_records; + // // Stores gate index of RAM writes (required by proving key) + // std::vector memory_write_records; + + // std::vector recursive_proof_public_input_indices; + // bool contains_recursive_proof = false; +}; +} // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp new file mode 100644 index 0000000000..0c8f1baefc --- /dev/null +++ b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.test.cpp @@ -0,0 +1,1036 @@ +#include "ultra_honk_composer.hpp" +#include "barretenberg/common/log.hpp" +#include "barretenberg/honk/proof_system/ultra_prover.hpp" +#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include +#include +#include "barretenberg/honk/proof_system/prover.hpp" +#include "barretenberg/honk/sumcheck/sumcheck_round.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/utils/public_inputs.hpp" + +// TODO(luke): TEMPORARY; for testing only (comparison with Ultra Plonk composers) +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp" +#include "barretenberg/plonk/proof_system/prover/prover.hpp" + +#include +#include + +using namespace proof_system::honk; + +namespace test_ultra_honk_composer { + +std::vector add_variables(auto& composer, std::vector variables) +{ + std::vector res; + for (size_t i = 0; i < variables.size(); i++) { + res.emplace_back(composer.add_variable(variables[i])); + } + return res; +} + +/** + * @brief TEMPORARY method for checking consistency of polynomials computed by Ultra Plonk/Honk composers + * + * @param honk_prover + * @param plonk_prover + */ +// NOTE: Currently checking exact consistency for witness polynomials (wires, sorted lists) and table polys. +// The permutation polys are computed differently between plonk and honk so we do not expect consistency. +// Equality is checked on all selectors but we ignore the final entry since we do not enforce non-zero selectors in +// Honk. +void verify_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plonk_prover) +{ + auto& honk_store = honk_prover.key->polynomial_store; + auto& plonk_store = plonk_prover.key->polynomial_store; + + // Check that all selectors agree (aside from the final element which will differ due to not enforcing non-zero + // selectors in Honk). + for (auto& entry : honk_store) { + std::string key = entry.first; + bool is_selector = (key.find("q_") != std::string::npos) || (key.find("table_type") != std::string::npos); + if (plonk_store.contains(key) && is_selector) { + // check equality for all but final entry + for (size_t i = 0; i < honk_store.get(key).size() - 1; ++i) { + ASSERT_EQ(honk_store.get(key)[i], plonk_store.get(key)[i]); + } + } + } + + // Check that sorted witness-table and table polys agree + for (auto& entry : honk_store) { + std::string key = entry.first; + bool is_sorted_table = (key.find("s_") != std::string::npos); + bool is_table = (key.find("table_value_") != std::string::npos); + if (plonk_store.contains(key) && (is_sorted_table || is_table)) { + ASSERT_EQ(honk_store.get(key), plonk_store.get(key)); + } + } + + // Check that all wires agree + // Note: for Honk, wires are owned directly by the prover. For Plonk they are stored in the key. + for (size_t i = 0; i < 4; ++i) { + std::string label = "w_" + std::to_string(i + 1) + "_lagrange"; + ASSERT_EQ(honk_prover.wire_polynomials[i], plonk_prover.key->polynomial_store.get(label)); + } +} + +/** + * @brief TEMPORARY (verbose) method for checking consistency of polynomials computed by Ultra Plonk/Honk composers + * + * @param honk_prover + * @param plonk_prover + */ +void check_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plonk_prover) +{ + auto& honk_store = honk_prover.key->polynomial_store; + auto& plonk_store = plonk_prover.key->polynomial_store; + for (auto& entry : honk_store) { + std::string key = entry.first; + if (plonk_store.contains(key)) { + + bool polys_equal = (honk_store.get(key) == plonk_store.get(key)); + if (polys_equal) { + info("Equal: ", key); + } + if (!polys_equal) { + info("UNEQUAL: ", key); + } + } + } + + for (size_t i = 0; i < 4; ++i) { + std::string label = "w_" + std::to_string(i + 1) + "_lagrange"; + bool wire_equal = (honk_prover.wire_polynomials[i] == plonk_prover.key->polynomial_store.get(label)); + if (wire_equal) { + info("Wire Equal: ", i); + } + if (!wire_equal) { + info("Wire UNEQUAL: ", i); + } + } + + // std::string label = "w_1_lagrange"; + // for (size_t i = 0; i < plonk_store.get(label).size(); ++i) { + // auto val_honk = honk_prover.wire_polynomials[0][i]; + // // auto val_honk = honk_store.get(label)[i]; + // auto val_plonk = plonk_store.get(label)[i]; + // if (val_honk != val_plonk) { + // info("UNEQUAL index = ", i); + // info("honk: ",val_honk); + // info("plonk: ", val_plonk); + // } + // } +} + +TEST(UltraHonkComposer, create_gates_from_plookup_accumulators) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + barretenberg::fr input_value = fr::random_element(); + { + + const fr input_hi = uint256_t(input_value).slice(126, 256); + const fr input_lo = uint256_t(input_value).slice(0, 126); + const auto input_hi_index = honk_composer.add_variable(input_hi); + const auto input_lo_index = honk_composer.add_variable(input_lo); + + const auto sequence_data_hi = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + const auto lookup_witnesses_hi = honk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = honk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + } + { + const fr input_hi = uint256_t(input_value).slice(126, 256); + const fr input_lo = uint256_t(input_value).slice(0, 126); + const auto input_hi_index = plonk_composer.add_variable(input_hi); + const auto input_lo_index = plonk_composer.add_variable(input_lo); + + const auto sequence_data_hi = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = + plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + const auto lookup_witnesses_hi = plonk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + const auto lookup_witnesses_lo = plonk_composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +/** + * @brief Build UltraHonkComposer + * + */ +TEST(UltraHonkComposer, test_no_lookup_proof) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + size_t MM = 4; + for (size_t i = 0; i < MM; ++i) { + for (size_t j = 0; j < MM; ++j) { + uint64_t left = static_cast(j); + uint64_t right = static_cast(i); + uint32_t left_idx = honk_composer.add_variable(fr(left)); + uint32_t right_idx = honk_composer.add_variable(fr(right)); + uint32_t result_idx = honk_composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = + honk_composer.add_variable(fr(left) + fr(right) + honk_composer.get_variable(result_idx)); + honk_composer.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + for (size_t i = 0; i < MM; ++i) { + for (size_t j = 0; j < MM; ++j) { + uint64_t left = static_cast(j); + uint64_t right = static_cast(i); + uint32_t left_idx = plonk_composer.add_variable(fr(left)); + uint32_t right_idx = plonk_composer.add_variable(fr(right)); + uint32_t result_idx = plonk_composer.add_variable(fr(left ^ right)); + + uint32_t add_idx = + plonk_composer.add_variable(fr(left) + fr(right) + plonk_composer.get_variable(result_idx)); + plonk_composer.create_big_add_gate( + { left_idx, right_idx, result_idx, add_idx, fr(1), fr(1), fr(1), fr(-1), fr(0) }); + } + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, test_elliptic_gate) +{ + typedef grumpkin::g1::affine_element affine_element; + typedef grumpkin::g1::element element; + + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = honk_composer.add_variable(p1.x); + uint32_t y1 = honk_composer.add_variable(p1.y); + uint32_t x2 = honk_composer.add_variable(p2.x); + uint32_t y2 = honk_composer.add_variable(p2.y); + uint32_t x3 = honk_composer.add_variable(p3.x); + uint32_t y3 = honk_composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + honk_composer.create_ecc_add_gate(gate); + + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); + affine_element p2_endo = p2; + p2_endo.x *= beta; + p3 = affine_element(element(p1) + element(p2_endo)); + x3 = honk_composer.add_variable(p3.x); + y3 = honk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + honk_composer.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = honk_composer.add_variable(p3.x); + y3 = honk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + honk_composer.create_ecc_add_gate(gate); + } + { + affine_element p1 = crypto::generators::get_generator_data({ 0, 0 }).generator; + affine_element p2 = crypto::generators::get_generator_data({ 0, 1 }).generator; + affine_element p3(element(p1) + element(p2)); + + uint32_t x1 = plonk_composer.add_variable(p1.x); + uint32_t y1 = plonk_composer.add_variable(p1.y); + uint32_t x2 = plonk_composer.add_variable(p2.x); + uint32_t y2 = plonk_composer.add_variable(p2.y); + uint32_t x3 = plonk_composer.add_variable(p3.x); + uint32_t y3 = plonk_composer.add_variable(p3.y); + + ecc_add_gate gate{ x1, y1, x2, y2, x3, y3, 1, 1 }; + plonk_composer.create_ecc_add_gate(gate); + + grumpkin::fq beta = grumpkin::fq::cube_root_of_unity(); + affine_element p2_endo = p2; + p2_endo.x *= beta; + p3 = affine_element(element(p1) + element(p2_endo)); + x3 = plonk_composer.add_variable(p3.x); + y3 = plonk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta, 1 }; + plonk_composer.create_ecc_add_gate(gate); + + p2_endo.x *= beta; + p3 = affine_element(element(p1) - element(p2_endo)); + x3 = plonk_composer.add_variable(p3.x); + y3 = plonk_composer.add_variable(p3.y); + gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; + plonk_composer.create_ecc_add_gate(gate); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_trivial_tag_permutation) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr b = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(b); + auto d_idx = honk_composer.add_variable(a); + + honk_composer.create_add_gate( + { a_idx, b_idx, honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { c_idx, d_idx, honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), fr::zero() }); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(b_idx, 1); + honk_composer.assign_tag(c_idx, 2); + honk_composer.assign_tag(d_idx, 2); + } + { + fr b = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(b); + auto d_idx = plonk_composer.add_variable(a); + + plonk_composer.create_add_gate( + { a_idx, b_idx, plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { c_idx, d_idx, plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), fr::zero() }); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(b_idx, 1); + plonk_composer.assign_tag(c_idx, 2); + plonk_composer.assign_tag(d_idx, 2); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_trivial_tag_permutation_and_cycles) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr c = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(a); + honk_composer.assert_equal(a_idx, b_idx); + auto c_idx = honk_composer.add_variable(c); + auto d_idx = honk_composer.add_variable(c); + honk_composer.assert_equal(c_idx, d_idx); + auto e_idx = honk_composer.add_variable(a); + auto f_idx = honk_composer.add_variable(a); + honk_composer.assert_equal(e_idx, f_idx); + auto g_idx = honk_composer.add_variable(c); + auto h_idx = honk_composer.add_variable(c); + honk_composer.assert_equal(g_idx, h_idx); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(c_idx, 1); + honk_composer.assign_tag(e_idx, 2); + honk_composer.assign_tag(g_idx, 2); + + honk_composer.create_add_gate( + { b_idx, a_idx, honk_composer.get_zero_idx(), fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { c_idx, g_idx, honk_composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + honk_composer.create_add_gate( + { e_idx, f_idx, honk_composer.get_zero_idx(), fr::one(), -fr::one(), fr::zero(), fr::zero() }); + } + { + fr c = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(a); + plonk_composer.assert_equal(a_idx, b_idx); + auto c_idx = plonk_composer.add_variable(c); + auto d_idx = plonk_composer.add_variable(c); + plonk_composer.assert_equal(c_idx, d_idx); + auto e_idx = plonk_composer.add_variable(a); + auto f_idx = plonk_composer.add_variable(a); + plonk_composer.assert_equal(e_idx, f_idx); + auto g_idx = plonk_composer.add_variable(c); + auto h_idx = plonk_composer.add_variable(c); + plonk_composer.assert_equal(g_idx, h_idx); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(c_idx, 1); + plonk_composer.assign_tag(e_idx, 2); + plonk_composer.assign_tag(g_idx, 2); + + plonk_composer.create_add_gate( + { b_idx, a_idx, plonk_composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { c_idx, g_idx, plonk_composer.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + plonk_composer.create_add_gate( + { e_idx, f_idx, plonk_composer.zero_idx, fr::one(), -fr::one(), fr::zero(), fr::zero() }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, bad_tag_permutation) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fr a = fr::random_element(); + { + fr b = -a; + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(b); + auto d_idx = honk_composer.add_variable(a + 1); + + honk_composer.create_add_gate({ a_idx, b_idx, honk_composer.get_zero_idx(), 1, 1, 0, 0 }); + honk_composer.create_add_gate({ c_idx, d_idx, honk_composer.get_zero_idx(), 1, 1, 0, -1 }); + + honk_composer.create_tag(1, 2); + honk_composer.create_tag(2, 1); + + honk_composer.assign_tag(a_idx, 1); + honk_composer.assign_tag(b_idx, 1); + honk_composer.assign_tag(c_idx, 2); + honk_composer.assign_tag(d_idx, 2); + } + { + fr b = -a; + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(b); + auto d_idx = plonk_composer.add_variable(a + 1); + + plonk_composer.create_add_gate({ a_idx, b_idx, plonk_composer.zero_idx, 1, 1, 0, 0 }); + plonk_composer.create_add_gate({ c_idx, d_idx, plonk_composer.zero_idx, 1, 1, 0, -1 }); + + plonk_composer.create_tag(1, 2); + plonk_composer.create_tag(2, 1); + + plonk_composer.assign_tag(a_idx, 1); + plonk_composer.assign_tag(b_idx, 1); + plonk_composer.assign_tag(c_idx, 2); + plonk_composer.assign_tag(d_idx, 2); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_widget) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = honk_composer.add_variable(a); + auto b_idx = honk_composer.add_variable(b); + auto c_idx = honk_composer.add_variable(c); + auto d_idx = honk_composer.add_variable(d); + honk_composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + } + { + fr a = fr::one(); + fr b = fr(2); + fr c = fr(3); + fr d = fr(4); + + auto a_idx = plonk_composer.add_variable(a); + auto b_idx = plonk_composer.add_variable(b); + auto c_idx = plonk_composer.add_variable(c); + auto d_idx = plonk_composer.add_variable(d); + plonk_composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_with_edges_gate) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_composer, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, + 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); + + honk_composer.create_sort_constraint_with_edges(idx, 1, 29); + } + { + auto idx = add_variables(plonk_composer, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, + 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); + + plonk_composer.create_sort_constraint_with_edges(idx, 1, 29); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_constraint) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto indices = + add_variables(honk_composer, { 1, 0, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); + for (size_t i = 0; i < indices.size(); i++) { + honk_composer.create_new_range_constraint(indices[i], 79); + } + honk_composer.create_dummy_constraints(indices); + } + { + auto indices = + add_variables(plonk_composer, { 1, 0, 3, 80, 5, 6, 29, 8, 15, 11, 32, 21, 42, 79, 16, 10, 3, 26, 13, 14 }); + for (size_t i = 0; i < indices.size(); i++) { + plonk_composer.create_new_range_constraint(indices[i], 79); + } + plonk_composer.create_dummy_constraints(indices); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_with_gates) +{ + + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + honk_composer.create_new_range_constraint(idx[i], 8); + } + + honk_composer.create_add_gate( + { idx[0], idx[1], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + honk_composer.create_add_gate( + { idx[2], idx[3], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + honk_composer.create_add_gate( + { idx[4], idx[5], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + honk_composer.create_add_gate( + { idx[6], idx[7], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + } + { + auto idx = add_variables(plonk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + plonk_composer.create_new_range_constraint(idx[i], 8); + } + + plonk_composer.create_add_gate( + { idx[0], idx[1], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + plonk_composer.create_add_gate( + { idx[2], idx[3], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + plonk_composer.create_add_gate( + { idx[4], idx[5], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + plonk_composer.create_add_gate( + { idx[6], idx[7], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, range_with_gates_where_range_is_not_a_power_of_two) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + auto idx = add_variables(honk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + honk_composer.create_new_range_constraint(idx[i], 12); + } + + honk_composer.create_add_gate( + { idx[0], idx[1], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -3 }); + honk_composer.create_add_gate( + { idx[2], idx[3], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -7 }); + honk_composer.create_add_gate( + { idx[4], idx[5], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -11 }); + honk_composer.create_add_gate( + { idx[6], idx[7], honk_composer.get_zero_idx(), fr::one(), fr::one(), fr::zero(), -15 }); + } + { + auto idx = add_variables(plonk_composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); + for (size_t i = 0; i < idx.size(); i++) { + plonk_composer.create_new_range_constraint(idx[i], 12); + } + + plonk_composer.create_add_gate( + { idx[0], idx[1], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -3 }); + plonk_composer.create_add_gate( + { idx[2], idx[3], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); + plonk_composer.create_add_gate( + { idx[4], idx[5], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); + plonk_composer.create_add_gate( + { idx[6], idx[7], plonk_composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, sort_widget_complex) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + { + std::vector a = { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; + std::vector ind; + for (size_t i = 0; i < a.size(); i++) + ind.emplace_back(honk_composer.add_variable(a[i])); + honk_composer.create_sort_constraint(ind); + } + { + std::vector a = { 1, 3, 4, 7, 7, 8, 16, 14, 15, 15, 18, 19, 21, 21, 24, 25, 26, 27, 30, 32 }; + std::vector ind; + for (size_t i = 0; i < a.size(); i++) + ind.emplace_back(plonk_composer.add_variable(a[i])); + plonk_composer.create_sort_constraint(ind); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, composed_range_constraint) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto c = fr::random_element(); + { + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = honk_composer.add_variable(fr(e)); + honk_composer.create_add_gate( + { a_idx, honk_composer.get_zero_idx(), honk_composer.get_zero_idx(), 1, 0, 0, -fr(e) }); + honk_composer.decompose_into_default_range(a_idx, 134); + } + { + auto d = uint256_t(c).slice(0, 133); + auto e = fr(d); + auto a_idx = plonk_composer.add_variable(fr(e)); + plonk_composer.create_add_gate({ a_idx, plonk_composer.zero_idx, plonk_composer.zero_idx, 1, 0, 0, -fr(e) }); + plonk_composer.decompose_into_default_range(a_idx, 134); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, non_native_field_multiplication) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + fq a = fq::random_element(); + fq b = fq::random_element(); + { + uint256_t modulus = fq::modulus; + + uint1024_t a_big = uint512_t(uint256_t(a)); + uint1024_t b_big = uint512_t(uint256_t(b)); + uint1024_t p_big = uint512_t(uint256_t(modulus)); + + uint1024_t q_big = (a_big * b_big) / p_big; + uint1024_t r_big = (a_big * b_big) % p_big; + + uint256_t q(q_big.lo.lo); + uint256_t r(r_big.lo.lo); + + const auto split_into_limbs = [&](const uint512_t& input) { + constexpr size_t NUM_BITS = 68; + std::array limbs; + limbs[0] = input.slice(0, NUM_BITS).lo; + limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; + limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; + limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; + limbs[4] = fr(input.lo); + return limbs; + }; + + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; + limb_indices[0] = honk_composer.add_variable(limbs[0]); + limb_indices[1] = honk_composer.add_variable(limbs[1]); + limb_indices[2] = honk_composer.add_variable(limbs[2]); + limb_indices[3] = honk_composer.add_variable(limbs[3]); + limb_indices[4] = honk_composer.add_variable(limbs[4]); + return limb_indices; + }; + const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); + auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus)); + + const auto a_indices = get_limb_witness_indices(split_into_limbs(uint256_t(a))); + const auto b_indices = get_limb_witness_indices(split_into_limbs(uint256_t(b))); + const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); + const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); + + proof_system::non_native_field_witnesses inputs{ + a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), + }; + const auto [lo_1_idx, hi_1_idx] = honk_composer.evaluate_non_native_field_multiplication(inputs); + honk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + } + { + uint256_t modulus = fq::modulus; + + uint1024_t a_big = uint512_t(uint256_t(a)); + uint1024_t b_big = uint512_t(uint256_t(b)); + uint1024_t p_big = uint512_t(uint256_t(modulus)); + + uint1024_t q_big = (a_big * b_big) / p_big; + uint1024_t r_big = (a_big * b_big) % p_big; + + uint256_t q(q_big.lo.lo); + uint256_t r(r_big.lo.lo); + + const auto split_into_limbs = [&](const uint512_t& input) { + constexpr size_t NUM_BITS = 68; + std::array limbs; + limbs[0] = input.slice(0, NUM_BITS).lo; + limbs[1] = input.slice(NUM_BITS * 1, NUM_BITS * 2).lo; + limbs[2] = input.slice(NUM_BITS * 2, NUM_BITS * 3).lo; + limbs[3] = input.slice(NUM_BITS * 3, NUM_BITS * 4).lo; + limbs[4] = fr(input.lo); + return limbs; + }; + + const auto get_limb_witness_indices = [&](const std::array& limbs) { + std::array limb_indices; + limb_indices[0] = plonk_composer.add_variable(limbs[0]); + limb_indices[1] = plonk_composer.add_variable(limbs[1]); + limb_indices[2] = plonk_composer.add_variable(limbs[2]); + limb_indices[3] = plonk_composer.add_variable(limbs[3]); + limb_indices[4] = plonk_composer.add_variable(limbs[4]); + return limb_indices; + }; + const uint512_t BINARY_BASIS_MODULUS = uint512_t(1) << (68 * 4); + auto modulus_limbs = split_into_limbs(BINARY_BASIS_MODULUS - uint512_t(modulus)); + + const auto a_indices = get_limb_witness_indices(split_into_limbs(uint256_t(a))); + const auto b_indices = get_limb_witness_indices(split_into_limbs(uint256_t(b))); + const auto q_indices = get_limb_witness_indices(split_into_limbs(uint256_t(q))); + const auto r_indices = get_limb_witness_indices(split_into_limbs(uint256_t(r))); + + proof_system::plonk::UltraComposer::non_native_field_witnesses inputs{ + a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), + }; + const auto [lo_1_idx, hi_1_idx] = plonk_composer.evaluate_non_native_field_multiplication(inputs); + plonk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, rom) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto a = fr::random_element(); + auto b = fr::random_element(); + auto c = fr::random_element(); + auto d = fr::random_element(); + auto e = fr::random_element(); + auto f = fr::random_element(); + auto g = fr::random_element(); + auto h = fr::random_element(); + { + uint32_t rom_values[8]{ + honk_composer.add_variable(a), honk_composer.add_variable(b), honk_composer.add_variable(c), + honk_composer.add_variable(d), honk_composer.add_variable(e), honk_composer.add_variable(f), + honk_composer.add_variable(g), honk_composer.add_variable(h), + }; + + size_t rom_id = honk_composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + honk_composer.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(4)); + uint32_t c_idx = honk_composer.read_ROM_array(rom_id, honk_composer.add_variable(1)); + + const auto d_value = + honk_composer.get_variable(a_idx) + honk_composer.get_variable(b_idx) + honk_composer.get_variable(c_idx); + uint32_t d_idx = honk_composer.add_variable(d_value); + + honk_composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + } + { + uint32_t rom_values[8]{ + plonk_composer.add_variable(a), plonk_composer.add_variable(b), plonk_composer.add_variable(c), + plonk_composer.add_variable(d), plonk_composer.add_variable(e), plonk_composer.add_variable(f), + plonk_composer.add_variable(g), plonk_composer.add_variable(h), + }; + + size_t rom_id = plonk_composer.create_ROM_array(8); + + for (size_t i = 0; i < 8; ++i) { + plonk_composer.set_ROM_element(rom_id, i, rom_values[i]); + } + + uint32_t a_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(5)); + EXPECT_EQ(a_idx != rom_values[5], true); + uint32_t b_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(4)); + uint32_t c_idx = plonk_composer.read_ROM_array(rom_id, plonk_composer.add_variable(1)); + + const auto d_value = plonk_composer.get_variable(a_idx) + plonk_composer.get_variable(b_idx) + + plonk_composer.get_variable(c_idx); + uint32_t d_idx = plonk_composer.add_variable(d_value); + + plonk_composer.create_big_add_gate({ + a_idx, + b_idx, + c_idx, + d_idx, + 1, + 1, + 1, + -1, + 0, + }); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + check_consistency(honk_prover, plonk_prover); + verify_consistency(honk_prover, plonk_prover); +} + +TEST(UltraHonkComposer, ram) +{ + auto honk_composer = UltraHonkComposer(); + auto plonk_composer = proof_system::plonk::UltraComposer(); + + auto a = fr::random_element(); + auto b = fr::random_element(); + auto c = fr::random_element(); + auto d = fr::random_element(); + auto e = fr::random_element(); + auto f = fr::random_element(); + auto g = fr::random_element(); + auto h = fr::random_element(); + { + uint32_t ram_values[8]{ + honk_composer.add_variable(a), honk_composer.add_variable(b), honk_composer.add_variable(c), + honk_composer.add_variable(d), honk_composer.add_variable(e), honk_composer.add_variable(f), + honk_composer.add_variable(g), honk_composer.add_variable(h), + }; + + size_t ram_id = honk_composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + honk_composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(4)); + uint32_t c_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(1)); + + honk_composer.write_RAM_array(ram_id, honk_composer.add_variable(4), honk_composer.add_variable(500)); + uint32_t d_idx = honk_composer.read_RAM_array(ram_id, honk_composer.add_variable(4)); + + EXPECT_EQ(honk_composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = honk_composer.get_variable(a_idx) + honk_composer.get_variable(b_idx) + + honk_composer.get_variable(c_idx) + honk_composer.get_variable(d_idx); + uint32_t e_idx = honk_composer.add_variable(e_value); + + honk_composer.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + honk_composer.create_big_add_gate( + { + honk_composer.get_zero_idx(), + honk_composer.get_zero_idx(), + honk_composer.get_zero_idx(), + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + } + { + uint32_t ram_values[8]{ + plonk_composer.add_variable(a), plonk_composer.add_variable(b), plonk_composer.add_variable(c), + plonk_composer.add_variable(d), plonk_composer.add_variable(e), plonk_composer.add_variable(f), + plonk_composer.add_variable(g), plonk_composer.add_variable(h), + }; + + size_t ram_id = plonk_composer.create_RAM_array(8); + + for (size_t i = 0; i < 8; ++i) { + plonk_composer.init_RAM_element(ram_id, i, ram_values[i]); + } + + uint32_t a_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(5)); + EXPECT_EQ(a_idx != ram_values[5], true); + + uint32_t b_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(4)); + uint32_t c_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(1)); + + plonk_composer.write_RAM_array(ram_id, plonk_composer.add_variable(4), plonk_composer.add_variable(500)); + uint32_t d_idx = plonk_composer.read_RAM_array(ram_id, plonk_composer.add_variable(4)); + + EXPECT_EQ(plonk_composer.get_variable(d_idx), 500); + + // ensure these vars get used in another arithmetic gate + const auto e_value = plonk_composer.get_variable(a_idx) + plonk_composer.get_variable(b_idx) + + plonk_composer.get_variable(c_idx) + plonk_composer.get_variable(d_idx); + uint32_t e_idx = plonk_composer.add_variable(e_value); + + plonk_composer.create_big_add_gate( + { + a_idx, + b_idx, + c_idx, + d_idx, + -1, + -1, + -1, + -1, + 0, + }, + true); + plonk_composer.create_big_add_gate( + { + plonk_composer.zero_idx, + plonk_composer.zero_idx, + plonk_composer.zero_idx, + e_idx, + 0, + 0, + 0, + 0, + 0, + }, + false); + } + + auto honk_prover = honk_composer.create_prover(); + auto plonk_prover = plonk_composer.create_prover(); + + verify_consistency(honk_prover, plonk_prover); +} + +} // namespace test_ultra_honk_composer diff --git a/cpp/src/barretenberg/honk/flavor/flavor.hpp b/cpp/src/barretenberg/honk/flavor/flavor.hpp index ad47783e1d..c420ed64aa 100644 --- a/cpp/src/barretenberg/honk/flavor/flavor.hpp +++ b/cpp/src/barretenberg/honk/flavor/flavor.hpp @@ -14,7 +14,7 @@ struct StandardArithmetization { * This separation must be maintained to allow for programmatic access, but the ordering of the * polynomials can be permuted within each category if necessary. Polynomials can also be added * or removed (assuming consistency with the prover algorithm) but the constants describing the - * number of poynomials in each category must be manually updated. + * number of polynomials in each category must be manually updated. * */ enum POLYNOMIAL { @@ -187,4 +187,58 @@ struct StandardHonk { return output; } }; + +struct UltraArithmetization { + /** + * @brief All of the multivariate polynomials used by the Ultra Honk Prover. + * @details The polynomials are broken into three categories: precomputed, witness, and shifted. + * This separation must be maintained to allow for programmatic access, but the ordering of the + * polynomials can be permuted within each category if necessary. Polynomials can also be added + * or removed (assuming consistency with the prover algorithm) but the constants describing the + * number of polynomials in each category must be manually updated. + * + */ + enum POLYNOMIAL { + /* --- PRECOMPUTED POLYNOMIALS --- */ + Q_C, + Q_L, + Q_R, + Q_O, + Q_4, + Q_M, + QARITH, + QSORT, + QELLIPTIC, + QAUX, + QLOOKUPTYPE, + SIGMA_1, + SIGMA_2, + SIGMA_3, + SIGMA_4, + ID_1, + ID_2, + ID_3, + ID_4, + LAGRANGE_FIRST, + LAGRANGE_LAST, // = LAGRANGE_N-1 whithout ZK, but can be less + /* --- WITNESS POLYNOMIALS --- */ + W_L, + W_R, + W_O, + W_4, + S_1, + S_2, + S_3, + S_4, + Z_PERM, + Z_LOOKUP, + /* --- SHIFTED POLYNOMIALS --- */ + W_1_SHIFT, + W_4_SHIFT, + Z_PERM_SHIFT, + Z_LOOKUP_SHIFT, + /* --- --- */ + COUNT // for programmatic determination of NUM_POLYNOMIALS + }; +}; } // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/proof_system/prover_library.cpp b/cpp/src/barretenberg/honk/proof_system/prover_library.cpp index d99a2947a4..753bee2b92 100644 --- a/cpp/src/barretenberg/honk/proof_system/prover_library.cpp +++ b/cpp/src/barretenberg/honk/proof_system/prover_library.cpp @@ -1,6 +1,7 @@ #include "prover_library.hpp" #include "barretenberg/plonk/proof_system/types/prover_settings.hpp" #include +#include namespace proof_system::honk::prover_library { @@ -55,19 +56,18 @@ Polynomial compute_permutation_grand_product(std::shared_ptr // Populate wire and permutation polynomials std::array, program_width> wires; std::array, program_width> sigmas; + std::array, program_width> ids; for (size_t i = 0; i < program_width; ++i) { - std::string sigma_id = "sigma_" + std::to_string(i + 1) + "_lagrange"; wires[i] = wire_polynomials[i]; - sigmas[i] = key->polynomial_store.get(sigma_id); + sigmas[i] = key->polynomial_store.get("sigma_" + std::to_string(i + 1) + "_lagrange"); + ids[i] = key->polynomial_store.get("id_" + std::to_string(i + 1) + "_lagrange"); } // Step (1) // TODO(#222)(kesha): Change the order to engage automatic prefetching and get rid of redundant computation for (size_t i = 0; i < key->circuit_size; ++i) { for (size_t k = 0; k < program_width; ++k) { - // Note(luke): this idx could be replaced by proper ID polys if desired - Fr idx = k * key->circuit_size + i; - numerator_accumulator[k][i] = wires[k][i] + (idx * beta) + gamma; // w_k(i) + β.(k*n+i) + γ + numerator_accumulator[k][i] = wires[k][i] + (ids[k][i] * beta) + gamma; // w_k(i) + β.id_k(i) + γ denominator_accumulator[k][i] = wires[k][i] + (sigmas[k][i] * beta) + gamma; // w_k(i) + β.σ_k(i) + γ } } diff --git a/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp b/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp index 0902129370..f62380ebc9 100644 --- a/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp +++ b/cpp/src/barretenberg/honk/proof_system/prover_library.test.cpp @@ -13,7 +13,6 @@ using namespace proof_system::honk; namespace prover_library_tests { -// field is named Fscalar here because of clash with the Fr template class ProverLibraryTests : public testing::Test { using Polynomial = barretenberg::Polynomial; @@ -60,13 +59,17 @@ template class ProverLibraryTests : public testing::Test { // can simply be random. We're not interested in the particular properties of the result. std::vector wires; std::vector sigmas; + std::vector ids; for (size_t i = 0; i < program_width; ++i) { wires.emplace_back(get_random_polynomial(num_gates)); sigmas.emplace_back(get_random_polynomial(num_gates)); + ids.emplace_back(get_random_polynomial(num_gates)); - // Add sigma polys to proving_key; to be used by the prover in constructing it's own z_perm - std::string sigma_id = "sigma_" + std::to_string(i + 1) + "_lagrange"; - proving_key->polynomial_store.put(sigma_id, Polynomial{ sigmas[i] }); + // Add sigma/ID polys to proving_key; to be used by the prover in constructing it's own z_perm + std::string sigma_label = "sigma_" + std::to_string(i + 1) + "_lagrange"; + proving_key->polynomial_store.put(sigma_label, Polynomial{ sigmas[i] }); + std::string id_label = "id_" + std::to_string(i + 1) + "_lagrange"; + proving_key->polynomial_store.put(id_label, Polynomial{ ids[i] }); } // Get random challenges @@ -109,8 +112,7 @@ template class ProverLibraryTests : public testing::Test { // Step (1) for (size_t i = 0; i < proving_key->circuit_size; ++i) { for (size_t k = 0; k < program_width; ++k) { - FF idx = k * proving_key->circuit_size + i; // id_k[i] - numererator_accum[k][i] = wires[k][i] + (idx * beta) + gamma; // w_k(i) + β.(k*n+i) + γ + numererator_accum[k][i] = wires[k][i] + (ids[k][i] * beta) + gamma; // w_k(i) + β.id_k(i) + γ denominator_accum[k][i] = wires[k][i] + (sigmas[k][i] * beta) + gamma; // w_k(i) + β.σ_k(i) + γ } } @@ -206,8 +208,8 @@ template class ProverLibraryTests : public testing::Test { // ∏(s_k + βs_{k+1} + γ(1 + β)) // // in a way that is simple to read (but inefficient). See prover library method for more details. - const Fr eta_sqr = eta.sqr(); - const Fr eta_cube = eta_sqr * eta; + const FF eta_sqr = eta.sqr(); + const FF eta_cube = eta_sqr * eta; std::array accumulators; for (size_t i = 0; i < 4; ++i) { @@ -219,12 +221,12 @@ template class ProverLibraryTests : public testing::Test { // Note: block_mask is used for efficient modulus, i.e. i % N := i & (N-1), for N = 2^k const size_t block_mask = circuit_size - 1; // Initialize 't(X)' to be used in an expression of the form t(X) + β*t(Xω) - Fr table_i = tables[0][0] + tables[1][0] * eta + tables[2][0] * eta_sqr + tables[3][0] * eta_cube; + FF table_i = tables[0][0] + tables[1][0] * eta + tables[2][0] * eta_sqr + tables[3][0] * eta_cube; for (size_t i = 0; i < circuit_size; ++i) { size_t shift_idx = (i + 1) & block_mask; // f = (w_1 + q_2*w_1(Xω)) + η(w_2 + q_m*w_2(Xω)) + η²(w_3 + q_c*w_3(Xω)) + η³q_index. - Fr f_i = (wires[0][i] + wires[0][shift_idx] * column_1_step_size[i]) + + FF f_i = (wires[0][i] + wires[0][shift_idx] * column_1_step_size[i]) + (wires[1][i] + wires[1][shift_idx] * column_2_step_size[i]) * eta + (wires[2][i] + wires[2][shift_idx] * column_3_step_size[i]) * eta_sqr + eta_cube * lookup_index_selector[i]; @@ -233,17 +235,17 @@ template class ProverLibraryTests : public testing::Test { accumulators[0][i] = lookup_selector[i] * f_i + gamma; // t = t_1 + ηt_2 + η²t_3 + η³t_4 - Fr table_i_plus_1 = tables[0][shift_idx] + eta * tables[1][shift_idx] + eta_sqr * tables[2][shift_idx] + + FF table_i_plus_1 = tables[0][shift_idx] + eta * tables[1][shift_idx] + eta_sqr * tables[2][shift_idx] + eta_cube * tables[3][shift_idx]; // t + βt(Xω) + γ(1 + β) - accumulators[1][i] = table_i + table_i_plus_1 * beta + gamma * (Fr::one() + beta); + accumulators[1][i] = table_i + table_i_plus_1 * beta + gamma * (FF::one() + beta); // (1 + β) - accumulators[2][i] = Fr::one() + beta; + accumulators[2][i] = FF::one() + beta; // s + βs(Xω) + γ(1 + β) - accumulators[3][i] = s_lagrange[i] + beta * s_lagrange[shift_idx] + gamma * (Fr::one() + beta); + accumulators[3][i] = s_lagrange[i] + beta * s_lagrange[shift_idx] + gamma * (FF::one() + beta); // Set t(X_i) for next iteration table_i = table_i_plus_1; @@ -303,8 +305,8 @@ template class ProverLibraryTests : public testing::Test { prover_library::compute_sorted_list_accumulator(proving_key, sorted_list_polynomials, eta); // Method 2: Compute local sorted list accumulator simply and inefficiently - const Fr eta_sqr = eta.sqr(); - const Fr eta_cube = eta_sqr * eta; + const FF eta_sqr = eta.sqr(); + const FF eta_cube = eta_sqr * eta; // Compute s = s_1 + η*s_2 + η²*s_3 + η³*s_4 Polynomial sorted_list_accumulator_expected{ sorted_list_polynomials[0] }; diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp new file mode 100644 index 0000000000..769e744a6d --- /dev/null +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.cpp @@ -0,0 +1,56 @@ +#include "ultra_prover.hpp" +#include +#include +#include "barretenberg/honk/proof_system/prover_library.hpp" +#include "barretenberg/honk/sumcheck/sumcheck.hpp" +#include +#include "barretenberg/honk/sumcheck/polynomials/univariate.hpp" // will go away +#include "barretenberg/honk/utils/power_polynomial.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include +#include +#include +#include +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/ecc/curves/bn254/g1.hpp" +#include "barretenberg/honk/sumcheck/relations/arithmetic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include "barretenberg/transcript/transcript_wrappers.hpp" +#include +#include "barretenberg/honk/pcs/claim.hpp" + +namespace proof_system::honk { + +/** + * Create UltraHonkProver from proving key, witness and manifest. + * + * @param input_key Proving key. + * @param input_manifest Input manifest + * + * @tparam settings Settings class. + * */ +template +UltraHonkProver::UltraHonkProver(std::vector&& wire_polys, + std::shared_ptr input_key) + : wire_polynomials(wire_polys) + , key(input_key) + , queue(key, transcript) +{} + +template plonk::proof& UltraHonkProver::export_proof() +{ + proof.proof_data = transcript.proof_data; + return proof; +} + +template plonk::proof& UltraHonkProver::construct_proof() +{ + return export_proof(); +} + +template class UltraHonkProver; + +} // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp new file mode 100644 index 0000000000..b645e5863f --- /dev/null +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -0,0 +1,63 @@ +#pragma once +#include "barretenberg/ecc/curves/bn254/fr.hpp" +#include "barretenberg/honk/pcs/shplonk/shplonk.hpp" +#include "barretenberg/polynomials/polynomial.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include +#include "barretenberg/plonk/proof_system/proving_key/proving_key.hpp" +#include "barretenberg/honk/pcs/commitment_key.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/types/program_settings.hpp" +#include "barretenberg/honk/pcs/gemini/gemini.hpp" +#include "barretenberg/honk/pcs/shplonk/shplonk_single.hpp" +#include "barretenberg/honk/pcs/kzg/kzg.hpp" +#include "barretenberg/honk/transcript/transcript.hpp" +#include "barretenberg/honk/sumcheck/sumcheck.hpp" +#include "barretenberg/honk/sumcheck/sumcheck_output.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include "barretenberg/honk/pcs/claim.hpp" +#include "barretenberg/honk/proof_system/prover_library.hpp" + +namespace proof_system::honk { + +// TODO(luke): The naming here is awkward. The Standard Honk prover is called "Prover" and aliased as StandardProver. To +// be consistent with that convention outside of the prover class itself, I've called this class UltraHonkProver and use +// the alias UltraProver externally. Resolve. +template class UltraHonkProver { + + using Fr = barretenberg::fr; + using Polynomial = barretenberg::Polynomial; + using Commitment = barretenberg::g1::affine_element; + using POLYNOMIAL = proof_system::honk::StandardArithmetization::POLYNOMIAL; + + public: + UltraHonkProver(std::vector&& wire_polys, + std::shared_ptr input_key = nullptr); + + plonk::proof& export_proof(); + plonk::proof& construct_proof(); + + ProverTranscript transcript; + + std::vector wire_polynomials; + + std::shared_ptr key; + + work_queue queue; + + private: + plonk::proof proof; +}; + +extern template class UltraHonkProver; + +using UltraProver = UltraHonkProver; + +} // namespace proof_system::honk diff --git a/cpp/src/barretenberg/honk/proof_system/verifier.cpp b/cpp/src/barretenberg/honk/proof_system/verifier.cpp index 26e4b7e033..b26f10fa47 100644 --- a/cpp/src/barretenberg/honk/proof_system/verifier.cpp +++ b/cpp/src/barretenberg/honk/proof_system/verifier.cpp @@ -3,7 +3,6 @@ #include #include #include "barretenberg/honk/transcript/transcript.hpp" -#include "barretenberg/plonk/proof_system/constants.hpp" #include "./verifier.hpp" #include "barretenberg/plonk/proof_system/public_inputs/public_inputs.hpp" #include "barretenberg/ecc/curves/bn254/fr.hpp" diff --git a/cpp/src/barretenberg/honk/proof_system/verifier.test.cpp b/cpp/src/barretenberg/honk/proof_system/verifier.test.cpp index b98d39e893..903ea36adb 100644 --- a/cpp/src/barretenberg/honk/proof_system/verifier.test.cpp +++ b/cpp/src/barretenberg/honk/proof_system/verifier.test.cpp @@ -1,5 +1,4 @@ #include "barretenberg/numeric/bitop/get_msb.hpp" -#include "barretenberg/plonk/proof_system/constants.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include "barretenberg/honk/flavor/flavor.hpp" #include "prover.hpp" diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp index 5ecdedf61d..61f799e030 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp @@ -90,4 +90,91 @@ template class GrandProductComputationRelation { (w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma)); }; }; + +// TODO(luke): With Cody's Flavor work it should be easier to create a simple templated relation +// for handling arbitrary width. For now I'm duplicating the width 3 logic for width 4. +template class UltraGrandProductComputationRelation { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 6; + using MULTIVARIATE = proof_system::honk::UltraArithmetization::POLYNOMIAL; + + /** + * @brief Compute contribution of the permutation relation for a given edge (internal function) + * + * @details This the relation confirms faithful calculation of the grand + * product polynomial Z_perm. + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + inline void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters& relation_parameters, + const FF& scaling_factor) const + { + const auto& beta = relation_parameters.beta; + const auto& gamma = relation_parameters.gamma; + const auto& public_input_delta = relation_parameters.public_input_delta; + + auto w_1 = UnivariateView(extended_edges[MULTIVARIATE::W_L]); + auto w_2 = UnivariateView(extended_edges[MULTIVARIATE::W_R]); + auto w_3 = UnivariateView(extended_edges[MULTIVARIATE::W_O]); + auto w_4 = UnivariateView(extended_edges[MULTIVARIATE::W_4]); + auto sigma_1 = UnivariateView(extended_edges[MULTIVARIATE::SIGMA_1]); + auto sigma_2 = UnivariateView(extended_edges[MULTIVARIATE::SIGMA_2]); + auto sigma_3 = UnivariateView(extended_edges[MULTIVARIATE::SIGMA_3]); + auto sigma_4 = UnivariateView(extended_edges[MULTIVARIATE::SIGMA_4]); + auto id_1 = UnivariateView(extended_edges[MULTIVARIATE::ID_1]); + auto id_2 = UnivariateView(extended_edges[MULTIVARIATE::ID_2]); + auto id_3 = UnivariateView(extended_edges[MULTIVARIATE::ID_3]); + auto id_4 = UnivariateView(extended_edges[MULTIVARIATE::ID_4]); + auto z_perm = UnivariateView(extended_edges[MULTIVARIATE::Z_PERM]); + auto z_perm_shift = UnivariateView(extended_edges[MULTIVARIATE::Z_PERM_SHIFT]); + auto lagrange_first = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_FIRST]); + auto lagrange_last = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_LAST]); + + // Contribution (1) + evals += (((z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * + (w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma)) - + ((z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * + (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * (w_4 + sigma_4 * beta + gamma))) * + scaling_factor; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + auto& purported_evaluations, + const RelationParameters& relation_parameters) const + { + const auto& beta = relation_parameters.beta; + const auto& gamma = relation_parameters.gamma; + const auto& public_input_delta = relation_parameters.public_input_delta; + + auto w_1 = purported_evaluations[MULTIVARIATE::W_L]; + auto w_2 = purported_evaluations[MULTIVARIATE::W_R]; + auto w_3 = purported_evaluations[MULTIVARIATE::W_O]; + auto w_4 = purported_evaluations[MULTIVARIATE::W_4]; + auto sigma_1 = purported_evaluations[MULTIVARIATE::SIGMA_1]; + auto sigma_2 = purported_evaluations[MULTIVARIATE::SIGMA_2]; + auto sigma_3 = purported_evaluations[MULTIVARIATE::SIGMA_3]; + auto sigma_4 = purported_evaluations[MULTIVARIATE::SIGMA_4]; + auto id_1 = purported_evaluations[MULTIVARIATE::ID_1]; + auto id_2 = purported_evaluations[MULTIVARIATE::ID_2]; + auto id_3 = purported_evaluations[MULTIVARIATE::ID_3]; + auto id_4 = purported_evaluations[MULTIVARIATE::ID_4]; + auto z_perm = purported_evaluations[MULTIVARIATE::Z_PERM]; + auto z_perm_shift = purported_evaluations[MULTIVARIATE::Z_PERM_SHIFT]; + auto lagrange_first = purported_evaluations[MULTIVARIATE::LAGRANGE_FIRST]; + auto lagrange_last = purported_evaluations[MULTIVARIATE::LAGRANGE_LAST]; + + // Contribution (1) + full_honk_relation_value += + ((z_perm + lagrange_first) * (w_1 + beta * id_1 + gamma) * (w_2 + beta * id_2 + gamma) * + (w_3 + beta * id_3 + gamma) * (w_4 + beta * id_4 + gamma) - + (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + beta * sigma_1 + gamma) * + (w_2 + beta * sigma_2 + gamma) * (w_3 + beta * sigma_3 + gamma) * (w_4 + beta * sigma_4 + gamma)); + }; +}; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp index 788cec7f52..6aa7d9f3b2 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp @@ -45,4 +45,48 @@ template class GrandProductInitializationRelation { full_honk_relation_value += lagrange_last * z_perm_shift; }; }; + +// TODO(luke): The only difference between the Ultra relation and the Standard version is the enum +// used to refer into the edge polynomials. Seems desireable to not duplicate the code here but +// leaving this as is until Codys Flavor work is settled. +template class UltraGrandProductInitializationRelation { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 3; + using MULTIVARIATE = UltraArithmetization::POLYNOMIAL; // could just get from StandardArithmetization + + /** + * @brief Add contribution of the permutation relation for a given edge + * + * @details There are 2 relations associated with enforcing the wire copy relations + * This file handles the relation Z_perm_shift(n_last) = 0 via the relation: + * + * C(X) = L_LAST(X) * Z_perm_shift(X) + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters&, + const FF& scaling_factor) const + { + auto z_perm_shift = UnivariateView(extended_edges[MULTIVARIATE::Z_PERM_SHIFT]); + auto lagrange_last = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_LAST]); + + evals += (lagrange_last * z_perm_shift) * scaling_factor; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + auto& purported_evaluations, + const RelationParameters&) const + { + auto z_perm_shift = purported_evaluations[MULTIVARIATE::Z_PERM_SHIFT]; + auto lagrange_last = purported_evaluations[MULTIVARIATE::LAGRANGE_LAST]; + + full_honk_relation_value += lagrange_last * z_perm_shift; + }; +}; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp index a7860faeeb..e03f09f990 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp @@ -2,7 +2,6 @@ namespace proof_system::honk::sumcheck { -// TODO(#226)(Adrian): Remove zeta, alpha as they are not used by the relations. template struct RelationParameters { FF beta; FF gamma; diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp similarity index 57% rename from cpp/src/barretenberg/honk/sumcheck/relations/relation.test.cpp rename to cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp index 6b7e1ac521..5557d7d47c 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp @@ -1,3 +1,5 @@ +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" #include "relation.hpp" #include "barretenberg/honk/flavor/flavor.hpp" #include "arithmetic_relation.hpp" @@ -9,6 +11,7 @@ #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/numeric/random/engine.hpp" +#include #include using namespace proof_system::honk::sumcheck; /** @@ -21,13 +24,11 @@ using namespace proof_system::honk::sumcheck; points), * extends them (using barycentric formula) to six evaluation points, and stores them to an array of polynomials. */ -static const size_t input_univariate_length = 2; -static constexpr size_t FULL_RELATION_LENGTH = 5; -static const size_t NUM_POLYNOMIALS = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; +static const size_t INPUT_UNIVARIATE_LENGTH = 2; namespace proof_system::honk_relation_tests { -template class SumcheckRelation : public testing::Test { +template class RelationConsistency : public testing::Test { public: template using Univariate = Univariate; template using UnivariateView = UnivariateView; @@ -35,11 +36,12 @@ template class SumcheckRelation : public testing::Test { // TODO(#225)(Adrian): Accept FULL_RELATION_LENGTH as a template parameter for this function only, so that the test // can decide to which degree the polynomials must be extended. Possible accept an existing list of "edges" and // extend them to the degree. + template static std::array, NUM_POLYNOMIALS> compute_mock_extended_edges( - std::array, NUM_POLYNOMIALS>& input_univariates) + std::array, NUM_POLYNOMIALS>& input_univariates) { - BarycentricData barycentric_2_to_max = - BarycentricData(); + BarycentricData barycentric_2_to_max = + BarycentricData(); std::array, NUM_POLYNOMIALS> extended_univariates; for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { extended_univariates[i] = barycentric_2_to_max.extend(input_univariates[i]); @@ -136,6 +138,7 @@ template class SumcheckRelation : public testing::Test { * @param extended_edges * @param relation_parameters */ + template static void validate_evaluations( const Univariate& expected_evals, const auto relation, @@ -166,31 +169,35 @@ template class SumcheckRelation : public testing::Test { }; }; using FieldTypes = testing::Types; -TYPED_TEST_SUITE(SumcheckRelation, FieldTypes); +TYPED_TEST_SUITE(RelationConsistency, FieldTypes); #define SUMCHECK_RELATION_TYPE_ALIASES using FF = TypeParam; -TYPED_TEST(SumcheckRelation, ArithmeticRelation) +TYPED_TEST(RelationConsistency, ArithmeticRelation) { SUMCHECK_RELATION_TYPE_ALIASES using MULTIVARIATE = honk::StandardArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 5; + static const size_t NUM_POLYNOMIALS = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); auto run_test = [&relation_parameters](bool is_random_input) { std::array, NUM_POLYNOMIALS> extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; + std::array, NUM_POLYNOMIALS> input_polynomials; if (!is_random_input) { // evaluation form, i.e. input_univariate(0) = 1, input_univariate(1) = 2,.. The polynomial is x+1. for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ 1, 2 }); + input_polynomials[i] = Univariate({ 1, 2 }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); } else { // input_univariates are random polynomials of degree one for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { input_polynomials[i] = - Univariate({ FF::random_element(), FF::random_element() }); + Univariate({ FF::random_element(), FF::random_element() }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); }; auto relation = ArithmeticRelation(); // Manually compute the expected edge contribution @@ -207,33 +214,37 @@ TYPED_TEST(SumcheckRelation, ArithmeticRelation) // Ensure that expression changes are detected. // expected_evals, length 4, extends to { { 5, 22, 57, 116, 205} } for input polynomial {1, 2} auto expected_evals = (q_m * w_r * w_l) + (q_r * w_r) + (q_l * w_l) + (q_o * w_o) + (q_c); - TestFixture::validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); }; run_test(/* is_random_input=*/true); run_test(/* is_random_input=*/false); }; -TYPED_TEST(SumcheckRelation, GrandProductComputationRelation) +TYPED_TEST(RelationConsistency, GrandProductComputationRelation) { SUMCHECK_RELATION_TYPE_ALIASES using MULTIVARIATE = honk::StandardArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 5; + static const size_t NUM_POLYNOMIALS = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); auto run_test = [&relation_parameters](bool is_random_input) { std::array, NUM_POLYNOMIALS> extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; + std::array, NUM_POLYNOMIALS> input_polynomials; if (!is_random_input) { // evaluation form, i.e. input_univariate(0) = 1, input_univariate(1) = 2,.. The polynomial is x+1. for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ 1, 2 }); + input_polynomials[i] = Univariate({ 1, 2 }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); } else { // input_univariates are random polynomials of degree one for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { input_polynomials[i] = - Univariate({ FF::random_element(), FF::random_element() }); + Univariate({ FF::random_element(), FF::random_element() }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); }; auto relation = GrandProductComputationRelation(); @@ -269,33 +280,37 @@ TYPED_TEST(SumcheckRelation, GrandProductComputationRelation) (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma); - TestFixture::validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); }; run_test(/* is_random_input=*/true); run_test(/* is_random_input=*/false); }; -TYPED_TEST(SumcheckRelation, GrandProductInitializationRelation) +TYPED_TEST(RelationConsistency, GrandProductInitializationRelation) { SUMCHECK_RELATION_TYPE_ALIASES using MULTIVARIATE = honk::StandardArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 5; + static const size_t NUM_POLYNOMIALS = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); auto run_test = [&relation_parameters](bool is_random_input) { std::array, NUM_POLYNOMIALS> extended_edges; - std::array, NUM_POLYNOMIALS> input_polynomials; + std::array, NUM_POLYNOMIALS> input_polynomials; if (!is_random_input) { // evaluation form, i.e. input_univariate(0) = 1, input_univariate(1) = 2,.. The polynomial is x+1. for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { - input_polynomials[i] = Univariate({ 1, 2 }); + input_polynomials[i] = Univariate({ 1, 2 }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); } else { // input_univariates are random polynomials of degree one for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { input_polynomials[i] = - Univariate({ FF::random_element(), FF::random_element() }); + Univariate({ FF::random_element(), FF::random_element() }); } - extended_edges = TestFixture::compute_mock_extended_edges(input_polynomials); + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); }; auto relation = GrandProductInitializationRelation(); const auto& z_perm_shift = extended_edges[MULTIVARIATE::Z_PERM_SHIFT]; @@ -305,10 +320,169 @@ TYPED_TEST(SumcheckRelation, GrandProductInitializationRelation) // expected_evals, lenght 3 (coeff form = x^2 + x), extends to { { 0, 2, 6, 12, 20 } } auto expected_evals = z_perm_shift * lagrange_last; - TestFixture::validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); }; run_test(/* is_random_input=*/true); run_test(/* is_random_input=*/false); }; +TYPED_TEST(RelationConsistency, UltraArithmeticRelation) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = UltraArithmeticRelation(); + + // Extract the extended edges for manual computation of relation contribution + const auto& w_1 = extended_edges[MULTIVARIATE::W_L]; + const auto& w_2 = extended_edges[MULTIVARIATE::W_R]; + const auto& w_3 = extended_edges[MULTIVARIATE::W_O]; + const auto& w_4 = extended_edges[MULTIVARIATE::W_4]; + const auto& w_4_shift = extended_edges[MULTIVARIATE::W_4_SHIFT]; + const auto& q_m = extended_edges[MULTIVARIATE::Q_M]; + const auto& q_l = extended_edges[MULTIVARIATE::Q_L]; + const auto& q_r = extended_edges[MULTIVARIATE::Q_R]; + const auto& q_o = extended_edges[MULTIVARIATE::Q_O]; + const auto& q_4 = extended_edges[MULTIVARIATE::Q_4]; + const auto& q_c = extended_edges[MULTIVARIATE::Q_C]; + const auto& q_arith = extended_edges[MULTIVARIATE::QARITH]; + + static const FF neg_half = FF(-2).invert(); + + auto expected_evals = (q_arith - 3) * (q_m * w_2 * w_1) * neg_half; + expected_evals += (q_l * w_1) + (q_r * w_2) + (q_o * w_3) + (q_4 * w_4) + q_c; + expected_evals += (q_arith - 1) * w_4_shift; + expected_evals *= q_arith; + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + +TYPED_TEST(RelationConsistency, UltraArithmeticRelationSecondary) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = UltraArithmeticRelationSecondary(); + + // Extract the extended edges for manual computation of relation contribution + const auto& w_1 = extended_edges[MULTIVARIATE::W_L]; + const auto& w_4 = extended_edges[MULTIVARIATE::W_4]; + const auto& w_1_shift = extended_edges[MULTIVARIATE::W_1_SHIFT]; + const auto& q_m = extended_edges[MULTIVARIATE::Q_M]; + const auto& q_arith = extended_edges[MULTIVARIATE::QARITH]; + + auto expected_evals = (w_1 + w_4 - w_1_shift + q_m); + expected_evals *= (q_arith - 2) * (q_arith - 1) * q_arith; + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + +TYPED_TEST(RelationConsistency, UltraGrandProductInitializationRelation) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = UltraGrandProductInitializationRelation(); + + // Extract the extended edges for manual computation of relation contribution + const auto& z_perm_shift = extended_edges[MULTIVARIATE::Z_PERM_SHIFT]; + const auto& lagrange_last = extended_edges[MULTIVARIATE::LAGRANGE_LAST]; + + // Compute the expected result using a simple to read version of the relation expression + auto expected_evals = z_perm_shift * lagrange_last; + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + +TYPED_TEST(RelationConsistency, UltraGrandProductComputationRelation) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = UltraGrandProductComputationRelation(); + + const auto& beta = relation_parameters.beta; + const auto& gamma = relation_parameters.gamma; + const auto& public_input_delta = relation_parameters.public_input_delta; + + // Extract the extended edges for manual computation of relation contribution + const auto& w_1 = extended_edges[MULTIVARIATE::W_L]; + const auto& w_2 = extended_edges[MULTIVARIATE::W_R]; + const auto& w_3 = extended_edges[MULTIVARIATE::W_O]; + const auto& w_4 = extended_edges[MULTIVARIATE::W_4]; + const auto& sigma_1 = extended_edges[MULTIVARIATE::SIGMA_1]; + const auto& sigma_2 = extended_edges[MULTIVARIATE::SIGMA_2]; + const auto& sigma_3 = extended_edges[MULTIVARIATE::SIGMA_3]; + const auto& sigma_4 = extended_edges[MULTIVARIATE::SIGMA_4]; + const auto& id_1 = extended_edges[MULTIVARIATE::ID_1]; + const auto& id_2 = extended_edges[MULTIVARIATE::ID_2]; + const auto& id_3 = extended_edges[MULTIVARIATE::ID_3]; + const auto& id_4 = extended_edges[MULTIVARIATE::ID_4]; + const auto& z_perm = extended_edges[MULTIVARIATE::Z_PERM]; + const auto& z_perm_shift = extended_edges[MULTIVARIATE::Z_PERM_SHIFT]; + const auto& lagrange_first = extended_edges[MULTIVARIATE::LAGRANGE_FIRST]; + const auto& lagrange_last = extended_edges[MULTIVARIATE::LAGRANGE_LAST]; + + // Compute the expected result using a simple to read version of the relation expression + auto expected_evals = (z_perm + lagrange_first) * (w_1 + id_1 * beta + gamma) * (w_2 + id_2 * beta + gamma) * + (w_3 + id_3 * beta + gamma) * (w_4 + id_4 * beta + gamma) - + (z_perm_shift + lagrange_last * public_input_delta) * (w_1 + sigma_1 * beta + gamma) * + (w_2 + sigma_2 * beta + gamma) * (w_3 + sigma_3 * beta + gamma) * + (w_4 + sigma_4 * beta + gamma); + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + } // namespace proof_system::honk_relation_tests diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp new file mode 100644 index 0000000000..f5556a31da --- /dev/null +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp @@ -0,0 +1,258 @@ +#include "barretenberg/honk/composer/ultra_honk_composer.hpp" +#include "barretenberg/honk/composer/standard_honk_composer.hpp" +#include "barretenberg/honk/sumcheck/relations/relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include +#include "barretenberg/honk/proof_system/prover.hpp" +#include "barretenberg/honk/sumcheck/sumcheck_round.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" +#include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" +#include "barretenberg/honk/utils/public_inputs.hpp" + +#include + +using namespace proof_system::honk; + +namespace test_honk_relations { + +/** + * @brief Test the correctness of the Standard Honk relations + * + * @details Check that the constraints encoded by the relations are satisfied by the polynomials produced by the + * Standard Honk Composer for a real circuit. + * + * TODO(Kesha): We'll have to update this function once we add zk, since the relation will be incorrect for he first few + * indices + * + */ +TEST(RelationCorrectness, StandardRelationCorrectness) +{ + // Create a composer and a dummy circuit with a few gates + auto composer = StandardHonkComposer(); + static const size_t num_wires = StandardHonkComposer::num_wires; + fr a = fr::one(); + // Using the public variable to check that public_input_delta is computed and added to the relation correctly + uint32_t a_idx = composer.add_public_variable(a); + fr b = fr::one(); + fr c = a + b; + fr d = a + c; + uint32_t b_idx = composer.add_variable(b); + uint32_t c_idx = composer.add_variable(c); + uint32_t d_idx = composer.add_variable(d); + for (size_t i = 0; i < 16; i++) { + composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() }); + composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() }); + } + // Create a prover (it will compute proving key and witness) + auto prover = composer.create_prover(); + + // Generate beta and gamma + fr beta = fr::random_element(); + fr gamma = fr::random_element(); + + // Compute public input delta + const auto public_inputs = composer.circuit_constructor.get_public_inputs(); + auto public_input_delta = + honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); + + sumcheck::RelationParameters params{ + .beta = beta, + .gamma = gamma, + .public_input_delta = public_input_delta, + }; + + constexpr size_t num_polynomials = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; + // Compute grand product polynomial + polynomial z_perm_poly = + prover_library::compute_permutation_grand_product(prover.key, prover.wire_polynomials, beta, gamma); + + // Create an array of spans to the underlying polynomials to more easily + // get the transposition. + // Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial + // in the list below + std::array, num_polynomials> evaluations_array; + + using POLYNOMIAL = proof_system::honk::StandardArithmetization::POLYNOMIAL; + evaluations_array[POLYNOMIAL::W_L] = prover.wire_polynomials[0]; + evaluations_array[POLYNOMIAL::W_R] = prover.wire_polynomials[1]; + evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2]; + evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly; + evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted(); + evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange"); + evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange"); + evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange"); + evaluations_array[POLYNOMIAL::Q_O] = prover.key->polynomial_store.get("q_3_lagrange"); + evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_1] = prover.key->polynomial_store.get("sigma_1_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_2] = prover.key->polynomial_store.get("sigma_2_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_3] = prover.key->polynomial_store.get("sigma_3_lagrange"); + evaluations_array[POLYNOMIAL::ID_1] = prover.key->polynomial_store.get("id_1_lagrange"); + evaluations_array[POLYNOMIAL::ID_2] = prover.key->polynomial_store.get("id_2_lagrange"); + evaluations_array[POLYNOMIAL::ID_3] = prover.key->polynomial_store.get("id_3_lagrange"); + evaluations_array[POLYNOMIAL::LAGRANGE_FIRST] = prover.key->polynomial_store.get("L_first_lagrange"); + evaluations_array[POLYNOMIAL::LAGRANGE_LAST] = prover.key->polynomial_store.get("L_last_lagrange"); + + // Construct the round for applying sumcheck relations and results for storing computed results + auto relations = std::tuple(honk::sumcheck::ArithmeticRelation(), + honk::sumcheck::GrandProductComputationRelation(), + honk::sumcheck::GrandProductInitializationRelation()); + + fr result = 0; + for (size_t i = 0; i < prover.key->circuit_size; i++) { + // Compute an array containing all the evaluations at a given row i + std::array evaluations_at_index_i; + for (size_t j = 0; j < num_polynomials; ++j) { + evaluations_at_index_i[j] = evaluations_array[j][i]; + } + + // For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the + // i-th row/vertex of the hypercube. + // We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at the first index at which the result is not + // 0, since result = 0 + C(transposed), which we expect will equal 0. + std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + } +} + +/** + * @brief Test the correctness of the Ultra Honk relations + * + * @details Check that the constraints encoded by the relations are satisfied by the polynomials produced by the + * Ultra Honk Composer for a real circuit. + * + * TODO(Kesha): We'll have to update this function once we add zk, since the relation will be incorrect for he first few + * indices + * + */ +// TODO(luke): Increase variety of gates in the test circuit to fully stress the relations, e.g. create_big_add_gate. +// NOTE(luke): More relations will be added as they are implemented for Ultra Honk +TEST(RelationCorrectness, UltraRelationCorrectness) +{ + // Create a composer and a dummy circuit with a few gates + auto composer = UltraHonkComposer(); + static const size_t num_wires = 4; + fr a = fr::one(); + // Using the public variable to check that public_input_delta is computed and added to the relation correctly + // TODO(luke): add method "add_public_variable" to UH composer + // uint32_t a_idx = composer.add_public_variable(a); + uint32_t a_idx = composer.add_variable(a); + fr b = fr::one(); + fr c = a + b; + fr d = a + c; + uint32_t b_idx = composer.add_variable(b); + uint32_t c_idx = composer.add_variable(c); + uint32_t d_idx = composer.add_variable(d); + for (size_t i = 0; i < 1; i++) { + composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() }); + composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() }); + } + // Create a prover (it will compute proving key and witness) + auto prover = composer.create_prover(); + + // Generate beta and gamma + fr beta = fr::random_element(); + fr gamma = fr::random_element(); + + // Compute public input delta + const auto public_inputs = composer.circuit_constructor.get_public_inputs(); + auto public_input_delta = + honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); + + info("public_input_delta = ", public_input_delta); + + sumcheck::RelationParameters params{ + .beta = beta, + .gamma = gamma, + .public_input_delta = public_input_delta, + }; + + constexpr size_t num_polynomials = proof_system::honk::UltraArithmetization::COUNT; + // Compute grand product polynomial + auto z_perm_poly = + prover_library::compute_permutation_grand_product(prover.key, prover.wire_polynomials, beta, gamma); + + // Create an array of spans to the underlying polynomials to more easily + // get the transposition. + // Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial + // in the list below + std::array, num_polynomials> evaluations_array; + + using POLYNOMIAL = proof_system::honk::UltraArithmetization::POLYNOMIAL; + evaluations_array[POLYNOMIAL::W_L] = prover.wire_polynomials[0]; + evaluations_array[POLYNOMIAL::W_R] = prover.wire_polynomials[1]; + evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2]; + evaluations_array[POLYNOMIAL::W_4] = prover.wire_polynomials[3]; + evaluations_array[POLYNOMIAL::W_1_SHIFT] = prover.wire_polynomials[0].shifted(); + evaluations_array[POLYNOMIAL::W_4_SHIFT] = prover.wire_polynomials[3].shifted(); + evaluations_array[POLYNOMIAL::S_1] = prover.key->polynomial_store.get("s_1_lagrange"); + evaluations_array[POLYNOMIAL::S_2] = prover.key->polynomial_store.get("s_2_lagrange"); + evaluations_array[POLYNOMIAL::S_3] = prover.key->polynomial_store.get("s_3_lagrange"); + evaluations_array[POLYNOMIAL::S_4] = prover.key->polynomial_store.get("s_4_lagrange"); + evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly; + evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted(); + evaluations_array[POLYNOMIAL::Z_LOOKUP] = z_perm_poly; + evaluations_array[POLYNOMIAL::Z_LOOKUP_SHIFT] = z_perm_poly.shifted(); + evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange"); + evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange"); + evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange"); + evaluations_array[POLYNOMIAL::Q_O] = prover.key->polynomial_store.get("q_3_lagrange"); + evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange"); + evaluations_array[POLYNOMIAL::Q_4] = prover.key->polynomial_store.get("q_4_lagrange"); + evaluations_array[POLYNOMIAL::QARITH] = prover.key->polynomial_store.get("q_arith_lagrange"); + evaluations_array[POLYNOMIAL::QSORT] = prover.key->polynomial_store.get("q_sort_lagrange"); + evaluations_array[POLYNOMIAL::QELLIPTIC] = prover.key->polynomial_store.get("q_elliptic_lagrange"); + evaluations_array[POLYNOMIAL::QAUX] = prover.key->polynomial_store.get("q_aux_lagrange"); + evaluations_array[POLYNOMIAL::QLOOKUPTYPE] = prover.key->polynomial_store.get("table_type_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_1] = prover.key->polynomial_store.get("sigma_1_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_2] = prover.key->polynomial_store.get("sigma_2_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_3] = prover.key->polynomial_store.get("sigma_3_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_4] = prover.key->polynomial_store.get("sigma_4_lagrange"); + evaluations_array[POLYNOMIAL::ID_1] = prover.key->polynomial_store.get("id_1_lagrange"); + evaluations_array[POLYNOMIAL::ID_2] = prover.key->polynomial_store.get("id_2_lagrange"); + evaluations_array[POLYNOMIAL::ID_3] = prover.key->polynomial_store.get("id_3_lagrange"); + evaluations_array[POLYNOMIAL::ID_4] = prover.key->polynomial_store.get("id_4_lagrange"); + evaluations_array[POLYNOMIAL::LAGRANGE_FIRST] = prover.key->polynomial_store.get("L_first_lagrange"); + evaluations_array[POLYNOMIAL::LAGRANGE_LAST] = prover.key->polynomial_store.get("L_last_lagrange"); + + // Construct the round for applying sumcheck relations and results for storing computed results + auto relations = std::tuple(honk::sumcheck::UltraArithmeticRelation(), + honk::sumcheck::UltraArithmeticRelationSecondary(), + honk::sumcheck::UltraGrandProductInitializationRelation(), + honk::sumcheck::UltraGrandProductComputationRelation()); + + fr result = 0; + for (size_t i = 0; i < prover.key->circuit_size; i++) { + // Compute an array containing all the evaluations at a given row i + std::array evaluations_at_index_i; + for (size_t j = 0; j < num_polynomials; ++j) { + evaluations_at_index_i[j] = evaluations_array[j][i]; + } + + // For each relation, call the `accumulate_relation_evaluation` over all witness/selector values at the + // i-th row/vertex of the hypercube. We use ASSERT_EQ instead of EXPECT_EQ so that the tests stops at + // the first index at which the result is not 0, since result = 0 + C(transposed), which we expect will + // equal 0. + std::get<0>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<1>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<2>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<3>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + } +} + +} // namespace test_honk_relations diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp new file mode 100644 index 0000000000..2df6efc2c7 --- /dev/null +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp @@ -0,0 +1,87 @@ +#pragma once +#include +#include + +#include "barretenberg/honk/flavor/flavor.hpp" +#include "../polynomials/univariate.hpp" +#include "relation.hpp" + +namespace proof_system::honk::sumcheck { + +template class UltraArithmeticRelation { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 6; // degree(q_arith^2 * q_m * w_r * w_l) = 5 + using MULTIVARIATE = UltraArithmetization::POLYNOMIAL; + + /** + * @brief Expression for the Ultra Arithmetic gate. + * @details The relation is defined as C(extended_edges(X)...) = + * q_arith * + * [ -1/2(q_arith - 3)(q_m * w_r * w_l) + + * (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c + + * (q_arith - 1)w_4_shift ] + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters&, + const FF& scaling_factor) const + { + // OPTIMIZATION?: Karatsuba in general, at least for some degrees? + // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both + + auto w_l = UnivariateView(extended_edges[MULTIVARIATE::W_L]); + auto w_r = UnivariateView(extended_edges[MULTIVARIATE::W_R]); + auto w_o = UnivariateView(extended_edges[MULTIVARIATE::W_O]); + auto w_4 = UnivariateView(extended_edges[MULTIVARIATE::W_4]); + auto w_4_shift = UnivariateView(extended_edges[MULTIVARIATE::W_4_SHIFT]); + auto q_m = UnivariateView(extended_edges[MULTIVARIATE::Q_M]); + auto q_l = UnivariateView(extended_edges[MULTIVARIATE::Q_L]); + auto q_r = UnivariateView(extended_edges[MULTIVARIATE::Q_R]); + auto q_o = UnivariateView(extended_edges[MULTIVARIATE::Q_O]); + auto q_4 = UnivariateView(extended_edges[MULTIVARIATE::Q_4]); + auto q_c = UnivariateView(extended_edges[MULTIVARIATE::Q_C]); + auto q_arith = UnivariateView(extended_edges[MULTIVARIATE::QARITH]); + + static const FF neg_half = FF(-2).invert(); + + auto tmp = (q_arith - 3) * (q_m * w_r * w_l) * neg_half; + tmp += (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c; + tmp += (q_arith - 1) * w_4_shift; + tmp *= q_arith; + tmp *= scaling_factor; + evals += tmp; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + const auto& purported_evaluations, + const RelationParameters&) const + { + auto w_l = purported_evaluations[MULTIVARIATE::W_L]; + auto w_r = purported_evaluations[MULTIVARIATE::W_R]; + auto w_o = purported_evaluations[MULTIVARIATE::W_O]; + auto w_4 = purported_evaluations[MULTIVARIATE::W_4]; + auto w_4_shift = purported_evaluations[MULTIVARIATE::W_4_SHIFT]; + auto q_m = purported_evaluations[MULTIVARIATE::Q_M]; + auto q_l = purported_evaluations[MULTIVARIATE::Q_L]; + auto q_r = purported_evaluations[MULTIVARIATE::Q_R]; + auto q_o = purported_evaluations[MULTIVARIATE::Q_O]; + auto q_4 = purported_evaluations[MULTIVARIATE::Q_4]; + auto q_c = purported_evaluations[MULTIVARIATE::Q_C]; + auto q_arith = purported_evaluations[MULTIVARIATE::QARITH]; + + static const FF neg_half = FF(-2).invert(); + + auto tmp = (q_arith - 3) * (q_m * w_r * w_l) * neg_half; + tmp += (q_l * w_l) + (q_r * w_r) + (q_o * w_o) + (q_4 * w_4) + q_c; + tmp += (q_arith - 1) * w_4_shift; + tmp *= q_arith; + full_honk_relation_value += tmp; + }; +}; +} // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp new file mode 100644 index 0000000000..78c01db356 --- /dev/null +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp @@ -0,0 +1,67 @@ +#pragma once +#include +#include + +#include "barretenberg/honk/flavor/flavor.hpp" +#include "../polynomials/univariate.hpp" +#include "relation.hpp" + +namespace proof_system::honk::sumcheck { + +template class UltraArithmeticRelationSecondary { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 5; // degree(q_arith^3 * w_l) = 4 + using MULTIVARIATE = UltraArithmetization::POLYNOMIAL; + + /** + * @brief Expression for the Ultra Arithmetic gate. + * @details The relation is defined as C(extended_edges(X)...) = + * q_arith * + * (q_arith - 2) * (q_arith - 1) * (w_l + w_4 - w_l_shift + q_m) + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters&, + const FF& scaling_factor) const + { + // OPTIMIZATION?: Karatsuba in general, at least for some degrees? + // See https://hackmd.io/xGLuj6biSsCjzQnYN-pEiA?both + + auto w_l = UnivariateView(extended_edges[MULTIVARIATE::W_L]); + auto w_4 = UnivariateView(extended_edges[MULTIVARIATE::W_4]); + auto w_l_shift = UnivariateView(extended_edges[MULTIVARIATE::W_1_SHIFT]); + auto q_m = UnivariateView(extended_edges[MULTIVARIATE::Q_M]); + auto q_arith = UnivariateView(extended_edges[MULTIVARIATE::QARITH]); + + auto tmp = w_l + w_4 - w_l_shift + q_m; + tmp *= (q_arith - 2); + tmp *= (q_arith - 1); + tmp *= q_arith; + tmp *= scaling_factor; + evals += tmp; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + const auto& purported_evaluations, + const RelationParameters&) const + { + auto w_l = purported_evaluations[MULTIVARIATE::W_L]; + auto w_4 = purported_evaluations[MULTIVARIATE::W_4]; + auto w_l_shift = purported_evaluations[MULTIVARIATE::W_1_SHIFT]; + auto q_m = purported_evaluations[MULTIVARIATE::Q_M]; + auto q_arith = purported_evaluations[MULTIVARIATE::QARITH]; + + auto tmp = w_l + w_4 - w_l_shift + q_m; + tmp *= (q_arith - 2); + tmp *= (q_arith - 1); + tmp *= q_arith; + full_honk_relation_value += tmp; + }; +}; +} // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/c_bind.cpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/c_bind.cpp index cb35a19d27..5ee4b90fad 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/c_bind.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/c_bind.cpp @@ -1,3 +1,6 @@ +#include +#include + #include "c_bind.h" #include "join_split.hpp" #include "compute_signing_data.hpp" @@ -5,14 +8,12 @@ #include "barretenberg/common/streams.hpp" #include "barretenberg/common/mem.hpp" #include "barretenberg/common/container.hpp" -#include #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" #include "barretenberg/srs/reference_string/pippenger_reference_string.hpp" #include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" -#include +#include "barretenberg/join_split_example/types.hpp" using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; using namespace join_split_example::proofs::join_split; #define WASM_EXPORT __attribute__((visibility("default"))) @@ -42,22 +43,10 @@ WASM_EXPORT void join_split__release_key() WASM_EXPORT uint32_t join_split__get_new_proving_key_data(uint8_t** output) { -// Computing the size of the serialized key is non trivial. We know it's ~331mb. -// Allocate a buffer large enough to hold it, and abort if we overflow. -// This is to keep memory usage down. -#ifdef USE_TURBO - size_t total_buf_len = 350 * 1024 * 1024; - auto raw_buf = (uint8_t*)malloc(total_buf_len); - auto raw_buf_end = raw_buf; - write(raw_buf_end, *get_proving_key()); - *output = raw_buf; - auto len = static_cast(raw_buf_end - raw_buf); - if (len > total_buf_len) { - info("Buffer overflow serializing proving key."); - std::abort(); - } - return len; -#else + // Computing the size of the serialized key is non trivial. We know it's ~331mb. + // Allocate a buffer large enough to hold it, and abort if we overflow. + // This is to keep memory usage down. + auto proving_key = get_proving_key(); auto buffer = to_buffer(*proving_key); auto raw_buf = (uint8_t*)malloc(buffer.size()); @@ -65,7 +54,6 @@ WASM_EXPORT uint32_t join_split__get_new_proving_key_data(uint8_t** output) *output = raw_buf; return static_cast(buffer.size()); -#endif } WASM_EXPORT void join_split__init_verification_key(void* pippenger, uint8_t const* g2x) @@ -103,13 +91,13 @@ WASM_EXPORT void* join_split__new_prover(uint8_t const* join_split_buf, bool moc { auto tx = from_buffer(join_split_buf); auto prover = new_join_split_prover(tx, mock); - auto heapProver = new stdlib::types::Prover(std::move(prover)); + auto heapProver = new join_split_example::Prover(std::move(prover)); return heapProver; } WASM_EXPORT void join_split__delete_prover(void* prover) { - delete reinterpret_cast(prover); + delete reinterpret_cast(prover); } WASM_EXPORT bool join_split__verify_proof(uint8_t* proof, uint32_t length) diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/compute_circuit_data.cpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/compute_circuit_data.cpp index 47fc4deec2..037799660b 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/compute_circuit_data.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/compute_circuit_data.cpp @@ -3,13 +3,14 @@ #include "sign_join_split_tx.hpp" #include "../notes/native/index.hpp" #include "barretenberg/stdlib/merkle_tree/hash_path.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { namespace join_split { using namespace join_split_example::proofs::join_split; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; using namespace join_split_example::proofs::notes::native; using namespace proof_system::plonk::stdlib::merkle_tree; diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.cpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.cpp index 44567b5b26..95aee064e2 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.cpp @@ -2,6 +2,7 @@ #include "join_split_circuit.hpp" #include "compute_circuit_data.hpp" #include "barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -23,7 +24,7 @@ void init_proving_key(std::shared_ptr cons join_split_tx tx = noop_tx(); if (!mock) { - stdlib::types::Composer composer(crs_factory); + Composer composer(crs_factory); join_split_circuit(composer, tx); proving_key = composer.compute_proving_key(); } else { @@ -55,8 +56,7 @@ void init_verification_key(std::unique_ptr // Patch the 'nothing' reference string fed to init_proving_key. proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size + 1); - verification_key = - plonk::stdlib::types::Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); + verification_key = Composer::compute_verification_key_base(proving_key, crs_factory->get_verifier_crs()); } void init_verification_key(std::shared_ptr const& crs, @@ -65,7 +65,7 @@ void init_verification_key(std::shared_ptr(std::move(vk_data), crs); } -stdlib::types::Prover new_join_split_prover(join_split_tx const& tx, bool mock) +Prover new_join_split_prover(join_split_tx const& tx, bool mock) { Composer composer(proving_key, nullptr); join_split_circuit(composer, tx); @@ -90,16 +90,10 @@ stdlib::types::Prover new_join_split_prover(join_split_tx const& tx, bool mock) bool verify_proof(plonk::proof const& proof) { - plonk::stdlib::types::Verifier verifier(verification_key, - Composer::create_manifest(verification_key->num_public_inputs)); + Verifier verifier(verification_key, Composer::create_manifest(verification_key->num_public_inputs)); -#ifdef USE_TURBO - std::unique_ptr> kate_commitment_scheme = - std::make_unique>(); -#else std::unique_ptr> kate_commitment_scheme = std::make_unique>(); -#endif verifier.commitment_scheme = std::move(kate_commitment_scheme); return verifier.verify_proof(proof); diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.hpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.hpp index 6c9b4ac5ab..a6c103a4a0 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.hpp @@ -1,14 +1,13 @@ #pragma once #include "join_split_tx.hpp" #include "barretenberg/srs/reference_string/mem_reference_string.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { namespace join_split { using namespace proof_system::plonk::stdlib::merkle_tree; -using namespace proof_system::plonk::stdlib::types; void init_proving_key(std::shared_ptr const& crs_factory, bool mock); @@ -22,7 +21,7 @@ void init_verification_key(std::unique_ptr void init_verification_key(std::shared_ptr const& crs, plonk::verification_key_data&& vk_data); -stdlib::types::Prover new_join_split_prover(join_split_tx const& tx, bool mock); +Prover new_join_split_prover(join_split_tx const& tx, bool mock); bool verify_proof(plonk::proof const& proof); diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.test.cpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.test.cpp index c729f91434..25b802cd62 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.test.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split.test.cpp @@ -1,3 +1,5 @@ +#include + #include "../../constants.hpp" #include "../inner_proof_data/inner_proof_data.hpp" #include "index.hpp" @@ -5,9 +7,9 @@ #include "join_split_circuit.hpp" #include "barretenberg/common/streams.hpp" #include "barretenberg/common/test.hpp" -#include #include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" #include "barretenberg/stdlib/merkle_tree/index.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example::proofs::join_split { @@ -122,7 +124,7 @@ constexpr bool CIRCUIT_CHANGE_EXPECTED = false; #endif using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; using namespace proof_system::plonk::stdlib::merkle_tree; using namespace join_split_example::proofs::notes::native; using key_pair = join_split_example::fixtures::grumpkin_key_pair; @@ -800,17 +802,12 @@ TEST_F(join_split_tests, test_0_input_notes_and_detect_circuit_change) EXPECT_TRUE(result.valid); -// The below part detects any changes in the join-split circuit -#ifdef USE_TURBO - constexpr uint32_t CIRCUIT_GATE_COUNT = 59175; - constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 65536; - const uint256_t VK_HASH("095cbe8f1b09690713d5161708b5ea77119575884e3cfab14f7364b9f1ba7faa"); -#else + // The below part detects any changes in the join-split circuit + constexpr uint32_t CIRCUIT_GATE_COUNT = 185573; constexpr uint32_t GATES_NEXT_POWER_OF_TWO = 524288; - const uint256_t VK_HASH("21389d5392ee23ffc96984689150b63d62113678b1ba127346a0ec72df842354"); + const uint256_t VK_HASH("13eb88883e80efb9bf306af2962cd1a49e9fa1b0bfb2d4b563b95217a17bcc74"); -#endif auto number_of_gates_js = result.number_of_gates; auto vk_hash_js = get_verification_key()->sha256_hash(); @@ -2623,11 +2620,8 @@ TEST_F(join_split_tests, serialized_proving_key_size) { uint8_t* ptr; auto len = join_split__get_new_proving_key_data(&ptr); -#ifdef USE_TURBO - EXPECT_LE(len, 2 * 170 * 1024 * 1024); -#else + EXPECT_LE(len, 2315258552); -#endif } } // namespace join_split_example::proofs::join_split diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.cpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.cpp index a0061cde88..c1ea2af1da 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.cpp @@ -6,6 +6,7 @@ #include "../notes/circuit/claim/claim_note.hpp" #include "verify_signature.hpp" #include "barretenberg/stdlib/merkle_tree/membership.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -22,7 +23,7 @@ using namespace proof_system::plonk::stdlib::merkle_tree; */ field_ct process_input_note(field_ct const& account_private_key, field_ct const& merkle_root, - merkle_tree::hash_path const& hash_path, + hash_path_ct const& hash_path, suint_ct const& index, value::value_note const& note, bool_ct is_propagated, @@ -31,7 +32,7 @@ field_ct process_input_note(field_ct const& account_private_key, const bool_ct valid_value = note.value == 0 || is_note_in_use; valid_value.assert_equal(true, "padding note non zero"); - const bool_ct exists = merkle_tree::check_membership( + const bool_ct exists = proof_system::plonk::stdlib::merkle_tree::check_membership( merkle_root, hash_path, note.commitment, index.value.decompose_into_bits(DATA_TREE_DEPTH)); const bool_ct valid = exists || is_propagated || !is_note_in_use; valid.assert_equal(true, "input note not a member"); @@ -216,10 +217,10 @@ join_split_outputs join_split_circuit_component(join_split_inputs const& inputs) const auto account_alias_hash = inputs.alias_hash; const auto account_note_data = account::account_note(account_alias_hash.value, account_public_key, signer); const bool_ct signing_key_exists = - merkle_tree::check_membership(inputs.merkle_root, - inputs.account_note_path, - account_note_data.commitment, - inputs.account_note_index.value.decompose_into_bits(DATA_TREE_DEPTH)); + stdlib::merkle_tree::check_membership(inputs.merkle_root, + inputs.account_note_path, + account_note_data.commitment, + inputs.account_note_index.value.decompose_into_bits(DATA_TREE_DEPTH)); (signing_key_exists || !inputs.account_required).assert_equal(true, "account check_membership failed"); } @@ -287,8 +288,8 @@ void join_split_circuit(Composer& composer, join_split_tx const& tx) .signing_pub_key = stdlib::create_point_witness(composer, tx.signing_pub_key), .signature = stdlib::schnorr::convert_signature(&composer, tx.signature), .merkle_root = witness_ct(&composer, tx.old_data_root), - .input_path1 = merkle_tree::create_witness_hash_path(composer, tx.input_path[0]), - .input_path2 = merkle_tree::create_witness_hash_path(composer, tx.input_path[1]), + .input_path1 = stdlib::merkle_tree::create_witness_hash_path(composer, tx.input_path[0]), + .input_path2 = stdlib::merkle_tree::create_witness_hash_path(composer, tx.input_path[1]), .account_note_index = suint_ct(witness_ct(&composer, tx.account_note_index), DATA_TREE_DEPTH, "account_note_index"), .account_note_path = merkle_tree::create_witness_hash_path(composer, tx.account_note_path), diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.hpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.hpp index b8821989b4..bde45a710c 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_circuit.hpp @@ -3,15 +3,14 @@ #include "../notes/circuit/value/witness_data.hpp" #include "../notes/circuit/claim/witness_data.hpp" #include "barretenberg/crypto/schnorr/schnorr.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { namespace join_split { -using namespace proof_system::plonk::stdlib::types; - struct join_split_inputs { + field_ct proof_id; suint_ct public_value; field_ct public_owner; @@ -25,12 +24,12 @@ struct join_split_inputs { notes::circuit::value::witness_data output_note2; notes::circuit::claim::partial_claim_note_witness_data partial_claim_note; point_ct signing_pub_key; - stdlib::schnorr::signature_bits signature; + schnorr::signature_bits signature; field_ct merkle_root; - merkle_tree::hash_path input_path1; - merkle_tree::hash_path input_path2; + hash_path_ct input_path1; + hash_path_ct input_path2; suint_ct account_note_index; - merkle_tree::hash_path account_note_path; + hash_path_ct account_note_path; field_ct account_private_key; suint_ct alias_hash; bool_ct account_required; diff --git a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_tx.hpp b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_tx.hpp index 4dae8f30e3..fe309b8dac 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_tx.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/join_split/join_split_tx.hpp @@ -3,14 +3,12 @@ #include "../notes/native/value/value_note.hpp" #include "barretenberg/crypto/schnorr/schnorr.hpp" #include "barretenberg/stdlib/merkle_tree/hash_path.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { namespace join_split { -using namespace proof_system::plonk::stdlib::types; - struct join_split_tx { uint32_t proof_id; uint256_t public_value; @@ -19,7 +17,7 @@ struct join_split_tx { uint32_t num_input_notes; std::array input_index; barretenberg::fr old_data_root; - std::array input_path; + std::array input_path; std::array input_note; std::array output_note; @@ -29,7 +27,7 @@ struct join_split_tx { barretenberg::fr alias_hash; bool account_required; uint32_t account_note_index; - merkle_tree::fr_hash_path account_note_path; + proof_system::plonk::stdlib::merkle_tree::fr_hash_path account_note_path; grumpkin::g1::affine_element signing_pub_key; barretenberg::fr backward_link; // 0: no link, otherwise: any commitment. diff --git a/cpp/src/barretenberg/join_split_example/proofs/mock/mock_circuit.test.cpp b/cpp/src/barretenberg/join_split_example/proofs/mock/mock_circuit.test.cpp index 16004e9787..6960962095 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/mock/mock_circuit.test.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/mock/mock_circuit.test.cpp @@ -1,9 +1,9 @@ #include "mock_circuit.hpp" #include "../join_split/join_split_tx.hpp" #include "barretenberg/common/test.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; namespace rollup { namespace proofs { diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/account_note.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/account_note.hpp index 334b2edfc3..30390eb103 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/account_note.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/account_note.hpp @@ -1,6 +1,7 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "commit.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -8,8 +9,6 @@ namespace notes { namespace circuit { namespace account { -using namespace proof_system::plonk::stdlib::types; - struct account_note { field_ct account_alias_hash; point_ct account_public_key; diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/commit.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/commit.hpp index 497644a951..dbe568197a 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/commit.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/account/commit.hpp @@ -1,6 +1,8 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/primitives/point/point.hpp" #include "../../constants.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -8,7 +10,7 @@ namespace notes { namespace circuit { namespace account { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; inline auto commit(field_ct const& account_alias_hash, point_ct const& account_public_key, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.cpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.cpp index 5163231bc5..6a7a559b70 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.cpp @@ -1,9 +1,9 @@ -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "../constants.hpp" namespace join_split_example::proofs::notes::circuit { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; std::pair deflag_asset_id(suint_ct const& asset_id) { diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.hpp index 3c67f16695..89e244f39b 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/asset_id.hpp @@ -1,9 +1,9 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example::proofs::notes::circuit { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; std::pair deflag_asset_id(suint_ct const& asset_id); diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/bridge_call_data.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/bridge_call_data.hpp index f5b1bbd319..f8ea941167 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/bridge_call_data.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/bridge_call_data.hpp @@ -1,5 +1,5 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "../native/bridge_call_data.hpp" #include "./asset_id.hpp" #include "../constants.hpp" @@ -9,7 +9,7 @@ namespace proofs { namespace notes { namespace circuit { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; constexpr uint32_t input_asset_id_a_shift = DEFI_BRIDGE_ADDRESS_ID_LEN; constexpr uint32_t input_asset_id_b_shift = input_asset_id_a_shift + DEFI_BRIDGE_INPUT_A_ASSET_ID_LEN; diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/claim_note.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/claim_note.hpp index 8021265a43..8a23ff9f93 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/claim_note.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/claim_note.hpp @@ -1,5 +1,6 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" + #include "../bridge_call_data.hpp" #include "witness_data.hpp" #include "../value/create_partial_commitment.hpp" @@ -12,7 +13,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; struct partial_claim_note { suint_ct deposit_value; diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/complete_partial_commitment.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/complete_partial_commitment.hpp index 9b1010ab58..fb20d7fef9 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/complete_partial_commitment.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/complete_partial_commitment.hpp @@ -1,6 +1,7 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "../../constants.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" namespace join_split_example { namespace proofs { @@ -8,7 +9,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; inline auto complete_partial_commitment(field_ct const& partial_commitment, field_ct const& interaction_nonce, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/compute_nullifier.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/compute_nullifier.hpp index 304f9f6205..2ffb181bb2 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/compute_nullifier.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/compute_nullifier.hpp @@ -1,5 +1,6 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" + +#include "barretenberg/join_split_example/types.hpp" #include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" #include "../../constants.hpp" @@ -9,8 +10,6 @@ namespace notes { namespace circuit { namespace claim { -using namespace proof_system::plonk::stdlib::types; - inline field_ct compute_nullifier(field_ct const& note_commitment) { return pedersen_commitment::compress(std::vector{ note_commitment }, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/create_partial_commitment.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/create_partial_commitment.hpp index 30eb7adb7a..36c37a8e78 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/create_partial_commitment.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/create_partial_commitment.hpp @@ -1,5 +1,5 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" #include "../../constants.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; inline auto create_partial_commitment(field_ct const& deposit_value, field_ct const& bridge_call_data, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/witness_data.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/witness_data.hpp index b49bddd3bb..e5fc35fb39 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/witness_data.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/claim/witness_data.hpp @@ -1,5 +1,6 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" + +#include "barretenberg/join_split_example/types.hpp" #include "../../native/claim/claim_note.hpp" #include "../../native/claim/claim_note_tx_data.hpp" #include "../../constants.hpp" @@ -11,7 +12,7 @@ namespace notes { namespace circuit { namespace claim { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; /** * Convert native claim note data into circuit witness data. diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/complete_partial_commitment.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/complete_partial_commitment.hpp index e5bc84b994..1ec344d17a 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/complete_partial_commitment.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/complete_partial_commitment.hpp @@ -1,15 +1,15 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" + +#include "barretenberg/join_split_example/types.hpp" #include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" #include "../../constants.hpp" - namespace join_split_example { namespace proofs { namespace notes { namespace circuit { namespace value { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; inline auto complete_partial_commitment(field_ct const& value_note_partial_commitment, suint_ct const& value, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.cpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.cpp index 30dafca6b6..6f73dd418f 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.cpp @@ -1,6 +1,6 @@ #include "compute_nullifier.hpp" #include "../../constants.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -8,7 +8,7 @@ namespace notes { namespace circuit { using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; field_ct compute_nullifier(field_ct const& note_commitment, field_ct const& account_private_key, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.hpp index 3271035abb..85a14dd416 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.hpp @@ -1,14 +1,11 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { namespace notes { namespace circuit { -using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; - field_ct compute_nullifier(field_ct const& note_commitment, field_ct const& account_private_key, bool_ct const& is_note_in_use); diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.test.cpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.test.cpp index 063ef93edd..88198aa7c5 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.test.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/compute_nullifier.test.cpp @@ -4,10 +4,10 @@ #include "./value_note.hpp" #include "../../native/value/compute_nullifier.hpp" #include "../../native/value/value_note.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" +namespace join_split_example { using namespace join_split_example::proofs::notes; -using namespace proof_system::plonk::stdlib::types; TEST(compute_nullifier_circuit, native_consistency) { @@ -18,7 +18,6 @@ TEST(compute_nullifier_circuit, native_consistency) native::value::value_note{ 100, 0, 0, user.owner.public_key, user.note_secret, 0, fr::random_element() }; auto native_commitment = native_input_note.commit(); auto native_nullifier = native::compute_nullifier(native_commitment, priv_key, true); - Composer composer; auto circuit_witness_data = circuit::value::witness_data(composer, native_input_note); auto circuit_input_note = circuit::value::value_note(circuit_witness_data); @@ -27,3 +26,4 @@ TEST(compute_nullifier_circuit, native_consistency) EXPECT_EQ(circuit_nullifier.get_value(), native_nullifier); } +} // namespace join_split_example \ No newline at end of file diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/create_partial_commitment.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/create_partial_commitment.hpp index f01324ee34..ac6104c65a 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/create_partial_commitment.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/create_partial_commitment.hpp @@ -1,6 +1,6 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" -#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "../../constants.hpp" namespace join_split_example { @@ -9,8 +9,6 @@ namespace notes { namespace circuit { namespace value { -using namespace proof_system::plonk::stdlib::types; - inline auto create_partial_commitment(field_ct const& secret, point_ct const& owner, bool_ct const& account_required, diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.hpp index a4f8cc04c8..9f2d47a6c6 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.hpp @@ -1,5 +1,5 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/join_split_example/types.hpp" #include "witness_data.hpp" #include "commit.hpp" @@ -9,7 +9,7 @@ namespace notes { namespace circuit { namespace value { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; struct value_note { point_ct owner; diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.test.cpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.test.cpp index e243b35fb8..55b65c9949 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.test.cpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/value_note.test.cpp @@ -2,13 +2,14 @@ #include "../../../../fixtures/user_context.hpp" #include "../../native/value/value_note.hpp" #include "../../constants.hpp" +#include "barretenberg/join_split_example/types.hpp" #include +namespace join_split_example { using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; using namespace join_split_example::proofs::notes; using namespace join_split_example::proofs::notes::circuit::value; - TEST(value_note, commits) { auto user = join_split_example::fixtures::create_user_context(); @@ -106,4 +107,5 @@ TEST(value_note, commit_with_oversized_asset_id_fails) bool proof_result = verifier.verify_proof(proof); EXPECT_EQ(proof_result, false); -} \ No newline at end of file +} +} // namespace join_split_example \ No newline at end of file diff --git a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/witness_data.hpp b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/witness_data.hpp index ff0cbc0cc2..367f0e704e 100644 --- a/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/witness_data.hpp +++ b/cpp/src/barretenberg/join_split_example/proofs/notes/circuit/value/witness_data.hpp @@ -1,7 +1,6 @@ #pragma once -#include "barretenberg/stdlib/types/types.hpp" #include "../../native/value/value_note.hpp" -#include "../../constants.hpp" +#include "barretenberg/join_split_example/types.hpp" namespace join_split_example { namespace proofs { @@ -9,7 +8,7 @@ namespace notes { namespace circuit { namespace value { -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; struct witness_data { point_ct owner; diff --git a/cpp/src/barretenberg/join_split_example/types.hpp b/cpp/src/barretenberg/join_split_example/types.hpp new file mode 100644 index 0000000000..65b6cca423 --- /dev/null +++ b/cpp/src/barretenberg/join_split_example/types.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" + +#include "barretenberg/plonk/proof_system/prover/prover.hpp" +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" +#include "barretenberg/stdlib/primitives/uint/uint.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" +#include "barretenberg/stdlib/commitment/pedersen/pedersen_plookup.hpp" +#include "barretenberg/stdlib/merkle_tree/hash_path.hpp" +#include "barretenberg/stdlib/encryption/schnorr/schnorr.hpp" +#include "barretenberg/stdlib/primitives/curves/bn254.hpp" + +namespace join_split_example { + +using Composer = plonk::UltraComposer; + +using Prover = std::conditional_t< + std::same_as, + plonk::UltraProver, + std::conditional_t, plonk::TurboProver, plonk::Prover>>; + +using Verifier = std::conditional_t< + std::same_as, + plonk::UltraVerifier, + std::conditional_t, plonk::TurboVerifier, plonk::Verifier>>; + +using witness_ct = proof_system::plonk::stdlib::witness_t; +using public_witness_ct = proof_system::plonk::stdlib::public_witness_t; +using bool_ct = proof_system::plonk::stdlib::bool_t; +using byte_array_ct = proof_system::plonk::stdlib::byte_array; +using field_ct = proof_system::plonk::stdlib::field_t; +using suint_ct = proof_system::plonk::stdlib::safe_uint_t; +using uint32_ct = proof_system::plonk::stdlib::uint32; +using point_ct = proof_system::plonk::stdlib::point; +using pedersen_commitment = proof_system::plonk::stdlib::pedersen_commitment; +using group_ct = proof_system::plonk::stdlib::group; +using bn254 = proof_system::plonk::stdlib::bn254; + +using hash_path_ct = proof_system::plonk::stdlib::merkle_tree::hash_path; + +namespace schnorr { +using signature_bits = proof_system::plonk::stdlib::schnorr::signature_bits; +} // namespace schnorr + +} // namespace join_split_example \ No newline at end of file diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index 597b2ddea0..8782f6ab61 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -890,6 +890,50 @@ UltraToStandardProver UltraComposer::create_ultra_to_standard_prover() return output_state; } +/** + * @brief Uses slightly different settings from the UltraProver. + */ +UltraWithKeccakProver UltraComposer::create_ultra_with_keccak_prover() +{ + compute_proving_key(); + compute_witness(); + + UltraWithKeccakProver output_state(circuit_proving_key, create_manifest(public_inputs.size())); + + std::unique_ptr> permutation_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> plookup_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> arithmetic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> sort_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> elliptic_widget = + std::make_unique>(circuit_proving_key.get()); + + std::unique_ptr> auxiliary_widget = + std::make_unique>(circuit_proving_key.get()); + + output_state.random_widgets.emplace_back(std::move(permutation_widget)); + output_state.random_widgets.emplace_back(std::move(plookup_widget)); + + output_state.transition_widgets.emplace_back(std::move(arithmetic_widget)); + output_state.transition_widgets.emplace_back(std::move(sort_widget)); + output_state.transition_widgets.emplace_back(std::move(elliptic_widget)); + output_state.transition_widgets.emplace_back(std::move(auxiliary_widget)); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + UltraVerifier UltraComposer::create_verifier() { compute_verification_key(); @@ -918,6 +962,20 @@ UltraToStandardVerifier UltraComposer::create_ultra_to_standard_verifier() return output_state; } +UltraWithKeccakVerifier UltraComposer::create_ultra_with_keccak_verifier() +{ + compute_verification_key(); + + UltraWithKeccakVerifier output_state(circuit_verification_key, create_manifest(public_inputs.size())); + + std::unique_ptr> kate_commitment_scheme = + std::make_unique>(); + + output_state.commitment_scheme = std::move(kate_commitment_scheme); + + return output_state; +} + void UltraComposer::initialize_precomputed_table( const plookup::BasicTableId id, bool (*generator)(std::vector&, std ::vector&, std::vector&), diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index 6cdc969423..b3157639fd 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -160,6 +160,9 @@ class UltraComposer : public ComposerBase { UltraToStandardProver create_ultra_to_standard_prover(); UltraToStandardVerifier create_ultra_to_standard_verifier(); + UltraWithKeccakProver create_ultra_with_keccak_prover(); + UltraWithKeccakVerifier create_ultra_with_keccak_verifier(); + void create_add_gate(const add_triple& in) override; void create_big_add_gate(const add_quad& in, const bool use_next_gate_w_4 = false); diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp index 8219f9183c..eef80fa56c 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp @@ -10,7 +10,7 @@ using namespace barretenberg; using namespace proof_system; -namespace proof_system::plonk { +namespace proof_system::plonk::test_ultra_composer { namespace { auto& engine = numeric::random::get_debug_engine(); @@ -27,7 +27,39 @@ std::vector add_variables(UltraComposer& composer, std::vector var } return res; } -TEST(ultra_composer, create_gates_from_plookup_accumulators) + +template class ultra_composer : public ::testing::Test { + public: + void prove_and_verify(UltraComposer& composer, bool expected_result) + { + if constexpr (T::use_keccak) { + auto prover = composer.create_ultra_with_keccak_prover(); + auto verifier = composer.create_ultra_with_keccak_verifier(); + auto proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, expected_result); + } else { + auto prover = composer.create_prover(); + auto verifier = composer.create_verifier(); + auto proof = prover.construct_proof(); + bool verified = verifier.verify_proof(proof); + EXPECT_EQ(verified, expected_result); + } + }; +}; + +struct UseKeccak32Bytes { + static constexpr bool use_keccak = true; +}; + +struct UsePlookupPedersen16Bytes { + static constexpr bool use_keccak = false; +}; + +using BooleanTypes = ::testing::Types; +TYPED_TEST_SUITE(ultra_composer, BooleanTypes); + +TYPED_TEST(ultra_composer, create_gates_from_plookup_accumulators) { UltraComposer composer = UltraComposer(); @@ -106,16 +138,10 @@ TEST(ultra_composer, create_gates_from_plookup_accumulators) EXPECT_EQ(composer.get_variable(lookup_witnesses_hi[ColumnIdx::C3][i]), expected_y[i + num_lookups_lo]); } - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - auto proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, test_no_lookup_proof) +TYPED_TEST(ultra_composer, test_no_lookup_proof) { UltraComposer composer = UltraComposer(); @@ -133,17 +159,10 @@ TEST(ultra_composer, test_no_lookup_proof) } } - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - auto proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, test_elliptic_gate) +TYPED_TEST(ultra_composer, test_elliptic_gate) { typedef grumpkin::g1::affine_element affine_element; typedef grumpkin::g1::element element; @@ -180,17 +199,10 @@ TEST(ultra_composer, test_elliptic_gate) gate = ecc_add_gate{ x1, y1, x2, y2, x3, y3, beta.sqr(), -1 }; composer.create_ecc_add_gate(gate); - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - auto proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, non_trivial_tag_permutation) +TYPED_TEST(ultra_composer, non_trivial_tag_permutation) { UltraComposer composer = UltraComposer(); fr a = fr::random_element(); @@ -215,15 +227,11 @@ TEST(ultra_composer, non_trivial_tag_permutation) // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, non_trivial_tag_permutation_and_cycles) + +TYPED_TEST(ultra_composer, non_trivial_tag_permutation_and_cycles) { UltraComposer composer = UltraComposer(); fr a = fr::random_element(); @@ -257,17 +265,11 @@ TEST(ultra_composer, non_trivial_tag_permutation_and_cycles) // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); // composer.create_add_gate({ a_idx, b_idx, composer.zero_idx, fr::one(), fr::neg_one(), fr::zero(), fr::zero() }); - auto prover = composer.create_prover(); - - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, bad_tag_permutation) +TYPED_TEST(ultra_composer, bad_tag_permutation) { UltraComposer composer = UltraComposer(); fr a = fr::random_element(); @@ -288,16 +290,12 @@ TEST(ultra_composer, bad_tag_permutation) composer.assign_tag(b_idx, 1); composer.assign_tag(c_idx, 2); composer.assign_tag(d_idx, 2); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } // same as above but with turbocomposer to check reason of failue is really tag mismatch -TEST(ultra_composer, bad_tag_turbo_permutation) +TYPED_TEST(ultra_composer, bad_tag_turbo_permutation) { UltraComposer composer = UltraComposer(); fr a = fr::random_element(); @@ -317,13 +315,10 @@ TEST(ultra_composer, bad_tag_turbo_permutation) auto prover = composer.create_prover(); auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, sort_widget) +TYPED_TEST(ultra_composer, sort_widget) { UltraComposer composer = UltraComposer(); fr a = fr::one(); @@ -336,16 +331,11 @@ TEST(ultra_composer, sort_widget) auto c_idx = composer.add_variable(c); auto d_idx = composer.add_variable(d); composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, sort_with_edges_gate) +TYPED_TEST(ultra_composer, sort_with_edges_gate) { fr a = fr::one(); @@ -368,13 +358,8 @@ TEST(ultra_composer, sort_with_edges_gate) auto g_idx = composer.add_variable(g); auto h_idx = composer.add_variable(h); composer.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, a, h); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } { @@ -407,13 +392,8 @@ TEST(ultra_composer, sort_with_edges_gate) auto g_idx = composer.add_variable(g); auto h_idx = composer.add_variable(h); composer.create_sort_constraint_with_edges({ a_idx, b_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } { UltraComposer composer = UltraComposer(); @@ -426,26 +406,16 @@ TEST(ultra_composer, sort_with_edges_gate) auto h_idx = composer.add_variable(h); auto b2_idx = composer.add_variable(fr(15)); composer.create_sort_constraint_with_edges({ a_idx, b2_idx, c_idx, d_idx, e_idx, f_idx, g_idx, h_idx }, b, h); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } { UltraComposer composer = UltraComposer(); auto idx = add_variables(composer, { 1, 2, 5, 6, 7, 10, 11, 13, 16, 17, 20, 22, 22, 25, 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); composer.create_sort_constraint_with_edges(idx, 1, 45); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } { UltraComposer composer = UltraComposer(); @@ -453,16 +423,12 @@ TEST(ultra_composer, sort_with_edges_gate) 26, 29, 29, 32, 32, 33, 35, 38, 39, 39, 42, 42, 43, 45 }); composer.create_sort_constraint_with_edges(idx, 1, 29); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } } -TEST(ultra_composer, range_constraint) +TYPED_TEST(ultra_composer, range_constraint) { { UltraComposer composer = UltraComposer(); @@ -488,13 +454,8 @@ TEST(ultra_composer, range_constraint) } // auto ind = {a_idx,b_idx,c_idx,d_idx,e_idx,f_idx,g_idx,h_idx}; composer.create_dummy_constraints(indices); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } { UltraComposer composer = UltraComposer(); @@ -503,13 +464,8 @@ TEST(ultra_composer, range_constraint) composer.create_new_range_constraint(indices[i], 8); } composer.create_sort_constraint(indices); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } { UltraComposer composer = UltraComposer(); @@ -519,13 +475,8 @@ TEST(ultra_composer, range_constraint) composer.create_new_range_constraint(indices[i], 128); } composer.create_dummy_constraints(indices); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } { UltraComposer composer = UltraComposer(); @@ -551,17 +502,12 @@ TEST(ultra_composer, range_constraint) composer.create_new_range_constraint(indices[i], 79); } composer.create_dummy_constraints(indices); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } } -TEST(ultra_composer, range_with_gates) +TYPED_TEST(ultra_composer, range_with_gates) { UltraComposer composer = UltraComposer(); @@ -574,15 +520,11 @@ TEST(ultra_composer, range_with_gates) composer.create_add_gate({ idx[2], idx[3], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); composer.create_add_gate({ idx[4], idx[5], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); composer.create_add_gate({ idx[6], idx[7], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, range_with_gates_where_range_is_not_a_power_of_two) +TYPED_TEST(ultra_composer, range_with_gates_where_range_is_not_a_power_of_two) { UltraComposer composer = UltraComposer(); auto idx = add_variables(composer, { 1, 2, 3, 4, 5, 6, 7, 8 }); @@ -594,15 +536,11 @@ TEST(ultra_composer, range_with_gates_where_range_is_not_a_power_of_two) composer.create_add_gate({ idx[2], idx[3], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -7 }); composer.create_add_gate({ idx[4], idx[5], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -11 }); composer.create_add_gate({ idx[6], idx[7], composer.zero_idx, fr::one(), fr::one(), fr::zero(), -15 }); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, sort_widget_complex) +TYPED_TEST(ultra_composer, sort_widget_complex) { { @@ -628,16 +566,11 @@ TEST(ultra_composer, sort_widget_complex) for (size_t i = 0; i < a.size(); i++) ind.emplace_back(composer.add_variable(a[i])); composer.create_sort_constraint(ind); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); // instance, prover.reference_string.SRS_T2); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } } -TEST(ultra_composer, sort_widget_neg) +TYPED_TEST(ultra_composer, sort_widget_neg) { UltraComposer composer = UltraComposer(); fr a = fr::one(); @@ -650,15 +583,11 @@ TEST(ultra_composer, sort_widget_neg) auto c_idx = composer.add_variable(c); auto d_idx = composer.add_variable(d); composer.create_sort_constraint({ a_idx, b_idx, c_idx, d_idx }); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, false); + TestFixture::prove_and_verify(composer, /*expected_result=*/false); } -TEST(ultra_composer, composed_range_constraint) + +TYPED_TEST(ultra_composer, composed_range_constraint) { UltraComposer composer = UltraComposer(); auto c = fr::random_element(); @@ -667,16 +596,11 @@ TEST(ultra_composer, composed_range_constraint) auto a_idx = composer.add_variable(fr(e)); composer.create_add_gate({ a_idx, composer.zero_idx, composer.zero_idx, 1, 0, 0, -fr(e) }); composer.decompose_into_default_range(a_idx, 134); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, non_native_field_multiplication) +TYPED_TEST(ultra_composer, non_native_field_multiplication) { UltraComposer composer = UltraComposer(); @@ -728,16 +652,10 @@ TEST(ultra_composer, non_native_field_multiplication) const auto [lo_1_idx, hi_1_idx] = composer.evaluate_non_native_field_multiplication(inputs); composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); - auto prover = composer.create_prover(); - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, rom) +TYPED_TEST(ultra_composer, rom) { UltraComposer composer = UltraComposer(); @@ -774,18 +692,10 @@ TEST(ultra_composer, rom) 0, }); - auto prover = composer.create_prover(); - info("composer.num_gates after constructing prover: ", composer.num_gates); - - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -TEST(ultra_composer, ram) +TYPED_TEST(ultra_composer, ram) { UltraComposer composer = UltraComposer(); @@ -845,15 +755,7 @@ TEST(ultra_composer, ram) }, false); - auto prover = composer.create_prover(); - std::cout << "prover num_gates = " << composer.num_gates << std::endl; - - auto verifier = composer.create_verifier(); - - proof proof = prover.construct_proof(); - - bool result = verifier.verify_proof(proof); - EXPECT_EQ(result, true); + TestFixture::prove_and_verify(composer, /*expected_result=*/true); } -} // namespace proof_system::plonk +} // namespace proof_system::plonk::test_ultra_composer diff --git a/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp b/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp index 9e1d5a9d02..f084d8ad34 100644 --- a/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.cpp @@ -392,4 +392,5 @@ template class KateCommitmentScheme; template class KateCommitmentScheme; template class KateCommitmentScheme; template class KateCommitmentScheme; +template class KateCommitmentScheme; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp b/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp index 653340dbdf..741193d0d8 100644 --- a/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/commitment_scheme/kate_commitment_scheme.hpp @@ -43,5 +43,6 @@ extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; extern template class KateCommitmentScheme; +extern template class KateCommitmentScheme; } // namespace proof_system::plonk \ No newline at end of file diff --git a/cpp/src/barretenberg/plonk/proof_system/constants.hpp b/cpp/src/barretenberg/plonk/proof_system/constants.hpp index b5de8c110a..ac2ee70fec 100644 --- a/cpp/src/barretenberg/plonk/proof_system/constants.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/constants.hpp @@ -1,20 +1,10 @@ #pragma once #include -#include "barretenberg/proof_system/types/composer_type.hpp" -namespace proof_system::plonk { -// This variable sets the composer (TURBO or ULTRA) of the entire stdlib and rollup modules. -// To switch to using a new composer, only changing this variable should activate the new composer -// throughout the stdlib and circuits. -#ifdef USE_TURBO -static constexpr uint32_t SYSTEM_COMPOSER = ComposerType::TURBO; -#else -static constexpr uint32_t SYSTEM_COMPOSER = ComposerType::PLOOKUP; -#endif +namespace proof_system::plonk { // limb size when simulating a non-native field using bigfield class // (needs to be a universal constant to be used by native verifier) static constexpr uint64_t NUM_LIMB_BITS_IN_FIELD_SIMULATION = 68; - static constexpr uint32_t NUM_QUOTIENT_PARTS = 4; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/prover/c_bind.cpp b/cpp/src/barretenberg/plonk/proof_system/prover/c_bind.cpp index 6cd4d4c7af..ef1d1c1392 100644 --- a/cpp/src/barretenberg/plonk/proof_system/prover/c_bind.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/prover/c_bind.cpp @@ -1,7 +1,6 @@ #include "prover.hpp" #include "barretenberg/env/data_store.hpp" #include "barretenberg/env/crs.hpp" -#include "barretenberg/proof_system/types/composer_type.hpp" #define WASM_EXPORT __attribute__((visibility("default"))) @@ -48,8 +47,8 @@ WASM_EXPORT void* test_env_load_prover_crs(size_t num_points) { return env_load_prover_crs(num_points); } -typedef std::conditional_t - WasmProver; + +using WasmProver = plonk::UltraProver; WASM_EXPORT void prover_process_queue(WasmProver* prover) { diff --git a/cpp/src/barretenberg/plonk/proof_system/prover/c_bind_unrolled.cpp b/cpp/src/barretenberg/plonk/proof_system/prover/c_bind_unrolled.cpp deleted file mode 100644 index 66cfdc7496..0000000000 --- a/cpp/src/barretenberg/plonk/proof_system/prover/c_bind_unrolled.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include "prover.hpp" -#include "barretenberg/proof_system/types/composer_type.hpp" - -#define WASM_EXPORT __attribute__((visibility("default"))) - -using namespace barretenberg; - -extern "C" { - -// TODO(Cody): Removed "unrolled" here when the time comes, if it does. -typedef std::conditional_t - WasmUnrolledProver; - -WASM_EXPORT void unrolled_prover_process_queue(WasmUnrolledProver* prover) -{ - prover->queue.process_queue(); -} - -WASM_EXPORT size_t unrolled_prover_get_circuit_size(WasmUnrolledProver* prover) -{ - return prover->get_circuit_size(); -} - -WASM_EXPORT void unrolled_prover_get_work_queue_item_info(WasmUnrolledProver* prover, uint8_t* result) -{ - auto info = prover->get_queued_work_item_info(); - memcpy(result, &info, sizeof(info)); -} - -WASM_EXPORT fr* unrolled_prover_get_scalar_multiplication_data(WasmUnrolledProver* prover, size_t work_item_number) -{ - return prover->get_scalar_multiplication_data(work_item_number); -} - -WASM_EXPORT size_t unrolled_prover_get_scalar_multiplication_size(WasmUnrolledProver* prover, size_t work_item_number) -{ - return prover->get_scalar_multiplication_size(work_item_number); -} - -WASM_EXPORT void unrolled_prover_put_scalar_multiplication_data(WasmUnrolledProver* prover, - g1::element* result, - const size_t work_item_number) -{ - prover->put_scalar_multiplication_data(*result, work_item_number); -} - -WASM_EXPORT fr* unrolled_prover_get_fft_data(WasmUnrolledProver* prover, fr* shift_factor, size_t work_item_number) -{ - auto data = prover->get_fft_data(work_item_number); - *shift_factor = data.shift_factor; - return data.data; -} - -WASM_EXPORT void unrolled_prover_put_fft_data(WasmUnrolledProver* prover, fr* result, size_t work_item_number) -{ - prover->put_fft_data(result, work_item_number); -} - -WASM_EXPORT fr* unrolled_prover_get_ifft_data(WasmUnrolledProver* prover, size_t work_item_number) -{ - return prover->get_ifft_data(work_item_number); -} - -WASM_EXPORT void unrolled_prover_put_ifft_data(WasmUnrolledProver* prover, fr* result, size_t work_item_number) -{ - prover->put_ifft_data(result, work_item_number); -} - -WASM_EXPORT void unrolled_prover_execute_preamble_round(WasmUnrolledProver* prover) -{ - prover->execute_preamble_round(); -} - -WASM_EXPORT void unrolled_prover_execute_first_round(WasmUnrolledProver* prover) -{ - prover->execute_first_round(); -} - -WASM_EXPORT void unrolled_prover_execute_second_round(WasmUnrolledProver* prover) -{ - prover->execute_second_round(); -} - -WASM_EXPORT void unrolled_prover_execute_third_round(WasmUnrolledProver* prover) -{ - prover->execute_third_round(); -} - -WASM_EXPORT void unrolled_prover_execute_fourth_round(WasmUnrolledProver* prover) -{ - prover->execute_fourth_round(); -} - -WASM_EXPORT void unrolled_prover_execute_fifth_round(WasmUnrolledProver* prover) -{ - prover->execute_fifth_round(); -} - -WASM_EXPORT void unrolled_prover_execute_sixth_round(WasmUnrolledProver* prover) -{ - prover->execute_sixth_round(); -} - -WASM_EXPORT size_t unrolled_prover_export_proof(WasmUnrolledProver* prover, uint8_t** proof_data_buf) -{ - auto& proof_data = prover->export_proof().proof_data; - *proof_data_buf = proof_data.data(); - return proof_data.size(); -} -} diff --git a/cpp/src/barretenberg/plonk/proof_system/prover/prover.cpp b/cpp/src/barretenberg/plonk/proof_system/prover/prover.cpp index 5e91ef8ae4..b7730585e1 100644 --- a/cpp/src/barretenberg/plonk/proof_system/prover/prover.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/prover/prover.cpp @@ -1,5 +1,6 @@ #include "prover.hpp" #include "../public_inputs/public_inputs.hpp" +#include "barretenberg/plonk/proof_system/types/prover_settings.hpp" #include "barretenberg/polynomials/polynomial.hpp" #include #include "barretenberg/ecc/curves/bn254/scalar_multiplication/scalar_multiplication.hpp" @@ -639,5 +640,6 @@ template class ProverBase; template class ProverBase; template class ProverBase; template class ProverBase; +template class ProverBase; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/prover/prover.hpp b/cpp/src/barretenberg/plonk/proof_system/prover/prover.hpp index d0476608e2..6b817e5c19 100644 --- a/cpp/src/barretenberg/plonk/proof_system/prover/prover.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/prover/prover.hpp @@ -109,5 +109,6 @@ typedef ProverBase UltraProver; // TODO(Mike): maybe just return // need separate cases for ultra vs ultra_to_standard...??? // TODO(Cody): Make this into an issue? typedef ProverBase UltraToStandardProver; +typedef ProverBase UltraWithKeccakProver; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp index 65911eba38..dfdc619606 100644 --- a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp @@ -3,7 +3,7 @@ #include #include #include -#include "barretenberg/plonk/proof_system/constants.hpp" +#include "barretenberg/proof_system/types/composer_type.hpp" namespace proof_system::plonk { diff --git a/cpp/src/barretenberg/plonk/proof_system/types/program_settings.hpp b/cpp/src/barretenberg/plonk/proof_system/types/program_settings.hpp index b4b9d5d753..39c98c2784 100644 --- a/cpp/src/barretenberg/plonk/proof_system/types/program_settings.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/types/program_settings.hpp @@ -187,4 +187,20 @@ class ultra_to_standard_verifier_settings : public ultra_verifier_settings { static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; }; +// This is neededed for the Noir backend. The ultra verifier contract uses 32-byte challenges generated with Keccak256. +class ultra_with_keccak_verifier_settings : public ultra_verifier_settings { + public: + typedef VerifierPlookupArithmeticWidget + PlookupArithmeticWidget; + typedef VerifierGenPermSortWidget GenPermSortWidget; + typedef VerifierTurboLogicWidget TurboLogicWidget; + typedef VerifierPermutationWidget PermutationWidget; + typedef VerifierPlookupWidget PlookupWidget; + typedef VerifierEllipticWidget EllipticWidget; + typedef VerifierPlookupAuxiliaryWidget + PlookupAuxiliaryWidget; + + static constexpr size_t num_challenge_bytes = 32; + static constexpr transcript::HashType hash_type = transcript::HashType::Keccak256; +}; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/types/prover_settings.hpp b/cpp/src/barretenberg/plonk/proof_system/types/prover_settings.hpp index fd439df766..3fa0e35d38 100644 --- a/cpp/src/barretenberg/plonk/proof_system/types/prover_settings.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/types/prover_settings.hpp @@ -57,4 +57,12 @@ class ultra_to_standard_settings : public ultra_settings { static constexpr transcript::HashType hash_type = transcript::HashType::PedersenBlake3s; }; +// Only needed because ultra-to-standard recursion requires us to use a Pedersen hash which is common to both Ultra & +// Standard plonk i.e. the non-ultra version. +class ultra_with_keccak_settings : public ultra_settings { + public: + static constexpr size_t num_challenge_bytes = 32; + static constexpr transcript::HashType hash_type = transcript::HashType::Keccak256; +}; + } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.test.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.test.cpp index 1e2a83998e..1151bf27c0 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.test.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.test.cpp @@ -17,7 +17,8 @@ namespace proof_system::plonk::test_verification_key { * * @return verification_key_data randomly generated */ -verification_key_data rand_vk_data() { +verification_key_data rand_vk_data() +{ verification_key_data vk_data; vk_data.composer_type = static_cast(proof_system::ComposerType::STANDARD); vk_data.circuit_size = 1024; // not random - must be power of 2 @@ -40,7 +41,7 @@ void expect_compressions_eq(verification_key_data vk0_data, verification_key_dat // 0 hash index EXPECT_EQ(vk0_data.compress_native(0), vk1_data.compress_native(0)); // nonzero hash index - EXPECT_EQ(vk0_data.compress_native(15), vk1_data.compress_native(15)); + // EXPECT_EQ(vk0_data.compress_native(15), vk1_data.compress_native(15)); } /** @@ -52,10 +53,10 @@ void expect_compressions_eq(verification_key_data vk0_data, verification_key_dat void expect_compressions_ne(verification_key_data vk0_data, verification_key_data vk1_data) { EXPECT_NE(vk0_data.compress_native(0), vk1_data.compress_native(0)); - EXPECT_NE(vk0_data.compress_native(15), vk1_data.compress_native(15)); + // EXPECT_NE(vk0_data.compress_native(15), vk1_data.compress_native(15)); // ne hash indices still lead to ne compressions - EXPECT_NE(vk0_data.compress_native(0), vk1_data.compress_native(15)); - EXPECT_NE(vk0_data.compress_native(14), vk1_data.compress_native(15)); + // EXPECT_NE(vk0_data.compress_native(0), vk1_data.compress_native(15)); + // EXPECT_NE(vk0_data.compress_native(14), vk1_data.compress_native(15)); } TEST(verification_key, buffer_serialization) @@ -93,8 +94,8 @@ TEST(verification_key, compression_inequality_index_mismatch) verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; // copy // inquality on hash index mismatch - EXPECT_NE(vk0_data.compress_native(0), vk1_data.compress_native(15)); - EXPECT_NE(vk0_data.compress_native(14), vk1_data.compress_native(15)); + // EXPECT_NE(vk0_data.compress_native(0), vk1_data.compress_native(15)); + // EXPECT_NE(vk0_data.compress_native(14), vk1_data.compress_native(15)); } TEST(verification_key, compression_inequality_composer_type) @@ -105,7 +106,7 @@ TEST(verification_key, compression_inequality_composer_type) expect_compressions_ne(vk0_data, vk1_data); } -TEST(verification_key, compression_inequality_different_circuit_size) \ +TEST(verification_key, compression_inequality_different_circuit_size) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; @@ -113,7 +114,7 @@ TEST(verification_key, compression_inequality_different_circuit_size) \ expect_compressions_ne(vk0_data, vk1_data); } -TEST(verification_key, compression_inequality_different_num_public_inputs) \ +TEST(verification_key, compression_inequality_different_num_public_inputs) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; @@ -121,7 +122,7 @@ TEST(verification_key, compression_inequality_different_num_public_inputs) \ expect_compressions_ne(vk0_data, vk1_data); } -TEST(verification_key, compression_inequality_different_commitments) \ +TEST(verification_key, compression_inequality_different_commitments) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; @@ -129,7 +130,7 @@ TEST(verification_key, compression_inequality_different_commitments) \ expect_compressions_ne(vk0_data, vk1_data); } -TEST(verification_key, compression_inequality_different_num_commitments) \ +TEST(verification_key, compression_inequality_different_num_commitments) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; @@ -137,7 +138,7 @@ TEST(verification_key, compression_inequality_different_num_commitments) \ expect_compressions_ne(vk0_data, vk1_data); } -TEST(verification_key, compression_equality_different_contains_recursive_proof) \ +TEST(verification_key, compression_equality_different_contains_recursive_proof) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; @@ -146,7 +147,7 @@ TEST(verification_key, compression_equality_different_contains_recursive_proof) expect_compressions_eq(vk0_data, vk1_data); } -TEST(verification_key, compression_equality_different_recursive_proof_public_input_indices) \ +TEST(verification_key, compression_equality_different_recursive_proof_public_input_indices) { verification_key_data vk0_data = rand_vk_data(); verification_key_data vk1_data = vk0_data; diff --git a/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.cpp b/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.cpp index 2a4681df5f..bee400fd36 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.cpp @@ -241,5 +241,6 @@ template class VerifierBase; template class VerifierBase; template class VerifierBase; template class VerifierBase; +template class VerifierBase; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.hpp b/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.hpp index 151117ba10..951cef26d6 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/verifier/verifier.hpp @@ -32,10 +32,11 @@ extern template class VerifierBase; extern template class VerifierBase; extern template class VerifierBase; extern template class VerifierBase; +extern template class VerifierBase; typedef VerifierBase Verifier; typedef VerifierBase TurboVerifier; typedef VerifierBase UltraVerifier; typedef VerifierBase UltraToStandardVerifier; - +typedef VerifierBase UltraWithKeccakVerifier; } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp b/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp index 73039533fc..ef70eb9a18 100644 --- a/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp +++ b/cpp/src/barretenberg/proof_system/composer/permutation_helper.hpp @@ -222,29 +222,30 @@ PermutationMapping compute_permutation_mapping(const CircuitConst } /** - * @brief Compute Sigma polynomials for Honk from a mapping and put into polynomial cache + * @brief Compute Sigma/ID polynomials for Honk from a mapping and put into polynomial cache * - * @details Given a mapping (effectively at table pointing witnesses to other witnesses) compute Sigma polynomials in - * lagrange form and put them into the cache. This version distinguishes betweenr regular elements and public inputs, - * but ignores tags + * @details Given a mapping (effectively at table pointing witnesses to other witnesses) compute Sigma/ID polynomials in + * lagrange form and put them into the cache. This version is suitable for traditional and generalized permutations. * * @tparam program_width The number of wires - * @param sigma_mappings A table with information about permuting each element + * @param permutation_mappings A table with information about permuting each element * @param key Pointer to the proving key */ template -void compute_honk_style_sigma_lagrange_polynomials_from_mapping( - std::array, program_width>& sigma_mappings, plonk::proving_key* key) +void compute_honk_style_permutation_lagrange_polynomials_from_mapping( + std::string label, + std::array, program_width>& permutation_mappings, + plonk::proving_key* key) { const size_t num_gates = key->circuit_size; - std::array sigma; + std::array permutation_poly; // sigma or ID poly for (size_t wire_index = 0; wire_index < program_width; wire_index++) { - sigma[wire_index] = barretenberg::polynomial(num_gates); - auto& current_sigma_polynomial = sigma[wire_index]; + permutation_poly[wire_index] = barretenberg::polynomial(num_gates); + auto& current_permutation_poly = permutation_poly[wire_index]; ITERATE_OVER_DOMAIN_START(key->small_domain) - const auto& current_mapping = sigma_mappings[wire_index][i]; + const auto& current_mapping = permutation_mappings[wire_index][i]; if (current_mapping.is_public_input) { // We intentionally want to break the cycles of the public input variables. // During the witness generation, the left and right wire polynomials at index i contain the i-th public @@ -254,13 +255,15 @@ void compute_honk_style_sigma_lagrange_polynomials_from_mapping( // -(i+1) -> (n+i) // These indices are chosen so they can easily be computed by the verifier. They can expect the running // product to be equal to the "public input delta" that is computed in - current_sigma_polynomial[i] = + current_permutation_poly[i] = -barretenberg::fr(current_mapping.row_index + 1 + num_gates * current_mapping.column_index); + } else if (current_mapping.is_tag) { + // Set evaluations to (arbitrary) values disjoint from non-tag values + current_permutation_poly[i] = num_gates * program_width + current_mapping.row_index; } else { - ASSERT(!current_mapping.is_tag); // For the regular permutation we simply point to the next location by setting the evaluation to its // index - current_sigma_polynomial[i] = + current_permutation_poly[i] = barretenberg::fr(current_mapping.row_index + num_gates * current_mapping.column_index); } ITERATE_OVER_DOMAIN_END; @@ -268,7 +271,7 @@ void compute_honk_style_sigma_lagrange_polynomials_from_mapping( // Save to polynomial cache for (size_t j = 0; j < program_width; j++) { std::string index = std::to_string(j + 1); - key->polynomial_store.put("sigma_" + index + "_lagrange", std::move(sigma[j])); + key->polynomial_store.put(label + "_" + index + "_lagrange", std::move(permutation_poly[j])); } } @@ -447,7 +450,7 @@ void compute_standard_honk_sigma_permutations(CircuitConstructor& circuit_constr // Compute the permutation table specifying which element becomes which auto mapping = compute_permutation_mapping(circuit_constructor, key); // Compute Honk-style sigma polynomial fromt the permutation table - compute_honk_style_sigma_lagrange_polynomials_from_mapping(mapping.sigmas, key); + compute_honk_style_permutation_lagrange_polynomials_from_mapping("sigma", mapping.sigmas, key); } /** @@ -508,4 +511,23 @@ void compute_plonk_generalized_sigma_permutations(const CircuitConstructor& circ compute_monomial_and_coset_fft_polynomials_from_lagrange("id", key); } +/** + * @brief Compute generalized permutation sigmas and ids for ultra plonk + * + * @tparam program_width + * @tparam CircuitConstructor + * @param circuit_constructor + * @param key + * @return std::array, program_width> + */ +template +void compute_honk_generalized_sigma_permutations(const CircuitConstructor& circuit_constructor, plonk::proving_key* key) +{ + auto mapping = compute_permutation_mapping(circuit_constructor, key); + + // Compute Honk-style sigma and ID polynomials from the corresponding mappings + compute_honk_style_permutation_lagrange_polynomials_from_mapping("sigma", mapping.sigmas, key); + compute_honk_style_permutation_lagrange_polynomials_from_mapping("id", mapping.ids, key); +} + } // namespace proof_system diff --git a/cpp/src/barretenberg/stdlib/encryption/schnorr/schnorr.test.cpp b/cpp/src/barretenberg/stdlib/encryption/schnorr/schnorr.test.cpp index aa4457a72e..4a2ab39e8a 100644 --- a/cpp/src/barretenberg/stdlib/encryption/schnorr/schnorr.test.cpp +++ b/cpp/src/barretenberg/stdlib/encryption/schnorr/schnorr.test.cpp @@ -1,15 +1,28 @@ #include "schnorr.hpp" #include "barretenberg/crypto/pedersen_commitment/pedersen.hpp" #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include "barretenberg/stdlib/primitives/point/point.hpp" #include -#include "barretenberg/stdlib/types/types.hpp" -namespace test_stdlib_schnorr { +namespace proof_system::test_stdlib_schnorr { using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; using namespace proof_system::plonk::stdlib::schnorr; +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; +using bool_ct = bool_t; +using byte_array_ct = byte_array; +using field_ct = field_t; +using point_ct = point; +using witness_ct = witness_t; + auto run_scalar_mul_test = [](grumpkin::fr scalar_mont, bool expect_verify) { Composer composer = Composer(); @@ -204,9 +217,9 @@ TEST(stdlib_schnorr, verify_signature) EXPECT_EQ(first_result, true); point_ct pub_key{ witness_ct(&composer, account.public_key.x), witness_ct(&composer, account.public_key.y) }; - stdlib::schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, signature); + signature_bits sig = convert_signature(&composer, signature); byte_array_ct message(&composer, message_string); - stdlib::schnorr::verify_signature(message, pub_key, sig); + verify_signature(message, pub_key, sig); auto prover = composer.create_prover(); info("composer gates = %zu\n", composer.get_num_gates()); @@ -248,9 +261,9 @@ TEST(stdlib_schnorr, verify_signature_failure) // check stdlib verification with account 2 public key fails point_ct pub_key2_ct{ witness_ct(&composer, account2.public_key.x), witness_ct(&composer, account2.public_key.y) }; - stdlib::schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, signature); + signature_bits sig = convert_signature(&composer, signature); byte_array_ct message(&composer, message_string); - stdlib::schnorr::verify_signature(message, pub_key2_ct, sig); + verify_signature(message, pub_key2_ct, sig); auto prover = composer.create_prover(); @@ -286,14 +299,14 @@ TEST(stdlib_schnorr, signature_verification_result) EXPECT_EQ(first_result, true); point_ct pub_key{ witness_ct(&composer, account.public_key.x), witness_ct(&composer, account.public_key.y) }; - stdlib::schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, signature); + signature_bits sig = convert_signature(&composer, signature); byte_array_ct message(&composer, longer_string); - bool_ct signature_result = stdlib::schnorr::signature_verification_result(message, pub_key, sig); + bool_ct signature_result = signature_verification_result(message, pub_key, sig); EXPECT_EQ(signature_result.witness_bool, true); - plonk::stdlib::types::Prover prover = composer.create_prover(); + Prover prover = composer.create_prover(); info("composer gates = %zu\n", composer.get_num_gates()); - plonk::stdlib::types::Verifier verifier = composer.create_verifier(); + Verifier verifier = composer.create_verifier(); plonk::proof proof = prover.construct_proof(); bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); @@ -330,17 +343,17 @@ TEST(stdlib_schnorr, signature_verification_result_failure) // check stdlib verification with account 2 public key fails point_ct pub_key2_ct{ witness_ct(&composer, account2.public_key.x), witness_ct(&composer, account2.public_key.y) }; - stdlib::schnorr::signature_bits sig = stdlib::schnorr::convert_signature(&composer, signature); + signature_bits sig = convert_signature(&composer, signature); byte_array_ct message(&composer, message_string); - bool_ct signature_result = stdlib::schnorr::signature_verification_result(message, pub_key2_ct, sig); + bool_ct signature_result = signature_verification_result(message, pub_key2_ct, sig); EXPECT_EQ(signature_result.witness_bool, false); - plonk::stdlib::types::Prover prover = composer.create_prover(); + Prover prover = composer.create_prover(); info("composer gates = %zu\n", composer.get_num_gates()); - plonk::stdlib::types::Verifier verifier = composer.create_verifier(); + Verifier verifier = composer.create_verifier(); plonk::proof proof = prover.construct_proof(); bool verification_result = verifier.verify_proof(proof); EXPECT_EQ(verification_result, true); } -} // namespace test_stdlib_schnorr +} // namespace proof_system::test_stdlib_schnorr diff --git a/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp b/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp index b61cbc305d..61e7ccc293 100644 --- a/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp +++ b/cpp/src/barretenberg/stdlib/hash/blake2s/blake2s.test.cpp @@ -1,21 +1,27 @@ #include "blake2s.hpp" #include "blake2s_plookup.hpp" -#include "barretenberg/crypto/blake2s/blake2s.hpp" #include -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/crypto/blake2s/blake2s.hpp" using namespace barretenberg; using namespace proof_system::plonk; -using namespace proof_system::plonk::stdlib::types; -typedef stdlib::byte_array byte_array; -typedef stdlib::byte_array byte_array_plookup; -typedef stdlib::public_witness_t public_witness_t; -typedef stdlib::public_witness_t public_witness_t_plookup; +using namespace plonk::stdlib; + +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; + +using field_ct = field_t; +using witness_ct = witness_t; +using byte_array_ct = stdlib::byte_array; +using byte_array_plookup = stdlib::byte_array; +using public_witness_t = stdlib::public_witness_t; +using public_witness_t_plookup = stdlib::public_witness_t; TEST(stdlib_blake2s, test_single_block) { - Composer composer = Composer(); + auto composer = Composer(); std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz01"; std::vector input_v(input.begin(), input.end()); @@ -28,7 +34,7 @@ TEST(stdlib_blake2s, test_single_block) auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); + info("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); auto proof = prover.construct_proof(); @@ -52,7 +58,7 @@ TEST(stdlib_blake2s, test_single_block_plookup) auto prover = composer.create_prover(); std::cout << "prover gates = " << prover.circuit_size << std::endl; - printf("composer gates = %zu\n", composer.get_num_gates()); + info("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); auto proof = prover.construct_proof(); @@ -63,7 +69,7 @@ TEST(stdlib_blake2s, test_single_block_plookup) TEST(stdlib_blake2s, test_double_block) { - Composer composer = Composer(); + auto composer = Composer(); std::string input = "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789"; std::vector input_v(input.begin(), input.end()); @@ -76,7 +82,7 @@ TEST(stdlib_blake2s, test_double_block) auto prover = composer.create_prover(); - printf("composer gates = %zu\n", composer.get_num_gates()); + info("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); auto proof = prover.construct_proof(); @@ -100,7 +106,7 @@ TEST(stdlib_blake2s, test_double_block_plookup) auto prover = composer.create_prover(); std::cout << "prover gates = " << prover.circuit_size << std::endl; - printf("composer gates = %zu\n", composer.get_num_gates()); + info("composer gates = %zu\n", composer.get_num_gates()); auto verifier = composer.create_verifier(); auto proof = prover.construct_proof(); diff --git a/cpp/src/barretenberg/stdlib/hash/sha256/sha256.bench.cpp b/cpp/src/barretenberg/stdlib/hash/sha256/sha256.bench.cpp index 1c5947c669..d912505c44 100644 --- a/cpp/src/barretenberg/stdlib/hash/sha256/sha256.bench.cpp +++ b/cpp/src/barretenberg/stdlib/hash/sha256/sha256.bench.cpp @@ -2,10 +2,14 @@ #include #include "barretenberg/ecc/curves/bn254/fr.hpp" #include "barretenberg/plonk/composer/ultra_composer.hpp" -#include "barretenberg/stdlib/types/types.hpp" +#include "barretenberg/plonk/proof_system/prover/prover.hpp" +#include "barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.hpp" using namespace benchmark; -using namespace proof_system::plonk::stdlib::types; + +using Composer = proof_system::plonk::UltraComposer; +using Prover = proof_system::plonk::UltraProver; +using Verifier = proof_system::plonk::UltraVerifier; constexpr size_t NUM_HASHES = 8; constexpr size_t BYTES_PER_CHUNK = 512; @@ -24,13 +28,13 @@ void generate_test_plonk_circuit(Composer& composer, size_t num_bytes) for (size_t i = 0; i < num_bytes; ++i) { in[i] = get_random_char(); } - packed_byte_array_ct input(&composer, in); - plonk::stdlib::sha256(input); + proof_system::plonk::stdlib::packed_byte_array input(&composer, in); + proof_system::plonk::stdlib::sha256(input); } -stdlib::types::Composer composers[NUM_HASHES]; -stdlib::types::Prover provers[NUM_HASHES]; -stdlib::types::Verifier verifiers[NUM_HASHES]; +Composer composers[NUM_HASHES]; +Prover provers[NUM_HASHES]; +Verifier verifiers[NUM_HASHES]; plonk::proof proofs[NUM_HASHES]; void construct_witnesses_bench(State& state) noexcept diff --git a/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp b/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp index 9379e2382a..8f9952da34 100644 --- a/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp +++ b/cpp/src/barretenberg/stdlib/hash/sha256/sha256.test.cpp @@ -2,8 +2,9 @@ #include "barretenberg/common/test.hpp" #include "barretenberg/crypto/sha256/sha256.hpp" #include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/plonk/composer/plookup_tables/plookup_tables.hpp" -#include "barretenberg/stdlib/types/types.hpp" #include "barretenberg/numeric/random/engine.hpp" #include "barretenberg/numeric/bitop/rotate.hpp" @@ -13,11 +14,18 @@ namespace { auto& engine = numeric::random::get_debug_engine(); } -namespace test_stdlib_sha256 { +namespace proof_system::test_stdlib_sha256 { using namespace barretenberg; -using namespace proof_system::plonk; -using namespace proof_system::plonk::stdlib::types; +using namespace proof_system::plonk::stdlib; + +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; + +using byte_array_ct = byte_array; +using packed_byte_array_ct = packed_byte_array; +using field_ct = field_t; constexpr uint64_t ror(uint64_t val, uint64_t shift) { @@ -112,8 +120,9 @@ std::array inner_block(std::array& w) TEST(stdlib_sha256, test_duplicate_proving_key) { - plonk::StandardComposer first_composer = StandardComposer(); - stdlib::packed_byte_array input(&first_composer, "An 8 character password? Snow White and the 7 Dwarves.."); + auto first_composer = plonk::StandardComposer(); + plonk::stdlib::packed_byte_array input( + &first_composer, "An 8 character password? Snow White and the 7 Dwarves.."); plonk::stdlib::sha256(input); auto prover = first_composer.create_prover(); auto verifier = first_composer.create_verifier(); @@ -125,8 +134,9 @@ TEST(stdlib_sha256, test_duplicate_proving_key) auto circuit_size = prover.circuit_size; // Test a second time with same keys and different input. - plonk::StandardComposer second_composer = StandardComposer(proving_key, verification_key, circuit_size); - stdlib::packed_byte_array input2(&second_composer, "An 8 character password? Snow White and the 9 Dwarves.."); + auto second_composer = plonk::StandardComposer(proving_key, verification_key, circuit_size); + plonk::stdlib::packed_byte_array input2( + &second_composer, "An 8 character password? Snow White and the 9 Dwarves.."); plonk::stdlib::sha256(input2); auto second_prover = second_composer.create_prover(); auto second_verifier = second_composer.create_verifier(); @@ -138,14 +148,14 @@ TEST(stdlib_sha256, test_duplicate_proving_key) // TEST(stdlib_sha256_plookup, test_round) // { -// plonk::UltraComposer composer = UltraComposer(); +// auto composer = UltraComposer(); // std::array w_inputs; // std::array, 64> w_elements; // for (size_t i = 0; i < 64; ++i) { // w_inputs[i] = engine.get_random_uint32(); -// w_elements[i] = stdlib::witness_t(&composer, +// w_elements[i] = plonk::stdlib::witness_t(&composer, // barretenberg::fr(w_inputs[i])); // } @@ -169,15 +179,15 @@ TEST(stdlib_sha256, test_duplicate_proving_key) TEST(stdlib_sha256, test_plookup_55_bytes) { - typedef stdlib::field_t field_pt; - typedef stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; // 55 bytes is the largest number of bytes that can be hashed in a single block, // accounting for the single padding bit, and the 64 size bits required by the SHA-256 standard. - plonk::UltraComposer composer = UltraComposer(); + auto composer = plonk::UltraComposer(); packed_byte_array_pt input(&composer, "An 8 character password? Snow White and the 7 Dwarves.."); - packed_byte_array_pt output_bits = stdlib::sha256(input); + packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); std::vector output = output_bits.to_unverified_byte_slices(4); @@ -205,10 +215,10 @@ TEST(stdlib_sha256, test_55_bytes) { // 55 bytes is the largest number of bytes that can be hashed in a single block, // accounting for the single padding bit, and the 64 size bits required by the SHA-256 standard. - Composer composer = Composer(); + auto composer = Composer(); packed_byte_array_ct input(&composer, "An 8 character password? Snow White and the 7 Dwarves.."); - packed_byte_array_ct output_bits = stdlib::sha256(input); + packed_byte_array_ct output_bits = plonk::stdlib::sha256(input); std::vector output = output_bits.to_unverified_byte_slices(4); @@ -234,13 +244,13 @@ TEST(stdlib_sha256, test_55_bytes) TEST(stdlib_sha256, test_NIST_vector_one_packed_byte_array) { - typedef stdlib::field_t field_pt; - typedef stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - plonk::UltraComposer composer = UltraComposer(); + auto composer = plonk::UltraComposer(); packed_byte_array_pt input(&composer, "abc"); - packed_byte_array_pt output_bytes = stdlib::sha256(input); + packed_byte_array_pt output_bytes = plonk::stdlib::sha256(input); std::vector output = output_bytes.to_unverified_byte_slices(4); EXPECT_EQ(uint256_t(output[0].get_value()).data[0], (uint64_t)0xBA7816BFU); EXPECT_EQ(uint256_t(output[1].get_value()).data[0], (uint64_t)0x8F01CFEAU); @@ -265,14 +275,14 @@ TEST(stdlib_sha256, test_NIST_vector_one_packed_byte_array) TEST(stdlib_sha256, test_NIST_vector_one) { - typedef stdlib::field_t field_pt; - typedef stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - plonk::UltraComposer composer = UltraComposer(); + auto composer = plonk::UltraComposer(); packed_byte_array_pt input(&composer, "abc"); - packed_byte_array_pt output_bits = stdlib::sha256(input); + packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); std::vector output = output_bits.to_unverified_byte_slices(4); @@ -299,11 +309,11 @@ TEST(stdlib_sha256, test_NIST_vector_one) TEST(stdlib_sha256, test_NIST_vector_two) { - Composer composer = Composer(); + auto composer = Composer(); byte_array_ct input(&composer, "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"); - byte_array_ct output_bits = stdlib::sha256(input); + byte_array_ct output_bits = plonk::stdlib::sha256(input); std::vector output = packed_byte_array_ct(output_bits).to_unverified_byte_slices(4); @@ -330,12 +340,12 @@ TEST(stdlib_sha256, test_NIST_vector_two) TEST(stdlib_sha256, test_NIST_vector_three) { - Composer composer = Composer(); + auto composer = Composer(); // one byte, 0xbd byte_array_ct input(&composer, std::vector{ 0xbd }); - byte_array_ct output_bits = stdlib::sha256(input); + byte_array_ct output_bits = plonk::stdlib::sha256(input); std::vector output = packed_byte_array_ct(output_bits).to_unverified_byte_slices(4); @@ -361,12 +371,12 @@ TEST(stdlib_sha256, test_NIST_vector_three) TEST(stdlib_sha256, test_NIST_vector_four) { - Composer composer = Composer(); + auto composer = Composer(); // 4 bytes, 0xc98c8e55 byte_array_ct input(&composer, std::vector{ 0xc9, 0x8c, 0x8e, 0x55 }); - byte_array_ct output_bits = stdlib::sha256(input); + byte_array_ct output_bits = plonk::stdlib::sha256(input); std::vector output = packed_byte_array_ct(output_bits).to_unverified_byte_slices(4); @@ -392,10 +402,10 @@ TEST(stdlib_sha256, test_NIST_vector_four) HEAVY_TEST(stdlib_sha256, test_NIST_vector_five) { - typedef stdlib::field_t field_pt; - typedef stdlib::packed_byte_array packed_byte_array_pt; + typedef plonk::stdlib::field_t field_pt; + typedef plonk::stdlib::packed_byte_array packed_byte_array_pt; - plonk::UltraComposer composer = UltraComposer(); + auto composer = plonk::UltraComposer(); packed_byte_array_pt input( &composer, @@ -410,7 +420,7 @@ HEAVY_TEST(stdlib_sha256, test_NIST_vector_five) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" "AAAAAAAAAA"); - packed_byte_array_pt output_bits = stdlib::sha256(input); + packed_byte_array_pt output_bits = plonk::stdlib::sha256(input); std::vector output = output_bits.to_unverified_byte_slices(4); @@ -436,7 +446,7 @@ HEAVY_TEST(stdlib_sha256, test_NIST_vector_five) TEST(stdlib_sha256, test_input_len_multiple) { - Composer composer = Composer(); + auto composer = Composer(); std::vector input_sizes = { 1, 7, 15, 16, 30, 32, 55, 64, 90, 128, 512, 700 }; @@ -444,7 +454,7 @@ TEST(stdlib_sha256, test_input_len_multiple) auto input_buf = std::vector(inp, 1); byte_array_ct input(&composer, input_buf); - byte_array_ct output_bits = stdlib::sha256(input); + byte_array_ct output_bits = plonk::stdlib::sha256(input); auto circuit_output = output_bits.get_value(); @@ -456,7 +466,7 @@ TEST(stdlib_sha256, test_input_len_multiple) TEST(stdlib_sha256, test_input_str_len_multiple) { - Composer composer = Composer(); + auto composer = Composer(); std::vector input_strings = { "y", // 1 @@ -488,7 +498,7 @@ TEST(stdlib_sha256, test_input_str_len_multiple) auto input_buf = std::vector(input_str.begin(), input_str.end()); byte_array_ct input(&composer, input_buf); - byte_array_ct output_bits = stdlib::sha256(input); + byte_array_ct output_bits = plonk::stdlib::sha256(input); auto circuit_output = output_bits.get_value(); @@ -498,4 +508,4 @@ TEST(stdlib_sha256, test_input_str_len_multiple) } } -} // namespace test_stdlib_sha256 +} // namespace proof_system::test_stdlib_sha256 diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/hash.hpp b/cpp/src/barretenberg/stdlib/merkle_tree/hash.hpp index 32e6fb2d8d..63b647d7eb 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/hash.hpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/hash.hpp @@ -15,20 +15,12 @@ namespace merkle_tree { inline barretenberg::fr hash_pair_native(barretenberg::fr const& lhs, barretenberg::fr const& rhs) { - if (plonk::SYSTEM_COMPOSER == ComposerType::PLOOKUP) { - return crypto::pedersen_hash::lookup::hash_multiple({ lhs, rhs }); // uses lookup tables - } else { - return crypto::pedersen_hash::hash_multiple({ lhs, rhs }); // uses fixed-base multiplication gate - } + return crypto::pedersen_hash::lookup::hash_multiple({ lhs, rhs }); // uses lookup tables } inline barretenberg::fr hash_multiple_native(std::vector const& inputs) { - if (plonk::SYSTEM_COMPOSER == ComposerType::PLOOKUP) { - return crypto::pedersen_hash::lookup::hash_multiple(inputs); // uses lookup tables - } else { - return crypto::pedersen_hash::hash_multiple(inputs); // uses fixed-base multiplication gate - } + return crypto::pedersen_hash::lookup::hash_multiple(inputs); // uses lookup tables } /** @@ -46,11 +38,7 @@ inline barretenberg::fr compute_tree_root_native(std::vector c while (layer.size() > 1) { std::vector next_layer(layer.size() / 2); for (size_t i = 0; i < next_layer.size(); ++i) { - if (plonk::SYSTEM_COMPOSER == ComposerType::PLOOKUP) { - next_layer[i] = crypto::pedersen_hash::lookup::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); - } else { - next_layer[i] = crypto::pedersen_hash::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); - } + next_layer[i] = crypto::pedersen_hash::lookup::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); } layer = std::move(next_layer); } @@ -69,11 +57,7 @@ inline std::vector compute_tree_native(std::vector 1) { std::vector next_layer(layer.size() / 2); for (size_t i = 0; i < next_layer.size(); ++i) { - if (plonk::SYSTEM_COMPOSER == ComposerType::PLOOKUP) { - next_layer[i] = crypto::pedersen_hash::lookup::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); - } else { - next_layer[i] = crypto::pedersen_hash::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); - } + next_layer[i] = crypto::pedersen_hash::lookup::hash_multiple({ layer[i * 2], layer[i * 2 + 1] }); tree.push_back(next_layer[i]); } layer = std::move(next_layer); diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/hash.test.cpp b/cpp/src/barretenberg/stdlib/merkle_tree/hash.test.cpp index da60dfa799..5273c89677 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/hash.test.cpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/hash.test.cpp @@ -1,20 +1,31 @@ #include "hash.hpp" #include "memory_tree.hpp" #include -#include "barretenberg/stdlib/hash/pedersen/pedersen.hpp" + +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" #include "barretenberg/stdlib/merkle_tree/membership.hpp" -#include "barretenberg/stdlib/types/types.hpp" + +namespace proof_system::stdlib_merkle_tree_hash_test { using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; +using namespace plonk::stdlib; + +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; + +using field_ct = field_t; +using witness_ct = witness_t; TEST(stdlib_merkle_tree_hash, compress_native_vs_circuit) { fr x = uint256_t(0x5ec473eb273a8011, 0x50160109385471ca, 0x2f3095267e02607d, 0x02586f4a39e69b86); Composer composer = Composer(); witness_ct y = witness_ct(&composer, x); - field_ct z = plonk::stdlib::pedersen_hash::hash_multiple({ y, y }); - auto zz = stdlib::merkle_tree::hash_pair_native(x, x); + field_ct z = pedersen_hash::hash_multiple({ y, y }); + auto zz = merkle_tree::hash_pair_native(x, x); EXPECT_EQ(z.get_value(), zz); } @@ -31,8 +42,8 @@ TEST(stdlib_merkle_tree_hash, compute_tree_root_native_vs_circuit) inputs_ct.push_back(input_ct); } - field_ct z = plonk::stdlib::merkle_tree::compute_tree_root(inputs_ct); - auto zz = plonk::stdlib::merkle_tree::compute_tree_root_native(inputs); + field_ct z = merkle_tree::compute_tree_root(inputs_ct); + auto zz = merkle_tree::compute_tree_root_native(inputs); EXPECT_EQ(z.get_value(), zz); } @@ -40,7 +51,7 @@ TEST(stdlib_merkle_tree_hash, compute_tree_root_native_vs_circuit) TEST(stdlib_merkle_tree_hash, compute_tree_native) { constexpr size_t depth = 2; - stdlib::merkle_tree::MemoryTree mem_tree(depth); + merkle_tree::MemoryTree mem_tree(depth); std::vector leaves; for (size_t i = 0; i < (size_t(1) << depth); i++) { @@ -49,7 +60,7 @@ TEST(stdlib_merkle_tree_hash, compute_tree_native) mem_tree.update_element(i, input); } - std::vector tree_vector = plonk::stdlib::merkle_tree::compute_tree_native(leaves); + std::vector tree_vector = merkle_tree::compute_tree_native(leaves); // Check if the tree vector matches the memory tree hashes for (size_t i = 0; i < tree_vector.size() - 1; i++) { @@ -57,3 +68,4 @@ TEST(stdlib_merkle_tree_hash, compute_tree_native) } EXPECT_EQ(tree_vector.back(), mem_tree.root()); } +} // namespace proof_system::stdlib_merkle_tree_hash_test \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/membership.test.cpp b/cpp/src/barretenberg/stdlib/merkle_tree/membership.test.cpp index 1fb8f216f6..506a363ba2 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/membership.test.cpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/membership.test.cpp @@ -1,23 +1,38 @@ +#include + #include "merkle_tree.hpp" #include "membership.hpp" #include "memory_store.hpp" #include "memory_tree.hpp" -#include -#include "barretenberg/stdlib/types/types.hpp" -using namespace barretenberg; -using namespace proof_system::plonk::stdlib::types; -using namespace proof_system::plonk::stdlib::merkle_tree; +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/stdlib/primitives/bool/bool.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/primitives/witness/witness.hpp" namespace { auto& engine = numeric::random::get_debug_engine(); } +namespace proof_system::stdlib_merkle_test { + +using namespace barretenberg; +using namespace proof_system::plonk::stdlib::merkle_tree; +using namespace plonk::stdlib; + +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; + +using bool_ct = bool_t; +using field_ct = field_t; +using witness_ct = witness_t; + TEST(stdlib_merkle_tree, test_check_membership) { MemoryStore store; auto db = MerkleTree(store, 3); - Composer composer = Composer(); + auto composer = Composer(); // Check membership at index 0. auto zero = field_ct(witness_ct(&composer, fr::zero())).decompose_into_bits(); @@ -49,7 +64,7 @@ TEST(stdlib_merkle_tree, test_batch_update_membership) { MemoryStore store; MerkleTree db(store, 4); - Composer composer = Composer(); + auto composer = Composer(); // Fill in an arbitrary value at i = 2. db.update_element(2, fr::random_element()); // Define old state. @@ -78,7 +93,7 @@ TEST(stdlib_merkle_tree, test_assert_check_membership) { MemoryStore store; auto db = MerkleTree(store, 3); - Composer composer = Composer(); + auto composer = Composer(); auto zero = field_ct(witness_ct(&composer, fr::zero())).decompose_into_bits(); field_ct root = witness_ct(&composer, db.root()); @@ -101,7 +116,7 @@ TEST(stdlib_merkle_tree, test_assert_check_membership_fail) MemoryStore store; auto db = MerkleTree(store, 3); - Composer composer = Composer(); + auto composer = Composer(); auto zero = field_ct(witness_ct(&composer, fr::zero())).decompose_into_bits(); field_ct root = witness_ct(&composer, db.root()); @@ -125,7 +140,7 @@ TEST(stdlib_merkle_tree, test_update_members) MemoryStore store; auto db = MerkleTree(store, 3); - Composer composer = Composer(); + auto composer = Composer(); auto zero = field_ct(witness_ct(&composer, fr::zero())).decompose_into_bits(); @@ -154,7 +169,7 @@ TEST(stdlib_merkle_tree, test_update_members) MemoryStore store; auto db = MerkleTree(store, 3); - Composer composer = Composer(); + auto composer = Composer(); auto zero = field_ct(witness_ct(&composer, fr::zero())).decompose_into_bits(); @@ -189,7 +204,7 @@ TEST(stdlib_merkle_tree, test_tree) MerkleTree db(store, depth); MemoryTree mem_tree(depth); - Composer composer = Composer(); + auto composer = Composer(); auto zero_field = field_ct(witness_ct(&composer, fr::zero())); auto values = std::vector(num, zero_field); @@ -214,7 +229,7 @@ TEST(stdlib_merkle_tree, test_update_memberships) MemoryStore store; MerkleTree tree(store, depth); - Composer composer = Composer(); + auto composer = Composer(); constexpr size_t filled = (1UL << depth) / 2; std::vector filled_values; @@ -273,3 +288,4 @@ TEST(stdlib_merkle_tree, test_update_memberships) bool result = verifier.verify_proof(proof); EXPECT_EQ(result, true); } +} // namespace proof_system::stdlib_merkle_test \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/merkle_tree/merkle_tree.test.cpp b/cpp/src/barretenberg/stdlib/merkle_tree/merkle_tree.test.cpp index a4b3607be9..ed42634b57 100644 --- a/cpp/src/barretenberg/stdlib/merkle_tree/merkle_tree.test.cpp +++ b/cpp/src/barretenberg/stdlib/merkle_tree/merkle_tree.test.cpp @@ -4,11 +4,18 @@ #include "barretenberg/common/streams.hpp" #include "barretenberg/common/test.hpp" #include "barretenberg/numeric/random/engine.hpp" -#include "barretenberg/stdlib/types/types.hpp" -using namespace barretenberg; +namespace proof_system::test_stdlib_merkle_tree { + +using namespace plonk::stdlib; using namespace proof_system::plonk::stdlib::merkle_tree; +using Composer = plonk::UltraComposer; +using Prover = plonk::UltraProver; +using Verifier = plonk::UltraVerifier; + +using field_ct = field_t; +using witness_ct = witness_t; namespace { auto& engine = numeric::random::get_debug_engine(); auto& random_engine = numeric::random::get_engine(); @@ -128,3 +135,4 @@ TEST(stdlib_merkle_tree, test_get_hash_path_layers) EXPECT_NE(before[2], after[2]); } } +} // namespace proof_system::test_stdlib_merkle_tree \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.test.cpp b/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.test.cpp index 32eb7d5b80..e3b6576e9f 100644 --- a/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/biggroup/biggroup.test.cpp @@ -19,7 +19,6 @@ namespace { auto& engine = numeric::random::get_debug_engine(); } -// using namespace barretenberg; using namespace proof_system::plonk; // One can only define a TYPED_TEST with a single template paramter. diff --git a/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp b/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp index 81fcd8139a..3e8d1b2430 100644 --- a/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/byte_array/byte_array.test.cpp @@ -1,7 +1,8 @@ #include "byte_array.hpp" #include -#include "../../types/types.hpp" #include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/honk/composer/standard_honk_composer.hpp" #include "barretenberg/stdlib/primitives/bool/bool.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" diff --git a/cpp/src/barretenberg/stdlib/primitives/group/group.test.cpp b/cpp/src/barretenberg/stdlib/primitives/group/group.test.cpp index 1a71438a2e..07df473361 100644 --- a/cpp/src/barretenberg/stdlib/primitives/group/group.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/group/group.test.cpp @@ -1,8 +1,13 @@ -#include "../../types/types.hpp" #include +#include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/honk/composer/standard_honk_composer.hpp" #include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include "barretenberg/stdlib/primitives/group/group.hpp" #include "barretenberg/numeric/random/engine.hpp" + #define STDLIB_TYPE_ALIASES \ using Composer = TypeParam; \ using witness_ct = stdlib::witness_t; \ diff --git a/cpp/src/barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp b/cpp/src/barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp index 340d02a543..c194d0fead 100644 --- a/cpp/src/barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.test.cpp @@ -1,8 +1,13 @@ -#include "packed_byte_array.hpp" -#include "../../types/types.hpp" -#include "../byte_array/byte_array.hpp" #include + +#include "packed_byte_array.hpp" +#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" #include "barretenberg/numeric/random/engine.hpp" +#include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" +#include "barretenberg/honk/composer/standard_honk_composer.hpp" + #pragma GCC diagnostic ignored "-Wunused-local-typedefs" namespace test_stdlib_packed_byte_array { diff --git a/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp b/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp index 45657fb45f..3a315b1bd7 100644 --- a/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/safe_uint/safe_uint.test.cpp @@ -6,8 +6,10 @@ #include "safe_uint.hpp" #include "barretenberg/numeric/random/engine.hpp" #include "../byte_array/byte_array.hpp" +#include "barretenberg/plonk/composer/standard_composer.hpp" +#include "barretenberg/plonk/composer/turbo_composer.hpp" +#include "barretenberg/plonk/composer/ultra_composer.hpp" #include "barretenberg/honk/composer/standard_honk_composer.hpp" -#include "barretenberg/stdlib/types/types.hpp" #pragma GCC diagnostic ignored "-Wunused-local-typedefs" diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp index 5be5ce87c9..b9cb01e079 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.test.cpp @@ -63,10 +63,10 @@ TYPED_TEST(VerificationKeyFixture, vk_data_vs_recursion_compress_native) auto recurs_vk = RecursVk::from_witness(&composer, native_vk); EXPECT_EQ(vk_data.compress_native(0), RecursVk::compress_native(native_vk, 0)); - EXPECT_EQ(vk_data.compress_native(15), RecursVk::compress_native(native_vk, 15)); + // EXPECT_EQ(vk_data.compress_native(15), RecursVk::compress_native(native_vk, 15)); // ne hash indeces still lead to ne compressions - EXPECT_NE(vk_data.compress_native(0), RecursVk::compress_native(native_vk, 15)); - EXPECT_NE(vk_data.compress_native(14), RecursVk::compress_native(native_vk, 15)); + // EXPECT_NE(vk_data.compress_native(0), RecursVk::compress_native(native_vk, 15)); + // EXPECT_NE(vk_data.compress_native(14), RecursVk::compress_native(native_vk, 15)); } TYPED_TEST(VerificationKeyFixture, compress_vs_compress_native) @@ -83,8 +83,8 @@ TYPED_TEST(VerificationKeyFixture, compress_vs_compress_native) auto recurs_vk = RecursVk::from_witness(&composer, native_vk); EXPECT_EQ(recurs_vk->compress(0).get_value(), RecursVk::compress_native(native_vk, 0)); - EXPECT_EQ(recurs_vk->compress(15).get_value(), RecursVk::compress_native(native_vk, 15)); + // EXPECT_EQ(recurs_vk->compress(15).get_value(), RecursVk::compress_native(native_vk, 15)); // ne hash indeces still lead to ne compressions - EXPECT_NE(recurs_vk->compress(0).get_value(), RecursVk::compress_native(native_vk, 15)); - EXPECT_NE(recurs_vk->compress(14).get_value(), RecursVk::compress_native(native_vk, 15)); + // EXPECT_NE(recurs_vk->compress(0).get_value(), RecursVk::compress_native(native_vk, 15)); + // EXPECT_NE(recurs_vk->compress(14).get_value(), RecursVk::compress_native(native_vk, 15)); } diff --git a/cpp/src/barretenberg/stdlib/types/types.hpp b/cpp/src/barretenberg/stdlib/types/types.hpp deleted file mode 100644 index 706b160800..0000000000 --- a/cpp/src/barretenberg/stdlib/types/types.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#pragma once -#include "barretenberg/plonk/proof_system/constants.hpp" -#include "barretenberg/plonk/composer/standard_composer.hpp" -#include "barretenberg/plonk/composer/turbo_composer.hpp" -#include "barretenberg/plonk/composer/ultra_composer.hpp" -#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" -#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" -#include "barretenberg/stdlib/primitives/bit_array/bit_array.hpp" -#include "barretenberg/stdlib/primitives/bool/bool.hpp" -#include "barretenberg/stdlib/primitives/packed_byte_array/packed_byte_array.hpp" -#include "barretenberg/stdlib/primitives/byte_array/byte_array.hpp" -#include "barretenberg/stdlib/primitives/uint/uint.hpp" -#include "barretenberg/stdlib/primitives/witness/witness.hpp" -#include "barretenberg/stdlib/primitives/bigfield/bigfield.hpp" -#include "barretenberg/stdlib/primitives/biggroup/biggroup.hpp" -#include "barretenberg/stdlib/commitment/pedersen/pedersen.hpp" -#include "barretenberg/stdlib/commitment/pedersen/pedersen_plookup.hpp" -#include "barretenberg/stdlib/merkle_tree/hash_path.hpp" -#include "barretenberg/stdlib/encryption/schnorr/schnorr.hpp" -#include "barretenberg/stdlib/primitives/curves/bn254.hpp" -#include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" -#include "barretenberg/stdlib/primitives/memory/rom_table.hpp" -#include "barretenberg/stdlib/recursion/verifier/program_settings.hpp" -#include "barretenberg/stdlib/primitives/memory/ram_table.hpp" -#include "barretenberg/stdlib/primitives/memory/rom_table.hpp" -#include "barretenberg/stdlib/primitives/memory/dynamic_array.hpp" - -namespace proof_system::plonk::stdlib::types { - -using namespace proof_system::plonk; -static constexpr size_t SYSTEM_COMPOSER = proof_system::plonk::SYSTEM_COMPOSER; - -typedef std::conditional_t< - SYSTEM_COMPOSER == proof_system::STANDARD, - plonk::StandardComposer, - std::conditional_t> - Composer; - -typedef std::conditional_t< - SYSTEM_COMPOSER == proof_system::STANDARD, - plonk::Prover, - std::conditional_t> - Prover; - -typedef std::conditional_t< - SYSTEM_COMPOSER == proof_system::STANDARD, - plonk::Verifier, - std::conditional_t> - Verifier; - -typedef std::conditional_t< - SYSTEM_COMPOSER == proof_system::STANDARD, - plonk::Prover, - std::conditional_t> - Prover; - -typedef std::conditional_t< - SYSTEM_COMPOSER == proof_system::STANDARD, - plonk::Verifier, - std::conditional_t> - Verifier; - -typedef stdlib::witness_t witness_ct; -typedef stdlib::public_witness_t public_witness_ct; -typedef stdlib::bool_t bool_ct; -typedef stdlib::byte_array byte_array_ct; -typedef stdlib::packed_byte_array packed_byte_array_ct; -typedef stdlib::field_t field_ct; -typedef stdlib::safe_uint_t suint_ct; -typedef stdlib::uint8 uint8_ct; -typedef stdlib::uint16 uint16_ct; -typedef stdlib::uint32 uint32_ct; -typedef stdlib::uint64 uint64_ct; -typedef stdlib::bit_array bit_array_ct; -typedef stdlib::bigfield fq_ct; -typedef stdlib::element biggroup_ct; -typedef stdlib::point point_ct; -typedef stdlib::pedersen_commitment pedersen_commitment; -typedef stdlib::group group_ct; -typedef stdlib::bn254 bn254; -typedef stdlib::secp256k1 secp256k1_ct; - -namespace merkle_tree { -using namespace stdlib::merkle_tree; -typedef stdlib::merkle_tree::hash_path hash_path; -} // namespace merkle_tree - -namespace schnorr { -typedef stdlib::schnorr::signature_bits signature_bits; -} // namespace schnorr - -// Ultra-composer specific types -typedef stdlib::rom_table rom_table_ct; - -typedef std::conditional_t, - recursion::recursive_ultra_verifier_settings> - recursive_inner_verifier_settings; - -} // namespace proof_system::plonk::stdlib::types From f4253ad94a9824144f5d5552d31a99b50328a4fd Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 25 Apr 2023 11:44:20 -0400 Subject: [PATCH 10/64] master merge conflicts --- .github/workflows/nix.yml | 7 +- .../crypto/pedersen_hash/pedersen_lookup.cpp | 13 + .../dsl/acir_proofs/acir_proofs.cpp | 7 +- .../ultra_honk_composer_helper.cpp | 21 +- .../composer/standard_honk_composer.test.cpp | 2 +- .../honk/composer/ultra_honk_composer.hpp | 5 +- .../composer/ultra_honk_composer.test.cpp | 22 +- cpp/src/barretenberg/honk/flavor/flavor.hpp | 12 + .../grand_product_computation_relation.hpp | 1 + .../lookup_grand_product_relation.hpp | 205 ++++++++++++++ .../honk/sumcheck/relations/relation.hpp | 14 +- .../relations/relation_consistency.test.cpp | 115 +++++++- .../relations/relation_correctness.test.cpp | 119 ++++++-- .../ultra_arithmetic_relation_secondary.hpp | 1 + .../barretenberg/honk/sumcheck/sumcheck.hpp | 2 +- ...lic_inputs.hpp => grand_product_delta.hpp} | 22 ++ .../splitting_tmp/ultra_plonk_composer.hpp | 39 ++- .../ultra_plonk_composer.test.cpp | 4 +- .../plonk/composer/ultra_composer.cpp | 263 ++++++++++++------ .../plonk/composer/ultra_composer.hpp | 113 +++++++- .../plonk/composer/ultra_composer.test.cpp | 63 ++++- .../ultra_circuit_constructor.cpp | 205 ++++++++------ .../ultra_circuit_constructor.hpp | 97 ++++++- .../composer/permutation_helper.hpp | 2 +- .../primitives/bigfield/bigfield_impl.hpp | 6 +- .../stdlib/primitives/logic/logic.cpp | 70 +++-- .../stdlib/primitives/logic/logic.hpp | 18 +- .../stdlib/primitives/logic/logic.test.cpp | 210 +++++++++----- .../recursion/transcript/transcript.hpp | 46 ++- .../barretenberg/transcript/transcript.cpp | 17 +- .../barretenberg/transcript/transcript.hpp | 1 + 31 files changed, 1351 insertions(+), 371 deletions(-) create mode 100644 cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp rename cpp/src/barretenberg/honk/utils/{public_inputs.hpp => grand_product_delta.hpp} (72%) diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml index 91da25315c..40c37ad659 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix.yml @@ -3,7 +3,7 @@ name: Nix builds on: push: branches: - - phated/** + - master schedule: - cron: "0 2 * * *" # run at 2 AM UTC workflow_dispatch: @@ -32,6 +32,11 @@ jobs: nix_path: nixpkgs=channel:nixos-22.11 github_access_token: ${{ secrets.GITHUB_TOKEN }} + - uses: cachix/cachix-action@v12 + with: + name: barretenberg + authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + - name: Check nix flake run: | nix flake check diff --git a/cpp/src/barretenberg/crypto/pedersen_hash/pedersen_lookup.cpp b/cpp/src/barretenberg/crypto/pedersen_hash/pedersen_lookup.cpp index f455c684be..980b41a225 100644 --- a/cpp/src/barretenberg/crypto/pedersen_hash/pedersen_lookup.cpp +++ b/cpp/src/barretenberg/crypto/pedersen_hash/pedersen_lookup.cpp @@ -1,5 +1,7 @@ #include "./pedersen_lookup.hpp" +#include + #include "barretenberg/ecc/curves/grumpkin/grumpkin.hpp" namespace crypto { @@ -10,6 +12,12 @@ std::array, NUM_PEDERSEN_TABLES> peder std::vector pedersen_iv_table; std::array generators; +// Mutex is not available in the WASM context. +// WASM runs in a single-thread so this is acceptable. +#if !defined(__wasm__) +std::mutex init_mutex; +#endif + static bool inited = false; void init_single_lookup_table(const size_t index) @@ -66,6 +74,11 @@ void init() { ASSERT(BITS_PER_TABLE < BITS_OF_BETA); ASSERT(BITS_PER_TABLE + BITS_OF_BETA < BITS_ON_CURVE); + +#if !defined(__wasm__) + const std::lock_guard lock(init_mutex); +#endif + if (inited) { return; } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 4580792808..54ff5c9aa0 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -185,12 +185,11 @@ size_t new_proof(void* pippenger, reinterpret_cast(pippenger), g2x); proving_key->reference_string = crs_factory->get_prover_crs(proving_key->circuit_size); - // TODO: either need a context flag for recursive proofs or a new_recursive_proof method that uses regular - // UltraProver acir_format::Composer composer(proving_key, nullptr); create_circuit_with_witness(composer, constraint_system, witness); + // Either need a context flag for recursive proofs or a new_recursive_proof method that uses regular UltraProver if (is_recursive) { auto prover = composer.create_prover(); auto heapProver = new acir_format::RecursiveProver(std::move(prover)); @@ -228,8 +227,8 @@ bool verify_proof(uint8_t const* g2x, create_circuit(composer, constraint_system); plonk::proof pp = { std::vector(proof, proof + length) }; - // for inner circuit use new prover and verifier method for outer circuit use the normal prover and verifier - // TODO: either need a context flag for recursive verify or a new_recursive_verify_proof method that uses + // For inner circuit use recursive prover and verifier, then for outer circuit use the normal prover and + // verifier Either need a context flag for recursive verify or a new_recursive_verify_proof method that uses // regular UltraVerifier if (is_recursive) { auto verifier = composer.create_verifier(); diff --git a/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp index 1ef6fdcba0..b1c5850b04 100644 --- a/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp +++ b/cpp/src/barretenberg/honk/composer/composer_helper/ultra_honk_composer_helper.cpp @@ -270,15 +270,18 @@ std::shared_ptr UltraHonkComposerHelper: // // all four columns. We don't want to have equal commitments, because biggroup operations assume no points are // // equal, so if we tried to verify an ultra proof in a circuit, the biggroup operations would fail. To combat // // this, we just choose distinct values: - size_t num_selectors = circuit_constructor.num_selectors; - ASSERT(offset == subgroup_size - 1); - auto unique_last_value = num_selectors + 1; // Note: in compute_proving_key_base, moments earlier, each selector - // vector was given a unique last value from 1..num_selectors. So we - // avoid those values and continue the count, to ensure uniqueness. - poly_q_table_column_1[subgroup_size - 1] = unique_last_value; - poly_q_table_column_2[subgroup_size - 1] = ++unique_last_value; - poly_q_table_column_3[subgroup_size - 1] = ++unique_last_value; - poly_q_table_column_4[subgroup_size - 1] = ++unique_last_value; + + // TODO(#217)(luke): Similar to the selectors, enforcing non-zero values by inserting an arbitrary final element + // in the table polys will result in lookup relations not being satisfied. Address this with issue #217. + // size_t num_selectors = circuit_constructor.num_selectors; + // ASSERT(offset == subgroup_size - 1); + // auto unique_last_value = num_selectors + 1; // Note: in compute_proving_key_base, moments earlier, each selector + // // vector was given a unique last value from 1..num_selectors. So we + // // avoid those values and continue the count, to ensure uniqueness. + // poly_q_table_column_1[subgroup_size - 1] = unique_last_value; + // poly_q_table_column_2[subgroup_size - 1] = ++unique_last_value; + // poly_q_table_column_3[subgroup_size - 1] = ++unique_last_value; + // poly_q_table_column_4[subgroup_size - 1] = ++unique_last_value; circuit_proving_key->polynomial_store.put("table_value_1_lagrange", std::move(poly_q_table_column_1)); circuit_proving_key->polynomial_store.put("table_value_2_lagrange", std::move(poly_q_table_column_2)); diff --git a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp index 8ab126ba01..d481d2083d 100644 --- a/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp +++ b/cpp/src/barretenberg/honk/composer/standard_honk_composer.test.cpp @@ -7,7 +7,7 @@ #include "barretenberg/honk/sumcheck/sumcheck_round.hpp" #include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" #include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" -#include "barretenberg/honk/utils/public_inputs.hpp" +#include "barretenberg/honk/utils/grand_product_delta.hpp" #include diff --git a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp index 5fa40b4626..f2eb030039 100644 --- a/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp +++ b/cpp/src/barretenberg/honk/composer/ultra_honk_composer.hpp @@ -357,11 +357,10 @@ class UltraHonkComposer { }; // std::array decompose_non_native_field_double_width_limb( // const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); - std::array evaluate_non_native_field_multiplication( + std::array queue_non_native_field_multiplication( const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) { - return circuit_constructor.evaluate_non_native_field_multiplication(input, - range_constrain_quotient_and_remainder); + return circuit_constructor.queue_non_native_field_multiplication(input, range_constrain_quotient_and_remainder); }; // std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& // input); typedef std::pair scaled_witness; typedef std::tuple add_variables(auto& composer, std::vector variables) * @param honk_prover * @param plonk_prover */ -// NOTE: Currently checking exact consistency for witness polynomials (wires, sorted lists) and table polys. -// The permutation polys are computed differently between plonk and honk so we do not expect consistency. -// Equality is checked on all selectors but we ignore the final entry since we do not enforce non-zero selectors in -// Honk. void verify_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plonk_prover) { auto& honk_store = honk_prover.key->polynomial_store; auto& plonk_store = plonk_prover.key->polynomial_store; - // Check that all selectors agree (aside from the final element which will differ due to not enforcing non-zero - // selectors in Honk). + // Check that all selectors and table polynomials agree (aside from the final element which will differ + // due to not enforcing non-zero polynomials in Honk). for (auto& entry : honk_store) { std::string key = entry.first; bool is_selector = (key.find("q_") != std::string::npos) || (key.find("table_type") != std::string::npos); - if (plonk_store.contains(key) && is_selector) { + bool is_table = (key.find("table_value_") != std::string::npos); + if (plonk_store.contains(key) && (is_selector || is_table)) { // check equality for all but final entry for (size_t i = 0; i < honk_store.get(key).size() - 1; ++i) { ASSERT_EQ(honk_store.get(key)[i], plonk_store.get(key)[i]); @@ -61,12 +58,11 @@ void verify_consistency(honk::UltraProver& honk_prover, plonk::UltraProver& plon } } - // Check that sorted witness-table and table polys agree + // Check that sorted witness-table polynomials agree for (auto& entry : honk_store) { std::string key = entry.first; bool is_sorted_table = (key.find("s_") != std::string::npos); - bool is_table = (key.find("table_value_") != std::string::npos); - if (plonk_store.contains(key) && (is_sorted_table || is_table)) { + if (plonk_store.contains(key) && is_sorted_table) { ASSERT_EQ(honk_store.get(key), plonk_store.get(key)); } } @@ -755,7 +751,7 @@ TEST(UltraHonkComposer, non_native_field_multiplication) proof_system::non_native_field_witnesses inputs{ a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), }; - const auto [lo_1_idx, hi_1_idx] = honk_composer.evaluate_non_native_field_multiplication(inputs); + const auto [lo_1_idx, hi_1_idx] = honk_composer.queue_non_native_field_multiplication(inputs); honk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); } { @@ -802,7 +798,7 @@ TEST(UltraHonkComposer, non_native_field_multiplication) proof_system::plonk::UltraComposer::non_native_field_witnesses inputs{ a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), }; - const auto [lo_1_idx, hi_1_idx] = plonk_composer.evaluate_non_native_field_multiplication(inputs); + const auto [lo_1_idx, hi_1_idx] = plonk_composer.queue_non_native_field_multiplication(inputs); plonk_composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); } diff --git a/cpp/src/barretenberg/honk/flavor/flavor.hpp b/cpp/src/barretenberg/honk/flavor/flavor.hpp index c420ed64aa..05e7f2fb50 100644 --- a/cpp/src/barretenberg/honk/flavor/flavor.hpp +++ b/cpp/src/barretenberg/honk/flavor/flavor.hpp @@ -219,6 +219,10 @@ struct UltraArithmetization { ID_2, ID_3, ID_4, + TABLE_1, + TABLE_2, + TABLE_3, + TABLE_4, LAGRANGE_FIRST, LAGRANGE_LAST, // = LAGRANGE_N-1 whithout ZK, but can be less /* --- WITNESS POLYNOMIALS --- */ @@ -230,11 +234,19 @@ struct UltraArithmetization { S_2, S_3, S_4, + S_ACCUM, Z_PERM, Z_LOOKUP, /* --- SHIFTED POLYNOMIALS --- */ W_1_SHIFT, + W_2_SHIFT, + W_3_SHIFT, W_4_SHIFT, + TABLE_1_SHIFT, + TABLE_2_SHIFT, + TABLE_3_SHIFT, + TABLE_4_SHIFT, + S_ACCUM_SHIFT, Z_PERM_SHIFT, Z_LOOKUP_SHIFT, /* --- --- */ diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp index 61f799e030..cdf89004d4 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp @@ -2,6 +2,7 @@ #include "relation.hpp" #include "barretenberg/honk/flavor/flavor.hpp" #include "../polynomials/univariate.hpp" +// TODO(luke): change name of this file to permutation_grand_product_relation(s).hpp and move 'init' relation into it. namespace proof_system::honk::sumcheck { diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp new file mode 100644 index 0000000000..a7495eee31 --- /dev/null +++ b/cpp/src/barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp @@ -0,0 +1,205 @@ +#pragma once +#include "relation.hpp" +#include "barretenberg/honk/flavor/flavor.hpp" +#include "../polynomials/univariate.hpp" + +namespace proof_system::honk::sumcheck { + +template class LookupGrandProductComputationRelation { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 6; // deg(z_lookup * column_selector * wire * q_lookup * table) = 5 + using MULTIVARIATE = proof_system::honk::UltraArithmetization::POLYNOMIAL; + + /** + * @brief Compute contribution of the lookup grand prod relation for a given edge (internal function) + * + * @details This the relation confirms faithful calculation of the lookup grand + * product polynomial Z_lookup. The contribution is + * z_lookup * (1 + β) * [q_lookup * f + γ] * (t_accum_k + βt_accum_{k+1} + γ(1 + β)) - + * z_lookup_shift * (s_accum_k + βs_accum_{k+1} + γ(1 + β)) + * where + * f = (w_1 + q_2*w_1_shift) + η(w_2 + q_m*w_2_shift) + η²(w_3 + q_c*w_3_shift) + η³q_index, + * t_accum = table_1 + ηtable_2 + η²table_3 + η³table_4, and + * s_accum = s_1 + ηs_2 + η²s_3 + η³s_4. + * Note: Selectors q_2, q_m and q_c are repurposed as 'column step size' for lookup gates. + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + inline void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters& relation_parameters, + const FF& scaling_factor) const + { + const auto& eta = relation_parameters.eta; + const auto& beta = relation_parameters.beta; + const auto& gamma = relation_parameters.gamma; + const auto& grand_product_delta = relation_parameters.lookup_grand_product_delta; + + const auto one_plus_beta = FF::one() + beta; + const auto gamma_by_one_plus_beta = gamma * one_plus_beta; + const auto eta_sqr = eta * eta; + const auto eta_cube = eta_sqr * eta; + + auto w_1 = UnivariateView(extended_edges[MULTIVARIATE::W_L]); + auto w_2 = UnivariateView(extended_edges[MULTIVARIATE::W_R]); + auto w_3 = UnivariateView(extended_edges[MULTIVARIATE::W_O]); + + auto w_1_shift = UnivariateView(extended_edges[MULTIVARIATE::W_1_SHIFT]); + auto w_2_shift = UnivariateView(extended_edges[MULTIVARIATE::W_2_SHIFT]); + auto w_3_shift = UnivariateView(extended_edges[MULTIVARIATE::W_3_SHIFT]); + + auto table_1 = UnivariateView(extended_edges[MULTIVARIATE::TABLE_1]); + auto table_2 = UnivariateView(extended_edges[MULTIVARIATE::TABLE_2]); + auto table_3 = UnivariateView(extended_edges[MULTIVARIATE::TABLE_3]); + auto table_4 = UnivariateView(extended_edges[MULTIVARIATE::TABLE_4]); + + auto table_1_shift = UnivariateView(extended_edges[MULTIVARIATE::TABLE_1_SHIFT]); + auto table_2_shift = UnivariateView(extended_edges[MULTIVARIATE::TABLE_2_SHIFT]); + auto table_3_shift = UnivariateView(extended_edges[MULTIVARIATE::TABLE_3_SHIFT]); + auto table_4_shift = UnivariateView(extended_edges[MULTIVARIATE::TABLE_4_SHIFT]); + + auto s_accum = UnivariateView(extended_edges[MULTIVARIATE::S_ACCUM]); + auto s_accum_shift = UnivariateView(extended_edges[MULTIVARIATE::S_ACCUM_SHIFT]); + + auto z_lookup = UnivariateView(extended_edges[MULTIVARIATE::Z_LOOKUP]); + auto z_lookup_shift = UnivariateView(extended_edges[MULTIVARIATE::Z_LOOKUP_SHIFT]); + + auto table_index = UnivariateView(extended_edges[MULTIVARIATE::Q_O]); + auto column_1_step_size = UnivariateView(extended_edges[MULTIVARIATE::Q_R]); + auto column_2_step_size = UnivariateView(extended_edges[MULTIVARIATE::Q_M]); + auto column_3_step_size = UnivariateView(extended_edges[MULTIVARIATE::Q_C]); + auto q_lookup = UnivariateView(extended_edges[MULTIVARIATE::QLOOKUPTYPE]); + + auto lagrange_first = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_FIRST]); + auto lagrange_last = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_LAST]); + + // (w_1 + q_2*w_1_shift) + η(w_2 + q_m*w_2_shift) + η²(w_3 + q_c*w_3_shift) + η³q_index. + auto wire_accum = (w_1 + column_1_step_size * w_1_shift) + (w_2 + column_2_step_size * w_2_shift) * eta + + (w_3 + column_3_step_size * w_3_shift) * eta_sqr + table_index * eta_cube; + + // t_1 + ηt_2 + η²t_3 + η³t_4 + auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; + // t_1_shift + ηt_2_shift + η²t_3_shift + η³t_4_shift + auto table_accum_shift = + table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; + + // Contribution (1) + auto tmp = (q_lookup * wire_accum + gamma); + tmp *= (table_accum + table_accum_shift * beta + gamma_by_one_plus_beta); + tmp *= one_plus_beta; + tmp *= (z_lookup + lagrange_first); + tmp -= (z_lookup_shift + lagrange_last * grand_product_delta) * + (s_accum + s_accum_shift * beta + gamma_by_one_plus_beta); + evals += tmp * scaling_factor; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + auto& purported_evaluations, + const RelationParameters& relation_parameters) const + { + const auto& eta = relation_parameters.eta; + const auto& beta = relation_parameters.beta; + const auto& gamma = relation_parameters.gamma; + const auto& grand_product_delta = relation_parameters.lookup_grand_product_delta; + + const auto one_plus_beta = FF::one() + beta; + const auto gamma_by_one_plus_beta = gamma * one_plus_beta; + const auto eta_sqr = eta * eta; + const auto eta_cube = eta_sqr * eta; + + auto w_1 = purported_evaluations[MULTIVARIATE::W_L]; + auto w_2 = purported_evaluations[MULTIVARIATE::W_R]; + auto w_3 = purported_evaluations[MULTIVARIATE::W_O]; + + auto w_1_shift = purported_evaluations[MULTIVARIATE::W_1_SHIFT]; + auto w_2_shift = purported_evaluations[MULTIVARIATE::W_2_SHIFT]; + auto w_3_shift = purported_evaluations[MULTIVARIATE::W_3_SHIFT]; + + auto table_1 = purported_evaluations[MULTIVARIATE::TABLE_1]; + auto table_2 = purported_evaluations[MULTIVARIATE::TABLE_2]; + auto table_3 = purported_evaluations[MULTIVARIATE::TABLE_3]; + auto table_4 = purported_evaluations[MULTIVARIATE::TABLE_4]; + + auto table_1_shift = purported_evaluations[MULTIVARIATE::TABLE_1_SHIFT]; + auto table_2_shift = purported_evaluations[MULTIVARIATE::TABLE_2_SHIFT]; + auto table_3_shift = purported_evaluations[MULTIVARIATE::TABLE_3_SHIFT]; + auto table_4_shift = purported_evaluations[MULTIVARIATE::TABLE_4_SHIFT]; + + auto s_accum = purported_evaluations[MULTIVARIATE::S_ACCUM]; + auto s_accum_shift = purported_evaluations[MULTIVARIATE::S_ACCUM_SHIFT]; + auto z_lookup = purported_evaluations[MULTIVARIATE::Z_LOOKUP]; + auto z_lookup_shift = purported_evaluations[MULTIVARIATE::Z_LOOKUP_SHIFT]; + + auto table_index = purported_evaluations[MULTIVARIATE::Q_O]; + auto column_1_step_size = purported_evaluations[MULTIVARIATE::Q_R]; + auto column_2_step_size = purported_evaluations[MULTIVARIATE::Q_M]; + auto column_3_step_size = purported_evaluations[MULTIVARIATE::Q_C]; + auto q_lookup = purported_evaluations[MULTIVARIATE::QLOOKUPTYPE]; + + auto lagrange_first = purported_evaluations[MULTIVARIATE::LAGRANGE_FIRST]; + auto lagrange_last = purported_evaluations[MULTIVARIATE::LAGRANGE_LAST]; + + // (w_1 + q_2*w_1_shift) + η(w_2 + q_m*w_2_shift) + η²(w_3 + q_c*w_3_shift) + η³q_index. + auto wire_accum = (w_1 + column_1_step_size * w_1_shift) + (w_2 + column_2_step_size * w_2_shift) * eta + + (w_3 + column_3_step_size * w_3_shift) * eta_sqr + table_index * eta_cube; + + // t_1 + ηt_2 + η²t_3 + η³t_4 + auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; + // t_1_shift + ηt_2_shift + η²t_3_shift + η³t_4_shift + auto table_accum_shift = + table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; + + // Contribution (1) + auto tmp = (q_lookup * wire_accum + gamma); + tmp *= (table_accum + beta * table_accum_shift + gamma_by_one_plus_beta); + tmp *= one_plus_beta; + tmp *= (z_lookup + lagrange_first); + tmp -= (z_lookup_shift + lagrange_last * grand_product_delta) * + (s_accum + beta * s_accum_shift + gamma_by_one_plus_beta); + full_honk_relation_value += tmp; + }; +}; + +template class LookupGrandProductInitializationRelation { + public: + // 1 + polynomial degree of this relation + static constexpr size_t RELATION_LENGTH = 3; // deg(lagrange_last * z_lookup_shift) = 2 + using MULTIVARIATE = proof_system::honk::UltraArithmetization::POLYNOMIAL; + + /** + * @brief Compute contribution of the lookup grand prod relation for a given edge (internal function) + * + * @details This the relation confirms correct initialization of the lookup grand + * product polynomial Z_lookup with Z_lookup[circuit_size] = 0. + * + * @param evals transformed to `evals + C(extended_edges(X)...)*scaling_factor` + * @param extended_edges an std::array containing the fully extended Univariate edges. + * @param parameters contains beta, gamma, and public_input_delta, .... + * @param scaling_factor optional term to scale the evaluation before adding to evals. + */ + inline void add_edge_contribution(Univariate& evals, + const auto& extended_edges, + const RelationParameters& /*unused*/, + const FF& scaling_factor) const + { + auto z_lookup_shift = UnivariateView(extended_edges[MULTIVARIATE::Z_LOOKUP_SHIFT]); + auto lagrange_last = UnivariateView(extended_edges[MULTIVARIATE::LAGRANGE_LAST]); + + evals += (lagrange_last * z_lookup_shift) * scaling_factor; + }; + + void add_full_relation_value_contribution(FF& full_honk_relation_value, + auto& purported_evaluations, + const RelationParameters& /*unused*/) const + { + auto z_lookup_shift = purported_evaluations[MULTIVARIATE::Z_LOOKUP_SHIFT]; + auto lagrange_last = purported_evaluations[MULTIVARIATE::LAGRANGE_LAST]; + + full_honk_relation_value += lagrange_last * z_lookup_shift; + }; +}; +} // namespace proof_system::honk::sumcheck \ No newline at end of file diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp index e03f09f990..863688560c 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation.hpp @@ -1,10 +1,18 @@ #pragma once +#include namespace proof_system::honk::sumcheck { +/** + * @brief Container for parameters used by the grand product (permutation, lookup) Honk relations + * + * @tparam FF + */ template struct RelationParameters { - FF beta; - FF gamma; - FF public_input_delta; + FF eta = FF::zero(); // Lookup + FF beta = FF::zero(); // Permutation + Lookup + FF gamma = FF::zero(); // Permutation + Lookup + FF public_input_delta = FF::zero(); // Permutation + FF lookup_grand_product_delta = FF::zero(); // Lookup }; } // namespace proof_system::honk::sumcheck diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp index 5557d7d47c..fb7a5875d7 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_consistency.test.cpp @@ -1,3 +1,4 @@ +#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" #include "relation.hpp" @@ -95,9 +96,11 @@ template class RelationConsistency : public testing::Test { */ RelationParameters compute_mock_relation_parameters() { - return { .beta = FF::random_element(), + return { .eta = FF::random_element(), + .beta = FF::random_element(), .gamma = FF::random_element(), - .public_input_delta = FF::random_element() }; + .public_input_delta = FF::random_element(), + .lookup_grand_product_delta = FF::random_element() }; } /** @@ -485,4 +488,112 @@ TYPED_TEST(RelationConsistency, UltraGrandProductComputationRelation) TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); }; +TYPED_TEST(RelationConsistency, LookupGrandProductComputationRelation) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = LookupGrandProductComputationRelation(); + + const auto eta = relation_parameters.eta; + const auto beta = relation_parameters.beta; + const auto gamma = relation_parameters.gamma; + auto grand_product_delta = relation_parameters.lookup_grand_product_delta; + + // Extract the extended edges for manual computation of relation contribution + auto one_plus_beta = FF::one() + beta; + auto gamma_by_one_plus_beta = gamma * one_plus_beta; + auto eta_sqr = eta * eta; + auto eta_cube = eta_sqr * eta; + + const auto& w_1 = extended_edges[MULTIVARIATE::W_L]; + const auto& w_2 = extended_edges[MULTIVARIATE::W_R]; + const auto& w_3 = extended_edges[MULTIVARIATE::W_O]; + + const auto& w_1_shift = extended_edges[MULTIVARIATE::W_1_SHIFT]; + const auto& w_2_shift = extended_edges[MULTIVARIATE::W_2_SHIFT]; + const auto& w_3_shift = extended_edges[MULTIVARIATE::W_3_SHIFT]; + + const auto& table_1 = extended_edges[MULTIVARIATE::TABLE_1]; + const auto& table_2 = extended_edges[MULTIVARIATE::TABLE_2]; + const auto& table_3 = extended_edges[MULTIVARIATE::TABLE_3]; + const auto& table_4 = extended_edges[MULTIVARIATE::TABLE_4]; + + const auto& table_1_shift = extended_edges[MULTIVARIATE::TABLE_1_SHIFT]; + const auto& table_2_shift = extended_edges[MULTIVARIATE::TABLE_2_SHIFT]; + const auto& table_3_shift = extended_edges[MULTIVARIATE::TABLE_3_SHIFT]; + const auto& table_4_shift = extended_edges[MULTIVARIATE::TABLE_4_SHIFT]; + + const auto& s_accum = extended_edges[MULTIVARIATE::S_ACCUM]; + const auto& s_accum_shift = extended_edges[MULTIVARIATE::S_ACCUM_SHIFT]; + const auto& z_lookup = extended_edges[MULTIVARIATE::Z_LOOKUP]; + const auto& z_lookup_shift = extended_edges[MULTIVARIATE::Z_LOOKUP_SHIFT]; + + const auto& table_index = extended_edges[MULTIVARIATE::Q_O]; + const auto& column_1_step_size = extended_edges[MULTIVARIATE::Q_R]; + const auto& column_2_step_size = extended_edges[MULTIVARIATE::Q_M]; + const auto& column_3_step_size = extended_edges[MULTIVARIATE::Q_C]; + const auto& q_lookup = extended_edges[MULTIVARIATE::QLOOKUPTYPE]; + + const auto& lagrange_first = extended_edges[MULTIVARIATE::LAGRANGE_FIRST]; + const auto& lagrange_last = extended_edges[MULTIVARIATE::LAGRANGE_LAST]; + + auto wire_accum = (w_1 + column_1_step_size * w_1_shift) + (w_2 + column_2_step_size * w_2_shift) * eta + + (w_3 + column_3_step_size * w_3_shift) * eta_sqr + table_index * eta_cube; + + auto table_accum = table_1 + table_2 * eta + table_3 * eta_sqr + table_4 * eta_cube; + auto table_accum_shift = table_1_shift + table_2_shift * eta + table_3_shift * eta_sqr + table_4_shift * eta_cube; + + // Compute the expected result using a simple to read version of the relation expression + auto expected_evals = (z_lookup + lagrange_first) * (q_lookup * wire_accum + gamma) * + (table_accum + table_accum_shift * beta + gamma_by_one_plus_beta) * one_plus_beta; + expected_evals -= (z_lookup_shift + lagrange_last * grand_product_delta) * + (s_accum + s_accum_shift * beta + gamma_by_one_plus_beta); + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + +TYPED_TEST(RelationConsistency, LookupGrandProductInitializationRelation) +{ + SUMCHECK_RELATION_TYPE_ALIASES + using MULTIVARIATE = honk::UltraArithmetization::POLYNOMIAL; + + static constexpr size_t FULL_RELATION_LENGTH = 6; + static const size_t NUM_POLYNOMIALS = proof_system::honk::UltraArithmetization::COUNT; + + const auto relation_parameters = TestFixture::compute_mock_relation_parameters(); + std::array, NUM_POLYNOMIALS> extended_edges; + std::array, NUM_POLYNOMIALS> input_polynomials; + + // input_univariates are random polynomials of degree one + for (size_t i = 0; i < NUM_POLYNOMIALS; ++i) { + input_polynomials[i] = Univariate({ FF::random_element(), FF::random_element() }); + } + extended_edges = TestFixture::template compute_mock_extended_edges(input_polynomials); + + auto relation = LookupGrandProductInitializationRelation(); + + // Extract the extended edges for manual computation of relation contribution + const auto& z_lookup_shift = extended_edges[MULTIVARIATE::Z_LOOKUP_SHIFT]; + const auto& lagrange_last = extended_edges[MULTIVARIATE::LAGRANGE_LAST]; + + // Compute the expected result using a simple to read version of the relation expression + auto expected_evals = z_lookup_shift * lagrange_last; + + TestFixture::template validate_evaluations(expected_evals, relation, extended_edges, relation_parameters); +}; + } // namespace proof_system::honk_relation_tests diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp index f5556a31da..39b9971d4c 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/relation_correctness.test.cpp @@ -1,18 +1,24 @@ #include "barretenberg/honk/composer/ultra_honk_composer.hpp" #include "barretenberg/honk/composer/standard_honk_composer.hpp" +#include "barretenberg/honk/proof_system/prover_library.hpp" #include "barretenberg/honk/sumcheck/relations/relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation.hpp" #include "barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp" #include "barretenberg/numeric/uint256/uint256.hpp" #include "barretenberg/honk/flavor/flavor.hpp" +#include #include #include "barretenberg/honk/proof_system/prover.hpp" #include "barretenberg/honk/sumcheck/sumcheck_round.hpp" #include "barretenberg/honk/sumcheck/relations/grand_product_computation_relation.hpp" #include "barretenberg/honk/sumcheck/relations/grand_product_initialization_relation.hpp" -#include "barretenberg/honk/utils/public_inputs.hpp" +#include "barretenberg/honk/sumcheck/relations/lookup_grand_product_relation.hpp" +#include "barretenberg/honk/utils/grand_product_delta.hpp" +#include "barretenberg/polynomials/polynomial.hpp" #include +#include +#include using namespace proof_system::honk; @@ -66,7 +72,7 @@ TEST(RelationCorrectness, StandardRelationCorrectness) constexpr size_t num_polynomials = proof_system::honk::StandardArithmetization::NUM_POLYNOMIALS; // Compute grand product polynomial - polynomial z_perm_poly = + polynomial z_permutation = prover_library::compute_permutation_grand_product(prover.key, prover.wire_polynomials, beta, gamma); // Create an array of spans to the underlying polynomials to more easily @@ -79,8 +85,8 @@ TEST(RelationCorrectness, StandardRelationCorrectness) evaluations_array[POLYNOMIAL::W_L] = prover.wire_polynomials[0]; evaluations_array[POLYNOMIAL::W_R] = prover.wire_polynomials[1]; evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2]; - evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly; - evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted(); + evaluations_array[POLYNOMIAL::Z_PERM] = z_permutation; + evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_permutation.shifted(); evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange"); evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange"); evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange"); @@ -133,13 +139,15 @@ TEST(RelationCorrectness, StandardRelationCorrectness) * indices * */ -// TODO(luke): Increase variety of gates in the test circuit to fully stress the relations, e.g. create_big_add_gate. -// NOTE(luke): More relations will be added as they are implemented for Ultra Honk +// TODO(luke): Ensure all relations are added as they are implemented for Ultra Honk TEST(RelationCorrectness, UltraRelationCorrectness) { // Create a composer and a dummy circuit with a few gates auto composer = UltraHonkComposer(); + static const size_t num_wires = 4; + + barretenberg::fr pedersen_input_value = fr::random_element(); fr a = fr::one(); // Using the public variable to check that public_input_delta is computed and added to the relation correctly // TODO(luke): add method "add_public_variable" to UH composer @@ -151,14 +159,38 @@ TEST(RelationCorrectness, UltraRelationCorrectness) uint32_t b_idx = composer.add_variable(b); uint32_t c_idx = composer.add_variable(c); uint32_t d_idx = composer.add_variable(d); - for (size_t i = 0; i < 1; i++) { - composer.create_add_gate({ a_idx, b_idx, c_idx, fr::one(), fr::one(), fr::neg_one(), fr::zero() }); - composer.create_add_gate({ d_idx, c_idx, a_idx, fr::one(), fr::neg_one(), fr::neg_one(), fr::zero() }); + for (size_t i = 0; i < 16; i++) { + composer.create_add_gate({ a_idx, b_idx, c_idx, 1, 1, -1, 0 }); + composer.create_add_gate({ d_idx, c_idx, a_idx, 1, -1, -1, 0 }); } + + // Add a big add gate with use of next row to test q_arith = 2 + fr e = a + b + c + d; + uint32_t e_idx = composer.add_variable(e); + + uint32_t zero_idx = composer.get_zero_idx(); + composer.create_big_add_gate({ a_idx, b_idx, c_idx, d_idx, -1, -1, -1, -1, 0 }, true); // use next row + composer.create_big_add_gate({ zero_idx, zero_idx, zero_idx, e_idx, 0, 0, 0, 0, 0 }, false); + + // Add some lookup gates (related to pedersen hashing) + const fr input_hi = uint256_t(pedersen_input_value).slice(126, 256); + const fr input_lo = uint256_t(pedersen_input_value).slice(0, 126); + const auto input_hi_index = composer.add_variable(input_hi); + const auto input_lo_index = composer.add_variable(input_lo); + + const auto sequence_data_hi = plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_HI, input_hi); + const auto sequence_data_lo = plookup::get_lookup_accumulators(plookup::MultiTableId::PEDERSEN_LEFT_LO, input_lo); + + composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_HI, sequence_data_hi, input_hi_index); + composer.create_gates_from_plookup_accumulators( + plookup::MultiTableId::PEDERSEN_LEFT_LO, sequence_data_lo, input_lo_index); + // Create a prover (it will compute proving key and witness) auto prover = composer.create_prover(); - // Generate beta and gamma + // Generate eta, beta and gamma + fr eta = fr::random_element(); fr beta = fr::random_element(); fr gamma = fr::random_element(); @@ -166,20 +198,37 @@ TEST(RelationCorrectness, UltraRelationCorrectness) const auto public_inputs = composer.circuit_constructor.get_public_inputs(); auto public_input_delta = honk::compute_public_input_delta(public_inputs, beta, gamma, prover.key->circuit_size); - - info("public_input_delta = ", public_input_delta); + auto lookup_grand_product_delta = + honk::compute_lookup_grand_product_delta(beta, gamma, prover.key->circuit_size); sumcheck::RelationParameters params{ + .eta = eta, .beta = beta, .gamma = gamma, .public_input_delta = public_input_delta, + .lookup_grand_product_delta = lookup_grand_product_delta, }; constexpr size_t num_polynomials = proof_system::honk::UltraArithmetization::COUNT; - // Compute grand product polynomial - auto z_perm_poly = + + // Compute permutation grand product polynomial + auto z_permutation = prover_library::compute_permutation_grand_product(prover.key, prover.wire_polynomials, beta, gamma); + // Construct local sorted_list_polynomials to pass to compute_sorted_list_accumulator() + std::vector sorted_list_polynomials; + for (size_t i = 0; i < 4; ++i) { + std::string label = "s_" + std::to_string(i + 1) + "_lagrange"; + sorted_list_polynomials.emplace_back(prover.key->polynomial_store.get(label)); + } + // Compute sorted witness-table accumulator + auto sorted_list_accumulator = + prover_library::compute_sorted_list_accumulator(prover.key, sorted_list_polynomials, eta); + + // Compute lookup grand product polynomial + auto z_lookup = prover_library::compute_lookup_grand_product( + prover.key, prover.wire_polynomials, sorted_list_accumulator, eta, beta, gamma); + // Create an array of spans to the underlying polynomials to more easily // get the transposition. // Ex: polynomial_spans[3][i] returns the i-th coefficient of the third polynomial @@ -192,34 +241,56 @@ TEST(RelationCorrectness, UltraRelationCorrectness) evaluations_array[POLYNOMIAL::W_O] = prover.wire_polynomials[2]; evaluations_array[POLYNOMIAL::W_4] = prover.wire_polynomials[3]; evaluations_array[POLYNOMIAL::W_1_SHIFT] = prover.wire_polynomials[0].shifted(); + evaluations_array[POLYNOMIAL::W_2_SHIFT] = prover.wire_polynomials[1].shifted(); + evaluations_array[POLYNOMIAL::W_3_SHIFT] = prover.wire_polynomials[2].shifted(); evaluations_array[POLYNOMIAL::W_4_SHIFT] = prover.wire_polynomials[3].shifted(); + evaluations_array[POLYNOMIAL::S_1] = prover.key->polynomial_store.get("s_1_lagrange"); evaluations_array[POLYNOMIAL::S_2] = prover.key->polynomial_store.get("s_2_lagrange"); evaluations_array[POLYNOMIAL::S_3] = prover.key->polynomial_store.get("s_3_lagrange"); evaluations_array[POLYNOMIAL::S_4] = prover.key->polynomial_store.get("s_4_lagrange"); - evaluations_array[POLYNOMIAL::Z_PERM] = z_perm_poly; - evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_perm_poly.shifted(); - evaluations_array[POLYNOMIAL::Z_LOOKUP] = z_perm_poly; - evaluations_array[POLYNOMIAL::Z_LOOKUP_SHIFT] = z_perm_poly.shifted(); + + evaluations_array[POLYNOMIAL::S_ACCUM] = sorted_list_accumulator; + evaluations_array[POLYNOMIAL::S_ACCUM_SHIFT] = sorted_list_accumulator.shifted(); + + evaluations_array[POLYNOMIAL::Z_PERM] = z_permutation; + evaluations_array[POLYNOMIAL::Z_PERM_SHIFT] = z_permutation.shifted(); + + evaluations_array[POLYNOMIAL::Z_LOOKUP] = z_lookup; + evaluations_array[POLYNOMIAL::Z_LOOKUP_SHIFT] = z_lookup.shifted(); + evaluations_array[POLYNOMIAL::Q_M] = prover.key->polynomial_store.get("q_m_lagrange"); evaluations_array[POLYNOMIAL::Q_L] = prover.key->polynomial_store.get("q_1_lagrange"); evaluations_array[POLYNOMIAL::Q_R] = prover.key->polynomial_store.get("q_2_lagrange"); evaluations_array[POLYNOMIAL::Q_O] = prover.key->polynomial_store.get("q_3_lagrange"); - evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange"); evaluations_array[POLYNOMIAL::Q_4] = prover.key->polynomial_store.get("q_4_lagrange"); + evaluations_array[POLYNOMIAL::Q_C] = prover.key->polynomial_store.get("q_c_lagrange"); evaluations_array[POLYNOMIAL::QARITH] = prover.key->polynomial_store.get("q_arith_lagrange"); evaluations_array[POLYNOMIAL::QSORT] = prover.key->polynomial_store.get("q_sort_lagrange"); evaluations_array[POLYNOMIAL::QELLIPTIC] = prover.key->polynomial_store.get("q_elliptic_lagrange"); evaluations_array[POLYNOMIAL::QAUX] = prover.key->polynomial_store.get("q_aux_lagrange"); evaluations_array[POLYNOMIAL::QLOOKUPTYPE] = prover.key->polynomial_store.get("table_type_lagrange"); + evaluations_array[POLYNOMIAL::SIGMA_1] = prover.key->polynomial_store.get("sigma_1_lagrange"); evaluations_array[POLYNOMIAL::SIGMA_2] = prover.key->polynomial_store.get("sigma_2_lagrange"); evaluations_array[POLYNOMIAL::SIGMA_3] = prover.key->polynomial_store.get("sigma_3_lagrange"); evaluations_array[POLYNOMIAL::SIGMA_4] = prover.key->polynomial_store.get("sigma_4_lagrange"); + evaluations_array[POLYNOMIAL::ID_1] = prover.key->polynomial_store.get("id_1_lagrange"); evaluations_array[POLYNOMIAL::ID_2] = prover.key->polynomial_store.get("id_2_lagrange"); evaluations_array[POLYNOMIAL::ID_3] = prover.key->polynomial_store.get("id_3_lagrange"); evaluations_array[POLYNOMIAL::ID_4] = prover.key->polynomial_store.get("id_4_lagrange"); + + evaluations_array[POLYNOMIAL::TABLE_1] = prover.key->polynomial_store.get("table_value_1_lagrange"); + evaluations_array[POLYNOMIAL::TABLE_2] = prover.key->polynomial_store.get("table_value_2_lagrange"); + evaluations_array[POLYNOMIAL::TABLE_3] = prover.key->polynomial_store.get("table_value_3_lagrange"); + evaluations_array[POLYNOMIAL::TABLE_4] = prover.key->polynomial_store.get("table_value_4_lagrange"); + + evaluations_array[POLYNOMIAL::TABLE_1_SHIFT] = prover.key->polynomial_store.get("table_value_1_lagrange").shifted(); + evaluations_array[POLYNOMIAL::TABLE_2_SHIFT] = prover.key->polynomial_store.get("table_value_2_lagrange").shifted(); + evaluations_array[POLYNOMIAL::TABLE_3_SHIFT] = prover.key->polynomial_store.get("table_value_3_lagrange").shifted(); + evaluations_array[POLYNOMIAL::TABLE_4_SHIFT] = prover.key->polynomial_store.get("table_value_4_lagrange").shifted(); + evaluations_array[POLYNOMIAL::LAGRANGE_FIRST] = prover.key->polynomial_store.get("L_first_lagrange"); evaluations_array[POLYNOMIAL::LAGRANGE_LAST] = prover.key->polynomial_store.get("L_last_lagrange"); @@ -227,7 +298,9 @@ TEST(RelationCorrectness, UltraRelationCorrectness) auto relations = std::tuple(honk::sumcheck::UltraArithmeticRelation(), honk::sumcheck::UltraArithmeticRelationSecondary(), honk::sumcheck::UltraGrandProductInitializationRelation(), - honk::sumcheck::UltraGrandProductComputationRelation()); + honk::sumcheck::UltraGrandProductComputationRelation(), + honk::sumcheck::LookupGrandProductComputationRelation(), + honk::sumcheck::LookupGrandProductInitializationRelation()); fr result = 0; for (size_t i = 0; i < prover.key->circuit_size; i++) { @@ -252,6 +325,12 @@ TEST(RelationCorrectness, UltraRelationCorrectness) std::get<3>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); ASSERT_EQ(result, 0); + + std::get<4>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); + + std::get<5>(relations).add_full_relation_value_contribution(result, evaluations_at_index_i, params); + ASSERT_EQ(result, 0); } } diff --git a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp index 78c01db356..0189d779f3 100644 --- a/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/relations/ultra_arithmetic_relation_secondary.hpp @@ -6,6 +6,7 @@ #include "../polynomials/univariate.hpp" #include "relation.hpp" +// TODO(luke): Move this into ultra_arithmetic_relation.hpp. namespace proof_system::honk::sumcheck { template class UltraArithmeticRelationSecondary { diff --git a/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp b/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp index 837922feb2..7107dd9526 100644 --- a/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp +++ b/cpp/src/barretenberg/honk/sumcheck/sumcheck.hpp @@ -3,7 +3,7 @@ #include #include "barretenberg/honk/sumcheck/relations/relation.hpp" #include "barretenberg/honk/transcript/transcript.hpp" -#include "barretenberg/honk/utils/public_inputs.hpp" +#include "barretenberg/honk/utils/grand_product_delta.hpp" #include "barretenberg/common/throw_or_abort.hpp" #include "sumcheck_round.hpp" #include "polynomials/univariate.hpp" diff --git a/cpp/src/barretenberg/honk/utils/public_inputs.hpp b/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp similarity index 72% rename from cpp/src/barretenberg/honk/utils/public_inputs.hpp rename to cpp/src/barretenberg/honk/utils/grand_product_delta.hpp index 52c66cff94..674ab15c2a 100644 --- a/cpp/src/barretenberg/honk/utils/public_inputs.hpp +++ b/cpp/src/barretenberg/honk/utils/grand_product_delta.hpp @@ -54,4 +54,26 @@ Field compute_public_input_delta(std::span public_inputs, return numerator / denominator; } +/** + * @brief Compute lookup grand product delta + * + * @details Similar to how incorporation of public inputs into the permutation grand product results in + * z_permutation(X_n) = \Delta_{PI}, the structure of the lookup grand product polynomial results in + * z_lookup(X_n) = (γ(1 + β))^n = \Delta_{lookup}. This is a side effect of the way in which we + * incorporate the original plookup construction (for which z_lookup(X_n) = 1) into plonk/honk. + * See https://hackmd.io/@aztec-network/ByjS5GplK? for a more detailed explanation. + * + * @tparam Field + * @param beta + * @param gamma + * @param domain_size dyadic circuit size + * @return Field + */ +template +Field compute_lookup_grand_product_delta(const Field& beta, const Field& gamma, const size_t domain_size) +{ + Field gamma_by_one_plus_beta = gamma * (Field(1) + beta); // γ(1 + β) + return gamma_by_one_plus_beta.pow(domain_size); // (γ(1 + β))^n +} + } // namespace proof_system::honk \ No newline at end of file diff --git a/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp index ccc3ece628..dd51d0aa3d 100644 --- a/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/splitting_tmp/ultra_plonk_composer.hpp @@ -133,18 +133,18 @@ class UltraPlonkComposer { // * 1) Current number number of actual gates // * 2) Number of public inputs, as we'll need to add a gate for each of them // * 3) Number of Rom array-associated gates - // * 4) NUmber of range-list associated gates + // * 4) Number of range-list associated gates + // * 5) Number of non-native field multiplication gates. // * // * // * @param count return arument, number of existing gates // * @param rangecount return argument, extra gates due to range checks // * @param romcount return argument, extra gates due to rom reads // * @param ramcount return argument, extra gates due to ram read/writes + // * @param nnfcount return argument, extra gates due to queued non native field gates // */ - // void get_num_gates_split_into_components(size_t& count, - // size_t& rangecount, - // size_t& romcount, - // size_t& ramcount) const + // void get_num_gates_split_into_components( + // size_t& count, size_t& rangecount, size_t& romcount, size_t& ramcount, size_t& nnfcount) const // { // count = num_gates; // // each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set @@ -213,17 +213,27 @@ class UltraPlonkComposer { // rangecount += ram_range_sizes[i]; // } // } + // std::vector nnf_copy(cached_non_native_field_multiplications); + // // update nnfcount + // std::sort(nnf_copy.begin(), nnf_copy.end()); + + // auto last = std::unique(nnf_copy.begin(), nnf_copy.end()); + // const size_t num_nnf_ops = static_cast(std::distance(nnf_copy.begin(), last)); + // nnfcount = num_nnf_ops * GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC; // } + // // /** // * @brief Get the final number of gates in a circuit, which consists of the sum of: // * 1) Current number number of actual gates // * 2) Number of public inputs, as we'll need to add a gate for each of them // * 3) Number of Rom array-associated gates - // * 4) NUmber of range-list associated gates + // * 4) Number of range-list associated gates + // * 5) Number of non-native field multiplication gates. // * // * @return size_t // */ + // // virtual size_t get_num_gates() const override // { // // if circuit finalised already added extra gates @@ -234,8 +244,9 @@ class UltraPlonkComposer { // size_t rangecount = 0; // size_t romcount = 0; // size_t ramcount = 0; - // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); - // return count + romcount + ramcount + rangecount; + // size_t nnfcount = 0; + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount, nnfcount); + // return count + romcount + ramcount + rangecount + nnfcount; // } // virtual void print_num_gates() const override @@ -244,12 +255,13 @@ class UltraPlonkComposer { // size_t rangecount = 0; // size_t romcount = 0; // size_t ramcount = 0; - - // get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + // size_t nnfcount = 0; + // get_num_gates_split_into_components(count, rangecount, romcount, ramcount, nnfcount); // size_t total = count + romcount + ramcount + rangecount; // std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount - // << ", range " << rangecount << "), pubinp = " << public_inputs.size() << std::endl; + // << ", range " << rangecount << ", non native field gates " << nnfcount + // << "), pubinp = " << public_inputs.size() << std::endl; // } void assert_equal(const uint32_t a_variable_idx, @@ -367,11 +379,10 @@ class UltraPlonkComposer { }; // std::array decompose_non_native_field_double_width_limb( // const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); - std::array evaluate_non_native_field_multiplication( + std::array queue_non_native_field_multiplication( const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true) { - return circuit_constructor.evaluate_non_native_field_multiplication(input, - range_constrain_quotient_and_remainder); + return circuit_constructor.queue_non_native_field_multiplication(input, range_constrain_quotient_and_remainder); }; // std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& // input); typedef std::pair scaled_witness; typedef std::tuple UltraComposer::compute_proving_key() * our circuit is finalised, and we must not to execute these functions again. */ if (!circuit_finalised) { + process_non_native_field_multiplications(); process_ROM_arrays(public_inputs.size()); process_RAM_arrays(public_inputs.size()); process_range_lists(); @@ -1216,18 +1217,65 @@ void UltraComposer::create_new_range_constraint(const uint32_t variable_index, range_lists.insert({ target_range, create_range_list(target_range) }); } + const auto existing_tag = real_variable_tags[real_variable_index[variable_index]]; auto& list = range_lists[target_range]; - assign_tag(variable_index, list.range_tag); - list.variable_indices.emplace_back(variable_index); + + // If the variable's tag matches the target range list's tag, do nothing. + if (existing_tag != list.range_tag) { + // If the variable is 'untagged' (i.e., it has the dummy tag), assign it the appropriate tag. + // Otherwise, find the range for which the variable has already been tagged. + if (existing_tag != DUMMY_TAG) { + bool found_tag = false; + for (const auto& r : range_lists) { + if (r.second.range_tag == existing_tag) { + found_tag = true; + if (r.first < target_range) { + // The variable already has a more restrictive range check, so do nothing. + return; + } else { + // The range constraint we are trying to impose is more restrictive than the existing range + // constraint. It would be difficult to remove an existing range check. Instead deep-copy the + // variable and apply a range check to new variable + const uint32_t copied_witness = add_variable(get_variable(variable_index)); + create_add_gate({ .a = variable_index, + .b = copied_witness, + .c = zero_idx, + .a_scaling = 1, + .b_scaling = -1, + .c_scaling = 0, + .const_scaling = 0 }); + // Recurse with new witness that has no tag attached. + create_new_range_constraint(copied_witness, target_range, msg); + return; + } + } + } + ASSERT(found_tag == true); + } + assign_tag(variable_index, list.range_tag); + list.variable_indices.emplace_back(variable_index); + } } -void UltraComposer::process_range_list(const RangeList& list) +void UltraComposer::process_range_list(RangeList& list) { assert_valid_variables(list.variable_indices); ASSERT(list.variable_indices.size() > 0); + + // replace witness index in variable_indices with the real variable index i.e. if a copy constraint has been + // applied on a variable after it was range constrained, this makes sure the indices in list point to the updated + // index in the range list so the set equivalence does not fail + for (uint32_t& x : list.variable_indices) { + x = real_variable_index[x]; + } + // remove duplicate witness indices to prevent the sorted list set size being wrong! + std::sort(list.variable_indices.begin(), list.variable_indices.end()); + auto back_iterator = std::unique(list.variable_indices.begin(), list.variable_indices.end()); + list.variable_indices.erase(back_iterator, list.variable_indices.end()); + // go over variables - // for each variable, create mirror variable with same value - with tau tag + // iterate over each variable and create mirror variable with same value - with tau tag // need to make sure that, in original list, increments of at most 3 std::vector sorted_list; sorted_list.reserve(list.variable_indices.size()); @@ -1262,7 +1310,7 @@ void UltraComposer::process_range_list(const RangeList& list) void UltraComposer::process_range_lists() { - for (const auto& i : range_lists) + for (auto& i : range_lists) process_range_list(i.second); } @@ -1811,18 +1859,22 @@ std::array UltraComposer::decompose_non_native_field_double_width_l } /** - * NON NATIVE FIELD MULTIPLICATION CUSTOM GATE SEQUENCE + * @brief Queue up non-native field multiplication data. + * + * @details The data queued represents a non-native field multiplication identity a * b = q * p + r, + * where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables. * - * This method will evaluate the equation (a * b = q * p + r) - * Where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables + * Without this queue some functions, such as proof_system::plonk::stdlib::element::double_montgomery_ladder, would + * duplicate non-native field operations, which can be quite expensive. We queue up these operations, and remove + * duplicates in the circuit finishing stage of the proving key computation. * * The non-native field modulus, p, is a circuit constant * * The return value are the witness indices of the two remainder limbs `lo_1, hi_2` * - * N.B. this method does NOT evaluate the prime field component of non-native field multiplications + * N.B.: This method does NOT evaluate the prime field component of non-native field multiplications. **/ -std::array UltraComposer::evaluate_non_native_field_multiplication( +std::array UltraComposer::queue_non_native_field_multiplication( const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder) { @@ -1854,8 +1906,6 @@ std::array UltraComposer::evaluate_non_native_field_multiplication( constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; constexpr barretenberg::fr LIMB_SHIFT_2 = uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr barretenberg::fr LIMB_SHIFT_3 = uint256_t(1) << (3 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); - constexpr barretenberg::fr LIMB_RSHIFT = - barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr barretenberg::fr LIMB_RSHIFT_2 = barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); @@ -1904,82 +1954,127 @@ std::array UltraComposer::evaluate_non_native_field_multiplication( range_constrain_two_limbs(input.q[2], input.q[3]); } - // product gate 1 - // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 - create_big_add_gate({ input.q[0], - input.q[1], - input.r[1], - lo_1_idx, - input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, - input.neg_modulus[0] * LIMB_SHIFT, - -LIMB_SHIFT, - -LIMB_SHIFT.sqr(), - 0 }, - true); + // Add witnesses into the multiplication cache + // (when finalising the circuit, we will remove duplicates; several dups produced by biggroup.hpp methods) + cached_non_native_field_multiplication cache_entry{ + .a = input.a, + .b = input.b, + .q = input.q, + .r = input.r, + .cross_terms = { lo_0_idx, lo_1_idx, hi_0_idx, hi_1_idx, hi_2_idx, hi_3_idx }, + .neg_modulus = input.neg_modulus, + }; + cached_non_native_field_multiplications.emplace_back(cache_entry); - w_l.emplace_back(input.a[1]); - w_r.emplace_back(input.b[1]); - w_o.emplace_back(input.r[0]); - w_4.emplace_back(lo_0_idx); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); - ++num_gates; - w_l.emplace_back(input.a[0]); - w_r.emplace_back(input.b[0]); - w_o.emplace_back(input.a[3]); - w_4.emplace_back(input.b[3]); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); - ++num_gates; - w_l.emplace_back(input.a[2]); - w_r.emplace_back(input.b[2]); - w_o.emplace_back(input.r[3]); - w_4.emplace_back(hi_0_idx); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); - ++num_gates; - w_l.emplace_back(input.a[1]); - w_r.emplace_back(input.b[1]); - w_o.emplace_back(input.r[2]); - w_4.emplace_back(hi_1_idx); - apply_aux_selectors(AUX_SELECTORS::NONE); - ++num_gates; + return std::array{ lo_1_idx, hi_3_idx }; +} - /** - * product gate 6 - * - * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 - * - **/ - create_big_add_gate( - { - input.q[2], - input.q[3], - lo_1_idx, - hi_1_idx, - -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], - -input.neg_modulus[0] * LIMB_SHIFT, - -1, - -1, - 0, - }, - true); +/** + * @brief Called in `compute_proving_key` when finalizing circuit. + * Iterates over the cached_non_native_field_multiplication objects, + * removes duplicates, and instantiates the remainder as constraints` + */ +void UltraComposer::process_non_native_field_multiplications() +{ + std::sort(cached_non_native_field_multiplications.begin(), cached_non_native_field_multiplications.end()); - /** - * product gate 7 - * - * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b - **/ - create_big_add_gate({ - hi_3_idx, - input.q[0], - input.q[1], - hi_2_idx, - -1, - input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, - input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, - LIMB_RSHIFT_2, - 0, - }); + auto last = + std::unique(cached_non_native_field_multiplications.begin(), cached_non_native_field_multiplications.end()); - return std::array{ lo_1_idx, hi_3_idx }; + auto it = cached_non_native_field_multiplications.begin(); + + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + constexpr barretenberg::fr LIMB_RSHIFT = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + constexpr barretenberg::fr LIMB_RSHIFT_2 = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); + + // iterate over the cached items and create constraints + while (it != last) { + const auto input = *it; + const uint32_t lo_0_idx = input.cross_terms.lo_0_idx; + const uint32_t lo_1_idx = input.cross_terms.lo_1_idx; + const uint32_t hi_0_idx = input.cross_terms.hi_0_idx; + const uint32_t hi_1_idx = input.cross_terms.hi_1_idx; + const uint32_t hi_2_idx = input.cross_terms.hi_2_idx; + const uint32_t hi_3_idx = input.cross_terms.hi_3_idx; + + // product gate 1 + // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 + create_big_add_gate({ input.q[0], + input.q[1], + input.r[1], + lo_1_idx, + input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, + input.neg_modulus[0] * LIMB_SHIFT, + -LIMB_SHIFT, + -LIMB_SHIFT.sqr(), + 0 }, + true); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[0]); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++num_gates; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++num_gates; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(input.r[3]); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++num_gates; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[2]); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++num_gates; + + /** + * product gate 6 + * + * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 + * + **/ + create_big_add_gate( + { + input.q[2], + input.q[3], + lo_1_idx, + hi_1_idx, + -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], + -input.neg_modulus[0] * LIMB_SHIFT, + -1, + -1, + 0, + }, + true); + + /** + * product gate 7 + * + * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b + **/ + create_big_add_gate({ + hi_3_idx, + input.q[0], + input.q[1], + hi_2_idx, + -1, + input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, + input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, + LIMB_RSHIFT_2, + 0, + }); + ++it; + } } /** diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index b3157639fd..d8988e3fca 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -7,6 +7,14 @@ namespace proof_system::plonk { +/** + * @brief UltraPlonK: a flavor of PlonK with plookup tables, efficient range constraints, RAM, ROM, and more! + * + * @warning The proof of PlonK's correctness uses the fact that verifier challenges are generated by a hash function + * standing in for a random oracle. UltraPlonK currently uses a hash function (a custom Pedersen hash that uses lookup + * tables) that is known to violate this random oracle assumption. We plan to switch to an algebraic hash function that + * is believed to have the random oracle property in a future upgrade. + */ class UltraComposer : public ComposerBase { public: @@ -27,7 +35,8 @@ class UltraComposer : public ComposerBase { static constexpr uint32_t UNINITIALIZED_MEMORY_RECORD = UINT32_MAX; static constexpr size_t NUMBER_OF_GATES_PER_RAM_ACCESS = 2; static constexpr size_t NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY = 1; - + // number of gates created per non-native field operation in process_non_native_field_multiplications + static constexpr size_t GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC = 7; struct non_native_field_witnesses { // first 4 array elements = limbs // 5th element = prime basis limb @@ -39,6 +48,66 @@ class UltraComposer : public ComposerBase { barretenberg::fr modulus; }; + struct non_native_field_multiplication_cross_terms { + uint32_t lo_0_idx; + uint32_t lo_1_idx; + uint32_t hi_0_idx; + uint32_t hi_1_idx; + uint32_t hi_2_idx; + uint32_t hi_3_idx; + }; + + /** + * @brief Used to store instructions to create non_native_field_multiplication gates. + * We want to cache these (and remove duplicates) as the stdlib code can end up multiplying the same inputs + * repeatedly. + */ + struct cached_non_native_field_multiplication { + std::array a; + std::array b; + std::array q; + std::array r; + non_native_field_multiplication_cross_terms cross_terms; + std::array neg_modulus; + + bool operator==(const cached_non_native_field_multiplication& other) const + { + bool valid = true; + for (size_t i = 0; i < 5; ++i) { + valid = valid && (a[i] == other.a[i]); + valid = valid && (b[i] == other.b[i]); + valid = valid && (q[i] == other.q[i]); + valid = valid && (r[i] == other.r[i]); + } + return valid; + } + bool operator<(const cached_non_native_field_multiplication& other) const + { + if (a < other.a) { + return true; + } + if (a == other.a) { + if (b < other.b) { + return true; + } + if (b == other.b) { + if (q < other.q) { + return true; + } + if (q == other.q) { + if (r < other.r) { + return true; + } + } + } + } + return false; + } + }; + + std::vector cached_non_native_field_multiplications; + void process_non_native_field_multiplications(); + enum AUX_SELECTORS { NONE, LIMB_ACCUMULATE_1, @@ -58,6 +127,8 @@ class UltraComposer : public ComposerBase { uint64_t target_range; uint32_t range_tag; uint32_t tau_tag; + // contains the list of variable indices that are range constrained to target_range + // as ordered in the vector of variable indoces from the composer std::vector variable_indices; }; @@ -242,18 +313,18 @@ class UltraComposer : public ComposerBase { * 1) Current number number of actual gates * 2) Number of public inputs, as we'll need to add a gate for each of them * 3) Number of Rom array-associated gates - * 4) NUmber of range-list associated gates + * 4) Number of range-list associated gates + * 5) Number of non-native field multiplication gates. * * * @param count return arument, number of existing gates * @param rangecount return argument, extra gates due to range checks * @param romcount return argument, extra gates due to rom reads * @param ramcount return argument, extra gates due to ram read/writes + * @param nnfcount return argument, extra gates due to queued non native field gates */ - void get_num_gates_split_into_components(size_t& count, - size_t& rangecount, - size_t& romcount, - size_t& ramcount) const + void get_num_gates_split_into_components( + size_t& count, size_t& rangecount, size_t& romcount, size_t& ramcount, size_t& nnfcount) const { count = num_gates; // each ROM gate adds +1 extra gate due to the rom reads being copied to a sorted list set @@ -321,6 +392,13 @@ class UltraComposer : public ComposerBase { rangecount += ram_range_sizes[i]; } } + std::vector nnf_copy(cached_non_native_field_multiplications); + // update nnfcount + std::sort(nnf_copy.begin(), nnf_copy.end()); + + auto last = std::unique(nnf_copy.begin(), nnf_copy.end()); + const size_t num_nnf_ops = static_cast(std::distance(nnf_copy.begin(), last)); + nnfcount = num_nnf_ops * GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC; } /** @@ -328,7 +406,8 @@ class UltraComposer : public ComposerBase { * 1) Current number number of actual gates * 2) Number of public inputs, as we'll need to add a gate for each of them * 3) Number of Rom array-associated gates - * 4) NUmber of range-list associated gates + * 4) Number of range-list associated gates + * 5) Number of non-native field multiplication gates. * * @return size_t */ @@ -342,8 +421,9 @@ class UltraComposer : public ComposerBase { size_t rangecount = 0; size_t romcount = 0; size_t ramcount = 0; - get_num_gates_split_into_components(count, rangecount, romcount, ramcount); - return count + romcount + ramcount + rangecount; + size_t nnfcount = 0; + get_num_gates_split_into_components(count, rangecount, romcount, ramcount, nnfcount); + return count + romcount + ramcount + rangecount + nnfcount; } virtual size_t get_total_circuit_size() const override @@ -366,12 +446,13 @@ class UltraComposer : public ComposerBase { size_t rangecount = 0; size_t romcount = 0; size_t ramcount = 0; - - get_num_gates_split_into_components(count, rangecount, romcount, ramcount); + size_t nnfcount = 0; + get_num_gates_split_into_components(count, rangecount, romcount, ramcount, nnfcount); size_t total = count + romcount + ramcount + rangecount; std::cout << "gates = " << total << " (arith " << count << ", rom " << romcount << ", ram " << ramcount - << ", range " << rangecount << "), pubinp = " << public_inputs.size() << std::endl; + << ", range " << rangecount << ", non native field gates " << nnfcount + << "), pubinp = " << public_inputs.size() << std::endl; } void assert_equal_constant(const uint32_t a_idx, @@ -425,6 +506,10 @@ class UltraComposer : public ComposerBase { void assign_tag(const uint32_t variable_index, const uint32_t tag) { ASSERT(tag <= current_tag); + // If we've already assigned this tag to this variable, return (can happen due to copy constraints) + if (real_variable_tags[real_variable_index[variable_index]] == tag) { + return; + } ASSERT(real_variable_tags[real_variable_index[variable_index]] == DUMMY_TAG); real_variable_tags[real_variable_index[variable_index]] = tag; } @@ -443,7 +528,7 @@ class UltraComposer : public ComposerBase { } RangeList create_range_list(const uint64_t target_range); - void process_range_list(const RangeList& list); + void process_range_list(RangeList& list); void process_range_lists(); /** @@ -460,7 +545,7 @@ class UltraComposer : public ComposerBase { const size_t hi_limb_bits = DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); std::array decompose_non_native_field_double_width_limb( const uint32_t limb_idx, const size_t num_limb_bits = (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); - std::array evaluate_non_native_field_multiplication( + std::array queue_non_native_field_multiplication( const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder = true); std::array evaluate_partial_non_native_field_multiplication(const non_native_field_witnesses& input); typedef std::pair scaled_witness; diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp index eef80fa56c..9745d397e2 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.test.cpp @@ -649,7 +649,7 @@ TYPED_TEST(ultra_composer, non_native_field_multiplication) UltraComposer::non_native_field_witnesses inputs{ a_indices, b_indices, q_indices, r_indices, modulus_limbs, fr(uint256_t(modulus)), }; - const auto [lo_1_idx, hi_1_idx] = composer.evaluate_non_native_field_multiplication(inputs); + const auto [lo_1_idx, hi_1_idx] = composer.queue_non_native_field_multiplication(inputs); composer.range_constrain_two_limbs(lo_1_idx, hi_1_idx, 70, 70); TestFixture::prove_and_verify(composer, /*expected_result=*/true); @@ -758,4 +758,65 @@ TYPED_TEST(ultra_composer, ram) TestFixture::prove_and_verify(composer, /*expected_result=*/true); } +TYPED_TEST(ultra_composer, range_checks_on_duplicates) +{ + UltraComposer composer = UltraComposer(); + + uint32_t a = composer.add_variable(100); + uint32_t b = composer.add_variable(100); + uint32_t c = composer.add_variable(100); + uint32_t d = composer.add_variable(100); + + composer.assert_equal(a, b); + composer.assert_equal(a, c); + composer.assert_equal(a, d); + + composer.create_new_range_constraint(a, 1000); + composer.create_new_range_constraint(b, 1001); + composer.create_new_range_constraint(c, 999); + composer.create_new_range_constraint(d, 1000); + + composer.create_big_add_gate( + { + a, + b, + c, + d, + 0, + 0, + 0, + 0, + 0, + }, + false); + + TestFixture::prove_and_verify(composer, /*expected_result=*/true); +} + +// Ensure copy constraints added on variables smaller than 2^14, which have been previously +// range constrained, do not break the set equivalence checks because of indices mismatch. +// 2^14 is DEFAULT_PLOOKUP_RANGE_BITNUM i.e. the maximum size before a variable gets sliced +// before range constraints are applied to it. +TEST(ultra_composer, range_constraint_small_variable) +{ + auto composer = UltraComposer(); + uint16_t mask = (1 << 8) - 1; + int a = engine.get_random_uint16() & mask; + uint32_t a_idx = composer.add_variable(fr(a)); + uint32_t b_idx = composer.add_variable(fr(a)); + ASSERT_NE(a_idx, b_idx); + uint32_t c_idx = composer.add_variable(fr(a)); + ASSERT_NE(c_idx, b_idx); + composer.create_range_constraint(b_idx, 8, "bad range"); + composer.assert_equal(a_idx, b_idx); + composer.create_range_constraint(c_idx, 8, "bad range"); + composer.assert_equal(a_idx, c_idx); + + auto prover = composer.create_prover(); + auto proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + } // namespace proof_system::plonk::test_ultra_composer diff --git a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp index d606ab173d..425efbdb58 100644 --- a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp +++ b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.cpp @@ -33,6 +33,7 @@ void UltraCircuitConstructor::finalize_circuit() * our circuit is finalised, and we must not to execute these functions again. */ if (!circuit_finalised) { + process_non_native_field_multiplications(); process_ROM_arrays(public_inputs.size()); process_RAM_arrays(public_inputs.size()); process_range_lists(); @@ -1236,18 +1237,22 @@ std::array UltraCircuitConstructor::decompose_non_native_field_doub } /** - * NON NATIVE FIELD MULTIPLICATION CUSTOM GATE SEQUENCE + * @brief Queue up non-native field multiplication data. * - * This method will evaluate the equation (a * b = q * p + r) - * Where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables + * @details The data queued represents a non-native field multiplication identity a * b = q * p + r, + * where a, b, q, r are all emulated non-native field elements that are each split across 4 distinct witness variables. + * + * Without this queue some functions, such as proof_system::plonk::stdlib::element::double_montgomery_ladder, would + * duplicate non-native field operations, which can be quite expensive. We queue up these operations, and remove + * duplicates in the circuit finishing stage of the proving key computation. * * The non-native field modulus, p, is a circuit constant * * The return value are the witness indices of the two remainder limbs `lo_1, hi_2` * - * N.B. this method does NOT evaluate the prime field component of non-native field multiplications + * N.B.: This method does NOT evaluate the prime field component of non-native field multiplications. **/ -std::array UltraCircuitConstructor::evaluate_non_native_field_multiplication( +std::array UltraCircuitConstructor::queue_non_native_field_multiplication( const non_native_field_witnesses& input, const bool range_constrain_quotient_and_remainder) { @@ -1279,8 +1284,6 @@ std::array UltraCircuitConstructor::evaluate_non_native_field_multi constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; constexpr barretenberg::fr LIMB_SHIFT_2 = uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr barretenberg::fr LIMB_SHIFT_3 = uint256_t(1) << (3 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); - constexpr barretenberg::fr LIMB_RSHIFT = - barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); constexpr barretenberg::fr LIMB_RSHIFT_2 = barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); @@ -1328,83 +1331,127 @@ std::array UltraCircuitConstructor::evaluate_non_native_field_multi range_constrain_two_limbs(input.q[0], input.q[1]); range_constrain_two_limbs(input.q[2], input.q[3]); } + // Add witnesses into the multiplication cache + // (when finalising the circuit, we will remove duplicates; several dups produced by biggroup.hpp methods) + cached_non_native_field_multiplication cache_entry{ + .a = input.a, + .b = input.b, + .q = input.q, + .r = input.r, + .cross_terms = { lo_0_idx, lo_1_idx, hi_0_idx, hi_1_idx, hi_2_idx, hi_3_idx }, + .neg_modulus = input.neg_modulus, + }; + cached_non_native_field_multiplications.emplace_back(cache_entry); - // product gate 1 - // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 - create_big_add_gate({ input.q[0], - input.q[1], - input.r[1], - lo_1_idx, - input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, - input.neg_modulus[0] * LIMB_SHIFT, - -LIMB_SHIFT, - -LIMB_SHIFT.sqr(), - 0 }, - true); + return std::array{ lo_1_idx, hi_3_idx }; +} - w_l.emplace_back(input.a[1]); - w_r.emplace_back(input.b[1]); - w_o.emplace_back(input.r[0]); - w_4.emplace_back(lo_0_idx); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); - ++num_gates; - w_l.emplace_back(input.a[0]); - w_r.emplace_back(input.b[0]); - w_o.emplace_back(input.a[3]); - w_4.emplace_back(input.b[3]); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); - ++num_gates; - w_l.emplace_back(input.a[2]); - w_r.emplace_back(input.b[2]); - w_o.emplace_back(input.r[3]); - w_4.emplace_back(hi_0_idx); - apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); - ++num_gates; - w_l.emplace_back(input.a[1]); - w_r.emplace_back(input.b[1]); - w_o.emplace_back(input.r[2]); - w_4.emplace_back(hi_1_idx); - apply_aux_selectors(AUX_SELECTORS::NONE); - ++num_gates; +/** + * @brief Called in `compute_proving_key` when finalizing circuit. + * Iterates over the cached_non_native_field_multiplication objects, + * removes duplicates, and instantiates the remainder as constraints` + */ +void UltraCircuitConstructor::process_non_native_field_multiplications() +{ + std::sort(cached_non_native_field_multiplications.begin(), cached_non_native_field_multiplications.end()); - /** - * product gate 6 - * - * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 - * - **/ - create_big_add_gate( - { - input.q[2], - input.q[3], - lo_1_idx, - hi_1_idx, - -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], - -input.neg_modulus[0] * LIMB_SHIFT, - -1, - -1, - 0, - }, - true); + auto last = + std::unique(cached_non_native_field_multiplications.begin(), cached_non_native_field_multiplications.end()); - /** - * product gate 7 - * - * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b - **/ - create_big_add_gate({ - hi_3_idx, - input.q[0], - input.q[1], - hi_2_idx, - -1, - input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, - input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, - LIMB_RSHIFT_2, - 0, - }); + auto it = cached_non_native_field_multiplications.begin(); - return std::array{ lo_1_idx, hi_3_idx }; + constexpr barretenberg::fr LIMB_SHIFT = uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS; + constexpr barretenberg::fr LIMB_RSHIFT = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << DEFAULT_NON_NATIVE_FIELD_LIMB_BITS); + constexpr barretenberg::fr LIMB_RSHIFT_2 = + barretenberg::fr(1) / barretenberg::fr(uint256_t(1) << (2 * DEFAULT_NON_NATIVE_FIELD_LIMB_BITS)); + + // iterate over the cached items and create constraints + while (it != last) { + const auto input = *it; + const uint32_t lo_0_idx = input.cross_terms.lo_0_idx; + const uint32_t lo_1_idx = input.cross_terms.lo_1_idx; + const uint32_t hi_0_idx = input.cross_terms.hi_0_idx; + const uint32_t hi_1_idx = input.cross_terms.hi_1_idx; + const uint32_t hi_2_idx = input.cross_terms.hi_2_idx; + const uint32_t hi_3_idx = input.cross_terms.hi_3_idx; + + // product gate 1 + // (lo_0 + q_0(p_0 + p_1*2^b) + q_1(p_0*2^b) - (r_1)2^b)2^-2b - lo_1 = 0 + create_big_add_gate({ input.q[0], + input.q[1], + input.r[1], + lo_1_idx, + input.neg_modulus[0] + input.neg_modulus[1] * LIMB_SHIFT, + input.neg_modulus[0] * LIMB_SHIFT, + -LIMB_SHIFT, + -LIMB_SHIFT.sqr(), + 0 }, + true); + + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[0]); + w_4.emplace_back(lo_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_1); + ++num_gates; + w_l.emplace_back(input.a[0]); + w_r.emplace_back(input.b[0]); + w_o.emplace_back(input.a[3]); + w_4.emplace_back(input.b[3]); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_2); + ++num_gates; + w_l.emplace_back(input.a[2]); + w_r.emplace_back(input.b[2]); + w_o.emplace_back(input.r[3]); + w_4.emplace_back(hi_0_idx); + apply_aux_selectors(AUX_SELECTORS::NON_NATIVE_FIELD_3); + ++num_gates; + w_l.emplace_back(input.a[1]); + w_r.emplace_back(input.b[1]); + w_o.emplace_back(input.r[2]); + w_4.emplace_back(hi_1_idx); + apply_aux_selectors(AUX_SELECTORS::NONE); + ++num_gates; + + /** + * product gate 6 + * + * hi_2 - hi_1 - lo_1 - q[2](p[1].2^b + p[0]) - q[3](p[0].2^b) = 0 + * + **/ + create_big_add_gate( + { + input.q[2], + input.q[3], + lo_1_idx, + hi_1_idx, + -input.neg_modulus[1] * LIMB_SHIFT - input.neg_modulus[0], + -input.neg_modulus[0] * LIMB_SHIFT, + -1, + -1, + 0, + }, + true); + + /** + * product gate 7 + * + * hi_3 - (hi_2 - q[0](p[3].2^b + p[2]) - q[1](p[2].2^b + p[1])).2^-2b + **/ + create_big_add_gate({ + hi_3_idx, + input.q[0], + input.q[1], + hi_2_idx, + -1, + input.neg_modulus[3] * LIMB_RSHIFT + input.neg_modulus[2] * LIMB_RSHIFT_2, + input.neg_modulus[2] * LIMB_RSHIFT + input.neg_modulus[1] * LIMB_RSHIFT_2, + LIMB_RSHIFT_2, + 0, + }); + ++it; + } } /** diff --git a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp index 383dccb6f0..2e4803fa44 100644 --- a/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp +++ b/cpp/src/barretenberg/proof_system/circuit_constructors/ultra_circuit_constructor.hpp @@ -29,6 +29,8 @@ static constexpr size_t DEFAULT_NON_NATIVE_FIELD_LIMB_BITS = 68; static constexpr uint32_t UNINITIALIZED_MEMORY_RECORD = UINT32_MAX; static constexpr size_t NUMBER_OF_GATES_PER_RAM_ACCESS = 2; static constexpr size_t NUMBER_OF_ARITHMETIC_GATES_PER_RAM_ARRAY = 1; +// number of gates created per non-native field operation in process_non_native_field_multiplications +static constexpr size_t GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC = 7; struct non_native_field_witnesses { // first 4 array elements = limbs @@ -41,6 +43,63 @@ struct non_native_field_witnesses { barretenberg::fr modulus; }; +struct non_native_field_multiplication_cross_terms { + uint32_t lo_0_idx; + uint32_t lo_1_idx; + uint32_t hi_0_idx; + uint32_t hi_1_idx; + uint32_t hi_2_idx; + uint32_t hi_3_idx; +}; + +/** + * @brief Used to store instructions to create non_native_field_multiplication gates. + * We want to cache these (and remove duplicates) as the stdlib code can end up multiplying the same inputs + * repeatedly. + */ +struct cached_non_native_field_multiplication { + std::array a; + std::array b; + std::array q; + std::array r; + non_native_field_multiplication_cross_terms cross_terms; + std::array neg_modulus; + + bool operator==(const cached_non_native_field_multiplication& other) const + { + bool valid = true; + for (size_t i = 0; i < 5; ++i) { + valid = valid && (a[i] == other.a[i]); + valid = valid && (b[i] == other.b[i]); + valid = valid && (q[i] == other.q[i]); + valid = valid && (r[i] == other.r[i]); + } + return valid; + } + bool operator<(const cached_non_native_field_multiplication& other) const + { + if (a < other.a) { + return true; + } + if (a == other.a) { + if (b < other.b) { + return true; + } + if (b == other.b) { + if (q < other.q) { + return true; + } + if (q == other.q) { + if (r < other.r) { + return true; + } + } + } + } + return false; + } +}; + enum AUX_SELECTORS { NONE, LIMB_ACCUMULATE_1, @@ -201,6 +260,10 @@ class UltraCircuitConstructor : public CircuitConstructorBase memory_write_records; + std::vector cached_non_native_field_multiplications; + + void process_non_native_field_multiplications(); + bool circuit_finalised = false; UltraCircuitConstructor(const size_t size_hint = 0) @@ -286,18 +349,18 @@ class UltraCircuitConstructor : public CircuitConstructorBase nnf_copy(cached_non_native_field_multiplications); + // // update nnfcount + // std::sort(nnf_copy.begin(), nnf_copy.end()); + + // auto last = std::unique(nnf_copy.begin(), nnf_copy.end()); + // const size_t num_nnf_ops = static_cast(std::distance(nnf_copy.begin(), last)); + // nnfcount = num_nnf_ops * GATES_PER_NON_NATIVE_FIELD_MULTIPLICATION_ARITHMETIC; // } // /** @@ -373,7 +443,8 @@ class UltraCircuitConstructor : public CircuitConstructorBase + // product to be equal to the "public input delta" that is computed in current_permutation_poly[i] = -barretenberg::fr(current_mapping.row_index + 1 + num_gates * current_mapping.column_index); } else if (current_mapping.is_tag) { diff --git a/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp b/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp index b32ec8d77e..d324bcc4af 100644 --- a/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/bigfield/bigfield_impl.hpp @@ -1982,7 +1982,7 @@ void bigfield::unsafe_evaluate_multiply_add(const bigfield& input_left, modulus, }; // N.B. this method also evaluates the prime field component of the non-native field mul - const auto [lo_idx, hi_idx] = ctx->evaluate_non_native_field_multiplication(witnesses, false); + const auto [lo_idx, hi_idx] = ctx->queue_non_native_field_multiplication(witnesses, false); barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); field_t::evaluate_polynomial_identity(left.prime_basis_limb, @@ -2267,7 +2267,7 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vector> limb_0_accumulator; std::vector> limb_2_accumulator; std::vector> prime_limb_accumulator; @@ -2416,7 +2416,7 @@ void bigfield::unsafe_evaluate_multiple_multiply_add(const std::vectorevaluate_non_native_field_multiplication(witnesses, false); + const auto [lo_1_idx, hi_1_idx] = ctx->queue_non_native_field_multiplication(witnesses, false); barretenberg::fr neg_prime = -barretenberg::fr(uint256_t(target_basis.modulus)); diff --git a/cpp/src/barretenberg/stdlib/primitives/logic/logic.cpp b/cpp/src/barretenberg/stdlib/primitives/logic/logic.cpp index 47d1d848f0..033ec3b9d2 100644 --- a/cpp/src/barretenberg/stdlib/primitives/logic/logic.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/logic/logic.cpp @@ -2,13 +2,19 @@ #include "../composers/composers.hpp" #include "../plookup/plookup.hpp" +#include "barretenberg/common/assert.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/stdlib/primitives/field/field.hpp" +#include namespace proof_system::plonk::stdlib { /** * @brief A logical AND or XOR over a variable number of bits. * - * @details Defaults to basic Composer method if not using plookup-compatible composer + * @details Defaults to basic Composer method if not using plookup-compatible composer. If the left and right operands + * are larger than num_bit, the result will be truncated to num_bits. However, the two operands could be + * range-constrained to num_bits before the call which would remove the need to range constrain inside this function. * * @tparam Composer * @param a @@ -18,10 +24,17 @@ namespace proof_system::plonk::stdlib { * @return field_t */ template -field_t logic::create_logic_constraint(field_pt& a, field_pt& b, size_t num_bits, bool is_xor_gate) +field_t logic::create_logic_constraint( + field_pt& a, + field_pt& b, + size_t num_bits, + bool is_xor_gate, + const std::function(uint256_t, uint256_t, size_t)>& get_chunk) { - // can't extend past field size! + // ensure the number of bits doesn't exceed field size and is not negatove ASSERT(num_bits < 254); + ASSERT(num_bits > 0); + if (a.is_constant() && b.is_constant()) { uint256_t a_native(a.get_value()); uint256_t b_native(b.get_value()); @@ -31,59 +44,72 @@ field_t logic::create_logic_constraint(field_pt& a, field_pt if (a.is_constant() && !b.is_constant()) { Composer* ctx = b.get_context(); uint256_t a_native(a.get_value()); - field_t a_witness = field_pt::from_witness_index(ctx, ctx->put_constant_variable(a_native)); - return create_logic_constraint(a_witness, b, num_bits, is_xor_gate); + field_pt a_witness = field_pt::from_witness_index(ctx, ctx->put_constant_variable(a_native)); + return create_logic_constraint(a_witness, b, num_bits, is_xor_gate, get_chunk); } if (!a.is_constant() && b.is_constant()) { Composer* ctx = a.get_context(); uint256_t b_native(b.get_value()); field_pt b_witness = field_pt::from_witness_index(ctx, ctx->put_constant_variable(b_native)); - return create_logic_constraint(a, b_witness, num_bits, is_xor_gate); + return create_logic_constraint(a, b_witness, num_bits, is_xor_gate, get_chunk); } if constexpr (Composer::type == ComposerType::PLOOKUP) { Composer* ctx = a.get_context(); const size_t num_chunks = (num_bits / 32) + ((num_bits % 32 == 0) ? 0 : 1); - uint256_t left(a.get_value()); - uint256_t right(b.get_value()); + auto left((uint256_t)a.get_value()); + auto right((uint256_t)b.get_value()); + + field_pt a_accumulator(barretenberg::fr::zero()); + field_pt b_accumulator(barretenberg::fr::zero()); field_pt res(ctx, 0); for (size_t i = 0; i < num_chunks; ++i) { - uint256_t left_chunk = left & ((uint256_t(1) << 32) - 1); - uint256_t right_chunk = right & ((uint256_t(1) << 32) - 1); - - const field_pt a_chunk = witness_pt(ctx, left_chunk); - const field_pt b_chunk = witness_pt(ctx, right_chunk); + size_t chunk_size = (i != num_chunks - 1) ? 32 : num_bits - i * 32; + auto [left_chunk, right_chunk] = get_chunk(left, right, chunk_size); + field_pt a_chunk = witness_pt(ctx, left_chunk); + field_pt b_chunk = witness_pt(ctx, right_chunk); field_pt result_chunk = 0; if (is_xor_gate) { result_chunk = stdlib::plookup_read::read_from_2_to_1_table(plookup::MultiTableId::UINT32_XOR, a_chunk, b_chunk); + } else { result_chunk = stdlib::plookup_read::read_from_2_to_1_table(plookup::MultiTableId::UINT32_AND, a_chunk, b_chunk); } - uint256_t scaling_factor = uint256_t(1) << (32 * i); - res += result_chunk * scaling_factor; + auto scaling_factor = uint256_t(1) << (32 * i); + a_accumulator += a_chunk * scaling_factor; + b_accumulator += b_chunk * scaling_factor; - if (i == num_chunks - 1) { - const size_t final_num_bits = num_bits - (i * 32); - if (final_num_bits != 32) { - ctx->create_range_constraint(a_chunk.witness_index, final_num_bits, "bad range on a"); - ctx->create_range_constraint(b_chunk.witness_index, final_num_bits, "bad range on b"); - } + if (chunk_size != 32) { + ctx->create_range_constraint( + a_chunk.witness_index, chunk_size, "stdlib logic: bad range on final chunk of left operand"); + ctx->create_range_constraint( + b_chunk.witness_index, chunk_size, "stdlib logic: bad range on final chunk of right operand"); } + res += result_chunk * scaling_factor; + left = left >> 32; right = right >> 32; } + field_pt a_slice = a.slice(static_cast(num_bits - 1), 0)[1]; + field_pt b_slice = b.slice(static_cast(num_bits - 1), 0)[1]; + a_slice.assert_equal(a_accumulator, "stdlib logic: failed to reconstruct left operand"); + b_slice.assert_equal(b_accumulator, "stdlib logic: failed to reconstruct right operand"); return res; } else { + // If the composer doesn't have lookups we call the expensive logic constraint gate + // which creates constraints for each bit. We only create constraints up to num_bits. Composer* ctx = a.get_context(); + field_pt a_slice = a.slice(static_cast(num_bits - 1), 0)[1]; + field_pt b_slice = b.slice(static_cast(num_bits - 1), 0)[1]; auto accumulator_triple = ctx->create_logic_constraint( - a.normalize().get_witness_index(), b.normalize().get_witness_index(), num_bits, is_xor_gate); + a_slice.normalize().get_witness_index(), b_slice.normalize().get_witness_index(), num_bits, is_xor_gate); auto out_idx = accumulator_triple.out[accumulator_triple.out.size() - 1]; return field_t::from_witness_index(ctx, out_idx); } diff --git a/cpp/src/barretenberg/stdlib/primitives/logic/logic.hpp b/cpp/src/barretenberg/stdlib/primitives/logic/logic.hpp index 2b8b99d560..1ec450de00 100644 --- a/cpp/src/barretenberg/stdlib/primitives/logic/logic.hpp +++ b/cpp/src/barretenberg/stdlib/primitives/logic/logic.hpp @@ -1,17 +1,31 @@ #pragma once +#include "barretenberg/numeric/uint256/uint256.hpp" #include "barretenberg/stdlib/primitives/composers/composers_fwd.hpp" #include "barretenberg/stdlib/primitives/field/field.hpp" #include "barretenberg/stdlib/primitives/witness/witness.hpp" +#include +#include +#include namespace proof_system::plonk::stdlib { template class logic { - private: + public: using field_pt = field_t; using witness_pt = witness_t; public: - static field_pt create_logic_constraint(field_pt& a, field_pt& b, size_t num_bits, bool is_xor_gate); + static field_pt create_logic_constraint( + field_pt& a, + field_pt& b, + size_t num_bits, + bool is_xor_gate, + const std::function(uint256_t, uint256_t, size_t)>& get_chunk = + [](uint256_t left, uint256_t right, size_t chunk_size) { + uint256_t left_chunk = left & ((uint256_t(1) << chunk_size) - 1); + uint256_t right_chunk = right & ((uint256_t(1) << chunk_size) - 1); + return std::make_pair(left_chunk, right_chunk); + }); }; EXTERN_STDLIB_TYPE(logic); diff --git a/cpp/src/barretenberg/stdlib/primitives/logic/logic.test.cpp b/cpp/src/barretenberg/stdlib/primitives/logic/logic.test.cpp index bf1dd73e5f..188a32980c 100644 --- a/cpp/src/barretenberg/stdlib/primitives/logic/logic.test.cpp +++ b/cpp/src/barretenberg/stdlib/primitives/logic/logic.test.cpp @@ -1,6 +1,7 @@ #include "../bool/bool.hpp" +#include "barretenberg/numeric/uint256/uint256.hpp" +#include "barretenberg/proof_system/types/composer_type.hpp" #include "logic.hpp" -#include "barretenberg/plonk/proof_system/constants.hpp" #include #include "barretenberg/honk/composer/standard_honk_composer.hpp" #include "barretenberg/plonk/composer/standard_composer.hpp" @@ -8,6 +9,15 @@ #include "barretenberg/plonk/composer/turbo_composer.hpp" #include "barretenberg/numeric/random/engine.hpp" +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" + +#define STDLIB_TYPE_ALIASES \ + using Composer = TypeParam; \ + using witness_ct = stdlib::witness_t; \ + using field_ct = stdlib::field_t; \ + using bool_ct = stdlib::bool_t; \ + using public_witness_ct = stdlib::public_witness_t; + namespace test_stdlib_logic { namespace { @@ -19,81 +29,137 @@ template void ignore_unused(T&) {} // use to ignore unused variables i using namespace barretenberg; using namespace proof_system::plonk; -template class stdlib_logic : public testing::Test { - typedef stdlib::bool_t bool_ct; - typedef stdlib::field_t field_ct; - typedef stdlib::witness_t witness_ct; - typedef stdlib::public_witness_t public_witness_ct; - - public: - /** - * @brief Test logic - */ - static void test_logic() - { - Composer composer; - auto run_test = [&](size_t num_bits) { - uint256_t mask = (uint256_t(1) << num_bits) - 1; - - uint256_t a = engine.get_random_uint256() & mask; - uint256_t b = engine.get_random_uint256() & mask; - - uint256_t and_expected = a & b; - uint256_t xor_expected = a ^ b; - - field_ct x = witness_ct(&composer, a); - field_ct y = witness_ct(&composer, b); - - field_ct x_const(&composer, a); - field_ct y_const(&composer, b); - field_ct and_result = stdlib::logic::create_logic_constraint(x, y, num_bits, false); - field_ct xor_result = stdlib::logic::create_logic_constraint(x, y, num_bits, true); - - field_ct and_result_left_constant = - stdlib::logic::create_logic_constraint(x_const, y, num_bits, false); - field_ct xor_result_left_constant = - stdlib::logic::create_logic_constraint(x_const, y, num_bits, true); - - field_ct and_result_right_constant = - stdlib::logic::create_logic_constraint(x, y_const, num_bits, false); - field_ct xor_result_right_constant = - stdlib::logic::create_logic_constraint(x, y_const, num_bits, true); - - field_ct and_result_both_constant = - stdlib::logic::create_logic_constraint(x_const, y_const, num_bits, false); - field_ct xor_result_both_constant = - stdlib::logic::create_logic_constraint(x_const, y_const, num_bits, true); - - EXPECT_EQ(uint256_t(and_result.get_value()), and_expected); - EXPECT_EQ(uint256_t(and_result_left_constant.get_value()), and_expected); - EXPECT_EQ(uint256_t(and_result_right_constant.get_value()), and_expected); - EXPECT_EQ(uint256_t(and_result_both_constant.get_value()), and_expected); - - EXPECT_EQ(uint256_t(xor_result.get_value()), xor_expected); - EXPECT_EQ(uint256_t(xor_result_left_constant.get_value()), xor_expected); - EXPECT_EQ(uint256_t(xor_result_right_constant.get_value()), xor_expected); - EXPECT_EQ(uint256_t(xor_result_both_constant.get_value()), xor_expected); - }; - - for (size_t i = 8; i < 248; i += 8) { - run_test(i); - } - auto prover = composer.create_prover(); - plonk::proof proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); - bool result = verifier.verify_proof(proof); +template class LogicTest : public testing::Test {}; - EXPECT_EQ(result, true); - } -}; +using ComposerTypes = + ::testing::Types; + +TYPED_TEST_SUITE(LogicTest, ComposerTypes); + +TYPED_TEST(LogicTest, TestCorrectLogic) +{ + STDLIB_TYPE_ALIASES + + auto run_test = [](size_t num_bits, Composer& composer) { + uint256_t mask = (uint256_t(1) << num_bits) - 1; + + uint256_t a = engine.get_random_uint256() & mask; + uint256_t b = engine.get_random_uint256() & mask; + + uint256_t and_expected = a & b; + uint256_t xor_expected = a ^ b; + + field_ct x = witness_ct(&composer, a); + field_ct y = witness_ct(&composer, b); + + field_ct x_const(&composer, a); + field_ct y_const(&composer, b); + + field_ct and_result = stdlib::logic::create_logic_constraint(x, y, num_bits, false); + field_ct xor_result = stdlib::logic::create_logic_constraint(x, y, num_bits, true); + + field_ct and_result_left_constant = + stdlib::logic::create_logic_constraint(x_const, y, num_bits, false); + field_ct xor_result_left_constant = + stdlib::logic::create_logic_constraint(x_const, y, num_bits, true); -typedef testing::Types - ComposerTypes; + field_ct and_result_right_constant = + stdlib::logic::create_logic_constraint(x, y_const, num_bits, false); + field_ct xor_result_right_constant = + stdlib::logic::create_logic_constraint(x, y_const, num_bits, true); -TYPED_TEST_SUITE(stdlib_logic, ComposerTypes); + field_ct and_result_both_constant = + stdlib::logic::create_logic_constraint(x_const, y_const, num_bits, false); + field_ct xor_result_both_constant = + stdlib::logic::create_logic_constraint(x_const, y_const, num_bits, true); -TYPED_TEST(stdlib_logic, test_logic) + EXPECT_EQ(uint256_t(and_result.get_value()), and_expected); + EXPECT_EQ(uint256_t(and_result_left_constant.get_value()), and_expected); + EXPECT_EQ(uint256_t(and_result_right_constant.get_value()), and_expected); + EXPECT_EQ(uint256_t(and_result_both_constant.get_value()), and_expected); + + EXPECT_EQ(uint256_t(xor_result.get_value()), xor_expected); + EXPECT_EQ(uint256_t(xor_result_left_constant.get_value()), xor_expected); + EXPECT_EQ(uint256_t(xor_result_right_constant.get_value()), xor_expected); + EXPECT_EQ(uint256_t(xor_result_both_constant.get_value()), xor_expected); + }; + + auto composer = Composer(); + for (size_t i = 8; i < 248; i += 8) { + run_test(8, composer); + } + auto prover = composer.create_prover(); + plonk::proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +// Tests the constraints will fail if the operands are larger than expected even though the result contains the correct +// number of bits when using the UltraComposer This is because the range constraints on the right and left operand are +// not being satisfied. +TYPED_TEST(LogicTest, LargeOperands) +{ + STDLIB_TYPE_ALIASES + auto composer = Composer(); + + uint256_t mask = (uint256_t(1) << 48) - 1; + uint256_t a = engine.get_random_uint256() & mask; + uint256_t b = engine.get_random_uint256() & mask; + + uint256_t expected_mask = (uint256_t(1) << 40) - 1; + uint256_t and_expected = (a & b) & expected_mask; + uint256_t xor_expected = (a ^ b) & expected_mask; + + field_ct x = witness_ct(&composer, a); + field_ct y = witness_ct(&composer, b); + + field_ct xor_result = stdlib::logic::create_logic_constraint(x, y, 40, true); + field_ct and_result = stdlib::logic::create_logic_constraint(x, y, 40, false); + EXPECT_EQ(uint256_t(and_result.get_value()), and_expected); + EXPECT_EQ(uint256_t(xor_result.get_value()), xor_expected); + + auto prover = composer.create_prover(); + plonk::proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, true); +} + +// Ensures that malicious witnesses which produce the same result are detected. This potential security issue cannot +// happen if the composer doesn't support lookup gates because constraints will be created for each bit of the left and +// right operand. +TYPED_TEST(LogicTest, DifferentWitnessSameResult) { - TestFixture::test_logic(); + + STDLIB_TYPE_ALIASES + auto composer = Composer(); + if (Composer::type == ComposerType::PLOOKUP) { + uint256_t a = 3758096391; + uint256_t b = 2147483649; + field_ct x = witness_ct(&composer, uint256_t(a)); + field_ct y = witness_ct(&composer, uint256_t(b)); + + uint256_t xor_expected = a ^ b; + const std::function(uint256_t, uint256_t, size_t)>& get_bad_chunk = + [](uint256_t left, uint256_t right, size_t chunk_size) { + (void)left; + (void)right; + (void)chunk_size; + auto left_chunk = uint256_t(2684354565); + auto right_chunk = uint256_t(3221225475); + return std::make_pair(left_chunk, right_chunk); + }; + + field_ct xor_result = stdlib::logic::create_logic_constraint(x, y, 32, true, get_bad_chunk); + EXPECT_EQ(uint256_t(xor_result.get_value()), xor_expected); + + auto prover = composer.create_prover(); + plonk::proof proof = prover.construct_proof(); + auto verifier = composer.create_verifier(); + bool result = verifier.verify_proof(proof); + EXPECT_EQ(result, false); + } } + } // namespace test_stdlib_logic \ No newline at end of file diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index db3d95c51b..a411e64837 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -183,7 +183,7 @@ template class Transcript { } const size_t bytes_per_element = 31; - // split work_element into 2 limbs and insert into element_buffer + // split element into 2 limbs and insert into element_buffer // each entry in element_buffer is 31 bytes const auto split = [&](field_pt& work_element, std::vector& element_buffer, @@ -301,8 +301,23 @@ template class Transcript { } byte_array compressed_buffer(T0); - byte_array base_hash = stdlib::blake3s(compressed_buffer); - + // TODO(@zac-williamson) make this a Poseidon hash + byte_array base_hash; + if constexpr (Composer::type == ComposerType::PLOOKUP) { + std::vector compression_buffer; + field_pt working_element(context); + size_t byte_counter = 0; + split(working_element, compression_buffer, field_pt(compressed_buffer), byte_counter, 32); + if (byte_counter != 0) { + const uint256_t down_shift = uint256_t(1) << uint256_t((bytes_per_element - byte_counter) * 8); + working_element = working_element / barretenberg::fr(down_shift); + working_element = working_element.normalize(); + compression_buffer.push_back(working_element); + } + base_hash = stdlib::pedersen_plookup_commitment::compress(compression_buffer); + } else { + base_hash = stdlib::blake3s(compressed_buffer); + } byte_array first(field_pt(0), 16); first.write(base_hash.slice(0, 16)); round_challenges.push_back(first); @@ -313,11 +328,30 @@ template class Transcript { round_challenges.push_back(second); } + // This block of code only executes for num_challenges > 2, which (currently) only happens in the nu round when + // we need to generate short scalars. In this case, we generate 32-byte challenges and split them in half to get + // the relevant challenges. for (size_t i = 2; i < num_challenges; i += 2) { byte_array rolling_buffer = base_hash; - rolling_buffer.write(byte_array(field_pt(i / 2), 1)); - byte_array hash_output = stdlib::blake3s(rolling_buffer); - + byte_array hash_output; + if constexpr (Composer::type == ComposerType::PLOOKUP) { + // TODO(@zac-williamson) make this a Poseidon hash not a Pedersen hash + std::vector compression_buffer; + field_pt working_element(context); + size_t byte_counter = 0; + split(working_element, compression_buffer, field_pt(rolling_buffer), byte_counter, 32); + split(working_element, compression_buffer, field_pt(field_pt(i / 2)), byte_counter, 1); + if (byte_counter != 0) { + const uint256_t down_shift = uint256_t(1) << uint256_t((bytes_per_element - byte_counter) * 8); + working_element = working_element / barretenberg::fr(down_shift); + working_element = working_element.normalize(); + compression_buffer.push_back(working_element); + } + hash_output = stdlib::pedersen_plookup_commitment::compress(compression_buffer); + } else { + rolling_buffer.write(byte_array(field_pt(i / 2), 1)); + hash_output = stdlib::blake3s(rolling_buffer); + } byte_array hi(field_pt(0), 16); hi.write(hash_output.slice(0, 16)); round_challenges.push_back(hi); diff --git a/cpp/src/barretenberg/transcript/transcript.cpp b/cpp/src/barretenberg/transcript/transcript.cpp index 91349628b9..9c448949c9 100644 --- a/cpp/src/barretenberg/transcript/transcript.cpp +++ b/cpp/src/barretenberg/transcript/transcript.cpp @@ -54,6 +54,19 @@ std::array Blake3sHasher::hash(std::ve return result; } +std::array Blake3sHasher::hash_plookup(std::vector const& buffer) +{ + // TODO(@zac-williamson) Change to call a Poseidon hash and create a PoseidonHasher + // (not making the name change right now as it will break concurrent work w. getting recursion working in Noir) + // We also need to implement a Poseidon gadget + std::vector compressed_buffer = crypto::pedersen_commitment::lookup::compress_native(buffer); + std::array result; + for (size_t i = 0; i < PRNG_OUTPUT_SIZE; ++i) { + result[i] = compressed_buffer[i]; + } + return result; +} + Transcript::Transcript(const std::vector& input_transcript, const Manifest input_manifest, const HashType hash_type, @@ -219,7 +232,7 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b } case HashType::PlookupPedersenBlake3s: { std::vector compressed_buffer = crypto::pedersen_commitment::lookup::compress_native(buffer); - base_hash = Blake3sHasher::hash(compressed_buffer); + base_hash = Blake3sHasher::hash_plookup(compressed_buffer); break; } default: { @@ -269,7 +282,7 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b break; } case HashType::PlookupPedersenBlake3s: { - hash_output = Blake3sHasher::hash(rolling_buffer); + hash_output = Blake3sHasher::hash_plookup(rolling_buffer); break; } default: { diff --git a/cpp/src/barretenberg/transcript/transcript.hpp b/cpp/src/barretenberg/transcript/transcript.hpp index 43d47e3972..92c73c77e4 100644 --- a/cpp/src/barretenberg/transcript/transcript.hpp +++ b/cpp/src/barretenberg/transcript/transcript.hpp @@ -22,6 +22,7 @@ struct Blake3sHasher { static constexpr size_t PRNG_OUTPUT_SIZE = 32; static std::array hash(std::vector const& input); + static std::array hash_plookup(std::vector const& input); }; enum HashType { Keccak256, PedersenBlake3s, PlookupPedersenBlake3s }; From 25f55742a84533a0e566da26356a755e5e68389f Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 25 Apr 2023 18:11:40 +0000 Subject: [PATCH 11/64] EnvReferenceString undefined reference to error workaround --- cpp/src/barretenberg/env/crs.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/cpp/src/barretenberg/env/crs.cpp b/cpp/src/barretenberg/env/crs.cpp index 80561d1c09..47e6b52b0a 100644 --- a/cpp/src/barretenberg/env/crs.cpp +++ b/cpp/src/barretenberg/env/crs.cpp @@ -3,17 +3,19 @@ #include "crs.hpp" #include "barretenberg/srs/reference_string/file_reference_string.hpp" +// TODO: Will get `undefined reference to `bbmalloc'` error when linking #include "barretenberg/ecc/curves/bn254/scalar_multiplication/c_bind.hpp" +#include "barretenberg/common/mem.hpp" const int NUM_POINTS_IN_TRANSCRIPT = 5040001; - extern "C" { /** * @brief In WASM, loads the verifier reference string. * Used in native code to quickly create an in-memory reference string. */ -uint8_t* env_load_verifier_crs() { +uint8_t* env_load_verifier_crs() +{ std::ifstream transcript; transcript.open("../srs_db/ignition/monomial/transcript00.dat", std::ifstream::binary); // We need two g2 points, each 64 bytes. @@ -22,7 +24,7 @@ uint8_t* env_load_verifier_crs() { transcript.seekg(28 + NUM_POINTS_IN_TRANSCRIPT * 64); transcript.read((char*)g2_points.data(), (std::streamsize)g2_points_size); transcript.close(); - auto* g2_points_copy = (uint8_t*)bbmalloc(g2_points_size); + auto* g2_points_copy = (uint8_t*)aligned_alloc(64, g2_points_size); memcpy(g2_points_copy, g2_points.data(), g2_points_size); return g2_points_copy; } @@ -33,20 +35,20 @@ uint8_t* env_load_verifier_crs() { * In native code, not intended to be used. * @param num_points The number of points to load. */ -uint8_t* env_load_prover_crs(size_t num_points) { +uint8_t* env_load_prover_crs(size_t num_points) +{ // Note: This implementation is only meant to be instructive. // This should only be used in c-binds to implement the C++ abstractions. std::ifstream transcript; transcript.open("../srs_db/ignition/monomial/transcript00.dat", std::ifstream::binary); // Each g1 point is 64 bytes. - size_t g1_points_size = (num_points) * 64; + size_t g1_points_size = (num_points)*64; std::vector g1_points(g1_points_size); transcript.seekg(28); transcript.read((char*)g1_points.data(), (std::streamsize)g1_points_size); transcript.close(); - auto* g1_points_copy = (uint8_t*)bbmalloc(g1_points_size); + auto* g1_points_copy = (uint8_t*)aligned_alloc(64, g1_points_size); memcpy(g1_points_copy, g1_points.data(), g1_points_size); return g1_points_copy; } - } \ No newline at end of file From 1c8cc772b123088e81a8589d5df50deb963f84dd Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 25 Apr 2023 18:23:02 +0000 Subject: [PATCH 12/64] add missing recursion_constraint field from acir_format tests --- cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 5c17c353c1..5c6534750a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -31,6 +31,7 @@ TEST(acir_format, test_a_single_constraint_no_pub_inputs) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { constraint }, }; @@ -129,6 +130,7 @@ TEST(acir_format, test_logic_gate_from_noir_circuit) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; @@ -193,6 +195,7 @@ TEST(acir_format, test_schnorr_verify_pass) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { poly_triple{ .a = schnorr_constraint.result, .b = schnorr_constraint.result, @@ -262,6 +265,7 @@ TEST(acir_format, test_schnorr_verify_small_range) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { poly_triple{ .a = schnorr_constraint.result, .b = schnorr_constraint.result, From 6109828cb940f8a1155d0f605b88037a504d44cb Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 25 Apr 2023 18:26:51 +0000 Subject: [PATCH 13/64] add recursion_constraints field back in acir_proofs.test inner circuit --- cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index b344086ca3..cb73eea86e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -87,6 +87,7 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; From 8358741f881a3b28534f0f9f7584d59266cedc86 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 25 Apr 2023 18:29:50 +0000 Subject: [PATCH 14/64] fix inner circuit in RecursionConstraint.TestRecursionConstraint --- .../barretenberg/dsl/acir_format/recursion_constraint.test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 2b23dcfa07..1b49c0f128 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -92,6 +92,7 @@ acir_format::Composer create_inner_circuit() .hash_to_field_constraints = {}, .pedersen_constraints = {}, .merkle_membership_constraints = {}, + .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; From 551277ba5d73667bbc3921585e7da2c5b9ada2b8 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 25 Apr 2023 15:23:26 -0400 Subject: [PATCH 15/64] Empty-Commit From ce46c01cf5d716810062ce848d0e78f6d04e0a28 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Thu, 27 Apr 2023 23:19:50 -0400 Subject: [PATCH 16/64] add verify_recursive_proof method for simulating recursive verification in the ACVM --- .../dsl/acir_proofs/acir_proofs.cpp | 91 ++++++++++++++++++- .../dsl/acir_proofs/acir_proofs.hpp | 7 ++ .../dsl/acir_proofs/acir_proofs.test.cpp | 2 + .../barretenberg/dsl/acir_proofs/c_bind.cpp | 16 ++++ .../barretenberg/dsl/acir_proofs/c_bind.hpp | 7 ++ 5 files changed, 121 insertions(+), 2 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 54ff5c9aa0..26ba502cd5 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -5,6 +5,7 @@ #include "barretenberg/dsl/types.hpp" #include "barretenberg/srs/reference_string/pippenger_reference_string.hpp" #include "barretenberg/plonk/proof_system/verification_key/sol_gen.hpp" +#include "barretenberg/stdlib/recursion/verifier/verifier.hpp" namespace acir_proofs { @@ -228,8 +229,9 @@ bool verify_proof(uint8_t const* g2x, plonk::proof pp = { std::vector(proof, proof + length) }; // For inner circuit use recursive prover and verifier, then for outer circuit use the normal prover and - // verifier Either need a context flag for recursive verify or a new_recursive_verify_proof method that uses - // regular UltraVerifier + // verifier. + // Either need a context flag for recursive verify or a new_recursive_verify_proof method that uses regular + // UltraVerifier if (is_recursive) { auto verifier = composer.create_verifier(); verified = verifier.verify_proof(pp); @@ -247,4 +249,89 @@ bool verify_proof(uint8_t const* g2x, return verified; } +/** + * @brief Enables simulation of recursion in the ACVM. In order to be able to simulate execute of ACIR, + * a DSL needs to be able to insert into a circuit's witness the correct output aggregation state of a recursive + * verification. The resulting aggergation state is what will be compared against in the recursion constraint. + * + * @param proof_buf - The proof to be verified + * @param proof_length - The length of the proof to enable reading the proof from buffer + * @param vk_buf - The verification key of the circuit being verified + * @param vk_length - The length of the verification eky to enable reading the proof from buffer + * @param public_inputs_buf - The public inputs of the proof being verified. To be included when construct the input + * aggregation state + * @param input_aggregation_obj_buf - The fields represnting the two G1 points that must be fed into a pairing. + * This will be empty if this is the first proof to be recursively. + * @param output_aggregation_obj_buf - Two G1 points that the top-level verifier needs to run a pairing upon to complete + * verification + */ +size_t verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint8_t const* public_inputs_buf, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf) +{ + // TODO: not doing anything with public_inputs_buf right now because we only have one layer of recursion + // and the previous aggregation state will be empty. When arbitrary depth recursion is available we will have to + // construct the correct input aggregation_state_ct + (void)public_inputs_buf; + (void)input_aggregation_obj_buf; + + acir_format::aggregation_state_ct previous_aggregation; + previous_aggregation.has_data = false; + + std::vector proof_fields(proof_length / 32); + std::vector key_fields(vk_length / 32); + for (size_t i = 0; i < proof_length / 32; i++) { + proof_fields[i] = acir_format::field_ct(barretenberg::fr::serialize_from_buffer(&proof_buf[i * 32])); + } + for (size_t i = 0; i < vk_length / 32; i++) { + key_fields[i] = acir_format::field_ct(barretenberg::fr::serialize_from_buffer(&vk_buf[i * 32])); + } + + acir_format::Composer composer; + + transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(1); + // We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. + // We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, + // or we need non-witness data to be provided as metadata in the ACIR opcode + std::shared_ptr vkey = + acir_format::verification_key_ct::template from_field_pt_vector(&composer, key_fields); + vkey->program_width = acir_format::noir_recursive_settings::program_width; + acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, 1); + acir_format::aggregation_state_ct result = + proof_system::plonk::stdlib::recursion::verify_proof_( + &composer, vkey, transcript, previous_aggregation); + + // just writing the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + const size_t output_size_bytes = 16 * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + + for (size_t i = 0; i < 4; ++i) { + auto field = result.P0.x.binary_basis_limbs[i].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 4; i < 8; ++i) { + auto field = result.P0.y.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 8; i < 12; ++i) { + auto field = result.P1.x.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 12; i < 16; ++i) { + auto field = result.P1.y.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + *output_aggregation_obj_buf = raw_buf; + + return output_size_bytes; +} + } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index 95556f11a7..e37756a5d6 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -28,5 +28,12 @@ bool verify_proof(uint8_t const* g2x, uint8_t* proof, uint32_t length, bool is_recursive); +size_t verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint8_t const* public_inputs_buf, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf); } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index cb73eea86e..4aaa74c59b 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -251,6 +251,8 @@ TEST(AcirProofs, TestSerializationWithRecursion) .q_c = -vk_hash_value, }; + std::cout << "vk_hash_value: " << vk_hash_value << std::endl; + std::vector witness; for (size_t i = 0; i < 18; ++i) { witness.emplace_back(0); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index ac26be3fd7..5ff7b29d96 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -72,4 +72,20 @@ WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, { return acir_proofs::verify_proof(g2x, vk_buf, constraint_system_buf, proof, length, is_recursive); } +WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint8_t const* public_inputs_buf, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf) +{ + return acir_proofs::verify_recursive_proof(proof_buf, + proof_length, + vk_buf, + vk_length, + public_inputs_buf, + input_aggregation_obj_buf, + output_aggregation_obj_buf); +} } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 39591b37d1..0db08d5573 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -34,4 +34,11 @@ WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, uint8_t* proof, uint32_t length, bool is_recursive); +WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint8_t const* public_inputs_buf, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf); } From 11e97beb64195b6b0d1b432bc9bd62e6cb77dbc5 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 28 Apr 2023 20:20:14 +0000 Subject: [PATCH 17/64] add ecc module to env package to fix linking of ennv_load_prover_crs and env_load_verifier_crs --- cpp/src/barretenberg/env/CMakeLists.txt | 2 +- cpp/src/barretenberg/env/crs.cpp | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/cpp/src/barretenberg/env/CMakeLists.txt b/cpp/src/barretenberg/env/CMakeLists.txt index f4a8428e63..e084984b85 100644 --- a/cpp/src/barretenberg/env/CMakeLists.txt +++ b/cpp/src/barretenberg/env/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(env) \ No newline at end of file +barretenberg_module(env ecc) \ No newline at end of file diff --git a/cpp/src/barretenberg/env/crs.cpp b/cpp/src/barretenberg/env/crs.cpp index 47e6b52b0a..923435057d 100644 --- a/cpp/src/barretenberg/env/crs.cpp +++ b/cpp/src/barretenberg/env/crs.cpp @@ -3,9 +3,7 @@ #include "crs.hpp" #include "barretenberg/srs/reference_string/file_reference_string.hpp" -// TODO: Will get `undefined reference to `bbmalloc'` error when linking #include "barretenberg/ecc/curves/bn254/scalar_multiplication/c_bind.hpp" -#include "barretenberg/common/mem.hpp" const int NUM_POINTS_IN_TRANSCRIPT = 5040001; @@ -24,7 +22,7 @@ uint8_t* env_load_verifier_crs() transcript.seekg(28 + NUM_POINTS_IN_TRANSCRIPT * 64); transcript.read((char*)g2_points.data(), (std::streamsize)g2_points_size); transcript.close(); - auto* g2_points_copy = (uint8_t*)aligned_alloc(64, g2_points_size); + auto* g2_points_copy = (uint8_t*)bbmalloc(g2_points_size); memcpy(g2_points_copy, g2_points.data(), g2_points_size); return g2_points_copy; } @@ -47,7 +45,7 @@ uint8_t* env_load_prover_crs(size_t num_points) transcript.seekg(28); transcript.read((char*)g1_points.data(), (std::streamsize)g1_points_size); transcript.close(); - auto* g1_points_copy = (uint8_t*)aligned_alloc(64, g1_points_size); + auto* g1_points_copy = (uint8_t*)bbmalloc(g1_points_size); memcpy(g1_points_copy, g1_points.data(), g1_points_size); return g1_points_copy; } From 941b27544c948bdb0c3ca0ae353dbea49c42e9c3 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 1 May 2023 15:31:57 +0000 Subject: [PATCH 18/64] add back reference strinng to stdlib recursion key to pass sol verifier generation tests --- cpp/src/barretenberg/solidity_helpers/CMakeLists.txt | 2 +- .../solidity_helpers/circuits/recursive_circuit.hpp | 5 ++--- .../stdlib/recursion/verification_key/verification_key.hpp | 4 +++- .../barretenberg/stdlib/recursion/verifier/verifier.test.cpp | 4 +++- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt b/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt index 085b5297e5..8ce6666afa 100644 --- a/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt +++ b/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt @@ -1,4 +1,4 @@ -barretenberg_module(stdlib_solidity_helpers plonk proof_system transcript crypto_pedersen_commitment polynomials crypto_sha256 ecc crypto_blake3s stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s stdlib_blake2s) +barretenberg_module(stdlib_solidity_helpers plonk proof_system transcript crypto_pedersen_commitment polynomials crypto_sha256 ecc crypto_blake3s stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s stdlib_blake2s srs) add_executable(solidity_key_gen key_gen.cpp) diff --git a/cpp/src/barretenberg/solidity_helpers/circuits/recursive_circuit.hpp b/cpp/src/barretenberg/solidity_helpers/circuits/recursive_circuit.hpp index 1226db4ae6..9f2f7786c3 100644 --- a/cpp/src/barretenberg/solidity_helpers/circuits/recursive_circuit.hpp +++ b/cpp/src/barretenberg/solidity_helpers/circuits/recursive_circuit.hpp @@ -106,12 +106,11 @@ template class RecursiveCircuit { public: static OuterComposer generate(std::string srs_path, uint256_t inputs[]) { - auto env_crs = std::make_unique(); - InnerComposer inner_composer = InnerComposer(srs_path); OuterComposer outer_composer = OuterComposer(srs_path); create_inner_circuit_no_tables(inner_composer, inputs); + auto circuit_output = create_outer_circuit(inner_composer, outer_composer); g1::affine_element P[2]; @@ -121,7 +120,7 @@ template class RecursiveCircuit { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); + P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); if (inner_proof_result != barretenberg::fq12::one()) { throw_or_abort("inner proof result != 1"); diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 48ce7c81ae..2cd486bbe1 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -174,6 +174,7 @@ template struct verification_key { std::shared_ptr key = std::make_shared(); // Native data: key->context = ctx; + key->reference_string = input_key->reference_string; key->polynomial_manifest = input_key->polynomial_manifest; // Circuit types: @@ -205,6 +206,7 @@ template struct verification_key { key->commitments.insert({ tag, typename Curve::g1_ct(value) }); } + key->reference_string = input_key->reference_string; key->polynomial_manifest = input_key->polynomial_manifest; return key; @@ -328,7 +330,7 @@ template struct verification_key { std::map commitments; // Native data: - + std::shared_ptr reference_string; PolynomialManifest polynomial_manifest; // Used to check in the circuit if a proof contains any aggregated state. bool contains_recursive_proof = false; diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp index ad28907bfb..9b57f7f8a6 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp @@ -287,13 +287,15 @@ template class stdlib_verifier : public testing::Test { create_inner_circuit(inner_composer, inner_inputs); auto circuit_output = create_outer_circuit(inner_composer, outer_composer); + std::cout << "circuit_output: " << circuit_output.aggregation_state << std::endl; g1::affine_element P[2]; P[0].x = barretenberg::fq(circuit_output.aggregation_state.P0.x.get_value().lo); P[0].y = barretenberg::fq(circuit_output.aggregation_state.P0.y.get_value().lo); P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); - + auto g2_lines = env_crs->get_verifier_crs()->get_precomputed_g2_lines(); + std::cout << "precompute g2 lines: " << g2_lines << std::endl; barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); From 6d86e39d4bc5f7acdcae4e5b41be48de57137d37 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 1 May 2023 15:33:44 +0000 Subject: [PATCH 19/64] remove prints from recursive verifier test --- .../barretenberg/stdlib/recursion/verifier/verifier.test.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp index 9b57f7f8a6..ad28907bfb 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp @@ -287,15 +287,13 @@ template class stdlib_verifier : public testing::Test { create_inner_circuit(inner_composer, inner_inputs); auto circuit_output = create_outer_circuit(inner_composer, outer_composer); - std::cout << "circuit_output: " << circuit_output.aggregation_state << std::endl; g1::affine_element P[2]; P[0].x = barretenberg::fq(circuit_output.aggregation_state.P0.x.get_value().lo); P[0].y = barretenberg::fq(circuit_output.aggregation_state.P0.y.get_value().lo); P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); - auto g2_lines = env_crs->get_verifier_crs()->get_precomputed_g2_lines(); - std::cout << "precompute g2 lines: " << g2_lines << std::endl; + barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); From 5a458d65e41cbc27e0a03cda759685f8f678dcf1 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Wed, 3 May 2023 18:54:55 +0000 Subject: [PATCH 20/64] fix dirty free for when serializing vk to fields, was working on macbook so not caught earlier, but need more clarity on ubuntu --- cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp | 7 +++++-- cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 2 -- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 26ba502cd5..527d944b61 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -149,18 +149,21 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, // NOTE: this output buffer will always have a fixed size! Maybe just precompute? // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + auto vk_hash_raw_buf = (uint8_t*)malloc(32); // The serialization code below will convert out of Montgomery form before writing to the buffer - for (size_t i = 0; i < output.size(); ++i) { + for (size_t i = 0; i < output.size() - 1; ++i) { barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); } + barretenberg::fr::serialize_to_buffer(output[output.size() - 1], vk_hash_raw_buf); // copy the vkey into serialized_vk_buf *serialized_vk_buf = raw_buf; // copy the vkey hash into serialized_vk_hash_buf - *serialized_vk_hash_buf = &raw_buf[(output.size() - 1) * 32]; + *serialized_vk_hash_buf = vk_hash_raw_buf; return output_size_bytes; } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 91ccff081a..bba475b798 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -251,8 +251,6 @@ TEST(AcirProofs, TestSerializationWithRecursion) .q_c = -vk_hash_value, }; - std::cout << "vk_hash_value: " << vk_hash_value << std::endl; - std::vector witness; for (size_t i = 0; i < 18; ++i) { witness.emplace_back(0); From 07a22f6e3be6d9b2be90c9b8f663299be33f0786 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 5 May 2023 20:06:11 +0000 Subject: [PATCH 21/64] fix ecdsa tests after master merge --- .../stdlib/encryption/ecdsa/ecdsa.test.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp index 78ac01571f..ca1f4a4940 100644 --- a/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp +++ b/cpp/src/barretenberg/stdlib/encryption/ecdsa/ecdsa.test.cpp @@ -80,8 +80,11 @@ TEST(stdlib_ecdsa, verify_signature_noassert_succeed) std::vector rr(signature.r.begin(), signature.r.end()); std::vector ss(signature.s.begin(), signature.s.end()); + uint8_t vv = signature.v; - stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), + curve::byte_array_ct(&composer, ss), + stdlib::uint8(&composer, vv) }; curve::byte_array_ct message(&composer, message_string); @@ -125,8 +128,11 @@ TEST(stdlib_ecdsa, verify_signature_noassert_fail) std::vector rr(signature.r.begin(), signature.r.end()); std::vector ss(signature.s.begin(), signature.s.end()); + uint8_t vv = signature.v; - stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), curve::byte_array_ct(&composer, ss) }; + stdlib::ecdsa::signature sig{ curve::byte_array_ct(&composer, rr), + curve::byte_array_ct(&composer, ss), + stdlib::uint8(&composer, vv) }; curve::byte_array_ct message(&composer, message_string); From 74a8327b40ee59cc858f9cb325c380b5ed905fb1 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 5 May 2023 20:15:14 +0000 Subject: [PATCH 22/64] missing keccak constraints fields in acir format and proofs tests --- cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp | 2 ++ .../barretenberg/dsl/acir_format/recursion_constraint.test.cpp | 2 ++ cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index 9045bab14b..57ce16d248 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -88,6 +88,7 @@ TEST(ECDSASecp256k1, TestECDSAConstraintSucceed) .ecdsa_constraints = { ecdsa_constraint }, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, @@ -127,6 +128,7 @@ TEST(ECDSASecp256k1, TestECDSAConstraintFail) .ecdsa_constraints = { ecdsa_constraint }, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 63b051c478..d68444bc20 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -89,6 +89,7 @@ acir_format::Composer create_inner_circuit() .ecdsa_constraints = {}, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, @@ -178,6 +179,7 @@ TEST(RecursionConstraint, TestRecursionConstraint) .ecdsa_constraints = {}, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index bba475b798..5159cdee9a 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -84,6 +84,7 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect .ecdsa_constraints = {}, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, @@ -272,6 +273,7 @@ TEST(AcirProofs, TestSerializationWithRecursion) .ecdsa_constraints = {}, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, From 18cfb94505ce5e5c65feb7d361a6d29bdb2f7de5 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 5 May 2023 20:19:47 +0000 Subject: [PATCH 23/64] one more missing keccak constraint --- cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 13833e0003..ad8506481a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -28,6 +28,7 @@ TEST(acir_format, test_a_single_constraint_no_pub_inputs) .ecdsa_constraints = {}, .sha256_constraints = {}, .blake2s_constraints = {}, + .keccak_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, From 155903e1c129576d054e332d2d06208051e854a8 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 15 May 2023 18:21:12 +0000 Subject: [PATCH 24/64] mismatched acir format structs for gcc build --- cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp | 1 + cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp | 1 + cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp | 1 + .../barretenberg/dsl/acir_format/recursion_constraint.test.cpp | 1 + cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 1 + 5 files changed, 5 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index cd1e4c8e5a..e7c9a53264 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -33,6 +33,7 @@ TEST(acir_format, test_a_single_constraint_no_pub_inputs) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, + .block_constraints = {}, .recursion_constraints = {}, .constraints = { constraint }, }; diff --git a/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 66ab73e84a..ae122849cd 100644 --- a/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -117,6 +117,7 @@ TEST(up_ram, TestBlockConstraint) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = { block }, + .recursion_constraints = {}, .constraints = {}, }; diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index b405fcdb55..f0b1aab787 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -135,6 +135,7 @@ TEST(ECDSASecp256k1, TestECDSACompilesForVerifier) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = {}, }; auto crs_factory = std::make_unique(); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index d68444bc20..c9904740fe 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -93,6 +93,7 @@ acir_format::Composer create_inner_circuit() .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, + .block_constraints = {}, .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 5159cdee9a..00f3b3c26c 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -88,6 +88,7 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, + .block_constraints = {}, .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; From 683a8759f80890b718e17ceaf6af1b12684a9003 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 15 May 2023 18:43:58 +0000 Subject: [PATCH 25/64] missing block constraints in acir tests --- .../barretenberg/dsl/acir_format/recursion_constraint.test.cpp | 1 + cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index c9904740fe..53fe69b979 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -184,6 +184,7 @@ TEST(RecursionConstraint, TestRecursionConstraint) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, + .block_constraints = {}, .recursion_constraints = { recursion_constraint }, .constraints = {}, }; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 00f3b3c26c..08e4e8fbea 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -278,6 +278,7 @@ TEST(AcirProofs, TestSerializationWithRecursion) .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, + .block_constraints = {}, .recursion_constraints = { recursion_constraint }, .constraints = { vk_equality_constraint }, }; From 7ecb574fd52fcc99de862b7ca75d7d0d9e8a5673 Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Tue, 16 May 2023 18:20:32 +0100 Subject: [PATCH 26/64] feat(dsl)!: Arbitrary depth recursion (#433) * merge conflicts and small updates to get acir_proofs test passing with arbitrary depth cahnges * inline with mv/noir-recursion and working double recursion acir_proofs serialization test * cleanup and working towards supporting full nested proofs, still some bugs in acir_proofs test * full recursive composition test working in acir_proofs * use two public inptus for inner circuit * delete commented out unnecessary acir proofs method * made dummy transcript points unique to prevent point collisions * handle nested proofs when exporting dummy transcript, export recursive proof indices when init vkey, fix full recursive comp test in acir_proofs * update bindings on verify_recursive_proof to accept num public inputs * missing acir_format field in tests * missing one more correct acir_format struct in recursion constraint test * cleanup and additional comment for recursion_constraint * fix up comment in recursion_constraint * remove unnecesary comments when we were including proof outputs as public inputs in the recursion constraint --------- Co-authored-by: zac-williamson --- .../dsl/acir_format/acir_format.cpp | 70 ++- .../dsl/acir_format/recursion_constraint.cpp | 39 +- .../dsl/acir_format/recursion_constraint.hpp | 33 +- .../acir_format/recursion_constraint.test.cpp | 209 ++++++--- .../dsl/acir_proofs/acir_proofs.cpp | 78 +++- .../dsl/acir_proofs/acir_proofs.hpp | 5 +- .../dsl/acir_proofs/acir_proofs.test.cpp | 415 +++++++++++------- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 9 +- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 5 +- .../honk/proof_system/ultra_prover.hpp | 3 +- .../plonk/composer/composer_base.hpp | 15 + .../plonk/composer/ultra_composer.hpp | 17 + .../verification_key/verification_key.cpp | 10 +- .../verification_key/verification_key.hpp | 13 +- .../transcript/transcript_wrappers.hpp | 38 +- 15 files changed, 670 insertions(+), 289 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 28343d3b1f..97ce130665 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -95,8 +95,18 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } } @@ -189,8 +199,18 @@ Composer create_circuit(const acir_format& constraint_system, } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -288,8 +308,18 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -384,8 +414,18 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -478,8 +518,18 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } } diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 441b47c2d8..1fcf95f594 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -22,9 +22,15 @@ void generate_dummy_proof() {} * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, * or we need non-witness data to be provided as metadata in the ACIR opcode */ -template +template void create_recursion_constraints(Composer& composer, const RecursionConstraint& input) { + const auto& nested_aggregation_indices = input.nested_aggregation_object; + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_indices) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; // If we do not have a witness, we must ensure that our dummy witness will not trigger // on-curve errors and inverting-zero errors @@ -32,9 +38,10 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // get a fake key/proof that satisfies on-curve + inversion-zero checks const std::vector dummy_key = plonk::verification_key::export_dummy_key_in_recursion_format( PolynomialManifest(Composer::type), inner_proof_contains_recursive_proof); - const auto manifest = Composer::create_unrolled_manifest(1); + const auto manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); const std::vector dummy_proof = - transcript::StandardTranscript::export_dummy_transcript_in_recursion_format(manifest); + transcript::StandardTranscript::export_dummy_transcript_in_recursion_format( + manifest, inner_proof_contains_recursive_proof); for (size_t i = 0; i < input.proof.size(); ++i) { const auto proof_field_idx = input.proof[i]; // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), @@ -88,12 +95,14 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& previous_aggregation.has_data = false; } - transcript::Manifest manifest = Composer::create_unrolled_manifest(1); + transcript::Manifest manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); + std::vector key_fields; key_fields.reserve(input.key.size()); for (const auto& idx : input.key) { key_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); } + std::vector proof_fields; proof_fields.reserve(input.proof.size()); for (const auto& idx : input.proof) { @@ -101,24 +110,22 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } // recursively verify the proof - std::shared_ptr vkey = - verification_key_ct::template from_field_pt_vector(&composer, key_fields); + std::shared_ptr vkey = verification_key_ct::from_field_pt_vector( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); vkey->program_width = noir_recursive_settings::program_width; - Transcript_ct transcript(&composer, manifest, proof_fields, 1); + Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); // Assign correct witness value to the verification key hash vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); - // Assign the output aggregation object to the proof public inputs (16 field elements representing two - // G1 points) - result.add_proof_outputs_as_public_inputs(); - - ASSERT(result.public_inputs.size() == 1); + ASSERT(result.public_inputs.size() == input.public_inputs.size()); // Assign the `public_input` field to the public input of the inner proof - result.public_inputs[0].assert_equal(field_ct::from_witness_index(&composer, input.public_input)); + for (size_t i = 0; i < input.public_inputs.size(); ++i) { + result.public_inputs[i].assert_equal(field_ct::from_witness_index(&composer, input.public_inputs[i])); + } // Assign the recursive proof outputs to `output_aggregation_object` for (size_t i = 0; i < result.proof_witness_indices.size(); ++i) { @@ -128,9 +135,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } } -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index cfc0116349..0a0ca53d12 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -24,40 +24,52 @@ namespace acir_format { * (and therefore an aggregation object is present) * @param public_input The index of the single public input * @param input_aggregation_object Witness indices of pre-existing aggregation object (if it exists) - * @param output_aggregation_object Witness indecies of the aggregation object produced by recursive verification + * @param output_aggregation_object Witness indices of the aggregation object produced by recursive verification + * @param nested_aggregation_object Public input indices of an aggregation object inside the proof. * * @note If input_aggregation_object witness indices are all zero, we interpret this to mean that the inner proof does - * NOT contain + * NOT contain a previously recursively verified proof + * @note nested_aggregation_object is used for cases where the proof being verified contains an aggregation object in + * its public inputs! If this is the case, we record the public input locations in `nested_aggregation_object`. If the + * inner proof is of a circuit that does not have a nested aggregation object, these values are all zero. + * + * To outline the interaction between the input_aggergation_object and the nested_aggregation_object take the following + * example: If we have a circuit that verifies 2 proofs A and B, the recursion constraint for B will have an + * input_aggregation_object that points to the aggregation output produced by verifying A. If circuit B also verifies a + * proof, in the above example the recursion constraint for verifying B will have a nested object that describes the + * aggregation object in B’s public inputs as well as an input aggregation object that points to the object produced by + * the previous recursion constraint in the circuit (the one that verifies A) + * */ struct RecursionConstraint { static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements std::vector key; std::vector proof; - uint32_t public_input; + std::vector public_inputs; uint32_t key_hash; std::array input_aggregation_object; std::array output_aggregation_object; + std::array nested_aggregation_object; friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; }; -template +template void create_recursion_constraints(Composer& composer, const RecursionConstraint& input); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; read(buf, constraint.key); read(buf, constraint.proof); - read(buf, constraint.public_input); + read(buf, constraint.public_inputs); read(buf, constraint.key_hash); read(buf, constraint.input_aggregation_object); read(buf, constraint.output_aggregation_object); + read(buf, constraint.nested_aggregation_object); } template inline void write(B& buf, RecursionConstraint const& constraint) @@ -65,10 +77,11 @@ template inline void write(B& buf, RecursionConstraint const& const using serialize::write; write(buf, constraint.key); write(buf, constraint.proof); - write(buf, constraint.public_input); + write(buf, constraint.public_inputs); write(buf, constraint.key_hash); write(buf, constraint.input_aggregation_object); write(buf, constraint.output_aggregation_object); + write(buf, constraint.nested_aggregation_object); } } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 53fe69b979..c6b511d662 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -75,13 +75,10 @@ acir_format::Composer create_inner_circuit() .q_o = 0, .q_c = 1, }; - // EXPR [ (1, _4, _5) (-1, _6) 0 ] - // EXPR [ (1, _4, _6) (-1, _4) 0 ] - // EXPR [ (-1, _6) 1 ] acir_format::acir_format constraint_system{ .varnum = 7, - .public_inputs = { 2 }, + .public_inputs = { 2, 3 }, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -112,67 +109,101 @@ acir_format::Composer create_inner_circuit() return composer; } -TEST(RecursionConstraint, TestRecursionConstraint) +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +acir_format::Composer create_outer_circuit(std::vector& inner_composers) { - auto inner_composer = create_inner_circuit(); + std::vector recursion_constraints; - auto inner_prover = inner_composer.create_prover(); - auto inner_proof = inner_prover.construct_proof(); - auto inner_verifier = inner_composer.create_verifier(); + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; - // std::vector keybuf; - // write(keybuf, *(inner_verifier.key)); + for (size_t i = 0; i < inner_composers.size(); ++i) { + const bool has_input_aggregation_object = i > 0; - std::array output_vars; - for (size_t i = 0; i < 16; ++i) { - // variable idx 1 = public input - // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 3)); - } + auto& inner_composer = inner_composers[i]; + auto inner_prover = inner_composer.create_prover(); + auto inner_proof = inner_prover.construct_proof(); + auto inner_verifier = inner_composer.create_verifier(); - transcript::StandardTranscript transcript(inner_proof.proof_data, - acir_format::Composer::create_manifest(1), - transcript::HashType::PlookupPedersenBlake3s, - 16); + const bool has_nested_proof = inner_verifier.key->contains_recursive_proof; + const size_t num_inner_public_inputs = inner_composer.get_num_public_inputs(); - const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); - const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); + transcript::StandardTranscript transcript(inner_proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); - std::vector proof_indices; - const size_t proof_size = proof_witnesses.size(); + const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); + const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); - for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 19)); - } + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); - std::vector key_indices; - const size_t key_size = key_witnesses.size(); - for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 19 + proof_size)); - } - acir_format::RecursionConstraint recursion_constraint{ - .key = key_indices, - .proof = proof_indices, - .public_input = 1, - .key_hash = 2, - .input_aggregation_object = {}, - .output_aggregation_object = output_vars, - }; + std::vector proof_indices; + std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 0; i < 16; ++i) { + nested_aggregation_object[i] = inner_composer.recursive_proof_public_input_indices[i]; + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); + } + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); + } - std::vector witness; - for (size_t i = 0; i < 18; ++i) { - witness.emplace_back(0); - } - for (const auto& wit : proof_witnesses) { - witness.emplace_back(wit); - } - for (const auto& wit : key_witnesses) { - witness.emplace_back(wit); + acir_format::RecursionConstraint recursion_constraint{ + .key = key_indices, + .proof = proof_indices, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, + }; + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { + witness.emplace_back(0); + } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } + witness_offset = key_indices_start_idx + key_witnesses.size(); } + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); + acir_format::acir_format constraint_system{ .varnum = static_cast(witness.size() + 1), - .public_inputs = { 1 }, + .public_inputs = public_inputs, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = {}, .range_constraints = {}, @@ -185,16 +216,84 @@ TEST(RecursionConstraint, TestRecursionConstraint) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, - .recursion_constraints = { recursion_constraint }, + .recursion_constraints = recursion_constraints, .constraints = {}, }; auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); - auto prover = composer.create_prover(); + return composer; +} + +TEST(RecursionConstraint, TestBasicDoubleRecursionConstraints) +{ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + + layer_1_composers.push_back(create_inner_circuit()); + + auto layer_2_composer = create_outer_circuit(layer_1_composers); + + std::cout << "composer gates = " << layer_2_composer.get_num_gates() << std::endl; + auto prover = layer_2_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; auto proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + auto verifier = layer_2_composer.create_ultra_with_keccak_verifier(); EXPECT_EQ(verifier.verify_proof(proof), true); +} - EXPECT_EQ(composer.get_variable(1), 10); +TEST(RecursionConstraint, TestFullRecursionConstraints) +{ + /** + * We want to test the following: + * 1. circuit that verifies a proof of another circuit + * 2. the above, but the inner circuit contains a recursive proof output that we have to aggregate + * 3. the above, but the outer circuit verifies 2 proofs, the aggregation outputs from the 2 proofs (+ the recursive + * proof output from 2) are aggregated together + * + * A = basic circuit + * B = circuit that verifies proof of A + * C = circuit that verifies proof of B and a proof of A + * + * Layer 1 = proof of A + * Layer 2 = verifies proof of A and proof of B + * Layer 3 = verifies proof of C + * + * Attempt at a visual graphic + * =========================== + * + * C + * ^ + * | + * | - B + * ^ ^ + * | | + * | -A + * | + * - A + * + * =========================== + * + * Final aggregation object contains aggregated proofs for 2 instances of A and 1 instance of B + */ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + std::cout << "created first inner circuit\n"; + std::vector layer_2_composers; + + layer_2_composers.push_back(create_inner_circuit()); + std::cout << "created second inner circuit\n"; + + layer_2_composers.push_back(create_outer_circuit(layer_1_composers)); + std::cout << "created first outer circuit\n"; + + auto layer_3_composer = create_outer_circuit(layer_2_composers); + std::cout << "created second outer circuit\n"; + + std::cout << "composer gates = " << layer_3_composer.get_num_gates() << std::endl; + auto prover = layer_3_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; + auto proof = prover.construct_proof(); + auto verifier = layer_3_composer.create_ultra_with_keccak_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 527d944b61..4310342f7e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -51,12 +51,11 @@ size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk { auto constraint_system = from_buffer(constraint_system_buf); - // constraint_system.recursion_constraints[0]. - // We know that we don't actually need any CRS to create a proving key, so just feed in a nothing. // Hacky, but, right now it needs *something*. auto crs_factory = std::make_unique(); auto composer = create_circuit(constraint_system, std::move(crs_factory)); + auto proving_key = composer.compute_proving_key(); auto buffer = to_buffer(*proving_key); @@ -86,6 +85,18 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* // construct the verification key so that we have the correct polynomial manifest verification_key->composer_type = proof_system::ComposerType::PLOOKUP; + // Set the recursive proof indices as this is not done in `compute_verification_key_base` + verification_key->contains_recursive_proof = proving_key->contains_recursive_proof; + for (size_t i = 0; i < 16; ++i) { + if (proving_key->recursive_proof_public_input_indices.size() > i) { + verification_key->recursive_proof_public_input_indices.emplace_back( + proving_key->recursive_proof_public_input_indices[i]); + } else { + verification_key->recursive_proof_public_input_indices.emplace_back(0); + ASSERT(verification_key->contains_recursive_proof == false); + } + } + auto buffer = to_buffer(*verification_key); auto raw_buf = (uint8_t*)malloc(buffer.size()); memcpy(raw_buf, (void*)buffer.data(), buffer.size()); @@ -104,12 +115,15 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* */ size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length) + size_t proof_data_length, + size_t num_inner_public_inputs) { plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; - transcript::StandardTranscript transcript( - proof.proof_data, acir_format::Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + transcript::StandardTranscript transcript(proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); std::vector output = transcript.export_transcript_in_recursion_format(); @@ -143,7 +157,6 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, plonk::verification_key_data vk_data; read(vk_buf, vk_data); auto vkey = std::make_shared(std::move(vk_data), crs); - std::vector output = vkey->export_key_in_recursion_format(); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? @@ -272,18 +285,36 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf) { - // TODO: not doing anything with public_inputs_buf right now because we only have one layer of recursion - // and the previous aggregation state will be empty. When arbitrary depth recursion is available we will have to - // construct the correct input aggregation_state_ct - (void)public_inputs_buf; - (void)input_aggregation_obj_buf; + + bool inner_aggregation_all_zero = true; + std::vector aggregation_input(16); + for (size_t i = 0; i < 16; i++) { + aggregation_input[i] = barretenberg::fr::serialize_from_buffer(&input_aggregation_obj_buf[i * 32]); + inner_aggregation_all_zero &= (aggregation_input[i].is_zero()); + } acir_format::aggregation_state_ct previous_aggregation; - previous_aggregation.has_data = false; + if (!inner_aggregation_all_zero) { + std::array aggregation_elements; + for (size_t i = 0; i < 4; ++i) { + aggregation_elements[i] = acir_format::bn254::fq_ct(acir_format::field_ct(aggregation_input[4 * i]), + acir_format::field_ct(aggregation_input[4 * i + 1]), + acir_format::field_ct(aggregation_input[4 * i + 2]), + acir_format::field_ct(aggregation_input[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included + // in stdlib::recursion::verify_proof + previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); + previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); + previous_aggregation.has_data = true; + } else { + previous_aggregation.has_data = false; + } std::vector proof_fields(proof_length / 32); std::vector key_fields(vk_length / 32); @@ -296,19 +327,30 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, acir_format::Composer composer; - transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(1); + transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); // We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. // We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, // or we need non-witness data to be provided as metadata in the ACIR opcode - std::shared_ptr vkey = - acir_format::verification_key_ct::template from_field_pt_vector(&composer, key_fields); + // recursively verify the proof + std::array nested_aggregation_object = {}; + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); + } + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_object) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + std::shared_ptr vkey = acir_format::verification_key_ct::from_field_pt_vector( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); vkey->program_width = acir_format::noir_recursive_settings::program_width; - acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, 1); + acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, num_public_inputs); acir_format::aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); - // just writing the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + // Just write the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + // as this should all be available elsewhere const size_t output_size_bytes = 16 * sizeof(barretenberg::fr); auto raw_buf = (uint8_t*)malloc(output_size_bytes); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index e37756a5d6..cad0d1a79f 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -14,7 +14,8 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, uint8_t** serialized_vk_hash_buf); size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length); + size_t proof_data_length, + size_t num_inner_public_inputs); size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -32,7 +33,7 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 08e4e8fbea..a5554d1b82 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -76,7 +76,7 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect constraint_system = acir_format::acir_format{ .varnum = 7, - .public_inputs = { 2 }, + .public_inputs = { 2, 3 }, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -141,7 +141,13 @@ TEST(AcirProofs, TestSerialization) EXPECT_EQ(verified, true); } -TEST(AcirProofs, TestSerializationWithRecursion) +struct RecursiveCircuitData { + std::vector key_witnesses; + std::vector proof_witnesses; + size_t num_public_inputs; +}; + +RecursiveCircuitData fetch_recursive_circuit_data(acir_format::acir_format constraint_system, std::vector witness) { uint8_t* proof_data_fields = nullptr; uint8_t* vk_fields = nullptr; @@ -149,112 +155,148 @@ TEST(AcirProofs, TestSerializationWithRecursion) size_t proof_fields_size = 0; size_t vk_fields_size = 0; - // inner circuit - { - acir_format::acir_format constraint_system; - std::vector witness; - create_inner_circuit(constraint_system, witness); - - std::vector witness_buf; // = to_buffer(witness); - std::vector constraint_system_buf; // - write(constraint_system_buf, constraint_system); - write(witness_buf, witness); - - uint8_t const* pk_buf = nullptr; - acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); - - uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); - uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); - auto env_crs = std::make_unique(); - auto verifier_crs = env_crs->get_verifier_crs(); - barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); - - auto* pippenger_ptr_base = - new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); - void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); - - std::vector g2x_buffer(128); - io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); - - uint8_t const* vk_buf = nullptr; - acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); - - uint8_t* proof_data_buf = nullptr; - size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); - proof_fields_size = - acir_proofs::serialize_proof_into_field_elements(proof_data_buf, &proof_data_fields, proof_length); - vk_fields_size = acir_proofs::serialize_verification_key_into_field_elements( - &g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); - - bool verified = acir_proofs::verify_proof(&g2x_buffer[0], - vk_buf, - &constraint_system_buf[0], - proof_data_buf, - static_cast(proof_length), - true); - EXPECT_EQ(verified, true); - - delete pippenger_ptr_base; - free((void*)vk_buf); - free((void*)pk_buf); - free((void*)proof_data_buf); + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + auto num_public_inputs = constraint_system.public_inputs.size(); + proof_fields_size = acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, &proof_data_fields, proof_length, num_public_inputs); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + + fr vk_hash_value; + std::vector proof_witnesses(proof_fields_size / 32); + std::vector key_witnesses((vk_fields_size / 32) + 1); + for (size_t i = 0; i < proof_fields_size / 32; i++) { + proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); + } + for (size_t i = 0; i < vk_fields_size / 32; i++) { + key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); } - // outer circuit - { - fr vk_hash_value; - std::vector proof_witnesses(proof_fields_size / 32); - std::vector key_witnesses(vk_fields_size / 32); - for (size_t i = 0; i < proof_fields_size / 32; i++) { - proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); - } - for (size_t i = 0; i < vk_fields_size / 32; i++) { - key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); - } - vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); - std::vector proof_indices; + free((void*)proof_data_fields); + free((void*)vk_fields); - const size_t proof_size = proof_witnesses.size(); - for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 19)); - } + vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); + key_witnesses[vk_fields_size / 32] = vk_hash_value; + auto inner_circuit = RecursiveCircuitData{ key_witnesses, proof_witnesses, num_public_inputs }; + return inner_circuit; +} + +RecursiveCircuitData fetch_inner_circuit_data() +{ + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + return fetch_recursive_circuit_data(constraint_system, witness); +} + +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +std::pair> create_outer_circuit( + std::vector& inner_circuits) +{ + std::vector recursion_constraints; + + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; + + for (size_t i = 0; i < inner_circuits.size(); ++i) { + const bool has_input_aggregation_object = i > 0; + + auto& inner_circuit = inner_circuits[i]; + const std::vector proof_witnesses = inner_circuit.proof_witnesses; + const std::vector key_witnesses = inner_circuit.key_witnesses; + + const bool has_nested_proof = uint32_t(key_witnesses[5]); + const size_t num_inner_public_inputs = inner_circuit.num_public_inputs; + + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); + + std::vector proof_indices; std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_witnesses[i]); + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } const size_t key_size = key_witnesses.size(); for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 19 + proof_size)); + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); } - - std::array output_vars; - for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { - // variable idx 1 = public input - // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 3)); + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); } + acir_format::RecursionConstraint recursion_constraint{ .key = key_indices, .proof = proof_indices, - .public_input = 1, - .key_hash = 2, - .input_aggregation_object = {}, - .output_aggregation_object = output_vars, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, }; - - // Add a constraint that fixes the vk hash to be the expected value! - poly_triple vk_equality_constraint{ - .a = recursion_constraint.key_hash, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = 1, - .q_r = 0, - .q_o = 0, - .q_c = -vk_hash_value, - }; - - std::vector witness; - for (size_t i = 0; i < 18; ++i) { + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { witness.emplace_back(0); } for (const auto& wit : proof_witnesses) { @@ -263,66 +305,129 @@ TEST(AcirProofs, TestSerializationWithRecursion) for (const auto& wit : key_witnesses) { witness.emplace_back(wit); } + witness_offset = key_indices_start_idx + key_witnesses.size(); + } - acir_format::acir_format constraint_system{ - .varnum = static_cast(witness.size() + 1), - .public_inputs = { 1 }, - .fixed_base_scalar_mul_constraints = {}, - .logic_constraints = {}, - .range_constraints = {}, - .schnorr_constraints = {}, - .ecdsa_constraints = {}, - .sha256_constraints = {}, - .blake2s_constraints = {}, - .keccak_constraints = {}, - .hash_to_field_constraints = {}, - .pedersen_constraints = {}, - .compute_merkle_root_constraints = {}, - .block_constraints = {}, - .recursion_constraints = { recursion_constraint }, - .constraints = { vk_equality_constraint }, - }; + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); - std::vector witness_buf; - std::vector constraint_system_buf; - write(constraint_system_buf, constraint_system); - write(witness_buf, witness); - uint8_t const* pk_buf = nullptr; - acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); - - uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); - uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); - auto env_crs = std::make_unique(); - auto verifier_crs = env_crs->get_verifier_crs(); - barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); - - auto* pippenger_ptr_base = - new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); - void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); - - std::vector g2x_buffer(128); - io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); - - uint8_t const* vk_buf = nullptr; - acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); - - uint8_t* proof_data_buf = nullptr; - size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); - - bool verified = acir_proofs::verify_proof(&g2x_buffer[0], - vk_buf, - &constraint_system_buf[0], - proof_data_buf, - static_cast(proof_length), - false); - EXPECT_EQ(verified, true); - - delete pippenger_ptr_base; - free((void*)vk_buf); - free((void*)pk_buf); - free((void*)proof_data_buf); - free((void*)proof_data_fields); - free((void*)vk_fields); - } + acir_format::acir_format constraint_system{ + .varnum = static_cast(witness.size() + 1), + .public_inputs = public_inputs, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = recursion_constraints, + .constraints = {}, + }; + + return std::make_pair(constraint_system, witness); +} + +// Working double recursion test +TEST(AcirProofs, TestSerializationWithBasicDoubleRecursion) +{ + std::vector inner_circuits; + auto a = fetch_inner_circuit_data(); + inner_circuits.push_back(a); + + auto b = fetch_inner_circuit_data(); + inner_circuits.push_back(b); + + auto c = create_outer_circuit(inner_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c.first); + write(witness_buf, c.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); +} + +TEST(AcirProofs, TestSerializationWithFullRecursiveComposition) +{ + std::vector layer_1_circuits; + auto a_data_circuit = fetch_inner_circuit_data(); + layer_1_circuits.push_back(a_data_circuit); + + std::vector layer_2_circuits; + auto a2_circuit = fetch_inner_circuit_data(); + layer_2_circuits.push_back(a2_circuit); + + auto b_cs_and_witness = create_outer_circuit(layer_1_circuits); + auto b_circuit = fetch_recursive_circuit_data(b_cs_and_witness.first, b_cs_and_witness.second); + layer_2_circuits.push_back(b_circuit); + + auto c_cs_and_witness = create_outer_circuit(layer_2_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c_cs_and_witness.first); + write(witness_buf, c_cs_and_witness.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 5ff7b29d96..62621d00be 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -45,10 +45,11 @@ WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t c } WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length) + size_t proof_data_length, + size_t num_inner_public_inputs) { return acir_proofs::serialize_proof_into_field_elements( - proof_data_buf, serialized_proof_data_buf, proof_data_length); + proof_data_buf, serialized_proof_data_buf, proof_data_length, num_inner_public_inputs); } WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, @@ -76,7 +77,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf) { @@ -84,7 +85,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, proof_length, vk_buf, vk_length, - public_inputs_buf, + num_public_inputs, input_aggregation_obj_buf, output_aggregation_obj_buf); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 0db08d5573..452f41d1ca 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -20,7 +20,8 @@ WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t c uint8_t** serialized_vk_hash_buf); WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length); + size_t proof_data_length, + size_t num_inner_public_inputs); WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -38,7 +39,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); } diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp index 9bbe7314f2..bcf665711c 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -13,8 +13,7 @@ namespace proof_system::honk { // We won't compile this class with honk::flavor::Standard, but we will like want to compile it (at least for testing) // with a flavor that uses the curve Grumpkin, or a flavor that does/does not have zk, etc. -template -concept UltraFlavor = IsAnyOf; +template concept UltraFlavor = IsAnyOf; template class UltraProver_ { using FF = typename Flavor::FF; diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index a3e57bc383..b1109d2d4d 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -186,6 +186,21 @@ class ComposerBase { barretenberg::fr get_public_input(const uint32_t index) const { return get_variable(public_inputs[index]); } + uint32_t get_public_input_index(const uint32_t witness_index) const + { + bool found = false; + uint32_t result = static_cast(-1); + for (size_t i = 0; i < public_inputs.size(); ++i) { + if (real_variable_index[public_inputs[i]] == real_variable_index[witness_index]) { + found = true; + result = static_cast(i); + break; + } + } + ASSERT(found == true); + return result; + } + std::vector get_public_inputs() const { std::vector result; diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index 6f22b40bb0..6a73f5f2bc 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -261,6 +261,23 @@ class UltraComposer : public ComposerBase { } } + /** + * @brief Update recursive_proof_public_input_indices with existing public inputs that represent a recursive proof + * + * @param proof_output_witness_indices + */ + void set_recursive_proof(const std::vector& proof_output_witness_indices) + { + if (contains_recursive_proof) { + failure("added recursive proof when one already exists"); + } + contains_recursive_proof = true; + for (size_t i = 0; i < proof_output_witness_indices.size(); ++i) { + recursive_proof_public_input_indices.push_back( + get_public_input_index(real_variable_index[proof_output_witness_indices[i]])); + } + } + void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string const msg = "create_new_range_constraint"); diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index 743f563770..f99fb5aed7 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -258,7 +258,14 @@ std::vector verification_key::export_dummy_key_in_recursion_fo for (const auto& descriptor : polynomial_manifest.get()) { if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { - const auto element = barretenberg::g1::affine_one; + // the std::biggroup class creates unsatisfiable constraints when identical points are added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not identical. + // And prover points should contain randomness for an honest Prover). + // This check can also trigger a runtime error due to causing 0 to be inverted. + // When creating dummy verification key points we must be mindful of the above and make sure that each + // transcript point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); const uint256_t x = element.x; const uint256_t y = element.y; const barretenberg::fr x_lo = x.slice(0, 136); @@ -271,6 +278,7 @@ std::vector verification_key::export_dummy_key_in_recursion_fo output.emplace_back(y_hi); } } + output.emplace_back(0); // key_hash return output; diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 2cd486bbe1..818f1f9d9a 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -120,10 +120,10 @@ template struct evaluation_domain { template struct verification_key { using Composer = typename Curve::Composer; - template static std::shared_ptr from_field_pt_vector( Composer* ctx, const std::vector>& fields, + bool inner_proof_contains_recursive_proof = false, std::array recursive_proof_public_input_indices = {}) { std::vector fields_raw; @@ -138,15 +138,10 @@ template struct verification_key { // NOTE: For now `contains_recursive_proof` and `recursive_proof_public_input_indices` need to be circuit // constants! - key->contains_recursive_proof = static_cast(uint256_t(fields[5].get_value())); + key->contains_recursive_proof = inner_proof_contains_recursive_proof; for (size_t i = 0; i < 16; ++i) { - const uint32_t idx = static_cast(uint256_t(fields[6 + i].get_value())); - key->recursive_proof_public_input_indices.emplace_back(idx); - } - // Apply constraints to force the recursive proof information to be circuit constants - fields[5].assert_equal(inner_proof_contains_recursive_proof); - for (size_t i = 0; i < 16; ++i) { - fields[6 + i].assert_equal(recursive_proof_public_input_indices[i]); + auto x = recursive_proof_public_input_indices[i]; + key->recursive_proof_public_input_indices.emplace_back(x); } size_t count = 22; diff --git a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp index ad849889cc..2fe0830c37 100644 --- a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp +++ b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp @@ -104,7 +104,8 @@ class StandardTranscript : public Transcript { * @param manifest * @return std::vector */ - static std::vector export_dummy_transcript_in_recursion_format(const Manifest& manifest) + static std::vector export_dummy_transcript_in_recursion_format( + const Manifest& manifest, const bool contains_recursive_proof) { std::vector fields; const auto num_rounds = manifest.get_num_rounds(); @@ -114,7 +115,14 @@ class StandardTranscript : public Transcript { if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { fields.emplace_back(0); } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { - const auto group_element = barretenberg::g1::affine_one; + // the std::biggroup class creates unsatisfiable constraints when identical points are + // added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not + // identical. And prover points should contain randomness for an honest Prover). This check can + // also trigger a runtime error due to causing 0 to be inverted. When creating dummy proof + // points we must be mindful of the above and make sure that each point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); const uint256_t x = group_element.x; const uint256_t y = group_element.y; const barretenberg::fr x_lo = x.slice(0, 136); @@ -128,8 +136,30 @@ class StandardTranscript : public Transcript { } else { ASSERT(manifest_element.name == "public_inputs"); const size_t num_public_inputs = manifest_element.num_bytes / 32; - for (size_t j = 0; j < num_public_inputs; ++j) { - fields.emplace_back(0); + // If we have a recursive proofs the public inputs must describe an aggregation object that + // is composed of two valid G1 points on the curve. Without this conditional we will get a + // runtime error that we are attempting to invert 0. + if (contains_recursive_proof) { + ASSERT(num_public_inputs == 16); + for (size_t k = 0; k < num_public_inputs / 4; ++k) { + auto scalar = barretenberg::fr::random_element(); + const auto group_element = + barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } + } else { + for (size_t j = 0; j < num_public_inputs; ++j) { + fields.emplace_back(0); + } } } } From a2f6cbbd5bc78d69d36b4a5dd1458572b1946edd Mon Sep 17 00:00:00 2001 From: Maxim Vezenov Date: Mon, 22 May 2023 10:33:10 -0400 Subject: [PATCH 27/64] move export key into dsl package --- .../dsl/acir_format/recursion_constraint.cpp | 110 +++++++++++++++++- .../dsl/acir_format/recursion_constraint.hpp | 6 + .../acir_format/recursion_constraint.test.cpp | 3 +- .../dsl/acir_proofs/acir_proofs.cpp | 2 +- 4 files changed, 117 insertions(+), 4 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 1fcf95f594..d47e493427 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -36,8 +36,8 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // on-curve errors and inverting-zero errors { // get a fake key/proof that satisfies on-curve + inversion-zero checks - const std::vector dummy_key = plonk::verification_key::export_dummy_key_in_recursion_format( - PolynomialManifest(Composer::type), inner_proof_contains_recursive_proof); + const std::vector dummy_key = export_dummy_key_in_recursion_format(PolynomialManifest(Composer::type), + inner_proof_contains_recursive_proof); const auto manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); const std::vector dummy_proof = transcript::StandardTranscript::export_dummy_transcript_in_recursion_format( @@ -138,4 +138,110 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& template void create_recursion_constraints(Composer&, const RecursionConstraint&); template void create_recursion_constraints(Composer&, const RecursionConstraint&); +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * NOTE: only used by the dsl at the moment. Might be cleaner to make this a dsl function? + * + * @return std::vector + */ +std::vector export_key_in_recursion_format(std::shared_ptr const& vkey) +{ + std::vector output; + output.emplace_back(vkey->domain.root); + output.emplace_back(vkey->domain.domain); + output.emplace_back(vkey->domain.generator); + output.emplace_back(vkey->circuit_size); + output.emplace_back(vkey->num_public_inputs); + output.emplace_back(vkey->contains_recursive_proof); + for (size_t i = 0; i < 16; ++i) { + if (vkey->recursive_proof_public_input_indices.size() > i) { + output.emplace_back(vkey->recursive_proof_public_input_indices[i]); + } else { + output.emplace_back(0); + ASSERT(vkey->contains_recursive_proof == false); + } + } + for (const auto& descriptor : vkey->polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + const auto element = vkey->commitments.at(std::string(descriptor.commitment_label)); + const uint256_t x = element.x; + const uint256_t y = element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + output.emplace_back(x_lo); + output.emplace_back(x_hi); + output.emplace_back(y_lo); + output.emplace_back(y_hi); + } + } + + verification_key_data vkey_data{ + .composer_type = vkey->composer_type, + .circuit_size = static_cast(vkey->circuit_size), + .num_public_inputs = static_cast(vkey->num_public_inputs), + .commitments = vkey->commitments, + .contains_recursive_proof = vkey->contains_recursive_proof, + .recursive_proof_public_input_indices = vkey->recursive_proof_public_input_indices, + }; + output.emplace_back(vkey_data.compress_native(0)); // key_hash + return output; +} + +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * A dummy key is used when building a circuit without a valid witness assignment. + * We want the transcript to contain valid G1 points to prevent on-curve errors being thrown. + * We want a non-zero circuit size as this element will be inverted by the circuit + * and we do not want an "inverting 0" error thrown + * + * @return std::vector + */ +std::vector export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, + const bool contains_recursive_proof) +{ + std::vector output; + output.emplace_back(1); // domain.domain (will be inverted) + output.emplace_back(1); // domain.root (will be inverted) + output.emplace_back(1); // domain.generator (will be inverted) + + output.emplace_back(1); // circuit size + output.emplace_back(1); // num public inputs + + output.emplace_back(contains_recursive_proof); // contains_recursive_proof + for (size_t i = 0; i < 16; ++i) { + output.emplace_back(0); // recursive_proof_public_input_indices + } + + for (const auto& descriptor : polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + // the std::biggroup class creates unsatisfiable constraints when identical points are added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not identical. + // And prover points should contain randomness for an honest Prover). + // This check can also trigger a runtime error due to causing 0 to be inverted. + // When creating dummy verification key points we must be mindful of the above and make sure that each + // transcript point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + const uint256_t x = element.x; + const uint256_t y = element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + output.emplace_back(x_lo); + output.emplace_back(x_hi); + output.emplace_back(y_lo); + output.emplace_back(y_hi); + } + } + + output.emplace_back(0); // key_hash + + return output; +} + } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 0a0ca53d12..15af19039f 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -6,6 +6,8 @@ namespace acir_format { +using namespace proof_system::plonk; + /** * @brief RecursionConstraint struct contains information required to recursively verify a proof! * @@ -60,6 +62,10 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +std::vector export_key_in_recursion_format(std::shared_ptr const& vkey); +std::vector export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, + bool contains_recursive_proof = 0); + template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index c6b511d662..2f4436c2a3 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -141,7 +141,8 @@ acir_format::Composer create_outer_circuit(std::vector& i 16); const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); - const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); + const std::vector key_witnesses = + acir_format::export_key_in_recursion_format(inner_verifier.key); const uint32_t key_hash_start_idx = static_cast(witness_offset); const uint32_t public_input_start_idx = key_hash_start_idx + 1; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 4310342f7e..690aec5e7a 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -157,7 +157,7 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, plonk::verification_key_data vk_data; read(vk_buf, vk_data); auto vkey = std::make_shared(std::move(vk_data), crs); - std::vector output = vkey->export_key_in_recursion_format(); + std::vector output = acir_format::export_key_in_recursion_format(vkey); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o From b56d3ca9e3914abc959e82942f9c728fecc9335b Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 14:34:24 +0000 Subject: [PATCH 28/64] chore: remove unused export key in recursion format from main proof system classes --- .../verification_key/verification_key.cpp | 106 ------------------ .../verification_key/verification_key.hpp | 3 - 2 files changed, 109 deletions(-) diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index f99fb5aed7..f6cf92bd73 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -178,110 +178,4 @@ sha256::hash verification_key::sha256_hash() return sha256::sha256(to_buffer(vk_data)); } -/** - * @brief When recursively verifying proofs, we represent the verification key using field elements. - * This method exports the key formatted in the manner our recursive verifier expects. - * NOTE: only used by the dsl at the moment. Might be cleaner to make this a dsl function? - * - * @return std::vector - */ -std::vector verification_key::export_key_in_recursion_format() -{ - std::vector output; - output.emplace_back(domain.root); - output.emplace_back(domain.domain); - output.emplace_back(domain.generator); - output.emplace_back(circuit_size); - output.emplace_back(num_public_inputs); - output.emplace_back(contains_recursive_proof); - for (size_t i = 0; i < 16; ++i) { - if (recursive_proof_public_input_indices.size() > i) { - output.emplace_back(recursive_proof_public_input_indices[i]); - } else { - output.emplace_back(0); - ASSERT(contains_recursive_proof == false); - } - } - for (const auto& descriptor : polynomial_manifest.get()) { - if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { - const auto element = commitments.at(std::string(descriptor.commitment_label)); - const uint256_t x = element.x; - const uint256_t y = element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - output.emplace_back(x_lo); - output.emplace_back(x_hi); - output.emplace_back(y_lo); - output.emplace_back(y_hi); - } - } - - verification_key_data vkey_data{ - .composer_type = composer_type, - .circuit_size = static_cast(circuit_size), - .num_public_inputs = static_cast(num_public_inputs), - .commitments = commitments, - .contains_recursive_proof = contains_recursive_proof, - .recursive_proof_public_input_indices = recursive_proof_public_input_indices, - }; - output.emplace_back(vkey_data.compress_native(0)); // key_hash - return output; -} - -/** - * @brief When recursively verifying proofs, we represent the verification key using field elements. - * This method exports the key formatted in the manner our recursive verifier expects. - * A dummy key is used when building a circuit without a valid witness assignment. - * We want the transcript to contain valid G1 points to prevent on-curve errors being thrown. - * We want a non-zero circuit size as this element will be inverted by the circuit - * and we do not want an "inverting 0" error thrown - * - * @return std::vector - */ -std::vector verification_key::export_dummy_key_in_recursion_format( - const PolynomialManifest& polynomial_manifest, const bool contains_recursive_proof) -{ - std::vector output; - output.emplace_back(1); // domain.domain (will be inverted) - output.emplace_back(1); // domain.root (will be inverted) - output.emplace_back(1); // domain.generator (will be inverted) - - output.emplace_back(1); // circuit size - output.emplace_back(1); // num public inputs - - output.emplace_back(contains_recursive_proof); // contains_recursive_proof - for (size_t i = 0; i < 16; ++i) { - output.emplace_back(0); // recursive_proof_public_input_indices - } - - for (const auto& descriptor : polynomial_manifest.get()) { - if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { - // the std::biggroup class creates unsatisfiable constraints when identical points are added/subtracted. - // (when verifying zk proofs this is acceptable as we make sure verification key points are not identical. - // And prover points should contain randomness for an honest Prover). - // This check can also trigger a runtime error due to causing 0 to be inverted. - // When creating dummy verification key points we must be mindful of the above and make sure that each - // transcript point is unique. - auto scalar = barretenberg::fr::random_element(); - const auto element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = element.x; - const uint256_t y = element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - output.emplace_back(x_lo); - output.emplace_back(x_hi); - output.emplace_back(y_lo); - output.emplace_back(y_hi); - } - } - - output.emplace_back(0); // key_hash - - return output; -} - } // namespace proof_system::plonk diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp index 974ada27b1..1e5dca35d2 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.hpp @@ -63,9 +63,6 @@ struct verification_key { ~verification_key() = default; sha256::hash sha256_hash(); - std::vector export_key_in_recursion_format(); - static std::vector export_dummy_key_in_recursion_format( - const PolynomialManifest& polynomial_manifest, bool contains_recursive_proof = 0); uint32_t composer_type; size_t circuit_size; From 97ede2ef96ebb19214bc2a4e4ac4ed4fedc31e10 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 14:46:47 +0000 Subject: [PATCH 29/64] pr review: moved from_witness comment and renamed from_field_pt_vector --- .../dsl/acir_format/recursion_constraint.cpp | 2 +- .../barretenberg/dsl/acir_proofs/acir_proofs.cpp | 2 +- .../verification_key/verification_key.hpp | 16 ++++++++-------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index d47e493427..52c4bf3e94 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -110,7 +110,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } // recursively verify the proof - std::shared_ptr vkey = verification_key_ct::from_field_pt_vector( + std::shared_ptr vkey = verification_key_ct::from_field_elements( &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); vkey->program_width = noir_recursive_settings::program_width; Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 690aec5e7a..8173cd4af0 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -341,7 +341,7 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, nested_aggregation_indices_all_zero &= (idx == 0); } const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; - std::shared_ptr vkey = acir_format::verification_key_ct::from_field_pt_vector( + std::shared_ptr vkey = acir_format::verification_key_ct::from_field_elements( &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); vkey->program_width = acir_format::noir_recursive_settings::program_width; acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, num_public_inputs); diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 818f1f9d9a..a8dcdc5787 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -25,7 +25,7 @@ namespace stdlib { namespace recursion { template struct evaluation_domain { - static evaluation_domain from_field_pt_vector(const std::vector>& fields) + static evaluation_domain from_field_elements(const std::vector>& fields) { evaluation_domain domain; domain.root = fields[0]; @@ -112,15 +112,10 @@ template struct evaluation_domain { uint32 size; }; -/** - * @brief Converts a 'native' verification key into a standard library type, instantiating the `input_key` parameter as - * circuit variables. This allows the recursive verifier to accept arbitrary verification keys, where the circuit being - * verified is not fixed as part of the recursive circuit. - */ template struct verification_key { using Composer = typename Curve::Composer; - static std::shared_ptr from_field_pt_vector( + static std::shared_ptr from_field_elements( Composer* ctx, const std::vector>& fields, bool inner_proof_contains_recursive_proof = false, @@ -131,7 +126,7 @@ template struct verification_key { key->context = ctx; key->polynomial_manifest = PolynomialManifest(Composer::type); - key->domain = evaluation_domain::from_field_pt_vector({ fields[0], fields[1], fields[2] }); + key->domain = evaluation_domain::from_field_elements({ fields[0], fields[1], fields[2] }); key->n = fields[3]; key->num_public_inputs = fields[4]; @@ -163,6 +158,11 @@ template struct verification_key { return key; } + /** + * @brief Converts a 'native' verification key into a standard library type, instantiating the `input_key` parameter + * as circuit variables. This allows the recursive verifier to accept arbitrary verification keys, where the circuit + * being verified is not fixed as part of the recursive circuit. + */ static std::shared_ptr from_witness(Composer* ctx, const std::shared_ptr& input_key) { From c64d8b5aa965c5f203151ec531c25a096e53a450 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 15:09:58 +0000 Subject: [PATCH 30/64] moved export transcript in recursion format to DSL package --- .../dsl/acir_format/recursion_constraint.cpp | 113 +++++++++++++++++- .../dsl/acir_format/recursion_constraint.hpp | 4 + .../acir_format/recursion_constraint.test.cpp | 3 +- .../dsl/acir_proofs/acir_proofs.cpp | 2 +- .../transcript/transcript_wrappers.hpp | 111 ----------------- 5 files changed, 118 insertions(+), 115 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 52c4bf3e94..f6bc8795ed 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -40,8 +40,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& inner_proof_contains_recursive_proof); const auto manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); const std::vector dummy_proof = - transcript::StandardTranscript::export_dummy_transcript_in_recursion_format( - manifest, inner_proof_contains_recursive_proof); + export_dummy_transcript_in_recursion_format(manifest, inner_proof_contains_recursive_proof); for (size_t i = 0; i < input.proof.size(); ++i) { const auto proof_field_idx = input.proof[i]; // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), @@ -244,4 +243,114 @@ std::vector export_dummy_key_in_recursion_format(const Polynom return output; } +/** + * @brief Returns transcript represented as a vector of barretenberg::fr. + * Used to represent recursive proofs (i.e. proof represented as circuit-native field elements) + * + * @return std::vector + */ +std::vector export_transcript_in_recursion_format(const transcript::StandardTranscript& transcript) +{ + std::vector fields; + const auto num_rounds = transcript.get_manifest().get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (const auto& manifest_element : transcript.get_manifest().get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(transcript.get_field_element(manifest_element.name)); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto group_element = transcript.get_group_element(manifest_element.name); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } else { + ASSERT(manifest_element.name == "public_inputs"); + const auto public_inputs_vector = transcript.get_field_element_vector(manifest_element.name); + for (const auto& ele : public_inputs_vector) { + fields.emplace_back(ele); + } + } + } + } + } + return fields; +} + +/** + * @brief Get a dummy fake proof for recursion. All elliptic curve group elements are still valid points to prevent + * errors being thrown. + * + * @param manifest + * @return std::vector + */ +std::vector export_dummy_transcript_in_recursion_format(const transcript::Manifest& manifest, + const bool contains_recursive_proof) +{ + std::vector fields; + const auto num_rounds = manifest.get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (const auto& manifest_element : manifest.get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(0); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + // the std::biggroup class creates unsatisfiable constraints when identical points are + // added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not + // identical. And prover points should contain randomness for an honest Prover). This check can + // also trigger a runtime error due to causing 0 to be inverted. When creating dummy proof + // points we must be mindful of the above and make sure that each point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } else { + ASSERT(manifest_element.name == "public_inputs"); + const size_t num_public_inputs = manifest_element.num_bytes / 32; + // If we have a recursive proofs the public inputs must describe an aggregation object that + // is composed of two valid G1 points on the curve. Without this conditional we will get a + // runtime error that we are attempting to invert 0. + if (contains_recursive_proof) { + ASSERT(num_public_inputs == 16); + for (size_t k = 0; k < num_public_inputs / 4; ++k) { + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } + } else { + for (size_t j = 0; j < num_public_inputs; ++j) { + fields.emplace_back(0); + } + } + } + } + } + } + return fields; +} + } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 15af19039f..615f23a553 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -66,6 +66,10 @@ std::vector export_key_in_recursion_format(std::shared_ptr export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, bool contains_recursive_proof = 0); +std::vector export_transcript_in_recursion_format(const transcript::StandardTranscript& transcript); +std::vector export_dummy_transcript_in_recursion_format(const transcript::Manifest& manifest, + const bool contains_recursive_proof); + template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 2f4436c2a3..82e6135622 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -140,7 +140,8 @@ acir_format::Composer create_outer_circuit(std::vector& i transcript::HashType::PlookupPedersenBlake3s, 16); - const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); + const std::vector proof_witnesses = + acir_format::export_transcript_in_recursion_format(transcript); const std::vector key_witnesses = acir_format::export_key_in_recursion_format(inner_verifier.key); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 8173cd4af0..319c74a34f 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -125,7 +125,7 @@ size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, transcript::HashType::PlookupPedersenBlake3s, 16); - std::vector output = transcript.export_transcript_in_recursion_format(); + std::vector output = acir_format::export_transcript_in_recursion_format(transcript); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); diff --git a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp index 2fe0830c37..bd33eff800 100644 --- a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp +++ b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp @@ -56,117 +56,6 @@ class StandardTranscript : public Transcript { // TODO(luke): temporary function for debugging barretenberg::fr get_mock_challenge() { return barretenberg::fr::random_element(); }; - - /** - * @brief Returns transcript represented as a vector of barretenberg::fr. - * Used to represent recursive proofs (i.e. proof represented as circuit-native field elements) - * - * @return std::vector - */ - std::vector export_transcript_in_recursion_format() - { - std::vector fields; - const auto num_rounds = get_manifest().get_num_rounds(); - for (size_t i = 0; i < num_rounds; ++i) { - for (const auto& manifest_element : get_manifest().get_round_manifest(i).elements) { - if (!manifest_element.derived_by_verifier) { - if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { - fields.emplace_back(get_field_element(manifest_element.name)); - } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { - const auto group_element = get_group_element(manifest_element.name); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); - } else { - ASSERT(manifest_element.name == "public_inputs"); - const auto public_inputs_vector = get_field_element_vector(manifest_element.name); - for (const auto& ele : public_inputs_vector) { - fields.emplace_back(ele); - } - } - } - } - } - return fields; - } - - /** - * @brief Get a dummy fake proof for recursion. All elliptic curve group elements are still valid points to prevent - * errors being thrown. - * - * @param manifest - * @return std::vector - */ - static std::vector export_dummy_transcript_in_recursion_format( - const Manifest& manifest, const bool contains_recursive_proof) - { - std::vector fields; - const auto num_rounds = manifest.get_num_rounds(); - for (size_t i = 0; i < num_rounds; ++i) { - for (const auto& manifest_element : manifest.get_round_manifest(i).elements) { - if (!manifest_element.derived_by_verifier) { - if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { - fields.emplace_back(0); - } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { - // the std::biggroup class creates unsatisfiable constraints when identical points are - // added/subtracted. - // (when verifying zk proofs this is acceptable as we make sure verification key points are not - // identical. And prover points should contain randomness for an honest Prover). This check can - // also trigger a runtime error due to causing 0 to be inverted. When creating dummy proof - // points we must be mindful of the above and make sure that each point is unique. - auto scalar = barretenberg::fr::random_element(); - const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); - } else { - ASSERT(manifest_element.name == "public_inputs"); - const size_t num_public_inputs = manifest_element.num_bytes / 32; - // If we have a recursive proofs the public inputs must describe an aggregation object that - // is composed of two valid G1 points on the curve. Without this conditional we will get a - // runtime error that we are attempting to invert 0. - if (contains_recursive_proof) { - ASSERT(num_public_inputs == 16); - for (size_t k = 0; k < num_public_inputs / 4; ++k) { - auto scalar = barretenberg::fr::random_element(); - const auto group_element = - barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); - } - } else { - for (size_t j = 0; j < num_public_inputs; ++j) { - fields.emplace_back(0); - } - } - } - } - } - } - return fields; - } }; } // namespace transcript From f3baab0fea65f6e04a4ecac96af37a13a5e76fc9 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 18:28:28 +0000 Subject: [PATCH 31/64] move order of acir functions --- .../dsl/acir_proofs/acir_proofs.cpp | 152 +++++++++--------- .../dsl/acir_proofs/acir_proofs.hpp | 18 ++- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 33 ++-- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 18 ++- cpp/src/barretenberg/dsl/types.hpp | 2 +- 5 files changed, 113 insertions(+), 110 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 319c74a34f..877921f217 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -105,82 +105,6 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* return buffer.size(); } -/** - * @brief Takes in a proof buffer and converts into a vector of field elements. - * The Recursion opcode requires the proof serialized as a vector of witnesses. - * Use this method to get the witness values! - * - * @param proof_data_buf - * @param serialized_proof_data_buf - */ -size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, - uint8_t** serialized_proof_data_buf, - size_t proof_data_length, - size_t num_inner_public_inputs) -{ - plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; - - transcript::StandardTranscript transcript(proof.proof_data, - acir_format::Composer::create_manifest(num_inner_public_inputs), - transcript::HashType::PlookupPedersenBlake3s, - 16); - - std::vector output = acir_format::export_transcript_in_recursion_format(transcript); - - // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); - auto raw_buf = (uint8_t*)malloc(output_size_bytes); - - // The serialization code below will convert out of Montgomery form before writing to the buffer - for (size_t i = 0; i < output.size(); ++i) { - barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); - } - *serialized_proof_data_buf = raw_buf; - - return output_size_bytes; -} - -/** - * @brief Takes in a verification key buffer and converts into a vector of field elements. - * The Recursion opcode requires the vk serialized as a vector of witnesses. - * Use this method to get the witness values! - * - * @param vk_buf - * @param serialized_vk_buf - */ -size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, - uint8_t const* vk_buf, - uint8_t** serialized_vk_buf, - uint8_t** serialized_vk_hash_buf) -{ - auto crs = std::make_shared(g2x); - plonk::verification_key_data vk_data; - read(vk_buf, vk_data); - auto vkey = std::make_shared(std::move(vk_data), crs); - std::vector output = acir_format::export_key_in_recursion_format(vkey); - - // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o - const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; - - auto raw_buf = (uint8_t*)malloc(output_size_bytes); - auto vk_hash_raw_buf = (uint8_t*)malloc(32); - - // The serialization code below will convert out of Montgomery form before writing to the buffer - for (size_t i = 0; i < output.size() - 1; ++i) { - barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); - } - barretenberg::fr::serialize_to_buffer(output[output.size() - 1], vk_hash_raw_buf); - - // copy the vkey into serialized_vk_buf - *serialized_vk_buf = raw_buf; - - // copy the vkey hash into serialized_vk_hash_buf - *serialized_vk_hash_buf = vk_hash_raw_buf; - - return output_size_bytes; -} - size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -379,4 +303,80 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, return output_size_bytes; } +/** + * @brief Takes in a proof buffer and converts into a vector of field elements. + * The Recursion opcode requires the proof serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param proof_data_buf + * @param serialized_proof_data_buf + */ +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs) +{ + plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; + + transcript::StandardTranscript transcript(proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); + + std::vector output = acir_format::export_transcript_in_recursion_format(transcript); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size(); ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } + *serialized_proof_data_buf = raw_buf; + + return output_size_bytes; +} + +/** + * @brief Takes in a verification key buffer and converts into a vector of field elements. + * The Recursion opcode requires the vk serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param vk_buf + * @param serialized_vk_buf + */ +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) +{ + auto crs = std::make_shared(g2x); + plonk::verification_key_data vk_data; + read(vk_buf, vk_data); + auto vkey = std::make_shared(std::move(vk_data), crs); + std::vector output = acir_format::export_key_in_recursion_format(vkey); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; + + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + auto vk_hash_raw_buf = (uint8_t*)malloc(32); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size() - 1; ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } + barretenberg::fr::serialize_to_buffer(output[output.size() - 1], vk_hash_raw_buf); + + // copy the vkey into serialized_vk_buf + *serialized_vk_buf = raw_buf; + + // copy the vkey hash into serialized_vk_hash_buf + *serialized_vk_hash_buf = vk_hash_raw_buf; + + return output_size_bytes; +} + } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index cad0d1a79f..24df5e4f3e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -8,14 +8,6 @@ uint32_t get_exact_circuit_size(uint8_t const* constraint_system_buf); uint32_t get_total_circuit_size(uint8_t const* constraint_system_buf); size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk_buf); size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); -size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, - uint8_t const* vk_buf, - uint8_t** serialized_vk_buf, - uint8_t** serialized_vk_hash_buf); -size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, - uint8_t** serialized_proof_data_buf, - size_t proof_data_length, - size_t num_inner_public_inputs); size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -29,6 +21,8 @@ bool verify_proof(uint8_t const* g2x, uint8_t* proof, uint32_t length, bool is_recursive); + +// Recursion specific methods size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, @@ -36,5 +30,13 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs); } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 62621d00be..34b735c67e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -35,23 +35,6 @@ WASM_EXPORT size_t acir_proofs_init_verification_key(void* pippenger, return acir_proofs::init_verification_key(pippenger, g2x, pk_buf, vk_buf); } -WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, - uint8_t const* vk_buf, - uint8_t** serialized_vk_buf, - uint8_t** serialized_vk_hash_buf) -{ - return acir_proofs::serialize_verification_key_into_field_elements( - g2x, vk_buf, serialized_vk_buf, serialized_vk_hash_buf); -} -WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, - uint8_t** serialized_proof_data_buf, - size_t proof_data_length, - size_t num_inner_public_inputs) -{ - return acir_proofs::serialize_proof_into_field_elements( - proof_data_buf, serialized_proof_data_buf, proof_data_length, num_inner_public_inputs); -} - WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -89,4 +72,20 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, input_aggregation_obj_buf, output_aggregation_obj_buf); } +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) +{ + return acir_proofs::serialize_verification_key_into_field_elements( + g2x, vk_buf, serialized_vk_buf, serialized_vk_hash_buf); +} +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs) +{ + return acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, serialized_proof_data_buf, proof_data_length, num_inner_public_inputs); +} } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 452f41d1ca..5e42747b57 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -14,14 +14,6 @@ WASM_EXPORT size_t acir_proofs_init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, uint8_t const** vk_buf); -WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, - uint8_t const* vk_buf, - uint8_t** serialized_vk_buf, - uint8_t** serialized_vk_hash_buf); -WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, - uint8_t** serialized_proof_data_buf, - size_t proof_data_length, - size_t num_inner_public_inputs); WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -35,6 +27,8 @@ WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, uint8_t* proof, uint32_t length, bool is_recursive); + +// Recursion specific methods WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, @@ -42,4 +36,12 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs); } diff --git a/cpp/src/barretenberg/dsl/types.hpp b/cpp/src/barretenberg/dsl/types.hpp index 192fc10fe9..f9823984bf 100644 --- a/cpp/src/barretenberg/dsl/types.hpp +++ b/cpp/src/barretenberg/dsl/types.hpp @@ -64,7 +64,7 @@ using hash_path_ct = proof_system::plonk::stdlib::merkle_tree::hash_path; -// Ultra-composer specific typesv +// Ultra-composer specific types using rom_table_ct = proof_system::plonk::stdlib::rom_table; using ram_table_ct = proof_system::plonk::stdlib::ram_table; From a793b2dbc9a17c4beddee1bddc984e97b7c790c8 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 18:28:58 +0000 Subject: [PATCH 32/64] remove ecc bb_module declaration in env package --- cpp/src/barretenberg/env/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/barretenberg/env/CMakeLists.txt b/cpp/src/barretenberg/env/CMakeLists.txt index e084984b85..f4a8428e63 100644 --- a/cpp/src/barretenberg/env/CMakeLists.txt +++ b/cpp/src/barretenberg/env/CMakeLists.txt @@ -1 +1 @@ -barretenberg_module(env ecc) \ No newline at end of file +barretenberg_module(env) \ No newline at end of file From f9542663b34bf5c10d439898f782309a86a66c46 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Mon, 22 May 2023 19:15:37 +0000 Subject: [PATCH 33/64] chore: remove usage of magic numbers when slicing g1::affine_element into barretenberg::fr --- .../dsl/acir_format/recursion_constraint.cpp | 90 +++++++++---------- .../dsl/acir_format/recursion_constraint.hpp | 11 +++ 2 files changed, 51 insertions(+), 50 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index f6bc8795ed..92e2e52de1 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -8,6 +8,9 @@ namespace acir_format { using namespace proof_system::plonk; +static constexpr uint64_t TWO_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 2; +static constexpr uint64_t FOUR_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 4; + void generate_dummy_proof() {} /** * @brief Add constraints required to recursively verify an UltraPlonk proof @@ -164,16 +167,11 @@ std::vector export_key_in_recursion_format(std::shared_ptrpolynomial_manifest.get()) { if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { const auto element = vkey->commitments.at(std::string(descriptor.commitment_label)); - const uint256_t x = element.x; - const uint256_t y = element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - output.emplace_back(x_lo); - output.emplace_back(x_hi); - output.emplace_back(y_lo); - output.emplace_back(y_hi); + auto g1_as_fields = export_g1_affine_element_as_fields(element); + output.emplace_back(g1_as_fields.x_lo); + output.emplace_back(g1_as_fields.x_hi); + output.emplace_back(g1_as_fields.y_lo); + output.emplace_back(g1_as_fields.y_hi); } } @@ -225,16 +223,11 @@ std::vector export_dummy_key_in_recursion_format(const Polynom // transcript point is unique. auto scalar = barretenberg::fr::random_element(); const auto element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = element.x; - const uint256_t y = element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - output.emplace_back(x_lo); - output.emplace_back(x_hi); - output.emplace_back(y_lo); - output.emplace_back(y_hi); + auto g1_as_fields = export_g1_affine_element_as_fields(element); + output.emplace_back(g1_as_fields.x_lo); + output.emplace_back(g1_as_fields.x_hi); + output.emplace_back(g1_as_fields.y_lo); + output.emplace_back(g1_as_fields.y_hi); } } @@ -260,16 +253,11 @@ std::vector export_transcript_in_recursion_format(const transc fields.emplace_back(transcript.get_field_element(manifest_element.name)); } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { const auto group_element = transcript.get_group_element(manifest_element.name); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); + auto g1_as_fields = export_g1_affine_element_as_fields(group_element); + fields.emplace_back(g1_as_fields.x_lo); + fields.emplace_back(g1_as_fields.x_hi); + fields.emplace_back(g1_as_fields.y_lo); + fields.emplace_back(g1_as_fields.y_hi); } else { ASSERT(manifest_element.name == "public_inputs"); const auto public_inputs_vector = transcript.get_field_element_vector(manifest_element.name); @@ -309,16 +297,11 @@ std::vector export_dummy_transcript_in_recursion_format(const // points we must be mindful of the above and make sure that each point is unique. auto scalar = barretenberg::fr::random_element(); const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); + auto g1_as_fields = export_g1_affine_element_as_fields(group_element); + fields.emplace_back(g1_as_fields.x_lo); + fields.emplace_back(g1_as_fields.x_hi); + fields.emplace_back(g1_as_fields.y_lo); + fields.emplace_back(g1_as_fields.y_hi); } else { ASSERT(manifest_element.name == "public_inputs"); const size_t num_public_inputs = manifest_element.num_bytes / 32; @@ -330,16 +313,11 @@ std::vector export_dummy_transcript_in_recursion_format(const for (size_t k = 0; k < num_public_inputs / 4; ++k) { auto scalar = barretenberg::fr::random_element(); const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); - const uint256_t x = group_element.x; - const uint256_t y = group_element.y; - const barretenberg::fr x_lo = x.slice(0, 136); - const barretenberg::fr x_hi = x.slice(136, 272); - const barretenberg::fr y_lo = y.slice(0, 136); - const barretenberg::fr y_hi = y.slice(136, 272); - fields.emplace_back(x_lo); - fields.emplace_back(x_hi); - fields.emplace_back(y_lo); - fields.emplace_back(y_hi); + auto g1_as_fields = export_g1_affine_element_as_fields(group_element); + fields.emplace_back(g1_as_fields.x_lo); + fields.emplace_back(g1_as_fields.x_hi); + fields.emplace_back(g1_as_fields.y_lo); + fields.emplace_back(g1_as_fields.y_hi); } } else { for (size_t j = 0; j < num_public_inputs; ++j) { @@ -353,4 +331,16 @@ std::vector export_dummy_transcript_in_recursion_format(const return fields; } +G1AsFields export_g1_affine_element_as_fields(const barretenberg::g1::affine_element& group_element) +{ + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, TWO_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr x_hi = x.slice(TWO_LIMBS_BITS_IN_FIELD_SIMULATION, FOUR_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr y_lo = y.slice(0, TWO_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr y_hi = y.slice(TWO_LIMBS_BITS_IN_FIELD_SIMULATION, FOUR_LIMBS_BITS_IN_FIELD_SIMULATION); + + return G1AsFields{ x_lo, x_hi, y_lo, y_hi }; +} + } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 615f23a553..d98b0dbda8 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -70,6 +70,17 @@ std::vector export_transcript_in_recursion_format(const transc std::vector export_dummy_transcript_in_recursion_format(const transcript::Manifest& manifest, const bool contains_recursive_proof); +// In order to interact with a recursive aggregation state inside of a circuit, we need to represent its internal G1 +// elements as field elements. This happens in multiple locations when creating a recursion constraint. The struct and +// method below export a g1 affine element as fields to use as part of the recursive circuit. +struct G1AsFields { + barretenberg::fr x_lo; + barretenberg::fr x_hi; + barretenberg::fr y_lo; + barretenberg::fr y_hi; +}; +G1AsFields export_g1_affine_element_as_fields(const barretenberg::g1::affine_element& group_element); + template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; From 0f0d519d973f347a0c3e79fe6347c2d7a4cdfb27 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 01:56:54 +0000 Subject: [PATCH 34/64] introduce NUM_AGGREGATION_ELEMENTS constant and use it through recursion constraint --- .../dsl/acir_format/recursion_constraint.cpp | 11 +++++---- .../dsl/acir_format/recursion_constraint.hpp | 8 +++++-- .../dsl/acir_proofs/acir_proofs.cpp | 23 +++++++++++-------- 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 92e2e52de1..564bfd9d68 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -8,6 +8,9 @@ namespace acir_format { using namespace proof_system::plonk; +// `NUM_LIMB_BITS_IN_FIELD_SIMULATION` is the limb size when simulating a non-native field using the bigfield class +// A aggregation object is two acir_format::g1_ct types where each coordinate in a point is a non-native field. +// Each field is represented as four limbs. We split those limbs in half when serializing to/from buffer. static constexpr uint64_t TWO_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 2; static constexpr uint64_t FOUR_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 4; @@ -156,7 +159,7 @@ std::vector export_key_in_recursion_format(std::shared_ptrcircuit_size); output.emplace_back(vkey->num_public_inputs); output.emplace_back(vkey->contains_recursive_proof); - for (size_t i = 0; i < 16; ++i) { + for (size_t i = 0; i < RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { if (vkey->recursive_proof_public_input_indices.size() > i) { output.emplace_back(vkey->recursive_proof_public_input_indices[i]); } else { @@ -209,7 +212,7 @@ std::vector export_dummy_key_in_recursion_format(const Polynom output.emplace_back(1); // num public inputs output.emplace_back(contains_recursive_proof); // contains_recursive_proof - for (size_t i = 0; i < 16; ++i) { + for (size_t i = 0; i < RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { output.emplace_back(0); // recursive_proof_public_input_indices } @@ -309,8 +312,8 @@ std::vector export_dummy_transcript_in_recursion_format(const // is composed of two valid G1 points on the curve. Without this conditional we will get a // runtime error that we are attempting to invert 0. if (contains_recursive_proof) { - ASSERT(num_public_inputs == 16); - for (size_t k = 0; k < num_public_inputs / 4; ++k) { + ASSERT(num_public_inputs == RecursionConstraint::AGGREGATION_OBJECT_SIZE); + for (size_t k = 0; k < RecursionConstraint::NUM_AGGREGATION_ELEMENTS; ++k) { auto scalar = barretenberg::fr::random_element(); const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); auto g1_as_fields = export_g1_affine_element_as_fields(group_element); diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index d98b0dbda8..4d9b428166 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -1,6 +1,5 @@ #pragma once #include -// #include "barretenberg/stdlib/types/types.hpp" #include "barretenberg/dsl/types.hpp" #include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" @@ -44,7 +43,12 @@ using namespace proof_system::plonk; * */ struct RecursionConstraint { - static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements + // An aggregation state is represented by two G1 affine elements. Each G1 point has + // two field element coordinates (x, y). Thus, four field elements + static constexpr size_t NUM_AGGREGATION_ELEMENTS = 4; + // Four limbs are used when simulating a non-native field using the bigfield class + static constexpr size_t AGGREGATION_OBJECT_SIZE = + NUM_AGGREGATION_ELEMENTS * NUM_QUOTIENT_PARTS; // 16 field elements std::vector key; std::vector proof; std::vector public_inputs; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 877921f217..8ccd5d13b3 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -87,7 +87,7 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* // Set the recursive proof indices as this is not done in `compute_verification_key_base` verification_key->contains_recursive_proof = proving_key->contains_recursive_proof; - for (size_t i = 0; i < 16; ++i) { + for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { if (proving_key->recursive_proof_public_input_indices.size() > i) { verification_key->recursive_proof_public_input_indices.emplace_back( proving_key->recursive_proof_public_input_indices[i]); @@ -213,22 +213,24 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf) { + const size_t NUM_AGGREGATION_ELEMENTS = acir_format::RecursionConstraint::NUM_AGGREGATION_ELEMENTS; bool inner_aggregation_all_zero = true; - std::vector aggregation_input(16); - for (size_t i = 0; i < 16; i++) { + std::vector aggregation_input(acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE); + for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; i++) { aggregation_input[i] = barretenberg::fr::serialize_from_buffer(&input_aggregation_obj_buf[i * 32]); inner_aggregation_all_zero &= (aggregation_input[i].is_zero()); } acir_format::aggregation_state_ct previous_aggregation; if (!inner_aggregation_all_zero) { - std::array aggregation_elements; - for (size_t i = 0; i < 4; ++i) { - aggregation_elements[i] = acir_format::bn254::fq_ct(acir_format::field_ct(aggregation_input[4 * i]), - acir_format::field_ct(aggregation_input[4 * i + 1]), - acir_format::field_ct(aggregation_input[4 * i + 2]), - acir_format::field_ct(aggregation_input[4 * i + 3])); + std::array aggregation_elements; + for (size_t i = 0; i < NUM_AGGREGATION_ELEMENTS; ++i) { + aggregation_elements[i] = + acir_format::bn254::fq_ct(acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 1]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 2]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 3])); aggregation_elements[i].assert_is_in_field(); } // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included @@ -275,7 +277,8 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, // Just write the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data // as this should all be available elsewhere - const size_t output_size_bytes = 16 * sizeof(barretenberg::fr); + const size_t output_size_bytes = + acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE * sizeof(barretenberg::fr); auto raw_buf = (uint8_t*)malloc(output_size_bytes); for (size_t i = 0; i < 4; ++i) { From 294c1fae20bfeed4e1d58250ad13e78009cedfba Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 02:16:56 +0000 Subject: [PATCH 35/64] nit ASSERT(result != -1) in get_public_input_index --- cpp/src/barretenberg/plonk/composer/composer_base.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index b1109d2d4d..cd34897239 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -197,7 +197,7 @@ class ComposerBase { break; } } - ASSERT(found == true); + ASSERT(result != -1); return result; } From 517ed2c77bac56f0dc324fcd1f43412aaab537b7 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 02:45:36 +0000 Subject: [PATCH 36/64] remove unused found var --- cpp/src/barretenberg/plonk/composer/composer_base.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index cd34897239..3933b6dca0 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -188,11 +188,9 @@ class ComposerBase { uint32_t get_public_input_index(const uint32_t witness_index) const { - bool found = false; uint32_t result = static_cast(-1); for (size_t i = 0; i < public_inputs.size(); ++i) { if (real_variable_index[public_inputs[i]] == real_variable_index[witness_index]) { - found = true; result = static_cast(i); break; } From 92159e0b899e8bc23a9b57c8fc852b15c476f0dd Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 03:22:29 +0000 Subject: [PATCH 37/64] cast -1 --- cpp/src/barretenberg/plonk/composer/composer_base.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index 3933b6dca0..8c8a92656b 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -195,7 +195,7 @@ class ComposerBase { break; } } - ASSERT(result != -1); + ASSERT(result != static_cast(-1)); return result; } From d8ee38821e5c8fd0fdc99aeca9a6af4e40b3b3fa Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 16:17:57 +0000 Subject: [PATCH 38/64] fix up verify_recursive_proof and add a test for it --- .../dsl/acir_proofs/acir_proofs.cpp | 17 +++-- .../dsl/acir_proofs/acir_proofs.test.cpp | 76 +++++++++++++++++++ 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 8ccd5d13b3..62dc6460d4 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -242,22 +242,25 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, previous_aggregation.has_data = false; } + acir_format::Composer composer; + std::vector proof_fields(proof_length / 32); std::vector key_fields(vk_length / 32); for (size_t i = 0; i < proof_length / 32; i++) { - proof_fields[i] = acir_format::field_ct(barretenberg::fr::serialize_from_buffer(&proof_buf[i * 32])); + // TODO(maxim): The stdlib pairing primitives fetch the context from the elements being used in the pairing + // computation When attempting to simulate recursive verification without a full circuit where the context of + // these elements are not set we will get a seg fault. Using `witness_ct` here provides a workaround where these + // elements will have their context set. We should enable the native verifier to return an aggregation state so + // that we can avoid creating an unnecessary circuit and this workaround + proof_fields[i] = acir_format::field_ct( + acir_format::witness_ct(&composer, barretenberg::fr::serialize_from_buffer(&proof_buf[i * 32]))); } for (size_t i = 0; i < vk_length / 32; i++) { key_fields[i] = acir_format::field_ct(barretenberg::fr::serialize_from_buffer(&vk_buf[i * 32])); } - acir_format::Composer composer; - transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); - // We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. - // We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, - // or we need non-witness data to be provided as metadata in the ACIR opcode - // recursively verify the proof + std::array nested_aggregation_object = {}; for (size_t i = 6; i < 22; ++i) { nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index a5554d1b82..536d9a7342 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -141,6 +141,82 @@ TEST(AcirProofs, TestSerialization) EXPECT_EQ(verified, true); } +TEST(AcirProofs, TestVerifyRecursiveProofPass) +{ + uint8_t* proof_data_fields = nullptr; + uint8_t* vk_fields = nullptr; + uint8_t* vk_hash_buf = nullptr; + size_t proof_fields_size = 0; + size_t vk_fields_size = 0; + + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + auto num_public_inputs = constraint_system.public_inputs.size(); + proof_fields_size = acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, &proof_data_fields, proof_length, num_public_inputs); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + + const size_t output_size_bytes = + acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE * sizeof(barretenberg::fr); + auto input_agg_buf = (uint8_t*)malloc(output_size_bytes); + + for (size_t i = 0; i < 16; ++i) { + auto field = barretenberg::fr::zero(); + barretenberg::fr::serialize_to_buffer(field, &input_agg_buf[i * 32]); + } + + uint8_t* output_agg_buf = nullptr; + acir_proofs::verify_recursive_proof(proof_data_fields, + static_cast(proof_fields_size), + vk_fields, + static_cast(vk_fields_size), + static_cast(num_public_inputs), + input_agg_buf, + &output_agg_buf); + + free((void*)proof_data_fields); + free((void*)vk_fields); +} + struct RecursiveCircuitData { std::vector key_witnesses; std::vector proof_witnesses; From cf9bdd63315d82c97c5b8689efa495660814bf64 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 16:27:42 +0000 Subject: [PATCH 39/64] moved from tempalte for has_valid_witness_assignments to a flag --- cpp/src/barretenberg/dsl/acir_format/acir_format.cpp | 10 +++++----- .../dsl/acir_format/recursion_constraint.cpp | 12 +++++------- .../dsl/acir_format/recursion_constraint.hpp | 8 +++----- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 97ce130665..f356af7d46 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -97,7 +97,7 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) // Add recursion constraints for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { auto& constraint = constraint_system.recursion_constraints[i]; - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); // make sure the verification key records the public input indices of the final recursion output // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public @@ -201,7 +201,7 @@ Composer create_circuit(const acir_format& constraint_system, // Add recursion constraints for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { auto& constraint = constraint_system.recursion_constraints[i]; - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint); // make sure the verification key records the public input indices of the final recursion output // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public @@ -310,7 +310,7 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, // Add recursion constraints for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { auto& constraint = constraint_system.recursion_constraints[i]; - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint, true); // make sure the verification key records the public input indices of the final recursion output // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public @@ -416,7 +416,7 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: // Add recursion constraints for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { auto& constraint = constraint_system.recursion_constraints[i]; - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint, true); // make sure the verification key records the public input indices of the final recursion output // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public @@ -520,7 +520,7 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai // Add recursion constraints for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { auto& constraint = constraint_system.recursion_constraints[i]; - create_recursion_constraints(composer, constraint); + create_recursion_constraints(composer, constraint, true); // make sure the verification key records the public input indices of the final recursion output // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 564bfd9d68..32cb775cd3 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -28,8 +28,9 @@ void generate_dummy_proof() {} * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, * or we need non-witness data to be provided as metadata in the ACIR opcode */ -template -void create_recursion_constraints(Composer& composer, const RecursionConstraint& input) +void create_recursion_constraints(Composer& composer, + const RecursionConstraint& input, + bool has_valid_witness_assignments) { const auto& nested_aggregation_indices = input.nested_aggregation_object; bool nested_aggregation_indices_all_zero = true; @@ -53,7 +54,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // we add our dummy proof values as Composer variables. // if we DO have a valid witness assignment, we use the real witness assignment barretenberg::fr dummy_field = - has_valid_witness_assignment ? composer.get_variable(proof_field_idx) : dummy_proof[i]; + has_valid_witness_assignments ? composer.get_variable(proof_field_idx) : dummy_proof[i]; // Create a copy constraint between our dummy field and the witness index provided by RecursionConstraint. // This will make the RecursionConstraint idx equal to `dummy_field`. // In the case of a valid witness assignment, this does nothing (as dummy_field = real value) @@ -64,7 +65,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& for (size_t i = 0; i < input.key.size(); ++i) { const auto key_field_idx = input.key[i]; barretenberg::fr dummy_field = - has_valid_witness_assignment ? composer.get_variable(key_field_idx) : dummy_key[i]; + has_valid_witness_assignments ? composer.get_variable(key_field_idx) : dummy_key[i]; composer.assert_equal(composer.add_variable(dummy_field), key_field_idx); } } @@ -140,9 +141,6 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } } -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); - /** * @brief When recursively verifying proofs, we represent the verification key using field elements. * This method exports the key formatted in the manner our recursive verifier expects. diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index 4d9b428166..f99dc21230 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -60,11 +60,9 @@ struct RecursionConstraint { friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; }; -template -void create_recursion_constraints(Composer& composer, const RecursionConstraint& input); - -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +void create_recursion_constraints(Composer& composer, + const RecursionConstraint& input, + bool has_valid_witness_assignments = false); std::vector export_key_in_recursion_format(std::shared_ptr const& vkey); std::vector export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, From 209667624f706be9106acab2cc0f7bfbdc7fa793 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 23 May 2023 16:31:46 +0000 Subject: [PATCH 40/64] chore: add comments to AcirProofs.TestVerifyRecursiveProofPass --- cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 536d9a7342..fadbe87d42 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -141,6 +141,8 @@ TEST(AcirProofs, TestSerialization) EXPECT_EQ(verified, true); } +// Test that the method `verify_recursive_proof` can successfully simulate +// an output aggregation state TEST(AcirProofs, TestVerifyRecursiveProofPass) { uint8_t* proof_data_fields = nullptr; From a87fdccaee8be3ce78f3b2963c096e247fb9054f Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 25 May 2023 16:02:06 +0000 Subject: [PATCH 41/64] update prove and verify exports to handle is_recursive --- .../dsl/acir_proofs/acir_composer.cpp | 27 ++++++--- .../dsl/acir_proofs/acir_composer.hpp | 6 +- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 10 +++- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 6 +- exports.json | 8 +++ ts/exports.json | 0 ts/src/barretenberg_api/index.ts | 16 ++--- ts/src/main.ts | 58 ++++++++++++++----- 8 files changed, 97 insertions(+), 34 deletions(-) create mode 100644 ts/exports.json diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 1efb8aa861..14b5045292 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -40,7 +40,8 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, } std::vector AcirComposer::create_proof(acir_format::acir_format& constraint_system, - acir_format::WitnessVector& witness) + acir_format::WitnessVector& witness, + bool is_recursive) { composer_ = acir_format::Composer(proving_key_, verification_key_, circuit_subgroup_size_); // You can't produce the verification key unless you manually set the crs. Which seems like a bug. @@ -57,12 +58,17 @@ std::vector AcirComposer::create_proof(acir_format::acir_format& constr witness.clear(); witness.shrink_to_fit(); - auto prover = composer_.create_ultra_with_keccak_prover(); std::cout << "num_gates: " << composer_.num_gates << std::endl; std::cout << "circuit_finalised: " << composer_.circuit_finalised << std::endl; - std::cout << "prover.get_circuit_size(): " << prover.get_circuit_size() << std::endl; - auto proof = prover.construct_proof().proof_data; + std::vector proof; + if (is_recursive) { + auto prover = composer_.create_prover(); + proof = prover.construct_proof().proof_data; + } else { + auto prover = composer_.create_ultra_with_keccak_prover(); + proof = prover.construct_proof().proof_data; + } return proof; } @@ -71,10 +77,17 @@ void AcirComposer::init_verification_key() verification_key_ = composer_.compute_verification_key(); } -bool AcirComposer::verify_proof(std::vector const& proof) +bool AcirComposer::verify_proof(std::vector const& proof, bool is_recursive) { - auto verifier = composer_.create_ultra_with_keccak_verifier(); - return verifier.verify_proof({ proof }); + bool verified = false; + if (is_recursive) { + auto verifier = composer_.create_verifier(); + verified = verifier.verify_proof({ proof }); + } else { + auto verifier = composer_.create_ultra_with_keccak_verifier(); + verified = verifier.verify_proof({ proof }); + } + return verified; } std::string AcirComposer::get_solidity_verifier() diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index 4a69e597e2..de3c9d3af8 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -13,11 +13,13 @@ class AcirComposer { void init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint = 0); - std::vector create_proof(acir_format::acir_format& constraint_system, acir_format::WitnessVector& witness); + std::vector create_proof(acir_format::acir_format& constraint_system, + acir_format::WitnessVector& witness, + bool is_recursive); void init_verification_key(); - bool verify_proof(std::vector const& proof); + bool verify_proof(std::vector const& proof, bool is_recursive); std::string get_solidity_verifier(); size_t get_exact_circuit_size() { return exact_circuit_size_; }; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index f4beb0a0a4..2a85971ca8 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -38,6 +38,7 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, + bool const* is_recursive, uint8_t** out) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); @@ -48,7 +49,7 @@ WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, free_mem_slab_raw((void*)constraint_system_buf); free_mem_slab_raw((void*)witness_buf); - auto proof_data = acir_composer->create_proof(constraint_system, witness); + auto proof_data = acir_composer->create_proof(constraint_system, witness, ntohl(*is_recursive)); *out = to_heap_buffer(proof_data); } @@ -58,11 +59,14 @@ WASM_EXPORT void acir_init_verification_key(in_ptr acir_composer_ptr) acir_composer->init_verification_key(); } -WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, uint8_t const* proof_buf, bool* result) +WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, + uint8_t const* proof_buf, + bool const* is_recursive, + bool* result) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto proof = from_buffer>(proof_buf); - *result = acir_composer->verify_proof(proof); + *result = acir_composer->verify_proof(proof, ntohl(*is_recursive)); } WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_buf out) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 9c29a4d2f6..72c11f2ee1 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -20,11 +20,15 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, + bool const* is_recursive, uint8_t** out); WASM_EXPORT void acir_init_verification_key(in_ptr acir_composer_ptr); -WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, uint8_t const* proof_buf, bool* result); +WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, + uint8_t const* proof_buf, + bool const* is_recursive, + bool* result); WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_buf out); diff --git a/exports.json b/exports.json index 8d285db274..12034c5d4f 100644 --- a/exports.json +++ b/exports.json @@ -688,6 +688,10 @@ { "name": "witness_buf", "type": "const uint8_t *" + }, + { + "name": "is_recursive", + "type": "const bool *" } ], "outArgs": [ @@ -719,6 +723,10 @@ { "name": "proof_buf", "type": "const uint8_t *" + }, + { + "name": "is_recursive", + "type": "const bool *" } ], "outArgs": [ diff --git a/ts/exports.json b/ts/exports.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ts/src/barretenberg_api/index.ts b/ts/src/barretenberg_api/index.ts index 8993c154e8..7f6719826b 100644 --- a/ts/src/barretenberg_api/index.ts +++ b/ts/src/barretenberg_api/index.ts @@ -201,8 +201,8 @@ export class BarretenbergApi { return; } - async acirCreateProof(acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array): Promise { - const result = await this.binder.callWasmExport('acir_create_proof', [acirComposerPtr, constraintSystemBuf, witnessBuf], [BufferDeserializer()]); + async acirCreateProof(acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array, isRecursive: boolean): Promise { + const result = await this.binder.callWasmExport('acir_create_proof', [acirComposerPtr, constraintSystemBuf, witnessBuf, isRecursive], [BufferDeserializer()]); return result[0]; } @@ -211,8 +211,8 @@ export class BarretenbergApi { return; } - async acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array): Promise { - const result = await this.binder.callWasmExport('acir_verify_proof', [acirComposerPtr, proofBuf], [BoolDeserializer()]); + async acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, isRecursive: boolean): Promise { + const result = await this.binder.callWasmExport('acir_verify_proof', [acirComposerPtr, proofBuf, isRecursive], [BoolDeserializer()]); return result[0]; } @@ -444,8 +444,8 @@ export class BarretenbergApiSync { return; } - acirCreateProof(acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array): Uint8Array { - const result = this.binder.callWasmExport('acir_create_proof', [acirComposerPtr, constraintSystemBuf, witnessBuf], [BufferDeserializer()]); + acirCreateProof(acirComposerPtr: Ptr, constraintSystemBuf: Uint8Array, witnessBuf: Uint8Array, isRecursive: boolean): Uint8Array { + const result = this.binder.callWasmExport('acir_create_proof', [acirComposerPtr, constraintSystemBuf, witnessBuf, isRecursive], [BufferDeserializer()]); return result[0]; } @@ -454,8 +454,8 @@ export class BarretenbergApiSync { return; } - acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array): boolean { - const result = this.binder.callWasmExport('acir_verify_proof', [acirComposerPtr, proofBuf], [BoolDeserializer()]); + acirVerifyProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, isRecursive: boolean): boolean { + const result = this.binder.callWasmExport('acir_verify_proof', [acirComposerPtr, proofBuf, isRecursive], [BoolDeserializer()]); return result[0]; } diff --git a/ts/src/main.ts b/ts/src/main.ts index 9659d7a522..07c9169a81 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -43,7 +43,7 @@ async function init() { return { api, acirComposer }; } -export async function proveAndVerify(jsonPath: string, witnessPath: string) { +export async function proveAndVerify(jsonPath: string, witnessPath: string, is_recursive: boolean) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -58,9 +58,9 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string) { debug(`creating proof...`); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness)); + const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), false); - const verified = await api.acirVerifyProof(acirComposer, proof); + const verified = await api.acirVerifyProof(acirComposer, proof, false); debug(`verified: ${verified}`); return verified; } finally { @@ -68,7 +68,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string) { } } -export async function prove(jsonPath: string, witnessPath: string, outputPath: string) { +export async function prove(jsonPath: string, witnessPath: string, is_recursive: boolean, outputPath: string) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -80,7 +80,7 @@ export async function prove(jsonPath: string, witnessPath: string, outputPath: s debug(`creating proof...`); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness)); + const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); writeFileSync(outputPath, proof); debug('done.'); @@ -89,7 +89,7 @@ export async function prove(jsonPath: string, witnessPath: string, outputPath: s } } -export async function verify(jsonPath: string, proofPath: string) { +export async function verify(jsonPath: string, proofPath: string, is_recursive: boolean) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -99,7 +99,7 @@ export async function verify(jsonPath: string, proofPath: string) { debug('initing verification key...'); await api.acirInitVerificationKey(acirComposer); - const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath)); + const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), is_recursive); debug(`verified: ${verified}`); return verified; } finally { @@ -129,6 +129,35 @@ export async function contract(jsonPath: string, outputPath: string) { } } +export async function proof_as_fields(proofPath: string, num_inner_public_inputs: number, outputPath: string) { + const { api, acirComposer } = await init(); + + try { + debug('serializing proof byte array into field elements'); + const proof_as_fields = await api.acirSerializeProofIntoFields(acirComposer, readFileSync(proofPath), num_inner_public_inputs); + + writeFileSync(outputPath, proof_as_fields); + debug('done.'); + } finally { + await api.destroy(); + } +} + +export async function vk_as_fields(proofPath: string, num_inner_public_inputs: number, vkey_oututPath: string, key_hash_outputPath: string) { + const { api, acirComposer } = await init(); + + try { + debug('serializing proof byte array into field elements'); + const [vk_as_fields, vk_hash_as_fields] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + + writeFileSync(vkey_oututPath, vk_as_fields); + writeFileSync(key_hash_outputPath, vk_hash_as_fields); + debug('done.'); + } finally { + await api.destroy(); + } +} + // nargo use bb.js: backend -> bb.js // backend prove --data-dir data --witness /foo/bar/witness.tr --json /foo/bar/main.json // backend verify ... @@ -146,8 +175,9 @@ program .description('Generate a proof and verify it. Process exits with success or failure code.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') - .action(async ({ jsonPath, witnessPath }) => { - const result = await proveAndVerify(jsonPath, witnessPath); + .option('-r, --recursive', 'prove and verify using recursive prover and verifier') + .action(async ({ jsonPath, witnessPath, is_recursive }) => { + const result = await proveAndVerify(jsonPath, witnessPath, is_recursive); process.exit(result ? 0 : 1); }); @@ -156,10 +186,11 @@ program .description('Generate a proof and write it to a file.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') + .option('-r, --recursive', 'prove using recursive prover') .option('-o, --output-dir ', 'Specify the proof output dir', './proofs') .requiredOption('-n, --name ', 'Output file name.') - .action(async ({ jsonPath, witnessPath, outputDir, name }) => { - await prove(jsonPath, witnessPath, outputDir + '/' + name); + .action(async ({ jsonPath, witnessPath, is_recursive, outputDir, name }) => { + await prove(jsonPath, witnessPath, is_recursive, outputDir + '/' + name); }); program @@ -167,8 +198,9 @@ program .description('Verify a proof. Process exists with success or failure code.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') - .action(async ({ jsonPath, proofPath }) => { - await verify(jsonPath, proofPath); + .option('-r, --recursive', 'prove using recursive prover') + .action(async ({ jsonPath, proofPath, is_recursive }) => { + await verify(jsonPath, proofPath, is_recursive); }); program From 66ed1f7f53e1885eab6fb76e6a568ea9ac5a3d8d Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 25 May 2023 16:12:22 +0000 Subject: [PATCH 42/64] remove install state --- .yarn/install-state.gz | Bin 562500 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .yarn/install-state.gz diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz deleted file mode 100644 index 2ffebfb0fb7417ea10a006f9e84e9e6103c7cb56..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 562500 zcmV($K;yq3iwFP!0000011z1((j_Z$rCSfs6Pcc%COw7V5+DuL!+r@Mz)WVm`{rjJP8-M-l{*OQZ^*{go+aLe`|BkIu;*(2`SW`1(a z-)gPYJ)YK2N%O09_7mS!$_lMK`Tas2`AMzxzMnkbvA<`3ahDzT>~)N9(rv>wD$+ysbta;k4YIxppscq-W0YE&lrkzNoz6nPaq;a(%;B)APJ5eWlfJ)fncA zah|?ru9+Wfv7WYP9<>LaBgS{XPtRd}VT`dy;{m_-d)9cKr_7zs%5VL6gq+u#dd$7Y z#Md@H`U_ugy^OtM`9dhqQ}B?Q+Q_Me+0Hs8tUSl*h5dWk))3gzxZiPRp6tf^vJ5Ya z;wsD%L)p)K`p6^j=qxjvSkq?zeUN#cwOaYy&OT~5bMzWxT)Tbc%U|e|x9%)BFZ=jG z7}Pu^t`)=A;^|mafAhJaMoVZI@H{WyLsFw@ z+Iy#O*O&cyVk;RV+V9?jJ>>oHE*^td>3yb}PAVDiSmT@TIw^(Dmhq(Q!+cImv9#dp zbQUcwUbZ~totsCf6@%psd7Fp*!~X*RP8wm&nH|BM&#trVlK~K3>Wo@q+Za}0t9BV@ zXkaS@R-(2C8^p_l?uyYoyYG3zZmde34^Nq}?S)&2V+y`Fh5d@8ZWUu*|co&4~-&$}lZR-amV$lzME^zQZHuM>YLqowb8 z0fYA7XKY@??=LO@Q-D+rnTJpDOCg0;bA4J_D?M#Jxz@;EdOpDQ!`rdQj4Nn%ei1wK z=6KvSY`WER*pv5+(J`ejVC`ptrE@H7a?~?2=KuCr7ieG;kI+9>^#EI`=5HTd7Zc+1 z_XlExjQ{ZTZJykE3iX|#HkSN3Sit+lP)`NP`Ii0-p1Y^oSC23LlSTw@!!Et1d_dM` z)&#)E8gb$sbkKc{8<7Bn^RBjV<|mCAfbxJj0&;QTm8VSs4QnF~u+V_`3~a~%dSja@ z?f0|+`lQjHXC)wSwRiai{hT>F53*a}eV!I`d-1n}Ik9*L=XiO9w)wuaU3!ojlqJdl zzd5a!PmDat8zW$S^5*w>t6d`q_x5kUUnM5&j7Q<5=K-%+`1wS>f9}@8haK1N!AC0q zf7oFFYKJG3BB-)l&y5kKmH`3-vHV}+7d`~Cw)_lY%(Gfppm|!QJ-BIkKnPa34_34D zLqtz-yYrXc3I>-F362Yl<|)%y9V2-0Z-5byc)^qL<{jEo2B--}EFJ-u26!r-7YQRN zvcF%L1TK(A%-uZzo3&uHFD8PQYxDTl#0I~xQ#pL`yMaVDz2i){ERTroJzpO@-yR9` z9lei4ENT3o%Ch6ZAjilC>$xi{3@l>=SSG=%yr=u!*ca>9N*m>kFGvY0;7VB-0w?j7 z9Wj;m9l}MZ8Iqc#flEhPJWd4 zBci@<{7xOvjtLxolfPZQiB;6S4hS-VU%;1T2Mc@QlY56Td;mIu3F~eRUPBNoU_Qvp zj_GgdCH9;$BM-x-f*L$RJYy5jv8bN;w_nF15vU3P{sljZw+X2waE-;Q^+e+~5gVKf z-;P_##4mBG#0!6*ZhA2S!fVd@G3%9qH}?14V|^VKpdZCK@ev;3sRT!&GCs2jLHJLq zgKdKs5o-^`;C*IUlhBJj>5VW7ke8=;_*nJ{<0Vq!Y8^JyUL1<3vl9XT`!bde^2eEh zMj6Iv{lyyhA#(P(o}?|jrR~KJ9k|AttijMmWKKe$B0+3Kg@XcvZO4Dpagc|3J0j* zhd#a-7aVm!DX`Ku;HiZ*d0BzN3V{?GYpU&gvAc%r@_En`f&{Uzl%3n;K>Id_MWfaL#B`N}1aa7jxTwLg7&rk+v%FjQKa)TI+>#!b#*#>hUO3eY#PS&qaR)(lJ z)C?y@_INxQzvK5|TMwZP=X;{@r~q<$X%T0>y?qkHX{il95HQMg8FX6lsZfH1H7!_S z2N)pc=~OJ8zd8Zc6Ob1YD2?py8t&YWU;}NkbbQg$(G6wt!idZwnh(=N8xy zL;ZOP(GmWIR(g&bfo#zhu;@1XI=DP+09}|mRxidh!q*|ZA=Y+dfs6Ld>aE5evG#C2 zBJm4}(@XbpOTlSq(o1xKnPW(`6+$x}fL-A6g{{76e2eC=VO8YX(~hH83^E zt1SB{6JUeo`FJK?6F))-yyHck@)7ZeFgb;{8h!W^bnI#6swuc7xQLCT--#cB%2 zEkUhN$(jFwNxs+%`~{$eHxf<=D!}vUXeho6^T2)wLX~yo@zxgI3A%(7NJa(ofzt-i z05UJgMOXt9(I9TblnxTcHsm)xt7Bb>Ai(bPq~BJo@leG5!QW~4azsH8+ITDMM5Y6C z;?d!m84~{yX`YuL6$!La222z_JC83AiIIj&?Tr1MfQEm=+h`@jwJ)M3qEb17+S;At+9MV6%P; z3vPoYEm|9RWU!t{NC-Gf2-oC~7vBeZ!ydoG#(e~{=kU_7TSPd_x+jPsA#0;@9s(RY ze6oihp3Jb%?}E!_NhtD*6+Sg%$rW&{A~C^>O`hRngI65_bn5_mdfC{>HV&ywRqM^q zIEy<&uoMbtJ6ww|f*O4j8H|VWzNqlddma!ic8?ygNo)f04te3p;s&q|t27ZvlV>_l zgI2sAH1JJ9bPTf4E28Rvz7_Bej*D0y@{13o;+ZpM68VZ9@ziX1C`yqB9MtDOeqzAG zfoOy|dITVwxN8bXkn(=QPAA|330Q|Na*z~2Y|eT_I{DQ!@r1@Ee^Mjei`OT#W=|Ae z)be2k*oPNp4LnFT!qyn^^c38*j#69QV+8&XfJc0SP{h^!;RysTB7M~*fF;d!M&tw> zL3CWJ3+~VN6}0srgyROF!R{Y1A7L5bAgQ~Bte__*cs+cs!VY;qevEZYqc+?-j|Kj9 z6-ePt&|gHS0w0;R!9c$3nsj0(z=(;0Jr|lnBqo-acf{gwhX-_dFY5^z3SwvuD8B5; zo74r;&(dJQJhTxU*n}O-q>Z(=fz;5JW~^PRur)V$RLc`yr60Sp2g(#qvBO3Z=#&NQ z<0oTG2$)@f8%O|Rx!nJzOVL_IEEWPVE(N6FwEQg8c0h5VD(nyJl2?HSVHOjS38O=Y z6;^@3OfZJr^$%vs&T0pt^XaVVBXOGpS9-^6T6koWUij05J#8fWM)7>m+gA>Pphq}7 ztjI*%8b_54vHylhRJTa?KBhfo~P%~ff26Hb1M?kT|+%{1M_sBRv0*#4ya0TKh zv>4}Lch=LfiEND6i%mN+2%<#2F@n!UgMndeQ=h96u3iv_Pzh_|g`>w5AtU!NCn9gF zkW}0n*#ksF$}kh~4z{qRy%5WUj&H!$SaNdK19=nVlF~Ym;d{YAgB<+gp12hv=nNPM zJYWr$h&@Zn!l@{U#KO{#Cq(js^$EPIL_nlrJ~(;xAOZE2jUO)Q+-Ep@#?d+0bT) z@5p{PPkeiL8@mo)1F0Ij0KXf?vJWSR+aMy2haFH*RusF}7T`yFKobE0Sc0bvs-J** zPi*ht>C00*k&xiW$TAS->(_CCXL|fFL1&NYc*5^B9?7x4vI*zS%mdGhGljOGMc~qS z;2812fxZWddnpUHXj0@ue?y|rm>81>GMGV-MFL!4+>{hAq8ES87rd+~j(&EWym0bS zGg1+IMc)Btfd_`)dN6}^galV4dbJIN5oC*G?oq#t8?3u$XBFexuoB3g1P=p%gM*g5 zjXu-rXWW0`2mFz_2foz9t{3%k0%!wrEXY;I8`fL|jsb@}HPwj(g)0$R4x$`?W(m`a z?FVx@{hA|G0o_9^fwHlcJkkQz*r%-H7SVM^WM=f#>xC*7EDZJzdB-f#xvAadfySYv z2)l$x!W9~3g#e9)(?m8BoP<~wSU>&71rqV_yE^$!e+s!&VTxqc&I~S-N5+R5(3XkXp_1+4&`yiN3XCwSW#H5Xi_F^tc$SL`0N10pygR^ z)X%q##=WoCZ)bav)2Lx>g@6DGbh&$kGvSK7CDQT3Ws=J7_Y@Ujeqb0Ce1%Hl#rc;Q zN>Za@!iX4L6b%?p14S?!mY9dEvy1P?)L}`8oqh;a9%`o7Gdbv8Rfl|WLnLt$o?YqE z%#5%Rh|!k-iW;aJQv@MAw%lNZl;wfa7Dxf{v*#(0K=Ij6h}}Y55x_tWf=s$}F@q^@ zD;cYRK_DT44+32M7N+&WHF)nS=Q@z`^I`;bh>^|XsrROXijf{K<;##0<+3IS6vlxd zd(VR_VhTBLC=KSrhIN%X{=<&?yKWv@;Y@&I|{g7afV28KGbzl+dIGL02P2(5d5T^W$Hw3}UuNQY;7}xwQ#e zVst1$ToIjAdV|>j3MqTmpg{DD-lO_TbOoRxzO@4CVB^;?-P3sBJ>(0YCj>$A>2c}o zfEtmmZ59EI{Z-tGwPd>qDjVXp&-I%3MdKo_Fb8x*g%u(_2rbwvFM^#8h^YGTf#C3h zxl|?AC`1t`(G#1xaJ72?9J_}XFjN>(#pweZG)xtNXAOx0CWuU3WmW!_^F)`+=>fJZnWVnqF}pj|*Q&=B_roY*Zw zzro_$U=exDS|A7&4`NRJ0QyfvE|Gcfq#Q7~{d5#L&BZ^VM` z5d>(nf5gq~Z8x_AL61bCV6t@$B)NyLr(hfrt`;8=-BS;|CZv5&XYdN> zA0G3CMm!-_xrv^#zd2z310pj@Fw|mNfEw%9?j!gShbV}L_m)`O9v&=IrOn?)x77^d z;e05!E>L5UZ#}I5#e7-B^9kh$ulIYP$%05n0->zgS;$hKX4YT4i;x{BDxH^aN-Mz3 zM*y^7aKwJhzJi~Km4prOPXbYb?0JqrXvXs~IM_Ax|4ZxhOy|I(>Nh=4!uo1DkH?i! z5XHfij3@#qAPWEv5b7E)7cuKM^D@yGa#w*cm~J531VY4X@5v%J5(=eJ}3!ER=SD9}|P3@oX+>InbAMpWrDA0X=Th zu+Sld3UnH>c@(q-bAjcVK4x3Z+zlBnuVZ##!0?fchJP#_&kvTL3?AomgP@mY$n+Oj zMkdBRA2A4>GERV9G~_4#GDOGD7QRqk0t%Q?kO)N1kEQE;B%*>qHQ}GUn#aEQ3I9JN ze6S&Kz$$g$I!G2E6t{YKgl~Yxl{b<%p=F+gFBE>iJzu)|iVaNP2$tEn3dL$HqQhD6 zOrj;io;Zcw2Fn2eEi@hk7omysuo}AutBDqp9&tL^6oLGc&VptSVg3P%*%=-m`GrXa zXcJF;FdxvhE%f9m*D}y)*CRxg5BALiqf4L)uZOo2Z+^C!YCtMgUMmnr;L7gey9^S5 zPu|v^fT9X?_~x@3HjF>uvSv-bn|4Etv9>KG2v7uq?`Qk)L4n3$XBJ z0YN#zSOAXYi5*dhd_os^r1HimdHjOQ9KN2f1kGT(xQ@24CVt5Ke6tvr2q!#9&hRQI z>IeBl@T$HcP*b6S&Bl`hbYo~H9ykZ!p5O)jvsw(`wQ3C*DyS?B5ABOw@Sq3tB>I|nI{8Y+5d7h8g`SB! z^F7a7%af+toXu^G!&e#9Ko1rM!NoK7(q~YCn_}9SJ#Slk1{e_r#I}+R*=$nW=)DQ- ztx!Ga@hlI028%}S;PuZZ;G<**$N;MaBzU^#ptH=7Vgu}AMX{m+@-06ixQ$^U2>wY} zW~pNKNLb3n`$1&(hx7&|U(bkm6CeaykxV&AS->3lETQ!??}Z^FB`h0S*#E+fOnYE~ zyreEPd7^`kK)I#&mTnRRI@WIG*CT<017;%;=K7VzW=~Iy(1HX0XJVXsFuBTwuE;P_G z$EA{nG+}-nzpR#STtKxR0%UtFcmSoMnXm^T3Ql9@26F;lGVcdv%h7YQm@Y2hcI?h) zaSC<-B|s_H230|$!bf(6A%5@n+%b7Bx=j2)ZCBftNnvWB3jF3XQnF8N;4t|-3997z z?b>f5!c`sv+?8D+B3@ zgnAq_fkm*feGh0%WR8P9nC$+82vjZW*>M8sl6<024Se+A-QeV34XkLmr>~_J)DVJa z>J|jR^Ogr@2Hz*(E>?mUGWifEYy-No6aEb9fJq<}!M$y$#AAwJnuX$f@iz~s3MglI z=KT|R%rj?s!^?uhB`rSG7Ep;ZU%0iVcw;CN6**GUm~#V{BK&RO4B9bo(0Bt@h24P? z8Z(R4Su6%@>?M$5#wLW(agb?re&V0o(;DPOGZQLHfl!~?`Y zCcBx(xPeDp59^8@Q_g@OAoMg3i4{TM9-7HBxEoxFvZ7kqt5w#6xKo0}EJjQ35^jh! z@@ct%!9S-gmIHSpGS&rbbVyw%RKNjxwwSG%ogNr$%SgN`Mwk3PXj>SQNA~7QpOg-S zshi!IL<&fiHRSsdzzAG`G~1qzr$iY~N4e*xpN{bK=o4hI*rBSFEm9o_e{flRb9p$i0<$v~sF19`D`oDQae^j^Vqv&ERtdXTZtCh&Q}X0aoPnu+n*96x512-?Ew z=V|oK(DBN%eTM~O--zuViWlMmln0{*i_noDk^%VzhwmP}p=!D~%^HAu?>5O8)3g|c z_-HCsa`se<+%!;vN=3uAl5QGf!(UD9de*Hu9u%BqGsq5XHSp~V8sm|j`#Rwl9k7NO z|NSu_g}6;sRz@|56?+xFQaqPH#tzU~W|I&uI5+A$1=f5;!u@y%*xhvr6GOoSFLARx zTd#oUf=4Idc;Dk8|1F7xO`DRoPz$JM$B98OX7+n{woXt`My+B3Y4MyLJK3)fV5d6~zGVM55m6Y`jB0N@RgLq!p+!|Km5 zh!(&D{~deU#Be$v0y>?20a_aO#zbJi8~35I`NG%Eg2(>2Nfvx2JSBi-eXc1E-!-Mr9im}4fAwRen9jPX2IM@ z1cw~}tXMe*hz50L?#|Q14B1E`pTA$L4!Zd zEELS;^TcF5C%rJpiS1+8ie|(baq_A)!fpfpKVS42x2VWNUczG8GQ9vE;#La=hb0jY zEuS8Qa@iIVb|Hu&AB82{!ubbx2iTA*ssWyatr%8c`a@~QzR%jbde&JXBWwr+wPQ1u zIWOdgs}N0Ks5rw%%(f=qO1o@{mlZ{40=hg(f2^y~6Zux_{rMG(mk{#1PP(S6{PW85TdW}q%cxDl+$-I~ zQg|r50?6oVusSR!?qHRP@vUc*Q+5y`zAOiOmH2Jm;+ZFJxaVs@Ioo5009ro1;}#VG zuWZDd&y}MKr_b)R$4(LXaok69h`^8iHw7DsEr$S+gQoXCFnaRab zPaYjeQ~{6*-}Ouh^PHm?_Di}t0-awftBEQS%c{zOHG=8z)YAehm{G=3VGy8N!7}z? z$@S9@@%BP3%E0F&tJ8voxEHX3g89jOB_OZv?2~=Ky6fc8)Oe_FqSW|BS^=z1Uwp*Ei>B& z1B2ks_xy@tb8X#X#F7zAotIu_DAzjjFGwM3gkUpd1>1s*o0TCXywTGnOR6Kr1S9>R zNMYOAmL4;b&jLqq6SI;{8ZXCBsNaFMjaCFrc|J=xpn%cRq%U~b7W5w5D8Sre7|$=b zhVUk+7)GAE5y%0UcH+IzjR@G50LXHbIsp*c+rwRKffdCruh@SN?L_uLtslE1m`tYWQ*#_Whk- zIOt~q$jj&)3;&A{PXlvw? zVsr??pi~Fy_kiRIS)i+ederrGJBcA^t@W8Or%F%ydTf?65H7tmi`G=6UWEeu#C0FCuhR9M<}od z-T^y)ByR65bLN*XA|il-CY)dp9!}wlf#^=uKAT^@=IdXckTu8vyGHp&SS;p|L$iC& zaxBo3e-KGC=9CSfd)&q_srUeQG9(OE7o&ki1|2fSnoaN%JuNl>*LlrpjkC?Z38jVB zFR57A3Zg)W%KHctgogq_`1p7$`Ip6Ky_&r)ut%GfroEMXC2TGhi?P`_EUj6SGd%g! z7N7^6x1AR55|N=;Ce&H$X3-ZQ$oqiGAJ%Jv(Vdl-G<$3}cxFl+H(2ZdLT=hZGLfa& z9SXvqn?%JH%@PpTq4OJ70h#2%&Dz~kY)l;^MbJI{GZ?q=*@Fv&6NZ9E*)U1sS~)>( zWgW96evj3og(w9RSeTuaHFi|XDegKcU+)SP?lpr_r zubO`-EFSyIsv}78Usl!3LPw5b;n_vyfp3}Ku1HmzEUjQbSrbipuTwiBXX}hTPgXjb z*@^tk+pq%HX3Hx3M?66lEdjbKFnBn;k?4X0oDX}e_98x`S*-uo4$*H`a#^LfCr<(7A{Jr<3Hqpc6qMNeG?mY88kOtqo6;5yQC7i6k039# zv0N{j>=9Hy8~>oD9_&mUL>-mwkhcP_j?c98RwY{6iIOVv%m*^~{AwPa??7Q2*hUnF z%JUi#v6l4mdlGMM6CG$J2z!lL8qo(~vP&ZHUlGJcK3oZ^9t^~Kbhg2@dg#)!DZpZs z#@nrjg1pBi|3?Bcu;-cJ!dvM9~Xb+aZY_8RK=xO^7@ttsOV#^1u^1O^kEU^&Q0irUG!#f}u%$s6>y7V+ zx3p$4#Qv~wc<-MuZ9V4smJ&Q`ny+oJ-*Dadp<$VHG2=K1CouhkCNxoVEWty(7sN;T<*WlB zZ>zRN=E+xR35|iDv+Ezie0s#d#-j_GBpzV*pam9~1($?>zgfq`9X!ggR}cNI8EBX5whQr?Zh%$+b#FoqxC3Xw~yU~`@)l~zRf1)BOW(x6n^XngoXrL`vI4m!hver zsW=g+NKy8vCzX8e2%2r$4#Z4jXX_cWQ#?wrsT3OW-M%TfITE1QYl?9Hj@%|XxMSM= zY-(Yp0qI({01v#C4f(oVqFsGQsLW6l(?Tx!z&xOKeR~YXTM=TNKN1YcuO%iAp60NS zwle(i@&)(-K>^NR&0Ft<^5&l(Ap>Oxte*l4!R3>6x9$6IH+&mpvJVEg46q#eZMRSs zXG_k2Sa|;4;578iy2CxN=M>RFg-D5$O4virE#LCnmOoJj+)I|I#>7iR^fR7kUBruA zS~lRBZCrNc+0bprBK+;v6{HH*6F70=iBCK>f7+2^(XXA|&BHg?PEvDt#C)7k0zzTh z{mNPlx}?_v$g?8wKN|W6TY>sRR&0t}+jLnLL)UkW{ZU{JOKhhVWvG4=Ay#^!r$O z{e)w)2p(mlKVcW?#+IO4u-`!JN0vcwBFr9EMC{`APDyZ(kc`f_Pq4PdbGi4{H6eD1 zId)>Hqih=Ii-963;9$Fuh*rhpEePZa_F<-IhNXQZGi?vCGg*E#9vIBS=iXi*>vnke zg_+nKP|RMl7`Rn2sr;7Y8S1&!;5L%NXf4AAq0l2}Bt&iI$LyIbHqB;-IS{O2brICK zr@E}Ue{vKUuahHSd{D4#4a@grC%_HVbzZKk`8DpO)PlBm~;p8sN+r9RGTwTfgo8KVae&)CAeV9)&;fW z!Ppb~1d0GXo8RM!wq1YCfAwvZinU}jn)Wc#8gK6W0DT_bFb!KDQXI*SyIZm*WrtGP zvf=oJ90`xcWun$IZP}bQcprQTxn%Wi;>qp-u)?6C%Y(UM=@08d*uBgg16M~NFf0GX z#+}RaVYp2kM3 z2PAR_p3*Nt+W_rX&I=gcNC~Q&RrXB8julJAHuUzcam){N6Jv@t(^c3*C7EVD!RLHFLzCYC!6BrMMerlt4xkv=dY zWD!&vk!otCnZ-%$Y9`XXR&rX?Vbv4g;7*wuM8{&GQ>sO@4aN$r3wJiL0$kwJeVC95 zH?QYdpKsbmbq{(V`QHX&;Syarf5#+z5Q}EnoG;-3B+z{a4?PI@ctNtLDcR9Y%*eJ( z_FMA67u%I#hnm3-pKyTCaSYTG!W=wWRb_UIYK~<>x0BGCg>K?*-x3OY_W6nmIwKhQ zYP%QgoG#XhZ^VS-w*XKWMmO<|V`0GPWE`L!ZC+7l%TWCI``{99@z1oo9x(2lM zdki*>g|`|rIA817TZdxY=;2}C=6wWQZHSwnqR(SXBEPk17z@U!{7mq+h2XXu6EnaA zZu8Q%>B|H;wm2HP^sR=g1PIi}8a{{30NGC8LP4kH`Dj?^LJc1QND$aJJqB_e^HdYq z#dGa{a{20T&dabSVVPSd+&D3T?Y8um@P!UZ*ex8##^!V&!5Cc2!-k=cHO0THGvQQq z_Ci5Q4O=QC8<=__I3ko~(A{(%h&rMJUF_wBd3Wx3c0ZAs*w02pm=JzV3FguX zOb~Q<#m$tKk2RHO8l2u_OAs;TBokZhw&a-oyOHw1(ukJmIGz@UX3^qjJ9S3~D}xTC ziuWLZKSnMTbaa}P84aN6HDZSOjS2bml+x$2hL|Nq>*BVjS|yCya28jBU_;jvT6jt) zirld*3q!WqS0a}u*iQI}k5cfS-oGnitX(eL2AlCJ%Go5A!`28-orfLi>sIED&VCGR zDalWekh~Iv$m#%ydbb7t4p6MaK?%QVaJCh4j(x-Zoc3g)mv#L%Y}hrQun!1~f?-+w zQw350NG1dA-JlaeMKa_8AaU6i$Y|?PL?&$T`WgtAawW^A)D)V z#D}%PtTfah{Kg&F(NOfgj@jErjZ(7rr`g9zEVZp*T7CrcHcXB=L_U`r%QmhzouL7thPiGRZU?-Vy13y{<@$HK?60R%rweZbTdV|;G8 z_j-%`I9jCh+4t9PZ|#KZy^mG15P4vb-*y%cVWAPyJZcANg2hwif#F47Ejdjm;yL>= zdSVz_32qGsfXJ8_Exey6q%Z_0Q{rGYuJ8C%gc(s#<%k7<@N!4gJD$%gHD|yT{10_@ z$9%SJ;Bfn4a;dttFc9{yYvmiF&L+rafL@qrhQK=E5TWfr1)dx#)jkOx4EPZ!UQbp} z_IMHydo7*PBgz3ZFv6R3;ZKMo&KGpx$srh_kSjz6vc&3rNDb3nb|?^#?a}{mr_}+6 zlcTc-&ROVpG*pdF9La?Mu5B#mfq}`k$&*n7|U0=P0B-LSI;{}@$ImAcv;rLwETwuMg!O8gy z)8Ve|4IgK^;1wed#4J$S(G-H))`wKMgle!OQPK3ygZM80}96d&C+qaF+ z8HD@W0disL96G^|m*4>*>UiBEK-9PsC-8MR^amKSr^rm)uCrzs$Lk0)=LTDz)|{kp zs^dHh_!BTx)8D5Pj-guJl(l^!0RO&IC>$0jT_3}lGACI*3%9hZhSy)#$y*KWSgB=! z1bF$%m)_5jr_NJU1SjhgZSCNf{B?E#AFFF1Na+?9P9Qd5y@<|>`M@^sq|&x z7Ct-l37j^~eNPErtBU!X+oM*G$WV(;Nv<12n;rc4b(>p_lB(pv){tl>rZ^S`WRh0| zs5%63c&uB@>Do1F<^VAZ(Id#A95&`|Iwe($F)Rp9PK2CJZ?c<)*aH)e@V;d7(j3cr zoq4;TbsOGO1P54VmBo{s@`FJ`)QKgCQDZl|mmOE)0bCEDc}AqCb2YzD<)rIi^(yA% z2~ryc5)p1Xw_2v}NZswEozopf0@rEIghlt?3|;mZ4O0gD95FhrYk0%hv1fvSKK40l z#WAsop!8Z|0XfU&0a0&{6{q+kD$(vD-X+2k`_mq~zN_;ZktVf%jwN%HnwpZmv(o}^ zx-kMWZ{r>s{6WZ64V>8Aur@nGF?8p}yv-@d1|&Fy8XiL##e99X+~2u-RfZ$;IG&2n{!a72 zz97F%u+rE;QhPTG)-tUp@RWschk=6IgbL7r{Y8GkwUNlrDNd*-BByN|FOCy!E*}mY zg(q5(x9yCw#TDV|uivsI-X9POi17Lt7Zm8$sJD*ocj5ibGK}}wzcnelvmtqujbMaaMq@zz5+fxLuvJBy)dj_pEGTFJEzxPXr&zZEFL^FYL{b;t;U z6+znvwi3ctV4x~MYs{?jhQ+B1$FfAMZro{Nu(V;OWN$Td?% zsMOdor-G2#o()8!Ibcnstmzhz&0NYY&G&Vt$r}rm9T|5l2Y;YKHgoYHrZ7L#a0WSa zM*r12vV0Ch<#At|W?ptvSymlu5xT*svFQAtUT{x{u_>Z&#eUB(AUY1&=9>@8ys&xn z&i6Tl`E+yFA1hv*kr}OLueJ@NPjuGk+-<{@MKi_m9P_68Y_?0MwOJtZ{Xl`5#2ERp zg5y_GaVqk3o=Ab^M1ahCAy0)tm0~@ph4rgDMnrZ5@dRibC+B=?+sImN1m`wAguJ%! zIDZ^=<*44hpin@<>nPU_(JlGjV&IJUgX=eI(%Wpo;+pOWm(86v;}V&zh;^*qBPFr7 z)ieUXut?9;reh&sh~~ucf2ic^QNj&V*t_%AUd!%?El4Xln1#>;8NN#Q!cU|$#IFZ! zW!f}5=gYL2Q^zrK2elPM1G0q>Kx7+4Whd#F0CJkKW6CV<;{ouWl<(~c<*F&%Z`e78 z=Ci%k*QsH8xdMjD?(t=wJ7rLkuxPDK%jVb!pdbdj&Vm!X_AvQiVPeZUF4&6YUU#JD zoV_)eF`N^8LJ^g{17Yk7PFxWe-sR{w6I(=>g+9y6j*T`2|<53D|=GUd#>&e2?dFEUh!hiL1_n)QGK;w9u_Ntx#1LBD|&BCSZxpgAFuZvkW{j zId{%EgK**Jj*_#uqDX2cCO1uS*ibH%D}8TcnA1Hie?MnfNM}!uUGF6|kM4MM8>>wF zSp73Bpuzx}l~&kFgDN1bz~bX<4YZJxR|lTrV7#j%)V$S^`Hz*7a5oRBV6qcB2-if? ztpL;Hzg8^~O6m!Y6Lps9@oI)U{b{i&@xzIp3fN;uDib@Y9Qk2mvd#CZ;9n$EsTkh0!@WcG|4l-(jibQxyCi+6uM?nBj-f*yWxbvy;r*Iu@rt z&j(KdD&bo59LXe7JJ}WL(amsxsq^yOh`&70abOiccekawE4Cv{EIg7aFpOp?M{MOv6|0XLVuC& zwIc6J45^;W1rJ8{q2zy0!()i5PbF2ASiewCb4KoNN(zAcS+8 z7OwN2>Cru$6zH_+>LKigjCs36**5Pv+Z4uX4S$RE zeeIO6LJ3ywaEJr5tiWhPP=NYC8PO9OUMDd&HHevmbyr+a zS7S{voNm)B7_*%JI1v)zo~qR}#Jb0z*ii)__mpKTz`y1$DTOhX5mCHNkWL?aXg@6i{ko;{;TlTDvXV{UBM>Ii^opBOs8!X*$c%=FNMr z&_N9gdbaj{CYP|vwmx(`Tj1dUV1i&HU;qutNkSp;i8o1D{k-B?m_w4bGls1jwoH;O z_ePsS&>44}Ir85zdIvmam9q*Nc9 zv7Gu@C1$rj!V;&BsajH1E$zCwT(}AjmF=RzU6F_A6YGc_#9z#5cy)>g2e-%etXD)K ziki^vNV;;TSG=jxP zXMknr5__$}CQ1ZPhOsXwno|m`KJZcxFTk0O&+3Wrj}8QsG669rC#gVCJe%_x9cRT7 zGS?kFyVC8eaxh}C4h*cf*{Ory-UGp3z<A|>>9!^+#9ngj^K`2lcOX}zhD_g;3-lfIrwZzJRcJUr@ z5aw(1+7{y0BmP_Bv@LS8Ga%WQR&GVPS6De*qdG1#>}M&PLH6LaEuUYooW}{5YIpRW zXR@O@;Dul^?u8!NTmqWJ4hE-0SzIB5=4BIzpB$pfi+K7N?}<48H9*S0>Lxp6VI~^| za5d+`Waad6?t*54UUot9MBng8Veww^j>zDvC;tzzi>A)q!O%`TR7Z5*EOdLw zI$u_v>1_h{#lbe^1+1(&M=57#hB>c)Sf53Vxpz|3MF@UxcFhqq5*xsc|2TH}PK3uI z98?4>*lB2G_N{S;_T1c*M;?xnAUr@_t=7IJ_@1cQf_M5uY<;uSK#j%x9vF=N4v%0i zqs2x=GrNvIdm{*mcK1V`=XPjj@irYt?mb&0_R5)N*%B(c)H`{=5j3_cNbxLhLnRj* zn{s+B8&8rPqORvMtj_V+_gT(`Hp8j;1k1U+RLF}oV0urJi$KiKHNylUJk*h<;f}1b zK0giCGk8pV`2x%U0!Zk$>18@JM2~Z75n{CJ{tgcD#x}FV%}mfo+$3n)EVRA0(Bam@ zL3QS>CjokSxnZ(CwmQ)6DK?D|MNn$qz}Hqm84$I{d(WC;fed?~lgv>Mz|dLRNb%}r z9F_tAh;RyLUV;@Les1v(g7uY*dO_XiXiE0SF~Z(A2F0+o`5b4~CvQ1&8rNs>^ou6!=jSX`ZCfz3pB4%4ehsQO(CR+wg6*Y2_9xre7% z{yk{rg~B3Yy&eclHl7M$YWVxHiK08wciC6BZ=bcd9ok&$DV9@%fl#ZP>`Td6qHYIE z@ZblxvEkj~m+nyg?ac23biiXBfE;@&POHX~Qom*2Y_0{c89#TnL?!}y*GRoXn+TsZ z+&cqq4G2^cfZ3g8>#PQ}GTHO;%@%WbCg1#($?En{apxTh|dTWS5S@!AWV-D>J_Ql-t z24c|+&PYT+d|sz^D>;heW57}vsL7Apvmy4pU=ZXLZ8VUtl^9?zwkPZfDscy4sNkHN zj9dI>k9Vxf(J=Xe6xyK=e=|0=hHrT|%O~PnLvia28d2D>oXN=}zsB)ny;0egD0f!1}g(b=mG(Q#Uu zok4{^wDJ(coDPIXj@lodJQig8!p(5><;bDsIhI9i+mi}|dOYj4dT;jY@Xp`r=5ZIW z=vgOgc(0_CxDLR=cBRM?duEMBe4%d`;Z*?nHi3FrS%!mb;f^Sd=xLj+1tXhXI(Mw< zRCa9#kl6kY*b7Zgclx4)c>fB&5P^;aAuf04reUoi%lf0UdXDvCeR%hy+|Oy;63!wq znXOpT<%sZQp-JS03vR-yS@M9HOTXs%e;4yw%rI4{OPTV@PhQ+f>l>=GOE5 zvbC(B)72i&sjP1eO5qLi@}N!-UY0yA@Bj0zHuKfaUg@_C(%Xfar7wJy<b=VFd*D|K3_&9|cIOKTCxV>ydA_xnub~ z5HTu4P>bH|6cH~NN@j}7huGy+gOu`t>u+b*c-aVmk|Eem)%`xl4inK{ul%*KEQphj zqh99g_}=fcGuZJYNDZeTc(dT8ukVr!W-rfy{J+CPh8tx88)VhXVm1pRvloJ<)}70p zZM`ua=7dr;Ab{sp8}Nvoz2oj?J=uweFzm|a;JCWunbCli_28XiuQx2tTNCf(17{7} z`HNG(Hi+8}2!uBK=7EVj2o&#SOZL_cyjpRZTgMNuzHMRgV>1o5=9t8O@8fgg7@B?Y zTotheRePeBt-)(ec45Z%y3}Ezu{Zqk@N8@L*?IZLdz0+uB|gH1yteAI=`};sS+H!m zD{PPuJS?R9Y^(g7@Sx0fmH~LMoS@7Gqt1wLUTy$d6HxNGb8HtX@`nr6TRHM=(`u+X zZ;mLY*&i=!j?qzJ_p(8Wu=izJ9$68JpO;A1e2d-Zv0u{4E$lYCe%)dwOJ$eM4SfyU z>XBy^U2l!4cOIgOZXwBLN3gwwWcwVER-NN`Z}){kIP7tIF^rR{i`PRZ&!aucy-D&r zKUE=ZkD>R44qG0Fod)nO3nuPF5s+gA$Lm-E);&WB>*jpgO%j^+`d82{xn4Oyr=ejw>xYR6O$4Iz7LB?vpvB+%ME#&ZE=>uH1 zc)Q@eIRNO^DKEa;Ie&`d^6YPQ2#uAdUc6Aexx+hFgR?v>*zvZ9xUFh*nryOXz*;S5 z6nVE7Xr4ebFYy7mobEX)d+TfqBUSdG6?UE!eYD=lCzUm%P1bb1vy37*#KAfHcrFKNM z^XT7xJ;JeW&SLaX_1*w&-rxH$UqZxZ1G9HjxGZ}_Z%hq!U!A2S5}w^g6r~+6kE8DJ zEWjt4rh2_g*M^$qlsL9v+inO3@V7jbx=qfH$!6e%FjO{7&?KVQTb~!ccyHc|$QiaB zEH+}bl8VC^*r1$IhOgr~UQ_GC!QSHzjd##k^g>%}l9mlD_&LGUyO&UDrUwG6-~c6^ z(%xsUzVjjr&(IE(%=1&C#2ql2H0t6|j-8O$u@SDl(8pU*Anb4%0+5%We7W73AI&is zUKs7^p#|Oi12E2Vxdh2M&V(<-HE)T(_ts`PAs+QQT*V%q&K&bX&gFrOdfc<7^g8hm^2eUdVSNtRDYwJ>`5bS}Th4yP?jQe5 z89+F&o!lv|YJ_0ygkCw!wRixSyyXS0@v0oWR>1U~BcF)s!6p{=eS25!xTEAOMU3D$ zaJ)Y?04_BAKJfP&-gE10DHDkN?O}AVY5yM5^L?KK^4{G7hT;7|84U!}7*1F6YSqV+ z6O@w&)DQ;ecm%J6!(Y9q9+SMQr|>4xG0c^Dh1OCCKc?n;^xB~4$S(rNvI;15^s?W3N^kaHN8z}mS0%-bsY~iHVVk}ns+f)C-A}Gfj(~= z1$DhQ&0FGa1X{MyI(WlNax_Ih&KXNi%sesQy8`+2gLSjn*A`Sh&fWBecBhtQ2W_&~ zi^uXVRQXgb7B4Kv)y4*N+e@dvj*n3Do{%g}I1rv_3(asGj)O`+OV6g$#1k>Stve9F zUWsVAA5z$HX08G;bp7((hLVmRH>*2|!^k!-fgBW-9a%*5SysY2;SGxBBY2;pGqIxgcC(dM{y_`ae*5D- zi=f1sI*8~YT=@22=`1220hUnJ?HD3H70*+=I>DyiWMh3;pA#0n20gsqY6PT5TV8Pm zNe%S&;sWr8B4gtq_`^$gkfR>sdh}^q+bNzZ7w4vWcWuahfxQR@TVjy#0BKkcjt<+r zbL;u|ki#9;Z8p#v(9dxXUJUuZCYhZm51}F?eD@gK(g(l-I12mT2>@SiUYG8<#yxKL z&X2)ITD0QmO@b&yI+>Sub|Iwg*fDR$#==twI%qKRhsG`5BEgC+$uZHV-n$(~vy022 z@b>_?IHZH{;03)fx9!zM7AUcP$#GgS?l@kd^xoN?e6qtjokKT4 zg>}GQN!70P-o{Jd@}>toKXx0szu|ztd>hb<-tWat@Q3M{F-GPklHT=Oqk|dmao03% z-;(ugm%!8Qo4t9Ud|%GH#Js|M-=;l;VK98#7ngz*(MEj2cuO8a=6gBm2Y6f7Z?A{L znw{~2p^nEJABcNi7LSy1p2NM3PljR#4O9Km9#kc+id?uAf7{!CwsR2?Ddx*R z5Xzb*{#uA$Rc(Qum*d&>m6Y@?--tiMgh_ z%K)rMfzwQ{8@190YrAd#Ug^8Nes?_ZuEjb!|Hm6vc~P?hqHGzGVjv3v4FZDVGZPKH zJ^8b7*bCVKL0HXK5}E_=J>Kc&jg8%$A((xT?Z5g8b6SxXPCqQh?~d39YzM+zVc*_v>UJklz+=WOzn#tLFXig-K$r#PsG;OpCnUPbULL zNO(WBH~3%&fmq=kE$_0J%1XKJ^aR%5OsTiOkH>@xam?%mCg0Y$>HA_CH0#c5+!6|> z?^~wP*HcUKh7hMae|Kt+L(3C^)y8(Optj1%dw*`*^Zc5_QoXCfw&hOgNy2tAPk9|Hi z#-7KC*PlT5cyzhWEvNB{Hk&wXql%s20ydbDw}s1V2OuMMR5%Oy5d5U z_%XuaQ0{4oRJl{Dp6vDU4yRZS3j8Gx2u1|hqn?5r0jZy5bzYe6M2FvOHDXudte&;IJi0E@dJ0wd@?Wp7igR_A( z+p-vplUd)fr%Zi1?oFL`PN8*>Qo+igk1s_gc?U{C2e5cH%}!U@B1v1U2|ju9kX=@K zue@qEf2R=Fnx+R!|hcbF-1(lz#XC>1ye9m32 zxQuf_?}174Qr5w& zf?e+o9b2usyn_|3g+U{EOljM5*SRUuL^G8&vGxc;j-5C8cdvMb zA}8-kkvMpjJK7XZ)V;YTV3Nyg8jG`e2on};c@6t*W(EOU@~WP%#oph0+6cP#al2pO zf8gaOVnZpgrrQ~yx(o`HlIFelF+)zQ0=Q!YK#f;50WN&W@lgAD?#*_0IEn;tn7jp-?_mssu46=Y)9T@F_eJs``WOF&Z zC@2NHonDZbcUoqZn{#Q11dr`z7I_?udNpGmILucXFqsJv!P-18RC#nx*jH} zRzP{P%)kY|lZvTy454{z?{;c8bklo*y(;O-+B=CYS~gd_!{Biws}s(;=S{2PEP#xc z!4mt>K-O$p!<;+^Np^M>iMLgIH5vy|J8w0$KRt2#Wz=s{)1n>RAI%}fZN#os@}(?@ z%m=3E?F;Ws_&6BE+Ho%j3fRUy;P0N2G}gcwWaC~oyc}-ieG%o}9k#sG+GY*m0n2>7 z4Qn`h0b>V*P1^$LR^d5?&pI*|*IE|GQrQ>UAUxju44bff96ID4JfBHFaPJM?p$&*w z@AFuWvid%bbu^m^qDQFe&$ z5J6;qabz&d?o6t=9TRrznr?ZPBN0yWfC<;w=W< z==sj^Mk)5l5ps^(^-lWN`$H{Vi4G1Rs=m(RhGRi}y0vyejZ)&B6Q9oZ1_@OJ`u51K z9BbqxX;tlq>mZcve|SBZ1jwcm3Gum+oDLlF1K~jM$l>_fLmadw+Ho5W&^%sS>)$_KkJa-&Kw_Cl~%i(CxdSGPszGu5+AMdG;6;kcY z-$wA9*8O{VqK7IQBmRrE;@f_H(cC z?2@-|v+Mb>CXdyu!Wr@#^FvOHaAY~FI5j12o0v9c_BuUd)b=0?-U)?<&R+ZCjkHJ* z1D(w+YuVrHZAti;1>tsj9q(_%A*b!Bpi;3t_8x<8v`PXWi6ySiGv|lHXKP|vM1yX< zGu^RoFZz2_eK^CAe0UA5_p)U#kUeJ92t-g_4BBjBB~;jbj!zU>e7YH>RNLpIZ5*~- z?&tsw)6)34T?@()N8@@$m>IEM93Fz@1DQNDgs6755 z4I|FS+86(Z?aFp>C6#2{%8FBM#aXFVhaN(si#f&wTzj$2aPAtQ0xT6Y)>Ts$;1Sx! z9Xx=&USpM5EvFniYrI3~yha@)ejGGK%x+fNISLUWZcW4S&bQ-1iuW2>Pk+bVK6i)- z#0OI|n~mLg;YS{^-=Y%!{b zRz&pwVeVdXZCQ>ZfkF*+1p%4?JL<5Jk&)l7+L}a0egFalpP{TXXY)V(b`FvUm<3iEk^^Nw7h?5dW(N&hH=i4m&Zd zyLQ9uOK(2vtBH{~n~=X(aew4Nmd(6v#>3OdJ1AQ7C$_Q$|B}DYBiJ+_A?I^CZTjO# z(2qFZLOl=VgtFqeEs2N0=}8KhD8U`* zICEk<|LnO~x(A`K=T1a?H8%4F=-IQ0HWh2^3s@uHsa^uqA6^q}-aq8)6uCicYpwO# z&VN1>Dns*}u4l}(CGwPiM$WeBCqzCK-5?eGli)Iw%j*;$XIqkIEu){BZ0j0q-mT3A z9o|y#73G(Po&iRY_+BK#u(yVb50G{xAj>vJEuO^GGe_AH_?oJ`cL zM!n>ursM-?5y(OS;j&9}f8Q`p_EgU=!U@03eAO=*`be3W@4G)_=AV>Q46r%Ko%AlCidyx%A4|Jo2ELZ9j6BRrh(|Zf>FkiLX zu}0AI3U$m0CXxapmCwRlDptFDh_KJe1j_nMzDb*Fy;@>cCFgXr_{)p5^io5@6y?)8 znDwoAH|#4F&L+bo7gPRH&COqY^ISK#FP|k}v_#1%&yNFj@F>F^WIt5+e4vy?U23Pzj{yg}L#DcLy4@sugFMV!t}T5~Q<#Z%rHv*p0fHI?B-G-%9NW!)&mC&Kr$ zw6Uv+XYLpL%FiXmKx+G?ETi=xf=U><1{EYWe4XuY9(GbnPq8t7lraKcX)i2iYSV!Q zN6sz@$W(2&U|7ln<=B=Rl!&~P{ldz0f=WydVb5XO5Ydz5+G{1=AeOMD;m^x3u=7G+ zICoR$U4t!~+ZTk8f6Z-VI@vf{B}qMQ)>MK&3rEg^ubcYgDpmKD5`ea|5&$?^gxTpX zE-hzgW2L=gUuw2glE8f2kEPRA4zeftkH8WeZo$#{!pZKW9p0R_ z651RzU->COg0qzzef76CE50)05CJu^X~F~pU%9Q;(~EHPz6seWTt3_~;%Rt8wJw(2 z%dBFza`+gVH0`I$*4r3x?qhRZHC66V-^x^~_mm{PowVzM_)=$BS?eF=;z9m4CoT7; zU6yt)?vvwZ`ko`X?FnG!H2e#4=r>c$8}dw~dvqsu>st4$0k)PDnFKz{wqTUC@xsgo z8}E7$n=Hwg4B7da0s-o42JJADI2>;v2};gw&9#|sQ^;~2d^w2Y|Ev}V7RtKU^blD~ zP_}ZvVjH1x1@cT)Q@(mf)|T>+1AT*(9@Y&=swrmkiGu+GEH!nWZe5)zMt)je3niF| zqx)yT$?$C^VNAL)c`iYGw|;f?1aeLwi=CvEAB2)P5HSzFL|5rcukO=1Yc5O)mI!d( z3%45T=6Ryei@UEE|Ims$8?8#R`T1KZnQl(bfofLtKTFbRnj@tXPw|c+mpgW!6z*uX zCWkKQ5{5cn#5!|f^o>q7*~{P6&)3B!{ub18-6<>YiT=b&RO~>&eJ!k9kUa0X0X~jh z|CR`CpsL-jV9S}_vO&tqc4icTghw@@S!0F$cO2kAa7gf&p1_CVK`9y2 z_qbq!@BM-wI(4p(Q#`@CeAC2W(Rew{sV7RhdEO^w*woyFHrHM6ZpV+;P+}GPl{|T3 z68F~BPqbB%UK4KbfE%9Eii;dM-t* zAy%YA$c#F{@f^uY+FPwB0o$9GKg6Z29Id-l;Rof`SjmgX z)9_c0nX&_k)LbysLhDWmc25ArH0NvVhUimpM?zmRD@u1xeG2^G%s{Y2(&^+h6O`k~ zBxvR$M1mBBP^t}W@KFU@%sZdHX--7}DSoPe`nUplurlfD&xYkgR9;W9B!lli& zPE{OTJ9frNU^u9>TPJ|KZ@Yj+!C{$CO zD_ljn3AQQyLy+mB;K#sovsT$z5MM{JxEJ&vPFtzDXxdK{+Xonmv!8GjkXP(3%UI+v z=G`qj>DKb`;%e5O#A52+DQUPs9S>=}slR&y{gpuclkiIQAwpa5%w1wQKQk~lm)c+J zEqOLM7{Re@Cdf6b?gJ6>tg5n3);l?3HO68rv7Xn)yu+V-yyg7N;p{2{<+0LzfU5 z{K#L7T43acDHcq*EV%3&L%)lS#A(n^w^qG+P%=$9^P~a;rk%eWy5MiCbsZkfHP$TT zb#&|Ibe9ro-)NFpao%?+9Hz{llKp}m@wF34l9+P50*OWqpl8Oifq^T4cT7f~JiN5) z%P+}ZMqp7%%WcFpC7#^4K(hyJ@yr)o*?El8ahvCc-Laz$J=6j7%h_-~sBuAn2`Tn4 z6G}cs2{e1J1hFp~4=?H*kMh=x+H=zrK6w2M+j~jbR88p^OZ$esb|)>*nq0n^VQ$1X z>`RW9W6#dyikXl`hP;(nxKY2FI+F}rh!6dT1S4G$ooZUOSMd5*8-l6+aSlF1sWb2V zY1{!fvO?kn@VD8SNNpDkwEkQ=gEsPI-ih{4{$JulCj)^~xGM+p^Naq&Cn(V9R{N&p z{*;+A|9RLZma|(>+{^D#DjrmHKUX9*C0|W(1qsX#Ob8DS{-}r)!EB>(PEN;0d`mcg zye-vzuy7E-6D%6&{21{LGG<95NjFT>8k~{eoJwul>D|s&CeE48aE@Vq(`#ICn%*)f zGwn*A!H|7UC+J=)K$a^eAl6qj~j=)URHig23 zLN|EcPKeM&NY9tEMb1AF^ikA{5=6my;t2 zX`IS3p2}9IRK$TAi`Hunqh5&WU^Ij2JI{LdVY(5;ETQJm!j?jaC2 zC-6w_NJY#*Y|_UFbUo+9N;U#H-ko7j>Fg!G4TC^%TkCovQmNHLlGN{ktEXc?_U012|KbO z?aj@mDZvKg1icj|hFXfT5 za9XuL%5t>}MR4DjTbiR|bo@!%{D4yvCY7AzM*aJIVCCqnc%C)P4`}Vbr|!Yc6RR+kF;Y7QVn&=iGkA7xsSgr??%f(65dqt+t8s#5k3lX%MUlecT7R8*s3N~M zVLT-^A6!eVshsb_$^?#LB4vpYzRW>$5x1lKAXoA$oz^UmHD!3IGs^#O`WnaPobNFgSY`U&&mFD;Ys!~9@ySd z!~*Y6T$sNS?MT_e(}!sS8-_&}BxidyCM@jVi%ZKy?jaa-AvgqJT1^XjYvekO9C^WYrUqoOaHBFf>=>?exhephTP8eUtrO z8Bm&E27%7Yd|X&9IiWL39Z}?U=(8t8%p<5bnf$df#Q$_>JA{U6@Eus~BV0?K+*iHk zi41OH5_$A3kuP8DoDb}zNyNdiPhn^oIxpF*prm6OXJ7VP2a0e&)ID;5ZQ;MYYm`vT zP6(zocPxipTN11{af~F{+6?#*lOFbX}^iDTPliT znUi-%#SvNx81u0ic)#0~43`+K>DGvm$ST z69vu$x^C&cgtLg=Yfk8VoC}{UTWVq~0|xFYIa4VJ#>HIOij!{6W%0W+(%Kbzx5eE= z{^cfaUY2Vq#os}Z;C(1OY-^$~h>(B89xpWjZs=95&*MOY`f4g2Bw!2IOJn<_a86hc zDo#CiVi4vzW3&YNGK%3KlIo>*!aHVlq=l5!bgKqBa2^_RcMkDB_S}_Jq(liUvcH73 zw&Jo(jIr58&-v7Bag3dEBgp*Fd#@ArQsnQ8M6{*|DSzMc1Ywi+As+<8EI|`q67}A@HHN@sG5w9 zU^-I0%rwStE0&t}Im)^fRSvkq)MHo)ED{r7kMfn1`fNJ)cZa?bB zMMl(aOv2%qN})JM~7p2fWL9J}?m#Y5ihQ)ekO*2Lbenk#kT0QS98-2jz{K z)0%6^Mt30AZ5;AeEvp3Wg**A<5%XgR=9am;Wy0b&$q`#e7u|M6;H`r+54y{Pa?(K> zp?`o8A*LsP_v04zUbQAVbKg`*CKpq_&nx3?%uSk>)W#Wf*R#D7=t_ia-wt^%^`jz~ zU0Ag~U-25MIDi38T@yP&H*OXqo>DH`yn>5K=^XU35K7I@xDMr;I~`;+@^*8sngwcl zE=*P4S>m=V*`c=u(n;+*9fUgr4T_2#lzNO(j{8p27z|yTGI=m8Z)fd%t~4pD?#m|9 z+~(jFJ7w#{Nw>&BxwW!Kf>X*$mfZn*yuT)7lKgrhsZZwu1 zs4>y?HDcz5*Y^Z9WZs~Slx`nJ(0&ED0Q!jNbm@tO<)Dpox1Ul90kzR~>%ApP2guZp z6y<~@&Y20*;Udmlk~^aetDQ*n{Hby)c$M+SxQgnXh)n#F5;-ur zLgKsMb#)Ph-%@}q{XJ`J$yTUVMI5>8+v;MDmsHaUrm3vk$t6HLJ&_<$I^{zi^IUaA zl6AG`x3%@QSHpk0&U(wqH1s^U*DSg9TKiMZ&$u4OHk-#Z^J1!amsqzf^OJ)hMjb>n z=M+%HP|FbTEeB&?{OIMwJrh??>5u=H96z|kE%t6x<+cPB3Nl9tjxfHwl|S2^Q1>y* zxhHFD{H*rU_3tCdtqj=nC%8sYh^FV4GW_5`t&X@2+Fr{^9)-zp9^%aiQs0V@-+0i$ zSjnxJeKR3K#N&xac@70$TsP2%2C)|N+ZKDQbAVAoj`AaTJE0?Kz|K5uvS zoVmp4YNb}^srD>%w%5uLOuFqlzJhY@Xe(8Wge%s^2kvZHXIR#KDQthQ?c%bwDhg}h z#%gsTkYy#t-)Y^vc`C~|c|-B*#MildIZfIY+=vSB*(ySMfYFJ3!LVTkmyUk(CSe)Y zenjjkQNms!ZUa}^-l-^TU&3D_`<=qV3x?Pn>ybwyCPi z+ji8Yq%@uXLRhEcE$M?{4H8052v6Li@DLC~7FrLVe%YEw_u|Fkb(M8aGIr_+rsoDX zUx~dUngws(dC$WzHc2Ocw3I}WLoV`~btzLl<=)-NlLsc}mH3$g_x8}Ql#1A5If)6O z2?vRZft>SM1BNqsIY$dU^1gG23=rojp!IpVRMCX~O?CJ2@kAgY)BBX-jXGxHPCMEC z0`U*F#d2=U>X%PzrKFz{z204J=?|F`@(g}QeLHp(Sfr^GTT70Zy>#MrICV^h$+{C_&esOiQVfs)cv5s7 zmdA-OP*%#LrNi6#*aUJN_5*l0PgO#5F$mh~77g0-%Y5?I>|5DT=cg5d?uJ+2!)~IHQsp|(I0&PSSh1WKk?k~46Fv2nQZI8a z+b!wSa8A}DJdq^)7>VUB9A&tT&#z0OzjU0`rrD#q*TT zuY}N6n%u8^d5Ju{M`F_rbzLWN zbbelLeBOH}g?rd1--)IsrB{85*YK8jsFHMAP3P9f4v|!<)k6H29@&G~r%As~c96Hn zmQzrFt6iC|yt(MH=J??hUhja0YwZ;!AYG_Zc zSlcT0AfDAam92;iK0Z&TF8mg!M12AuZ>|OR89Vk&i+5(MbN=kiN|Yd)9?HYHC#6F` z+*$+UgA8`bYH;CAfJ&m|Lx0SH;43Zq52pZQBiSqGik{r_-1hO3e{^kB9?-U|USc&Lv9v&eWYEN& ze4```bh=Azc{8qi^P%qTD??V~9RfHF-hLV5TomyS@)SF>R^@DBPM-G8r0g_*E@i{( zhJUYwW5Hm-&@?ivE$-J;ykX%jR9imuj38Q;8^JZNMwqpMjq7xhqVd3#r}E7vS&5y= z|407hny;2vy1ewuNxQR~U$iEc5@RODkIdpH=4(s`-4!O@>cGQrbV|)MGtHoWNSv4m zMIss%$wc#|W}H`kWnS$wt$NXktQWm$?0$~lw-$a7WeNL!$U#B1RZ2o8VSHWqs9em)j+UZ9*F10Iv*m%2oA7wjF;CJS31AHB z?#Y2b@<}9Brp{DEtalc9b<0D~In75WEOnfC+0v;?S1Cx28NBi_tmZvEySRbM|Z68-Ab_O%zgr4 z^27;=&z4{?3*ikPL8wfoy7T<#B?Q`R%d{2=6oFY>%|Fiz$U|mSIEdpDW6H6~tA+|} z5l~j_$;g`o|137Y?p@SPmNfQGj-SUPrF2Il>EO-Xs*Rzq#4PVliZI9cI#HgeaFb#{ z!SO@0SG2|XcpoWT!D zj5)c5%uAIrVrNZcmk5As(gECa~P~#d) zx3EE~flG6xc4bnoCmikhg`DJ$y_GQPj^>avL6JU{f-!53IXMF6S#PdoV}Fo(I5~ny z>~$C_KLbO|T6*e=@oL`-4jeH?YME4HP)D*lgB8CsU?o*(7T7=$k{{(o?Ay%=Du{Made7xG`FW&3OfcAM#9zgNDkvb)T-rwmZe$J#8VC=Wq@VFb{Q8BTOI3NVT1wL zw>BH-CyG}WB#1kCFX;tmQMURWmTyi*oNd~N4Jru$=B#-d3e3!%(H%5fDuV`XTg?ho<07} z%Eqv9mACyc+kD%|D)a#VJe2k3XPEn!z=32mlT*w&+4(?PUkvivOdnO-HpQNobCeD4 z)5tubop~(oNwbQX2UcexCFs{tw!hat0&dXS7xHldEP9Z;wqF>y^YSi5d0oeQ=l&-BkF~@Pn*2jmkC3;?P|C}J z`k7yROf^q`nzGcte^@3aHr(9&ELr7~=8OnoUgJWKhu}+Ufp03uOGY@mK}FdU^9o7z z9CZH@`TlBk>)~g0*UHISW!u7Lc3U2mp4F{H&z7fX3ppkJ%&{x6-5*F0O<~B_oB%OE z&c9ME)yd?URDdUYXICdhonB#yy^`tqun>}|gL3idnbihBUfH$c-zH42v$cuYTOh#-2Hk^^RUMwnT`9 z{#pQ*7h%fqk>x|O7XD|H=cM0@BTw#zQ}(j+OYPgwz2$GxG+OAAvcN0;%i{#?)twK@ zNW97g=g!4SqfxkdYy5S#@6z6E8yQ)KCbi6H1*vmut|fqWX$F3YC^_04L@M9Pvr}$Z z=PnfsZXf#ZHk-s8w0(@~k_kv(GADcYkBVT*UwD+8O;Z9fS4`U6Q$CGv+OGTOLx(0i z2V>KX3-aCA0`I9Y?a$wu5AQ?z>9C~!<$#Tn(c{FHg65qBm4}%E9tVG3t>nYLc=hN% zoXlNYET@x7I1j&xmcu-?U?)$Z<+a%C4m8{__ohJllZbrBj1MLrWu{4%aU?!X-28&8 zA!-}kw}ANQzDIUcqIU~)ixwHNuZs64};axi6@_)WZZZuJ_~;2 zXERollUjDqcbV;vtT!@)uzp#0;>oW#`#tmm}ZvR=vyFGn|+#A=QGkX08f7>-5B zoWqa{nW)RWl}&oB>*UhW46|rRM2r9hgPVz&y0Gx%c(R{ zqSLeN1PGM)Pqzwqqf_Ss+0ston+qH0pn5^hFrM{Y7*U_b6J%}N{D6*zOnh)<9r`)d z;$WW^gvb9c5Sb2>$=dzV>+V14{CvJ$RXZw31UX)1bT!dm_K_#J;>C>8Pq;a$1|Yl( z%GO~$W?|Ab$o!AH7L2%YU`Cfl&o?&v{g-|6q_u~t0p($_E;lQOGU?dv@^`37_(EFE zxdJm%p9fau@V-s6lWE*il`df{-LAm6TxBaHqTaqqn5l;pQR~)?;&$VeJbfY|JY-f{T#4(7)aO?k@cRVAGq*H4JZi?A zIMv8{F+6*Yo)f((ag-oAhbP3+tPbbAEy05axuWA(t)z8(#7i;##mrsFh?rBHVlzm&Q}E%E2K%zgL9{)lvR?U?%|7czSf-mdIgWW;FLGsy-DGD1a|A8Np$T~5 z3nYs$Fvj7nT@lM|{1!Bo-yDkGae{+}xv;jw_9pAUU2T_BxRWGEja(C_0D(k@oCh9_ z56-q1fmL={3V28XSaUUQSr-(Rlc)k#Ygd)eZ@&^K9u2Z22+p8HhmVvZyKPsBiZ?pe z!H4Zih6)5NkvNxR$lpX4d)$U3tymMRFbV%rXzzt*Q0P;$wiM#na`BJnH^HTx~ zF7UVnUGiEI&MxY!RbrPxw*^c=>E?gDqT(}=Ay;jsJ0CQ!@v&dA2mWhuz|MKdL>2-Y zW>z4z5Sc)difOgCe5`bleyf0Ua(k}nTU#W_ zA86C5LthWi;=`J{I7Ro&LBF4$YhgA~BA{+0+D*)+Df3$gt}csaVdCxCkqSn67nPdm#L_E&B+u?X z^}p|aKdFj2yfst$*ZSs`V~lK&M2|YVnHG|Lr4@?taB(nBu(q(e+DF!@VD^G)7>rO( zOZf^!@Q=L4!2jT0=R`vx(3eBuMJs|-N*-)*ESlV28pr}Qt(^n$A$dbhoR@=;J)zKE zC#oiax0V{7@OCSM-C|Um`LU(i#RWV5&5UQClTcG!24>g+TFDxpNYBmm$4qHYES?#w zk?*z3&$}n@dS_U9Y4?x;p=xudwcRuE?KWx18M}(eH!~yV4j6- zlBYI)@?~CYv>Y>X;7grmLAugvTW5LzMEEKGzeq2ECD(N%UgoU_J6ZH+&0d=T*R{MQ z|AuvZe_UdMF6YDkMc0={x=4t^Wm?PZAhmh&YOXfJ0XjZ(`2SR*C;=1(j}JTM&M@f- z)&}BhU8%nD(;3hG z+jrWn4xF@mo9W=b7teSqzHQ#n#NW(H^~I*1JomR_Y3I`SW3v6Zk`^x?=46iE53;9I zTe+ui?I%53u8`EHI{5*T6pxa9wvn7WJqare%SoN*J*RL;F`b%#>J-J7oSasRVO9h6 zpb3mVrA>#1Py6f1nPx^x+a{Oi_CMI^Z(f1KjAWVRdSQ(CiY@+MWDJj?Re zwIjDsd0lwsP|9d8VVLO+hDiDP>SiI?57rpy%DE2YqY1b}e>W?SAdEXC8naV78~+ zXZp2qth_HKs!4vjh8X{rZ7++3hm9**gR~r{FaP7NenwuHx64Sn3MQi_YZLHIMg4I| zS_(m@9OA%{xA;&Xn9M#`!?O{zdypy%d_Gn@jeMSz6p7gH(bsAeqLkgF8V#OcAWoXjcF9rY!3nW}k0I`wJaTRD~slQ)urP|_AOFBT%nn`KpWm71~TypNw=%_(2I zwk%MO{0zc~=f`h;-R*#i>}}-drf}gWJS7L`Fy$AhBCxt{s}OGjUBJ>JacZfgKAj2f z9YjciBXamib|-&mYvt)4#n4`fSL9ZW}B?(T})L3ktyfhv14V1VgrOSUbz zdU@V1tQDE}BqJ*u=nGBduZ(WZquh!ich)>jJ{Wg`{?2*(0R?Z8##P*7d!ax)mwzT@yJD7~%b>vdF#3BFxA6?uL{>T)%3AR5Y`b0BK!F6$^{2-^jm3D$ zw@uN3o;M%QNmJ%K4LG*)0Ns;wcUrLt6rCEU^A|`^9+>KZZ{U(8@}5=wB?nB}T=^+m z9#^$KkxuDx?g2SWL@-7%vl-g(Ri97k)wG$&GV92hc?Ih;M8JK?B z6QU*96Sd6IVB?(DMCpJt#MY%i`pXizObLU&7biCdc+R2k&1PP3&R43_wanP49wrv1 z?EL6Huok2yXjb}s9}W25^!K*lo|`zcqdl0Aorwl?5;b@CedGM1YbGpHI;R}-@psBc z_5RzFV>#Kf$hAo<_j&GGPT(v0=~LKMdJx-RE|QJb`lVT#V3$kt)ZWddC@Z}S6!ObF z5XVlh0@8U?8u!HeyKgt`YN{l*Z-@x>dds<>f%%Py&|b|fA7KM!Q~c9YO70*F+dhTg z+0{%biZtkpuQ1BR6Z6cWU-qU;`Psl>Hl(qyV6t;9cU}gR)=3BMfye zro!an0tmf`B&N%bVNP*^oCO1%prxl0$mcOc2nzkk4+P%o+ArqzZxW#af$K;nf#=h) zASvcKPPqXG+Q`a(PTM^-^!^1y1P+gb)Ol(xO|?W)kR|2fo>L#7&%BB#*^&a~*pT!_ zxh~@*<&M=#xwXQ3)s?qs&5*HBQayPQLr4PTRmO0M%a2` z(Gj2Y5vn)&q+Dl=`!&M!ZIHX!I63w+F=siLJtka#Ce{h1Ye_7TQOFf1qo0=-bn@&> zT?YdYQd;!ZOa(2R1jBxs4d-FCkFZCiRUv+wOe~q4Xt8% zoWmLjvR!~pzG_~vm6NVhnS8$N_>g8N{0=G9`F`g^SKTNz57&a{QwcTbEg+nA(agdw zF=L}baFj;k`jP^BI;$~wVbX6-JRY>L+L6zzh_H1g%HUilz_q94iGPgCpts3Zq%@7S zW#5a;GQi6-95$>5WWKnGlb*=?l|m1tyF{_qiL^HNm9I>$){?sSTT`Lii8T{lbh5TU zo7%2S3dyGTeQ{&0BP= ze4lfDugVdut?Hv6Ik7jj)lwNKVzefGt~b&TEr08xu8X-+vckM4a2(*mI?+o+g*|zU zqcx`9%EM~@Ivj!?qeJ;|HKmoW-~KM6k~ObflBh{%76AgZb7u&&@r_L;d@UY6rWwH` zx+*=tvp==?NfG{)wATA<$Y0tMPR>vFQO`H zWXeRwXQ#Y!^wL2};jDL1t?%o!;H+xL$jiN5Y?S6rP$JFuT+WBk##aj7RAC4PC2!9g zg<>+(Ik7=e^jR=ZHg!@is_3{`Kgxlc?x{kAm4wE^U{*??zouwn2-X~_*TNpzbQH!M zqTRZN?eK-LRkEU$Wa|hi!BTn$JYL%3O$P@7WZ{p~aqt6dp1Zm%x%TbA$DkpcXGLS; zsJOpsjxf98lL__{09&X>TU4~u(ik?`Q11k(d+_sa#+H+R)kZT_On!kW#S%^l&K`h% z5=_p%VdPbMO;=KgWgp@;>z$hBq4gte@^BtOFC^cn`z2jZ+oR2vKb^QTlMJTj9;k9G z^kfqGNa(P(gtkVlHy-Cot|3PVwU`d`PLJSZ0vOODI@;t4XuVkpy-#3mo>v}fbsSsD zi9FDbVVb}g^hU!>e)ou4$qw?HaaVP}y1^8_&c~fT=u8oB33s-!Dn9g`uxcLPTV0XD zj)U>}$gjPi$68*R>|#*;%Nz4CeM}BRHye zTUfs~4ms-Dly97^x>kG=TJps^6<3Ci+Rb?bc%d({v}?m+{zhU1k;4~vH6cZ8ou~C= z_qHz&&D0swE&-p{3wlwzijb~Pa6o+wxd#)qh3@9dESik-m`ilzt#v2caXGawtu&GA zOm%vHsWWCY(7oy6tVl#w+tviFk?JP-aiFfAgeZ~@7IuA{;P;@yn$V?iY$G9QMex=H zEdiCN*io1=4^F0G-4Ad!G{;D@XauU=d;Ov?-9s*G4lcT=e_2-PK!$_7qdGhK67Kj}4r0NO^Fj*PN_CQCs=L%YtQ$94gQ+Js{k9;B zT6pJydT#lWopahQ&tTDQ#ZTAfJV~I>r5Hd+Er@iY@qqe#ux2ddKHd6)vGdPpD)X!&q=IX~>P9`MG5^O{z);x#|)NR$t52v*7^b6#%8p_B*4OyS)R zPkwuX=92g}hy490Bqt(K2)G$!ibKKS34(JoKI5O9=ac(D&o7a{es4}}i9T|&eR3in z>*|fc*+%a2qiffh_Kp0Q63!ZS85zP%cB;?()4Uh{U8xI)@eoyCkyrBw{n9(I1g*?T zp~4bwWuToRW+6g;mToN?L%_yjzw1TvPqIjo3Wg#+l*IS;H?g$V`1r|dDGoKr$H~;n zI+EIkS)Q79V}be3d~s7YGzN#CF4J`xRqJUz`Pm%mr<(V|aqow?`!m}<6{^e3U71_> zi+zb6-HnWESB3=h(UlK5fFxa48`}2y2<%cI+?K#NF5L7j=t6vkqok~n<{27pFCmbW z%nGek3Y!MjbL6R+tKe(%s%3k{2ANv*4rSyAcnWl3jJ7O2(pJiEpi#oBZ^A&Zu5*(a zZ1kB^3y@T1>LNB%h*&JqF`0g+B#fNoyr2YaJ}kL>iDuT_Q{>`M24UMR`Bags;<(O2 zKLaZc`}z}qu(L$=Q>dQQ;4CJv*uy&tpojYHYq*rGl>3{6a1kau zU;B4IKl>n!qX(EcD&$acS_PIHCFJTHh$X=#FY9;_Tk>e;8IkYJRr+K&Pa(N+vMRp&srnai2USEQyWX7T^Z<}4_hh}{FC4GSJbMcaHhq<+dLysRJ0*Jm0Rv< z;^_HW#sz?nDd=qDCSfjQM)g<}FZC6>wkb%^6^8ya=g!=osl+-0`Zt{3gHXrru0H$u znMeTfZ|*Dg;S?M1LYAc50O0swgqJ_JaeqQY!a?F!zjm`q^idiFFJdS2K)5R*IwkBT z98#CuZ~BKv<6jVb1UQ)pIkhC9my|ea$`t6VptBgA zzd*4!sA(q#PJuK>Wv5!^XM3x&WyL@a_F}Enj%FgYIsqxOB@5Ooba8XT2!EI>n5P6h z9*hjMI`)Np#g?L|47)z+GLm&rO}iEgNuTQ(xLO$^_%|^%xG7(3ryxwoy-&6t%m8!s z=W@zaSYSBpR0gH6BnNx`o&Rr6Z!Qoqi{!u5Y-%{#wksk3_ptd}B`N4{bA+91S zcqUJzFE5`G49~=9&alCrIxUvH)$=KPQ`My+1Sb2{lB;(HcBi{_-M#P##0J3ihil659Yn!13EyyUK4(_uZ&f_QNYeFX_JO>Y|r1HEEvOnazF8}Sc z-T%#oYPZ4ddtcq`FF0u=AKM~Kav)O$lV zdGjxqTQ}8==G~a)F*`=^W3~WQwS~Ow*x$7m_jQmEPc^yI(ZyH|z|c+DICy+KKvFT$ zax6dUT=Rp=+{s#J7`0rSsM#$Yh^FLP-wCg88pu>bJ-nv~OHd7TCpf*y!h`SSQ!gS( z<+jqcVXALsQTA;YO~ueTO?0u2{Rjr+QM+x}bi=9WpFnTarE>60)Kg%{e|Hoyl{LeFv0-NK8^ zW#OhkIV!O|ha-2q8CefO_}vl!0^K-hah478=5hQzMK9$qOs25UMcH;KMH!ogi`7C3 zUzF(mHw;o1b2;k=@$W>zV4ZblMy5dN`1XTys7WVZ)2&qb^U}`4u38z~l9+7yBFxR7 z!D-v2e3*l(pNc%d6ychx(pjfEW(qG0RChON4cul8OpX8KfLJCF@0)Lr+WC3;>&-R} zJt;Zfa2WIY&^9fo#Pb`0JBd9N+JXGluZz~nS|nuJA=jZlbkt18iJEZBn0aY^O}Sxr zPH4W-mJ~%EpsnhjJaSXS%Xo0G%E~!g%cIbcGFTwRrQKD#Gx@hk-B#X~y%x&#q`Auf z+Fje32+V=X-t>WDRXT}{-DUA9^Rp*Rr^_gLzc4=u{xS~_*!ygkT#a!v<#X2dr|ZbL zDginWF(q&E)OOwiazIHcZeT^_uZ8fH10m6_buWXTIl;UXD<;E)0EVUioamRow;7`OA2 zQxz<@Sz;7($#P7kl96;&H)7`ZH_m zK{=xa#!Ik%GbT*1TS_B0aidO}<}}7BA=hKu>q$ISRG96!Xfo{ZlTQ?;Gkz(>AxtNgLxftflY}0${%Syd3cfu$N>qQ7aco_yHW8EizY#>m|ENgQ`_uZF#=8QL@uIVtMjl- zXTm^^bAI^MtWzdnm@(!}J(x489$vdvm~?yjEZQ+jdD3ncRLM7FEllIyTJ=u4NUV64 zhhFVx7gw8j4Jvx_Y|H*ShxB++=B+c5VY$aKeMw}ArWdjHoJGcv2?Yxc2b*butH~^@ zZHL^xW{V*~W420@ESOAYjjGoEb6c3&!vmZ*QfcJwtx3xSOKU|TcWQr`u-TqgravC| z#IHPQ(~Yv9!Yq=eoR0i+5eCCMWG#^>pD&caCj+L|KAFI?ooUpY0JnU{Ad-|1EKVNr zekt^wl4LuMTJ^X%WILdXQtp|Dx6N%3@h8VngCQ)-Bi|-?ARiSOy(_`#ASU5$ zrVEGR&bPfqAF|bmmb9?$!c}`XnpWt@-8$X`sBfi_o*W-kj1`f8-_Cr|*;1!?cdqNh zFCbCMSy)efhdUM2zJFF6{dbnwd9yUW&Q!woH?aYTT+TzEy0K>68QWaJFM%Wm2f>5H z_{L?B7Ct}4zi68)ymdq`$}eI z*L%+eOko6F%nM!fZRd}g0%WrhI}gHp%R*Ftt{pFJ9j*a)^2YU}(-=H1XGOYcX!nt? zp73uH;P<@r0K!!BWHGxzemjHV=l95YH4w_BEYZjO(vP;|;N_9}(^_6fAxqtx;)B3a z0@t7hgY#X!;)H{0LWjqf6EQvew&k4!9ge_PI%=ff6IG~O@<>bJQY`n@Y)R%Ne5k^m z+4+W5ZzPu5oYu$AInR>GbFO>v{*=^(QZ}PX4g+0kdCz|8FXUAvPNddShvY-g?}mXk z8Bvo&3jj-zb$(0Ibz=gsKa{BhNZ=4_P3DpegQj;qIk!Le7wBOb>ZgiE(|w}Lo5yOe zcvXHUn7=8w>P9SEs&NGDVRJK4e!}*3LQ&ah1>|$AnC`hVxy@Tj=N!Q@ZP~%hLz=yF z-IAwTQ3Dz+^1hLvdMcNax?9)@28h$DNbO^OL^8j$K5ft3R0V$H-6emU4d$22%RzQx z?AZf+f|%bkxAHCD-|cvp(j84%kwDwfS6+Iash0CC6WcoLUn}QNGF3p(1w99tiptEe zXvWV!^1zC?&i)N$=xNCa$7>9#AJCXh^wbPN$n&%2Ya~0bmM8a=ZVBlR*+1(8#Mi#x z=TKFw`75pTLN1$p2JSu8&a?pIK|8e&7|?qCTsmzh=WV7&S?(aiWvP|^3Ay>Y==!65 z8>iB1zG@?tsr=39B=I*@HR4Nx0t-+!OlnFhk+qSTb1sWs0XlH~<~s&WZp_E@bG(TC zT=GyfL^;DiA}hV7SND#7iDog9Y7WP`>&4(5!6nSqreR7QxPMH-o8^&r<|7ttk|;z5 zFtNd#edft7T}pj57wP(>;{-Q&p(jBff$qF$n>DgMS*L^vj{@}DnJavdX_!q(|s2!lcqGZr6QyCgw%u=qGanMrxDvM35VilxDk7jbg*PV0UB}w zP71Sp2DG)2Cj)xtOkV^;#xrcqe;hXlyTL@}fkQtMl1Mslx6HB+Ea_B67cyT!$5Z9aqJz{R#U-Xq;|Ua_soyO! zuV2t5tLmJE6Z`E`bT9o4`-b^&IS>-(@#%?(72Y4W)oZ+iS!5#zdYv zDQ`Ae=D0X-oQlhV6o@A=q93zZ9b-Rm1()6lblZ0CYywmMV0hbn<~V(vRGid3yO$eW z{7~S(i9|EOP>h{-4@hT#LD1gpDQ9p@{JirP%04aMxig^<7hZmR2`L;d@$cV#rN7@< zMi5=gNhXe!BYRAH-YqZBvZ24!dy5l$L>{s3fq@nl|pXI7@ z9;S_DRF#4OYJoXpq3b0YUCxFx>?HGiB-3e?NSUJ9MmWhcqlE^m%NmiF2U zGjb0C7jW_<0;36cq@Z4YGDSfw@)5j{8r=SAavSbBPF2d*r?#inNJ7_1*&_hLmc`}f z4pMz7bGLG}``|UOK@|qOM{V-$;Ly2hcO#0o zx%B8x41zllWB&HCq6t*u|4a|o`hT!!iUMcBF#bV~Lb^q-J(O2mAIJ$GPVf~(@@)xv zIcYjpowc$i`LdILo93_RQya=cLBGC883U{3qhu^BYwHA85(*j+|y*s!koEb+e)7>&^HxH{t@>6wb(D+=r7a7 z4^BVrz=IokfSrIxO7AVf_W}OeJ*-FNUR`*(78Kq)@!DqJ^=pIQWS&|AZV(9->JBBk zj9sf_!Mxj*4d?pnE*u*{@~u>~nY=9uhE(|zF|+TW2fzn+yNj#7AbU;s{n{-~5})!A z>0Vzq7qu^U**xN*^Lpj&EM=gEn{4s~l)QJjMJEYp^){=BIUr;}Q4VeJBw=EGCBo-P zse}`dc)zDCUo_{KD+SDS8&;SP*DZ+*l5nHOV;Fd>w2(wu2rApK^EJii+pr2;h`&BfJp-z?>}b_O*f3FnC? z_FFCD){d{rW>RNEnhHqee{w1aHvR1BAmc&id3NlUECEZHDO)a169?WMf0SfJ z?!0%9Q-E1gj3tf0DZ<*U<#X3_1joz-$*d!hbpPJ63psbk5x5&Ghryg~zJ%CB>t(I& zd0uMA`)3MpY{X^9w?kjdxjOEUg;Ru*uU5r^m|t!1ocQONQ1TMO?zwhygZ@$`+A+wX zPAa*3sp^&l#HYn_T5`xkN=jD56K>d;IA1GGnbY1%4ly4jWA}ul%kb9l^c+R??rbLs zit1NE1MT>%HwH&3=}yb+2|f8HNu4R(Q>Al`t*0>cJP>mG8`sxA>DvGtdX; zNEe7HX7G^Z(AA#5srsA(+>m&-jdCnUQhqK_UTM2FYAVbtuV$S(5hq|gKYW|Eb)8ey zJo%63@>Z{j0#5P=p{W!&?>QsnzE{~RSsPZbvj*oS`1}zV)ksWoo$<#frn1R2N%cAKLC&=uF#-ru_^nJOz9-3!s%tRO z@MRMDpO3u`c5PAQbr)>_d>>v^E@L?}UP(KoeUN`{RIPp`^jpyA2lgfR@RSL}*k8hd zJ1*T$u1Rrr(#@Q&i*K5&uQ&E0pI;i8@MSXOB8$8u2`ANi^5~PIf+^gT1mGzE|Ece8 zV$`KYns`H&?o#_v6t*GUC;XzR`^{`!T7p2ra=&X2KlwXv|H{T*D547SSs8fd4MGPm0z?Cb2VJtZ{*abG;UmaT#7s+{o9G+2R*eOuwLZl zR7lI)PMh010;C2RZ;VB=E4U+7&C)O`60p8RBaXl+a0vaZ7xlq1b-CBtRzLyIH(L`R zE?OTK!HcByAgo@7Nu<+C6Pm9A>h{aAIY90!C)~-iWKBVxtkcJSnK>3)^|5erUSEwZ zgCI{Er2#nmUNXdkkmEa3mOkjR<;4bCJKA@v+;X=XNvcE(K+Lz`jm4~%5cdrrzQy4T}bpcj8)tRn(gSnDB_9u)*CvPreYfU+odU8&Nyk*&8@3$S}QOGFUxe?nc zJf=ASpGMcW8e(nlt@ZP=nAq6;FuNln(KzZV~N7x0N4c`DQ1r?c4(7&(C{IMVPH5wZP7O+lVMXbk~{R zNnG!>%!s}1j$&s~aLs!&$w=MZlK4}0Nf`lV)`#2uXh+^_8N^#Asne4~V-^}np14~4 zoz&xj4qJ{G(Xo_vR#nKK@#o8-%#%r#%eB*ST%TDnl89z`gvH|8Y7m{|JIHu%Nrq(R zFE-YJAj|CwB~G}2HArj+uhqj8c&GEiS$ehzb?3QjwB;!$v?k|)4xF8)DTrw$Y3m~p z!Pi|(DW){1(HUGnXx84#6bQBuuiR7aH9L+!(X}Uq{(f$O`HAl-s$PQZq?GXX-<7*i zBe`*#bqzDyAlo33mO#1fx$M*kmE2$T9k=`^W5B@Y#OARU$NPKO0w3Ia67(tkrkKdl z%_E!P+rUD)zPMj0v^W%MJk6!f637bP=W1C|OGR{Ux#;u~Qu=b=;;NP-94uAJh7&e* zZQIU!o_HJMHcNVQi*GLLi$2*{g1`xf=3QIx*DJ{lP4!II_9!9+E1siTezmDgy*sfv zU~^qH2i{u2OSnsEF(cHhcvFV}-AT;_t=B_=v}6Gy1Ledn)D`8mHuoRW#jXJ*W8 zEZoc=Ifo(Q0QucUN}y*dI8++YASWGt=P4%OXHf87PpUG@4YcpQh?2#h8251gL%5$$ z{vN4h6>}Ul{DkSrrtjSQf^ynk49zS0v3D1UZO*g&aF+p z7CjLJLRazBql+s~H8R1*7Zx5{FNX_0|i)l-y!t=}h~ zZ6DRc52c_zlVQe$e#RO0X1*NMjWvoL6FjEV|1e7Q5y~ejN5SOMX-R|&%&3&K+S=uS zz3-`}=xu+~#&P2)jnz5TT?=|G*FhXh>ch81IuYbD`2Mb3@Kl)9%(sPoGhxQ|4A4rF zFE;o}RR7vvG~ryRT*_e1e%d=sD9rsECTRp3K7^ZPZ?;jc!@?MyqGym)V-#`K(Y@^R zCH#>l-1k=g$!M=!y_4%__$U`)AX=>FR{NAl&q3^MX#n9-8hR5CUczB1AG(#$`RL7~?-y|8@FUVN7=U>&+V|-^1bEP79zQmFUjy7GqhXSXb1c^0Vrj<5cPV9V$ zah8VWw=CDDrU!LjnC8g|C4})2Wg%uxkc?DLvYJ_00?f#xU%0pv+^Q1bc(G)XJ(D4Z zso_eSif#BIhO~x1D<493(-&x2+aZK?NRkB}2d1VJHGF$0`}V?)<=il~4VX&8rviKq z!1cbmY2Y1rs+?6_-Z|K&orz0SkpqzOVUr_LgUpOEJKJ0A+=Eflt@=pVMj_;^IZ{1X zK4ayK0|WnvjyZ_j4w~7v)<>$|o|Aa$)e+eF6o%_tos3+(1UEy69F}WcF(o#bI!7d^ za{Ypwk~CH}&@{HsZ~m-A=apz-=dxmIrP$R=BD7fn`o6X0?40u9NhzMnXI78ow|*q$ zUKXuwWA~eI@;#;c`tU4B1;289@D%i33?Sy*){j1HuwhQ~MKM0^pi-ow$~gj<>|piCmFG5HK(3SnbuBdD(WwKt3ge(7VCmr)l%xWyd7d3dEFl( zcrR=7oci4&d~)2x|EL*dBrfMGsHCoBq}=&}EGgF?$UL|wsB&jkdKm1Z@#KCU7e5l> z!g<{1PLB5BmAOAZf0cGD++HjqThF5{@&=qG0P+J2Fc-~r>o!|82_7kRHGfXB;l=1V zTh4-YZ8Pfp^Rv;Ing;^)L`ft6n{q{d^r?@PT)ScnTljy1gg8SbqAi*QmcL91ilBEU zOC<KcYr)3AF(NZ# ztF!>`oA8sWWiQEvGW#LDbK4?MxyQC8u{8zSp1_o|Y{HhOZGq$-%snP8hT^h_g@D3K ze50k3#~v7|!kOLFGBTIvkHYN!@RkiW>RO9k{b@}vpq?(IiMYtnkY|H9+FZf_?uJc% ziry6R7F6Y?vmFdK7rU#^^x|r+NE$y`C+XX8Uy+3^pat9(uH&!fAThs{qE3Q0%F4A6 zy*ga@q20V54)VpPmo@NN>tYKIGq+AM4#XCk{e`6(JsV4-gd+`vRRdtVX?|TfN*FCj zozyo2C6e}1F0T5hxm77*gNCQ=O>W3X$~Wyi`7plnS59OFhL3z@Xy3o2YUcAoAd(l! z;Jm3JORwnzCIW4Bmvg6c!4TF`#M^DY5XaCxQ`oLInX0@pH1O0|skl=wP!=8@+mmXd zNIZa2Vo30g0`c&`FPzOpsOvGVRO97Lr%Hbc#8U_x=eAu3QRw6%2G_~AiA0NgFwwKS zZqwM{Vq>pGMKUJ!$Y<{McW6C4q*Trn)oV(eB+A5o@+=n5R!-M> z*}i7N-fdlL6;?>HO~JZTs?L1mq+dRa)dsiOMdc}F+sQK{uoF9_|@I7AVN54+nSWm3sf!Uz+{Ld)P%!zI<711z7Bsn%Okx3%|tQbSaAypX@sE@CDJfG#a>(KJQY{XxaV)+gWzWopdn`M-IlYU|s_)=amMHL47bBJI z;J9eHiqVn(f@?a_C>|g4K?ZZg9%qCiHuqRndPN_if%!-B!O26rWCh=aZ)ehLd9HT1 z?AR5)%*HqE-PF)|LBwE^dlZ&5T;u6QPm%(+1H#nJ1I*m85;4WG@SDBs7Jb433lI;zw6LXfOMx|uN^5Y2PB_QO*8L@0S&u1Xj zPI@%qi7*p;k@Ym?9?zUBUy~@-5bY}c?bNuhd%YRDf3>VmZI=ii)EZ>CrNF$GnJ`|m z_upMQnWs$cI4LtWlKQs8i%y;EO1(a>{8YscWAztra!~9V+(;Wugli{zQkzetZ!zdK zTTQABMsv*scU^{C8{<*yvsUETGu5t4Dr0W(GiLzo+8pL%Sa4@1Xeh!r;;g;Tmtu#F z_ta<{c8q6E?fPIz{f@-Sg^=L#1TSALG4OI_2W1!a{#zapTGeEo5@PSCL{SFU&X2dC zO>bx$&mga(k7e@Fb0G2mPB$i?DWQ;q2A%7!ZI#q(V6qZ0%&Szr5(rv99tF9{V^(_R z@SxrE?%wZ?x{JSjefOXL_aFcCFZUmRd;ap@fBEBY*I)nrkNf)XKmPN-{#EiQ9u&L* z88$8E!}H*&Fu#bsDWsq6IBR2I5U62aus^9Y5(xWos}jlk?iLpKyexC)Nsw}>vq`Y7 z-2xWcQC?Xb%ILBX90^kX^FRFQpP#?{*ERn7KL5`?*FXL7AHV1IZ~ya`zdrS;>`hn* z8;PuYSw}J(ry`WvZ=)opIFgHUjZ;r?DG!)U8n>g*784V>z9$sbkz7FOVzc$UEU9x> z^?^Z+qwuukkX)Q&k$sY+wxdtgo~e=O_7##or5LY@4MBJ?Fkw^g;Kg;W(Wxx zDJ4e$NI zp4H39iEP+ysb?|-2=cF{gzgmnc(Q)Z!`rTrGA3n4PrCP5sdA%LV{(g)i}&2oC}ra*a(!%p~W`80R5SIyY(T0_9ePI++I z%)dW$#2RjCD=muB>#->GmiF$Ad7q2a5+e_2mMDs8#3;e=dB5!RX@_;9*eRUzk5L!p zz+56Fd?O-|%e8Z>Ku=A8pvjQ9l**TO#n18;+2PzgY3Un}U`0&+kn1KpBC92V+_VIy z!*0Xc3CiS|P0K={g?S@^qJD8ryGWm;%nIgtJ$j}V2_)1oa$EA0FQli3p@Lp_a@DjXV-OKgp7iShNP&FJ*+K~V;!!~djuSmRKd35 zTr@v0e9k)~0(=ECOnM>(BPJ%$i7p{*i6%o}On2&o8TzM$t*B==u|l7)AJun8;T9og z?ttP(I?fCK*eGqR@}Tojds3Pn!Hi~C5w`Wq9tZ} zxl(E{pe@udkvrYIhVVje8H`0m8~^TohW5*`OV@D;xthY{n(chwQ@&Nm)~}-*4=Fxn z0fq&RKA_XJ&Y05U2Cx~vYyumGGGY@#7Yb+wIHTYpSi0$O>!+1M09^o-Nj#w~q&xyw zT6aYFZ6F1LtVV=K6QDbH8WlS_-$ZwnYc?9ET4fUz*2Fah0!7Ff^LKP!;OQwqj4wPQ zal$s6$Ncm5$XIS+?rPAn(^2~EnW371w1_yMpvsZzjv3Q;aqiOq(T|^Y6$Dgt#HLi) z9Os-xo2@-h(YkhG2NiEiRQk2=`WI_OJD{5QWxA;opuB*Fnr>pk>JzD;fJT!p3&R^k zMka+jZK(lGMtAGq#qQ2F=Hv9;2dzhn7-Hgi7RC~ShP20qf@vBkDnc} z!h+BX$%k^fPKI)ms75yoxne)g@JkBxl%ojb7==)TaM&1~jlf%uNAHekyD?`@*fl#k zq#ujh(izV-$ZKLuS+&`et&Y4;B|0&Gtq86yQf%6ZVUmT6iqHXO^oVd`AFmq@jHW@$ zDoP7AhIH#<&caBz8fj~`Limn3ItFLzPPuOq+!`Ab|I{axncU{G;D1 zE=hnMPAihpK4FGx?#dno?2sEuDF}8!Yn8-Pix5ndmWqBSjT%cRl-4(fe`zA3$C_F0 zentZDs_;S!ma$SoH$#QTSj=MGVfhamkp&8261pj9Q34#6$Rl%r+FTt+bV5bl6crFD zrHDfFfWQfMMEbM3LR}c4a8m^kwvH!~;?`yA_?`hd5%Z)tCn#s9IsK%D;!n68M{x^` z8n;fBas&sTw}5PxK8k1sY}~SyR*8)MS7w7;=8g+Ihy))0p+x~C3FDzmid(q0yy9(X zGJ*}~-Cw|$+DSj}^7NGmYV0E%>v-GsluZcUd^3xy2Ko+uF(`mfQpN>TpmFK=@qDy# zKv3F7MmbWuM_X2m)S%0Qq)kOGX3-Qa?u8JVIHvLpl1?K!oMnak^gs7$8?~PKT`PE_ zK$Ccz7Osi~jq8T`E0RR?8;HdTZXu|nFvDm$9cnJ<0|+GNusYHZn?79}3O$)hDJnbK zEwyl#!WGY97&;$$aY*LrP(y7;e_x@_v)U%nQf(_Rs6Z7I4fGixbWs5T`71=qnBnP+ zT!C}^hDk&DWTgp_&8>2>rtB19AgUEqmyFmKlB zPC~+N1}nI%T);;l{(1cmg{4SET4a~;NB!87)jbZAII>XcR$i`%FA~SV)^wLDOgBWB z%uR_ZFn5qAE#!)9NvuWa895J}c2Qp4c`$uWJ97~LAX!sMV{}YxgCPttxTfrxkZ3nt z9mYGW_Sc+J7Vk9`g)~2pnxyh@S9DH_ff%KjV>qrPp-8X$BifF5EJm%97cB})Zc+KS zsI2RM1Gq7XkWk7Sl){uG6up_`w6M%ASRi7isC){q`0+47NYh8h?JFU>+1t^MQL+|L z6+o#)M@1lo7AvOo8KYM`(W#>Lvn+!}ym$+&h4ZGOq^>Aq*2d%sriU?nN9R>J0hehJ zO8x5X(9>fg3s(h(x|jw}0j)b+=b4RWi;l&WmO$w=wljuxb= zbj+hpiKrhfat&W@R4+3UL;`^gNv%xNyJ9IX{#jUDpYBxiQ|hZ^dYpu)hehZKRc(dZ zapJ-e4F$IwCa-9{%bRSggn)`jVIs{lHYVmbDd~#uS;0rgifqNz)0D{eAGa8$DRglQ zWE1Z~gqMXib+mXu5kSLIv}VT95IH-eh=K7%McK}qGJa@6ETi*YpcA98sf7!Nmns(a ziZWKB(-mo$&WsX|i`YqXGu*%z9jgn+fN?xx7fP~*ZYG+l6P>p@yHl52fKchh9rXmf zNs53Ax?b3DV^4}h4t3*%kAUDBGcsE-Lw7+$6Ei0L5<-_gAx_-2Y(|D7uWq6)y!UW8 z;G4&=ZXyqar4o`fT3X~vu%|uq!PzxzNvGCyV{M@5RGO$=J+Jm_I-GbTm~Jt+pe&%U zR6sq7CG@K+c=Z^jz5-PlSwXfLJwgQqhCy$62NFUyakWpXG} zZ<=BS@pN4h1tl7Ku_xk629)s%f`_=lPzl$!9RYBLzKb>KFrcBRS_O}h`Vbrz*IhWU zV1VR~ZI`&QIn4qcx7QN&uPP5Wor1~+5f41(#khC?MoPUf*o{F>W;7$=s9doi!8~|+ z5FScj+mlrms*Z?*wD%jFSk^j23Ixhf7Cb+|#Xsq&CCe;=Q&UDJ`Bcl-_?l zf={TnD#AFBFglJ%B70Q3DbfZ!EpjnmxUJ%CNViKVuAC6% zq(Kd12L-3&%XUBtt;~kWN$7OqS?%zIf;IJ-z|_GitQ9RZ+E^u)$>Znb(bFWph%%RQ zRD~rd!y9ZqopLx`wFruFDGRQQo1!H%tqrV7_og;V@G>el4}$Qq)k83y#&8F;w&G1p z7CLNT-I!=5;G~VoTmZTA&df*Vs?+^tPM?Bdb{4Z|?WNef_h(RBP*ZD^ZDpE5aE9x| z(>xAOhIeOeabVuCE!2W$tPJvrpNV3Kx)`0D#M>x}gcOc5FMI|}AG(bwPcbn;vE3`~ zBeftnj;_MQMCX5kR}wPiU>+ejhD!p5VQJ%BDRRh7_89}aW_m)D7!?H^f&yh-g*1zz zdE#H77^SUGq#p5P?bmrJQ#k8{w=Ss zo-$#kS#u&yBgVdtG`B-Y8Sft;660V^XtQa>j<_vybqZaWA8NIQQLI7DOdw8IoiJL# z(uoEc>gNOCMyfGU$pIJZ$as-1jiM1UtTP}`JcE#-0k!F*FyVIE8!i2DRAn6#W>_p) zDmoZb=>P*dhBBGr7z&k0owIJDA&utPWl81=RL8c9NebKCftI=lJCGq8B7G>dH(sJK zWfqpLnN*IMG;Tx#5)Zdl(m2fno*D&HF}NWbY6=~=gaR27*@F0DEE)DiQ86)IXm9d1 zN;VFy=wsl)EBh!qZ>aqvJH1X5Va8gdiAM=x(}CaHL}fU@pRg|Ljx_=GU9MMDEE zoq&G6<;N?^(po|5oR1iP1{W~55Nlt6dO>Dhg)uim!$ojG^mb)OJGSs6N6Z~%Z?SmZ z#Rc&6H`j(U2PTvvGgzHb4HRE-Poj|TK{Ep}TOATX8}brqaOCB!t{)1m31N1jZVUm1 z(DV%vNA-%ii&rGZ!-A5dWsd8N7IfNaW{#qt^zxa4Dn)bTj)qeYZ~1wg-+V14Kra0yHM?>Ks+Utv$$|d`ifD*)Ry;+ zRFCk-gm(=^V&OYoOBBb%NO`+CKDY|FI5j1j&V9kRH!1Haq|he5@E8DS>0fp1PZh2e z+!pmTM-y?PY(TprLm{UD;z%c7JEUN1uRII6nqL|2iidFfGb^VOmpJ4z`D=K_boRi4KyywVbemq!JjfpX)fSe z)p;GmQEl@DwEGj`Q|dp6E-QiwPT*C}wCE0KX6nqN7)|qnuSedfQNU6BYiP)hj`>yO z8c*R&(aaMUl#JjE5#gyydv&G1U#LR?M)5G94|!yzj)5}WDJ_ZLBrM9EBC0k<^s84S zKnj-GmQuk`9#e=RISF4xC}qP$3;!vE;GOcbGURcKLXu){dMSk(BdTNCcYXRnw|>>K zim-R49rbHzYl~QHC1q#jH*IDQNxm8M@NEInV z1OXFwXsyRmichzX_CUsfJqpQFz$my!pk5l7V%JYOYJs|&0abj=2RzfU_@D_mbWr8- ztr7J^e@bZa*pe_5q`+_#>^)j`$POty;{3#M6f)m-(0+IN1rx(F7*x2(%wmX?EPD~A zqy?!VMIzXf5Fa%(WdBKHVN z(@BqNsu#HtzFBA$Gb@$o1kx)cZmMM3JBPdB|A;C7L^PH{hgHO3GH_=fGjBz*K z?+$H#@urF$tJYL7C1MibN!A+;e}zN_UWFQGv!WH)1}jds41nX=V^5R}(kOv}PQ`T# z;UNJu5gWv;QQr_O4pnT$-tdAL`o@EVLyp-4sDl*NeH(R3YzO~k??!!Buv7cAWJLh8$&)>*jgX!$Y0X9 zg&wSS3CH7ruZkl0JU8O#n;0}91tIf!jna`p;Yb|XWZoY|(leSjJOnmTbMz8UeIE(1 zmcj1$K-^}}ak3^>M@V1OUA^tG<#ozFrxCjyW=&V*I6i4bJE~vCG+09| zz4n3>_fn8plP2?i(v4sXGYaRbPuw6ja`<`C`c_eTQ(~m|m(~@VczzY8#m<1K_tPG> zah^#$k{pjsW%68fSo!*mnAhlHp(k7cabT~9C$TOxZT$*OixMy6VL}*V;!j*Fd8 zl!>B=LciD~i$_|mDuE*$^rSz^=%65^aG`Ll!MGlM9b-GgSqf-6iWtcuZbf@KT_n(Z zMcY$l5v0(vSdSulCeku3p!2iw!dW8ZXw9?~^IkgE z0b3XgkJ3*s^kSgYi^Y>rScz5XdFV%xe7r#}J)q|6v-CC9SSpNh2- zoDPd<6%w;TuS(F0Zj1+JB?l@H)N478XyXjtG9APDi)@7?b8%5ih*2OQz0#kLkz1ek z_)2K!u(v=V5&iMxbUHn@m#CLkVQ}mLB3(^hY(saeGpB_=^`w6c-vpGp>v^YS>^6GE zUPc74KJJ}H6|qb@#22*i3^)dgIRR^A?Wppt;Ge_B1P?Xd)Vv~OISuP5W$A?Q@`0Hq z(|GiFqhEW%DmoGR;`C`8Pj03Fl=d>BQ4#Y2-DoWAw}1rsnVBr(kAO8FngQs#>DgC) zMDd!4&08hCUW{xO-ocdMMe>vb=&85fprYqkPpobd>fx2>x?8?YLn<02K?FQ>v1Z50 z0U6xVP@v&8Q_x%gqgUbnrrIyZ*2=!El* z=WEtE*P=v|wGI|%D2}5JU&UyF*C8g}L^~Nm(%K20^TLl?t77)VYggZM(*=MWo`PXg zNUj_27^s+yNJT3MC?euKv8K+G==4UQ5Q>PrrlmKn(C5dyPrr$aJDp_Z2Yn+QH6>Ua zj!+T^s3M?ihm;$S;4vVg4Mu5%p0TQA=G0r;!2%%6-+6qT#yQ(Kg5hM zok2?;8-PJAsD#Ar=Bny+>3h&a%m5VbTbL?TvBQOvWF<{$IfQ&x#O`Aj-mW7PPvZ3( z3J`SOXxV0>R5#9<dt*7rHi3-kMcnkuj2uM0t*vKqCssdD}hFF)u1Sp zJg+jsU4ZyAYEPI^y5qhnVkw-V$f=*~Z%-ozlh&vsg>zuBOJPr>9cy&tS=Q#UVtNI3 za|RMB({7fzUKnVaw&Bm@lIdO%qOSrFDo0h+VMeu2`w|wCNP-$9ebGBY z{7Q#Rup(7r2Xh?}gSteGGMwRLr<P9;L80L=>)eF# zg)~ku8?agZlLghmI-do+n7 zGc0)JaB-=ELsJQ;8D5G+1$20}IXcgtf$pUAkTzqg4s}U|Uy9o8%!Kle6*eZE7|W;F znYZqsLgk%p=nzmS#!O9#D)b_NDgMrIf*I(&k7GXT=#xXr8!LB&<){7?B?ISb)*sCd zkH#{@4iVu|HixL*ELrf-^Yq|)il2yIi7X2TD3~ey>&^{f4rH%5q+r(Aa1+q+Wx)Lm zX> zB1|r5Yj_P+mMMzRS%^-@jd>nSBk2#Ch-}r$D@jFuThIs>V4Sv&@eKW%czRPii^yGJ zd)+(wXu7ebpC=LJd)?-Vh{dj$e+POF8sZ#EsA5_XqgR}-H@KR*nL@bkUt^FMeLaJs z2x{2CovJ(35FORe7j8lI#b6nc=AfRtGt)1MoceIq-Wt-yXK5M;-g)ZPSdk?r52d2Go#sBevRt5oDxwjloxkypW>=KwF2$ z|5mZ>7+hjiDEy$L#EiC5aWM40H}p)k5=-2k@m{2bCQ3&UX%KD5!W_U@o({1G){mr{ zJwJQ{t%~W8c{{@x$#%nD0utDe7;{JhboOS|#dB>y~ zp2qaX;bg`z-|R~4LHX6d+cDvxfmf(9Qu-8+^LS}1KhC zLTn2qVJ35NfoTd$x={jIQSqxBLW-V`k(R)W zpl~26d7_e{5kdmET_9A4bpmPigruWf;Ge*nPTLEhbdDl~4r?w()p`5X%SC z6KH4_lt{7wI-v>kcWtr5h>)-l!zy^J(x#c)m4WZd2@q|nQqvDEU3w>j2B{=2XGsTn@V&I9RP3P zybbRlOyMx&8fl#eBn%4~>!KsQ?HN!9!$OJ6$pEv+6Lu{}5{kAgU5%D`U2C2d#8EY~ z5n{oD&>Mk`5b!ajn;DfSuGdI35;0jUf~iX=3XTniVM1RF7~$&?aH7VnVOPq3SV&ukbXrMhVzu~BiXUSnr&+%tuyAc_)y=f{Z^^R^$U8|%^vQYz-0cAS0V5w?N9 zgy-}*nVd%qKSQ50zIKXSh_(y@#ZvAK4)09tc!abo$~E)>qgZSO8UzXgX4(}i>|)Od zU=2rf7JKhFg#HTmQ-7fpUTga9p@_=k214(;y2orH5`iL$8@0JxhOC@&Sa)d)Gers z3xYB_4Pd|?0KL$TN6w}X6#da8J`gL9B!WqF7om%Qg4F}E;B(e@)9j#S$5R0OJ|Iv^ol%R)^|xAVT~`_< zHygB0PjJZkZhb6l&U8&Mz%(MLiXcktNB;)$no9mZzWn$PpTB&3TJ!&1pU2bNPs{=T zUo_t((Z!o{vbZ>!ui83GnN-J8R|`Ompq74g)bUKr^NYwchE!;e=OsH}e8$6P5Zlu+ zgpko&D`bTMPDsq_;YovxH(eE_p1wWZG5hhyU%j2{!WEC70ayv38 z87+3D>SXZUb2&M^x{2~AG^?&=}Tmhahnf;{{&LfD|Pfbi}pk5&w zuxONJE`$CKN(M%F28mC_bbZ^v_=+N=ec z7Y(~?Gj~dnu%5?i;``AUGrpl8DiBj~umKLB!XB6vtHwH7;Tl%6m>KCCGx(-(q(7~% z))jGt7pKm=wm`XK!tI$qFbSt9ec1pp)744y?l%AZ|G3Ss|M~Ns{eAQ@e%s&Q{&|1D zd_EgYAztjhADriL2M&1WWni|z0B?er=S6)|oiQV7j7cP>#60f>9{ohOm&h6BUxGMT z?=TLFk%a<{GGs=AY$M#~(V_G>pwQX~Ty`ZESwGSkh)A((9fdgfm@ z*nGRe>+`#}AHKXAA3yB3Z(e=UHh+1$p6=}Mqu24<4*&YYr}zK(Pvv!aZO;DUD~ln9 zXne$92?{IBHZ0G$o#|qR;Ui@WQ662Y+1qJ)aF&V62CXKt>Q5oEEz*L2^wV%>a z2q;;Oih*F%CG}{e3%ps>{rhS2MYG1~hc6UiC+#a!EX*!v4G)X>5PGx~Q0L>79s79w zU2HgN$XSu`vY~(TuUyebuiW=n)VsEkcWA%0H`2@b7sS6Nz7<6>&Nq??g)~*D_wSU zwmqm*HzZeiKi@uK`he7$SsMZFS>bp^F^QMZhWvxBF3Ql22DE}6F#RV>2cJh8${-># zsN5FnRcON6x)jM4Ca5A07I83i8Em@j=)e8)r%!8s9CtSK(F^x;Ry`YPUOWHVrL(1e zAxU;xafBN%_}0>7-h58iH4}*|qdHkBxGBA1`io~$B$_o5!Z7L?Ue%{07di|{6mE{z zS0UzmL;^8SnZ!mVHR1cCFNfmRWlQU5A)BqhvjbxnSn+WCLeoH5!8kPmUszM zm1QG4fjFr?8>U#W|A+#$Zh|SsIi(+bwh=-QA~ndcqd`R#NbI>$5=0j}T`%dc+0xry z-r3bhuidv@^`F|8@w}g88fMx}B-d2$<^e9kz z+L&`w>Kmx#GP$;ijXHhk4MwOmZl;t05Ef%fmGX=bfIG=f>7pF>Kk6*=49_$A#guTM z`o%0mHZfdb)M7BtV&%8(3Lf`8UNmv$Q;fxvVWDS@2Ina*PYisENwI1NnXC&;;UIz$ zZz9&(LxGn}X)CNVlvRfI^fanBG=pnF@|!^w-L1=>{^H$FAMRz`N3Ys7c$ z*olN~BhXG;nBqW3j5aoWe{^VQ3eJr0rlZ+dLbnJ|D7i-kC9SHcxQ-q`M(JniquZ)m zLFqTOBac`j^@7zDu+V}pMc>JK6j!gHxE&w0k*DsNPf>d`w&-a<|9UJsXLp{iW5bxK zj^nKC6$Z88c^#KFesIg>NRxSHi-baZ=aMem4I^)p`tTFhBP!W(fOF%fdYAGQ6u)mz zzhCptem;8Lz6ZtOwR>&d+0d@_u#BZEWO=1)WYH0QEhuD*EnyGr&$9d$QS`J$>}jeSb%ViC_C$qs&qM?*4HTZN2`BSwCfmvJLLt1jj4~y+++=_55&0i zhj?`x;$TeADu-RRG!+KN_t=Jckjd*FFG5xvKxiev{jzfrUz|Nw?S}ycUnQ> zrf*^f5yIiLiwbLRynMb5a2&6@Eq=m4x0-%25G}Nu>d@ne^p-jx>z|Co% z7L@FD!u%4w8Y}3}I1EUaONfju!b(fxUOp>FxG?u+KLrX`%L-Y}P=VpWJ0E3&OhXr9 zBimC$48u2sbB>dWKc>tUhw{AwZY2s;4t+Z1CY#ZqZVWBvM%fm7c2q13x&ZY5CB*0v z*uv7{UM2C@`{Pd^W<%Zb9^HBH3f?Ia|I^vp%zF<)8rYkxTc0=n<{LQ#1-ZpAan@>U zg_Nu42cp{*L!(92LkM4l3uTv#mWn8u#S#;b^^&xV6)zv8tGj+W(usm+9^L z7BC#6>En!5ECdpfA538?8}Urd(5a=n&xrCVg)!pSsum(4p0x}56}l8SV(Z4x#HFo8 z2}u9z3ff$_NDh0)O5l^Dbm`%-917D=LAJMXH+D6?U>F7(y*#*xG`9v*Q|sEo5l#WiMW zmRaBj6iXN(pesJcMjb()l_4B2`j3o$7yFCOmTRSHqs*G{Y_LdoMBUd+>kbW$I7{o= zVT>eP!f2o`>#!a~=FqN>4c*{?I5d-$4L{RJJC?{sLDwB~U*s+oiP1?Ko6?1ls-s~* z@b}xc{_5$c&%gWprH$Wz-P_-OEfRlvA;Ug;9ly`8Rv9+jB632a6gf-<38fvc@MIm0 z&2;o^t&`gsewdB2n@);L$%!&}i@i+)WFsExC+{u#_;@T>Cdi^;z>2x~f_yc2oTdP4 z1oQF|s0a!B+-uzl8H0-KF3S*|Fg{Z5^tBm%ImGYy_0H3pFP6~e5mQ;HsxdiQ^vN*w z2^euGh{wPXem%~Ff?G<@`n=~7M}38GX2tUv`VoXxQv5k0AAnvQn5T0ZF?ixYlqk(&5p{AW3VEpI9mAo6@1>NN*SlGN+C; zD^8_}8G^8uC@0;$0`IUxD}akPW#xM=yXhTlX-1Q;1cx7j9t72cCTJMN!4-cau0zw| z&{2`m6ibi6(6t{L`a7Z-h-HUP4>41Vy6^U=rDf#x4UO`p51L91xCHAk4B{-pW?H+z zM48Z{hh~nfS45cPAd-HZ`HIim9mL81{B}PSgS})n9YRC3$)*Sg`$7oS|(o z@Vcp-g(X=w%*-7%F`VRz_xe~N>|tufdu}LrY_BjIXeh`CL5lnP)@c|o?pi0-W^lx@ z3T#-SH0_4)RG=FT)dI!0xUmmSU4bw9+>uEd3q3*+1q_C0Z@g(17zQnwS6@7Sc&i+j zBCZ?-5jynWbzCrEtw+2KzH@(5U%04`Uc2v0#kRc8uiY)nc5!0`s|C@D87Yy1ILr|{ z^M+`GSuqdYk&#lmQS3(BJ5zl_oJV=Qxl!|?tJ~Fnghvh2I!^0sX*NYWD%p1YQm1Yh zLf`{okbI58*c3AOob_Q4&s_RS*&+fM1U!~tqwXj+>df2G_=&!$lIYIh4#8VHcJL8F zH$MV}b;K?lh9C+6(83DD+*O}UtHj{JNa&O><23BgS@s|PTqb<=^^xwj!+-lN+rInh z&D(o0`Oypd{r!!v<2MF`)*Wg(`2EaG!$}&vHWcNHiXFsO`V+cTWUDlpYzDL#Ul%_V z-yhhDG0G5?-Gx??&}ygi$6#!K%oj0mK(jPrYQ!j_jxD|xgBS06Fy)1lZ?%69 zHcLuggI?2=h0oeh=0ER%Hf4s57CLwsZ!%6m6ZHW$sNRJ*c(*#HMR=L|NY8vk`IRvq zEVYtAjt;Bg^&F*m)J*eD^-p~5N^hC4NP$U>0QjlMX1XmfyNkZ4vT3HyV+eY*xW#(3 zp&L?FVloik!Mq#OI^mc{?j~BB5l1#m_hF8!aL>FRR#p>8zV|Gm6l-?ZfE{ z0>L{2ptx!?>1u%|T?a^9-q6F3f-h>AqW;m=HLzO~M(_-2GEs+7uEk@dZgLv}pa$Zi zOrb=hqpV-k0g-7V?(MI)v@pPyBC3QmoZ>;gHUoIj0A4LU?k`H56Y9|9%46L3k>HCgx%xuC&I8*MCsWJ-3w&r(W`bLJLTu{ ze-6{32vXtw2-z>c#lZZrg`gDH%obAF$>TOp`MFN~G)hDI3%!VpuJK|N4|QrXy^YL7 zA;-uJNo(38+Cl;{1C>!!#lYzrZ{gQ+@n2D!)1z1J8ga|la+C5iW)j&84xZ8+j`5S) zB{Ll;KWS~(n5G{RrW}W0hILA4%fkBH=sU%^73T%rna~mp5b5UayEs$}1Nxrf*!e|- zOocP^G4-B1UmMq)K;ZIN?&x0PD@cDe{Y}9&rj+#^E9F;RS7uT}TgR-`;d-O+Hqh%r zlw@mVcICE*rjLN(lR`E!t@`bAqK2SbDpq)&U$or<(#PNY+UE56)eCp@(F^zNj+)o` z*66aGK2SX08f7m9DslLu@Qe70Xr57KVocu=`vP@#Lp5Hj#n{TexsEEl(bry)i!VK_ zV`8xCm^gytK(9>THyRgU@#p?J$LZF$HS+HfJ_E} z*Y->;*LGy63tkLXfmNWk*jsyTaYurfVy(WL&I)P(r-34eps(FGe!b4nYBG=?Uw3x& z1yJL6D^KGzd~)&J|a9geuCh$BC5jcvCS;3$qpP z`{}qbOtFuekk!D`W{!o3VWd8Ep26^m+<}e~3b+#)zEp>~-qDCOxMQAMS)-t{k#E_gT>%+j%ZXJS%8c;im$Y1c&H7e8Jjnuln*qGK>9 zDK9R1;=SN{M}PPtRMhY6=cAYH8gjo?3@yH<3*%JO?XmH|#3(8cC|7Yfa0ym=@u3J_ zN3^f7r%=8SP0%M^M}@?B>LQ9B3EPFfD5g`&RY-Bn8)_o}GsB%%(jtN}8Amt$iiw(T zCkA$6BT#ftLz0a{@CAoUBoa#L<%kk^CKRWVOc~K*Mx=08A|px+TSU79Gl3IivDRP1Ft|Gq!9v0klz`P4pqzW?O<=mmUU-?v}8`kDU3 z>{OAhOf;Reo+~;eW(+;$nC#|>tdUONnPN{&x+t<4pW@C(3aqCjB-VIOL37ni!z*ZG z+RiD*w2Uro+`osew`9xMT^s*%HS~!-Xn=R z)bqJ~W;z3-ps7@a3O+`zLv%IqHg8kHTq+x^;gjN_SZrk~26523b*q_>y=?4%U_W1d zUjOB%^+6l_Vj1bttN8q3?kkmHGRPIYoL90BX{^XY1o`}UGq+l zFRCJ7*KsW9He=|6pPax-4G2ZKssyLib(trrCK!XD-7r)$l~;~w$+JSC=UHX11U}TS z5HM;CHrLhhK`6>qN-Lj&jd5< zK2$D%=||yIpNVmqg>J}YcYpiOpFe&WFYoH3SMORza$k3~>YR8m;;Bp(F@$4c*ZDom zh|n9H+YtxxopI~V3A>_Qh3z>4hDTJjBUp3%y^v|Ff|OMkBneqMPY-9s zkGPB;l$q)n$5|fXO5V8JPY zUYdRI3SRGP{<^c<<498Rn?N8&C{p+vO*eEJl@kOd1v)~_aTg^lbWnxMJ5oHt)*r+AIVfB$u7zpQ`x^1gk!hld}%fY;%LI%A9{1g$G# zi?#{@MufetkoYFAj{x2nVj>-V=R%vAf-leHu^1)GXpUVR0)FBrs2LsW4CFH+ZGTCN zt-G5WUmge?6#|t$tJ3yBban8Bwl2{S7)9h#7rUTvcG=Ru>+M|=W}2^FY8QU=`dwr2 zx2qcUWouwUVBggFmN>&gKB8JkHr;iY>+t)mk{6=PxPc3uDB!o)(pCt}ift+2t%eY} zzJoz#bty+E^9%RSxljQVdYuSa6`ed_tv=(m2T_~DB>rBf&p;#=wa<2l+@P1xIP@TO z?RvNL(oS(6i~JI}1yhj9XMTvX}VMPT`%6V7J z^h=SPHSW&2K6u@pb-R4q(24AtKc>pa^kwB`0lRT5u`{y|sPs>Kol(;sl_x9|IU2a! z(w`Fsrow3JHtj|ZV%L}{bcM}DGa=3M9?5y79?<@o%{2p;j9IVcLjB;jCJ*4{h`lUY zdK*33j8iNj-ZD`rBQgyv&r$+DRYXaGRUpILi|2L}Qw2UIdj6I8Rg_J!x7Frai`W+9 zIu;cJN0EsOlj;>N{p~3L|LN8K_@tY9@18z*`Ci;t`i;-5Ri;9REl$g5#AGc0rfHi| z=%q|yP}~g6Z&Dx7;fhLQrCT&h5mVJ4$(?6TeF)(e9{t3}Qo=|;OvHgcG29O5$0Xbp zvlYR+Qdfc58ST2gv00R>bYnwB6n5ijBs{6FJdYtKtu8o&E|>g9bPlf^{l$&Ds<5v^ zI8otR>o+Zt7<;OXe}U_pet8r(M^@BBotX%^?llYlW_|d>+oz8o-mm*la1UO=XIuO8 z6C9H8R*0*4`r?_%_3fscpN`mgI%CyZFAPUEmTz6FEGkwk;_3}q%#9U~7LsQ5rX;p= z3CbfGhc+}1-7uM2&_d87*X9yF01a)LGB zm|CEdyBukF1;4P5P#@L{p=|x`s4P39o6*c*9PRKMV&C=W>FIYgMl>%9N=<=HCq_56 zEu@@idJ=a)w>B|aoLdOj_J?1wx3AuR9KXMtSs%M}FYc)p%n{^8BsNV@sEN8P#xRgK zP=L^(5Lyw=(7`)u&SfGJrtpTBv!dH5hQ~i9!W8Dhz_hGaEnlW5rO={_J3?~is3ErF zOB5Ep*(1+q9S~hKm9k-ox&w61iRNXeAF8BYmp_O}jPMN}GE^a*(Q zVUJTnxiX`FZg8%%sF+6jXAgDMSw}E0cXS4qs4@Gft0iJ1eoT8VPP$@&ypAqrxfq6S ze{rK<49|~V$d@))s0}T2ktvKwtPcwbc63*Dm{2qand`IY>L?c~{Djloq3jOYQLORF zg(XZfFk~F*lt~c;?fO5J+@^5u6na;#v2z$_Las)qah_7&xVDjshv4wAdt?TdcBZ1A zmKbN!rEtElr1-mVGk`$MBzB(&hn;GhB4KD@oM#@4GCX4tV_TX{up7~JF-+?q$J)f9 z`VxUuHbbNE_*>uEySj7xksht`;fvg22CSP^Sr57?2B-ct}#Ux?|HDg$|Lo#R+d&!Ppwb+XGXmI814S zuJ}qR3uEVM3+jhTkVU-Ct|YS4VExoC^cjvhUKESmkuk~57_jJsxMt-AHx8=wsaaik zCFK~mE=vyRCCw1T;v@YKgPI+hvhrM~FnL0@@b zg||?_=%SH~G_*c=x16DNi$TowF~Ang+C_s<)u@a?I^Hld&1`4+4R1Vo(GHO-u3_5Ye_-$9$fy#`1o}1 zhCX`fUTj9hL=gdpfQ|4m)zlFmj9+K;HZtu(TR)Vpb=@96#QA9)!=FgMJ5`ZK!A|5% zMA8^tEd79;V3*YwD4it)LT;WK-ElI5-3#Jwv9!Nt3iRl;d%453=~T3qqHu{r3A`~b z-&U@Q<_-yRi;ww4+ho~*=n(x?`mh)qV&&$-=`# z&*`1lwG(-9aV@{E)e0hw2Q&2)5t|Q~*G(KIJOr5$}@ z=;JF&Bg&VoRxi3bv=uS)?QqjvdGM~{r`S}qQ=6#2lTRFC3Bk zA4hw4@2)<0^{%h%=JibpMifKTkQu{C7RdA*>&&#Q1zbZEJ7E#6b5ham(|-g?@l&vZ zoefp)pmiNFA})=jE;C;lNiDwnL$biX**?dOPY!+#!(| zb8Uw~)mP6uCAXY*kk$^GZX>T=3Y@ z2&yYA_A^q$bI+pb!HB8_Vd_WwycO*6;c(CN)c*Lp_T`UnUj6iR_nJOGHLaiJ7zjM7z?-?`cNUEnH_v z4O4RrpLeTRD$wU`JQ}buAttJvX($Y_i&kN6qy2LQDW% z0`HsqRf$J0-M20Mw`{3-{V%tY1eSq|5kR_Vv17(DC77tQO>?C1n9Fz;7W8zNIE2JzY3)kp%uZU5t7nyMU@(-#>;;G$K4cm>05jC`Ah5Xmj3JM?T0sS_n+=nB_6z% z-)*q_y1};Y5^-G8-6$P%@4W@yV9u@U$fqsnKNK>C_!k{QbS#MZYc-YH>!vzau~)+_ zHw&TQNH>thKz~J`K+ zyfXKS&6w_F&&L+=6i6P0(YS)67iAAIfpuO~C?2TzDYKi$%t4Q&D9r zI4U94PP=bKKSwx*(RvJjnMK1*p&JgJGFe~@7t59vaO+ZTFtg(~fC*+-G`$M^@bYal zV{xcK2+ZmmO~eLh=y!@eR1?DJMch;Sqq{wD5TD`=UisGPmxSr6qMbd`@Ypnqw2R83 z@rs9gVmgaF3R?_dQ)<%%xghbOYvqCa8t?4qqnGX46z;w@g{PbLG)|}IoJL+%l%e%8 zEVeCFmK`!?IB(%7N((?~ST>WQbH$vKElOV+yGHAqX<+GMG%seJ84SWiV39 z(7S>g+GdPX3lKV86GcP8y=n}=#(;Y_!mxvdI{&QbR zzZ;8Y;b)w~h$*&-+Kn8AKq5fgE1<1?4C4jaqF+eanOna}5d%|z(hVyJoo9%@GM*^E z=-D-qDjflF!@`z)DALr#fXnH?+t`&UsVGJ@;Ri9;eeQ~$(`j(!N*?h&ur zjgOW>?Q|Qvc+9iy0Y^tuY1&L0bUc7hj1jK{^_QR5`#;=;y^mhE?=~}k-AvtuhDd(3 zMHEU4f^?kK=P1hR$a?C-V!27rt2jPJ;7G0L-=RhmsgaIc#j|Qd{tO3Z28IO6ZytBL zWexu{tHX*inC?fn1*p@p@4Vj5Y#kszRuln{=@ytDm6l*$ZyJDKkU;Dli&46V6)~0m ztWu#MNELy|!BtED@5WkN_g3UqWVP*%`kLSs?yE!m5Wl{T;iuT7B4$Zuj_YRr_D}8U z&F7aVH6Fcs7hq(69gp`70b!5=7q17 zkHi)f7xY_eVJf<8#hxjUr=%Ek5F5tGmQt9~zvwwi7radgt%%HB-`^I*ix{MslD)xuc?k;)ec=K4`RYiTyiz(D7|C)>@1ckC$UC2r^6@ z5%EHNrV{P+mrGrs3@nK;CTge~W@ozK#!{@?Ib&XE5h!D1p<`F1Z!0#}mD2Q4L>qX# z;TcCOww9-iBfftYpH5DtyPohrq&B-djeyq79Trwv)K zK`Dz@983aIt<{*pw7rJ~DCH<*34zwrRO^`|FsS%ei}QDkI)#?!Z1jB5Txj~Rf;Oh& z6v2~-VPdf`8o`WiYfSkIsEfc|%lHjh`i@ngR-GX>k57Cy4F5((%Q1DQCq_rPl5`nI z_pMg#G2;08*vIad0UZGpEU!f;27VT!332t!q}#=A|Owcsx7pyh+&JBJP;V#ws&Ih|8=*3wv0i2z_<&q!4{>XLKw* zwJL|WYN%fOQI>%D3`3iPD!yPqP^@r`8NRFxqEwN#wxR}+wP(daE?pejNNmDIXAX

o6(vw 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 z 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

UQNXkJm!y0*r)tJB@Ii{ZgyqU7 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 From 1fa02e77d20337b194e2e664f5a06f19cfe1e880 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 25 May 2023 18:20:45 +0000 Subject: [PATCH 43/64] remove debug output and use total_circuit_size --- .../barretenberg/dsl/acir_proofs/acir_composer.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index cf15c5bf00..2fd5a8d61c 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -27,16 +27,10 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, constraint_system.constraints.clear(); constraint_system.constraints.shrink_to_fit(); - exact_circuit_size_ = composer_.get_num_gates(); total_circuit_size_ = composer_.get_total_circuit_size(); - std::cout << "total_circuit_size_: " << total_circuit_size_ << std::endl; - // Exact or total fed in here? circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); - std::cout << "circuit_subgroup_size_: " << circuit_subgroup_size_ << std::endl; proving_key_ = composer_.compute_proving_key(); - - std::cout << "proving key circuit_size: " << proving_key_.get()->circuit_size << std::endl; } std::vector AcirComposer::create_proof(acir_format::acir_format& constraint_system, @@ -46,9 +40,6 @@ std::vector AcirComposer::create_proof(acir_format::acir_format& constr composer_ = acir_format::Composer(proving_key_, verification_key_, circuit_subgroup_size_); // You can't produce the verification key unless you manually set the crs. Which seems like a bug. composer_.crs_factory_ = crs_factory_; - for (size_t i = 0; i < witness.size(); i++) { - std::cout << "witness: " << witness[i] << std::endl; - } create_circuit_with_witness(composer_, constraint_system, witness); @@ -58,9 +49,6 @@ std::vector AcirComposer::create_proof(acir_format::acir_format& constr witness.clear(); witness.shrink_to_fit(); - std::cout << "num_gates: " << composer_.num_gates << std::endl; - std::cout << "circuit_finalised: " << composer_.circuit_finalised << std::endl; - std::vector proof; if (is_recursive) { auto prover = composer_.create_prover(); From 1bf8db25a8e31461b71c52da1fef98395b7c5907 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 25 May 2023 21:35:37 +0000 Subject: [PATCH 44/64] remove verify_recursive_proof as we might not need it for simulation --- .../dsl/acir_proofs/acir_composer.cpp | 174 +++++++++--------- .../dsl/acir_proofs/acir_composer.hpp | 10 +- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 12 +- ts/src/main.ts | 25 ++- 4 files changed, 123 insertions(+), 98 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 2fd5a8d61c..70be67430e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -28,6 +28,7 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, constraint_system.constraints.shrink_to_fit(); total_circuit_size_ = composer_.get_total_circuit_size(); + std::cout << "total_circuit_size_: " << total_circuit_size_ << std::endl; circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); proving_key_ = composer_.compute_proving_key(); @@ -85,91 +86,6 @@ std::string AcirComposer::get_solidity_verifier() return stream.str(); } -std::vector AcirComposer::verify_recursive_proof( - std::vector const& proof, - std::vector const& verification_key, - uint32_t num_public_inputs, - std::vector input_aggregation_object) -{ - const size_t NUM_AGGREGATION_ELEMENTS = acir_format::RecursionConstraint::NUM_AGGREGATION_ELEMENTS; - - bool inner_aggregation_all_zero = true; - for (size_t i = 0; i < input_aggregation_object.size(); i++) { - inner_aggregation_all_zero &= (input_aggregation_object[i].is_zero()); - } - - acir_format::aggregation_state_ct previous_aggregation; - if (!inner_aggregation_all_zero) { - std::array aggregation_elements; - for (size_t i = 0; i < NUM_AGGREGATION_ELEMENTS; ++i) { - aggregation_elements[i] = acir_format::bn254::fq_ct( - acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i]), - acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 1]), - acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 2]), - acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 3])); - aggregation_elements[i].assert_is_in_field(); - } - // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included - // in stdlib::recursion::verify_proof - previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); - previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); - previous_aggregation.has_data = true; - } else { - previous_aggregation.has_data = false; - } - - composer_ = acir_format::Composer(); - - std::vector proof_fields(proof.size()); - std::vector key_fields(verification_key.size()); - for (size_t i = 0; i < proof.size(); i++) { - // TODO(maxim): The stdlib pairing primitives fetch the context from the elements being used in the pairing - // computation When attempting to simulate recursive verification without a full circuit where the context of - // these elements are not set we will get a seg fault. Using `witness_ct` here provides a workaround where these - // elements will have their context set. We should enable the native verifier to return an aggregation state so - // that we can avoid creating an unnecessary circuit and this workaround - proof_fields[i] = acir_format::field_ct(acir_format::witness_ct(&composer_, proof[i])); - } - for (size_t i = 0; i < verification_key.size(); i++) { - key_fields[i] = acir_format::field_ct(verification_key[i]); - } - - transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); - - std::array nested_aggregation_object = {}; - for (size_t i = 6; i < 22; ++i) { - nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); - } - bool nested_aggregation_indices_all_zero = true; - for (const auto& idx : nested_aggregation_object) { - nested_aggregation_indices_all_zero &= (idx == 0); - } - const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; - - std::shared_ptr vkey = acir_format::verification_key_ct::from_field_elements( - &composer_, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); - - acir_format::Transcript_ct transcript(&composer_, manifest, proof_fields, num_public_inputs); - - acir_format::aggregation_state_ct result = - proof_system::plonk::stdlib::recursion::verify_proof_( - &composer_, vkey, transcript, previous_aggregation); - - std::vector output_aggregation_object; - for (size_t i = 0; i < 4; ++i) { - auto P0_x = result.P0.x.binary_basis_limbs[i].element.get_value(); - auto P0_y = result.P0.y.binary_basis_limbs[i].element.get_value(); - auto P1_x = result.P1.x.binary_basis_limbs[i].element.get_value(); - auto P1_y = result.P1.y.binary_basis_limbs[i].element.get_value(); - output_aggregation_object.emplace_back(P0_x); - output_aggregation_object.emplace_back(P0_y); - output_aggregation_object.emplace_back(P1_x); - output_aggregation_object.emplace_back(P1_y); - } - - return output_aggregation_object; -} - std::vector AcirComposer::serialize_proof_into_fields(std::vector const& proof, size_t num_inner_public_inputs) { @@ -189,4 +105,92 @@ std::vector AcirComposer::serialize_verification_key_into_fiel return output; } +// std::vector AcirComposer::verify_recursive_proof( +// std::vector const& proof, +// std::vector const& verification_key, +// uint32_t num_public_inputs, +// std::vector input_aggregation_object) +// { +// const size_t NUM_AGGREGATION_ELEMENTS = acir_format::RecursionConstraint::NUM_AGGREGATION_ELEMENTS; + +// bool inner_aggregation_all_zero = true; +// for (size_t i = 0; i < input_aggregation_object.size(); i++) { +// inner_aggregation_all_zero &= (input_aggregation_object[i].is_zero()); +// } + +// acir_format::aggregation_state_ct previous_aggregation; +// if (!inner_aggregation_all_zero) { +// std::array aggregation_elements; +// for (size_t i = 0; i < NUM_AGGREGATION_ELEMENTS; ++i) { +// aggregation_elements[i] = acir_format::bn254::fq_ct( +// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i]), +// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 1]), +// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 2]), +// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 3])); +// aggregation_elements[i].assert_is_in_field(); +// } +// // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included +// // in stdlib::recursion::verify_proof +// previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); +// previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); +// previous_aggregation.has_data = true; +// } else { +// previous_aggregation.has_data = false; +// } + +// composer_ = acir_format::Composer(); + +// std::vector proof_fields(proof.size()); +// std::vector key_fields(verification_key.size()); +// for (size_t i = 0; i < proof.size(); i++) { +// // TODO(maxim): The stdlib pairing primitives fetch the context from the elements being used in the pairing +// // computation When attempting to simulate recursive verification without a full circuit where the context of +// // these elements are not set we will get a seg fault. Using `witness_ct` here provides a workaround where +// these +// // elements will have their context set. We should enable the native verifier to return an aggregation state +// so +// // that we can avoid creating an unnecessary circuit and this workaround +// proof_fields[i] = acir_format::field_ct(acir_format::witness_ct(&composer_, proof[i])); +// } +// for (size_t i = 0; i < verification_key.size(); i++) { +// key_fields[i] = acir_format::field_ct(verification_key[i]); +// } + +// transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); + +// std::array nested_aggregation_object = {}; +// for (size_t i = 6; i < 22; ++i) { +// nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); +// } +// bool nested_aggregation_indices_all_zero = true; +// for (const auto& idx : nested_aggregation_object) { +// nested_aggregation_indices_all_zero &= (idx == 0); +// } +// const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + +// std::shared_ptr vkey = acir_format::verification_key_ct::from_field_elements( +// &composer_, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); + +// acir_format::Transcript_ct transcript(&composer_, manifest, proof_fields, num_public_inputs); + +// acir_format::aggregation_state_ct result = +// proof_system::plonk::stdlib::recursion::verify_proof_( +// &composer_, vkey, transcript, previous_aggregation); + +// std::vector output_aggregation_object; +// for (size_t i = 0; i < 4; ++i) { +// auto P0_x = result.P0.x.binary_basis_limbs[i].element.get_value(); +// auto P0_y = result.P0.y.binary_basis_limbs[i].element.get_value(); +// auto P1_x = result.P1.x.binary_basis_limbs[i].element.get_value(); +// auto P1_y = result.P1.y.binary_basis_limbs[i].element.get_value(); +// output_aggregation_object.emplace_back(P0_x); +// output_aggregation_object.emplace_back(P0_y); +// output_aggregation_object.emplace_back(P1_x); +// output_aggregation_object.emplace_back(P1_y); +// } + +// return output_aggregation_object; +// } + } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index 1045146dec..1105cee7ac 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -25,16 +25,16 @@ class AcirComposer { size_t get_exact_circuit_size() { return exact_circuit_size_; }; size_t get_total_circuit_size() { return total_circuit_size_; }; - std::vector verify_recursive_proof(std::vector const& proof, - std::vector const& verification_key, - uint32_t num_public_inputs, - std::vector input_aggregation_object); - std::vector serialize_proof_into_fields(std::vector const& proof, size_t num_inner_public_inputs); std::vector serialize_verification_key_into_fields(); + // std::vector verify_recursive_proof(std::vector const& proof, + // std::vector const& verification_key, + // uint32_t num_public_inputs, + // std::vector input_aggregation_object) + private: std::shared_ptr crs_factory_; acir_format::Composer composer_; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index dfdbe6560f..7df04f3394 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -38,11 +38,6 @@ WASM_EXPORT void acir_get_exact_circuit_size(in_ptr acir_composer_ptr, uint32_t* WASM_EXPORT void acir_get_total_circuit_size(in_ptr acir_composer_ptr, uint32_t* out); -WASM_EXPORT void acir_verify_recursive_proof(in_ptr acir_composer_ptr, - uint8_t const* proof_buf, - uint32_t const* num_inner_public_inputs, - uint8_t** out); - WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, uint8_t const* proof_buf, uint32_t const* num_inner_public_inputs, @@ -50,4 +45,9 @@ WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_composer_ptr, uint8_t** out_vkey, - uint8_t** out_key_hash); \ No newline at end of file + uint8_t** out_key_hash); + +// WASM_EXPORT void acir_verify_recursive_proof(in_ptr acir_composer_ptr, +// uint8_t const* proof_buf, +// uint32_t const* num_inner_public_inputs, +// uint8_t** out); \ No newline at end of file diff --git a/ts/src/main.ts b/ts/src/main.ts index 0c5f38c0a4..6a18180d7d 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -13,7 +13,7 @@ const debug = createDebug('bb.js'); createDebug.enable('*'); // Maximum we support. -const CIRCUIT_SIZE = 2 ** 19; +const CIRCUIT_SIZE = 2 ** 18; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -163,9 +163,11 @@ export async function proof_as_fields(proofPath: string, num_inner_public_inputs } } -export async function vk_as_fields(proofPath: string, num_inner_public_inputs: number, vkey_oututPath: string, key_hash_outputPath: string) { +export async function vk_as_fields(vkey_oututPath: string, key_hash_outputPath: string) { const { api, acirComposer } = await init(); + // TODO: move to passing in the key so we don't have to recompute it + api.acirInitVerificationKey(acirComposer); try { debug('serializing proof byte array into field elements'); const [vk_as_fields, vk_hash_as_fields] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); @@ -241,4 +243,23 @@ program await writeVk(jsonPath, outputPath); }); +program + .command('proof_as_fields') + .description('Return the proof as fields elements') + .requiredOption('-p, --proof-path ', 'Specify the proof path') + .requiredOption('-n, --num-public-inputs', 'Specify the number of public inputs') + .requiredOption('-o, --output-path ', 'Specify the path to write the proof fields') + .action(async ({ proofPath, numPublicInputs, outputPath }) => { + await proof_as_fields(proofPath, numPublicInputs, outputPath); + }) + +program + .command('vk_as_fields') + .description('Return the verifiation key represented as fields elements. Also return the verification key hash.') + .requiredOption('-v, --vkey-output-path ', 'Specify the path to write the verification key fields') + .requiredOption('-h, --key-hash-output-path ', 'Specify the path to write the verification key hash') + .action(async ({vkey_oututPath, key_hash_outputPath }) => { + await vk_as_fields(vkey_oututPath, key_hash_outputPath); + }) + program.parse(process.argv); From 381ef1daf5155069b87f0836c435d54cf0483461 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Thu, 25 May 2023 23:16:04 +0000 Subject: [PATCH 45/64] switch how we do is_recursive --- .../dsl/acir_proofs/acir_composer.cpp | 1 + .../barretenberg/dsl/acir_proofs/c_bind.cpp | 7 +++--- exports.json | 24 ------------------- ts/src/barretenberg_api/index.ts | 10 -------- 4 files changed, 5 insertions(+), 37 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 70be67430e..8557c7db67 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -20,6 +20,7 @@ AcirComposer::AcirComposer(std::shared_ptr void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint) { + std::cout << "about to create circuit: " << total_circuit_size_ << std::endl; composer_ = create_circuit(constraint_system, crs_factory_, size_hint); // We are done with the constraint system at this point, and we need the memory slab back. diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 3885b332c4..f18fe6b4e0 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -26,9 +26,10 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, uint8_t const* constraint_system_buf, uint32_t const* size_hint) { + std::cout << "got constraint_system\n"; auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto constraint_system = from_buffer(constraint_system_buf); - + std::cout << "got constraint_system\n"; // The binder would normally free the the constraint_system_buf, but we need the memory now. free_mem_slab_raw((void*)constraint_system_buf); @@ -49,7 +50,7 @@ WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, free_mem_slab_raw((void*)constraint_system_buf); free_mem_slab_raw((void*)witness_buf); - auto proof_data = acir_composer->create_proof(constraint_system, witness, ntohl(*is_recursive)); + auto proof_data = acir_composer->create_proof(constraint_system, witness, *is_recursive); *out = to_heap_buffer(proof_data); } @@ -76,7 +77,7 @@ WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto proof = from_buffer>(proof_buf); - *result = acir_composer->verify_proof(proof, ntohl(*is_recursive)); + *result = acir_composer->verify_proof(proof, *is_recursive); } WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_buf out) diff --git a/exports.json b/exports.json index 99c57282ad..c6aea3331a 100644 --- a/exports.json +++ b/exports.json @@ -801,30 +801,6 @@ ], "isAsync": false }, - { - "functionName": "acir_verify_recursive_proof", - "inArgs": [ - { - "name": "acir_composer_ptr", - "type": "in_ptr" - }, - { - "name": "proof_buf", - "type": "const uint8_t *" - }, - { - "name": "num_inner_public_inputs", - "type": "const uint32_t *" - } - ], - "outArgs": [ - { - "name": "out", - "type": "uint8_t **" - } - ], - "isAsync": false - }, { "functionName": "acir_serialize_proof_into_fields", "inArgs": [ diff --git a/ts/src/barretenberg_api/index.ts b/ts/src/barretenberg_api/index.ts index e8eaae804e..b26c77890a 100644 --- a/ts/src/barretenberg_api/index.ts +++ b/ts/src/barretenberg_api/index.ts @@ -236,11 +236,6 @@ export class BarretenbergApi { return result[0]; } - async acirVerifyRecursiveProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Promise { - const result = await this.binder.callWasmExport('acir_verify_recursive_proof', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); - return result[0]; - } - async acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Promise { const result = await this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); return result[0]; @@ -484,11 +479,6 @@ export class BarretenbergApiSync { return result[0]; } - acirVerifyRecursiveProof(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Uint8Array { - const result = this.binder.callWasmExport('acir_verify_recursive_proof', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); - return result[0]; - } - acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Uint8Array { const result = this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); return result[0]; From 88e364f1c1026c11b194810640bea0e1c2f4adf8 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 01:28:53 +0000 Subject: [PATCH 46/64] remove acir recursive simulation method --- .../dsl/acir_proofs/acir_composer.cpp | 88 ------------------- .../dsl/acir_proofs/acir_composer.hpp | 5 -- 2 files changed, 93 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 8557c7db67..5a9e2047bc 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -106,92 +106,4 @@ std::vector AcirComposer::serialize_verification_key_into_fiel return output; } -// std::vector AcirComposer::verify_recursive_proof( -// std::vector const& proof, -// std::vector const& verification_key, -// uint32_t num_public_inputs, -// std::vector input_aggregation_object) -// { -// const size_t NUM_AGGREGATION_ELEMENTS = acir_format::RecursionConstraint::NUM_AGGREGATION_ELEMENTS; - -// bool inner_aggregation_all_zero = true; -// for (size_t i = 0; i < input_aggregation_object.size(); i++) { -// inner_aggregation_all_zero &= (input_aggregation_object[i].is_zero()); -// } - -// acir_format::aggregation_state_ct previous_aggregation; -// if (!inner_aggregation_all_zero) { -// std::array aggregation_elements; -// for (size_t i = 0; i < NUM_AGGREGATION_ELEMENTS; ++i) { -// aggregation_elements[i] = acir_format::bn254::fq_ct( -// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i]), -// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 1]), -// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 2]), -// acir_format::field_ct(input_aggregation_object[NUM_AGGREGATION_ELEMENTS * i + 3])); -// aggregation_elements[i].assert_is_in_field(); -// } -// // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included -// // in stdlib::recursion::verify_proof -// previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); -// previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); -// previous_aggregation.has_data = true; -// } else { -// previous_aggregation.has_data = false; -// } - -// composer_ = acir_format::Composer(); - -// std::vector proof_fields(proof.size()); -// std::vector key_fields(verification_key.size()); -// for (size_t i = 0; i < proof.size(); i++) { -// // TODO(maxim): The stdlib pairing primitives fetch the context from the elements being used in the pairing -// // computation When attempting to simulate recursive verification without a full circuit where the context of -// // these elements are not set we will get a seg fault. Using `witness_ct` here provides a workaround where -// these -// // elements will have their context set. We should enable the native verifier to return an aggregation state -// so -// // that we can avoid creating an unnecessary circuit and this workaround -// proof_fields[i] = acir_format::field_ct(acir_format::witness_ct(&composer_, proof[i])); -// } -// for (size_t i = 0; i < verification_key.size(); i++) { -// key_fields[i] = acir_format::field_ct(verification_key[i]); -// } - -// transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); - -// std::array nested_aggregation_object = {}; -// for (size_t i = 6; i < 22; ++i) { -// nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); -// } -// bool nested_aggregation_indices_all_zero = true; -// for (const auto& idx : nested_aggregation_object) { -// nested_aggregation_indices_all_zero &= (idx == 0); -// } -// const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; - -// std::shared_ptr vkey = acir_format::verification_key_ct::from_field_elements( -// &composer_, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); - -// acir_format::Transcript_ct transcript(&composer_, manifest, proof_fields, num_public_inputs); - -// acir_format::aggregation_state_ct result = -// proof_system::plonk::stdlib::recursion::verify_proof_( -// &composer_, vkey, transcript, previous_aggregation); - -// std::vector output_aggregation_object; -// for (size_t i = 0; i < 4; ++i) { -// auto P0_x = result.P0.x.binary_basis_limbs[i].element.get_value(); -// auto P0_y = result.P0.y.binary_basis_limbs[i].element.get_value(); -// auto P1_x = result.P1.x.binary_basis_limbs[i].element.get_value(); -// auto P1_y = result.P1.y.binary_basis_limbs[i].element.get_value(); -// output_aggregation_object.emplace_back(P0_x); -// output_aggregation_object.emplace_back(P0_y); -// output_aggregation_object.emplace_back(P1_x); -// output_aggregation_object.emplace_back(P1_y); -// } - -// return output_aggregation_object; -// } - } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index 1105cee7ac..145cc0509c 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -30,11 +30,6 @@ class AcirComposer { std::vector serialize_verification_key_into_fields(); - // std::vector verify_recursive_proof(std::vector const& proof, - // std::vector const& verification_key, - // uint32_t num_public_inputs, - // std::vector input_aggregation_object) - private: std::shared_ptr crs_factory_; acir_format::Composer composer_; From 49e238ad8580b97d2250cd4302c4e221b339ad77 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 01:53:32 +0000 Subject: [PATCH 47/64] changed wasi stubs and removed cout from acir_init_proving_key --- cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp | 3 +-- cpp/src/barretenberg/wasi/wasi_stubs.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index f18fe6b4e0..9adfee02f8 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -26,10 +26,9 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, uint8_t const* constraint_system_buf, uint32_t const* size_hint) { - std::cout << "got constraint_system\n"; auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto constraint_system = from_buffer(constraint_system_buf); - std::cout << "got constraint_system\n"; + // The binder would normally free the the constraint_system_buf, but we need the memory now. free_mem_slab_raw((void*)constraint_system_buf); diff --git a/cpp/src/barretenberg/wasi/wasi_stubs.cpp b/cpp/src/barretenberg/wasi/wasi_stubs.cpp index 55dd6c6900..b561da0a96 100644 --- a/cpp/src/barretenberg/wasi/wasi_stubs.cpp +++ b/cpp/src/barretenberg/wasi/wasi_stubs.cpp @@ -25,8 +25,8 @@ int32_t __imported_wasi_snapshot_preview1_poll_oneoff(int32_t, int32_t, int32_t, int32_t __imported_wasi_snapshot_preview1_fd_write(int32_t, int32_t, int32_t, int32_t) { - info("fd_write not implemented."); - abort(); + // info("fd_write not implemented."); + // abort(); return 0; } @@ -67,8 +67,8 @@ int32_t __imported_wasi_snapshot_preview1_environ_sizes_get(int32_t, int32_t) int32_t __imported_wasi_snapshot_preview1_fd_fdstat_get(int32_t, int32_t) { - info("fd_fdstat_get not implemented."); - abort(); + // info("fd_fdstat_get not implemented."); + // abort(); return 0; } From 5f195dcd88817f5fbe6e75e8aea127fbb9b3a2f3 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 02:52:33 +0000 Subject: [PATCH 48/64] removed cout outs getting runtime error for func definition --- cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp | 2 -- cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp | 7 +------ cpp/src/barretenberg/plonk/composer/ultra_composer.cpp | 1 - cpp/src/barretenberg/wasi/wasi_stubs.cpp | 8 ++++---- ts/src/main.ts | 6 ++++-- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 5a9e2047bc..25059ae6ad 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -20,7 +20,6 @@ AcirComposer::AcirComposer(std::shared_ptr void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint) { - std::cout << "about to create circuit: " << total_circuit_size_ << std::endl; composer_ = create_circuit(constraint_system, crs_factory_, size_hint); // We are done with the constraint system at this point, and we need the memory slab back. @@ -29,7 +28,6 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, constraint_system.constraints.shrink_to_fit(); total_circuit_size_ = composer_.get_total_circuit_size(); - std::cout << "total_circuit_size_: " << total_circuit_size_ << std::endl; circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); proving_key_ = composer_.compute_proving_key(); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 7df04f3394..c07d7c6eea 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -45,9 +45,4 @@ WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_composer_ptr, uint8_t** out_vkey, - uint8_t** out_key_hash); - -// WASM_EXPORT void acir_verify_recursive_proof(in_ptr acir_composer_ptr, -// uint8_t const* proof_buf, -// uint32_t const* num_inner_public_inputs, -// uint8_t** out); \ No newline at end of file + uint8_t** out_key_hash); \ No newline at end of file diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index 8c3e8a0d1c..bf13074fdf 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -541,7 +541,6 @@ std::shared_ptr UltraComposer::compute_proving_key() } if (circuit_proving_key) { - std::cout << "already have proving_key\n"; return circuit_proving_key; } diff --git a/cpp/src/barretenberg/wasi/wasi_stubs.cpp b/cpp/src/barretenberg/wasi/wasi_stubs.cpp index b561da0a96..55dd6c6900 100644 --- a/cpp/src/barretenberg/wasi/wasi_stubs.cpp +++ b/cpp/src/barretenberg/wasi/wasi_stubs.cpp @@ -25,8 +25,8 @@ int32_t __imported_wasi_snapshot_preview1_poll_oneoff(int32_t, int32_t, int32_t, int32_t __imported_wasi_snapshot_preview1_fd_write(int32_t, int32_t, int32_t, int32_t) { - // info("fd_write not implemented."); - // abort(); + info("fd_write not implemented."); + abort(); return 0; } @@ -67,8 +67,8 @@ int32_t __imported_wasi_snapshot_preview1_environ_sizes_get(int32_t, int32_t) int32_t __imported_wasi_snapshot_preview1_fd_fdstat_get(int32_t, int32_t) { - // info("fd_fdstat_get not implemented."); - // abort(); + info("fd_fdstat_get not implemented."); + abort(); return 0; } diff --git a/ts/src/main.ts b/ts/src/main.ts index 6a18180d7d..e1768f2f65 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -7,13 +7,14 @@ import { gunzipSync } from 'zlib'; import { RawBuffer } from './types/index.js'; import { numToInt32BE } from './serialize/serialize.js'; import { Command } from 'commander'; +import { info } from 'console'; createDebug.log = console.error.bind(console); const debug = createDebug('bb.js'); createDebug.enable('*'); // Maximum we support. -const CIRCUIT_SIZE = 2 ** 18; +const CIRCUIT_SIZE = 2 ** 19; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -167,6 +168,7 @@ export async function vk_as_fields(vkey_oututPath: string, key_hash_outputPath: const { api, acirComposer } = await init(); // TODO: move to passing in the key so we don't have to recompute it + // or just keep it as the writeVK method currently recomputes the pkey too api.acirInitVerificationKey(acirComposer); try { debug('serializing proof byte array into field elements'); @@ -199,7 +201,7 @@ program .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') .option('-r, --recursive', 'prove and verify using recursive prover and verifier') .action(async ({ jsonPath, witnessPath, is_recursive }) => { - const result = await proveAndVerify(jsonPath, witnessPath, is_recursive); + const result = await proveAndVerify(jsonPath, witnessPath, false); process.exit(result ? 0 : 1); }); From 45328c19068222a5fb945c449f64ba9398a800ad Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 19:28:49 +0000 Subject: [PATCH 49/64] working recursion format funcs, but strangely getting errors about trying to invert zero in the field for dummy transcript and key --- .../dsl/acir_format/acir_format.hpp | 18 ++--- .../dsl/acir_format/recursion_constraint.cpp | 23 +++++- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 31 ++++---- .../recursion/transcript/transcript.hpp | 2 + .../stdlib/recursion/verifier/verifier.hpp | 19 ++++- ts/src/main.ts | 73 ++++++++++++------- 6 files changed, 106 insertions(+), 60 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index 9aa57902fe..a9b60e9913 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -103,20 +103,12 @@ template inline void write(B& buf, acir_format const& data) write(buf, data.hash_to_field_constraints); write(buf, data.fixed_base_scalar_mul_constraints); write(buf, data.recursion_constraints); - for (size_t i = 0; i < data.constraints.size(); i++) { - write(buf, data.constraints[i]); - } - // write(buf, data.constraints); + // TODO: probably delete, used for writing AcirComposer tests in CPP + // for (size_t i = 0; i < data.constraints.size(); i++) { + // write(buf, data.constraints[i]); + // } + write(buf, data.constraints); write(buf, data.block_constraints); } -// template inline void write(&B buf, std::vector> const& -// constraints) -// { -// using serialize::write; -// for (size_t i = 0; i < constraints.size(); i++) { -// write(buf, constraints[i]); -// } -// } - } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 32cb775cd3..2a2a63fd91 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -38,7 +38,11 @@ void create_recursion_constraints(Composer& composer, nested_aggregation_indices_all_zero &= (idx == 0); } const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + info("inner_proof_contains_recursive_proof"); + info(inner_proof_contains_recursive_proof); + info("has_valid_witness_assignments"); + info(has_valid_witness_assignments); // If we do not have a witness, we must ensure that our dummy witness will not trigger // on-curve errors and inverting-zero errors { @@ -82,6 +86,9 @@ void create_recursion_constraints(Composer& composer, for (const auto& idx : aggregation_input) { inner_aggregation_indices_all_zero &= (idx == 0); } + info("inner_aggregation_indices_all_zero"); + info(inner_aggregation_indices_all_zero); + if (!inner_aggregation_indices_all_zero) { std::array aggregation_elements; for (size_t i = 0; i < 4; ++i) { @@ -101,18 +108,26 @@ void create_recursion_constraints(Composer& composer, previous_aggregation.has_data = false; } + info("input.public_inputs.size()"); + info(input.public_inputs.size()); transcript::Manifest manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); std::vector key_fields; key_fields.reserve(input.key.size()); + info("key_fields"); for (const auto& idx : input.key) { - key_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + auto field = field_ct::from_witness_index(&composer, idx); + info(field); + key_fields.emplace_back(field); } std::vector proof_fields; proof_fields.reserve(input.proof.size()); + info("proof_fields"); for (const auto& idx : input.proof) { - proof_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + auto field = field_ct::from_witness_index(&composer, idx); + info(field); + proof_fields.emplace_back(field); } // recursively verify the proof @@ -120,8 +135,10 @@ void create_recursion_constraints(Composer& composer, &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); vkey->program_width = noir_recursive_settings::program_width; Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); + info("about to verify_proof_"); aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); + info("got agg result"); // Assign correct witness value to the verification key hash vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); @@ -288,6 +305,7 @@ std::vector export_dummy_transcript_in_recursion_format(const for (const auto& manifest_element : manifest.get_round_manifest(i).elements) { if (!manifest_element.derived_by_verifier) { if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + // auto scalar = barretenberg::fr::random_element(); fields.emplace_back(0); } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { // the std::biggroup class creates unsatisfiable constraints when identical points are @@ -322,6 +340,7 @@ std::vector export_dummy_transcript_in_recursion_format(const } } else { for (size_t j = 0; j < num_public_inputs; ++j) { + // auto scalar = barretenberg::fr::random_element(); fields.emplace_back(0); } } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 9adfee02f8..0ded4c9dcd 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -28,6 +28,7 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto constraint_system = from_buffer(constraint_system_buf); + info("got constraint_system"); // The binder would normally free the the constraint_system_buf, but we need the memory now. free_mem_slab_raw((void*)constraint_system_buf); @@ -109,42 +110,40 @@ WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, auto proof_as_fields = acir_composer->serialize_proof_into_fields(proof, ntohl(*num_inner_public_inputs)); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - const size_t output_size_bytes = proof_as_fields.size() * sizeof(barretenberg::fr); - auto raw_buf = (uint8_t*)malloc(output_size_bytes); - - // TODO: possible switch to instead use fr::to_buffer inside the AcirComposer and call `to_heap_buffer` + std::vector proof_fields_data; // The serialization code below will convert out of Montgomery form before writing to the buffer for (size_t i = 0; i < proof_as_fields.size(); ++i) { - barretenberg::fr::serialize_to_buffer(proof_as_fields[i], &raw_buf[i * 32]); + auto proof_field = proof_as_fields[i].to_buffer(); + copy(proof_field.begin(), proof_field.end(), std::back_inserter(proof_fields_data)); } - *out = raw_buf; + *out = to_heap_buffer(proof_fields_data); } WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_composer_ptr, uint8_t** out_vkey, uint8_t** out_key_hash) { + info("about to serialize vkey"); auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto vkey_as_fields = acir_composer->serialize_verification_key_into_fields(); - // TODO: possible switch to instead use fr::to_buffer inside the AcirComposer and call `to_heap_buffer` // NOTE: this output buffer will always have a fixed size! Maybe just precompute? // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o - const size_t output_size_bytes = vkey_as_fields.size() * sizeof(barretenberg::fr) - 32; - - auto raw_buf = (uint8_t*)malloc(output_size_bytes); - auto vk_hash_raw_buf = (uint8_t*)malloc(32); - + std::vector vk_fields_data; // The serialization code below will convert out of Montgomery form before writing to the buffer for (size_t i = 0; i < vkey_as_fields.size() - 1; ++i) { - barretenberg::fr::serialize_to_buffer(vkey_as_fields[i], &raw_buf[i * 32]); + auto vk_field = vkey_as_fields[i].to_buffer(); + copy(vk_field.begin(), vk_field.end(), std::back_inserter(vk_fields_data)); } - barretenberg::fr::serialize_to_buffer(vkey_as_fields[vkey_as_fields.size() - 1], vk_hash_raw_buf); + + std::vector vk_hash_data; + auto vk_hash_field = vkey_as_fields[vkey_as_fields.size() - 1].to_buffer(); + copy(vk_hash_field.begin(), vk_hash_field.end(), std::back_inserter(vk_hash_data)); // copy the vkey into serialized_vk_buf - *out_vkey = raw_buf; + *out_vkey = to_heap_buffer(vk_fields_data); // copy the vkey hash into serialized_vk_hash_buf - *out_key_hash = vk_hash_raw_buf; + *out_key_hash = to_heap_buffer(vk_hash_data); } diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index 34a983b362..ed9bbce0b4 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -175,6 +175,8 @@ template class Transcript { void apply_fiat_shamir(const std::string& challenge_name) { + info("apply_fiat_shamir"); + info(challenge_name); const size_t num_challenges = get_manifest().get_round_manifest(current_round).num_challenges; transcript_base.apply_fiat_shamir(challenge_name); diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index 7e9bb09468..b117a91c1d 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -215,6 +215,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, const auto PI_Z = transcript.get_circuit_group_element("PI_Z"); const auto PI_Z_OMEGA = transcript.get_circuit_group_element("PI_Z_OMEGA"); + info("get_circuit_group_element"); field_t circuit_size = key->n; field_t public_input_size = key->num_public_inputs; @@ -227,6 +228,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, transcript.apply_fiat_shamir("beta"); transcript.apply_fiat_shamir("alpha"); transcript.apply_fiat_shamir("z"); + info("apply_fiat_shamir"); fr_ct alpha = transcript.get_challenge_field_element("alpha"); fr_ct zeta = transcript.get_challenge_field_element("z"); @@ -234,21 +236,30 @@ aggregation_state verify_proof_(typename Curve::Composer* context, key->z_pow_n = zeta.pow(key->domain.domain); lagrange_evaluations lagrange_evals = get_lagrange_evaluations(zeta, key->domain); + info("lagrange_evals"); // reconstruct evaluation of quotient polynomial from prover messages fr_ct quotient_numerator_eval = fr_ct(0); program_settings::compute_quotient_evaluation_contribution(key.get(), alpha, transcript, quotient_numerator_eval); - + info("compute_quotient_evaluation_contribution"); + info(quotient_numerator_eval); fr_ct t_eval = quotient_numerator_eval / lagrange_evals.vanishing_poly; transcript.add_field_element("t", t_eval); - + info(t_eval); + info("Curve::Composer::type"); + info(Curve::Composer::type); transcript.apply_fiat_shamir("nu"); + info("apply_fiat_shamir nu"); transcript.apply_fiat_shamir("separator"); + info("apply_fiat_shamir separator"); fr_ct u = transcript.get_challenge_field_element("separator", 0); + info("get_challenge_field_element on separator"); fr_ct batch_opening_scalar; + info("about to populate_kate_element_map"); + populate_kate_element_map, program_settings>(context, key.get(), transcript, @@ -257,7 +268,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, kate_fr_elements_at_zeta_large, kate_fr_elements_at_zeta_omega, batch_opening_scalar); - + info("populate_kate_element_map"); std::vector double_opening_scalars; std::vector double_opening_elements; std::vector opening_scalars; @@ -376,6 +387,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, rhs_elements.push_back((-g1_ct(x1, y1))); rhs_scalars.push_back(recursion_separator_challenge); } + info("about to bn254_endo_batch_mul_with_generator"); auto opening_result = g1_ct::template bn254_endo_batch_mul_with_generator( big_opening_elements, big_opening_scalars, opening_elements, opening_scalars, batch_opening_scalar, 128); @@ -384,6 +396,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, for (const auto& to_add : elements_to_add) { opening_result = opening_result + to_add; } + info("final opening_result"); g1_ct rhs = g1_ct::template wnaf_batch_mul<128>(rhs_elements, rhs_scalars); diff --git a/ts/src/main.ts b/ts/src/main.ts index e1768f2f65..6ed60555ba 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -8,6 +8,7 @@ import { RawBuffer } from './types/index.js'; import { numToInt32BE } from './serialize/serialize.js'; import { Command } from 'commander'; import { info } from 'console'; +import { exit } from 'process'; createDebug.log = console.error.bind(console); const debug = createDebug('bb.js'); @@ -59,9 +60,9 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r debug(`creating proof...`); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), false); + const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); - const verified = await api.acirVerifyProof(acirComposer, proof, false); + const verified = await api.acirVerifyProof(acirComposer, proof, is_recursive); debug(`verified: ${verified}`); return verified; } finally { @@ -82,8 +83,8 @@ export async function prove(jsonPath: string, witnessPath: string, is_recursive: debug(`creating proof...`); const witness = getWitness(witnessPath); const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); - - writeFileSync(outputPath, proof); + debug(`got proof`); + writeFileSync(outputPath, Buffer.from(proof)); debug('done.'); } finally { await api.destroy(); @@ -155,7 +156,8 @@ export async function proof_as_fields(proofPath: string, num_inner_public_inputs try { debug('serializing proof byte array into field elements'); - const proof_as_fields = await api.acirSerializeProofIntoFields(acirComposer, readFileSync(proofPath), num_inner_public_inputs); + const proof_as_fields_buffer = await api.acirSerializeProofIntoFields(acirComposer, readFileSync(proofPath), num_inner_public_inputs); + let proof_as_fields = bufferAsFieldHex(Buffer.from(proof_as_fields_buffer)) writeFileSync(outputPath, proof_as_fields); debug('done.'); @@ -164,24 +166,42 @@ export async function proof_as_fields(proofPath: string, num_inner_public_inputs } } -export async function vk_as_fields(vkey_oututPath: string, key_hash_outputPath: string) { +export async function vk_as_fields(jsonPath: string, vkeyOutputPath: string) { const { api, acirComposer } = await init(); - // TODO: move to passing in the key so we don't have to recompute it - // or just keep it as the writeVK method currently recomputes the pkey too - api.acirInitVerificationKey(acirComposer); - try { - debug('serializing proof byte array into field elements'); - const [vk_as_fields, vk_hash_as_fields] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + // TODO: consider moving to passing in the key so we don't have to recompute it + // or just keep it as the writeVK method currently recomputes the pkey too ¯\_(ツ)_/¯ + debug('initing proving key...'); + const bytecode = getBytecode(jsonPath); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); - writeFileSync(vkey_oututPath, vk_as_fields); - writeFileSync(key_hash_outputPath, vk_hash_as_fields); + await api.acirInitVerificationKey(acirComposer); + try { + debug('serializing vk byte array into field elements'); + const [vk_as_fields_buffer, vk_hash_buffer] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + let vk_fields = bufferAsFieldHex(Buffer.concat([Buffer.from(vk_hash_buffer), Buffer.from(vk_as_fields_buffer)])); + + writeFileSync(vkeyOutputPath, vk_fields); debug('done.'); } finally { await api.destroy(); } } +function bufferAsFieldHex(buffer: Buffer): string { + let hex = buffer.toString('hex'); + let split_hex = hex.match(/.{1,64}/g); + if (split_hex == null) { + exit(); + } else { + for (let i = 0; i < split_hex.length; i++) { + split_hex[i] = "0x".concat(split_hex[i]); + } + let separate_fields = JSON.stringify(split_hex); + return separate_fields; + } +} + // nargo use bb.js: backend -> bb.js // backend prove --data-dir data --witness /foo/bar/witness.tr --json /foo/bar/main.json // backend verify ... @@ -200,8 +220,8 @@ program .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') .option('-r, --recursive', 'prove and verify using recursive prover and verifier') - .action(async ({ jsonPath, witnessPath, is_recursive }) => { - const result = await proveAndVerify(jsonPath, witnessPath, false); + .action(async ({ jsonPath, witnessPath, recursive }) => { + const result = await proveAndVerify(jsonPath, witnessPath, recursive); process.exit(result ? 0 : 1); }); @@ -213,8 +233,8 @@ program .option('-r, --recursive', 'prove using recursive prover') .option('-o, --output-dir ', 'Specify the proof output dir', './proofs') .requiredOption('-n, --name ', 'Output file name.') - .action(async ({ jsonPath, witnessPath, is_recursive, outputDir, name }) => { - await prove(jsonPath, witnessPath, is_recursive, outputDir + '/' + name); + .action(async ({ jsonPath, witnessPath, recursive, outputDir, name }) => { + await prove(jsonPath, witnessPath, recursive, outputDir + '/' + name); }); program @@ -223,8 +243,8 @@ program .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') .option('-r, --recursive', 'prove using recursive prover') - .action(async ({ jsonPath, proofPath, is_recursive }) => { - await verify(jsonPath, proofPath, is_recursive); + .action(async ({ jsonPath, proofPath, recursive }) => { + await verify(jsonPath, proofPath, recursive); }); program @@ -249,19 +269,20 @@ program .command('proof_as_fields') .description('Return the proof as fields elements') .requiredOption('-p, --proof-path ', 'Specify the proof path') - .requiredOption('-n, --num-public-inputs', 'Specify the number of public inputs') - .requiredOption('-o, --output-path ', 'Specify the path to write the proof fields') + .requiredOption('-n, --num-public-inputs ', 'Specify the number of public inputs') + .requiredOption('-o, --output-path ', 'Specify the JSON path to write the proof fields') .action(async ({ proofPath, numPublicInputs, outputPath }) => { + console.log(numPublicInputs) await proof_as_fields(proofPath, numPublicInputs, outputPath); }) program .command('vk_as_fields') .description('Return the verifiation key represented as fields elements. Also return the verification key hash.') - .requiredOption('-v, --vkey-output-path ', 'Specify the path to write the verification key fields') - .requiredOption('-h, --key-hash-output-path ', 'Specify the path to write the verification key hash') - .action(async ({vkey_oututPath, key_hash_outputPath }) => { - await vk_as_fields(vkey_oututPath, key_hash_outputPath); + .requiredOption('-j, --json-path ', 'Specify the JSON path', './target/main.json') + .requiredOption('-v, --vkey-output-path ', 'Specify the JSON path to write the verification key fields and key hash') + .action(async ({jsonPath, vkeyOutputPath }) => { + await vk_as_fields(jsonPath, vkeyOutputPath); }) program.parse(process.argv); From 0abc2bb2466030daddda05fd611a8b8bde21d1e0 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 20:12:04 +0000 Subject: [PATCH 50/64] most recent debug output, hitting bad alloc in compute_proving_key_base --- .../barretenberg/dsl/acir_format/recursion_constraint.cpp | 8 ++++---- cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp | 6 ++++-- cpp/src/barretenberg/plonk/composer/composer_base.cpp | 3 ++- cpp/src/barretenberg/plonk/composer/ultra_composer.cpp | 3 ++- .../stdlib/recursion/transcript/transcript.hpp | 2 +- cpp/src/barretenberg/transcript/transcript.cpp | 4 +++- ts/src/main.ts | 5 +++-- 7 files changed, 19 insertions(+), 12 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 2a2a63fd91..81f722aa6e 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -114,19 +114,19 @@ void create_recursion_constraints(Composer& composer, std::vector key_fields; key_fields.reserve(input.key.size()); - info("key_fields"); + // info("key_fields"); for (const auto& idx : input.key) { auto field = field_ct::from_witness_index(&composer, idx); - info(field); + // info(field); key_fields.emplace_back(field); } std::vector proof_fields; proof_fields.reserve(input.proof.size()); - info("proof_fields"); + // info("proof_fields"); for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&composer, idx); - info(field); + // info(field); proof_fields.emplace_back(field); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 25059ae6ad..05fabd4d90 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -20,8 +20,8 @@ AcirComposer::AcirComposer(std::shared_ptr void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint) { - composer_ = create_circuit(constraint_system, crs_factory_, size_hint); - + composer_ = acir_format::create_circuit(constraint_system, crs_factory_, size_hint); + info("got composer_"); // We are done with the constraint system at this point, and we need the memory slab back. // constraint_system = acir_format::acir_format(); constraint_system.constraints.clear(); @@ -29,6 +29,8 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, total_circuit_size_ = composer_.get_total_circuit_size(); circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); + info("circuit_subgroup_size_"); + info(circuit_subgroup_size_); proving_key_ = composer_.compute_proving_key(); } diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.cpp b/cpp/src/barretenberg/plonk/composer/composer_base.cpp index b4fdb7b130..8cc92c204f 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.cpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.cpp @@ -238,7 +238,8 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const Compos const size_t num_filled_gates = num_gates + public_inputs.size(); const size_t total_num_gates = std::max(minimum_circuit_size, num_filled_gates); const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + num_reserved_gates); // next power of 2 - + info("subgroup_size"); + info(subgroup_size); // Prealloc slabs of memory for polynomials, pippenger, scratch space, etc. init_slab_allocator(subgroup_size); diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index bf13074fdf..b0152992b7 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -565,7 +565,7 @@ std::shared_ptr UltraComposer::compute_proving_key() // Compute selector polynomials and appropriate fft versions and put them in the proving key ComposerBase::compute_proving_key_base(type, tables_size + lookups_size, NUM_RESERVED_GATES); - + info("got pkey base"); const size_t subgroup_size = circuit_proving_key->circuit_size; polynomial poly_q_table_column_1(subgroup_size); @@ -656,6 +656,7 @@ std::shared_ptr UltraComposer::compute_proving_key() std::copy(memory_write_records.begin(), memory_write_records.end(), std::back_inserter(circuit_proving_key->memory_write_records)); + info("copied memory write records"); circuit_proving_key->recursive_proof_public_input_indices = std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index ed9bbce0b4..fbcd55e303 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -64,7 +64,7 @@ template class Transcript { const std::vector& field_buffer, const size_t num_public_inputs) : context(in_context) - , transcript_base(input_manifest, transcript::HashType::PedersenBlake3s, 16) + , transcript_base(input_manifest, transcript::HashType::PlookupPedersenBlake3s, 16) , current_challenge(in_context) { size_t count = 0; diff --git a/cpp/src/barretenberg/transcript/transcript.cpp b/cpp/src/barretenberg/transcript/transcript.cpp index 1371a08eac..b510f312cd 100644 --- a/cpp/src/barretenberg/transcript/transcript.cpp +++ b/cpp/src/barretenberg/transcript/transcript.cpp @@ -233,11 +233,13 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b break; } case HashType::PedersenBlake3s: { + info("in pedersen"); std::vector compressed_buffer = to_buffer(crypto::pedersen_commitment::compress_native(buffer)); base_hash = Blake3sHasher::hash(compressed_buffer); break; } case HashType::PlookupPedersenBlake3s: { + info("in plookup pedersen"); std::vector compressed_buffer = crypto::pedersen_commitment::lookup::compress_native(buffer); base_hash = Blake3sHasher::hash_plookup(compressed_buffer); break; @@ -246,7 +248,7 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b throw_or_abort("no hasher was selected for the transcript"); } } - + info("got base_hash"); // Depending on the settings, we might be able to chunk the bytes of a single hash across multiple challenges: const size_t challenges_per_hash = PRNG_OUTPUT_SIZE / num_challenge_bytes; diff --git a/ts/src/main.ts b/ts/src/main.ts index 6ed60555ba..de32a73971 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -32,7 +32,7 @@ function getWitness(witnessPath: string) { async function init() { // Plus 1 needed! - const crs = await Crs.new(CIRCUIT_SIZE + 1); + const crs = await Crs.new(2 ** 19 + 1); const api = await newBarretenbergApiAsync(); @@ -51,7 +51,8 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r debug('initing proving key...'); const bytecode = getBytecode(jsonPath); await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); - + debug(`got proving key`); + const exactCircuitSize = await api.acirGetExactCircuitSize(acirComposer); debug(`circuit size: ${exactCircuitSize}`); From 826fb5969de93ab1485f3250b01a28a0b0f2078a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Fri, 26 May 2023 20:17:19 +0000 Subject: [PATCH 51/64] delete old comment --- cpp/src/barretenberg/dsl/acir_format/acir_format.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index a9b60e9913..fe5ab0ef56 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -103,10 +103,6 @@ template inline void write(B& buf, acir_format const& data) write(buf, data.hash_to_field_constraints); write(buf, data.fixed_base_scalar_mul_constraints); write(buf, data.recursion_constraints); - // TODO: probably delete, used for writing AcirComposer tests in CPP - // for (size_t i = 0; i < data.constraints.size(); i++) { - // write(buf, data.constraints[i]); - // } write(buf, data.constraints); write(buf, data.block_constraints); } From 07e55578445cd233eab20952b04b29a17017f8e3 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Sun, 28 May 2023 21:13:34 +0000 Subject: [PATCH 52/64] bb.js cmd changes --- ts/src/main.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ts/src/main.ts b/ts/src/main.ts index de32a73971..ea860813b7 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -15,7 +15,7 @@ const debug = createDebug('bb.js'); createDebug.enable('*'); // Maximum we support. -const CIRCUIT_SIZE = 2 ** 19; +const CIRCUIT_SIZE = 2 ** 18; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -52,7 +52,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r const bytecode = getBytecode(jsonPath); await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); debug(`got proving key`); - + const exactCircuitSize = await api.acirGetExactCircuitSize(acirComposer); debug(`circuit size: ${exactCircuitSize}`); @@ -62,7 +62,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r debug(`creating proof...`); const witness = getWitness(witnessPath); const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); - + console.log(proof); const verified = await api.acirVerifyProof(acirComposer, proof, is_recursive); debug(`verified: ${verified}`); return verified; From 7fb94f187511ab8e56facab3f385770057265456 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Tue, 30 May 2023 14:27:49 +0000 Subject: [PATCH 53/64] Lint and default fixes. --- ts/bb.js-dev | 4 + ts/package.json | 2 +- ts/src/barretenberg_wasm/barretenberg_wasm.ts | 3 - ts/src/main.ts | 79 ++++++++++--------- 4 files changed, 48 insertions(+), 40 deletions(-) create mode 100755 ts/bb.js-dev diff --git a/ts/bb.js-dev b/ts/bb.js-dev new file mode 100755 index 0000000000..79077737e6 --- /dev/null +++ b/ts/bb.js-dev @@ -0,0 +1,4 @@ +#!/bin/sh +SCRIPT_PATH=$(dirname $(realpath $0)) +export TS_NODE_PROJECT="$SCRIPT_PATH/tsconfig.json" +NODE_OPTIONS="--loader $SCRIPT_PATH/node_modules/ts-node/esm/transpile-only.mjs --no-warnings" node $SCRIPT_PATH/src/main.ts $@ diff --git a/ts/package.json b/ts/package.json index e8bb1dc842..d997e85f80 100644 --- a/ts/package.json +++ b/ts/package.json @@ -9,7 +9,7 @@ }, "bin": { "bb.js": "./dest/main.js", - "bb.js-dev": "./src/main-dev.ts" + "bb.js-dev": "./bb.js-dev" }, "scripts": { "clean": "rm -rf ./dest .tsbuildinfo", diff --git a/ts/src/barretenberg_wasm/barretenberg_wasm.ts b/ts/src/barretenberg_wasm/barretenberg_wasm.ts index a597f89812..f8139aa536 100644 --- a/ts/src/barretenberg_wasm/barretenberg_wasm.ts +++ b/ts/src/barretenberg_wasm/barretenberg_wasm.ts @@ -1,5 +1,4 @@ import { type Worker } from 'worker_threads'; -import { EventEmitter } from 'events'; import createDebug from 'debug'; import { Remote, proxy } from 'comlink'; import { randomBytes } from '../random/index.js'; @@ -10,8 +9,6 @@ import { fetchCode, getNumCpu, createWorker, getRemoteBarretenbergWasm, threadLo const debug = createDebug('wasm'); -EventEmitter.defaultMaxListeners = 30; - export class BarretenbergWasm { static MAX_THREADS = 16; private memory!: WebAssembly.Memory; diff --git a/ts/src/main.ts b/ts/src/main.ts index ea860813b7..4a2bf82c6c 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -7,7 +7,6 @@ import { gunzipSync } from 'zlib'; import { RawBuffer } from './types/index.js'; import { numToInt32BE } from './serialize/serialize.js'; import { Command } from 'commander'; -import { info } from 'console'; import { exit } from 'process'; createDebug.log = console.error.bind(console); @@ -45,7 +44,7 @@ async function init() { return { api, acirComposer }; } -export async function proveAndVerify(jsonPath: string, witnessPath: string, is_recursive: boolean) { +export async function proveAndVerify(jsonPath: string, witnessPath: string, isRecursive: boolean) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -61,9 +60,9 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r debug(`creating proof...`); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); - console.log(proof); - const verified = await api.acirVerifyProof(acirComposer, proof, is_recursive); + const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), isRecursive); + + const verified = await api.acirVerifyProof(acirComposer, proof, isRecursive); debug(`verified: ${verified}`); return verified; } finally { @@ -71,7 +70,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r } } -export async function prove(jsonPath: string, witnessPath: string, is_recursive: boolean, outputPath: string) { +export async function prove(jsonPath: string, witnessPath: string, isRecursive: boolean, outputPath: string) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -83,7 +82,8 @@ export async function prove(jsonPath: string, witnessPath: string, is_recursive: debug(`creating proof...`); const witness = getWitness(witnessPath); - const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); + debug({ bytecode, witness }); + const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), isRecursive); debug(`got proof`); writeFileSync(outputPath, Buffer.from(proof)); debug('done.'); @@ -92,7 +92,7 @@ export async function prove(jsonPath: string, witnessPath: string, is_recursive: } } -export async function verify(jsonPath: string, proofPath: string, is_recursive: boolean) { +export async function verify(jsonPath: string, proofPath: string, isRecursive: boolean) { const { api, acirComposer } = await init(); try { debug('initing proving key...'); @@ -102,7 +102,7 @@ export async function verify(jsonPath: string, proofPath: string, is_recursive: debug('initing verification key...'); await api.acirInitVerificationKey(acirComposer); - const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), is_recursive); + const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive); debug(`verified: ${verified}`); return verified; } finally { @@ -152,22 +152,26 @@ export async function writeVk(jsonPath: string, outputPath: string) { } } -export async function proof_as_fields(proofPath: string, num_inner_public_inputs: number, outputPath: string) { +export async function proofAsFields(proofPath: string, numInnerPublicInputs: number, outputPath: string) { const { api, acirComposer } = await init(); try { debug('serializing proof byte array into field elements'); - const proof_as_fields_buffer = await api.acirSerializeProofIntoFields(acirComposer, readFileSync(proofPath), num_inner_public_inputs); - let proof_as_fields = bufferAsFieldHex(Buffer.from(proof_as_fields_buffer)) - - writeFileSync(outputPath, proof_as_fields); + const proofAsFieldsBuffer = await api.acirSerializeProofIntoFields( + acirComposer, + readFileSync(proofPath), + numInnerPublicInputs, + ); + const proofAsFields = bufferAsFieldHex(Buffer.from(proofAsFieldsBuffer)); + + writeFileSync(outputPath, proofAsFields); debug('done.'); } finally { await api.destroy(); } } -export async function vk_as_fields(jsonPath: string, vkeyOutputPath: string) { +export async function vkAsFields(jsonPath: string, vkeyOutputPath: string) { const { api, acirComposer } = await init(); // TODO: consider moving to passing in the key so we don't have to recompute it @@ -179,10 +183,10 @@ export async function vk_as_fields(jsonPath: string, vkeyOutputPath: string) { await api.acirInitVerificationKey(acirComposer); try { debug('serializing vk byte array into field elements'); - const [vk_as_fields_buffer, vk_hash_buffer] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); - let vk_fields = bufferAsFieldHex(Buffer.concat([Buffer.from(vk_hash_buffer), Buffer.from(vk_as_fields_buffer)])); - - writeFileSync(vkeyOutputPath, vk_fields); + const [vkAsFieldsBuffer, vkHashBuffer] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + const vkFields = bufferAsFieldHex(Buffer.concat([Buffer.from(vkHashBuffer), Buffer.from(vkAsFieldsBuffer)])); + + writeFileSync(vkeyOutputPath, vkFields); debug('done.'); } finally { await api.destroy(); @@ -190,16 +194,16 @@ export async function vk_as_fields(jsonPath: string, vkeyOutputPath: string) { } function bufferAsFieldHex(buffer: Buffer): string { - let hex = buffer.toString('hex'); - let split_hex = hex.match(/.{1,64}/g); - if (split_hex == null) { + const hex = buffer.toString('hex'); + const splitHex = hex.match(/.{1,64}/g); + if (splitHex == null) { exit(); } else { - for (let i = 0; i < split_hex.length; i++) { - split_hex[i] = "0x".concat(split_hex[i]); + for (let i = 0; i < splitHex.length; i++) { + splitHex[i] = '0x'.concat(splitHex[i]); } - let separate_fields = JSON.stringify(split_hex); - return separate_fields; + const separateFields = JSON.stringify(splitHex); + return separateFields; } } @@ -220,7 +224,7 @@ program .description('Generate a proof and verify it. Process exits with success or failure code.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') - .option('-r, --recursive', 'prove and verify using recursive prover and verifier') + .option('-r, --recursive', 'prove and verify using recursive prover and verifier', false) .action(async ({ jsonPath, witnessPath, recursive }) => { const result = await proveAndVerify(jsonPath, witnessPath, recursive); process.exit(result ? 0 : 1); @@ -231,7 +235,7 @@ program .description('Generate a proof and write it to a file.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') - .option('-r, --recursive', 'prove using recursive prover') + .option('-r, --recursive', 'prove using recursive prover', false) .option('-o, --output-dir ', 'Specify the proof output dir', './proofs') .requiredOption('-n, --name ', 'Output file name.') .action(async ({ jsonPath, witnessPath, recursive, outputDir, name }) => { @@ -243,7 +247,7 @@ program .description('Verify a proof. Process exists with success or failure code.') .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') - .option('-r, --recursive', 'prove using recursive prover') + .option('-r, --recursive', 'prove using recursive prover', false) .action(async ({ jsonPath, proofPath, recursive }) => { await verify(jsonPath, proofPath, recursive); }); @@ -273,17 +277,20 @@ program .requiredOption('-n, --num-public-inputs ', 'Specify the number of public inputs') .requiredOption('-o, --output-path ', 'Specify the JSON path to write the proof fields') .action(async ({ proofPath, numPublicInputs, outputPath }) => { - console.log(numPublicInputs) - await proof_as_fields(proofPath, numPublicInputs, outputPath); - }) + console.log(numPublicInputs); + await proofAsFields(proofPath, numPublicInputs, outputPath); + }); program .command('vk_as_fields') .description('Return the verifiation key represented as fields elements. Also return the verification key hash.') .requiredOption('-j, --json-path ', 'Specify the JSON path', './target/main.json') - .requiredOption('-v, --vkey-output-path ', 'Specify the JSON path to write the verification key fields and key hash') - .action(async ({jsonPath, vkeyOutputPath }) => { - await vk_as_fields(jsonPath, vkeyOutputPath); - }) + .requiredOption( + '-v, --vkey-output-path ', + 'Specify the JSON path to write the verification key fields and key hash', + ) + .action(async ({ jsonPath, vkeyOutputPath }) => { + await vkAsFields(jsonPath, vkeyOutputPath); + }); program.parse(process.argv); From 057f77839b71a6665c8baf38d7ca4925e025be43 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 14:32:18 +0000 Subject: [PATCH 54/64] cleanup info and debug comments --- .../dsl/acir_format/recursion_constraint.cpp | 14 -- .../dsl/acir_proofs/acir_composer.cpp | 4 +- .../dsl/acir_proofs/acir_composer.test.cpp | 142 ------------------ .../barretenberg/dsl/acir_proofs/c_bind.cpp | 2 - .../plonk/composer/composer_base.cpp | 3 +- .../plonk/composer/ultra_composer.cpp | 2 - .../recursion/transcript/transcript.hpp | 2 - .../stdlib/recursion/verifier/verifier.hpp | 18 +-- .../barretenberg/transcript/transcript.cpp | 4 +- ts/src/main.ts | 3 +- 10 files changed, 7 insertions(+), 187 deletions(-) delete mode 100644 cpp/src/barretenberg/dsl/acir_proofs/acir_composer.test.cpp diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 81f722aa6e..f5064788cb 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -38,11 +38,7 @@ void create_recursion_constraints(Composer& composer, nested_aggregation_indices_all_zero &= (idx == 0); } const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; - info("inner_proof_contains_recursive_proof"); - info(inner_proof_contains_recursive_proof); - info("has_valid_witness_assignments"); - info(has_valid_witness_assignments); // If we do not have a witness, we must ensure that our dummy witness will not trigger // on-curve errors and inverting-zero errors { @@ -86,8 +82,6 @@ void create_recursion_constraints(Composer& composer, for (const auto& idx : aggregation_input) { inner_aggregation_indices_all_zero &= (idx == 0); } - info("inner_aggregation_indices_all_zero"); - info(inner_aggregation_indices_all_zero); if (!inner_aggregation_indices_all_zero) { std::array aggregation_elements; @@ -108,25 +102,19 @@ void create_recursion_constraints(Composer& composer, previous_aggregation.has_data = false; } - info("input.public_inputs.size()"); - info(input.public_inputs.size()); transcript::Manifest manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); std::vector key_fields; key_fields.reserve(input.key.size()); - // info("key_fields"); for (const auto& idx : input.key) { auto field = field_ct::from_witness_index(&composer, idx); - // info(field); key_fields.emplace_back(field); } std::vector proof_fields; proof_fields.reserve(input.proof.size()); - // info("proof_fields"); for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&composer, idx); - // info(field); proof_fields.emplace_back(field); } @@ -135,10 +123,8 @@ void create_recursion_constraints(Composer& composer, &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); vkey->program_width = noir_recursive_settings::program_width; Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); - info("about to verify_proof_"); aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); - info("got agg result"); // Assign correct witness value to the verification key hash vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index 05fabd4d90..e739ee81d2 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -21,7 +21,7 @@ AcirComposer::AcirComposer(std::shared_ptr void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint) { composer_ = acir_format::create_circuit(constraint_system, crs_factory_, size_hint); - info("got composer_"); + // We are done with the constraint system at this point, and we need the memory slab back. // constraint_system = acir_format::acir_format(); constraint_system.constraints.clear(); @@ -29,8 +29,6 @@ void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, total_circuit_size_ = composer_.get_total_circuit_size(); circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); - info("circuit_subgroup_size_"); - info(circuit_subgroup_size_); proving_key_ = composer_.compute_proving_key(); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.test.cpp deleted file mode 100644 index 3fe85694b9..0000000000 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.test.cpp +++ /dev/null @@ -1,142 +0,0 @@ -// #include -// #include -// #include "barretenberg/srs/reference_string/pippenger_reference_string.hpp" -// #include -// #include "acir_composer.hpp" -// #include - -// using namespace proof_system::plonk; - -// void create_inner_circuit(acir_format::acir_format& constraint_system, std::vector& witness) -// { -// /** -// * constraints produced by Noir program: -// * fn main(x : u32, y : pub u32) { -// * let z = x ^ y; -// * -// * constrain z != 10; -// * } -// **/ -// acir_format::RangeConstraint range_a{ -// .witness = 1, -// .num_bits = 32, -// }; -// acir_format::RangeConstraint range_b{ -// .witness = 2, -// .num_bits = 32, -// }; - -// acir_format::LogicConstraint logic_constraint{ -// .a = 1, -// .b = 2, -// .result = 3, -// .num_bits = 32, -// .is_xor_gate = 1, -// }; -// poly_triple expr_a{ -// .a = 3, -// .b = 4, -// .c = 0, -// .q_m = 0, -// .q_l = 1, -// .q_r = -1, -// .q_o = 0, -// .q_c = -10, -// }; -// poly_triple expr_b{ -// .a = 4, -// .b = 5, -// .c = 6, -// .q_m = 1, -// .q_l = 0, -// .q_r = 0, -// .q_o = -1, -// .q_c = 0, -// }; -// poly_triple expr_c{ -// .a = 4, -// .b = 6, -// .c = 4, -// .q_m = 1, -// .q_l = 0, -// .q_r = 0, -// .q_o = -1, -// .q_c = 0, - -// }; -// poly_triple expr_d{ -// .a = 6, -// .b = 0, -// .c = 0, -// .q_m = 0, -// .q_l = -1, -// .q_r = 0, -// .q_o = 0, -// .q_c = 1, -// }; -// std::vector> constraints = { expr_a, expr_b, expr_c, expr_d }; - -// constraint_system = acir_format::acir_format{ -// .varnum = 7, -// .public_inputs = { 2 }, -// .fixed_base_scalar_mul_constraints = {}, -// .logic_constraints = { logic_constraint }, -// .range_constraints = { range_a, range_b }, -// .schnorr_constraints = {}, -// .ecdsa_constraints = {}, -// .sha256_constraints = {}, -// .blake2s_constraints = {}, -// .keccak_constraints = {}, -// .hash_to_field_constraints = {}, -// .pedersen_constraints = {}, -// .compute_merkle_root_constraints = {}, -// .block_constraints = {}, -// .recursion_constraints = {}, -// .constraints = constraints, -// }; - -// uint256_t inverse_of_five = fr(5).invert(); - -// witness.emplace_back(5); -// witness.emplace_back(10); -// witness.emplace_back(15); -// witness.emplace_back(5); -// witness.emplace_back(inverse_of_five); -// witness.emplace_back(1); -// } - -// TEST(AcirComposer, TestLogicCircuit) -// { -// acir_format::acir_format constraint_system; -// std::vector witness_inner; -// create_inner_circuit(constraint_system, witness_inner); - -// std::vector witness_buf; // = to_buffer(witness); -// write(witness_buf, witness_inner); -// // TODO: get WitnessVector in different way -// auto witness = from_buffer>>(witness_buf); - -// auto crs_factory = std::shared_ptr(new FileReferenceStringFactory("../srs_db/ignition")); -// auto g2x = crs_factory.get()->get_verifier_crs()->get_g2x().to_buffer(); -// // uint32_t pow2_size = 1 << 19UL; -// uint32_t pow2_size = 8192; - -// auto prover_crs = crs_factory.get()->get_prover_crs(pow2_size + 1); -// auto g1x = prover_crs.get()->get_monomial_points(); -// auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(g1x, pow2_size + 1); -// void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); - -// auto pippenger_factory = std::make_shared( -// reinterpret_cast(pippenger_ptr), g2x.data()); - -// auto composer = new acir_proofs::AcirComposer(pippenger_factory); - -// composer->init_proving_key(constraint_system); -// // std::cout << "composer info: " << composer -// composer->init_verification_key(); - -// auto proof = composer->create_proof(constraint_system, witness); - -// auto verified = composer->verify_proof(proof); -// EXPECT_EQ(verified, true); -// } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 0ded4c9dcd..c0e00aa771 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -28,7 +28,6 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto constraint_system = from_buffer(constraint_system_buf); - info("got constraint_system"); // The binder would normally free the the constraint_system_buf, but we need the memory now. free_mem_slab_raw((void*)constraint_system_buf); @@ -123,7 +122,6 @@ WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_compose uint8_t** out_vkey, uint8_t** out_key_hash) { - info("about to serialize vkey"); auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto vkey_as_fields = acir_composer->serialize_verification_key_into_fields(); diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.cpp b/cpp/src/barretenberg/plonk/composer/composer_base.cpp index 8cc92c204f..b4fdb7b130 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.cpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.cpp @@ -238,8 +238,7 @@ std::shared_ptr ComposerBase::compute_proving_key_base(const Compos const size_t num_filled_gates = num_gates + public_inputs.size(); const size_t total_num_gates = std::max(minimum_circuit_size, num_filled_gates); const size_t subgroup_size = get_circuit_subgroup_size(total_num_gates + num_reserved_gates); // next power of 2 - info("subgroup_size"); - info(subgroup_size); + // Prealloc slabs of memory for polynomials, pippenger, scratch space, etc. init_slab_allocator(subgroup_size); diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp index b0152992b7..a9fdd96067 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.cpp @@ -565,7 +565,6 @@ std::shared_ptr UltraComposer::compute_proving_key() // Compute selector polynomials and appropriate fft versions and put them in the proving key ComposerBase::compute_proving_key_base(type, tables_size + lookups_size, NUM_RESERVED_GATES); - info("got pkey base"); const size_t subgroup_size = circuit_proving_key->circuit_size; polynomial poly_q_table_column_1(subgroup_size); @@ -656,7 +655,6 @@ std::shared_ptr UltraComposer::compute_proving_key() std::copy(memory_write_records.begin(), memory_write_records.end(), std::back_inserter(circuit_proving_key->memory_write_records)); - info("copied memory write records"); circuit_proving_key->recursive_proof_public_input_indices = std::vector(recursive_proof_public_input_indices.begin(), recursive_proof_public_input_indices.end()); diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index fbcd55e303..87e55977d0 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -175,8 +175,6 @@ template class Transcript { void apply_fiat_shamir(const std::string& challenge_name) { - info("apply_fiat_shamir"); - info(challenge_name); const size_t num_challenges = get_manifest().get_round_manifest(current_round).num_challenges; transcript_base.apply_fiat_shamir(challenge_name); diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index b117a91c1d..244aa0950c 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -215,7 +215,6 @@ aggregation_state verify_proof_(typename Curve::Composer* context, const auto PI_Z = transcript.get_circuit_group_element("PI_Z"); const auto PI_Z_OMEGA = transcript.get_circuit_group_element("PI_Z_OMEGA"); - info("get_circuit_group_element"); field_t circuit_size = key->n; field_t public_input_size = key->num_public_inputs; @@ -228,7 +227,6 @@ aggregation_state verify_proof_(typename Curve::Composer* context, transcript.apply_fiat_shamir("beta"); transcript.apply_fiat_shamir("alpha"); transcript.apply_fiat_shamir("z"); - info("apply_fiat_shamir"); fr_ct alpha = transcript.get_challenge_field_element("alpha"); fr_ct zeta = transcript.get_challenge_field_element("z"); @@ -236,29 +234,21 @@ aggregation_state verify_proof_(typename Curve::Composer* context, key->z_pow_n = zeta.pow(key->domain.domain); lagrange_evaluations lagrange_evals = get_lagrange_evaluations(zeta, key->domain); - info("lagrange_evals"); // reconstruct evaluation of quotient polynomial from prover messages fr_ct quotient_numerator_eval = fr_ct(0); program_settings::compute_quotient_evaluation_contribution(key.get(), alpha, transcript, quotient_numerator_eval); - info("compute_quotient_evaluation_contribution"); - info(quotient_numerator_eval); + fr_ct t_eval = quotient_numerator_eval / lagrange_evals.vanishing_poly; transcript.add_field_element("t", t_eval); - info(t_eval); - info("Curve::Composer::type"); - info(Curve::Composer::type); + transcript.apply_fiat_shamir("nu"); - info("apply_fiat_shamir nu"); transcript.apply_fiat_shamir("separator"); - info("apply_fiat_shamir separator"); fr_ct u = transcript.get_challenge_field_element("separator", 0); - info("get_challenge_field_element on separator"); fr_ct batch_opening_scalar; - info("about to populate_kate_element_map"); populate_kate_element_map, program_settings>(context, key.get(), @@ -268,7 +258,7 @@ aggregation_state verify_proof_(typename Curve::Composer* context, kate_fr_elements_at_zeta_large, kate_fr_elements_at_zeta_omega, batch_opening_scalar); - info("populate_kate_element_map"); + std::vector double_opening_scalars; std::vector double_opening_elements; std::vector opening_scalars; @@ -387,7 +377,6 @@ aggregation_state verify_proof_(typename Curve::Composer* context, rhs_elements.push_back((-g1_ct(x1, y1))); rhs_scalars.push_back(recursion_separator_challenge); } - info("about to bn254_endo_batch_mul_with_generator"); auto opening_result = g1_ct::template bn254_endo_batch_mul_with_generator( big_opening_elements, big_opening_scalars, opening_elements, opening_scalars, batch_opening_scalar, 128); @@ -396,7 +385,6 @@ aggregation_state verify_proof_(typename Curve::Composer* context, for (const auto& to_add : elements_to_add) { opening_result = opening_result + to_add; } - info("final opening_result"); g1_ct rhs = g1_ct::template wnaf_batch_mul<128>(rhs_elements, rhs_scalars); diff --git a/cpp/src/barretenberg/transcript/transcript.cpp b/cpp/src/barretenberg/transcript/transcript.cpp index b510f312cd..1371a08eac 100644 --- a/cpp/src/barretenberg/transcript/transcript.cpp +++ b/cpp/src/barretenberg/transcript/transcript.cpp @@ -233,13 +233,11 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b break; } case HashType::PedersenBlake3s: { - info("in pedersen"); std::vector compressed_buffer = to_buffer(crypto::pedersen_commitment::compress_native(buffer)); base_hash = Blake3sHasher::hash(compressed_buffer); break; } case HashType::PlookupPedersenBlake3s: { - info("in plookup pedersen"); std::vector compressed_buffer = crypto::pedersen_commitment::lookup::compress_native(buffer); base_hash = Blake3sHasher::hash_plookup(compressed_buffer); break; @@ -248,7 +246,7 @@ void Transcript::apply_fiat_shamir(const std::string& challenge_name /*, const b throw_or_abort("no hasher was selected for the transcript"); } } - info("got base_hash"); + // Depending on the settings, we might be able to chunk the bytes of a single hash across multiple challenges: const size_t challenges_per_hash = PRNG_OUTPUT_SIZE / num_challenge_bytes; diff --git a/ts/src/main.ts b/ts/src/main.ts index ea860813b7..544155bb5e 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -62,7 +62,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, is_r debug(`creating proof...`); const witness = getWitness(witnessPath); const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness), is_recursive); - console.log(proof); + const verified = await api.acirVerifyProof(acirComposer, proof, is_recursive); debug(`verified: ${verified}`); return verified; @@ -273,7 +273,6 @@ program .requiredOption('-n, --num-public-inputs ', 'Specify the number of public inputs') .requiredOption('-o, --output-path ', 'Specify the JSON path to write the proof fields') .action(async ({ proofPath, numPublicInputs, outputPath }) => { - console.log(numPublicInputs) await proof_as_fields(proofPath, numPublicInputs, outputPath); }) From 0798faec9f40d24b39c14b5967ff695d94e19acf Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 14:46:49 +0000 Subject: [PATCH 55/64] add comment to recursion serialization methods --- .../dsl/acir_proofs/acir_composer.cpp | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index e739ee81d2..d562c4cc1f 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -85,6 +85,14 @@ std::string AcirComposer::get_solidity_verifier() return stream.str(); } +/** + * @brief Takes in a proof buffer and converts into a vector of field elements. + * The Recursion opcode requires the proof serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param proof + * @param num_inner_public_inputs - number of public inputs on the proof being serialized + */ std::vector AcirComposer::serialize_proof_into_fields(std::vector const& proof, size_t num_inner_public_inputs) { @@ -97,7 +105,12 @@ std::vector AcirComposer::serialize_proof_into_fields(std::vec return output; } -// The composer should already have a verification key initialized +/** + * @brief Takes in a verification key buffer and converts into a vector of field elements. + * The Recursion opcode requires the vk serialized as a vector of witnesses. + * Use this method to get the witness values! + * The composer should already have a verification key initialized. + */ std::vector AcirComposer::serialize_verification_key_into_fields() { std::vector output = acir_format::export_key_in_recursion_format(verification_key_); From 4da23758088dec421bee1b3223a00102435da3a1 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 15:27:11 +0000 Subject: [PATCH 56/64] match package.json on cl/acir_cbinds --- ts/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ts/package.json b/ts/package.json index d997e85f80..e8bb1dc842 100644 --- a/ts/package.json +++ b/ts/package.json @@ -9,7 +9,7 @@ }, "bin": { "bb.js": "./dest/main.js", - "bb.js-dev": "./bb.js-dev" + "bb.js-dev": "./src/main-dev.ts" }, "scripts": { "clean": "rm -rf ./dest .tsbuildinfo", From 75b8fbf8df3c5f71dbe2b4604109bbfe97a1d3b6 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 15:34:54 +0000 Subject: [PATCH 57/64] don't remove EventEmitter from bb_wasm.ts --- ts/src/barretenberg_wasm/barretenberg_wasm.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/src/barretenberg_wasm/barretenberg_wasm.ts b/ts/src/barretenberg_wasm/barretenberg_wasm.ts index f8139aa536..a597f89812 100644 --- a/ts/src/barretenberg_wasm/barretenberg_wasm.ts +++ b/ts/src/barretenberg_wasm/barretenberg_wasm.ts @@ -1,4 +1,5 @@ import { type Worker } from 'worker_threads'; +import { EventEmitter } from 'events'; import createDebug from 'debug'; import { Remote, proxy } from 'comlink'; import { randomBytes } from '../random/index.js'; @@ -9,6 +10,8 @@ import { fetchCode, getNumCpu, createWorker, getRemoteBarretenbergWasm, threadLo const debug = createDebug('wasm'); +EventEmitter.defaultMaxListeners = 30; + export class BarretenbergWasm { static MAX_THREADS = 16; private memory!: WebAssembly.Memory; From cf866900d05a5d7176cf44039cdf5eed5851438a Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 15:43:44 +0000 Subject: [PATCH 58/64] delete empty exports file --- .vscode/settings.json | 3 +-- ts/exports.json | 0 ts/src/main.ts | 7 ++----- 3 files changed, 3 insertions(+), 7 deletions(-) delete mode 100644 ts/exports.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 2b6b8f70a7..0134c08493 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -105,8 +105,7 @@ "span": "cpp", "*.inc": "cpp", "*.macros": "cpp", - "cuchar": "cpp", - "__verbose_abort": "cpp" + "cuchar": "cpp" }, "solidity.compileUsingRemoteVersion": "v0.6.10+commit.00c0fcaf", "search.exclude": { diff --git a/ts/exports.json b/ts/exports.json deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/ts/src/main.ts b/ts/src/main.ts index bda33f7eb1..6676895eba 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -14,7 +14,7 @@ const debug = createDebug('bb.js'); // createDebug.enable('*'); // Maximum we support. -const CIRCUIT_SIZE = 2 ** 18; +const CIRCUIT_SIZE = 2 ** 19; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -32,20 +32,17 @@ function getWitness(witnessPath: string) { async function init() { // Plus 1 needed! const crs = await Crs.new(CIRCUIT_SIZE + 1); - debug('got crs'); + const api = await newBarretenbergApiAsync(); // Import to init slab allocator as first thing, to ensure maximum memory efficiency. await api.commonInitSlabAllocator(CIRCUIT_SIZE); - debug('got slab allocator'); // Load CRS into wasm global CRS state. // TODO: Make RawBuffer be default behaviour, and have a specific Vector type for when wanting length prefixed. await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); - debug('got srs'); const acirComposer = await api.acirNewAcirComposer(); - debug('got acirComposer'); return { api, acirComposer }; } From e17275c26d2641726ed45d68cd307ac9538a3d1d Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 15:46:34 +0000 Subject: [PATCH 59/64] use process.exit --- ts/src/main.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ts/src/main.ts b/ts/src/main.ts index 6676895eba..fb4d4a7f75 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -7,7 +7,6 @@ import { gunzipSync } from 'zlib'; import { RawBuffer } from './types/index.js'; import { numToUInt32BE } from './serialize/serialize.js'; import { Command } from 'commander'; -import { exit } from 'process'; createDebug.log = console.error.bind(console); const debug = createDebug('bb.js'); @@ -211,7 +210,7 @@ function bufferAsFieldHex(buffer: Buffer): string { const hex = buffer.toString('hex'); const splitHex = hex.match(/.{1,64}/g); if (splitHex == null) { - exit(); + process.exit(1); } else { for (let i = 0; i < splitHex.length; i++) { splitHex[i] = '0x'.concat(splitHex[i]); From 8d785e181ebe3844d23cb42b5cbf1abfb4b5c9f6 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Tue, 30 May 2023 15:57:35 +0000 Subject: [PATCH 60/64] log --- ts/src/main.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ts/src/main.ts b/ts/src/main.ts index c5a04d06cd..008b499042 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -62,6 +62,7 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string) { debug(`creating proof...`); const witness = getWitness(witnessPath); const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness)); + debug(`done.`); const verified = await api.acirVerifyProof(acirComposer, proof); console.log(`verified: ${verified}`); @@ -84,6 +85,7 @@ export async function prove(jsonPath: string, witnessPath: string, outputPath: s debug(`creating proof...`); const witness = getWitness(witnessPath); const proof = await api.acirCreateProof(acirComposer, new RawBuffer(bytecode), new RawBuffer(witness)); + debug(`done.`); writeFileSync(outputPath, proof); console.log(`proof written to: ${outputPath}`); @@ -101,6 +103,7 @@ export async function gateCount(jsonPath: string) { const gates = await api.acirGetTotalCircuitSize(acirComposer); console.log(`${gates}`); } finally { + await api.acirDeleteAcirComposer(acirComposer); await api.destroy(); } } From 6723609300008696baa995fa52d87da9b6445340 Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 16:19:03 +0000 Subject: [PATCH 61/64] add comment for why CIRCUIT_SIZE is 2**18 --- ts/src/main.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ts/src/main.ts b/ts/src/main.ts index fb4d4a7f75..9658133c96 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -13,7 +13,9 @@ const debug = createDebug('bb.js'); // createDebug.enable('*'); // Maximum we support. -const CIRCUIT_SIZE = 2 ** 19; +// TODO: When generating a proving key with a recursion constraint in the circuit +// we go over the memory limit with the circuit size set to 2**19 +const CIRCUIT_SIZE = 2 ** 18; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -239,7 +241,6 @@ program .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') .option('-r, --recursive', 'prove and verify using recursive prover and verifier', false) .action(async ({ jsonPath, witnessPath, recursive }) => { - debug(recursive); const result = await proveAndVerify(jsonPath, witnessPath, recursive); process.exit(result ? 0 : 1); }); From cc968764f75bd696477f7c6cb9fbcdd79dc379ec Mon Sep 17 00:00:00 2001 From: vezenovm Date: Tue, 30 May 2023 17:45:22 +0000 Subject: [PATCH 62/64] mising keccak var constraints in new acir format tests --- cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp | 1 + .../barretenberg/dsl/acir_format/recursion_constraint.test.cpp | 2 ++ 2 files changed, 3 insertions(+) diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index 867c3f1091..96c067805a 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -31,6 +31,7 @@ TEST(acir_format, test_a_single_constraint_no_pub_inputs) .sha256_constraints = {}, .blake2s_constraints = {}, .keccak_constraints = {}, + .keccak_var_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index ed7bf8deaf..0117b4f0a5 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -87,6 +87,7 @@ acir_format::Composer create_inner_circuit() .sha256_constraints = {}, .blake2s_constraints = {}, .keccak_constraints = {}, + .keccak_var_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, @@ -214,6 +215,7 @@ acir_format::Composer create_outer_circuit(std::vector& i .sha256_constraints = {}, .blake2s_constraints = {}, .keccak_constraints = {}, + .keccak_var_constraints = {}, .hash_to_field_constraints = {}, .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, From d5a5a2640a5ced240f085a0b42a0f156219f910e Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 1 Jun 2023 07:29:53 +0000 Subject: [PATCH 63/64] Cleanup some serialization. --- .../barretenberg/common/slab_allocator.cpp | 4 +- .../barretenberg/common/slab_allocator.hpp | 2 +- .../dsl/acir_proofs/acir_composer.cpp | 54 ++++-- .../dsl/acir_proofs/acir_composer.hpp | 15 +- .../barretenberg/dsl/acir_proofs/c_bind.cpp | 91 ++++----- .../barretenberg/dsl/acir_proofs/c_bind.hpp | 19 +- exports.json | 77 ++++---- ts/package.json | 4 +- ts/src/barretenberg_api/index.ts | 56 +++--- ts/src/barretenberg_wasm/barretenberg_wasm.ts | 2 + ts/src/main.ts | 177 +++++++++--------- ts/src/serialize/serialize.ts | 4 + ts/src/types/fields.ts | 4 +- 13 files changed, 269 insertions(+), 240 deletions(-) diff --git a/cpp/src/barretenberg/common/slab_allocator.cpp b/cpp/src/barretenberg/common/slab_allocator.cpp index 89597d8cce..96be5d7426 100644 --- a/cpp/src/barretenberg/common/slab_allocator.cpp +++ b/cpp/src/barretenberg/common/slab_allocator.cpp @@ -195,9 +195,9 @@ SlabAllocator allocator; } // namespace namespace barretenberg { -void init_slab_allocator(size_t circuit_size) +void init_slab_allocator(size_t circuit_subgroup_size) { - allocator.init(circuit_size); + allocator.init(circuit_subgroup_size); } // auto init = ([]() { diff --git a/cpp/src/barretenberg/common/slab_allocator.hpp b/cpp/src/barretenberg/common/slab_allocator.hpp index 2db615b00b..be5ef6ea1b 100644 --- a/cpp/src/barretenberg/common/slab_allocator.hpp +++ b/cpp/src/barretenberg/common/slab_allocator.hpp @@ -25,7 +25,7 @@ namespace barretenberg { * TODO: De-globalise. Init the allocator and pass around. Use a PolynomialFactory (PolynomialStore?). * TODO: Consider removing, but once due-dilligence has been done that we no longer have memory limitations. */ -void init_slab_allocator(size_t circuit_size); +void init_slab_allocator(size_t circuit_subgroup_size); /** * Returns a slab from the preallocated pool of slabs, or fallback to a new heap allocation (32 byte aligned). diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp index dc48c0f9be..0dc392c284 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.cpp @@ -4,43 +4,56 @@ #include "barretenberg/plonk/proof_system/proving_key/serialize.hpp" #include "barretenberg/dsl/acir_format/acir_format.hpp" #include "barretenberg/dsl/types.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/srs/factories/crs_factory.hpp" #include "barretenberg/plonk/proof_system/verification_key/sol_gen.hpp" #include "barretenberg/srs/factories/crs_factory.hpp" namespace acir_proofs { -AcirComposer::AcirComposer(std::shared_ptr const& crs_factory) - : crs_factory_(crs_factory) - , composer_(0, 0, 0) +AcirComposer::AcirComposer() + : composer_(0, 0, 0) {} -void AcirComposer::create_circuit(acir_format::acir_format& constraint_system, size_t size_hint) +void AcirComposer::create_circuit(acir_format::acir_format& constraint_system) { - composer_ = acir_format::create_circuit(constraint_system, crs_factory_, size_hint); + composer_ = acir_format::create_circuit(constraint_system, nullptr); // We are done with the constraint system at this point, and we need the memory slab back. - // constraint_system = acir_format::acir_format(); constraint_system.constraints.clear(); constraint_system.constraints.shrink_to_fit(); + exact_circuit_size_ = composer_.get_num_gates(); total_circuit_size_ = composer_.get_total_circuit_size(); circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); } -void AcirComposer::init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint) +void AcirComposer::init_proving_key(std::shared_ptr const& crs_factory, + acir_format::acir_format& constraint_system, + size_t size_hint) { - create_circuit(constraint_system, size_hint); + composer_ = acir_format::create_circuit(constraint_system, crs_factory, size_hint); + + // We are done with the constraint system at this point, and we need the memory slab back. + constraint_system.constraints.clear(); + constraint_system.constraints.shrink_to_fit(); + + exact_circuit_size_ = composer_.get_num_gates(); + total_circuit_size_ = composer_.get_total_circuit_size(); + circuit_subgroup_size_ = composer_.get_circuit_subgroup_size(total_circuit_size_); + proving_key_ = composer_.compute_proving_key(); } -std::vector AcirComposer::create_proof(acir_format::acir_format& constraint_system, - acir_format::WitnessVector& witness, - bool is_recursive) +std::vector AcirComposer::create_proof( + std::shared_ptr const& crs_factory, + acir_format::acir_format& constraint_system, + acir_format::WitnessVector& witness, + bool is_recursive) { composer_ = acir_format::Composer(proving_key_, verification_key_, circuit_subgroup_size_); // You can't produce the verification key unless you manually set the crs. Which seems like a bug. - composer_.crs_factory_ = crs_factory_; + composer_.crs_factory_ = crs_factory; create_circuit_with_witness(composer_, constraint_system, witness); @@ -66,17 +79,26 @@ std::shared_ptr AcirComposer::init_verifi return verification_key_ = composer_.compute_verification_key(); } +void AcirComposer::load_verification_key(std::shared_ptr const& crs_factory, + proof_system::plonk::verification_key_data&& data) +{ + verification_key_ = + std::make_shared(std::move(data), crs_factory->get_verifier_crs()); + composer_ = acir_format::Composer(proving_key_, verification_key_, circuit_subgroup_size_); +} + bool AcirComposer::verify_proof(std::vector const& proof, bool is_recursive) { - bool verified = false; + // Hack. Shouldn't need to do this. 2144 is size with no public inputs. + composer_.public_inputs.resize((proof.size() - 2144) / 32); + if (is_recursive) { auto verifier = composer_.create_verifier(); - verified = verifier.verify_proof({ proof }); + return verifier.verify_proof({ proof }); } else { auto verifier = composer_.create_ultra_with_keccak_verifier(); - verified = verifier.verify_proof({ proof }); + return verifier.verify_proof({ proof }); } - return verified; } std::string AcirComposer::get_solidity_verifier() diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp index b1dc7957ab..c6de53c205 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_composer.hpp @@ -9,16 +9,22 @@ namespace acir_proofs { class AcirComposer { public: - AcirComposer(std::shared_ptr const& crs_factory); + AcirComposer(); - void create_circuit(acir_format::acir_format& constraint_system, size_t size_hint = 0); + void create_circuit(acir_format::acir_format& constraint_system); - void init_proving_key(acir_format::acir_format& constraint_system, size_t size_hint = 0); + void init_proving_key(std::shared_ptr const& crs_factory, + acir_format::acir_format& constraint_system, + size_t size_hint = 0); - std::vector create_proof(acir_format::acir_format& constraint_system, + std::vector create_proof(std::shared_ptr const& crs_factory, + acir_format::acir_format& constraint_system, acir_format::WitnessVector& witness, bool is_recursive); + void load_verification_key(std::shared_ptr const& crs_factory, + proof_system::plonk::verification_key_data&& data); + std::shared_ptr init_verification_key(); bool verify_proof(std::vector const& proof, bool is_recursive); @@ -33,7 +39,6 @@ class AcirComposer { std::vector serialize_verification_key_into_fields(); private: - std::shared_ptr crs_factory_; acir_format::Composer composer_; size_t exact_circuit_size_; size_t total_circuit_size_; diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 9e9c1c9235..8cce085b72 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -7,29 +7,30 @@ #include "barretenberg/common/serialize.hpp" #include "barretenberg/common/slab_allocator.hpp" #include "barretenberg/dsl/acir_format/acir_format.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include "barretenberg/srs/global_crs.hpp" -WASM_EXPORT void acir_new_acir_composer(out_ptr out) +WASM_EXPORT void acir_get_circuit_sizes(uint8_t const* constraint_system_buf, + uint32_t* exact, + uint32_t* total, + uint32_t* subgroup) { - *out = new acir_proofs::AcirComposer(barretenberg::srs::get_crs_factory()); + auto constraint_system = from_buffer(constraint_system_buf); + free_mem_slab_raw((void*)constraint_system_buf); + auto composer = acir_format::create_circuit(constraint_system, nullptr, 1 << 19); + *exact = htonl((uint32_t)composer.get_num_gates()); + *total = htonl((uint32_t)composer.get_total_circuit_size()); + *subgroup = htonl((uint32_t)composer.get_circuit_subgroup_size(composer.get_total_circuit_size())); } -WASM_EXPORT void acir_delete_acir_composer(in_ptr acir_composer_ptr) +WASM_EXPORT void acir_new_acir_composer(out_ptr out) { - delete reinterpret_cast(*acir_composer_ptr); + *out = new acir_proofs::AcirComposer(); } -WASM_EXPORT void acir_create_circuit(in_ptr acir_composer_ptr, - uint8_t const* constraint_system_buf, - uint32_t const* size_hint) +WASM_EXPORT void acir_delete_acir_composer(in_ptr acir_composer_ptr) { - auto acir_composer = reinterpret_cast(*acir_composer_ptr); - auto constraint_system = from_buffer(constraint_system_buf); - - // The binder would normally free the the constraint_system_buf, but we need the memory now. - free_mem_slab_raw((void*)constraint_system_buf); - - acir_composer->create_circuit(constraint_system, ntohl(*size_hint)); + delete reinterpret_cast(*acir_composer_ptr); } WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, @@ -42,7 +43,7 @@ WASM_EXPORT void acir_init_proving_key(in_ptr acir_composer_ptr, // The binder would normally free the the constraint_system_buf, but we need the memory now. free_mem_slab_raw((void*)constraint_system_buf); - acir_composer->init_proving_key(constraint_system, ntohl(*size_hint)); + acir_composer->init_proving_key(barretenberg::srs::get_crs_factory(), constraint_system, ntohl(*size_hint)); } WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, @@ -59,10 +60,18 @@ WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, free_mem_slab_raw((void*)constraint_system_buf); free_mem_slab_raw((void*)witness_buf); - auto proof_data = acir_composer->create_proof(constraint_system, witness, *is_recursive); + auto proof_data = + acir_composer->create_proof(barretenberg::srs::get_crs_factory(), constraint_system, witness, *is_recursive); *out = to_heap_buffer(proof_data); } +WASM_EXPORT void acir_load_verification_key(in_ptr acir_composer_ptr, uint8_t const* vk_buf) +{ + auto acir_composer = reinterpret_cast(*acir_composer_ptr); + auto vk_data = from_buffer(vk_buf); + acir_composer->load_verification_key(barretenberg::srs::get_crs_factory(), std::move(vk_data)); +} + WASM_EXPORT void acir_init_verification_key(in_ptr acir_composer_ptr) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); @@ -96,62 +105,28 @@ WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_bu *out = to_heap_buffer(str); } -WASM_EXPORT void acir_get_exact_circuit_size(in_ptr acir_composer_ptr, uint32_t* out) -{ - auto acir_composer = reinterpret_cast(*acir_composer_ptr); - *out = htonl((uint32_t)acir_composer->get_exact_circuit_size()); -} - -WASM_EXPORT void acir_get_total_circuit_size(in_ptr acir_composer_ptr, uint32_t* out) -{ - auto acir_composer = reinterpret_cast(*acir_composer_ptr); - *out = htonl((uint32_t)acir_composer->get_total_circuit_size()); -} - WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, uint8_t const* proof_buf, uint32_t const* num_inner_public_inputs, - uint8_t** out) + fr::vec_out_buf out) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto proof = from_buffer>(proof_buf); - auto proof_as_fields = acir_composer->serialize_proof_into_fields(proof, ntohl(*num_inner_public_inputs)); - // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - std::vector proof_fields_data; - // The serialization code below will convert out of Montgomery form before writing to the buffer - for (size_t i = 0; i < proof_as_fields.size(); ++i) { - auto proof_field = proof_as_fields[i].to_buffer(); - copy(proof_field.begin(), proof_field.end(), std::back_inserter(proof_fields_data)); - } - *out = to_heap_buffer(proof_fields_data); + *out = to_heap_buffer(proof_as_fields); } WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_composer_ptr, - uint8_t** out_vkey, - uint8_t** out_key_hash) + fr::vec_out_buf out_vkey, + fr::out_buf out_key_hash) { auto acir_composer = reinterpret_cast(*acir_composer_ptr); auto vkey_as_fields = acir_composer->serialize_verification_key_into_fields(); + auto vk_hash = vkey_as_fields.back(); + vkey_as_fields.pop_back(); - // NOTE: this output buffer will always have a fixed size! Maybe just precompute? - // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o - std::vector vk_fields_data; - // The serialization code below will convert out of Montgomery form before writing to the buffer - for (size_t i = 0; i < vkey_as_fields.size() - 1; ++i) { - auto vk_field = vkey_as_fields[i].to_buffer(); - copy(vk_field.begin(), vk_field.end(), std::back_inserter(vk_fields_data)); - } - - std::vector vk_hash_data; - auto vk_hash_field = vkey_as_fields[vkey_as_fields.size() - 1].to_buffer(); - copy(vk_hash_field.begin(), vk_hash_field.end(), std::back_inserter(vk_hash_data)); - - // copy the vkey into serialized_vk_buf - *out_vkey = to_heap_buffer(vk_fields_data); - - // copy the vkey hash into serialized_vk_hash_buf - *out_key_hash = to_heap_buffer(vk_hash_data); + *out_vkey = to_heap_buffer(vkey_as_fields); + write(out_key_hash, vk_hash); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 3d70fc13d2..9fbd0be14a 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -4,6 +4,13 @@ #include #include +using namespace barretenberg; + +WASM_EXPORT void acir_get_circuit_sizes(uint8_t const* constraint_system_buf, + uint32_t* exact, + uint32_t* total, + uint32_t* subgroup); + WASM_EXPORT void acir_new_acir_composer(out_ptr out); WASM_EXPORT void acir_delete_acir_composer(in_ptr acir_composer_ptr); @@ -27,6 +34,8 @@ WASM_EXPORT void acir_create_proof(in_ptr acir_composer_ptr, bool const* is_recursive, uint8_t** out); +WASM_EXPORT void acir_load_verification_key(in_ptr acir_composer_ptr, uint8_t const* vk_buf); + WASM_EXPORT void acir_init_verification_key(in_ptr acir_composer_ptr); WASM_EXPORT void acir_get_verification_key(in_ptr acir_composer_ptr, uint8_t** out); @@ -38,15 +47,11 @@ WASM_EXPORT void acir_verify_proof(in_ptr acir_composer_ptr, WASM_EXPORT void acir_get_solidity_verifier(in_ptr acir_composer_ptr, out_str_buf out); -WASM_EXPORT void acir_get_exact_circuit_size(in_ptr acir_composer_ptr, uint32_t* out); - -WASM_EXPORT void acir_get_total_circuit_size(in_ptr acir_composer_ptr, uint32_t* out); - WASM_EXPORT void acir_serialize_proof_into_fields(in_ptr acir_composer_ptr, uint8_t const* proof_buf, uint32_t const* num_inner_public_inputs, - uint8_t** out); + fr::vec_out_buf out); WASM_EXPORT void acir_serialize_verification_key_into_fields(in_ptr acir_composer_ptr, - uint8_t** out_vkey, - uint8_t** out_key_hash); \ No newline at end of file + fr::vec_out_buf out_vkey, + fr::out_buf out_key_hash); \ No newline at end of file diff --git a/exports.json b/exports.json index d0257c8567..60fb981dd1 100644 --- a/exports.json +++ b/exports.json @@ -535,6 +535,30 @@ "outArgs": [], "isAsync": false }, + { + "functionName": "acir_get_circuit_sizes", + "inArgs": [ + { + "name": "constraint_system_buf", + "type": "const uint8_t *" + } + ], + "outArgs": [ + { + "name": "exact", + "type": "uint32_t *" + }, + { + "name": "total", + "type": "uint32_t *" + }, + { + "name": "subgroup", + "type": "uint32_t *" + } + ], + "isAsync": false + }, { "functionName": "acir_new_acir_composer", "inArgs": [], @@ -623,6 +647,21 @@ ], "isAsync": false }, + { + "functionName": "acir_load_verification_key", + "inArgs": [ + { + "name": "acir_composer_ptr", + "type": "in_ptr" + }, + { + "name": "vk_buf", + "type": "const uint8_t *" + } + ], + "outArgs": [], + "isAsync": false + }, { "functionName": "acir_init_verification_key", "inArgs": [ @@ -690,38 +729,6 @@ ], "isAsync": false }, - { - "functionName": "acir_get_exact_circuit_size", - "inArgs": [ - { - "name": "acir_composer_ptr", - "type": "in_ptr" - } - ], - "outArgs": [ - { - "name": "out", - "type": "uint32_t *" - } - ], - "isAsync": false - }, - { - "functionName": "acir_get_total_circuit_size", - "inArgs": [ - { - "name": "acir_composer_ptr", - "type": "in_ptr" - } - ], - "outArgs": [ - { - "name": "out", - "type": "uint32_t *" - } - ], - "isAsync": false - }, { "functionName": "acir_serialize_proof_into_fields", "inArgs": [ @@ -741,7 +748,7 @@ "outArgs": [ { "name": "out", - "type": "uint8_t **" + "type": "fr::vec_out_buf" } ], "isAsync": false @@ -757,11 +764,11 @@ "outArgs": [ { "name": "out_vkey", - "type": "uint8_t **" + "type": "fr::vec_out_buf" }, { "name": "out_key_hash", - "type": "uint8_t **" + "type": "fr::out_buf" } ], "isAsync": false diff --git a/ts/package.json b/ts/package.json index e8bb1dc842..91c469b3c9 100644 --- a/ts/package.json +++ b/ts/package.json @@ -15,7 +15,7 @@ "clean": "rm -rf ./dest .tsbuildinfo", "build": "yarn clean && yarn build:wasm && tsc -b && webpack && chmod +x ./dest/main.js", "build:dev": "tsc -b --watch", - "build:wasm": "cd ../cpp && cmake --preset wasm-threads && cmake --build --preset wasm-threads", + "build:wasm": "cd ../cpp && cmake --preset wasm-threads && cmake --build --preset wasm-threads && cmake --preset wasm && cmake --build --preset wasm", "serve": "webpack serve", "formatting": "prettier --check ./src && eslint --max-warnings 0 ./src", "formatting:fix": "prettier -w ./src", @@ -42,6 +42,7 @@ "dependencies": { "bigint-buffer": "^1.1.5", "commander": "^10.0.1", + "ts-node": "^10.9.1", "tslib": "^2.4.0" }, "devDependencies": { @@ -66,7 +67,6 @@ "resolve-typescript-plugin": "^2.0.1", "ts-jest": "^29.1.0", "ts-loader": "^9.4.2", - "ts-node": "^10.9.1", "typescript": "^5.0.4", "webpack": "^5.82.1", "webpack-cli": "^5.1.1", diff --git a/ts/src/barretenberg_api/index.ts b/ts/src/barretenberg_api/index.ts index 9649dffed4..3e27fe74ae 100644 --- a/ts/src/barretenberg_api/index.ts +++ b/ts/src/barretenberg_api/index.ts @@ -166,6 +166,11 @@ export class BarretenbergApi { return; } + async acirGetCircuitSizes(constraintSystemBuf: Uint8Array): Promise<[number, number, number]> { + const result = await this.binder.callWasmExport('acir_get_circuit_sizes', [constraintSystemBuf], [NumberDeserializer(), NumberDeserializer(), NumberDeserializer()]); + return result as any; + } + async acirNewAcirComposer(): Promise { const result = await this.binder.callWasmExport('acir_new_acir_composer', [], [Ptr]); return result[0]; @@ -191,6 +196,11 @@ export class BarretenbergApi { return result[0]; } + async acirLoadVerificationKey(acirComposerPtr: Ptr, vkBuf: Uint8Array): Promise { + const result = await this.binder.callWasmExport('acir_load_verification_key', [acirComposerPtr, vkBuf], []); + return; + } + async acirInitVerificationKey(acirComposerPtr: Ptr): Promise { const result = await this.binder.callWasmExport('acir_init_verification_key', [acirComposerPtr], []); return; @@ -211,23 +221,13 @@ export class BarretenbergApi { return result[0]; } - async acirGetExactCircuitSize(acirComposerPtr: Ptr): Promise { - const result = await this.binder.callWasmExport('acir_get_exact_circuit_size', [acirComposerPtr], [NumberDeserializer()]); - return result[0]; - } - - async acirGetTotalCircuitSize(acirComposerPtr: Ptr): Promise { - const result = await this.binder.callWasmExport('acir_get_total_circuit_size', [acirComposerPtr], [NumberDeserializer()]); - return result[0]; - } - - async acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Promise { - const result = await this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); + async acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Promise { + const result = await this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [VectorDeserializer(Fr)]); return result[0]; } - async acirSerializeVerificationKeyIntoFields(acirComposerPtr: Ptr): Promise<[Uint8Array, Uint8Array]> { - const result = await this.binder.callWasmExport('acir_serialize_verification_key_into_fields', [acirComposerPtr], [BufferDeserializer(), BufferDeserializer()]); + async acirSerializeVerificationKeyIntoFields(acirComposerPtr: Ptr): Promise<[Fr[], Fr]> { + const result = await this.binder.callWasmExport('acir_serialize_verification_key_into_fields', [acirComposerPtr], [VectorDeserializer(Fr), Fr]); return result as any; } } @@ -394,6 +394,11 @@ export class BarretenbergApiSync { return; } + acirGetCircuitSizes(constraintSystemBuf: Uint8Array): [number, number, number] { + const result = this.binder.callWasmExport('acir_get_circuit_sizes', [constraintSystemBuf], [NumberDeserializer(), NumberDeserializer(), NumberDeserializer()]); + return result as any; + } + acirNewAcirComposer(): Ptr { const result = this.binder.callWasmExport('acir_new_acir_composer', [], [Ptr]); return result[0]; @@ -419,6 +424,11 @@ export class BarretenbergApiSync { return result[0]; } + acirLoadVerificationKey(acirComposerPtr: Ptr, vkBuf: Uint8Array): void { + const result = this.binder.callWasmExport('acir_load_verification_key', [acirComposerPtr, vkBuf], []); + return; + } + acirInitVerificationKey(acirComposerPtr: Ptr): void { const result = this.binder.callWasmExport('acir_init_verification_key', [acirComposerPtr], []); return; @@ -439,23 +449,13 @@ export class BarretenbergApiSync { return result[0]; } - acirGetExactCircuitSize(acirComposerPtr: Ptr): number { - const result = this.binder.callWasmExport('acir_get_exact_circuit_size', [acirComposerPtr], [NumberDeserializer()]); - return result[0]; - } - - acirGetTotalCircuitSize(acirComposerPtr: Ptr): number { - const result = this.binder.callWasmExport('acir_get_total_circuit_size', [acirComposerPtr], [NumberDeserializer()]); - return result[0]; - } - - acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Uint8Array { - const result = this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [BufferDeserializer()]); + acirSerializeProofIntoFields(acirComposerPtr: Ptr, proofBuf: Uint8Array, numInnerPublicInputs: number): Fr[] { + const result = this.binder.callWasmExport('acir_serialize_proof_into_fields', [acirComposerPtr, proofBuf, numInnerPublicInputs], [VectorDeserializer(Fr)]); return result[0]; } - acirSerializeVerificationKeyIntoFields(acirComposerPtr: Ptr): [Uint8Array, Uint8Array] { - const result = this.binder.callWasmExport('acir_serialize_verification_key_into_fields', [acirComposerPtr], [BufferDeserializer(), BufferDeserializer()]); + acirSerializeVerificationKeyIntoFields(acirComposerPtr: Ptr): [Fr[], Fr] { + const result = this.binder.callWasmExport('acir_serialize_verification_key_into_fields', [acirComposerPtr], [VectorDeserializer(Fr), Fr]); return result as any; } } diff --git a/ts/src/barretenberg_wasm/barretenberg_wasm.ts b/ts/src/barretenberg_wasm/barretenberg_wasm.ts index 9b6d93e155..d94b32f989 100644 --- a/ts/src/barretenberg_wasm/barretenberg_wasm.ts +++ b/ts/src/barretenberg_wasm/barretenberg_wasm.ts @@ -65,6 +65,8 @@ export class BarretenbergWasm { this.memory = new WebAssembly.Memory({ initial, maximum, shared: threads > 1 }); + // Annoyingly the wasm declares if it's memory is shared or not. So now we need two wasms if we want to be + // able to fallback on "non shared memory" situations. const code = await fetchCode(threads > 1 ? 'barretenberg-threads.wasm' : 'barretenberg.wasm'); const { instance, module } = await WebAssembly.instantiate(code, this.getImportObj(this.memory)); diff --git a/ts/src/main.ts b/ts/src/main.ts index e026d59313..587117832d 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -1,7 +1,7 @@ #!/usr/bin/env -S node --no-warnings import { Crs } from './crs/index.js'; import createDebug from 'debug'; -import { newBarretenbergApiAsync } from './factory/index.js'; +import { BarretenbergApiAsync, newBarretenbergApiAsync } from './factory/index.js'; import { readFileSync, writeFileSync } from 'fs'; import { gunzipSync } from 'zlib'; import { RawBuffer } from './types/index.js'; @@ -10,12 +10,10 @@ import { Command } from 'commander'; createDebug.log = console.error.bind(console); const debug = createDebug('bb.js'); -createDebug.enable('*'); +// createDebug.enable('*'); // Maximum we support. -// TODO: When generating a proving key with a recursion constraint in the circuit -// we go over the memory limit with the circuit size set to 2**19 -const CIRCUIT_SIZE = 2 ** 19; +const MAX_CIRCUIT_SIZE = 2 ** 19; function getBytecode(jsonPath: string) { const json = readFileSync(jsonPath, 'utf-8'); @@ -30,32 +28,59 @@ function getWitness(witnessPath: string) { return Buffer.concat([numToUInt32BE(data.length / 32), data]); } -async function init() { - // Plus 1 needed! - const crs = await Crs.new(CIRCUIT_SIZE + 1); +async function getCircuitSize(jsonPath: string, api: BarretenbergApiAsync) { + const bytecode = getBytecode(jsonPath); + const [exact, total, subgroup] = await api.acirGetCircuitSizes(new RawBuffer(bytecode)); + return { exact, total, subgroup }; +} +async function init(jsonPath: string) { const api = await newBarretenbergApiAsync(); - // Import to init slab allocator as first thing, to ensure maximum memory efficiency. - await api.commonInitSlabAllocator(CIRCUIT_SIZE); + // First compute circuit size. + const circuitSizes = await getCircuitSize(jsonPath, api); + debug(`circuit size: ${circuitSizes.total}`); + + if (circuitSizes.subgroup > MAX_CIRCUIT_SIZE) { + throw new Error(`Circuit size of ${circuitSizes.subgroup} exceeds max supported of ${MAX_CIRCUIT_SIZE}`); + } + + // Plus 1 needed! (Move +1 into Crs?) + const crs = await Crs.new(circuitSizes.subgroup + 1); + + // Important to init slab allocator as first thing, to ensure maximum memory efficiency. + await api.commonInitSlabAllocator(circuitSizes.subgroup); // Load CRS into wasm global CRS state. // TODO: Make RawBuffer be default behaviour, and have a specific Vector type for when wanting length prefixed. await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + const acirComposer = await api.acirNewAcirComposer(); + return { api, acirComposer, circuitSize: circuitSizes.subgroup }; +} + +async function initLite() { + const api = await newBarretenbergApiAsync(1); + + // Plus 1 needed! (Move +1 into Crs?) + const crs = await Crs.new(1); + + // Load CRS into wasm global CRS state. + await api.srsInitSrs(new RawBuffer(crs.getG1Data()), crs.numPoints, new RawBuffer(crs.getG2Data())); + const acirComposer = await api.acirNewAcirComposer(); return { api, acirComposer }; } export async function proveAndVerify(jsonPath: string, witnessPath: string, isRecursive: boolean) { - const { api, acirComposer } = await init(); + const { api, acirComposer, circuitSize } = await init(jsonPath); try { debug('initing proving key...'); const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize); - const exactCircuitSize = await api.acirGetExactCircuitSize(acirComposer); - debug(`circuit size: ${exactCircuitSize}`); + // const circuitSize = await api.acirGetExactCircuitSize(acirComposer); + // debug(`circuit size: ${circuitSize}`); debug('initing verification key...'); await api.acirInitVerificationKey(acirComposer); @@ -74,14 +99,14 @@ export async function proveAndVerify(jsonPath: string, witnessPath: string, isRe } export async function prove(jsonPath: string, witnessPath: string, isRecursive: boolean, outputPath: string) { - const { api, acirComposer } = await init(); + const { api, acirComposer, circuitSize } = await init(jsonPath); try { debug('initing proving key...'); const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize); - const exactCircuitSize = await api.acirGetExactCircuitSize(acirComposer); - debug(`circuit size: ${exactCircuitSize}`); + // const circuitSize = await api.acirGetExactCircuitSize(acirComposer); + // debug(`circuit size: ${circuitSize}`); debug(`creating proof...`); const witness = getWitness(witnessPath); @@ -96,43 +121,51 @@ export async function prove(jsonPath: string, witnessPath: string, isRecursive: } export async function gateCount(jsonPath: string) { - const { api, acirComposer } = await init(); + const api = await newBarretenbergApiAsync(1); try { - const bytecode = getBytecode(jsonPath); - await api.acirCreateCircuit(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); - - const gates = await api.acirGetTotalCircuitSize(acirComposer); - console.log(`${gates}`); + const circuitSizes = await getCircuitSize(jsonPath, api); + console.log(`${circuitSizes.exact}`); } finally { - await api.acirDeleteAcirComposer(acirComposer); await api.destroy(); } } -export async function verify(jsonPath: string, proofPath: string, isRecursive: boolean) { - const { api, acirComposer } = await init(); - try { - debug('initing proving key...'); - const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); - - debug('initing verification key...'); - await api.acirInitVerificationKey(acirComposer); - - const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive); - debug(`verified: ${verified}`); - return verified; - } finally { - await api.destroy(); +export async function verify(jsonPath: string, proofPath: string, isRecursive: boolean, vkPath?: string) { + if (vkPath) { + const { api, acirComposer } = await initLite(); + try { + await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath))); + const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive); + console.log(`verified: ${verified}`); + return verified; + } finally { + await api.destroy(); + } + } else { + const { api, acirComposer, circuitSize } = await init(jsonPath); + try { + debug('initing proving key...'); + const bytecode = getBytecode(jsonPath); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize); + + debug('initing verification key...'); + await api.acirInitVerificationKey(acirComposer); + + const verified = await api.acirVerifyProof(acirComposer, readFileSync(proofPath), isRecursive); + console.log(`verified: ${verified}`); + return verified; + } finally { + await api.destroy(); + } } } export async function contract(jsonPath: string, outputPath: string) { - const { api, acirComposer } = await init(); + const { api, acirComposer, circuitSize } = await init(jsonPath); try { debug('initing proving key...'); const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize); debug('initing verification key...'); await api.acirInitVerificationKey(acirComposer); @@ -150,11 +183,11 @@ export async function contract(jsonPath: string, outputPath: string) { } export async function writeVk(jsonPath: string, outputPath: string) { - const { api, acirComposer } = await init(); + const { api, acirComposer, circuitSize } = await init(jsonPath); try { debug('initing proving key...'); const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); + await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), circuitSize); debug('initing verification key...'); const vk = await api.acirGetVerificationKey(acirComposer); @@ -170,60 +203,38 @@ export async function writeVk(jsonPath: string, outputPath: string) { } export async function proofAsFields(proofPath: string, numInnerPublicInputs: number, outputPath: string) { - const { api, acirComposer } = await init(); + const { api, acirComposer } = await initLite(); try { debug('serializing proof byte array into field elements'); - const proofAsFieldsBuffer = await api.acirSerializeProofIntoFields( + const proofAsFields = await api.acirSerializeProofIntoFields( acirComposer, readFileSync(proofPath), numInnerPublicInputs, ); - const proofAsFields = bufferAsFieldHex(Buffer.from(proofAsFieldsBuffer)); - writeFileSync(outputPath, proofAsFields); + writeFileSync(outputPath, JSON.stringify(proofAsFields.map(f => f.toString()))); debug('done.'); } finally { await api.destroy(); } } -export async function vkAsFields(jsonPath: string, vkeyOutputPath: string) { - const { api, acirComposer } = await init(); - - // TODO: consider moving to passing in the key so we don't have to recompute it - // or just keep it as the writeVK method currently recomputes the pkey too ¯\_(ツ)_/¯ - debug('initing proving key...'); - const bytecode = getBytecode(jsonPath); - await api.acirInitProvingKey(acirComposer, new RawBuffer(bytecode), CIRCUIT_SIZE); +export async function vkAsFields(vkPath: string, vkeyOutputPath: string) { + const { api, acirComposer } = await initLite(); - await api.acirInitVerificationKey(acirComposer); try { debug('serializing vk byte array into field elements'); - const [vkAsFieldsBuffer, vkHashBuffer] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); - const vkFields = bufferAsFieldHex(Buffer.concat([Buffer.from(vkHashBuffer), Buffer.from(vkAsFieldsBuffer)])); - - writeFileSync(vkeyOutputPath, vkFields); + await api.acirLoadVerificationKey(acirComposer, new RawBuffer(readFileSync(vkPath))); + const [vkAsFields, vkHash] = await api.acirSerializeVerificationKeyIntoFields(acirComposer); + const output = [vkHash, ...vkAsFields].map(f => f.toString()); + writeFileSync(vkeyOutputPath, JSON.stringify(output)); debug('done.'); } finally { await api.destroy(); } } -function bufferAsFieldHex(buffer: Buffer): string { - const hex = buffer.toString('hex'); - const splitHex = hex.match(/.{1,64}/g); - if (splitHex == null) { - process.exit(1); - } else { - for (let i = 0; i < splitHex.length; i++) { - splitHex[i] = '0x'.concat(splitHex[i]); - } - const separateFields = JSON.stringify(splitHex); - return separateFields; - } -} - // nargo use bb.js: backend -> bb.js // backend prove --data-dir data --witness /foo/bar/witness.tr --json /foo/bar/main.json // backend verify ... @@ -273,8 +284,9 @@ program .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') .option('-r, --recursive', 'prove using recursive prover', false) - .action(async ({ jsonPath, proofPath, recursive }) => { - await verify(jsonPath, proofPath, recursive); + .option('-v, --vk ', 'path to a verification key. avoids recomputation.') + .action(async ({ jsonPath, proofPath, recursive, vk }) => { + await verify(jsonPath, proofPath, recursive, vk); }); program @@ -308,13 +320,10 @@ program program .command('vk_as_fields') .description('Return the verifiation key represented as fields elements. Also return the verification key hash.') - .requiredOption('-j, --json-path ', 'Specify the JSON path', './target/main.json') - .requiredOption( - '-v, --vkey-output-path ', - 'Specify the JSON path to write the verification key fields and key hash', - ) - .action(async ({ jsonPath, vkeyOutputPath }) => { - await vkAsFields(jsonPath, vkeyOutputPath); + .requiredOption('-i, --input-path ', 'Specifies the vk path (output from write_vk)') + .requiredOption('-o, --output-path ', 'Specify the JSON path to write the verification key fields and key hash') + .action(async ({ inputPath, outputPath }) => { + await vkAsFields(inputPath, outputPath); }); program.name('bb.js').parse(process.argv); diff --git a/ts/src/serialize/serialize.ts b/ts/src/serialize/serialize.ts index aa3634cc73..b09caac6b0 100644 --- a/ts/src/serialize/serialize.ts +++ b/ts/src/serialize/serialize.ts @@ -46,6 +46,10 @@ export function concatenateUint8Arrays(arrayOfUint8Arrays: Uint8Array[]) { return result; } +export function uint8ArrayToHexString(uint8Array: Uint8Array) { + return uint8Array.reduce((accumulator, byte) => accumulator + byte.toString(16).padStart(2, '0'), ''); +} + // For serializing a buffer as a vector. export function serializeBufferToVector(buf: Uint8Array) { return concatenateUint8Arrays([numToInt32BE(buf.length), buf]); diff --git a/ts/src/types/fields.ts b/ts/src/types/fields.ts index b1ac2ef438..a0b095813d 100644 --- a/ts/src/types/fields.ts +++ b/ts/src/types/fields.ts @@ -1,6 +1,6 @@ import { randomBytes } from '../random/index.js'; import { toBigIntBE, toBufferBE } from '../bigint-array/index.js'; -import { BufferReader } from '../serialize/index.js'; +import { BufferReader, uint8ArrayToHexString } from '../serialize/index.js'; export class Fr { static ZERO = new Fr(0n); @@ -38,7 +38,7 @@ export class Fr { } toString() { - return '0x' + this.value.toString(16); + return '0x' + uint8ArrayToHexString(this.toBuffer()); } equals(rhs: Fr) { From e8e866018c68a49cb71bd2b0dfd70bd650135424 Mon Sep 17 00:00:00 2001 From: Charlie Lye Date: Thu, 1 Jun 2023 11:23:28 +0000 Subject: [PATCH 64/64] Cleanup for publishing. README. --- ts/README.md | 117 +- ts/bb.js-dev | 4 +- ts/package-lock.json | 11910 ----------------------------------------- ts/package.json | 21 +- ts/src/index.ts | 2 + ts/src/main-dev.ts | 2 - ts/src/main.ts | 17 +- ts/yarn.lock | 356 +- 8 files changed, 307 insertions(+), 12122 deletions(-) delete mode 100644 ts/package-lock.json delete mode 100755 ts/src/main-dev.ts diff --git a/ts/README.md b/ts/README.md index c25f356420..a65bad3250 100644 --- a/ts/README.md +++ b/ts/README.md @@ -1,3 +1,116 @@ -# Barretenberg.js +# bb.js -Javascript bindings for barretenberg WASM. +Prover/verifier executable and API for barretenberg. Default cli arguments are appropriate for running within Noir +project structures. + +## Performance and limitations + +Max circuit size is 2^19 gates (524,288). This is due to the underlying WASM 4GB memory limit. This should improve +with future proving systems, and/or introduction of wasm64. + +If running from terminal, or within browser where you can set shared memory CORS headers, multithreading is enabled. +Note there are two independent WASM builds, one with threading enabled and one without. This is because the shared +memory flag is set within the WASM itself. If you're running in a context where you can't have shared memory, we want +to fallback to single threaded performance. + +Performance for 2^19: + +- 16 core x86: ~13s. +- 10 core M1 Mac Pro: ~18s. + +Linear scaling was observed up to 32 cores, however we limit to 16 as 2^19 runs out of memory with 32 cores. +This maybe resolvable. + +## Using as a standalone binary + +### Installing + +To install the package globally for running as a terminal application: + +``` +npm install -g @aztec/bb.js@alpha +``` + +This will install `bb.js` into your path. + +### Usage + +Run `bb.js` for further usage information, you'll see e.g. + +``` +% bb.js +Usage: bb.js [options] [command] + +Options: + -v, --verbose enable verbose logging (default: false) + -h, --help display help for command + +Commands: + prove_and_verify [options] Generate a proof and verify it. Process exits with success or failure code. + prove [options] Generate a proof and write it to a file. + gates [options] Print gate count to standard output. + verify [options] Verify a proof. Process exists with success or failure code. + contract [options] Output solidity verification key contract. + write_vk [options] Output verification key. + proof_as_fields [options] Return the proof as fields elements + vk_as_fields [options] Return the verifiation key represented as fields elements. Also return the verification key hash. + help [command] display help for command +``` + +## Using as a library + +### Installing + +To install as a package to be used as a library: + +``` +npm install @aztec/bb.js@alpha +``` + +or with yarn + +``` +yarn add @aztec/bb.js@alpha +``` + +### Usage + +To create a multithreaded version of the API: + +```typescript +const api = await newBarretenbergApiAsync(/* num_threads */); +// Use. +const input = Buffer.from('hello world!'); +const result = await api.blake2s(input); +await api.destroy(); +``` + +All methods are asynchronous. If no threads are specified, will default to number of cores with a maximum of 16. +If `1` is specified, fallback to non multi-threaded wasm that doesn't need shared memory. + +You can also create a synchronous version of the api that also has no multi-threading. This is only useful in the +browser if you don't call any multi-threaded functions. It's probably best to just always use async version of the api +unless you're really trying to avoid the small overhead of worker communication. + +```typescript +const api = await newBarretenbergApiSync(); +// Use. +const input = Buffer.from('hello world!'); +const result = api.blake2s(input); +await api.destroy(); +``` + +See `src/main.ts` for one example of how to use. + +## Development + +Create a symlink to the root script `bb.js-dev` in your path. You can now run the current state of the code from +anywhere in your filesystem with no `yarn build` required. + +If you change the C++ code run `yarn build:wasm`. + +To run the tests run `yarn test`. + +To run a continuous "stress tests" run `yarn simple_test` to do 10 full pk/proof/vk iterations. + +To run the same test in the browser run `yarn serve`, navigate to appropriate URL and open the console. diff --git a/ts/bb.js-dev b/ts/bb.js-dev index e32ba65206..5255fe4b37 100755 --- a/ts/bb.js-dev +++ b/ts/bb.js-dev @@ -1,4 +1,6 @@ #!/bin/sh +# Add a symlink to this somewhere in your path. +# Now you can run bb.js-dev anywhere to execute latest code, no 'yarn build' required. SCRIPT_PATH=$(dirname $(realpath $0)) export TS_NODE_PROJECT="$SCRIPT_PATH/tsconfig.json" -DEBUG="bb.js*" NODE_OPTIONS="--loader $SCRIPT_PATH/node_modules/ts-node/esm/transpile-only.mjs --no-warnings" node $SCRIPT_PATH/src/main.ts $@ +NODE_OPTIONS="--loader $SCRIPT_PATH/node_modules/ts-node/esm/transpile-only.mjs --no-warnings" node $SCRIPT_PATH/src/main.ts $@ diff --git a/ts/package-lock.json b/ts/package-lock.json deleted file mode 100644 index 2b533c549f..0000000000 --- a/ts/package-lock.json +++ /dev/null @@ -1,11910 +0,0 @@ -{ - "name": "@aztec/barretenberg.js", - "version": "0.0.0", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@aztec/barretenberg.js", - "version": "0.0.0", - "dependencies": { - "bigint-buffer": "^1.1.5", - "commander": "^10.0.1", - "tslib": "^2.4.0" - }, - "bin": { - "bb.js": "dest/main.js", - "bb.js-dev": "src/main-dev.ts" - }, - "devDependencies": { - "@jest/globals": "^29.4.3", - "@types/debug": "^4.1.7", - "@types/detect-node": "^2.0.0", - "@types/jest": "^29.4.0", - "@types/node": "^18.7.23", - "@types/source-map-support": "^0.5.6", - "@typescript-eslint/eslint-plugin": "^5.54.1", - "@typescript-eslint/parser": "^5.54.1", - "buffer": "^6.0.3", - "comlink": "^4.4.1", - "copy-webpack-plugin": "^11.0.0", - "debug": "^4.3.4", - "eslint": "^8.35.0", - "eslint-config-prettier": "^8.8.0", - "html-webpack-plugin": "^5.5.1", - "idb-keyval": "^6.2.1", - "jest": "^29.5.0", - "prettier": "^2.8.4", - "resolve-typescript-plugin": "^2.0.1", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.2", - "ts-node": "^10.9.1", - "typescript": "^5.0.4", - "webpack": "^5.82.1", - "webpack-cli": "^5.1.1", - "webpack-dev-server": "^4.15.0" - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.4", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.4", - "@babel/types": "^7.21.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.21.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.21.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.20.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.20.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.19.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.19.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.21.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.18.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.20.2" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.20.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.21.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.1", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/js": { - "version": "8.38.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { - "version": "3.14.1", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/core": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/environment": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.4.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/globals": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/schemas": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.25.16" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/transform/node_modules/convert-source-map": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/types": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - } - }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true, - "license": "MIT" - }, - "node_modules/@leichtgewicht/ip-codec": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.25.24", - "dev": true, - "license": "MIT" - }, - "node_modules/@sinonjs/commons": { - "version": "2.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "10.0.2", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@sinonjs/commons": "^2.0.0" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/babel__core": { - "version": "7.20.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.18.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/bonjour": { - "version": "3.5.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/connect-history-api-fallback": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "node_modules/@types/debug": { - "version": "4.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/detect-node": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/eslint": { - "version": "8.37.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "4.17.17", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.34", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/html-minifier-terser": { - "version": "6.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/http-proxy": { - "version": "1.17.11", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/mime": { - "version": "1.3.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "0.7.31", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "18.15.11", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/prettier": { - "version": "2.7.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/retry": { - "version": "0.12.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/semver": { - "version": "7.3.13", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "0.17.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "node_modules/@types/serve-index": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/express": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "1.15.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/mime": "*", - "@types/node": "*" - } - }, - "node_modules/@types/serve-static/node_modules/@types/mime": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/sockjs": { - "version": "0.3.33", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/source-map-support": { - "version": "0.5.6", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.6.0" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/yargs": { - "version": "17.0.24", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/type-utils": "5.57.1", - "@typescript-eslint/utils": "5.57.1", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^5.0.0", - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.57.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/typescript-estree": "5.57.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/visitor-keys": "5.57.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "5.57.1", - "@typescript-eslint/utils": "5.57.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.57.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/visitor-keys": "5.57.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/typescript-estree": "5.57.1", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.57.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "5.57.1", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "dev": true, - "license": "MIT" - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/configtest": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/info": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.15.0" - }, - "peerDependencies": { - "webpack": "5.x.x", - "webpack-cli": "5.x.x" - }, - "peerDependenciesMeta": { - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/accepts": { - "version": "1.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.2", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.9.0", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/ajv": { - "version": "8.12.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3" - }, - "peerDependencies": { - "ajv": "^8.8.2" - } - }, - "node_modules/ajv/node_modules/json-schema-traverse": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-html-community": { - "version": "0.0.8", - "dev": true, - "engines": [ - "node >= 0.8.0" - ], - "license": "Apache-2.0", - "bin": { - "ansi-html": "bin/ansi-html" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/argparse": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/array-flatten": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/array-union": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/batch": { - "version": "0.6.1", - "dev": true, - "license": "MIT" - }, - "node_modules/bigint-buffer": { - "version": "1.1.5", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "bindings": "^1.3.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "license": "MIT", - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/body-parser": { - "version": "1.20.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/bonjour-service": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.21.5", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bs-logger": { - "version": "0.2.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-json-stable-stringify": "2.x" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/bytes": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/camel-case": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001476", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chokidar/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.8.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/cjs-module-lexer": { - "version": "1.2.2", - "dev": true, - "license": "MIT" - }, - "node_modules/clean-css": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "~0.6.0" - }, - "engines": { - "node": ">= 10.0" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "dev": true, - "license": "MIT" - }, - "node_modules/comlink": { - "version": "4.4.1", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/commander": { - "version": "10.0.1", - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/compressible": { - "version": "2.0.18", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/concat-map": { - "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/connect-history-api-fallback": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8" - } - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "0.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/copy-webpack-plugin": { - "version": "11.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "engines": { - "node": ">= 14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - } - }, - "node_modules/copy-webpack-plugin/node_modules/globby": { - "version": "13.1.4", - "dev": true, - "license": "MIT", - "dependencies": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/copy-webpack-plugin/node_modules/slash": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-select": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "4.3.4", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/dedent": { - "version": "0.7.0", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/default-gateway": { - "version": "6.0.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "execa": "^5.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/detect-node": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dns-equal": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/dns-packet": { - "version": "5.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@leichtgewicht/ip-codec": "^2.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/dom-converter": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "utila": "~0.4" - } - }, - "node_modules/dom-serializer": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "4.3.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.2.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "2.8.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dot-case": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.356", - "dev": true, - "license": "ISC" - }, - "node_modules/emittery": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.14.0", - "dev": true, - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/entities": { - "version": "2.2.0", - "dev": true, - "license": "BSD-2-Clause", - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "dev": true, - "license": "MIT", - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/es-module-lexer": { - "version": "1.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "dev": true, - "license": "MIT" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.38.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "8.8.0", - "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-scope/node_modules/estraverse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.4.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/find-up": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/locate-path": { - "version": "6.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/p-locate": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/espree": { - "version": "9.5.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "dev": true, - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.5.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "dev": true, - "license": "MIT" - }, - "node_modules/events": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expect": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/express": { - "version": "4.18.2", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/express/node_modules/array-flatten": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-glob": { - "version": "3.2.12", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fastest-levenshtein": { - "version": "1.0.16", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4.9.1" - } - }, - "node_modules/fastq": { - "version": "1.15.0", - "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/faye-websocket": { - "version": "0.11.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.2", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/file-entry-cache": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/flatted": { - "version": "3.2.7", - "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fs-monkey": { - "version": "1.0.3", - "dev": true, - "license": "Unlicense" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "dev": true, - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "dev": true, - "license": "BSD-2-Clause" - }, - "node_modules/globals": { - "version": "13.20.0", - "dev": true, - "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "dev": true, - "license": "ISC" - }, - "node_modules/grapheme-splitter": { - "version": "1.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/handle-thing": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/has": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/he": { - "version": "1.2.0", - "dev": true, - "license": "MIT", - "bin": { - "he": "bin/he" - } - }, - "node_modules/hpack.js": { - "version": "2.1.6", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - } - }, - "node_modules/hpack.js/node_modules/readable-stream": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/hpack.js/node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/hpack.js/node_modules/string_decoder": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/html-entities": { - "version": "2.3.3", - "dev": true, - "license": "MIT" - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/html-minifier-terser": { - "version": "6.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "bin": { - "html-minifier-terser": "cli.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/html-minifier-terser/node_modules/commander": { - "version": "8.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "node_modules/html-webpack-plugin": { - "version": "5.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/html-webpack-plugin" - }, - "peerDependencies": { - "webpack": "^5.20.0" - } - }, - "node_modules/htmlparser2": { - "version": "6.1.0", - "dev": true, - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "node_modules/http-deceiver": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/http-errors": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "dev": true, - "license": "MIT" - }, - "node_modules/http-proxy": { - "version": "1.18.1", - "dev": true, - "license": "MIT", - "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-middleware": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "@types/express": "^4.17.13" - }, - "peerDependenciesMeta": { - "@types/express": { - "optional": true - } - } - }, - "node_modules/human-signals": { - "version": "2.1.0", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=10.17.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "dev": true, - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/idb-keyval": { - "version": "6.2.1", - "dev": true, - "license": "Apache-2.0" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore": { - "version": "5.2.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/import-local": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "node_modules/interpret": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.11.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-plain-obj": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-plain-object": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/isobject": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.2.0", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-reports": { - "version": "3.1.5", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", - "import-local": "^3.0.2", - "jest-cli": "^29.5.0" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-circus": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-cli": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-config": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-docblock": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-each": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-node": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-message-util": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-util": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "29.4.3", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-runner/node_modules/source-map-support": { - "version": "0.5.13", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/jest-runtime": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-util": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.5.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.5.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.5.0", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "dev": true, - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/js-yaml/node_modules/argparse": { - "version": "2.0.1", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/jsesc": { - "version": "2.5.2", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/launch-editor": { - "version": "2.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "dev": true, - "license": "MIT" - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.21", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.memoize": { - "version": "4.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/lower-case": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^2.0.3" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, - "node_modules/makeerror": { - "version": "1.0.12", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memfs": { - "version": "3.5.1", - "dev": true, - "license": "Unlicense", - "dependencies": { - "fs-monkey": "^1.0.3" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/merge2": { - "version": "1.4.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "dev": true, - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "dev": true, - "license": "ISC" - }, - "node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/multicast-dns": { - "version": "7.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - }, - "bin": { - "multicast-dns": "cli.js" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/natural-compare-lite": { - "version": "1.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "dev": true, - "license": "MIT" - }, - "node_modules/no-case": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "dev": true, - "license": "(BSD-3-Clause OR GPL-2.0)", - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/node-int64": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.10", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.12.3", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/obuf": { - "version": "1.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "8.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-retry": { - "version": "4.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-retry/node_modules/retry": { - "version": "0.13.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/param-case": { - "version": "3.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascal-case": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "dev": true, - "license": "MIT" - }, - "node_modules/path-type": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.8.7", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-error": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "node_modules/pretty-format": { - "version": "29.5.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/prompts": { - "version": "2.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/punycode": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/pure-rand": { - "version": "6.0.1", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/dubzzz" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fast-check" - } - ], - "license": "MIT" - }, - "node_modules/qs": { - "version": "6.11.0", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/randombytes": { - "version": "2.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "dev": true, - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve": "^1.20.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/relateurl": { - "version": "0.2.7", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/renderkid": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/requires-port": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/resolve": { - "version": "1.22.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-typescript-plugin": { - "version": "2.0.1", - "dev": true, - "license": "ISC", - "dependencies": { - "tslib": "2.5.0" - }, - "engines": { - "node": "^14 || ^16 || >=18" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/schema-utils": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/select-hose": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/selfsigned": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/semver": { - "version": "7.3.8", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/serialize-javascript": { - "version": "6.0.1", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-index": { - "version": "1.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index/node_modules/debug": { - "version": "2.6.9", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index/node_modules/depd": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/http-errors": { - "version": "1.6.3", - "dev": true, - "license": "MIT", - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index/node_modules/inherits": { - "version": "2.0.3", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/ms": { - "version": "2.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/serve-index/node_modules/setprototypeof": { - "version": "1.1.0", - "dev": true, - "license": "ISC" - }, - "node_modules/serve-index/node_modules/statuses": { - "version": "1.5.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "dev": true, - "license": "ISC" - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.8.1", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "dev": true, - "license": "ISC" - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/sockjs": { - "version": "0.3.24", - "dev": true, - "license": "MIT", - "dependencies": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/spdy": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/spdy-transport": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/stack-utils": { - "version": "2.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-length": { - "version": "4.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/terser": { - "version": "5.17.3", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.8", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { - "version": "3.5.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/thunky": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/tmpl": { - "version": "1.0.5", - "dev": true, - "license": "BSD-3-Clause" - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/ts-jest": { - "version": "29.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-loader": { - "version": "9.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/semver": { - "version": "7.5.0", - "dev": true, - "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/ts-node": { - "version": "10.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/tslib": { - "version": "2.5.0", - "license": "0BSD" - }, - "node_modules/tsutils": { - "version": "3.21.0", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - } - }, - "node_modules/tsutils/node_modules/tslib": { - "version": "1.14.1", - "dev": true, - "license": "0BSD" - }, - "node_modules/type-check": { - "version": "0.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.20.2", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "dev": true, - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typescript": { - "version": "5.0.4", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.10", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/utila": { - "version": "0.4.0", - "dev": true, - "license": "MIT" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "dev": true, - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "dev": true, - "license": "ISC", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - }, - "engines": { - "node": ">=10.12.0" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/wbuf": { - "version": "1.7.3", - "dev": true, - "license": "MIT", - "dependencies": { - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/webpack": { - "version": "5.82.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-cli": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.0", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.4", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=14.15.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "5.x.x" - }, - "peerDependenciesMeta": { - "@webpack-cli/generators": { - "optional": true - }, - "webpack-bundle-analyzer": { - "optional": true - }, - "webpack-dev-server": { - "optional": true - } - } - }, - "node_modules/webpack-dev-middleware": { - "version": "5.3.3", - "dev": true, - "license": "MIT", - "dependencies": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/webpack-dev-server": { - "version": "4.15.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "bin": { - "webpack-dev-server": "bin/webpack-dev-server.js" - }, - "engines": { - "node": ">= 12.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^4.37.0 || ^5.0.0" - }, - "peerDependenciesMeta": { - "webpack": { - "optional": true - }, - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-dev-server/node_modules/ipaddr.js": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10" - } - }, - "node_modules/webpack-merge": { - "version": "5.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/ajv": { - "version": "6.12.6", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/webpack/node_modules/ajv-keywords": { - "version": "3.5.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/webpack/node_modules/schema-utils": { - "version": "3.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wildcard": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/write-file-atomic": { - "version": "4.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "8.13.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/yargs": { - "version": "17.7.1", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/yargs-parser": { - "version": "21.1.1", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.1", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.21.4", - "dev": true - }, - "@babel/core": { - "version": "7.21.4", - "dev": true, - "requires": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-compilation-targets": "^7.21.4", - "@babel/helper-module-transforms": "^7.21.2", - "@babel/helpers": "^7.21.0", - "@babel/parser": "^7.21.4", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.4", - "@babel/types": "^7.21.4", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.2", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/types": "^7.21.4", - "@jridgewell/gen-mapping": "^0.3.2", - "@jridgewell/trace-mapping": "^0.3.17", - "jsesc": "^2.5.1" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/compat-data": "^7.21.4", - "@babel/helper-validator-option": "^7.21.0", - "browserslist": "^4.21.3", - "lru-cache": "^5.1.1", - "semver": "^6.3.0" - }, - "dependencies": { - "lru-cache": { - "version": "5.1.1", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "semver": { - "version": "6.3.0", - "dev": true - }, - "yallist": { - "version": "3.1.1", - "dev": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "dev": true - }, - "@babel/helper-function-name": { - "version": "7.21.0", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/types": "^7.21.0" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.18.6", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-imports": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/types": "^7.21.4" - } - }, - "@babel/helper-module-transforms": { - "version": "7.21.2", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.20.2", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.2", - "@babel/types": "^7.21.2" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.20.2", - "dev": true - }, - "@babel/helper-simple-access": { - "version": "7.20.2", - "dev": true, - "requires": { - "@babel/types": "^7.20.2" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.18.6", - "dev": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.19.4", - "dev": true - }, - "@babel/helper-validator-identifier": { - "version": "7.19.1", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.21.0", - "dev": true - }, - "@babel/helpers": { - "version": "7.21.0", - "dev": true, - "requires": { - "@babel/template": "^7.20.7", - "@babel/traverse": "^7.21.0", - "@babel/types": "^7.21.0" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.21.4", - "dev": true - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.14.5", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.20.2" - } - }, - "@babel/template": { - "version": "7.20.7", - "dev": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7" - } - }, - "@babel/traverse": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/code-frame": "^7.21.4", - "@babel/generator": "^7.21.4", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.21.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.21.4", - "@babel/types": "^7.21.4", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "dev": true - } - } - }, - "@babel/types": { - "version": "7.21.4", - "dev": true, - "requires": { - "@babel/helper-string-parser": "^7.19.4", - "@babel/helper-validator-identifier": "^7.19.1", - "to-fast-properties": "^2.0.0" - } - }, - "@bcoe/v8-coverage": { - "version": "0.2.3", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "dependencies": { - "@jridgewell/resolve-uri": { - "version": "3.1.1", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - } - } - }, - "@discoveryjs/json-ext": { - "version": "0.5.7", - "dev": true - }, - "@eslint-community/eslint-utils": { - "version": "4.4.0", - "dev": true, - "requires": { - "eslint-visitor-keys": "^3.3.0" - } - }, - "@eslint-community/regexpp": { - "version": "4.5.0", - "dev": true - }, - "@eslint/eslintrc": { - "version": "2.0.2", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.5.1", - "globals": "^13.19.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - } - } - }, - "@eslint/js": { - "version": "8.38.0", - "dev": true - }, - "@humanwhocodes/config-array": { - "version": "0.11.8", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } - }, - "@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "dev": true, - "requires": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "dependencies": { - "js-yaml": { - "version": "3.14.1", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - } - } - }, - "@istanbuljs/schema": { - "version": "0.1.3", - "dev": true - }, - "@jest/console": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0" - } - }, - "@jest/core": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/console": "^29.5.0", - "@jest/reporters": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.5.0", - "jest-config": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-resolve-dependencies": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "jest-watcher": "^29.5.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "@jest/environment": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0" - } - }, - "@jest/expect": { - "version": "29.5.0", - "dev": true, - "requires": { - "expect": "^29.5.0", - "jest-snapshot": "^29.5.0" - } - }, - "@jest/expect-utils": { - "version": "29.5.0", - "dev": true, - "requires": { - "jest-get-type": "^29.4.3" - } - }, - "@jest/fake-timers": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "@sinonjs/fake-timers": "^10.0.2", - "@types/node": "*", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - } - }, - "@jest/globals": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/types": "^29.5.0", - "jest-mock": "^29.5.0" - } - }, - "@jest/reporters": { - "version": "29.5.0", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^5.1.0", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "v8-to-istanbul": "^9.0.1" - } - }, - "@jest/schemas": { - "version": "29.4.3", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.25.16" - } - }, - "@jest/source-map": { - "version": "29.4.3", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.15", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/console": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/test-result": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "29.5.0", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.5.0", - "@jridgewell/trace-mapping": "^0.3.15", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" - }, - "dependencies": { - "convert-source-map": { - "version": "2.0.0", - "dev": true - } - } - }, - "@jest/types": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.3", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "dev": true - }, - "@jridgewell/source-map": { - "version": "0.3.3", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.18", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" - }, - "dependencies": { - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "dev": true - } - } - }, - "@leichtgewicht/ip-codec": { - "version": "2.0.4", - "dev": true - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@sinclair/typebox": { - "version": "0.25.24", - "dev": true - }, - "@sinonjs/commons": { - "version": "2.0.0", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "10.0.2", - "dev": true, - "requires": { - "@sinonjs/commons": "^2.0.0" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "dev": true - }, - "@types/babel__core": { - "version": "7.20.0", - "dev": true, - "requires": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.4", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.1", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.3", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/body-parser": { - "version": "1.19.2", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/bonjour": { - "version": "3.5.10", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect": { - "version": "3.4.35", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/connect-history-api-fallback": { - "version": "1.5.0", - "dev": true, - "requires": { - "@types/express-serve-static-core": "*", - "@types/node": "*" - } - }, - "@types/debug": { - "version": "4.1.7", - "dev": true, - "requires": { - "@types/ms": "*" - } - }, - "@types/detect-node": { - "version": "2.0.0", - "dev": true - }, - "@types/eslint": { - "version": "8.37.0", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "1.0.1", - "dev": true - }, - "@types/express": { - "version": "4.17.17", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.33", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.34", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "@types/graceful-fs": { - "version": "4.1.6", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/html-minifier-terser": { - "version": "6.1.0", - "dev": true - }, - "@types/http-proxy": { - "version": "1.17.11", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "29.5.0", - "dev": true, - "requires": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.11", - "dev": true - }, - "@types/mime": { - "version": "1.3.2", - "dev": true - }, - "@types/ms": { - "version": "0.7.31", - "dev": true - }, - "@types/node": { - "version": "18.15.11", - "dev": true - }, - "@types/prettier": { - "version": "2.7.2", - "dev": true - }, - "@types/qs": { - "version": "6.9.7", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "dev": true - }, - "@types/retry": { - "version": "0.12.0", - "dev": true - }, - "@types/semver": { - "version": "7.3.13", - "dev": true - }, - "@types/send": { - "version": "0.17.1", - "dev": true, - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/serve-index": { - "version": "1.9.1", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/serve-static": { - "version": "1.15.1", - "dev": true, - "requires": { - "@types/mime": "*", - "@types/node": "*" - }, - "dependencies": { - "@types/mime": { - "version": "3.0.1", - "dev": true - } - } - }, - "@types/sockjs": { - "version": "0.3.33", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/source-map-support": { - "version": "0.5.6", - "dev": true, - "requires": { - "source-map": "^0.6.0" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "dev": true - }, - "@types/ws": { - "version": "8.5.4", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/yargs": { - "version": "17.0.24", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.57.1", - "dev": true, - "requires": { - "@eslint-community/regexpp": "^4.4.0", - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/type-utils": "5.57.1", - "@typescript-eslint/utils": "5.57.1", - "debug": "^4.3.4", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "natural-compare-lite": "^1.4.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.57.1", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/typescript-estree": "5.57.1", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.57.1", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/visitor-keys": "5.57.1" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.57.1", - "dev": true, - "requires": { - "@typescript-eslint/typescript-estree": "5.57.1", - "@typescript-eslint/utils": "5.57.1", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.57.1", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.57.1", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/visitor-keys": "5.57.1", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.57.1", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@types/json-schema": "^7.0.9", - "@types/semver": "^7.3.12", - "@typescript-eslint/scope-manager": "5.57.1", - "@typescript-eslint/types": "5.57.1", - "@typescript-eslint/typescript-estree": "5.57.1", - "eslint-scope": "^5.1.1", - "semver": "^7.3.7" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.57.1", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.57.1", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.6", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.6", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.6", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.6", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-opt": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6", - "@webassemblyjs/wast-printer": "1.11.6" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-buffer": "1.11.6", - "@webassemblyjs/wasm-gen": "1.11.6", - "@webassemblyjs/wasm-parser": "1.11.6" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.6", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "@webpack-cli/configtest": { - "version": "2.1.0", - "dev": true, - "requires": {} - }, - "@webpack-cli/info": { - "version": "2.0.1", - "dev": true, - "requires": {} - }, - "@webpack-cli/serve": { - "version": "2.0.4", - "dev": true, - "requires": {} - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "dev": true - }, - "accepts": { - "version": "1.3.8", - "dev": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.2", - "dev": true - }, - "acorn-import-assertions": { - "version": "1.9.0", - "dev": true, - "requires": {} - }, - "acorn-jsx": { - "version": "5.3.2", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "8.2.0", - "dev": true - }, - "ajv": { - "version": "8.12.0", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" - }, - "dependencies": { - "json-schema-traverse": { - "version": "1.0.0", - "dev": true - } - } - }, - "ajv-formats": { - "version": "2.1.1", - "dev": true, - "requires": { - "ajv": "^8.0.0" - } - }, - "ajv-keywords": { - "version": "5.1.0", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.3" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "dev": true - } - } - }, - "ansi-html-community": { - "version": "0.0.8", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.3", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-flatten": { - "version": "2.1.2", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "dev": true - }, - "babel-jest": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/transform": "^29.5.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.5.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - } - }, - "babel-plugin-istanbul": { - "version": "6.1.1", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "29.5.0", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-preset-current-node-syntax": { - "version": "1.0.1", - "dev": true, - "requires": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - } - }, - "babel-preset-jest": { - "version": "29.5.0", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^29.5.0", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "dev": true - }, - "base64-js": { - "version": "1.5.1", - "dev": true - }, - "batch": { - "version": "0.6.1", - "dev": true - }, - "bigint-buffer": { - "version": "1.1.5", - "requires": { - "bindings": "^1.3.0" - } - }, - "binary-extensions": { - "version": "2.2.0", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "body-parser": { - "version": "1.20.1", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "bonjour-service": { - "version": "1.1.1", - "dev": true, - "requires": { - "array-flatten": "^2.1.2", - "dns-equal": "^1.0.0", - "fast-deep-equal": "^3.1.3", - "multicast-dns": "^7.2.5" - } - }, - "boolbase": { - "version": "1.0.0", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.21.5", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001449", - "electron-to-chromium": "^1.4.284", - "node-releases": "^2.0.8", - "update-browserslist-db": "^1.0.10" - } - }, - "bs-logger": { - "version": "0.2.6", - "dev": true, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "6.0.3", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-from": { - "version": "1.1.2", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "dev": true - }, - "camel-case": { - "version": "4.1.2", - "dev": true, - "requires": { - "pascal-case": "^3.1.2", - "tslib": "^2.0.3" - } - }, - "camelcase": { - "version": "5.3.1", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001476", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "char-regex": { - "version": "1.0.2", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "dev": true - }, - "ci-info": { - "version": "3.8.0", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.2", - "dev": true - }, - "clean-css": { - "version": "5.3.2", - "dev": true, - "requires": { - "source-map": "~0.6.0" - } - }, - "cliui": { - "version": "8.0.1", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - } - }, - "clone-deep": { - "version": "4.0.1", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "dev": true - }, - "collect-v8-coverage": { - "version": "1.0.1", - "dev": true - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "colorette": { - "version": "2.0.20", - "dev": true - }, - "comlink": { - "version": "4.4.1", - "dev": true - }, - "commander": { - "version": "10.0.1" - }, - "compressible": { - "version": "2.0.18", - "dev": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "dev": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "dev": true - }, - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "dev": true - }, - "connect-history-api-fallback": { - "version": "2.0.0", - "dev": true - }, - "content-disposition": { - "version": "0.5.4", - "dev": true, - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.5", - "dev": true - }, - "convert-source-map": { - "version": "1.9.0", - "dev": true - }, - "cookie": { - "version": "0.5.0", - "dev": true - }, - "cookie-signature": { - "version": "1.0.6", - "dev": true - }, - "copy-webpack-plugin": { - "version": "11.0.0", - "dev": true, - "requires": { - "fast-glob": "^3.2.11", - "glob-parent": "^6.0.1", - "globby": "^13.1.1", - "normalize-path": "^3.0.0", - "schema-utils": "^4.0.0", - "serialize-javascript": "^6.0.0" - }, - "dependencies": { - "globby": { - "version": "13.1.4", - "dev": true, - "requires": { - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.11", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^4.0.0" - } - }, - "slash": { - "version": "4.0.0", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.3", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "css-select": { - "version": "4.3.0", - "dev": true, - "requires": { - "boolbase": "^1.0.0", - "css-what": "^6.0.1", - "domhandler": "^4.3.1", - "domutils": "^2.8.0", - "nth-check": "^2.0.1" - } - }, - "css-what": { - "version": "6.1.0", - "dev": true - }, - "debug": { - "version": "4.3.4", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "dev": true - } - } - }, - "dedent": { - "version": "0.7.0", - "dev": true - }, - "deep-is": { - "version": "0.1.4", - "dev": true - }, - "deepmerge": { - "version": "4.3.1", - "dev": true - }, - "default-gateway": { - "version": "6.0.3", - "dev": true, - "requires": { - "execa": "^5.0.0" - } - }, - "define-lazy-prop": { - "version": "2.0.0", - "dev": true - }, - "depd": { - "version": "2.0.0", - "dev": true - }, - "destroy": { - "version": "1.2.0", - "dev": true - }, - "detect-newline": { - "version": "3.1.0", - "dev": true - }, - "detect-node": { - "version": "2.1.0", - "dev": true - }, - "diff": { - "version": "4.0.2", - "dev": true - }, - "diff-sequences": { - "version": "29.4.3", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "dev": true, - "requires": { - "path-type": "^4.0.0" - } - }, - "dns-equal": { - "version": "1.0.0", - "dev": true - }, - "dns-packet": { - "version": "5.6.0", - "dev": true, - "requires": { - "@leichtgewicht/ip-codec": "^2.0.1" - } - }, - "doctrine": { - "version": "3.0.0", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-converter": { - "version": "0.2.0", - "dev": true, - "requires": { - "utila": "~0.4" - } - }, - "dom-serializer": { - "version": "1.4.1", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.2.0", - "entities": "^2.0.0" - } - }, - "domelementtype": { - "version": "2.3.0", - "dev": true - }, - "domhandler": { - "version": "4.3.1", - "dev": true, - "requires": { - "domelementtype": "^2.2.0" - } - }, - "domutils": { - "version": "2.8.0", - "dev": true, - "requires": { - "dom-serializer": "^1.0.1", - "domelementtype": "^2.2.0", - "domhandler": "^4.2.0" - } - }, - "dot-case": { - "version": "3.0.4", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "ee-first": { - "version": "1.1.1", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.356", - "dev": true - }, - "emittery": { - "version": "0.13.1", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "dev": true - }, - "encodeurl": { - "version": "1.0.2", - "dev": true - }, - "enhanced-resolve": { - "version": "5.14.0", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "entities": { - "version": "2.2.0", - "dev": true - }, - "envinfo": { - "version": "7.8.1", - "dev": true - }, - "error-ex": { - "version": "1.3.2", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-module-lexer": { - "version": "1.2.1", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "dev": true - }, - "eslint": { - "version": "8.38.0", - "dev": true, - "requires": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.2", - "@eslint/js": "8.38.0", - "@humanwhocodes/config-array": "^0.11.8", - "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.3.2", - "doctrine": "^3.0.0", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.4.0", - "espree": "^9.5.1", - "esquery": "^1.4.2", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", - "js-yaml": "^4.1.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "find-up": { - "version": "5.0.0", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "6.0.0", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "p-locate": { - "version": "5.0.0", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - } - } - }, - "eslint-config-prettier": { - "version": "8.8.0", - "dev": true, - "requires": {} - }, - "eslint-scope": { - "version": "5.1.1", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "dependencies": { - "estraverse": { - "version": "4.3.0", - "dev": true - } - } - }, - "eslint-visitor-keys": { - "version": "3.4.0", - "dev": true - }, - "espree": { - "version": "9.5.1", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.0" - } - }, - "esprima": { - "version": "4.0.1", - "dev": true - }, - "esquery": { - "version": "1.5.0", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - } - }, - "esrecurse": { - "version": "4.3.0", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "dev": true - }, - "etag": { - "version": "1.8.1", - "dev": true - }, - "eventemitter3": { - "version": "4.0.7", - "dev": true - }, - "events": { - "version": "3.3.0", - "dev": true - }, - "execa": { - "version": "5.1.1", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "dev": true - }, - "expect": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/expect-utils": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0" - } - }, - "express": { - "version": "4.18.2", - "dev": true, - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.1", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.5.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "array-flatten": { - "version": "1.1.1", - "dev": true - }, - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "dev": true - }, - "fast-glob": { - "version": "3.2.12", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "dependencies": { - "glob-parent": { - "version": "5.1.2", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - } - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "dev": true - }, - "fastest-levenshtein": { - "version": "1.0.16", - "dev": true - }, - "fastq": { - "version": "1.15.0", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "faye-websocket": { - "version": "0.11.4", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, - "fb-watchman": { - "version": "2.0.2", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "file-uri-to-path": { - "version": "1.0.0" - }, - "fill-range": { - "version": "7.0.1", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.2.0", - "dev": true, - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "dev": true - } - } - }, - "find-up": { - "version": "4.1.0", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "3.0.4", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - } - }, - "flatted": { - "version": "3.2.7", - "dev": true - }, - "follow-redirects": { - "version": "1.15.2", - "dev": true - }, - "forwarded": { - "version": "0.2.0", - "dev": true - }, - "fresh": { - "version": "0.5.2", - "dev": true - }, - "fs-monkey": { - "version": "1.0.3", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "dev": true - }, - "function-bind": { - "version": "1.1.1", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "dev": true - }, - "get-caller-file": { - "version": "2.0.5", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.0", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-package-type": { - "version": "0.1.0", - "dev": true - }, - "get-stream": { - "version": "6.0.1", - "dev": true - }, - "glob": { - "version": "7.2.3", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "6.0.2", - "dev": true, - "requires": { - "is-glob": "^4.0.3" - } - }, - "glob-to-regexp": { - "version": "0.4.1", - "dev": true - }, - "globals": { - "version": "13.20.0", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } - }, - "globby": { - "version": "11.1.0", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.11", - "dev": true - }, - "grapheme-splitter": { - "version": "1.0.4", - "dev": true - }, - "handle-thing": { - "version": "2.0.1", - "dev": true - }, - "has": { - "version": "1.0.3", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "dev": true - }, - "he": { - "version": "1.2.0", - "dev": true - }, - "hpack.js": { - "version": "2.1.6", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "obuf": "^1.0.0", - "readable-stream": "^2.0.1", - "wbuf": "^1.1.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.8", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "html-entities": { - "version": "2.3.3", - "dev": true - }, - "html-escaper": { - "version": "2.0.2", - "dev": true - }, - "html-minifier-terser": { - "version": "6.1.0", - "dev": true, - "requires": { - "camel-case": "^4.1.2", - "clean-css": "^5.2.2", - "commander": "^8.3.0", - "he": "^1.2.0", - "param-case": "^3.0.4", - "relateurl": "^0.2.7", - "terser": "^5.10.0" - }, - "dependencies": { - "commander": { - "version": "8.3.0", - "dev": true - } - } - }, - "html-webpack-plugin": { - "version": "5.5.1", - "dev": true, - "requires": { - "@types/html-minifier-terser": "^6.0.0", - "html-minifier-terser": "^6.0.2", - "lodash": "^4.17.21", - "pretty-error": "^4.0.0", - "tapable": "^2.0.0" - } - }, - "htmlparser2": { - "version": "6.1.0", - "dev": true, - "requires": { - "domelementtype": "^2.0.1", - "domhandler": "^4.0.0", - "domutils": "^2.5.2", - "entities": "^2.0.0" - } - }, - "http-deceiver": { - "version": "1.2.7", - "dev": true - }, - "http-errors": { - "version": "2.0.0", - "dev": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "http-parser-js": { - "version": "0.5.8", - "dev": true - }, - "http-proxy": { - "version": "1.18.1", - "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-middleware": { - "version": "2.0.6", - "dev": true, - "requires": { - "@types/http-proxy": "^1.17.8", - "http-proxy": "^1.18.1", - "is-glob": "^4.0.1", - "is-plain-obj": "^3.0.0", - "micromatch": "^4.0.2" - } - }, - "human-signals": { - "version": "2.1.0", - "dev": true - }, - "iconv-lite": { - "version": "0.4.24", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "idb-keyval": { - "version": "6.2.1", - "dev": true - }, - "ieee754": { - "version": "1.2.1", - "dev": true - }, - "ignore": { - "version": "5.2.4", - "dev": true - }, - "import-fresh": { - "version": "3.3.0", - "dev": true, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "dependencies": { - "resolve-from": { - "version": "4.0.0", - "dev": true - } - } - }, - "import-local": { - "version": "3.1.0", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "dev": true - }, - "interpret": { - "version": "3.1.1", - "dev": true - }, - "ipaddr.js": { - "version": "1.9.1", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-core-module": { - "version": "2.11.0", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-docker": { - "version": "2.2.1", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "dev": true - }, - "is-generator-fn": { - "version": "2.1.0", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "dev": true - }, - "is-path-inside": { - "version": "3.0.3", - "dev": true - }, - "is-plain-obj": { - "version": "3.0.0", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-stream": { - "version": "2.0.1", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "dev": true, - "requires": { - "is-docker": "^2.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.2.0", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "5.2.1", - "dev": true, - "requires": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "dev": true - } - } - }, - "istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.1", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.1.5", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/core": "^29.5.0", - "@jest/types": "^29.5.0", - "import-local": "^3.0.2", - "jest-cli": "^29.5.0" - } - }, - "jest-changed-files": { - "version": "29.5.0", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/environment": "^29.5.0", - "@jest/expect": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.5.0", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "p-limit": "^3.1.0", - "pretty-format": "^29.5.0", - "pure-rand": "^6.0.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-cli": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/core": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "jest-config": { - "version": "29.5.0", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.5.0", - "@jest/types": "^29.5.0", - "babel-jest": "^29.5.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.5.0", - "jest-environment-node": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-runner": "^29.5.0", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - } - }, - "jest-diff": { - "version": "29.5.0", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^29.4.3", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - } - }, - "jest-docblock": { - "version": "29.4.3", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "jest-util": "^29.5.0", - "pretty-format": "^29.5.0" - } - }, - "jest-environment-node": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-mock": "^29.5.0", - "jest-util": "^29.5.0" - } - }, - "jest-get-type": { - "version": "29.4.3", - "dev": true - }, - "jest-haste-map": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.4.3", - "jest-util": "^29.5.0", - "jest-worker": "^29.5.0", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "29.5.0", - "dev": true, - "requires": { - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - } - }, - "jest-matcher-utils": { - "version": "29.5.0", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "pretty-format": "^29.5.0" - } - }, - "jest-message-util": { - "version": "29.5.0", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.5.0", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.5.0", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-mock": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "jest-util": "^29.5.0" - } - }, - "jest-pnp-resolver": { - "version": "1.2.3", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "29.4.3", - "dev": true - }, - "jest-resolve": { - "version": "29.5.0", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.5.0", - "jest-validate": "^29.5.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "29.5.0", - "dev": true, - "requires": { - "jest-regex-util": "^29.4.3", - "jest-snapshot": "^29.5.0" - } - }, - "jest-runner": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/console": "^29.5.0", - "@jest/environment": "^29.5.0", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.4.3", - "jest-environment-node": "^29.5.0", - "jest-haste-map": "^29.5.0", - "jest-leak-detector": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-resolve": "^29.5.0", - "jest-runtime": "^29.5.0", - "jest-util": "^29.5.0", - "jest-watcher": "^29.5.0", - "jest-worker": "^29.5.0", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "source-map-support": { - "version": "0.5.13", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/environment": "^29.5.0", - "@jest/fake-timers": "^29.5.0", - "@jest/globals": "^29.5.0", - "@jest/source-map": "^29.4.3", - "@jest/test-result": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-mock": "^29.5.0", - "jest-regex-util": "^29.4.3", - "jest-resolve": "^29.5.0", - "jest-snapshot": "^29.5.0", - "jest-util": "^29.5.0", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - } - }, - "jest-snapshot": { - "version": "29.5.0", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.5.0", - "@jest/transform": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.5.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.5.0", - "jest-get-type": "^29.4.3", - "jest-matcher-utils": "^29.5.0", - "jest-message-util": "^29.5.0", - "jest-util": "^29.5.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.5.0", - "semver": "^7.3.5" - } - }, - "jest-util": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-validate": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/types": "^29.5.0", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.4.3", - "leven": "^3.1.0", - "pretty-format": "^29.5.0" - }, - "dependencies": { - "camelcase": { - "version": "6.3.0", - "dev": true - } - } - }, - "jest-watcher": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/test-result": "^29.5.0", - "@jest/types": "^29.5.0", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.13.1", - "jest-util": "^29.5.0", - "string-length": "^4.0.1" - } - }, - "jest-worker": { - "version": "29.5.0", - "dev": true, - "requires": { - "@types/node": "*", - "jest-util": "^29.5.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-sdsl": { - "version": "4.4.0", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "dev": true, - "requires": { - "argparse": "^2.0.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "dev": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "dev": true - }, - "json5": { - "version": "2.2.3", - "dev": true - }, - "kind-of": { - "version": "6.0.3", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "dev": true - }, - "launch-editor": { - "version": "2.6.0", - "dev": true, - "requires": { - "picocolors": "^1.0.0", - "shell-quote": "^1.7.3" - } - }, - "leven": { - "version": "3.1.0", - "dev": true - }, - "levn": { - "version": "0.4.1", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "dev": true - }, - "lodash.memoize": { - "version": "4.1.2", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "dev": true - }, - "lower-case": { - "version": "2.0.2", - "dev": true, - "requires": { - "tslib": "^2.0.3" - } - }, - "lru-cache": { - "version": "6.0.0", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "media-typer": { - "version": "0.3.0", - "dev": true - }, - "memfs": { - "version": "3.5.1", - "dev": true, - "requires": { - "fs-monkey": "^1.0.3" - } - }, - "merge-descriptors": { - "version": "1.0.1", - "dev": true - }, - "merge-stream": { - "version": "2.0.0", - "dev": true - }, - "merge2": { - "version": "1.4.1", - "dev": true - }, - "methods": { - "version": "1.1.2", - "dev": true - }, - "micromatch": { - "version": "4.0.5", - "dev": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "1.6.0", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "dev": true - }, - "minimalistic-assert": { - "version": "1.0.1", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "ms": { - "version": "2.1.3", - "dev": true - }, - "multicast-dns": { - "version": "7.2.5", - "dev": true, - "requires": { - "dns-packet": "^5.2.2", - "thunky": "^1.0.2" - } - }, - "natural-compare": { - "version": "1.4.0", - "dev": true - }, - "natural-compare-lite": { - "version": "1.4.0", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "dev": true - }, - "no-case": { - "version": "3.0.4", - "dev": true, - "requires": { - "lower-case": "^2.0.2", - "tslib": "^2.0.3" - } - }, - "node-forge": { - "version": "1.3.1", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "dev": true - }, - "node-releases": { - "version": "2.0.10", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "nth-check": { - "version": "2.1.1", - "dev": true, - "requires": { - "boolbase": "^1.0.0" - } - }, - "object-inspect": { - "version": "1.12.3", - "dev": true - }, - "obuf": { - "version": "1.1.2", - "dev": true - }, - "on-finished": { - "version": "2.4.1", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "dev": true - }, - "once": { - "version": "1.4.0", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "8.4.2", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "optionator": { - "version": "0.9.1", - "dev": true, - "requires": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - } - }, - "p-limit": { - "version": "3.1.0", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "4.1.0", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - }, - "dependencies": { - "p-limit": { - "version": "2.3.0", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - } - } - }, - "p-retry": { - "version": "4.6.2", - "dev": true, - "requires": { - "@types/retry": "0.12.0", - "retry": "^0.13.1" - }, - "dependencies": { - "retry": { - "version": "0.13.1", - "dev": true - } - } - }, - "p-try": { - "version": "2.2.0", - "dev": true - }, - "param-case": { - "version": "3.0.4", - "dev": true, - "requires": { - "dot-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "parent-module": { - "version": "1.0.1", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parseurl": { - "version": "1.3.3", - "dev": true - }, - "pascal-case": { - "version": "3.1.2", - "dev": true, - "requires": { - "no-case": "^3.0.4", - "tslib": "^2.0.3" - } - }, - "path-exists": { - "version": "4.0.0", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "dev": true - }, - "path-key": { - "version": "3.1.1", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "dev": true - }, - "path-type": { - "version": "4.0.0", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "dev": true - }, - "pirates": { - "version": "4.0.5", - "dev": true - }, - "pkg-dir": { - "version": "4.2.0", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.2.1", - "dev": true - }, - "prettier": { - "version": "2.8.7", - "dev": true - }, - "pretty-error": { - "version": "4.0.0", - "dev": true, - "requires": { - "lodash": "^4.17.20", - "renderkid": "^3.0.0" - } - }, - "pretty-format": { - "version": "29.5.0", - "dev": true, - "requires": { - "@jest/schemas": "^29.4.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "proxy-addr": { - "version": "2.0.7", - "dev": true, - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - } - }, - "punycode": { - "version": "2.3.0", - "dev": true - }, - "pure-rand": { - "version": "6.0.1", - "dev": true - }, - "qs": { - "version": "6.11.0", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "queue-microtask": { - "version": "1.2.3", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "range-parser": { - "version": "1.2.1", - "dev": true - }, - "raw-body": { - "version": "2.5.1", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react-is": { - "version": "18.2.0", - "dev": true - }, - "readable-stream": { - "version": "3.6.2", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "rechoir": { - "version": "0.8.0", - "dev": true, - "requires": { - "resolve": "^1.20.0" - } - }, - "relateurl": { - "version": "0.2.7", - "dev": true - }, - "renderkid": { - "version": "3.0.0", - "dev": true, - "requires": { - "css-select": "^4.1.3", - "dom-converter": "^0.2.0", - "htmlparser2": "^6.1.0", - "lodash": "^4.17.21", - "strip-ansi": "^6.0.1" - } - }, - "require-directory": { - "version": "2.1.1", - "dev": true - }, - "require-from-string": { - "version": "2.0.2", - "dev": true - }, - "requires-port": { - "version": "1.0.0", - "dev": true - }, - "resolve": { - "version": "1.22.2", - "dev": true, - "requires": { - "is-core-module": "^2.11.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - } - }, - "resolve-from": { - "version": "5.0.0", - "dev": true - }, - "resolve-typescript-plugin": { - "version": "2.0.1", - "dev": true, - "requires": { - "tslib": "2.5.0" - } - }, - "resolve.exports": { - "version": "2.0.2", - "dev": true - }, - "reusify": { - "version": "1.0.4", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-parallel": { - "version": "1.2.0", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "dev": true - }, - "schema-utils": { - "version": "4.0.1", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "ajv": "^8.9.0", - "ajv-formats": "^2.1.1", - "ajv-keywords": "^5.1.0" - } - }, - "select-hose": { - "version": "2.0.0", - "dev": true - }, - "selfsigned": { - "version": "2.1.1", - "dev": true, - "requires": { - "node-forge": "^1" - } - }, - "semver": { - "version": "7.3.8", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "dev": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "dev": true - } - } - } - } - }, - "serialize-javascript": { - "version": "6.0.1", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-index": { - "version": "1.9.1", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.17", - "parseurl": "~1.3.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "dev": true - }, - "ms": { - "version": "2.0.0", - "dev": true - }, - "setprototypeof": { - "version": "1.1.0", - "dev": true - }, - "statuses": { - "version": "1.5.0", - "dev": true - } - } - }, - "serve-static": { - "version": "1.15.0", - "dev": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "setprototypeof": { - "version": "1.2.0", - "dev": true - }, - "shallow-clone": { - "version": "3.0.1", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "dev": true - }, - "shell-quote": { - "version": "1.8.1", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "dev": true - }, - "slash": { - "version": "3.0.0", - "dev": true - }, - "sockjs": { - "version": "0.3.24", - "dev": true, - "requires": { - "faye-websocket": "^0.11.3", - "uuid": "^8.3.2", - "websocket-driver": "^0.7.4" - } - }, - "source-map": { - "version": "0.6.1", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "spdy": { - "version": "4.0.2", - "dev": true, - "requires": { - "debug": "^4.1.0", - "handle-thing": "^2.0.0", - "http-deceiver": "^1.2.7", - "select-hose": "^2.0.0", - "spdy-transport": "^3.0.0" - } - }, - "spdy-transport": { - "version": "3.0.0", - "dev": true, - "requires": { - "debug": "^4.1.0", - "detect-node": "^2.0.4", - "hpack.js": "^2.1.6", - "obuf": "^1.1.2", - "readable-stream": "^3.0.6", - "wbuf": "^1.7.3" - } - }, - "sprintf-js": { - "version": "1.0.3", - "dev": true - }, - "stack-utils": { - "version": "2.0.6", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "dev": true - } - } - }, - "statuses": { - "version": "2.0.1", - "dev": true - }, - "string_decoder": { - "version": "1.3.0", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "string-length": { - "version": "4.0.2", - "dev": true, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "string-width": { - "version": "4.2.3", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "strip-bom": { - "version": "4.0.0", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "dev": true - }, - "terser": { - "version": "5.17.3", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "dev": true - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.8", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.17", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.1", - "terser": "^5.16.8" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "dev": true, - "requires": {} - }, - "jest-worker": { - "version": "27.5.1", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "schema-utils": { - "version": "3.1.2", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "supports-color": { - "version": "8.1.1", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "test-exclude": { - "version": "6.0.0", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "dev": true - }, - "thunky": { - "version": "1.1.0", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "toidentifier": { - "version": "1.0.1", - "dev": true - }, - "ts-jest": { - "version": "29.1.0", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^29.0.0", - "json5": "^2.2.3", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - } - }, - "ts-loader": { - "version": "9.4.2", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "semver": { - "version": "7.5.0", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - } - } - }, - "ts-node": { - "version": "10.9.1", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - } - }, - "tslib": { - "version": "2.5.0" - }, - "tsutils": { - "version": "3.21.0", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "dev": true - } - } - }, - "type-check": { - "version": "0.4.0", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "type-detect": { - "version": "4.0.8", - "dev": true - }, - "type-fest": { - "version": "0.20.2", - "dev": true - }, - "type-is": { - "version": "1.6.18", - "dev": true, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typescript": { - "version": "5.0.4", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "dev": true - }, - "update-browserslist-db": { - "version": "1.0.10", - "dev": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "util-deprecate": { - "version": "1.0.2", - "dev": true - }, - "utila": { - "version": "0.4.0", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "dev": true - }, - "uuid": { - "version": "8.3.2", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true - }, - "v8-to-istanbul": { - "version": "9.1.0", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "vary": { - "version": "1.1.2", - "dev": true - }, - "walker": { - "version": "1.0.8", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.4.0", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wbuf": { - "version": "1.7.3", - "dev": true, - "requires": { - "minimalistic-assert": "^1.0.0" - } - }, - "webpack": { - "version": "5.82.1", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^1.0.0", - "@webassemblyjs/ast": "^1.11.5", - "@webassemblyjs/wasm-edit": "^1.11.5", - "@webassemblyjs/wasm-parser": "^1.11.5", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.14.0", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.7", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "ajv": { - "version": "6.12.6", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "dev": true, - "requires": {} - }, - "schema-utils": { - "version": "3.1.2", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - } - } - }, - "webpack-cli": { - "version": "5.1.1", - "dev": true, - "requires": { - "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^2.1.0", - "@webpack-cli/info": "^2.0.1", - "@webpack-cli/serve": "^2.0.4", - "colorette": "^2.0.14", - "commander": "^10.0.1", - "cross-spawn": "^7.0.3", - "envinfo": "^7.7.3", - "fastest-levenshtein": "^1.0.12", - "import-local": "^3.0.2", - "interpret": "^3.1.1", - "rechoir": "^0.8.0", - "webpack-merge": "^5.7.3" - } - }, - "webpack-dev-middleware": { - "version": "5.3.3", - "dev": true, - "requires": { - "colorette": "^2.0.10", - "memfs": "^3.4.3", - "mime-types": "^2.1.31", - "range-parser": "^1.2.1", - "schema-utils": "^4.0.0" - } - }, - "webpack-dev-server": { - "version": "4.15.0", - "dev": true, - "requires": { - "@types/bonjour": "^3.5.9", - "@types/connect-history-api-fallback": "^1.3.5", - "@types/express": "^4.17.13", - "@types/serve-index": "^1.9.1", - "@types/serve-static": "^1.13.10", - "@types/sockjs": "^0.3.33", - "@types/ws": "^8.5.1", - "ansi-html-community": "^0.0.8", - "bonjour-service": "^1.0.11", - "chokidar": "^3.5.3", - "colorette": "^2.0.10", - "compression": "^1.7.4", - "connect-history-api-fallback": "^2.0.0", - "default-gateway": "^6.0.3", - "express": "^4.17.3", - "graceful-fs": "^4.2.6", - "html-entities": "^2.3.2", - "http-proxy-middleware": "^2.0.3", - "ipaddr.js": "^2.0.1", - "launch-editor": "^2.6.0", - "open": "^8.0.9", - "p-retry": "^4.5.0", - "rimraf": "^3.0.2", - "schema-utils": "^4.0.0", - "selfsigned": "^2.1.1", - "serve-index": "^1.9.1", - "sockjs": "^0.3.24", - "spdy": "^4.0.2", - "webpack-dev-middleware": "^5.3.1", - "ws": "^8.13.0" - }, - "dependencies": { - "ipaddr.js": { - "version": "2.0.1", - "dev": true - } - } - }, - "webpack-merge": { - "version": "5.8.0", - "dev": true, - "requires": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - } - }, - "webpack-sources": { - "version": "3.2.3", - "dev": true - }, - "websocket-driver": { - "version": "0.7.4", - "dev": true, - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "dev": true - }, - "which": { - "version": "2.0.2", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wildcard": { - "version": "2.0.1", - "dev": true - }, - "word-wrap": { - "version": "1.2.3", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "dev": true - }, - "write-file-atomic": { - "version": "4.0.2", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "8.13.0", - "dev": true, - "requires": {} - }, - "y18n": { - "version": "5.0.8", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "dev": true - }, - "yargs": { - "version": "17.7.1", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "yargs-parser": { - "version": "21.1.1", - "dev": true - }, - "yn": { - "version": "3.1.1", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "dev": true - } - } -} diff --git a/ts/package.json b/ts/package.json index 91c469b3c9..6bcbcd5dff 100644 --- a/ts/package.json +++ b/ts/package.json @@ -1,16 +1,18 @@ { - "name": "@aztec/barretenberg.js", - "version": "0.0.0", + "name": "@aztec/bb.js", + "version": "0.0.1-alpha.1", "type": "module", "typedoc": { "entryPoint": "./src/index.ts", - "displayName": "Barretenberg.js", + "displayName": "bb.js", "tsconfig": "./tsconfig.json" }, - "bin": { - "bb.js": "./dest/main.js", - "bb.js-dev": "./src/main-dev.ts" - }, + "bin": "./dest/main.js", + "files": [ + "src/", + "dest/", + "README.md" + ], "scripts": { "clean": "rm -rf ./dest .tsbuildinfo", "build": "yarn clean && yarn build:wasm && tsc -b && webpack && chmod +x ./dest/main.js", @@ -40,8 +42,9 @@ "rootDir": "./src" }, "dependencies": { - "bigint-buffer": "^1.1.5", + "comlink": "^4.4.1", "commander": "^10.0.1", + "debug": "^4.3.4", "ts-node": "^10.9.1", "tslib": "^2.4.0" }, @@ -55,9 +58,7 @@ "@typescript-eslint/eslint-plugin": "^5.54.1", "@typescript-eslint/parser": "^5.54.1", "buffer": "^6.0.3", - "comlink": "^4.4.1", "copy-webpack-plugin": "^11.0.0", - "debug": "^4.3.4", "eslint": "^8.35.0", "eslint-config-prettier": "^8.8.0", "html-webpack-plugin": "^5.5.1", diff --git a/ts/src/index.ts b/ts/src/index.ts index b6df75a32a..686cf4af06 100644 --- a/ts/src/index.ts +++ b/ts/src/index.ts @@ -1,3 +1,5 @@ export * from './crs/index.js'; export * from './barretenberg_wasm/index.js'; export * from './barretenberg_api/index.js'; +export * from './factory/index.js'; +export { RawBuffer } from './types/index.js'; diff --git a/ts/src/main-dev.ts b/ts/src/main-dev.ts deleted file mode 100755 index 8c090cb92d..0000000000 --- a/ts/src/main-dev.ts +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env -S node --loader ts-node/esm --no-warnings -import './main.js'; diff --git a/ts/src/main.ts b/ts/src/main.ts index 587117832d..dc8c5c50cd 100755 --- a/ts/src/main.ts +++ b/ts/src/main.ts @@ -1,16 +1,13 @@ #!/usr/bin/env -S node --no-warnings -import { Crs } from './crs/index.js'; +import { Crs, BarretenbergApiAsync, newBarretenbergApiAsync, RawBuffer } from './index.js'; import createDebug from 'debug'; -import { BarretenbergApiAsync, newBarretenbergApiAsync } from './factory/index.js'; import { readFileSync, writeFileSync } from 'fs'; import { gunzipSync } from 'zlib'; -import { RawBuffer } from './types/index.js'; import { numToUInt32BE } from './serialize/serialize.js'; import { Command } from 'commander'; createDebug.log = console.error.bind(console); const debug = createDebug('bb.js'); -// createDebug.enable('*'); // Maximum we support. const MAX_CIRCUIT_SIZE = 2 ** 19; @@ -247,6 +244,14 @@ export async function vkAsFields(vkPath: string, vkeyOutputPath: string) { const program = new Command(); +program.option('-v, --verbose', 'enable verbose logging', false); + +function handleGlobalOptions() { + if (program.opts().verbose) { + createDebug.enable('bb.js*'); + } +} + program .command('prove_and_verify') .description('Generate a proof and verify it. Process exits with success or failure code.') @@ -254,6 +259,7 @@ program .option('-w, --witness-path ', 'Specify the witness path', './target/witness.tr') .option('-r, --recursive', 'prove and verify using recursive prover and verifier', false) .action(async ({ jsonPath, witnessPath, recursive }) => { + handleGlobalOptions(); const result = await proveAndVerify(jsonPath, witnessPath, recursive); process.exit(result ? 0 : 1); }); @@ -267,6 +273,7 @@ program .option('-o, --output-dir ', 'Specify the proof output dir', './proofs') .requiredOption('-n, --name ', 'Output file name.') .action(async ({ jsonPath, witnessPath, recursive, outputDir, name }) => { + handleGlobalOptions(); await prove(jsonPath, witnessPath, recursive, outputDir + '/' + name); }); @@ -284,7 +291,7 @@ program .option('-j, --json-path ', 'Specify the JSON path', './target/main.json') .requiredOption('-p, --proof-path ', 'Specify the path to the proof') .option('-r, --recursive', 'prove using recursive prover', false) - .option('-v, --vk ', 'path to a verification key. avoids recomputation.') + .option('-k, --vk ', 'path to a verification key. avoids recomputation.') .action(async ({ jsonPath, proofPath, recursive, vk }) => { await verify(jsonPath, proofPath, recursive, vk); }); diff --git a/ts/yarn.lock b/ts/yarn.lock index 56f0530fc7..0b068d22d7 100644 --- a/ts/yarn.lock +++ b/ts/yarn.lock @@ -15,9 +15,9 @@ __metadata: languageName: node linkType: hard -"@aztec/barretenberg.js@workspace:.": +"@aztec/bb.js@workspace:.": version: 0.0.0-use.local - resolution: "@aztec/barretenberg.js@workspace:." + resolution: "@aztec/bb.js@workspace:." dependencies: "@jest/globals": ^29.4.3 "@types/debug": ^4.1.7 @@ -27,7 +27,6 @@ __metadata: "@types/source-map-support": ^0.5.6 "@typescript-eslint/eslint-plugin": ^5.54.1 "@typescript-eslint/parser": ^5.54.1 - bigint-buffer: ^1.1.5 buffer: ^6.0.3 comlink: ^4.4.1 commander: ^10.0.1 @@ -50,7 +49,6 @@ __metadata: webpack-dev-server: ^4.15.0 bin: bb.js: ./dest/main.js - bb.js-dev: ./src/main-dev.ts languageName: unknown linkType: soft @@ -63,67 +61,67 @@ __metadata: languageName: node linkType: hard -"@babel/compat-data@npm:^7.21.5": - version: 7.21.9 - resolution: "@babel/compat-data@npm:7.21.9" - checksum: df97be04955c0801f5a23846f79a100660aa98f9433cfd1fad8f53ecd9f3454538e78522e86275939aa8aa7d6f9e32f23f94bc04ae843f7246b7cd4bffe3a175 +"@babel/compat-data@npm:^7.22.0": + version: 7.22.3 + resolution: "@babel/compat-data@npm:7.22.3" + checksum: eb001646f41459f42ccb0d39ee8bb3c3c495bc297234817044c0002689c625e3159a6678c53fd31bd98cf21f31472b73506f350fc6906e3bdfa49cb706e2af8d languageName: node linkType: hard "@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": - version: 7.21.8 - resolution: "@babel/core@npm:7.21.8" + version: 7.22.1 + resolution: "@babel/core@npm:7.22.1" dependencies: "@ampproject/remapping": ^2.2.0 "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-compilation-targets": ^7.21.5 - "@babel/helper-module-transforms": ^7.21.5 - "@babel/helpers": ^7.21.5 - "@babel/parser": ^7.21.8 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 + "@babel/generator": ^7.22.0 + "@babel/helper-compilation-targets": ^7.22.1 + "@babel/helper-module-transforms": ^7.22.1 + "@babel/helpers": ^7.22.0 + "@babel/parser": ^7.22.0 + "@babel/template": ^7.21.9 + "@babel/traverse": ^7.22.1 + "@babel/types": ^7.22.0 convert-source-map: ^1.7.0 debug: ^4.1.0 gensync: ^1.0.0-beta.2 json5: ^2.2.2 semver: ^6.3.0 - checksum: f28118447355af2a90bd340e2e60699f94c8020517eba9b71bf8ebff62fa9e00d63f076e033f9dfb97548053ad62ada45fafb0d96584b1a90e8aef5a3b8241b1 + checksum: bbe45e791f223a7e692d2ea6597a73f48050abd24b119c85c48ac6504c30ce63343a2ea3f79b5847bf4b409ddd8a68b6cdc4f0272ded1d2ef6f6b1e9663432f0 languageName: node linkType: hard -"@babel/generator@npm:^7.21.5, @babel/generator@npm:^7.7.2": - version: 7.21.9 - resolution: "@babel/generator@npm:7.21.9" +"@babel/generator@npm:^7.22.0, @babel/generator@npm:^7.22.3, @babel/generator@npm:^7.7.2": + version: 7.22.3 + resolution: "@babel/generator@npm:7.22.3" dependencies: - "@babel/types": ^7.21.5 + "@babel/types": ^7.22.3 "@jridgewell/gen-mapping": ^0.3.2 "@jridgewell/trace-mapping": ^0.3.17 jsesc: ^2.5.1 - checksum: 5bd10334ebdf7f2a30eb4a1fd99d369a57703aa2234527784449187512c254a1174fa739c9d4c31bcbb6018732012a0664bec7c314f12b5ec2458737ddbb01c7 + checksum: ccb6426ca5b5a38f0d47a3ac9628e223d2aaaa489cbf90ffab41468795c22afe86855f68a58667f0f2673949f1810d4d5a57b826c17984eab3e28fdb34a909e6 languageName: node linkType: hard -"@babel/helper-compilation-targets@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-compilation-targets@npm:7.21.5" +"@babel/helper-compilation-targets@npm:^7.22.1": + version: 7.22.1 + resolution: "@babel/helper-compilation-targets@npm:7.22.1" dependencies: - "@babel/compat-data": ^7.21.5 + "@babel/compat-data": ^7.22.0 "@babel/helper-validator-option": ^7.21.0 browserslist: ^4.21.3 lru-cache: ^5.1.1 semver: ^6.3.0 peerDependencies: "@babel/core": ^7.0.0 - checksum: 0edecb9c970ddc22ebda1163e77a7f314121bef9e483e0e0d9a5802540eed90d5855b6bf9bce03419b35b2e07c323e62d0353b153fa1ca34f17dbba897a83c25 + checksum: a686a01bd3288cf95ca26faa27958d34c04e2501c4b0858c3a6558776dec20317b5635f33d64c5a635b6fbdfe462a85c30d4bfa0ae7e7ffe3467e4d06442d7c8 languageName: node linkType: hard -"@babel/helper-environment-visitor@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-environment-visitor@npm:7.21.5" - checksum: e436af7b62956e919066448013a3f7e2cd0b51010c26c50f790124dcd350be81d5597b4e6ed0a4a42d098a27de1e38561cd7998a116a42e7899161192deac9a6 +"@babel/helper-environment-visitor@npm:^7.22.1": + version: 7.22.1 + resolution: "@babel/helper-environment-visitor@npm:7.22.1" + checksum: a6b4bb5505453bff95518d361ac1de393f0029aeb8b690c70540f4317934c53c43cc4afcda8c752ffa8c272e63ed6b929a56eca28e4978424177b24238b21bf9 languageName: node linkType: hard @@ -155,19 +153,19 @@ __metadata: languageName: node linkType: hard -"@babel/helper-module-transforms@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helper-module-transforms@npm:7.21.5" +"@babel/helper-module-transforms@npm:^7.22.1": + version: 7.22.1 + resolution: "@babel/helper-module-transforms@npm:7.22.1" dependencies: - "@babel/helper-environment-visitor": ^7.21.5 + "@babel/helper-environment-visitor": ^7.22.1 "@babel/helper-module-imports": ^7.21.4 "@babel/helper-simple-access": ^7.21.5 "@babel/helper-split-export-declaration": ^7.18.6 "@babel/helper-validator-identifier": ^7.19.1 - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: 1ccfc88830675a5d485d198e918498f9683cdd46f973fdd4fe1c85b99648fb70f87fca07756c7a05dc201bd9b248c74ced06ea80c9991926ac889f53c3659675 + "@babel/template": ^7.21.9 + "@babel/traverse": ^7.22.1 + "@babel/types": ^7.22.0 + checksum: dfa084211a93c9f0174ab07385fdbf7831bbf5c1ff3d4f984effc489f48670825ad8b817b9e9d2ec6492fde37ed6518c15944e9dd7a60b43a3d9874c9250f5f8 languageName: node linkType: hard @@ -217,14 +215,14 @@ __metadata: languageName: node linkType: hard -"@babel/helpers@npm:^7.21.5": - version: 7.21.5 - resolution: "@babel/helpers@npm:7.21.5" +"@babel/helpers@npm:^7.22.0": + version: 7.22.3 + resolution: "@babel/helpers@npm:7.22.3" dependencies: - "@babel/template": ^7.20.7 - "@babel/traverse": ^7.21.5 - "@babel/types": ^7.21.5 - checksum: a6f74b8579713988e7f5adf1a986d8b5255757632ba65b2552f0f609ead5476edb784044c7e4b18f3681ee4818ca9d08c41feb9bd4e828648c25a00deaa1f9e4 + "@babel/template": ^7.21.9 + "@babel/traverse": ^7.22.1 + "@babel/types": ^7.22.3 + checksum: 385289ee8b87cf9af448bbb9fcf747f6e67600db5f7f64eb4ad97761ee387819bf2212b6a757008286c6bfacf4f3fc0b6de88686f2e517a70fb59996bdfbd1e9 languageName: node linkType: hard @@ -239,12 +237,12 @@ __metadata: languageName: node linkType: hard -"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.5, @babel/parser@npm:^7.21.8, @babel/parser@npm:^7.21.9": - version: 7.21.9 - resolution: "@babel/parser@npm:7.21.9" +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.9, @babel/parser@npm:^7.22.0, @babel/parser@npm:^7.22.4": + version: 7.22.4 + resolution: "@babel/parser@npm:7.22.4" bin: parser: ./bin/babel-parser.js - checksum: 985ccc311eb286a320331fd21ff54d94935df76e081abdb304cd4591ea2051a6c799c6b0d8e26d09a9dd041797d9a91ebadeb0c50699d0101bd39fc565082d5c + checksum: 0ca6d3a2d9aae2504ba1bc494704b64a83140884f7379f609de69bd39b60adb58a4f8ec692fe53fef8657dd82705d01b7e6efb65e18296326bdd66f71d52d9a9 languageName: node linkType: hard @@ -402,7 +400,7 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": +"@babel/template@npm:^7.20.7, @babel/template@npm:^7.21.9, @babel/template@npm:^7.3.3": version: 7.21.9 resolution: "@babel/template@npm:7.21.9" dependencies: @@ -413,32 +411,32 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.21.5, @babel/traverse@npm:^7.7.2": - version: 7.21.5 - resolution: "@babel/traverse@npm:7.21.5" +"@babel/traverse@npm:^7.22.1, @babel/traverse@npm:^7.7.2": + version: 7.22.4 + resolution: "@babel/traverse@npm:7.22.4" dependencies: "@babel/code-frame": ^7.21.4 - "@babel/generator": ^7.21.5 - "@babel/helper-environment-visitor": ^7.21.5 + "@babel/generator": ^7.22.3 + "@babel/helper-environment-visitor": ^7.22.1 "@babel/helper-function-name": ^7.21.0 "@babel/helper-hoist-variables": ^7.18.6 "@babel/helper-split-export-declaration": ^7.18.6 - "@babel/parser": ^7.21.5 - "@babel/types": ^7.21.5 + "@babel/parser": ^7.22.4 + "@babel/types": ^7.22.4 debug: ^4.1.0 globals: ^11.1.0 - checksum: b403733fa7d858f0c8e224f0434a6ade641bc469a4f92975363391e796629d5bf53e544761dfe85039aab92d5389ebe7721edb309d7a5bb7df2bf74f37bf9f47 + checksum: 9560ae22092d5a7c52849145dd3e5aed2ffb73d61255e70e19e3fbd06bcbafbbdecea28df40a42ee3b60b01e85a42224ec841df93e867547e329091cc2f2bb6f languageName: node linkType: hard -"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.4, @babel/types@npm:^7.21.5, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": - version: 7.21.5 - resolution: "@babel/types@npm:7.21.5" +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.18.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.4, @babel/types@npm:^7.21.5, @babel/types@npm:^7.22.0, @babel/types@npm:^7.22.3, @babel/types@npm:^7.22.4, @babel/types@npm:^7.3.3, @babel/types@npm:^7.8.3": + version: 7.22.4 + resolution: "@babel/types@npm:7.22.4" dependencies: "@babel/helper-string-parser": ^7.21.5 "@babel/helper-validator-identifier": ^7.19.1 to-fast-properties: ^2.0.0 - checksum: 43242a99c612d13285ee4af46cc0f1066bcb6ffd38307daef7a76e8c70f36cfc3255eb9e75c8e768b40e761176c313aec4d5c0b9d97a21e494d49d5fd123a9f7 + checksum: ffe36bb4f4a99ad13c426a98c3b508d70736036cae4e471d9c862e3a579847ed4f480686af0fce2633f6f7c0f0d3bf02da73da36e7edd3fde0b2061951dcba9a languageName: node linkType: hard @@ -980,15 +978,15 @@ __metadata: linkType: hard "@types/babel__core@npm:^7.1.14": - version: 7.20.0 - resolution: "@types/babel__core@npm:7.20.0" + version: 7.20.1 + resolution: "@types/babel__core@npm:7.20.1" dependencies: "@babel/parser": ^7.20.7 "@babel/types": ^7.20.7 "@types/babel__generator": "*" "@types/babel__template": "*" "@types/babel__traverse": "*" - checksum: 49b601a0a7637f1f387442c8156bd086cfd10ff4b82b0e1994e73a6396643b5435366fb33d6b604eade8467cca594ef97adcbc412aede90bb112ebe88d0ad6df + checksum: 9fcd9691a33074802d9057ff70b0e3ff3778f52470475b68698a0f6714fbe2ccb36c16b43dc924eb978cd8a81c1f845e5ff4699e7a47606043b539eb8c6331a8 languageName: node linkType: hard @@ -1012,11 +1010,11 @@ __metadata: linkType: hard "@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": - version: 7.18.5 - resolution: "@types/babel__traverse@npm:7.18.5" + version: 7.20.0 + resolution: "@types/babel__traverse@npm:7.20.0" dependencies: - "@babel/types": ^7.3.0 - checksum: b9e7f39eb84626cc8f83ebf75a621d47f04b53cb085a3ea738a9633d57cf65208e503b1830db91aa5e297bc2ba761681ac0b0cbfb7a3d56afcfb2296212668ef + "@babel/types": ^7.20.7 + checksum: 030d647a61baa70aff5bc1193227694098191578e45e18720db3a14614f1827664d609630a668ad75cddffd7b80cd14a55455364239d1f14ea55f1f4d7d2c9ef languageName: node linkType: hard @@ -1176,19 +1174,19 @@ __metadata: linkType: hard "@types/jest@npm:^29.4.0": - version: 29.5.1 - resolution: "@types/jest@npm:29.5.1" + version: 29.5.2 + resolution: "@types/jest@npm:29.5.2" dependencies: expect: ^29.0.0 pretty-format: ^29.0.0 - checksum: 0a22491dec86333c0e92b897be2c809c922a7b2b0aa5604ac369810d6b2360908b4a3f2c6892e8a237a54fa1f10ecefe0e823ec5fcb7915195af4dfe88d2197e + checksum: 7d205599ea3cccc262bad5cc173d3242d6bf8138c99458509230e4ecef07a52d6ddcde5a1dbd49ace655c0af51d2dbadef3748697292ea4d86da19d9e03e19c0 languageName: node linkType: hard "@types/json-schema@npm:*, @types/json-schema@npm:^7.0.8, @types/json-schema@npm:^7.0.9": - version: 7.0.11 - resolution: "@types/json-schema@npm:7.0.11" - checksum: 527bddfe62db9012fccd7627794bd4c71beb77601861055d87e3ee464f2217c85fca7a4b56ae677478367bbd248dbde13553312b7d4dbc702a2f2bbf60c4018d + version: 7.0.12 + resolution: "@types/json-schema@npm:7.0.12" + checksum: 00239e97234eeb5ceefb0c1875d98ade6e922bfec39dd365ec6bd360b5c2f825e612ac4f6e5f1d13601b8b30f378f15e6faa805a3a732f4a1bbe61915163d293 languageName: node linkType: hard @@ -1214,23 +1212,23 @@ __metadata: linkType: hard "@types/node@npm:*": - version: 20.2.3 - resolution: "@types/node@npm:20.2.3" - checksum: 576065e8fc1fa45798c8f59a6bf809169582d04abc2e25fab1a048ffc734975b9992ae31be0d960cf705a21fb37112f7fcde11aa322beddf7491e73d5a5a988c + version: 20.2.5 + resolution: "@types/node@npm:20.2.5" + checksum: 38ce7c7e9d76880dc632f71d71e0d5914fcda9d5e9a7095d6c339abda55ca4affb0f2a882aeb29398f8e09d2c5151f0b6586c81c8ccdfe529c34b1ea3337425e languageName: node linkType: hard "@types/node@npm:^18.7.23": - version: 18.16.14 - resolution: "@types/node@npm:18.16.14" - checksum: c11cb3c787236414efe58240ae71854971592554d82ff9d201876ce7cafd51c37aaa001c63602d002e8238614d7331bd6d48ac4c1c0caa826799980b6846fb08 + version: 18.16.16 + resolution: "@types/node@npm:18.16.16" + checksum: 0efad726dd1e0bef71c392c708fc5d78c5b39c46b0ac5186fee74de4ccb1b2e847b3fa468da67d62812f56569da721b15bf31bdc795e6c69b56c73a45079ed2d languageName: node linkType: hard "@types/prettier@npm:^2.1.5": - version: 2.7.2 - resolution: "@types/prettier@npm:2.7.2" - checksum: b47d76a5252265f8d25dd2fe2a5a61dc43ba0e6a96ffdd00c594cb4fd74c1982c2e346497e3472805d97915407a09423804cc2110a0b8e1b22cffcab246479b7 + version: 2.7.3 + resolution: "@types/prettier@npm:2.7.3" + checksum: 705384209cea6d1433ff6c187c80dcc0b95d99d5c5ce21a46a9a58060c527973506822e428789d842761e0280d25e3359300f017fbe77b9755bc772ab3dc2f83 languageName: node linkType: hard @@ -1342,13 +1340,13 @@ __metadata: linkType: hard "@typescript-eslint/eslint-plugin@npm:^5.54.1": - version: 5.59.7 - resolution: "@typescript-eslint/eslint-plugin@npm:5.59.7" + version: 5.59.8 + resolution: "@typescript-eslint/eslint-plugin@npm:5.59.8" dependencies: "@eslint-community/regexpp": ^4.4.0 - "@typescript-eslint/scope-manager": 5.59.7 - "@typescript-eslint/type-utils": 5.59.7 - "@typescript-eslint/utils": 5.59.7 + "@typescript-eslint/scope-manager": 5.59.8 + "@typescript-eslint/type-utils": 5.59.8 + "@typescript-eslint/utils": 5.59.8 debug: ^4.3.4 grapheme-splitter: ^1.0.4 ignore: ^5.2.0 @@ -1361,43 +1359,43 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 10d28bac7a5af9e41767be0bb9c270ee3dcdfeaa38d1b036c6822e7260b88821c460699ba943664eb1ef272d00de6a81b99d7d955332044ea87b624e7ead84a1 + checksum: 3e05cd06149ec3741c3c2fb638e2d19a55687b4614a5c8820433db82997687650297e51c17828d320162ccf4241798cf5712c405561e7605cb17e984a6967f7b languageName: node linkType: hard "@typescript-eslint/parser@npm:^5.54.1": - version: 5.59.7 - resolution: "@typescript-eslint/parser@npm:5.59.7" + version: 5.59.8 + resolution: "@typescript-eslint/parser@npm:5.59.8" dependencies: - "@typescript-eslint/scope-manager": 5.59.7 - "@typescript-eslint/types": 5.59.7 - "@typescript-eslint/typescript-estree": 5.59.7 + "@typescript-eslint/scope-manager": 5.59.8 + "@typescript-eslint/types": 5.59.8 + "@typescript-eslint/typescript-estree": 5.59.8 debug: ^4.3.4 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: typescript: optional: true - checksum: bc44f37a11a44f84ae5f0156213f3e2e49aef2ecac94d9e161a0c721acd29462e288f306ad4648095ac1c0e5a5f62b78280c1735883cf39f79ee3afcba312119 + checksum: bac9f09d8552086ceb882a7b87ce4d98dfaa41579249216c75d97e3fc07af33cddc4cbbd07a127a5823c826a258882643aaf658bec19cb2a434002b55c5f0d12 languageName: node linkType: hard -"@typescript-eslint/scope-manager@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/scope-manager@npm:5.59.7" +"@typescript-eslint/scope-manager@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/scope-manager@npm:5.59.8" dependencies: - "@typescript-eslint/types": 5.59.7 - "@typescript-eslint/visitor-keys": 5.59.7 - checksum: 43f7ea93fddbe2902122a41050677fe3eff2ea468f435b981592510cfc6136e8c28ac7d3a3e05fb332c0b3078a29bd0c91c35b2b1f4e788b4eb9aaeb70e21583 + "@typescript-eslint/types": 5.59.8 + "@typescript-eslint/visitor-keys": 5.59.8 + checksum: e1e810ee991cfeb433330b04ee949bb6784abe4dbdb7d9480aa7a7536671b4fec914b7803edf662516c8ecb1b31dcff126797f9923270a529c26e2b00b0ea96f languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/type-utils@npm:5.59.7" +"@typescript-eslint/type-utils@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/type-utils@npm:5.59.8" dependencies: - "@typescript-eslint/typescript-estree": 5.59.7 - "@typescript-eslint/utils": 5.59.7 + "@typescript-eslint/typescript-estree": 5.59.8 + "@typescript-eslint/utils": 5.59.8 debug: ^4.3.4 tsutils: ^3.21.0 peerDependencies: @@ -1405,23 +1403,23 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 9cbeffad27b145b478e4cbbab2b44c5b246a9b922f01fd06d401ea4c41a4fa6dc8ba75d13a6409b3b4474ccaf2018770a4c6c599172e22ec2004110e00f4e721 + checksum: d9fde31397da0f0e62a5568f64bad99d06bcd324b7e3aac7fd997a3d045a0fe4c084b2e85d440e0a39645acd2269ad6593f196399c2c0f880d293417fec894e3 languageName: node linkType: hard -"@typescript-eslint/types@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/types@npm:5.59.7" - checksum: 52eccec9e2d631eb2808e48b5dc33a837b5e242fa9eddace89fc707c9f2283b5364f1d38b33d418a08d64f45f6c22f051800898e1881a912f8aac0c3ae300d0a +"@typescript-eslint/types@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/types@npm:5.59.8" + checksum: 559473d5601c849eb0da1874a2ac67c753480beed484ad6f6cda62fa6023273f2c3005c7f2864d9c2afb7c6356412d0d304b57db10c53597207f18a7f6cd4f18 languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/typescript-estree@npm:5.59.7" +"@typescript-eslint/typescript-estree@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.8" dependencies: - "@typescript-eslint/types": 5.59.7 - "@typescript-eslint/visitor-keys": 5.59.7 + "@typescript-eslint/types": 5.59.8 + "@typescript-eslint/visitor-keys": 5.59.8 debug: ^4.3.4 globby: ^11.1.0 is-glob: ^4.0.3 @@ -1430,35 +1428,35 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: eefe82eedf9ee2e14463c3f2b5b18df084c1328a859b245ee897a9a7075acce7cca0216a21fd7968b75aa64189daa008bfde1e2f9afbcc336f3dfe856e7f342e + checksum: d93371cc866f573a6a1ddc0eb10d498a8e59f36763a99ce21da0737fff2b4c942eef1587216aad273f8d896ebc0b19003677cba63a27d2646aa2c087638963eb languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/utils@npm:5.59.7" +"@typescript-eslint/utils@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/utils@npm:5.59.8" dependencies: "@eslint-community/eslint-utils": ^4.2.0 "@types/json-schema": ^7.0.9 "@types/semver": ^7.3.12 - "@typescript-eslint/scope-manager": 5.59.7 - "@typescript-eslint/types": 5.59.7 - "@typescript-eslint/typescript-estree": 5.59.7 + "@typescript-eslint/scope-manager": 5.59.8 + "@typescript-eslint/types": 5.59.8 + "@typescript-eslint/typescript-estree": 5.59.8 eslint-scope: ^5.1.1 semver: ^7.3.7 peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: d8682700187ca94cc6441480cb6b87d0514a9748103c15dd93206c5b1c6fefa59063662f27a4103e16abbcfb654a61d479bc55af8f23d96f342431b87f31bb4e + checksum: cbaa057485c7f52c45d0dfb4f5a8e9273abccb1c52dcb4426a79f9e71d2c1062cf2525bad6d4aca5ec42db3fe723d749843bcade5a323bde7fbe4b5d5b5d5c3b languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.59.7": - version: 5.59.7 - resolution: "@typescript-eslint/visitor-keys@npm:5.59.7" +"@typescript-eslint/visitor-keys@npm:5.59.8": + version: 5.59.8 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.8" dependencies: - "@typescript-eslint/types": 5.59.7 + "@typescript-eslint/types": 5.59.8 eslint-visitor-keys: ^3.3.0 - checksum: 4367f2ea68dd96a0520485434ad11e1bd26239eeeb3a2150bee7478a0f1df3c2099a39f96486722932be0456bcb7a47a483b452876d1d30bdeb9b81d354eef3d + checksum: 6bfa7918dbb0e08d8a7404aeeef7bcd1a85736dc8d01614d267c0c5ec10f94d2746b50a945bf5c82c54fda67926e8deaeba8565c919da17f725fc11209ef8987 languageName: node linkType: hard @@ -1677,7 +1675,7 @@ __metadata: languageName: node linkType: hard -"acorn-import-assertions@npm:^1.7.6": +"acorn-import-assertions@npm:^1.9.0": version: 1.9.0 resolution: "acorn-import-assertions@npm:1.9.0" peerDependencies: @@ -2017,16 +2015,6 @@ __metadata: languageName: node linkType: hard -"bigint-buffer@npm:^1.1.5": - version: 1.1.5 - resolution: "bigint-buffer@npm:1.1.5" - dependencies: - bindings: ^1.3.0 - node-gyp: latest - checksum: d010c9f57758bcdaccb435d88b483ffcc95fe8bbc6e7fb3a44fb5221f29c894ffaf4a3c5a4a530e0e7d6608203c2cde9b79ee4f2386cd6d4462d1070bc8c9f4e - languageName: node - linkType: hard - "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" @@ -2034,15 +2022,6 @@ __metadata: languageName: node linkType: hard -"bindings@npm:^1.3.0": - version: 1.5.0 - resolution: "bindings@npm:1.5.0" - dependencies: - file-uri-to-path: 1.0.0 - checksum: 65b6b48095717c2e6105a021a7da4ea435aa8d3d3cd085cb9e85bcb6e5773cf318c4745c3f7c504412855940b585bdf9b918236612a1c7a7942491de176f1ae7 - languageName: node - linkType: hard - "body-parser@npm:1.20.1": version: 1.20.1 resolution: "body-parser@npm:1.20.1" @@ -2111,16 +2090,16 @@ __metadata: linkType: hard "browserslist@npm:^4.14.5, browserslist@npm:^4.21.3": - version: 4.21.5 - resolution: "browserslist@npm:4.21.5" + version: 4.21.7 + resolution: "browserslist@npm:4.21.7" dependencies: - caniuse-lite: ^1.0.30001449 - electron-to-chromium: ^1.4.284 - node-releases: ^2.0.8 - update-browserslist-db: ^1.0.10 + caniuse-lite: ^1.0.30001489 + electron-to-chromium: ^1.4.411 + node-releases: ^2.0.12 + update-browserslist-db: ^1.0.11 bin: browserslist: cli.js - checksum: 9755986b22e73a6a1497fd8797aedd88e04270be33ce66ed5d85a1c8a798292a65e222b0f251bafa1c2522261e237d73b08b58689d4920a607e5a53d56dc4706 + checksum: 3d0d025e6d381c4db5e71b538258952660ba574c060832095f182a9877ca798836fa550736269e669a2080e486f0cfdf5d3bcf2769b9f7cf123f6c6b8c005f8f languageName: node linkType: hard @@ -2240,10 +2219,10 @@ __metadata: languageName: node linkType: hard -"caniuse-lite@npm:^1.0.30001449": - version: 1.0.30001489 - resolution: "caniuse-lite@npm:1.0.30001489" - checksum: 94585a351fd7661b855c83eace474db0ee5a617159b46f2eff1f6fe4b85d7a205418471fdec8cf5cd647a7f79958706d5e664c0bbf3c7c09118b35db9bb95a1b +"caniuse-lite@npm:^1.0.30001489": + version: 1.0.30001492 + resolution: "caniuse-lite@npm:1.0.30001492" + checksum: 261869f910ec905ab6aa5a754e4ae57da8c5c33f3b723db2fa21840da307667bff61057aef3abaca32091c1561c254dd3a807c0fdb054cdc9e7e3ba495a55e20 languageName: node linkType: hard @@ -2812,10 +2791,10 @@ __metadata: languageName: node linkType: hard -"electron-to-chromium@npm:^1.4.284": - version: 1.4.405 - resolution: "electron-to-chromium@npm:1.4.405" - checksum: d1cf421eaf63dbd5481bcc4296a94e5db5cf831bdc5cbdad283b4b0d53d8fd87254b64fa6cda88f1cb4789eab012f078c1eed4cbb01c5a34bd0ce657dcfe08c8 +"electron-to-chromium@npm:^1.4.411": + version: 1.4.416 + resolution: "electron-to-chromium@npm:1.4.416" + checksum: a1804fe6f1955b3b80519bf2feb69d5e2111914e824cdacdff85545dba48da44d16f43d5fb702ff4cfc9148f7d0be7cd2669cb5e939b0ed13ee9772cae3edfd4 languageName: node linkType: hard @@ -2849,13 +2828,13 @@ __metadata: languageName: node linkType: hard -"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.14.0": - version: 5.14.0 - resolution: "enhanced-resolve@npm:5.14.0" +"enhanced-resolve@npm:^5.0.0, enhanced-resolve@npm:^5.14.1": + version: 5.14.1 + resolution: "enhanced-resolve@npm:5.14.1" dependencies: graceful-fs: ^4.2.4 tapable: ^2.2.0 - checksum: fff1aaebbf376371e5df4502e111967f6247c37611ad3550e4e7fca657f6dcb29ef7ffe88bf14e5010b78997f1ddd984a8db97af87ee0a5477771398fd326f5b + checksum: ad2a31928b6649eed40d364838449587f731baa63863e83d2629bebaa8be1eabac18b90f89c1784bc805b0818363e99b22547159edd485d7e5ccf18cdc640642 languageName: node linkType: hard @@ -3261,13 +3240,6 @@ __metadata: languageName: node linkType: hard -"file-uri-to-path@npm:1.0.0": - version: 1.0.0 - resolution: "file-uri-to-path@npm:1.0.0" - checksum: b648580bdd893a008c92c7ecc96c3ee57a5e7b6c4c18a9a09b44fb5d36d79146f8e442578bc0e173dc027adf3987e254ba1dfd6e3ec998b7c282873010502144 - languageName: node - linkType: hard - "fill-range@npm:^7.0.1": version: 7.0.1 resolution: "fill-range@npm:7.0.1" @@ -3363,9 +3335,9 @@ __metadata: linkType: hard "fs-monkey@npm:^1.0.3": - version: 1.0.3 - resolution: "fs-monkey@npm:1.0.3" - checksum: cf50804833f9b88a476911ae911fe50f61a98d986df52f890bd97e7262796d023698cb2309fa9b74fdd8974f04315b648748a0a8ee059e7d5257b293bfc409c0 + version: 1.0.4 + resolution: "fs-monkey@npm:1.0.4" + checksum: 8b254c982905c0b7e028eab22b410dc35a5c0019c1c860456f5f54ae6a61666e1cb8c6b700d6c88cc873694c00953c935847b9959cc4dcf274aacb8673c1e8bf languageName: node linkType: hard @@ -3944,9 +3916,9 @@ __metadata: linkType: hard "ipaddr.js@npm:^2.0.1": - version: 2.0.1 - resolution: "ipaddr.js@npm:2.0.1" - checksum: dd194a394a843d470f88d17191b0948f383ed1c8e320813f850c336a0fcb5e9215d97ec26ca35ab4fbbd31392c8b3467f3e8344628029ed3710b2ff6b5d1034e + version: 2.1.0 + resolution: "ipaddr.js@npm:2.1.0" + checksum: 807a054f2bd720c4d97ee479d6c9e865c233bea21f139fb8dabd5a35c4226d2621c42e07b4ad94ff3f82add926a607d8d9d37c625ad0319f0e08f9f2bd1968e2 languageName: node linkType: hard @@ -5148,7 +5120,7 @@ __metadata: languageName: node linkType: hard -"node-releases@npm:^2.0.8": +"node-releases@npm:^2.0.12": version: 2.0.12 resolution: "node-releases@npm:2.0.12" checksum: b8c56db82c4642a0f443332b331a4396dae452a2ac5a65c8dbd93ef89ecb2fbb0da9d42ac5366d4764973febadca816cf7587dad492dce18d2a6b2af59cda260 @@ -6576,7 +6548,7 @@ __metadata: languageName: node linkType: hard -"update-browserslist-db@npm:^1.0.10": +"update-browserslist-db@npm:^1.0.11": version: 1.0.11 resolution: "update-browserslist-db@npm:1.0.11" dependencies: @@ -6794,8 +6766,8 @@ __metadata: linkType: hard "webpack@npm:^5.82.1": - version: 5.83.1 - resolution: "webpack@npm:5.83.1" + version: 5.85.0 + resolution: "webpack@npm:5.85.0" dependencies: "@types/eslint-scope": ^3.7.3 "@types/estree": ^1.0.0 @@ -6803,10 +6775,10 @@ __metadata: "@webassemblyjs/wasm-edit": ^1.11.5 "@webassemblyjs/wasm-parser": ^1.11.5 acorn: ^8.7.1 - acorn-import-assertions: ^1.7.6 + acorn-import-assertions: ^1.9.0 browserslist: ^4.14.5 chrome-trace-event: ^1.0.2 - enhanced-resolve: ^5.14.0 + enhanced-resolve: ^5.14.1 es-module-lexer: ^1.2.1 eslint-scope: 5.1.1 events: ^3.2.0 @@ -6826,7 +6798,7 @@ __metadata: optional: true bin: webpack: bin/webpack.js - checksum: 219d5ef50380bc0fd3702ed17feddf13819d8173b78f7a5b857dc74ac177e63d1f79c050792754411cc088bbc02e0971b989efddadbb8e393cf27d64c0ad9ff8 + checksum: b013be9fbc7f6810d1f229f570c70710ddbc7290f817411acffe4214b2b6c783a041ab1f2005d9e1109f4ab21c136f0f8d8c067a5fb64f20a82dcbc1ee0d3f42 languageName: node linkType: hard