diff --git a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt index c5822d80b4c..0d504b53b65 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt +++ b/barretenberg/cpp/src/barretenberg/dsl/CMakeLists.txt @@ -9,4 +9,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 45ecf778496..bc44ee31956 100644 --- a/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/barretenberg/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -222,53 +222,32 @@ 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 }; - // 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. 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 - // indices of the public inputs - nested_aggregation_object[i] = static_cast(constraint.public_inputs.size()); - // 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]); + // 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(), + // in they way that the recursion constraint expects + constraint.proof.erase(constraint.proof.begin() + HonkRecursionConstraint::inner_public_input_offset, constraint.proof.begin() + - static_cast(HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); + static_cast(HonkRecursionConstraint::inner_public_input_offset + + HonkRecursionConstraint::AGGREGATION_OBJECT_SIZE)); current_aggregation_object = create_honk_recursion_constraints(builder, constraint, current_aggregation_object, 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..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 @@ -1,23 +1,37 @@ #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" namespace acir_format { -using namespace bb::plonk; +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 + * @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, @@ -30,88 +44,50 @@ 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); + // Ignore the case of invalid witness assignments for now. + static_cast(has_valid_witness_assignments); - 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); - } - } + // Construct aggregation points from the nested aggregation witness indices + 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; // the nested aggregation object always exists - // If we have previously recursively verified proofs, `inner_aggregation_object_nonzero = true` + // 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! - 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(); - } - // 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; - } else { - previous_aggregation.has_data = false; + // 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, 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; // update the challenge to be challenge squared for the next aggregation } - 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) { @@ -120,36 +96,55 @@ 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()); - for (const auto& idx : input.public_inputs) { - auto field = field_ct::from_witness_index(&builder, idx); - proof_fields.emplace_back(field); - } + size_t i = 0; for (const auto& idx : input.proof) { auto field = field_ct::from_witness_index(&builder, idx); proof_fields.emplace_back(field); + i++; + 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); + } + } } - // 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)); + // 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); + + // 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; + + std::vector proof_witness_indices = { + 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 = 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 - 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); @@ -161,210 +156,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. - * 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. - * 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; -} - -/** - * @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..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 @@ -1,11 +1,10 @@ #pragma once #include "barretenberg/dsl/types.hpp" -#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" #include namespace acir_format { -using namespace bb::plonk; +using namespace bb; /** * @brief HonkRecursionConstraint struct contains information required to recursively verify a proof! @@ -20,7 +19,7 @@ using namespace bb::plonk; * * @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 @@ -41,18 +40,20 @@ using namespace bb::plonk; * 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 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; - uint32_t key_hash; friend bool operator==(HonkRecursionConstraint const& lhs, HonkRecursionConstraint const& rhs) = default; }; @@ -64,13 +65,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(const PolynomialManifest& polynomial_manifest, - 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(); - } // 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..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 @@ -1,16 +1,20 @@ #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 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() { /** @@ -133,46 +137,55 @@ 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; + // 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(), - proof_witnesses.begin() + static_cast(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(), + proof_witnesses.erase(proof_witnesses.begin() + static_cast(inner_public_input_offset), proof_witnesses.begin() + - static_cast(num_inner_public_inputs - + static_cast(inner_public_input_offset + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE)); - std::vector key_witnesses = export_honk_key_in_recursion_format(inner_verifier.key); - 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 proof_indices_start_idx = static_cast( - public_input_start_idx + num_inner_public_inputs - RecursionConstraint::AGGREGATION_OBJECT_SIZE); + std::vector key_witnesses = verification_key->to_field_elements(); + + // 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()); + 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 < proof_witnesses.size(); ++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() - 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(); @@ -190,16 +203,24 @@ 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); - } + // Setting the witness vector which just appends proof witnesses and key witnesses. + // 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); + idx++; + 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) { @@ -270,11 +291,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 +349,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 +379,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); } 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 3e509653b11..7a8e5a0b1a7 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 c1b975ff910..1994da14b6d 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/stdlib_circuit_builders/mega_recursive_flavor.hpp b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_recursive_flavor.hpp index f4e662b1fde..8fb2b70c659 100644 --- a/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_recursive_flavor.hpp +++ b/barretenberg/cpp/src/barretenberg/stdlib_circuit_builders/mega_recursive_flavor.hpp @@ -166,22 +166,22 @@ template class MegaRecursiveFlavor_ { 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 aa5dd42eafa..2979b9ac0d0 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 @@ -332,22 +332,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; } }