diff --git a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp index 28343d3b1f..97ce130665 100644 --- a/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/acir_format.cpp @@ -95,8 +95,18 @@ void create_circuit(Composer& composer, const acir_format& constraint_system) } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } } @@ -189,8 +199,18 @@ Composer create_circuit(const acir_format& constraint_system, } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -288,8 +308,18 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -384,8 +414,18 @@ Composer create_circuit_with_witness(const acir_format& constraint_system, std:: } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } return composer; @@ -478,8 +518,18 @@ void create_circuit_with_witness(Composer& composer, const acir_format& constrai } // Add recursion constraints - for (const auto& constraint : constraint_system.recursion_constraints) { - create_recursion_constraints(composer, constraint); + for (size_t i = 0; i < constraint_system.recursion_constraints.size(); ++i) { + auto& constraint = constraint_system.recursion_constraints[i]; + create_recursion_constraints(composer, constraint); + + // make sure the verification key records the public input indices of the final recursion output + // (N.B. up to the ACIR description to make sure that the final output aggregation object wires are public + // inputs!) + if (i == constraint_system.recursion_constraints.size() - 1) { + std::vector proof_output_witness_indices(constraint.output_aggregation_object.begin(), + constraint.output_aggregation_object.end()); + composer.set_recursive_proof(proof_output_witness_indices); + } } } diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp index 441b47c2d8..1fcf95f594 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.cpp @@ -22,9 +22,15 @@ void generate_dummy_proof() {} * We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, * or we need non-witness data to be provided as metadata in the ACIR opcode */ -template +template void create_recursion_constraints(Composer& composer, const RecursionConstraint& input) { + const auto& nested_aggregation_indices = input.nested_aggregation_object; + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_indices) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; // If we do not have a witness, we must ensure that our dummy witness will not trigger // on-curve errors and inverting-zero errors @@ -32,9 +38,10 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& // get a fake key/proof that satisfies on-curve + inversion-zero checks const std::vector dummy_key = plonk::verification_key::export_dummy_key_in_recursion_format( PolynomialManifest(Composer::type), inner_proof_contains_recursive_proof); - const auto manifest = Composer::create_unrolled_manifest(1); + const auto manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); const std::vector dummy_proof = - transcript::StandardTranscript::export_dummy_transcript_in_recursion_format(manifest); + transcript::StandardTranscript::export_dummy_transcript_in_recursion_format( + manifest, inner_proof_contains_recursive_proof); for (size_t i = 0; i < input.proof.size(); ++i) { const auto proof_field_idx = input.proof[i]; // if we do NOT have a witness assignment (i.e. are just building the proving/verification keys), @@ -88,12 +95,14 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& previous_aggregation.has_data = false; } - transcript::Manifest manifest = Composer::create_unrolled_manifest(1); + transcript::Manifest manifest = Composer::create_unrolled_manifest(input.public_inputs.size()); + std::vector key_fields; key_fields.reserve(input.key.size()); for (const auto& idx : input.key) { key_fields.emplace_back(field_ct::from_witness_index(&composer, idx)); } + std::vector proof_fields; proof_fields.reserve(input.proof.size()); for (const auto& idx : input.proof) { @@ -101,24 +110,22 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } // recursively verify the proof - std::shared_ptr vkey = - verification_key_ct::template from_field_pt_vector(&composer, key_fields); + std::shared_ptr vkey = verification_key_ct::from_field_pt_vector( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_indices); vkey->program_width = noir_recursive_settings::program_width; - Transcript_ct transcript(&composer, manifest, proof_fields, 1); + Transcript_ct transcript(&composer, manifest, proof_fields, input.public_inputs.size()); aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); // Assign correct witness value to the verification key hash vkey->compress().assert_equal(field_ct::from_witness_index(&composer, input.key_hash)); - // Assign the output aggregation object to the proof public inputs (16 field elements representing two - // G1 points) - result.add_proof_outputs_as_public_inputs(); - - ASSERT(result.public_inputs.size() == 1); + ASSERT(result.public_inputs.size() == input.public_inputs.size()); // Assign the `public_input` field to the public input of the inner proof - result.public_inputs[0].assert_equal(field_ct::from_witness_index(&composer, input.public_input)); + for (size_t i = 0; i < input.public_inputs.size(); ++i) { + result.public_inputs[i].assert_equal(field_ct::from_witness_index(&composer, input.public_inputs[i])); + } // Assign the recursive proof outputs to `output_aggregation_object` for (size_t i = 0; i < result.proof_witness_indices.size(); ++i) { @@ -128,9 +135,7 @@ void create_recursion_constraints(Composer& composer, const RecursionConstraint& } } -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); -template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); +template void create_recursion_constraints(Composer&, const RecursionConstraint&); } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp index cfc0116349..0a0ca53d12 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.hpp @@ -24,40 +24,52 @@ namespace acir_format { * (and therefore an aggregation object is present) * @param public_input The index of the single public input * @param input_aggregation_object Witness indices of pre-existing aggregation object (if it exists) - * @param output_aggregation_object Witness indecies of the aggregation object produced by recursive verification + * @param output_aggregation_object Witness indices of the aggregation object produced by recursive verification + * @param nested_aggregation_object Public input indices of an aggregation object inside the proof. * * @note If input_aggregation_object witness indices are all zero, we interpret this to mean that the inner proof does - * NOT contain + * NOT contain a previously recursively verified proof + * @note nested_aggregation_object is used for cases where the proof being verified contains an aggregation object in + * its public inputs! If this is the case, we record the public input locations in `nested_aggregation_object`. If the + * inner proof is of a circuit that does not have a nested aggregation object, these values are all zero. + * + * To outline the interaction between the input_aggergation_object and the nested_aggregation_object take the following + * example: If we have a circuit that verifies 2 proofs A and B, the recursion constraint for B will have an + * input_aggregation_object that points to the aggregation output produced by verifying A. If circuit B also verifies a + * proof, in the above example the recursion constraint for verifying B will have a nested object that describes the + * aggregation object in B’s public inputs as well as an input aggregation object that points to the object produced by + * the previous recursion constraint in the circuit (the one that verifies A) + * */ struct RecursionConstraint { static constexpr size_t AGGREGATION_OBJECT_SIZE = 16; // 16 field elements std::vector key; std::vector proof; - uint32_t public_input; + std::vector public_inputs; uint32_t key_hash; std::array input_aggregation_object; std::array output_aggregation_object; + std::array nested_aggregation_object; friend bool operator==(RecursionConstraint const& lhs, RecursionConstraint const& rhs) = default; }; -template +template void create_recursion_constraints(Composer& composer, const RecursionConstraint& input); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); -extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); +extern template void create_recursion_constraints(Composer&, const RecursionConstraint&); template inline void read(B& buf, RecursionConstraint& constraint) { using serialize::read; read(buf, constraint.key); read(buf, constraint.proof); - read(buf, constraint.public_input); + read(buf, constraint.public_inputs); read(buf, constraint.key_hash); read(buf, constraint.input_aggregation_object); read(buf, constraint.output_aggregation_object); + read(buf, constraint.nested_aggregation_object); } template inline void write(B& buf, RecursionConstraint const& constraint) @@ -65,10 +77,11 @@ template inline void write(B& buf, RecursionConstraint const& const using serialize::write; write(buf, constraint.key); write(buf, constraint.proof); - write(buf, constraint.public_input); + write(buf, constraint.public_inputs); write(buf, constraint.key_hash); write(buf, constraint.input_aggregation_object); write(buf, constraint.output_aggregation_object); + write(buf, constraint.nested_aggregation_object); } } // namespace acir_format diff --git a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp index 53fe69b979..c6b511d662 100644 --- a/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_format/recursion_constraint.test.cpp @@ -75,13 +75,10 @@ acir_format::Composer create_inner_circuit() .q_o = 0, .q_c = 1, }; - // EXPR [ (1, _4, _5) (-1, _6) 0 ] - // EXPR [ (1, _4, _6) (-1, _4) 0 ] - // EXPR [ (-1, _6) 1 ] acir_format::acir_format constraint_system{ .varnum = 7, - .public_inputs = { 2 }, + .public_inputs = { 2, 3 }, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -112,67 +109,101 @@ acir_format::Composer create_inner_circuit() return composer; } -TEST(RecursionConstraint, TestRecursionConstraint) +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +acir_format::Composer create_outer_circuit(std::vector& inner_composers) { - auto inner_composer = create_inner_circuit(); + std::vector recursion_constraints; - auto inner_prover = inner_composer.create_prover(); - auto inner_proof = inner_prover.construct_proof(); - auto inner_verifier = inner_composer.create_verifier(); + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; - // std::vector keybuf; - // write(keybuf, *(inner_verifier.key)); + for (size_t i = 0; i < inner_composers.size(); ++i) { + const bool has_input_aggregation_object = i > 0; - std::array output_vars; - for (size_t i = 0; i < 16; ++i) { - // variable idx 1 = public input - // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 3)); - } + auto& inner_composer = inner_composers[i]; + auto inner_prover = inner_composer.create_prover(); + auto inner_proof = inner_prover.construct_proof(); + auto inner_verifier = inner_composer.create_verifier(); - transcript::StandardTranscript transcript(inner_proof.proof_data, - acir_format::Composer::create_manifest(1), - transcript::HashType::PlookupPedersenBlake3s, - 16); + const bool has_nested_proof = inner_verifier.key->contains_recursive_proof; + const size_t num_inner_public_inputs = inner_composer.get_num_public_inputs(); - const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); - const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); + transcript::StandardTranscript transcript(inner_proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); - std::vector proof_indices; - const size_t proof_size = proof_witnesses.size(); + const std::vector proof_witnesses = transcript.export_transcript_in_recursion_format(); + const std::vector key_witnesses = inner_verifier.key->export_key_in_recursion_format(); - for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 19)); - } + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); - std::vector key_indices; - const size_t key_size = key_witnesses.size(); - for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 19 + proof_size)); - } - acir_format::RecursionConstraint recursion_constraint{ - .key = key_indices, - .proof = proof_indices, - .public_input = 1, - .key_hash = 2, - .input_aggregation_object = {}, - .output_aggregation_object = output_vars, - }; + std::vector proof_indices; + std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 0; i < 16; ++i) { + nested_aggregation_object[i] = inner_composer.recursive_proof_public_input_indices[i]; + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } + const size_t key_size = key_witnesses.size(); + for (size_t i = 0; i < key_size; ++i) { + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); + } + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); + } - std::vector witness; - for (size_t i = 0; i < 18; ++i) { - witness.emplace_back(0); - } - for (const auto& wit : proof_witnesses) { - witness.emplace_back(wit); - } - for (const auto& wit : key_witnesses) { - witness.emplace_back(wit); + acir_format::RecursionConstraint recursion_constraint{ + .key = key_indices, + .proof = proof_indices, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, + }; + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { + witness.emplace_back(0); + } + for (const auto& wit : proof_witnesses) { + witness.emplace_back(wit); + } + for (const auto& wit : key_witnesses) { + witness.emplace_back(wit); + } + witness_offset = key_indices_start_idx + key_witnesses.size(); } + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); + acir_format::acir_format constraint_system{ .varnum = static_cast(witness.size() + 1), - .public_inputs = { 1 }, + .public_inputs = public_inputs, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = {}, .range_constraints = {}, @@ -185,16 +216,84 @@ TEST(RecursionConstraint, TestRecursionConstraint) .pedersen_constraints = {}, .compute_merkle_root_constraints = {}, .block_constraints = {}, - .recursion_constraints = { recursion_constraint }, + .recursion_constraints = recursion_constraints, .constraints = {}, }; auto composer = acir_format::create_circuit_with_witness(constraint_system, witness); - auto prover = composer.create_prover(); + return composer; +} + +TEST(RecursionConstraint, TestBasicDoubleRecursionConstraints) +{ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + + layer_1_composers.push_back(create_inner_circuit()); + + auto layer_2_composer = create_outer_circuit(layer_1_composers); + + std::cout << "composer gates = " << layer_2_composer.get_num_gates() << std::endl; + auto prover = layer_2_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; auto proof = prover.construct_proof(); - auto verifier = composer.create_verifier(); + auto verifier = layer_2_composer.create_ultra_with_keccak_verifier(); EXPECT_EQ(verifier.verify_proof(proof), true); +} - EXPECT_EQ(composer.get_variable(1), 10); +TEST(RecursionConstraint, TestFullRecursionConstraints) +{ + /** + * We want to test the following: + * 1. circuit that verifies a proof of another circuit + * 2. the above, but the inner circuit contains a recursive proof output that we have to aggregate + * 3. the above, but the outer circuit verifies 2 proofs, the aggregation outputs from the 2 proofs (+ the recursive + * proof output from 2) are aggregated together + * + * A = basic circuit + * B = circuit that verifies proof of A + * C = circuit that verifies proof of B and a proof of A + * + * Layer 1 = proof of A + * Layer 2 = verifies proof of A and proof of B + * Layer 3 = verifies proof of C + * + * Attempt at a visual graphic + * =========================== + * + * C + * ^ + * | + * | - B + * ^ ^ + * | | + * | -A + * | + * - A + * + * =========================== + * + * Final aggregation object contains aggregated proofs for 2 instances of A and 1 instance of B + */ + std::vector layer_1_composers; + layer_1_composers.push_back(create_inner_circuit()); + std::cout << "created first inner circuit\n"; + std::vector layer_2_composers; + + layer_2_composers.push_back(create_inner_circuit()); + std::cout << "created second inner circuit\n"; + + layer_2_composers.push_back(create_outer_circuit(layer_1_composers)); + std::cout << "created first outer circuit\n"; + + auto layer_3_composer = create_outer_circuit(layer_2_composers); + std::cout << "created second outer circuit\n"; + + std::cout << "composer gates = " << layer_3_composer.get_num_gates() << std::endl; + auto prover = layer_3_composer.create_ultra_with_keccak_prover(); + std::cout << "prover gates = " << prover.circuit_size << std::endl; + auto proof = prover.construct_proof(); + auto verifier = layer_3_composer.create_ultra_with_keccak_verifier(); + EXPECT_EQ(verifier.verify_proof(proof), true); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp index 527d944b61..4310342f7e 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.cpp @@ -51,12 +51,11 @@ size_t init_proving_key(uint8_t const* constraint_system_buf, uint8_t const** pk { auto constraint_system = from_buffer(constraint_system_buf); - // constraint_system.recursion_constraints[0]. - // We know that we don't actually need any CRS to create a proving key, so just feed in a nothing. // Hacky, but, right now it needs *something*. auto crs_factory = std::make_unique(); auto composer = create_circuit(constraint_system, std::move(crs_factory)); + auto proving_key = composer.compute_proving_key(); auto buffer = to_buffer(*proving_key); @@ -86,6 +85,18 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* // construct the verification key so that we have the correct polynomial manifest verification_key->composer_type = proof_system::ComposerType::PLOOKUP; + // Set the recursive proof indices as this is not done in `compute_verification_key_base` + verification_key->contains_recursive_proof = proving_key->contains_recursive_proof; + for (size_t i = 0; i < 16; ++i) { + if (proving_key->recursive_proof_public_input_indices.size() > i) { + verification_key->recursive_proof_public_input_indices.emplace_back( + proving_key->recursive_proof_public_input_indices[i]); + } else { + verification_key->recursive_proof_public_input_indices.emplace_back(0); + ASSERT(verification_key->contains_recursive_proof == false); + } + } + auto buffer = to_buffer(*verification_key); auto raw_buf = (uint8_t*)malloc(buffer.size()); memcpy(raw_buf, (void*)buffer.data(), buffer.size()); @@ -104,12 +115,15 @@ size_t init_verification_key(void* pippenger, uint8_t const* g2x, uint8_t const* */ size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length) + size_t proof_data_length, + size_t num_inner_public_inputs) { plonk::proof proof = { std::vector(proof_data_buf, &proof_data_buf[proof_data_length]) }; - transcript::StandardTranscript transcript( - proof.proof_data, acir_format::Composer::create_manifest(1), transcript::HashType::PlookupPedersenBlake3s, 16); + transcript::StandardTranscript transcript(proof.proof_data, + acir_format::Composer::create_manifest(num_inner_public_inputs), + transcript::HashType::PlookupPedersenBlake3s, + 16); std::vector output = transcript.export_transcript_in_recursion_format(); @@ -143,7 +157,6 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, plonk::verification_key_data vk_data; read(vk_buf, vk_data); auto vkey = std::make_shared(std::move(vk_data), crs); - std::vector output = vkey->export_key_in_recursion_format(); // NOTE: this output buffer will always have a fixed size! Maybe just precompute? @@ -272,18 +285,36 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf) { - // TODO: not doing anything with public_inputs_buf right now because we only have one layer of recursion - // and the previous aggregation state will be empty. When arbitrary depth recursion is available we will have to - // construct the correct input aggregation_state_ct - (void)public_inputs_buf; - (void)input_aggregation_obj_buf; + + bool inner_aggregation_all_zero = true; + std::vector aggregation_input(16); + for (size_t i = 0; i < 16; i++) { + aggregation_input[i] = barretenberg::fr::serialize_from_buffer(&input_aggregation_obj_buf[i * 32]); + inner_aggregation_all_zero &= (aggregation_input[i].is_zero()); + } acir_format::aggregation_state_ct previous_aggregation; - previous_aggregation.has_data = false; + if (!inner_aggregation_all_zero) { + std::array aggregation_elements; + for (size_t i = 0; i < 4; ++i) { + aggregation_elements[i] = acir_format::bn254::fq_ct(acir_format::field_ct(aggregation_input[4 * i]), + acir_format::field_ct(aggregation_input[4 * i + 1]), + acir_format::field_ct(aggregation_input[4 * i + 2]), + acir_format::field_ct(aggregation_input[4 * i + 3])); + aggregation_elements[i].assert_is_in_field(); + } + // If we have a previous aggregation object, assign it to `previous_aggregation` so that it is included + // in stdlib::recursion::verify_proof + previous_aggregation.P0 = acir_format::bn254::g1_ct(aggregation_elements[0], aggregation_elements[1]); + previous_aggregation.P1 = acir_format::bn254::g1_ct(aggregation_elements[2], aggregation_elements[3]); + previous_aggregation.has_data = true; + } else { + previous_aggregation.has_data = false; + } std::vector proof_fields(proof_length / 32); std::vector key_fields(vk_length / 32); @@ -296,19 +327,30 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, acir_format::Composer composer; - transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(1); + transcript::Manifest manifest = acir_format::Composer::create_unrolled_manifest(num_public_inputs); // We currently only support RecursionConstraint where inner_proof_contains_recursive_proof = false. // We would either need a separate ACIR opcode where inner_proof_contains_recursive_proof = true, // or we need non-witness data to be provided as metadata in the ACIR opcode - std::shared_ptr vkey = - acir_format::verification_key_ct::template from_field_pt_vector(&composer, key_fields); + // recursively verify the proof + std::array nested_aggregation_object = {}; + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_fields[i].get_value()); + } + bool nested_aggregation_indices_all_zero = true; + for (const auto& idx : nested_aggregation_object) { + nested_aggregation_indices_all_zero &= (idx == 0); + } + const bool inner_proof_contains_recursive_proof = !nested_aggregation_indices_all_zero; + std::shared_ptr vkey = acir_format::verification_key_ct::from_field_pt_vector( + &composer, key_fields, inner_proof_contains_recursive_proof, nested_aggregation_object); vkey->program_width = acir_format::noir_recursive_settings::program_width; - acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, 1); + acir_format::Transcript_ct transcript(&composer, manifest, proof_fields, num_public_inputs); acir_format::aggregation_state_ct result = proof_system::plonk::stdlib::recursion::verify_proof_( &composer, vkey, transcript, previous_aggregation); - // just writing the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + // Just write the output aggregation G1 elements, and no public inputs, proof witnesses, or any other data + // as this should all be available elsewhere const size_t output_size_bytes = 16 * sizeof(barretenberg::fr); auto raw_buf = (uint8_t*)malloc(output_size_bytes); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp index e37756a5d6..cad0d1a79f 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.hpp @@ -14,7 +14,8 @@ size_t serialize_verification_key_into_field_elements(uint8_t const* g2x, uint8_t** serialized_vk_hash_buf); size_t serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length); + size_t proof_data_length, + size_t num_inner_public_inputs); size_t new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -32,7 +33,7 @@ size_t verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); diff --git a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp index 08e4e8fbea..a5554d1b82 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/acir_proofs.test.cpp @@ -76,7 +76,7 @@ void create_inner_circuit(acir_format::acir_format& constraint_system, std::vect constraint_system = acir_format::acir_format{ .varnum = 7, - .public_inputs = { 2 }, + .public_inputs = { 2, 3 }, .fixed_base_scalar_mul_constraints = {}, .logic_constraints = { logic_constraint }, .range_constraints = { range_a, range_b }, @@ -141,7 +141,13 @@ TEST(AcirProofs, TestSerialization) EXPECT_EQ(verified, true); } -TEST(AcirProofs, TestSerializationWithRecursion) +struct RecursiveCircuitData { + std::vector key_witnesses; + std::vector proof_witnesses; + size_t num_public_inputs; +}; + +RecursiveCircuitData fetch_recursive_circuit_data(acir_format::acir_format constraint_system, std::vector witness) { uint8_t* proof_data_fields = nullptr; uint8_t* vk_fields = nullptr; @@ -149,112 +155,148 @@ TEST(AcirProofs, TestSerializationWithRecursion) size_t proof_fields_size = 0; size_t vk_fields_size = 0; - // inner circuit - { - acir_format::acir_format constraint_system; - std::vector witness; - create_inner_circuit(constraint_system, witness); - - std::vector witness_buf; // = to_buffer(witness); - std::vector constraint_system_buf; // - write(constraint_system_buf, constraint_system); - write(witness_buf, witness); - - uint8_t const* pk_buf = nullptr; - acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); - - uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); - uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); - auto env_crs = std::make_unique(); - auto verifier_crs = env_crs->get_verifier_crs(); - barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); - - auto* pippenger_ptr_base = - new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); - void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); - - std::vector g2x_buffer(128); - io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); - - uint8_t const* vk_buf = nullptr; - acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); - - uint8_t* proof_data_buf = nullptr; - size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); - proof_fields_size = - acir_proofs::serialize_proof_into_field_elements(proof_data_buf, &proof_data_fields, proof_length); - vk_fields_size = acir_proofs::serialize_verification_key_into_field_elements( - &g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); - - bool verified = acir_proofs::verify_proof(&g2x_buffer[0], - vk_buf, - &constraint_system_buf[0], - proof_data_buf, - static_cast(proof_length), - true); - EXPECT_EQ(verified, true); - - delete pippenger_ptr_base; - free((void*)vk_buf); - free((void*)pk_buf); - free((void*)proof_data_buf); + std::vector witness_buf; // = to_buffer(witness); + std::vector constraint_system_buf; // + write(constraint_system_buf, constraint_system); + write(witness_buf, witness); + + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, true); + + auto num_public_inputs = constraint_system.public_inputs.size(); + proof_fields_size = acir_proofs::serialize_proof_into_field_elements( + proof_data_buf, &proof_data_fields, proof_length, num_public_inputs); + vk_fields_size = + acir_proofs::serialize_verification_key_into_field_elements(&g2x_buffer[0], vk_buf, &vk_fields, &vk_hash_buf); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), true); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); + + fr vk_hash_value; + std::vector proof_witnesses(proof_fields_size / 32); + std::vector key_witnesses((vk_fields_size / 32) + 1); + for (size_t i = 0; i < proof_fields_size / 32; i++) { + proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); + } + for (size_t i = 0; i < vk_fields_size / 32; i++) { + key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); } - // outer circuit - { - fr vk_hash_value; - std::vector proof_witnesses(proof_fields_size / 32); - std::vector key_witnesses(vk_fields_size / 32); - for (size_t i = 0; i < proof_fields_size / 32; i++) { - proof_witnesses[i] = barretenberg::fr::serialize_from_buffer(&proof_data_fields[i * 32]); - } - for (size_t i = 0; i < vk_fields_size / 32; i++) { - key_witnesses[i] = barretenberg::fr::serialize_from_buffer(&vk_fields[i * 32]); - } - vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); - std::vector proof_indices; + free((void*)proof_data_fields); + free((void*)vk_fields); - const size_t proof_size = proof_witnesses.size(); - for (size_t i = 0; i < proof_size; ++i) { - proof_indices.emplace_back(static_cast(i + 19)); - } + vk_hash_value = barretenberg::fr::serialize_from_buffer(vk_hash_buf); + key_witnesses[vk_fields_size / 32] = vk_hash_value; + auto inner_circuit = RecursiveCircuitData{ key_witnesses, proof_witnesses, num_public_inputs }; + return inner_circuit; +} + +RecursiveCircuitData fetch_inner_circuit_data() +{ + acir_format::acir_format constraint_system; + std::vector witness; + create_inner_circuit(constraint_system, witness); + + return fetch_recursive_circuit_data(constraint_system, witness); +} + +/** + * @brief Create a circuit that recursively verifies one or more inner circuits + * + * @param inner_composers + * @return acir_format::Composer + */ +std::pair> create_outer_circuit( + std::vector& inner_circuits) +{ + std::vector recursion_constraints; + + // witness count starts at 1 (Composer reserves 1st witness to be the zero-valued zero_idx) + size_t witness_offset = 1; + std::array output_aggregation_object; + std::vector witness; + + for (size_t i = 0; i < inner_circuits.size(); ++i) { + const bool has_input_aggregation_object = i > 0; + + auto& inner_circuit = inner_circuits[i]; + const std::vector proof_witnesses = inner_circuit.proof_witnesses; + const std::vector key_witnesses = inner_circuit.key_witnesses; + + const bool has_nested_proof = uint32_t(key_witnesses[5]); + const size_t num_inner_public_inputs = inner_circuit.num_public_inputs; + + const uint32_t key_hash_start_idx = static_cast(witness_offset); + const uint32_t public_input_start_idx = key_hash_start_idx + 1; + const uint32_t output_aggregation_object_start_idx = + static_cast(public_input_start_idx + num_inner_public_inputs + (has_nested_proof ? 16 : 0)); + const uint32_t proof_indices_start_idx = output_aggregation_object_start_idx + 16; + const uint32_t key_indices_start_idx = static_cast(proof_indices_start_idx + proof_witnesses.size()); + + std::vector proof_indices; std::vector key_indices; + std::vector inner_public_inputs; + std::array input_aggregation_object = {}; + std::array nested_aggregation_object = {}; + if (has_input_aggregation_object) { + input_aggregation_object = output_aggregation_object; + } + for (size_t i = 0; i < 16; ++i) { + output_aggregation_object[i] = (static_cast(i + output_aggregation_object_start_idx)); + } + if (has_nested_proof) { + for (size_t i = 6; i < 22; ++i) { + nested_aggregation_object[i - 6] = uint32_t(key_witnesses[i]); + } + } + for (size_t i = 0; i < proof_witnesses.size(); ++i) { + proof_indices.emplace_back(static_cast(i + proof_indices_start_idx)); + } const size_t key_size = key_witnesses.size(); for (size_t i = 0; i < key_size; ++i) { - key_indices.emplace_back(static_cast(i + 19 + proof_size)); + key_indices.emplace_back(static_cast(i + key_indices_start_idx)); } - - std::array output_vars; - for (size_t i = 0; i < acir_format::RecursionConstraint::AGGREGATION_OBJECT_SIZE; ++i) { - // variable idx 1 = public input - // variable idx 2-18 = output_vars - output_vars[i] = (static_cast(i + 3)); + for (size_t i = 0; i < num_inner_public_inputs; ++i) { + inner_public_inputs.push_back(static_cast(i + public_input_start_idx)); } + acir_format::RecursionConstraint recursion_constraint{ .key = key_indices, .proof = proof_indices, - .public_input = 1, - .key_hash = 2, - .input_aggregation_object = {}, - .output_aggregation_object = output_vars, + .public_inputs = inner_public_inputs, + .key_hash = key_hash_start_idx, + .input_aggregation_object = input_aggregation_object, + .output_aggregation_object = output_aggregation_object, + .nested_aggregation_object = nested_aggregation_object, }; - - // Add a constraint that fixes the vk hash to be the expected value! - poly_triple vk_equality_constraint{ - .a = recursion_constraint.key_hash, - .b = 0, - .c = 0, - .q_m = 0, - .q_l = 1, - .q_r = 0, - .q_o = 0, - .q_c = -vk_hash_value, - }; - - std::vector witness; - for (size_t i = 0; i < 18; ++i) { + recursion_constraints.push_back(recursion_constraint); + for (size_t i = 0; i < proof_indices_start_idx - witness_offset; ++i) { witness.emplace_back(0); } for (const auto& wit : proof_witnesses) { @@ -263,66 +305,129 @@ TEST(AcirProofs, TestSerializationWithRecursion) for (const auto& wit : key_witnesses) { witness.emplace_back(wit); } + witness_offset = key_indices_start_idx + key_witnesses.size(); + } - acir_format::acir_format constraint_system{ - .varnum = static_cast(witness.size() + 1), - .public_inputs = { 1 }, - .fixed_base_scalar_mul_constraints = {}, - .logic_constraints = {}, - .range_constraints = {}, - .schnorr_constraints = {}, - .ecdsa_constraints = {}, - .sha256_constraints = {}, - .blake2s_constraints = {}, - .keccak_constraints = {}, - .hash_to_field_constraints = {}, - .pedersen_constraints = {}, - .compute_merkle_root_constraints = {}, - .block_constraints = {}, - .recursion_constraints = { recursion_constraint }, - .constraints = { vk_equality_constraint }, - }; + std::vector public_inputs(output_aggregation_object.begin(), output_aggregation_object.end()); - std::vector witness_buf; - std::vector constraint_system_buf; - write(constraint_system_buf, constraint_system); - write(witness_buf, witness); - uint8_t const* pk_buf = nullptr; - acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); - - uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); - uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); - auto env_crs = std::make_unique(); - auto verifier_crs = env_crs->get_verifier_crs(); - barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); - - auto* pippenger_ptr_base = - new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); - void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); - - std::vector g2x_buffer(128); - io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); - - uint8_t const* vk_buf = nullptr; - acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); - - uint8_t* proof_data_buf = nullptr; - size_t proof_length = acir_proofs::new_proof( - pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); - - bool verified = acir_proofs::verify_proof(&g2x_buffer[0], - vk_buf, - &constraint_system_buf[0], - proof_data_buf, - static_cast(proof_length), - false); - EXPECT_EQ(verified, true); - - delete pippenger_ptr_base; - free((void*)vk_buf); - free((void*)pk_buf); - free((void*)proof_data_buf); - free((void*)proof_data_fields); - free((void*)vk_fields); - } + acir_format::acir_format constraint_system{ + .varnum = static_cast(witness.size() + 1), + .public_inputs = public_inputs, + .fixed_base_scalar_mul_constraints = {}, + .logic_constraints = {}, + .range_constraints = {}, + .schnorr_constraints = {}, + .ecdsa_constraints = {}, + .sha256_constraints = {}, + .blake2s_constraints = {}, + .keccak_constraints = {}, + .hash_to_field_constraints = {}, + .pedersen_constraints = {}, + .compute_merkle_root_constraints = {}, + .block_constraints = {}, + .recursion_constraints = recursion_constraints, + .constraints = {}, + }; + + return std::make_pair(constraint_system, witness); +} + +// Working double recursion test +TEST(AcirProofs, TestSerializationWithBasicDoubleRecursion) +{ + std::vector inner_circuits; + auto a = fetch_inner_circuit_data(); + inner_circuits.push_back(a); + + auto b = fetch_inner_circuit_data(); + inner_circuits.push_back(b); + + auto c = create_outer_circuit(inner_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c.first); + write(witness_buf, c.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); +} + +TEST(AcirProofs, TestSerializationWithFullRecursiveComposition) +{ + std::vector layer_1_circuits; + auto a_data_circuit = fetch_inner_circuit_data(); + layer_1_circuits.push_back(a_data_circuit); + + std::vector layer_2_circuits; + auto a2_circuit = fetch_inner_circuit_data(); + layer_2_circuits.push_back(a2_circuit); + + auto b_cs_and_witness = create_outer_circuit(layer_1_circuits); + auto b_circuit = fetch_recursive_circuit_data(b_cs_and_witness.first, b_cs_and_witness.second); + layer_2_circuits.push_back(b_circuit); + + auto c_cs_and_witness = create_outer_circuit(layer_2_circuits); + + std::vector witness_buf; + std::vector constraint_system_buf; + write(constraint_system_buf, c_cs_and_witness.first); + write(witness_buf, c_cs_and_witness.second); + uint8_t const* pk_buf = nullptr; + acir_proofs::init_proving_key(&constraint_system_buf[0], &pk_buf); + + uint32_t total_circuit_size = acir_proofs::get_total_circuit_size(&constraint_system_buf[0]); + uint32_t pow2_size = 1 << (numeric::get_msb(total_circuit_size) + 1); + auto env_crs = std::make_unique(); + auto verifier_crs = env_crs->get_verifier_crs(); + barretenberg::g2::affine_element g2x = verifier_crs->get_g2x(); + + auto* pippenger_ptr_base = new scalar_multiplication::Pippenger(env_load_prover_crs(pow2_size + 1), pow2_size + 1); + void* pippenger_ptr = reinterpret_cast(pippenger_ptr_base); + + std::vector g2x_buffer(128); + io::write_g2_elements_to_buffer(&g2x, (char*)(&g2x_buffer[0]), 1); + + uint8_t const* vk_buf = nullptr; + acir_proofs::init_verification_key(pippenger_ptr, &g2x_buffer[0], pk_buf, &vk_buf); + + uint8_t* proof_data_buf = nullptr; + size_t proof_length = acir_proofs::new_proof( + pippenger_ptr, &g2x_buffer[0], pk_buf, &constraint_system_buf[0], &witness_buf[0], &proof_data_buf, false); + + bool verified = acir_proofs::verify_proof( + &g2x_buffer[0], vk_buf, &constraint_system_buf[0], proof_data_buf, static_cast(proof_length), false); + EXPECT_EQ(verified, true); + + delete pippenger_ptr_base; + free((void*)vk_buf); + free((void*)pk_buf); + free((void*)proof_data_buf); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp index 5ff7b29d96..62621d00be 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.cpp @@ -45,10 +45,11 @@ WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t c } WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length) + size_t proof_data_length, + size_t num_inner_public_inputs) { return acir_proofs::serialize_proof_into_field_elements( - proof_data_buf, serialized_proof_data_buf, proof_data_length); + proof_data_buf, serialized_proof_data_buf, proof_data_length, num_inner_public_inputs); } WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, @@ -76,7 +77,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf) { @@ -84,7 +85,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, proof_length, vk_buf, vk_length, - public_inputs_buf, + num_public_inputs, input_aggregation_obj_buf, output_aggregation_obj_buf); } diff --git a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp index 0db08d5573..452f41d1ca 100644 --- a/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp +++ b/cpp/src/barretenberg/dsl/acir_proofs/c_bind.hpp @@ -20,7 +20,8 @@ WASM_EXPORT size_t acir_serialize_verification_key_into_field_elements(uint8_t c uint8_t** serialized_vk_hash_buf); WASM_EXPORT size_t acir_serialize_proof_into_field_elements(uint8_t const* proof_data_buf, uint8_t** serialized_proof_data_buf, - size_t proof_data_length); + size_t proof_data_length, + size_t num_inner_public_inputs); WASM_EXPORT size_t acir_proofs_new_proof(void* pippenger, uint8_t const* g2x, uint8_t const* pk_buf, @@ -38,7 +39,7 @@ WASM_EXPORT size_t acir_proofs_verify_recursive_proof(uint8_t const* proof_buf, uint32_t proof_length, uint8_t const* vk_buf, uint32_t vk_length, - uint8_t const* public_inputs_buf, + uint32_t num_public_inputs, uint8_t const* input_aggregation_obj_buf, uint8_t** output_aggregation_obj_buf); } diff --git a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp index 9bbe7314f2..bcf665711c 100644 --- a/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp +++ b/cpp/src/barretenberg/honk/proof_system/ultra_prover.hpp @@ -13,8 +13,7 @@ namespace proof_system::honk { // We won't compile this class with honk::flavor::Standard, but we will like want to compile it (at least for testing) // with a flavor that uses the curve Grumpkin, or a flavor that does/does not have zk, etc. -template -concept UltraFlavor = IsAnyOf; +template concept UltraFlavor = IsAnyOf; template class UltraProver_ { using FF = typename Flavor::FF; diff --git a/cpp/src/barretenberg/plonk/composer/composer_base.hpp b/cpp/src/barretenberg/plonk/composer/composer_base.hpp index a3e57bc383..b1109d2d4d 100644 --- a/cpp/src/barretenberg/plonk/composer/composer_base.hpp +++ b/cpp/src/barretenberg/plonk/composer/composer_base.hpp @@ -186,6 +186,21 @@ class ComposerBase { barretenberg::fr get_public_input(const uint32_t index) const { return get_variable(public_inputs[index]); } + uint32_t get_public_input_index(const uint32_t witness_index) const + { + bool found = false; + uint32_t result = static_cast(-1); + for (size_t i = 0; i < public_inputs.size(); ++i) { + if (real_variable_index[public_inputs[i]] == real_variable_index[witness_index]) { + found = true; + result = static_cast(i); + break; + } + } + ASSERT(found == true); + return result; + } + std::vector get_public_inputs() const { std::vector result; diff --git a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp index 6f22b40bb0..6a73f5f2bc 100644 --- a/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp +++ b/cpp/src/barretenberg/plonk/composer/ultra_composer.hpp @@ -261,6 +261,23 @@ class UltraComposer : public ComposerBase { } } + /** + * @brief Update recursive_proof_public_input_indices with existing public inputs that represent a recursive proof + * + * @param proof_output_witness_indices + */ + void set_recursive_proof(const std::vector& proof_output_witness_indices) + { + if (contains_recursive_proof) { + failure("added recursive proof when one already exists"); + } + contains_recursive_proof = true; + for (size_t i = 0; i < proof_output_witness_indices.size(); ++i) { + recursive_proof_public_input_indices.push_back( + get_public_input_index(real_variable_index[proof_output_witness_indices[i]])); + } + } + void create_new_range_constraint(const uint32_t variable_index, const uint64_t target_range, std::string const msg = "create_new_range_constraint"); diff --git a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp index 743f563770..f99fb5aed7 100644 --- a/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp +++ b/cpp/src/barretenberg/plonk/proof_system/verification_key/verification_key.cpp @@ -258,7 +258,14 @@ std::vector verification_key::export_dummy_key_in_recursion_fo for (const auto& descriptor : polynomial_manifest.get()) { if (descriptor.source == PolynomialSource::SELECTOR || descriptor.source == PolynomialSource::PERMUTATION) { - const auto element = barretenberg::g1::affine_one; + // the std::biggroup class creates unsatisfiable constraints when identical points are added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not identical. + // And prover points should contain randomness for an honest Prover). + // This check can also trigger a runtime error due to causing 0 to be inverted. + // When creating dummy verification key points we must be mindful of the above and make sure that each + // transcript point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); const uint256_t x = element.x; const uint256_t y = element.y; const barretenberg::fr x_lo = x.slice(0, 136); @@ -271,6 +278,7 @@ std::vector verification_key::export_dummy_key_in_recursion_fo output.emplace_back(y_hi); } } + output.emplace_back(0); // key_hash return output; diff --git a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp index 2cd486bbe1..818f1f9d9a 100644 --- a/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp +++ b/cpp/src/barretenberg/stdlib/recursion/verification_key/verification_key.hpp @@ -120,10 +120,10 @@ template struct evaluation_domain { template struct verification_key { using Composer = typename Curve::Composer; - template static std::shared_ptr from_field_pt_vector( Composer* ctx, const std::vector>& fields, + bool inner_proof_contains_recursive_proof = false, std::array recursive_proof_public_input_indices = {}) { std::vector fields_raw; @@ -138,15 +138,10 @@ template struct verification_key { // NOTE: For now `contains_recursive_proof` and `recursive_proof_public_input_indices` need to be circuit // constants! - key->contains_recursive_proof = static_cast(uint256_t(fields[5].get_value())); + key->contains_recursive_proof = inner_proof_contains_recursive_proof; for (size_t i = 0; i < 16; ++i) { - const uint32_t idx = static_cast(uint256_t(fields[6 + i].get_value())); - key->recursive_proof_public_input_indices.emplace_back(idx); - } - // Apply constraints to force the recursive proof information to be circuit constants - fields[5].assert_equal(inner_proof_contains_recursive_proof); - for (size_t i = 0; i < 16; ++i) { - fields[6 + i].assert_equal(recursive_proof_public_input_indices[i]); + auto x = recursive_proof_public_input_indices[i]; + key->recursive_proof_public_input_indices.emplace_back(x); } size_t count = 22; diff --git a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp index ad849889cc..2fe0830c37 100644 --- a/cpp/src/barretenberg/transcript/transcript_wrappers.hpp +++ b/cpp/src/barretenberg/transcript/transcript_wrappers.hpp @@ -104,7 +104,8 @@ class StandardTranscript : public Transcript { * @param manifest * @return std::vector */ - static std::vector export_dummy_transcript_in_recursion_format(const Manifest& manifest) + static std::vector export_dummy_transcript_in_recursion_format( + const Manifest& manifest, const bool contains_recursive_proof) { std::vector fields; const auto num_rounds = manifest.get_num_rounds(); @@ -114,7 +115,14 @@ class StandardTranscript : public Transcript { if (manifest_element.num_bytes == 32 && manifest_element.name != "public_inputs") { fields.emplace_back(0); } else if (manifest_element.num_bytes == 64 && manifest_element.name != "public_inputs") { - const auto group_element = barretenberg::g1::affine_one; + // the std::biggroup class creates unsatisfiable constraints when identical points are + // added/subtracted. + // (when verifying zk proofs this is acceptable as we make sure verification key points are not + // identical. And prover points should contain randomness for an honest Prover). This check can + // also trigger a runtime error due to causing 0 to be inverted. When creating dummy proof + // points we must be mindful of the above and make sure that each point is unique. + auto scalar = barretenberg::fr::random_element(); + const auto group_element = barretenberg::g1::affine_element(barretenberg::g1::one * scalar); const uint256_t x = group_element.x; const uint256_t y = group_element.y; const barretenberg::fr x_lo = x.slice(0, 136); @@ -128,8 +136,30 @@ class StandardTranscript : public Transcript { } else { ASSERT(manifest_element.name == "public_inputs"); const size_t num_public_inputs = manifest_element.num_bytes / 32; - for (size_t j = 0; j < num_public_inputs; ++j) { - fields.emplace_back(0); + // If we have a recursive proofs the public inputs must describe an aggregation object that + // is composed of two valid G1 points on the curve. Without this conditional we will get a + // runtime error that we are attempting to invert 0. + if (contains_recursive_proof) { + ASSERT(num_public_inputs == 16); + for (size_t k = 0; k < num_public_inputs / 4; ++k) { + auto scalar = barretenberg::fr::random_element(); + const auto group_element = + barretenberg::g1::affine_element(barretenberg::g1::one * scalar); + const uint256_t x = group_element.x; + const uint256_t y = group_element.y; + const barretenberg::fr x_lo = x.slice(0, 136); + const barretenberg::fr x_hi = x.slice(136, 272); + const barretenberg::fr y_lo = y.slice(0, 136); + const barretenberg::fr y_hi = y.slice(136, 272); + fields.emplace_back(x_lo); + fields.emplace_back(x_hi); + fields.emplace_back(y_lo); + fields.emplace_back(y_hi); + } + } else { + for (size_t j = 0; j < num_public_inputs; ++j) { + fields.emplace_back(0); + } } } }