diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 8f2c4d17f5..f356af7d46 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -93,6 +93,21 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) for (const auto& constraint : constraint_system.block_constraints) { create_block_constraints(composer, constraint); } + + // Add recursion constraints + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } + } } Composer create_circuit(const acir_format& constraint_system, @@ -116,6 +131,7 @@ Composer create_circuit(const acir_format& constraint_system, composer.add_variable(0); } } + // Add arithmetic gates for (const auto& constraint : constraint_system.constraints) { composer.create_poly_gate(constraint); @@ -182,6 +198,21 @@ Composer create_circuit(const acir_format& constraint_system, create_block_constraints(composer, constraint); } + // Add recursion constraints + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } + } + return composer; } @@ -276,6 +307,21 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, create_block_constraints(composer, constraint); } + // Add recursion constraints + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint, true); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } + } + return composer; } Composer create_circuit_with_witness(const acir_format& constraint_system, std::vector witness) @@ -367,6 +413,21 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: create_block_constraints(composer, constraint); } + // Add recursion constraints + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint, true); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } + } + return composer; } void create_circuit_with_witness(Composer& composer, const acir_format& constraint_system, std::vector witness) @@ -455,6 +516,21 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai for (const auto& constraint : constraint_system.block_constraints) { create_block_constraints(composer, constraint); } + + // Add recursion constraints + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint, true); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } + } } } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp index cf14558c31..92f54de398 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.hpp @@ -8,6 +8,7 @@ #include "schnorr_verify.hpp" #include "ecdsa_secp256k1.hpp" #include "compute_merkle_root_constraint.hpp" +#include "recursion_constraint.hpp" #include "block_constraint.hpp" #include "pedersen.hpp" #include "hash_to_field.hpp" @@ -33,6 +34,7 @@ struct acir_format { std::vector pedersen_constraints; std::vector compute_merkle_root_constraints; std::vector block_constraints; + std::vector recursion_constraints; // A standard plonk arithmetic constraint, as defined in the poly_triple struct, consists of selector values // for q_M,q_L,q_R,q_O,q_C and indices of three variables taking the role of left, right and output wire std::vector constraints; @@ -72,6 +74,7 @@ template inline void read(B& buf, acir_format& data) read(buf, data.pedersen_constraints); read(buf, data.hash_to_field_constraints); read(buf, data.fixed_base_scalar_mul_constraints); + read(buf, data.recursion_constraints); read(buf, data.constraints); read(buf, data.block_constraints); } @@ -92,6 +95,7 @@ template inline void write(B& buf, acir_format const& data) write(buf, data.pedersen_constraints); write(buf, data.hash_to_field_constraints); write(buf, data.fixed_base_scalar_mul_constraints); + write(buf, data.recursion_constraints); write(buf, data.constraints); write(buf, data.block_constraints); } diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp index b9fed48eb9..fd560e98ea 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.test.cpp @@ -5,6 +5,50 @@ #include "barretenberg/common/streams.hpp" #include "barretenberg/serialize/test_helper.hpp" #include "ecdsa_secp256k1.hpp" + +TEST(acir_format, test_a_single_constraint_no_pub_inputs) +{ + + poly_triple constraint{ + .a = 1, + .b = 2, + .c = 3, + .q_m = 0, + .q_l = 1, + .q_r = 1, + .q_o = -1, + .q_c = 0, + }; + + acir_format::acir_format constraint_system{ + .varnum = 4, + .public_inputs = {}, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = {}, + .constraints = { constraint }, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, { 0, 0, 1 }); + + auto prover = composer.create_ultra_with_keccak_prover(); + auto proof = prover.construct_proof(); + + auto verifier = composer.create_ultra_with_keccak_verifier(); + + EXPECT_EQ(verifier.verify_proof(proof), false); +} + TEST(acir_format, msgpack_logic_constraint) { auto [actual, expected] = msgpack_roundtrip(acir_format::LogicConstraint{}); @@ -97,6 +141,7 @@ TEST(acir_format, test_logic_gate_from_noir_circuit) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = { expr_a, expr_b, expr_c, expr_d }, }; @@ -163,6 +208,7 @@ TEST(acir_format, test_schnorr_verify_pass) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = { poly_triple{ .a = schnorr_constraint.result, .b = schnorr_constraint.result, @@ -234,6 +280,7 @@ TEST(acir_format, test_schnorr_verify_small_range) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = { poly_triple{ .a = schnorr_constraint.result, .b = schnorr_constraint.result, diff --git a/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp index 66ab73e84a..ae122849cd 100644 --- a/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/block_constraint.test.cpp @@ -117,6 +117,7 @@ TEST(up_ram, TestBlockConstraint) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = { block }, + .recursion_constraints = {}, .constraints = {}, }; diff --git a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp index fb8b3711b0..f0b1aab787 100644 --- a/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/ecdsa_secp256k1.test.cpp @@ -98,6 +98,7 @@ TEST(ECDSASecp256k1, TestECDSAConstraintSucceed) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = {}, }; @@ -134,6 +135,7 @@ TEST(ECDSASecp256k1, TestECDSACompilesForVerifier) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = {}, }; auto crs_factory = std::make_unique(); @@ -167,6 +169,7 @@ TEST(ECDSASecp256k1, TestECDSAConstraintFail) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, + .recursion_constraints = {}, .constraints = {}, }; diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp new file mode 100644 index 0000000000..32cb775cd3 --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -0,0 +1,347 @@ +#include "recursion_constraint.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" +#include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" +#include "barretenberg/stdlib/recursion/verifier/verifier.hpp" +#include "barretenberg/transcript/transcript_wrappers.hpp" + +namespace acir_format { + +using namespace proof_system::plonk; + +// `NUM_LIMB_BITS_IN_FIELD_SIMULATION` is the limb size when simulating a non-native field using the bigfield class +// A aggregation object is two acir_format::g1_ct types where each coordinate in a point is a non-native field. +// Each field is represented as four limbs. We split those limbs in half when serializing to/from buffer. +static constexpr uint64_t TWO_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 2; +static constexpr uint64_t FOUR_LIMBS_BITS_IN_FIELD_SIMULATION = NUM_LIMB_BITS_IN_FIELD_SIMULATION * 4; + +void generate_dummy_proof() {} +/** + * @brief Add constraints required to recursively verify an UltraPlonk proof + * + * @param composer + * @param input + * @tparam has_valid_witness_assignment. Do we have witnesses or are we just generating keys? + * @tparam inner_proof_contains_recursive_proof. Do we expect the inner proof to also have performed recursive + * verification? We need to know this at circuit-compile time. + * + * @note We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. + * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, + * or we need non-witness data to be provided as metadata in the ACIR opcode + */ +void create_recursion_constraints(Composer& composer, + const RecursionConstraint& input, + bool has_valid_witness_assignments) +{ + const auto& nested_aggregation_indices = input.nested_aggregation_object; + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_indices) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + + // If we do not have a witness, we must ensure that our dummy witness will not trigger + // on-curve errors and inverting-zero errors + { + // get a fake key/proof that satisfies on-curve + inversion-zero checks + const std::vector dummy_key = export_dummy_key_in_recursion_format(PolynomialManifest(Composer::type), + inner_proof_contains_recursive_proof); + const auto manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); + const std::vector dummy_proof = + export_dummy_transcript_in_recursion_format(manifest, inner_proof_contains_recursive_proof); + for (size_t i = 0; i < input.proof.size(); ++i) { + const auto proof_field_idx = input.proof[i]; + // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), + // we add our dummy proof values as Composer variables. + // if we DO have a valid witness assignment, we use the real witness assignment + barretenberg::fr dummy_field = + has_valid_witness_assignments ? composer.get_variable(proof_field_idx) : dummy_proof[i]; + // Create a copy constraint between our dummy field and the witness index provided by RecursionConstraint. + // This will make the RecursionConstraint idx equal to `dummy_field`. + // In the case of a valid witness assignment, this does nothing (as dummy_field = real value) + // In the case of no valid witness assignment, this makes sure that the RecursionConstraint witness indices + // will not trigger basic errors (check inputs are on-curve, check we are not inverting 0) + composer.assert_equal(composer.add_variable(dummy_field), proof_field_idx); + } + for (size_t i = 0; i < input.key.size(); ++i) { + const auto key_field_idx = input.key[i]; + barretenberg::fr dummy_field = + has_valid_witness_assignments ? composer.get_variable(key_field_idx) : dummy_key[i]; + composer.assert_equal(composer.add_variable(dummy_field), key_field_idx); + } + } + + // 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.input_aggregation_object; + aggregation_state_ct previous_aggregation; + + // If we have previously recursively verified proofs, `is_aggregation_object_nonzero = true` + // For now this is a complile-time constant i.e. whether this is true/false is fixed for the circuit! + bool inner_aggregation_indices_all_zero = true; + for (const auto& idx : aggregation_input) { + inner_aggregation_indices_all_zero &= (idx == 0); + } + if (!inner_aggregation_indices_all_zero) { + std::array aggregation_elements; + for (size_t i = 0; i < 4; ++i) { + aggregation_elements[i] = + bn254::fq_ct(field_ct::from_witness_index(&composer, aggregation_input[4 * i]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 1]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 2]), + field_ct::from_witness_index(&composer, aggregation_input[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included + // in stdlib::recursion::verify_proof + previous_aggregation.P0 = bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); + previous_aggregation.P1 = bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); + previous_aggregation.has_data = true; + } else { + previous_aggregation.has_data = false; + } + + transcript::Manifest manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); + + std::vector key_fields; + key_fields.reserve(input.key.size()); + for (const auto& idx : input.key) { + key_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + } + + std::vector proof_fields; + proof_fields.reserve(input.proof.size()); + for (const auto& idx : input.proof) { + proof_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); + } + + // recursively verify the proof + std::shared_ptr vkey = verification_key_ct::from_field_elements( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); + vkey->program_width = noir_recursive_settings::program_width; + Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); + aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( + &composer, vkey, transcript, previous_aggregation); + + // Assign correct witness value to the verification key hash + vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); + + 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(&composer, input.public_inputs[i])); + } + + // Assign the recursive proof outputs to `output_aggregation_object` + for (size_t i = 0; i < result.proof_witness_indices.size(); ++i) { + const auto lhs = field_ct::from_witness_index(&composer, result.proof_witness_indices[i]); + const auto rhs = field_ct::from_witness_index(&composer, input.output_aggregation_object[i]); + lhs.assert_equal(rhs); + } +} + +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * NOTE: only used by the dsl at the moment. Might be cleaner to make this a dsl function? + * + * @return std::vector + */ +std::vector export_key_in_recursion_format(std::shared_ptr const& vkey) +{ + std::vector output; + output.emplace_back(vkey->domain.root); + output.emplace_back(vkey->domain.domain); + output.emplace_back(vkey->domain.generator); + output.emplace_back(vkey->circuit_size); + output.emplace_back(vkey->num_public_inputs); + output.emplace_back(vkey->contains_recursive_proof); + for (size_t i = 0; i < RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { + if (vkey->recursive_proof_public_input_indices.size() > i) { + output.emplace_back(vkey->recursive_proof_public_input_indices[i]); + } else { + 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{ + .composer_type = vkey->composer_type, + .circuit_size = static_cast(vkey->circuit_size), + .num_public_inputs = static_cast(vkey->num_public_inputs), + .commitments = vkey->commitments, + .contains_recursive_proof = vkey->contains_recursive_proof, + .recursive_proof_public_input_indices = vkey->recursive_proof_public_input_indices, + }; + output.emplace_back(vkey_data.compress_native(0)); // key_hash + return output; +} + +/** + * @brief When recursively verifying proofs, we represent the verification key using field elements. + * This method exports the key formatted in the manner our recursive verifier expects. + * A dummy key is used when building a circuit without a valid witness assignment. + * We want the transcript to contain valid G1 points to prevent on-curve errors being thrown. + * We want a non-zero circuit size as this element will be inverted by the circuit + * and we do not want an "inverting 0" error thrown + * + * @return std::vector + */ +std::vector export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, + const bool contains_recursive_proof) +{ + std::vector output; + output.emplace_back(1); // domain.domain (will be inverted) + output.emplace_back(1); // domain.root (will be inverted) + output.emplace_back(1); // domain.generator (will be inverted) + + output.emplace_back(1); // circuit size + output.emplace_back(1); // num public inputs + + output.emplace_back(contains_recursive_proof); // contains_recursive_proof + for (size_t i = 0; i < RecursionConstraint::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 = barretenberg::fr::random_element(); + const auto element = barretenberg::g1::affine_element(barretenberg::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 barretenberg::fr. + * Used to represent recursive proofs (i.e. proof represented as circuit-native field elements) + * + * @return std::vector + */ +std::vector export_transcript_in_recursion_format(const transcript::StandardTranscript& transcript) +{ + std::vector fields; + const auto num_rounds = transcript.get_manifest().get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (const auto& manifest_element : transcript.get_manifest().get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(transcript.get_field_element(manifest_element.name)); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto group_element = transcript.get_group_element(manifest_element.name); + 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_transcript_in_recursion_format(const transcript::Manifest& manifest, + const bool contains_recursive_proof) +{ + std::vector fields; + const auto num_rounds = manifest.get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (const auto& manifest_element : manifest.get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + fields.emplace_back(0); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + // the std::biggroup class creates unsatisfiable constraints when identical points are + // added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not + // identical. And prover points should contain randomness for an honest Prover). This check can + // also trigger a runtime error due to causing 0 to be inverted. When creating dummy proof + // points we must be mindful of the above and make sure that each point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + 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) { + ASSERT(num_public_inputs == RecursionConstraint::AGGREGATION_OBJECT_SIZE); + for (size_t k = 0; k < RecursionConstraint::NUM_AGGREGATION_ELEMENTS; ++k) { + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + auto g1_as_fields = export_g1_affine_element_as_fields(group_element); + 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) { + fields.emplace_back(0); + } + } + } + } + } + } + return fields; +} + +G1AsFields export_g1_affine_element_as_fields(const barretenberg::g1::affine_element& group_element) +{ + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, TWO_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr x_hi = x.slice(TWO_LIMBS_BITS_IN_FIELD_SIMULATION, FOUR_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr y_lo = y.slice(0, TWO_LIMBS_BITS_IN_FIELD_SIMULATION); + const barretenberg::fr y_hi = y.slice(TWO_LIMBS_BITS_IN_FIELD_SIMULATION, FOUR_LIMBS_BITS_IN_FIELD_SIMULATION); + + return G1AsFields{ x_lo, x_hi, y_lo, y_hi }; +} + +} // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp new file mode 100644 index 0000000000..f99dc21230 --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -0,0 +1,110 @@ +#pragma once +#include +#include "barretenberg/dsl/types.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" + +namespace acir_format { + +using namespace proof_system::plonk; + +/** + * @brief RecursionConstraint struct contains information required to recursively verify a proof! + * + * @details The recursive verifier algorithm produces an 'aggregation object' representing 2 G1 points, expressed as 16 + * witness values. The smart contract Verifier must be aware of this aggregation object in order to complete the full + * recursive verification. If the circuit verifies more than 1 proof, the recursion algorithm will update a pre-existing + * aggregation object (`input_aggregation_object`). + * + * @details We currently require that the inner circuit being verified only has a single public input. If more are + * required, the outer circuit can hash them down to 1 input. + * + * @param verification_key_data The inner circuit vkey. Is converted into circuit witness values (internal to the + * backend) + * @param proof The plonk proof. Is converted into circuit witness values (internal to the backend) + * @param is_aggregation_object_nonzero A flag to tell us whether the circuit has already recursively verified proofs + * (and therefore an aggregation object is present) + * @param public_input The index of the single public input + * @param input_aggregation_object Witness indices of pre-existing aggregation object (if it exists) + * @param output_aggregation_object Witness indices of the aggregation object produced by recursive verification + * @param nested_aggregation_object Public input indices of an aggregation object inside the proof. + * + * @note If input_aggregation_object witness indices are all zero, we interpret this to mean that the inner proof does + * NOT contain a previously recursively verified proof + * @note nested_aggregation_object is used for cases where the proof being verified contains an aggregation object in + * its public inputs! If this is the case, we record the public input locations in `nested_aggregation_object`. If the + * inner proof is of a circuit that does not have a nested aggregation object, these values are all zero. + * + * To outline the interaction between the input_aggergation_object and the nested_aggregation_object take the following + * example: If we have a circuit that verifies 2 proofs A and B, the recursion constraint for B will have an + * input_aggregation_object that points to the aggregation output produced by verifying A. If circuit B also verifies a + * proof, in the above example the recursion constraint for verifying B will have a nested object that describes the + * aggregation object in B’s public inputs as well as an input aggregation object that points to the object produced by + * the previous recursion constraint in the circuit (the one that verifies A) + * + */ +struct RecursionConstraint { + // An aggregation state is represented by two G1 affine elements. Each G1 point has + // two field element coordinates (x, y). Thus, four field elements + static constexpr size_t NUM_AGGREGATION_ELEMENTS = 4; + // Four limbs are used when simulating a non-native field using the bigfield class + static constexpr size_t AGGREGATION_OBJECT_SIZE = + NUM_AGGREGATION_ELEMENTS * NUM_QUOTIENT_PARTS; // 16 field elements + std::vector key; + std::vector proof; + std::vector public_inputs; + uint32_t key_hash; + std::array input_aggregation_object; + std::array output_aggregation_object; + std::array nested_aggregation_object; + + friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; +}; + +void create_recursion_constraints(Composer& composer, + const RecursionConstraint& input, + bool has_valid_witness_assignments = false); + +std::vector export_key_in_recursion_format(std::shared_ptr const& vkey); +std::vector export_dummy_key_in_recursion_format(const PolynomialManifest& polynomial_manifest, + bool contains_recursive_proof = 0); + +std::vector export_transcript_in_recursion_format(const transcript::StandardTranscript& transcript); +std::vector export_dummy_transcript_in_recursion_format(const transcript::Manifest& manifest, + const bool contains_recursive_proof); + +// In order to interact with a recursive aggregation state inside of a circuit, we need to represent its internal G1 +// elements as field elements. This happens in multiple locations when creating a recursion constraint. The struct and +// method below export a g1 affine element as fields to use as part of the recursive circuit. +struct G1AsFields { + barretenberg::fr x_lo; + barretenberg::fr x_hi; + barretenberg::fr y_lo; + barretenberg::fr y_hi; +}; +G1AsFields export_g1_affine_element_as_fields(const barretenberg::g1::affine_element& group_element); + +template inline void read(B& buf, RecursionConstraint& constraint) +{ + using serialize::read; + read(buf, constraint.key); + read(buf, constraint.proof); + read(buf, constraint.public_inputs); + read(buf, constraint.key_hash); + read(buf, constraint.input_aggregation_object); + read(buf, constraint.output_aggregation_object); + read(buf, constraint.nested_aggregation_object); +} + +template inline void write(B& buf, RecursionConstraint const& constraint) +{ + using serialize::write; + write(buf, constraint.key); + write(buf, constraint.proof); + write(buf, constraint.public_inputs); + write(buf, constraint.key_hash); + write(buf, constraint.input_aggregation_object); + write(buf, constraint.output_aggregation_object); + write(buf, constraint.nested_aggregation_object); +} + +} // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp new file mode 100644 index 0000000000..82e6135622 --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -0,0 +1,301 @@ +#include "acir_format.hpp" +#include "recursion_constraint.hpp" +#include "barretenberg/plonk/proof_system/types/proof.hpp" +#include "barretenberg/plonk/proof_system/verification_key/verification_key.hpp" + +#include +#include + +using namespace proof_system::plonk; + +acir_format::Composer create_inner_circuit() +{ + /** + * constraints produced by Noir program: + * fn main(x : u32, y : pub u32) { + * let z = x ^ y; + * + * constrain z != 10; + * } + **/ + acir_format::RangeConstraint range_a{ + .witness = 1, + .num_bits = 32, + }; + acir_format::RangeConstraint range_b{ + .witness = 2, + .num_bits = 32, + }; + + acir_format::LogicConstraint logic_constraint{ + .a = 1, + .b = 2, + .result = 3, + .num_bits = 32, + .is_xor_gate = 1, + }; + poly_triple expr_a{ + .a = 3, + .b = 4, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = 0, + .q_c = -10, + }; + poly_triple expr_b{ + .a = 4, + .b = 5, + .c = 6, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }; + poly_triple expr_c{ + .a = 4, + .b = 6, + .c = 4, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + + }; + poly_triple expr_d{ + .a = 6, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = -1, + .q_r = 0, + .q_o = 0, + .q_c = 1, + }; + + acir_format::acir_format constraint_system{ + .varnum = 7, + .public_inputs = { 2, 3 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = {}, + .constraints = { expr_a, expr_b, expr_c, expr_d }, + }; + + uint256_t inverse_of_five = fr(5).invert(); + auto composer = acir_format::create_circuit_with_witness(constraint_system, + { + 5, + 10, + 15, + 5, + inverse_of_five, + 1, + }); + + return composer; +} + +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +acir_format::Composer create_outer_circuit(std::vector& inner_composers) +{ + std::vector recursion_constraints; + + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; + + for (size_t i = 0; i < inner_composers.size(); ++i) { + const bool has_input_aggregation_object = i > 0; + + auto& inner_composer = inner_composers[i]; + auto inner_prover = inner_composer.create_prover(); + auto inner_proof = inner_prover.construct_proof(); + auto inner_verifier = inner_composer.create_verifier(); + + const bool has_nested_proof = inner_verifier.key->contains_recursive_proof; + const size_t num_inner_public_inputs = inner_composer.get_num_public_inputs(); + + transcript::StandardTranscript transcript(inner_proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); + + const std::vector proof_witnesses = + acir_format::export_transcript_in_recursion_format(transcript); + const std::vector key_witnesses = + acir_format::export_key_in_recursion_format(inner_verifier.key); + + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); + + std::vector proof_indices; + std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 0; i < 16; ++i) { + nested_aggregation_object[i] = inner_composer.recursive_proof_public_input_indices[i]; + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); + } + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); + } + + acir_format::RecursionConstraint recursion_constraint{ + .key = key_indices, + .proof = proof_indices, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, + }; + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { + witness.emplace_back(0); + } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } + witness_offset = key_indices_start_idx + key_witnesses.size(); + } + + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); + + acir_format::acir_format constraint_system{ + .varnum = static_cast(witness.size() + 1), + .public_inputs = public_inputs, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = recursion_constraints, + .constraints = {}, + }; + + auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); + + return composer; +} + +TEST(RecursionConstraint, TestBasicDoubleRecursionConstraints) +{ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + + layer_1_composers.push_back(create_inner_circuit()); + + auto layer_2_composer = create_outer_circuit(layer_1_composers); + + std::cout << "composer gates = " << layer_2_composer.get_num_gates() << std::endl; + auto prover = layer_2_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; + auto proof = prover.construct_proof(); + auto verifier = layer_2_composer.create_ultra_with_keccak_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} + +TEST(RecursionConstraint, TestFullRecursionConstraints) +{ + /** + * We want to test the following: + * 1. circuit that verifies a proof of another circuit + * 2. the above, but the inner circuit contains a recursive proof output that we have to aggregate + * 3. the above, but the outer circuit verifies 2 proofs, the aggregation outputs from the 2 proofs (+ the recursive + * proof output from 2) are aggregated together + * + * A = basic circuit + * B = circuit that verifies proof of A + * C = circuit that verifies proof of B and a proof of A + * + * Layer 1 = proof of A + * Layer 2 = verifies proof of A and proof of B + * Layer 3 = verifies proof of C + * + * Attempt at a visual graphic + * =========================== + * + * C + * ^ + * | + * | - B + * ^ ^ + * | | + * | -A + * | + * - A + * + * =========================== + * + * Final aggregation object contains aggregated proofs for 2 instances of A and 1 instance of B + */ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + std::cout << "created first inner circuit\n"; + std::vector layer_2_composers; + + layer_2_composers.push_back(create_inner_circuit()); + std::cout << "created second inner circuit\n"; + + layer_2_composers.push_back(create_outer_circuit(layer_1_composers)); + std::cout << "created first outer circuit\n"; + + auto layer_3_composer = create_outer_circuit(layer_2_composers); + std::cout << "created second outer circuit\n"; + + std::cout << "composer gates = " << layer_3_composer.get_num_gates() << std::endl; + auto prover = layer_3_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; + auto proof = prover.construct_proof(); + auto verifier = layer_3_composer.create_ultra_with_keccak_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); +} diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 2010c592fb..62dc6460d4 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -5,6 +5,7 @@ #include "barretenberg/dsl/types.hpp" #include "barretenberg/srs/reference_string/pippenger_reference_string.hpp" #include "barretenberg/plonk/proof_system/verification_key/sol_gen.hpp" +#include "barretenberg/stdlib/recursion/verifier/verifier.hpp" namespace acir_proofs { @@ -54,6 +55,7 @@ size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk // Hacky, but, right now it needs *something*. auto crs_factory = std::make_unique(); auto composer = create_circuit(constraint_system, std::move(crs_factory)); + auto proving_key = composer.compute_proving_key(); auto buffer = to_buffer(*proving_key); @@ -83,6 +85,18 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* // construct the verification key so that we have the correct polynomial manifest verification_key->composer_type = proof_system::ComposerType::PLOOKUP; + // Set the recursive proof indices as this is not done in `compute_verification_key_base` + verification_key->contains_recursive_proof = proving_key->contains_recursive_proof; + for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { + if (proving_key->recursive_proof_public_input_indices.size() > i) { + verification_key->recursive_proof_public_input_indices.emplace_back( + proving_key->recursive_proof_public_input_indices[i]); + } else { + verification_key->recursive_proof_public_input_indices.emplace_back(0); + ASSERT(verification_key->contains_recursive_proof == false); + } + } + auto buffer = to_buffer(*verification_key); auto raw_buf = (uint8_t*)malloc(buffer.size()); memcpy(raw_buf, (void*)buffer.data(), buffer.size()); @@ -96,7 +110,8 @@ size_t new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf) + uint8_t** proof_data_buf, + bool is_recursive) { auto constraint_system = from_buffer(constraint_system_buf); @@ -115,17 +130,28 @@ size_t new_proof(void* pippenger, create_circuit_with_witness(composer, constraint_system, witness); - auto prover = composer.create_ultra_with_keccak_prover(); - - auto heapProver = new acir_format::Prover(std::move(prover)); - auto& proof_data = heapProver->construct_proof().proof_data; - *proof_data_buf = proof_data.data(); - - return proof_data.size(); + // Either need a context flag for recursive proofs or a new_recursive_proof method that uses regular UltraProver + if (is_recursive) { + auto prover = composer.create_prover(); + auto heapProver = new acir_format::RecursiveProver(std::move(prover)); + auto& proof_data = heapProver->construct_proof().proof_data; + *proof_data_buf = proof_data.data(); + return proof_data.size(); + } else { + auto prover = composer.create_ultra_with_keccak_prover(); + auto heapProver = new acir_format::Prover(std::move(prover)); + auto& proof_data = heapProver->construct_proof().proof_data; + *proof_data_buf = proof_data.data(); + return proof_data.size(); + } } -bool verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +bool verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive) { bool verified = false; @@ -142,9 +168,18 @@ bool verify_proof( create_circuit(composer, constraint_system); plonk::proof pp = { std::vector(proof, proof + length) }; - auto verifier = composer.create_ultra_with_keccak_verifier(); + // For inner circuit use recursive prover and verifier, then for outer circuit use the normal prover and + // verifier. + // Either need a context flag for recursive verify or a new_recursive_verify_proof method that uses regular + // UltraVerifier + if (is_recursive) { + auto verifier = composer.create_verifier(); + verified = verifier.verify_proof(pp); + } else { + auto verifier = composer.create_ultra_with_keccak_verifier(); + verified = verifier.verify_proof(pp); + } - verified = verifier.verify_proof(pp); #ifndef __wasm__ } catch (const std::exception& e) { verified = false; @@ -154,4 +189,200 @@ bool verify_proof( return verified; } +/** + * @brief Enables simulation of recursion in the ACVM. In order to be able to simulate execute of ACIR, + * a DSL needs to be able to insert into a circuit's witness the correct output aggregation state of a recursive + * verification. The resulting aggergation state is what will be compared against in the recursion constraint. + * + * @param proof_buf - The proof to be verified + * @param proof_length - The length of the proof to enable reading the proof from buffer + * @param vk_buf - The verification key of the circuit being verified + * @param vk_length - The length of the verification eky to enable reading the proof from buffer + * @param public_inputs_buf - The public inputs of the proof being verified. To be included when construct the input + * aggregation state + * @param input_aggregation_obj_buf - The fields represnting the two G1 points that must be fed into a pairing. + * This will be empty if this is the first proof to be recursively. + * @param output_aggregation_obj_buf - Two G1 points that the top-level verifier needs to run a pairing upon to complete + * verification + */ +size_t verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint32_t num_public_inputs, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf) +{ + const size_t NUM_AGGREGATION_ELEMENTS = acir_format::RecursionConstraint::NUM_AGGREGATION_ELEMENTS; + + bool inner_aggregation_all_zero = true; + std::vector aggregation_input(acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE); + for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; i++) { + aggregation_input[i] = barretenberg::fr::serialize_from_buffer(&input_aggregation_obj_buf[i * 32]); + inner_aggregation_all_zero &= (aggregation_input[i].is_zero()); + } + + acir_format::aggregation_state_ct previous_aggregation; + if (!inner_aggregation_all_zero) { + std::array aggregation_elements; + for (size_t i = 0; i < NUM_AGGREGATION_ELEMENTS; ++i) { + aggregation_elements[i] = + acir_format::bn254::fq_ct(acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 1]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 2]), + acir_format::field_ct(aggregation_input[NUM_AGGREGATION_ELEMENTS * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included + // in stdlib::recursion::verify_proof + previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); + previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); + previous_aggregation.has_data = true; + } else { + previous_aggregation.has_data = false; + } + + acir_format::Composer composer; + + std::vector proof_fields(proof_length / 32); + std::vector key_fields(vk_length / 32); + for (size_t i = 0; i < proof_length / 32; i++) { + // TODO(maxim): The stdlib pairing primitives fetch the context from the elements being used in the pairing + // computation When attempting to simulate recursive verification without a full circuit where the context of + // these elements are not set we will get a seg fault. Using `witness_ct` here provides a workaround where these + // elements will have their context set. We should enable the native verifier to return an aggregation state so + // that we can avoid creating an unnecessary circuit and this workaround + proof_fields[i] = acir_format::field_ct( + acir_format::witness_ct(&composer, barretenberg::fr::serialize_from_buffer(&proof_buf[i * 32]))); + } + for (size_t i = 0; i < vk_length / 32; i++) { + key_fields[i] = acir_format::field_ct(barretenberg::fr::serialize_from_buffer(&vk_buf[i * 32])); + } + + transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); + + std::array nested_aggregation_object = {}; + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); + } + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_object) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + std::shared_ptr vkey = acir_format::verification_key_ct::from_field_elements( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); + vkey->program_width = acir_format::noir_recursive_settings::program_width; + acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, num_public_inputs); + acir_format::aggregation_state_ct result = + proof_system::plonk::stdlib::recursion::verify_proof_( + &composer, vkey, transcript, previous_aggregation); + + // Just write the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + // as this should all be available elsewhere + const size_t output_size_bytes = + acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + + for (size_t i = 0; i < 4; ++i) { + auto field = result.P0.x.binary_basis_limbs[i].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 4; i < 8; ++i) { + auto field = result.P0.y.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 8; i < 12; ++i) { + auto field = result.P1.x.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + for (size_t i = 12; i < 16; ++i) { + auto field = result.P1.y.binary_basis_limbs[i % 4].element.get_value(); + barretenberg::fr::serialize_to_buffer(field, &raw_buf[i * 32]); + } + + *output_aggregation_obj_buf = raw_buf; + + return output_size_bytes; +} + +/** + * @brief Takes in a proof buffer and converts into a vector of field elements. + * The Recursion opcode requires the proof serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param proof_data_buf + * @param serialized_proof_data_buf + */ +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs) +{ + plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; + + transcript::StandardTranscript transcript(proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); + + std::vector output = acir_format::export_transcript_in_recursion_format(transcript); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr); + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size(); ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } + *serialized_proof_data_buf = raw_buf; + + return output_size_bytes; +} + +/** + * @brief Takes in a verification key buffer and converts into a vector of field elements. + * The Recursion opcode requires the vk serialized as a vector of witnesses. + * Use this method to get the witness values! + * + * @param vk_buf + * @param serialized_vk_buf + */ +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) +{ + auto crs = std::make_shared(g2x); + plonk::verification_key_data vk_data; + read(vk_buf, vk_data); + auto vkey = std::make_shared(std::move(vk_data), crs); + std::vector output = acir_format::export_key_in_recursion_format(vkey); + + // NOTE: this output buffer will always have a fixed size! Maybe just precompute? + // Cut off 32 bytes as last element is the verification key hash which is not part of the key :o + const size_t output_size_bytes = output.size() * sizeof(barretenberg::fr) - 32; + + auto raw_buf = (uint8_t*)malloc(output_size_bytes); + auto vk_hash_raw_buf = (uint8_t*)malloc(32); + + // The serialization code below will convert out of Montgomery form before writing to the buffer + for (size_t i = 0; i < output.size() - 1; ++i) { + barretenberg::fr::serialize_to_buffer(output[i], &raw_buf[i * 32]); + } + barretenberg::fr::serialize_to_buffer(output[output.size() - 1], vk_hash_raw_buf); + + // copy the vkey into serialized_vk_buf + *serialized_vk_buf = raw_buf; + + // copy the vkey hash into serialized_vk_hash_buf + *serialized_vk_hash_buf = vk_hash_raw_buf; + + return output_size_bytes; +} + } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index 4bd126aae9..24df5e4f3e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -13,8 +13,30 @@ size_t new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf); -bool verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); + uint8_t** proof_data_buf, + bool is_recursive); +bool verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive); + +// Recursion specific methods +size_t verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint32_t num_public_inputs, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf); +size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); +size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs); } // namespace acir_proofs diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp new file mode 100644 index 0000000000..fadbe87d42 --- /dev/null +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -0,0 +1,511 @@ +#include "acir_proofs.hpp" +#include "../acir_format/acir_format.hpp" +#include "barretenberg/srs/io.hpp" +#include +#include + +using namespace proof_system::plonk; + +void create_inner_circuit(acir_format::acir_format& constraint_system, std::vector& witness) +{ + /** + * constraints produced by Noir program: + * fn main(x : u32, y : pub u32) { + * let z = x ^ y; + * + * constrain z != 10; + * } + **/ + acir_format::RangeConstraint range_a{ + .witness = 1, + .num_bits = 32, + }; + acir_format::RangeConstraint range_b{ + .witness = 2, + .num_bits = 32, + }; + + acir_format::LogicConstraint logic_constraint{ + .a = 1, + .b = 2, + .result = 3, + .num_bits = 32, + .is_xor_gate = 1, + }; + poly_triple expr_a{ + .a = 3, + .b = 4, + .c = 0, + .q_m = 0, + .q_l = 1, + .q_r = -1, + .q_o = 0, + .q_c = -10, + }; + poly_triple expr_b{ + .a = 4, + .b = 5, + .c = 6, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + }; + poly_triple expr_c{ + .a = 4, + .b = 6, + .c = 4, + .q_m = 1, + .q_l = 0, + .q_r = 0, + .q_o = -1, + .q_c = 0, + + }; + poly_triple expr_d{ + .a = 6, + .b = 0, + .c = 0, + .q_m = 0, + .q_l = -1, + .q_r = 0, + .q_o = 0, + .q_c = 1, + }; + + constraint_system = acir_format::acir_format{ + .varnum = 7, + .public_inputs = { 2, 3 }, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = { logic_constraint }, + .range_constraints = { range_a, range_b }, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = {}, + .constraints = { expr_a, expr_b, expr_c, expr_d }, + }; + + uint256_t inverse_of_five = fr(5).invert(); + + witness.emplace_back(5); + witness.emplace_back(10); + witness.emplace_back(15); + witness.emplace_back(5); + witness.emplace_back(inverse_of_five); + witness.emplace_back(1); +} + +TEST(AcirProofs, TestSerialization) +{ + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); +} + +// Test that the method `verify_recursive_proof` can successfully simulate +// an output aggregation state +TEST(AcirProofs, TestVerifyRecursiveProofPass) +{ + uint8_t* proof_data_fields = nullptr; + uint8_t* vk_fields = nullptr; + uint8_t* vk_hash_buf = nullptr; + size_t proof_fields_size = 0; + size_t vk_fields_size = 0; + + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + auto num_public_inputs = constraint_system.public_inputs.size(); + proof_fields_size = acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, &proof_data_fields, proof_length, num_public_inputs); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + + const size_t output_size_bytes = + acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE * sizeof(barretenberg::fr); + auto input_agg_buf = (uint8_t*)malloc(output_size_bytes); + + for (size_t i = 0; i < 16; ++i) { + auto field = barretenberg::fr::zero(); + barretenberg::fr::serialize_to_buffer(field, &input_agg_buf[i * 32]); + } + + uint8_t* output_agg_buf = nullptr; + acir_proofs::verify_recursive_proof(proof_data_fields, + static_cast(proof_fields_size), + vk_fields, + static_cast(vk_fields_size), + static_cast(num_public_inputs), + input_agg_buf, + &output_agg_buf); + + free((void*)proof_data_fields); + free((void*)vk_fields); +} + +struct RecursiveCircuitData { + std::vector key_witnesses; + std::vector proof_witnesses; + size_t num_public_inputs; +}; + +RecursiveCircuitData fetch_recursive_circuit_data(acir_format::acir_format constraint_system, std::vector witness) +{ + uint8_t* proof_data_fields = nullptr; + uint8_t* vk_fields = nullptr; + uint8_t* vk_hash_buf = nullptr; + size_t proof_fields_size = 0; + size_t vk_fields_size = 0; + + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + auto num_public_inputs = constraint_system.public_inputs.size(); + proof_fields_size = acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, &proof_data_fields, proof_length, num_public_inputs); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + + fr vk_hash_value; + std::vector proof_witnesses(proof_fields_size / 32); + std::vector key_witnesses((vk_fields_size / 32) + 1); + for (size_t i = 0; i < proof_fields_size / 32; i++) { + proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); + } + for (size_t i = 0; i < vk_fields_size / 32; i++) { + key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); + } + + free((void*)proof_data_fields); + free((void*)vk_fields); + + vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); + key_witnesses[vk_fields_size / 32] = vk_hash_value; + auto inner_circuit = RecursiveCircuitData{ key_witnesses, proof_witnesses, num_public_inputs }; + return inner_circuit; +} + +RecursiveCircuitData fetch_inner_circuit_data() +{ + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + return fetch_recursive_circuit_data(constraint_system, witness); +} + +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +std::pair> create_outer_circuit( + std::vector& inner_circuits) +{ + std::vector recursion_constraints; + + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; + + for (size_t i = 0; i < inner_circuits.size(); ++i) { + const bool has_input_aggregation_object = i > 0; + + auto& inner_circuit = inner_circuits[i]; + const std::vector proof_witnesses = inner_circuit.proof_witnesses; + const std::vector key_witnesses = inner_circuit.key_witnesses; + + const bool has_nested_proof = uint32_t(key_witnesses[5]); + + const size_t num_inner_public_inputs = inner_circuit.num_public_inputs; + + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); + + std::vector proof_indices; + std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_witnesses[i]); + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); + } + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); + } + + acir_format::RecursionConstraint recursion_constraint{ + .key = key_indices, + .proof = proof_indices, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, + }; + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { + witness.emplace_back(0); + } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } + witness_offset = key_indices_start_idx + key_witnesses.size(); + } + + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); + + acir_format::acir_format constraint_system{ + .varnum = static_cast(witness.size() + 1), + .public_inputs = public_inputs, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = recursion_constraints, + .constraints = {}, + }; + + return std::make_pair(constraint_system, witness); +} + +// Working double recursion test +TEST(AcirProofs, TestSerializationWithBasicDoubleRecursion) +{ + std::vector inner_circuits; + auto a = fetch_inner_circuit_data(); + inner_circuits.push_back(a); + + auto b = fetch_inner_circuit_data(); + inner_circuits.push_back(b); + + auto c = create_outer_circuit(inner_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c.first); + write(witness_buf, c.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); +} + +TEST(AcirProofs, TestSerializationWithFullRecursiveComposition) +{ + std::vector layer_1_circuits; + auto a_data_circuit = fetch_inner_circuit_data(); + layer_1_circuits.push_back(a_data_circuit); + + std::vector layer_2_circuits; + auto a2_circuit = fetch_inner_circuit_data(); + layer_2_circuits.push_back(a2_circuit); + + auto b_cs_and_witness = create_outer_circuit(layer_1_circuits); + auto b_circuit = fetch_recursive_circuit_data(b_cs_and_witness.first, b_cs_and_witness.second); + layer_2_circuits.push_back(b_circuit); + + auto c_cs_and_witness = create_outer_circuit(layer_2_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c_cs_and_witness.first); + write(witness_buf, c_cs_and_witness.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); +} diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 4051aada3a..34b735c67e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -40,14 +40,52 @@ WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf) + uint8_t** proof_data_buf, + bool is_recursive) { - return acir_proofs::new_proof(pippenger, g2x, pk_buf, constraint_system_buf, witness_buf, proof_data_buf); + return acir_proofs::new_proof( + pippenger, g2x, pk_buf, constraint_system_buf, witness_buf, proof_data_buf, is_recursive); } -WASM_EXPORT bool acir_proofs_verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length) +WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive) { - return acir_proofs::verify_proof(g2x, vk_buf, constraint_system_buf, proof, length); + return acir_proofs::verify_proof(g2x, vk_buf, constraint_system_buf, proof, length, is_recursive); +} +WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint32_t num_public_inputs, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf) +{ + return acir_proofs::verify_recursive_proof(proof_buf, + proof_length, + vk_buf, + vk_length, + num_public_inputs, + input_aggregation_obj_buf, + output_aggregation_obj_buf); +} +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf) +{ + return acir_proofs::serialize_verification_key_into_field_elements( + g2x, vk_buf, serialized_vk_buf, serialized_vk_hash_buf); +} +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs) +{ + return acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, serialized_proof_data_buf, proof_data_length, num_inner_public_inputs); } } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index a628c928cb..5e42747b57 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -19,7 +19,29 @@ WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* pk_buf, uint8_t const* constraint_system_buf, uint8_t const* witness_buf, - uint8_t** proof_data_buf); -WASM_EXPORT bool acir_proofs_verify_proof( - uint8_t const* g2x, uint8_t const* vk_buf, uint8_t const* constraint_system_buf, uint8_t* proof, uint32_t length); + uint8_t** proof_data_buf, + bool is_recursive); +WASM_EXPORT bool acir_proofs_verify_proof(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t const* constraint_system_buf, + uint8_t* proof, + uint32_t length, + bool is_recursive); + +// Recursion specific methods +WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, + uint32_t proof_length, + uint8_t const* vk_buf, + uint32_t vk_length, + uint32_t num_public_inputs, + uint8_t const* input_aggregation_obj_buf, + uint8_t** output_aggregation_obj_buf); +WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t const* g2x, + uint8_t const* vk_buf, + uint8_t** serialized_vk_buf, + uint8_t** serialized_vk_hash_buf); +WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, + uint8_t** serialized_proof_data_buf, + size_t proof_data_length, + size_t num_inner_public_inputs); } diff --git a/cpp/src/barretenberg/dsl/types.hpp b/cpp/src/barretenberg/dsl/types.hpp index e5277faf2d..f9823984bf 100644 --- a/cpp/src/barretenberg/dsl/types.hpp +++ b/cpp/src/barretenberg/dsl/types.hpp @@ -20,6 +20,10 @@ #include "barretenberg/stdlib/primitives/curves/secp256k1.hpp" #include "barretenberg/stdlib/primitives/memory/rom_table.hpp" #include "barretenberg/stdlib/primitives/memory/ram_table.hpp" +#include "barretenberg/stdlib/recursion/verifier/program_settings.hpp" +#include "barretenberg/stdlib/recursion/verification_key/verification_key.hpp" +#include "barretenberg/stdlib/recursion/aggregation_state/aggregation_state.hpp" + namespace acir_format { using Composer = plonk::UltraComposer; @@ -34,6 +38,8 @@ using Verifier = std::conditional_t< plonk::UltraWithKeccakVerifier, std::conditional_t, plonk::TurboVerifier, plonk::Verifier>>; +using RecursiveProver = plonk::UltraProver; + using witness_ct = proof_system::plonk::stdlib::witness_t; using public_witness_ct = proof_system::plonk::stdlib::public_witness_t; using bool_ct = proof_system::plonk::stdlib::bool_t; @@ -58,8 +64,13 @@ using hash_path_ct = proof_system::plonk::stdlib::merkle_tree::hash_path; -// Ultra-composer specific typesv +// Ultra-composer specific types using rom_table_ct = proof_system::plonk::stdlib::rom_table; using ram_table_ct = proof_system::plonk::stdlib::ram_table; +using verification_key_ct = proof_system::plonk::stdlib::recursion::verification_key; +using aggregation_state_ct = proof_system::plonk::stdlib::recursion::aggregation_state; +using noir_recursive_settings = proof_system::plonk::stdlib::recursion::recursive_ultra_verifier_settings; +using Transcript_ct = proof_system::plonk::stdlib::recursion::Transcript; + } // namespace acir_format diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index a3e57bc383..8c8a92656b 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -186,6 +186,19 @@ class ComposerBase { barretenberg::fr get_public_input(const uint32_t index) const { return get_variable(public_inputs[index]); } + uint32_t get_public_input_index(const uint32_t witness_index) const + { + uint32_t result = static_cast(-1); + for (size_t i = 0; i < public_inputs.size(); ++i) { + if (real_variable_index[public_inputs[i]] == real_variable_index[witness_index]) { + result = static_cast(i); + break; + } + } + ASSERT(result != static_cast(-1)); + return result; + } + std::vector get_public_inputs() const { std::vector result; diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index eb0e5b0e92..0fafc2576d 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -254,6 +254,23 @@ class UltraComposer : public ComposerBase { } } + /** + * @brief Update recursive_proof_public_input_indices with existing public inputs that represent a recursive proof + * + * @param proof_output_witness_indices + */ + void set_recursive_proof(const std::vector& proof_output_witness_indices) + { + if (contains_recursive_proof) { + failure("added recursive proof when one already exists"); + } + contains_recursive_proof = true; + for (size_t i = 0; i < proof_output_witness_indices.size(); ++i) { + recursive_proof_public_input_indices.push_back( + get_public_input_index(real_variable_index[proof_output_witness_indices[i]])); + } + } + void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string const msg = "create_new_range_constraint"); diff --git a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp index c16aeaafdf..e2b4cc6e10 100644 --- a/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp +++ b/cpp/src/barretenberg/plonk/proof_system/types/polynomial_manifest.hpp @@ -200,7 +200,7 @@ class PolynomialManifest { }; } - const std::vector& get() { return manifest; }; + const std::vector& get() const { return manifest; }; size_t size() const { return manifest.size(); } diff --git a/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt b/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt index 085b5297e5..8ce6666afa 100644 --- a/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt +++ b/cpp/src/barretenberg/solidity_helpers/CMakeLists.txt @@ -1,4 +1,4 @@ -barretenberg_module(stdlib_solidity_helpers plonk proof_system transcript crypto_pedersen_commitment polynomials crypto_sha256 ecc crypto_blake3s stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s stdlib_blake2s) +barretenberg_module(stdlib_solidity_helpers plonk proof_system transcript crypto_pedersen_commitment polynomials crypto_sha256 ecc crypto_blake3s stdlib_primitives stdlib_pedersen_commitment stdlib_blake3s stdlib_blake2s srs) add_executable(solidity_key_gen key_gen.cpp) diff --git a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp index 9db2f12dc9..34a983b362 100644 --- a/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/transcript/transcript.hpp @@ -49,6 +49,54 @@ template class Transcript { // } } + /** + * @brief Construct a new Transcript object using a proof represented as a field_pt vector + * + * N.B. If proof is represented as a uint8_t vector, Transcript will convert into witnesses in-situ. + * Use this constructor method if the proof is *already present* as circuit witnesses! + * @param in_context + * @param input_manifest + * @param field_buffer + * @param num_public_inputs + */ + Transcript(Composer* in_context, + const transcript::Manifest input_manifest, + const std::vector& field_buffer, + const size_t num_public_inputs) + : context(in_context) + , transcript_base(input_manifest, transcript::HashType::PedersenBlake3s, 16) + , current_challenge(in_context) + { + size_t count = 0; + + const auto num_rounds = input_manifest.get_num_rounds(); + for (size_t i = 0; i < num_rounds; ++i) { + for (auto manifest_element : input_manifest.get_round_manifest(i).elements) { + if (!manifest_element.derived_by_verifier) { + if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { + add_field_element(manifest_element.name, field_buffer[count++]); + } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { + const auto x_lo = field_buffer[count++]; + const auto x_hi = field_buffer[count++]; + const auto y_lo = field_buffer[count++]; + const auto y_hi = field_buffer[count++]; + fq_pt x(x_lo, x_hi); + fq_pt y(y_lo, y_hi); + group_pt element(x, y); + add_group_element(manifest_element.name, element); + } else { + ASSERT(manifest_element.name == "public_inputs"); + std::vector public_inputs; + for (size_t i = 0; i < num_public_inputs; ++i) { + public_inputs.emplace_back(field_buffer[count++]); + } + add_field_element_vector(manifest_element.name, public_inputs); + } + } + } + } + } + transcript::Manifest get_manifest() const { return transcript_base.get_manifest(); } int check_field_element_cache(const std::string& element_name) const diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 0c91945554..09306e7434 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -178,6 +178,20 @@ template struct PedersenPreimage }; template struct evaluation_domain { + static evaluation_domain from_field_elements(const std::vector>& fields) + { + evaluation_domain domain; + domain.root = fields[0]; + + domain.root_inverse = domain.root.invert(); + domain.domain = fields[1]; + domain.domain_inverse = domain.domain.invert(); + domain.generator = fields[2]; + domain.generator_inverse = domain.generator.invert(); + domain.size = domain.domain; + return domain; + } + static evaluation_domain from_witness(Composer* ctx, const barretenberg::evaluation_domain& input) { evaluation_domain domain; @@ -213,20 +227,63 @@ template struct evaluation_domain { uint32 size; }; -/** - * @brief Converts a 'native' verification key into a standard library type, instantiating the `input_key` parameter as - * circuit variables. This allows the recursive verifier to accept arbitrary verification keys, where the circuit being - * verified is not fixed as part of the recursive circuit. - */ template struct verification_key { using Composer = typename Curve::Composer; + + static std::shared_ptr from_field_elements( + Composer* ctx, + const std::vector>& fields, + bool inner_proof_contains_recursive_proof = false, + std::array recursive_proof_public_input_indices = {}) + { + std::vector fields_raw; + std::shared_ptr key = std::make_shared(); + key->context = ctx; + + key->polynomial_manifest = PolynomialManifest(Composer::type); + key->domain = evaluation_domain::from_field_elements({ fields[0], fields[1], fields[2] }); + + key->n = fields[3]; + key->num_public_inputs = fields[4]; + + // NOTE: For now `contains_recursive_proof` and `recursive_proof_public_input_indices` need to be circuit + // constants! + key->contains_recursive_proof = inner_proof_contains_recursive_proof; + for (size_t i = 0; i < 16; ++i) { + auto x = recursive_proof_public_input_indices[i]; + key->recursive_proof_public_input_indices.emplace_back(x); + } + + size_t count = 22; + for (const auto& descriptor : key->polynomial_manifest.get()) { + if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { + + const auto x_lo = fields[count++]; + const auto x_hi = fields[count++]; + const auto y_lo = fields[count++]; + const auto y_hi = fields[count++]; + const typename Curve::fq_ct x(x_lo, x_hi); + const typename Curve::fq_ct y(y_lo, y_hi); + const typename Curve::g1_ct element(x, y); + + key->commitments.insert({ std::string(descriptor.commitment_label), element }); + } + } + + return key; + } + + /** + * @brief Converts a 'native' verification key into a standard library type, instantiating the `input_key` parameter + * as circuit variables. This allows the recursive verifier to accept arbitrary verification keys, where the circuit + * being verified is not fixed as part of the recursive circuit. + */ static std::shared_ptr from_witness(Composer* ctx, const std::shared_ptr& input_key) { std::shared_ptr key = std::make_shared(); // Native data: key->context = ctx; - key->base_key = input_key; key->reference_string = input_key->reference_string; key->polynomial_manifest = input_key->polynomial_manifest; @@ -234,7 +291,8 @@ template struct verification_key { key->n = witness_t(ctx, barretenberg::fr(input_key->circuit_size)); key->num_public_inputs = witness_t(ctx, input_key->num_public_inputs); key->domain = evaluation_domain::from_witness(ctx, input_key->domain); - key->contains_recursive_proof = witness_t(ctx, input_key->contains_recursive_proof); + key->contains_recursive_proof = input_key->contains_recursive_proof; + key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices; for (const auto& [tag, value] : input_key->commitments) { // We do not perform on_curve() circuit checks when constructing the Curve::g1_ct element. // The assumption is that the circuit creator is honest and that the verification key hash (or some other @@ -254,19 +312,18 @@ template struct verification_key { { std::shared_ptr key = std::make_shared(); key->context = ctx; - key->base_key = input_key; key->n = field_t(ctx, input_key->circuit_size); key->num_public_inputs = field_t(ctx, input_key->num_public_inputs); - key->contains_recursive_proof = bool_t(ctx, input_key->contains_recursive_proof); + key->contains_recursive_proof = input_key->contains_recursive_proof; + key->recursive_proof_public_input_indices = input_key->recursive_proof_public_input_indices; key->domain = evaluation_domain::from_constants(ctx, input_key->domain); - key->reference_string = input_key->reference_string; - for (const auto& [tag, value] : input_key->commitments) { key->commitments.insert({ tag, typename Curve::g1_ct(value) }); } + key->reference_string = input_key->reference_string; key->polynomial_manifest = input_key->polynomial_manifest; return key; @@ -380,23 +437,17 @@ template struct verification_key { field_t num_public_inputs; field_t z_pow_n; - // NOTE: This does not strictly need to be a circuit type. It can be used to check in the circuit - // if a proof contains any aggregated state. - bool_t contains_recursive_proof; - evaluation_domain domain; std::map commitments; // Native data: - std::shared_ptr reference_string; - PolynomialManifest polynomial_manifest; - + // Used to check in the circuit if a proof contains any aggregated state. + bool contains_recursive_proof = false; + std::vector recursive_proof_public_input_indices; size_t program_width = 4; - - std::shared_ptr base_key; Composer* context; }; diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp index 009e732b20..7e9bb09468 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.hpp @@ -181,6 +181,25 @@ aggregation_state verify_proof(typename Curve::Composer* context, const transcript::Manifest& manifest, const plonk::proof& proof, const aggregation_state previous_output = aggregation_state()) +{ + using Composer = typename Curve::Composer; + + key->program_width = program_settings::program_width; + + Transcript transcript = Transcript(context, proof.proof_data, manifest); + + return verify_proof_(context, key, transcript, previous_output); +} + +/** + * Refer to src/barretenberg/plonk/proof_system/verifier/verifier.cpp verify_proof() for the native implementation, + * which includes detailed comments. + */ +template +aggregation_state verify_proof_(typename Curve::Composer* context, + std::shared_ptr> key, + Transcript& transcript, + const aggregation_state previous_output = aggregation_state()) { using fr_ct = typename Curve::fr_ct; using fq_ct = typename Curve::fq_ct; @@ -189,7 +208,6 @@ aggregation_state verify_proof(typename Curve::Composer* context, key->program_width = program_settings::program_width; - Transcript transcript = Transcript(context, proof.proof_data, manifest); std::map kate_g1_elements; std::map kate_fr_elements_at_zeta; std::map kate_fr_elements_at_zeta_large; @@ -311,10 +329,6 @@ aggregation_state verify_proof(typename Curve::Composer* context, rhs_scalars.push_back(random_separator); } - // Check if recursive proof information is correctly set. - key->contains_recursive_proof.assert_equal(key->base_key->contains_recursive_proof, - "contains_recursive_proof is incorrectly set"); - /** * N.B. if this key contains a recursive proof, then ALL potential verification keys being verified by the outer *circuit must ALSO contain a recursive proof (this is not a concern if the key is being generated from circuit @@ -322,7 +336,7 @@ aggregation_state verify_proof(typename Curve::Composer* context, *code path should be used with extreme caution if the verification key is not being generated from circuit *constants **/ - if (key->base_key->contains_recursive_proof) { + if (key->contains_recursive_proof) { const auto public_inputs = transcript.get_field_element_vector("public_inputs"); const auto recover_fq_from_public_inputs = [&public_inputs](const size_t idx0, const size_t idx1, const size_t idx2, const size_t idx3) { @@ -339,22 +353,22 @@ aggregation_state verify_proof(typename Curve::Composer* context, fr_ct recursion_separator_challenge = transcript.get_challenge_field_element("separator", 2); - const auto x0 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[0], - key->base_key->recursive_proof_public_input_indices[1], - key->base_key->recursive_proof_public_input_indices[2], - key->base_key->recursive_proof_public_input_indices[3]); - const auto y0 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[4], - key->base_key->recursive_proof_public_input_indices[5], - key->base_key->recursive_proof_public_input_indices[6], - key->base_key->recursive_proof_public_input_indices[7]); - const auto x1 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[8], - key->base_key->recursive_proof_public_input_indices[9], - key->base_key->recursive_proof_public_input_indices[10], - key->base_key->recursive_proof_public_input_indices[11]); - const auto y1 = recover_fq_from_public_inputs(key->base_key->recursive_proof_public_input_indices[12], - key->base_key->recursive_proof_public_input_indices[13], - key->base_key->recursive_proof_public_input_indices[14], - key->base_key->recursive_proof_public_input_indices[15]); + const auto x0 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[0], + key->recursive_proof_public_input_indices[1], + key->recursive_proof_public_input_indices[2], + key->recursive_proof_public_input_indices[3]); + const auto y0 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[4], + key->recursive_proof_public_input_indices[5], + key->recursive_proof_public_input_indices[6], + key->recursive_proof_public_input_indices[7]); + const auto x1 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[8], + key->recursive_proof_public_input_indices[9], + key->recursive_proof_public_input_indices[10], + key->recursive_proof_public_input_indices[11]); + const auto y1 = recover_fq_from_public_inputs(key->recursive_proof_public_input_indices[12], + key->recursive_proof_public_input_indices[13], + key->recursive_proof_public_input_indices[14], + key->recursive_proof_public_input_indices[15]); opening_elements.push_back(g1_ct(x0, y0)); opening_scalars.push_back(recursion_separator_challenge); diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp index db3dfc2cc1..88b5ae2d97 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier.test.cpp @@ -279,6 +279,7 @@ template class stdlib_verifier : public testing::Test { public: static void test_recursive_proof_composition() { + auto env_crs = std::make_unique(); InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); std::vector inner_inputs{ barretenberg::fr::random_element(), @@ -296,7 +297,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -328,6 +329,8 @@ template class stdlib_verifier : public testing::Test { static void test_recursive_proof_composition_ultra_no_tables() { + auto env_crs = std::make_unique(); + InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -344,7 +347,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(inner_proof_result, barretenberg::fq12::one()); @@ -377,6 +380,7 @@ template class stdlib_verifier : public testing::Test { return; // We only care about running this test for turbo and ultra outer circuits, since in practice the // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates // (16m) that it's a waste of time testing it. + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); @@ -421,7 +425,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output_a.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output_a.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -459,6 +463,7 @@ template class stdlib_verifier : public testing::Test { std::vector inner_inputs_b{ barretenberg::fr::random_element(), barretenberg::fr::random_element(), barretenberg::fr::random_element() }; + auto env_crs = std::make_unique(); create_inner_circuit(inner_composer_a, inner_inputs_a); create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); @@ -472,7 +477,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -511,6 +516,7 @@ template class stdlib_verifier : public testing::Test { auto circuit_output = create_outer_circuit_with_variable_inner_circuit(inner_composer_a, inner_composer_b, outer_composer, false); g1::affine_element P[2]; + auto env_crs = std::make_unique(); P[0].x = barretenberg::fq(circuit_output.aggregation_state.P0.x.get_value().lo); P[0].y = barretenberg::fq(circuit_output.aggregation_state.P0.y.get_value().lo); @@ -518,7 +524,7 @@ template class stdlib_verifier : public testing::Test { P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_b[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_b[1]); @@ -553,6 +559,7 @@ template class stdlib_verifier : public testing::Test { create_inner_circuit(inner_composer_a, inner_inputs_a); create_alternate_inner_circuit(inner_composer_b, inner_inputs_b); + auto env_crs = std::make_unique(); auto circuit_output = create_outer_circuit_with_variable_inner_circuit( inner_composer_a, inner_composer_b, outer_composer, true, true); @@ -563,7 +570,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -585,6 +592,7 @@ template class stdlib_verifier : public testing::Test { static void test_recursive_proof_composition_with_constant_verification_key() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -608,7 +616,7 @@ template class stdlib_verifier : public testing::Test { P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); diff --git a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp index 36c128f3ea..a4d97f013d 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp +++ b/cpp/src/barretenberg/stdlib/recursion/verifier/verifier_turbo.test.cpp @@ -165,6 +165,7 @@ template class stdlib_verifier_turbo : public testing:: public: static void test_recursive_proof_composition() { + auto env_crs = std::make_unique(); InnerComposer inner_composer = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); std::vector inner_inputs{ barretenberg::fr::random_element(), @@ -181,7 +182,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -209,6 +210,8 @@ template class stdlib_verifier_turbo : public testing:: static void test_double_verification() { + auto env_crs = std::make_unique(); + if constexpr (std::is_same::value) return; // We only care about running this test for turbo and ultra outer circuits, since in practice the // only circuits which verify >1 proof are ultra or turbo circuits. Standard uses so many gates @@ -234,7 +237,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs[1]); @@ -262,6 +265,8 @@ template class stdlib_verifier_turbo : public testing:: // variable circuits static void test_recursive_proof_composition_with_variable_verification_key_a() { + auto env_crs = std::make_unique(); + InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -285,7 +290,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -309,6 +314,7 @@ template class stdlib_verifier_turbo : public testing:: // variable circuits static void test_recursive_proof_composition_with_variable_verification_key_b() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -332,7 +338,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_b[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_b[1]); @@ -354,6 +360,7 @@ template class stdlib_verifier_turbo : public testing:: static void test_recursive_proof_composition_with_variable_verification_key_failure_case() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -377,7 +384,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]); @@ -399,6 +406,7 @@ template class stdlib_verifier_turbo : public testing:: static void test_recursive_proof_composition_with_constant_verification_key() { + auto env_crs = std::make_unique(); InnerComposer inner_composer_a = InnerComposer("../srs_db/ignition"); InnerComposer inner_composer_b = InnerComposer("../srs_db/ignition"); OuterComposer outer_composer = OuterComposer("../srs_db/ignition"); @@ -422,7 +430,7 @@ template class stdlib_verifier_turbo : public testing:: P[1].x = barretenberg::fq(circuit_output.aggregation_state.P1.x.get_value().lo); P[1].y = barretenberg::fq(circuit_output.aggregation_state.P1.y.get_value().lo); barretenberg::fq12 inner_proof_result = barretenberg::pairing::reduced_ate_pairing_batch_precomputed( - P, circuit_output.verification_key->reference_string->get_precomputed_g2_lines(), 2); + P, env_crs->get_verifier_crs()->get_precomputed_g2_lines(), 2); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[0].get_value(), inner_inputs_a[0]); EXPECT_EQ(circuit_output.aggregation_state.public_inputs[1].get_value(), inner_inputs_a[1]);