From 4d2798226648007b0012f54856d14bb101e20d73 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Mon, 20 May 2024 21:05:35 +0000 Subject: [PATCH 01/10] update honk recursion --- .../cpp/src/barretenberg/dsl/CMakeLists.txt | 2 + .../dsl/acir_format/acir_format.cpp | 27 +- .../acir_format/honk_recursion_constraint.cpp | 256 +++--------------- .../acir_format/honk_recursion_constraint.hpp | 17 +- .../honk_recursion_constraint.test.cpp | 27 +- .../verifier/ultra_recursive_verifier.cpp | 26 +- .../verifier/ultra_recursive_verifier.hpp | 2 + .../ultra_honk/ultra_verifier.hpp | 2 +- 8 files changed, 90 insertions(+), 269 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt index 158c5752774..1054b656290 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt @@ -7,4 +7,6 @@ barretenberg_module( stdlib_poseidon2 crypto_merkle_tree stdlib_schnorr + ultra_honk + stdlib_honk_recursion ) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 3dc7fc15e6c..0e3b8269f32 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -230,31 +230,14 @@ void build_constraints(Builder& builder, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; - // Get the size of proof with no public inputs prepended to it - // This is used while processing recursion constraints to determine whether - // the proof we are verifying contains a recursive proof itself - auto proof_size_no_pub_inputs = recursion_honk_proof_size_without_public_inputs(); - // Add recursion constraints for (auto constraint : constraint_system.honk_recursion_constraints) { - // A proof passed into the constraint should be stripped of its public inputs, except in the case where a - // proof contains an aggregation object itself. We refer to this as the `nested_aggregation_object`. The - // verifier circuit requires that the indices to a nested proof aggregation state are a circuit constant. - // The user tells us they how they want these constants set by keeping the nested aggregation object - // attached to the proof as public inputs. As this is the only object that can prepended to the proof if the - // proof is above the expected size (with public inputs stripped) + // A proof passed into the constraint should be stripped of its inner public inputs, but not the nested + // aggregation object itself. The verifier circuit requires that the indices to a nested proof aggregation + // state are a circuit constant. The user tells us they how they want these constants set by keeping the + // nested aggregation object attached to the proof as public inputs. As this is the only object that can + // prepended to the proof if the proof is above the expected size (with public inputs stripped) std::array nested_aggregation_object = {}; - // If the proof has public inputs attached to it, we should handle setting the nested aggregation object - // The public inputs attached to a proof should match the aggregation object in size - if (constraint.proof.size() - proof_size_no_pub_inputs != - HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE) { - auto error_string = format( - "Public inputs are always stripped from proofs unless we have a recursive proof.\n" - "Thus, public inputs attached to a proof must match the recursive aggregation object in size " - "which is ", - HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE); - throw_or_abort(error_string); - } for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { // Set the nested aggregation object indices to the current size of the public inputs // This way we know that the nested aggregation object indices will always be the last diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index fb46384ce01..6900836abfa 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -8,7 +8,8 @@ namespace acir_format { -using namespace bb::plonk; +using namespace bb; +using namespace bb::stdlib::recursion::honk; /** * @brief Add constraints required to recursively verify an UltraPlonk proof @@ -30,53 +31,14 @@ std::array create_ho std::array nested_aggregation_object, bool has_valid_witness_assignments) { - const auto& nested_aggregation_indices = nested_aggregation_object; - const bool inner_proof_contains_recursive_proof = true; + using Flavor = UltraRecursiveFlavor_; + using RecursiveVerificationKey = Flavor::VerificationKey; + using RecursiveVerifier = UltraRecursiveVerifier_; - // 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 = export_dummy_honk_key_in_recursion_format( - PolynomialManifest(Builder::CIRCUIT_TYPE), inner_proof_contains_recursive_proof); - const auto manifest = Composer::create_manifest(input.public_inputs.size()); - std::vector dummy_proof = - export_dummy_honk_transcript_in_recursion_format(manifest, inner_proof_contains_recursive_proof); - - for (size_t i = 0; i < input.public_inputs.size(); ++i) { - const auto public_input_idx = input.public_inputs[i]; - // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), - // we add our dummy public input values as Builder variables. - // if we DO have a valid witness assignment, we use the real witness assignment - bb::fr dummy_field = - has_valid_witness_assignments ? builder.get_variable(public_input_idx) : dummy_proof[i]; - // Create a copy constraint between our dummy field and the witness index provided by - // HonkRecursionConstraint. This will make the HonkRecursionConstraint 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 HonkRecursionConstraint witness indices will not - // trigger basic errors (check inputs are on-curve, check we are not inverting 0) - // - // Failing to do these copy constraints on public inputs will trigger these basic errors - // in the case of a nested proof, as an aggregation object is expected to be two G1 points even - // in the case of no valid witness assignments. - builder.assert_equal(builder.add_variable(dummy_field), public_input_idx); - } - // Remove the public inputs from the dummy proof - // The proof supplied to the recursion constraint will already be stripped of public inputs - // while the barretenberg API works with public inputs prepended to the proof. - dummy_proof.erase(dummy_proof.begin(), - dummy_proof.begin() + static_cast(input.public_inputs.size())); - for (size_t i = 0; i < input.proof.size(); ++i) { - const auto proof_field_idx = input.proof[i]; - bb::fr dummy_field = has_valid_witness_assignments ? builder.get_variable(proof_field_idx) : dummy_proof[i]; - builder.assert_equal(builder.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]; - bb::fr dummy_field = has_valid_witness_assignments ? builder.get_variable(key_field_idx) : dummy_key[i]; - builder.assert_equal(builder.add_variable(dummy_field), key_field_idx); - } - } + static_cast(has_valid_witness_assignments); + static_cast(nested_aggregation_object); + // const auto& nested_aggregation_indices = nested_aggregation_object; + // const bool inner_proof_contains_recursive_proof = true; // Construct an in-circuit representation of the verification key. // For now, the v-key is a circuit constant and is fixed for the circuit. @@ -110,8 +72,6 @@ std::array create_ho previous_aggregation.has_data = false; } - transcript::Manifest manifest = Composer::create_manifest(input.public_inputs.size()); - std::vector key_fields; key_fields.reserve(input.key.size()); for (const auto& idx : input.key) { @@ -133,23 +93,36 @@ std::array create_ho } // recursively verify the proof - std::shared_ptr vkey = verification_key_ct::from_field_elements( - &builder, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); - vkey->program_width = noir_recursive_settings::program_width; - - Transcript_ct transcript(&builder, manifest, proof_fields, input.public_inputs.size()); - aggregation_state_ct result = bb::stdlib::recursion::verify_proof_( - &builder, vkey, transcript, previous_aggregation); - - // Assign correct witness value to the verification key hash - vkey->hash().assert_equal(field_ct::from_witness_index(&builder, input.key_hash)); + auto vkey = std::make_shared(builder, key_fields); + RecursiveVerifier verifier(&builder, vkey); + std::array pairing_points = verifier.verify_proof(proof_fields); + + std::vector proof_witness_indices = { + pairing_points[0].x.binary_basis_limbs[0].element.normalize().witness_index, + pairing_points[0].x.binary_basis_limbs[1].element.normalize().witness_index, + pairing_points[0].x.binary_basis_limbs[2].element.normalize().witness_index, + pairing_points[0].x.binary_basis_limbs[3].element.normalize().witness_index, + pairing_points[0].y.binary_basis_limbs[0].element.normalize().witness_index, + pairing_points[0].y.binary_basis_limbs[1].element.normalize().witness_index, + pairing_points[0].y.binary_basis_limbs[2].element.normalize().witness_index, + pairing_points[0].y.binary_basis_limbs[3].element.normalize().witness_index, + pairing_points[1].x.binary_basis_limbs[0].element.normalize().witness_index, + pairing_points[1].x.binary_basis_limbs[1].element.normalize().witness_index, + pairing_points[1].x.binary_basis_limbs[2].element.normalize().witness_index, + pairing_points[1].x.binary_basis_limbs[3].element.normalize().witness_index, + pairing_points[1].y.binary_basis_limbs[0].element.normalize().witness_index, + pairing_points[1].y.binary_basis_limbs[1].element.normalize().witness_index, + pairing_points[1].y.binary_basis_limbs[2].element.normalize().witness_index, + pairing_points[1].y.binary_basis_limbs[3].element.normalize().witness_index, + }; + auto result = aggregation_state_ct{ pairing_points[0], pairing_points[1], {}, proof_witness_indices, true }; - ASSERT(result.public_inputs.size() == input.public_inputs.size()); + // ASSERT(result.public_inputs.size() == input.public_inputs.size()); // Assign the `public_input` field to the public input of the inner proof - for (size_t i = 0; i < input.public_inputs.size(); ++i) { - result.public_inputs[i].assert_equal(field_ct::from_witness_index(&builder, input.public_inputs[i])); - } + // for (size_t i = 0; i < input.public_inputs.size(); ++i) { + // result.public_inputs[i].assert_equal(field_ct::from_witness_index(&builder, input.public_inputs[i])); + // } // We want to return an array, so just copy the vector into the array ASSERT(result.proof_witness_indices.size() == HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE); @@ -161,53 +134,6 @@ std::array create_ho return resulting_output_aggregation_object; } -/** - * @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_honk_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 < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++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)); - 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); - } - } - - verification_key_data vkey_data{ - .circuit_type = static_cast(vkey->circuit_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.hash_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. @@ -257,114 +183,4 @@ std::vector export_dummy_honk_key_in_recursion_format(const PolynomialMa return output; } -/** - * @brief Returns transcript represented as a vector of bb::fr. - * Used to represent recursive proofs (i.e. proof represented as circuit-native field elements) - * - * @return std::vector - */ -std::vector export_honk_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); - 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); - 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_honk_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") { - // auto scalar = bb::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 - // 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 = bb::fr::random_element(); - const auto group_element = bb::g1::affine_element(bb::g1::one * scalar); - 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; - // 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) { - // When setting up the ACIR we emplace back the nested aggregation object - // fetched from the proof onto the public inputs. Thus, we can expect the - // nested aggregation object to always be at the end of the public inputs. - for (size_t k = 0; k < num_public_inputs - HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; - ++k) { - fields.emplace_back(0); - } - for (size_t k = 0; k < HonkRecursionConstraint::NUM_AGGREGATION_ELEMENTS; ++k) { - auto scalar = bb::fr::random_element(); - const auto group_element = bb::g1::affine_element(bb::g1::one * scalar); - 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) { - // auto scalar = bb::fr::random_element(); - fields.emplace_back(0); - } - } - } - } - } - } - return fields; -} - -size_t recursion_honk_proof_size_without_public_inputs() -{ - const auto manifest = Composer::create_manifest(0); - auto dummy_transcript = export_dummy_honk_transcript_in_recursion_format(manifest, false); - return dummy_transcript.size(); -} - } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index 8696e9ef073..74a6bd81de1 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -5,7 +5,7 @@ namespace acir_format { -using namespace bb::plonk; +using namespace bb; /** * @brief HonkRecursionConstraint struct contains information required to recursively verify a proof! @@ -47,8 +47,7 @@ struct HonkRecursionConstraint { // 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 + static constexpr size_t AGGREGATION_OBJECT_SIZE = NUM_AGGREGATION_ELEMENTS * fq_ct::NUM_LIMBS; // 16 field elements std::vector key; std::vector proof; std::vector public_inputs; @@ -64,13 +63,11 @@ std::array create_ho std::array nested_aggregation_object, bool has_valid_witness_assignments = false); -std::vector export_honk_key_in_recursion_format(std::shared_ptr const& vkey); -std::vector export_dummy_honk_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, - bool contains_recursive_proof = 0); +// std::vector export_honk_key_in_recursion_format(std::shared_ptr const& vkey); +// std::vector export_dummy_honk_key_in_recursion_format(bool contains_recursive_proof = 0); -std::vector export_honk_transcript_in_recursion_format(const transcript::StandardTranscript& transcript); -std::vector export_dummy_honk_transcript_in_recursion_format(const transcript::Manifest& manifest, - const bool contains_recursive_proof); -size_t recursion_honk_proof_size_without_public_inputs(); +// std::vector export_honk_transcript_in_recursion_format(const HonkProof& proof); +// std::vector export_dummy_honk_proof_in_recursion_format(const bool contains_recursive_proof); +// size_t recursion_honk_proof_size_without_public_inputs(); } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 7ac1fc58eb0..11ceed7469e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -7,10 +7,16 @@ #include using namespace acir_format; -using namespace bb::plonk; +using namespace bb; class AcirHonkRecursionConstraint : public ::testing::Test { + public: + using ProverInstance = ProverInstance_; + using Prover = bb::UltraProver; + using VerificationKey = UltraFlavor::VerificationKey; + using Verifier = bb::UltraVerifier; + Builder create_inner_circuit() { /** @@ -126,6 +132,8 @@ class AcirHonkRecursionConstraint : public ::testing::Test { */ Builder create_outer_circuit(std::vector& inner_circuits) { + using ProverInstance = ProverInstance_; + std::vector honk_recursion_constraints; size_t witness_offset = 0; @@ -133,18 +141,15 @@ class AcirHonkRecursionConstraint : public ::testing::Test { for (auto& inner_circuit : inner_circuits) { - auto inner_composer = Composer(); - auto inner_prover = inner_composer.create_prover(inner_circuit); - auto inner_proof = inner_prover.construct_proof(); - auto inner_verifier = inner_composer.create_verifier(inner_circuit); + auto instance = std::make_shared(inner_circuit); + Prover prover(instance); + auto verification_key = std::make_shared(instance->proving_key); + Verifier verifier(verification_key); + auto inner_proof = prover.construct_proof(); const size_t num_inner_public_inputs = inner_circuit.get_public_inputs().size(); - transcript::StandardTranscript transcript(inner_proof.proof_data, - Composer::create_manifest(num_inner_public_inputs), - transcript::HashType::PedersenBlake3s, - 16); - std::vector proof_witnesses = export_honk_transcript_in_recursion_format(transcript); + std::vector proof_witnesses = inner_proof; // - Save the public inputs so that we can set their values. // - Then truncate them from the proof because the ACIR API expects proofs without public inputs std::vector inner_public_input_values( @@ -158,7 +163,7 @@ class AcirHonkRecursionConstraint : public ::testing::Test { static_cast(num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); - std::vector key_witnesses = export_honk_key_in_recursion_format(inner_verifier.key); + std::vector key_witnesses = verification_key->to_field_elements(); bb::fr key_hash = key_witnesses.back(); key_witnesses.pop_back(); diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp index 538c7376bac..b2126e68f71 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.cpp @@ -13,12 +13,30 @@ UltraRecursiveVerifier_::UltraRecursiveVerifier_( , builder(builder) {} +template +UltraRecursiveVerifier_::UltraRecursiveVerifier_(Builder* builder, const std::shared_ptr& vkey) + : key(vkey) + , builder(builder) +{} + /** - * @brief This function constructs a recursive verifier circuit for an Ultra Honk proof of a given flavor. + * @brief This function constructs a recursive verifier circuit for a native Ultra Honk proof of a given flavor. * */ template std::array UltraRecursiveVerifier_::verify_proof(const HonkProof& proof) +{ + StdlibProof stdlib_proof = bb::convert_proof_to_witness(builder, proof); + return verify_proof(stdlib_proof); +} + +/** + * @brief This function constructs a recursive verifier circuit for a native Ultra Honk proof of a given flavor. + * + */ +template +std::array UltraRecursiveVerifier_::verify_proof( + const StdlibProof& proof) { using Sumcheck = ::bb::SumcheckVerifier; using PCS = typename Flavor::PCS; @@ -28,11 +46,9 @@ std::array UltraRecursiveVerifier_::ve using RelationParams = ::bb::RelationParameters; using Transcript = typename Flavor::Transcript; - RelationParams relation_parameters; - - StdlibProof stdlib_proof = bb::convert_proof_to_witness(builder, proof); - transcript = std::make_shared(stdlib_proof); + transcript = std::make_shared(proof); + RelationParams relation_parameters; VerifierCommitments commitments{ key }; CommitmentLabels commitment_labels; diff --git a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.hpp b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.hpp index aa8077c11ca..5155946d8ca 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib/honk_recursion/verifier/ultra_recursive_verifier.hpp @@ -21,10 +21,12 @@ template class UltraRecursiveVerifier_ { explicit UltraRecursiveVerifier_(Builder* builder, const std::shared_ptr& native_verifier_key); + explicit UltraRecursiveVerifier_(Builder* builder, const std::shared_ptr& vkey); // TODO(luke): Eventually this will return something like aggregation_state but I'm simplifying for now until we // determine the exact interface. Simply returns the two pairing points. PairingPoints verify_proof(const HonkProof& proof); + PairingPoints verify_proof(const StdlibProof& proof); std::shared_ptr key; std::map commitments; diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp index f32deb49155..cdd8acc6762 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp @@ -26,7 +26,7 @@ template class UltraVerifier_ { bool verify_proof(const HonkProof& proof); std::shared_ptr key; - std::shared_ptr transcript; + std::shared_ptr transcript; // this seems useless }; using UltraVerifier = UltraVerifier_; From 094f6a3703fee7fb36aded80378c8070739ddb08 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Mon, 20 May 2024 21:47:00 +0000 Subject: [PATCH 02/10] removing key hash, fixing deserialization --- .../dsl/acir_format/honk_recursion_constraint.hpp | 8 -------- .../dsl/acir_format/honk_recursion_constraint.test.cpp | 7 +------ .../goblin_ultra_recursive_flavor.hpp | 8 ++++---- .../stdlib_circuit_builders/ultra_recursive_flavor.hpp | 8 ++++---- 4 files changed, 9 insertions(+), 22 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index 74a6bd81de1..1b18131387b 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -51,7 +51,6 @@ struct HonkRecursionConstraint { std::vector key; std::vector proof; std::vector public_inputs; - uint32_t key_hash; friend bool operator==(HonkRecursionConstraint const& lhs, HonkRecursionConstraint const& rhs) = default; }; @@ -63,11 +62,4 @@ std::array create_ho std::array nested_aggregation_object, bool has_valid_witness_assignments = false); -// std::vector export_honk_key_in_recursion_format(std::shared_ptr const& vkey); -// std::vector export_dummy_honk_key_in_recursion_format(bool contains_recursive_proof = 0); - -// std::vector export_honk_transcript_in_recursion_format(const HonkProof& proof); -// std::vector export_dummy_honk_proof_in_recursion_format(const bool contains_recursive_proof); -// size_t recursion_honk_proof_size_without_public_inputs(); - } // namespace acir_format diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 11ceed7469e..5023a91cb92 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -164,11 +164,8 @@ class AcirHonkRecursionConstraint : public ::testing::Test { RecursionConstraint::AGGREGATION_OBJECT_SIZE)); std::vector key_witnesses = verification_key->to_field_elements(); - bb::fr key_hash = key_witnesses.back(); - key_witnesses.pop_back(); - 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 public_input_start_idx = static_cast(witness_offset); const uint32_t proof_indices_start_idx = static_cast( public_input_start_idx + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE); const uint32_t key_indices_start_idx = @@ -195,11 +192,9 @@ class AcirHonkRecursionConstraint : public ::testing::Test { .key = key_indices, .proof = proof_indices, .public_inputs = inner_public_inputs, - .key_hash = key_hash_start_idx, }; honk_recursion_constraints.push_back(honk_recursion_constraint); - witness.emplace_back(key_hash); for (size_t i = 0; i < proof_indices_start_idx - public_input_start_idx; ++i) { witness.emplace_back(0); } diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/goblin_ultra_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/goblin_ultra_recursive_flavor.hpp index 393014d1f48..39faffce91d 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/goblin_ultra_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/goblin_ultra_recursive_flavor.hpp @@ -167,22 +167,22 @@ template class GoblinUltraRecursiveFlavor_ { size_t num_frs_Comm = bb::stdlib::field_conversion::calc_num_bn254_frs(); this->circuit_size = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; this->num_public_inputs = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; this->pub_inputs_offset = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; for (Commitment& comm : this->get_all()) { comm = bb::stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_Comm)); + builder, elements.subspan(num_frs_read, num_frs_Comm)); num_frs_read += num_frs_Comm; } } diff --git a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_recursive_flavor.hpp index e72937089d6..a2a76862cfc 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/ultra_recursive_flavor.hpp @@ -333,22 +333,22 @@ template class UltraRecursiveFlavor_ { size_t num_frs_Comm = bb::stdlib::field_conversion::calc_num_bn254_frs(); this->circuit_size = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; this->num_public_inputs = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; this->pub_inputs_offset = uint64_t(stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_FF)) + builder, elements.subspan(num_frs_read, num_frs_FF)) .get_value()); num_frs_read += num_frs_FF; for (Commitment& comm : this->get_all()) { comm = bb::stdlib::field_conversion::convert_from_bn254_frs( - builder, elements.subspan(num_frs_read, num_frs_read + num_frs_Comm)); + builder, elements.subspan(num_frs_read, num_frs_Comm)); num_frs_read += num_frs_Comm; } } From c238cd7d204291fab90e50e1d38f799e22d2533e Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Wed, 22 May 2024 20:49:41 +0000 Subject: [PATCH 03/10] use honk in the honk rec constraint tests Co-authored-by: Lucas Xia --- .../honk_recursion_constraint.test.cpp | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 5023a91cb92..e9be6b9e284 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -1,7 +1,5 @@ #include "honk_recursion_constraint.hpp" #include "acir_format.hpp" -#include "barretenberg/plonk/proof_system/types/proof.hpp" -#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include #include @@ -132,8 +130,6 @@ class AcirHonkRecursionConstraint : public ::testing::Test { */ Builder create_outer_circuit(std::vector& inner_circuits) { - using ProverInstance = ProverInstance_; - std::vector honk_recursion_constraints; size_t witness_offset = 0; @@ -270,11 +266,12 @@ TEST_F(AcirHonkRecursionConstraint, TestBasicDoubleHonkRecursionConstraints) info("circuit gates = ", layer_2_circuit.get_num_gates()); - auto layer_2_composer = Composer(); - auto prover = layer_2_composer.create_ultra_with_keccak_prover(layer_2_circuit); - info("prover gates = ", prover.circuit_size); + auto instance = std::make_shared(layer_2_circuit); + Prover prover(instance); + info("prover gates = ", instance->proving_key.circuit_size); auto proof = prover.construct_proof(); - auto verifier = layer_2_composer.create_ultra_with_keccak_verifier(layer_2_circuit); + auto verification_key = std::make_shared(instance->proving_key); + Verifier verifier(verification_key); EXPECT_EQ(verifier.verify_proof(proof), true); } @@ -327,11 +324,12 @@ TEST_F(AcirHonkRecursionConstraint, TestOneOuterRecursiveCircuit) info("created second outer circuit"); info("number of gates in layer 3 = ", layer_3_circuit.get_num_gates()); - auto layer_3_composer = Composer(); - auto prover = layer_3_composer.create_ultra_with_keccak_prover(layer_3_circuit); - info("prover gates = ", prover.circuit_size); + auto instance = std::make_shared(layer_3_circuit); + Prover prover(instance); + info("prover gates = ", instance->proving_key.circuit_size); auto proof = prover.construct_proof(); - auto verifier = layer_3_composer.create_ultra_with_keccak_verifier(layer_3_circuit); + auto verification_key = std::make_shared(instance->proving_key); + Verifier verifier(verification_key); EXPECT_EQ(verifier.verify_proof(proof), true); } @@ -356,10 +354,11 @@ TEST_F(AcirHonkRecursionConstraint, TestFullRecursiveComposition) info("created third outer circuit"); info("number of gates in layer 3 circuit = ", layer_3_circuit.get_num_gates()); - auto layer_3_composer = Composer(); - auto prover = layer_3_composer.create_ultra_with_keccak_prover(layer_3_circuit); - info("prover gates = ", prover.circuit_size); + auto instance = std::make_shared(layer_3_circuit); + Prover prover(instance); + info("prover gates = ", instance->proving_key.circuit_size); auto proof = prover.construct_proof(); - auto verifier = layer_3_composer.create_ultra_with_keccak_verifier(layer_3_circuit); + auto verification_key = std::make_shared(instance->proving_key); + Verifier verifier(verification_key); EXPECT_EQ(verifier.verify_proof(proof), true); } From f3110849f89e0ee7128ca4ed327c670b42fcf901 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Wed, 22 May 2024 20:50:22 +0000 Subject: [PATCH 04/10] aggregation, but failed (because biggroup elements being added can't have same x value?) --- .../acir_format/honk_recursion_constraint.cpp | 162 ++++++++---------- 1 file changed, 75 insertions(+), 87 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index 6900836abfa..d9439f31a65 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -11,6 +11,23 @@ namespace acir_format { using namespace bb; using namespace bb::stdlib::recursion::honk; +std::array agg_points_from_witness_indicies( + Builder& builder, const std::array& obj_witness_indices) +{ + std::array aggregation_elements; + for (size_t i = 0; i < 4; ++i) { + aggregation_elements[i] = + bn254::BaseField(field_ct::from_witness_index(&builder, obj_witness_indices[4 * i]), + field_ct::from_witness_index(&builder, obj_witness_indices[4 * i + 1]), + field_ct::from_witness_index(&builder, obj_witness_indices[4 * i + 2]), + field_ct::from_witness_index(&builder, obj_witness_indices[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + + return { bn254::Group(aggregation_elements[0], aggregation_elements[1]), + bn254::Group(aggregation_elements[2], aggregation_elements[3]) }; +} + /** * @brief Add constraints required to recursively verify an UltraPlonk proof * @@ -37,39 +54,40 @@ std::array create_ho static_cast(has_valid_witness_assignments); static_cast(nested_aggregation_object); - // const auto& nested_aggregation_indices = nested_aggregation_object; - // const bool inner_proof_contains_recursive_proof = true; + // std::array nested_aggregation_points = + // agg_points_from_witness_indicies(builder, nested_aggregation_object); // 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) const auto& aggregation_input = input_aggregation_object; - aggregation_state_ct previous_aggregation; + aggregation_state_ct cur_aggregation_object; + // cur_aggregation_object.P0 = nested_aggregation_points[0]; + // cur_aggregation_object.P1 = nested_aggregation_points[1]; + // cur_aggregation_object.has_data = true; - // If we have previously recursively verified proofs, `inner_aggregation_object_nonzero = true` + // If we have previously recursively verified proofs, `previous_aggregation_object_nonzero = true` // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! - bool inner_aggregation_indices_all_zero = true; + bool previous_aggregation_indices_all_zero = true; for (const auto& idx : aggregation_input) { - inner_aggregation_indices_all_zero &= (idx == 0); + previous_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] = - bn254::BaseField(field_ct::from_witness_index(&builder, aggregation_input[4 * i]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 1]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 2]), - field_ct::from_witness_index(&builder, aggregation_input[4 * i + 3])); - aggregation_elements[i].assert_is_in_field(); - } + // Aggregate the aggregation object if it exists. It exists if we have previously verified proofs, i.e. if this is + // not the first recursion constraint. + if (!previous_aggregation_indices_all_zero) { + std::array inner_agg_points = agg_points_from_witness_indicies(builder, aggregation_input); // 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::Group(aggregation_elements[0], aggregation_elements[1]); - previous_aggregation.P1 = bn254::Group(aggregation_elements[2], aggregation_elements[3]); - previous_aggregation.has_data = true; + // cur_aggregation_object.P0 += inner_agg_points[0]; // TODO: use a recursion separator + // cur_aggregation_object.P1 += inner_agg_points[1]; + cur_aggregation_object.P0 = inner_agg_points[0]; + cur_aggregation_object.P1 = inner_agg_points[1]; + UltraFlavor::VerifierCommitmentKey pcs_verification_key; + ASSERT(pcs_verification_key.pairing_check(inner_agg_points[0].get_value(), inner_agg_points[1].get_value())); + cur_aggregation_object.has_data = true; } else { - previous_aggregation.has_data = false; + cur_aggregation_object.has_data = false; } std::vector key_fields; @@ -96,27 +114,46 @@ std::array create_ho auto vkey = std::make_shared(builder, key_fields); RecursiveVerifier verifier(&builder, vkey); std::array pairing_points = verifier.verify_proof(proof_fields); + UltraFlavor::VerifierCommitmentKey pcs_verification_key; + ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); + + // aggregate with the input aggregation object + if (cur_aggregation_object.has_data) { + info("agg points: ", cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value()); + info("pairing points: ", pairing_points[0].get_value(), pairing_points[1].get_value()); + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); + ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); + cur_aggregation_object.P0 += pairing_points[0]; + cur_aggregation_object.P1 += pairing_points[1]; + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); + } else { + cur_aggregation_object.P0 = pairing_points[0]; + cur_aggregation_object.P1 = pairing_points[1]; + cur_aggregation_object.has_data = true; + } std::vector proof_witness_indices = { - pairing_points[0].x.binary_basis_limbs[0].element.normalize().witness_index, - pairing_points[0].x.binary_basis_limbs[1].element.normalize().witness_index, - pairing_points[0].x.binary_basis_limbs[2].element.normalize().witness_index, - pairing_points[0].x.binary_basis_limbs[3].element.normalize().witness_index, - pairing_points[0].y.binary_basis_limbs[0].element.normalize().witness_index, - pairing_points[0].y.binary_basis_limbs[1].element.normalize().witness_index, - pairing_points[0].y.binary_basis_limbs[2].element.normalize().witness_index, - pairing_points[0].y.binary_basis_limbs[3].element.normalize().witness_index, - pairing_points[1].x.binary_basis_limbs[0].element.normalize().witness_index, - pairing_points[1].x.binary_basis_limbs[1].element.normalize().witness_index, - pairing_points[1].x.binary_basis_limbs[2].element.normalize().witness_index, - pairing_points[1].x.binary_basis_limbs[3].element.normalize().witness_index, - pairing_points[1].y.binary_basis_limbs[0].element.normalize().witness_index, - pairing_points[1].y.binary_basis_limbs[1].element.normalize().witness_index, - pairing_points[1].y.binary_basis_limbs[2].element.normalize().witness_index, - pairing_points[1].y.binary_basis_limbs[3].element.normalize().witness_index, + cur_aggregation_object.P0.x.binary_basis_limbs[0].element.normalize().witness_index, + cur_aggregation_object.P0.x.binary_basis_limbs[1].element.normalize().witness_index, + cur_aggregation_object.P0.x.binary_basis_limbs[2].element.normalize().witness_index, + cur_aggregation_object.P0.x.binary_basis_limbs[3].element.normalize().witness_index, + cur_aggregation_object.P0.y.binary_basis_limbs[0].element.normalize().witness_index, + cur_aggregation_object.P0.y.binary_basis_limbs[1].element.normalize().witness_index, + cur_aggregation_object.P0.y.binary_basis_limbs[2].element.normalize().witness_index, + cur_aggregation_object.P0.y.binary_basis_limbs[3].element.normalize().witness_index, + cur_aggregation_object.P1.x.binary_basis_limbs[0].element.normalize().witness_index, + cur_aggregation_object.P1.x.binary_basis_limbs[1].element.normalize().witness_index, + cur_aggregation_object.P1.x.binary_basis_limbs[2].element.normalize().witness_index, + cur_aggregation_object.P1.x.binary_basis_limbs[3].element.normalize().witness_index, + cur_aggregation_object.P1.y.binary_basis_limbs[0].element.normalize().witness_index, + cur_aggregation_object.P1.y.binary_basis_limbs[1].element.normalize().witness_index, + cur_aggregation_object.P1.y.binary_basis_limbs[2].element.normalize().witness_index, + cur_aggregation_object.P1.y.binary_basis_limbs[3].element.normalize().witness_index, }; - auto result = aggregation_state_ct{ pairing_points[0], pairing_points[1], {}, proof_witness_indices, true }; - + auto result = cur_aggregation_object; + result.proof_witness_indices = proof_witness_indices; // ASSERT(result.public_inputs.size() == input.public_inputs.size()); // Assign the `public_input` field to the public input of the inner proof @@ -134,53 +171,4 @@ std::array create_ho return resulting_output_aggregation_object; } -/** - * @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_honk_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 < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++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 = bb::fr::random_element(); - const auto element = bb::g1::affine_element(bb::g1::one * scalar); - 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); - } - } - - output.emplace_back(0); // key_hash - - return output; -} - } // namespace acir_format From be711cdef7c4647ab18dfbae0c33fa0fbc83b163 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Thu, 23 May 2024 16:14:30 +0000 Subject: [PATCH 05/10] aggregating input object with current object works (using 2 as the recursion separator challenge) --- .../dsl/acir_format/honk_recursion_constraint.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index d9439f31a65..c73a62b19bf 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -124,8 +124,9 @@ std::array create_ho ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value())); ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); - cur_aggregation_object.P0 += pairing_points[0]; - cur_aggregation_object.P1 += pairing_points[1]; + field_ct recursion_separator = bb::stdlib::witness_t(&builder, 2); + cur_aggregation_object.P0 += pairing_points[0] * recursion_separator; + cur_aggregation_object.P1 += pairing_points[1] * recursion_separator; ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value())); } else { From d97b66371abe64666a8c355c7348e43d54014e20 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Thu, 23 May 2024 19:08:12 +0000 Subject: [PATCH 06/10] hacking harder than I've ever hacked indexing and other related fixes --- .../dsl/acir_format/acir_format.cpp | 13 +++- .../acir_format/honk_recursion_constraint.cpp | 75 ++++++++++--------- .../honk_recursion_constraint.test.cpp | 30 +++++--- 3 files changed, 67 insertions(+), 51 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 0e3b8269f32..38ae60e4d02 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -238,20 +238,24 @@ void build_constraints(Builder& builder, // nested aggregation object attached to the proof as public inputs. As this is the only object that can // prepended to the proof if the proof is above the expected size (with public inputs stripped) std::array nested_aggregation_object = {}; + const size_t public_input_offset = 3; + info("constraint proof: ", constraint.proof); for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { // Set the nested aggregation object indices to the current size of the public inputs // This way we know that the nested aggregation object indices will always be the last // indices of the public inputs - nested_aggregation_object[i] = static_cast(constraint.public_inputs.size()); + nested_aggregation_object[i] = static_cast(constraint.proof[public_input_offset + i]); // Attach the nested aggregation object to the end of the public inputs to fill in // the slot where the nested aggregation object index will point into - constraint.public_inputs.emplace_back(constraint.proof[i]); + constraint.public_inputs.emplace_back(constraint.proof[public_input_offset + i]); } + info("nested aggregation object: ", nested_aggregation_object); // Remove the aggregation object so that they can be handled as normal public inputs // in they way taht the recursion constraint expects - constraint.proof.erase(constraint.proof.begin(), + constraint.proof.erase(constraint.proof.begin() + public_input_offset, constraint.proof.begin() + - static_cast(HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); + static_cast(public_input_offset + + HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); current_aggregation_object = create_honk_recursion_constraints(builder, constraint, current_aggregation_object, @@ -295,6 +299,7 @@ void build_constraints(Builder& builder, x.slice(fq_ct::NUM_LIMB_BITS * 3, bb::stdlib::field_conversion::TOTAL_BITS) }; for (size_t i = 0; i < fq_ct::NUM_LIMBS; ++i) { + info("default object val limb: ", val_limbs[i]); uint32_t idx = builder.add_variable(val_limbs[i]); builder.set_public_input(idx); current_aggregation_object[agg_obj_indices_idx] = idx; diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index c73a62b19bf..da88f77dac1 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -53,18 +53,24 @@ std::array create_ho using RecursiveVerifier = UltraRecursiveVerifier_; static_cast(has_valid_witness_assignments); - static_cast(nested_aggregation_object); - // std::array nested_aggregation_points = - // agg_points_from_witness_indicies(builder, nested_aggregation_object); + // actual nested + std::array nested_aggregation_points = + agg_points_from_witness_indicies(builder, nested_aggregation_object); // 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) const auto& aggregation_input = input_aggregation_object; aggregation_state_ct cur_aggregation_object; - // cur_aggregation_object.P0 = nested_aggregation_points[0]; - // cur_aggregation_object.P1 = nested_aggregation_points[1]; - // cur_aggregation_object.has_data = true; + cur_aggregation_object.P0 = nested_aggregation_points[0]; + cur_aggregation_object.P1 = nested_aggregation_points[1]; + info("aggregation object = ", cur_aggregation_object); + cur_aggregation_object.has_data = true; + field_ct recursion_separator = bb::stdlib::witness_t(&builder, 2); + + UltraFlavor::VerifierCommitmentKey pcs_verification_key; + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); // If we have previously recursively verified proofs, `previous_aggregation_object_nonzero = true` // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! @@ -79,15 +85,11 @@ std::array create_ho std::array inner_agg_points = agg_points_from_witness_indicies(builder, aggregation_input); // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included // in stdlib::recursion::verify_proof - // cur_aggregation_object.P0 += inner_agg_points[0]; // TODO: use a recursion separator - // cur_aggregation_object.P1 += inner_agg_points[1]; - cur_aggregation_object.P0 = inner_agg_points[0]; - cur_aggregation_object.P1 = inner_agg_points[1]; - UltraFlavor::VerifierCommitmentKey pcs_verification_key; - ASSERT(pcs_verification_key.pairing_check(inner_agg_points[0].get_value(), inner_agg_points[1].get_value())); - cur_aggregation_object.has_data = true; - } else { - cur_aggregation_object.has_data = false; + cur_aggregation_object.P0 += inner_agg_points[0] * recursion_separator; // TODO: use a recursion separator + cur_aggregation_object.P1 += inner_agg_points[1] * recursion_separator; + recursion_separator = recursion_separator * recursion_separator; + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); } std::vector key_fields; @@ -101,39 +103,38 @@ std::array create_ho // Prepend the public inputs to the proof fields because this is how the // core barretenberg library processes proofs (with the public inputs first and not separated) proof_fields.reserve(input.proof.size() + input.public_inputs.size()); - for (const auto& idx : input.public_inputs) { - auto field = field_ct::from_witness_index(&builder, idx); - proof_fields.emplace_back(field); - } + info("input.proof: ", input.proof); + size_t i = 0; + const size_t public_input_offset = 3; for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); + i++; + if (i == public_input_offset) { + for (const auto& idx : input.public_inputs) { + auto field = field_ct::from_witness_index(&builder, idx); + proof_fields.emplace_back(field); + } + } } + info("proof_fields: ", proof_fields); // recursively verify the proof auto vkey = std::make_shared(builder, key_fields); RecursiveVerifier verifier(&builder, vkey); std::array pairing_points = verifier.verify_proof(proof_fields); - UltraFlavor::VerifierCommitmentKey pcs_verification_key; ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); - // aggregate with the input aggregation object - if (cur_aggregation_object.has_data) { - info("agg points: ", cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value()); - info("pairing points: ", pairing_points[0].get_value(), pairing_points[1].get_value()); - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); - ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); - field_ct recursion_separator = bb::stdlib::witness_t(&builder, 2); - cur_aggregation_object.P0 += pairing_points[0] * recursion_separator; - cur_aggregation_object.P1 += pairing_points[1] * recursion_separator; - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); - } else { - cur_aggregation_object.P0 = pairing_points[0]; - cur_aggregation_object.P1 = pairing_points[1]; - cur_aggregation_object.has_data = true; - } + // aggregate the current aggregation object with these pairing points from verify_proof + info("agg points: ", cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value()); + info("pairing points: ", pairing_points[0].get_value(), pairing_points[1].get_value()); + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); + ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); + cur_aggregation_object.P0 += pairing_points[0] * recursion_separator; + cur_aggregation_object.P1 += pairing_points[1] * recursion_separator; + ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), + cur_aggregation_object.P1.get_value())); std::vector proof_witness_indices = { cur_aggregation_object.P0.x.binary_basis_limbs[0].element.normalize().witness_index, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index e9be6b9e284..94a718f25e2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -146,31 +146,36 @@ class AcirHonkRecursionConstraint : public ::testing::Test { const size_t num_inner_public_inputs = inner_circuit.get_public_inputs().size(); std::vector proof_witnesses = inner_proof; + info("proof witnesses: ", proof_witnesses); + const size_t public_input_offset = 3; // - Save the public inputs so that we can set their values. // - Then truncate them from the proof because the ACIR API expects proofs without public inputs std::vector inner_public_input_values( - proof_witnesses.begin(), - proof_witnesses.begin() + static_cast(num_inner_public_inputs - + proof_witnesses.begin() + static_cast(public_input_offset), + proof_witnesses.begin() + static_cast(public_input_offset + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); // We want to make sure that we do not remove the nested aggregation object. - proof_witnesses.erase(proof_witnesses.begin(), + proof_witnesses.erase(proof_witnesses.begin() + static_cast(public_input_offset), proof_witnesses.begin() + - static_cast(num_inner_public_inputs - + static_cast(public_input_offset + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); std::vector key_witnesses = verification_key->to_field_elements(); - const uint32_t public_input_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = static_cast(public_input_offset + witness_offset); const uint32_t proof_indices_start_idx = static_cast( public_input_start_idx + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE); const uint32_t key_indices_start_idx = - static_cast(proof_indices_start_idx + proof_witnesses.size()); + static_cast(proof_indices_start_idx + proof_witnesses.size() - public_input_offset); std::vector proof_indices; std::vector key_indices; std::vector inner_public_inputs; - for (size_t i = 0; i < proof_witnesses.size(); ++i) { + for (size_t i = 0; i < public_input_offset; ++i) { + proof_indices.emplace_back(static_cast(i + witness_offset)); + } + for (size_t i = 0; i < proof_witnesses.size() - public_input_offset; ++i) { proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); } const size_t key_size = key_witnesses.size(); @@ -191,11 +196,16 @@ class AcirHonkRecursionConstraint : public ::testing::Test { }; honk_recursion_constraints.push_back(honk_recursion_constraint); - for (size_t i = 0; i < proof_indices_start_idx - public_input_start_idx; ++i) { - witness.emplace_back(0); - } + size_t idx = 0; for (const auto& wit : proof_witnesses) { + info("wit ", wit, "at idx: ", witness.size()); witness.emplace_back(wit); + idx++; + if (idx == public_input_offset) { + for (size_t i = 0; i < proof_indices_start_idx - public_input_start_idx; ++i) { + witness.emplace_back(0); + } + } } for (const auto& wit : key_witnesses) { From f329435455ae5b2af1c5ed50c4b06e15358d2a5a Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Thu, 23 May 2024 20:57:43 +0000 Subject: [PATCH 07/10] fixed some of the comments, removed debug --- .../dsl/acir_format/acir_format.cpp | 10 +-- .../acir_format/honk_recursion_constraint.cpp | 65 ++++++++----------- .../acir_format/honk_recursion_constraint.hpp | 3 +- .../honk_recursion_constraint.test.cpp | 47 +++++++++----- 4 files changed, 63 insertions(+), 62 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 38ae60e4d02..d7d62cb6b51 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -238,23 +238,23 @@ void build_constraints(Builder& builder, // nested aggregation object attached to the proof as public inputs. As this is the only object that can // prepended to the proof if the proof is above the expected size (with public inputs stripped) std::array nested_aggregation_object = {}; - const size_t public_input_offset = 3; + const size_t inner_public_input_offset = 3; info("constraint proof: ", constraint.proof); for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { // Set the nested aggregation object indices to the current size of the public inputs // This way we know that the nested aggregation object indices will always be the last // indices of the public inputs - nested_aggregation_object[i] = static_cast(constraint.proof[public_input_offset + i]); + nested_aggregation_object[i] = static_cast(constraint.proof[inner_public_input_offset + i]); // Attach the nested aggregation object to the end of the public inputs to fill in // the slot where the nested aggregation object index will point into - constraint.public_inputs.emplace_back(constraint.proof[public_input_offset + i]); + constraint.public_inputs.emplace_back(constraint.proof[inner_public_input_offset + i]); } info("nested aggregation object: ", nested_aggregation_object); // Remove the aggregation object so that they can be handled as normal public inputs // in they way taht the recursion constraint expects - constraint.proof.erase(constraint.proof.begin() + public_input_offset, + constraint.proof.erase(constraint.proof.begin() + inner_public_input_offset, constraint.proof.begin() + - static_cast(public_input_offset + + static_cast(inner_public_input_offset + HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); current_aggregation_object = create_honk_recursion_constraints(builder, constraint, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index da88f77dac1..a1b9f4e5574 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -1,8 +1,4 @@ #include "honk_recursion_constraint.hpp" -#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" -#include "barretenberg/plonk/transcript/transcript_wrappers.hpp" -#include "barretenberg/stdlib/plonk_recursion/aggregation_state/aggregation_state.hpp" -#include "barretenberg/stdlib/plonk_recursion/verifier/verifier.hpp" #include "barretenberg/stdlib/primitives/bigfield/constants.hpp" #include "recursion_constraint.hpp" @@ -29,13 +25,13 @@ std::array agg_points_from_witness_indicies( } /** - * @brief Add constraints required to recursively verify an UltraPlonk proof + * @brief Add constraints required to recursively verify an UltraHonk proof * * @param builder * @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. + * @param input_aggregation_object. The aggregation object coming from previous Honk recursion constraints. + * @param nested_aggregation_object. The aggregation object coming from the inner proof. + * @param has_valid_witness_assignment. Do we have witnesses or are we just generating keys? * * @note We currently only support HonkRecursionConstraint where inner_proof_contains_recursive_proof = false. * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, @@ -52,8 +48,10 @@ std::array create_ho using RecursiveVerificationKey = Flavor::VerificationKey; using RecursiveVerifier = UltraRecursiveVerifier_; + // Ignore the case of invalid witness assignments for now. static_cast(has_valid_witness_assignments); - // actual nested + + // Construct aggregation points from the nested aggregation witness indices std::array nested_aggregation_points = agg_points_from_witness_indicies(builder, nested_aggregation_object); @@ -64,13 +62,10 @@ std::array create_ho aggregation_state_ct cur_aggregation_object; cur_aggregation_object.P0 = nested_aggregation_points[0]; cur_aggregation_object.P1 = nested_aggregation_points[1]; - info("aggregation object = ", cur_aggregation_object); - cur_aggregation_object.has_data = true; - field_ct recursion_separator = bb::stdlib::witness_t(&builder, 2); + cur_aggregation_object.has_data = true; // the nested aggregation object always exists - UltraFlavor::VerifierCommitmentKey pcs_verification_key; - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); + // TODO(https://github.com/AztecProtocol/barretenberg/issues/995): generate this challenge properly. + field_ct recursion_separator = bb::stdlib::witness_t(&builder, 2); // If we have previously recursively verified proofs, `previous_aggregation_object_nonzero = true` // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! @@ -83,13 +78,14 @@ std::array create_ho // not the first recursion constraint. if (!previous_aggregation_indices_all_zero) { std::array inner_agg_points = agg_points_from_witness_indicies(builder, aggregation_input); - // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included - // in stdlib::recursion::verify_proof - cur_aggregation_object.P0 += inner_agg_points[0] * recursion_separator; // TODO: use a recursion separator + // If we have a previous aggregation object, aggregate it into the current aggregation object. + // TODO(https://github.com/AztecProtocol/barretenberg/issues/995): Verify that using challenge and challenge + // squared is safe. + cur_aggregation_object.P0 += inner_agg_points[0] * recursion_separator; cur_aggregation_object.P1 += inner_agg_points[1] * recursion_separator; - recursion_separator = recursion_separator * recursion_separator; - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); + recursion_separator = + recursion_separator * + recursion_separator; // update the challenge to be challenge squared for the next aggregation } std::vector key_fields; @@ -100,41 +96,32 @@ std::array create_ho } std::vector proof_fields; - // Prepend the public inputs to the proof fields because this is how the - // core barretenberg library processes proofs (with the public inputs first and not separated) + // Insert the public inputs in the middle the proof fields after 'inner_public_input_offset' because this is how the + // core barretenberg library processes proofs (with the public inputs starting at the third element and not + // separate from the rest of the proof) proof_fields.reserve(input.proof.size() + input.public_inputs.size()); - info("input.proof: ", input.proof); size_t i = 0; - const size_t public_input_offset = 3; + const size_t inner_public_input_offset = 3; for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); i++; - if (i == public_input_offset) { + if (i == inner_public_input_offset) { for (const auto& idx : input.public_inputs) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); } } } - info("proof_fields: ", proof_fields); - // recursively verify the proof + // Recursively verify the proof auto vkey = std::make_shared(builder, key_fields); RecursiveVerifier verifier(&builder, vkey); std::array pairing_points = verifier.verify_proof(proof_fields); - ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); - - // aggregate the current aggregation object with these pairing points from verify_proof - info("agg points: ", cur_aggregation_object.P0.get_value(), cur_aggregation_object.P1.get_value()); - info("pairing points: ", pairing_points[0].get_value(), pairing_points[1].get_value()); - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); - ASSERT(pcs_verification_key.pairing_check(pairing_points[0].get_value(), pairing_points[1].get_value())); + + // Aggregate the current aggregation object with these pairing points from verify_proof cur_aggregation_object.P0 += pairing_points[0] * recursion_separator; cur_aggregation_object.P1 += pairing_points[1] * recursion_separator; - ASSERT(pcs_verification_key.pairing_check(cur_aggregation_object.P0.get_value(), - cur_aggregation_object.P1.get_value())); std::vector proof_witness_indices = { cur_aggregation_object.P0.x.binary_basis_limbs[0].element.normalize().witness_index, @@ -156,6 +143,8 @@ std::array create_ho }; auto result = cur_aggregation_object; result.proof_witness_indices = proof_witness_indices; + + // TODO(https://github.com/AztecProtocol/barretenberg/issues/996): investigate whether this is important. // ASSERT(result.public_inputs.size() == input.public_inputs.size()); // Assign the `public_input` field to the public input of the inner proof diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index 1b18131387b..fdcbb9b3a09 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -1,6 +1,5 @@ #pragma once #include "barretenberg/dsl/types.hpp" -#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include namespace acir_format { @@ -20,7 +19,7 @@ using namespace bb; * * @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 proof The honk 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 diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 94a718f25e2..6ef2a22d809 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -146,36 +146,46 @@ class AcirHonkRecursionConstraint : public ::testing::Test { const size_t num_inner_public_inputs = inner_circuit.get_public_inputs().size(); std::vector proof_witnesses = inner_proof; - info("proof witnesses: ", proof_witnesses); - const size_t public_input_offset = 3; + // where the inner public inputs start (after circuit_size, num_pub_inputs, pub_input_offset) + const size_t inner_public_input_offset = 3; // - Save the public inputs so that we can set their values. // - Then truncate them from the proof because the ACIR API expects proofs without public inputs std::vector inner_public_input_values( - proof_witnesses.begin() + static_cast(public_input_offset), - proof_witnesses.begin() + static_cast(public_input_offset + num_inner_public_inputs - - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); + proof_witnesses.begin() + static_cast(inner_public_input_offset), + proof_witnesses.begin() + + static_cast(inner_public_input_offset + num_inner_public_inputs - + RecursionConstraint::AGGREGATION_OBJECT_SIZE)); // We want to make sure that we do not remove the nested aggregation object. - proof_witnesses.erase(proof_witnesses.begin() + static_cast(public_input_offset), + proof_witnesses.erase(proof_witnesses.begin() + static_cast(inner_public_input_offset), proof_witnesses.begin() + - static_cast(public_input_offset + num_inner_public_inputs - + static_cast(inner_public_input_offset + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); std::vector key_witnesses = verification_key->to_field_elements(); - const uint32_t public_input_start_idx = static_cast(public_input_offset + witness_offset); - const uint32_t proof_indices_start_idx = static_cast( - public_input_start_idx + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE); + // This is the structure of proof_witnesses and key_witnesses concatenated, which is what we end up putting + // in witness: + // [ circuit size, num_pub_inputs, pub_input_offset, public_input_0, public_input_1, agg_obj_0, + // agg_obj_1, ..., agg_obj_15, rest of proof..., vkey_0, vkey_1, vkey_2, vkey_3...] + const uint32_t public_input_start_idx = + static_cast(inner_public_input_offset + witness_offset); // points to public_input_0 + const uint32_t proof_indices_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs - + RecursionConstraint::AGGREGATION_OBJECT_SIZE); // points to agg_obj_0 const uint32_t key_indices_start_idx = - static_cast(proof_indices_start_idx + proof_witnesses.size() - public_input_offset); + static_cast(proof_indices_start_idx + proof_witnesses.size() - + inner_public_input_offset); // would point to vkey_3 without the - + // inner_public_input_offset, points to vkey_0 std::vector proof_indices; std::vector key_indices; std::vector inner_public_inputs; - for (size_t i = 0; i < public_input_offset; ++i) { + for (size_t i = 0; i < inner_public_input_offset; ++i) { // go over circuit size, num_pub_inputs, pub_offset proof_indices.emplace_back(static_cast(i + witness_offset)); } - for (size_t i = 0; i < proof_witnesses.size() - public_input_offset; ++i) { + for (size_t i = 0; i < proof_witnesses.size() - inner_public_input_offset; + ++i) { // goes over agg_obj_0, agg_obj_1, ..., agg_obj_15 and rest of proof proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); } const size_t key_size = key_witnesses.size(); @@ -196,16 +206,19 @@ class AcirHonkRecursionConstraint : public ::testing::Test { }; honk_recursion_constraints.push_back(honk_recursion_constraint); + // Setting the witness vector which just appends proof witnesses and key witnesses. + // We need to reconstruct the proof witnesses in the same order as size_t idx = 0; for (const auto& wit : proof_witnesses) { - info("wit ", wit, "at idx: ", witness.size()); witness.emplace_back(wit); idx++; - if (idx == public_input_offset) { - for (size_t i = 0; i < proof_indices_start_idx - public_input_start_idx; ++i) { + if (idx == + inner_public_input_offset) { // before this is true, the loop adds the first three into witness + for (size_t i = 0; i < proof_indices_start_idx - public_input_start_idx; + ++i) { // adds the inner public inputs witness.emplace_back(0); } - } + } // after this, it adds the agg obj and rest of proof } for (const auto& wit : key_witnesses) { From b26555b9eca0fd6186efb39f8b9f0b28d584e17e Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Thu, 23 May 2024 21:39:04 +0000 Subject: [PATCH 08/10] small cleanup --- .../barretenberg/dsl/acir_format/acir_format.cpp | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 38dd13bb3a5..bed11aa5e54 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -222,10 +222,8 @@ void build_constraints(Builder& builder, // These are set and modified whenever we encounter a recursion opcode // // These should not be set by the caller - // TODO(maxim): Check if this is always the case. ie I won't receive a proof that will set the first - // TODO(maxim): input_aggregation_object to be non-zero. - // TODO(maxim): if not, we can add input_aggregation_object to the proof too for all recursive proofs - // TODO(maxim): This might be the case for proof trees where the proofs are created on different machines + // TODO(https://github.com/AztecProtocol/barretenberg/issues/996): this usage of all zeros is a hack and could + // use types or enums to properly fix. std::array current_aggregation_object = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; @@ -239,17 +237,13 @@ void build_constraints(Builder& builder, // prepended to the proof if the proof is above the expected size (with public inputs stripped) std::array nested_aggregation_object = {}; const size_t inner_public_input_offset = 3; - info("constraint proof: ", constraint.proof); for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { - // Set the nested aggregation object indices to the current size of the public inputs - // This way we know that the nested aggregation object indices will always be the last - // indices of the public inputs + // Set the nested aggregation object indices to witness indices nested_aggregation_object[i] = static_cast(constraint.proof[inner_public_input_offset + i]); // Attach the nested aggregation object to the end of the public inputs to fill in // the slot where the nested aggregation object index will point into constraint.public_inputs.emplace_back(constraint.proof[inner_public_input_offset + i]); } - info("nested aggregation object: ", nested_aggregation_object); // Remove the aggregation object so that they can be handled as normal public inputs // in they way taht the recursion constraint expects constraint.proof.erase(constraint.proof.begin() + inner_public_input_offset, @@ -299,7 +293,6 @@ void build_constraints(Builder& builder, x.slice(fq_ct::NUM_LIMB_BITS * 3, bb::stdlib::field_conversion::TOTAL_BITS) }; for (size_t i = 0; i < fq_ct::NUM_LIMBS; ++i) { - info("default object val limb: ", val_limbs[i]); uint32_t idx = builder.add_variable(val_limbs[i]); builder.set_public_input(idx); current_aggregation_object[agg_obj_indices_idx] = idx; From a8ce1da3add22bd1f2a9fc18ba30a0cb4dae64d4 Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Fri, 24 May 2024 18:32:13 +0000 Subject: [PATCH 09/10] changes to comments, making inner_public_input_offset part of HonkRecursionConstraint --- .../dsl/acir_format/acir_format.cpp | 20 +++++++++---------- .../acir_format/honk_recursion_constraint.cpp | 3 +-- .../acir_format/honk_recursion_constraint.hpp | 1 + 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index bed11aa5e54..6392020d6dd 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -233,22 +233,20 @@ void build_constraints(Builder& builder, // A proof passed into the constraint should be stripped of its inner public inputs, but not the nested // aggregation object itself. The verifier circuit requires that the indices to a nested proof aggregation // state are a circuit constant. The user tells us they how they want these constants set by keeping the - // nested aggregation object attached to the proof as public inputs. As this is the only object that can - // prepended to the proof if the proof is above the expected size (with public inputs stripped) + // nested aggregation object attached to the proof as public inputs. std::array nested_aggregation_object = {}; - const size_t inner_public_input_offset = 3; for (size_t i = 0; i < HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { - // Set the nested aggregation object indices to witness indices - nested_aggregation_object[i] = static_cast(constraint.proof[inner_public_input_offset + i]); - // Attach the nested aggregation object to the end of the public inputs to fill in - // the slot where the nested aggregation object index will point into - constraint.public_inputs.emplace_back(constraint.proof[inner_public_input_offset + i]); + // Set the nested aggregation object indices to witness indices from the proof + nested_aggregation_object[i] = + static_cast(constraint.proof[HonkRecursionConstraint::inner_public_input_offset + i]); + // Adding the nested aggregation object to the constraint's public inputs + constraint.public_inputs.emplace_back(nested_aggregation_object[i]); } // Remove the aggregation object so that they can be handled as normal public inputs - // in they way taht the recursion constraint expects - constraint.proof.erase(constraint.proof.begin() + inner_public_input_offset, + // in they way that the recursion constraint expects + constraint.proof.erase(constraint.proof.begin() + HonkRecursionConstraint::inner_public_input_offset, constraint.proof.begin() + - static_cast(inner_public_input_offset + + static_cast(HonkRecursionConstraint::inner_public_input_offset + HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); current_aggregation_object = create_honk_recursion_constraints(builder, constraint, diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index a1b9f4e5574..a6c09d05b7e 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -101,12 +101,11 @@ std::array create_ho // separate from the rest of the proof) proof_fields.reserve(input.proof.size() + input.public_inputs.size()); size_t i = 0; - const size_t inner_public_input_offset = 3; for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); i++; - if (i == inner_public_input_offset) { + if (i == HonkRecursionConstraint::inner_public_input_offset) { for (const auto& idx : input.public_inputs) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index fdcbb9b3a09..a2f94844f9d 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -44,6 +44,7 @@ using namespace bb; struct HonkRecursionConstraint { // 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 inner_public_input_offset = 3; 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 * fq_ct::NUM_LIMBS; // 16 field elements From aa9bfcc97fe8984231b713ee73ffd7f57866019a Mon Sep 17 00:00:00 2001 From: lucasxia01 Date: Fri, 24 May 2024 18:45:46 +0000 Subject: [PATCH 10/10] address comments --- .../dsl/acir_format/honk_recursion_constraint.cpp | 9 ++------- .../dsl/acir_format/honk_recursion_constraint.hpp | 5 ++++- .../dsl/acir_format/honk_recursion_constraint.test.cpp | 4 +++- .../cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp index a6c09d05b7e..54bf05f5e70 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.cpp @@ -143,13 +143,8 @@ std::array create_ho auto result = cur_aggregation_object; result.proof_witness_indices = proof_witness_indices; - // TODO(https://github.com/AztecProtocol/barretenberg/issues/996): investigate whether this is important. - // ASSERT(result.public_inputs.size() == input.public_inputs.size()); - - // Assign the `public_input` field to the public input of the inner proof - // for (size_t i = 0; i < input.public_inputs.size(); ++i) { - // result.public_inputs[i].assert_equal(field_ct::from_witness_index(&builder, input.public_inputs[i])); - // } + // TODO(https://github.com/AztecProtocol/barretenberg/issues/996): investigate whether assert_equal on public inputs + // is important, like what the plonk recursion constraint does. // We want to return an array, so just copy the vector into the array ASSERT(result.proof_witness_indices.size() == HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE); diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp index a2f94844f9d..bf70c2866f2 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.hpp @@ -40,11 +40,14 @@ using namespace bb; * 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) * + * TODO(https://github.com/AztecProtocol/barretenberg/issues/996): Update these comments for Honk. */ struct HonkRecursionConstraint { + // In Honk, the proof starts with circuit_size, num_public_inputs, and pub_input_offset. We use this offset to keep + // track of where the public inputs start. + static constexpr size_t inner_public_input_offset = 3; // 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 inner_public_input_offset = 3; 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 * fq_ct::NUM_LIMBS; // 16 field elements diff --git a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp index 6ef2a22d809..9eb67c39bef 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/honk_recursion_constraint.test.cpp @@ -207,7 +207,9 @@ class AcirHonkRecursionConstraint : public ::testing::Test { honk_recursion_constraints.push_back(honk_recursion_constraint); // Setting the witness vector which just appends proof witnesses and key witnesses. - // We need to reconstruct the proof witnesses in the same order as + // We need to reconstruct the proof witnesses in the same order as the proof indices, with this structure: + // [ circuit size, num_pub_inputs, pub_input_offset, public_input_0, public_input_1, agg_obj_0, + // agg_obj_1, ..., agg_obj_15, rest of proof..., vkey_0, vkey_1, vkey_2, vkey_3...] size_t idx = 0; for (const auto& wit : proof_witnesses) { witness.emplace_back(wit); diff --git a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp index cdd8acc6762..f32deb49155 100644 --- a/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp +++ b/barretenberg/cpp/src/barretenberg/ultra_honk/ultra_verifier.hpp @@ -26,7 +26,7 @@ template class UltraVerifier_ { bool verify_proof(const HonkProof& proof); std::shared_ptr key; - std::shared_ptr transcript; // this seems useless + std::shared_ptr transcript; }; using UltraVerifier = UltraVerifier_;